Rework modules to be individual objects rather than references to templates

This commit is contained in:
Cmdr McDonald
2016-10-22 09:47:43 +01:00
parent f29f3f4f8f
commit 2c45011664
16 changed files with 397 additions and 51 deletions

View File

@@ -32,6 +32,7 @@
], ],
"automock": true, "automock": true,
"unmockedModulePathPatterns": [ "unmockedModulePathPatterns": [
"<rootDir>/node_modules/lodash",
"<rootDir>/node_modules/react", "<rootDir>/node_modules/react",
"<rootDir>/node_modules/react-dom", "<rootDir>/node_modules/react-dom",
"<rootDir>/node_modules/react-addons-test-utils", "<rootDir>/node_modules/react-addons-test-utils",
@@ -86,6 +87,7 @@
"coriolis-data": "EDCD/coriolis-data", "coriolis-data": "EDCD/coriolis-data",
"d3": "3.5.16", "d3": "3.5.16",
"fbemitter": "^2.0.0", "fbemitter": "^2.0.0",
"lodash": "^4.15.0",
"lz-string": "^1.4.4", "lz-string": "^1.4.4",
"react": "^15.0.1", "react": "^15.0.1",
"react-dom": "^15.0.1", "react-dom": "^15.0.1",

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import Slot from './Slot'; import Slot from './Slot';
import { DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret } from './SvgIcons'; import { DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret, Modifications } from './SvgIcons';
/** /**
* Hardpoint / Utility Slot * Hardpoint / Utility Slot
@@ -48,17 +48,18 @@ export default class HardpointSlot extends Slot {
{m.type && m.type == 'KT' ? <span><DamageKinetic /><DamageThermal /></span> : ''} {m.type && m.type == 'KT' ? <span><DamageKinetic /><DamageThermal /></span> : ''}
{m.type && m.type == 'E' ? <DamageExplosive /> : ''} {m.type && m.type == 'E' ? <DamageExplosive /> : ''}
{classRating} {translate(m.name || m.grp)}</div> {classRating} {translate(m.name || m.grp)}</div>
<div className={'r'}>{m.mass}{u.T}</div> <div className={'r'}>{m.getMass()}{u.T}</div>
</div> </div>
<div className={'cb'}> <div className={'cb'}>
{ m.dps ? <div className={'l'}>{translate('DPS')}: {formats.round1(m.dps)} { m.clip ? <span>({formats.round1((m.clip * m.dps / m.rof) / ((m.clip / m.rof) + m.reload)) })</span> : null }</div> : null } { m.dps ? <div className={'l'}>{translate('DPS')}: {formats.round1(m.dps)} { m.clip ? <span>({formats.round1((m.clip * m.dps / m.rof) / ((m.clip / m.rof) + m.reload)) })</span> : null }</div> : null }
{ m.eps ? <div className={'l'}>{translate('EPS')}: {formats.round1(m.eps)} { m.clip ? <span>({formats.round1((m.clip * m.eps / m.rof) / ((m.clip / m.rof) + m.reload)) })</span> : null }</div> : null } { m.eps ? <div className={'l'}>{translate('EPS')}: {formats.round1(m.eps)}{u.MW} { m.clip ? <span>({formats.round1((m.clip * m.eps / m.rof) / ((m.clip / m.rof) + m.reload)) }{u.MW})</span> : null }</div> : null }
{ m.hps ? <div className={'l'}>{translate('HPS')}: {formats.round1(m.hps)} { m.clip ? <span>({formats.round1((m.clip * m.hps / m.rof) / ((m.clip / m.rof) + m.reload)) })</span> : null }</div> : null } { m.hps ? <div className={'l'}>{translate('HPS')}: {formats.round1(m.hps)} { m.clip ? <span>({formats.round1((m.clip * m.hps / m.rof) / ((m.clip / m.rof) + m.reload)) })</span> : null }</div> : null }
{ m.dps && m.eps ? <div className={'l'}>{translate('DPE')}: {formats.round1(m.dps / m.eps)}</div> : null } { m.dps && m.eps ? <div className={'l'}>{translate('DPE')}: {formats.round1(m.dps / m.eps)}</div> : null }
{ m.rof ? <div className={'l'}>{translate('ROF')}: {m.rof}{u.ps}</div> : null } { m.rof ? <div className={'l'}>{translate('ROF')}: {m.rof}{u.ps}</div> : null }
{ m.range && !m.dps ? <div className={'l'}>{translate('Range')} : {formats.round(m.range / 1000)}{u.km}</div> : null } { m.range && !m.dps ? <div className={'l'}>{translate('Range')} : {formats.round(m.range / 1000)}{u.km}</div> : null }
{ m.shieldmul ? <div className={'l'}>+{formats.rPct(m.shieldmul)}</div> : null } { m.shieldmul ? <div className={'l'}>+{formats.rPct(m.shieldmul)}</div> : null }
{ m.ammo >= 0 ? <div className={'l'}>{translate('ammo')}: {formats.int(m.clip)}/{formats.int(m.ammo)}</div> : null } { m.ammo >= 0 ? <div className={'l'}>{translate('ammo')}: {formats.int(m.clip)}/{formats.int(m.ammo)}</div> : null }
<div className={'r'}><Modifications /></div>
</div> </div>
</div>; </div>;
} else { } else {

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import Slot from './Slot'; import Slot from './Slot';
import { Infinite } from './SvgIcons'; import { Modifications } from './SvgIcons';
/** /**
* Internal Slot * Internal Slot
@@ -23,7 +23,7 @@ export default class InternalSlot extends Slot {
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}> return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
<div className={'cb'}> <div className={'cb'}>
<div className={'l'}>{classRating} {translate(m.name || m.grp)}</div> <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'}>{m.getMass() || m.cargo || m.fuel || 0}{u.T}</div>
</div> </div>
<div className={'cb'}> <div className={'cb'}>
{ m.optmass ? <div className={'l'}>{translate('optimal mass')}: {m.optmass}{u.T}</div> : null } { m.optmass ? <div className={'l'}>{translate('optimal mass')}: {m.optmass}{u.T}</div> : null }
@@ -42,6 +42,7 @@ export default class InternalSlot extends Slot {
{ m.rangeLS === null ? <div className={'l'}>{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.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.armouradd ? <div className={'l'}>+{m.armouradd} <u className='cap'>{translate('armour')}</u></div> : null }
<div className={'r'}><Modifications /></div>
</div> </div>
</div>; </div>;
} else { } else {

View File

@@ -71,7 +71,7 @@ export default class PowerManagement extends TranslatedComponent {
case 'n': comp = comp(null, desc); break; case 'n': comp = comp(null, desc); break;
case 't': comp = comp((a, b) => a.type.localeCompare(b.type), 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 '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 '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; 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++) { for (let i = 0, l = ship.powerList.length; i < l; i++) {
let slot = ship.powerList[i]; let slot = ship.powerList[i];
if (slot.m && slot.m.power) { if (slot.m && slot.m.getPowerUsage() > 0) {
let m = slot.m; let m = slot.m;
let toggleEnabled = this._toggleEnabled.bind(this, slot); let toggleEnabled = this._toggleEnabled.bind(this, slot);
let retractedElem = null, deployedElem = null; let retractedElem = null, deployedElem = null;
@@ -134,8 +134,8 @@ export default class PowerManagement extends TranslatedComponent {
{' ' + (slot.priority + 1) + ' '} {' ' + (slot.priority + 1) + ' '}
<span className='ptr btn' onClick={this._priority.bind(this, slot, 1)}>&#9658;</span> <span className='ptr btn' onClick={this._priority.bind(this, slot, 1)}>&#9658;</span>
</td> </td>
<td className='ri ptr' style={{ width: '3.25em' }} onClick={toggleEnabled}>{pwr(m.power)}</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.power / ship.powerAvailable)}</u></td> <td className='ri ptr' style={{ width: '3em' }} onClick={toggleEnabled}><u>{pct(m.getPowerUsage() / ship.powerAvailable)}</u></td>
{retractedElem} {retractedElem}
{deployedElem} {deployedElem}
</tr>); </tr>);
@@ -214,7 +214,7 @@ export default class PowerManagement extends TranslatedComponent {
<td className='le shorten cap' >{translate('pp')}</td> <td className='le shorten cap' >{translate('pp')}</td>
<td><u >{translate('SYS')}</u></td> <td><u >{translate('SYS')}</u></td>
<td>1</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 className='ri'><u>100%</u></td>
<td></td> <td></td>
<td></td> <td></td>
@@ -223,7 +223,7 @@ export default class PowerManagement extends TranslatedComponent {
{this._renderPowerRows(ship, translate, pwr, formats.pct1)} {this._renderPowerRows(ship, translate, pwr, formats.pct1)}
</tbody> </tbody>
</table> </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> </div>
); );
} }

View File

@@ -52,6 +52,8 @@ export default class ShipSummaryTable extends TranslatedComponent {
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !ship.canThrust() }) }>{translate('speed')}</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 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, '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 rowSpan={2}>{translate('armour')}</th>
<th colSpan={3}>{translate('shields')}</th> <th colSpan={3}>{translate('shields')}</th>
<th colSpan={3}>{translate('mass')}</th> <th colSpan={3}>{translate('mass')}</th>
@@ -83,6 +85,8 @@ export default class ShipSummaryTable extends TranslatedComponent {
<td>{ ship.canThrust() ? <span>{int(ship.topSpeed)} {u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</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>{ ship.canBoost() ? <span>{int(ship.topBoost)} {u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td>{round(ship.totalDps)}</td> <td>{round(ship.totalDps)}</td>
<td>{round(ship.totalEps)}</td>
<td>{round(ship.totalHps)}</td>
<td>{int(ship.armour)} {armourDetails}</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 className={sgClassNames}>{int(ship.shieldStrength)} {u.MJ} { ship.shieldMultiplier > 1 && ship.shieldStrength > 0 ? <u>({formats.rPct(ship.shieldMultiplier)})</u> : null }</td>
<td className={sgClassNames}>{sgRecover}</td> <td className={sgClassNames}>{sgRecover}</td>

View File

@@ -4,6 +4,7 @@ import TranslatedComponent from './TranslatedComponent';
import { jumpRange } from '../shipyard/Calculations'; import { jumpRange } from '../shipyard/Calculations';
import { diffDetails } from '../utils/SlotFunctions'; import { diffDetails } from '../utils/SlotFunctions';
import AvailableModulesMenu from './AvailableModulesMenu'; import AvailableModulesMenu from './AvailableModulesMenu';
import { Modifications } from './SvgIcons';
/** /**
* Standard Slot * Standard Slot
@@ -49,7 +50,8 @@ export default class StandardSlot extends TranslatedComponent {
<div className={'sz'}>{slot.maxClass}</div> <div className={'sz'}>{slot.maxClass}</div>
<div> <div>
<div className='l'>{classRating} {translate(m.grp == 'bh' ? m.grp : m.name || m.grp)}</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'}>{m.getMass() || m.fuel || 0}{units.T}</div>
<div/>
<div className={'cb'}> <div className={'cb'}>
{ m.grp == 'bh' && m.name ? <div className='l'>{translate(m.name)}</div> : null } { 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.optmass ? <div className='l'>{translate('optimal mass')}: {m.optmass}{units.T}</div> : null }
@@ -57,11 +59,12 @@ export default class StandardSlot extends TranslatedComponent {
{ m.range ? <div className='l'>{translate('range')}: {m.range}{units.km}</div> : null } { m.range ? <div className='l'>{translate('range')}: {m.range}{units.km}</div> : null }
{ m.time ? <div className='l'>{translate('time')}: {formats.time(m.time)}</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.eff ? <div className='l'>{translate('efficiency')}: {m.eff}</div> : null }
{ m.pGen ? <div className='l'>{translate('power')}: {m.pGen}{units.MW}</div> : null } { m.getPowerGeneration() > 0 ? <div className='l'>{translate('power')}: {formats.round(m.getPowerGeneration())}{units.MW}</div> : null }
{ m.maxfuel ? <div className='l'>{translate('max')} {translate('fuel')}: {m.maxfuel}{units.T}</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.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.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.enginecapacity ? <div className='l'>{translate('ENG')}: {m.enginecapacity}{units.MJ} / {m.enginerecharge}{units.MW}</div> : null }
<div className={'r'}><Modifications /></div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -488,6 +488,25 @@ export class Rocket extends SvgIcon {
} }
} }
/**
* Modifications (engineers)
*/
export class Modifications 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 * Hammer
*/ */

View File

@@ -63,7 +63,7 @@ export default class ComparisonPage extends Page {
* @return {Object} New state object * @return {Object} New state object
*/ */
_initState(context) { _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 params = context.route.params;
let code = params.code; let code = params.code;
let name = params.name ? decodeURIComponent(params.name) : null; let name = params.name ? decodeURIComponent(params.name) : null;

View File

@@ -193,6 +193,20 @@ export const ShipFacets = [
lbls: ['DPS'], lbls: ['DPS'],
fmt: 'round', fmt: 'round',
i: 11 i: 11
},
{ // 12
title: 'EPS',
props: ['totalEps'],
lbls: ['EPS'],
fmt: 'round',
i: 12
},
{ // 13
title: 'HPS',
props: ['totalHps'],
lbls: ['HPS'],
fmt: 'round',
i: 13
} }
]; ];

View 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;
}
}

