mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-10 07:05:35 +00:00
Embed battle centre in main pages
This commit is contained in:
@@ -36,7 +36,7 @@
|
|||||||
"Test": "A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.Iw18ZVA=.Aw18ZVA=."
|
"Test": "A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.Iw18ZVA=.Aw18ZVA=."
|
||||||
},
|
},
|
||||||
"diamondback_explorer": {
|
"diamondback_explorer": {
|
||||||
"Explorer": "A0p0tdFfldddsdf5---0202--320p432i2f.AwRj4zTI.AwiMIypI."
|
"Explorer": "A0p0tdFfldddsdf5---0202--320p432i2f-.AwRj4zTYg===.AwiMIyoo."
|
||||||
},
|
},
|
||||||
"vulture": {
|
"vulture": {
|
||||||
"Bounty Hunter": "A3patcFalddksff31e1e0404-0l4a-5d27662j.AwRj4z2I.MwBhBYy6oJmAjLIA."
|
"Bounty Hunter": "A3patcFalddksff31e1e0404-0l4a-5d27662j.AwRj4z2I.MwBhBYy6oJmAjLIA."
|
||||||
|
|||||||
@@ -1,144 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
|
||||||
import Ship from '../shipyard/Ship';
|
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import Slider from './Slider';
|
|
||||||
import Pips from './Pips';
|
|
||||||
import Fuel from './Fuel';
|
|
||||||
import Cargo from './Cargo';
|
|
||||||
import Movement from './Movement';
|
|
||||||
import EngagementRange from './EngagementRange';
|
|
||||||
import ShipPicker from './ShipPicker';
|
|
||||||
import Defence from './Defence';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Battle centre allows you to pit your current build against another ship,
|
|
||||||
* adjust pips and engagement range, and see a wide variety of information
|
|
||||||
*/
|
|
||||||
export default class BattleCentre extends TranslatedComponent {
|
|
||||||
static propTypes = {
|
|
||||||
ship: React.PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
* @param {Object} props React Component properties
|
|
||||||
* @param {Object} context React Component context
|
|
||||||
*/
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
const { ship } = this.props;
|
|
||||||
|
|
||||||
this._cargoUpdated = this._cargoUpdated.bind(this);
|
|
||||||
this._fuelUpdated = this._fuelUpdated.bind(this);
|
|
||||||
this._pipsUpdated = this._pipsUpdated.bind(this);
|
|
||||||
this._engagementRangeUpdated = this._engagementRangeUpdated.bind(this);
|
|
||||||
this._opponentUpdated = this._opponentUpdated.bind(this);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
sys: 2,
|
|
||||||
eng: 2,
|
|
||||||
wep: 2,
|
|
||||||
fuel: ship.fuelCapacity,
|
|
||||||
cargo: ship.cargoCapacity,
|
|
||||||
boost: false,
|
|
||||||
engagementRange: 1500,
|
|
||||||
opponent: new Ship('anaconda', Ships['anaconda'].properties, Ships['anaconda'].slots).buildWith(Ships['anaconda'].defaults)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update state based on property and context changes
|
|
||||||
* @param {Object} nextProps Incoming/Next properties
|
|
||||||
* @returns {boolean} true if an update is required
|
|
||||||
*/
|
|
||||||
componentWillReceiveProps(nextProps) {
|
|
||||||
// Rather than try to keep track of what changes our children require we force an update and let them work it out
|
|
||||||
this.forceUpdate();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggered when pips have been updated
|
|
||||||
* @param {number} sys SYS pips
|
|
||||||
* @param {number} eng ENG pips
|
|
||||||
* @param {number} wep WEP pips
|
|
||||||
* @param {boolean} boost true if boosting
|
|
||||||
*/
|
|
||||||
_pipsUpdated(sys, eng, wep, boost) {
|
|
||||||
this.setState({ sys, eng, wep, boost });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggered when fuel has been updated
|
|
||||||
* @param {number} fuel the amount of fuel, in T
|
|
||||||
*/
|
|
||||||
_fuelUpdated(fuel) {
|
|
||||||
this.setState({ fuel });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggered when cargo has been updated
|
|
||||||
* @param {number} cargo the amount of cargo, in T
|
|
||||||
*/
|
|
||||||
_cargoUpdated(cargo) {
|
|
||||||
this.setState({ cargo });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggered when engagement range has been updated
|
|
||||||
* @param {number} engagementRange the engagement range, in m
|
|
||||||
*/
|
|
||||||
_engagementRangeUpdated(engagementRange) {
|
|
||||||
this.setState({ engagementRange });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggered when target ship has been updated
|
|
||||||
* @param {object} opponent the opponent's ship
|
|
||||||
* @param {string} opponentBuild the name of the opponent's build
|
|
||||||
*/
|
|
||||||
_opponentUpdated(opponent, opponentBuild) {
|
|
||||||
this.setState({ opponent, opponentBuild });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render
|
|
||||||
* @return {React.Component} contents
|
|
||||||
*/
|
|
||||||
render() {
|
|
||||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
|
||||||
const { formats, translate, units } = language;
|
|
||||||
const { sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild } = this.state;
|
|
||||||
const { ship } = this.props;
|
|
||||||
|
|
||||||
// Markers are used to propagate state changes without requiring a deep comparison of the ship, as that takes a long time
|
|
||||||
const pipsMarker = '' + ship.canBoost();
|
|
||||||
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 + ship.standard[4].m.getSystemsCapacity() + ':' + ship.standard[4].m.getSystemsRechargeRate() + ':' + opponent.name + ':' + opponentBuild + ':' + engagementRange;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
<h1>{translate('battle centre')}</h1>
|
|
||||||
<div className='group third'>
|
|
||||||
<h1>{translate('ship management')}</h1>
|
|
||||||
<Pips marker={pipsMarker} ship={ship} onChange={this._pipsUpdated}/>
|
|
||||||
<Fuel ship={ship} onChange={this._fuelUpdated}/>
|
|
||||||
{ ship.cargoCapacity > 0 ? <Cargo ship={ship} onChange={this._cargoUpdated}/> : null }
|
|
||||||
<h1>{translate('opponent')}</h1>
|
|
||||||
<ShipPicker onChange={this._opponentUpdated}/>
|
|
||||||
<EngagementRange ship={ship} onChange={this._engagementRangeUpdated}/>
|
|
||||||
</div>
|
|
||||||
<div className='group third'>
|
|
||||||
<h1>{translate('movement')}</h1>
|
|
||||||
<Movement marker={movementMarker} ship={ship} boost={boost} eng={eng} cargo={cargo} fuel={fuel}/>
|
|
||||||
</div>
|
|
||||||
<div className='group full'>
|
|
||||||
<h1>{translate('defence')}</h1>
|
|
||||||
<Defence marker={shieldMarker} ship={ship} opponent={opponent} sys={sys} engagementrange={engagementRange}/>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
116
src/app/components/Boost.jsx
Normal file
116
src/app/components/Boost.jsx
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
|
import { Ships } from 'coriolis-data/dist';
|
||||||
|
import ShipSelector from './ShipSelector';
|
||||||
|
import { nameComparator } from '../utils/SlotFunctions';
|
||||||
|
import { Pip } from './SvgIcons';
|
||||||
|
import LineChart from '../components/LineChart';
|
||||||
|
import Slider from '../components/Slider';
|
||||||
|
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||||
|
import Module from '../shipyard/Module';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boost displays a boost button that toggles bosot
|
||||||
|
* Requires an onChange() function of the form onChange(boost) which is triggered whenever the boost changes.
|
||||||
|
*/
|
||||||
|
export default class Boost extends TranslatedComponent {
|
||||||
|
static propTypes = {
|
||||||
|
marker: React.PropTypes.string.isRequired,
|
||||||
|
ship: React.PropTypes.object.isRequired,
|
||||||
|
onChange: React.PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param {Object} props React Component properties
|
||||||
|
* @param {Object} context React Component context
|
||||||
|
*/
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props);
|
||||||
|
const ship = props.ship;
|
||||||
|
|
||||||
|
this._keyDown = this._keyDown.bind(this);
|
||||||
|
this._toggleBoost = this._toggleBoost.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
boost: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add listeners after mounting
|
||||||
|
*/
|
||||||
|
componentDidMount() {
|
||||||
|
document.addEventListener('keydown', this._keyDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove listeners before unmounting
|
||||||
|
*/
|
||||||
|
componentWillUnmount() {
|
||||||
|
document.removeEventListener('keydown', this._keyDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update values if we change ship
|
||||||
|
* @param {Object} nextProps Incoming/Next properties
|
||||||
|
* @returns {boolean} Returns true if the component should be rerendered
|
||||||
|
*/
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
const { boost } = this.state;
|
||||||
|
const nextShip = nextProps.ship;
|
||||||
|
|
||||||
|
const nextBoost = nextShip.canBoost() ? boost : false;
|
||||||
|
if (nextBoost != boost) {
|
||||||
|
this.setState({
|
||||||
|
boost: nextBoost
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle Key Down
|
||||||
|
* @param {Event} e Keyboard Event
|
||||||
|
*/
|
||||||
|
_keyDown(e) {
|
||||||
|
if (e.ctrlKey || e.metaKey) { // CTRL/CMD
|
||||||
|
switch (e.keyCode) {
|
||||||
|
case 66: // b == boost
|
||||||
|
if (this.props.ship.canBoost()) {
|
||||||
|
e.preventDefault();
|
||||||
|
this._toggleBoost();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle the boost feature
|
||||||
|
*/
|
||||||
|
_toggleBoost() {
|
||||||
|
let { boost } = this.state;
|
||||||
|
boost = !boost;
|
||||||
|
this.setState({ boost });
|
||||||
|
this.props.onChange(boost);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render boost
|
||||||
|
* @return {React.Component} contents
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const { formats, translate, units } = this.context.language;
|
||||||
|
const { ship } = this.props;
|
||||||
|
const { boost } = this.state;
|
||||||
|
|
||||||
|
// TODO disable if ship cannot boost
|
||||||
|
return (
|
||||||
|
<span id='boost'>
|
||||||
|
<button id='boost' className={boost ? 'selected' : null} onClick={this._toggleBoost}>{translate('boost')}</button>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -182,10 +182,10 @@ export default class Defence extends TranslatedComponent {
|
|||||||
if (sys === 0) {
|
if (sys === 0) {
|
||||||
// No system pips so will never recover shields
|
// No system pips so will never recover shields
|
||||||
recover = Math.Inf;
|
recover = Math.Inf;
|
||||||
} else {
|
} else {
|
||||||
// Recover remaining shields at the rate of the power distributor's recharge
|
// Recover remaining shields at the rate of the power distributor's recharge
|
||||||
recover += remainingShieldToRecover / (sysRechargeRate / 0.6);
|
recover += remainingShieldToRecover / (sysRechargeRate / 0.6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recharge time is the time taken to go from 50% to 100%
|
// Recharge time is the time taken to go from 50% to 100%
|
||||||
@@ -207,10 +207,10 @@ export default class Defence extends TranslatedComponent {
|
|||||||
if (sys === 0) {
|
if (sys === 0) {
|
||||||
// No system pips so will never recharge shields
|
// No system pips so will never recharge shields
|
||||||
recharge = Math.Inf;
|
recharge = Math.Inf;
|
||||||
} else {
|
} else {
|
||||||
// Recharge remaining shields at the rate of the power distributor's recharge
|
// Recharge remaining shields at the rate of the power distributor's recharge
|
||||||
recharge += remainingShieldToRecharge / (sysRechargeRate / 0.6);
|
recharge += remainingShieldToRecharge / (sysRechargeRate / 0.6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shield = {
|
shield = {
|
||||||
|
|||||||
@@ -16,7 +16,11 @@ export default class EngineProfile extends TranslatedComponent {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: React.PropTypes.object.isRequired,
|
ship: React.PropTypes.object.isRequired,
|
||||||
chartWidth: React.PropTypes.number.isRequired,
|
chartWidth: React.PropTypes.number.isRequired,
|
||||||
code: React.PropTypes.string.isRequired
|
cargo: React.PropTypes.number.isRequired,
|
||||||
|
fuel: React.PropTypes.number.isRequired,
|
||||||
|
eng: React.PropTypes.number.isRequired,
|
||||||
|
boost: React.PropTypes.bool.isRequired,
|
||||||
|
marker: React.PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,8 +34,7 @@ export default class EngineProfile extends TranslatedComponent {
|
|||||||
const ship = this.props.ship;
|
const ship = this.props.ship;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
cargo: ship.cargoCapacity,
|
calcMaxSpeedFunc: this.calcMaxSpeed.bind(this, ship, this.props.eng, this.props.boost)
|
||||||
calcMaxSpeedFunc: this._calcMaxSpeed.bind(this, ship)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,36 +45,23 @@ export default class EngineProfile extends TranslatedComponent {
|
|||||||
* @return {boolean} Returns true if the component should be rerendered
|
* @return {boolean} Returns true if the component should be rerendered
|
||||||
*/
|
*/
|
||||||
componentWillReceiveProps(nextProps, nextContext) {
|
componentWillReceiveProps(nextProps, nextContext) {
|
||||||
if (nextProps.code != this.props.code) {
|
if (nextProps.marker != this.props.marker) {
|
||||||
this.setState({ cargo: nextProps.ship.cargoCapacity, calcMaxSpeedFunc: this._calcMaxSpeed.bind(this, nextProps.ship) });
|
this.setState({ calcMaxSpeedFunc: this.calcMaxSpeed.bind(this, nextProps.ship, nextProps.eng, nextProps.boost) });
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the maximum speed for this ship across its applicable mass
|
* Calculate the top speed for this ship given thrusters, mass and pips to ENG
|
||||||
* @param {Object} ship The ship
|
* @param {Object} ship The ship
|
||||||
|
* @param {Object} eng The number of pips to ENG
|
||||||
|
* @param {Object} boost If boost is enabled
|
||||||
* @param {Object} mass The mass at which to calculate the top speed
|
* @param {Object} mass The mass at which to calculate the top speed
|
||||||
* @return {number} The maximum speed
|
* @return {number} The maximum speed
|
||||||
*/
|
*/
|
||||||
_calcMaxSpeed(ship, mass) {
|
calcMaxSpeed(ship, eng, boost, mass) {
|
||||||
// Obtain the thrusters for this ship
|
|
||||||
const thrusters = ship.standard[1].m;
|
|
||||||
|
|
||||||
// Obtain the top speed
|
// Obtain the top speed
|
||||||
return Calc.speed(mass, ship.speed, thrusters, ship.engpip)[4];
|
return Calc.calcSpeed(mass, ship.speed, ship.standard[1].m, ship.pipSpeed, eng, ship.boost / ship.speed, boost);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update cargo level
|
|
||||||
* @param {number} cargoLevel Cargo level 0 - 1
|
|
||||||
*/
|
|
||||||
_cargoChange(cargoLevel) {
|
|
||||||
let ship = this.props.ship;
|
|
||||||
let cargo = Math.round(ship.cargoCapacity * cargoLevel);
|
|
||||||
this.setState({
|
|
||||||
cargo
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,24 +71,21 @@ export default class EngineProfile extends TranslatedComponent {
|
|||||||
render() {
|
render() {
|
||||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
||||||
const { formats, translate, units } = language;
|
const { formats, translate, units } = language;
|
||||||
const { ship } = this.props;
|
const { ship, cargo, eng, fuel, boost } = this.props;
|
||||||
const { cargo } = this.state;
|
|
||||||
|
|
||||||
// Calculate bounds for our line chart
|
// Calculate bounds for our line chart
|
||||||
const thrusters = ship.standard[1].m;
|
const thrusters = ship.standard[1].m;
|
||||||
const minMass = ship.calcLowestPossibleMass({ th: thrusters });
|
const minMass = ship.calcLowestPossibleMass({ th: thrusters });
|
||||||
const maxMass = thrusters.getMaxMass();
|
const maxMass = thrusters.getMaxMass();
|
||||||
let mass = ship.unladenMass + ship.fuelCapacity + cargo;
|
const mass = ship.unladenMass + fuel + cargo;
|
||||||
const minSpeed = Calc.speed(maxMass, ship.speed, thrusters, ship.engpip)[4];
|
const minSpeed = Calc.calcSpeed(maxMass, ship.speed, thrusters, ship.pipSpeed, 0, ship.boost / ship.speed, false);
|
||||||
const maxSpeed = Calc.speed(minMass, ship.speed, thrusters, ship.engpip)[4];
|
const maxSpeed = Calc.calcSpeed(minMass, ship.speed, thrusters, ship.pipSpeed, 4, ship.boost / ship.speed, true);
|
||||||
// Add a mark at our current mass
|
// Add a mark at our current mass
|
||||||
const mark = Math.min(mass, maxMass);
|
const mark = Math.min(mass, maxMass);
|
||||||
|
|
||||||
const cargoPercent = cargo / ship.cargoCapacity;
|
const code = `${ship.toString()}:${cargo}:${fuel}:${eng}:${boost}`;
|
||||||
|
|
||||||
const code = ship.toString() + '.' + ship.getModificationsString() + '.' + ship.getPowerEnabledString();
|
// This graph can have a precipitous fall-off so we use lots of points to make it look a little smoother
|
||||||
|
|
||||||
// This graph has a precipitous fall-off so we use lots of points to make it look a little smoother
|
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
<h1>{translate('engine profile')}</h1>
|
<h1>{translate('engine profile')}</h1>
|
||||||
@@ -117,27 +104,6 @@ export default class EngineProfile extends TranslatedComponent {
|
|||||||
points={1000}
|
points={1000}
|
||||||
code={code}
|
code={code}
|
||||||
/>
|
/>
|
||||||
{ship.cargoCapacity ?
|
|
||||||
<span>
|
|
||||||
<h3>{translate('cargo carried')}: {formats.int(cargo)}{units.T}</h3>
|
|
||||||
<table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}>
|
|
||||||
<tbody >
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Slider
|
|
||||||
axis={true}
|
|
||||||
onChange={this._cargoChange.bind(this)}
|
|
||||||
axisUnit={translate('T')}
|
|
||||||
percent={cargoPercent}
|
|
||||||
max={ship.cargoCapacity}
|
|
||||||
scale={sizeRatio}
|
|
||||||
onResize={onWindowResize}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</span> : '' }
|
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ export default class FSDProfile extends TranslatedComponent {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: React.PropTypes.object.isRequired,
|
ship: React.PropTypes.object.isRequired,
|
||||||
chartWidth: React.PropTypes.number.isRequired,
|
chartWidth: React.PropTypes.number.isRequired,
|
||||||
code: React.PropTypes.string.isRequired
|
cargo: React.PropTypes.number.isRequired,
|
||||||
|
fuel: React.PropTypes.number.isRequired,
|
||||||
|
marker: React.PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,8 +32,7 @@ export default class FSDProfile extends TranslatedComponent {
|
|||||||
const ship = this.props.ship;
|
const ship = this.props.ship;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
cargo: ship.cargoCapacity,
|
calcMaxRangeFunc: this._calcMaxRange.bind(this, ship, this.props.fuel)
|
||||||
calcMaxRangeFunc: this._calcMaxRange.bind(this, ship)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,8 +43,8 @@ export default class FSDProfile extends TranslatedComponent {
|
|||||||
* @return {boolean} Returns true if the component should be rerendered
|
* @return {boolean} Returns true if the component should be rerendered
|
||||||
*/
|
*/
|
||||||
componentWillReceiveProps(nextProps, nextContext) {
|
componentWillReceiveProps(nextProps, nextContext) {
|
||||||
if (nextProps.code != this.props.code) {
|
if (nextProps.marker != this.props.marker) {
|
||||||
this.setState({ cargo: nextProps.ship.cargoCapacity, calcMaxRangeFunc: this._calcMaxRange.bind(this, nextProps.ship) });
|
this.setState({ calcMaxRangeFunc: this._calcMaxRange.bind(this, nextProps.ship, nextProps.fuel) });
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -51,38 +52,23 @@ export default class FSDProfile extends TranslatedComponent {
|
|||||||
/**
|
/**
|
||||||
* Calculate the maximum range for this ship across its applicable mass
|
* Calculate the maximum range for this ship across its applicable mass
|
||||||
* @param {Object} ship The ship
|
* @param {Object} ship The ship
|
||||||
|
* @param {Object} fuel The fuel on the ship
|
||||||
* @param {Object} mass The mass at which to calculate the maximum range
|
* @param {Object} mass The mass at which to calculate the maximum range
|
||||||
* @return {number} The maximum range
|
* @return {number} The maximum range
|
||||||
*/
|
*/
|
||||||
_calcMaxRange(ship, mass) {
|
_calcMaxRange(ship, fuel, mass) {
|
||||||
// Obtain the FSD for this ship
|
|
||||||
const fsd = ship.standard[2].m;
|
|
||||||
|
|
||||||
// Obtain the maximum range
|
// Obtain the maximum range
|
||||||
return Calc.jumpRange(mass, fsd, fsd.getMaxFuelPerJump());
|
return Calc.jumpRange(mass, ship.standard[2].m, Math.min(fuel, ship.standard[2].m.getMaxFuelPerJump()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update cargo level
|
* Render FSD profile
|
||||||
* @param {number} cargoLevel Cargo level 0 - 1
|
|
||||||
*/
|
|
||||||
_cargoChange(cargoLevel) {
|
|
||||||
let ship = this.props.ship;
|
|
||||||
let cargo = Math.round(ship.cargoCapacity * cargoLevel);
|
|
||||||
this.setState({
|
|
||||||
cargo
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render engine profile
|
|
||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
||||||
const { formats, translate, units } = language;
|
const { formats, translate, units } = language;
|
||||||
const { ship } = this.props;
|
const { ship, cargo, fuel } = this.props;
|
||||||
const { cargo } = this.state;
|
|
||||||
|
|
||||||
|
|
||||||
// Calculate bounds for our line chart - use thruster info for X
|
// Calculate bounds for our line chart - use thruster info for X
|
||||||
@@ -90,15 +76,13 @@ export default class FSDProfile extends TranslatedComponent {
|
|||||||
const fsd = ship.standard[2].m;
|
const fsd = ship.standard[2].m;
|
||||||
const minMass = ship.calcLowestPossibleMass({ th: thrusters });
|
const minMass = ship.calcLowestPossibleMass({ th: thrusters });
|
||||||
const maxMass = thrusters.getMaxMass();
|
const maxMass = thrusters.getMaxMass();
|
||||||
let mass = ship.unladenMass + ship.fuelCapacity + cargo;
|
const mass = ship.unladenMass + fuel + cargo;
|
||||||
const minRange = Calc.jumpRange(maxMass, fsd, ship.fuelCapacity);
|
const minRange = 0;
|
||||||
const maxRange = Calc.jumpRange(minMass + fsd.getMaxFuelPerJump(), fsd, fsd.getMaxFuelPerJump());
|
const maxRange = Calc.jumpRange(minMass + fsd.getMaxFuelPerJump(), fsd, fsd.getMaxFuelPerJump());
|
||||||
// Add a mark at our current mass
|
// Add a mark at our current mass
|
||||||
const mark = Math.min(mass, maxMass);
|
const mark = Math.min(mass, maxMass);
|
||||||
|
|
||||||
const cargoPercent = cargo / ship.cargoCapacity;
|
const code = ship.name + ship.toString() + '.' + fuel;
|
||||||
|
|
||||||
const code = ship.name + ship.toString() + '.' + ship.getModificationsString() + '.' + ship.getPowerEnabledString();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
@@ -118,27 +102,6 @@ export default class FSDProfile extends TranslatedComponent {
|
|||||||
points={200}
|
points={200}
|
||||||
code={code}
|
code={code}
|
||||||
/>
|
/>
|
||||||
{ship.cargoCapacity ?
|
|
||||||
<span>
|
|
||||||
<h3>{translate('cargo carried')}: {formats.int(cargo)}{units.T}</h3>
|
|
||||||
<table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}>
|
|
||||||
<tbody >
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Slider
|
|
||||||
axis={true}
|
|
||||||
onChange={this._cargoChange.bind(this)}
|
|
||||||
axisUnit={translate('T')}
|
|
||||||
percent={cargoPercent}
|
|
||||||
max={ship.cargoCapacity}
|
|
||||||
scale={sizeRatio}
|
|
||||||
onResize={onWindowResize}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</span> : '' }
|
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export default class LineChart extends TranslatedComponent {
|
|||||||
_updateDimensions(props, scale) {
|
_updateDimensions(props, scale) {
|
||||||
let { width, xMax, xMin, yMin, yMax } = props;
|
let { width, xMax, xMin, yMin, yMax } = props;
|
||||||
let innerWidth = width - MARGIN.left - MARGIN.right;
|
let innerWidth = width - MARGIN.left - MARGIN.right;
|
||||||
let outerHeight = Math.round(width * 0.5 * scale);
|
let outerHeight = Math.round(width * 0.8 * scale);
|
||||||
let innerHeight = outerHeight - MARGIN.top - MARGIN.bottom;
|
let innerHeight = outerHeight - MARGIN.top - MARGIN.bottom;
|
||||||
|
|
||||||
this.state.xScale.range([0, innerWidth]).domain([xMin, xMax || 1]).clamp(true);
|
this.state.xScale.range([0, innerWidth]).domain([xMin, xMax || 1]).clamp(true);
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export default class Movement extends TranslatedComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<span id='movement'>
|
<span id='movement'>
|
||||||
|
<h1>{translate('movement profile')}</h1>
|
||||||
<svg viewBox='0 0 600 600' fillRule="evenodd" clipRule="evenodd">
|
<svg viewBox='0 0 600 600' fillRule="evenodd" clipRule="evenodd">
|
||||||
// Axes
|
// Axes
|
||||||
<path d="M150 250v300" strokeWidth='1'/>
|
<path d="M150 250v300" strokeWidth='1'/>
|
||||||
|
|||||||
498
src/app/components/Offence.jsx
Normal file
498
src/app/components/Offence.jsx
Normal file
@@ -0,0 +1,498 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import cn from 'classnames';
|
||||||
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
|
import * as Calc from '../shipyard/Calculations';
|
||||||
|
import { DamageAbsolute, DamageExplosive, DamageKinetic, DamageThermal } from './SvgIcons';
|
||||||
|
import PieChart from './PieChart';
|
||||||
|
import VerticalBarChart from './VerticalBarChart';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offence information
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param {Object} props React Component properties
|
||||||
|
*/
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
const { shield, armour, shielddamage, armourdamage } = this._calcMetrics(props.ship, props.opponent, props.sys, props.engagementrange);
|
||||||
|
this.state = { shield, armour, shielddamage, armourdamage };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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, shielddamage, armourdamage } = this._calcMetrics(nextProps.ship, nextProps.opponent, nextProps.sys, nextProps.engagementrange);
|
||||||
|
this.setState({ shield, armour, shielddamage, armourdamage });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the sustained DPS for a ship at a given range, excluding resistances
|
||||||
|
* @param {Object} ship The ship
|
||||||
|
* @param {Object} opponent The opponent ship
|
||||||
|
* @param {int} engagementrange The range between the ship and opponent
|
||||||
|
* @returns {Object} Sustained DPS for shield and armour
|
||||||
|
*/
|
||||||
|
_calcSustainedDps(ship, opponent, engagementrange) {
|
||||||
|
const shieldsdps = {
|
||||||
|
absolute: 0,
|
||||||
|
explosive: 0,
|
||||||
|
kinetic: 0,
|
||||||
|
thermal: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const armoursdps = {
|
||||||
|
absolute: 0,
|
||||||
|
explosive: 0,
|
||||||
|
kinetic: 0,
|
||||||
|
thermal: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 0; i < ship.hardpoints.length; i++) {
|
||||||
|
if (ship.hardpoints[i].m && ship.hardpoints[i].enabled && ship.hardpoints[i].maxClass > 0) {
|
||||||
|
const m = ship.hardpoints[i].m;
|
||||||
|
// Initial sustained DPS
|
||||||
|
let sDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getDps();
|
||||||
|
// Take fall-off in to account
|
||||||
|
const falloff = m.getFalloff();
|
||||||
|
if (falloff && engagementrange > falloff) {
|
||||||
|
const dropoffRange = m.getRange() - falloff;
|
||||||
|
sDps *= 1 - Math.min((engagementrange - falloff) / dropoffRange, 1);
|
||||||
|
}
|
||||||
|
// Piercing/hardness modifier (for armour only)
|
||||||
|
const armourMultiple = m.getPiercing() >= opponent.hardness ? 1 : m.getPiercing() / opponent.hardness;
|
||||||
|
// Break out the damage according to type
|
||||||
|
if (m.getDamageDist().A) {
|
||||||
|
shieldsdps.absolute += sDps * m.getDamageDist().A;
|
||||||
|
armoursdps.absolute += sDps * m.getDamageDist().A * armourMultiple;
|
||||||
|
}
|
||||||
|
if (m.getDamageDist().E) {
|
||||||
|
shieldsdps.explosive += sDps * m.getDamageDist().E;
|
||||||
|
armoursdps.explosive += sDps * m.getDamageDist().E * armourMultiple;
|
||||||
|
}
|
||||||
|
if (m.getDamageDist().K) {
|
||||||
|
shieldsdps.kinetic += sDps * m.getDamageDist().K;
|
||||||
|
armoursdps.kinetic += sDps * m.getDamageDist().K * armourMultiple;
|
||||||
|
}
|
||||||
|
if (m.getDamageDist().T) {
|
||||||
|
shieldsdps.thermal += sDps * m.getDamageDist().T;
|
||||||
|
armoursdps.thermal += sDps * m.getDamageDist().T * armourMultiple;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { shieldsdps, armoursdps };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain the recharge rate of the SYS capacitor of a power distributor given pips
|
||||||
|
* @param {Object} pd The power distributor
|
||||||
|
* @param {number} sys The number of pips to SYS
|
||||||
|
* @returns {number} The recharge rate in MJ/s
|
||||||
|
*/
|
||||||
|
_calcSysRechargeRate(pd, sys) {
|
||||||
|
return pd.getSystemsRechargeRate() * Math.pow(sys, 1.1) / Math.pow(4, 1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate shield metrics
|
||||||
|
* @param {Object} ship The ship
|
||||||
|
* @param {Object} opponent The opponent ship
|
||||||
|
* @param {int} sys The opponent ship
|
||||||
|
* @param {int} engagementrange The range between the ship and opponent
|
||||||
|
* @returns {Object} Shield metrics
|
||||||
|
*/
|
||||||
|
_calcMetrics(ship, opponent, sys, engagementrange) {
|
||||||
|
const sysResistance = this._calcSysResistance(sys);
|
||||||
|
const maxSysResistance = this._calcSysResistance(4);
|
||||||
|
|
||||||
|
// Obtain the opponent's sustained DPS on us for later damage calculations
|
||||||
|
const { shieldsdps, armoursdps } = this._calcSustainedDps(opponent, ship, engagementrange);
|
||||||
|
|
||||||
|
let shielddamage = {};
|
||||||
|
let shield = {};
|
||||||
|
const shieldGeneratorSlot = ship.findInternalByGroup('sg');
|
||||||
|
if (shieldGeneratorSlot && shieldGeneratorSlot.enabled && shieldGeneratorSlot.m) {
|
||||||
|
const shieldGenerator = shieldGeneratorSlot.m;
|
||||||
|
|
||||||
|
// Boosters
|
||||||
|
let boost = 1;
|
||||||
|
let boosterExplDmg = 1;
|
||||||
|
let boosterKinDmg = 1;
|
||||||
|
let boosterThermDmg = 1;
|
||||||
|
for (let slot of ship.hardpoints) {
|
||||||
|
if (slot.enabled && slot.m && slot.m.grp == 'sb') {
|
||||||
|
boost += slot.m.getShieldBoost();
|
||||||
|
boosterExplDmg = boosterExplDmg * (1 - slot.m.getExplosiveResistance());
|
||||||
|
boosterKinDmg = boosterKinDmg * (1 - slot.m.getKineticResistance());
|
||||||
|
boosterThermDmg = boosterThermDmg * (1 - slot.m.getThermalResistance());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate diminishing returns for boosters
|
||||||
|
boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5);
|
||||||
|
// Remove base shield generator strength
|
||||||
|
boost -= 1;
|
||||||
|
// Apply diminishing returns
|
||||||
|
boosterExplDmg = boosterExplDmg > 0.7 ? boosterExplDmg : 0.7 - (0.7 - boosterExplDmg) / 2;
|
||||||
|
boosterKinDmg = boosterKinDmg > 0.7 ? boosterKinDmg : 0.7 - (0.7 - boosterKinDmg) / 2;
|
||||||
|
boosterThermDmg = boosterThermDmg > 0.7 ? boosterThermDmg : 0.7 - (0.7 - boosterThermDmg) / 2;
|
||||||
|
|
||||||
|
const generatorStrength = Calc.shieldStrength(ship.hullMass, ship.baseShieldStrength, shieldGenerator, 1);
|
||||||
|
const boostersStrength = generatorStrength * boost;
|
||||||
|
|
||||||
|
// Recover time is the time taken to go from 0 to 50%. It includes a 16-second wait before shields start to recover
|
||||||
|
const shieldToRecover = (generatorStrength + boostersStrength) / 2;
|
||||||
|
const powerDistributor = ship.standard[4].m;
|
||||||
|
const sysRechargeRate = this._calcSysRechargeRate(powerDistributor, sys);
|
||||||
|
|
||||||
|
// Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes
|
||||||
|
// 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration
|
||||||
|
let capacitorDrain = (shieldGenerator.getBrokenRegenerationRate() * 0.6) - sysRechargeRate;
|
||||||
|
let capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain;
|
||||||
|
|
||||||
|
let recover = 16;
|
||||||
|
if (capacitorDrain <= 0 || shieldToRecover < capacitorLifetime * shieldGenerator.getBrokenRegenerationRate()) {
|
||||||
|
// We can recover the entire shield from the capacitor store
|
||||||
|
recover += shieldToRecover / shieldGenerator.getBrokenRegenerationRate();
|
||||||
|
} else {
|
||||||
|
// We can recover some of the shield from the capacitor store
|
||||||
|
recover += capacitorLifetime;
|
||||||
|
const remainingShieldToRecover = shieldToRecover - capacitorLifetime * shieldGenerator.getBrokenRegenerationRate();
|
||||||
|
if (sys === 0) {
|
||||||
|
// No system pips so will never recover shields
|
||||||
|
recover = Math.Inf;
|
||||||
|
} else {
|
||||||
|
// Recover remaining shields at the rate of the power distributor's recharge
|
||||||
|
recover += remainingShieldToRecover / (sysRechargeRate / 0.6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recharge time is the time taken to go from 50% to 100%
|
||||||
|
const shieldToRecharge = (generatorStrength + boostersStrength) / 2;
|
||||||
|
|
||||||
|
// Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes
|
||||||
|
// 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration
|
||||||
|
capacitorDrain = (shieldGenerator.getRegenerationRate() * 0.6) - sysRechargeRate;
|
||||||
|
capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain;
|
||||||
|
|
||||||
|
let recharge = 0;
|
||||||
|
if (capacitorDrain <= 0 || shieldToRecharge < capacitorLifetime * shieldGenerator.getRegenerationRate()) {
|
||||||
|
// We can recharge the entire shield from the capacitor store
|
||||||
|
recharge += shieldToRecharge / shieldGenerator.getRegenerationRate();
|
||||||
|
} else {
|
||||||
|
// We can recharge some of the shield from the capacitor store
|
||||||
|
recharge += capacitorLifetime;
|
||||||
|
const remainingShieldToRecharge = shieldToRecharge - capacitorLifetime * shieldGenerator.getRegenerationRate();
|
||||||
|
if (sys === 0) {
|
||||||
|
// No system pips so will never recharge shields
|
||||||
|
recharge = Math.Inf;
|
||||||
|
} else {
|
||||||
|
// Recharge remaining shields at the rate of the power distributor's recharge
|
||||||
|
recharge += remainingShieldToRecharge / (sysRechargeRate / 0.6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shield = {
|
||||||
|
generator: generatorStrength,
|
||||||
|
boosters: boostersStrength,
|
||||||
|
cells: ship.shieldCells,
|
||||||
|
total: generatorStrength + boostersStrength + ship.shieldCells,
|
||||||
|
recover,
|
||||||
|
recharge,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Shield resistances have three components: the shield generator, the shield boosters and the SYS pips.
|
||||||
|
// We re-cast these as damage percentages
|
||||||
|
shield.absolute = {
|
||||||
|
generator: 1,
|
||||||
|
boosters: 1,
|
||||||
|
sys: 1 - sysResistance,
|
||||||
|
total: 1 - sysResistance,
|
||||||
|
max: 1 - maxSysResistance
|
||||||
|
};
|
||||||
|
|
||||||
|
shield.explosive = {
|
||||||
|
generator: 1 - shieldGenerator.getExplosiveResistance(),
|
||||||
|
boosters: boosterExplDmg,
|
||||||
|
sys: (1 - sysResistance),
|
||||||
|
total: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - sysResistance),
|
||||||
|
max: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - maxSysResistance)
|
||||||
|
};
|
||||||
|
|
||||||
|
shield.kinetic = {
|
||||||
|
generator: 1 - shieldGenerator.getKineticResistance(),
|
||||||
|
boosters: boosterKinDmg,
|
||||||
|
sys: (1 - sysResistance),
|
||||||
|
total: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - sysResistance),
|
||||||
|
max: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - maxSysResistance)
|
||||||
|
};
|
||||||
|
|
||||||
|
shield.thermal = {
|
||||||
|
generator: 1 - shieldGenerator.getThermalResistance(),
|
||||||
|
boosters: boosterThermDmg,
|
||||||
|
sys: (1 - sysResistance),
|
||||||
|
total: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - sysResistance),
|
||||||
|
max: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - maxSysResistance)
|
||||||
|
};
|
||||||
|
|
||||||
|
shielddamage.absolutesdps = shieldsdps.absolute *= shield.absolute.total;
|
||||||
|
shielddamage.explosivesdps = shieldsdps.explosive *= shield.explosive.total;
|
||||||
|
shielddamage.kineticsdps = shieldsdps.kinetic *= shield.kinetic.total;
|
||||||
|
shielddamage.thermalsdps = shieldsdps.thermal *= shield.thermal.total;
|
||||||
|
shielddamage.totalsdps = shielddamage.absolutesdps + shielddamage.explosivesdps + shielddamage.kineticsdps + shielddamage.thermalsdps;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Armour from bulkheads
|
||||||
|
const armourBulkheads = ship.baseArmour + (ship.baseArmour * ship.bulkheads.m.getHullBoost());
|
||||||
|
let armourReinforcement = 0;
|
||||||
|
|
||||||
|
let moduleArmour = 0;
|
||||||
|
let moduleProtection = 1;
|
||||||
|
|
||||||
|
let hullExplDmg = 1;
|
||||||
|
let hullKinDmg = 1;
|
||||||
|
let hullThermDmg = 1;
|
||||||
|
|
||||||
|
// Armour from HRPs and module armour from MRPs
|
||||||
|
for (let slot of ship.internal) {
|
||||||
|
if (slot.m && slot.m.grp == 'hr') {
|
||||||
|
armourReinforcement += slot.m.getHullReinforcement();
|
||||||
|
// Hull boost for HRPs is applied against the ship's base armour
|
||||||
|
armourReinforcement += ship.baseArmour * slot.m.getModValue('hullboost') / 10000;
|
||||||
|
|
||||||
|
hullExplDmg = hullExplDmg * (1 - slot.m.getExplosiveResistance());
|
||||||
|
hullKinDmg = hullKinDmg * (1 - slot.m.getKineticResistance());
|
||||||
|
hullThermDmg = hullThermDmg * (1 - slot.m.getThermalResistance());
|
||||||
|
}
|
||||||
|
if (slot.m && slot.m.grp == 'mrp') {
|
||||||
|
moduleArmour += slot.m.getIntegrity();
|
||||||
|
moduleProtection = moduleProtection * (1 - slot.m.getProtection());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
moduleProtection = 1 - moduleProtection;
|
||||||
|
|
||||||
|
// Apply diminishing returns
|
||||||
|
hullExplDmg = hullExplDmg > 0.7 ? hullExplDmg : 0.7 - (0.7 - hullExplDmg) / 2;
|
||||||
|
hullKinDmg = hullKinDmg > 0.7 ? hullKinDmg : 0.7 - (0.7 - hullKinDmg) / 2;
|
||||||
|
hullThermDmg = hullThermDmg > 0.7 ? hullThermDmg : 0.7 - (0.7 - hullThermDmg) / 2;
|
||||||
|
|
||||||
|
const armour = {
|
||||||
|
bulkheads: armourBulkheads,
|
||||||
|
reinforcement: armourReinforcement,
|
||||||
|
modulearmour: moduleArmour,
|
||||||
|
moduleprotection: moduleProtection,
|
||||||
|
total: armourBulkheads + armourReinforcement
|
||||||
|
};
|
||||||
|
|
||||||
|
// Armour resistances have two components: bulkheads and HRPs
|
||||||
|
// We re-cast these as damage percentages
|
||||||
|
armour.absolute = {
|
||||||
|
bulkheads: 1,
|
||||||
|
reinforcement: 1,
|
||||||
|
total: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
armour.explosive = {
|
||||||
|
bulkheads: 1 - ship.bulkheads.m.getExplosiveResistance(),
|
||||||
|
reinforcement: hullExplDmg,
|
||||||
|
total: (1 - ship.bulkheads.m.getExplosiveResistance()) * hullExplDmg
|
||||||
|
};
|
||||||
|
|
||||||
|
armour.kinetic = {
|
||||||
|
bulkheads: 1 - ship.bulkheads.m.getKineticResistance(),
|
||||||
|
reinforcement: hullKinDmg,
|
||||||
|
total: (1 - ship.bulkheads.m.getKineticResistance()) * hullKinDmg
|
||||||
|
};
|
||||||
|
|
||||||
|
armour.thermal = {
|
||||||
|
bulkheads: 1 - ship.bulkheads.m.getThermalResistance(),
|
||||||
|
reinforcement: hullThermDmg,
|
||||||
|
total: (1 - ship.bulkheads.m.getThermalResistance()) * hullThermDmg
|
||||||
|
};
|
||||||
|
|
||||||
|
const armourdamage = {
|
||||||
|
absolutesdps: armoursdps.absolute *= armour.absolute.total,
|
||||||
|
explosivesdps: armoursdps.explosive *= armour.explosive.total,
|
||||||
|
kineticsdps: armoursdps.kinetic *= armour.kinetic.total,
|
||||||
|
thermalsdps: armoursdps.thermal *= armour.thermal.total
|
||||||
|
};
|
||||||
|
armourdamage.totalsdps = armourdamage.absolutesdps + armourdamage.explosivesdps + armourdamage.kineticsdps + armourdamage.thermalsdps;
|
||||||
|
|
||||||
|
return { shield, armour, shielddamage, armourdamage };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the resistance provided by SYS pips
|
||||||
|
* @param {integer} sys the value of the SYS pips
|
||||||
|
* @returns {integer} the resistance for the given pips
|
||||||
|
*/
|
||||||
|
_calcSysResistance(sys) {
|
||||||
|
return Math.pow(sys,0.85) * 0.6 / Math.pow(4,0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render shields
|
||||||
|
* @return {React.Component} contents
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const { ship, sys } = this.props;
|
||||||
|
const { language, tooltip, termtip } = this.context;
|
||||||
|
const { formats, translate, units } = language;
|
||||||
|
const { shield, armour, shielddamage, armourdamage } = this.state;
|
||||||
|
|
||||||
|
const shieldSourcesData = [];
|
||||||
|
const effectiveShieldData = [];
|
||||||
|
const shieldDamageTakenData = [];
|
||||||
|
const shieldTooltipDetails = [];
|
||||||
|
const shieldAbsoluteTooltipDetails = [];
|
||||||
|
const shieldExplosiveTooltipDetails = [];
|
||||||
|
const shieldKineticTooltipDetails = [];
|
||||||
|
const shieldThermalTooltipDetails = [];
|
||||||
|
let maxEffectiveShield = 0;
|
||||||
|
if (shield.total) {
|
||||||
|
if (Math.round(shield.generator) > 0) shieldSourcesData.push({ value: Math.round(shield.generator), label: translate('generator') });
|
||||||
|
if (Math.round(shield.boosters) > 0) shieldSourcesData.push({ value: Math.round(shield.boosters), label: translate('boosters') });
|
||||||
|
if (Math.round(shield.cells) > 0) shieldSourcesData.push({ value: Math.round(shield.cells), label: translate('cells') });
|
||||||
|
|
||||||
|
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>);
|
||||||
|
|
||||||
|
const effectiveAbsoluteShield = shield.total / shield.absolute.total;
|
||||||
|
effectiveShieldData.push({ value: Math.round(effectiveAbsoluteShield), label: translate('absolute') });
|
||||||
|
const effectiveExplosiveShield = shield.total / shield.explosive.total;
|
||||||
|
effectiveShieldData.push({ value: Math.round(effectiveExplosiveShield), label: translate('explosive') });
|
||||||
|
const effectiveKineticShield = shield.total / shield.kinetic.total;
|
||||||
|
effectiveShieldData.push({ value: Math.round(effectiveKineticShield), label: translate('kinetic') });
|
||||||
|
const effectiveThermalShield = shield.total / shield.thermal.total;
|
||||||
|
effectiveShieldData.push({ value: Math.round(effectiveThermalShield), label: translate('thermal') });
|
||||||
|
|
||||||
|
shieldDamageTakenData.push({ value: Math.round(shield.absolute.total * 100), label: translate('absolute') });
|
||||||
|
shieldDamageTakenData.push({ value: Math.round(shield.explosive.total * 100), label: translate('explosive') });
|
||||||
|
shieldDamageTakenData.push({ value: Math.round(shield.kinetic.total * 100), label: translate('kinetic') });
|
||||||
|
shieldDamageTakenData.push({ value: Math.round(shield.thermal.total * 100), label: translate('thermal') });
|
||||||
|
|
||||||
|
maxEffectiveShield = Math.max(shield.total / shield.absolute.max, shield.total / shield.explosive.max, shield.total / shield.kinetic.max, shield.total / shield.thermal.max);
|
||||||
|
}
|
||||||
|
|
||||||
|
const armourSourcesData = [];
|
||||||
|
if (Math.round(armour.bulkheads) > 0) armourSourcesData.push({ value: Math.round(armour.bulkheads), label: translate('bulkheads') });
|
||||||
|
if (Math.round(armour.reinforcement) > 0) armourSourcesData.push({ value: Math.round(armour.reinforcement), label: translate('reinforcement') });
|
||||||
|
|
||||||
|
const 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>);
|
||||||
|
|
||||||
|
const effectiveArmourData = [];
|
||||||
|
const effectiveAbsoluteArmour = armour.total / armour.absolute.total;
|
||||||
|
effectiveArmourData.push({ value: Math.round(effectiveAbsoluteArmour), label: translate('absolute') });
|
||||||
|
const effectiveExplosiveArmour = armour.total / armour.explosive.total;
|
||||||
|
effectiveArmourData.push({ value: Math.round(effectiveExplosiveArmour), label: translate('explosive') });
|
||||||
|
const effectiveKineticArmour = armour.total / armour.kinetic.total;
|
||||||
|
effectiveArmourData.push({ value: Math.round(effectiveKineticArmour), label: translate('kinetic') });
|
||||||
|
const effectiveThermalArmour = armour.total / armour.thermal.total;
|
||||||
|
effectiveArmourData.push({ value: Math.round(effectiveThermalArmour), label: translate('thermal') });
|
||||||
|
|
||||||
|
const armourDamageTakenData = [];
|
||||||
|
armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute') });
|
||||||
|
armourDamageTakenData.push({ value: Math.round(armour.explosive.total * 100), label: translate('explosive') });
|
||||||
|
armourDamageTakenData.push({ value: Math.round(armour.kinetic.total * 100), label: translate('kinetic') });
|
||||||
|
armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal') });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span id='defence'>
|
||||||
|
{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('infinity') : 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>
|
||||||
|
</span>);
|
||||||
|
}
|
||||||
|
}
|
||||||
156
src/app/components/OutfittingSubpages.jsx
Normal file
156
src/app/components/OutfittingSubpages.jsx
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import cn from 'classnames';
|
||||||
|
import { Ships } from 'coriolis-data/dist';
|
||||||
|
import Ship from '../shipyard/Ship';
|
||||||
|
import { Insurance } from '../shipyard/Constants';
|
||||||
|
import { slotName, slotComparator } from '../utils/SlotFunctions';
|
||||||
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
|
import PowerManagement from './PowerManagement';
|
||||||
|
import CostSection from './CostSection';
|
||||||
|
import EngineProfile from './EngineProfile';
|
||||||
|
import FSDProfile from './FSDProfile';
|
||||||
|
import Movement from './Movement';
|
||||||
|
import Defence from './Defence';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outfitting subpages
|
||||||
|
*/
|
||||||
|
export default class OutfittingSubpages extends TranslatedComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
ship: React.PropTypes.object.isRequired,
|
||||||
|
code: React.PropTypes.string.isRequired,
|
||||||
|
onChange: React.PropTypes.func.isRequired,
|
||||||
|
chartWidth: React.PropTypes.number.isRequired,
|
||||||
|
buildName: React.PropTypes.string,
|
||||||
|
sys: React.PropTypes.number.isRequired,
|
||||||
|
eng: React.PropTypes.number.isRequired,
|
||||||
|
wep: React.PropTypes.number.isRequired,
|
||||||
|
cargo: React.PropTypes.number.isRequired,
|
||||||
|
fuel: React.PropTypes.number.isRequired,
|
||||||
|
boost: React.PropTypes.bool.isRequired,
|
||||||
|
engagementRange: React.PropTypes.number.isRequired,
|
||||||
|
opponent: React.PropTypes.object.isRequired,
|
||||||
|
opponentBuild: React.PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param {Object} props React Component properties
|
||||||
|
*/
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this._powerTab = this._powerTab.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
tab: 'power'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show selected tab
|
||||||
|
* @param {string} tab Tab name
|
||||||
|
*/
|
||||||
|
_showTab(tab) {
|
||||||
|
this.setState({ tab });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the power tab
|
||||||
|
* @return {React.Component} Tab contents
|
||||||
|
*/
|
||||||
|
_powerTab() {
|
||||||
|
let { ship, buildName, code, onChange } = this.props;
|
||||||
|
|
||||||
|
return <div>
|
||||||
|
<PowerManagement ship={ship} code={code} onChange={onChange} />
|
||||||
|
<CostSection ship={ship} buildName={buildName} code={code} />
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the profiles tab
|
||||||
|
* @return {React.Component} Tab contents
|
||||||
|
*/
|
||||||
|
_profilesTab() {
|
||||||
|
const { ship, code, chartWidth, cargo, fuel, eng, boost } = this.props;
|
||||||
|
let realBoost = boost && ship.canBoost();
|
||||||
|
|
||||||
|
const engineProfileMarker = `${ship.toString()}:${cargo}:${fuel}:${eng}:${realBoost}`;
|
||||||
|
const fsdProfileMarker = `${ship.toString()}:${cargo}:${fuel}`;
|
||||||
|
const movementMarker = `${ship.topSpeed}:${ship.pitch}:${ship.roll}:${ship.yaw}:${ship.canBoost()}`;
|
||||||
|
|
||||||
|
return <div>
|
||||||
|
<div className='group third'>
|
||||||
|
<EngineProfile ship={ship} marker={engineProfileMarker} chartWidth={chartWidth} fuel={fuel} cargo={cargo} eng={eng} boost={realBoost} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='group third'>
|
||||||
|
<FSDProfile ship={ship} marker={fsdProfileMarker} fuel={fuel} cargo={cargo} chartWidth={chartWidth} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='group third'>
|
||||||
|
<Movement marker={movementMarker} ship={ship} boost={boost} eng={eng} cargo={cargo} fuel={fuel}/>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the offence tab
|
||||||
|
* @return {React.Component} Tab contents
|
||||||
|
*/
|
||||||
|
_offenceTab() {
|
||||||
|
const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild } = this.props;
|
||||||
|
|
||||||
|
return <div>
|
||||||
|
<h1>Offence goes here</h1>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the defence tab
|
||||||
|
* @return {React.Component} Tab contents
|
||||||
|
*/
|
||||||
|
_defenceTab() {
|
||||||
|
const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild } = this.props;
|
||||||
|
|
||||||
|
const marker = `${ship.shield}:${ship.shieldCells}:${ship.shieldExplRes}:${ship.shieldKinRes}:${ship.shieldThermRes}:${ship.armour}:${ship.standard[4].m.getSystemsCapacity()}:${ship.standard[4].m.getSystemsRechargeRate()}:${opponent.name}:${opponentBuild}:${engagementRange}`;
|
||||||
|
|
||||||
|
return <div>
|
||||||
|
<Defence marker={marker} ship={ship} opponent={opponent} sys={sys} engagementrange={engagementRange}/>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the section
|
||||||
|
* @return {React.Component} Contents
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const tab = this.state.tab || 'power';
|
||||||
|
const translate = this.context.language.translate;
|
||||||
|
let tabSection;
|
||||||
|
|
||||||
|
switch (tab) {
|
||||||
|
case 'power': tabSection = this._powerTab(); break;
|
||||||
|
case 'profiles': tabSection = this._profilesTab(); break;
|
||||||
|
case 'offence': tabSection = this._offenceTab(); break;
|
||||||
|
case 'defence': tabSection = this._defenceTab(); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='group full' style={{ minHeight: '900px' }}>
|
||||||
|
<table className='tabs'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style={{ width:'25%' }} className={cn({ active: tab == 'power' })} onClick={this._showTab.bind(this, 'power')} >{translate('power and costs')}</th>
|
||||||
|
<th style={{ width:'25%' }} className={cn({ active: tab == 'profiles' })} onClick={this._showTab.bind(this, 'profiles')} >{translate('profiles')}</th>
|
||||||
|
<th style={{ width:'25%' }} className={cn({ active: tab == 'offence' })} onClick={this._showTab.bind(this, 'offence')} >{translate('offence')}</th>
|
||||||
|
<th style={{ width:'25%' }} className={cn({ active: tab == 'defence' })} onClick={this._showTab.bind(this, 'defence')} >{translate('defence')}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
{tabSection}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,10 @@ const LABEL_COLOUR = '#FFFFFF';
|
|||||||
*/
|
*/
|
||||||
export default class PieChart extends Component {
|
export default class PieChart extends Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
data : React.PropTypes.array.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
|
|||||||
@@ -11,11 +11,10 @@ import Module from '../shipyard/Module';
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Pips displays SYS/ENG/WEP pips and allows users to change them with key presses by clicking on the relevant area.
|
* Pips displays SYS/ENG/WEP pips and allows users to change them with key presses by clicking on the relevant area.
|
||||||
* Requires an onChange() function of the form onChange(sys, eng, wep, boost) which is triggered whenever the pips change.
|
* Requires an onChange() function of the form onChange(sys, eng, wep) which is triggered whenever the pips change.
|
||||||
*/
|
*/
|
||||||
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
|
||||||
};
|
};
|
||||||
@@ -31,14 +30,12 @@ export default class Pips extends TranslatedComponent {
|
|||||||
const pd = ship.standard[4].m;
|
const pd = ship.standard[4].m;
|
||||||
|
|
||||||
this._keyDown = this._keyDown.bind(this);
|
this._keyDown = this._keyDown.bind(this);
|
||||||
this._toggleBoost = this._toggleBoost.bind(this);
|
|
||||||
|
|
||||||
let pipsSvg = this._renderPips(2, 2, 2);
|
let pipsSvg = this._renderPips(2, 2, 2);
|
||||||
this.state = {
|
this.state = {
|
||||||
sys: 2,
|
sys: 2,
|
||||||
eng: 2,
|
eng: 2,
|
||||||
wep: 2,
|
wep: 2,
|
||||||
boost: false,
|
|
||||||
sysCap: pd.getSystemsCapacity(),
|
sysCap: pd.getSystemsCapacity(),
|
||||||
engCap: pd.getEnginesCapacity(),
|
engCap: pd.getEnginesCapacity(),
|
||||||
wepCap: pd.getWeaponsCapacity(),
|
wepCap: pd.getWeaponsCapacity(),
|
||||||
@@ -69,7 +66,7 @@ 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, boost } = this.state;
|
const { sysCap, engCap, wepCap, sysRate, engRate, wepRate } = this.state;
|
||||||
const nextShip = nextProps.ship;
|
const nextShip = nextProps.ship;
|
||||||
const pd = nextShip.standard[4].m;
|
const pd = nextShip.standard[4].m;
|
||||||
|
|
||||||
@@ -79,22 +76,19 @@ 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
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,12 +102,6 @@ export default class Pips extends TranslatedComponent {
|
|||||||
_keyDown(e) {
|
_keyDown(e) {
|
||||||
if (e.ctrlKey || e.metaKey) { // CTRL/CMD
|
if (e.ctrlKey || e.metaKey) { // CTRL/CMD
|
||||||
switch (e.keyCode) {
|
switch (e.keyCode) {
|
||||||
case 66: // b == boost
|
|
||||||
if (this.props.ship.canBoost()) {
|
|
||||||
e.preventDefault();
|
|
||||||
this._toggleBoost();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 37: // Left arrow == increase SYS
|
case 37: // Left arrow == increase SYS
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this._incSys();
|
this._incSys();
|
||||||
@@ -154,11 +142,11 @@ export default class Pips extends TranslatedComponent {
|
|||||||
* Reset the capacitor
|
* Reset the capacitor
|
||||||
*/
|
*/
|
||||||
_reset() {
|
_reset() {
|
||||||
let { sys, eng, wep, boost } = this.state;
|
let { sys, eng, wep } = this.state;
|
||||||
if (sys != 2 || eng != 2 || wep != 2) {
|
if (sys != 2 || eng != 2 || wep != 2) {
|
||||||
sys = eng = wep = 2;
|
sys = eng = wep = 2;
|
||||||
this.setState({ sys, eng, wep, pipsSvg: this._renderPips(sys, eng, wep) });
|
this.setState({ sys, eng, wep, pipsSvg: this._renderPips(sys, eng, wep) });
|
||||||
this.props.onChange(sys, eng, wep, boost);
|
this.props.onChange(sys, eng, wep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +154,7 @@ export default class Pips extends TranslatedComponent {
|
|||||||
* Increment the SYS capacitor
|
* Increment the SYS capacitor
|
||||||
*/
|
*/
|
||||||
_incSys() {
|
_incSys() {
|
||||||
let { sys, eng, wep, boost } = this.state;
|
let { sys, eng, wep } = this.state;
|
||||||
|
|
||||||
const required = Math.min(1, 4 - sys);
|
const required = Math.min(1, 4 - sys);
|
||||||
if (required > 0) {
|
if (required > 0) {
|
||||||
@@ -194,7 +182,7 @@ export default class Pips extends TranslatedComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.setState({ sys, eng, wep, pipsSvg: this._renderPips(sys, eng, wep) });
|
this.setState({ sys, eng, wep, pipsSvg: this._renderPips(sys, eng, wep) });
|
||||||
this.props.onChange(sys, eng, wep, boost);
|
this.props.onChange(sys, eng, wep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +190,7 @@ export default class Pips extends TranslatedComponent {
|
|||||||
* Increment the ENG capacitor
|
* Increment the ENG capacitor
|
||||||
*/
|
*/
|
||||||
_incEng() {
|
_incEng() {
|
||||||
let { sys, eng, wep, boost } = this.state;
|
let { sys, eng, wep } = this.state;
|
||||||
|
|
||||||
const required = Math.min(1, 4 - eng);
|
const required = Math.min(1, 4 - eng);
|
||||||
if (required > 0) {
|
if (required > 0) {
|
||||||
@@ -230,7 +218,7 @@ export default class Pips extends TranslatedComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.setState({ sys, eng, wep, pipsSvg: this._renderPips(sys, eng, wep) });
|
this.setState({ sys, eng, wep, pipsSvg: this._renderPips(sys, eng, wep) });
|
||||||
this.props.onChange(sys, eng, wep, boost);
|
this.props.onChange(sys, eng, wep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +226,7 @@ export default class Pips extends TranslatedComponent {
|
|||||||
* Increment the WEP capacitor
|
* Increment the WEP capacitor
|
||||||
*/
|
*/
|
||||||
_incWep() {
|
_incWep() {
|
||||||
let { sys, eng, wep, boost } = this.state;
|
let { sys, eng, wep } = this.state;
|
||||||
|
|
||||||
const required = Math.min(1, 4 - wep);
|
const required = Math.min(1, 4 - wep);
|
||||||
if (required > 0) {
|
if (required > 0) {
|
||||||
@@ -266,20 +254,10 @@ export default class Pips extends TranslatedComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.setState({ sys, eng, wep, pipsSvg: this._renderPips(sys, eng, wep) });
|
this.setState({ sys, eng, wep, pipsSvg: this._renderPips(sys, eng, wep) });
|
||||||
this.props.onChange(sys, eng, wep, boost);
|
this.props.onChange(sys, eng, wep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle the boost feature
|
|
||||||
*/
|
|
||||||
_toggleBoost() {
|
|
||||||
let { boost, sys, eng, wep } = this.state;
|
|
||||||
boost = !boost;
|
|
||||||
this.setState({ boost });
|
|
||||||
this.props.onChange(sys, eng, wep, boost);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up the rendering for pips
|
* Set up the rendering for pips
|
||||||
* @param {int} sys the SYS pips
|
* @param {int} sys the SYS pips
|
||||||
@@ -336,7 +314,7 @@ export default class Pips extends TranslatedComponent {
|
|||||||
render() {
|
render() {
|
||||||
const { formats, translate, units } = this.context.language;
|
const { formats, translate, units } = this.context.language;
|
||||||
const { ship } = this.props;
|
const { ship } = this.props;
|
||||||
const { boost, sys, eng, wep, sysCap, engCap, wepCap, sysRate, engRate, wepRate, pipsSvg } = this.state;
|
const { sys, eng, wep, sysCap, engCap, wepCap, sysRate, engRate, wepRate, pipsSvg } = this.state;
|
||||||
|
|
||||||
const onSysClicked = this.onClick.bind(this, 'SYS');
|
const onSysClicked = this.onClick.bind(this, 'SYS');
|
||||||
const onEngClicked = this.onClick.bind(this, 'ENG');
|
const onEngClicked = this.onClick.bind(this, 'ENG');
|
||||||
@@ -344,16 +322,9 @@ export default class Pips extends TranslatedComponent {
|
|||||||
const onRstClicked = this.onClick.bind(this, 'RST');
|
const onRstClicked = this.onClick.bind(this, 'RST');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id='pips'>
|
<span id='pips'>
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
{ ship.canBoost() ?
|
|
||||||
<tr>
|
|
||||||
<td> </td>
|
|
||||||
<td> </td>
|
|
||||||
<td><button className={boost ? 'selected' : null} onClick={this._toggleBoost}>{translate('boost')}</button></td>
|
|
||||||
<td> </td>
|
|
||||||
</tr> : null }
|
|
||||||
<tr>
|
<tr>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
@@ -374,7 +345,7 @@ export default class Pips extends TranslatedComponent {
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,12 +128,13 @@ export default class ShipPicker extends TranslatedComponent {
|
|||||||
const { formats, translate, units } = language;
|
const { formats, translate, units } = language;
|
||||||
const { menuOpen, ship, build } = this.state;
|
const { menuOpen, ship, build } = this.state;
|
||||||
|
|
||||||
const shipString = ship.name + ': ' + (build ? build : 'stock');
|
const shipString = ship.name + ': ' + (build ? build : translate('stock'));
|
||||||
return (
|
return (
|
||||||
<div className='shippicker' onClick={ (e) => e.stopPropagation() }>
|
<div className='shippicker' onClick={ (e) => e.stopPropagation() }>
|
||||||
<div className='menu'>
|
<div className='menu'>
|
||||||
<div className={cn('menu-header', { selected: menuOpen })} onClick={this._toggleMenu}>
|
<div className={cn('menu-header', { selected: menuOpen })} onClick={this._toggleMenu}>
|
||||||
<Rocket className='warning' /><span className='menu-item-label'>{shipString}</span>
|
<span><Rocket className='warning' /></span>
|
||||||
|
<span className='menu-item-label'>{shipString}</span>
|
||||||
</div>
|
</div>
|
||||||
{ menuOpen ?
|
{ menuOpen ?
|
||||||
<div className='menu-list' onClick={ (e) => e.stopPropagation() }>
|
<div className='menu-list' onClick={ (e) => e.stopPropagation() }>
|
||||||
|
|||||||
@@ -17,12 +17,13 @@ import UtilitySlotSection from '../components/UtilitySlotSection';
|
|||||||
import OffenceSummary from '../components/OffenceSummary';
|
import OffenceSummary from '../components/OffenceSummary';
|
||||||
import DefenceSummary from '../components/DefenceSummary';
|
import DefenceSummary from '../components/DefenceSummary';
|
||||||
import MovementSummary from '../components/MovementSummary';
|
import MovementSummary from '../components/MovementSummary';
|
||||||
import EngineProfile from '../components/EngineProfile';
|
import Pips from '../components/Pips';
|
||||||
import FSDProfile from '../components/FSDProfile';
|
import Boost from '../components/Boost';
|
||||||
import JumpRange from '../components/JumpRange';
|
import Fuel from '../components/Fuel';
|
||||||
import BattleCentre from '../components/BattleCentre';
|
import Cargo from '../components/Cargo';
|
||||||
import PowerManagement from '../components/PowerManagement';
|
import ShipPicker from '../components/ShipPicker';
|
||||||
import CostSection from '../components/CostSection';
|
import EngagementRange from '../components/EngagementRange';
|
||||||
|
import OutfittingSubpages from '../components/OutfittingSubpages';
|
||||||
import ModalExport from '../components/ModalExport';
|
import ModalExport from '../components/ModalExport';
|
||||||
import ModalPermalink from '../components/ModalPermalink';
|
import ModalPermalink from '../components/ModalPermalink';
|
||||||
|
|
||||||
@@ -51,6 +52,12 @@ export default class OutfittingPage extends Page {
|
|||||||
this.state = this._initState(context);
|
this.state = this._initState(context);
|
||||||
this._keyDown = this._keyDown.bind(this);
|
this._keyDown = this._keyDown.bind(this);
|
||||||
this._exportBuild = this._exportBuild.bind(this);
|
this._exportBuild = this._exportBuild.bind(this);
|
||||||
|
this._pipsUpdated = this._pipsUpdated.bind(this);
|
||||||
|
this._boostUpdated = this._boostUpdated.bind(this);
|
||||||
|
this._cargoUpdated = this._cargoUpdated.bind(this);
|
||||||
|
this._fuelUpdated = this._fuelUpdated.bind(this);
|
||||||
|
this._opponentUpdated = this._opponentUpdated.bind(this);
|
||||||
|
this._engagementRangeUpdated = this._engagementRangeUpdated.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -90,7 +97,15 @@ export default class OutfittingPage extends Page {
|
|||||||
shipId,
|
shipId,
|
||||||
ship,
|
ship,
|
||||||
code,
|
code,
|
||||||
savedCode
|
savedCode,
|
||||||
|
sys: 2,
|
||||||
|
eng: 2,
|
||||||
|
wep: 2,
|
||||||
|
fuel: ship.fuelCapacity,
|
||||||
|
cargo: 0,
|
||||||
|
boost: false,
|
||||||
|
engagementRange: 1500,
|
||||||
|
opponent: new Ship('anaconda', Ships['anaconda'].properties, Ships['anaconda'].slots).buildWith(Ships['anaconda'].defaults)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,6 +127,57 @@ export default class OutfittingPage extends Page {
|
|||||||
this.setState(stateChanges);
|
this.setState(stateChanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered when pips have been updated
|
||||||
|
* @param {number} sys SYS pips
|
||||||
|
* @param {number} eng ENG pips
|
||||||
|
* @param {number} wep WEP pips
|
||||||
|
*/
|
||||||
|
_pipsUpdated(sys, eng, wep) {
|
||||||
|
this.setState({ sys, eng, wep });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered when boost has been updated
|
||||||
|
* @param {boolean} boost true if boosting
|
||||||
|
*/
|
||||||
|
_boostUpdated(boost) {
|
||||||
|
this.setState({ boost });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered when fuel has been updated
|
||||||
|
* @param {number} fuel the amount of fuel, in T
|
||||||
|
*/
|
||||||
|
_fuelUpdated(fuel) {
|
||||||
|
this.setState({ fuel });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered when cargo has been updated
|
||||||
|
* @param {number} cargo the amount of cargo, in T
|
||||||
|
*/
|
||||||
|
_cargoUpdated(cargo) {
|
||||||
|
this.setState({ cargo });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered when engagement range has been updated
|
||||||
|
* @param {number} engagementRange the engagement range, in m
|
||||||
|
*/
|
||||||
|
_engagementRangeUpdated(engagementRange) {
|
||||||
|
this.setState({ engagementRange });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered when target ship has been updated
|
||||||
|
* @param {object} opponent the opponent's ship
|
||||||
|
* @param {string} opponentBuild the name of the opponent's build
|
||||||
|
*/
|
||||||
|
_opponentUpdated(opponent, opponentBuild) {
|
||||||
|
this.setState({ opponent, opponentBuild });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save the current build
|
* Save the current build
|
||||||
*/
|
*/
|
||||||
@@ -294,7 +360,7 @@ export default class OutfittingPage extends Page {
|
|||||||
let state = this.state,
|
let state = this.state,
|
||||||
{ language, termtip, tooltip, sizeRatio, onWindowResize } = this.context,
|
{ language, termtip, tooltip, sizeRatio, onWindowResize } = this.context,
|
||||||
{ translate, units, formats } = language,
|
{ translate, units, formats } = language,
|
||||||
{ ship, code, savedCode, buildName, newBuildName, halfChartWidth, thirdChartWidth } = state,
|
{ ship, code, savedCode, buildName, newBuildName, halfChartWidth, thirdChartWidth, sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = state,
|
||||||
hide = tooltip.bind(null, null),
|
hide = tooltip.bind(null, null),
|
||||||
menu = this.props.currentMenu,
|
menu = this.props.currentMenu,
|
||||||
shipUpdated = this._shipUpdated,
|
shipUpdated = this._shipUpdated,
|
||||||
@@ -307,6 +373,9 @@ export default class OutfittingPage extends Page {
|
|||||||
// Code can be blank for a default loadout. Prefix it with the ship name to ensure that changes in default ships is picked up
|
// Code can be blank for a default loadout. Prefix it with the ship name to ensure that changes in default ships is picked up
|
||||||
code = ship.name + (code || '');
|
code = ship.name + (code || '');
|
||||||
|
|
||||||
|
// Markers are used to propagate state changes without requiring a deep comparison of the ship, as that takes a long time
|
||||||
|
const boostMarker = `${ship.canBoost()}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id='outfit' className={'page'} style={{ fontSize: (sizeRatio * 0.9) + 'em' }}>
|
<div id='outfit' className={'page'} style={{ fontSize: (sizeRatio * 0.9) + 'em' }}>
|
||||||
<div id='overview'>
|
<div id='overview'>
|
||||||
@@ -340,40 +409,59 @@ export default class OutfittingPage extends Page {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Main tables */}
|
||||||
<ShipSummaryTable ship={ship} code={code} />
|
<ShipSummaryTable ship={ship} code={code} />
|
||||||
<StandardSlotSection ship={ship} code={code} onChange={shipUpdated} currentMenu={menu} />
|
<StandardSlotSection ship={ship} code={code} onChange={shipUpdated} currentMenu={menu} />
|
||||||
<InternalSlotSection ship={ship} code={iStr} onChange={shipUpdated} currentMenu={menu} />
|
<InternalSlotSection ship={ship} code={iStr} onChange={shipUpdated} currentMenu={menu} />
|
||||||
<HardpointsSlotSection ship={ship} code={hStr || ''} onChange={shipUpdated} currentMenu={menu} />
|
<HardpointsSlotSection ship={ship} code={hStr || ''} onChange={shipUpdated} currentMenu={menu} />
|
||||||
<UtilitySlotSection ship={ship} code={hStr || ''} onChange={shipUpdated} currentMenu={menu} />
|
<UtilitySlotSection ship={ship} code={hStr || ''} onChange={shipUpdated} currentMenu={menu} />
|
||||||
|
|
||||||
<div ref='chartThird' className='group third'>
|
{/* Control of ship and opponent */}
|
||||||
<OffenceSummary ship={ship} code={code}/>
|
<div className='group quarter'>
|
||||||
|
<div className='group half'>
|
||||||
|
<h2 style={{ verticalAlign: 'middle', textAlign: 'left' }}>{translate('ship control')}</h2>
|
||||||
|
</div>
|
||||||
|
<div className='group half'>
|
||||||
|
<Boost marker={boostMarker} ship={ship} onChange={this._boostUpdated} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='group third'>
|
<div className='group quarter'>
|
||||||
<DefenceSummary ship={ship} code={code}/>
|
<Pips ship={ship} onChange={this._pipsUpdated} />
|
||||||
</div>
|
</div>
|
||||||
<div className='group third'>
|
<div className='group quarter'>
|
||||||
<MovementSummary ship={ship} code={code}/>
|
<Fuel ship={ship} onChange={this._fuelUpdated}/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className='group quarter'>
|
||||||
<PowerManagement ship={ship} code={code} onChange={shipUpdated} />
|
{ ship.cargoCapacity > 0 ? <Cargo ship={ship} onChange={this._cargoUpdated}/> : null }
|
||||||
<CostSection ship={ship} buildName={buildName} code={code} />
|
|
||||||
|
|
||||||
<div className='group third'>
|
|
||||||
<EngineProfile ship={ship} code={code} chartWidth={thirdChartWidth} />
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className='group half'>
|
||||||
<div className='group third'>
|
<div className='group quarter'>
|
||||||
<FSDProfile ship={ship} code={code} chartWidth={thirdChartWidth} />
|
<h2 style={{ verticalAlign: 'middle', textAlign: 'left' }}>{translate('opponent')}</h2>
|
||||||
|
</div>
|
||||||
|
<div className='group threequarters'>
|
||||||
|
<ShipPicker onChange={this._opponentUpdated}/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className='group half'>
|
||||||
<div className='group third'>
|
<EngagementRange ship={ship} onChange={this._engagementRangeUpdated}/>
|
||||||
<JumpRange ship={ship} code={code} chartWidth={thirdChartWidth} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<BattleCentre ship={ship} />
|
|
||||||
</div>
|
</div>
|
||||||
|
{/* Tabbed subpages */}
|
||||||
|
<OutfittingSubpages
|
||||||
|
ship={ship}
|
||||||
|
code={code}
|
||||||
|
buildName={buildName}
|
||||||
|
onChange={shipUpdated}
|
||||||
|
sys={sys}
|
||||||
|
eng={eng}
|
||||||
|
wep={wep}
|
||||||
|
boost={boost}
|
||||||
|
cargo={cargo}
|
||||||
|
fuel={fuel}
|
||||||
|
engagementRange={engagementRange}
|
||||||
|
opponent={opponent}
|
||||||
|
opponentBuild={opponentBuild}
|
||||||
|
chartWidth={thirdChartWidth}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
@import 'sortable';
|
@import 'sortable';
|
||||||
@import 'loader';
|
@import 'loader';
|
||||||
@import 'pips';
|
@import 'pips';
|
||||||
|
@import 'boost';
|
||||||
@import 'movement';
|
@import 'movement';
|
||||||
@import 'shippicker';
|
@import 'shippicker';
|
||||||
@import 'defence';
|
@import 'defence';
|
||||||
|
|||||||
14
src/less/boost.less
Executable file
14
src/less/boost.less
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#boost {
|
||||||
|
button {
|
||||||
|
font-size: 1.2em;
|
||||||
|
background: @primary-bg;
|
||||||
|
color: @primary;
|
||||||
|
border: 1px solid @primary;
|
||||||
|
&.selected {
|
||||||
|
// Shown when button is selected
|
||||||
|
background: @primary;
|
||||||
|
color: @primary-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -220,6 +220,14 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.threequarters {
|
||||||
|
width: 75%;
|
||||||
|
|
||||||
|
.smallTablet({
|
||||||
|
width: 100% !important;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
&.full {
|
&.full {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
|||||||
@@ -12,19 +12,6 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
|
||||||
font-size: 1.2em;
|
|
||||||
background: @primary-bg;
|
|
||||||
color: @primary;
|
|
||||||
border: 1px solid @primary;
|
|
||||||
margin: 20px;
|
|
||||||
&.selected {
|
|
||||||
// Shown when button is selected
|
|
||||||
background: @primary;
|
|
||||||
color: @primary-bg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A full pip
|
// A full pip
|
||||||
.full {
|
.full {
|
||||||
stroke: @primary;
|
stroke: @primary;
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
.shippicker {
|
.shippicker {
|
||||||
background-color: @bgBlack;
|
background-color: @bgBlack;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0 0 0 1em;
|
|
||||||
height: 3em;
|
height: 3em;
|
||||||
line-height: 3em;
|
|
||||||
font-family: @fTitle;
|
font-family: @fTitle;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
|
||||||
.user-select-none();
|
.user-select-none();
|
||||||
|
|
||||||
@@ -34,7 +33,6 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: @warning;
|
color: @warning;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
// Less than 600px screen width: hide text
|
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
color: @warning-disabled;
|
color: @warning-disabled;
|
||||||
@@ -47,7 +45,6 @@
|
|||||||
|
|
||||||
.menu-item-label {
|
.menu-item-label {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
display: inline-block;
|
|
||||||
|
|
||||||
.smallTablet({
|
.smallTablet({
|
||||||
display: none;
|
display: none;
|
||||||
|
|||||||
Reference in New Issue
Block a user