Merge branch 'feature/fixes' into develop

This commit is contained in:
Cmdr McDonald
2017-03-31 16:49:10 +01:00
35 changed files with 252 additions and 1632 deletions

View File

@@ -1,18 +1,28 @@
#2.3.0
* Add 2.3 diminishing returns on shield value
* Make scan time visible on scanners where available
* Update power distributor able-to-boost calculation to take fractional MJ values in to account
* Revert to floating header due to issues on iOS
* Add effective total shield value to defence summary
* Fix issue where new module added to a slot did not reset its enabled status
* Show integrity value for relevant modules
* Reset old modification values when a new roll is applied
* Fix issue with miner role where refinery would not be present in ships with class 5 slots but no class 4
* Ensure that boost value is set correctly when modifications to power distributor enable/disable boost
* Ensure that hull reinforcement modifications take the inherent resistance in to account when calculating modification percentages
* Add tooltip for blueprints providing details of the features they alter
* Add tooltip for blueprints providing details of the features they alter, the components required for the blueprint and the engineer(s) who cam craft them
* Use opponent's saved pips if available
* Ignore rounds per shot for EPS and HPS calculations; it's already factored in to the numbers
* Ensure that clip size modification imports result in whole numbers
* Rework of separate offence/defence/movement sections to a unified interface
* Use cargo hatch information on import if available
* Additional information of power distributor pips, boost, cargo and fuel loads added to build
* Additional information of opponent and engagement range added to build
* Reworking of offence, defence and movement information in to separate tabs as part of the outfitting screen:
* Power and costs section provides the existing 'Power' and 'Costs' sections
* Profiles section provides a number of graphs that show how various components of the build (top speed, sustained DPS against opponent's shields and armour etc) are affected by mass, range, etc.
* Offence section provides details of your build's damage distribution and per-weapon effectiveness. It also gives summary information for how long it will take for your build to wear down your opponent's shields and armour
* Defence section provides details of your build's defences against your selected opponent. It provides details of the effectiveness of your resistances of both shields and armour, and effective strength of each as a result. It also provides key metrics around shield longevity and recovery times, as well as module protection
* Fix power band marker to show safe power limit at 40% rather than 50%
* Restyle blueprint list to improve consistency with similar menus
#2.2.19
* Power management panel now displays modules in descending order of power usage by default

View File

@@ -1,7 +1,6 @@
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';

View File

@@ -507,7 +507,7 @@ export default class CostSection extends TranslatedComponent {
scoop = true;
break;
case 'scb':
q = slotGroup[i].m.getCells();
q = slotGroup[i].m.getAmmo() + 1;
break;
case 'am':
q = slotGroup[i].m.getAmmo();

View File

@@ -1,589 +0,0 @@
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import ShipSelector from './ShipSelector';
import { nameComparator } from '../utils/SlotFunctions';
import { CollapseSection, ExpandSection, MountFixed, MountGimballed, MountTurret } from './SvgIcons';
import LineChart from '../components/LineChart';
import Slider from '../components/Slider';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import Module from '../shipyard/Module';
const DAMAGE_DEALT_COLORS = ['#FFFFFF', '#FF0000', '#00FF00', '#7777FF', '#FFFF00', '#FF00FF', '#00FFFF', '#777777'];
/**
* Generates an internationalization friendly weapon comparator that will
* sort by specified property (if provided) then by name/group, class, rating
* @param {function} translate Translation function
* @param {function} propComparator Optional property comparator
* @param {boolean} desc Use descending order
* @return {function} Comparator function for names
*/
export function weaponComparator(translate, propComparator, desc) {
return (a, b) => {
if (!desc) { // Flip A and B if ascending order
let t = a;
a = b;
b = t;
}
// If a property comparator is provided use it first
let diff = propComparator ? propComparator(a, b) : nameComparator(translate, a, b);
if (diff) {
return diff;
}
// Property matches so sort by name / group, then class, rating
if (a.name === b.name && a.grp === b.grp) {
if(a.class == b.class) {
return a.rating > b.rating ? 1 : -1;
}
return a.class - b.class;
}
return nameComparator(translate, a, b);
};
}
/**
* Damage against a selected ship
*/
export default class DamageDealt extends TranslatedComponent {
static propTypes = {
ship: React.PropTypes.object.isRequired,
code: React.PropTypes.string.isRequired
};
static DEFAULT_AGAINST = Ships['anaconda'];
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
this._sort = this._sort.bind(this);
this._onShipChange = this._onShipChange.bind(this);
this._onCollapseExpand = this._onCollapseExpand.bind(this);
const ship = this.props.ship;
const against = DamageDealt.DEFAULT_AGAINST;
const maxRange = this._calcMaxRange(ship);
const range = 1000 / maxRange;
const maxDps = this._calcMaxSDps(ship, against);
const weaponNames = this._weaponNames(ship, context);
this.state = {
predicate: 'n',
desc: true,
against,
expanded: false,
range,
maxRange,
maxDps,
weaponNames,
calcHullDpsFunc: this._calcDps.bind(this, context, ship, weaponNames, against, true),
calcShieldsDpsFunc: this._calcDps.bind(this, context, ship, weaponNames, against, false)
};
}
/**
* Set the initial weapons state
*/
componentWillMount() {
const data = this._calcWeaponsDps(this.props.ship, this.state.against, this.state.range * this.state.maxRange, true);
this.setState({ weapons: data.weapons, totals: data.totals });
}
/**
* Set the updated weapons state if our ship changes
* @param {Object} nextProps Incoming/Next properties
* @param {Object} nextContext Incoming/Next conext
* @return {boolean} Returns true if the component should be rerendered
*/
componentWillReceiveProps(nextProps, nextContext) {
if (nextProps.code != this.props.code) {
const data = this._calcWeaponsDps(nextProps.ship, this.state.against, this.state.range * this.state.maxRange, this.props.hull);
const weaponNames = this._weaponNames(nextProps.ship, nextContext);
const maxRange = this._calcMaxRange(nextProps.ship);
const maxDps = this._calcMaxSDps(nextProps.ship, this.state.against);
this.setState({ weapons: data.weapons,
totals: data.totals,
weaponNames,
maxRange,
maxDps,
calcHullDpsFunc: this._calcDps.bind(this, nextContext, nextProps.ship, weaponNames, this.state.against, true),
calcShieldsDpsFunc: this._calcDps.bind(this, nextContext, nextProps.ship, weaponNames, this.state.against, false) });
}
return true;
}
/**
* Calculate the maximum sustained single-weapon DPS for this ship against another ship
* @param {Object} ship The ship
* @param {Object} against The target
* @return {number} The maximum sustained single-weapon DPS
*/
_calcMaxSDps(ship, against) {
let maxSDps = 0;
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
const m = ship.hardpoints[i].m;
const thisSDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getDps();
if (thisSDps > maxSDps) {
maxSDps = thisSDps;
}
}
}
return maxSDps;
}
/**
* Calculate the per-weapon DPS for this ship against another ship at a given range
* @param {Object} context The context
* @param {Object} ship The ship
* @param {Object} weaponNames The names of the weapons for which to calculate DPS
* @param {Object} against The target
* @param {bool} hull true if to calculate against hull, false if to calculate against shields
* @param {Object} range The engagement range
* @return {array} The array of weapon DPS
*/
_calcDps(context, ship, weaponNames, against, hull, range) {
let results = {};
let weaponNum = 0;
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
const m = ship.hardpoints[i].m;
results[weaponNames[weaponNum++]] = this._calcWeaponDps(context, m, against, hull, range);
}
}
return results;
}
/**
* Calculate the maximum range of a ship's weapons
* @param {Object} ship The ship
* @returns {int} The maximum range, in metres
*/
_calcMaxRange(ship) {
let maxRange = 1000;
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
const thisRange = ship.hardpoints[i].m.getRange();
if (thisRange > maxRange) {
maxRange = thisRange;
}
}
}
return maxRange;
}
/**
* Obtain the weapon names for this ship
* @param {Object} ship The ship
* @param {Object} context The context
* @return {array} The weapon names
*/
_weaponNames(ship, context) {
const translate = context.language.translate;
let names = [];
let num = 1;
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
const m = ship.hardpoints[i].m;
let name = '' + num++ + ': ' + m.class + m.rating + (m.missile ? '/' + m.missile : '') + ' ' + translate(m.name || m.grp);
let engineering;
if (m.blueprint && m.blueprint.name) {
engineering = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
if (m.blueprint.special && m.blueprint.special.id) {
engineering += ', ' + translate(m.blueprint.special.name);
}
}
if (engineering) {
name = name + ' (' + engineering + ')';
}
names.push(name);
}
}
return names;
}
/**
* Calculate a specific weapon DPS for this ship against another ship at a given range
* @param {Object} context The context
* @param {Object} m The weapon
* @param {Object} against The target
* @param {bool} hull true if to calculate against hull, false if to calculate against shields
* @param {Object} range The engagement range
* @return {number} The weapon DPS
*/
_calcWeaponDps(context, m, against, hull, range) {
const translate = context.language.translate;
let dropoff = 1;
if (m.getFalloff()) {
// Calculate the dropoff % due to range
if (range > m.getRange()) {
// Weapon is out of range
dropoff = 0;
} else {
const falloff = m.getFalloff();
if (range > falloff) {
const dropoffRange = m.getRange() - falloff;
// Assuming straight-line falloff
dropoff = 1 - (range - falloff) / dropoffRange;
}
}
}
const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
let engineering;
if (m.blueprint && m.blueprint.name) {
engineering = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
if (m.blueprint.special && m.blueprint.special.id) {
engineering += ', ' + translate(m.blueprint.special.name);
}
}
const effectivenessShields = dropoff;
const effectiveDpsShields = m.getDps() * effectivenessShields;
const effectiveSDpsShields = (m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectivenessShields : effectiveDpsShields);
const effectivenessHull = (m.getPiercing() >= against.properties.hardness ? 1 : m.getPiercing() / against.properties.hardness) * dropoff;
const effectiveDpsHull = m.getDps() * effectivenessHull;
const effectiveSDpsHull = (m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectivenessHull : effectiveDpsHull);
return hull ? effectiveSDpsHull : effectiveSDpsShields;
}
/**
* Calculate the damage dealt by a ship
* @param {Object} ship The ship which will deal the damage
* @param {Object} against The ship against which damage will be dealt
* @param {Object} range The engagement range
* @return {object} Returns the per-weapon damage
*/
_calcWeaponsDps(ship, against, range) {
const translate = this.context.language.translate;
// Tidy up the range so that it's to 4 decimal places
range = Math.round(10000 * range) / 10000;
// Track totals
let totals = {};
totals.effectivenessShields = 0;
totals.effectiveDpsShields = 0;
totals.effectiveSDpsShields = 0;
totals.effectivenessHull = 0;
totals.effectiveDpsHull = 0;
totals.effectiveSDpsHull = 0;
let totalDps = 0;
let weapons = [];
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
const m = ship.hardpoints[i].m;
if (m.getDamage() && m.grp !== 'po') {
let dropoff = 1;
if (m.getFalloff()) {
// Calculate the dropoff % due to range
if (range > m.getRange()) {
// Weapon is out of range
dropoff = 0;
} else {
const falloff = m.getFalloff();
if (range > falloff) {
const dropoffRange = m.getRange() - falloff;
// Assuming straight-line falloff
dropoff = 1 - (range - falloff) / dropoffRange;
}
}
}
const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
let engineering;
if (m.blueprint && m.blueprint.name) {
engineering = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
if (m.blueprint.special && m.blueprint.special.id >= 0) {
engineering += ', ' + translate(m.blueprint.special.name);
}
}
// Alter effectiveness as per standard shields (all have the same resistances)
const sg = ModuleUtils.findModule('sg', '3v');
let effectivenessShields = 0;
if (m.getDamageDist().E) {
effectivenessShields += m.getDamageDist().E * (1 - sg.getExplosiveResistance());
}
if (m.getDamageDist().K) {
effectivenessShields += m.getDamageDist().K * (1 - sg.getKineticResistance());
}
if (m.getDamageDist().T) {
effectivenessShields += m.getDamageDist().T * (1 - sg.getThermalResistance());
}
if (m.getDamageDist().A) {
effectivenessShields += m.getDamageDist().A;
}
effectivenessShields *= dropoff;
const effectiveDpsShields = m.getDps() * effectivenessShields;
const effectiveSDpsShields = (m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectivenessShields : effectiveDpsShields);
// Alter effectiveness as per standard hull
const bulkheads = new Module({ template: against.bulkheads });
let effectivenessHull = 0;
if (m.getDamageDist().E) {
effectivenessHull += m.getDamageDist().E * (1 - bulkheads.getExplosiveResistance());
}
if (m.getDamageDist().K) {
effectivenessHull += m.getDamageDist().K * (1 - bulkheads.getKineticResistance());
}
if (m.getDamageDist().T) {
effectivenessHull += m.getDamageDist().T * (1 - bulkheads.getThermalResistance());
}
if (m.getDamageDist().A) {
effectivenessHull += m.getDamageDist().A;
}
effectivenessHull *= Math.min(m.getPiercing() / against.properties.hardness, 1) * dropoff;
const effectiveDpsHull = m.getDps() * effectivenessHull;
const effectiveSDpsHull = (m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectivenessHull : effectiveDpsHull);
totals.effectiveDpsShields += effectiveDpsShields;
totals.effectiveSDpsShields += effectiveSDpsShields;
totals.effectiveDpsHull += effectiveDpsHull;
totals.effectiveSDpsHull += effectiveSDpsHull;
totalDps += m.getDps();
weapons.push({ id: i,
mount: m.mount,
name: m.name || m.grp,
classRating,
engineering,
effectiveDpsShields,
effectiveSDpsShields,
effectivenessShields,
effectiveDpsHull,
effectiveSDpsHull,
effectivenessHull });
}
}
}
totals.effectivenessShields = totalDps == 0 ? 0 : totals.effectiveDpsShields / totalDps;
totals.effectivenessHull = totalDps == 0 ? 0 : totals.effectiveDpsHull / totalDps;
return { weapons, totals };
}
/**
* Triggered when the collapse or expand icons are clicked
*/
_onCollapseExpand() {
this.setState({ expanded: !this.state.expanded });
}
/**
* Triggered when the ship we compare against changes
* @param {string} s the new ship ID
*/
_onShipChange(s) {
const against = Ships[s];
const data = this._calcWeaponsDps(this.props.ship, against, this.state.range * this.state.maxRange);
this.setState({ against,
weapons: data.weapons,
totals: data.totals,
calcHullDpsFunc: this._calcDps.bind(this, this.context, this.props.ship, this.state.weaponNames, against, true),
calcShieldsDpsFunc: this._calcDps.bind(this, this.context, this.props.ship, this.state.weaponNames, against, false) });
}
/**
* Set the sort order and sort
* @param {string} predicate Sort predicate
*/
_sortOrder(predicate) {
let desc = this.state.desc;
if (predicate == this.state.predicate) {
desc = !desc;
} else {
desc = true;
}
this._sort(this.props.ship, predicate, desc);
this.setState({ predicate, desc });
}
/**
* Sorts the weapon list
* @param {Ship} ship Ship instance
* @param {string} predicate Sort predicate
* @param {Boolean} desc Sort order descending
*/
_sort(ship, predicate, desc) {
let comp = weaponComparator.bind(null, this.context.language.translate);
switch (predicate) {
case 'n': comp = comp(null, desc); break;
case 'edpss': comp = comp((a, b) => a.effectiveDpsShields - b.effectiveDpsShields, desc); break;
case 'esdpss': comp = comp((a, b) => a.effectiveSDpsShields - b.effectiveSDpsShields, desc); break;
case 'es': comp = comp((a, b) => a.effectivenessShields - b.effectivenessShields, desc); break;
case 'edpsh': comp = comp((a, b) => a.effectiveDpsHull - b.effectiveDpsHull, desc); break;
case 'esdpsh': comp = comp((a, b) => a.effectiveSDpsHull - b.effectiveSDpsHull, desc); break;
case 'eh': comp = comp((a, b) => a.effectivenessHull - b.effectivenessHull, desc); break;
}
this.state.weapons.sort(comp);
}
/**
* Render individual rows for hardpoints
* @param {Function} translate Translate function
* @param {Object} formats Localised formats map
* @return {array} The individual rows
*
*/
_renderRows(translate, formats) {
const { termtip, tooltip } = this.context;
let rows = [];
if (this.state.weapons) {
for (let i = 0; i < this.state.weapons.length; i++) {
const weapon = this.state.weapons[i];
rows.push(<tr key={weapon.id}>
<td className='ri'>
{weapon.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed className='icon'/></span> : null}
{weapon.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : null}
{weapon.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : null}
{weapon.classRating} {translate(weapon.name)}
{weapon.engineering ? ' (' + weapon.engineering + ')' : null }
</td>
<td className='ri'>{formats.f1(weapon.effectiveDpsShields)}</td>
<td className='ri'>{formats.f1(weapon.effectiveSDpsShields)}</td>
<td className='ri'>{formats.pct(weapon.effectivenessShields)}</td>
<td className='ri'>{formats.f1(weapon.effectiveDpsHull)}</td>
<td className='ri'>{formats.f1(weapon.effectiveSDpsHull)}</td>
<td className='ri'>{formats.pct(weapon.effectivenessHull)}</td>
</tr>);
}
}
return rows;
}
/**
* Update current range
* @param {number} range Range 0-1
*/
_rangeChange(range) {
const data = this._calcWeaponsDps(this.props.ship, this.state.against, this.state.range * this.state.maxRange);
this.setState({ range,
weapons: data.weapons,
totals: data.totals });
}
/**
* Render damage dealt
* @return {React.Component} contents
*/
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { against, expanded, maxRange, range, totals } = this.state;
const { ship } = this.props;
const sortOrder = this._sortOrder;
const onCollapseExpand = this._onCollapseExpand;
const code = ship.getHardpointsString() + '.' + ship.getModificationsString() + '.' + ship.getPowerEnabledString() + '.' + against.properties.name;
return (
<span>
<h1>{translate('damage dealt to')} {expanded ? <span onClick={onCollapseExpand}><CollapseSection className='summary'/></span> : <span onClick={onCollapseExpand}><ExpandSection className='summary'/></span>}</h1>
{expanded ? <span>
<ShipSelector initial={against} currentMenu={this.props.currentMenu} onChange={this._onShipChange} />
<table className='summary' style={{ width: '100%' }}>
<thead>
<tr className='main'>
<th rowSpan='2' className='sortable' onClick={sortOrder.bind(this, 'n')}>{translate('weapon')}</th>
<th colSpan='3'>{translate('standard shields')}</th>
<th colSpan='3'>{translate('standard armour')}</th>
</tr>
<tr>
<th className='lft sortable' onClick={sortOrder.bind(this, 'edpss')}>{translate('effective dps')}</th>
<th className='sortable' onClick={sortOrder.bind(this, 'esdpss')}>{translate('effective sdps')}</th>
<th className='sortable' onClick={sortOrder.bind(this, 'es')}>{translate('effectiveness')}</th>
<th className='lft sortable' onClick={sortOrder.bind(this, 'edpsh')}>{translate('effective dps')}</th>
<th className='sortable' onClick={sortOrder.bind(this, 'esdpsh')}>{translate('effective sdps')}</th>
<th className='sortable' onClick={sortOrder.bind(this, 'eh')}>{translate('effectiveness')}</th>
</tr>
</thead>
<tbody>
{this._renderRows(translate, formats)}
</tbody>
<tfoot>
<tr className='main'>
<td className='ri'><i>{translate('total')}</i></td>
<td className='ri'><i>{formats.f1(totals.effectiveDpsShields)}</i></td>
<td className='ri'><i>{formats.f1(totals.effectiveSDpsShields)}</i></td>
<td className='ri'><i>{formats.pct(totals.effectivenessShields)}</i></td>
<td className='ri'><i>{formats.f1(totals.effectiveDpsHull)}</i></td>
<td className='ri'><i>{formats.f1(totals.effectiveSDpsHull)}</i></td>
<td className='ri'><i>{formats.pct(totals.effectivenessHull)}</i></td>
</tr>
</tfoot>
</table>
<table style={{ width: '80%', lineHeight: '1em', backgroundColor: 'transparent', margin: 'auto' }}>
<tbody >
<tr>
<td style={{ verticalAlign: 'top', padding: 0, width: '2.5em' }} onMouseEnter={termtip.bind(null, 'PHRASE_ENGAGEMENT_RANGE')} onMouseLeave={tooltip.bind(null, null)}>{translate('engagement range')}</td>
<td>
<Slider
axis={true}
onChange={this._rangeChange.bind(this)}
axisUnit={translate('m')}
percent={range}
max={maxRange}
scale={sizeRatio}
onResize={onWindowResize}
/>
</td>
<td className='primary' style={{ width: '10em', verticalAlign: 'top', fontSize: '0.9em', textAlign: 'left' }}>
{formats.f2(range * maxRange / 1000)}{units.km}
</td>
</tr>
</tbody>
</table>
<div className='group half'>
<h1>{translate('sustained dps against standard shields')}</h1>
<LineChart
xMax={maxRange}
yMax={this.state.maxDps}
xLabel={translate('range')}
xUnit={translate('m')}
yLabel={translate('sdps')}
series={this.state.weaponNames}
colors={DAMAGE_DEALT_COLORS}
func={this.state.calcShieldsDpsFunc}
points={200}
code={code}
/>
</div>
<div className='group half'>
<h1>{translate('sustained dps against standard armour')}</h1>
<LineChart
xMax={maxRange}
yMax={this.state.maxDps}
xLabel={translate('range')}
xUnit={translate('m')}
yLabel={translate('sdps')}
series={this.state.weaponNames}
colors={DAMAGE_DEALT_COLORS}
func={this.state.calcHullDpsFunc}
points={200}
code={code}
/>
</div>
</span> : null }
</span>
);
}
}