107
src/app/shipyard/Module.js Executable file
View File

@@ -0,0 +1,107 @@
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]; }
}
}
}
/**
* Get a value for a given modification ID
* @param {Number} modId The ID of the modification
* @return {Number} The value of the modification
*/
_getModValue(modId) {
let result = null;
if (this.mods) {
let mod = _.find(this.mods, function(o) { return o.id == modId; });
if (mod) {
result = mod.value;
}
}
return result;
}
/**
* Get the power generation of this module, taking in to account modifications
* @return {Number} the power generation of this module
*/
getPowerGeneration() {
let result = 0;
if (this.pGen) {
result = this.pGen;
if (result) {
let mult = this._getModValue(1);
if (mult) { result = result * (1 + (mult / 1000)); }
}
}
return result;
}
/**
* Get the power usage of this module, taking in to account modifications
* @return {Number} the power usage of this module
*/
getPowerUsage() {
let result = 0;
if (this.power) {
result = this.power;
if (result) {
let mult = this._getModValue(2);
if (mult) { result = result * (1 + (mult / 1000)); }
}
}
return result;
}
/**
* Get the mass of this module, taking in to account modifications
* @return {Number} the mass of this module
*/
getMass() {
let result = 0;
if (this.mass) {
result = this.mass;
if (result) {
let mult = this._getModValue(3);
if (mult) { result = result * (1 + (mult / 1000)); }
}
}
return result;
}
/**
* Get the integrity of this module, taking in to account modifications
* @return {Number} the integrity of this module
*/
getIntegrity() {
let result = 0;
if (this.health) {
result = this.health;
if (result) {
let mult = this._getModValue(4);
if (mult) { result = result * (1 + (mult / 1000)); }
}
}
return result;
}
}

