diff --git a/src/app/components/.AvailableModulesMenu.jsx.swp b/src/app/components/.AvailableModulesMenu.jsx.swp new file mode 100644 index 00000000..12d3cfc9 Binary files /dev/null and b/src/app/components/.AvailableModulesMenu.jsx.swp differ diff --git a/src/app/components/.BattleCentre.jsx.swp b/src/app/components/.BattleCentre.jsx.swp new file mode 100644 index 00000000..a8be2023 Binary files /dev/null and b/src/app/components/.BattleCentre.jsx.swp differ diff --git a/src/app/components/BattleCentre.jsx b/src/app/components/BattleCentre.jsx index 88ce19eb..addfe793 100644 --- a/src/app/components/BattleCentre.jsx +++ b/src/app/components/BattleCentre.jsx @@ -1,5 +1,6 @@ import React from 'react'; import TranslatedComponent from './TranslatedComponent'; +import Ship from '../shipyard/Ship'; import { Ships } from 'coriolis-data/dist'; import Slider from './Slider'; import Pips from './Pips'; @@ -8,6 +9,7 @@ import Cargo from './Cargo'; import Movement from './Movement'; import EngagementRange from './EngagementRange'; import ShipPicker from './ShipPicker'; +import Shields from './Shields'; /** * Battle centre allows you to pit your current build against another ship, @@ -27,26 +29,29 @@ export default class BattleCentre extends TranslatedComponent { super(props); const { ship } = this.props; - const opponent = BattleCentre.DEFAULT_OPPONENT; this._cargoUpdated = this._cargoUpdated.bind(this); this._fuelUpdated = this._fuelUpdated.bind(this); this._pipsUpdated = this._pipsUpdated.bind(this); this._engagementRangeUpdated = this._engagementRangeUpdated.bind(this); - this._targetShipUpdated = this._targetShipUpdated.bind(this); + this._opponentUpdated = this._opponentUpdated.bind(this); this.state = { - // Pips sys: 2, eng: 2, wep: 2, fuel: ship.fuelCapacity, cargo: ship.cargoCapacity, engagementRange: 1500, - targetShip: Ships['anaconda'] + opponent: new Ship('anaconda', Ships['anaconda'].properties, Ships['anaconda'].slots) }; } + /** + * Update state based on property and context changes + * @param {Object} nextProps Incoming/Next properties + * @returns {boolean} true if an update is required + */ componentWillReceiveProps(nextProps) { // Rather than try to keep track of what changes our children require we force an update and let them work it out this.forceUpdate(); @@ -55,6 +60,9 @@ export default class BattleCentre extends TranslatedComponent { /** * Triggered when pips have been updated + * @param {number} sys SYS pips + * @param {number} eng ENG pips + * @param {number} wep WEP pips */ _pipsUpdated(sys, eng, wep) { this.setState({ sys, eng, wep }); @@ -62,6 +70,7 @@ export default class BattleCentre extends TranslatedComponent { /** * Triggered when fuel has been updated + * @param {number} fuel the amount of fuel, in T */ _fuelUpdated(fuel) { this.setState({ fuel }); @@ -69,6 +78,7 @@ export default class BattleCentre extends TranslatedComponent { /** * Triggered when cargo has been updated + * @param {number} cargo the amount of cargo, in T */ _cargoUpdated(cargo) { this.setState({ cargo }); @@ -76,6 +86,7 @@ export default class BattleCentre extends TranslatedComponent { /** * Triggered when engagement range has been updated + * @param {number} engagementRange the engagement range, in m */ _engagementRangeUpdated(engagementRange) { this.setState({ engagementRange }); @@ -83,9 +94,11 @@ export default class BattleCentre extends TranslatedComponent { /** * Triggered when target ship has been updated + * @param {object} opponent the opponent's ship + * @param {string} opponentBuild the name of the opponent's build */ - _targetShipUpdated(targetShip, targetBuild) { - this.setState({ targetShip, targetBuild: targetBuild }); + _opponentUpdated(opponent, opponentBuild) { + this.setState({ opponent, opponentBuild }); } /** @@ -95,13 +108,17 @@ export default class BattleCentre extends TranslatedComponent { render() { const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context; const { formats, translate, units } = language; - const { sys, eng, wep, cargo, fuel, engagementRange } = this.state; + const { sys, eng, wep, cargo, fuel, engagementRange, opponent } = this.state; const { ship } = this.props; + // Markers are used to propagate state changes + const movementMarker = '' + ship.topSpeed + ':' + ship.pitch + ':' + ship.roll + ':' + ship.yaw; + const shieldMarker = '' + ship.shield + ':' + ship.cells + ':' + ship.shieldExplRes + ':' + ship.shieldKinRes + ':' + ship.shieldThermRes; + return ( {translate('battle centre')} - + @@ -111,7 +128,10 @@ export default class BattleCentre extends TranslatedComponent { - + + + + ); diff --git a/src/app/components/Cargo.jsx b/src/app/components/Cargo.jsx index 25ec985a..bad1e2cf 100644 --- a/src/app/components/Cargo.jsx +++ b/src/app/components/Cargo.jsx @@ -25,7 +25,7 @@ export default class Cargo extends TranslatedComponent { this.state = { cargoCapacity: ship.cargoCapacity, - cargoLevel: 1, + cargoLevel: 0, }; } diff --git a/src/app/components/EngagementRange.jsx b/src/app/components/EngagementRange.jsx index c09b56ec..2f624bd0 100644 --- a/src/app/components/EngagementRange.jsx +++ b/src/app/components/EngagementRange.jsx @@ -26,7 +26,7 @@ export default class Range extends TranslatedComponent { const maxRange = this._calcMaxRange(ship); this.state = { - maxRange: maxRange, + maxRange, rangeLevel: 1, }; } @@ -75,7 +75,7 @@ export default class Range extends TranslatedComponent { /** * Update range - * @param {number} range percentage level from 0 to 1 + * @param {number} rangeLevel percentage level from 0 to 1 */ _rangeChange(rangeLevel) { const { maxRange } = this.state; diff --git a/src/app/components/Movement.jsx b/src/app/components/Movement.jsx index f61843df..a098ea2e 100644 --- a/src/app/components/Movement.jsx +++ b/src/app/components/Movement.jsx @@ -7,6 +7,7 @@ import TranslatedComponent from './TranslatedComponent'; */ export default class Movement extends TranslatedComponent { static propTypes = { + marker: React.PropTypes.string.isRequired, ship: React.PropTypes.object.isRequired, eng: React.PropTypes.number.isRequired, fuel: React.PropTypes.number.isRequired, @@ -44,7 +45,6 @@ export default class Movement extends TranslatedComponent { const { formats, translate, units } = language; const { boost } = this.state; - // return ( @@ -73,13 +73,13 @@ export default class Movement extends TranslatedComponent { // Speed {formats.int(ship.calcSpeed(eng, fuel, cargo, boost))}m/s // Pitch - {formats.f1(ship.calcPitch(eng, fuel, cargo, boost))}°/s + {formats.int(ship.calcPitch(eng, fuel, cargo, boost))}°/s // Roll - {formats.f1(ship.calcRoll(eng, fuel, cargo, boost))}°/s + {formats.int(ship.calcRoll(eng, fuel, cargo, boost))}°/s // Yaw - {formats.f1(ship.calcYaw(eng, fuel, cargo, boost))}°/s + {formats.int(ship.calcYaw(eng, fuel, cargo, boost))}°/s - Boost + { ship.canBoost() ? Boost : null } ); } } diff --git a/src/app/components/Pips.jsx b/src/app/components/Pips.jsx index dfc7c7ce..8d123e82 100644 --- a/src/app/components/Pips.jsx +++ b/src/app/components/Pips.jsx @@ -242,6 +242,7 @@ export default class Pips extends TranslatedComponent { /** * Handle a click + * @param {string} which Which item was clicked */ onClick(which) { if (which == 'SYS') { diff --git a/src/app/components/Shields.jsx b/src/app/components/Shields.jsx new file mode 100644 index 00000000..376f6f88 --- /dev/null +++ b/src/app/components/Shields.jsx @@ -0,0 +1,194 @@ +import React from 'react'; +import cn from 'classnames'; +import TranslatedComponent from './TranslatedComponent'; +import * as Calc from '../shipyard/Calculations'; +import { DamageAbsolute, DamageExplosive, DamageKinetic, DamageThermal } from './SvgIcons'; + +/** + * Shields + * Effective shield strength (including SCBs) + * Time for opponent to down shields + * - need sustained DPS for each type of damage (K/T/E/R) + * - turn in to % of shields removed per second + */ +export default class Shields extends TranslatedComponent { + static propTypes = { + marker: React.PropTypes.string.isRequired, + ship: React.PropTypes.object.isRequired, + opponent: React.PropTypes.object.isRequired, + sys: React.PropTypes.number.isRequired + }; + + /** + * Constructor + * @param {Object} props React Component properties + */ + constructor(props) { + super(props); + + const { shield, absolute, explosive, kinetic, thermal } = this._calcMetrics(props.ship, props.opponent, props.sys); + this.state = { shield, absolute, explosive, kinetic, thermal }; + } + + /** + * Update the state if our properties change + * @param {Object} nextProps Incoming/Next properties + * @return {boolean} Returns true if the component should be rerendered + */ + componentWillReceiveProps(nextProps) { + if (this.props.marker != nextProps.marker || this.props.sys != nextProps.sys) { + const { shield, absolute, explosive, kinetic, thermal } = this._calcMetrics(nextProps.ship, nextProps.opponent, nextProps.sys); + this.setState({ shield, absolute, explosive, kinetic, thermal }); + return true; + } + } + + /** + * Calculate shield metrics + * @param {Object} ship The ship + * @param {Object} opponent The opponent ship + * @param {int} sys The opponent ship + * @returns {Object} Shield metrics + */ + _calcMetrics(ship, opponent, sys) { + const sysResistance = this._calcSysResistance(sys); + + const shieldGenerator = ship.findShieldGenerator(); + + // Boosters + let boost = 1; + let boosterExplDmg = 1; + let boosterKinDmg = 1; + let boosterThermDmg = 1; + for (let slot of ship.hardpoints) { + if (slot.enabled && slot.m && slot.m.grp == 'sb') { + boost += slot.m.getShieldBoost(); + boosterExplDmg = boosterExplDmg * (1 - slot.m.getExplosiveResistance()); + boosterKinDmg = boosterKinDmg * (1 - slot.m.getKineticResistance()); + boosterThermDmg = boosterThermDmg * (1 - slot.m.getThermalResistance()); + } + } + + // Calculate diminishing returns for boosters + boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5); + // Remove base shield generator strength + boost -= 1; + boosterExplDmg = boosterExplDmg > 0.7 ? boosterExplDmg : 0.7 - (0.7 - boosterExplDmg) / 2; + boosterKinDmg = boosterKinDmg > 0.7 ? boosterExplDmg : 0.7 - (0.7 - boosterKinDmg) / 2; + boosterThermDmg = boosterThermDmg > 0.7 ? boosterExplDmg : 0.7 - (0.7 - boosterThermDmg) / 2; + + const generatorStrength = Calc.shieldStrength(ship.hullMass, ship.baseShieldStrength, shieldGenerator, 1); + const boostersStrength = generatorStrength * boost; + const shield = { + generator: generatorStrength, + boosters: boostersStrength, + total: generatorStrength + boostersStrength + }; + + // Resistances have three components: the shield generator, the shield boosters and the SYS pips. + // We re-cast these as damage percentages + const absolute = { + generator: 1, + boosters: 1, + sys: 1 - sysResistance, + total: 1 - sysResistance + }; + + const explosive = { + generator: 1 - shieldGenerator.getExplosiveResistance(), + boosters: boosterExplDmg, + sys: (1 - sysResistance), + total: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - sysResistance) + }; + + const kinetic = { + generator: 1 - shieldGenerator.getKineticResistance(), + boosters: boosterKinDmg, + sys: (1 - sysResistance), + total: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - sysResistance) + }; + + const thermal = { + generator: 1 - shieldGenerator.getThermalResistance(), + boosters: boosterThermDmg, + sys: (1 - sysResistance), + total: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - sysResistance) + }; + + return { shield, absolute, explosive, kinetic, thermal }; + } + + /** + * Calculate the resistance provided by SYS pips + * @param {integer} sys the value of the SYS pips + * @returns {integer} the resistance for the given pips + */ + _calcSysResistance(sys) { + return Math.pow(sys,0.85) * 0.6 / Math.pow(4,0.85); + } + + /** + * Render shields + * @return {React.Component} contents + */ + render() { + const { ship, sys } = this.props; + const { language, tooltip, termtip } = this.context; + const { formats, translate, units } = language; + const { shield, absolute, explosive, kinetic, thermal } = this.state; + + const shieldTooltipDetails = []; + shieldTooltipDetails.push({translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}); + shieldTooltipDetails.push({translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}); + + const absoluteTooltipDetails = []; + absoluteTooltipDetails.push({translate('generator') + ' ' + formats.pct1(absolute.generator)}); + absoluteTooltipDetails.push({translate('boosters') + ' ' + formats.pct1(absolute.boosters)}); + absoluteTooltipDetails.push({translate('power distributor') + ' ' + formats.pct1(absolute.sys)}); + + const explosiveTooltipDetails = []; + explosiveTooltipDetails.push({translate('generator') + ' ' + formats.pct1(explosive.generator)}); + explosiveTooltipDetails.push({translate('boosters') + ' ' + formats.pct1(explosive.boosters)}); + explosiveTooltipDetails.push({translate('power distributor') + ' ' + formats.pct1(explosive.sys)}); + + const kineticTooltipDetails = []; + kineticTooltipDetails.push({translate('generator') + ' ' + formats.pct1(kinetic.generator)}); + kineticTooltipDetails.push({translate('boosters') + ' ' + formats.pct1(kinetic.boosters)}); + kineticTooltipDetails.push({translate('power distributor') + ' ' + formats.pct1(kinetic.sys)}); + + const thermalTooltipDetails = []; + thermalTooltipDetails.push({translate('generator') + ' ' + formats.pct1(thermal.generator)}); + thermalTooltipDetails.push({translate('boosters') + ' ' + formats.pct1(thermal.boosters)}); + thermalTooltipDetails.push({translate('power distributor') + ' ' + formats.pct1(thermal.sys)}); + + return ( + + + + + {shieldTooltipDetails})} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('shields')}: {formats.int(shield.total)}{units.MJ} + + + {translate('damage from')} + + + {absoluteTooltipDetails})} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(absolute.total)} + + + + {explosiveTooltipDetails})} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(explosive.total)} + + + + {kineticTooltipDetails})} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(kinetic.total)} + + + + {thermalTooltipDetails})} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(thermal.total)} + + + + + ); + } +} diff --git a/src/app/components/ShipPicker.jsx b/src/app/components/ShipPicker.jsx index 49b67a2e..9f319a14 100644 --- a/src/app/components/ShipPicker.jsx +++ b/src/app/components/ShipPicker.jsx @@ -56,7 +56,8 @@ export default class ShipPicker extends TranslatedComponent { /** * Update ship - * @param {object} ship the ship + * @param {object} shipId the ship + * @param {string} build the build, if present */ _shipChange(shipId, build) { const ship = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots); @@ -71,6 +72,7 @@ export default class ShipPicker extends TranslatedComponent { /** * Render the menu for the picker + * @returns {object} the picker menu */ _renderPickerMenu() { const { ship, build } = this.state; diff --git a/src/app/shipyard/Calculations.js b/src/app/shipyard/Calculations.js index e5b56dc0..7e74913b 100644 --- a/src/app/shipyard/Calculations.js +++ b/src/app/shipyard/Calculations.js @@ -174,6 +174,20 @@ function normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, bas res]; } +/** + * Calculate a single value + * @param {number} minMass the minimum mass of the thrusters + * @param {number} optMass the optimum mass of the thrusters + * @param {number} maxMass the maximum mass of the thrusters + * @param {number} minMul the minimum multiplier of the thrusters + * @param {number} optMul the optimum multiplier of the thrusters + * @param {number} maxMul the maximum multiplier of the thrusters + * @param {number} mass the mass of the ship + * @param {base} base the base value from which to calculate + * @param {number} engpip the multiplier per pip to engines + * @param {number} eng the pips to engines + * @returns {number} the resultant value + */ function calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, base, engpip, eng) { const xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass)); const exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass))); @@ -184,6 +198,17 @@ function calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, base return res * (1 - (engpip * (4 - eng))); } +/** + * Calculate speed for a given setup + * @param {number} mass the mass of the ship + * @param {number} baseSpeed the base speed of the ship + * @param {ojbect} thrusters the thrusters of the ship + * @param {number} engpip the multiplier per pip to engines + * @param {number} eng the pips to engines + * @param {number} boostFactor the boost factor for ths ship + * @param {boolean} boost true if the boost is activated + * @returns {number} the resultant speed + */ export function calcSpeed(mass, baseSpeed, thrusters, engpip, eng, boostFactor, boost) { // thrusters might be a module or a template; handle either here const minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass; @@ -201,6 +226,17 @@ export function calcSpeed(mass, baseSpeed, thrusters, engpip, eng, boostFactor, return result; } +/** + * Calculate pitch for a given setup + * @param {number} mass the mass of the ship + * @param {number} basePitch the base pitch of the ship + * @param {ojbect} thrusters the thrusters of the ship + * @param {number} engpip the multiplier per pip to engines + * @param {number} eng the pips to engines + * @param {number} boostFactor the boost factor for ths ship + * @param {boolean} boost true if the boost is activated + * @returns {number} the resultant pitch + */ export function calcPitch(mass, basePitch, thrusters, engpip, eng, boostFactor, boost) { // thrusters might be a module or a template; handle either here let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass; @@ -218,6 +254,17 @@ export function calcPitch(mass, basePitch, thrusters, engpip, eng, boostFactor, return result; } +/** + * Calculate roll for a given setup + * @param {number} mass the mass of the ship + * @param {number} baseRoll the base roll of the ship + * @param {ojbect} thrusters the thrusters of the ship + * @param {number} engpip the multiplier per pip to engines + * @param {number} eng the pips to engines + * @param {number} boostFactor the boost factor for ths ship + * @param {boolean} boost true if the boost is activated + * @returns {number} the resultant roll + */ export function calcRoll(mass, baseRoll, thrusters, engpip, eng, boostFactor, boost) { // thrusters might be a module or a template; handle either here let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass; @@ -235,6 +282,17 @@ export function calcRoll(mass, baseRoll, thrusters, engpip, eng, boostFactor, bo return result; } +/** + * Calculate yaw for a given setup + * @param {number} mass the mass of the ship + * @param {number} baseYaw the base yaw of the ship + * @param {ojbect} thrusters the thrusters of the ship + * @param {number} engpip the multiplier per pip to engines + * @param {number} eng the pips to engines + * @param {number} boostFactor the boost factor for ths ship + * @param {boolean} boost true if the boost is activated + * @returns {number} the resultant yaw + */ export function calcYaw(mass, baseYaw, thrusters, engpip, eng, boostFactor, boost) { // thrusters might be a module or a template; handle either here let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass; diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index a9b99652..10da4582 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -1286,13 +1286,13 @@ export default class Ship { shield = Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sgSlot.m, 1); shieldExplRes = 1 - sgSlot.m.getExplosiveResistance(); shieldExplDRStart = shieldExplRes * 0.7; - shieldExplDREnd = shieldExplRes * 0; // Currently don't know where this is + shieldExplDREnd = 0; shieldKinRes = 1 - sgSlot.m.getKineticResistance(); shieldKinDRStart = shieldKinRes * 0.7; - shieldKinDREnd = shieldKinRes * 0; // Currently don't know where this is + shieldKinDREnd = 0; shieldThermRes = 1 - sgSlot.m.getThermalResistance(); shieldThermDRStart = shieldThermRes * 0.7; - shieldThermDREnd = shieldThermRes * 0; // Currently don't know where this is + shieldThermDREnd = 0; // Shield from boosters for (let slot of this.hardpoints) {