View File

@@ -1,327 +0,0 @@
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import { Modules } from 'coriolis-data/dist';
import { nameComparator } from '../utils/SlotFunctions';
import { CollapseSection, ExpandSection, MountFixed, MountGimballed, MountTurret } from './SvgIcons';
import Module from '../shipyard/Module';
import Slider from '../components/Slider';
/**
* Generates an internationalization friendly weapon comparator that will
* sort by specified property (if provided) then by name/group, class, rating
* @param {function} translate Translation function
* @param {function} propComparator Optional property comparator
* @param {boolean} desc Use descending order
* @return {function} Comparator function for names
*/
export function weaponComparator(translate, propComparator, desc) {
return (a, b) => {
if (!desc) { // Flip A and B if ascending order
let t = a;
a = b;
b = t;
}
// If a property comparator is provided use it first
let diff = propComparator ? propComparator(a, b) : nameComparator(translate, a, b);
if (diff) {
return diff;
}
// Property matches so sort by name / group, then class, rating
if (a.name === b.name && a.grp === b.grp) {
if(a.class == b.class) {
return a.rating > b.rating ? 1 : -1;
}
return a.class - b.class;
}
return nameComparator(translate, a, b);
};
}
/**
* Damage received by a selected ship
*/
export default class DamageReceived extends TranslatedComponent {
static propTypes = {
ship: React.PropTypes.object.isRequired,
code: React.PropTypes.string.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this._sort = this._sort.bind(this);
this._onCollapseExpand = this._onCollapseExpand.bind(this);
this.state = {
predicate: 'n',
desc: true,
expanded: false,
range: 0.1667,
maxRange: 6000
};
}
/**
* Set the initial weapons state
*/
componentWillMount() {
this.setState({ weapons: this._calcWeapons(this.props.ship, this.state.range * this.state.maxRange) });
}
/**
* Set the updated weapons state
* @param {Object} nextProps Incoming/Next properties
* @param {Object} nextContext Incoming/Next conext
* @return {boolean} Returns true if the component should be rerendered
*/
componentWillReceiveProps(nextProps, nextContext) {
if (nextProps.code != this.props.code) {
this.setState({ weapons: this._calcWeapons(nextProps.ship, this.state.range * this.state.maxRange) });
}
return true;
}
/**
* Calculate the damage received by a ship
* @param {Object} ship The ship which will receive the damage
* @param {Object} range The engagement range
* @return {boolean} Returns the per-weapon damage
*/
_calcWeapons(ship, range) {
// Tidy up the range so that it's to 4 decimal places
range = Math.round(10000 * range) / 10000;
let weapons = [];
for (let grp in Modules.hardpoints) {
if (Modules.hardpoints[grp][0].damage && Modules.hardpoints[grp][0].damagedist) {
for (let mId in Modules.hardpoints[grp]) {
const m = new Module(Modules.hardpoints[grp][mId]);
let dropoff = 1;
if (m.getFalloff()) {
// Calculate the dropoff % due to range
if (range > m.getRange()) {
// Weapon is out of range
dropoff = 0;
} else {
const falloff = m.getFalloff();
if (range > falloff) {
const dropoffRange = m.getRange() - falloff;
// Assuming straight-line falloff
dropoff = 1 - (range - falloff) / dropoffRange;
}
}
}
const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
// Base DPS
const baseDps = m.getDps() * dropoff;
const baseSDps = m.getClip() ? ((m.getClip() * baseDps / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) * dropoff : baseDps;
// Effective DPS taking in to account shield resistance
let effectivenessShields = 0;
if (m.getDamageDist().E) {
effectivenessShields += m.getDamageDist().E * (1 - ship.shieldExplRes);
}
if (m.getDamageDist().K) {
effectivenessShields += m.getDamageDist().K * (1 - ship.shieldKinRes);
}
if (m.getDamageDist().T) {
effectivenessShields += m.getDamageDist().T * (1 - ship.shieldThermRes);
}
if (m.getDamageDist().A) {
effectivenessShields += m.getDamageDist().A;
}
effectivenessShields *= dropoff;
const effectiveDpsShields = baseDps * effectivenessShields;
const effectiveSDpsShields = baseSDps * effectivenessShields;
// Effective DPS taking in to account hull hardness and resistance
let effectivenessHull = 0;
if (m.getDamageDist().E) {
effectivenessHull += m.getDamageDist().E * (1 - ship.hullExplRes);
}
if (m.getDamageDist().K) {
effectivenessHull += m.getDamageDist().K * (1 - ship.hullKinRes);
}
if (m.getDamageDist().T) {
effectivenessHull += m.getDamageDist().T * (1 - ship.hullThermRes);
}
if (m.getDamageDist().A) {
effectivenessHull += m.getDamageDist().A;
}
effectivenessHull *= Math.min(m.getPiercing() / ship.hardness, 1) * dropoff;
const effectiveDpsHull = baseDps * effectivenessHull;
const effectiveSDpsHull = baseSDps * effectivenessHull;
weapons.push({ id: m.id,
classRating,
name: m.name || m.grp,
mount: m.mount,
effectiveDpsShields,
effectiveSDpsShields,
effectivenessShields,
effectiveDpsHull,
effectiveSDpsHull,
effectivenessHull });
}
}
}
return weapons;
}
/**
* Triggered when the collapse or expand icons are clicked
*/
_onCollapseExpand() {
this.setState({ expanded: !this.state.expanded });
}
/**
* Set the sort order and sort
* @param {string} predicate Sort predicate
*/
_sortOrder(predicate) {
let desc = this.state.desc;
if (predicate == this.state.predicate) {
desc = !desc;
} else {
desc = true;
}
this._sort(this.props.ship, predicate, desc);
this.setState({ predicate, desc });
}
/**
* Sorts the weapon list
* @param {Ship} ship Ship instance
* @param {string} predicate Sort predicate
* @param {Boolean} desc Sort order descending
*/
_sort(ship, predicate, desc) {
let comp = weaponComparator.bind(null, this.context.language.translate);
switch (predicate) {
case 'n': comp = comp(null, desc); break;
case 'edpss': comp = comp((a, b) => a.effectiveDpsShields - b.effectiveDpsShields, desc); break;
case 'esdpss': comp = comp((a, b) => a.effectiveSDpsShields - b.effectiveSDpsShields, desc); break;
case 'es': comp = comp((a, b) => a.effectivenessShields - b.effectivenessShields, desc); break;
case 'edpsh': comp = comp((a, b) => a.effectiveDpsHull - b.effectiveDpsHull, desc); break;
case 'esdpsh': comp = comp((a, b) => a.effectiveSDpsHull - b.effectiveSDpsHull, desc); break;
case 'eh': comp = comp((a, b) => a.effectivenessHull - b.effectivenessHull, desc); break;
}
this.state.weapons.sort(comp);
}
/**
* Render individual rows for weapons
* @param {Function} translate Translate function
* @param {Object} formats Localised formats map
* @return {array} The individual rows
*
*/
_renderRows(translate, formats) {
const { termtip, tooltip } = this.context;
let rows = [];
for (let i = 0; i < this.state.weapons.length; i++) {
const weapon = this.state.weapons[i];
rows.push(<tr key={weapon.id}>
<td className='ri'>
{weapon.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed className='icon'/></span> : null}
{weapon.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : null}
{weapon.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : null}
{weapon.classRating} {translate(weapon.name)}
</td>
<td>{formats.round1(weapon.effectiveDpsShields)}</td>
<td>{formats.round1(weapon.effectiveSDpsShields)}</td>
<td>{formats.pct(weapon.effectivenessShields)}</td>
<td>{formats.round1(weapon.effectiveDpsHull)}</td>
<td>{formats.round1(weapon.effectiveSDpsHull)}</td>
<td>{formats.pct(weapon.effectivenessHull)}</td>
</tr>);
}
return rows;
}
/**
* Update current range
* @param {number} range Range 0-1
*/
_rangeChange(range) {
this.setState({ range, weapons: this._calcWeapons(this.props.ship, this.state.range * this.state.maxRange) });
}
/**
* Render damage received
* @return {React.Component} contents
*/
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { expanded, maxRange, range } = this.state;
const sortOrder = this._sortOrder;
const onCollapseExpand = this._onCollapseExpand;
return (
<span>
<h1>{translate('damage received from')} {expanded ? <span onClick={onCollapseExpand}><CollapseSection className='summary'/></span> : <span onClick={onCollapseExpand}><ExpandSection className='summary'/></span>}</h1>
{expanded ? <span>
<table className='summary' style={{ width: '100%' }}>
<thead>
<tr className='main'>
<th rowSpan={2} className='sortable' onClick={sortOrder.bind(this, 'n')} >{translate('weapon')}</th>
<th colSpan={3} >{translate('against shields')}</th>
<th colSpan={3} >{translate('against hull')}</th>
</tr>
<tr>
<th className='sortable lft' onClick={sortOrder.bind(this, 'edpss')} onMouseOver={termtip.bind(null, 'dps')} onMouseOut={tooltip.bind(null, null)}>{translate('DPS')}</th>
<th className='sortable' onClick={sortOrder.bind(this, 'esdpss')} onMouseOver={termtip.bind(null, 'sdps')} onMouseOut={tooltip.bind(null, null)}>{translate('SDPS')}</th>
<th className='sortable' onClick={sortOrder.bind(this, 'es')} >{translate('effectiveness')}</th>
<th className='sortable lft' onClick={sortOrder.bind(this, 'edpsh')} onMouseOver={termtip.bind(null, 'dps')} onMouseOut={tooltip.bind(null, null)}>{translate('DPS')}</th>
<th className='sortable' onClick={sortOrder.bind(this, 'esdpsh')} onMouseOver={termtip.bind(null, 'sdps')} onMouseOut={tooltip.bind(null, null)}>{translate('SDPS')}</th>
<th className='sortable' onClick={sortOrder.bind(this, 'eh')} >{translate('effectiveness')}</th>
</tr>
</thead>
<tbody>
{this._renderRows(translate, formats)}
</tbody>
</table>
<table style={{ width: '80%', lineHeight: '1em', backgroundColor: 'transparent', margin: 'auto' }}>
<tbody >
<tr>
<td style={{ verticalAlign: 'top', padding: 0, width: '2.5em' }} onMouseEnter={termtip.bind(null, 'PHRASE_ENGAGEMENT_RANGE')} onMouseLeave={tooltip.bind(null, null)}>{translate('engagement range')}</td>
<td>
<Slider
axis={true}
onChange={this._rangeChange.bind(this)}
axisUnit={translate('m')}
percent={range}
max={maxRange}
scale={sizeRatio}
onResize={onWindowResize}
/>
</td>
<td className='primary' style={{ width: '10em', verticalAlign: 'top', fontSize: '0.9em', textAlign: 'left' }}>
{formats.f2(range * maxRange / 1000)}{units.km}
</td>
</tr>
</tbody>
</table></span> : null }
</span>
);
}
}

