diff --git a/__tests__/fixtures/anaconda-test-detailed-export-v4.json b/__tests__/fixtures/anaconda-test-detailed-export-v4.json index 65893b57..32d622cf 100644 --- a/__tests__/fixtures/anaconda-test-detailed-export-v4.json +++ b/__tests__/fixtures/anaconda-test-detailed-export-v4.json @@ -310,7 +310,7 @@ "unladenRange": 18.74, "yaw": 10, "fullTankRange": 18.36, - "hardness": 170, + "hardness": 65, "ladenRange": 16.59, "unladenFastestRange": 74.2, "ladenFastestRange": 66.96, diff --git a/src/app/components/AvailableModulesMenu.jsx b/src/app/components/AvailableModulesMenu.jsx index fba8ad15..1d3df29c 100644 --- a/src/app/components/AvailableModulesMenu.jsx +++ b/src/app/components/AvailableModulesMenu.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import * as ModuleUtils from '../shipyard/ModuleUtils'; import { findDOMNode } from 'react-dom'; import TranslatedComponent from './TranslatedComponent'; import { stopCtxPropagation } from '../utils/UtilityFunctions'; @@ -213,7 +214,14 @@ export default class AvailableModulesMenu extends TranslatedComponent { for (let i = 0; i < sortedModules.length; i++) { let m = sortedModules[i]; let mount = null; - let disabled = m.maxmass && (mass + (m.mass ? m.mass : 0)) > m.maxmass; + let disabled = false; + if (ModuleUtils.isShieldGenerator(m.grp)) { + // Shield generators care about maximum hull mass + disabled = mass > m.maxmass; + } else if (m.maxmass) { + // Thrusters care about total mass + disabled = mass + m.mass > m.maxmass; + } let active = mountedModule && mountedModule.id === m.id; let classes = cn(m.name ? 'lc' : 'c', { warning: !disabled && warningFunc && warningFunc(m), diff --git a/src/app/components/Defence.jsx b/src/app/components/Defence.jsx index 7d6f5e78..545e6eac 100644 --- a/src/app/components/Defence.jsx +++ b/src/app/components/Defence.jsx @@ -61,50 +61,90 @@ export default class Defence extends TranslatedComponent { const shieldSourcesData = []; const effectiveShieldData = []; const shieldDamageTakenData = []; - const shieldTooltipDetails = []; - const shieldAbsoluteTooltipDetails = []; - const shieldExplosiveTooltipDetails = []; - const shieldKineticTooltipDetails = []; - const shieldThermalTooltipDetails = []; + const shieldSourcesTt = []; + const shieldDamageTakenAbsoluteTt = []; + const shieldDamageTakenExplosiveTt = []; + const shieldDamageTakenKineticTt = []; + const shieldDamageTakenThermalTt = []; + const effectiveShieldAbsoluteTt = []; + const effectiveShieldExplosiveTt = []; + const effectiveShieldKineticTt = []; + const effectiveShieldThermalTt = []; let maxEffectiveShield = 0; if (shield.total) { shieldSourcesData.push({ value: Math.round(shield.generator), label: translate('generator') }); shieldSourcesData.push({ value: Math.round(shield.boosters), label: translate('boosters') }); shieldSourcesData.push({ value: Math.round(shield.cells), label: translate('cells') }); - if (shield.generator > 0) shieldTooltipDetails.push(
{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}
); - if (shield.boosters > 0) shieldTooltipDetails.push(
{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}
); - if (shield.cells > 0) shieldTooltipDetails.push(
{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}
); + if (shield.generator > 0) { + shieldSourcesTt.push(
{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}
); + effectiveShieldAbsoluteTt.push(
{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}
); + effectiveShieldExplosiveTt.push(
{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}
); + effectiveShieldKineticTt.push(
{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}
); + effectiveShieldThermalTt.push(
{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}
); + if (shield.boosters > 0) { + shieldSourcesTt.push(
{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}
); + effectiveShieldAbsoluteTt.push(
{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}
); + effectiveShieldExplosiveTt.push(
{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}
); + effectiveShieldKineticTt.push(
{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}
); + effectiveShieldThermalTt.push(
{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}
); + } - shieldAbsoluteTooltipDetails.push(
{translate('generator') + ' ' + formats.pct1(shield.absolute.generator)}
); - shieldAbsoluteTooltipDetails.push(
{translate('boosters') + ' ' + formats.pct1(shield.absolute.boosters)}
); - shieldAbsoluteTooltipDetails.push(
{translate('power distributor') + ' ' + formats.pct1(shield.absolute.sys)}
); + if (shield.cells > 0) { + shieldSourcesTt.push(
{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}
); + effectiveShieldAbsoluteTt.push(
{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}
); + effectiveShieldExplosiveTt.push(
{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}
); + effectiveShieldKineticTt.push(
{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}
); + effectiveShieldThermalTt.push(
{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}
); + } - shieldExplosiveTooltipDetails.push(
{translate('generator') + ' ' + formats.pct1(shield.explosive.generator)}
); - shieldExplosiveTooltipDetails.push(
{translate('boosters') + ' ' + formats.pct1(shield.explosive.boosters)}
); - shieldExplosiveTooltipDetails.push(
{translate('power distributor') + ' ' + formats.pct1(shield.explosive.sys)}
); + // Add effective shield from resistances + const rawMj = shield.generator + shield.boosters + shield.cells; + const explosiveMj = rawMj / (shield.explosive.generator * shield.explosive.boosters) - rawMj; + if (explosiveMj != 0) effectiveShieldExplosiveTt.push(
{translate('resistance') + ' ' + formats.int(explosiveMj)}{units.MJ}
); + const kineticMj = rawMj / (shield.kinetic.generator * shield.kinetic.boosters) - rawMj; + if (kineticMj != 0) effectiveShieldKineticTt.push(
{translate('resistance') + ' ' + formats.int(kineticMj)}{units.MJ}
); + const thermalMj = rawMj / (shield.thermal.generator * shield.thermal.boosters) - rawMj; + if (thermalMj != 0) effectiveShieldThermalTt.push(
{translate('resistance') + ' ' + formats.int(thermalMj)}{units.MJ}
); - shieldKineticTooltipDetails.push(
{translate('generator') + ' ' + formats.pct1(shield.kinetic.generator)}
); - shieldKineticTooltipDetails.push(
{translate('boosters') + ' ' + formats.pct1(shield.kinetic.boosters)}
); - shieldKineticTooltipDetails.push(
{translate('power distributor') + ' ' + formats.pct1(shield.kinetic.sys)}
); + // Add effective shield from power distributor SYS pips + if (shield.absolute.sys != 1) { + effectiveShieldAbsoluteTt.push(
{translate('power distributor') + ' ' + formats.int(rawMj / shield.absolute.sys - rawMj)}{units.MJ}
); + effectiveShieldExplosiveTt.push(
{translate('power distributor') + ' ' + formats.int(rawMj / shield.explosive.sys - rawMj)}{units.MJ}
); + effectiveShieldKineticTt.push(
{translate('power distributor') + ' ' + formats.int(rawMj / shield.kinetic.sys - rawMj)}{units.MJ}
); + effectiveShieldThermalTt.push(
{translate('power distributor') + ' ' + formats.int(rawMj / shield.thermal.sys - rawMj)}{units.MJ}
); + } + } - shieldThermalTooltipDetails.push(
{translate('generator') + ' ' + formats.pct1(shield.thermal.generator)}
); - shieldThermalTooltipDetails.push(
{translate('boosters') + ' ' + formats.pct1(shield.thermal.boosters)}
); - shieldThermalTooltipDetails.push(
{translate('power distributor') + ' ' + formats.pct1(shield.thermal.sys)}
); + shieldDamageTakenAbsoluteTt.push(
{translate('generator') + ' ' + formats.pct1(shield.absolute.generator)}
); + shieldDamageTakenAbsoluteTt.push(
{translate('boosters') + ' ' + formats.pct1(shield.absolute.boosters)}
); + shieldDamageTakenAbsoluteTt.push(
{translate('power distributor') + ' ' + formats.pct1(shield.absolute.sys)}
); + + shieldDamageTakenExplosiveTt.push(
{translate('generator') + ' ' + formats.pct1(shield.explosive.generator)}
); + shieldDamageTakenExplosiveTt.push(
{translate('boosters') + ' ' + formats.pct1(shield.explosive.boosters)}
); + shieldDamageTakenExplosiveTt.push(
{translate('power distributor') + ' ' + formats.pct1(shield.explosive.sys)}
); + + shieldDamageTakenKineticTt.push(
{translate('generator') + ' ' + formats.pct1(shield.kinetic.generator)}
); + shieldDamageTakenKineticTt.push(
{translate('boosters') + ' ' + formats.pct1(shield.kinetic.boosters)}
); + shieldDamageTakenKineticTt.push(
{translate('power distributor') + ' ' + formats.pct1(shield.kinetic.sys)}
); + + shieldDamageTakenThermalTt.push(
{translate('generator') + ' ' + formats.pct1(shield.thermal.generator)}
); + shieldDamageTakenThermalTt.push(
{translate('boosters') + ' ' + formats.pct1(shield.thermal.boosters)}
); + shieldDamageTakenThermalTt.push(
{translate('power distributor') + ' ' + formats.pct1(shield.thermal.sys)}
); const effectiveAbsoluteShield = shield.total / shield.absolute.total; - effectiveShieldData.push({ value: Math.round(effectiveAbsoluteShield), label: translate('absolute') }); + effectiveShieldData.push({ value: Math.round(effectiveAbsoluteShield), label: translate('absolute'), tooltip: effectiveShieldAbsoluteTt }); const effectiveExplosiveShield = shield.total / shield.explosive.total; - effectiveShieldData.push({ value: Math.round(effectiveExplosiveShield), label: translate('explosive') }); + effectiveShieldData.push({ value: Math.round(effectiveExplosiveShield), label: translate('explosive'), tooltip: effectiveShieldExplosiveTt }); const effectiveKineticShield = shield.total / shield.kinetic.total; - effectiveShieldData.push({ value: Math.round(effectiveKineticShield), label: translate('kinetic') }); + effectiveShieldData.push({ value: Math.round(effectiveKineticShield), label: translate('kinetic'), tooltip: effectiveShieldKineticTt }); const effectiveThermalShield = shield.total / shield.thermal.total; - effectiveShieldData.push({ value: Math.round(effectiveThermalShield), label: translate('thermal') }); + effectiveShieldData.push({ value: Math.round(effectiveThermalShield), label: translate('thermal'), tooltip: effectiveShieldThermalTt }); - shieldDamageTakenData.push({ value: Math.round(shield.absolute.total * 100), label: translate('absolute'), tooltip: shieldAbsoluteTooltipDetails }); - shieldDamageTakenData.push({ value: Math.round(shield.explosive.total * 100), label: translate('explosive'), tooltip: shieldExplosiveTooltipDetails }); - shieldDamageTakenData.push({ value: Math.round(shield.kinetic.total * 100), label: translate('kinetic'), tooltip: shieldKineticTooltipDetails }); - shieldDamageTakenData.push({ value: Math.round(shield.thermal.total * 100), label: translate('thermal'), tooltip: shieldThermalTooltipDetails }); + shieldDamageTakenData.push({ value: Math.round(shield.absolute.total * 100), label: translate('absolute'), tooltip: shieldDamageTakenAbsoluteTt }); + shieldDamageTakenData.push({ value: Math.round(shield.explosive.total * 100), label: translate('explosive'), tooltip: shieldDamageTakenExplosiveTt }); + shieldDamageTakenData.push({ value: Math.round(shield.kinetic.total * 100), label: translate('kinetic'), tooltip: shieldDamageTakenKineticTt }); + shieldDamageTakenData.push({ value: Math.round(shield.thermal.total * 100), label: translate('thermal'), tooltip: shieldDamageTakenThermalTt }); maxEffectiveShield = Math.max(shield.total / shield.absolute.max, shield.total / shield.explosive.max, shield.total / shield.kinetic.max, shield.total / shield.thermal.max); } @@ -113,41 +153,62 @@ export default class Defence extends TranslatedComponent { armourSourcesData.push({ value: Math.round(armour.bulkheads), label: translate('bulkheads') }); armourSourcesData.push({ value: Math.round(armour.reinforcement), label: translate('reinforcement') }); - const armourTooltipDetails = []; - if (armour.bulkheads > 0) armourTooltipDetails.push(
{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}
); - if (armour.reinforcement > 0) armourTooltipDetails.push(
{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}
); + const armourSourcesTt = []; + const effectiveArmourAbsoluteTt = []; + const effectiveArmourExplosiveTt = []; + const effectiveArmourKineticTt = []; + const effectiveArmourThermalTt = []; + if (armour.bulkheads > 0) { + armourSourcesTt.push(
{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}
); + effectiveArmourAbsoluteTt.push(
{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}
); + effectiveArmourExplosiveTt.push(
{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}
); + effectiveArmourKineticTt.push(
{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}
); + effectiveArmourThermalTt.push(
{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}
); + if (armour.reinforcement > 0) { + armourSourcesTt.push(
{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}
); + effectiveArmourAbsoluteTt.push(
{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}
); + effectiveArmourExplosiveTt.push(
{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}
); + effectiveArmourKineticTt.push(
{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}
); + effectiveArmourThermalTt.push(
{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}
); + } + } - const armourAbsoluteTooltipDetails = []; - armourAbsoluteTooltipDetails.push(
{translate('bulkheads') + ' ' + formats.pct1(armour.absolute.bulkheads)}
); - armourAbsoluteTooltipDetails.push(
{translate('reinforcement') + ' ' + formats.pct1(armour.absolute.reinforcement)}
); + const rawArmour = armour.bulkheads + armour.reinforcement; - const armourExplosiveTooltipDetails = []; - armourExplosiveTooltipDetails.push(
{translate('bulkheads') + ' ' + formats.pct1(armour.explosive.bulkheads)}
); - armourExplosiveTooltipDetails.push(
{translate('reinforcement') + ' ' + formats.pct1(armour.explosive.reinforcement)}
); + const armourDamageTakenTt = []; + armourDamageTakenTt.push(
{translate('bulkheads') + ' ' + formats.pct1(armour.absolute.bulkheads)}
); + armourDamageTakenTt.push(
{translate('reinforcement') + ' ' + formats.pct1(armour.absolute.reinforcement)}
); - const armourKineticTooltipDetails = []; - armourKineticTooltipDetails.push(
{translate('bulkheads') + ' ' + formats.pct1(armour.kinetic.bulkheads)}
); - armourKineticTooltipDetails.push(
{translate('reinforcement') + ' ' + formats.pct1(armour.kinetic.reinforcement)}
); + const armourDamageTakenExplosiveTt = []; + armourDamageTakenExplosiveTt.push(
{translate('bulkheads') + ' ' + formats.pct1(armour.explosive.bulkheads)}
); + armourDamageTakenExplosiveTt.push(
{translate('reinforcement') + ' ' + formats.pct1(armour.explosive.reinforcement)}
); + if (armour.explosive.bulkheads * armour.explosive.reinforcement != 1) effectiveArmourExplosiveTt.push(
{translate('resistance') + ' ' + formats.int(rawArmour / (armour.explosive.bulkheads * armour.explosive.reinforcement) - rawArmour)}
); - const armourThermalTooltipDetails = []; - armourThermalTooltipDetails.push(
{translate('bulkheads') + ' ' + formats.pct1(armour.thermal.bulkheads)}
); - armourThermalTooltipDetails.push(
{translate('reinforcement') + ' ' + formats.pct1(armour.thermal.reinforcement)}
); + const armourDamageTakenKineticTt = []; + armourDamageTakenKineticTt.push(
{translate('bulkheads') + ' ' + formats.pct1(armour.kinetic.bulkheads)}
); + armourDamageTakenKineticTt.push(
{translate('reinforcement') + ' ' + formats.pct1(armour.kinetic.reinforcement)}
); + if (armour.kinetic.bulkheads * armour.kinetic.reinforcement != 1) effectiveArmourKineticTt.push(
{translate('resistance') + ' ' + formats.int(rawArmour / (armour.kinetic.bulkheads * armour.kinetic.reinforcement) - rawArmour)}
); + + const armourDamageTakenThermalTt = []; + armourDamageTakenThermalTt.push(
{translate('bulkheads') + ' ' + formats.pct1(armour.thermal.bulkheads)}
); + armourDamageTakenThermalTt.push(
{translate('reinforcement') + ' ' + formats.pct1(armour.thermal.reinforcement)}
); + if (armour.thermal.bulkheads * armour.thermal.reinforcement != 1) effectiveArmourThermalTt.push(
{translate('resistance') + ' ' + formats.int(rawArmour / (armour.thermal.bulkheads * armour.thermal.reinforcement) - rawArmour)}
); const effectiveArmourData = []; const effectiveAbsoluteArmour = armour.total / armour.absolute.total; - effectiveArmourData.push({ value: Math.round(effectiveAbsoluteArmour), label: translate('absolute') }); + effectiveArmourData.push({ value: Math.round(effectiveAbsoluteArmour), label: translate('absolute'), tooltip: effectiveArmourAbsoluteTt }); const effectiveExplosiveArmour = armour.total / armour.explosive.total; - effectiveArmourData.push({ value: Math.round(effectiveExplosiveArmour), label: translate('explosive') }); + effectiveArmourData.push({ value: Math.round(effectiveExplosiveArmour), label: translate('explosive'), tooltip: effectiveArmourExplosiveTt }); const effectiveKineticArmour = armour.total / armour.kinetic.total; - effectiveArmourData.push({ value: Math.round(effectiveKineticArmour), label: translate('kinetic') }); + effectiveArmourData.push({ value: Math.round(effectiveKineticArmour), label: translate('kinetic'), tooltip: effectiveArmourKineticTt }); const effectiveThermalArmour = armour.total / armour.thermal.total; - effectiveArmourData.push({ value: Math.round(effectiveThermalArmour), label: translate('thermal') }); + effectiveArmourData.push({ value: Math.round(effectiveThermalArmour), label: translate('thermal'), tooltip: effectiveArmourThermalTt }); const armourDamageTakenData = []; - armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute'), tooltip: armourAbsoluteTooltipDetails }); - armourDamageTakenData.push({ value: Math.round(armour.explosive.total * 100), label: translate('explosive'), tooltip: armourExplosiveTooltipDetails }); - armourDamageTakenData.push({ value: Math.round(armour.kinetic.total * 100), label: translate('kinetic'), tooltip: armourKineticTooltipDetails }); - armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal'), tooltip: armourThermalTooltipDetails }); + armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute'), tooltip: armourDamageTakenTt }); + armourDamageTakenData.push({ value: Math.round(armour.explosive.total * 100), label: translate('explosive'), tooltip: armourDamageTakenExplosiveTt }); + armourDamageTakenData.push({ value: Math.round(armour.kinetic.total * 100), label: translate('kinetic'), tooltip: armourDamageTakenKineticTt }); + armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal'), tooltip: armourDamageTakenThermalTt }); return ( @@ -155,7 +216,7 @@ export default class Defence extends TranslatedComponent {

{translate('shield metrics')}


-

{shieldTooltipDetails}

)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw shield strength')}
{formats.int(shield.total)}{units.MJ} +

{shieldSourcesTt})} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw shield strength')}
{formats.int(shield.total)}{units.MJ}