View File

@@ -1,4 +1,4 @@
import Module from './Module';
import { BulkheadNames } from './Constants'; import { BulkheadNames } from './Constants';
/** /**
@@ -37,7 +37,7 @@ export default class ModuleSet {
this.intClass = {}; this.intClass = {};
this.bulkheads = shipData.bulkheads.map((b, i) => { 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 this.standard[0] = filter(stnd.pp, maxStandardArr[0], 0, mass); // Power Plant
@@ -130,7 +130,7 @@ export default class ModuleSet {
pd = p; pd = p;
} }
} }
return pd; return new Module({ template: pd });
}; };
/** /**
@@ -146,7 +146,7 @@ export default class ModuleSet {
th = t; th = t;
} }
} }
return th; return new Module({ template: th });
}; };
/** /**
@@ -162,7 +162,7 @@ export default class ModuleSet {
sg = s; sg = s;
} }
} }
return sg; return new Module({ template: sg });
}; };
/** /**
@@ -179,6 +179,6 @@ export default class ModuleSet {
pp = p; pp = p;
} }
} }
return pp; return new Module({ template: pp });
} }
} }

View File

@@ -1,7 +1,12 @@
import { ModuleNameToGroup, BulkheadNames, StandardArray } from './Constants'; import { ModuleNameToGroup, BulkheadNames, StandardArray } from './Constants';
import ModuleSet from './ModuleSet'; import ModuleSet from './ModuleSet';
import Module from './Module';
import { Ships, Modules } from 'coriolis-data/dist'; 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 * @return {Object} Cargo hatch model
*/ */
export function cargoHatch() { 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 * Finds the standard module type with the specified ID
* @param {String|Number} type Standard Module Type (0/pp - Power Plant, 1/t - Thrusters, etc) * @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))); 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; return s || null;
}; };
@@ -37,7 +81,7 @@ export function hardpoints(id) {
let group = Modules.hardpoints[n]; let group = Modules.hardpoints[n];
for (let i = 0; i < group.length; i++) { for (let i = 0; i < group.length; i++) {
if (group[i].id == id) { if (group[i].id == id) {
return group[i]; return new Module({ template: group[i] });
} }
} }
} }