View File

@@ -1,122 +0,0 @@
import React from 'react';
import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent';
import { DamageKinetic, DamageThermal, DamageExplosive } from './SvgIcons';
/**
* Defence summary
*/
export default class DefenceSummary extends TranslatedComponent {
static propTypes = {
ship: React.PropTypes.object.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
}
/**
* Render defence summary
* @return {React.Component} contents
*/
render() {
let ship = this.props.ship;
let { language, tooltip, termtip } = this.context;
let { formats, translate, units } = language;
let hide = tooltip.bind(null, null);
const shieldGenerator = ship.findShieldGenerator();
// Damage values are 1 - resistance values
return (
<span>
<h1>{translate('defence summary')}</h1>
<table className='summary' style={{ marginLeft: 'auto', marginRight: 'auto', lineHeight: '1em', backgroundColor: 'transparent' }}>
<tbody>
{ship.shield ?
<tr>
<td colSpan='4' className='summary'><h2>{translate('shields')}: {formats.int(ship.shield)} {units.MJ}</h2></td>
</tr> : null }
{ship.shield ?
<tr>
<td className='ri' onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECOVER', { cap: 0 })} onMouseLeave={hide}>{translate('recovery')}</td>
<td className='le'>{formats.time(ship.calcShieldRecovery())}</td>
<td className='ri' onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECHARGE', { cap: 0 })} onMouseLeave={hide}>{translate('recharge')}</td>
<td className='le'>{formats.time(ship.calcShieldRecharge())}</td>
</tr> : null }
{ship.shield ?
<tr>
<td className='le'>{translate('damage from')}</td>
<td className='ri'>
<span onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /></span>&nbsp;
<span onMouseOver={termtip.bind(null, translate('base') + ' ' + formats.pct1(1 - shieldGenerator.explres))} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(1 - ship.shieldExplRes)}</span>
</td>
<td className='ri'>
<span onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /></span>&nbsp;
<span onMouseOver={termtip.bind(null, translate('base') + ' ' + formats.pct1(1 - shieldGenerator.kinres))} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(1 - ship.shieldKinRes)}</span>
</td>
<td className='ri'>
<span onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /></span>&nbsp;
<span onMouseOver={termtip.bind(null, translate('base') + ' ' + formats.pct1(1 - shieldGenerator.thermres))} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(1 - ship.shieldThermRes)}</span>
</td>
</tr> : null }
{ship.shield ?
<tr>
<td className='le'><span onMouseOver={termtip.bind(null,'PHRASE_TOTAL_EFFECTIVE_SHIELD')} onMouseOut={tooltip.bind(null, null)}>{translate('total effective shield')}</span></td>
<td className='ri'>
<span onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /></span>&nbsp;
{formats.int((ship.shield + ship.shieldCells) / (1 - ship.shieldExplRes))}{units.MJ}
</td>
<td className='ri'>
<span onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /></span>&nbsp;
{formats.int((ship.shield + ship.shieldCells) / (1 - ship.shieldKinRes))}{units.MJ}
</td>
<td className='ri'>
<span onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /></span>&nbsp;
{formats.int((ship.shield + ship.shieldCells) / (1 - ship.shieldThermRes))}{units.MJ}
</td>
</tr> : null }
{ ship.shield && ship.shieldCells ?
<tr>
<td colSpan='4'><h2>{translate('shield cells')}: {formats.int(ship.shieldCells)} {units.MJ}</h2></td>
</tr> : null }
<tr>
<td colSpan='4'><h2>{translate('armour')}: {formats.int(ship.armour)}</h2></td>
</tr>
<tr>
<td className='le'>{translate('damage from')}</td>
<td className='ri'>
<span onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /></span>&nbsp;
<span onMouseOver={termtip.bind(null, translate('base') + ' ' + formats.pct1(1 - ship.bulkheads.m.explres))} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(1 - ship.hullExplRes)}</span></td>
<td className='ri'>
<span onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /></span>&nbsp;
<span onMouseOver={termtip.bind(null, translate('base') + ' ' + formats.pct1(1 - ship.bulkheads.m.kinres))} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(1 - ship.hullKinRes)}</span>
</td>
<td className='ri'>
<span onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /></span>&nbsp;
<span onMouseOver={termtip.bind(null, translate('base') + ' ' + formats.pct1(1 - ship.bulkheads.m.thermres))} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(1 - ship.hullThermRes)}</span>
</td>
</tr>
{ship.modulearmour > 0 ?
<tr>
<td colSpan='4'><h2>{translate('module armour')}: {formats.int(ship.modulearmour)}</h2></td>
</tr> : null }
{ship.moduleprotection > 0 ?
<tr>
<td colSpan='2' className='cn'>{translate('internal protection')} {formats.pct1(ship.moduleprotection)}</td>
<td colSpan='2' className='cn'>{translate('external protection')} {formats.pct1(ship.moduleprotection / 2)}</td>
</tr> : null }
</tbody>
</table>
</span>
);
}
}

