diff --git a/package.json b/package.json index 4c12a8b1..aaf8d19e 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ ], "automock": true, "unmockedModulePathPatterns": [ + "/node_modules/lodash", "/node_modules/react", "/node_modules/react-dom", "/node_modules/react-addons-test-utils", @@ -86,6 +87,7 @@ "coriolis-data": "EDCD/coriolis-data", "d3": "3.5.16", "fbemitter": "^2.0.0", + "lodash": "^4.15.0", "lz-string": "^1.4.4", "react": "^15.0.1", "react-dom": "^15.0.1", diff --git a/src/app/components/HardpointSlot.jsx b/src/app/components/HardpointSlot.jsx index dd55be07..0c5773a8 100644 --- a/src/app/components/HardpointSlot.jsx +++ b/src/app/components/HardpointSlot.jsx @@ -1,6 +1,6 @@ import React from 'react'; 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 @@ -48,17 +48,18 @@ export default class HardpointSlot extends Slot { {m.type && m.type == 'KT' ? : ''} {m.type && m.type == 'E' ? : ''} {classRating} {translate(m.name || m.grp)} -
{m.mass}{u.T}
+
{m.getMass()}{u.T}
{ m.dps ?
{translate('DPS')}: {formats.round1(m.dps)} { m.clip ? ({formats.round1((m.clip * m.dps / m.rof) / ((m.clip / m.rof) + m.reload)) }) : null }
: null } - { m.eps ?
{translate('EPS')}: {formats.round1(m.eps)} { m.clip ? ({formats.round1((m.clip * m.eps / m.rof) / ((m.clip / m.rof) + m.reload)) }) : null }
: null } + { m.eps ?
{translate('EPS')}: {formats.round1(m.eps)}{u.MW} { m.clip ? ({formats.round1((m.clip * m.eps / m.rof) / ((m.clip / m.rof) + m.reload)) }{u.MW}) : null }
: null } { m.hps ?
{translate('HPS')}: {formats.round1(m.hps)} { m.clip ? ({formats.round1((m.clip * m.hps / m.rof) / ((m.clip / m.rof) + m.reload)) }) : null }
: null } { m.dps && m.eps ?
{translate('DPE')}: {formats.round1(m.dps / m.eps)}
: null } { m.rof ?
{translate('ROF')}: {m.rof}{u.ps}
: null } { m.range && !m.dps ?
{translate('Range')} : {formats.round(m.range / 1000)}{u.km}
: null } { m.shieldmul ?
+{formats.rPct(m.shieldmul)}
: null } { m.ammo >= 0 ?
{translate('ammo')}: {formats.int(m.clip)}/{formats.int(m.ammo)}
: null } +
; } else { diff --git a/src/app/components/InternalSlot.jsx b/src/app/components/InternalSlot.jsx index 4c38afd7..fa29f27c 100644 --- a/src/app/components/InternalSlot.jsx +++ b/src/app/components/InternalSlot.jsx @@ -1,6 +1,6 @@ import React from 'react'; import Slot from './Slot'; -import { Infinite } from './SvgIcons'; +import { Modifications } from './SvgIcons'; /** * Internal Slot @@ -23,7 +23,7 @@ export default class InternalSlot extends Slot { return
{classRating} {translate(m.name || m.grp)}
-
{m.mass || m.cargo || m.fuel || 0}{u.T}
+
{m.getMass() || m.cargo || m.fuel || 0}{u.T}
{ m.optmass ?
{translate('optimal mass')}: {m.optmass}{u.T}
: null } @@ -42,6 +42,7 @@ export default class InternalSlot extends Slot { { m.rangeLS === null ?
∞{u.Ls}
: null } { m.rangeRating ?
{translate('range')}: {m.rangeRating}
: null } { m.armouradd ?
+{m.armouradd} {translate('armour')}
: null } +
; } else { diff --git a/src/app/components/PowerManagement.jsx b/src/app/components/PowerManagement.jsx index 0345d895..84b66bee 100644 --- a/src/app/components/PowerManagement.jsx +++ b/src/app/components/PowerManagement.jsx @@ -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) + ' '} - {pwr(m.power)} - {pct(m.power / ship.powerAvailable)} + {pwr(m.getPowerUsage())} + {pct(m.getPowerUsage() / ship.powerAvailable)} {retractedElem} {deployedElem} ); @@ -214,7 +214,7 @@ export default class PowerManagement extends TranslatedComponent { {translate('pp')} {translate('SYS')} 1 - {pwr(pp.pGen)} + {pwr(pp.getPowerGeneration())} 100% @@ -223,7 +223,7 @@ export default class PowerManagement extends TranslatedComponent { {this._renderPowerRows(ship, translate, pwr, formats.pct1)} - + ); } diff --git a/src/app/components/ShipSummaryTable.jsx b/src/app/components/ShipSummaryTable.jsx index 2e8e57b0..7ebc8163 100644 --- a/src/app/components/ShipSummaryTable.jsx +++ b/src/app/components/ShipSummaryTable.jsx @@ -52,6 +52,8 @@ export default class ShipSummaryTable extends TranslatedComponent { {translate('speed')} {translate('boost')} {translate('DPS')} + {translate('EPS')} + {translate('HPS')} {translate('armour')} {translate('shields')} {translate('mass')} @@ -83,6 +85,8 @@ export default class ShipSummaryTable extends TranslatedComponent { { ship.canThrust() ? {int(ship.topSpeed)} {u['m/s']} : 0 } { ship.canBoost() ? {int(ship.topBoost)} {u['m/s']} : 0 } {round(ship.totalDps)} + {round(ship.totalEps)} + {round(ship.totalHps)} {int(ship.armour)} {armourDetails} {int(ship.shieldStrength)} {u.MJ} { ship.shieldMultiplier > 1 && ship.shieldStrength > 0 ? ({formats.rPct(ship.shieldMultiplier)}) : null } {sgRecover} diff --git a/src/app/components/StandardSlot.jsx b/src/app/components/StandardSlot.jsx index 00d81ac3..a62e28e7 100644 --- a/src/app/components/StandardSlot.jsx +++ b/src/app/components/StandardSlot.jsx @@ -4,6 +4,7 @@ import TranslatedComponent from './TranslatedComponent'; import { jumpRange } from '../shipyard/Calculations'; import { diffDetails } from '../utils/SlotFunctions'; import AvailableModulesMenu from './AvailableModulesMenu'; +import { Modifications } from './SvgIcons'; /** * Standard Slot @@ -49,7 +50,8 @@ export default class StandardSlot extends TranslatedComponent {
{slot.maxClass}
{classRating} {translate(m.grp == 'bh' ? m.grp : m.name || m.grp)}
-
{m.mass || m.fuel || 0}{units.T}
+
{m.getMass() || m.fuel || 0}{units.T}
+
{ m.grp == 'bh' && m.name ?
{translate(m.name)}
: null } { m.optmass ?
{translate('optimal mass')}: {m.optmass}{units.T}
: null } @@ -57,11 +59,12 @@ export default class StandardSlot extends TranslatedComponent { { m.range ?
{translate('range')}: {m.range}{units.km}
: null } { m.time ?
{translate('time')}: {formats.time(m.time)}
: null } { m.eff ?
{translate('efficiency')}: {m.eff}
: null } - { m.pGen ?
{translate('power')}: {m.pGen}{units.MW}
: null } + { m.getPowerGeneration() > 0 ?
{translate('power')}: {formats.round(m.getPowerGeneration())}{units.MW}
: null } { m.maxfuel ?
{translate('max')} {translate('fuel')}: {m.maxfuel}{units.T}
: null } { m.weaponcapacity ?
{translate('WEP')}: {m.weaponcapacity}{units.MJ} / {m.weaponrecharge}{units.MW}
: null } { m.systemcapacity ?
{translate('SYS')}: {m.systemcapacity}{units.MJ} / {m.systemrecharge}{units.MW}
: null } { m.enginecapacity ?
{translate('ENG')}: {m.enginecapacity}{units.MJ} / {m.enginerecharge}{units.MW}
: null } +
diff --git a/src/app/components/SvgIcons.jsx b/src/app/components/SvgIcons.jsx index 6e228026..984415d1 100644 --- a/src/app/components/SvgIcons.jsx +++ b/src/app/components/SvgIcons.jsx @@ -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 ; + } +} + /** * Hammer */ diff --git a/src/app/pages/ComparisonPage.jsx b/src/app/pages/ComparisonPage.jsx index 1ab50fd6..5531525f 100644 --- a/src/app/pages/ComparisonPage.jsx +++ b/src/app/pages/ComparisonPage.jsx @@ -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; diff --git a/src/app/shipyard/Constants.js b/src/app/shipyard/Constants.js index 70688066..66898802 100755 --- a/src/app/shipyard/Constants.js +++ b/src/app/shipyard/Constants.js @@ -193,6 +193,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 } ]; diff --git a/src/app/shipyard/Modification.js b/src/app/shipyard/Modification.js new file mode 100755 index 00000000..5c2d4566 --- /dev/null +++ b/src/app/shipyard/Modification.js @@ -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; + } + +} diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js new file mode 100755 index 00000000..9e301368 --- /dev/null +++ b/src/app/shipyard/Module.js @@ -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; + } +} diff --git a/src/app/shipyard/ModuleSet.js b/src/app/shipyard/ModuleSet.js index 04711cb8..a9148a57 100755 --- a/src/app/shipyard/ModuleSet.js +++ b/src/app/shipyard/ModuleSet.js @@ -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 @@ -130,7 +130,7 @@ export default class ModuleSet { pd = p; } } - return pd; + return new Module({ template: pd }); }; /** @@ -146,7 +146,7 @@ export default class ModuleSet { th = t; } } - return th; + return new Module({ template: th }); }; /** @@ -162,7 +162,7 @@ export default class ModuleSet { sg = s; } } - return sg; + return new Module({ template: sg }); }; /** @@ -179,6 +179,6 @@ export default class ModuleSet { pp = p; } } - return pp; + return new Module({ template: pp }); } } diff --git a/src/app/shipyard/ModuleUtils.js b/src/app/shipyard/ModuleUtils.js index 4543bf32..d2066c61 100755 --- a/src/app/shipyard/ModuleUtils.js +++ b/src/app/shipyard/ModuleUtils.js @@ -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] }); } } } diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index 3b006005..a997ec1d 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -1,14 +1,16 @@ 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'; 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) { @@ -20,6 +22,25 @@ function powerUsageType(slot, modul) { 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 * the provided code @@ -284,7 +305,9 @@ export default class Ship { '.', this.getPowerEnabledString(), '.', - this.getPowerPrioritesString() + this.getPowerPrioritiesString(), + '.', + this.getModificationsString() ].join(''); } @@ -324,6 +347,18 @@ export default class Ship { 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 * @return {String} Serialized active/inactive settings @@ -336,7 +371,7 @@ export default class Ship { * Get the serialized module priority settings * @return {String} Serialized priority settings */ - getPowerPrioritesString() { + getPowerPrioritiesString() { return this.serialized.priorities; } @@ -373,9 +408,10 @@ export default class Ship { * @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, @@ -393,11 +429,15 @@ export default class Ship { this.totalCost = this.m.incCost ? this.m.discountedCost : 0; this.unladenMass = this.hullMass; this.totalDps = 0; + this.totalEps = 0; + this.totalHps = 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; + this.cargoHatch.mods = mods ? mods[0] : []; for (i = 0, l = this.priorityBands.length; i < l; i++) { this.priorityBands[i].deployed = 0; @@ -405,9 +445,10 @@ export default class Ship { } 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++) { standard[i].cat = 0; standard[i].enabled = enabled ? enabled[i + 1] * 1 : true; @@ -415,9 +456,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 +476,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,7 +493,9 @@ 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); } } @@ -461,7 +507,7 @@ export default class Ship { .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), 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 +535,10 @@ export default class Ship { 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))); return this.buildWith( @@ -498,7 +549,8 @@ export default class Ship { internal }, priorities, - enabled + enabled, + mods ); }; @@ -584,16 +636,23 @@ 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; + this.priorityBands[slot.priority][powerUsageType(slot, slot.m)] += enabled ? slot.m.getPowerUsage() : - slot.m.getPowerUsage(); 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) { + } + if (slot.m.dps) { 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.updatePowerEnabledString(); @@ -616,8 +675,8 @@ export default class Ship { 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.priorityBands[oldPriority][usage] -= slot.m.getPowerUsage(); + this.priorityBands[newPriority][usage] += slot.m.getPowerUsage(); this.updatePower(); } return true; @@ -656,15 +715,26 @@ export default class Ship { this.totalCost -= old.cost * this.moduleCostMultiplier; } - if (old.power && slot.enabled) { - this.priorityBands[slot.priority][powerUsageType(slot, old)] -= old.power; + if (!(old instanceof Module)) { + 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; if (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) { @@ -688,14 +758,20 @@ export default class Ship { } 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; if (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; @@ -726,7 +802,7 @@ export default class Ship { 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.powerDeployed = prevDeployed; return this; @@ -811,16 +887,75 @@ export default class Ship { 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. * 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 { + // 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 // 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 +978,7 @@ export default class Ship { case 1: this.serialized.hardpoints = null; break; case 2: this.serialized.internal = null; } + this.serialized.modifications = null; } return this; } @@ -907,14 +1043,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; } diff --git a/src/app/shipyard/ShipRoles.js b/src/app/shipyard/ShipRoles.js index fc06fbe0..42b03ee6 100644 --- a/src/app/shipyard/ShipRoles.js +++ b/src/app/shipyard/ShipRoles.js @@ -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); diff --git a/src/app/utils/SlotFunctions.js b/src/app/utils/SlotFunctions.js index fa63852c..f4a68822 100644 --- a/src/app/utils/SlotFunctions.js +++ b/src/app/utils/SlotFunctions.js @@ -210,14 +210,14 @@ export function diffDetails(language, m, mm) { let { formats, translate, units } = language; let propDiffs = []; let mMass = m.mass || 0; - let mmMass = mm.mass || 0; + let mmMass = mm ? mm.getMass() : 0; let massDiff = mMass - mmMass; let capDiff = (m.fuel || m.cargo || 0) - (mm.fuel || mm.cargo || 0); let mAffectsShield = isShieldGenerator(m.grp) || m.grp == 'sb'; let mmAffectsShield = isShieldGenerator(mm.grp) || mm.grp == 'sb'; propDiffs.push(
{translate('cost')}: {m.cost ? Math.round(m.cost * (1 - Persist.getModuleDiscount())) : 0}{units.CR}
); - propDiffs.push(
{translate('mass')}: {diff(formats.round, mMass, mmMass)}{units.T}
); + propDiffs.push(
{translate('mass')}: {diff(formats.round, mMass, mmMass)}{units.T}
); for (let p in m) { if (!PROP_BLACKLIST[p] && !isNaN(m[p])) {