mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-09 14:45:35 +00:00
Add vertical bar chart and use it in battle centre
This commit is contained in:
@@ -77,6 +77,7 @@
|
|||||||
"less": "^2.5.3",
|
"less": "^2.5.3",
|
||||||
"less-loader": "^2.2.1",
|
"less-loader": "^2.2.1",
|
||||||
"react-addons-test-utils": "^15.0.1",
|
"react-addons-test-utils": "^15.0.1",
|
||||||
|
"@types/react-measure": "^0.4.6",
|
||||||
"react-testutils-additions": "^15.1.0",
|
"react-testutils-additions": "^15.1.0",
|
||||||
"rimraf": "^2.4.3",
|
"rimraf": "^2.4.3",
|
||||||
"rollup": "0.36",
|
"rollup": "0.36",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import Cargo from './Cargo';
|
|||||||
import Movement from './Movement';
|
import Movement from './Movement';
|
||||||
import EngagementRange from './EngagementRange';
|
import EngagementRange from './EngagementRange';
|
||||||
import ShipPicker from './ShipPicker';
|
import ShipPicker from './ShipPicker';
|
||||||
import Shields from './Shields';
|
import Defence from './Defence';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Battle centre allows you to pit your current build against another ship,
|
* Battle centre allows you to pit your current build against another ship,
|
||||||
@@ -113,28 +113,31 @@ export default class BattleCentre extends TranslatedComponent {
|
|||||||
const { sys, eng, wep, cargo, fuel, boost, engagementRange, opponent } = this.state;
|
const { sys, eng, wep, cargo, fuel, boost, engagementRange, opponent } = this.state;
|
||||||
const { ship } = this.props;
|
const { ship } = this.props;
|
||||||
|
|
||||||
// Markers are used to propagate state changes
|
// Markers are used to propagate state changes without requiring a deep comparison of the ship
|
||||||
const movementMarker = '' + ship.topSpeed + ':' + ship.pitch + ':' + ship.roll + ':' + ship.yaw;
|
const pipsMarker = '' + ship.canBoost();
|
||||||
const shieldMarker = '' + ship.shield + ':' + ship.cells + ':' + ship.shieldExplRes + ':' + ship.shieldKinRes + ':' + ship.shieldThermRes;
|
const movementMarker = '' + ship.topSpeed + ':' + ship.pitch + ':' + ship.roll + ':' + ship.yaw + ':' + ship.canBoost();
|
||||||
|
const shieldMarker = '' + ship.shield + ':' + ship.shieldCells + ':' + ship.shieldExplRes + ':' + ship.shieldKinRes + ':' + ship.shieldThermRes + ':' + ship.armour;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
<h1>{translate('battle centre')}</h1>
|
<h1>{translate('battle centre')}</h1>
|
||||||
<ShipPicker onChange={this._opponentUpdated}/>
|
|
||||||
<div className='group third'>
|
<div className='group third'>
|
||||||
<Pips ship={ship} onChange={this._pipsUpdated}/>
|
<h1>{translate('ship management')}</h1>
|
||||||
</div>
|
<Pips marker={pipsMarker} ship={ship} onChange={this._pipsUpdated}/>
|
||||||
<div className='group twothirds'>
|
|
||||||
<Fuel ship={ship} onChange={this._fuelUpdated}/>
|
<Fuel ship={ship} onChange={this._fuelUpdated}/>
|
||||||
<Cargo ship={ship} onChange={this._cargoUpdated}/>
|
{ ship.cargoCapacity > 0 ? <Cargo ship={ship} onChange={this._cargoUpdated}/> : null }
|
||||||
|
<h1>{translate('opponent')}</h1>
|
||||||
|
<ShipPicker onChange={this._opponentUpdated}/>
|
||||||
<EngagementRange ship={ship} onChange={this._engagementRangeUpdated}/>
|
<EngagementRange ship={ship} onChange={this._engagementRangeUpdated}/>
|
||||||
</div>
|
</div>
|
||||||
<div className='group third'>
|
<div className='group third'>
|
||||||
<Shields marker={shieldMarker} ship={ship} opponent={opponent} sys={sys}/>
|
<h1>{translate('movement')}</h1>
|
||||||
</div>
|
|
||||||
<div className='group third'>
|
|
||||||
<Movement marker={movementMarker} ship={ship} boost={boost} eng={eng} cargo={cargo} fuel={fuel}/>
|
<Movement marker={movementMarker} ship={ship} boost={boost} eng={eng} cargo={cargo} fuel={fuel}/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className='group full'>
|
||||||
|
<h1>{translate('defence')}</h1>
|
||||||
|
<Defence marker={shieldMarker} ship={ship} opponent={opponent} sys={sys}/>
|
||||||
|
</div>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
421
src/app/components/Defence.jsx
Normal file
421
src/app/components/Defence.jsx
Normal file
@@ -0,0 +1,421 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defence information
|
||||||
|
* Shield information consists of four panels:
|
||||||
|
* - textual information (time to lose shields etc.)
|
||||||
|
* - breakdown of shield sources (pie chart)
|
||||||
|
* - comparison of shield resistances (bar chart)
|
||||||
|
* - effective shield (bar chart)
|
||||||
|
*/
|
||||||
|
export default class Defence extends TranslatedComponent {
|
||||||
|
static propTypes = {
|
||||||
|
marker: React.PropTypes.string.isRequired,
|
||||||
|
ship: React.PropTypes.object.isRequired,
|
||||||
|
opponent: React.PropTypes.object.isRequired,
|
||||||
|
sys: React.PropTypes.number.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param {Object} props React Component properties
|
||||||
|
*/
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
const { shield, armour, damagetaken } = this._calcMetrics(props.ship, props.opponent, props.sys);
|
||||||
|
this.state = { shield, armour, damagetaken };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.sys != nextProps.sys) {
|
||||||
|
const { shield, armour, damagetaken } = this._calcMetrics(nextProps.ship, nextProps.opponent, nextProps.sys);
|
||||||
|
this.setState({ shield, armour, damagetaken });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate shield metrics
|
||||||
|
* @param {Object} ship The ship
|
||||||
|
* @param {Object} opponent The opponent ship
|
||||||
|
* @param {int} sys The opponent ship
|
||||||
|
* @returns {Object} Shield metrics
|
||||||
|
*/
|
||||||
|
_calcMetrics(ship, opponent, sys) {
|
||||||
|
const sysResistance = this._calcSysResistance(sys);
|
||||||
|
|
||||||
|
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;
|
||||||
|
shield = {
|
||||||
|
generator: generatorStrength,
|
||||||
|
boosters: boostersStrength,
|
||||||
|
cells: ship.shieldCells,
|
||||||
|
total: generatorStrength + boostersStrength + ship.shieldCells
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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
|
||||||
|
};
|
||||||
|
|
||||||
|
shield.explosive = {
|
||||||
|
generator: 1 - shieldGenerator.getExplosiveResistance(),
|
||||||
|
boosters: boosterExplDmg,
|
||||||
|
sys: (1 - sysResistance),
|
||||||
|
total: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - sysResistance)
|
||||||
|
};
|
||||||
|
|
||||||
|
shield.kinetic = {
|
||||||
|
generator: 1 - shieldGenerator.getKineticResistance(),
|
||||||
|
boosters: boosterKinDmg,
|
||||||
|
sys: (1 - sysResistance),
|
||||||
|
total: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - sysResistance)
|
||||||
|
};
|
||||||
|
|
||||||
|
shield.thermal = {
|
||||||
|
generator: 1 - shieldGenerator.getThermalResistance(),
|
||||||
|
boosters: boosterThermDmg,
|
||||||
|
sys: (1 - sysResistance),
|
||||||
|
total: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - sysResistance)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use the SDPS for each weapon type of the opponent to work out how long the shields and armour will last
|
||||||
|
// const opponentSDps = Calc.sustainedDps(opponent, range);
|
||||||
|
const opponentSDps = {
|
||||||
|
absolute: 62.1,
|
||||||
|
explosive: 0,
|
||||||
|
kinetic: 7.4,
|
||||||
|
thermal: 7.4
|
||||||
|
};
|
||||||
|
|
||||||
|
// Modify according to resistances to see how much damage we actually take
|
||||||
|
//opponentSDps.absolute *= shield.absolute.total;
|
||||||
|
//opponentSDps.explosive *= shield.explosive.total;
|
||||||
|
//opponentSDps.kinetic *= shield.kinetic.total;
|
||||||
|
//opponentSDps.thermal *= shield.thermal.total;
|
||||||
|
opponentSDps.total = opponentSDps.absolute + opponentSDps.explosive + opponentSDps.kinetic + opponentSDps.thermal;
|
||||||
|
|
||||||
|
const damagetaken = {
|
||||||
|
absolutesdps: opponentSDps.absolute,
|
||||||
|
explosivesdps: opponentSDps.explosive,
|
||||||
|
kineticsdps: opponentSDps.kinetic,
|
||||||
|
thermalsdps: opponentSDps.thermal,
|
||||||
|
tts: (shield.total + ship.shieldCells) / opponentSDps.total,
|
||||||
|
};
|
||||||
|
|
||||||
|
return { shield, armour, damagetaken };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @return {React.Component} contents
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const { ship, sys } = this.props;
|
||||||
|
const { language, tooltip, termtip } = this.context;
|
||||||
|
const { formats, translate, units } = language;
|
||||||
|
const { shield, armour, damagetaken } = this.state;
|
||||||
|
|
||||||
|
const shieldSourcesData = [];
|
||||||
|
const effectiveShieldData = [];
|
||||||
|
const damageTakenData = [];
|
||||||
|
const shieldTooltipDetails = [];
|
||||||
|
const shieldAbsoluteTooltipDetails = [];
|
||||||
|
const shieldExplosiveTooltipDetails = [];
|
||||||
|
const shieldKineticTooltipDetails = [];
|
||||||
|
const shieldThermalTooltipDetails = [];
|
||||||
|
let effectiveAbsoluteShield = 0;
|
||||||
|
let effectiveExplosiveShield = 0;
|
||||||
|
let effectiveKineticShield = 0;
|
||||||
|
let effectiveThermalShield = 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') });
|
||||||
|
|
||||||
|
if (Math.round(shield.generator) > 0) shieldTooltipDetails.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
||||||
|
if (Math.round(shield.boosters) > 0) shieldTooltipDetails.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
||||||
|
if (Math.round(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>);
|
||||||
|
shieldAbsoluteTooltipDetails.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.absolute.sys)}</div>);
|
||||||
|
|
||||||
|
shieldExplosiveTooltipDetails.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.explosive.generator)}</div>);
|
||||||
|
shieldExplosiveTooltipDetails.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.explosive.boosters)}</div>);
|
||||||
|
shieldExplosiveTooltipDetails.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.explosive.sys)}</div>);
|
||||||
|
|
||||||
|
shieldKineticTooltipDetails.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.kinetic.generator)}</div>);
|
||||||
|
shieldKineticTooltipDetails.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.kinetic.boosters)}</div>);
|
||||||
|
shieldKineticTooltipDetails.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.kinetic.sys)}</div>);
|
||||||
|
|
||||||
|
shieldThermalTooltipDetails.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.thermal.generator)}</div>);
|
||||||
|
shieldThermalTooltipDetails.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.thermal.boosters)}</div>);
|
||||||
|
shieldThermalTooltipDetails.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.thermal.sys)}</div>);
|
||||||
|
|
||||||
|
effectiveAbsoluteShield = shield.total / shield.absolute.total;
|
||||||
|
effectiveShieldData.push({ value: Math.round(effectiveAbsoluteShield), label: translate('absolute') });
|
||||||
|
effectiveExplosiveShield = shield.total / shield.explosive.total;
|
||||||
|
effectiveShieldData.push({ value: Math.round(effectiveExplosiveShield), label: translate('explosive') });
|
||||||
|
effectiveKineticShield = shield.total / shield.kinetic.total;
|
||||||
|
effectiveShieldData.push({ value: Math.round(effectiveKineticShield), label: translate('kinetic') });
|
||||||
|
effectiveThermalShield = shield.total / shield.thermal.total;
|
||||||
|
effectiveShieldData.push({ value: Math.round(effectiveThermalShield), label: translate('thermal') });
|
||||||
|
|
||||||
|
damageTakenData.push({ value: Math.round(shield.absolute.total * 100), label: translate('absolute') });
|
||||||
|
damageTakenData.push({ value: Math.round(shield.explosive.total * 100), label: translate('explosive') });
|
||||||
|
damageTakenData.push({ value: Math.round(shield.kinetic.total * 100), label: translate('kinetic') });
|
||||||
|
damageTakenData.push({ value: Math.round(shield.thermal.total * 100), label: translate('thermal') });
|
||||||
|
}
|
||||||
|
|
||||||
|
const armourData = [];
|
||||||
|
if (Math.round(armour.bulkheads) > 0) armourData.push({ value: Math.round(armour.bulkheads), label: translate('bulkheads') });
|
||||||
|
if (Math.round(armour.reinforcement) > 0) armourData.push({ value: Math.round(armour.reinforcement), label: translate('reinforcement') });
|
||||||
|
|
||||||
|
const armourTooltipDetails = [];
|
||||||
|
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>);
|
||||||
|
armourAbsoluteTooltipDetails.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.absolute.reinforcement)}</div>);
|
||||||
|
|
||||||
|
const armourExplosiveTooltipDetails = [];
|
||||||
|
armourExplosiveTooltipDetails.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.explosive.bulkheads)}</div>);
|
||||||
|
armourExplosiveTooltipDetails.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.explosive.reinforcement)}</div>);
|
||||||
|
|
||||||
|
const armourKineticTooltipDetails = [];
|
||||||
|
armourKineticTooltipDetails.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.kinetic.bulkheads)}</div>);
|
||||||
|
armourKineticTooltipDetails.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.kinetic.reinforcement)}</div>);
|
||||||
|
|
||||||
|
const armourThermalTooltipDetails = [];
|
||||||
|
armourThermalTooltipDetails.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.thermal.bulkheads)}</div>);
|
||||||
|
armourThermalTooltipDetails.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.thermal.reinforcement)}</div>);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span id='defence'>
|
||||||
|
{shield.total ? <span>
|
||||||
|
<div className='group half'>
|
||||||
|
<div className='group half'>
|
||||||
|
<h2 onMouseOver={termtip.bind(null, <div>{shieldTooltipDetails}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('shields')}: {formats.int(shield.total)}{units.MJ}</h2>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td className='le'>{translate('damage type')}</td>
|
||||||
|
<td className='ce'>
|
||||||
|
<span className='icon' onMouseOver={termtip.bind(null, 'absolute')} onMouseOut={tooltip.bind(null, null)}><DamageAbsolute /></span>
|
||||||
|
</td>
|
||||||
|
<td className='ce'>
|
||||||
|
<span className='icon' onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /></span>
|
||||||
|
</td>
|
||||||
|
<td className='ce'>
|
||||||
|
<span className='icon' onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /></span>
|
||||||
|
</td>
|
||||||
|
<td className='ce'>
|
||||||
|
<span className='icon' onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className='le'>{translate('damage taken')}</td>
|
||||||
|
<td className='ri'>
|
||||||
|
<span onMouseOver={termtip.bind(null, <div>{shieldAbsoluteTooltipDetails}</div>)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(shield.absolute.total)}</span>
|
||||||
|
</td>
|
||||||
|
<td className='ri'>
|
||||||
|
<span onMouseOver={termtip.bind(null, <div>{shieldExplosiveTooltipDetails}</div>)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(shield.explosive.total)}</span>
|
||||||
|
</td>
|
||||||
|
<td className='ri'>
|
||||||
|
<span onMouseOver={termtip.bind(null, <div>{shieldKineticTooltipDetails}</div>)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(shield.kinetic.total)}</span>
|
||||||
|
</td>
|
||||||
|
<td className='ri'>
|
||||||
|
<span onMouseOver={termtip.bind(null, <div>{shieldThermalTooltipDetails}</div>)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(shield.thermal.total)}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className='le'>{translate('effective shield')}</td>
|
||||||
|
<td className='ri'>
|
||||||
|
<span>{formats.int(effectiveAbsoluteShield)}{units.MJ}</span>
|
||||||
|
</td>
|
||||||
|
<td className='ri'>
|
||||||
|
<span>{formats.int(effectiveExplosiveShield)}{units.MJ}</span>
|
||||||
|
</td>
|
||||||
|
<td className='ri'>
|
||||||
|
<span>{formats.int(effectiveKineticShield)}{units.MJ}</span>
|
||||||
|
</td>
|
||||||
|
<td className='ri'>
|
||||||
|
<span>{formats.int(effectiveThermalShield)}{units.MJ}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colSpan='5' className='summary'>{translate('shields will hold against opponent for')} {formats.time(damagetaken.tts)}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div className='group half'>
|
||||||
|
<h2>{translate('shield sources')}</h2>
|
||||||
|
<PieChart data={shieldSourcesData} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='group half'>
|
||||||
|
<div className='group half'>
|
||||||
|
<h2>{translate('effective shield')}(MJ)</h2>
|
||||||
|
<VerticalBarChart data={effectiveShieldData} />
|
||||||
|
</div>
|
||||||
|
<div className='group half'>
|
||||||
|
<h2>{translate('damage taken')}(%)</h2>
|
||||||
|
<VerticalBarChart data={damageTakenData} />
|
||||||
|
</div>
|
||||||
|
</div></span> : null }
|
||||||
|
|
||||||
|
<div className='group twothirds'>
|
||||||
|
<h2 onMouseOver={termtip.bind(null, <div>{armourTooltipDetails}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('armour')}: {formats.int(armour.total)}</h2>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td className='le'>{translate('damage taken')}</td>
|
||||||
|
<td className='ri'>
|
||||||
|
<span onMouseOver={termtip.bind(null, <div>{armourAbsoluteTooltipDetails}</div>)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(armour.absolute.total)}</span>
|
||||||
|
</td>
|
||||||
|
<td className='ri'>
|
||||||
|
<span onMouseOver={termtip.bind(null, <div>{armourExplosiveTooltipDetails}</div>)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(armour.explosive.total)}</span>
|
||||||
|
</td>
|
||||||
|
<td className='ri'>
|
||||||
|
<span onMouseOver={termtip.bind(null, <div>{armourKineticTooltipDetails}</div>)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(armour.kinetic.total)}</span>
|
||||||
|
</td>
|
||||||
|
<td className='ri'>
|
||||||
|
<span onMouseOver={termtip.bind(null, <div>{armourThermalTooltipDetails}</div>)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(armour.thermal.total)}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div className='group third'>
|
||||||
|
<h2>{translate('armour sources')}</h2>
|
||||||
|
<PieChart data={armourData} />
|
||||||
|
</div>
|
||||||
|
</span>);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,10 +27,18 @@ export default class Range extends TranslatedComponent {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
maxRange,
|
maxRange,
|
||||||
rangeLevel: 1,
|
rangeLevel: 0.5,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
componentWillMount() {
|
||||||
|
// Pass initial state
|
||||||
|
this.props.onChange(this.state.maxRange * this.state.rangeLevel);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the state if our ship changes
|
* Update the state if our ship changes
|
||||||
* @param {Object} nextProps Incoming/Next properties
|
* @param {Object} nextProps Incoming/Next properties
|
||||||
|
|||||||
78
src/app/components/PieChart.jsx
Normal file
78
src/app/components/PieChart.jsx
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import Measure from 'react-measure';
|
||||||
|
import * as d3 from 'd3';
|
||||||
|
|
||||||
|
const CORIOLIS_COLOURS = [ '#FF8C0D', '#1FB0FF', '#519032', '#D5420D' ];
|
||||||
|
const LABEL_COLOUR = '#FFFFFF';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A pie chart
|
||||||
|
*/
|
||||||
|
export default class PieChart extends Component {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param {Object} props React Component properties
|
||||||
|
* @param {Object} context React Component context
|
||||||
|
*/
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.pie = d3.pie().value((d) => d.value);
|
||||||
|
this.colors = CORIOLIS_COLOURS;
|
||||||
|
this.arc = d3.arc();
|
||||||
|
this.arc.innerRadius(0);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
dimensions: {
|
||||||
|
width: 100,
|
||||||
|
height: 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a slice of the pie chart
|
||||||
|
*/
|
||||||
|
sliceGenerator(d, i) {
|
||||||
|
const { width, height } = this.state.dimensions;
|
||||||
|
const { data } = this.props;
|
||||||
|
|
||||||
|
// Push the labels further out from the centre of the slice
|
||||||
|
let [labelX, labelY] = this.arc.centroid(d);
|
||||||
|
const labelTranslate = `translate(${labelX * 1.5}, ${labelY * 1.5})`;
|
||||||
|
|
||||||
|
// Put the keys in a line with equal spacing
|
||||||
|
const keyX = -width / 2 + (width / data.length) * (i + 0.5);
|
||||||
|
const keyTranslate = `translate(${keyX}, ${width * 0.45})`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<g key={`group-${i}`}>
|
||||||
|
<path key={`arc-${i}`} d={this.arc(d)} style={{fill: this.colors[i]}} />
|
||||||
|
<text key={`label-${i}`} transform={labelTranslate} stroke={LABEL_COLOUR} strokeWidth='1px' fill='None' textAnchor='middle'>{d.value}</text>
|
||||||
|
<text key={`key-${i}`} transform={keyTranslate} style={{stroke: this.colors[i], strokeWidth:'1px', fill:'None'}} textAnchor='middle'>{d.data.label}</text>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { width, height } = this.state.dimensions;
|
||||||
|
const pie = this.pie(this.props.data),
|
||||||
|
translate = `translate(${width / 2}, ${width * 0.4})`;
|
||||||
|
|
||||||
|
this.arc.outerRadius(width * 0.4);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Measure width='100%' whitelist={[ 'width', 'top' ]} onMeasure={(dimensions) => { this.setState({dimensions}) }}>
|
||||||
|
<div width={width} height={width}>
|
||||||
|
<svg style={{stroke: 'None' }} width={width} height={width * 0.9}>
|
||||||
|
<g transform={translate}>
|
||||||
|
{pie.map((d, i) => this.sliceGenerator(d, i))}
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</Measure>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import Module from '../shipyard/Module';
|
|||||||
*/
|
*/
|
||||||
export default class Pips extends TranslatedComponent {
|
export default class Pips extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
marker: React.PropTypes.string.isRequired,
|
||||||
ship: React.PropTypes.object.isRequired,
|
ship: React.PropTypes.object.isRequired,
|
||||||
onChange: React.PropTypes.func.isRequired
|
onChange: React.PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
@@ -68,9 +69,9 @@ export default class Pips extends TranslatedComponent {
|
|||||||
* @returns {boolean} Returns true if the component should be rerendered
|
* @returns {boolean} Returns true if the component should be rerendered
|
||||||
*/
|
*/
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
const { sysCap, engCap, wepCap, sysRate, engRate, wepRate } = this.state;
|
const { sysCap, engCap, wepCap, sysRate, engRate, wepRate, boost } = this.state;
|
||||||
const ship = nextProps.ship;
|
const nextShip = nextProps.ship;
|
||||||
const pd = ship.standard[4].m;
|
const pd = nextShip.standard[4].m;
|
||||||
|
|
||||||
const nextSysCap = pd.getSystemsCapacity();
|
const nextSysCap = pd.getSystemsCapacity();
|
||||||
const nextEngCap = pd.getEnginesCapacity();
|
const nextEngCap = pd.getEnginesCapacity();
|
||||||
@@ -78,19 +79,22 @@ export default class Pips extends TranslatedComponent {
|
|||||||
const nextSysRate = pd.getSystemsRechargeRate();
|
const nextSysRate = pd.getSystemsRechargeRate();
|
||||||
const nextEngRate = pd.getEnginesRechargeRate();
|
const nextEngRate = pd.getEnginesRechargeRate();
|
||||||
const nextWepRate = pd.getWeaponsRechargeRate();
|
const nextWepRate = pd.getWeaponsRechargeRate();
|
||||||
|
const nextBoost = nextShip.canBoost() ? boost : false;
|
||||||
if (nextSysCap != sysCap ||
|
if (nextSysCap != sysCap ||
|
||||||
nextEngCap != engCap ||
|
nextEngCap != engCap ||
|
||||||
nextWepCap != wepCap ||
|
nextWepCap != wepCap ||
|
||||||
nextSysRate != sysRate ||
|
nextSysRate != sysRate ||
|
||||||
nextEngRate != engRate ||
|
nextEngRate != engRate ||
|
||||||
nextWepRate != wepRate) {
|
nextWepRate != wepRate ||
|
||||||
|
nextBoost != boost) {
|
||||||
this.setState({
|
this.setState({
|
||||||
sysCap: nextSysCap,
|
sysCap: nextSysCap,
|
||||||
engCap: nextEngCap,
|
engCap: nextEngCap,
|
||||||
wepCap: nextWepCap,
|
wepCap: nextWepCap,
|
||||||
sysRate: nextSysRate,
|
sysRate: nextSysRate,
|
||||||
engRate: nextEngRate,
|
engRate: nextEngRate,
|
||||||
wepRate: nextWepRate
|
wepRate: nextWepRate,
|
||||||
|
boost: nextBoost
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,8 +108,10 @@ export default class Pips extends TranslatedComponent {
|
|||||||
_keyDown(e) {
|
_keyDown(e) {
|
||||||
switch (e.keyCode) {
|
switch (e.keyCode) {
|
||||||
case 9: // Tab == boost
|
case 9: // Tab == boost
|
||||||
e.preventDefault();
|
if (this.props.ship.canBoost()) {
|
||||||
this._toggleBoost();
|
e.preventDefault();
|
||||||
|
this._toggleBoost();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 37: // Left arrow == increase SYS
|
case 37: // Left arrow == increase SYS
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -364,21 +370,21 @@ export default class Pips extends TranslatedComponent {
|
|||||||
<td className='clickable' onClick={onRstClicked}>{translate('RST')}</td>
|
<td className='clickable' onClick={onRstClicked}>{translate('RST')}</td>
|
||||||
<td className='clickable' onClick={onWepClicked}>{translate('WEP')}</td>
|
<td className='clickable' onClick={onWepClicked}>{translate('WEP')}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td>{translate('capacity')} ({units.MJ})</td>
|
|
||||||
<td>{formats.f1(sysCap)}</td>
|
|
||||||
<td>{formats.f1(engCap)}</td>
|
|
||||||
<td>{formats.f1(wepCap)}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{translate('recharge')} ({units.MW})</td>
|
|
||||||
<td>{formats.f1(sysRate * (sys / 4))}</td>
|
|
||||||
<td>{formats.f1(engRate * (eng / 4))}</td>
|
|
||||||
<td>{formats.f1(wepRate * (wep / 4))}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// <tr>
|
||||||
|
// <td>{translate('capacity')} ({units.MJ})</td>
|
||||||
|
// <td>{formats.f1(sysCap)}</td>
|
||||||
|
// <td>{formats.f1(engCap)}</td>
|
||||||
|
// <td>{formats.f1(wepCap)}</td>
|
||||||
|
// </tr>
|
||||||
|
// <tr>
|
||||||
|
// <td>{translate('recharge')} ({units.MW})</td>
|
||||||
|
// <td>{formats.f1(sysRate * (sys / 4))}</td>
|
||||||
|
// <td>{formats.f1(engRate * (eng / 4))}</td>
|
||||||
|
// <td>{formats.f1(wepRate * (wep / 4))}</td>
|
||||||
|
// </tr>
|
||||||
|
|||||||
@@ -1,194 +0,0 @@
|
|||||||
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';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shields
|
|
||||||
* Effective shield strength (including SCBs)
|
|
||||||
* Time for opponent to down shields
|
|
||||||
* - need sustained DPS for each type of damage (K/T/E/R)
|
|
||||||
* - turn in to % of shields removed per second
|
|
||||||
*/
|
|
||||||
export default class Shields extends TranslatedComponent {
|
|
||||||
static propTypes = {
|
|
||||||
marker: React.PropTypes.string.isRequired,
|
|
||||||
ship: React.PropTypes.object.isRequired,
|
|
||||||
opponent: React.PropTypes.object.isRequired,
|
|
||||||
sys: React.PropTypes.number.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
* @param {Object} props React Component properties
|
|
||||||
*/
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
const { shield, absolute, explosive, kinetic, thermal } = this._calcMetrics(props.ship, props.opponent, props.sys);
|
|
||||||
this.state = { shield, absolute, explosive, kinetic, thermal };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.sys != nextProps.sys) {
|
|
||||||
const { shield, absolute, explosive, kinetic, thermal } = this._calcMetrics(nextProps.ship, nextProps.opponent, nextProps.sys);
|
|
||||||
this.setState({ shield, absolute, explosive, kinetic, thermal });
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate shield metrics
|
|
||||||
* @param {Object} ship The ship
|
|
||||||
* @param {Object} opponent The opponent ship
|
|
||||||
* @param {int} sys The opponent ship
|
|
||||||
* @returns {Object} Shield metrics
|
|
||||||
*/
|
|
||||||
_calcMetrics(ship, opponent, sys) {
|
|
||||||
const sysResistance = this._calcSysResistance(sys);
|
|
||||||
|
|
||||||
const shieldGenerator = ship.findShieldGenerator();
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
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;
|
|
||||||
const shield = {
|
|
||||||
generator: generatorStrength,
|
|
||||||
boosters: boostersStrength,
|
|
||||||
total: generatorStrength + boostersStrength
|
|
||||||
};
|
|
||||||
|
|
||||||
// Resistances have three components: the shield generator, the shield boosters and the SYS pips.
|
|
||||||
// We re-cast these as damage percentages
|
|
||||||
const absolute = {
|
|
||||||
generator: 1,
|
|
||||||
boosters: 1,
|
|
||||||
sys: 1 - sysResistance,
|
|
||||||
total: 1 - sysResistance
|
|
||||||
};
|
|
||||||
|
|
||||||
const explosive = {
|
|
||||||
generator: 1 - shieldGenerator.getExplosiveResistance(),
|
|
||||||
boosters: boosterExplDmg,
|
|
||||||
sys: (1 - sysResistance),
|
|
||||||
total: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - sysResistance)
|
|
||||||
};
|
|
||||||
|
|
||||||
const kinetic = {
|
|
||||||
generator: 1 - shieldGenerator.getKineticResistance(),
|
|
||||||
boosters: boosterKinDmg,
|
|
||||||
sys: (1 - sysResistance),
|
|
||||||
total: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - sysResistance)
|
|
||||||
};
|
|
||||||
|
|
||||||
const thermal = {
|
|
||||||
generator: 1 - shieldGenerator.getThermalResistance(),
|
|
||||||
boosters: boosterThermDmg,
|
|
||||||
sys: (1 - sysResistance),
|
|
||||||
total: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - sysResistance)
|
|
||||||
};
|
|
||||||
|
|
||||||
return { shield, absolute, explosive, kinetic, thermal };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
* @return {React.Component} contents
|
|
||||||
*/
|
|
||||||
render() {
|
|
||||||
const { ship, sys } = this.props;
|
|
||||||
const { language, tooltip, termtip } = this.context;
|
|
||||||
const { formats, translate, units } = language;
|
|
||||||
const { shield, absolute, explosive, kinetic, thermal } = this.state;
|
|
||||||
|
|
||||||
const shieldTooltipDetails = [];
|
|
||||||
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>);
|
|
||||||
|
|
||||||
const absoluteTooltipDetails = [];
|
|
||||||
absoluteTooltipDetails.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(absolute.generator)}</div>);
|
|
||||||
absoluteTooltipDetails.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(absolute.boosters)}</div>);
|
|
||||||
absoluteTooltipDetails.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(absolute.sys)}</div>);
|
|
||||||
|
|
||||||
const explosiveTooltipDetails = [];
|
|
||||||
explosiveTooltipDetails.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(explosive.generator)}</div>);
|
|
||||||
explosiveTooltipDetails.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(explosive.boosters)}</div>);
|
|
||||||
explosiveTooltipDetails.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(explosive.sys)}</div>);
|
|
||||||
|
|
||||||
const kineticTooltipDetails = [];
|
|
||||||
kineticTooltipDetails.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(kinetic.generator)}</div>);
|
|
||||||
kineticTooltipDetails.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(kinetic.boosters)}</div>);
|
|
||||||
kineticTooltipDetails.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(kinetic.sys)}</div>);
|
|
||||||
|
|
||||||
const thermalTooltipDetails = [];
|
|
||||||
thermalTooltipDetails.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(thermal.generator)}</div>);
|
|
||||||
thermalTooltipDetails.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(thermal.boosters)}</div>);
|
|
||||||
thermalTooltipDetails.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(thermal.sys)}</div>);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span id='shields'>
|
|
||||||
<table>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td colSpan='5' onMouseOver={termtip.bind(null, <div>{shieldTooltipDetails}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('shields')}: {formats.int(shield.total)}{units.MJ}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td className='le'>{translate('damage from')}</td>
|
|
||||||
<td className='ri'>
|
|
||||||
<span onMouseOver={termtip.bind(null, 'absolute')} onMouseOut={tooltip.bind(null, null)}><DamageAbsolute /></span>
|
|
||||||
<span onMouseOver={termtip.bind(null, <div>{absoluteTooltipDetails}</div>)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(absolute.total)}</span>
|
|
||||||
</td>
|
|
||||||
<td className='ri'>
|
|
||||||
<span onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /></span>
|
|
||||||
<span onMouseOver={termtip.bind(null, <div>{explosiveTooltipDetails}</div>)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(explosive.total)}</span>
|
|
||||||
</td>
|
|
||||||
<td className='ri'>
|
|
||||||
<span onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /></span>
|
|
||||||
<span onMouseOver={termtip.bind(null, <div>{kineticTooltipDetails}</div>)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(kinetic.total)}</span>
|
|
||||||
</td>
|
|
||||||
<td className='ri'>
|
|
||||||
<span onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /></span>
|
|
||||||
<span onMouseOver={termtip.bind(null, <div>{thermalTooltipDetails}</div>)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(thermal.total)}</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</span>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
122
src/app/components/VerticalBarChart.jsx
Normal file
122
src/app/components/VerticalBarChart.jsx
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import Measure from 'react-measure';
|
||||||
|
import * as d3 from 'd3';
|
||||||
|
|
||||||
|
const CORIOLIS_COLOURS = [ '#FF8C0D', '#1FB0FF', '#519032', '#D5420D' ];
|
||||||
|
const LABEL_COLOUR = '#FFFFFF';
|
||||||
|
|
||||||
|
var margin = {top: 10, right: 0, bottom: 0, left: 50};
|
||||||
|
|
||||||
|
const ASPECT = 1;
|
||||||
|
|
||||||
|
const merge = function(one, two) {
|
||||||
|
return Object.assign({}, one, two);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A vertical bar chart
|
||||||
|
*/
|
||||||
|
export default class VerticalBarChart extends Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
data : React.PropTypes.array.isRequired,
|
||||||
|
ylabel : React.PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param {Object} props React Component properties
|
||||||
|
* @param {Object} context React Component context
|
||||||
|
*/
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
dimensions: {
|
||||||
|
width: 300,
|
||||||
|
height: 300
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderGraph(props){
|
||||||
|
let { width, height } = this.state.dimensions;
|
||||||
|
|
||||||
|
width = width - margin.left - margin.right,
|
||||||
|
height = width * ASPECT - margin.top - margin.bottom;
|
||||||
|
|
||||||
|
// X axis is a band scale with values being 'label'
|
||||||
|
this.x = d3.scaleBand();
|
||||||
|
this.x.domain(this.props.data.map(d => d.label)).padding(0.2);
|
||||||
|
this.xAxis = d3.axisBottom(this.x).tickValues(this.props.data.map(d => d.label));
|
||||||
|
this.x.range([0, width]);
|
||||||
|
|
||||||
|
// Y axis is a numeric scale with values being 'value'
|
||||||
|
this.y = d3.scaleLinear();
|
||||||
|
this.y.domain([0, d3.max(this.props.data, d => d.value)]);
|
||||||
|
this.yAxis = d3.axisLeft(this.y);
|
||||||
|
this.y.range([height, 0]);
|
||||||
|
|
||||||
|
let svg = d3.select(this.svg).select('g');
|
||||||
|
|
||||||
|
svg.selectAll('rect').remove();
|
||||||
|
svg.selectAll('text').remove();
|
||||||
|
|
||||||
|
svg.select('.x.axis').remove();
|
||||||
|
svg.select('.y.axis').remove();
|
||||||
|
|
||||||
|
svg.append('g')
|
||||||
|
.attr('class', 'x axis')
|
||||||
|
.attr('transform', `translate(0, ${height})`)
|
||||||
|
.call(this.xAxis);
|
||||||
|
|
||||||
|
svg.append('g')
|
||||||
|
.attr('class', 'y axis')
|
||||||
|
.call(this.yAxis)
|
||||||
|
.attr('fill', CORIOLIS_COLOURS[0])
|
||||||
|
|
||||||
|
svg.selectAll('rect.bar')
|
||||||
|
.data(props.data)
|
||||||
|
.enter().append('rect')
|
||||||
|
.attr('class', 'bar')
|
||||||
|
.attr('x', d => this.x(d.label))
|
||||||
|
.attr('width', this.x.bandwidth())
|
||||||
|
.attr('y', d => this.y(d.value))
|
||||||
|
.attr('height', d => height - this.y(d.value))
|
||||||
|
.attr('fill', CORIOLIS_COLOURS[0]);
|
||||||
|
|
||||||
|
svg.selectAll('text.bar')
|
||||||
|
.data(props.data)
|
||||||
|
.enter().append('text')
|
||||||
|
.attr('class', 'bar')
|
||||||
|
.attr('text-anchor', 'middle')
|
||||||
|
.attr('x', 100)
|
||||||
|
.attr('y', 100)
|
||||||
|
.attr('stroke', '#ffffff')
|
||||||
|
.attr('stroke-width', '1px')
|
||||||
|
.attr('x', d => this.x(d.label) + this.x.bandwidth() / 2)
|
||||||
|
.attr('y', d => this.y(d.value) + 15)
|
||||||
|
.text(d => d.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { width } = this.state.dimensions;
|
||||||
|
|
||||||
|
const translate = `translate(${margin.left}, ${margin.top})`;
|
||||||
|
|
||||||
|
this._renderGraph(this.props);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Measure width='100%' whitelist={[ 'width', 'top' ]} onMeasure={(dimensions) => { this.setState({dimensions}) }}>
|
||||||
|
<div width={width} height={width * ASPECT}>
|
||||||
|
{ this.x ?
|
||||||
|
<svg ref={ref => this.svg = ref} width={width} height={width * ASPECT} transform={translate}>
|
||||||
|
<g transform={translate}></g>
|
||||||
|
</svg> : null }
|
||||||
|
</div>
|
||||||
|
</Measure>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1348,13 +1348,13 @@ export default class Ship {
|
|||||||
let moduleprotection = 1;
|
let moduleprotection = 1;
|
||||||
let hullExplRes = 1 - bulkhead.getExplosiveResistance();
|
let hullExplRes = 1 - bulkhead.getExplosiveResistance();
|
||||||
const hullExplResDRStart = hullExplRes * 0.7;
|
const hullExplResDRStart = hullExplRes * 0.7;
|
||||||
const hullExplResDREnd = hullExplRes * 0; // Currently don't know where this is
|
const hullExplResDREnd = hullExplRes * 0;
|
||||||
let hullKinRes = 1 - bulkhead.getKineticResistance();
|
let hullKinRes = 1 - bulkhead.getKineticResistance();
|
||||||
const hullKinResDRStart = hullKinRes * 0.7;
|
const hullKinResDRStart = hullKinRes * 0.7;
|
||||||
const hullKinResDREnd = hullKinRes * 0; // Currently don't know where this is
|
const hullKinResDREnd = hullKinRes * 0;
|
||||||
let hullThermRes = 1 - bulkhead.getThermalResistance();
|
let hullThermRes = 1 - bulkhead.getThermalResistance();
|
||||||
const hullThermResDRStart = hullThermRes * 0.7;
|
const hullThermResDRStart = hullThermRes * 0.7;
|
||||||
const hullThermResDREnd = hullThermRes * 0; // Currently don't know where this is
|
const hullThermResDREnd = hullThermRes * 0;
|
||||||
|
|
||||||
// Armour from HRPs and module armour from MRPs
|
// Armour from HRPs and module armour from MRPs
|
||||||
for (let slot of this.internal) {
|
for (let slot of this.internal) {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
@import 'pips';
|
@import 'pips';
|
||||||
@import 'movement';
|
@import 'movement';
|
||||||
@import 'shippicker';
|
@import 'shippicker';
|
||||||
|
@import 'defence';
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
14
src/less/defence.less
Executable file
14
src/less/defence.less
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#defence {
|
||||||
|
table {
|
||||||
|
background-color: @bgBlack;
|
||||||
|
color: @primary;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
stroke: @primary;
|
||||||
|
stroke-width: 20;
|
||||||
|
fill: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -206,6 +206,14 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.full {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.smallTablet({
|
||||||
|
width: 100% !important;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
.smallScreen({
|
.smallScreen({
|
||||||
.axis.x {
|
.axis.x {
|
||||||
g.tick:nth-child(2n + 1) text {
|
g.tick:nth-child(2n + 1) text {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
// The pips table - keep the background black
|
// The pips table - keep the background black
|
||||||
#pips {
|
#pips {
|
||||||
|
|
||||||
table {
|
table {
|
||||||
background-color: @bgBlack;
|
background-color: @bgBlack;
|
||||||
color: @primary;
|
color: @primary;
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A clickable entity in the pips table
|
// A clickable entity in the pips table
|
||||||
|
|||||||
Reference in New Issue
Block a user