{translate('PHRASE_TIME_TO_LOSE_SHIELDS')}
{shielddamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(shield.total, shielddamage.totalsdps, shielddamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * opponentWep / 4))}

{translate('PHRASE_TIME_TO_RECOVER_SHIELDS')}
{shield.recover === Math.Inf ? translate('never') : formats.time(shield.recover)}

{translate('PHRASE_TIME_TO_RECHARGE_SHIELDS')}
{shield.recharge === Math.Inf ? translate('never') : formats.time(shield.recharge)}

@@ -176,7 +237,7 @@ export default class Defence extends TranslatedComponent {

{translate('armour metrics')}

-

{armourTooltipDetails}

)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw armour strength')}
{formats.int(armour.total)} +

{armourSourcesTt})} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw armour strength')}
{formats.int(armour.total)}

{translate('PHRASE_TIME_TO_LOSE_ARMOUR')}
{armourdamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(armour.total, armourdamage.totalsdps, armourdamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * opponentWep / 4))}

{translate('raw module armour')}
{formats.int(armour.modulearmour)}

{translate('PHRASE_MODULE_PROTECTION_EXTERNAL')}
{formats.pct1(armour.moduleprotection / 2)}

diff --git a/src/app/components/HardpointSlot.jsx b/src/app/components/HardpointSlot.jsx index 6ac44e65..b10d22df 100644 --- a/src/app/components/HardpointSlot.jsx +++ b/src/app/components/HardpointSlot.jsx @@ -86,6 +86,7 @@ export default class HardpointSlot extends Slot { { m.getFalloff() ?
{translate('falloff')} {formats.round(m.getFalloff() / 1000)}{u.km}
: null } { m.getShieldBoost() ?
+{formats.pct1(m.getShieldBoost())}
: null } { m.getAmmo() ?
{translate('ammunition')}: {formats.int(m.getClip())}/{formats.int(m.getAmmo())}
: null } + { m.getReload() ?
{translate('reload')}: {formats.round(m.getReload())}{u.s}
: null } { m.getShotSpeed() ?
{translate('shotspeed')}: {formats.int(m.getShotSpeed())}{u.mps}
: null } { m.getPiercing() ?
{translate('piercing')}: {formats.int(m.getPiercing())}
: null } { m.getJitter() ?
{translate('jitter')}: {formats.f2(m.getJitter())}°
: null } diff --git a/src/app/components/Modification.jsx b/src/app/components/Modification.jsx index 4212e3e0..4869a694 100644 --- a/src/app/components/Modification.jsx +++ b/src/app/components/Modification.jsx @@ -54,6 +54,9 @@ export default class Modification extends TranslatedComponent { this.setState({ value }); } + /** + * Triggered when an update to slider value is finished i.e. when losing focus + */ _updateFinished() { this.props.onChange(); } diff --git a/src/app/components/Offence.jsx b/src/app/components/Offence.jsx index f67f721f..18bb24b0 100644 --- a/src/app/components/Offence.jsx +++ b/src/app/components/Offence.jsx @@ -230,8 +230,8 @@ export default class Offence extends TranslatedComponent { {translate('weapon')} - {translate('opponent\`s shields')} - {translate('opponent\`s armour')} + {translate('opponent\'s shields')} + {translate('opponent\'s armour')} {'sdps'} diff --git a/src/app/components/OutfittingSubpages.jsx b/src/app/components/OutfittingSubpages.jsx index 2d25037c..e849ecfe 100644 --- a/src/app/components/OutfittingSubpages.jsx +++ b/src/app/components/OutfittingSubpages.jsx @@ -85,12 +85,12 @@ export default class OutfittingSubpages extends TranslatedComponent { _profilesTab() { const { ship, opponent, cargo, fuel, eng, boost, engagementRange, opponentSys } = this.props; const { translate } = this.context.language; - let realBoost = boost && ship.canBoost(); + let realBoost = boost && ship.canBoost(cargo, fuel); Persist.setOutfittingTab('profiles'); const engineProfileMarker = `${ship.toString()}:${cargo}:${fuel}:${eng}:${realBoost}`; const fsdProfileMarker = `${ship.toString()}:${cargo}:${fuel}`; - const movementMarker = `${ship.topSpeed}:${ship.pitch}:${ship.roll}:${ship.yaw}:${ship.canBoost()}`; + const movementMarker = `${ship.topSpeed}:${ship.pitch}:${ship.roll}:${ship.yaw}:${ship.canBoost(cargo, fuel)}`; const damageMarker = `${ship.toString()}:${opponent.toString()}:${engagementRange}:${opponentSys}`; return
diff --git a/src/app/components/ShipSummaryTable.jsx b/src/app/components/ShipSummaryTable.jsx index 9212bb1b..881a3b25 100644 --- a/src/app/components/ShipSummaryTable.jsx +++ b/src/app/components/ShipSummaryTable.jsx @@ -11,6 +11,8 @@ export default class ShipSummaryTable extends TranslatedComponent { static propTypes = { ship: React.PropTypes.object.isRequired, + cargo: React.PropTypes.number.isRequired, + fuel: React.PropTypes.number.isRequired, marker: React.PropTypes.string.isRequired, }; @@ -19,7 +21,7 @@ export default class ShipSummaryTable extends TranslatedComponent { * @return {React.Component} Summary table */ render() { - const { ship } = this.props; + const { ship, cargo, fuel } = this.props; let { language, tooltip, termtip } = this.context; let translate = language.translate; let u = language.units; @@ -31,9 +33,9 @@ export default class ShipSummaryTable extends TranslatedComponent { const sgClassNames = cn({ warning: shieldGenerator && !ship.shield, muted: !shieldGenerator }); const sgTooltip = shieldGenerator ? 'TT_SUMMARY_SHIELDS' : 'TT_SUMMARY_SHIELDS_NONFUNCTIONAL'; const timeToDrain = Calc.timeToDrainWep(ship, 4); - const canThrust = ship.canThrust(); + const canThrust = ship.canThrust(cargo, fuel); const speedTooltip = canThrust ? 'TT_SUMMARY_SPEED' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL'; - const canBoost = ship.canBoost(); + const canBoost = ship.canBoost(cargo, fuel); const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL'; return
diff --git a/src/app/components/StandardSlot.jsx b/src/app/components/StandardSlot.jsx index 2d1fda65..10d4a69c 100644 --- a/src/app/components/StandardSlot.jsx +++ b/src/app/components/StandardSlot.jsx @@ -5,6 +5,7 @@ import TranslatedComponent from './TranslatedComponent'; import { diffDetails } from '../utils/SlotFunctions'; import AvailableModulesMenu from './AvailableModulesMenu'; import ModificationsMenu from './ModificationsMenu'; +import * as ModuleUtils from '../shipyard/ModuleUtils'; import { ListModifications, Modified } from './SvgIcons'; import { Modifications } from 'coriolis-data/dist'; import { stopCtxPropagation } from '../utils/UtilityFunctions'; @@ -82,7 +83,7 @@ export default class StandardSlot extends TranslatedComponent { menu = m instanceof Module ? m.getMaxMass() < (ship.ladenMass - st[1].mass + m.mass) : m.maxmass < (ship.ladenMass - st[1].mass + m.mass)} + warning={m => m instanceof Module ? m.getMaxMass() < (ship.unladenMass + cargo + fuel - st[1].m.mass + m.mass) : m.maxmass < (ship.unladenMass + cargo + fuel - st[1].m.mass + m.mass)} />; diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx index cc3aec70..358443f3 100644 --- a/src/app/pages/OutfittingPage.jsx +++ b/src/app/pages/OutfittingPage.jsx @@ -267,15 +267,29 @@ export default class OutfittingPage extends Page { */ _opponentUpdated(opponent, opponentBuild) { const opponentShip = new Ship(opponent, Ships[opponent].properties, Ships[opponent].slots); + let opponentSys = this.state.opponentSys; + let opponentEng = this.state.opponentEng; + let opponentWep = this.state.opponentWep; if (opponentBuild && Persist.getBuild(opponent, opponentBuild)) { // Ship is a particular build opponentShip.buildFrom(Persist.getBuild(opponent, opponentBuild)); + // Set pips for opponent + const opponentParts = Persist.getBuild(opponent, opponentBuild).split('.'); + if (opponentParts.length >= 5) { + const opponentControl = LZString.decompressFromBase64(Utils.fromUrlSafe(opponentParts[4])).split('/'); + opponentSys = parseFloat(opponentControl[0]); + opponentEng = parseFloat(opponentControl[1]); + opponentWep = parseFloat(opponentControl[2]); + } } else { // Ship is a stock build opponentShip.buildWith(Ships[opponent].defaults); + opponentSys = 2; + opponentEng = 2; + opponentWep = 2; } - this.setState({ opponent: opponentShip, opponentBuild }, () => this._updateRouteOnControlChange()); + this.setState({ opponent: opponentShip, opponentBuild, opponentSys, opponentEng, opponentWep }, () => this._updateRouteOnControlChange()); } /** @@ -531,11 +545,11 @@ export default class OutfittingPage extends Page { const _pStr = `${ship.getPowerEnabledString()}${ship.getPowerPrioritiesString()}`; const _mStr = ship.getModificationsString(); - const standardSlotMarker = `${ship.name}${_sStr}${_pStr}${_mStr}`; + const standardSlotMarker = `${ship.name}${_sStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`; const internalSlotMarker = `${ship.name}${_iStr}${_pStr}${_mStr}`; const hardpointsSlotMarker = `${ship.name}${_hStr}${_pStr}${_mStr}`; - const boostMarker = `${ship.canBoost()}`; - const shipSummaryMarker = `${ship.name}${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}`; + const boostMarker = `${ship.canBoost(cargo, fuel)}`; + const shipSummaryMarker = `${ship.name}${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}${ship.ladenMass}${ship.cargo}${ship.fuel}`; return (
@@ -571,8 +585,8 @@ export default class OutfittingPage extends Page {
{/* Main tables */} - - + + diff --git a/src/app/shipyard/Calculations.js b/src/app/shipyard/Calculations.js index ad040e0c..42dc5cee 100644 --- a/src/app/shipyard/Calculations.js +++ b/src/app/shipyard/Calculations.js @@ -342,7 +342,7 @@ export function shieldMetrics(ship, sys) { // Calculate diminishing returns for boosters // Diminishing returns not currently in-game - //boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5); + // boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5); // Remove base shield generator strength boost -= 1; diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js index 37984d85..db4cb8a1 100755 --- a/src/app/shipyard/Module.js +++ b/src/app/shipyard/Module.js @@ -162,14 +162,6 @@ export default class Module { return result; } - /** - * Return true if this is a shield generator - * @return {Boolean} if this is a shield generator - */ - isShieldGenerator() { - return (this.grp === 'sg' || this.grp === 'psg' || this.grp === 'bsg'); - } - /** * Get the power generation of this module, taking in to account modifications * @return {Number} the power generation of this module diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index d622254e..0de8b2ef 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -123,19 +123,23 @@ export default class Ship { /** * Can the ship thrust/move + * @param {Number} cargo Amount of cargo in the ship + * @param {Number} fuel Amount of fuel in the ship * @return {[type]} True if thrusters operational */ - canThrust() { + canThrust(cargo, fuel) { return this.getSlotStatus(this.standard[1]) == 3 && // Thrusters are powered - this.ladenMass < this.standard[1].m.getMaxMass(); // Max mass not exceeded + this.unladenMass + cargo + fuel < this.standard[1].m.getMaxMass(); // Max mass not exceeded } /** * Can the ship boost + * @param {Number} cargo Amount of cargo in the ship + * @param {Number} fuel Amount of fuel in the ship * @return {[type]} True if boost capable */ - canBoost() { - return this.canThrust() && // Thrusters operational + canBoost(cargo, fuel) { + return this.canThrust(cargo, fuel) && // Thrusters operational this.standard[4].m.getEnginesCapacity() > this.boostEnergy; // PD capacitor is sufficient for boost } @@ -1185,7 +1189,7 @@ export default class Ship { updateMovement() { this.speeds = Calc.speed(this.unladenMass + this.fuelCapacity, this.speed, this.standard[1].m, this.pipSpeed); this.topSpeed = this.speeds[4]; - this.topBoost = this.canBoost() ? this.speeds[4] * this.boost / this.speed : 0; + this.topBoost = this.canBoost(0, 0) ? this.speeds[4] * this.boost / this.speed : 0; this.pitches = Calc.pitch(this.unladenMass + this.fuelCapacity, this.pitch, this.standard[1].m, this.pipSpeed); this.topPitch = this.pitches[4]; @@ -1204,53 +1208,13 @@ export default class Ship { * @return {this} The ship instance (for chaining operations) */ recalculateShield() { - let shield = 0; - let shieldBoost = 1; - let shieldExplRes = null; - let shieldKinRes = null; - let shieldThermRes = null; - let shieldExplDRStart = null; - let shieldExplDREnd = null; - let shieldKinDRStart = null; - let shieldKinDREnd = null; - let shieldThermDRStart = null; - let shieldThermDREnd = null; - - const sgSlot = this.findInternalByGroup('sg'); - if (sgSlot && sgSlot.enabled) { - // Shield from generator - shield = Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sgSlot.m, 1); - shieldExplRes = 1 - sgSlot.m.getExplosiveResistance(); - shieldExplDRStart = shieldExplRes * 0.7; - shieldExplDREnd = 0; - shieldKinRes = 1 - sgSlot.m.getKineticResistance(); - shieldKinDRStart = shieldKinRes * 0.7; - shieldKinDREnd = 0; - shieldThermRes = 1 - sgSlot.m.getThermalResistance(); - shieldThermDRStart = shieldThermRes * 0.7; - shieldThermDREnd = 0; - - // Shield from boosters - for (let slot of this.hardpoints) { - if (slot.enabled && slot.m && slot.m.grp == 'sb') { - shieldBoost += slot.m.getShieldBoost(); - shieldExplRes *= (1 - slot.m.getExplosiveResistance()); - shieldKinRes *= (1 - slot.m.getKineticResistance()); - shieldThermRes *= (1 - slot.m.getThermalResistance()); - } - } - } - - // We apply diminishing returns to the boosted value - shieldBoost = Math.min(shieldBoost, (1 - Math.pow(Math.E, -0.7 * shieldBoost)) * 2.5); - - shield = shield * shieldBoost; - - this.shield = shield; - this.shieldExplRes = shieldExplRes ? 1 - this.diminishingReturns(shieldExplRes, shieldExplDREnd, shieldExplDRStart) : null; - this.shieldKinRes = shieldKinRes ? 1 - this.diminishingReturns(shieldKinRes, shieldKinDREnd, shieldKinDRStart) : null; - this.shieldThermRes = shieldThermRes ? 1 - this.diminishingReturns(shieldThermRes, shieldThermDREnd, shieldThermDRStart) : null; + // Obtain shield metrics with 0 pips to sys (parts affected by SYS aren't used here) + const metrics = Calc.shieldMetrics(this, 0); + this.shield = metrics.generator ? metrics.generator + metrics.boosters : 0; + this.shieldExplRes = this.shield > 0 ? 1 - metrics.explosive.total : null; + this.shieldKinRes = this.shield > 0 ? 1 - metrics.kinetic.total : null; + this.shieldThermRes = this.shield > 0 ? 1 - metrics.thermal.total : null; return this; } @@ -1679,11 +1643,11 @@ export default class Ship { let mass = this.hullMass; mass += m.pp ? m.pp.getMass() : ModuleUtils.standard(0, '2D').getMass(); mass += m.th ? m.th.getMass() : ModuleUtils.standard(1, '2D').getMass(); - mass += m.fsd ? m.fsd.getMass() : ModuleUtils.standard(2, this.standard[2].maxClass + 'D').getMass(); + mass += m.fsd ? m.fsd.getMass() : ModuleUtils.standard(2, '2D').getMass(); mass += m.ls ? m.ls.getMass() : ModuleUtils.standard(3, this.standard[3].maxClass + 'D').getMass() * 0.3; // Lightweight grade 4 mod reduces mass by up to 70% - mass += m.pd ? m.pd.getMass() : ModuleUtils.standard(4, '2D').getMass(); - mass += m.s ? m.s.getMass() : ModuleUtils.standard(5, this.standard[5].maxClass + 'D').getMass(); - mass += m.ft ? m.ft.getMass() : ModuleUtils.standard(6, '1C').getMass(); + mass += m.pd ? m.pd.getMass() : ModuleUtils.standard(4, '1D').getMass(); + mass += m.s ? m.s.getMass() : ModuleUtils.standard(5, this.standard[5].maxClass + 'D').getMass() * 0.2; // Lightweight grade 5 mod reduces mass by up to 80% + // Ignore fuel tank as it could be empty return mass; } diff --git a/src/app/utils/CompanionApiUtils.js b/src/app/utils/CompanionApiUtils.js index 5f6df7c5..25124864 100644 --- a/src/app/utils/CompanionApiUtils.js +++ b/src/app/utils/CompanionApiUtils.js @@ -3,6 +3,7 @@ import { Modifications, Modules, Ships } from 'coriolis-data/dist'; import Module from '../shipyard/Module'; import Ship from '../shipyard/Ship'; import { getBlueprint } from '../utils/BlueprintFunctions'; +import * as ModuleUtils from '../shipyard/ModuleUtils'; // mapping from fd's ship model names to coriolis' const SHIP_FD_NAME_TO_CORIOLIS_NAME = { @@ -378,7 +379,7 @@ function _addModifications(module, modifiers, blueprint, grade) { // Shield generator resistance is actually a damage modifier, so needs to be inverted. // In addition, the modification is based off the inherent resistance of the module - if (module.isShieldGenerator()) { + if (ModuleUtils.isShieldGenerator(module.grp)) { if (module.getModValue('explres')) { module.setModValue('explres', ((1 - (1 - module.explres) * (1 + module.getModValue('explres') / 10000)) - module.explres) * 10000); } diff --git a/src/app/utils/SlotFunctions.js b/src/app/utils/SlotFunctions.js index 8a57e63e..c2f5f4ba 100644 --- a/src/app/utils/SlotFunctions.js +++ b/src/app/utils/SlotFunctions.js @@ -1,9 +1,9 @@ import React from 'react'; import cn from 'classnames'; -import { isShieldGenerator } from '../shipyard/ModuleUtils'; import Module from '../shipyard/Module'; import { Infinite } from '../components/SvgIcons'; import Persist from '../stores/Persist'; +import * as ModuleUtils from '../shipyard/ModuleUtils'; /** * Determine if a slot on a ship can mount a module of a particular class and group @@ -159,8 +159,8 @@ export function diffDetails(language, m, mm) { let mmDps = mm ? mm.getDps() || 0 : 0; if (mDps && mDps != mmDps) propDiffs.push(
{translate('dps')}: {diff(formats.round, mDps, mmDps)}
); - let mAffectsShield = isShieldGenerator(m.grp) || m.grp == 'sb'; - let mmAffectsShield = isShieldGenerator(mm ? mm.grp : null) || mm && mm.grp == 'sb'; + let mAffectsShield = ModuleUtils.isShieldGenerator(m.grp) || m.grp == 'sb'; + let mmAffectsShield = mm ? ModuleUtils.isShieldGenerator(m.grp) || mm.grp == 'sb' : false; if (mAffectsShield || mmAffectsShield) { let shield = this.calcShieldStrengthWith(); // Get shield strength regardless of slot active / inactive let newShield = 0;