View File

@@ -1,7 +1,6 @@
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import ShipSelector from './ShipSelector';
import { nameComparator } from '../utils/SlotFunctions';
import LineChart from '../components/LineChart';
import Slider from '../components/Slider';

View File

@@ -1,7 +1,6 @@
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import ShipSelector from './ShipSelector';
import { nameComparator } from '../utils/SlotFunctions';
import LineChart from '../components/LineChart';
import Slider from '../components/Slider';

View File

@@ -55,7 +55,7 @@ export default class HardpointSlot extends Slot {
modTT = (
<div>
<div>{modTT}</div>
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade].features, m)}
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
</div>
);
}

View File

@@ -8,7 +8,7 @@ import { stopCtxPropagation } from '../utils/UtilityFunctions';
/**
* Hardpoint slot section
*/
export default class HardpointsSlotSection extends SlotSection {
export default class HardpointSlotSection extends SlotSection {
/**
* Constructor

View File

@@ -34,7 +34,7 @@ export default class InternalSlot extends Slot {
modTT = (
<div>
<div>{modTT}</div>
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade].features, m)}
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
</div>
);
}
@@ -52,14 +52,16 @@ export default class InternalSlot extends Slot {
{ m.bays ? <div className={'l'}>{translate('bays')}: {m.bays}</div> : null }
{ m.rebuildsperbay ? <div className={'l'}>{translate('rebuildsperbay')}: {m.rebuildsperbay}</div> : null }
{ m.rate ? <div className={'l'}>{translate('rate')}: {m.rate}{u.kgs}&nbsp;&nbsp;&nbsp;{translate('refuel time')}: {formats.time(this.props.fuel * 1000 / m.rate)}</div> : null }
{ m.getAmmo() ? <div className={'l'}>{translate('ammunition')}: {formats.gen(m.getAmmo())}</div> : null }
{ m.cells ? <div className={'l'}>{translate('cells')}: {m.cells}</div> : null }
{ m.getShieldReinforcement() ? <div className={'l'}>{translate('shieldreinforcement')}: {formats.int(m.getShieldReinforcement())} <u>MJ</u>&nbsp;&nbsp;&nbsp;{translate('total')}: {formats.int(m.cells * m.getShieldReinforcement())}{u.MJ}</div> : null }
{ m.getAmmo() && m.grp !== 'scb' ? <div className={'l'}>{translate('ammunition')}: {formats.gen(m.getAmmo())}</div> : null }
{ m.getSpinup() ? <div className={'l'}>{translate('spinup')}: {formats.f1(m.getSpinup())}{u.s}</div> : null }
{ m.getDuration() ? <div className={'l'}>{translate('duration')}: {formats.f1(m.getDuration())}{u.s}</div> : null }
{ m.grp === 'scb' ? <div className={'l'}>{translate('cells')}: {formats.int(m.getAmmo() + 1)}</div> : null }
{ m.getShieldReinforcement() ? <div className={'l'}>{translate('shieldreinforcement')}: {formats.f1(m.getDuration() * m.getShieldReinforcement())}{u.MJ}</div> : null }
{ m.getShieldReinforcement() ? <div className={'l'}>{translate('total')}: {formats.int((m.getAmmo() + 1) * (m.getDuration() * m.getShieldReinforcement()))}{u.MJ}</div> : null }
{ m.repair ? <div className={'l'}>{translate('repair')}: {m.repair}</div> : null }
{ m.getFacingLimit() ? <div className={'l'}>{translate('facinglimit')} {formats.f1(m.getFacingLimit())}°</div> : null }
{ m.getRange() ? <div className={'l'}>{translate('range')} {formats.f2(m.getRange())}{u.km}</div> : null }
{ m.getRangeT() ? <div className={'l'}>{translate('ranget')} {formats.f1(m.getRangeT())}{u.s}</div> : null }
{ m.getSpinup() ? <div className={'l'}>{translate('spinup')}: {formats.f1(m.getSpinup())}{u.s}</div> : null }
{ m.getTime() ? <div className={'l'}>{translate('time')}: {formats.time(m.getTime())}</div> : null }
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
{ m.rangeLS ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null }

View File

@@ -1,7 +1,6 @@
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import ShipSelector from './ShipSelector';
import { nameComparator } from '../utils/SlotFunctions';
import LineChart from '../components/LineChart';
import Slider from '../components/Slider';

View File

@@ -37,7 +37,7 @@ export default class Modification extends TranslatedComponent {
const name = this.props.name;
let scaledValue = Math.round(Number(value) * 100);
// Limit to +1000% / -100%
// Limit to +1000% / -99.99%
if (scaledValue > 100000) {
scaledValue = 100000;
value = 1000;
@@ -52,6 +52,9 @@ export default class Modification extends TranslatedComponent {
ship.setModification(m, name, scaledValue, true);
this.setState({ value });
}
_updateFinished() {
this.props.onChange();
}
@@ -79,7 +82,7 @@ export default class Modification extends TranslatedComponent {
}
return (
<div className={'cb'} key={name}>
<div onBlur={this._updateFinished.bind(this)} className={'cb'} key={name}>
<div className={'cb'}>{translate(name, m.grp)}{symbol}</div>
<NumberEditor className={'cb'} style={{ width: '90%', textAlign: 'center' }} step={0.01} stepModifier={1} decimals={2} value={this.state.value} onValueChange={this._updateValue.bind(this)} />
</div>

View File

@@ -15,6 +15,7 @@ export default class ModificationsMenu extends TranslatedComponent {
static propTypes = {
ship: React.PropTypes.object.isRequired,
m: React.PropTypes.object.isRequired,
marker: React.PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired
};
@@ -25,7 +26,6 @@ export default class ModificationsMenu extends TranslatedComponent {
*/
constructor(props, context) {
super(props);
this.state = this._initState(props, context);
this._toggleBlueprintsMenu = this._toggleBlueprintsMenu.bind(this);
this._toggleSpecialsMenu = this._toggleSpecialsMenu.bind(this);
@@ -34,33 +34,59 @@ export default class ModificationsMenu extends TranslatedComponent {
this._rollBest = this._rollBest.bind(this);
this._rollExtreme = this._rollExtreme.bind(this);
this._reset = this._reset.bind(this);
this.state = {
blueprintMenuOpened: false,
specialMenuOpened: false
};
}
/**
* Initialise state
* @param {Object} props React Component properties
* @param {Object} context React Component context
* Render the blueprints
* @param {Object} props React component properties
* @param {Object} context React component context
* @return {Object} list: Array of React Components
*/
_initState(props, context) {
let { m } = props;
_renderBlueprints(props, context) {
const { m } = props;
const { language, tooltip, termtip } = context;
const translate = language.translate;
// Set up the blueprints
let blueprints = [];
const blueprints = [];
for (const blueprintName in Modifications.modules[m.grp].blueprints) {
for (const grade of Modifications.modules[m.grp].blueprints[blueprintName]) {
const blueprint = getBlueprint(blueprintName, m);
let blueprintGrades = [];
for (let grade in Modifications.modules[m.grp].blueprints[blueprintName].grades) {
// Grade is a string in the JSON so make it a number
grade = Number(grade);
const classes = cn('c', {
active: m.blueprint && blueprint.id === m.blueprint.id && grade === m.blueprint.grade
});
const close = this._blueprintSelected.bind(this, blueprintName, grade);
const key = blueprintName + ':' + grade;
const blueprint = getBlueprint(blueprintName, m);
const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade].features);
blueprints.push(<div style={{ cursor: 'pointer' }} key={ key } onMouseOver={termtip.bind(null, tooltipContent)} onMouseOut={tooltip.bind(null, null)} onClick={ close }>{translate(blueprint.name + ' grade ' + grade)}</div>);
const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers, m.grp);
blueprintGrades.unshift(<li key={key} className={classes} style={{ width: '2em' }} onMouseOver={termtip.bind(null, tooltipContent)} onMouseOut={tooltip.bind(null, null)} onClick={close}>{grade}</li>);
}
if (blueprintGrades) {
blueprints.push(<div key={blueprint.name} className={'select-group cap'}>{translate(blueprint.name)}</div>);
blueprints.push(<ul key={blueprintName}>{blueprintGrades}</ul>);
}
}
return blueprints;
}
// Set up the special effects
let specials = [];
/**
* Render the specials
* @param {Object} props React component properties
* @param {Object} context React component context
* @return {Object} list: Array of React Components
*/
_renderSpecials(props, context) {
const { m } = props;
const { language, tooltip, termtip } = context;
const translate = language.translate;
const specials = [];
if (Modifications.modules[m.grp].specials && Modifications.modules[m.grp].specials.length > 0) {
const close = this._specialSelected.bind(this, null);
specials.push(<div style={{ cursor: 'pointer' }} key={ 'none' } onClick={ close }>{translate('PHRASE_NO_SPECIAL')}</div>);
@@ -69,24 +95,17 @@ export default class ModificationsMenu extends TranslatedComponent {
specials.push(<div style={{ cursor: 'pointer' }} key={ specialName } onClick={ close }>{translate(Modifications.specials[specialName].name)}</div>);
}
}
// Set up the modifications
const modifications = this._setModifications(props);
const blueprintMenuOpened = false;
const specialMenuOpened = false;
return { blueprintMenuOpened, blueprints, modifications, specialMenuOpened, specials };
return specials;
}
/**
* Initialise the modifications
* Render the modifications
* @param {Object} props React Component properties
* @return {Object} list: Array of React Components
*/
_setModifications(props) {
_renderModifications(props) {
const { m, onChange, ship } = props;
let modifications = [];
const modifications = [];
for (const modName of Modifications.modules[m.grp].modifications) {
if (!Modifications.modifications[modName].hidden) {
const key = modName + (m.getModValue(modName) / 100 || 0);
@@ -110,13 +129,13 @@ export default class ModificationsMenu extends TranslatedComponent {
* @param {int} grade The grade of the selected blueprint
*/
_blueprintSelected(fdname, grade) {
this.context.tooltip(null);
const { m } = this.props;
const blueprint = getBlueprint(fdname, m);
blueprint.grade = grade;
m.blueprint = blueprint;
const blueprintMenuOpened = false;
this.setState({ blueprintMenuOpened });
this.setState({ blueprintMenuOpened: false });
this.props.onChange();
}
@@ -133,6 +152,7 @@ export default class ModificationsMenu extends TranslatedComponent {
* @param {int} special The name of the selected special
*/
_specialSelected(special) {
this.context.tooltip(null);
const { m, ship } = this.props;
if (m.blueprint) {
@@ -146,8 +166,7 @@ export default class ModificationsMenu extends TranslatedComponent {
ship.recalculateEps();
}
const specialMenuOpened = false;
this.setState({ specialMenuOpened, modifications: this._setModifications(this.props) });
this.setState({ specialMenuOpened: false });
this.props.onChange();
}
@@ -179,7 +198,7 @@ export default class ModificationsMenu extends TranslatedComponent {
let value = features[featureName][0];
this._setRollResult(ship, m, featureName, value);
}
this.setState({ modifications: this._setModifications(this.props) });
this.props.onChange();
}
@@ -194,7 +213,7 @@ export default class ModificationsMenu extends TranslatedComponent {
let value = features[featureName][0] + (Math.random() * (features[featureName][1] - features[featureName][0]));
this._setRollResult(ship, m, featureName, value);
}
this.setState({ modifications: this._setModifications(this.props) });
this.props.onChange();
}
@@ -208,7 +227,7 @@ export default class ModificationsMenu extends TranslatedComponent {
let value = features[featureName][1];
this._setRollResult(ship, m, featureName, value);
}
this.setState({ modifications: this._setModifications(this.props) });
this.props.onChange();
}
@@ -239,7 +258,7 @@ export default class ModificationsMenu extends TranslatedComponent {
this._setRollResult(ship, m, featureName, value);
}
this.setState({ modifications: this._setModifications(this.props) });
this.props.onChange();
}
@@ -251,7 +270,6 @@ export default class ModificationsMenu extends TranslatedComponent {
ship.clearModifications(m);
ship.clearBlueprint(m);
this.setState({ modifications: this._setModifications(this.props) });
this.props.onChange();
}
@@ -279,7 +297,7 @@ export default class ModificationsMenu extends TranslatedComponent {
if (m.blueprint && !isEmpty(m.blueprint)) {
blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
haveBlueprint = true;
blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade].features);
blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade].engineers, m.grp);
}
let specialLabel;
@@ -289,8 +307,10 @@ export default class ModificationsMenu extends TranslatedComponent {
specialLabel = translate('PHRASE_SELECT_SPECIAL');
}
const specials = this._renderSpecials(this.props, this.context);
const showBlueprintsMenu = blueprintMenuOpened;
const showSpecial = haveBlueprint && this.state.specials.length > 0 && !blueprintMenuOpened;
const showSpecial = haveBlueprint && specials.length && !blueprintMenuOpened;
const showSpecialsMenu = specialMenuOpened;
const showRolls = haveBlueprint && !blueprintMenuOpened && !specialMenuOpened;
const showReset = !blueprintMenuOpened && !specialMenuOpened;
@@ -302,12 +322,12 @@ export default class ModificationsMenu extends TranslatedComponent {
onClick={(e) => e.stopPropagation() }
onContextMenu={stopCtxPropagation}
>
{ haveBlueprint ?
{ showBlueprintsMenu ? '' : haveBlueprint ?
<div className={ cn('section-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={termtip.bind(null, blueprintTt)} onMouseOut={tooltip.bind(null, null)} onClick={_toggleBlueprintsMenu}>{blueprintLabel}</div> :
<div className={ cn('section-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleBlueprintsMenu}>{translate('PHRASE_SELECT_BLUEPRINT')}</div> }
{ showBlueprintsMenu ? this.state.blueprints : null }
{ showBlueprintsMenu ? this._renderBlueprints(this.props, this.context) : null }
{ showSpecial ? <div className={ cn('section-menu', { selected: specialMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleSpecialsMenu}>{specialLabel}</div> : null }
{ showSpecialsMenu ? this.state.specials : null }
{ showSpecialsMenu ? specials : null }
{ showRolls || showReset ?
<table style={{ width: '100%', backgroundColor: 'transparent' }}>
<tbody>
@@ -327,7 +347,7 @@ export default class ModificationsMenu extends TranslatedComponent {
</table> : null }
{ showMods ?
<span onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')} onMouseOut={tooltip.bind(null, null)} >
{ this.state.modifications }
{ this._renderModifications(this.props) }
</span> : null }
</div>
);

View File

@@ -1,90 +0,0 @@
import React from 'react';
import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent';
/**
* Movement summary
*/
export default class MovementSummary extends TranslatedComponent {
static propTypes = {
ship: React.PropTypes.object.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
}
/**
* Render movement summary
* @return {React.Component} contents
*/
render() {
let ship = this.props.ship;
let { language, tooltip, termtip } = this.context;
let { formats, translate, units } = language;
let boostMultiplier = ship.topBoost / ship.topSpeed;
return (
<span>
<h1>{translate('movement summary')}</h1>
<table style={{ marginLeft: 'auto', marginRight: 'auto', lineHeight: '1em', backgroundColor: 'transparent', borderSpacing: '0.5em' }}>
<tbody>
<tr>
<td >&nbsp;</td>
<td colSpan='6'>{translate('engine pips')}</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td onMouseOver={termtip.bind(null, '4b')} onMouseOut={tooltip.bind(null, null)}>4B</td>
</tr>
<tr>
<td className='ri'>{translate('speed')} ({units['m/s']})</td>
<td className='ri'>{formats.int(ship.speeds[0])}</td>
<td className='ri'>{formats.int(ship.speeds[1])}</td>
<td className='ri'>{formats.int(ship.speeds[2])}</td>
<td className='ri'>{formats.int(ship.speeds[3])}</td>
<td className='ri'>{formats.int(ship.speeds[4])}</td>
<td className='ri'>{formats.int(ship.speeds[4] * boostMultiplier)}</td>
</tr>
<tr>
<td className='ri'>{translate('pitch')} ({units['°/s']})</td>
<td className='ri'>{formats.int(ship.pitches[0])}</td>
<td className='ri'>{formats.int(ship.pitches[1])}</td>
<td className='ri'>{formats.int(ship.pitches[2])}</td>
<td className='ri'>{formats.int(ship.pitches[3])}</td>
<td className='ri'>{formats.int(ship.pitches[4])}</td>
<td className='ri'>{formats.int(ship.pitches[4] * boostMultiplier)}</td>
</tr>
<tr>
<td className='ri'>{translate('roll')} ({units['°/s']})</td>
<td className='ri'>{formats.int(ship.rolls[0])}</td>
<td className='ri'>{formats.int(ship.rolls[1])}</td>
<td className='ri'>{formats.int(ship.rolls[2])}</td>
<td className='ri'>{formats.int(ship.rolls[3])}</td>
<td className='ri'>{formats.int(ship.rolls[4])}</td>
<td className='ri'>{formats.int(ship.rolls[4] * boostMultiplier)}</td>
</tr>
<tr>
<td className='ri'>{translate('yaw')} ({units['°/s']})</td>
<td className='ri'>{formats.int(ship.yaws[0])}</td>
<td className='ri'>{formats.int(ship.yaws[1])}</td>
<td className='ri'>{formats.int(ship.yaws[2])}</td>
<td className='ri'>{formats.int(ship.yaws[3])}</td>
<td className='ri'>{formats.int(ship.yaws[4])}</td>
<td className='ri'>{formats.int(ship.yaws[4] * boostMultiplier)}</td>
</tr>
</tbody>
</table>
</span>
);
}
}

View File

@@ -248,11 +248,13 @@ export default class Offence extends TranslatedComponent {
<div className='group quarter'>
<h2>{translate('offence metrics')}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_DRAIN_WEP'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_DRAIN_WEP')}<br/>{timeToDrain === Infinity ? translate('never') : formats.time(timeToDrain)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_EFFECTIVE_SDPS_SHIELDS')}<br/>{formats.f1(totalShieldsSDps)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_REMOVE_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_REMOVE_SHIELDS')}<br/>{timeToDepleteShields === Infinity ? translate('never') : formats.time(timeToDepleteShields)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_EFFECTIVE_SDPS_ARMOUR')}<br/>{formats.f1(totalArmourSDps)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_REMOVE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_REMOVE_ARMOUR')}<br/>{timeToDepleteArmour === Infinity ? translate('never') : formats.time(timeToDepleteArmour)}</h2>
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('armour metrics'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield damage sources')}</h2>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield damage sources')}</h2>
<PieChart data={shieldsSDpsData} />
</div>
<div className='group quarter'>

View File

@@ -1,73 +0,0 @@
import React from 'react';
import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent';
import { DamageAbsolute, DamageKinetic, DamageThermal, DamageExplosive } from './SvgIcons';
/**
* Offence summary
*/
export default class OffenceSummary extends TranslatedComponent {
static propTypes = {
ship: React.PropTypes.object.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
}
/**
* Render offence summary
* @return {React.Component} contents
*/
render() {
let ship = this.props.ship;
let { language, tooltip, termtip } = this.context;
let { formats, translate } = language;
return (
<span>
<h1>{translate('offence summary')}</h1>
<table className='summary' style={{ marginLeft: 'auto', marginRight: 'auto', lineHeight: '1em', backgroundColor: 'transparent' }}>
<tbody>
<tr>
<td colSpan='5' className='summary'><h2>{translate('dps')}: {formats.f1(ship.totalDps)}</h2></td>
</tr>
<tr>
<td className='le'>{translate('damage by')}</td>
<td className='ri' onMouseOver={termtip.bind(null, 'absolute')} onMouseOut={tooltip.bind(null, null)}><DamageAbsolute /> {formats.f1(ship.totalAbsDps)}</td>
<td className='ri' onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /> {formats.f1(ship.totalExplDps)}</td>
<td className='ri' onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /> {formats.f1(ship.totalKinDps)}</td>
<td className='ri' onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /> {formats.f1(ship.totalThermDps)}</td>
</tr>
<tr>
<td colSpan='5' className='summary'><h2>{translate('sdps')}: {formats.f1(ship.totalSDps)}</h2></td>
</tr>
<tr>
<td className='le'>{translate('damage by')}</td>
<td className='ri' onMouseOver={termtip.bind(null, 'absolute')} onMouseOut={tooltip.bind(null, null)}><DamageAbsolute /> {formats.f1(ship.totalAbsSDps)}</td>
<td className='ri' onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /> {formats.f1(ship.totalExplSDps)}</td>
<td className='ri' onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /> {formats.f1(ship.totalKinSDps)}</td>
<td className='ri' onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /> {formats.f1(ship.totalThermSDps)}</td>
</tr>
<tr>
<td colSpan='5' className='summary'><h2>{translate('dpe')}: {formats.f1(ship.totalDpe)}</h2></td>
</tr>
<tr>
<td className='le'>{translate('damage by')}</td>
<td className='ri' onMouseOver={termtip.bind(null, 'absolute')} onMouseOut={tooltip.bind(null, null)}><DamageAbsolute /> {formats.f1(ship.totalAbsDpe)}</td>
<td className='ri' onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /> {formats.f1(ship.totalExplDpe)}</td>
<td className='ri' onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /> {formats.f1(ship.totalKinDpe)}</td>
<td className='ri' onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /> {formats.f1(ship.totalThermDpe)}</td>
</tr>
</tbody>
</table>
</span>
);
}
}

View File

@@ -1,7 +1,6 @@
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';

View File

@@ -189,7 +189,7 @@ export default class PowerBands extends TranslatedComponent {
let { f2, pct1 } = formats; // wattFmt, pctFmt
let { available, bands } = props;
let { innerWidth, ret, dep } = state;
let pwrWarningClass = cn('threshold', { exceeded: bands[0].retractedSum * 2 >= available });
let pwrWarningClass = cn('threshold', { exceeded: bands[0].retractedSum > available * 0.4 });
let deployed = [];
let retracted = [];
let retSelected = Object.keys(ret).length > 0;
@@ -268,7 +268,7 @@ export default class PowerBands extends TranslatedComponent {
axis.call(this.pctAxis);
axis.select('g:nth-child(6)').selectAll('line, text').attr('class', pwrWarningClass);
}} className='pct axis' transform={`translate(0,${state.innerHeight})`}></g>
<line x1={pctScale(0.5)} x2={pctScale(0.5)} y1='0' y2={state.innerHeight} className={pwrWarningClass} />
<line x1={pctScale(0.4)} x2={pctScale(0.4)} y1='0' y2={state.innerHeight} className={pwrWarningClass} />
<text dy='0.5em' x='-3' y={state.retY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'retracted')} onMouseLeave={this._hidetip}>{translate('ret')}</text>
<text dy='0.5em' x='-3' y={state.depY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'deployed', { orientation: 's', cap: 1 })} onMouseLeave={this._hidetip}>{translate('dep')}</text>
<text dy='0.5em' x={innerWidth + 5} y={state.retY} className={getClass(retSelected, retSum, available)}>{f2(Math.max(0, retSum))} ({pct1(Math.max(0, retSum / available))})</text>

View File

@@ -1,89 +0,0 @@
import React from 'react';
import cn from 'classnames';
import { Ships } from 'coriolis-data/dist';
import TranslatedComponent from './TranslatedComponent';
import { Rocket } from './SvgIcons';
/**
* Selector for ships
*/
export default class ShipSelector extends TranslatedComponent {
static propTypes = {
initial: React.PropTypes.object.isRequired,
onChange: React.PropTypes.func.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this.state = { ship : this.props.initial };
}
/**
* Generate the ships menu
* @return {React.Component} Menu
*/
_getShipsMenu() {
const _selectShip = this._selectShip;
let shipList = [];
for (let s in Ships) {
shipList.push(<div key={s} onClick={_selectShip.bind(this, s)} className='block' >{Ships[s].properties.name}</div>);
}
return shipList;
}
/**
* Handle opening the menu
* @param {string} menu The ID of the opened menu
* @param {SyntheticEvent} event Event
*/
_openMenu(menu, event) {
event.stopPropagation();
if (this.props.currentMenu == menu) {
menu = null;
}
this.context.openMenu(menu);
}
/**
* Handle selection of a ship
* @param {string} s The selected ship ID
*/
_selectShip(s) {
this.setState({ ship: Ships[s] });
this.context.openMenu(null);
this.props.onChange(s);
}
/**
* Render ship selector
* @return {React.Component} contents
*/
render() {
const currentMenu = this.props.currentMenu;
const ship = this.state.ship;
return (
<div className='shipselector'>
<div className='menu'>
<div className={cn('menu-header', { selected: currentMenu == 'wds' })} onClick={this._openMenu.bind(this, 'wds')}>
<Rocket className='warning' /><span className='menu-item-label'>{ship.properties.name}</span>
{currentMenu == 'wds' ?
<div className='menu-list quad no-wrap' onClick={ (e) => e.stopPropagation() }>
{this._getShipsMenu()}
</div> : null }
</div>
</div>
</div>
);
}
}

View File

@@ -79,7 +79,7 @@ export default class Slot extends TranslatedComponent {
let language = this.context.language;
let translate = language.translate;
let { ship, m, dropClass, dragOver, onOpen, onChange, selected, eligible, onSelect, warning, availableModules } = this.props;
let slotDetails, menu;
let slotDetails, modificationsMarker, menu;
if (!selected) {
// If not selected then sure that modifications flag is unset
@@ -88,8 +88,10 @@ export default class Slot extends TranslatedComponent {
if (m) {
slotDetails = this._getSlotDetails(m, translate, language.formats, language.units); // Must be implemented by sub classes
modificationsMarker = JSON.stringify(m);
} else {
slotDetails = <div className={'empty'}>{translate(eligible ? 'emptyrestricted' : 'empty')}</div>;
modificationsMarker = '';
}
if (selected) {
@@ -99,6 +101,7 @@ export default class Slot extends TranslatedComponent {
onChange={onChange}
ship={ship}
m={m}
marker={modificationsMarker}
/>;
} else {
menu = <AvailableModulesMenu

View File

@@ -57,7 +57,7 @@ export default class StandardSlot extends TranslatedComponent {
modTT = (
<div>
<div>{modTT}</div>
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade].features, m)}
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
</div>
);
}
@@ -67,6 +67,8 @@ export default class StandardSlot extends TranslatedComponent {
this._modificationsSelected = false;
}
const modificationsMarker = JSON.stringify(m);
if (selected) {
if (this._modificationsSelected) {
menu = <ModificationsMenu
@@ -74,6 +76,7 @@ export default class StandardSlot extends TranslatedComponent {
onChange={onChange}
ship={ship}
m={m}
marker={modificationsMarker}
/>;
} else {
menu = <AvailableModulesMenu
@@ -100,6 +103,7 @@ export default class StandardSlot extends TranslatedComponent {
{ m.getMinMass() ? <div className='l'>{translate('minimum mass')}: {formats.int(m.getMinMass())}{units.T}</div> : null }
{ m.getOptMass() ? <div className='l'>{translate('optimal mass')}: {formats.int(m.getOptMass())}{units.T}</div> : null }
{ m.getMaxMass() ? <div className='l'>{translate('max mass')}: {formats.int(m.getMaxMass())}{units.T}</div> : null }
{ m.getOptMul() ? <div className='l'>{translate('optimal multiplier')}: {formats.rPct(m.getOptMul())}</div> : null }
{ m.getRange() ? <div className='l'>{translate('range', m.grp)}: {formats.f2(m.getRange())}{units.km}</div> : null }
{ m.time ? <div className='l'>{translate('time')}: {formats.time(m.time)}</div> : null }
{ m.getThermalEfficiency() ? <div className='l'>{translate('efficiency')}: {formats.f2(m.getThermalEfficiency())}</div> : null }

View File

@@ -98,8 +98,10 @@ const ValueLabel = React.createClass({
render() {
const { x, y, payload, value } = this.props;
const em = value < 1000 ? '1em' : value < 1000 ? '0.8em' : '0.7em';
return (
<text x={x} y={y} fill="#000000" textAnchor="middle" dy={20}>{value}</text>
<text x={x} y={y} fill="#000000" textAnchor="middle" dy={20} style={{ fontSize: em }}>{value}</text>
);
}
});

View File

@@ -1,7 +1,6 @@
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import ShipSelector from './ShipSelector';
import { nameComparator } from '../utils/SlotFunctions';
import { CollapseSection, ExpandSection, MountFixed, MountGimballed, MountTurret } from './SvgIcons';
import LineChart from '../components/LineChart';

View File

@@ -70,6 +70,9 @@ export const terms = {
TT_EFFECTIVE_SDPS_ARMOUR: 'Actual sustained DPS whilst WEP capacitor is not empty',
TT_EFFECTIVENESS_ARMOUR: 'Effectivness compared to hitting a 0-resistance target at 0m',
PHRASE_EFFECTIVE_SDPS_SHIELDS: 'SDPS against shields',
PHRASE_EFFECTIVE_SDPS_ARMOUR: 'SDPS against armour',
TT_SUMMARY_SPEED: 'With full fuel tank and 4 pips to ENG',
TT_SUMMARY_SPEED_NONFUNCTIONAL: 'Thrusters powered off or over maximum mass',
TT_SUMMARY_BOOST: 'With full fuel tank and 4 pips to ENG',
@@ -251,6 +254,18 @@ export const terms = {
minmul_sg: 'Minimum strength',
optmul_sg: 'Optimal strength',
maxmul_sg: 'Minimum strength',
minmass_psg: 'Minimum hull mass',
optmass_psg: 'Optimal hull mass',
maxmass_psg: 'Maximum hull mass',
minmul_psg: 'Minimum strength',
optmul_psg: 'Optimal strength',
maxmul_psg: 'Minimum strength',
minmass_bsg: 'Minimum hull mass',
optmass_bsg: 'Optimal hull mass',
maxmass_bsg: 'Maximum hull mass',
minmul_bsg: 'Minimum strength',
optmul_bsg: 'Optimal strength',
maxmul_bsg: 'Minimum strength',
range_s: 'Typical emission range',

View File

@@ -14,7 +14,7 @@ import { FloppyDisk, Bin, Switch, Download, Reload, LinkIcon, ShoppingIcon } fro
import LZString from 'lz-string';
import ShipSummaryTable from '../components/ShipSummaryTable';
import StandardSlotSection from '../components/StandardSlotSection';
import HardpointsSlotSection from '../components/HardpointsSlotSection';
import HardpointSlotSection from '../components/HardpointSlotSection';
import InternalSlotSection from '../components/InternalSlotSection';
import UtilitySlotSection from '../components/UtilitySlotSection';
import Pips from '../components/Pips';
@@ -574,7 +574,7 @@ export default class OutfittingPage extends Page {
<ShipSummaryTable ship={ship} marker={shipSummaryMarker} />
<StandardSlotSection ship={ship} code={standardSlotMarker} onChange={shipUpdated} currentMenu={menu} />
<InternalSlotSection ship={ship} code={internalSlotMarker} onChange={shipUpdated} currentMenu={menu} />
<HardpointsSlotSection ship={ship} code={hardpointsSlotMarker} onChange={shipUpdated} currentMenu={menu} />
<HardpointSlotSection ship={ship} code={hardpointsSlotMarker} onChange={shipUpdated} currentMenu={menu} />
<UtilitySlotSection ship={ship} code={hardpointsSlotMarker} onChange={shipUpdated} currentMenu={menu} />
{/* Control of ship and opponent */}

View File

@@ -341,7 +341,9 @@ export function shieldMetrics(ship, sys) {
}
// Calculate diminishing returns for boosters
boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5);
// Diminishing returns not currently in-game
//boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5);
// Remove base shield generator strength
boost -= 1;
// Apply diminishing returns

View File

@@ -48,7 +48,7 @@ export default class Module {
// this special effect modifies our returned value
const modification = Modifications.modifications[name];
if (modification.method === 'additive') {
result = result + modifierActions[name];
result = result + modifierActions[name] * 100;
} else if (modification.method === 'overwrite') {
result = modifierActions[name];
} else {
@@ -680,14 +680,6 @@ export default class Module {
return this._getModifiedValue('rebuildsperbay');
}
/**
* Get the cells for this module, taking in to account modifications
* @return {Number} the cells for this module
*/
getCells() {
return this._getModifiedValue('cells');
}
/**
* Get the jitter for this module, taking in to account modifications
* @return {Number} the jitter for this module

View File

@@ -221,39 +221,6 @@ export default class Ship {
return Calc.calcYaw(this.unladenMass + fuel + cargo, this.yaw, this.standard[1].m, this.pipSpeed, eng, this.topBoost / this.topSpeed, boost);
}
/**
* Calculate the recovery time after losing or turning on shields
* Thanks to CMDRs Al Gray, GIF, and Nomad Enigma for providing Shield recharge data and formulas
*
* @return {Number} Recovery time in seconds
*/
calcShieldRecovery() {
const shieldGenerator = this.findShieldGenerator();
if (shieldGenerator) {
const brokenRegenRate = shieldGenerator.getBrokenRegenerationRate();
// 50% of shield strength / broken recharge rate + 15 second delay before recharge starts
return ((this.shield / 2) / brokenRegenRate) + 15;
}
return 0;
}
/**
* Calculate the recharge time for a shield going from 50% to 100%
* Thanks to CMDRs Al Gray, GIF, and Nomad Enigma for providing Shield recharge data and formulas
*
* @return {Number} 50 - 100% Recharge time in seconds
*/
calcShieldRecharge() {
const shieldGenerator = this.findShieldGenerator();
if (shieldGenerator) {
const regenRate = shieldGenerator.getRegenerationRate();
// 50% of shield strength / recharge rate
return (this.shield / 2) / regenRate;
}
return 0;
}
/**
* Calculate the hypothetical shield strength for the ship using the specified parameters
* @param {Object} sg [optional] Shield Generator to use
@@ -1296,7 +1263,9 @@ export default class Ship {
for (let slot of this.internal) {
if (slot.m && slot.m.grp == 'scb') {
shieldCells += slot.m.getShieldReinforcement() * slot.m.getCells();
// There is currently a bug with Elite where you can have a clip > 1 thanks to engineering but it doesn't do anything,
// so we need to hard-code clip to 1
shieldCells += slot.m.getShieldReinforcement() * slot.m.getDuration() * (slot.m.getAmmo() + 1);
}
}

View File

@@ -4,14 +4,16 @@ import { Modifications } from 'coriolis-data/dist';
/**
* Generate a tooltip with details of a blueprint's effects
* @param {Object} translate The translate object
* @param {Object} features The features of the blueprint
* @param {Object} blueprint The blueprint at the required grade
* @param {Array} engineers The engineers supplying this blueprint
* @param {string} grp The group of the module
* @param {Object} m The module to compare with
* @returns {Object} The react components
*/
export function blueprintTooltip(translate, features, m) {
const results = [];
for (const feature in features) {
const featureIsBeneficial = isBeneficial(feature, features[feature]);
export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
const effects = [];
for (const feature in blueprint.features) {
const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]);
const featureDef = Modifications.modifications[feature];
if (!featureDef.hidden) {
let symbol = '';
@@ -20,8 +22,8 @@ export function blueprintTooltip(translate, features, m) {
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let lowerBound = features[feature][0];
let upperBound = features[feature][1];
let lowerBound = blueprint.features[feature][0];
let upperBound = blueprint.features[feature][1];
if (featureDef.type === 'percentage') {
lowerBound = Math.round(lowerBound * 1000) / 10;
upperBound = Math.round(upperBound * 1000) / 10;
@@ -33,11 +35,13 @@ export function blueprintTooltip(translate, features, m) {
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
results.push(
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature)}</td>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
@@ -45,9 +49,9 @@ export function blueprintTooltip(translate, features, m) {
);
} else {
// We do not have a module, no value
results.push(
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature)}</td>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
</tr>
@@ -55,21 +59,97 @@ export function blueprintTooltip(translate, features, m) {
}
}
}
if (m) {
// Because we have a module add in any benefits that aren't part of the primary blueprint
for (const feature in m.mods) {
if (!blueprint.features[feature]) {
const featureDef = Modifications.modifications[feature];
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td>
</tr>
);
}
}
}
let components;
if (!m) {
components = [];
for (const component in blueprint.components) {
components.push(
<tr key={component}>
<td style={{ textAlign: 'left' }}>{translate(component)}</td>
<td style={{ textAlign: 'right' }}>{blueprint.components[component]}</td>
</tr>
);
}
}
let engineersList;
if (engineers) {
engineersList = [];
for (const engineer of engineers) {
engineersList.push(
<tr key={engineer}>
<td style={{ textAlign: 'left' }}>{engineer}</td>
</tr>
);
}
}
return (
<table>
<thead>
<tr>
<td>{translate('feature')}</td>
<td>{translate('worst')}</td>
{m ? <td>{translate('current')}</td> : null }
<td>{translate('best')}</td>
</tr>
</thead>
<tbody>
{results}
</tbody>
</table>
<div>
<table width='100%'>
<thead>
<tr>
<td>{translate('feature')}</td>
<td>{translate('worst')}</td>
{m ? <td>{translate('current')}</td> : null }
<td>{translate('best')}</td>
</tr>
</thead>
<tbody>
{effects}
</tbody>
</table>
{ components ? <table width='100%'>
<thead>
<tr>
<td>{translate('component')}</td>
<td>{translate('amount')}</td>
</tr>
</thead>
<tbody>
{components}
</tbody>
</table> : null }
{ engineersList ? <table width='100%'>
<thead>
<tr>
<td>{translate('engineers')}</td>
</tr>
</thead>
<tbody>
{engineersList}
</tbody>
</table> : null }
</div>
);
}
@@ -112,8 +192,8 @@ export function getBlueprint(name, module) {
// Start with a copy of the blueprint
const blueprint = JSON.parse(JSON.stringify(Modifications.blueprints[name]));
if (module) {
if (module.grp === 'bh' || module.grp === 'hr') {
// Bulkheads and hull reinforcements need to have their resistances altered by the base values
if (module.grp === 'bh' || module.grp === 'hr' || module.grp === 'sg' || module.grp === 'psg' || module.grp === 'bsg') {
// Bulkheads, hull reinforcements and shield generators need to have their resistances altered by the base values
for (const grade in blueprint.grades) {
for (const feature in blueprint.grades[grade].features) {
if (feature === 'explres') {

View File

@@ -130,9 +130,15 @@ export function shipFromJson(json) {
let ship = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots);
ship.buildWith(null);
// Set the cargo hatch. We don't have any information on it so guess it's priority 5 and disabled
ship.cargoHatch.enabled = false;
ship.cargoHatch.priority = 4;
// Set the cargo hatch
if (json.modules.CargoHatch) {
ship.cargoHatch.enabled = json.modules.CargoHatch.module.on == true;
ship.cargoHatch.priority = json.modules.CargoHatch.module.priority;
} else {
// We don't have any information on it so guess it's priority 5 and disabled
ship.cargoHatch.enabled = false;
ship.cargoHatch.priority = 4;
}
// Add the bulkheads
const armourJson = json.modules.Armour.module;
@@ -427,4 +433,10 @@ function _addModifications(module, modifiers, blueprint, grade) {
if (module.getModValue('rof')) {
module.setModValue('rof', ((1 / (1 + module.getModValue('rof') / 10000)) - 1) * 10000);
}
// Clip size is rounded up so that the result is a whole number
if (module.getModValue('clip')) {
const individual = 1 / (module.clip || 1);
module.setModValue('clip', Math.ceil((module.getModValue('clip') / 10000) / individual) * individual * 10000);
}
}

View File

@@ -243,7 +243,7 @@ export function diffDetails(language, m, mm) {
}
}
let mIntegrity = m.integrity;
let mIntegrity = m.integrity || 0;
let mmIntegrity = mm ? mm.getIntegrity() || 0 : 0;
if (mIntegrity != mmIntegrity) {
propDiffs.push(<div key='integrity'>{translate('integrity')}: <span className={diffClass(mmIntegrity, mIntegrity, true)}>{diff(formats.round, mIntegrity, mmIntegrity)}</span></div>);

View File

@@ -16,7 +16,6 @@
@import 'tooltip';
@import 'buttons';
@import 'error';
@import 'shipselector';
@import 'sortable';
@import 'loader';
@import 'pips';

View File

@@ -29,7 +29,7 @@ select {
padding: 0.5em 0;
width: 100%;
margin: 0;
max-height: 400px;
max-height: 500px;
overflow-y: auto;
overflow-x: hidden;
z-index: 0;

View File

@@ -1,199 +0,0 @@
.shipselector {
background-color: @bgBlack;
margin: 0;
padding: 0 0 0 1em;
height: 3em;
line-height: 3em;
font-family: @fTitle;
vertical-align: middle;
position: relative;
.user-select-none();
.menu {
position: relative;
z-index: 1;
cursor: default;
&.r {
.menu-list {
right: 0;
}
}
.smallTablet({
position: static;
position: initial;
});
}
.menu-header {
padding : 0 1em;
cursor: pointer;
color: @warning;
text-transform: uppercase;
}
.menu-list {
font-family: @fStandard;
position: absolute;
padding: 0.5em 1em;
box-sizing: border-box;
min-width: 100%;
overflow-x: hidden;
background-color: @bgBlack;
font-size: 0.9em;
overflow-y: auto;
z-index: 0;
-webkit-overflow-scrolling: touch;
max-height: 500px;
&::-webkit-scrollbar {
width: 0.5em;
}
&::-webkit-scrollbar-track {
background-color: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: @warning-disabled;
}
input {
border: none;
background-color: transparent;
text-align: right;
font-size: 1em;
font-family: @fStandard;
}
.smallTablet({
max-height: 400px;
left: 0;
right: 0;
border-bottom: 1px solid @bg;
});
.tablet({
li, a {
padding: 0.3em 0;
}
});
}
.dbl {
-webkit-column-count: 2; /* Chrome, Safari, Opera */
-moz-column-count: 2; /* Firefox */
column-count: 2;
ul {
min-width: 10em;
}
.smallTablet({
-webkit-column-count: 3; /* Chrome, Safari, Opera */
-moz-column-count: 3; /* Firefox */
column-count: 3;
ul {
min-width: 20em;
}
});
.largePhone({
-webkit-column-count: 2; /* Chrome, Safari, Opera */
-moz-column-count: 2; /* Firefox */
column-count: 2;
});
.smallPhone({
-webkit-column-count: 1; /* Chrome, Safari, Opera */
-moz-column-count: 1; /* Firefox */
column-count: 1;
});
}
.quad {
-webkit-column-count: 4; /* Chrome, Safari, Opera */
-moz-column-count: 4; /* Firefox */
column-count: 4;
ul {
min-width: 10em;
}
.smallTablet({
-webkit-column-count: 3; /* Chrome, Safari, Opera */
-moz-column-count: 3; /* Firefox */
column-count: 3;
ul {
min-width: 20em;
}
});
.largePhone({
-webkit-column-count: 2; /* Chrome, Safari, Opera */
-moz-column-count: 2; /* Firefox */
column-count: 2;
});
.smallPhone({
-webkit-column-count: 1; /* Chrome, Safari, Opera */
-moz-column-count: 1; /* Firefox */
column-count: 1;
});
}
ul {
display: inline-block;
white-space: nowrap;
margin: 0 0 0.5em;
padding: 0;
line-height: 1.3em;
}
li {
white-space: normal;
list-style: none;
margin-left: 1em;
line-height: 1.1em;
}
a {
vertical-align: middle;
color: @warning;
text-decoration: none;
&:visited {
color: @warning;
}
.no-touch &:hover {
color: teal;
}
&.active {
color: @primary;
}
}
hr {
border: none;
border-top: 1px solid @disabled;
}
.no-wrap {
white-space: nowrap;
}
.block {
display: block;
line-height: 1.5em;
}
.title {
font-size: 1.3em;
display: inline-block;
margin:0px;
text-transform: uppercase;
}
}