diff --git a/src/app/components/Defence.jsx b/src/app/components/Defence.jsx index 39b4a22f..0a1ba4fe 100644 --- a/src/app/components/Defence.jsx +++ b/src/app/components/Defence.jsx @@ -1,8 +1,6 @@ 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'; import PieChart from './PieChart'; import VerticalBarChart from './VerticalBarChart'; @@ -48,64 +46,7 @@ export default class Defence extends TranslatedComponent { } /** - * Calculate the sustained DPS for a ship at a given range, excluding resistances - * @param {Object} ship The ship - * @param {Object} opponent The opponent ship - * @param {int} engagementrange The range between the ship and opponent - * @returns {Object} Sustained DPS for shield and armour - */ - _calcSustainedDps(ship, opponent, engagementrange) { - const shieldsdps = { - absolute: 0, - explosive: 0, - kinetic: 0, - thermal: 0 - }; - - const armoursdps = { - absolute: 0, - explosive: 0, - kinetic: 0, - thermal: 0 - }; - - for (let i = 0; i < ship.hardpoints.length; i++) { - if (ship.hardpoints[i].m && ship.hardpoints[i].enabled && ship.hardpoints[i].maxClass > 0) { - const m = ship.hardpoints[i].m; - // Initial sustained DPS - let sDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getDps(); - // Take fall-off in to account - const falloff = m.getFalloff(); - if (falloff && engagementrange > falloff) { - const dropoffRange = m.getRange() - falloff; - sDps *= 1 - Math.min((engagementrange - falloff) / dropoffRange, 1); - } - // Piercing/hardness modifier (for armour only) - const armourMultiple = m.getPiercing() >= opponent.hardness ? 1 : m.getPiercing() / opponent.hardness; - // Break out the damage according to type - if (m.getDamageDist().A) { - shieldsdps.absolute += sDps * m.getDamageDist().A; - armoursdps.absolute += sDps * m.getDamageDist().A * armourMultiple; - } - if (m.getDamageDist().E) { - shieldsdps.explosive += sDps * m.getDamageDist().E; - armoursdps.explosive += sDps * m.getDamageDist().E * armourMultiple; - } - if (m.getDamageDist().K) { - shieldsdps.kinetic += sDps * m.getDamageDist().K; - armoursdps.kinetic += sDps * m.getDamageDist().K * armourMultiple; - } - if (m.getDamageDist().T) { - shieldsdps.thermal += sDps * m.getDamageDist().T; - armoursdps.thermal += sDps * m.getDamageDist().T * armourMultiple; - } - } - } - return { shieldsdps, armoursdps }; - } - - /** - * Render shields + * Render defence * @return {React.Component} contents */ render() { diff --git a/src/app/components/Offence.jsx b/src/app/components/Offence.jsx index 86307353..506b6fcd 100644 --- a/src/app/components/Offence.jsx +++ b/src/app/components/Offence.jsx @@ -1,13 +1,16 @@ 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'; import PieChart from './PieChart'; import VerticalBarChart from './VerticalBarChart'; /** * Offence information + * Offence information consists of four panels: + * - textual information (time to drain cap, time to take down shields etc.) + * - breakdown of damage sources (pie chart) + * - comparison of shield resistances (table chart) + * - effective sustained DPS of weapons (bar chart) */ export default class Offence extends TranslatedComponent { static propTypes = { @@ -25,7 +28,7 @@ export default class Offence extends TranslatedComponent { constructor(props) { super(props); - const { shield, armour, shielddamage, armourdamage } = this._calcMetrics(props.ship, props.opponent, props.sys, props.engagementrange); + const { shield, armour, shielddamage, armourdamage } = Calc.offenceMetrics(props.ship, props.opponent, props.eng, props.engagementrange); this.state = { shield, armour, shielddamage, armourdamage }; } @@ -36,419 +39,116 @@ export default class Offence extends TranslatedComponent { */ componentWillReceiveProps(nextProps) { if (this.props.marker != nextProps.marker || this.props.sys != nextProps.sys) { - const { shield, armour, shielddamage, armourdamage } = this._calcMetrics(nextProps.ship, nextProps.opponent, nextProps.sys, nextProps.engagementrange); + const { shield, armour, shielddamage, armourdamage } = Calc.offenceMetrics(nextProps.ship, nextProps.opponent, nextProps.wep, nextProps.engagementrange); this.setState({ shield, armour, shielddamage, armourdamage }); - return true; } + return true; } /** - * Calculate the sustained DPS for a ship at a given range, excluding resistances - * @param {Object} ship The ship - * @param {Object} opponent The opponent ship - * @param {int} engagementrange The range between the ship and opponent - * @returns {Object} Sustained DPS for shield and armour - */ - _calcSustainedDps(ship, opponent, engagementrange) { - const shieldsdps = { - absolute: 0, - explosive: 0, - kinetic: 0, - thermal: 0 - }; - - const armoursdps = { - absolute: 0, - explosive: 0, - kinetic: 0, - thermal: 0 - }; - - for (let i = 0; i < ship.hardpoints.length; i++) { - if (ship.hardpoints[i].m && ship.hardpoints[i].enabled && ship.hardpoints[i].maxClass > 0) { - const m = ship.hardpoints[i].m; - // Initial sustained DPS - let sDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getDps(); - // Take fall-off in to account - const falloff = m.getFalloff(); - if (falloff && engagementrange > falloff) { - const dropoffRange = m.getRange() - falloff; - sDps *= 1 - Math.min((engagementrange - falloff) / dropoffRange, 1); - } - // Piercing/hardness modifier (for armour only) - const armourMultiple = m.getPiercing() >= opponent.hardness ? 1 : m.getPiercing() / opponent.hardness; - // Break out the damage according to type - if (m.getDamageDist().A) { - shieldsdps.absolute += sDps * m.getDamageDist().A; - armoursdps.absolute += sDps * m.getDamageDist().A * armourMultiple; - } - if (m.getDamageDist().E) { - shieldsdps.explosive += sDps * m.getDamageDist().E; - armoursdps.explosive += sDps * m.getDamageDist().E * armourMultiple; - } - if (m.getDamageDist().K) { - shieldsdps.kinetic += sDps * m.getDamageDist().K; - armoursdps.kinetic += sDps * m.getDamageDist().K * armourMultiple; - } - if (m.getDamageDist().T) { - shieldsdps.thermal += sDps * m.getDamageDist().T; - armoursdps.thermal += sDps * m.getDamageDist().T * armourMultiple; - } - } - } - return { shieldsdps, armoursdps }; - } - - /** - * Obtain the recharge rate of the SYS capacitor of a power distributor given pips - * @param {Object} pd The power distributor - * @param {number} sys The number of pips to SYS - * @returns {number} The recharge rate in MJ/s - */ - _calcSysRechargeRate(pd, sys) { - return pd.getSystemsRechargeRate() * Math.pow(sys, 1.1) / Math.pow(4, 1.1); - } - - /** - * Calculate shield metrics - * @param {Object} ship The ship - * @param {Object} opponent The opponent ship - * @param {int} sys The opponent ship - * @param {int} engagementrange The range between the ship and opponent - * @returns {Object} Shield metrics - */ - _calcMetrics(ship, opponent, sys, engagementrange) { - const sysResistance = this._calcSysResistance(sys); - const maxSysResistance = this._calcSysResistance(4); - - // Obtain the opponent's sustained DPS on us for later damage calculations - const { shieldsdps, armoursdps } = this._calcSustainedDps(opponent, ship, engagementrange); - - let shielddamage = {}; - let shield = {}; - const shieldGeneratorSlot = ship.findInternalByGroup('sg'); - if (shieldGeneratorSlot && shieldGeneratorSlot.enabled && shieldGeneratorSlot.m) { - const shieldGenerator = shieldGeneratorSlot.m; - - // 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; - // Apply diminishing returns - boosterExplDmg = boosterExplDmg > 0.7 ? boosterExplDmg : 0.7 - (0.7 - boosterExplDmg) / 2; - boosterKinDmg = boosterKinDmg > 0.7 ? boosterKinDmg : 0.7 - (0.7 - boosterKinDmg) / 2; - boosterThermDmg = boosterThermDmg > 0.7 ? boosterThermDmg : 0.7 - (0.7 - boosterThermDmg) / 2; - - const generatorStrength = Calc.shieldStrength(ship.hullMass, ship.baseShieldStrength, shieldGenerator, 1); - const boostersStrength = generatorStrength * boost; - - // Recover time is the time taken to go from 0 to 50%. It includes a 16-second wait before shields start to recover - const shieldToRecover = (generatorStrength + boostersStrength) / 2; - const powerDistributor = ship.standard[4].m; - const sysRechargeRate = this._calcSysRechargeRate(powerDistributor, sys); - - // Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes - // 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration - let capacitorDrain = (shieldGenerator.getBrokenRegenerationRate() * 0.6) - sysRechargeRate; - let capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain; - - let recover = 16; - if (capacitorDrain <= 0 || shieldToRecover < capacitorLifetime * shieldGenerator.getBrokenRegenerationRate()) { - // We can recover the entire shield from the capacitor store - recover += shieldToRecover / shieldGenerator.getBrokenRegenerationRate(); - } else { - // We can recover some of the shield from the capacitor store - recover += capacitorLifetime; - const remainingShieldToRecover = shieldToRecover - capacitorLifetime * shieldGenerator.getBrokenRegenerationRate(); - if (sys === 0) { - // No system pips so will never recover shields - recover = Math.Inf; - } else { - // Recover remaining shields at the rate of the power distributor's recharge - recover += remainingShieldToRecover / (sysRechargeRate / 0.6); - } - } - - // Recharge time is the time taken to go from 50% to 100% - const shieldToRecharge = (generatorStrength + boostersStrength) / 2; - - // Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes - // 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration - capacitorDrain = (shieldGenerator.getRegenerationRate() * 0.6) - sysRechargeRate; - capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain; - - let recharge = 0; - if (capacitorDrain <= 0 || shieldToRecharge < capacitorLifetime * shieldGenerator.getRegenerationRate()) { - // We can recharge the entire shield from the capacitor store - recharge += shieldToRecharge / shieldGenerator.getRegenerationRate(); - } else { - // We can recharge some of the shield from the capacitor store - recharge += capacitorLifetime; - const remainingShieldToRecharge = shieldToRecharge - capacitorLifetime * shieldGenerator.getRegenerationRate(); - if (sys === 0) { - // No system pips so will never recharge shields - recharge = Math.Inf; - } else { - // Recharge remaining shields at the rate of the power distributor's recharge - recharge += remainingShieldToRecharge / (sysRechargeRate / 0.6); - } - } - - shield = { - generator: generatorStrength, - boosters: boostersStrength, - cells: ship.shieldCells, - total: generatorStrength + boostersStrength + ship.shieldCells, - recover, - recharge, - }; - - // Shield resistances have three components: the shield generator, the shield boosters and the SYS pips. - // We re-cast these as damage percentages - shield.absolute = { - generator: 1, - boosters: 1, - sys: 1 - sysResistance, - total: 1 - sysResistance, - max: 1 - maxSysResistance - }; - - shield.explosive = { - generator: 1 - shieldGenerator.getExplosiveResistance(), - boosters: boosterExplDmg, - sys: (1 - sysResistance), - total: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - sysResistance), - max: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - maxSysResistance) - }; - - shield.kinetic = { - generator: 1 - shieldGenerator.getKineticResistance(), - boosters: boosterKinDmg, - sys: (1 - sysResistance), - total: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - sysResistance), - max: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - maxSysResistance) - }; - - shield.thermal = { - generator: 1 - shieldGenerator.getThermalResistance(), - boosters: boosterThermDmg, - sys: (1 - sysResistance), - total: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - sysResistance), - max: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - maxSysResistance) - }; - - shielddamage.absolutesdps = shieldsdps.absolute *= shield.absolute.total; - shielddamage.explosivesdps = shieldsdps.explosive *= shield.explosive.total; - shielddamage.kineticsdps = shieldsdps.kinetic *= shield.kinetic.total; - shielddamage.thermalsdps = shieldsdps.thermal *= shield.thermal.total; - shielddamage.totalsdps = shielddamage.absolutesdps + shielddamage.explosivesdps + shielddamage.kineticsdps + shielddamage.thermalsdps; - } - - // Armour from bulkheads - const armourBulkheads = ship.baseArmour + (ship.baseArmour * ship.bulkheads.m.getHullBoost()); - let armourReinforcement = 0; - - let moduleArmour = 0; - let moduleProtection = 1; - - let hullExplDmg = 1; - let hullKinDmg = 1; - let hullThermDmg = 1; - - // Armour from HRPs and module armour from MRPs - for (let slot of ship.internal) { - if (slot.m && slot.m.grp == 'hr') { - armourReinforcement += slot.m.getHullReinforcement(); - // Hull boost for HRPs is applied against the ship's base armour - armourReinforcement += ship.baseArmour * slot.m.getModValue('hullboost') / 10000; - - hullExplDmg = hullExplDmg * (1 - slot.m.getExplosiveResistance()); - hullKinDmg = hullKinDmg * (1 - slot.m.getKineticResistance()); - hullThermDmg = hullThermDmg * (1 - slot.m.getThermalResistance()); - } - if (slot.m && slot.m.grp == 'mrp') { - moduleArmour += slot.m.getIntegrity(); - moduleProtection = moduleProtection * (1 - slot.m.getProtection()); - } - } - moduleProtection = 1 - moduleProtection; - - // Apply diminishing returns - hullExplDmg = hullExplDmg > 0.7 ? hullExplDmg : 0.7 - (0.7 - hullExplDmg) / 2; - hullKinDmg = hullKinDmg > 0.7 ? hullKinDmg : 0.7 - (0.7 - hullKinDmg) / 2; - hullThermDmg = hullThermDmg > 0.7 ? hullThermDmg : 0.7 - (0.7 - hullThermDmg) / 2; - - const armour = { - bulkheads: armourBulkheads, - reinforcement: armourReinforcement, - modulearmour: moduleArmour, - moduleprotection: moduleProtection, - total: armourBulkheads + armourReinforcement - }; - - // Armour resistances have two components: bulkheads and HRPs - // We re-cast these as damage percentages - armour.absolute = { - bulkheads: 1, - reinforcement: 1, - total: 1 - }; - - armour.explosive = { - bulkheads: 1 - ship.bulkheads.m.getExplosiveResistance(), - reinforcement: hullExplDmg, - total: (1 - ship.bulkheads.m.getExplosiveResistance()) * hullExplDmg - }; - - armour.kinetic = { - bulkheads: 1 - ship.bulkheads.m.getKineticResistance(), - reinforcement: hullKinDmg, - total: (1 - ship.bulkheads.m.getKineticResistance()) * hullKinDmg - }; - - armour.thermal = { - bulkheads: 1 - ship.bulkheads.m.getThermalResistance(), - reinforcement: hullThermDmg, - total: (1 - ship.bulkheads.m.getThermalResistance()) * hullThermDmg - }; - - const armourdamage = { - absolutesdps: armoursdps.absolute *= armour.absolute.total, - explosivesdps: armoursdps.explosive *= armour.explosive.total, - kineticsdps: armoursdps.kinetic *= armour.kinetic.total, - thermalsdps: armoursdps.thermal *= armour.thermal.total - }; - armourdamage.totalsdps = armourdamage.absolutesdps + armourdamage.explosivesdps + armourdamage.kineticsdps + armourdamage.thermalsdps; - - return { shield, armour, shielddamage, armourdamage }; - } - - /** - * 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 + * Render offence * @return {React.Component} contents */ render() { - const { ship, sys } = this.props; + const { ship, wep } = this.props; const { language, tooltip, termtip } = this.context; const { formats, translate, units } = language; - const { shield, armour, shielddamage, armourdamage } = this.state; +// const { shield, armour, shielddamage, armourdamage } = this.state; - const shieldSourcesData = []; - const effectiveShieldData = []; - const shieldDamageTakenData = []; - const shieldTooltipDetails = []; - const shieldAbsoluteTooltipDetails = []; - const shieldExplosiveTooltipDetails = []; - const shieldKineticTooltipDetails = []; - const shieldThermalTooltipDetails = []; - let maxEffectiveShield = 0; - if (shield.total) { - if (Math.round(shield.generator) > 0) shieldSourcesData.push({ value: Math.round(shield.generator), label: translate('generator') }); - if (Math.round(shield.boosters) > 0) shieldSourcesData.push({ value: Math.round(shield.boosters), label: translate('boosters') }); - if (Math.round(shield.cells) > 0) shieldSourcesData.push({ value: Math.round(shield.cells), label: translate('cells') }); +// const shieldSourcesData = []; +// const effectiveShieldData = []; +// const shieldDamageTakenData = []; +// const shieldTooltipDetails = []; +// const shieldAbsoluteTooltipDetails = []; +// const shieldExplosiveTooltipDetails = []; +// const shieldKineticTooltipDetails = []; +// const shieldThermalTooltipDetails = []; +// 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 (Math.round(shield.generator) > 0) shieldTooltipDetails.push(
{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}
); - if (Math.round(shield.boosters) > 0) shieldTooltipDetails.push(
{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}
); - if (Math.round(shield.cells) > 0) shieldTooltipDetails.push(
{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}
); +// shieldTooltipDetails.push(
{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}
); +// shieldTooltipDetails.push(
{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}
); +// shieldTooltipDetails.push(
{translate('cells') + ' ' + formats.int(shield.cells)}{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)}
); +// 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)}
); - 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)}
); +// 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)}
); - 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)}
); +// 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)}
); - 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)}
); +// 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)}
); - const effectiveAbsoluteShield = shield.total / shield.absolute.total; - effectiveShieldData.push({ value: Math.round(effectiveAbsoluteShield), label: translate('absolute') }); - const effectiveExplosiveShield = shield.total / shield.explosive.total; - effectiveShieldData.push({ value: Math.round(effectiveExplosiveShield), label: translate('explosive') }); - const effectiveKineticShield = shield.total / shield.kinetic.total; - effectiveShieldData.push({ value: Math.round(effectiveKineticShield), label: translate('kinetic') }); - const effectiveThermalShield = shield.total / shield.thermal.total; - effectiveShieldData.push({ value: Math.round(effectiveThermalShield), label: translate('thermal') }); +// const effectiveAbsoluteShield = shield.total / shield.absolute.total; +// effectiveShieldData.push({ value: Math.round(effectiveAbsoluteShield), label: translate('absolute') }); +// const effectiveExplosiveShield = shield.total / shield.explosive.total; +// effectiveShieldData.push({ value: Math.round(effectiveExplosiveShield), label: translate('explosive') }); +// const effectiveKineticShield = shield.total / shield.kinetic.total; +// effectiveShieldData.push({ value: Math.round(effectiveKineticShield), label: translate('kinetic') }); +// const effectiveThermalShield = shield.total / shield.thermal.total; +// effectiveShieldData.push({ value: Math.round(effectiveThermalShield), label: translate('thermal') }); - shieldDamageTakenData.push({ value: Math.round(shield.absolute.total * 100), label: translate('absolute') }); - shieldDamageTakenData.push({ value: Math.round(shield.explosive.total * 100), label: translate('explosive') }); - shieldDamageTakenData.push({ value: Math.round(shield.kinetic.total * 100), label: translate('kinetic') }); - shieldDamageTakenData.push({ value: Math.round(shield.thermal.total * 100), label: translate('thermal') }); +// shieldDamageTakenData.push({ value: Math.round(shield.absolute.total * 100), label: translate('absolute') }); +// shieldDamageTakenData.push({ value: Math.round(shield.explosive.total * 100), label: translate('explosive') }); +// shieldDamageTakenData.push({ value: Math.round(shield.kinetic.total * 100), label: translate('kinetic') }); +// shieldDamageTakenData.push({ value: Math.round(shield.thermal.total * 100), label: translate('thermal') }); - maxEffectiveShield = Math.max(shield.total / shield.absolute.max, shield.total / shield.explosive.max, shield.total / shield.kinetic.max, shield.total / shield.thermal.max); - } +// maxEffectiveShield = Math.max(shield.total / shield.absolute.max, shield.total / shield.explosive.max, shield.total / shield.kinetic.max, shield.total / shield.thermal.max); +// } - const armourSourcesData = []; - if (Math.round(armour.bulkheads) > 0) armourSourcesData.push({ value: Math.round(armour.bulkheads), label: translate('bulkheads') }); - if (Math.round(armour.reinforcement) > 0) armourSourcesData.push({ value: Math.round(armour.reinforcement), label: translate('reinforcement') }); +// const armourSourcesData = []; +// 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 armourTooltipDetails = []; +// armourTooltipDetails.push(
{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}
); +// armourTooltipDetails.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 armourAbsoluteTooltipDetails = []; +// armourAbsoluteTooltipDetails.push(
{translate('bulkheads') + ' ' + formats.pct1(armour.absolute.bulkheads)}
); +// armourAbsoluteTooltipDetails.push(
{translate('reinforcement') + ' ' + formats.pct1(armour.absolute.reinforcement)}
); - const armourExplosiveTooltipDetails = []; - armourExplosiveTooltipDetails.push(
{translate('bulkheads') + ' ' + formats.pct1(armour.explosive.bulkheads)}
); - armourExplosiveTooltipDetails.push(
{translate('reinforcement') + ' ' + formats.pct1(armour.explosive.reinforcement)}
); +// const armourExplosiveTooltipDetails = []; +// armourExplosiveTooltipDetails.push(
{translate('bulkheads') + ' ' + formats.pct1(armour.explosive.bulkheads)}
); +// armourExplosiveTooltipDetails.push(
{translate('reinforcement') + ' ' + formats.pct1(armour.explosive.reinforcement)}
); - const armourKineticTooltipDetails = []; - armourKineticTooltipDetails.push(
{translate('bulkheads') + ' ' + formats.pct1(armour.kinetic.bulkheads)}
); - armourKineticTooltipDetails.push(
{translate('reinforcement') + ' ' + formats.pct1(armour.kinetic.reinforcement)}
); +// const armourKineticTooltipDetails = []; +// armourKineticTooltipDetails.push(
{translate('bulkheads') + ' ' + formats.pct1(armour.kinetic.bulkheads)}
); +// armourKineticTooltipDetails.push(
{translate('reinforcement') + ' ' + formats.pct1(armour.kinetic.reinforcement)}
); - const armourThermalTooltipDetails = []; - armourThermalTooltipDetails.push(
{translate('bulkheads') + ' ' + formats.pct1(armour.thermal.bulkheads)}
); - armourThermalTooltipDetails.push(
{translate('reinforcement') + ' ' + formats.pct1(armour.thermal.reinforcement)}
); +// const armourThermalTooltipDetails = []; +// armourThermalTooltipDetails.push(
{translate('bulkheads') + ' ' + formats.pct1(armour.thermal.bulkheads)}
); +// armourThermalTooltipDetails.push(
{translate('reinforcement') + ' ' + formats.pct1(armour.thermal.reinforcement)}
); - const effectiveArmourData = []; - const effectiveAbsoluteArmour = armour.total / armour.absolute.total; - effectiveArmourData.push({ value: Math.round(effectiveAbsoluteArmour), label: translate('absolute') }); - const effectiveExplosiveArmour = armour.total / armour.explosive.total; - effectiveArmourData.push({ value: Math.round(effectiveExplosiveArmour), label: translate('explosive') }); - const effectiveKineticArmour = armour.total / armour.kinetic.total; - effectiveArmourData.push({ value: Math.round(effectiveKineticArmour), label: translate('kinetic') }); - const effectiveThermalArmour = armour.total / armour.thermal.total; - effectiveArmourData.push({ value: Math.round(effectiveThermalArmour), label: translate('thermal') }); +// const effectiveArmourData = []; +// const effectiveAbsoluteArmour = armour.total / armour.absolute.total; +// effectiveArmourData.push({ value: Math.round(effectiveAbsoluteArmour), label: translate('absolute') }); +// const effectiveExplosiveArmour = armour.total / armour.explosive.total; +// effectiveArmourData.push({ value: Math.round(effectiveExplosiveArmour), label: translate('explosive') }); +// const effectiveKineticArmour = armour.total / armour.kinetic.total; +// effectiveArmourData.push({ value: Math.round(effectiveKineticArmour), label: translate('kinetic') }); +// const effectiveThermalArmour = armour.total / armour.thermal.total; +// effectiveArmourData.push({ value: Math.round(effectiveThermalArmour), label: translate('thermal') }); - const armourDamageTakenData = []; - armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute') }); - armourDamageTakenData.push({ value: Math.round(armour.explosive.total * 100), label: translate('explosive') }); - armourDamageTakenData.push({ value: Math.round(armour.kinetic.total * 100), label: translate('kinetic') }); - armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal') }); +// const armourDamageTakenData = []; +// armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute') }); +// armourDamageTakenData.push({ value: Math.round(armour.explosive.total * 100), label: translate('explosive') }); +// armourDamageTakenData.push({ value: Math.round(armour.kinetic.total * 100), label: translate('kinetic') }); +// armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal') }); return ( - + + {/* {shield.total ?

