mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-08 14:33:22 +00:00
Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cae7edbe01 | ||
|
|
2fa96fc1a5 | ||
|
|
6b8a4d1f85 | ||
|
|
07df44a907 | ||
|
|
be45637435 | ||
|
|
87ab684ba3 | ||
|
|
916a6d5e48 | ||
|
|
b99fbf07f5 | ||
|
|
6d6ef2a93e | ||
|
|
d08e5e2858 | ||
|
|
c1f4a8d416 | ||
|
|
c17c7125e3 | ||
|
|
97fc4ce45d | ||
|
|
abfc338240 | ||
|
|
3d129946ce | ||
|
|
2aab31d317 | ||
|
|
f8ff9a6a87 | ||
|
|
6727c2fc13 | ||
|
|
ba1f11a88b | ||
|
|
c88e4369c8 | ||
|
|
c490f97c22 | ||
|
|
7c71555384 | ||
|
|
35538f971a | ||
|
|
b3c82ac2de | ||
|
|
ff0d8dccea | ||
|
|
06841b1485 | ||
|
|
f55448c0a8 | ||
|
|
258701c377 | ||
|
|
1a0f05511b | ||
|
|
3ec9679893 | ||
|
|
e5cc3e269e | ||
|
|
4b14f617ec | ||
|
|
0df051e40f | ||
|
|
2b464f34e5 | ||
|
|
7a33bd64f8 | ||
|
|
3656c7a18f | ||
|
|
3114852c63 | ||
|
|
45337913ba | ||
|
|
adc5d1c039 | ||
|
|
183f22c223 | ||
|
|
43eb4935e6 | ||
|
|
2c45011664 | ||
|
|
f29f3f4f8f | ||
|
|
c347336055 | ||
|
|
cd87184169 | ||
|
|
09895852c3 | ||
|
|
32759d3e0e | ||
|
|
965134fc02 | ||
|
|
7da5360349 | ||
|
|
bf0701340c | ||
|
|
a3a490f442 | ||
|
|
33b22b8280 | ||
|
|
e6278858b9 | ||
|
|
ed7a9ef037 | ||
|
|
9c42179617 |
@@ -108,7 +108,8 @@ describe('Import Modal', function() {
|
||||
it('catches an invalid backup', function() {
|
||||
const importData = require('./fixtures/valid-backup');
|
||||
let invalidImportData = Object.assign({}, importData);
|
||||
invalidImportData.builds.asp = null; // Remove Asp Miner build used in comparison
|
||||
//invalidImportData.builds.asp = null; // Remove Asp Miner build used in comparison
|
||||
delete(invalidImportData.builds.asp);
|
||||
|
||||
pasteText('"this is not valid"');
|
||||
expect(modal.state.importValid).toBeFalsy();
|
||||
|
||||
@@ -16,7 +16,7 @@ describe("Ship", function() {
|
||||
ship.buildWith(shipData.defaults);
|
||||
|
||||
expect(ship.totalCost).toEqual(shipData.retailCost, s + ' retail cost does not match default build cost');
|
||||
expect(ship.cargoCapacity).toBeDefined(s + ' cargo');
|
||||
expect(ship.cargoCapacity).toBeDefined();
|
||||
expect(ship.priorityBands[0].retracted).toBeGreaterThan(0, s + ' priorityBands');
|
||||
expect(ship.powerAvailable).toBeGreaterThan(0, s + ' powerAvailable');
|
||||
expect(ship.unladenRange).toBeGreaterThan(0, s + ' unladenRange');
|
||||
|
||||
18
package.json
18
package.json
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "coriolis_shipyard",
|
||||
"version": "2.1.2",
|
||||
"version": "2.2.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/cmmcleod/coriolis"
|
||||
"url": "https://github.com/EDCD/coriolis"
|
||||
},
|
||||
"homepage": "https://coriolis.io",
|
||||
"bugs": "https://github.com/cmmcleod/coriolis/issues",
|
||||
"homepage": "https://coriolis.edcd.io",
|
||||
"bugs": "https://github.com/EDCD/coriolis/issues",
|
||||
"private": true,
|
||||
"engine": "node >= 4.0.0",
|
||||
"license": "MIT",
|
||||
@@ -24,15 +24,15 @@
|
||||
},
|
||||
"jest": {
|
||||
"scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
|
||||
"testFileExtensions": [
|
||||
"js"
|
||||
],
|
||||
"testRegex": "(/__tests__/test-.*|\\.(test|spec))\\.js$",
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"jsx"
|
||||
],
|
||||
"automock": true,
|
||||
"unmockedModulePathPatterns": [
|
||||
"<rootDir>/node_modules/lodash",
|
||||
"<rootDir>/node_modules/react",
|
||||
"<rootDir>/node_modules/react-dom",
|
||||
"<rootDir>/node_modules/react-addons-test-utils",
|
||||
@@ -68,7 +68,7 @@
|
||||
"extract-text-webpack-plugin": "^0.9.1",
|
||||
"file-loader": "^0.8.4",
|
||||
"html-webpack-plugin": "^1.7.0",
|
||||
"jest-cli": "^0.9.2",
|
||||
"jest-cli": "^16.0.1",
|
||||
"jsen": "^0.6.0",
|
||||
"json-loader": "^0.5.3",
|
||||
"less": "^2.5.3",
|
||||
@@ -87,7 +87,9 @@
|
||||
"coriolis-data": "EDCD/coriolis-data",
|
||||
"d3": "3.5.16",
|
||||
"fbemitter": "^2.0.0",
|
||||
"lodash": "^4.15.0",
|
||||
"lz-string": "^1.4.4",
|
||||
"react-number-editor": "^4.0.2",
|
||||
"react": "^15.0.1",
|
||||
"react-dom": "^15.0.1",
|
||||
"superagent": "^1.4.0"
|
||||
|
||||
@@ -97,7 +97,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
let m = modules[i];
|
||||
let mount = null;
|
||||
let disabled = m.maxmass && (mass + (m.mass ? m.mass : 0)) > m.maxmass;
|
||||
let active = mountedModule && mountedModule === m;
|
||||
let active = mountedModule && mountedModule.id === m.id;
|
||||
let classes = cn(m.name ? 'lc' : 'c', {
|
||||
warning: !disabled && warningFunc && warningFunc(m),
|
||||
active,
|
||||
|
||||
@@ -606,6 +606,7 @@ export default class CostSection extends TranslatedComponent {
|
||||
}
|
||||
|
||||
if (nextProps.ship != this.props.ship || nextProps.code != this.props.code) {
|
||||
nextProps.ship.applyDiscounts(Persist.getShipDiscount(), Persist.getModuleDiscount());
|
||||
this._updateAmmoCosts(nextProps.ship);
|
||||
this._updateRetrofit(nextProps.ship, retrofitShip);
|
||||
this._sortCost(nextProps.ship);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import React from 'react';
|
||||
import Slot from './Slot';
|
||||
import { DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret, ListModifications } from './SvgIcons';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
|
||||
|
||||
/**
|
||||
* Hardpoint / Utility Slot
|
||||
@@ -33,24 +37,33 @@ export default class HardpointSlot extends Slot {
|
||||
*/
|
||||
_getSlotDetails(m, translate, formats, u) {
|
||||
if (m) {
|
||||
let classRating = `${m.class}${m.rating}${m.mount ? '/' + m.mount : ''}${m.missile ? m.missile : ''}`;
|
||||
let classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
|
||||
let { drag, drop } = this.props;
|
||||
let { termtip, tooltip } = this.context;
|
||||
let validMods = Modifications.validity[m.grp] || [];
|
||||
|
||||
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
|
||||
<div className={'cb'}>
|
||||
<div className={'l'}>{classRating} {translate(m.name || m.grp)}</div>
|
||||
<div className={'r'}>{m.mass}{u.T}</div>
|
||||
<div className={'l'}>
|
||||
{m.mount && m.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed /></span> : ''}
|
||||
{m.mount && m.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : ''}
|
||||
{m.mount && m.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : ''}
|
||||
{m.type && m.type.match('K') ? <span onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /></span> : ''}
|
||||
{m.type && m.type.match('T') ? <span onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /></span> : ''}
|
||||
{m.type && m.type.match('E') ? <span onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /></span> : ''}
|
||||
{classRating} {translate(m.name || m.grp)}</div>
|
||||
<div className={'r'}>{formats.round(m.getMass())}{u.T}</div>
|
||||
</div>
|
||||
<div className={'cb'}>
|
||||
{ m.damage ? <div className={'l'}>{translate('damage')}: {m.damage} { m.ssdam ? <span>({formats.int(m.ssdam)} {u.MJ})</span> : null }</div> : null }
|
||||
{ m.dps ? <div className={'l'}>{translate('DPS')}: {m.dps} { m.mjdps ? <span>({formats.int(m.mjdps)} {u.MJ})</span> : null }</div> : null }
|
||||
{ m.thermload ? <div className={'l'}>{translate('T-Load')}: {m.thermload}</div> : null }
|
||||
{ m.type ? <div className={'l'}>{translate('type')}: {m.type}</div> : null }
|
||||
{ m.rof ? <div className={'l'}>{translate('ROF')}: {m.rof}{u.ps}</div> : null }
|
||||
{ m.armourpen ? <div className={'l'}>{translate('pen')}: {m.armourpen}</div> : null }
|
||||
{ m.shieldmul ? <div className={'l'}>+{formats.rPct(m.shieldmul)}</div> : null }
|
||||
{ m.range ? <div className={'l'}>{m.range} <u>km</u></div> : null }
|
||||
{ m.ammo >= 0 ? <div className={'l'}>{translate('ammo')}: {formats.int(m.clip)}+{formats.int(m.ammo)}</div> : null }
|
||||
{ m.getDps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'dpssdps' : 'dps')} onMouseOut={tooltip.bind(null, null)}>{translate('DPS')}: {formats.round1(m.getDps())} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null }
|
||||
{ m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'epsseps' : 'eps')} onMouseOut={tooltip.bind(null, null)}>{translate('EPS')}: {formats.round1(m.getEps())}{u.MW} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) }{u.MW})</span> : null }</div> : null }
|
||||
{ m.getHps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'hpsshps' : 'hps')} onMouseOut={tooltip.bind(null, null)}>{translate('HPS')}: {formats.round1(m.getHps())} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getHps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null }
|
||||
{ m.getDps() && m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, 'dpe')} onMouseOut={tooltip.bind(null, null)}>{translate('DPE')}: {formats.f1(m.getDps() / m.getEps())}</div> : null }
|
||||
{ m.getRoF() ? <div className={'l'} onMouseOver={termtip.bind(null, 'rof')} onMouseOut={tooltip.bind(null, null)}>{translate('ROF')}: {formats.f1(m.getRoF())}{u.ps}</div> : null }
|
||||
{ m.getRange() && !m.getDps() ? <div className={'l'}>{translate('Range')} : {formats.round(m.getRange() / 1000)}{u.km}</div> : null }
|
||||
{ m.getShieldBoost() ? <div className={'l'}>+{formats.pct1(m.getShieldBoost())}</div> : null }
|
||||
{ m.getAmmo() ? <div className={'l'}>{translate('ammunition')}: {formats.int(m.getClip())}/{formats.int(m.getAmmo())}</div> : null }
|
||||
{ m && validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
|
||||
</div>
|
||||
</div>;
|
||||
} else {
|
||||
|
||||
@@ -69,6 +69,7 @@ export default class HardpointsSlotSection extends SlotSection {
|
||||
availableModules={() => availableModules.getHps(h.maxClass)}
|
||||
onOpen={this._openMenu.bind(this, h)}
|
||||
onSelect={this._selectModule.bind(this, h)}
|
||||
onChange={this.props.onChange}
|
||||
selected={currentMenu == h}
|
||||
drag={this._drag.bind(this, h)}
|
||||
dragOver={this._dragOverSlot.bind(this, h)}
|
||||
@@ -126,6 +127,16 @@ export default class HardpointsSlotSection extends SlotSection {
|
||||
<li className='c' onClick={_fill.bind(this, 'c', 'G')}><MountGimballed className='lg'/></li>
|
||||
<li className='c' onClick={_fill.bind(this, 'c', 'T')}><MountTurret className='lg'/></li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('fc')}</div>
|
||||
<ul>
|
||||
<li className='c' onClick={_fill.bind(this, 'fc', 'F')}><MountFixed className='lg'/></li>
|
||||
<li className='c' onClick={_fill.bind(this, 'fc', 'G')}><MountGimballed className='lg'/></li>
|
||||
<li className='c' onClick={_fill.bind(this, 'fc', 'T')}><MountTurret className='lg'/></li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('nl')}</div>
|
||||
<ul>
|
||||
<li className='lc' onClick={_fill.bind(this, 'nl', 'F')}>{translate('nl')}</li>
|
||||
</ul>
|
||||
</div>;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ function normalizePercent(val) {
|
||||
if (val === '' || isNaN(val)) {
|
||||
return 0;
|
||||
}
|
||||
val = Math.round(val * 100) / 100;
|
||||
val = Math.round(val * 1000) / 1000;
|
||||
return val >= 100 ? 100 : val;
|
||||
}
|
||||
|
||||
@@ -512,4 +512,4 @@ export default class Header extends TranslatedComponent {
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React from 'react';
|
||||
import Slot from './Slot';
|
||||
import { Infinite } from './SvgIcons';
|
||||
import { ListModifications } from './SvgIcons';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
|
||||
/**
|
||||
* Internal Slot
|
||||
@@ -18,30 +20,39 @@ export default class InternalSlot extends Slot {
|
||||
_getSlotDetails(m, translate, formats, u) {
|
||||
if (m) {
|
||||
let classRating = m.class + m.rating;
|
||||
let { drag, drop } = this.props;
|
||||
let { drag, drop, ship } = this.props;
|
||||
let { termtip, tooltip } = this.context;
|
||||
let validMods = Modifications.validity[m.grp] || [];
|
||||
|
||||
let mass = m.getMass() || m.cargo || m.fuel || 0;
|
||||
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
|
||||
<div className={'cb'}>
|
||||
<div className={'l'}>{classRating} {translate(m.name || m.grp)}</div>
|
||||
<div className={'r'}>{m.mass || m.cargo || m.fuel || 0}{u.T}</div>
|
||||
<div className={'r'}>{formats.round(mass)}{u.T}</div>
|
||||
</div>
|
||||
<div className={'cb'}>
|
||||
{ m.optmass ? <div className={'l'}>{translate('optimal mass')}: {m.optmass}{u.T}</div> : null }
|
||||
{ m.maxmass ? <div className={'l'}>{translate('max mass')}: {m.maxmass}{u.T}</div> : null }
|
||||
{ m.getOptMass() ? <div className={'l'}>{translate('optimal mass')}: {formats.int(m.getOptMass())}{u.T}</div> : null }
|
||||
{ m.getMaxMass() ? <div className={'l'}>{translate('max mass')}: {formats.int(m.getMaxMass())}{u.T}</div> : null }
|
||||
{ m.bins ? <div className={'l'}>{m.bins} <u>{translate('bins')}</u></div> : null }
|
||||
{ m.bays ? <div className={'l'}>{translate('bays')}: {m.bays}</div> : null }
|
||||
{ m.rate ? <div className={'l'}>{translate('rate')}: {m.rate}{u.kgs} {translate('refuel time')}: {formats.time(this.props.fuel * 1000 / m.rate)}</div> : null }
|
||||
{ m.ammo ? <div className={'l'}>{translate('ammo')}: {formats.gen(m.ammo)}</div> : null }
|
||||
{ m.getAmmo() ? <div className={'l'}>{translate('ammunition')}: {formats.gen(m.getAmmo())}</div> : null }
|
||||
{ m.cells ? <div className={'l'}>{translate('cells')}: {m.cells}</div> : null }
|
||||
{ m.recharge ? <div className={'l'}>{translate('recharge')}: {m.recharge} <u>MJ</u> {translate('total')}: {m.cells * m.recharge}{u.MJ}</div> : null }
|
||||
{ m.repair ? <div className={'l'}>{translate('repair')}: {m.repair}</div> : null }
|
||||
{ m.range ? <div className={'l'}>{translate('range')} {m.range}{u.km}</div> : null }
|
||||
{ m.getFacingLimit() ? <div className={'l'}>{translate('facinglimit')} {formats.f1(m.getFacingLimit())}°</div> : null }
|
||||
{ m.getRange() ? <div className={'l'}>{translate('range')} {formats.f2(m.getRange())}{u.km}</div> : null }
|
||||
{ m.getRangeT() ? <div className={'l'}>{translate('ranget')} {formats.f1(m.getRangeT())}{u.s}</div> : null }
|
||||
{ m.spinup ? <div className={'l'}>{translate('spinup')}: {formats.f1(m.spinup)}{u.s}</div> : null }
|
||||
{ m.time ? <div className={'l'}>{translate('time')}: {formats.time(m.time)}</div> : null }
|
||||
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
|
||||
{ m.rangeLS ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null }
|
||||
{ m.rangeLS === null ? <div className={'l'}><Infinite/>{u.Ls}</div> : null }
|
||||
{ m.rangeLS === null ? <div className={'l'}>∞{u.Ls}</div> : null }
|
||||
{ m.rangeRating ? <div className={'l'}>{translate('range')}: {m.rangeRating}</div> : null }
|
||||
{ m.armouradd ? <div className={'l'}>+{m.armouradd} <u className='cap'>{translate('armour')}</u></div> : null }
|
||||
{ m.getHullReinforcement() ? <div className={'l'}>+{formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost'))} <u className='cap'>{translate('armour')}</u></div> : null }
|
||||
{ m.passengers ? <div className={'l'}>{translate('passengers')}: {m.passengers}</div> : null }
|
||||
{ m && validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
|
||||
|
||||
</div>
|
||||
</div>;
|
||||
} else {
|
||||
|
||||
@@ -16,12 +16,17 @@ export default class InternalSlotSection extends SlotSection {
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props, context, 'internal', 'internal compartments');
|
||||
super(props, context, 'internal', 'optional internal');
|
||||
|
||||
this._empty = this._empty.bind(this);
|
||||
this._fillWithCargo = this._fillWithCargo.bind(this);
|
||||
this._fillWithCells = this._fillWithCells.bind(this);
|
||||
this._fillWithArmor = this._fillWithArmor.bind(this);
|
||||
this._fillWithFuelTanks = this._fillWithFuelTanks.bind(this);
|
||||
this._fillWithLuxuryCabins = this._fillWithLuxuryCabins.bind(this);
|
||||
this._fillWithFirstClassCabins = this._fillWithFirstClassCabins.bind(this);
|
||||
this._fillWithBusinessClassCabins = this._fillWithBusinessClassCabins.bind(this);
|
||||
this._fillWithEconomyClassCabins = this._fillWithEconomyClassCabins.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,6 +54,86 @@ export default class InternalSlotSection extends SlotSection {
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill all slots with fuel tanks
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithFuelTanks(event) {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if (clobber || !slot.m) {
|
||||
ship.use(slot, ModuleUtils.findInternal('ft', slot.maxClass, 'C'));
|
||||
}
|
||||
});
|
||||
this.props.onChange();
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill all slots with luxury passenger cabins
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithLuxuryCabins(event) {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if (clobber || !slot.m) {
|
||||
ship.use(slot, ModuleUtils.findInternal('pcq', Math.min(slot.maxClass, 6), 'B')); // Passenger cabins top out at 6
|
||||
}
|
||||
});
|
||||
this.props.onChange();
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill all slots with first class passenger cabins
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithFirstClassCabins(event) {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if (clobber || !slot.m) {
|
||||
ship.use(slot, ModuleUtils.findInternal('pcm', Math.min(slot.maxClass, 6), 'C')); // Passenger cabins top out at 6
|
||||
}
|
||||
});
|
||||
this.props.onChange();
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill all slots with business class passenger cabins
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithBusinessClassCabins(event) {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if (clobber || !slot.m) {
|
||||
ship.use(slot, ModuleUtils.findInternal('pci', Math.min(slot.maxClass, 6), 'D')); // Passenger cabins top out at 6
|
||||
}
|
||||
});
|
||||
this.props.onChange();
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill all slots with economy class passenger cabins
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithEconomyClassCabins(event) {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if (clobber || !slot.m) {
|
||||
ship.use(slot, ModuleUtils.findInternal('pce', Math.min(slot.maxClass, 6), 'E')); // Passenger cabins top out at 6
|
||||
}
|
||||
});
|
||||
this.props.onChange();
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill all slots with Shield Cell Banks
|
||||
* @param {SyntheticEvent} event Event
|
||||
@@ -58,7 +143,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
let ship = this.props.ship;
|
||||
let chargeCap = 0; // Capacity of single activation
|
||||
ship.internal.forEach(function(slot) {
|
||||
if ((!slot.m || (clobber && !ModuleUtils.isShieldGenerator(slot.m.grp))) && (!slot.eligible || slot.eligible.scb)) { // Check eligibility due to Orca special case
|
||||
if ((!slot.m || (clobber && !ModuleUtils.isShieldGenerator(slot.m.grp))) && (!slot.eligible || slot.eligible.scb)) { // Check eligibility due to passenger ships special case
|
||||
ship.use(slot, ModuleUtils.findInternal('scb', slot.maxClass, 'A'));
|
||||
ship.setSlotEnabled(slot, chargeCap <= ship.shieldStrength); // Don't waste cell capacity on overcharge
|
||||
chargeCap += slot.m.recharge;
|
||||
@@ -99,7 +184,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
let slots = [];
|
||||
let { currentMenu, ship } = this.props;
|
||||
let { originSlot, targetSlot } = this.state;
|
||||
let { internal, fuelCapacity, ladenMass } = ship;
|
||||
let { internal, fuelCapacity } = ship;
|
||||
let availableModules = ship.getAvailableModules();
|
||||
|
||||
for (let i = 0, l = internal.length; i < l; i++) {
|
||||
@@ -108,11 +193,13 @@ export default class InternalSlotSection extends SlotSection {
|
||||
slots.push(<InternalSlot
|
||||
key={i}
|
||||
maxClass={s.maxClass}
|
||||
availableModules={() => availableModules.getInts(s.maxClass, s.eligible)}
|
||||
availableModules={() => availableModules.getInts(ship, s.maxClass, s.eligible)}
|
||||
onOpen={this._openMenu.bind(this,s)}
|
||||
onChange={this.props.onChange}
|
||||
onSelect={this._selectModule.bind(this, s)}
|
||||
selected={currentMenu == s}
|
||||
enabled={s.enabled}
|
||||
eligible={s.eligible}
|
||||
m={s.m}
|
||||
drag={this._drag.bind(this, s)}
|
||||
dragOver={this._dragOverSlot.bind(this, s)}
|
||||
@@ -129,15 +216,21 @@ export default class InternalSlotSection extends SlotSection {
|
||||
/**
|
||||
* Generate the section drop-down menu
|
||||
* @param {Function} translate Translate function
|
||||
* @param {Function} ship The ship
|
||||
* @return {React.Component} Section menu
|
||||
*/
|
||||
_getSectionMenu(translate) {
|
||||
_getSectionMenu(translate, ship) {
|
||||
return <div className='select' onClick={e => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
|
||||
<ul>
|
||||
<li className='lc' onClick={this._empty}>{translate('empty all')}</li>
|
||||
<li className='lc' onClick={this._fillWithCargo}>{translate('cargo')}</li>
|
||||
<li className='lc' onClick={this._fillWithCells}>{translate('scb')}</li>
|
||||
<li className='lc' onClick={this._fillWithArmor}>{translate('hr')}</li>
|
||||
<li className='lc' onClick={this._fillWithFuelTanks}>{translate('ft')}</li>
|
||||
<li className='lc' onClick={this._fillWithEconomyClassCabins}>{translate('pce')}</li>
|
||||
<li className='lc' onClick={this._fillWithBusinessClassCabins}>{translate('pci')}</li>
|
||||
<li className='lc' onClick={this._fillWithFirstClassCabins}>{translate('pcm')}</li>
|
||||
{ ship.luxuryCabins ? <li className='lc' onClick={this._fillWithLuxuryCabins}>{translate('pcq')}</li> : ''}
|
||||
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
|
||||
</ul>
|
||||
</div>;
|
||||
|
||||
69
src/app/components/Modification.jsx
Normal file
69
src/app/components/Modification.jsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
import { findDOMNode } from 'react-dom';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import cn from 'classnames';
|
||||
import NumberEditor from 'react-number-editor';
|
||||
|
||||
/**
|
||||
* Modification
|
||||
*/
|
||||
export default class ModificationsMenu extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
ship: React.PropTypes.object.isRequired,
|
||||
m: React.PropTypes.object.isRequired,
|
||||
name: React.PropTypes.string.isRequired,
|
||||
onChange: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
this.state.value = this.props.m.getModValue(this.props.name) * 100 || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update modification given a value.
|
||||
* @param {Number} value The value to set
|
||||
*/
|
||||
_updateValue(value) {
|
||||
let scaledValue = Math.floor(Number(value) * 100) / 10000;
|
||||
// Limit to +1000% / -100%
|
||||
if (scaledValue > 10) {
|
||||
scaledValue = 10;
|
||||
value = 1000;
|
||||
}
|
||||
if (scaledValue < -1) {
|
||||
scaledValue = -1;
|
||||
value = -100;
|
||||
}
|
||||
let m = this.props.m;
|
||||
let name = this.props.name;
|
||||
let ship = this.props.ship;
|
||||
ship.setModification(m, name, scaledValue);
|
||||
|
||||
this.setState({ value });
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the modification
|
||||
* @return {React.Component} modification
|
||||
*/
|
||||
render() {
|
||||
let translate = this.context.language.translate;
|
||||
let name = this.props.name;
|
||||
|
||||
return (
|
||||
<div className={'cb'} key={name}>
|
||||
<div className={'cb'}>{translate(name)}{name === 'jitter' ? ' (°)' : ' (%)'}</div>
|
||||
<NumberEditor className={'cb'} style={{ width: '100%', textAlign: 'center' }} step={0.01} stepModifier={1} decimals={2} value={this.state.value} onValueChange={this._updateValue.bind(this)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
63
src/app/components/ModificationsMenu.jsx
Normal file
63
src/app/components/ModificationsMenu.jsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import { findDOMNode } from 'react-dom';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
import cn from 'classnames';
|
||||
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
import Modification from './Modification';
|
||||
|
||||
/**
|
||||
* Modifications menu
|
||||
*/
|
||||
export default class ModificationsMenu extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
ship: React.PropTypes.object.isRequired,
|
||||
m: React.PropTypes.object.isRequired,
|
||||
onChange: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
this.state = this._initState(props, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate the list of modifications
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
* @return {Object} list: Array of React Components
|
||||
*/
|
||||
_initState(props, context) {
|
||||
let { m, onChange, ship } = props;
|
||||
let list = [];
|
||||
|
||||
for (let modName of Modifications.validity[m.grp]) {
|
||||
list.push(<Modification key={ modName } ship={ ship } m={ m } name={ modName } onChange={ onChange }/>);
|
||||
}
|
||||
|
||||
return { list };
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the list
|
||||
* @return {React.Component} List
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className={cn('select', this.props.className)}
|
||||
onClick={(e) => e.stopPropagation() }
|
||||
onContextMenu={stopCtxPropagation}
|
||||
>
|
||||
{this.state.list}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ export default class PowerManagement extends TranslatedComponent {
|
||||
case 'n': comp = comp(null, desc); break;
|
||||
case 't': comp = comp((a, b) => a.type.localeCompare(b.type), desc); break;
|
||||
case 'pri': comp = comp((a, b) => a.priority - b.priority, desc); break;
|
||||
case 'pwr': comp = comp((a, b) => a.m.power - b.m.power, desc); break;
|
||||
case 'pwr': comp = comp((a, b) => a.m.getPowerUsage() - b.m.getPowerUsage(), desc); break;
|
||||
case 'r': comp = comp((a, b) => ship.getSlotStatus(a) - ship.getSlotStatus(b), desc); break;
|
||||
case 'd': comp = comp((a, b) => ship.getSlotStatus(a, true) - ship.getSlotStatus(b, true), desc); break;
|
||||
}
|
||||
@@ -113,7 +113,7 @@ export default class PowerManagement extends TranslatedComponent {
|
||||
for (let i = 0, l = ship.powerList.length; i < l; i++) {
|
||||
let slot = ship.powerList[i];
|
||||
|
||||
if (slot.m && slot.m.power) {
|
||||
if (slot.m && slot.m.getPowerUsage() > 0) {
|
||||
let m = slot.m;
|
||||
let toggleEnabled = this._toggleEnabled.bind(this, slot);
|
||||
let retractedElem = null, deployedElem = null;
|
||||
@@ -134,8 +134,8 @@ export default class PowerManagement extends TranslatedComponent {
|
||||
{' ' + (slot.priority + 1) + ' '}
|
||||
<span className='ptr btn' onClick={this._priority.bind(this, slot, 1)}>►</span>
|
||||
</td>
|
||||
<td className='ri ptr' style={{ width: '3.25em' }} onClick={toggleEnabled}>{pwr(m.power)}</td>
|
||||
<td className='ri ptr' style={{ width: '3em' }} onClick={toggleEnabled}><u>{pct(m.power / ship.powerAvailable)}</u></td>
|
||||
<td className='ri ptr' style={{ width: '3.25em' }} onClick={toggleEnabled}>{pwr(m.getPowerUsage())}</td>
|
||||
<td className='ri ptr' style={{ width: '3em' }} onClick={toggleEnabled}><u>{pct(m.getPowerUsage() / ship.powerAvailable)}</u></td>
|
||||
{retractedElem}
|
||||
{deployedElem}
|
||||
</tr>);
|
||||
@@ -214,7 +214,7 @@ export default class PowerManagement extends TranslatedComponent {
|
||||
<td className='le shorten cap' >{translate('pp')}</td>
|
||||
<td><u >{translate('SYS')}</u></td>
|
||||
<td>1</td>
|
||||
<td className='ri'>{pwr(pp.pGen)}</td>
|
||||
<td className='ri'>{pwr(pp.getPowerGeneration())}</td>
|
||||
<td className='ri'><u>100%</u></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
@@ -223,7 +223,7 @@ export default class PowerManagement extends TranslatedComponent {
|
||||
{this._renderPowerRows(ship, translate, pwr, formats.pct1)}
|
||||
</tbody>
|
||||
</table>
|
||||
<PowerBands width={this.state.width} code={code} available={ship.standard[0].m.pGen} bands={ship.priorityBands} />
|
||||
<PowerBands width={this.state.width} code={code} available={pp.getPowerGeneration()} bands={ship.priorityBands} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,35 +23,40 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
||||
let translate = language.translate;
|
||||
let u = language.units;
|
||||
let formats = language.formats;
|
||||
let round = formats.round;
|
||||
let { time, int } = formats;
|
||||
let armourDetails = null;
|
||||
let sgClassNames = cn({ warning: ship.sgSlot && !ship.shieldStrength, muted: !ship.sgSlot });
|
||||
let { time, int, round, f1, f2, pct } = formats;
|
||||
let sgClassNames = cn({ warning: ship.findInternalByGroup('sg') && !ship.shield, muted: !ship.findInternalByGroup('sg') });
|
||||
let sgRecover = '-';
|
||||
let sgRecharge = '-';
|
||||
let hide = tooltip.bind(null, null);
|
||||
|
||||
if (ship.armourMultiplier > 1 || ship.armourAdded) {
|
||||
armourDetails = <u>({
|
||||
(ship.armourMultiplier > 1 ? formats.rPct(ship.armourMultiplier) : '') +
|
||||
(ship.armourAdded ? ' + ' + ship.armourAdded : '')
|
||||
})</u>;
|
||||
}
|
||||
|
||||
if (ship.shieldStrength) {
|
||||
if (ship.shield) {
|
||||
sgRecover = time(ship.calcShieldRecovery());
|
||||
sgRecharge = time(ship.calcShieldRecharge());
|
||||
}
|
||||
|
||||
// <th colSpan={3}>{translate('shield resistance')}</th>
|
||||
// <th colSpan={3}>{translate('hull resistance')}</th>
|
||||
// <th className='lft'>{translate('explosive')}</th>
|
||||
// <th className='lft'>{translate('kinetic')}</th>
|
||||
// <th className='lft'>{translate('thermal')}</th>
|
||||
// <th className='lft'>{translate('explosive')}</th>
|
||||
// <th className='lft'>{translate('kinetic')}</th>
|
||||
// <th className='lft'>{translate('thermal')}</th>
|
||||
// <td>{pct(ship.shieldExplRes)}</td>
|
||||
// <td>{pct(ship.shieldKinRes)}</td>
|
||||
// <td>{pct(ship.shieldThermRes)}</td>
|
||||
// <td>{pct(ship.hullExplRes)}</td>
|
||||
// <td>{pct(ship.hullKinRes)}</td>
|
||||
// <td>{pct(ship.hullThermRes)}</td>
|
||||
return <div id='summary'>
|
||||
<table id='summaryTable'>
|
||||
<thead>
|
||||
<tr className='main'>
|
||||
<th rowSpan={2}>{translate('size')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'maneuverability')} onMouseLeave={hide} rowSpan={2}>{translate('MNV')}</th>
|
||||
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !ship.canThrust() }) }>{translate('speed')}</th>
|
||||
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !ship.canBoost() }) }>{translate('boost')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'damage per second')} onMouseLeave={hide} rowSpan={2}>{translate('DPS')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'energy per second')} onMouseLeave={hide} rowSpan={2}>{translate('EPS')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th>
|
||||
<th rowSpan={2}>{translate('armour')}</th>
|
||||
<th colSpan={3}>{translate('shields')}</th>
|
||||
<th colSpan={3}>{translate('mass')}</th>
|
||||
@@ -78,26 +83,26 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className='cap'>{translate(SizeMap[ship.class])}</td>
|
||||
<td>{ship.agility}/10</td>
|
||||
<td>{ ship.canThrust() ? <span>{int(ship.topSpeed)} {u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
||||
<td>{ ship.canBoost() ? <span>{int(ship.topBoost)} {u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
||||
<td>{round(ship.totalDps)}</td>
|
||||
<td>{int(ship.armour)} {armourDetails}</td>
|
||||
<td className={sgClassNames}>{int(ship.shieldStrength)} {u.MJ} { ship.shieldMultiplier > 1 && ship.shieldStrength > 0 ? <u>({formats.rPct(ship.shieldMultiplier)})</u> : null }</td>
|
||||
<td>{f1(ship.totalDps)}</td>
|
||||
<td>{f1(ship.totalEps)}</td>
|
||||
<td>{f1(ship.totalHps)}</td>
|
||||
<td>{int(ship.armour)}</td>
|
||||
<td className={sgClassNames}>{int(ship.shield)} {u.MJ}</td>
|
||||
<td className={sgClassNames}>{sgRecover}</td>
|
||||
<td className={sgClassNames}>{sgRecharge}</td>
|
||||
<td>{ship.hullMass} {u.T}</td>
|
||||
<td>{round(ship.unladenMass)} {u.T}</td>
|
||||
<td>{round(ship.ladenMass)} {u.T}</td>
|
||||
<td>{int(ship.unladenMass)} {u.T}</td>
|
||||
<td>{int(ship.ladenMass)} {u.T}</td>
|
||||
<td>{round(ship.cargoCapacity)} {u.T}</td>
|
||||
<td>{round(ship.fuelCapacity)} {u.T}</td>
|
||||
<td>{round(ship.unladenRange)} {u.LY}</td>
|
||||
<td>{round(ship.fullTankRange)} {u.LY}</td>
|
||||
<td>{round(ship.ladenRange)} {u.LY}</td>
|
||||
<td>{round(ship.maxJumpCount)}</td>
|
||||
<td>{round(ship.unladenFastestRange)} {u.LY}</td>
|
||||
<td>{round(ship.ladenFastestRange)} {u.LY}</td>
|
||||
<td>{f2(ship.unladenRange)} {u.LY}</td>
|
||||
<td>{f2(ship.fullTankRange)} {u.LY}</td>
|
||||
<td>{f2(ship.ladenRange)} {u.LY}</td>
|
||||
<td>{int(ship.maxJumpCount)}</td>
|
||||
<td>{f2(ship.unladenFastestRange)} {u.LY}</td>
|
||||
<td>{f2(ship.ladenFastestRange)} {u.LY}</td>
|
||||
<td>{ship.masslock}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -2,8 +2,12 @@ import React from 'react';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import cn from 'classnames';
|
||||
import AvailableModulesMenu from './AvailableModulesMenu';
|
||||
import ModificationsMenu from './ModificationsMenu';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
import { ListModifications } from './SvgIcons';
|
||||
import { diffDetails } from '../utils/SlotFunctions';
|
||||
import { wrapCtxMenu } from '../utils/UtilityFunctions';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
|
||||
/**
|
||||
* Abstract Slot
|
||||
@@ -18,6 +22,7 @@ export default class Slot extends TranslatedComponent {
|
||||
selected: React.PropTypes.bool,
|
||||
m: React.PropTypes.object,
|
||||
ship: React.PropTypes.object.isRequired,
|
||||
eligible: React.PropTypes.object,
|
||||
warning: React.PropTypes.func,
|
||||
drag: React.PropTypes.func,
|
||||
drop: React.PropTypes.func,
|
||||
@@ -31,6 +36,8 @@ export default class Slot extends TranslatedComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._modificationsSelected = false;
|
||||
|
||||
this._contextMenu = wrapCtxMenu(this._contextMenu.bind(this));
|
||||
this._getMaxClassLabel = this._getMaxClassLabel.bind(this);
|
||||
}
|
||||
@@ -73,25 +80,39 @@ export default class Slot extends TranslatedComponent {
|
||||
render() {
|
||||
let language = this.context.language;
|
||||
let translate = language.translate;
|
||||
let { ship, m, dropClass, dragOver, onOpen, selected, onSelect, warning, shipMass, availableModules } = this.props;
|
||||
let { ship, m, dropClass, dragOver, onOpen, onChange, selected, eligible, onSelect, warning, availableModules } = this.props;
|
||||
let slotDetails, menu;
|
||||
|
||||
if (!selected) {
|
||||
// If not selected then sure that modifications flag is unset
|
||||
this._modificationsSelected = false;
|
||||
}
|
||||
|
||||
if (m) {
|
||||
slotDetails = this._getSlotDetails(m, translate, language.formats, language.units); // Must be implemented by sub classes
|
||||
} else {
|
||||
slotDetails = <div className={'empty'}>{translate('empty')}</div>;
|
||||
slotDetails = <div className={'empty'}>{translate(eligible ? 'emptyrestricted' : 'empty')}</div>;
|
||||
}
|
||||
|
||||
if (this.props.selected) {
|
||||
menu = <AvailableModulesMenu
|
||||
className={this._getClassNames()}
|
||||
modules={availableModules()}
|
||||
shipMass={ship.hullMass}
|
||||
m={m}
|
||||
onSelect={onSelect}
|
||||
warning={warning}
|
||||
diffDetails={diffDetails.bind(ship, this.context.language)}
|
||||
/>;
|
||||
if (selected) {
|
||||
if (this._modificationsSelected) {
|
||||
menu = <ModificationsMenu
|
||||
className={this._getClassNames()}
|
||||
onChange={onChange}
|
||||
ship={ship}
|
||||
m={m}
|
||||
/>;
|
||||
} else {
|
||||
menu = <AvailableModulesMenu
|
||||
className={this._getClassNames()}
|
||||
modules={availableModules()}
|
||||
shipMass={ship.hullMass}
|
||||
m={m}
|
||||
onSelect={onSelect}
|
||||
warning={warning}
|
||||
diffDetails={diffDetails.bind(ship, this.context.language)}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: implement touch dragging
|
||||
@@ -100,10 +121,17 @@ export default class Slot extends TranslatedComponent {
|
||||
<div className={cn('slot', dropClass, { selected })} onClick={onOpen} onContextMenu={this._contextMenu} onDragOver={dragOver}>
|
||||
<div className='details-container'>
|
||||
<div className='sz'>{this._getMaxClassLabel(translate)}</div>
|
||||
{slotDetails}
|
||||
</div>
|
||||
{slotDetails}
|
||||
</div>
|
||||
{menu}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the modifications flag when selecting the modifications icon
|
||||
*/
|
||||
_toggleModifications() {
|
||||
this._modificationsSelected = !this._modificationsSelected;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { wrapCtxMenu } from '../utils/UtilityFunctions';
|
||||
import { canMount } from '../utils/SlotFunctions';
|
||||
import { Equalizer } from '../components/SvgIcons';
|
||||
import cn from 'classnames';
|
||||
|
||||
@@ -90,7 +91,7 @@ export default class SlotSection extends TranslatedComponent {
|
||||
e.stopPropagation();
|
||||
let os = this.state.originSlot;
|
||||
if (os) {
|
||||
e.dataTransfer.dropEffect = os != targetSlot && targetSlot.maxClass >= os.m.class ? 'copyMove' : 'none';
|
||||
e.dataTransfer.dropEffect = os != targetSlot && canMount(this.props.ship, targetSlot, os.m.grp, os.m.class) ? 'copyMove' : 'none';
|
||||
this.setState({ targetSlot });
|
||||
} else {
|
||||
e.dataTransfer.dropEffect = 'none';
|
||||
@@ -116,9 +117,9 @@ export default class SlotSection extends TranslatedComponent {
|
||||
let { originSlot, targetSlot } = this.state;
|
||||
let m = originSlot.m;
|
||||
|
||||
if (targetSlot && m && targetSlot.maxClass >= m.class) {
|
||||
if (targetSlot && m && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
|
||||
// Swap modules if possible
|
||||
if (targetSlot.m && originSlot.maxClass >= targetSlot.m.class) {
|
||||
if (targetSlot.m && canMount(this.props.ship, originSlot, targetSlot.m.grp, targetSlot.m.class)) {
|
||||
this.props.ship.use(originSlot, targetSlot.m, true);
|
||||
} else { // Otherwise empty the origin slot
|
||||
this.props.ship.use(originSlot, null, true); // Empty but prevent summary update
|
||||
@@ -141,12 +142,12 @@ export default class SlotSection extends TranslatedComponent {
|
||||
return null;
|
||||
}
|
||||
if (slot === originSlot) {
|
||||
if (targetSlot && targetSlot.m && originSlot.maxClass < targetSlot.m.class) {
|
||||
if (targetSlot && targetSlot.m && !canMount(this.props.ship, originSlot, targetSlot.m.grp, targetSlot.m.class)) {
|
||||
return 'dropEmpty'; // Origin slot will be emptied
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (originSlot.m && slot.maxClass >= originSlot.m.class) { // Eligble drop slot
|
||||
if (originSlot.m && canMount(this.props.ship, slot, originSlot.m.grp, originSlot.m.class)) { // Eligble drop slot
|
||||
if (slot === targetSlot) {
|
||||
return 'drop'; // Can drop
|
||||
}
|
||||
@@ -179,7 +180,7 @@ export default class SlotSection extends TranslatedComponent {
|
||||
<div id={this.sectionId} className={'group'} onDragLeave={this._dragOverNone}>
|
||||
<div className={cn('section-menu', { selected: sectionMenuOpened })} onClick={open} onContextMenu={ctx}>
|
||||
<h1>{translate(this.sectionName)} <Equalizer/></h1>
|
||||
{sectionMenuOpened ? this._getSectionMenu(translate) : null }
|
||||
{sectionMenuOpened ? this._getSectionMenu(translate, this.props.ship) : null }
|
||||
</div>
|
||||
{this._getSlots()}
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,10 @@ import TranslatedComponent from './TranslatedComponent';
|
||||
import { jumpRange } from '../shipyard/Calculations';
|
||||
import { diffDetails } from '../utils/SlotFunctions';
|
||||
import AvailableModulesMenu from './AvailableModulesMenu';
|
||||
import ModificationsMenu from './ModificationsMenu';
|
||||
import { ListModifications } from './SvgIcons';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
|
||||
/**
|
||||
* Standard Slot
|
||||
@@ -15,53 +19,83 @@ export default class StandardSlot extends TranslatedComponent {
|
||||
modules: React.PropTypes.array.isRequired,
|
||||
onSelect: React.PropTypes.func.isRequired,
|
||||
onOpen: React.PropTypes.func.isRequired,
|
||||
onChange: React.PropTypes.func.isRequired,
|
||||
ship: React.PropTypes.object.isRequired,
|
||||
selected: React.PropTypes.bool,
|
||||
warning: React.PropTypes.func,
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct the slot
|
||||
* @param {object} props Object properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._modificationsSelected = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the slot
|
||||
* @return {React.Component} Slot component
|
||||
*/
|
||||
render() {
|
||||
let { termtip, tooltip } = this.context;
|
||||
let { translate, formats, units } = this.context.language;
|
||||
let { modules, slot, warning, onSelect, ladenMass, ship } = this.props;
|
||||
let { modules, slot, selected, warning, onSelect, onChange, ship } = this.props;
|
||||
let m = slot.m;
|
||||
let classRating = m.class + m.rating;
|
||||
let menu;
|
||||
let validMods = m == null ? [] : (Modifications.validity[m.grp] || []);
|
||||
let mass = m.getMass() || m.cargo || m.fuel || 0;
|
||||
|
||||
if (this.props.selected) {
|
||||
menu = <AvailableModulesMenu
|
||||
className='standard'
|
||||
modules={modules}
|
||||
shipMass={ship.ladenMass}
|
||||
m={m}
|
||||
onSelect={onSelect}
|
||||
warning={warning}
|
||||
diffDetails={diffDetails.bind(ship, this.context.language)}
|
||||
/>;
|
||||
if (!selected) {
|
||||
// If not selected then sure that modifications flag is unset
|
||||
this._modificationsSelected = false;
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
if (this._modificationsSelected) {
|
||||
menu = <ModificationsMenu
|
||||
className='standard'
|
||||
onChange={onChange}
|
||||
ship={ship}
|
||||
m={m}
|
||||
/>;
|
||||
} else {
|
||||
menu = <AvailableModulesMenu
|
||||
className='standard'
|
||||
modules={modules}
|
||||
shipMass={ship.ladenMass}
|
||||
m={m}
|
||||
onSelect={onSelect}
|
||||
warning={warning}
|
||||
diffDetails={diffDetails.bind(ship, this.context.language)}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen}>
|
||||
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen} onContextMenu={stopCtxPropagation}>
|
||||
<div className={cn('details-container', { warning: warning && warning(slot.m) })}>
|
||||
<div className={'sz'}>{slot.maxClass}</div>
|
||||
<div>
|
||||
<div className='l'>{classRating} {translate(m.grp == 'bh' ? m.grp : m.name || m.grp)}</div>
|
||||
<div className={'r'}>{m.mass || m.fuel || 0}{units.T}</div>
|
||||
<div className={'r'}>{formats.round(mass)}{units.T}</div>
|
||||
<div/>
|
||||
<div className={'cb'}>
|
||||
{ m.grp == 'bh' && m.name ? <div className='l'>{translate(m.name)}</div> : null }
|
||||
{ m.optmass ? <div className='l'>{translate('optimal mass')}: {m.optmass}{units.T}</div> : null }
|
||||
{ m.maxmass ? <div className='l'>{translate('max mass')}: {m.maxmass}{units.T}</div> : null }
|
||||
{ m.range ? <div className='l'>{translate('range')}: {m.range}{units.km}</div> : null }
|
||||
{ m.getOptimalMass() ? <div className='l'>{translate('optimal mass')}: {formats.int(m.getOptimalMass())}{units.T}</div> : null }
|
||||
{ m.getMaxMass() ? <div className='l'>{translate('max mass')}: {formats.int(m.getMaxMass())}{units.T}</div> : null }
|
||||
{ m.getRange() ? <div className='l'>{translate('range')}: {formats.f2(m.getRange())}{units.km}</div> : null }
|
||||
{ m.time ? <div className='l'>{translate('time')}: {formats.time(m.time)}</div> : null }
|
||||
{ m.eff ? <div className='l'>{translate('efficiency')}: {m.eff}</div> : null }
|
||||
{ m.pGen ? <div className='l'>{translate('power')}: {m.pGen}{units.MW}</div> : null }
|
||||
{ m.maxfuel ? <div className='l'>{translate('max')} {translate('fuel')}: {m.maxfuel}{units.T}</div> : null }
|
||||
{ m.weaponcapacity ? <div className='l'>{translate('WEP')}: {m.weaponcapacity}{units.MJ} / {m.weaponrecharge}{units.MW}</div> : null }
|
||||
{ m.systemcapacity ? <div className='l'>{translate('SYS')}: {m.systemcapacity}{units.MJ} / {m.systemrecharge}{units.MW}</div> : null }
|
||||
{ m.enginecapacity ? <div className='l'>{translate('ENG')}: {m.enginecapacity}{units.MJ} / {m.enginerecharge}{units.MW}</div> : null }
|
||||
{ m.getThermalEfficiency() ? <div className='l'>{translate('efficiency')}: {formats.f2(m.getThermalEfficiency())}</div> : null }
|
||||
{ m.getPowerGeneration() > 0 ? <div className='l'>{translate('pgen')}: {formats.f1(m.getPowerGeneration())}{units.MW}</div> : null }
|
||||
{ m.getMaxFuelPerJump() ? <div className='l'>{translate('max')} {translate('fuel')}: {formats.f1(m.getMaxFuelPerJump())}{units.T}</div> : null }
|
||||
{ m.getWeaponsCapacity() ? <div className='l'>{translate('WEP')}: {formats.f1(m.getWeaponsCapacity())}{units.MJ} / {formats.f1(m.getWeaponsRechargeRate())}{units.MW}</div> : null }
|
||||
{ m.getSystemsCapacity() ? <div className='l'>{translate('SYS')}: {formats.f1(m.getSystemsCapacity())}{units.MJ} / {formats.f1(m.getSystemsRechargeRate())}{units.MW}</div> : null }
|
||||
{ m.getEnginesCapacity() ? <div className='l'>{translate('ENG')}: {formats.f1(m.getEnginesCapacity())}{units.MJ} / {formats.f1(m.getEnginesRechargeRate())}{units.MW}</div> : null }
|
||||
|
||||
{ validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -69,4 +103,11 @@ export default class StandardSlot extends TranslatedComponent {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the modifications flag when selecting the modifications icon
|
||||
*/
|
||||
_toggleModifications() {
|
||||
this._modificationsSelected = !this._modificationsSelected;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export default class StandardSlotSection extends SlotSection {
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props, context, 'standard', 'standard');
|
||||
super(props, context, 'standard', 'core internal');
|
||||
this._optimizeStandard = this._optimizeStandard.bind(this);
|
||||
this._selectBulkhead = this._selectBulkhead.bind(this);
|
||||
}
|
||||
@@ -86,12 +86,10 @@ export default class StandardSlotSection extends SlotSection {
|
||||
* @return {Array} Array of Slots
|
||||
*/
|
||||
_getSlots() {
|
||||
let { translate, units, formats } = this.context.language;
|
||||
let { ship, currentMenu } = this.props;
|
||||
let slots = new Array(8);
|
||||
let open = this._openMenu;
|
||||
let select = this._selectModule;
|
||||
let selBulkhead = this._selectBulkhead;
|
||||
let st = ship.standard;
|
||||
let avail = ship.getAvailableModules().standard;
|
||||
let bh = ship.bulkheads;
|
||||
@@ -103,6 +101,7 @@ export default class StandardSlotSection extends SlotSection {
|
||||
onOpen={open.bind(this, bh)}
|
||||
onSelect={this._selectBulkhead}
|
||||
selected={currentMenu == bh}
|
||||
onChange={this.props.onChange}
|
||||
ship={ship}
|
||||
/>;
|
||||
|
||||
@@ -113,8 +112,9 @@ export default class StandardSlotSection extends SlotSection {
|
||||
onOpen={open.bind(this, st[0])}
|
||||
onSelect={select.bind(this, st[0])}
|
||||
selected={currentMenu == st[0]}
|
||||
onChange={this.props.onChange}
|
||||
ship={ship}
|
||||
warning={m => m.pGen < ship.powerRetracted}
|
||||
warning={m => m.pgen < ship.powerRetracted}
|
||||
/>;
|
||||
|
||||
slots[2] = <StandardSlot
|
||||
@@ -124,6 +124,7 @@ export default class StandardSlotSection extends SlotSection {
|
||||
onOpen={open.bind(this, st[1])}
|
||||
onSelect={select.bind(this, st[1])}
|
||||
selected={currentMenu == st[1]}
|
||||
onChange={this.props.onChange}
|
||||
ship={ship}
|
||||
warning={m => m.maxmass < (ship.ladenMass - st[1].mass + m.mass)}
|
||||
/>;
|
||||
@@ -135,6 +136,7 @@ export default class StandardSlotSection extends SlotSection {
|
||||
modules={avail[2]}
|
||||
onOpen={open.bind(this, st[2])}
|
||||
onSelect={select.bind(this, st[2])}
|
||||
onChange={this.props.onChange}
|
||||
ship={ship}
|
||||
selected={currentMenu == st[2]}
|
||||
/>;
|
||||
@@ -145,6 +147,7 @@ export default class StandardSlotSection extends SlotSection {
|
||||
modules={avail[3]}
|
||||
onOpen={open.bind(this, st[3])}
|
||||
onSelect={select.bind(this, st[3])}
|
||||
onChange={this.props.onChange}
|
||||
ship={ship}
|
||||
selected={currentMenu == st[3]}
|
||||
/>;
|
||||
@@ -156,8 +159,9 @@ export default class StandardSlotSection extends SlotSection {
|
||||
onOpen={open.bind(this, st[4])}
|
||||
onSelect={select.bind(this, st[4])}
|
||||
selected={currentMenu == st[4]}
|
||||
onChange={this.props.onChange}
|
||||
ship={ship}
|
||||
warning= {m => m.enginecapacity < ship.boostEnergy}
|
||||
warning= {m => m.engcap < ship.boostEnergy}
|
||||
/>;
|
||||
|
||||
slots[6] = <StandardSlot
|
||||
@@ -167,8 +171,8 @@ export default class StandardSlotSection extends SlotSection {
|
||||
onOpen={open.bind(this, st[5])}
|
||||
onSelect={select.bind(this, st[5])}
|
||||
selected={currentMenu == st[5]}
|
||||
onChange={this.props.onChange}
|
||||
ship={ship}
|
||||
warning= {m => m.enginecapacity < ship.boostEnergy}
|
||||
/>;
|
||||
|
||||
slots[7] = <StandardSlot
|
||||
@@ -178,6 +182,7 @@ export default class StandardSlotSection extends SlotSection {
|
||||
onOpen={open.bind(this, st[6])}
|
||||
onSelect={select.bind(this, st[6])}
|
||||
selected={currentMenu == st[6]}
|
||||
onChange={this.props.onChange}
|
||||
ship={ship}
|
||||
warning= {m => m.fuel < st[2].m.maxfuel} // Show warning when fuel tank is smaller than FSD Max Fuel
|
||||
/>;
|
||||
|
||||
@@ -319,6 +319,90 @@ export class Warning extends SvgIcon {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thermal damage
|
||||
*/
|
||||
export class DamageThermal extends SvgIcon {
|
||||
/**
|
||||
* Overriden view box
|
||||
* @return {String} view box
|
||||
*/
|
||||
viewBox() { return '0 0 200 200'; }
|
||||
/**
|
||||
* Generate the SVG
|
||||
* @return {React.Component} SVG Contents
|
||||
*/
|
||||
svg() {
|
||||
return <g>
|
||||
<ellipse cx='100' cy='100' rx='90' ry='90' fillOpacity='0' />
|
||||
<ellipse cx='100' cy='100' rx='30' ry='30' fillOpacity='1' />
|
||||
<path d='M100 20v80' />
|
||||
</g>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kinetic damage
|
||||
*/
|
||||
export class DamageKinetic extends SvgIcon {
|
||||
/**
|
||||
* Overriden view box
|
||||
* @return {String} view box
|
||||
*/
|
||||
viewBox() { return '0 0 200 200'; }
|
||||
/**
|
||||
* Generate the SVG
|
||||
* @return {React.Component} SVG Contents
|
||||
*/
|
||||
svg() {
|
||||
return <g>
|
||||
<ellipse cx='100' cy='100' rx='90' ry='90' fillOpacity='0' />
|
||||
<ellipse cx='62' cy='67' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='62' cy='101' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='62' cy='135' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='100' cy='50' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='100' cy='84' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='100' cy='118' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='100' cy='152' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='138' cy='67' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='138' cy='101' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='138' cy='135' rx='5' ry='5' fillOpacity='1' />
|
||||
</g>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Explosive damage
|
||||
*/
|
||||
export class DamageExplosive extends SvgIcon {
|
||||
/**
|
||||
* Overriden view box
|
||||
* @return {String} view box
|
||||
*/
|
||||
viewBox() { return '0 0 200 200'; }
|
||||
/**
|
||||
* Generate the SVG
|
||||
* @return {React.Component} SVG Contents
|
||||
*/
|
||||
svg() {
|
||||
return <g>
|
||||
<ellipse cx='100' cy='100' rx='50' ry='50' fillOpacity='0' />
|
||||
<ellipse cx='100' cy='20' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='156.57' cy='36.57' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='180' cy='100' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='156.57' cy='163.43' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='100' cy='180' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='43.43' cy='163.43' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='20' cy='100' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='43.43' cy='36.57' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='100' cy='75' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='125' cy='100' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='100' cy='125' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='75' cy='100' rx='5' ry='5' fillOpacity='1' />
|
||||
</g>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixed mount hardpoint
|
||||
*/
|
||||
@@ -334,11 +418,11 @@ export class MountFixed extends SvgIcon {
|
||||
*/
|
||||
svg() {
|
||||
return <g>
|
||||
<circle fillOpacity='0' r='70' cy='100' cx='100' strokeWidth='5' />
|
||||
<line y2='60' x2='101' y1='0' x1='101' strokeWidth='5' />
|
||||
<line y2='101' x2='200' y1='101' x1='140' strokeWidth='5' />
|
||||
<line y2='101' x2='60' y1='101' x1='0' strokeWidth='5' />
|
||||
<line y2='200' x2='101' y1='140' x1='101' strokeWidth='5' />
|
||||
<circle cx='100' cy='100' r='76' fillOpacity='0' />
|
||||
<path d='M0 100h48' />
|
||||
<path d='M152 100h48' />
|
||||
<path d='M100 0v48' />
|
||||
<path d='M100 152v48' />
|
||||
</g>;
|
||||
}
|
||||
}
|
||||
@@ -358,8 +442,8 @@ export class MountGimballed extends SvgIcon {
|
||||
*/
|
||||
svg() {
|
||||
return <g>
|
||||
<ellipse ry='25' rx='95' cy='100' cx='100' fillOpacity='0' strokeWidth='5' />
|
||||
<ellipse ry='95' rx='25' cy='100' cx='100' fillOpacity='0' strokeWidth='5' />
|
||||
<ellipse cx='100' cy='100' rx='90' ry='25' fillOpacity='0' />
|
||||
<ellipse cx='100' cy='100' rx='20' ry='95' fillOpacity='0' />
|
||||
</g>;
|
||||
}
|
||||
}
|
||||
@@ -379,9 +463,14 @@ export class MountTurret extends SvgIcon {
|
||||
*/
|
||||
svg() {
|
||||
return <g>
|
||||
<line y2='170' x2='162' y1='170' x1='8' strokeWidth='6' />
|
||||
<path d='m13,138l144,0l0,-50l-27,-40l-90,0l-27,40l0,50z' id='svg_12' fillOpacity='0' strokeWidth='6' />
|
||||
<line y2='91' x2='200' y1='91' x1='159' strokeWidth='6' />
|
||||
<path d='M40 50 A 40 40 0 0 0 0 90' />
|
||||
<path d='M40 50h40' />
|
||||
<path d='M120 90 A 40 40 0 0 0 80 50' />
|
||||
<path d='M0 90v40' />
|
||||
<path d='M120 90v40' />
|
||||
<path d='M0 120h120' />
|
||||
<path d='M120 90h80' />
|
||||
<path d='M0 160h120' />
|
||||
</g>;
|
||||
}
|
||||
}
|
||||
@@ -399,6 +488,25 @@ export class Rocket extends SvgIcon {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ListModifications (engineers)
|
||||
*/
|
||||
export class ListModifications extends SvgIcon {
|
||||
/**
|
||||
* Overriden view box
|
||||
* @return {String} view box
|
||||
*/
|
||||
viewBox() { return '0 0 200 200'; }
|
||||
/**
|
||||
/**
|
||||
* Generate the SVG
|
||||
* @return {React.Component} SVG Contents
|
||||
*/
|
||||
svg() {
|
||||
return <path d='M20 180l90-100-90 100zM176 40a40 40 0 1 1-27-28'/>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hammer
|
||||
*/
|
||||
|
||||
@@ -68,6 +68,7 @@ export default class UtilitySlotSection extends SlotSection {
|
||||
availableModules={() => availableModules.getHps(h.maxClass)}
|
||||
onOpen={this._openMenu.bind(this,h)}
|
||||
onSelect={this._selectModule.bind(this, h)}
|
||||
onChange={this.props.onChange}
|
||||
selected={currentMenu == h}
|
||||
drag={this._drag.bind(this, h)}
|
||||
dragOver={this._dragOverSlot.bind(this, h)}
|
||||
@@ -104,9 +105,17 @@ export default class UtilitySlotSection extends SlotSection {
|
||||
<li className='c' onClick={_use.bind(this, 'sb', 'B', null)}>B</li>
|
||||
<li className='c' onClick={_use.bind(this, 'sb', 'A', null)}>A</li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('cm')}</div>
|
||||
<div className='select-group cap'>{translate('hs')}</div>
|
||||
<ul>
|
||||
<li className='lc' onClick={_use.bind(this, 'cm', null, 'Heat Sink Launcher')}>{translate('Heat Sink Launcher')}</li>
|
||||
<li className='lc' onClick={_use.bind(this, 'hs', null, 'Heat Sink Launcher')}>{translate('Heat Sink Launcher')}</li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('ch')}</div>
|
||||
<ul>
|
||||
<li className='lc' onClick={_use.bind(this, 'ch', null, 'Chaff Launcher')}>{translate('Chaff Launcher')}</li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('po')}</div>
|
||||
<ul>
|
||||
<li className='lc' onClick={_use.bind(this, 'po', null, 'Point Defence')}>{translate('Point Defence')}</li>
|
||||
</ul>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -41,12 +41,15 @@ export function getLanguage(langCode) {
|
||||
formats: {
|
||||
gen, // General number format (.e.g 1,001,001.1234)
|
||||
int: d3Locale.numberFormat(',.0f'), // Fixed to 0 decimal places (.e.g 1,001)
|
||||
f1: d3Locale.numberFormat(',.1f'), // Fixed to 1 decimal place (.e.g 1,001.1)
|
||||
f2: d3Locale.numberFormat(',.2f'), // Fixed to 2 decimal places (.e.g 1,001.10)
|
||||
s2: d3Locale.numberFormat('.2s'), // SI Format to 2 decimal places (.e.g 1.1k)
|
||||
pct: d3Locale.numberFormat('.2%'), // % to 2 decimal places (.e.g 5.40%)
|
||||
pct1: d3Locale.numberFormat('.1%'), // % to 1 decimal places (.e.g 5.4%)
|
||||
r1: d3Locale.numberFormat('.1r'), // Rounded to 1 significant number (.e.g 512 => 500, 4.122 => 4)
|
||||
r2: d3Locale.numberFormat('.2r'), // Rounded to 2 significant numbers (.e.g 512 => 510, 4.122 => 4.1)
|
||||
rPct: d3.format('%'), // % to 0 decimal places (.e.g 5%)
|
||||
round1: (d) => gen(d3.round(d, 1)), // Rounded to 0-1 decimal places (.e.g 5.1, 4)
|
||||
round: (d) => gen(d3.round(d, 2)), // Rounded to 0-2 decimal places (.e.g 5.12, 4.1)
|
||||
time: (d) => (d < 0 ? '-' : '') + Math.floor(Math.abs(d) / 60) + ':' + ('00' + Math.floor(Math.abs(d) % 60)).substr(-2, 2)
|
||||
},
|
||||
@@ -63,6 +66,7 @@ export function getLanguage(langCode) {
|
||||
MW: <u> {translate('MW')}</u>, // Mega Watts (same as Mega Joules per second)
|
||||
ps: <u>{translate('/s')}</u>, // per second
|
||||
pm: <u>{translate('/min')}</u>, // per minute
|
||||
s: <u>{translate('secs')}</u>, // Seconds
|
||||
T: <u> {translate('T')}</u>, // Metric Tons
|
||||
}
|
||||
};
|
||||
|
||||
@@ -37,11 +37,13 @@ export const terms = {
|
||||
bsg: 'Bi-Weave Shield Generator',
|
||||
c: 'Cannon',
|
||||
cc: 'Collector Limpet Controller',
|
||||
cm: 'Countermeasure',
|
||||
ch: 'Chaff Launcher',
|
||||
cr: 'Cargo Rack',
|
||||
cs: 'Cargo Scanner',
|
||||
cs: 'Manifest Scanner',
|
||||
dc: 'Docking Computer',
|
||||
ec: 'Electronic Countermeasure',
|
||||
fc: 'Fragment Cannon',
|
||||
fh: 'Fighter Hangar',
|
||||
fi: 'FSD Interdictor',
|
||||
fs: 'Fuel Scoop',
|
||||
fsd: 'Frame Shift Drive',
|
||||
@@ -49,6 +51,7 @@ export const terms = {
|
||||
fx: 'Fuel Transfer Limpet Controller',
|
||||
hb: 'Hatch Breaker Limpet Controller',
|
||||
hr: 'Hull Reinforcement Package',
|
||||
hs: 'Heat Sink Launcher',
|
||||
kw: 'Kill Warrant Scanner',
|
||||
ls: 'Life Support',
|
||||
mc: 'Multi-cannon',
|
||||
@@ -58,8 +61,13 @@ export const terms = {
|
||||
pa: 'Plasma Accelerator',
|
||||
pas: 'Planetary Approach Suite',
|
||||
pc: 'Prospector Limpet Controller',
|
||||
pce: 'Economy Class Passenger Cabin',
|
||||
pci: 'Business Class Passenger Cabin',
|
||||
pcm: 'First Class Passenger Cabin',
|
||||
pcq: 'Luxury Passenger Cabin',
|
||||
pd: 'power distributor',
|
||||
pl: 'Pulse Laser',
|
||||
po: 'Point Defence',
|
||||
pp: 'Power Plant',
|
||||
psg: 'Prismatic Shield Generator',
|
||||
pv: 'Planetary Vehicle Hangar',
|
||||
@@ -73,5 +81,64 @@ export const terms = {
|
||||
t: 'thrusters',
|
||||
tp: 'Torpedo Pylon',
|
||||
ul: 'Burst Laser',
|
||||
ws: 'Frame Shift Wake Scanner'
|
||||
ws: 'Frame Shift Wake Scanner',
|
||||
|
||||
// Items on the outfitting page
|
||||
// Notification of restricted slot for Orca/Beluga
|
||||
emptyrestricted: 'empty (restricted)',
|
||||
// 'ammo' was overloaded for outfitting page and modul info, so changed to ammunition for outfitting page
|
||||
ammunition: 'Ammo',
|
||||
|
||||
// Unit for seconds
|
||||
secs: 's',
|
||||
|
||||
// Hardpoint abbreviations
|
||||
dpe: 'Damage per MJ of energy',
|
||||
dps: 'Damage per second',
|
||||
dpssdps: 'Damage per second (sustained damage per second)',
|
||||
eps: 'Energy per second',
|
||||
epsseps: 'Energy per second (sustained energy per second)',
|
||||
hps: 'Heat per second',
|
||||
hpsshps: 'Heat per second (sustained heat per second)',
|
||||
|
||||
// Modifications
|
||||
ammo: 'Ammunition maximum',
|
||||
boot: 'Boot time',
|
||||
brokenregen: 'Broken regeneration rate',
|
||||
burst: 'Burst',
|
||||
clip: 'Ammunition clip',
|
||||
damage: 'Damage',
|
||||
distdraw: 'Distributor draw',
|
||||
duration: 'Duration',
|
||||
eff: 'Efficiency',
|
||||
engcap: 'Engines capacity',
|
||||
engrate: 'Engines recharge rate',
|
||||
explres: 'Explosive resistance',
|
||||
facinglimit: 'Facing limit',
|
||||
hullboost: 'Hull boost',
|
||||
hullreinforcement: 'Hull reinforcement',
|
||||
integrity: 'Integrity',
|
||||
jitter: 'Jitter',
|
||||
kinres: 'Kinetic resistance',
|
||||
maxfuel: 'Maximum fuel per jump',
|
||||
mass: 'Mass',
|
||||
optmass: 'Optimal mass',
|
||||
optmul: 'Optimal multiplier',
|
||||
pgen: 'Power generation',
|
||||
piercing: 'Piercing',
|
||||
power: 'Power draw',
|
||||
range: 'Range',
|
||||
ranget: 'Range', // Range in time (for FSD interdictor)
|
||||
regen: 'Regeneration rate',
|
||||
reload: 'Reload time',
|
||||
rof: 'Rate of fire',
|
||||
shield: 'Shield',
|
||||
shieldboost: 'Shield boost',
|
||||
spinup: 'Spin up time',
|
||||
syscap: 'Systems capacity',
|
||||
sysrate: 'Systems recharge rate',
|
||||
thermload: 'Thermal load',
|
||||
thermres: 'Thermal resistance',
|
||||
wepcap: 'Weapons capacity',
|
||||
weprate: 'Weapons recharge rate',
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ export default class AboutPage extends Page {
|
||||
<p>This is a clone of the Coriolis project, whose original author is currently unable to maintain it. This clone is maintained by the <a href="http://edcd.github.io/">EDCD community</a>.</p>
|
||||
<p>To recover your builds, go to <a href='https://coriolis.io/' target='_blank'>https://coriolis.io/</a>, backup your builds (Settings / Backup), copy the text, return here and import (Settings / Import).</p>
|
||||
<p>The Coriolis project was inspired by <a href='http://www.edshipyard.com/' target='_blank'>E:D Shipyard</a> and, of course, <a href='http://www.elitedangerous.com' target='_blank'>Elite Dangerous</a>. The ultimate goal of Coriolis is to provide rich features to support in-game play and planning while engaging the E:D community to support its development.</p>
|
||||
<p>Coriolis was created using assets and imagery from Elite: Dangerous, with the permission of Frontier Developments plc, for non-commercial purposes. It is not endorsed by nor reflects the views or opinions of Frontier Developments.</p>
|
||||
<p>Coriolis was created using assets and imagery from Elite: Dangerous, with the permission of Frontier Developments plc, for non-commercial purposes. It is not endorsed by nor reflects the views or opinions of Frontier Developments. A number of assets were sourced from <a href='http://edassets.org' target='_blank'>ED Assets</a></p>
|
||||
|
||||
<a style={{ display: 'block', textDecoration: 'none' }} href='https://github.com/EDCD/coriolis' target='_blank' title='Coriolis Github Project'>
|
||||
<GitHub style={{ margin: '0.4em' }} className='l fg xl'/>
|
||||
|
||||
@@ -63,7 +63,7 @@ export default class ComparisonPage extends Page {
|
||||
* @return {Object} New state object
|
||||
*/
|
||||
_initState(context) {
|
||||
let defaultFacets = [9, 6, 4, 1, 3, 2]; // Reverse order of Armour, Shields, Speed, Jump Range, Cargo Capacity, Cost
|
||||
let defaultFacets = [13, 12, 11, 9, 6, 4, 1, 3, 2]; // Reverse order of Armour, Shields, Speed, Jump Range, Cargo Capacity, Cost, DPS, EPS, HPS
|
||||
let params = context.route.params;
|
||||
let code = params.code;
|
||||
let name = params.name ? decodeURIComponent(params.name) : null;
|
||||
|
||||
@@ -284,16 +284,16 @@ export default class OutfittingPage extends Page {
|
||||
canSave = (newBuildName || buildName) && code !== savedCode,
|
||||
canRename = buildName && newBuildName && buildName != newBuildName,
|
||||
canReload = savedCode && canSave,
|
||||
hStr = ship.getHardpointsString(),
|
||||
sStr = ship.getStandardString(),
|
||||
iStr = ship.getInternalString();
|
||||
hStr = ship.getHardpointsString() + '.' + ship.getModificationsString(),
|
||||
sStr = ship.getStandardString() + '.' + ship.getModificationsString(),
|
||||
iStr = ship.getInternalString() + '.' + ship.getModificationsString();
|
||||
|
||||
return (
|
||||
<div id='outfit' className={'page'} style={{ fontSize: (sizeRatio * 0.9) + 'em' }}>
|
||||
<div id='overview'>
|
||||
<h1>{ship.name}</h1>
|
||||
<div id='build'>
|
||||
<input value={newBuildName} onChange={this._buildNameChange} placeholder={translate('Enter Name')} maxsize={50} />
|
||||
<input value={newBuildName} onChange={this._buildNameChange} placeholder={translate('Enter Name')} maxLength={50} />
|
||||
<button onClick={canSave && this._saveBuild} disabled={!canSave} onMouseOver={termtip.bind(null, 'save')} onMouseOut={hide}>
|
||||
<FloppyDisk className='lg' />
|
||||
</button>
|
||||
|
||||
@@ -241,7 +241,7 @@ export default class ShipyardPage extends Page {
|
||||
|
||||
return (
|
||||
<div className='page' style={{ fontSize: sizeRatio + 'em' }}>
|
||||
<p style={{ textAlign: 'center' }}>This is <strong>Coriolis EDCD Edition</strong> - a temporary clone of <a href='https://coriolis.io/' target='_blank'>https://coriolis.io/</a> with added support for E:D 2.1. For more info see Settings / <Link href="/about" className='block'>About</Link></p>
|
||||
<p style={{ textAlign: 'center' }}>This is <strong>Coriolis EDCD Edition</strong> - a temporary clone of <a href='https://coriolis.io/' target='_blank'>https://coriolis.io/</a> with added support for E:D 2.2. For more info see Settings / <Link href="/about" className='block'>About</Link></p>
|
||||
<div style={{ whiteSpace: 'nowrap', margin: '0 auto', fontSize: '0.8em', position: 'relative', display: 'inline-block', maxWidth: '100%' }}>
|
||||
<table style={{ width: '12em', position: 'absolute', zIndex: 1 }}>
|
||||
<thead>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Module from './Module';
|
||||
|
||||
/**
|
||||
* Calculate the maximum single jump range based on mass and a specific FSD
|
||||
@@ -8,7 +9,9 @@
|
||||
* @return {number} Distance in Light Years
|
||||
*/
|
||||
export function jumpRange(mass, fsd, fuel) {
|
||||
return Math.pow(Math.min(fuel === undefined ? fsd.maxfuel : fuel, fsd.maxfuel) / fsd.fuelmul, 1 / fsd.fuelpower) * fsd.optmass / mass;
|
||||
let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
|
||||
let fsdOptimalMass = fsd instanceof Module ? fsd.getOptimalMass() : fsd.optmass;
|
||||
return Math.pow(Math.min(fuel === undefined ? fsdMaxFuelPerJump : fuel, fsdMaxFuelPerJump) / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -20,15 +23,17 @@ export function jumpRange(mass, fsd, fuel) {
|
||||
* @return {number} Distance in Light Years
|
||||
*/
|
||||
export function fastestRange(mass, fsd, fuel) {
|
||||
let fuelRemaining = fuel % fsd.maxfuel; // Fuel left after making N max jumps
|
||||
let jumps = Math.floor(fuel / fsd.maxfuel);
|
||||
let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
|
||||
let fsdOptimalMass = fsd instanceof Module ? fsd.getOptimalMass() : fsd.optmass;
|
||||
let fuelRemaining = fuel % fsdMaxFuelPerJump; // Fuel left after making N max jumps
|
||||
let jumps = Math.floor(fuel / fsdMaxFuelPerJump);
|
||||
mass += fuelRemaining;
|
||||
// Going backwards, start with the last jump using the remaining fuel
|
||||
let fastestRange = fuelRemaining > 0 ? Math.pow(fuelRemaining / fsd.fuelmul, 1 / fsd.fuelpower) * fsd.optmass / mass : 0;
|
||||
let fastestRange = fuelRemaining > 0 ? Math.pow(fuelRemaining / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass : 0;
|
||||
// For each max fuel jump, calculate the max jump range based on fuel mass left in the tank
|
||||
for (let j = 0; j < jumps; j++) {
|
||||
mass += fsd.maxfuel;
|
||||
fastestRange += Math.pow(fsd.maxfuel / fsd.fuelmul, 1 / fsd.fuelpower) * fsd.optmass / mass;
|
||||
fastestRange += Math.pow(fsdMaxFuelPerJump / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass;
|
||||
}
|
||||
return fastestRange;
|
||||
};
|
||||
@@ -36,29 +41,27 @@ export function fastestRange(mass, fsd, fuel) {
|
||||
/**
|
||||
* Calculate the a ships shield strength based on mass, shield generator and shield boosters used.
|
||||
*
|
||||
* @param {number} mass Current mass of the ship
|
||||
* @param {number} shields Base Shield strength MJ for ship
|
||||
* @param {object} sg The shield generator used
|
||||
* @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any)
|
||||
* @return {number} Approximate shield strengh in MJ
|
||||
* @param {number} mass Current mass of the ship
|
||||
* @param {number} baseShield Base Shield strength MJ for ship
|
||||
* @param {object} sg The shield generator used
|
||||
* @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any)
|
||||
* @return {number} Approximate shield strengh in MJ
|
||||
*/
|
||||
export function shieldStrength(mass, shields, sg, multiplier) {
|
||||
let opt;
|
||||
if (mass < sg.minmass) {
|
||||
return shields * multiplier * sg.minmul;
|
||||
}
|
||||
if (mass > sg.maxmass) {
|
||||
return shields * multiplier * sg.maxmul;
|
||||
}
|
||||
if (mass < sg.optmass) {
|
||||
opt = (sg.optmass - mass) / (sg.optmass - sg.minmass);
|
||||
opt = 1 - Math.pow(1 - opt, 0.87);
|
||||
return shields * multiplier * ((opt * sg.minmul) + ((1 - opt) * sg.optmul));
|
||||
} else {
|
||||
opt = (sg.optmass - mass) / (sg.maxmass - sg.optmass);
|
||||
opt = -1 + Math.pow(1 + opt, 2.425);
|
||||
return shields * multiplier * ((-1 * opt * sg.maxmul) + ((1 + opt) * sg.optmul));
|
||||
}
|
||||
export function shieldStrength(mass, baseShield, sg, multiplier) {
|
||||
// sg might be a module or a template; handle either here
|
||||
let minMass = sg instanceof Module ? sg.getMinMass() : sg.minmass;
|
||||
let optMass = sg instanceof Module ? sg.getOptMass() : sg.optmass;
|
||||
let maxMass = sg instanceof Module ? sg.getMaxMass() : sg.maxmass;
|
||||
let minMul = sg instanceof Module ? sg.getMinMul() : sg.minmul;
|
||||
let optMul = sg instanceof Module ? sg.getOptMul() : sg.optmul;
|
||||
let maxMul = sg instanceof Module ? sg.getMaxMul() : sg.maxmul;
|
||||
|
||||
let xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass));
|
||||
let exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass)));
|
||||
let ynorm = Math.pow(xnorm, exponent);
|
||||
let mul = minMul + ynorm * (maxMul - minMul);
|
||||
|
||||
return baseShield * mul * multiplier;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,13 +75,24 @@ export function shieldStrength(mass, shields, sg, multiplier) {
|
||||
* @return {object} Approximate speed by pips
|
||||
*/
|
||||
export function speed(mass, baseSpeed, baseBoost, thrusters, pipSpeed) {
|
||||
let multiplier = mass > thrusters.maxmass ? 0 : ((1 - thrusters.M) + (thrusters.M * Math.pow(3 - (2 * Math.max(0.5, mass / thrusters.optmass)), thrusters.P)));
|
||||
let speed = baseSpeed * multiplier;
|
||||
// thrusters might be a module or a template; handle either here
|
||||
let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass;
|
||||
let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass;
|
||||
let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass;
|
||||
let minMul = thrusters instanceof Module ? thrusters.getMinMul() : thrusters.minmul;
|
||||
let optMul = thrusters instanceof Module ? thrusters.getOptMul() : thrusters.optmul;
|
||||
let maxMul = thrusters instanceof Module ? thrusters.getMaxMul() : thrusters.maxmul;
|
||||
|
||||
let xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass));
|
||||
let exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass)));
|
||||
let ynorm = Math.pow(xnorm, exponent);
|
||||
let mul = minMul + ynorm * (maxMul - minMul);
|
||||
let speed = baseSpeed * mul;
|
||||
|
||||
return {
|
||||
'0 Pips': speed * (1 - (pipSpeed * 4)),
|
||||
'2 Pips': speed * (1 - (pipSpeed * 2)),
|
||||
'4 Pips': speed,
|
||||
'boost': baseBoost * multiplier
|
||||
'boost': baseBoost * mul
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
|
||||
export const ArmourMultiplier = [
|
||||
1, // Lightweight
|
||||
1.4, // Reinforced
|
||||
1.945, // Military
|
||||
1.945, // Mirrored
|
||||
1.945 // Reactive
|
||||
];
|
||||
|
||||
export const SizeMap = ['', 'small', 'medium', 'large', 'capital'];
|
||||
|
||||
export const StandardArray = [
|
||||
@@ -37,6 +29,7 @@ export const ModuleGroupToName = {
|
||||
am: 'Auto Field-Maintenance Unit',
|
||||
bsg: 'Bi-Weave Shield Generator',
|
||||
cr: 'Cargo Rack',
|
||||
fh: 'Fighter Hangar',
|
||||
fi: 'Frame Shift Drive Interdictor',
|
||||
hb: 'Hatch Breaker Limpet Controller',
|
||||
hr: 'Hull Reinforcement Package',
|
||||
@@ -48,6 +41,10 @@ export const ModuleGroupToName = {
|
||||
dc: 'Docking Computer',
|
||||
fx: 'Fuel Transfer Limpet Controller',
|
||||
pc: 'Prospector Limpet Controller',
|
||||
pce: 'Economy Class Passenger Cabin',
|
||||
pci: 'Business Class Passenger Cabin',
|
||||
pcm: 'First Class Passenger Cabin',
|
||||
pcq: 'Luxury Passenger Cabin',
|
||||
cc: 'Collector Limpet Controller',
|
||||
|
||||
// Hard Points
|
||||
@@ -188,6 +185,20 @@ export const ShipFacets = [
|
||||
lbls: ['DPS'],
|
||||
fmt: 'round',
|
||||
i: 11
|
||||
},
|
||||
{ // 12
|
||||
title: 'EPS',
|
||||
props: ['totalEps'],
|
||||
lbls: ['EPS'],
|
||||
fmt: 'round',
|
||||
i: 12
|
||||
},
|
||||
{ // 13
|
||||
title: 'HPS',
|
||||
props: ['totalHps'],
|
||||
lbls: ['HPS'],
|
||||
fmt: 'round',
|
||||
i: 13
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
15
src/app/shipyard/Modification.js
Executable file
15
src/app/shipyard/Modification.js
Executable file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Modification - a modification and its value
|
||||
*/
|
||||
export default class Modification {
|
||||
|
||||
/**
|
||||
* @param {String} id Unique modification ID
|
||||
* @param {Number} value Value of the modification
|
||||
*/
|
||||
constructor(id, value) {
|
||||
this.id = id;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
}
|
||||
487
src/app/shipyard/Module.js
Executable file
487
src/app/shipyard/Module.js
Executable file
@@ -0,0 +1,487 @@
|
||||
import * as ModuleUtils from './ModuleUtils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
/**
|
||||
* Module - active module in a ship's buildout
|
||||
*/
|
||||
export default class Module {
|
||||
|
||||
/**
|
||||
* Construct a new module
|
||||
* @param {Object} params Module parameters. Either grp/id or template
|
||||
*/
|
||||
constructor(params) {
|
||||
let properties = Object.assign({ grp: null, id: null, template: null }, params);
|
||||
|
||||
let template;
|
||||
if (properties.template == undefined) {
|
||||
return ModuleUtils.findModule(properties.grp, properties.id);
|
||||
} else {
|
||||
template = properties.template;
|
||||
if (template) {
|
||||
// Copy all properties from coriolis-data template
|
||||
for (let p in template) { this[p] = template[p]; }
|
||||
}
|
||||
}
|
||||
this.mods = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value for a given modification
|
||||
* @param {Number} name The name of the modification
|
||||
* @return {Number} The value of the modification, as a decimal value where 1 is 100%
|
||||
*/
|
||||
getModValue(name) {
|
||||
return this.mods && this.mods[name] ? this.mods[name] / 10000 : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value for a given modification ID
|
||||
* @param {Number} name The name of the modification
|
||||
* @param {Number} value The value of the modification, as a decimal value where 1 is 100%
|
||||
*/
|
||||
setModValue(name, value) {
|
||||
if (value == null || value == 0) {
|
||||
delete this.mods[name];
|
||||
} else {
|
||||
// Store value with 2dp
|
||||
this.mods[name] = Math.round(value * 10000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to obtain a modified value using standard multipliers
|
||||
* @param {String} name the name of the modifier to obtain
|
||||
* @return {Number} the mass of this module
|
||||
*/
|
||||
_getModifiedValue(name) {
|
||||
let result = 0;
|
||||
if (this[name]) {
|
||||
result = this[name];
|
||||
if (result) {
|
||||
let mult = this.getModValue(name);
|
||||
if (mult) { result = result * (1 + mult); }
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Get the power generation of this module, taking in to account modifications
|
||||
* @return {Number} the power generation of this module
|
||||
*/
|
||||
getPowerGeneration() {
|
||||
return this._getModifiedValue('pgen');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the power usage of this module, taking in to account modifications
|
||||
* @return {Number} the power usage of this module
|
||||
*/
|
||||
getPowerUsage() {
|
||||
return this._getModifiedValue('power');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the integrity of this module, taking in to account modifications
|
||||
* @return {Number} the integrity of this module
|
||||
*/
|
||||
getIntegrity() {
|
||||
return this._getModifiedValue('integrity');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mass of this module, taking in to account modifications
|
||||
* @return {Number} the mass of this module
|
||||
*/
|
||||
getMass() {
|
||||
return this._getModifiedValue('mass');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the thermal efficiency of this module, taking in to account modifications
|
||||
* @return {Number} the thermal efficiency of this module
|
||||
*/
|
||||
getThermalEfficiency() {
|
||||
return this._getModifiedValue('eff');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum mass of this module, taking in to account modifications
|
||||
* @return {Number} the maximum mass of this module
|
||||
*/
|
||||
getMaxMass() {
|
||||
return this._getModifiedValue('maxmass');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the optimal mass of this module, taking in to account modifications
|
||||
* @return {Number} the optimal mass of this module
|
||||
*/
|
||||
getOptimalMass() {
|
||||
return this._getModifiedValue('optmass');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the optimal multiplier of this module, taking in to account modifications
|
||||
* @return {Number} the optimal multiplier of this module
|
||||
*/
|
||||
getOptimalMultiplier() {
|
||||
return this._getModifiedValue('optmult');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum fuel per jump for this module, taking in to account modifications
|
||||
* @return {Number} the maximum fuel per jump of this module
|
||||
*/
|
||||
getMaxFuelPerJump() {
|
||||
return this._getModifiedValue('maxfuel');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the systems capacity for this module, taking in to account modifications
|
||||
* @return {Number} the systems capacity of this module
|
||||
*/
|
||||
getSystemsCapacity() {
|
||||
return this._getModifiedValue('syscap');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the engines capacity for this module, taking in to account modifications
|
||||
* @return {Number} the engines capacity of this module
|
||||
*/
|
||||
getEnginesCapacity() {
|
||||
return this._getModifiedValue('engcap');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the weapons capacity for this module, taking in to account modifications
|
||||
* @return {Number} the weapons capacity of this module
|
||||
*/
|
||||
getWeaponsCapacity() {
|
||||
return this._getModifiedValue('wepcap');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the systems recharge rate for this module, taking in to account modifications
|
||||
* @return {Number} the systems recharge rate of this module
|
||||
*/
|
||||
getSystemsRechargeRate() {
|
||||
return this._getModifiedValue('sysrate');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the engines recharge rate for this module, taking in to account modifications
|
||||
* @return {Number} the engines recharge rate of this module
|
||||
*/
|
||||
getEnginesRechargeRate() {
|
||||
return this._getModifiedValue('engrate');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the weapons recharge rate for this module, taking in to account modifications
|
||||
* @return {Number} the weapons recharge rate of this module
|
||||
*/
|
||||
getWeaponsRechargeRate() {
|
||||
return this._getModifiedValue('weprate');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the kinetic resistance for this module, taking in to account modifications
|
||||
* @return {Number} the kinetic resistance of this module
|
||||
*/
|
||||
getKineticResistance() {
|
||||
return this._getModifiedValue('kinres');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the thermal resistance for this module, taking in to account modifications
|
||||
* @return {Number} the thermal resistance of this module
|
||||
*/
|
||||
getThermalResistance() {
|
||||
return this._getModifiedValue('thermres');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the explosive resistance for this module, taking in to account modifications
|
||||
* @return {Number} the explosive resistance of this module
|
||||
*/
|
||||
getExplosiveResistance() {
|
||||
return this._getModifiedValue('explres');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the regeneration rate for this module, taking in to account modifications
|
||||
* @return {Number} the regeneration rate of this module
|
||||
*/
|
||||
getRegenerationRate() {
|
||||
return this._getModifiedValue('regen');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the broken regeneration rate for this module, taking in to account modifications
|
||||
* @return {Number} the broken regeneration rate of this module
|
||||
*/
|
||||
getBrokenRegenerationRate() {
|
||||
return this._getModifiedValue('brokenregen');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the range for this module, taking in to account modifications
|
||||
* @return {Number} the range rate of this module
|
||||
*/
|
||||
getRange() {
|
||||
return this._getModifiedValue('range');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the range (in terms of seconds, for FSDI) for this module, taking in to account modifications
|
||||
* @return {Number} the range of this module
|
||||
*/
|
||||
getRangeT() {
|
||||
return this._getModifiedValue('ranget');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the capture arc for this module, taking in to account modifications
|
||||
* @return {Number} the capture arc of this module
|
||||
*/
|
||||
getCaptureArc() {
|
||||
return this._getModifiedValue('arc');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hull reinforcement for this module, taking in to account modifications
|
||||
* @return {Number} the hull reinforcement of this module
|
||||
*/
|
||||
getHullReinforcement() {
|
||||
return this._getModifiedValue('hullreinforcement');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the delay for this module, taking in to account modifications
|
||||
* @return {Number} the delay of this module
|
||||
*/
|
||||
getDelay() {
|
||||
return this._getModifiedValue('delay');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the duration for this module, taking in to account modifications
|
||||
* @return {Number} the duration of this module
|
||||
*/
|
||||
getDuration() {
|
||||
return this._getModifiedValue('duration');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the shield boost for this module, taking in to account modifications
|
||||
* @return {Number} the shield boost of this module
|
||||
*/
|
||||
getShieldBoost() {
|
||||
return this._getModifiedValue('shieldboost');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the minimum mass for this module, taking in to account modifications
|
||||
* @return {Number} the minimum mass of this module
|
||||
*/
|
||||
getMinMass() {
|
||||
// Modifier is optmass
|
||||
let result = 0;
|
||||
if (this['minmass']) {
|
||||
result = this['minmass'];
|
||||
if (result) {
|
||||
let mult = this.getModValue('optmass');
|
||||
if (mult) { result = result * (1 + mult); }
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the optimum mass for this module, taking in to account modifications
|
||||
* @return {Number} the optimum mass of this module
|
||||
*/
|
||||
getOptMass() {
|
||||
return this._getModifiedValue('optmass');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum mass for this module, taking in to account modifications
|
||||
* @return {Number} the maximum mass of this module
|
||||
*/
|
||||
getMaxMass() {
|
||||
// Modifier is optmass
|
||||
let result = 0;
|
||||
if (this['maxmass']) {
|
||||
result = this['maxmass'];
|
||||
if (result) {
|
||||
let mult = this.getModValue('optmass');
|
||||
if (mult) { result = result * (1 + mult); }
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the minimum multiplier for this module, taking in to account modifications
|
||||
* @return {Number} the minimum multiplier of this module
|
||||
*/
|
||||
getMinMul() {
|
||||
// Modifier is optmul
|
||||
let result = 0;
|
||||
if (this['minmul']) {
|
||||
result = this['minmul'];
|
||||
if (result) {
|
||||
let mult = this.getModValue('optmul');
|
||||
if (mult) { result = result * (1 + mult); }
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the optimum multiplier for this module, taking in to account modifications
|
||||
* @return {Number} the optimum multiplier of this module
|
||||
*/
|
||||
getOptMul() {
|
||||
return this._getModifiedValue('optmul');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum multiplier for this module, taking in to account modifications
|
||||
* @return {Number} the maximum multiplier of this module
|
||||
*/
|
||||
getMaxMul() {
|
||||
// Modifier is optmul
|
||||
let result = 0;
|
||||
if (this['maxmul']) {
|
||||
result = this['maxmul'];
|
||||
if (result) {
|
||||
let mult = this.getModValue('optmul');
|
||||
if (mult) { result = result * (1 + mult); }
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the damage for this module, taking in to account modifications
|
||||
* @return {Number} the damage of this module
|
||||
*/
|
||||
getDamage() {
|
||||
return this._getModifiedValue('damage');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the distributor draw for this module, taking in to account modifications
|
||||
* @return {Number} the distributor draw of this module
|
||||
*/
|
||||
getDistDraw() {
|
||||
return this._getModifiedValue('distdraw');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the thermal load for this module, taking in to account modifications
|
||||
* @return {Number} the thermal load of this module
|
||||
*/
|
||||
getThermalLoad() {
|
||||
return this._getModifiedValue('thermload');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the rounds per shot for this module, taking in to account modifications
|
||||
* @return {Number} the rounds per shot of this module
|
||||
*/
|
||||
getRoundsPerShot() {
|
||||
return this._getModifiedValue('roundspershot');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DPS for this module, taking in to account modifications
|
||||
* @return {Number} the DPS of this module
|
||||
*/
|
||||
getDps() {
|
||||
// DPS is a synthetic value
|
||||
let damage = this.getDamage();
|
||||
let rpshot = this.getRoundsPerShot() || 1;
|
||||
let rof = this.getRoF() || 1;
|
||||
|
||||
return damage * rpshot * rof;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the EPS for this module, taking in to account modifications
|
||||
* @return {Number} the EPS of this module
|
||||
*/
|
||||
getEps() {
|
||||
// EPS is a synthetic value
|
||||
let distdraw = this.getDistDraw();
|
||||
let rpshot = this.getRoundsPerShot() || 1;
|
||||
let rof = this.getRoF() || 1;
|
||||
|
||||
return distdraw * rpshot * rof;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HPS for this module, taking in to account modifications
|
||||
* @return {Number} the HPS of this module
|
||||
*/
|
||||
getHps() {
|
||||
// HPS is a synthetic value
|
||||
let heat = this.getThermalLoad();
|
||||
let rpshot = this.getRoundsPerShot() || 1;
|
||||
let rof = this.getRoF() || 1;
|
||||
|
||||
return heat * rpshot * rof;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the clip size for this module, taking in to account modifications
|
||||
* @return {Number} the clip size of this module
|
||||
*/
|
||||
getClip() {
|
||||
return this._getModifiedValue('clip');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ammo size for this module, taking in to account modifications
|
||||
* @return {Number} the ammo size of this module
|
||||
*/
|
||||
getAmmo() {
|
||||
return this._getModifiedValue('ammo');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reload time for this module, taking in to account modifications
|
||||
* @return {Number} the reload time of this module
|
||||
*/
|
||||
getReload() {
|
||||
return this._getModifiedValue('reload');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the rate of fire for this module, taking in to account modifications
|
||||
* @return {Number} the rate of fire for this module
|
||||
*/
|
||||
getRoF() {
|
||||
return this._getModifiedValue('rof');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the facing limit for this module, taking in to account modifications
|
||||
* @return {Number} the facing limit for this module
|
||||
*/
|
||||
getFacingLimit() {
|
||||
return this._getModifiedValue('facinglimit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hull boost for this module, taking in to account modifications
|
||||
* @return {Number} the hull boost for this module
|
||||
*/
|
||||
getHullBoost() {
|
||||
return this._getModifiedValue('hullboost');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
import Module from './Module';
|
||||
import { BulkheadNames } from './Constants';
|
||||
|
||||
/**
|
||||
@@ -37,7 +37,7 @@ export default class ModuleSet {
|
||||
this.intClass = {};
|
||||
|
||||
this.bulkheads = shipData.bulkheads.map((b, i) => {
|
||||
return Object.assign({ grp: 'bh', name: BulkheadNames[i], index: i, class: '', rating: '' }, b);
|
||||
return Object.assign(new Module(), { grp: 'bh', id: i, name: BulkheadNames[i], index: i, class: '', rating: '' }, b);
|
||||
});
|
||||
|
||||
this.standard[0] = filter(stnd.pp, maxStandardArr[0], 0, mass); // Power Plant
|
||||
@@ -66,21 +66,28 @@ export default class ModuleSet {
|
||||
* @return {Object} Bulkhead module details
|
||||
*/
|
||||
getBulkhead(index) {
|
||||
return this.bulkheads[index] || null;
|
||||
return this.bulkheads[index] ? new Module({ template: this.bulkheads[index] }) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the modules that areeligible for an internal slot
|
||||
* @param {Object} ship The ship
|
||||
* @param {integer} c The max class module that can be mounted in the slot
|
||||
* @param {Object} eligible) The map of eligible internal groups
|
||||
* @return {object} A map of all eligible modules by group
|
||||
*/
|
||||
getInts(c, eligible) {
|
||||
getInts(ship, c, eligible) {
|
||||
let o = {};
|
||||
for (let key in this.internal) {
|
||||
if (eligible && !eligible[key]) {
|
||||
continue;
|
||||
}
|
||||
if (key == 'pcq' && !(ship.luxuryCabins && ship.luxuryCabins === true)) {
|
||||
continue;
|
||||
}
|
||||
if (key == 'fh' && !(ship.fighterHangars && ship.fighterHangars === true)) {
|
||||
continue;
|
||||
}
|
||||
let data = filter(this.internal[key], c, 0, this.mass);
|
||||
if (data.length) { // If group is not empty
|
||||
o[key] = data;
|
||||
@@ -119,11 +126,11 @@ export default class ModuleSet {
|
||||
let pd = this.standard[4][0];
|
||||
|
||||
for (let p of this.standard[4]) {
|
||||
if (p.mass < pd.mass && p.enginecapacity >= boostEnergy) {
|
||||
if (p.mass < pd.mass && p.engcap >= boostEnergy) {
|
||||
pd = p;
|
||||
}
|
||||
}
|
||||
return pd;
|
||||
return new Module({ template: pd });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -139,7 +146,7 @@ export default class ModuleSet {
|
||||
th = t;
|
||||
}
|
||||
}
|
||||
return th;
|
||||
return new Module({ template: th });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -155,7 +162,7 @@ export default class ModuleSet {
|
||||
sg = s;
|
||||
}
|
||||
}
|
||||
return sg;
|
||||
return new Module({ template: sg });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -168,10 +175,10 @@ export default class ModuleSet {
|
||||
|
||||
for (let p of this.standard[0]) {
|
||||
// Provides enough power, is lighter or the same mass as current power plant but better output/efficiency
|
||||
if (p.pGen >= powerNeeded && (p.mass < pp.mass || (p.mass == pp.mass && p.pGen > pp.pGen))) {
|
||||
if (p.pgen >= powerNeeded && (p.mass < pp.mass || (p.mass == pp.mass && p.pgen > pp.pgen))) {
|
||||
pp = p;
|
||||
}
|
||||
}
|
||||
return pp;
|
||||
return new Module({ template: pp });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { ModuleNameToGroup, BulkheadNames, StandardArray } from './Constants';
|
||||
import ModuleSet from './ModuleSet';
|
||||
import Module from './Module';
|
||||
import { Ships, Modules } from 'coriolis-data/dist';
|
||||
|
||||
/*
|
||||
* All functions below must return a fresh Module rather than a definition or existing module, as
|
||||
* the resultant object can be altered with modifications.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
@@ -9,9 +14,45 @@ import { Ships, Modules } from 'coriolis-data/dist';
|
||||
* @return {Object} Cargo hatch model
|
||||
*/
|
||||
export function cargoHatch() {
|
||||
return { name: 'Cargo Hatch', class: 1, rating: 'H', power: 0.6 };
|
||||
let hatch = new Module();
|
||||
Object.assign(hatch, { name: 'Cargo Hatch', class: 1, rating: 'H', power: 0.6 });
|
||||
return hatch;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the module with the specific group and ID
|
||||
* @param {String} grp Module group (pp - power plant, pl - pulse laser etc)
|
||||
* @param {String} id The module ID
|
||||
* @return {Object} The module or null
|
||||
*/
|
||||
export function findModule(grp, id) {
|
||||
// See if it's a standard module
|
||||
if (Modules.standard[grp]) {
|
||||
let standardmod = Modules.standard[grp].find(e => e.id == id);
|
||||
if (standardmod != null) {
|
||||
return new Module({ template: standardmod });
|
||||
}
|
||||
}
|
||||
|
||||
// See if it's an internal module
|
||||
if (Modules.internal[grp]) {
|
||||
let internalmod = Modules.internal[grp].find(e => e.id == id);
|
||||
if (internalmod != null) {
|
||||
return new Module({ template: internalmod });
|
||||
}
|
||||
}
|
||||
|
||||
// See if it's a hardpoint module
|
||||
if (Modules.hardpoints[grp]) {
|
||||
let hardpointmod = Modules.hardpoints[grp].find(e => e.id == id);
|
||||
if (hardpointmod != null) {
|
||||
return new Module({ template: hardpointmod });
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the standard module type with the specified ID
|
||||
* @param {String|Number} type Standard Module Type (0/pp - Power Plant, 1/t - Thrusters, etc)
|
||||
@@ -24,6 +65,9 @@ export function standard(type, id) {
|
||||
}
|
||||
|
||||
let s = Modules.standard[type].find(e => e.id == id || (e.class == id.charAt(0) && e.rating == id.charAt(1)));
|
||||
if (s) {
|
||||
s = new Module({ template: s });
|
||||
}
|
||||
return s || null;
|
||||
};
|
||||
|
||||
@@ -37,7 +81,7 @@ export function hardpoints(id) {
|
||||
let group = Modules.hardpoints[n];
|
||||
for (let i = 0; i < group.length; i++) {
|
||||
if (group[i].id == id) {
|
||||
return group[i];
|
||||
return new Module({ template: group[i] });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,7 +98,7 @@ export function internal(id) {
|
||||
let group = Modules.internal[n];
|
||||
for (let i = 0; i < group.length; i++) {
|
||||
if (group[i].id == id) {
|
||||
return group[i];
|
||||
return new Module({ template: group[i] });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,33 @@ import LZString from 'lz-string';
|
||||
|
||||
const STANDARD = ['powerPlant', 'thrusters', 'frameShiftDrive', 'lifeSupport', 'powerDistributor', 'sensors', 'fuelTank'];
|
||||
|
||||
/**
|
||||
* Generates ship-loadout JSON Schema standard object
|
||||
* @param {Object} standard model
|
||||
* @return {Object} JSON Schema
|
||||
*/
|
||||
function standardToSchema(standard) {
|
||||
if (standard.m) {
|
||||
let o = {
|
||||
class: standard.m.class,
|
||||
rating: standard.m.rating,
|
||||
enabled: Boolean(standard.enabled),
|
||||
priority: standard.priority + 1
|
||||
};
|
||||
|
||||
if (standard.m.name) {
|
||||
o.name = standard.m.name;
|
||||
}
|
||||
|
||||
if (standard.m.mods && Object.keys(standard.m.mods).length > 0) {
|
||||
o.modifications = standard.m.mods;
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates ship-loadout JSON Schema slot object
|
||||
* @param {Object} slot Slot model
|
||||
@@ -30,6 +57,9 @@ function slotToSchema(slot) {
|
||||
if (slot.m.missile) {
|
||||
o.missile = slot.m.missile;
|
||||
}
|
||||
if (slot.m.mods && Object.keys(slot.m.mods).length > 0) {
|
||||
o.modifications = slot.m.mods;
|
||||
}
|
||||
return o;
|
||||
}
|
||||
return null;
|
||||
@@ -48,12 +78,12 @@ export function toDetailedBuild(buildName, ship) {
|
||||
code = ship.toString();
|
||||
|
||||
let data = {
|
||||
$schema: 'http://cdn.coriolis.io/schemas/ship-loadout/3.json#',
|
||||
$schema: 'http://cdn.coriolis.io/schemas/ship-loadout/4.json#',
|
||||
name: buildName,
|
||||
ship: ship.name,
|
||||
references: [{
|
||||
name: 'Coriolis.io',
|
||||
url: `https://coriolis.io/outfit/${ship.id}/${code}?bn=${encodeURIComponent(buildName)}`,
|
||||
url: `https://coriolis.edcd.io/outfit/${ship.id}/${code}?bn=${encodeURIComponent(buildName)}`,
|
||||
code,
|
||||
shipId: ship.id
|
||||
}],
|
||||
@@ -61,13 +91,13 @@ export function toDetailedBuild(buildName, ship) {
|
||||
standard: {
|
||||
bulkheads: BulkheadNames[ship.bulkheads.m.index],
|
||||
cargoHatch: { enabled: Boolean(ship.cargoHatch.enabled), priority: ship.cargoHatch.priority + 1 },
|
||||
powerPlant: { class: standard[0].m.class, rating: standard[0].m.rating, enabled: Boolean(standard[0].enabled), priority: standard[0].priority + 1 },
|
||||
thrusters: { class: standard[1].m.class, rating: standard[1].m.rating, enabled: Boolean(standard[1].enabled), priority: standard[1].priority + 1 },
|
||||
frameShiftDrive: { class: standard[2].m.class, rating: standard[2].m.rating, enabled: Boolean(standard[2].enabled), priority: standard[2].priority + 1 },
|
||||
lifeSupport: { class: standard[3].m.class, rating: standard[3].m.rating, enabled: Boolean(standard[3].enabled), priority: standard[3].priority + 1 },
|
||||
powerDistributor: { class: standard[4].m.class, rating: standard[4].m.rating, enabled: Boolean(standard[4].enabled), priority: standard[4].priority + 1 },
|
||||
sensors: { class: standard[5].m.class, rating: standard[5].m.rating, enabled: Boolean(standard[5].enabled), priority: standard[5].priority + 1 },
|
||||
fuelTank: { class: standard[6].m.class, rating: standard[6].m.rating, enabled: Boolean(standard[6].enabled), priority: standard[6].priority + 1 }
|
||||
powerPlant: standardToSchema(standard[0]),
|
||||
thrusters: standardToSchema(standard[1]),
|
||||
frameShiftDrive: standardToSchema(standard[2]),
|
||||
lifeSupport: standardToSchema(standard[3]),
|
||||
powerDistributor: standardToSchema(standard[4]),
|
||||
sensors: standardToSchema(standard[5]),
|
||||
fuelTank: standardToSchema(standard[6])
|
||||
},
|
||||
hardpoints: hardpoints.filter(slot => slot.maxClass > 0).map(slotToSchema),
|
||||
utility: hardpoints.filter(slot => slot.maxClass === 0).map(slotToSchema),
|
||||
@@ -86,7 +116,7 @@ export function toDetailedBuild(buildName, ship) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiates a ship from a ship-loadout object
|
||||
* Instantiates a ship from a ship-loadout object, using the code
|
||||
* @param {Object} detailedBuild ship-loadout object
|
||||
* @return {Ship} Ship instance
|
||||
*/
|
||||
@@ -97,6 +127,26 @@ export function fromDetailedBuild(detailedBuild) {
|
||||
throw 'No such ship: ' + detailedBuild.ship;
|
||||
}
|
||||
|
||||
let comps = detailedBuild.components;
|
||||
let stn = comps.standard;
|
||||
let shipData = Ships[shipId];
|
||||
let ship = new Ship(shipId, shipData.properties, shipData.slots);
|
||||
|
||||
return ship.buildFrom(detailedBuild.references[0].code);
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiates a ship from a ship-loadout object
|
||||
* @param {Object} detailedBuild ship-loadout object
|
||||
* @return {Ship} Ship instance
|
||||
*/
|
||||
export function oldfromDetailedBuild(detailedBuild) {
|
||||
let shipId = Object.keys(Ships).find((shipId) => Ships[shipId].properties.name.toLowerCase() == detailedBuild.ship.toLowerCase());
|
||||
|
||||
if (!shipId) {
|
||||
throw 'No such ship: ' + detailedBuild.ship;
|
||||
}
|
||||
|
||||
let comps = detailedBuild.components;
|
||||
let stn = comps.standard;
|
||||
let priorities = [stn.cargoHatch && stn.cargoHatch.priority !== undefined ? stn.cargoHatch.priority - 1 : 0];
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { ArmourMultiplier } from './Constants';
|
||||
import * as Calc from './Calculations';
|
||||
import * as ModuleUtils from './ModuleUtils';
|
||||
import Module from './Module';
|
||||
import LZString from 'lz-string';
|
||||
import isEqual from 'lodash/lang';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
const zlib = require('zlib');
|
||||
|
||||
const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs'];
|
||||
const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs', 'fh'];
|
||||
|
||||
/**
|
||||
* Returns the power usage type of a slot and it's particular modul
|
||||
* Returns the power usage type of a slot and it's particular module
|
||||
* @param {Object} slot The Slot
|
||||
* @param {Object} modul The modul in the slot
|
||||
* @param {Object} modul The module in the slot
|
||||
* @return {String} The key for the power usage type
|
||||
*/
|
||||
function powerUsageType(slot, modul) {
|
||||
@@ -114,7 +117,7 @@ export default class Ship {
|
||||
*/
|
||||
canThrust() {
|
||||
return this.getSlotStatus(this.standard[1]) == 3 && // Thrusters are powered
|
||||
this.ladenMass < this.standard[1].m.maxmass; // Max mass not exceeded
|
||||
this.ladenMass < this.standard[1].m.getMaxMass(); // Max mass not exceeded
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,9 +125,9 @@ export default class Ship {
|
||||
* @return {[type]} True if boost capable
|
||||
*/
|
||||
canBoost() {
|
||||
return this.canThrust() && // Thrusters operational
|
||||
this.getSlotStatus(this.standard[4]) == 3 && // Power distributor operational
|
||||
this.boostEnergy <= this.standard[4].m.enginecapacity; // PD capacitor is sufficient for boost
|
||||
return this.canThrust() && // Thrusters operational
|
||||
this.getSlotStatus(this.standard[4]) == 3 && // Power distributor operational
|
||||
this.boostEnergy <= this.standard[4].m.getEnginesCapacity(); // PD capacitor is sufficient for boost
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,7 +161,8 @@ export default class Ship {
|
||||
*/
|
||||
calcUnladenRange(massDelta, fuel, fsd) {
|
||||
fsd = fsd || this.standard[2].m;
|
||||
return Calc.jumpRange(this.unladenMass + (massDelta || 0) + Math.min(fsd.maxfuel, fuel || this.fuelCapacity), fsd || this.standard[2].m, fuel);
|
||||
let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
|
||||
return Calc.jumpRange(this.unladenMass + (massDelta || 0) + Math.min(fsdMaxFuelPerJump, fuel || this.fuelCapacity), fsd || this.standard[2].m, fuel);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,9 +193,11 @@ export default class Ship {
|
||||
* @return {Number} Recovery time in seconds
|
||||
*/
|
||||
calcShieldRecovery() {
|
||||
if (this.shieldStrength && this.sgSlot) {
|
||||
if (this.shield > 0) {
|
||||
let sgSlot = this.findInternalByGroup('sg');
|
||||
let brokenRegenRate = 1 + sgSlot.m.getModValue('brokenregen');
|
||||
// 50% of shield strength / recovery recharge rate + 15 second delay before recharge starts
|
||||
return ((this.shieldStrength / 2) / this.sgSlot.m.recover) + 15;
|
||||
return ((this.shield / 2) / (sgSlot.m.recover * brokenRegenRate)) + 15;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -203,9 +209,11 @@ export default class Ship {
|
||||
* @return {Number} 50 - 100% Recharge time in seconds
|
||||
*/
|
||||
calcShieldRecharge() {
|
||||
if (this.shieldStrength && this.sgSlot) {
|
||||
if (this.shield > 0) {
|
||||
let sgSlot = this.findInternalByGroup('sg');
|
||||
let regenRate = 1 + sgSlot.m.getModValue('regen');
|
||||
// 50% -> 100% recharge time, Bi-Weave shields charge at 1.8 MJ/s
|
||||
return (this.shieldStrength / 2) / (this.sgSlot.m.grp == 'bsg' ? 1.8 : 1);
|
||||
return (this.shield / 2) / ((sgSlot.m.grp == 'bsg' ? 1.8 : 1) * regenRate);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -214,16 +222,20 @@ export default class Ship {
|
||||
* Calculate the hypothetical shield strength for the ship using the specified parameters
|
||||
* @param {Object} sg [optional] Shield Generator to use
|
||||
* @param {Number} multiplierDelta [optional] Change to shield multiplier (+0.2, - 0.12, etc)
|
||||
* @return {Number} Shield strength in MH
|
||||
* @return {Number} Shield strength in MJ
|
||||
*/
|
||||
calcShieldStrengthWith(sg, multiplierDelta) {
|
||||
if (!sg) {
|
||||
if (!this.sgSlot) {
|
||||
let sgSlot = this.findInternalByGroup('sg');
|
||||
if (!sgSlot) {
|
||||
return 0;
|
||||
}
|
||||
sg = this.sgSlot.m;
|
||||
sg = sgSlot.m;
|
||||
}
|
||||
return Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sg, this.shieldMultiplier + (multiplierDelta || 0));
|
||||
|
||||
// TODO obtain shield boost
|
||||
// return Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sg, this.shieldMultiplier + (multiplierDelta || 0));
|
||||
return Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sg, 0 + (multiplierDelta || 0));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -284,7 +296,9 @@ export default class Ship {
|
||||
'.',
|
||||
this.getPowerEnabledString(),
|
||||
'.',
|
||||
this.getPowerPrioritesString()
|
||||
this.getPowerPrioritiesString(),
|
||||
'.',
|
||||
this.getModificationsString()
|
||||
].join('');
|
||||
}
|
||||
|
||||
@@ -324,6 +338,17 @@ export default class Ship {
|
||||
return this.serialized.hardpoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the modifications to a string
|
||||
* @return {String} Serialized modifications 'code'
|
||||
*/
|
||||
getModificationsString() {
|
||||
// Modifications can be updated outside of the ship's direct knowledge, for example when sliders change the value,
|
||||
// so always recreate it from scratch
|
||||
this.updateModificationsString();
|
||||
return this.serialized.modifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the serialized module active/inactive settings
|
||||
* @return {String} Serialized active/inactive settings
|
||||
@@ -336,7 +361,7 @@ export default class Ship {
|
||||
* Get the serialized module priority settings
|
||||
* @return {String} Serialized priority settings
|
||||
*/
|
||||
getPowerPrioritesString() {
|
||||
getPowerPrioritiesString() {
|
||||
return this.serialized.priorities;
|
||||
}
|
||||
|
||||
@@ -368,18 +393,68 @@ export default class Ship {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a modification value
|
||||
* @param {Object} m The module to change
|
||||
* @param {Object} name The name of the modification to change
|
||||
* @param {Number} value The new value of the modification
|
||||
*/
|
||||
setModification(m, name, value) {
|
||||
// Handle special cases
|
||||
if (name == 'pgen') {
|
||||
// Power generation
|
||||
m.setModValue(name, value);
|
||||
this.updatePowerGenerated();
|
||||
} else if (name == 'power') {
|
||||
// Power usage
|
||||
m.setModValue(name, value);
|
||||
this.updatePowerUsed();
|
||||
} else if (name == 'mass') {
|
||||
// Mass
|
||||
let oldMass = m.getMass();
|
||||
m.setModValue(name, value);
|
||||
let newMass = m.getMass();
|
||||
this.unladenMass = this.unladenMass - oldMass + newMass;
|
||||
this.ladenMass = this.ladenMass - oldMass + newMass;
|
||||
this.updateTopSpeed();
|
||||
this.updateJumpStats();
|
||||
} else if (name == 'maxfuel') {
|
||||
m.setModValue(name, value);
|
||||
this.updateJumpStats();
|
||||
} else if (name == 'optmass') {
|
||||
m.setModValue(name, value);
|
||||
// Could be for either thrusters or FSD
|
||||
this.updateTopSpeed();
|
||||
this.updateJumpStats();
|
||||
} else if (name == 'optmul') {
|
||||
m.setModValue(name, value);
|
||||
// Could be for either thrusters or FSD
|
||||
this.updateTopSpeed();
|
||||
this.updateJumpStats();
|
||||
} else if (name == 'shieldboost') {
|
||||
m.setModValue(name, value);
|
||||
this.updateShield();
|
||||
} else if (name == 'hullboost') {
|
||||
m.setModValue(name, value);
|
||||
this.updateArmour();
|
||||
} else {
|
||||
// Generic
|
||||
m.setModValue(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds/Updates the ship instance with the ModuleUtils[comps] passed in.
|
||||
* @param {Object} comps Collection of ModuleUtils used to build the ship
|
||||
* @param {array} priorities Slot priorities
|
||||
* @param {Array} enabled Slot active/inactive
|
||||
* @param {Array} mods Modifications
|
||||
* @return {this} The current ship instance for chaining
|
||||
*/
|
||||
buildWith(comps, priorities, enabled) {
|
||||
buildWith(comps, priorities, enabled, mods) {
|
||||
let internal = this.internal,
|
||||
standard = this.standard,
|
||||
hps = this.hardpoints,
|
||||
bands = this.priorityBands,
|
||||
cl = standard.length,
|
||||
i, l;
|
||||
|
||||
@@ -387,26 +462,26 @@ export default class Ship {
|
||||
this.fuelCapacity = 0;
|
||||
this.cargoCapacity = 0;
|
||||
this.ladenMass = 0;
|
||||
this.armourAdded = 0;
|
||||
this.armourMultiplier = 1;
|
||||
this.shieldMultiplier = 1;
|
||||
this.armour = this.baseArmour;
|
||||
this.shield = this.baseShieldStrength;
|
||||
this.totalCost = this.m.incCost ? this.m.discountedCost : 0;
|
||||
this.unladenMass = this.hullMass;
|
||||
this.totalDps = 0;
|
||||
this.totalEps = 0;
|
||||
this.totalHps = 0;
|
||||
this.shieldExplRes = 0;
|
||||
this.shieldKinRes = 0;
|
||||
this.shieldThermRes = 0;
|
||||
this.hullExplRes = 0;
|
||||
this.hullKinRes = 0;
|
||||
this.hullThermRes = 0;
|
||||
|
||||
this.bulkheads.m = null;
|
||||
this.useBulkhead(comps && comps.bulkheads ? comps.bulkheads : 0, true);
|
||||
this.bulkheads.m.mods = mods && mods[0] ? mods[0] : {};
|
||||
this.cargoHatch.priority = priorities ? priorities[0] * 1 : 0;
|
||||
this.cargoHatch.enabled = enabled ? enabled[0] * 1 : true;
|
||||
|
||||
for (i = 0, l = this.priorityBands.length; i < l; i++) {
|
||||
this.priorityBands[i].deployed = 0;
|
||||
this.priorityBands[i].retracted = 0;
|
||||
}
|
||||
|
||||
if (this.cargoHatch.enabled) {
|
||||
bands[this.cargoHatch.priority].retracted += this.cargoHatch.m.power;
|
||||
}
|
||||
this.cargoHatch.mods = mods ? mods[0] : {};
|
||||
|
||||
for (i = 0; i < cl; i++) {
|
||||
standard[i].cat = 0;
|
||||
@@ -415,9 +490,10 @@ export default class Ship {
|
||||
standard[i].type = 'SYS';
|
||||
standard[i].m = null; // Resetting 'old' modul if there was one
|
||||
standard[i].discountedCost = 0;
|
||||
|
||||
if (comps) {
|
||||
this.use(standard[i], ModuleUtils.standard(i, comps.standard[i]), true);
|
||||
let module = ModuleUtils.standard(i, comps.standard[i]);
|
||||
if (module != null) { module.mods = mods && mods[i + 1] ? mods[i + 1] : {}; }
|
||||
this.use(standard[i], module, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,7 +510,9 @@ export default class Ship {
|
||||
hps[i].discountedCost = 0;
|
||||
|
||||
if (comps && comps.hardpoints[i] !== 0) {
|
||||
this.use(hps[i], ModuleUtils.hardpoints(comps.hardpoints[i]), true);
|
||||
let module = ModuleUtils.hardpoints(comps.hardpoints[i]);
|
||||
if (module != null) { module.mods = mods && mods[cl + i] ? mods[cl + i] : {}; }
|
||||
this.use(hps[i], module, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,19 +527,23 @@ export default class Ship {
|
||||
internal[i].discountedCost = 0;
|
||||
|
||||
if (comps && comps.internal[i] !== 0) {
|
||||
this.use(internal[i], ModuleUtils.internal(comps.internal[i]), true);
|
||||
let module = ModuleUtils.internal(comps.internal[i]);
|
||||
if (module != null) { module.mods = mods && mods[cl + i] ? mods[cl + i] : {}; }
|
||||
this.use(internal[i], module, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Update aggragated stats
|
||||
if (comps) {
|
||||
this.updatePower()
|
||||
this.updatePowerGenerated()
|
||||
.updatePowerUsed()
|
||||
.updateJumpStats()
|
||||
.updateShieldStrength()
|
||||
.updateShield()
|
||||
.updateArmour()
|
||||
.updateTopSpeed();
|
||||
}
|
||||
|
||||
return this.updatePowerPrioritesString().updatePowerEnabledString();
|
||||
return this.updatePowerPrioritesString().updatePowerEnabledString().updateModificationsString();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -475,6 +557,7 @@ export default class Ship {
|
||||
let standard = new Array(this.standard.length),
|
||||
hardpoints = new Array(this.hardpoints.length),
|
||||
internal = new Array(this.internal.length),
|
||||
mods = new Array(1 + this.standard.length + this.hardpoints.length + this.internal.length),
|
||||
parts = serializedString.split('.'),
|
||||
priorities = null,
|
||||
enabled = null,
|
||||
@@ -488,6 +571,19 @@ export default class Ship {
|
||||
priorities = LZString.decompressFromBase64(parts[2].replace(/-/g, '/')).split('');
|
||||
}
|
||||
|
||||
if (parts[3]) {
|
||||
const modstr = parts[3].replace(/-/g, '/');
|
||||
if (modstr.match(':')) {
|
||||
this.decodeModificationsString(modstr, mods);
|
||||
} else {
|
||||
try {
|
||||
this.decodeModificationsStruct(zlib.gunzipSync(new Buffer(modstr, 'base64')), mods);
|
||||
} catch (err) {
|
||||
// Could be out-of-date URL; ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decodeToArray(code, internal, decodeToArray(code, hardpoints, decodeToArray(code, standard, 1)));
|
||||
|
||||
return this.buildWith(
|
||||
@@ -498,7 +594,8 @@ export default class Ship {
|
||||
internal
|
||||
},
|
||||
priorities,
|
||||
enabled
|
||||
enabled,
|
||||
mods
|
||||
);
|
||||
};
|
||||
|
||||
@@ -584,18 +681,20 @@ export default class Ship {
|
||||
if (slot.enabled != enabled) { // Enabled state is changing
|
||||
slot.enabled = enabled;
|
||||
if (slot.m) {
|
||||
this.priorityBands[slot.priority][powerUsageType(slot, slot.m)] += enabled ? slot.m.power : -slot.m.power;
|
||||
|
||||
if (ModuleUtils.isShieldGenerator(slot.m.grp)) {
|
||||
this.updateShieldStrength();
|
||||
} else if (slot.m.grp == 'sb') {
|
||||
this.shieldMultiplier += slot.m.shieldmul * (enabled ? 1 : -1);
|
||||
this.updateShieldStrength();
|
||||
} else if (slot.m.dps) {
|
||||
this.totalDps += slot.m.dps * (enabled ? 1 : -1);
|
||||
if (ModuleUtils.isShieldGenerator(slot.m.grp) || slot.m.grp == 'sb') {
|
||||
this.updateShield();
|
||||
}
|
||||
if (slot.m.getDps()) {
|
||||
this.totalDps += slot.m.getDps() * (enabled ? 1 : -1);
|
||||
}
|
||||
if (slot.m.getEps()) {
|
||||
this.totalEps += slot.m.getEps() * (enabled ? 1 : -1);
|
||||
}
|
||||
if (slot.m.getHps()) {
|
||||
this.totalHps += slot.m.getHps() * (enabled ? 1 : -1);
|
||||
}
|
||||
|
||||
this.updatePower();
|
||||
this.updatePowerUsed();
|
||||
this.updatePowerEnabledString();
|
||||
}
|
||||
}
|
||||
@@ -615,10 +714,7 @@ export default class Ship {
|
||||
this.updatePowerPrioritesString();
|
||||
|
||||
if (slot.enabled) { // Only update power if the slot is enabled
|
||||
let usage = powerUsageType(slot, slot.m);
|
||||
this.priorityBands[oldPriority][usage] -= slot.m.power;
|
||||
this.priorityBands[newPriority][usage] += slot.m.power;
|
||||
this.updatePower();
|
||||
this.updatePowerUsed();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -634,7 +730,12 @@ export default class Ship {
|
||||
* @return {this} The ship instance (for chaining operations)
|
||||
*/
|
||||
updateStats(slot, n, old, preventUpdate) {
|
||||
let powerChange = slot == this.standard[0];
|
||||
let powerGeneratedChange = slot == this.standard[0];
|
||||
let powerUsedChange = false;
|
||||
|
||||
let armourChange = (slot == this.bulkheads) || (n && n.grp == 'hr') || (old && old.grp == 'hr');
|
||||
|
||||
let shieldChange = (n && n.grp == 'bsg') || (old && old.grp == 'bsg') || (n && n.grp == 'psg') || (old && old.grp == 'psg') || (n && n.grp == 'sg') || (old && old.grp == 'sg') || (n && n.grp == 'sb') || (old && old.grp == 'sb');
|
||||
|
||||
if (old) { // Old modul now being removed
|
||||
switch (old.grp) {
|
||||
@@ -644,27 +745,27 @@ export default class Ship {
|
||||
case 'cr':
|
||||
this.cargoCapacity -= old.cargo;
|
||||
break;
|
||||
case 'hr':
|
||||
this.armourAdded -= old.armouradd;
|
||||
break;
|
||||
case 'sb':
|
||||
this.shieldMultiplier -= slot.enabled ? old.shieldmul : 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (slot.incCost && old.cost) {
|
||||
this.totalCost -= old.cost * this.moduleCostMultiplier;
|
||||
}
|
||||
|
||||
if (old.power && slot.enabled) {
|
||||
this.priorityBands[slot.priority][powerUsageType(slot, old)] -= old.power;
|
||||
powerChange = true;
|
||||
|
||||
if (old.dps) {
|
||||
this.totalDps -= old.dps;
|
||||
}
|
||||
if (old.getPowerUsage() > 0 && slot.enabled) {
|
||||
powerUsedChange = true;
|
||||
}
|
||||
this.unladenMass -= old.mass || 0;
|
||||
|
||||
if (old.getDps()) {
|
||||
this.totalDps -= old.getDps();
|
||||
}
|
||||
if (old.getEps()) {
|
||||
this.totalEps -= old.getEps();
|
||||
}
|
||||
if (old.getHps()) {
|
||||
this.totalHps -= old.getHps();
|
||||
}
|
||||
|
||||
this.unladenMass -= old.getMass() || 0;
|
||||
}
|
||||
|
||||
if (n) {
|
||||
@@ -675,12 +776,6 @@ export default class Ship {
|
||||
case 'cr':
|
||||
this.cargoCapacity += n.cargo;
|
||||
break;
|
||||
case 'hr':
|
||||
this.armourAdded += n.armouradd;
|
||||
break;
|
||||
case 'sb':
|
||||
this.shieldMultiplier += slot.enabled ? n.shieldmul : 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (slot.incCost && n.cost) {
|
||||
@@ -688,49 +783,105 @@ export default class Ship {
|
||||
}
|
||||
|
||||
if (n.power && slot.enabled) {
|
||||
this.priorityBands[slot.priority][powerUsageType(slot, n)] += n.power;
|
||||
powerChange = true;
|
||||
|
||||
if (n.dps) {
|
||||
this.totalDps += n.dps;
|
||||
}
|
||||
powerUsedChange = true;
|
||||
}
|
||||
this.unladenMass += n.mass || 0;
|
||||
|
||||
if (n.getDps()) {
|
||||
this.totalDps += n.getDps();
|
||||
}
|
||||
if (n.getEps()) {
|
||||
this.totalEps += n.getEps();
|
||||
}
|
||||
if (n.getHps()) {
|
||||
this.totalHps += n.getHps();
|
||||
}
|
||||
|
||||
this.unladenMass += n.getMass() || 0;
|
||||
}
|
||||
|
||||
this.ladenMass = this.unladenMass + this.cargoCapacity + this.fuelCapacity;
|
||||
this.armour = this.armourAdded + Math.round(this.baseArmour * this.armourMultiplier);
|
||||
|
||||
if (!preventUpdate) {
|
||||
if (powerChange) {
|
||||
this.updatePower();
|
||||
if (powerGeneratedChange) {
|
||||
this.updatePowerGenerated();
|
||||
}
|
||||
if (powerUsedChange) {
|
||||
this.updatePowerUsed();
|
||||
}
|
||||
if (armourChange) {
|
||||
this.updateArmour();
|
||||
}
|
||||
if (shieldChange) {
|
||||
this.updateShield();
|
||||
}
|
||||
this.updateTopSpeed();
|
||||
this.updateJumpStats();
|
||||
this.updateShieldStrength();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all power calculations
|
||||
* Update power calculations when amount generated changes
|
||||
* @return {this} The ship instance (for chaining operations)
|
||||
*/
|
||||
updatePower() {
|
||||
let bands = this.priorityBands;
|
||||
let prevRetracted = 0, prevDeployed = 0;
|
||||
updatePowerGenerated() {
|
||||
this.powerAvailable = this.standard[0].m.getPowerGeneration();
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update power calculations when amount consumed changes
|
||||
* @return {this} The ship instance (for chaining operations)
|
||||
*/
|
||||
updatePowerUsed() {
|
||||
let bands = [
|
||||
{ deployed: 0, retracted: 0, },
|
||||
{ deployed: 0, retracted: 0, },
|
||||
{ deployed: 0, retracted: 0, },
|
||||
{ deployed: 0, retracted: 0, },
|
||||
{ deployed: 0, retracted: 0, }
|
||||
];
|
||||
|
||||
if (this.cargoHatch.enabled) {
|
||||
bands[this.cargoHatch.priority].retracted += this.cargoHatch.m.getPowerUsage();
|
||||
}
|
||||
|
||||
for (let slotNum in this.standard) {
|
||||
const slot = this.standard[slotNum];
|
||||
if (slot.m && slot.enabled) {
|
||||
bands[slot.priority][powerUsageType(slot, slot.m)] += slot.m.getPowerUsage();
|
||||
}
|
||||
}
|
||||
|
||||
for (let slotNum in this.internal) {
|
||||
const slot = this.internal[slotNum];
|
||||
if (slot.m && slot.enabled) {
|
||||
bands[slot.priority][powerUsageType(slot, slot.m)] += slot.m.getPowerUsage();
|
||||
}
|
||||
}
|
||||
|
||||
for (let slotNum in this.hardpoints) {
|
||||
const slot = this.hardpoints[slotNum];
|
||||
if (slot.m && slot.enabled) {
|
||||
bands[slot.priority][powerUsageType(slot, slot.m)] += slot.m.getPowerUsage();
|
||||
}
|
||||
}
|
||||
|
||||
// Work out the running totals
|
||||
let prevRetracted = 0, prevDeployed = 0;
|
||||
for (let i = 0, l = bands.length; i < l; i++) {
|
||||
let band = bands[i];
|
||||
prevRetracted = band.retractedSum = prevRetracted + band.retracted;
|
||||
prevDeployed = band.deployedSum = prevDeployed + band.deployed + band.retracted;
|
||||
}
|
||||
|
||||
this.powerAvailable = this.standard[0].m.pGen;
|
||||
// Update global stats
|
||||
this.powerRetracted = prevRetracted;
|
||||
this.powerDeployed = prevDeployed;
|
||||
this.priorityBands = bands;
|
||||
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update top speed and boost
|
||||
@@ -744,12 +895,47 @@ export default class Ship {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Shield strength
|
||||
* Update shield
|
||||
* @return {this} The ship instance (for chaining operations)
|
||||
*/
|
||||
updateShieldStrength() {
|
||||
this.sgSlot = this.findInternalByGroup('sg'); // Find Shield Generator slot Index if any
|
||||
this.shieldStrength = this.sgSlot && this.sgSlot.enabled ? Calc.shieldStrength(this.hullMass, this.baseShieldStrength, this.sgSlot.m, this.shieldMultiplier) : 0;
|
||||
updateShield() {
|
||||
// Base shield from generator
|
||||
let baseShield = 0;
|
||||
let sgSlot = this.findInternalByGroup('sg');
|
||||
if (sgSlot && sgSlot.enabled) {
|
||||
baseShield = Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sgSlot.m, 1);
|
||||
}
|
||||
|
||||
let shield = baseShield;
|
||||
|
||||
// Shield from boosters
|
||||
for (let slot of this.hardpoints) {
|
||||
if (slot.m && slot.m.grp == 'sb') {
|
||||
shield += baseShield * slot.m.getShieldBoost();
|
||||
}
|
||||
}
|
||||
this.shield = shield;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update armour
|
||||
* @return {this} The ship instance (for chaining operations)
|
||||
*/
|
||||
updateArmour() {
|
||||
// Armour from bulkheads
|
||||
let bulkhead = this.bulkheads.m;
|
||||
let armour = this.baseArmour + (this.baseArmour * bulkhead.getHullBoost());
|
||||
|
||||
// Armour from HRPs
|
||||
for (let slot of this.internal) {
|
||||
if (slot.m && slot.m.grp == 'hr') {
|
||||
armour += slot.m.getHullReinforcement();
|
||||
// Hull boost for HRPs is applied against the ship's base armour
|
||||
armour += this.baseArmour * slot.m.getModValue('hullboost');
|
||||
}
|
||||
}
|
||||
this.armour = armour;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -759,7 +945,7 @@ export default class Ship {
|
||||
*/
|
||||
updateJumpStats() {
|
||||
let fsd = this.standard[2].m; // Frame Shift Drive;
|
||||
let { unladenMass, ladenMass, fuelCapacity } = this;
|
||||
let { unladenMass, fuelCapacity } = this;
|
||||
this.unladenRange = this.calcUnladenRange(); // Includes fuel weight for jump
|
||||
this.fullTankRange = Calc.jumpRange(unladenMass + fuelCapacity, fsd); // Full Tank
|
||||
this.ladenRange = this.calcLadenRange(); // Includes full tank and caro
|
||||
@@ -811,16 +997,206 @@ export default class Ship {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the modifications string
|
||||
* @return {this} The ship instance (for chaining operations)
|
||||
*/
|
||||
oldupdateModificationsString() {
|
||||
let allMods = new Array();
|
||||
|
||||
let bulkheadMods = new Array();
|
||||
if (this.bulkheads.m && this.bulkheads.m.mods) {
|
||||
for (let modKey in this.bulkheads.m.mods) {
|
||||
bulkheadMods.push(Modifications.modifiers.indexOf(modKey) + ':' + Math.round(this.bulkheads.m.getModValue(modKey) * 10000));
|
||||
}
|
||||
}
|
||||
allMods.push(bulkheadMods.join(';'));
|
||||
|
||||
for (let slot of this.standard) {
|
||||
let slotMods = new Array();
|
||||
if (slot.m && slot.m.mods) {
|
||||
for (let modKey in slot.m.mods) {
|
||||
slotMods.push(Modifications.modifiers.indexOf(modKey) + ':' + Math.round(slot.m.getModValue(modKey) * 10000));
|
||||
}
|
||||
}
|
||||
allMods.push(slotMods.join(';'));
|
||||
}
|
||||
for (let slot of this.hardpoints) {
|
||||
let slotMods = new Array();
|
||||
if (slot.m && slot.m.mods) {
|
||||
for (let modKey in slot.m.mods) {
|
||||
slotMods.push(Modifications.modifiers.indexOf(modKey) + ':' + Math.round(slot.m.getModValue(modKey) * 10000));
|
||||
}
|
||||
}
|
||||
allMods.push(slotMods.join(';'));
|
||||
}
|
||||
for (let slot of this.internal) {
|
||||
let slotMods = new Array();
|
||||
if (slot.m && slot.m.mods) {
|
||||
for (let modKey in slot.m.mods) {
|
||||
slotMods.push(Modifications.modifiers.indexOf(modKey) + ':' + Math.round(slot.m.getModValue(modKey) * 10000));
|
||||
}
|
||||
}
|
||||
allMods.push(slotMods.join(';'));
|
||||
}
|
||||
this.serialized.modifications = LZString.compressToBase64(allMods.join(',').replace(/,+$/, '')).replace(/\//g, '-');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the modifications array with modification values from the code
|
||||
* @param {String} code Serialized modification code
|
||||
* @param {Array} arr Modification array
|
||||
*/
|
||||
decodeModificationsString(code, arr) {
|
||||
let moduleMods = code.split(',');
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
arr[i] = {};
|
||||
if (moduleMods.length > i && moduleMods[i] != '') {
|
||||
let mods = moduleMods[i].split(';');
|
||||
for (let j = 0; j < mods.length; j++) {
|
||||
let modElements = mods[j].split(':');
|
||||
if (modElements[0].match('[0-9]+')) {
|
||||
arr[i][Modifications.modifiers[modElements[0]]] = Number(modElements[1]);
|
||||
} else {
|
||||
arr[i][modElements[0]] = Number(modElements[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the modifications string.
|
||||
* This is a binary structure. It starts with a byte that identifies a slot, with bulkheads being ID 0 and moving through
|
||||
* standard modules, hardpoints, and finally internal modules. It then contains one or more modifications, with each
|
||||
* modification being a one-byte modification ID and at two-byte modification value. Modification IDs are based on the array
|
||||
* in Modifications.modifiers. The list of modifications is terminated by a modification ID of -1. The structure then repeats
|
||||
* for the next module, and the next, and is terminated by a slot ID of -1.
|
||||
* @return {this} The ship instance (for chaining operations)
|
||||
*/
|
||||
updateModificationsString() {
|
||||
// Start off by gathering the information that we need
|
||||
let slots = new Array();
|
||||
|
||||
let bulkheadMods = new Array();
|
||||
if (this.bulkheads.m && this.bulkheads.m.mods) {
|
||||
for (let modKey in this.bulkheads.m.mods) {
|
||||
bulkheadMods.push({ id: Modifications.modifiers.indexOf(modKey), value: Math.round(this.bulkheads.m.getModValue(modKey) * 10000) });
|
||||
}
|
||||
}
|
||||
slots.push(bulkheadMods);
|
||||
|
||||
for (let slot of this.standard) {
|
||||
let slotMods = new Array();
|
||||
if (slot.m && slot.m.mods) {
|
||||
for (let modKey in slot.m.mods) {
|
||||
slotMods.push({ id: Modifications.modifiers.indexOf(modKey), value: Math.round(slot.m.getModValue(modKey) * 10000) });
|
||||
}
|
||||
}
|
||||
slots.push(slotMods);
|
||||
}
|
||||
|
||||
for (let slot of this.hardpoints) {
|
||||
let slotMods = new Array();
|
||||
if (slot.m && slot.m.mods) {
|
||||
for (let modKey in slot.m.mods) {
|
||||
slotMods.push({ id: Modifications.modifiers.indexOf(modKey), value: Math.round(slot.m.getModValue(modKey) * 10000) });
|
||||
}
|
||||
}
|
||||
slots.push(slotMods);
|
||||
}
|
||||
|
||||
for (let slot of this.internal) {
|
||||
let slotMods = new Array();
|
||||
if (slot.m && slot.m.mods) {
|
||||
for (let modKey in slot.m.mods) {
|
||||
slotMods.push({ id: Modifications.modifiers.indexOf(modKey), value: Math.round(slot.m.getModValue(modKey) * 10000) });
|
||||
}
|
||||
}
|
||||
slots.push(slotMods);
|
||||
}
|
||||
|
||||
// Now work out the size of the binary buffer from our modifications array
|
||||
let bufsize = 0;
|
||||
for (let slot of slots) {
|
||||
if (slot.length > 0) {
|
||||
bufsize = bufsize + 1 + (5 * slot.length) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (bufsize > 0) {
|
||||
bufsize = bufsize + 1; // For end marker
|
||||
// Now create and populate the buffer
|
||||
let buffer = Buffer.alloc(bufsize);
|
||||
let curpos = 0;
|
||||
let i = 0;
|
||||
for (let slot of slots) {
|
||||
if (slot.length > 0) {
|
||||
buffer.writeInt8(i, curpos++);
|
||||
for (let slotMod of slot) {
|
||||
buffer.writeInt8(slotMod.id, curpos++);
|
||||
buffer.writeInt32LE(slotMod.value, curpos);
|
||||
curpos += 4;
|
||||
}
|
||||
buffer.writeInt8(-1, curpos++);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (curpos > 0) {
|
||||
buffer.writeInt8(-1, curpos++);
|
||||
}
|
||||
|
||||
this.serialized.modifications = zlib.gzipSync(buffer).toString('base64').replace(/\//g, '-');
|
||||
} else {
|
||||
this.serialized.modifications = null;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the modifications array with modification values from the code.
|
||||
* See updateModificationsString() for details of the structure.
|
||||
* @param {String} buffer Buffer holding modification info
|
||||
* @param {Array} arr Modification array
|
||||
*/
|
||||
decodeModificationsStruct(buffer, arr) {
|
||||
let curpos = 0;
|
||||
let slot = buffer.readInt8(curpos++);
|
||||
while (slot != -1) {
|
||||
let modifications = {};
|
||||
let modificationId = buffer.readInt8(curpos++);
|
||||
while (modificationId != -1) {
|
||||
let modificationValue = buffer.readInt32LE(curpos);
|
||||
curpos += 4;
|
||||
modifications[Modifications.modifiers[modificationId]] = modificationValue;
|
||||
modificationId = buffer.readInt8(curpos++);
|
||||
}
|
||||
arr[slot] = modifications;
|
||||
slot = buffer.readInt8(curpos++);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a slot with a the modul if the id is different from the current id for this slot.
|
||||
* Has logic handling ModuleUtils that you may only have 1 of (Shield Generator or Refinery).
|
||||
*
|
||||
* @param {Object} slot The modul slot
|
||||
* @param {Object} m Properties for the selected module
|
||||
* @param {Object} mdef Properties for the selected modul
|
||||
* @param {boolean} preventUpdate If true, do not update aggregated stats
|
||||
* @return {this} The ship instance (for chaining operations)
|
||||
*/
|
||||
use(slot, m, preventUpdate) {
|
||||
use(slot, mdef, preventUpdate) {
|
||||
// See if the module passed in is really a module or just a definition, and fix it accordingly so that we have a module instance
|
||||
let m;
|
||||
if (mdef == null) {
|
||||
m = null;
|
||||
} else if (mdef instanceof Module) {
|
||||
m = mdef;
|
||||
} else {
|
||||
m = new Module({ grp: mdef.grp, id: mdef.id });
|
||||
}
|
||||
|
||||
if (slot.m != m) { // Selecting a different modul
|
||||
// Slot is an internal slot, is not being emptied, and the selected modul group/type must be of unique
|
||||
if (slot.cat == 2 && m && UNIQUE_MODULES.indexOf(m.grp) != -1) {
|
||||
@@ -843,6 +1219,7 @@ export default class Ship {
|
||||
case 1: this.serialized.hardpoints = null; break;
|
||||
case 2: this.serialized.internal = null;
|
||||
}
|
||||
this.serialized.modifications = null;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -857,7 +1234,6 @@ export default class Ship {
|
||||
let oldBulkhead = this.bulkheads.m;
|
||||
this.bulkheads.m = this.availCS.getBulkhead(index);
|
||||
this.bulkheads.discountedCost = this.bulkheads.m.cost * this.moduleCostMultiplier;
|
||||
this.armourMultiplier = ArmourMultiplier[index];
|
||||
this.updateStats(this.bulkheads, this.bulkheads.m, oldBulkhead, preventUpdate);
|
||||
this.serialized.standard = null;
|
||||
return this;
|
||||
@@ -907,14 +1283,14 @@ export default class Ship {
|
||||
updated = false;
|
||||
// Find lightest Thruster that still works for the ship at max mass
|
||||
let th = m.th ? ModuleUtils.standard(1, m.th) : this.availCS.lightestThruster(this.ladenMass);
|
||||
if (th !== standard[1].m) {
|
||||
if (!isEqual.isEqual(th, standard[1].m)) {
|
||||
this.use(standard[1], th);
|
||||
updated = true;
|
||||
}
|
||||
// Find lightest Power plant that can power the ship
|
||||
let pp = m.pp ? ModuleUtils.standard(0, m.pp) : this.availCS.lightestPowerPlant(Math.max(this.powerRetracted, this.powerDeployed), m.ppRating);
|
||||
|
||||
if (pp !== standard[0].m) {
|
||||
if (!isEqual.isEqual(pp, standard[0].m)) {
|
||||
this.use(standard[0], pp);
|
||||
updated = true;
|
||||
}
|
||||
@@ -965,5 +1341,4 @@ export default class Ship {
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export function multiPurpose(ship, shielded, bulkheadIndex) {
|
||||
|
||||
if (shielded) {
|
||||
ship.internal.some(function(slot) {
|
||||
if (canMount(slot, 'sg')) { // Assuming largest slot can hold an eligible shield
|
||||
if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield
|
||||
ship.use(slot, ModuleUtils.findInternal('sg', slot.maxClass, 'A'));
|
||||
ship.setSlotEnabled(slot, true);
|
||||
return true;
|
||||
@@ -35,7 +35,7 @@ export function trader(ship, shielded, standardOpts) {
|
||||
|
||||
for (let i = ship.internal.length; i--;) {
|
||||
let slot = ship.internal[i];
|
||||
if (sg && canMount(slot, 'sg', sg.class)) {
|
||||
if (sg && canMount(ship, slot, 'sg', sg.class)) {
|
||||
ship.use(slot, sg);
|
||||
sg = null;
|
||||
} else {
|
||||
@@ -77,23 +77,23 @@ export function explorer(ship, planetary) {
|
||||
let slot = ship.internal[i];
|
||||
let nextSlot = (i + 1) < intLength ? ship.internal[i + 1] : null;
|
||||
// Fit best possible Fuel Scoop
|
||||
if (!fuelScoopSlot && canMount(slot, 'fs')) {
|
||||
if (!fuelScoopSlot && canMount(ship, slot, 'fs')) {
|
||||
fuelScoopSlot = slot;
|
||||
ship.use(slot, ModuleUtils.findInternal('fs', slot.maxClass, 'A'));
|
||||
ship.setSlotEnabled(slot, true);
|
||||
// Mount a Shield generator if possible AND an AFM Unit has been mounted already (Guarantees at least 1 AFM Unit)
|
||||
} else if (!sgSlot && shieldNext && canMount(slot, 'sg', sg.class) && !canMount(nextSlot, 'sg', sg.class)) {
|
||||
} else if (!sgSlot && shieldNext && canMount(ship, slot, 'sg', sg.class) && !canMount(ship, nextSlot, 'sg', sg.class)) {
|
||||
sgSlot = slot;
|
||||
shieldNext = false;
|
||||
ship.use(slot, sg);
|
||||
ship.setSlotEnabled(slot, true);
|
||||
// if planetary explorer and the next slot cannot mount a PVH or the next modul to mount is a SG
|
||||
} else if (planetary && !pvhSlot && canMount(slot, 'pv') && (shieldNext || !canMount(nextSlot, 'pv', 2))) {
|
||||
} else if (planetary && !pvhSlot && canMount(ship, slot, 'pv') && (shieldNext || !canMount(ship, nextSlot, 'pv', 2))) {
|
||||
pvhSlot = slot;
|
||||
ship.use(slot, ModuleUtils.findInternal('pv', Math.min(Math.floor(pvhSlot.maxClass / 2) * 2, 6), 'G'));
|
||||
ship.setSlotEnabled(slot, false); // Disabled power for PVH
|
||||
shieldNext = !sgSlot;
|
||||
} else if (afmUnitCount > 0 && canMount(slot, 'am')) {
|
||||
} else if (afmUnitCount > 0 && canMount(ship, slot, 'am')) {
|
||||
afmUnitCount--;
|
||||
ship.use(slot, ModuleUtils.findInternal('am', slot.maxClass, 'A'));
|
||||
ship.setSlotEnabled(slot, false); // Disabled power for AFM Unit
|
||||
@@ -115,7 +115,7 @@ export function explorer(ship, planetary) {
|
||||
|
||||
if (sgSlot) {
|
||||
// The SG and Fuel scoop to not need to be powered at the same time
|
||||
if (sgSlot.m.power > fuelScoopSlot.m.power) { // The Shield generator uses the most power
|
||||
if (sgSlot.m.getPowerUsage() > fuelScoopSlot.m.getPowerUsage()) { // The Shield generator uses the most power
|
||||
ship.setSlotEnabled(fuelScoopSlot, false);
|
||||
} else { // The Fuel scoop uses the most power
|
||||
ship.setSlotEnabled(sgSlot, false);
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import { isShieldGenerator } from '../shipyard/ModuleUtils';
|
||||
import Module from '../shipyard/Module';
|
||||
import { Infinite } from '../components/SvgIcons';
|
||||
import Persist from '../stores/Persist';
|
||||
|
||||
/**
|
||||
* Determine if a slot can mount a module of a particular class and group
|
||||
* Determine if a slot on a ship can mount a module of a particular class and group
|
||||
* @param {Object} ship Ship object
|
||||
* @param {Object} slot Slot object
|
||||
* @param {String} group Module group/type abbrivation/code
|
||||
* @param {Integer} clazz [Optional] Module Class/Size
|
||||
* @return {Boolean} True if the slot can mount the module
|
||||
*/
|
||||
export function canMount(slot, group, clazz) {
|
||||
if (slot && (!slot.eligible || slot.eligible[group]) && (clazz === undefined || slot.maxClass >= clazz)) {
|
||||
export function canMount(ship, slot, group, clazz) {
|
||||
if (slot &&
|
||||
(!slot.eligible || slot.eligible[group]) &&
|
||||
(group != 'pcq' || (ship.luxuryCabins && ship.luxuryCabins === true)) &&
|
||||
(group != 'fh' || (ship.fighterHangars && ship.fighterHangars === true)) &&
|
||||
(clazz === undefined || slot.maxClass >= clazz)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -112,30 +118,30 @@ const PROP_BLACKLIST = {
|
||||
ssdam: 1,
|
||||
mjdps: 1,
|
||||
mjeps: 1,
|
||||
M: 1,
|
||||
P: 1,
|
||||
mass: 1,
|
||||
cost: 1,
|
||||
recover: 1,
|
||||
weaponcapacity: 1,
|
||||
weaponrecharge: 1,
|
||||
enginecapacity: 1,
|
||||
enginerecharge: 1,
|
||||
systemcapacity: 1,
|
||||
systemrecharge: 1
|
||||
wepcap: 1,
|
||||
weprate: 1,
|
||||
engcap: 1,
|
||||
engrate: 1,
|
||||
syscap: 1,
|
||||
sysrate: 1,
|
||||
breachdps: 1,
|
||||
breachmin: 1,
|
||||
breachmax: 1,
|
||||
integrity: 1
|
||||
};
|
||||
|
||||
const TERM_LOOKUP = {
|
||||
pGen: 'power',
|
||||
pgen: 'power',
|
||||
armouradd: 'armour',
|
||||
shieldmul: 'multiplier',
|
||||
rof: 'ROF',
|
||||
dps: 'DPS'
|
||||
};
|
||||
|
||||
const FORMAT_LOOKUP = {
|
||||
time: 'time',
|
||||
shieldmul: 'rPct'
|
||||
time: 'time'
|
||||
};
|
||||
|
||||
const UNIT_LOOKUP = {
|
||||
@@ -146,7 +152,7 @@ const UNIT_LOOKUP = {
|
||||
recharge: 'MJ',
|
||||
rangeLS: 'Ls',
|
||||
power: 'MJ',
|
||||
pGen: 'MJ',
|
||||
pgen: 'MJ',
|
||||
rof: 'ps'
|
||||
};
|
||||
|
||||
@@ -175,7 +181,7 @@ function diffClass(a, b, negative) {
|
||||
*/
|
||||
function diff(format, mVal, mmVal) {
|
||||
if (mVal == Infinity) {
|
||||
return <Infinite/>;
|
||||
return '∞';
|
||||
} else {
|
||||
let diff = mVal - mmVal;
|
||||
if (!diff || mVal === undefined || diff == mVal || Math.abs(diff) == Infinity) {
|
||||
@@ -197,42 +203,54 @@ function diff(format, mVal, mmVal) {
|
||||
* @return {React.Component} Component to be rendered
|
||||
*/
|
||||
export function diffDetails(language, m, mm) {
|
||||
mm = mm || {};
|
||||
let { formats, translate, units } = language;
|
||||
let propDiffs = [];
|
||||
|
||||
let mCost = m.cost || 0;
|
||||
let mmCost = mm ? mm.cost : 0;
|
||||
if (mCost != mmCost) propDiffs.push(<div key='cost'>{translate('cost')}: <span className={diffClass(mCost, mmCost, true) }>{mCost ? Math.round(mCost * (1 - Persist.getModuleDiscount())) : 0}{units.CR}</span></div>);
|
||||
|
||||
let mMass = m.mass || 0;
|
||||
let mmMass = mm.mass || 0;
|
||||
let massDiff = mMass - mmMass;
|
||||
let capDiff = (m.fuel || m.cargo || 0) - (mm.fuel || mm.cargo || 0);
|
||||
let mmMass = mm ? mm.getMass() : 0;
|
||||
if (mMass != mmMass) propDiffs.push(<div key='mass'>{translate('mass')}: <span className={diffClass(mMass, mmMass, true)}>{diff(formats.round, mMass, mmMass)}{units.T}</span></div>);
|
||||
|
||||
let mPowerUsage = m.power || 0;
|
||||
let mmPowerUsage = mm ? mm.getPowerUsage() : 0;
|
||||
if (mPowerUsage != mmPowerUsage) propDiffs.push(<div key='power'>{translate('power')}: <span className={diffClass(mPowerUsage, mmPowerUsage, true)}>{diff(formats.round, mPowerUsage, mmPowerUsage)}{units.MJ}</span></div>);
|
||||
|
||||
// for (let p in m) {
|
||||
// if (!PROP_BLACKLIST[p] && !isNaN(m[p])) {
|
||||
// let mVal = m[p] === null ? Infinity : m[p];
|
||||
// let mmVal = mm[p] === null ? Infinity : mm[p];
|
||||
// let format = formats[FORMAT_LOOKUP[p]] || formats.round;
|
||||
// propDiffs.push(<div key={p}>
|
||||
// {`${translate(TERM_LOOKUP[p] || p)}: `}
|
||||
// <span className={diffClass(mVal, mmVal, p == 'power')}>{diff(format, mVal, mmVal)}{units[UNIT_LOOKUP[p]]}</span>
|
||||
// </div>);
|
||||
// }
|
||||
// }
|
||||
|
||||
let mDps = m.damage * (m.rpshot || 1) * m.rof || 0;
|
||||
let mmDps = mm ? mm.getDps() || 0 : 0;
|
||||
if (mDps != mmDps) propDiffs.push(<div key='dps'>{translate('dps')}: <span className={diffClass(mmDps, mDps, true)}>{diff(formats.round, mDps, mmDps)}</span></div>);
|
||||
|
||||
let mAffectsShield = isShieldGenerator(m.grp) || m.grp == 'sb';
|
||||
let mmAffectsShield = isShieldGenerator(mm.grp) || mm.grp == 'sb';
|
||||
|
||||
propDiffs.push(<div key='cost'>{translate('cost')}: <span className={diffClass(m.cost, mm.cost, true) }>{m.cost ? Math.round(m.cost * (1 - Persist.getModuleDiscount())) : 0}{units.CR}</span></div>);
|
||||
propDiffs.push(<div key='mass'>{translate('mass')}: <span className={diffClass(mMass, mm.mass, true)}>{diff(formats.round, mMass, mmMass)}{units.T}</span></div>);
|
||||
|
||||
for (let p in m) {
|
||||
if (!PROP_BLACKLIST[p] && !isNaN(m[p])) {
|
||||
let mVal = m[p] === null ? Infinity : m[p];
|
||||
let mmVal = mm[p] === null ? Infinity : mm[p];
|
||||
let format = formats[FORMAT_LOOKUP[p]] || formats.round;
|
||||
propDiffs.push(<div key={p}>
|
||||
{`${translate(TERM_LOOKUP[p] || p)}: `}
|
||||
<span className={diffClass(mVal, mmVal, p == 'power')}>{diff(format, mVal, mmVal)}{units[UNIT_LOOKUP[p]]}</span>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
let mmAffectsShield = isShieldGenerator(mm ? mm.grp : null) || mm && mm.grp == 'sb';
|
||||
if (mAffectsShield || mmAffectsShield) {
|
||||
let shield = this.calcShieldStrengthWith(); // Get shield strength regardless of slot active / inactive
|
||||
let newShield = 0;
|
||||
|
||||
if (mAffectsShield) {
|
||||
if (m.grp == 'sb') { // Both m and mm must be utility modules if this is true
|
||||
newShield = this.calcShieldStrengthWith(null, m.shieldmul - (mm.shieldmul || 0));
|
||||
newShield = this.calcShieldStrengthWith(null, m.shieldboost - (mm ? mm.getShieldBoost() || 0 : 0));
|
||||
} else {
|
||||
newShield = this.calcShieldStrengthWith(m);
|
||||
}
|
||||
} else {
|
||||
// Old module must be a shield booster
|
||||
newShield = this.calcShieldStrengthWith(null, -mm.getShieldBoost());
|
||||
}
|
||||
|
||||
let sgDiffClass = Math.round((newShield - shield) * 100) / 100 == 0 ? 'muted' : (newShield > shield ? 'secondary' : 'warning');
|
||||
|
||||
propDiffs.push(<div key='shields'>{translate('shields')}: <span className={sgDiffClass}>{diff(formats.int, newShield, shield)}{units.MJ}</span></div>);
|
||||
@@ -241,24 +259,28 @@ export function diffDetails(language, m, mm) {
|
||||
if (m.grp == 'pd') {
|
||||
propDiffs.push(<div key='wep'>
|
||||
{`${translate('WEP')}: `}
|
||||
<span className={diffClass(m.weaponcapacity, mm.weaponcapacity)}>{m.weaponcapacity}{units.MJ}</span>
|
||||
<span className={diffClass(m.wepcap, mm.getWeaponsCapacity())}>{m.wepcap}{units.MJ}</span>
|
||||
{' / '}
|
||||
<span className={diffClass(m.weaponrecharge, mm.weaponrecharge)}>{m.weaponrecharge}{units.MW}</span>
|
||||
<span className={diffClass(m.weprate, mm.getWeaponsRechargeRate())}>{m.weprate}{units.MW}</span>
|
||||
</div>);
|
||||
propDiffs.push(<div key='sys'>
|
||||
{`${translate('SYS')}: `}
|
||||
<span className={diffClass(m.systemcapacity, mm.systemcapacity)}>{m.systemcapacity}{units.MJ}</span>
|
||||
<span className={diffClass(m.syscap, mm.getSystemsCapacity())}>{m.syscap}{units.MJ}</span>
|
||||
{' / '}
|
||||
<span className={diffClass(m.systemrecharge, mm.systemrecharge)}>{m.systemrecharge}{units.MW}</span>
|
||||
<span className={diffClass(m.sysrate, mm.getSystemsRechargeRate())}>{m.sysrate}{units.MW}</span>
|
||||
</div>);
|
||||
propDiffs.push(<div key='eng'>
|
||||
{`${translate('ENG')}: `}
|
||||
<span className={diffClass(m.enginecapacity, mm.enginecapacity)}>{m.enginecapacity}{units.MJ}</span>
|
||||
<span className={diffClass(m.engcap, mm.getEnginesCapacity())}>{m.engcap}{units.MJ}</span>
|
||||
{' / '}
|
||||
<span className={diffClass(m.enginerecharge, mm.enginerecharge)}>{m.enginerecharge}{units.MW}</span>
|
||||
<span className={diffClass(m.engrate, mm.getEnginesRechargeRate())}>{m.engrate}{units.MW}</span>
|
||||
</div>);
|
||||
}
|
||||
|
||||
let massDiff = mMass - mmMass;
|
||||
let mCap = m.fuel || m.cargo || 0;
|
||||
let mmCap = mm ? mm.fuel || mm.cargo || 0 : 0;
|
||||
let capDiff = mCap - mmCap;
|
||||
if (m.grp == 'fsd' || massDiff || capDiff) {
|
||||
let fsd = m.grp == 'fsd' ? m : null;
|
||||
let maxRange = this.calcUnladenRange(massDiff, m.fuel, fsd);
|
||||
@@ -272,5 +294,5 @@ export function diffDetails(language, m, mm) {
|
||||
}
|
||||
}
|
||||
|
||||
return <div className='cap' style={{ whiteSpace: 'nowrap' }}>{propDiffs}</div>;
|
||||
return propDiffs ? <div className='cap' style={{ whiteSpace: 'nowrap' }}>{propDiffs}</div> : null;
|
||||
}
|
||||
|
||||
@@ -163,4 +163,4 @@ footer {
|
||||
float: right;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
background-color: @primary-bg;
|
||||
border: 1px solid @primary-disabled;
|
||||
color: @fg;
|
||||
stroke: @fg;
|
||||
stroke-width: 20;
|
||||
fill: @fg;
|
||||
|
||||
.details-container {
|
||||
@@ -54,6 +56,8 @@
|
||||
font-size: 1.2em;
|
||||
width: 1.2em;
|
||||
color: @primary-disabled;
|
||||
stroke: @primary-disabled;
|
||||
stroke-width: 20;
|
||||
border-right: 1px solid @primary-disabled;
|
||||
box-sizing: border-box;
|
||||
padding-top: 0.2em;
|
||||
@@ -64,6 +68,8 @@
|
||||
text-transform: uppercase;
|
||||
font-size: 1.3em;
|
||||
color: lighten(@primary-bg, 12%);
|
||||
stroke: lighten(@primary-bg, 12%);
|
||||
stroke-width: 20;
|
||||
text-align: center;
|
||||
letter-spacing: 0.1em;
|
||||
line-height: 1.7em;
|
||||
@@ -71,12 +77,16 @@
|
||||
|
||||
&.selected {
|
||||
color: @primary-bg;
|
||||
stroke: @primary-bg;
|
||||
stroke-width: 20;
|
||||
fill: @primary-bg;
|
||||
background-color: @primary;
|
||||
border: 1px solid @primary;
|
||||
z-index: 1;
|
||||
.sz {
|
||||
color: @primary;
|
||||
stroke: @primary;
|
||||
stroke-width: 20;
|
||||
background-color: @primary-bg;
|
||||
border-right: 1px solid @primary;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,11 @@
|
||||
"class": { "type": "integer", "minimum": 2, "maximum": 8 },
|
||||
"rating": { "$ref": "#/definitions/standardRatings" },
|
||||
"enabled": { "type": "boolean" },
|
||||
"priority": { "type": "integer", "minimum": 1, "maximum": 5 }
|
||||
"priority": { "type": "integer", "minimum": 1, "maximum": 5 },
|
||||
"name": {
|
||||
"description": "The name identifing the thrusters (if applicable), e.g. 'Enhanced Performance'",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"frameShiftDrive": {
|
||||
|
||||
Reference in New Issue
Block a user