mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-08 22:33:24 +00:00
Updates
This commit is contained in:
@@ -69,9 +69,9 @@ export default class Defence extends TranslatedComponent {
|
||||
shieldSourcesData.push({ value: Math.round(shield.boosters), label: translate('boosters') });
|
||||
shieldSourcesData.push({ value: Math.round(shield.cells), label: translate('cells') });
|
||||
|
||||
shieldTooltipDetails.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
||||
shieldTooltipDetails.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
||||
shieldTooltipDetails.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
||||
if (shield.generator > 0) shieldTooltipDetails.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
||||
if (shield.boosters > 0) shieldTooltipDetails.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
||||
if (shield.cells > 0) shieldTooltipDetails.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
||||
|
||||
shieldAbsoluteTooltipDetails.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.absolute.generator)}</div>);
|
||||
shieldAbsoluteTooltipDetails.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.absolute.boosters)}</div>);
|
||||
@@ -98,10 +98,10 @@ export default class Defence extends TranslatedComponent {
|
||||
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'), 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 });
|
||||
|
||||
maxEffectiveShield = Math.max(shield.total / shield.absolute.max, shield.total / shield.explosive.max, shield.total / shield.kinetic.max, shield.total / shield.thermal.max);
|
||||
}
|
||||
@@ -111,8 +111,8 @@ export default class Defence extends TranslatedComponent {
|
||||
armourSourcesData.push({ value: Math.round(armour.reinforcement), label: translate('reinforcement') });
|
||||
|
||||
const armourTooltipDetails = [];
|
||||
armourTooltipDetails.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
||||
armourTooltipDetails.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
||||
if (armour.bulkheads > 0) armourTooltipDetails.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
||||
if (armour.reinforcement > 0) armourTooltipDetails.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
||||
|
||||
const armourAbsoluteTooltipDetails = [];
|
||||
armourAbsoluteTooltipDetails.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.absolute.bulkheads)}</div>);
|
||||
@@ -141,10 +141,10 @@ export default class Defence extends TranslatedComponent {
|
||||
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') });
|
||||
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 });
|
||||
|
||||
return (
|
||||
<span id='defence'>
|
||||
|
||||
@@ -84,7 +84,7 @@ export default class LineChart extends TranslatedComponent {
|
||||
y0 = func(x0),
|
||||
tips = this.tipContainer,
|
||||
yTotal = 0,
|
||||
flip = (xPos / width > 0.60),
|
||||
flip = (xPos / width > 0.50),
|
||||
tipWidth = 0,
|
||||
tipHeightPx = tips.selectAll('rect').node().getBoundingClientRect().height;
|
||||
|
||||
|
||||
@@ -2,8 +2,45 @@ import React from 'react';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import * as Calc from '../shipyard/Calculations';
|
||||
import PieChart from './PieChart';
|
||||
import { nameComparator } from '../utils/SlotFunctions';
|
||||
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
||||
import VerticalBarChart from './VerticalBarChart';
|
||||
|
||||
/**
|
||||
* Generates an internationalization friendly weapon comparator that will
|
||||
* sort by specified property (if provided) then by name/group, class, rating
|
||||
* @param {function} translate Translation function
|
||||
* @param {function} propComparator Optional property comparator
|
||||
* @param {boolean} desc Use descending order
|
||||
* @return {function} Comparator function for names
|
||||
*/
|
||||
export function weaponComparator(translate, propComparator, desc) {
|
||||
return (a, b) => {
|
||||
if (!desc) { // Flip A and B if ascending order
|
||||
let t = a;
|
||||
a = b;
|
||||
b = t;
|
||||
}
|
||||
|
||||
// If a property comparator is provided use it first
|
||||
let diff = propComparator ? propComparator(a, b) : nameComparator(translate, a, b);
|
||||
|
||||
if (diff) {
|
||||
return diff;
|
||||
}
|
||||
|
||||
// Property matches so sort by name / group, then class, rating
|
||||
if (a.name === b.name && a.grp === b.grp) {
|
||||
if(a.class == b.class) {
|
||||
return a.rating > b.rating ? 1 : -1;
|
||||
}
|
||||
return a.class - b.class;
|
||||
}
|
||||
|
||||
return nameComparator(translate, a, b);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Offence information
|
||||
* Offence information consists of four panels:
|
||||
@@ -28,8 +65,14 @@ export default class Offence extends TranslatedComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const { shield, armour, shielddamage, armourdamage } = Calc.offenceMetrics(props.ship, props.opponent, props.eng, props.engagementrange);
|
||||
this.state = { shield, armour, shielddamage, armourdamage };
|
||||
this._sort = this._sort.bind(this);
|
||||
|
||||
const damage = Calc.offenceMetrics(props.ship, props.opponent, props.eng, props.engagementrange);
|
||||
this.state = {
|
||||
predicate: 'n',
|
||||
desc: true,
|
||||
damage
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,12 +82,51 @@ export default class Offence extends TranslatedComponent {
|
||||
*/
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.marker != nextProps.marker || this.props.sys != nextProps.sys) {
|
||||
const { shield, armour, shielddamage, armourdamage } = Calc.offenceMetrics(nextProps.ship, nextProps.opponent, nextProps.wep, nextProps.engagementrange);
|
||||
this.setState({ shield, armour, shielddamage, armourdamage });
|
||||
const damage = Calc.offenceMetrics(nextProps.ship, nextProps.opponent, nextProps.wep, nextProps.engagementrange);
|
||||
this.setState({ damage });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sort order and sort
|
||||
* @param {string} predicate Sort predicate
|
||||
*/
|
||||
_sortOrder(predicate) {
|
||||
let desc = this.state.desc;
|
||||
|
||||
if (predicate == this.state.predicate) {
|
||||
desc = !desc;
|
||||
} else {
|
||||
desc = true;
|
||||
}
|
||||
|
||||
this._sort(this.props.ship, predicate, desc);
|
||||
this.setState({ predicate, desc });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the weapon list
|
||||
* @param {Ship} ship Ship instance
|
||||
* @param {string} predicate Sort predicate
|
||||
* @param {Boolean} desc Sort order descending
|
||||
*/
|
||||
_sort(ship, predicate, desc) {
|
||||
let comp = weaponComparator.bind(null, this.context.language.translate);
|
||||
|
||||
switch (predicate) {
|
||||
case 'n': comp = comp(null, desc); break;
|
||||
case 'edpss': comp = comp((a, b) => a.effectiveDpsShields - b.effectiveDpsShields, desc); break;
|
||||
case 'esdpss': comp = comp((a, b) => a.effectiveSDpsShields - b.effectiveSDpsShields, desc); break;
|
||||
case 'es': comp = comp((a, b) => a.effectivenessShields - b.effectivenessShields, desc); break;
|
||||
case 'edpsh': comp = comp((a, b) => a.effectiveDpsHull - b.effectiveDpsHull, desc); break;
|
||||
case 'esdpsh': comp = comp((a, b) => a.effectiveSDpsHull - b.effectiveSDpsHull, desc); break;
|
||||
case 'eh': comp = comp((a, b) => a.effectivenessHull - b.effectivenessHull, desc); break;
|
||||
}
|
||||
|
||||
this.state.damage.sort(comp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render offence
|
||||
* @return {React.Component} contents
|
||||
@@ -53,8 +135,26 @@ export default class Offence extends TranslatedComponent {
|
||||
const { ship, wep } = this.props;
|
||||
const { language, tooltip, termtip } = this.context;
|
||||
const { formats, translate, units } = language;
|
||||
// const { shield, armour, shielddamage, armourdamage } = this.state;
|
||||
const { damage } = this.state;
|
||||
const sortOrder = this._sortOrder;
|
||||
|
||||
const rows = [];
|
||||
for (let i = 0; i < damage.length; i++) {
|
||||
const weapon = damage[i];
|
||||
rows.push(<tr key={weapon.id}>
|
||||
<td className='ri'>
|
||||
{weapon.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed className='icon'/></span> : null}
|
||||
{weapon.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : null}
|
||||
{weapon.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : null}
|
||||
{weapon.classRating} {translate(weapon.name)}
|
||||
{weapon.engineering ? ' (' + weapon.engineering + ')' : null }
|
||||
</td>
|
||||
<td className='ri'>{formats.f1(weapon.sdpsShields)}</td>
|
||||
<td className='ri'>{formats.pct1(weapon.effectivenessShields)}</td>
|
||||
<td className='ri'>{formats.f1(weapon.sdpsArmour)}</td>
|
||||
<td className='ri'>{formats.pct1(weapon.effectivenessArmour)}</td>
|
||||
</tr>);
|
||||
}
|
||||
// const shieldSourcesData = [];
|
||||
// const effectiveShieldData = [];
|
||||
// const shieldDamageTakenData = [];
|
||||
@@ -148,52 +248,24 @@ export default class Offence extends TranslatedComponent {
|
||||
|
||||
return (
|
||||
<span id='offence'>
|
||||
{/*
|
||||
{shield.total ? <span>
|
||||
<div className='group quarter'>
|
||||
<h2>{translate('shield metrics')}</h2>
|
||||
<br/>
|
||||
<h2 onMouseOver={termtip.bind(null, <div>{shieldTooltipDetails}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw shield strength')}<br/>{formats.int(shield.total)}{units.MJ}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_SHIELDS')}<br/>{shielddamage.totalsdps == 0 ? translate('ever') : formats.time(shield.total / shielddamage.totalsdps)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECOVER'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECOVER_SHIELDS')}<br/>{shield.recover === Math.Inf ? translate('never') : formats.time(shield.recover)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECHARGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECHARGE_SHIELDS')}<br/>{shield.recharge === Math.Inf ? translate('never') : formats.time(shield.recharge)}</h2>
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_SOURCES'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield sources')}</h2>
|
||||
<PieChart data={shieldSourcesData} />
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_DAMAGE_TAKEN'))} onMouseOut={tooltip.bind(null, null)}>{translate('damage taken')}(%)</h2>
|
||||
<VerticalBarChart data={shieldDamageTakenData} yMax={100} />
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_EFFECTIVE_SHIELD'))} onMouseOut={tooltip.bind(null, null)}>{translate('effective shield')}(MJ)</h2>
|
||||
<VerticalBarChart data={effectiveShieldData} yMax={maxEffectiveShield}/>
|
||||
</div>
|
||||
</span> : null }
|
||||
|
||||
<div className='group quarter'>
|
||||
<h2>{translate('armour metrics')}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, <div>{armourTooltipDetails}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw armour strength')}<br/>{formats.int(armour.total)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_ARMOUR')}<br/>{armourdamage.totalsdps == 0 ? translate('ever') : formats.time(armour.total / armourdamage.totalsdps)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('raw module armour')}<br/>{formats.int(armour.modulearmour)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_PROTECTION_EXTERNAL'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_MODULE_PROTECTION_EXTERNAL')}<br/>{formats.pct1(armour.moduleprotection / 2)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_PROTECTION_INTERNAL'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_MODULE_PROTECTION_INTERNAL')}<br/>{formats.pct1(armour.moduleprotection)}</h2>
|
||||
<br/>
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_ARMOUR_SOURCES'))} onMouseOut={tooltip.bind(null, null)}>{translate('armour sources')}</h2>
|
||||
<PieChart data={armourSourcesData} />
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_DAMAGE_TAKEN'))} onMouseOut={tooltip.bind(null, null)}>{translate('damage taken')}(%)</h2>
|
||||
<VerticalBarChart data={armourDamageTakenData} yMax={100} />
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_EFFECTIVE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('effective armour')}</h2>
|
||||
<VerticalBarChart data={effectiveArmourData} />
|
||||
</div>
|
||||
*/}
|
||||
<table width='100%'>
|
||||
<thead>
|
||||
<tr className='main'>
|
||||
<th rowSpan='2' className='sortable' onClick={sortOrder.bind(this, 'n')}>{translate('weapon')}</th>
|
||||
<th colSpan='2'>{translate('opponent shields')}</th>
|
||||
<th colSpan='2'>{translate('opponent armour')}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className='lft sortable' onClick={sortOrder.bind(this, 'esdpss')}>{translate('effective sdps')}</th>
|
||||
<th className='sortable' onClick={sortOrder.bind(this, 'es')}>{translate('effectiveness')}</th>
|
||||
<th className='lft sortable' onClick={sortOrder.bind(this, 'esdpsh')}>{translate('effective sdps')}</th>
|
||||
<th className='sortable' onClick={sortOrder.bind(this, 'eh')}>{translate('effectiveness')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows}
|
||||
</tbody>
|
||||
</table>
|
||||
</span>);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import Measure from 'react-measure';
|
||||
import * as d3 from 'd3';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
|
||||
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#519032', '#D5420D'];
|
||||
const LABEL_COLOUR = '#FFFFFF';
|
||||
@@ -16,7 +17,7 @@ const merge = function(one, two) {
|
||||
/**
|
||||
* A vertical bar chart
|
||||
*/
|
||||
export default class VerticalBarChart extends Component {
|
||||
export default class VerticalBarChart extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
data : React.PropTypes.array.isRequired,
|
||||
@@ -45,6 +46,7 @@ export default class VerticalBarChart extends Component {
|
||||
*/
|
||||
_renderGraph(props) {
|
||||
let { width, height } = this.state.dimensions;
|
||||
const { tooltip, termtip } = this.context;
|
||||
|
||||
width = width - margin.left - margin.right,
|
||||
height = width * ASPECT - margin.top - margin.bottom;
|
||||
@@ -104,9 +106,9 @@ export default class VerticalBarChart extends Component {
|
||||
.attr('y', 100)
|
||||
.attr('stroke-width', '0px')
|
||||
.attr('fill', '#ffffff')
|
||||
.attr('x', d => this.x(d.label) + this.x.bandwidth() / 2)
|
||||
.attr('y', d => this.y(d.value) + 15)
|
||||
.text(d => d.value);
|
||||
.attr('x', d => this.x(d.label) + this.x.bandwidth() / 2)
|
||||
.attr('y', d => this.y(d.value) + 15)
|
||||
.text(d => d.value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,7 +127,7 @@ export default class VerticalBarChart extends Component {
|
||||
<Measure width='100%' whitelist={['width', 'top']} onMeasure={ (dimensions) => { this.setState({ dimensions }); }}>
|
||||
<div width={width} height={height}>
|
||||
{ this.x ?
|
||||
<svg ref={ref => this.svg = ref} width={width} height={height} transform={translate}>
|
||||
<svg ref={ref => this.svg = ref} width={width} height={height}>
|
||||
<g transform={translate}></g>
|
||||
</svg> : null }
|
||||
</div>
|
||||
|
||||
@@ -30,17 +30,6 @@ export default class WeaponDamageChart extends TranslatedComponent {
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
const { ship, opponent, hull } = this.props;
|
||||
|
||||
const maxRange = this._calcMaxRange(ship);
|
||||
// We take whichever is the higher for shields and hull to ensure same Y axis for both
|
||||
const maxDps = Math.max(this._calcMaxSDps(ship, opponent, true), this._calcMaxSDps(ship, opponent, false));
|
||||
|
||||
this.state = {
|
||||
maxRange,
|
||||
maxDps
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,7 +37,12 @@ export default class WeaponDamageChart extends TranslatedComponent {
|
||||
*/
|
||||
componentWillMount() {
|
||||
const weaponNames = this._weaponNames(this.props.ship, this.context);
|
||||
this.setState({ weaponNames, calcSDpsFunc: this._calcSDps.bind(this, this.props.ship, weaponNames, this.props.opponent, this.props.hull) });
|
||||
const opponentShields = Calc.shieldMetrics(this.props.opponent, 4);
|
||||
const opponentArmour = Calc.armourMetrics(this.props.opponent);
|
||||
const maxRange = this._calcMaxRange(this.props.ship);
|
||||
const maxDps = this._calcMaxSDps(this.props.ship, this.props.opponent, opponentShields, opponentArmour);
|
||||
|
||||
this.setState({ maxRange, maxDps, weaponNames, opponentShields, opponentArmour, calcSDpsFunc: this._calcSDps.bind(this, this.props.ship, weaponNames, this.props.opponent, opponentShields, opponentArmour, this.props.hull) });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,13 +54,16 @@ export default class WeaponDamageChart extends TranslatedComponent {
|
||||
componentWillReceiveProps(nextProps, nextContext) {
|
||||
if (nextProps.marker != this.props.marker) {
|
||||
const weaponNames = this._weaponNames(nextProps.ship, nextContext);
|
||||
const opponentShields = Calc.shieldMetrics(nextProps.opponent, 4);
|
||||
const opponentArmour = Calc.armourMetrics(nextProps.opponent);
|
||||
const maxRange = this._calcMaxRange(nextProps.ship);
|
||||
// We take whichever is the higher for shields and hull to ensure same Y axis for both
|
||||
const maxDps = Math.max(this._calcMaxSDps(nextProps.ship, nextProps.opponent, true), this._calcMaxSDps(nextProps.ship, nextProps.opponent, false));
|
||||
const maxDps = this._calcMaxSDps(nextProps.ship, nextProps.opponent, opponentShields, opponentArmour);
|
||||
this.setState({ weaponNames,
|
||||
opponentShields,
|
||||
opponentArmour,
|
||||
maxRange,
|
||||
maxDps,
|
||||
calcSDpsFunc: this._calcSDps.bind(this, nextProps.ship, weaponNames, nextProps.opponent, nextProps.hull)
|
||||
calcSDpsFunc: this._calcSDps.bind(this, nextProps.ship, weaponNames, nextProps.opponent, opponentShields, opponentArmour, nextProps.hull)
|
||||
});
|
||||
}
|
||||
return true;
|
||||
@@ -93,19 +90,21 @@ export default class WeaponDamageChart extends TranslatedComponent {
|
||||
|
||||
/**
|
||||
* Calculate the maximum sustained single-weapon DPS for this ship
|
||||
* @param {Object} ship The ship
|
||||
* @param {Object} opponent The opponent ship
|
||||
* @param {bool} hull True if against hull
|
||||
* @return {number} The maximum sustained single-weapon DPS
|
||||
* @param {Object} ship The ship
|
||||
* @param {Object} opponent The opponent ship
|
||||
* @param {Object} opponentShields The opponent's shields
|
||||
* @param {Object} opponentArmour The opponent's armour
|
||||
* @return {number} The maximum sustained single-weapon DPS
|
||||
*/
|
||||
_calcMaxSDps(ship, opponent, hull) {
|
||||
_calcMaxSDps(ship, opponent, opponentShields, opponentArmour) {
|
||||
// Additional information to allow effectiveness calculations
|
||||
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, defence, 0);
|
||||
|
||||
const sustainedDps = Calc._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, 0);
|
||||
const thisSDps = sustainedDps.damage.armour.total > sustainedDps.damage.shields.total ? sustainedDps.damage.armour.total : sustainedDps.damage.shields.total;
|
||||
if (thisSDps > maxSDps) {
|
||||
maxSDps = thisSDps;
|
||||
}
|
||||
@@ -149,69 +148,25 @@ export default class WeaponDamageChart extends TranslatedComponent {
|
||||
* @param {Object} ship The ship
|
||||
* @param {Object} weaponNames The names of the weapons for which to calculate DPS
|
||||
* @param {Object} opponent The target
|
||||
* @param {Object} opponentShields The opponent's shields
|
||||
* @param {Object} opponentArmour The opponent's armour
|
||||
* @param {bool} hull true if to calculate against hull, false if to calculate against shields
|
||||
* @param {Object} engagementRange The engagement range
|
||||
* @return {array} The array of weapon DPS
|
||||
*/
|
||||
_calcSDps(ship, weaponNames, opponent, hull, engagementRange) {
|
||||
// Additional information to allow effectiveness calculations
|
||||
const defence = hull ? Calc.armourMetrics(opponent) : Calc.shieldMetrics(opponent, 4);
|
||||
|
||||
_calcSDps(ship, weaponNames, opponent, opponentShields, opponentArmour, hull, engagementRange) {
|
||||
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, defence, engagementRange);
|
||||
const sustainedDps = Calc._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementRange);
|
||||
results[weaponNames[weaponNum++]] = hull ? sustainedDps.damage.armour.total : sustainedDps.damage.shields.total;
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the sustained DPS for a particular weapon for this ship against another ship at a given range
|
||||
* @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 {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, defence, engagementRange) {
|
||||
let falloff = 1;
|
||||
if (m.getFalloff()) {
|
||||
// Calculate the falloff % due to range
|
||||
if (engagementRange > m.getRange()) {
|
||||
// Weapon is out of range
|
||||
falloff = 0;
|
||||
} else {
|
||||
const falloffPoint = m.getFalloff();
|
||||
if (engagementRange > falloffPoint) {
|
||||
const falloffRange = m.getRange() - falloffPoint;
|
||||
// Assuming straight-line falloff
|
||||
falloff = 1 - (engagementRange - falloffPoint) / falloffRange;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let effectiveness = 0;
|
||||
if (m.getDamageDist().E) {
|
||||
effectiveness += m.getDamageDist().E * defence.explosive.total;
|
||||
}
|
||||
if (m.getDamageDist().K) {
|
||||
effectiveness += m.getDamageDist().K * defence.kinetic.total;
|
||||
}
|
||||
if (m.getDamageDist().T) {
|
||||
effectiveness += m.getDamageDist().T * defence.thermal.total;
|
||||
}
|
||||
if (m.getDamageDist().A) {
|
||||
effectiveness += m.getDamageDist().A * defence.absolute.total;
|
||||
}
|
||||
|
||||
// Return the final effective SDPS
|
||||
return (m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getDps()) * falloff * effectiveness;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render damage dealt
|
||||
* @return {React.Component} contents
|
||||
|
||||
@@ -544,23 +544,23 @@ export function defenceMetrics(ship, opponent, sys, engagementrange) {
|
||||
const armour = this.armourMetrics(ship);
|
||||
|
||||
// Obtain the opponent's sustained DPS on us
|
||||
const { shieldsdps, armoursdps } = this._sustainedDps(opponent, ship, engagementrange);
|
||||
const sustainedDps = this.sustainedDps(opponent, ship, sys, 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 shielddamage = shield.generator ? {
|
||||
absolutesdps: sustainedDps.shieldsdps.absolute,
|
||||
explosivesdps: sustainedDps.shieldsdps.explosive,
|
||||
kineticsdps: sustainedDps.shieldsdps.kinetic,
|
||||
thermalsdps: sustainedDps.shieldsdps.thermal,
|
||||
totalsdps: sustainedDps.shieldsdps.absolute + sustainedDps.shieldsdps.explosive + sustainedDps.shieldsdps.kinetic + sustainedDps.shieldsdps.thermal
|
||||
} : {};
|
||||
|
||||
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
|
||||
absolutesdps: sustainedDps.armoursdps.absolute,
|
||||
explosivesdps: sustainedDps.armoursdps.explosive,
|
||||
kineticsdps: sustainedDps.armoursdps.kinetic,
|
||||
thermalsdps: sustainedDps.armoursdps.thermal,
|
||||
totalsdps: sustainedDps.armoursdps.absolute + sustainedDps.armoursdps.explosive + sustainedDps.armoursdps.kinetic + sustainedDps.armoursdps.thermal
|
||||
};
|
||||
armourdamage.totalsdps = armourdamage.absolutesdps + armourdamage.explosivesdps + armourdamage.kineticsdps + armourdamage.thermalsdps;
|
||||
|
||||
return { shield, armour, shielddamage, armourdamage };
|
||||
}
|
||||
@@ -571,19 +571,48 @@ export function defenceMetrics(ship, opponent, sys, engagementrange) {
|
||||
* @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
|
||||
* @returns {array} Offence metrics
|
||||
*/
|
||||
export function offenceMetrics(ship, opponent, wep, engagementrange) {
|
||||
|
||||
// Per-weapon and total damage to armour
|
||||
const armourdamage = {};
|
||||
// Per-weapon and total damage
|
||||
const damage = [];
|
||||
|
||||
// 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 ? {
|
||||
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 classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
|
||||
let engineering;
|
||||
if (m.blueprint && m.blueprint.name) {
|
||||
engineering = m.blueprint.name + ' ' + 'grade' + ' ' + m.blueprint.grade;
|
||||
if (m.blueprint.special && m.blueprint.special.id >= 0) {
|
||||
engineering += ', ' + m.blueprint.special.name;
|
||||
}
|
||||
}
|
||||
|
||||
const weaponSustainedDps = this._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange);
|
||||
damage.push({
|
||||
id: i,
|
||||
mount: m.mount,
|
||||
name: m.name || m.grp,
|
||||
classRating,
|
||||
engineering,
|
||||
sdpsShields: weaponSustainedDps.damage.shields.total,
|
||||
sdpsArmour: weaponSustainedDps.damage.armour.total,
|
||||
effectivenessShields: weaponSustainedDps.effectiveness.shields.total,
|
||||
effectivenessArmour: weaponSustainedDps.effectiveness.armour.total
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return damage;
|
||||
|
||||
const shielddamage = opponentShields.generator ? {
|
||||
absolute: {
|
||||
weapon1: 10,
|
||||
weapon2: 10,
|
||||
@@ -593,7 +622,7 @@ export function offenceMetrics(ship, opponent, wep, engagementrange) {
|
||||
}
|
||||
} : {};
|
||||
|
||||
return { shielddamage, armourdamage };
|
||||
return damage;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -616,13 +645,31 @@ export function sysRechargeRate(pd, sys) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the sustained DPS for a ship at a given range, excluding resistances
|
||||
* Calculate the sustained DPS for a ship against an opponent at a given range
|
||||
* @param {Object} ship The ship
|
||||
* @param {Object} opponent The opponent ship
|
||||
* @param {number} sys Pips to opponent's SYS
|
||||
* @param {int} engagementrange The range between the ship and opponent
|
||||
* @returns {Object} Sustained DPS for shield and armour
|
||||
*/
|
||||
export function _sustainedDps(ship, opponent, engagementrange) {
|
||||
export function sustainedDps(ship, opponent, sys, engagementrange) {
|
||||
// Obtain the opponent's shield and armour metrics
|
||||
const opponentShields = this.shieldMetrics(opponent, sys);
|
||||
const opponentArmour = this.armourMetrics(opponent);
|
||||
|
||||
return this._sustainedDps(ship, opponent, opponentShields, opponentArmour, engagementrange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the sustained DPS for a ship against an opponent at a given range
|
||||
* @param {Object} ship The ship
|
||||
* @param {Object} opponent The opponent ship
|
||||
* @param {Object} opponentShields The opponent's shield resistances
|
||||
* @param {Object} opponentArmour The opponent's armour resistances
|
||||
* @param {int} engagementrange The range between the ship and opponent
|
||||
* @returns {Object} Sustained DPS for shield and armour
|
||||
*/
|
||||
export function _sustainedDps(ship, opponent, opponentShields, opponentArmour, engagementrange) {
|
||||
const shieldsdps = {
|
||||
absolute: 0,
|
||||
explosive: 0,
|
||||
@@ -640,35 +687,112 @@ export function _sustainedDps(ship, opponent, engagementrange) {
|
||||
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;
|
||||
}
|
||||
const sustainedDps = this._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange);
|
||||
shieldsdps.absolute += sustainedDps.damage.shields.absolute;
|
||||
shieldsdps.explosive += sustainedDps.damage.shields.explosive;
|
||||
shieldsdps.kinetic += sustainedDps.damage.shields.kinetic;
|
||||
shieldsdps.thermal += sustainedDps.damage.shields.thermal;
|
||||
armoursdps.absolute += sustainedDps.damage.armour.absolute;
|
||||
armoursdps.explosive += sustainedDps.damage.armour.explosive;
|
||||
armoursdps.kinetic += sustainedDps.damage.armour.kinetic;
|
||||
armoursdps.thermal += sustainedDps.damage.armour.thermal;
|
||||
}
|
||||
}
|
||||
|
||||
return { shieldsdps, armoursdps };
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the sustained DPS for a weapon at a given range
|
||||
* @param {Object} m The weapon
|
||||
* @param {Object} opponent The opponent ship
|
||||
* @param {Object} opponentShields The opponent's shield resistances
|
||||
* @param {Object} opponentArmour The opponent's armour resistances
|
||||
* @param {int} engagementrange The range between the ship and opponent
|
||||
* @returns {Object} Sustained DPS for shield and armour
|
||||
*/
|
||||
export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange) {
|
||||
const weapon = {
|
||||
damage: {
|
||||
shields: {
|
||||
absolute: 0,
|
||||
explosive: 0,
|
||||
kinetic: 0,
|
||||
thermal: 0,
|
||||
total: 0
|
||||
},
|
||||
armour: {
|
||||
absolute: 0,
|
||||
explosive: 0,
|
||||
kinetic: 0,
|
||||
thermal: 0,
|
||||
total: 0
|
||||
},
|
||||
},
|
||||
effectiveness: {
|
||||
shields: {
|
||||
range: 1,
|
||||
sys: opponentShields.absolute.sys,
|
||||
resistance: 1
|
||||
},
|
||||
armour: {
|
||||
range: 1,
|
||||
hardness: 1,
|
||||
resistance: 1
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 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;
|
||||
const dropoff = 1 - Math.min((engagementrange - falloff) / dropoffRange, 1);
|
||||
weapon.effectiveness.shields.range = weapon.effectiveness.armour.range = dropoff;
|
||||
sDps *= dropoff;
|
||||
}
|
||||
|
||||
// Piercing/hardness modifier (for armour only)
|
||||
const armourMultiple = m.getPiercing() >= opponent.hardness ? 1 : m.getPiercing() / opponent.hardness;
|
||||
weapon.effectiveness.armour.hardness = armourMultiple;
|
||||
|
||||
// Break out the damage according to type
|
||||
let shieldsResistance = 0;
|
||||
let armourResistance = 0;
|
||||
if (m.getDamageDist().A) {
|
||||
weapon.damage.shields.absolute += sDps * m.getDamageDist().A * opponentShields.absolute.total;
|
||||
weapon.damage.armour.absolute += sDps * m.getDamageDist().A * armourMultiple * opponentArmour.absolute.total;
|
||||
shieldsResistance += m.getDamageDist().A * opponentShields.absolute.generator * opponentShields.absolute.boosters;
|
||||
armourResistance += m.getDamageDist().A * opponentArmour.absolute.bulkheads * opponentArmour.absolute.reinforcement;
|
||||
}
|
||||
if (m.getDamageDist().E) {
|
||||
weapon.damage.shields.explosive += sDps * m.getDamageDist().E * opponentShields.explosive.total;
|
||||
weapon.damage.armour.explosive += sDps * m.getDamageDist().E * armourMultiple * opponentArmour.explosive.total;
|
||||
shieldsResistance += m.getDamageDist().E * opponentShields.explosive.generator * opponentShields.explosive.boosters;
|
||||
armourResistance += m.getDamageDist().E * opponentArmour.explosive.bulkheads * opponentArmour.explosive.reinforcement;
|
||||
}
|
||||
if (m.getDamageDist().K) {
|
||||
weapon.damage.shields.kinetic += sDps * m.getDamageDist().K * opponentShields.kinetic.total;
|
||||
weapon.damage.armour.kinetic += sDps * m.getDamageDist().K * armourMultiple * opponentArmour.kinetic.total;
|
||||
shieldsResistance += m.getDamageDist().K * opponentShields.kinetic.generator * opponentShields.kinetic.boosters;
|
||||
armourResistance += m.getDamageDist().K * opponentArmour.kinetic.bulkheads * opponentArmour.kinetic.reinforcement;
|
||||
}
|
||||
if (m.getDamageDist().T) {
|
||||
weapon.damage.shields.thermal += sDps * m.getDamageDist().T * opponentShields.thermal.total;
|
||||
weapon.damage.armour.thermal += sDps * m.getDamageDist().T * armourMultiple * opponentArmour.thermal.total;
|
||||
shieldsResistance += m.getDamageDist().T * opponentShields.thermal.generator * opponentShields.thermal.boosters;
|
||||
armourResistance += m.getDamageDist().T * opponentArmour.thermal.bulkheads * opponentArmour.thermal.reinforcement;
|
||||
}
|
||||
weapon.damage.shields.total = weapon.damage.shields.absolute + weapon.damage.shields.explosive + weapon.damage.shields.kinetic + weapon.damage.shields.thermal;
|
||||
weapon.damage.armour.total = weapon.damage.armour.absolute + weapon.damage.armour.explosive + weapon.damage.armour.kinetic + weapon.damage.armour.thermal;
|
||||
|
||||
weapon.effectiveness.shields.resistance *= shieldsResistance;
|
||||
weapon.effectiveness.armour.resistance *= armourResistance;
|
||||
|
||||
weapon.effectiveness.shields.total = weapon.effectiveness.shields.range * weapon.effectiveness.shields.sys * weapon.effectiveness.shields.resistance;
|
||||
weapon.effectiveness.armour.total = weapon.effectiveness.armour.range * weapon.effectiveness.armour.resistance * weapon.effectiveness.armour.hardness;
|
||||
return weapon;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user