{translate('shield metrics')}

@@ -475,7 +175,7 @@ export default class Offence extends TranslatedComponent {

{translate('armour metrics')}

{armourTooltipDetails}

)} 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('infinity') : formats.time(armour.total / armourdamage.totalsdps)}

+

{translate('PHRASE_TIME_TO_LOSE_ARMOUR')}
{armourdamage.totalsdps == 0 ? translate('ever') : formats.time(armour.total / armourdamage.totalsdps)}

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

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

{translate('PHRASE_MODULE_PROTECTION_INTERNAL')}
{formats.pct1(armour.moduleprotection)}

@@ -493,6 +193,7 @@ export default class Offence extends TranslatedComponent {

{translate('effective armour')}

+ */}
); } } diff --git a/src/app/components/OutfittingSubpages.jsx b/src/app/components/OutfittingSubpages.jsx index 6a760833..18f7ac47 100644 --- a/src/app/components/OutfittingSubpages.jsx +++ b/src/app/components/OutfittingSubpages.jsx @@ -10,6 +10,7 @@ import CostSection from './CostSection'; import EngineProfile from './EngineProfile'; import FSDProfile from './FSDProfile'; import Movement from './Movement'; +import Offence from './Offence'; import Defence from './Defence'; import WeaponDamageChart from './WeaponDamageChart'; @@ -117,8 +118,10 @@ export default class OutfittingSubpages extends TranslatedComponent { _offenceTab() { const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild } = this.props; + const marker = `${ship.toString()}:${opponent.name}:${opponentBuild}:${engagementRange}`; + return
-

Offence goes here

+
; } diff --git a/src/app/components/WeaponDamageChart.jsx b/src/app/components/WeaponDamageChart.jsx index 90121e5b..990225aa 100644 --- a/src/app/components/WeaponDamageChart.jsx +++ b/src/app/components/WeaponDamageChart.jsx @@ -100,13 +100,12 @@ export default class WeaponDamageChart extends TranslatedComponent { */ _calcMaxSDps(ship, opponent, hull) { // Additional information to allow effectiveness calculations - const { shield, armour, shielddamage, armourdamage } = Calc.defenceMetrics(opponent, ship, 4, 0); - + const defence = hull ? Calc.armourMetrics(opponent) : Calc.shieldMetrics(opponent, 4); let maxSDps = 0; for (let i = 0; i < ship.hardpoints.length; i++) { if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) { const m = ship.hardpoints[i].m; - const thisSDps = this._calcWeaponSDps(ship, m, opponent, hull, shield, armour, 0); + const thisSDps = this._calcWeaponSDps(ship, m, opponent, defence, 0); if (thisSDps > maxSDps) { maxSDps = thisSDps; } @@ -156,14 +155,14 @@ export default class WeaponDamageChart extends TranslatedComponent { */ _calcSDps(ship, weaponNames, opponent, hull, engagementRange) { // Additional information to allow effectiveness calculations - const { shield, armour, shielddamage, armourdamage } = Calc.defenceMetrics(opponent, ship, 4, engagementRange); + const defence = hull ? Calc.armourMetrics(opponent) : Calc.shieldMetrics(opponent, 4); let results = {}; let weaponNum = 0; for (let i = 0; i < ship.hardpoints.length; i++) { if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) { const m = ship.hardpoints[i].m; - results[weaponNames[weaponNum++]] = this._calcWeaponSDps(ship, m, opponent, hull, shield, armour, engagementRange); + results[weaponNames[weaponNum++]] = this._calcWeaponSDps(ship, m, opponent, defence, engagementRange); } } return results; @@ -174,13 +173,11 @@ export default class WeaponDamageChart extends TranslatedComponent { * @param {Object} ship The ship that will deal the damage * @param {Object} m The weapon that will deal the damage * @param {Object} opponent The ship against which damage will be dealt - * @param {boolean} hull True if hitting hull - * @param {Object} shield Shield defence metrics - * @param {Object} armour Armour defence metrics + * @param {Object} defence defence metrics (either shield or hull) * @param {Object} engagementRange The engagement range * @return {object} Returns the sustained DPS for the weapon */ - _calcWeaponSDps(ship, m, opponent, hull, shield, armour, engagementRange) { + _calcWeaponSDps(ship, m, opponent, defence, engagementRange) { let falloff = 1; if (m.getFalloff()) { // Calculate the falloff % due to range @@ -199,16 +196,16 @@ export default class WeaponDamageChart extends TranslatedComponent { let effectiveness = 0; if (m.getDamageDist().E) { - effectiveness += m.getDamageDist().E * (hull ? armour.explosive.total : shield.explosive.total); + effectiveness += m.getDamageDist().E * defence.explosive.total; } if (m.getDamageDist().K) { - effectiveness += m.getDamageDist().K * (hull ? armour.kinetic.total : shield.kinetic.total); + effectiveness += m.getDamageDist().K * defence.kinetic.total; } if (m.getDamageDist().T) { - effectiveness += m.getDamageDist().T * (hull ? armour.thermal.total : shield.thermal.total); + effectiveness += m.getDamageDist().T * defence.thermal.total; } if (m.getDamageDist().A) { - effectiveness += m.getDamageDist().A * (hull ? armour.absolute.total : shield.absolute.total); + effectiveness += m.getDamageDist().A * defence.absolute.total; } // Return the final effective SDPS diff --git a/src/app/shipyard/Calculations.js b/src/app/shipyard/Calculations.js index 8c7a5368..b27be63e 100644 --- a/src/app/shipyard/Calculations.js +++ b/src/app/shipyard/Calculations.js @@ -310,23 +310,18 @@ export function calcYaw(mass, baseYaw, thrusters, engpip, eng, boostFactor, boos return result; } - /** - * Calculate defence metrics - * @param {Object} ship The ship - * @param {Object} opponent The opponent ship - * @param {int} sys The pips to SYS - * @param {int} engagementrange The range between the ship and opponent - * @returns {Object} Defence metrics - */ -export function defenceMetrics(ship, opponent, sys, engagementrange) { +/** + * Calculate shield metrics + * @param {Object} ship The ship + * @param {int} sys The pips to SYS + * @returns {Object} Shield metrics + */ +export function shieldMetrics(ship, sys) { const sysResistance = this.sysResistance(sys); const maxSysResistance = this.sysResistance(4); - // Obtain the opponent's sustained DPS on us for later damage calculations - const { shieldsdps, armoursdps } = this._sustainedDps(opponent, ship, engagementrange); - - let shielddamage = {}; let shield = {}; + const shieldGeneratorSlot = ship.findInternalByGroup('sg'); if (shieldGeneratorSlot && shieldGeneratorSlot.enabled && shieldGeneratorSlot.m) { const shieldGenerator = shieldGeneratorSlot.m; @@ -451,14 +446,17 @@ export function defenceMetrics(ship, opponent, sys, engagementrange) { total: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - sysResistance), max: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - maxSysResistance) }; - - shielddamage.absolutesdps = shieldsdps.absolute *= shield.absolute.total; - shielddamage.explosivesdps = shieldsdps.explosive *= shield.explosive.total; - shielddamage.kineticsdps = shieldsdps.kinetic *= shield.kinetic.total; - shielddamage.thermalsdps = shieldsdps.thermal *= shield.thermal.total; - shielddamage.totalsdps = shielddamage.absolutesdps + shielddamage.explosivesdps + shielddamage.kineticsdps + shielddamage.thermalsdps; } + return shield; +} + +/** + * Calculate armour metrics + * @param {Object} ship The ship + * @returns {Object} Armour metrics + */ +export function armourMetrics(ship) { // Armour from bulkheads const armourBulkheads = ship.baseArmour + (ship.baseArmour * ship.bulkheads.m.getHullBoost()); let armourReinforcement = 0; @@ -527,6 +525,35 @@ export function defenceMetrics(ship, opponent, sys, engagementrange) { total: (1 - ship.bulkheads.m.getThermalResistance()) * hullThermDmg }; + return armour; +} + +/** + * Calculate defence metrics for a ship + * @param {Object} ship The ship + * @param {Object} opponent The opponent ship + * @param {int} sys The pips to SYS + * @param {int} engagementrange The range between the ship and opponent + * @returns {Object} Defence metrics + */ +export function defenceMetrics(ship, opponent, sys, engagementrange) { + // Obtain the shield metrics + const shield = this.shieldMetrics(ship, sys); + + // Obtain the armour metrics + const armour = this.armourMetrics(ship); + + // Obtain the opponent's sustained DPS on us + const { shieldsdps, armoursdps } = this._sustainedDps(opponent, ship, engagementrange); + + const shielddamage = shield.generatorStrength ? { + absolutesdps: shieldsdps.absolute *= shield.absolute.total, + explosivesdps: shieldsdps.explosive *= shield.explosive.total, + kineticsdps: shieldsdps.kinetic *= shield.kinetic.total, + thermalsdps: shieldsdps.thermal *= shield.thermal.total, + totalsdps: shielddamage.absolutesdps + shielddamage.explosivesdps + shielddamage.kineticsdps + shielddamage.thermalsdps + } : {} ; + const armourdamage = { absolutesdps: armoursdps.absolute *= armour.absolute.total, explosivesdps: armoursdps.explosive *= armour.explosive.total, @@ -538,6 +565,37 @@ export function defenceMetrics(ship, opponent, sys, engagementrange) { return { shield, armour, shielddamage, armourdamage }; } +/** + * Calculate offence metrics for a ship + * @param {Object} ship The ship + * @param {Object} opponent The opponent ship + * @param {int} wep The pips to WEP + * @param {int} engagementrange The range between the ship and opponent + * @returns {Object} Offence metrics + */ +export function offenceMetrics(ship, opponent, wep, engagementrange) { + + // Per-weapon and total damage to armour + const armourdamage = {}; + + // Obtain the opponent's shield and armour metrics + const opponentShields = this.shieldMetrics(opponent, 4); + const opponentArmour = this.armourMetrics(opponent); + + // Per-weapon and total damage to shields + const shielddamage = opponentShields.generatorStrength ? { + absolute: { + weapon1: 10, + weapon2: 10, + weapon3: 10, + weapon4: 10, + total: 40 + } + } : {}; + + return { shielddamage, armourdamage }; +} + /** * Calculate the resistance provided by SYS pips * @param {integer} sys the value of the SYS pips