diff --git a/__tests__/fixtures/anaconda-test-detailed-export-v4.json b/__tests__/fixtures/anaconda-test-detailed-export-v4.json index 93aa7a93..65893b57 100644 --- a/__tests__/fixtures/anaconda-test-detailed-export-v4.json +++ b/__tests__/fixtures/anaconda-test-detailed-export-v4.json @@ -320,7 +320,6 @@ "shieldExplRes": 0.5, "shieldKinRes": 0.4, "shieldThermRes": -0.2, - "timeToDrain": 7.04, "crew": 3 } } diff --git a/package.json b/package.json index efc71869..c934693c 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "json-loader": "^0.5.3", "less": "^2.5.3", "less-loader": "^2.2.1", + "react-addons-perf": "^15.4.2", "react-addons-test-utils": "^15.0.1", "react-measure": "^1.4.6", "react-testutils-additions": "^15.1.0", diff --git a/src/app/components/Boost.jsx b/src/app/components/Boost.jsx index cc39d28e..10e60927 100644 --- a/src/app/components/Boost.jsx +++ b/src/app/components/Boost.jsx @@ -17,6 +17,7 @@ export default class Boost extends TranslatedComponent { static propTypes = { marker: React.PropTypes.string.isRequired, ship: React.PropTypes.object.isRequired, + boost: React.PropTypes.bool.isRequired, onChange: React.PropTypes.func.isRequired }; @@ -27,14 +28,10 @@ export default class Boost extends TranslatedComponent { */ constructor(props, context) { super(props); - const ship = props.ship; + const { ship, boost } = props; this._keyDown = this._keyDown.bind(this); this._toggleBoost = this._toggleBoost.bind(this); - - this.state = { - boost: false - }; } /** @@ -51,25 +48,6 @@ export default class Boost extends TranslatedComponent { document.removeEventListener('keydown', this._keyDown); } - /** - * Update values if we change ship - * @param {Object} nextProps Incoming/Next properties - * @returns {boolean} Returns true if the component should be rerendered - */ - componentWillReceiveProps(nextProps) { - const { boost } = this.state; - const nextShip = nextProps.ship; - - const nextBoost = nextShip.canBoost() ? boost : false; - if (nextBoost != boost) { - this.setState({ - boost: nextBoost - }); - } - - return true; - } - /** * Handle Key Down * @param {Event} e Keyboard Event @@ -91,10 +69,7 @@ export default class Boost extends TranslatedComponent { * Toggle the boost feature */ _toggleBoost() { - let { boost } = this.state; - boost = !boost; - this.setState({ boost }); - this.props.onChange(boost); + this.props.onChange(!this.props.boost); } /** @@ -103,8 +78,7 @@ export default class Boost extends TranslatedComponent { */ render() { const { formats, translate, units } = this.context.language; - const { ship } = this.props; - const { boost } = this.state; + const { ship, boost } = this.props; // TODO disable if ship cannot boost return ( diff --git a/src/app/components/Cargo.jsx b/src/app/components/Cargo.jsx index bad1e2cf..96bbac83 100644 --- a/src/app/components/Cargo.jsx +++ b/src/app/components/Cargo.jsx @@ -9,7 +9,8 @@ import Slider from '../components/Slider'; */ export default class Cargo extends TranslatedComponent { static propTypes = { - ship: React.PropTypes.object.isRequired, + cargo: React.PropTypes.number.isRequired, + cargoCapacity: React.PropTypes.number.isRequired, onChange: React.PropTypes.func.isRequired }; @@ -21,35 +22,7 @@ export default class Cargo extends TranslatedComponent { constructor(props, context) { super(props); - const ship = this.props.ship; - - this.state = { - cargoCapacity: ship.cargoCapacity, - cargoLevel: 0, - }; - } - - /** - * Update the state if our ship changes - * @param {Object} nextProps Incoming/Next properties - * @return {boolean} Returns true if the component should be rerendered - */ - componentWillReceiveProps(nextProps) { - const { cargoLevel, cargoCapacity } = this.state; - const nextCargoCapacity = nextProps.ship.cargoCapacity; - - if (nextCargoCapacity != cargoCapacity) { - // We keep the absolute cargo amount the same if possible so recalculate the relative level - const nextCargoLevel = Math.min((cargoLevel * cargoCapacity) / nextCargoCapacity, 1); - - this.setState({ cargoLevel: nextCargoLevel, cargoCapacity: nextCargoCapacity }); - - // Notify if appropriate - if (nextCargoLevel * nextCargoCapacity != cargoLevel * cargoCapacity) { - this.props.onChange(Math.round(nextCargoLevel * nextCargoCapacity)); - } - } - return true; + this._cargoChange = this._cargoChange.bind(this); } /** @@ -57,14 +30,12 @@ export default class Cargo extends TranslatedComponent { * @param {number} cargoLevel percentage level from 0 to 1 */ _cargoChange(cargoLevel) { - const { cargoCapacity } = this.state; + const { cargo, cargoCapacity } = this.props; if (cargoCapacity > 0) { - // We round the cargo level to a suitable value given the capacity - cargoLevel = Math.round(cargoLevel * cargoCapacity) / cargoCapacity; - - if (cargoLevel != this.state.cargoLevel) { - this.setState({ cargoLevel }); - this.props.onChange(Math.round(cargoLevel * cargoCapacity)); + // We round the cargo to whole number of tonnes + const newCargo = Math.round(cargoLevel * cargoCapacity); + if (newCargo != cargo) { + this.props.onChange(newCargo); } } } @@ -76,20 +47,20 @@ export default class Cargo extends TranslatedComponent { render() { const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context; const { formats, translate, units } = language; - const { cargoLevel, cargoCapacity } = this.state; + const { cargo, cargoCapacity } = this.props; return ( -

{translate('cargo carried')}: {formats.int(cargoLevel * cargoCapacity)}{units.T}

+

{translate('cargo carried')}: {formats.int(cargo)}{units.T}

-

{translate('engagement range')}: {formats.int(rangeLevel * maxRange)}{translate('m')}

+

{translate('engagement range')}: {formats.int(engagementRange)}{translate('m')}

@@ -116,7 +86,7 @@ export default class Range extends TranslatedComponent { axis={true} onChange={this._rangeChange.bind(this)} axisUnit={translate('m')} - percent={rangeLevel} + percent={engagementRange / maxRange} max={maxRange} scale={sizeRatio} onResize={onWindowResize} diff --git a/src/app/components/Fuel.jsx b/src/app/components/Fuel.jsx index c3f9cc6b..8c8ea8d7 100644 --- a/src/app/components/Fuel.jsx +++ b/src/app/components/Fuel.jsx @@ -9,7 +9,8 @@ import Slider from '../components/Slider'; */ export default class Fuel extends TranslatedComponent { static propTypes = { - ship: React.PropTypes.object.isRequired, + fuel: React.PropTypes.number.isRequired, + fuelCapacity: React.PropTypes.number.isRequired, onChange: React.PropTypes.func.isRequired }; @@ -21,35 +22,7 @@ export default class Fuel extends TranslatedComponent { constructor(props, context) { super(props); - const ship = this.props.ship; - - this.state = { - fuelCapacity: ship.fuelCapacity, - fuelLevel: 1, - }; - } - - /** - * Update the state if our ship changes - * @param {Object} nextProps Incoming/Next properties - * @return {boolean} Returns true if the component should be rerendered - */ - componentWillReceiveProps(nextProps) { - const { fuelLevel, fuelCapacity } = this.state; - const nextFuelCapacity = nextProps.ship.fuelCapacity; - - if (nextFuelCapacity != fuelCapacity) { - // We keep the absolute fuel amount the same if possible so recalculate the relative level - const nextFuelLevel = Math.min((fuelLevel * fuelCapacity) / nextFuelCapacity, 1); - - this.setState({ fuelLevel: nextFuelLevel, fuelCapacity: nextFuelCapacity }); - - // Notify if appropriate - if (nextFuelLevel * nextFuelCapacity != fuelLevel * fuelCapacity) { - this.props.onChange(nextFuelLevel * nextFuelCapacity); - } - } - return true; + this._fuelChange = this._fuelChange.bind(this); } /** @@ -57,8 +30,13 @@ export default class Fuel extends TranslatedComponent { * @param {number} fuelLevel percentage level from 0 to 1 */ _fuelChange(fuelLevel) { - this.setState({ fuelLevel }); - this.props.onChange(fuelLevel * this.state.fuelCapacity); + const { fuel, fuelCapacity } = this.props; + + const newFuel = fuelLevel * fuelCapacity; + // Only send an update if the fuel has changed significantly + if (Math.round(fuel * 10) != Math.round(newFuel * 10)) { + this.props.onChange(Math.round(newFuel * 10) / 10); + } } /** @@ -68,20 +46,20 @@ export default class Fuel extends TranslatedComponent { render() { const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context; const { formats, translate, units } = language; - const { fuelLevel, fuelCapacity } = this.state; + const { fuel, fuelCapacity } = this.props; return ( -

{translate('fuel carried')}: {formats.f2(fuelLevel * fuelCapacity)}{units.T}

+

{translate('fuel carried')}: {formats.f1(fuel)}{units.T}

- - + + ; } diff --git a/src/app/components/Pips.jsx b/src/app/components/Pips.jsx index 7c9f86ec..29051318 100644 --- a/src/app/components/Pips.jsx +++ b/src/app/components/Pips.jsx @@ -15,7 +15,9 @@ import Module from '../shipyard/Module'; */ export default class Pips extends TranslatedComponent { static propTypes = { - ship: React.PropTypes.object.isRequired, + sys: React.PropTypes.number.isRequired, + eng: React.PropTypes.number.isRequired, + wep: React.PropTypes.number.isRequired, onChange: React.PropTypes.func.isRequired }; @@ -26,24 +28,9 @@ export default class Pips extends TranslatedComponent { */ constructor(props, context) { super(props); - const ship = props.ship; - const pd = ship.standard[4].m; + const { sys, eng, wep } = props; this._keyDown = this._keyDown.bind(this); - - let pipsSvg = this._renderPips(2, 2, 2); - this.state = { - sys: 2, - eng: 2, - wep: 2, - sysCap: pd.getSystemsCapacity(), - engCap: pd.getEnginesCapacity(), - wepCap: pd.getWeaponsCapacity(), - sysRate: pd.getSystemsRechargeRate(), - engRate: pd.getEnginesRechargeRate(), - wepRate: pd.getWeaponsRechargeRate(), - pipsSvg - }; } /** @@ -60,41 +47,6 @@ export default class Pips extends TranslatedComponent { document.removeEventListener('keydown', this._keyDown); } - /** - * Update values if we change ship - * @param {Object} nextProps Incoming/Next properties - * @returns {boolean} Returns true if the component should be rerendered - */ - componentWillReceiveProps(nextProps) { - const { sysCap, engCap, wepCap, sysRate, engRate, wepRate } = this.state; - const nextShip = nextProps.ship; - const pd = nextShip.standard[4].m; - - const nextSysCap = pd.getSystemsCapacity(); - const nextEngCap = pd.getEnginesCapacity(); - const nextWepCap = pd.getWeaponsCapacity(); - const nextSysRate = pd.getSystemsRechargeRate(); - const nextEngRate = pd.getEnginesRechargeRate(); - const nextWepRate = pd.getWeaponsRechargeRate(); - if (nextSysCap != sysCap || - nextEngCap != engCap || - nextWepCap != wepCap || - nextSysRate != sysRate || - nextEngRate != engRate || - nextWepRate != wepRate) { - this.setState({ - sysCap: nextSysCap, - engCap: nextEngCap, - wepCap: nextWepCap, - sysRate: nextSysRate, - engRate: nextEngRate, - wepRate: nextWepRate - }); - } - - return true; - } - /** * Handle Key Down * @param {Event} e Keyboard Event @@ -142,10 +94,9 @@ export default class Pips extends TranslatedComponent { * Reset the capacitor */ _reset() { - let { sys, eng, wep } = this.state; + let { sys, eng, wep } = this.props; if (sys != 2 || eng != 2 || wep != 2) { sys = eng = wep = 2; - this.setState({ sys, eng, wep, pipsSvg: this._renderPips(sys, eng, wep) }); this.props.onChange(sys, eng, wep); } } @@ -154,7 +105,7 @@ export default class Pips extends TranslatedComponent { * Increment the SYS capacitor */ _incSys() { - let { sys, eng, wep } = this.state; + let { sys, eng, wep } = this.props; const required = Math.min(1, 4 - sys); if (required > 0) { @@ -181,7 +132,6 @@ export default class Pips extends TranslatedComponent { sys += 1; } } - this.setState({ sys, eng, wep, pipsSvg: this._renderPips(sys, eng, wep) }); this.props.onChange(sys, eng, wep); } } @@ -190,7 +140,7 @@ export default class Pips extends TranslatedComponent { * Increment the ENG capacitor */ _incEng() { - let { sys, eng, wep } = this.state; + let { sys, eng, wep } = this.props; const required = Math.min(1, 4 - eng); if (required > 0) { @@ -217,7 +167,6 @@ export default class Pips extends TranslatedComponent { eng += 1; } } - this.setState({ sys, eng, wep, pipsSvg: this._renderPips(sys, eng, wep) }); this.props.onChange(sys, eng, wep); } } @@ -226,7 +175,7 @@ export default class Pips extends TranslatedComponent { * Increment the WEP capacitor */ _incWep() { - let { sys, eng, wep } = this.state; + let { sys, eng, wep } = this.props; const required = Math.min(1, 4 - wep); if (required > 0) { @@ -253,7 +202,6 @@ export default class Pips extends TranslatedComponent { wep += 1; } } - this.setState({ sys, eng, wep, pipsSvg: this._renderPips(sys, eng, wep) }); this.props.onChange(sys, eng, wep); } } @@ -313,14 +261,14 @@ export default class Pips extends TranslatedComponent { */ render() { const { formats, translate, units } = this.context.language; - const { ship } = this.props; - const { sys, eng, wep, sysCap, engCap, wepCap, sysRate, engRate, wepRate, pipsSvg } = this.state; + const { sys, eng, wep } = this.props; const onSysClicked = this.onClick.bind(this, 'SYS'); const onEngClicked = this.onClick.bind(this, 'ENG'); const onWepClicked = this.onClick.bind(this, 'WEP'); const onRstClicked = this.onClick.bind(this, 'RST'); + const pipsSvg = this._renderPips(sys, eng, wep); return ( @@ -349,15 +297,3 @@ export default class Pips extends TranslatedComponent { ); } } -// -// -// -// -// -// -// -// -// -// -// -// diff --git a/src/app/components/ShipPicker.jsx b/src/app/components/ShipPicker.jsx index cebfa173..3b048b86 100644 --- a/src/app/components/ShipPicker.jsx +++ b/src/app/components/ShipPicker.jsx @@ -13,12 +13,12 @@ import cn from 'classnames'; export default class ShipPicker extends TranslatedComponent { static propTypes = { onChange: React.PropTypes.func.isRequired, - ship: React.PropTypes.object, + ship: React.PropTypes.string.isRequired, build: React.PropTypes.string }; static defaultProps = { - ship: new Ship('anaconda', Ships['anaconda'].properties, Ships['anaconda'].slots).buildWith(Ships['anaconda'].defaults) + ship: 'eagle' } /** @@ -33,44 +33,21 @@ export default class ShipPicker extends TranslatedComponent { this._toggleMenu = this._toggleMenu.bind(this); this._closeMenu = this._closeMenu.bind(this); - this.state = { - ship: props.ship, - build: props.build - }; - } - - /** - * Update the state if our ship changes - * @param {Object} nextProps Incoming/Next properties - * @return {boolean} Returns true if the component should be rerendered - */ - componentWillReceiveProps(nextProps) { - const { ship, build } = this.state; - const { nextShip, nextBuild } = nextProps; - - if (nextShip != undefined && nextShip != ship && nextBuild != build) { - this.setState({ ship: nextShip, build: nextBuild }); - } - return true; + this.state = { menuOpen: false }; } /** * Update ship - * @param {object} shipId the ship + * @param {object} ship the ship * @param {string} build the build, if present */ - _shipChange(shipId, build) { - const ship = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots); - if (build) { - // Ship is a particular build - ship.buildFrom(Persist.getBuild(shipId, build)); - } else { - // Ship is a stock build - ship.buildWith(Ships[shipId].defaults); - } + _shipChange(ship, build) { this._closeMenu(); - this.setState({ ship, build }); - this.props.onChange(ship, build); + + // Ensure that the ship has changed + if (ship !== this.props.ship || this.build !== this.props.build) { + this.props.onChange(ship, build); + } } /** @@ -78,7 +55,7 @@ export default class ShipPicker extends TranslatedComponent { * @returns {object} the picker menu */ _renderPickerMenu() { - const { ship, build } = this.state; + const { ship, build } = this.props; const _shipChange = this._shipChange; const builds = Persist.getBuilds(); @@ -86,7 +63,7 @@ export default class ShipPicker extends TranslatedComponent { for (let shipId of this.shipOrder) { const shipBuilds = []; // Add stock build - const stockSelected = (ship.id == shipId && !build); + const stockSelected = (ship == shipId && !build); shipBuilds.push(
  • Stock
  • ); if (builds[shipId]) { let buildNameOrder = Object.keys(builds[shipId]).sort(); @@ -126,9 +103,10 @@ export default class ShipPicker extends TranslatedComponent { render() { const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context; const { formats, translate, units } = language; - const { menuOpen, ship, build } = this.state; + const { ship, build } = this.props; + const { menuOpen } = this.state; - const shipString = ship.name + ': ' + (build ? build : translate('stock')); + const shipString = ship + ': ' + (build ? build : translate('stock')); return (
    e.stopPropagation() }>
    diff --git a/src/app/components/ShipSummaryTable.jsx b/src/app/components/ShipSummaryTable.jsx index 370cb03b..e4fb8edd 100644 --- a/src/app/components/ShipSummaryTable.jsx +++ b/src/app/components/ShipSummaryTable.jsx @@ -30,24 +30,20 @@ export default class ShipSummaryTable extends TranslatedComponent { let u = language.units; let formats = language.formats; let { time, int, round, f1, f2 } = 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.shield) { - sgRecover = time(ship.calcShieldRecovery()); - sgRecharge = time(ship.calcShieldRecharge()); - } - + const shieldGenerator = ship.findInternalByGroup('sg'); + const sgClassNames = cn({ warning: shieldGenerator && !ship.shield, muted: !shieldGenerator }); const timeToDrain = Calc.timeToDrainWep(ship, wep); + const canThrust = ship.canThrust(); + const canBoost = ship.canBoost(); return
    {translate('capacity')} ({units.MJ}){formats.f1(sysCap)}{formats.f1(engCap)}{formats.f1(wepCap)}
    {translate('recharge')} ({units.MW}){formats.f1(sysRate * (sys / 4))}{formats.f1(engRate * (eng / 4))}{formats.f1(wepRate * (wep / 4))}
    - - + + @@ -72,8 +68,8 @@ export default class ShipSummaryTable extends TranslatedComponent { - - + + diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx index b96c2ef3..583a5485 100644 --- a/src/app/pages/OutfittingPage.jsx +++ b/src/app/pages/OutfittingPage.jsx @@ -1,14 +1,17 @@ import React from 'react'; +// import Perf from 'react-addons-perf'; import { findDOMNode } from 'react-dom'; import { Ships } from 'coriolis-data/dist'; import cn from 'classnames'; import Page from './Page'; import Router from '../Router'; import Persist from '../stores/Persist'; +import * as Utils from '../utils/UtilityFunctions'; import Ship from '../shipyard/Ship'; import { toDetailedBuild } from '../shipyard/Serializer'; import { outfitURL } from '../utils/UrlGenerators'; import { FloppyDisk, Bin, Switch, Download, Reload, LinkIcon, ShoppingIcon } from '../components/SvgIcons'; +import LZString from 'lz-string'; import ShipSummaryTable from '../components/ShipSummaryTable'; import StandardSlotSection from '../components/StandardSlotSection'; import HardpointsSlotSection from '../components/HardpointsSlotSection'; @@ -46,7 +49,8 @@ export default class OutfittingPage extends Page { */ constructor(props, context) { super(props, context); - this.state = this._initState(context); + // window.Perf = Perf; + this.state = this._initState(props, context); this._keyDown = this._keyDown.bind(this); this._exportBuild = this._exportBuild.bind(this); this._pipsUpdated = this._pipsUpdated.bind(this); @@ -59,10 +63,11 @@ export default class OutfittingPage extends Page { /** * [Re]Create initial state from context + * @param {Object} props React component properties * @param {context} context React component context * @return {Object} New state object */ - _initState(context) { + _initState(props, context) { let params = context.route.params; let shipId = params.ship; let code = params.code; @@ -84,6 +89,8 @@ export default class OutfittingPage extends Page { this._getTitle = getTitle.bind(this, data.properties.name); + // Obtain ship control from code + const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = this._obtainControlFromCode(ship, code); return { error: null, title: this._getTitle(buildName), @@ -94,14 +101,15 @@ export default class OutfittingPage extends Page { ship, code, savedCode, - sys: 2, - eng: 2, - wep: 2, - fuel: ship.fuelCapacity, - cargo: 0, - boost: false, - engagementRange: 1000, - opponent: new Ship('anaconda', Ships['anaconda'].properties, Ships['anaconda'].slots).buildWith(Ships['anaconda'].defaults) + sys, + eng, + wep, + boost, + fuel, + cargo, + opponent, + opponentBuild, + engagementRange }; } @@ -123,6 +131,76 @@ export default class OutfittingPage extends Page { this.setState(stateChanges); } + /** + * Update the control part of the route + */ + _updateRouteOnControlChange() { + const { ship, shipId, buildName } = this.state; + const code = this._fullCode(ship); + this._updateRoute(shipId, buildName, code); + this.setState({ code }); + } + + /** + * Provide a full code for this ship, including any additions due to the outfitting page + * @param {Object} ship the ship + * @param {number} fuel the fuel carried by the ship (if different from that in state) + * @param {number} cargo the cargo carried by the ship (if different from that in state) + * @returns {string} the code for this ship + */ + _fullCode(ship, fuel, cargo) { + return `${ship.toString()}.${LZString.compressToBase64(this._controlCode(fuel, cargo))}`; + } + + /** + * Obtain the control information from the build code + * @param {Object} ship The ship + * @param {string} code The build code + * @returns {Object} The control information + */ + _obtainControlFromCode(ship, code) { + // Defaults + let sys = 2; + let eng = 2; + let wep = 2; + let boost = false; + let fuel = ship.fuelCapacity; + let cargo = ship.cargoCapacity; + let opponent = new Ship('eagle', Ships['eagle'].properties, Ships['eagle'].slots).buildWith(Ships['eagle'].defaults); + let opponentBuild = undefined; + let engagementRange = 1000; + + // Obtain updates from code, if available + if (code) { + const parts = code.split('.'); + if (parts.length >= 5) { + // We have control information in the code + const control = LZString.decompressFromBase64(Utils.fromUrlSafe(parts[4])).split('/'); + sys = parseFloat(control[0]); + eng = parseFloat(control[1]); + wep = parseFloat(control[2]); + boost = control[3] == 1 ? true : false; + fuel = parseFloat(control[4]); + cargo = parseInt(control[5]); + if (control[6]) { + const shipId = control[6]; + opponent = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots); + if (control[7] && Persist.getBuild(shipId, control[7])) { + // Ship is a particular build + opponent.buildFrom(Persist.getBuild(shipId, control[7])); + opponentBuild = control[7]; + } else { + // Ship is a stock build + opponent.buildWith(Ships[shipId].defaults); + } + } + engagementRange = parseInt(control[8]); + } + } + + return { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange }; + } + /** * Triggered when pips have been updated * @param {number} sys SYS pips @@ -130,7 +208,7 @@ export default class OutfittingPage extends Page { * @param {number} wep WEP pips */ _pipsUpdated(sys, eng, wep) { - this.setState({ sys, eng, wep }); + this.setState({ sys, eng, wep }, () => this._updateRouteOnControlChange()); } /** @@ -138,7 +216,7 @@ export default class OutfittingPage extends Page { * @param {boolean} boost true if boosting */ _boostUpdated(boost) { - this.setState({ boost }); + this.setState({ boost }, () => this._updateRouteOnControlChange()); } /** @@ -146,7 +224,7 @@ export default class OutfittingPage extends Page { * @param {number} fuel the amount of fuel, in T */ _fuelUpdated(fuel) { - this.setState({ fuel }); + this.setState({ fuel }, () => this._updateRouteOnControlChange()); } /** @@ -154,7 +232,7 @@ export default class OutfittingPage extends Page { * @param {number} cargo the amount of cargo, in T */ _cargoUpdated(cargo) { - this.setState({ cargo }); + this.setState({ cargo }, () => this._updateRouteOnControlChange()); } /** @@ -162,24 +240,44 @@ export default class OutfittingPage extends Page { * @param {number} engagementRange the engagement range, in m */ _engagementRangeUpdated(engagementRange) { - this.setState({ engagementRange }); + this.setState({ engagementRange }, () => this._updateRouteOnControlChange()); } /** * Triggered when target ship has been updated - * @param {object} opponent the opponent's ship - * @param {string} opponentBuild the name of the opponent's build + * @param {string} opponent the opponent's ship model + * @param {string} opponentBuild the name of the opponent's build */ _opponentUpdated(opponent, opponentBuild) { - this.setState({ opponent, opponentBuild }); + const opponentShip = new Ship(opponent, Ships[opponent].properties, Ships[opponent].slots); + if (opponentBuild && Persist.getBuild(opponent, opponentBuild)) { + // Ship is a particular build + opponentShip.buildFrom(Persist.getBuild(opponent, opponentBuild)); + } else { + // Ship is a stock build + opponentShip.buildWith(Ships[opponent].defaults); + } + + this.setState({ opponent: opponentShip, opponentBuild }, () => this._updateRouteOnControlChange()); + } + + /** + * Set the control code for this outfitting page + * @param {number} fuel the fuel carried by the ship (if different from that in state) + * @param {number} cargo the cargo carried by the ship (if different from that in state) + * @returns {string} The control code + */ + _controlCode(fuel, cargo) { + const { sys, eng, wep, boost, opponent, opponentBuild, engagementRange } = this.state; + const code = `${sys}/${eng}/${wep}/${boost ? 1 : 0}/${fuel || this.state.fuel}/${cargo || this.state.cargo}/${opponent.id}/${opponentBuild ? opponentBuild : ''}/${engagementRange}`; + return code; } /** * Save the current build */ _saveBuild() { - let code = this.state.ship.toString(); - let { buildName, newBuildName, shipId } = this.state; + const { code, buildName, newBuildName, shipId } = this.state; if (buildName === newBuildName) { Persist.saveBuild(shipId, buildName, code); @@ -196,9 +294,8 @@ export default class OutfittingPage extends Page { * Rename the current build */ _renameBuild() { - let { buildName, newBuildName, shipId, ship } = this.state; + const { code, buildName, newBuildName, shipId, ship } = this.state; if (buildName != newBuildName && newBuildName.length) { - let code = ship.toString(); Persist.deleteBuild(shipId, buildName); Persist.saveBuild(shipId, newBuildName, code); this._updateRoute(shipId, newBuildName, code); @@ -210,16 +307,31 @@ export default class OutfittingPage extends Page { * Reload build from last save */ _reloadBuild() { - this.state.ship.buildFrom(this.state.savedCode); - this._shipUpdated(); + this.setState({ code: this.state.savedCode }, () => this._codeUpdated()); } /** * Reset build to Stock/Factory defaults */ _resetBuild() { - this.state.ship.buildWith(Ships[this.state.shipId].defaults); - this._shipUpdated(); + const { ship, shipId, buildName } = this.state; + // Rebuild ship + ship.buildWith(Ships[shipId].defaults); + // Reset controls + const code = ship.toString(); + const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = this._obtainControlFromCode(ship, code); + // Update state, and refresh the ship + this.setState({ + sys, + eng, + wep, + boost, + fuel, + cargo, + opponent, + opponentBuild, + engagementRange + }, () => this._updateRoute(shipId, buildName, code)); } /** @@ -244,14 +356,43 @@ export default class OutfittingPage extends Page { } /** - * Trigger render on ship model change + * Called when the code for the ship has been updated, to synchronise the rest of the data + */ + _codeUpdated() { + const { code, ship, shipId, buildName } = this.state; + + // Rebuild ship from the code + this.state.ship.buildFrom(code); + + // Obtain controls from the code + const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = this._obtainControlFromCode(ship, code); + // Update state, and refresh the route when complete + this.setState({ + sys, + eng, + wep, + boost, + fuel, + cargo, + opponent, + opponentBuild, + engagementRange + }, () => this._updateRoute(shipId, buildName, code)); + } + + /** + * Called when the ship has been updated, to set the code and then update accordingly */ _shipUpdated() { - let { shipId, buildName, ship } = this.state; - let code = ship.toString(); - - this._updateRoute(shipId, buildName, code); - this.setState({ code }); + let { ship, shipId, buildName, cargo, fuel } = this.state; + if (cargo > ship.cargoCapacity) { + cargo = ship.cargoCapacity; + } + if (fuel > ship.fuelCapacity) { + fuel = ship.fuelCapacity; + } + const code = this._fullCode(ship, fuel, cargo); + this.setState({ code, cargo, fuel }, () => this._updateRoute(shipId, buildName, code)); } /** @@ -271,7 +412,7 @@ export default class OutfittingPage extends Page { */ componentWillReceiveProps(nextProps, nextContext) { if (this.context.route !== nextContext.route) { // Only reinit state if the route has changed - this.setState(this._initState(nextContext)); + this.setState(this._initState(nextProps, nextContext)); } } @@ -340,16 +481,23 @@ export default class OutfittingPage extends Page { shipUpdated = this._shipUpdated, canSave = (newBuildName || buildName) && code !== savedCode, canRename = buildName && newBuildName && buildName != newBuildName, - canReload = savedCode && canSave, - hStr = ship.getHardpointsString() + '.' + ship.getModificationsString(), - iStr = ship.getInternalString() + '.' + ship.getModificationsString(); + canReload = savedCode && canSave; // Code can be blank for a default loadout. Prefix it with the ship name to ensure that changes in default ships is picked up code = ship.name + (code || ''); // Markers are used to propagate state changes without requiring a deep comparison of the ship, as that takes a long time + const _sStr = ship.getStandardString(); + const _iStr = ship.getInternalString(); + const _hStr = ship.getHardpointsString(); + const _pStr = `${ship.getPowerEnabledString()}${ship.getPowerPrioritiesString()}`; + const _mStr = ship.getModificationsString(); + + const standardSlotMarker = `${ship.name}${_sStr}${_pStr}${_mStr}`; + const internalSlotMarker = `${ship.name}${_iStr}${_pStr}${_mStr}`; + const hardpointsSlotMarker = `${ship.name}${_hStr}${_pStr}${_mStr}`; const boostMarker = `${ship.canBoost()}`; - const shipSummaryMarker = `${ship.toString()}:${eng}:${fuel}:${cargo}`; + const shipSummaryMarker = `${ship.toString()}${eng}${fuel}${cargo}`; return (
    @@ -386,10 +534,10 @@ export default class OutfittingPage extends Page { {/* Main tables */} - - - - + + + + {/* Control of ship and opponent */}
    @@ -397,28 +545,28 @@ export default class OutfittingPage extends Page {

    {translate('ship control')}

    - +
    - +
    - +
    - { ship.cargoCapacity > 0 ? : null } + { ship.cargoCapacity > 0 ? : null }

    {translate('opponent')}

    - +
    - +
    {/* Tabbed subpages */} diff --git a/src/app/shipyard/Calculations.js b/src/app/shipyard/Calculations.js index 183ec30e..30432949 100644 --- a/src/app/shipyard/Calculations.js +++ b/src/app/shipyard/Calculations.js @@ -808,9 +808,9 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour /** * Calculate time to drain WEP capacitor - * @param {object} ship The ship - * @param {number} wep Pips to WEP - * @return The time to drain the WEP capacitor, in seconds + * @param {object} ship The ship + * @param {number} wep Pips to WEP + * @returns {number} The time to drain the WEP capacitor, in seconds */ export function timeToDrainWep(ship, wep) { let totalSEps = 0; @@ -835,6 +835,12 @@ export function timeToDrainWep(ship, wep) { /** * Calculate the time to deplete an amount of shields or armour + * @param {number} amount The amount to be depleted + * @param {number} dps The depletion per second + * @param {number} eps The energy drained per second + * @param {number} capacity The initial energy capacity + * @param {number} recharge The energy recharged per second + * @returns {number} The number of seconds to deplete to 0 */ export function timeToDeplete(amount, dps, eps, capacity, recharge) { const drainPerSecond = eps - recharge;
    {translate('speed')}{translate('boost')}{translate('speed')}{translate('boost')} {translate('DPS')} {translate('EPS')} {translate('TTD')}
    { ship.canThrust() ? {int(ship.calcSpeed(eng, fuel, cargo, false))}{u['m/s']} : 0 }{ ship.canBoost() ? {int(ship.calcSpeed(eng, fuel, cargo, true))}{u['m/s']} : 0 }{ canThrust ? {int(ship.calcSpeed(eng, fuel, cargo, false))}{u['m/s']} : 0 }{ canBoost ? {int(ship.calcSpeed(eng, fuel, cargo, true))}{u['m/s']} : 0 } {f1(ship.totalDps)} {f1(ship.totalEps)} {timeToDrain === Infinity ? '∞' : time(timeToDrain)}