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;