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: * - 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 = { marker: React.PropTypes.string.isRequired, ship: React.PropTypes.object.isRequired, opponent: React.PropTypes.object.isRequired, engagementrange: React.PropTypes.number.isRequired, wep: React.PropTypes.number.isRequired, opponentSys: React.PropTypes.number.isRequired }; /** * Constructor * @param {Object} props React Component properties */ constructor(props) { super(props); this._sort = this._sort.bind(this); const damage = Calc.offenceMetrics(props.ship, props.opponent, props.wep, props.opponentSys, props.engagementrange); this.state = { predicate: 'n', desc: true, damage }; } /** * Update the state if our properties change * @param {Object} nextProps Incoming/Next properties * @return {boolean} Returns true if the component should be rerendered */ componentWillReceiveProps(nextProps) { if (this.props.marker != nextProps.marker || this.props.eng != nextProps.eng) { const damage = Calc.offenceMetrics(nextProps.ship, nextProps.opponent, nextProps.wep, nextProps.opponentSys, 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(predicate, desc); this.setState({ predicate, desc }); } /** * Sorts the weapon list * @param {string} predicate Sort predicate * @param {Boolean} desc Sort order descending */ _sort(predicate, desc) { let comp = weaponComparator.bind(null, this.context.language.translate); switch (predicate) { case 'n': comp = comp(null, desc); break; case 'esdpss': comp = comp((a, b) => a.sdps.shields.total - b.sdps.shields.total, desc); break; case 'es': comp = comp((a, b) => a.effectiveness.shields.total - b.effectiveness.shields.total, desc); break; case 'esdpsh': comp = comp((a, b) => a.sdps.armour.total - b.sdps.armour.total, desc); break; case 'eh': comp = comp((a, b) => a.effectiveness.armour.total - b.effectiveness.armour.total, desc); break; } this.state.damage.sort(comp); } /** * Render offence * @return {React.Component} contents */ render() { const { ship, opponent, wep, engagementrange } = this.props; const { language, tooltip, termtip } = this.context; const { formats, translate, units } = language; const { damage } = this.state; const sortOrder = this._sortOrder; const pd = ship.standard[4].m; const opponentShields = Calc.shieldMetrics(opponent, 4); const opponentArmour = Calc.armourMetrics(opponent); const timeToDrain = Calc.timeToDrainWep(ship, wep); let absoluteShieldsSDps = 0; let explosiveShieldsSDps = 0; let kineticShieldsSDps = 0; let thermalShieldsSDps = 0; let absoluteArmourSDps = 0; let explosiveArmourSDps = 0; let kineticArmourSDps = 0; let thermalArmourSDps = 0; let totalSEps = 0; const rows = []; for (let i = 0; i < damage.length; i++) { const weapon = damage[i]; totalSEps += weapon.seps; absoluteShieldsSDps += weapon.sdps.shields.absolute; explosiveShieldsSDps += weapon.sdps.shields.explosive; kineticShieldsSDps += weapon.sdps.shields.kinetic; thermalShieldsSDps += weapon.sdps.shields.thermal; absoluteArmourSDps += weapon.sdps.armour.absolute; explosiveArmourSDps += weapon.sdps.armour.explosive; kineticArmourSDps += weapon.sdps.armour.kinetic; thermalArmourSDps += weapon.sdps.armour.thermal; const effectivenessShieldsTooltipDetails = []; effectivenessShieldsTooltipDetails.push(
{translate('range') + ' ' + formats.pct1(weapon.effectiveness.shields.range)}
); effectivenessShieldsTooltipDetails.push(
{translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.shields.resistance)}
); effectivenessShieldsTooltipDetails.push(
{translate('power distributor') + ' ' + formats.pct1(weapon.effectiveness.shields.sys)}
); const effectiveShieldsSDpsTooltipDetails = []; if (weapon.sdps.shields.absolute) effectiveShieldsSDpsTooltipDetails.push(
{translate('absolute') + ' ' + formats.f1(weapon.sdps.shields.absolute)}
); if (weapon.sdps.shields.explosive) effectiveShieldsSDpsTooltipDetails.push(
{translate('explosive') + ' ' + formats.f1(weapon.sdps.shields.explosive)}
); if (weapon.sdps.shields.kinetic) effectiveShieldsSDpsTooltipDetails.push(
{translate('kinetic') + ' ' + formats.f1(weapon.sdps.shields.kinetic)}
); if (weapon.sdps.shields.thermal) effectiveShieldsSDpsTooltipDetails.push(
{translate('thermal') + ' ' + formats.f1(weapon.sdps.shields.thermal)}
); const effectivenessArmourTooltipDetails = []; effectivenessArmourTooltipDetails.push(
{translate('range') + ' ' + formats.pct1(weapon.effectiveness.armour.range)}
); effectivenessArmourTooltipDetails.push(
{translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.armour.resistance)}
); effectivenessArmourTooltipDetails.push(
{translate('hardness') + ' ' + formats.pct1(weapon.effectiveness.armour.hardness)}
); const effectiveArmourSDpsTooltipDetails = []; if (weapon.sdps.armour.absolute) effectiveArmourSDpsTooltipDetails.push(
{translate('absolute') + ' ' + formats.f1(weapon.sdps.armour.absolute)}
); if (weapon.sdps.armour.explosive) effectiveArmourSDpsTooltipDetails.push(
{translate('explosive') + ' ' + formats.f1(weapon.sdps.armour.explosive)}
); if (weapon.sdps.armour.kinetic) effectiveArmourSDpsTooltipDetails.push(
{translate('kinetic') + ' ' + formats.f1(weapon.sdps.armour.kinetic)}
); if (weapon.sdps.armour.thermal) effectiveArmourSDpsTooltipDetails.push(
{translate('thermal') + ' ' + formats.f1(weapon.sdps.armour.thermal)}
); rows.push( {weapon.mount == 'F' ? : null} {weapon.mount == 'G' ? : null} {weapon.mount == 'T' ? : null} {weapon.classRating} {translate(weapon.name)} {weapon.engineering ? ' (' + weapon.engineering + ')' : null } {formats.f1(weapon.sdps.shields.total)} {formats.pct1(weapon.effectiveness.shields.total)} {formats.f1(weapon.sdps.armour.total)} {formats.pct1(weapon.effectiveness.armour.total)} ); } const totalShieldsSDps = absoluteShieldsSDps + explosiveShieldsSDps + kineticShieldsSDps + thermalShieldsSDps; const totalArmourSDps = absoluteArmourSDps + explosiveArmourSDps + kineticArmourSDps + thermalArmourSDps; const shieldsSDpsData = []; shieldsSDpsData.push({ value: Math.round(absoluteShieldsSDps), label: translate('absolute') }); shieldsSDpsData.push({ value: Math.round(explosiveShieldsSDps), label: translate('explosive') }); shieldsSDpsData.push({ value: Math.round(kineticShieldsSDps), label: translate('kinetic') }); shieldsSDpsData.push({ value: Math.round(thermalShieldsSDps), label: translate('thermal') }); const armourSDpsData = []; armourSDpsData.push({ value: Math.round(absoluteArmourSDps), label: translate('absolute') }); armourSDpsData.push({ value: Math.round(explosiveArmourSDps), label: translate('explosive') }); armourSDpsData.push({ value: Math.round(kineticArmourSDps), label: translate('kinetic') }); armourSDpsData.push({ value: Math.round(thermalArmourSDps), label: translate('thermal') }); const timeToDepleteShields = Calc.timeToDeplete(opponentShields.total, totalShieldsSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4)); const timeToDepleteArmour = Calc.timeToDeplete(opponentArmour.total, totalArmourSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4)); return (
{rows}
{translate('weapon')} {translate('opponent\'s shields')} {translate('opponent\'s armour')}
{'sdps'} {'eft'} {'sdps'} {'eft'}

{translate('offence metrics')}

{translate('PHRASE_TIME_TO_DRAIN_WEP')}
{timeToDrain === Infinity ? translate('never') : formats.time(timeToDrain)}

{translate('PHRASE_EFFECTIVE_SDPS_SHIELDS')}
{formats.f1(totalShieldsSDps)}

{translate('PHRASE_TIME_TO_REMOVE_SHIELDS')}
{timeToDepleteShields === Infinity ? translate('never') : formats.time(timeToDepleteShields)}

{translate('PHRASE_EFFECTIVE_SDPS_ARMOUR')}
{formats.f1(totalArmourSDps)}

{translate('PHRASE_TIME_TO_REMOVE_ARMOUR')}
{timeToDepleteArmour === Infinity ? translate('never') : formats.time(timeToDepleteArmour)}

{translate('shield damage sources')}

{translate('armour damage sources')}

); } }