View File

@@ -1,14 +1,16 @@
import { ArmourMultiplier } from './Constants'; import { ArmourMultiplier } from './Constants';
import * as Calc from './Calculations'; import * as Calc from './Calculations';
import * as ModuleUtils from './ModuleUtils'; import * as ModuleUtils from './ModuleUtils';
import Module from './Module';
import LZString from 'lz-string'; import LZString from 'lz-string';
import isEqual from 'lodash/lang';
const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs', 'fh']; 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} 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 * @return {String} The key for the power usage type
*/ */
function powerUsageType(slot, modul) { function powerUsageType(slot, modul) {
@@ -20,6 +22,25 @@ function powerUsageType(slot, modul) {
return slot.cat != 1 ? 'retracted' : 'deployed'; return slot.cat != 1 ? 'retracted' : 'deployed';
} }
/**
* Populate the modifications array with modification values from the code
* @param {String} code Serialized modification code
* @param {Array} arr Modification array
*/
function decodeModsToArray(code, arr) {
let moduleMods = code.split(',');
for (let i = 0; i < arr.length; i++) {
arr[i] = new Array();
if (moduleMods.length > i && moduleMods[i] != '') {
let mods = moduleMods[i].split(';');
for (let j = 0; j < mods.length; j++) {
let modElements = mods[j].split(':');
arr[i].push({ id: Number(modElements[0]), value: Number(modElements[1]) });
}
}
}
}
/** /**
* Populates the category array with module IDs from * Populates the category array with module IDs from
* the provided code * the provided code
@@ -284,7 +305,9 @@ export default class Ship {
'.', '.',
this.getPowerEnabledString(), this.getPowerEnabledString(),
'.', '.',
this.getPowerPrioritesString() this.getPowerPrioritiesString(),
'.',
this.getModificationsString()
].join(''); ].join('');
} }
@@ -324,6 +347,18 @@ export default class Ship {
return this.serialized.hardpoints; return this.serialized.hardpoints;
} }
/**
* Serializes the modifications to a string
* @return {String} Serialized modifications 'code'
*/
getModificationsString() {
if(!this.serialized.modifications) {
this.updateModificationsString();
}
return this.serialized.modifications;
}
/** /**
* Get the serialized module active/inactive settings * Get the serialized module active/inactive settings
* @return {String} Serialized active/inactive settings * @return {String} Serialized active/inactive settings
@@ -336,7 +371,7 @@ export default class Ship {
* Get the serialized module priority settings * Get the serialized module priority settings
* @return {String} Serialized priority settings * @return {String} Serialized priority settings
*/ */
getPowerPrioritesString() { getPowerPrioritiesString() {
return this.serialized.priorities; return this.serialized.priorities;
} }
@@ -373,9 +408,10 @@ export default class Ship {
* @param {Object} comps Collection of ModuleUtils used to build the ship * @param {Object} comps Collection of ModuleUtils used to build the ship
* @param {array} priorities Slot priorities * @param {array} priorities Slot priorities
* @param {Array} enabled Slot active/inactive * @param {Array} enabled Slot active/inactive
* @param {Array} mods Modifications
* @return {this} The current ship instance for chaining * @return {this} The current ship instance for chaining
*/ */
buildWith(comps, priorities, enabled) { buildWith(comps, priorities, enabled, mods) {
let internal = this.internal, let internal = this.internal,
standard = this.standard, standard = this.standard,
hps = this.hardpoints, hps = this.hardpoints,
@@ -393,11 +429,15 @@ export default class Ship {
this.totalCost = this.m.incCost ? this.m.discountedCost : 0; this.totalCost = this.m.incCost ? this.m.discountedCost : 0;
this.unladenMass = this.hullMass; this.unladenMass = this.hullMass;
this.totalDps = 0; this.totalDps = 0;
this.totalEps = 0;
this.totalHps = 0;
this.bulkheads.m = null; this.bulkheads.m = null;
this.useBulkhead(comps && comps.bulkheads ? comps.bulkheads : 0, true); 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.priority = priorities ? priorities[0] * 1 : 0;
this.cargoHatch.enabled = enabled ? enabled[0] * 1 : true; this.cargoHatch.enabled = enabled ? enabled[0] * 1 : true;
this.cargoHatch.mods = mods ? mods[0] : [];
for (i = 0, l = this.priorityBands.length; i < l; i++) { for (i = 0, l = this.priorityBands.length; i < l; i++) {
this.priorityBands[i].deployed = 0; this.priorityBands[i].deployed = 0;
@@ -405,9 +445,10 @@ export default class Ship {
} }
if (this.cargoHatch.enabled) { if (this.cargoHatch.enabled) {
bands[this.cargoHatch.priority].retracted += this.cargoHatch.m.power; bands[this.cargoHatch.priority].retracted += this.cargoHatch.m.getPowerUsage();
} }
for (i = 0; i < cl; i++) { for (i = 0; i < cl; i++) {
standard[i].cat = 0; standard[i].cat = 0;
standard[i].enabled = enabled ? enabled[i + 1] * 1 : true; standard[i].enabled = enabled ? enabled[i + 1] * 1 : true;
@@ -415,9 +456,10 @@ export default class Ship {
standard[i].type = 'SYS'; standard[i].type = 'SYS';
standard[i].m = null; // Resetting 'old' modul if there was one standard[i].m = null; // Resetting 'old' modul if there was one
standard[i].discountedCost = 0; standard[i].discountedCost = 0;
if (comps) { 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 +476,9 @@ export default class Ship {
hps[i].discountedCost = 0; hps[i].discountedCost = 0;
if (comps && comps.hardpoints[i] !== 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,7 +493,9 @@ export default class Ship {
internal[i].discountedCost = 0; internal[i].discountedCost = 0;
if (comps && comps.internal[i] !== 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);
} }
} }
@@ -461,7 +507,7 @@ export default class Ship {
.updateTopSpeed(); .updateTopSpeed();
} }
return this.updatePowerPrioritesString().updatePowerEnabledString(); return this.updatePowerPrioritesString().updatePowerEnabledString().updateModificationsString();
} }
/** /**
@@ -475,6 +521,7 @@ export default class Ship {
let standard = new Array(this.standard.length), let standard = new Array(this.standard.length),
hardpoints = new Array(this.hardpoints.length), hardpoints = new Array(this.hardpoints.length),
internal = new Array(this.internal.length), internal = new Array(this.internal.length),
mods = new Array(1 + this.standard.length + this.hardpoints.length + this.internal.length),
parts = serializedString.split('.'), parts = serializedString.split('.'),
priorities = null, priorities = null,
enabled = null, enabled = null,
@@ -488,6 +535,10 @@ export default class Ship {
priorities = LZString.decompressFromBase64(parts[2].replace(/-/g, '/')).split(''); priorities = LZString.decompressFromBase64(parts[2].replace(/-/g, '/')).split('');
} }
if (parts[3]) {
decodeModsToArray(parts[3], mods);
}
decodeToArray(code, internal, decodeToArray(code, hardpoints, decodeToArray(code, standard, 1))); decodeToArray(code, internal, decodeToArray(code, hardpoints, decodeToArray(code, standard, 1)));
return this.buildWith( return this.buildWith(
@@ -498,7 +549,8 @@ export default class Ship {
internal internal
}, },
priorities, priorities,
enabled enabled,
mods
); );
}; };
@@ -584,16 +636,23 @@ export default class Ship {
if (slot.enabled != enabled) { // Enabled state is changing if (slot.enabled != enabled) { // Enabled state is changing
slot.enabled = enabled; slot.enabled = enabled;
if (slot.m) { if (slot.m) {
this.priorityBands[slot.priority][powerUsageType(slot, slot.m)] += enabled ? slot.m.power : -slot.m.power; this.priorityBands[slot.priority][powerUsageType(slot, slot.m)] += enabled ? slot.m.getPowerUsage() : - slot.m.getPowerUsage();
if (ModuleUtils.isShieldGenerator(slot.m.grp)) { if (ModuleUtils.isShieldGenerator(slot.m.grp)) {
this.updateShieldStrength(); this.updateShieldStrength();
} else if (slot.m.grp == 'sb') { } else if (slot.m.grp == 'sb') {
this.shieldMultiplier += slot.m.shieldmul * (enabled ? 1 : -1); this.shieldMultiplier += slot.m.shieldmul * (enabled ? 1 : -1);
this.updateShieldStrength(); this.updateShieldStrength();
} else if (slot.m.dps) { }
if (slot.m.dps) {
this.totalDps += slot.m.dps * (enabled ? 1 : -1); this.totalDps += slot.m.dps * (enabled ? 1 : -1);
} }
if (slot.m.eps) {
this.totalEps += slot.m.eps * (enabled ? 1 : -1);
}
if (slot.m.hps) {
this.totalHps += slot.m.hps * (enabled ? 1 : -1);
}
this.updatePower(); this.updatePower();
this.updatePowerEnabledString(); this.updatePowerEnabledString();
@@ -616,8 +675,8 @@ export default class Ship {
if (slot.enabled) { // Only update power if the slot is enabled if (slot.enabled) { // Only update power if the slot is enabled
let usage = powerUsageType(slot, slot.m); let usage = powerUsageType(slot, slot.m);
this.priorityBands[oldPriority][usage] -= slot.m.power; this.priorityBands[oldPriority][usage] -= slot.m.getPowerUsage();
this.priorityBands[newPriority][usage] += slot.m.power; this.priorityBands[newPriority][usage] += slot.m.getPowerUsage();
this.updatePower(); this.updatePower();
} }
return true; return true;
@@ -656,15 +715,26 @@ export default class Ship {
this.totalCost -= old.cost * this.moduleCostMultiplier; this.totalCost -= old.cost * this.moduleCostMultiplier;
} }
if (old.power && slot.enabled) { if (!(old instanceof Module)) {
this.priorityBands[slot.priority][powerUsageType(slot, old)] -= old.power; console.log(JSON.stringify(old) + ' is not a module');
console.log(new Error().stack);
}
if (old.getPowerUsage() > 0 && slot.enabled) {
this.priorityBands[slot.priority][powerUsageType(slot, old)] -= old.getPowerUsage();
powerChange = true; powerChange = true;
if (old.dps) { if (old.dps) {
this.totalDps -= old.dps; this.totalDps -= old.dps;
} }
if (old.eps) {
this.totalEps -= old.eps;
}
if (old.hps) {
this.totalHps -= old.hps;
}
} }
this.unladenMass -= old.mass || 0; this.unladenMass -= old.getMass() || 0;
} }
if (n) { if (n) {
@@ -688,14 +758,20 @@ export default class Ship {
} }
if (n.power && slot.enabled) { if (n.power && slot.enabled) {
this.priorityBands[slot.priority][powerUsageType(slot, n)] += n.power; this.priorityBands[slot.priority][powerUsageType(slot, n)] += n.getPowerUsage();
powerChange = true; powerChange = true;
if (n.dps) { if (n.dps) {
this.totalDps += n.dps; this.totalDps += n.dps;
} }
if (n.eps) {
this.totalEps += n.eps;
}
if (n.hps) {
this.totalHps += n.hps;
}
} }
this.unladenMass += n.mass || 0; this.unladenMass += n.getMass() || 0;
} }
this.ladenMass = this.unladenMass + this.cargoCapacity + this.fuelCapacity; this.ladenMass = this.unladenMass + this.cargoCapacity + this.fuelCapacity;
@@ -726,7 +802,7 @@ export default class Ship {
prevDeployed = band.deployedSum = prevDeployed + band.deployed + band.retracted; prevDeployed = band.deployedSum = prevDeployed + band.deployed + band.retracted;
} }
this.powerAvailable = this.standard[0].m.pGen; this.powerAvailable = this.standard[0].m.getPowerGeneration();
this.powerRetracted = prevRetracted; this.powerRetracted = prevRetracted;
this.powerDeployed = prevDeployed; this.powerDeployed = prevDeployed;
return this; return this;
@@ -811,16 +887,75 @@ export default class Ship {
return this; return this;
} }
/**
* Update the modifications string
* @return {this} The ship instance (for chaining operations)
*/
updateModificationsString() {
let allMods = new Array();
let bulkheadMods = new Array();
if (this.bulkheads.m && this.bulkheads.m.mods) {
for (let mod of this.bulkheads.m.mods) {
bulkheadMods.push(mod.id + ':' + mod.value);
}
}
allMods.push(bulkheadMods.join(';'));
for (let slot of this.standard) {
let slotMods = new Array();
if (slot.m && slot.m.mods) {
for (let mod of slot.m.mods) {
slotMods.push(mod.id + ':' + mod.value);
}
}
allMods.push(slotMods.join(';'));
}
for (let slot of this.hardpoints) {
let slotMods = new Array();
if (slot.m && slot.m.mods) {
for (let mod of slot.m.mods) {
slotMods.push(mod.id + ':' + mod.value);
}
}
allMods.push(slotMods.join(';'));
}
for (let slot of this.internal) {
let slotMods = new Array();
if (slot.m && slot.m.mods) {
for (let mod of slot.m.mods) {
slotMods.push(mod.id + ':' + mod.value);
}
}
allMods.push(slotMods.join(';'));
}
this.serialized.modifications = allMods.join(',').replace(/,+$/, '');
console.log('Final serialized string is ' + this.serialized.modifications);
return this;
}
/** /**
* Update a slot with a the modul if the id is different from the current id for this slot. * 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). * Has logic handling ModuleUtils that you may only have 1 of (Shield Generator or Refinery).
* *
* @param {Object} slot The modul slot * @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 * @param {boolean} preventUpdate If true, do not update aggregated stats
* @return {this} The ship instance (for chaining operations) * @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 {
// jgmjgm TODO see if we can use the module template instead of its group and id
// m = new Module({template: mdef});
m = new Module({ grp: mdef.grp, id: mdef.id });
}
if (slot.m != m) { // Selecting a different modul 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 // 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) { if (slot.cat == 2 && m && UNIQUE_MODULES.indexOf(m.grp) != -1) {
@@ -843,6 +978,7 @@ export default class Ship {
case 1: this.serialized.hardpoints = null; break; case 1: this.serialized.hardpoints = null; break;
case 2: this.serialized.internal = null; case 2: this.serialized.internal = null;
} }
this.serialized.modifications = null;
} }
return this; return this;
} }
@@ -907,14 +1043,14 @@ export default class Ship {
updated = false; updated = false;
// Find lightest Thruster that still works for the ship at max mass // 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); 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); this.use(standard[1], th);
updated = true; updated = true;
} }
// Find lightest Power plant that can power the ship // 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); 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); this.use(standard[0], pp);
updated = true; updated = true;
} }

View File

@@ -115,7 +115,7 @@ export function explorer(ship, planetary) {
if (sgSlot) { if (sgSlot) {
// The SG and Fuel scoop to not need to be powered at the same time // 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); ship.setSlotEnabled(fuelScoopSlot, false);
} else { // The Fuel scoop uses the most power } else { // The Fuel scoop uses the most power
ship.setSlotEnabled(sgSlot, false); ship.setSlotEnabled(sgSlot, false);

View File

@@ -210,14 +210,14 @@ export function diffDetails(language, m, mm) {
let { formats, translate, units } = language; let { formats, translate, units } = language;
let propDiffs = []; let propDiffs = [];
let mMass = m.mass || 0; let mMass = m.mass || 0;
let mmMass = mm.mass || 0; let mmMass = mm ? mm.getMass() : 0;
let massDiff = mMass - mmMass; let massDiff = mMass - mmMass;
let capDiff = (m.fuel || m.cargo || 0) - (mm.fuel || mm.cargo || 0); let capDiff = (m.fuel || m.cargo || 0) - (mm.fuel || mm.cargo || 0);
let mAffectsShield = isShieldGenerator(m.grp) || m.grp == 'sb'; let mAffectsShield = isShieldGenerator(m.grp) || m.grp == 'sb';
let mmAffectsShield = isShieldGenerator(mm.grp) || mm.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='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>); propDiffs.push(<div key='mass'>{translate('mass')}: <span className={diffClass(mMass, mm.getMass(), true)}>{diff(formats.round, mMass, mmMass)}{units.T}</span></div>);
for (let p in m) { for (let p in m) {
if (!PROP_BLACKLIST[p] && !isNaN(m[p])) { if (!PROP_BLACKLIST[p] && !isNaN(m[p])) {