mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-08 22:33:24 +00:00
Merge branch 'feature/fixes' into develop
This commit is contained in:
16
ChangeLog.md
16
ChangeLog.md
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
{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>
|
||||
{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>
|
||||
{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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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} {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> {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 }
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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 > </td>
|
||||
<td colSpan='6'>{translate('engine pips')}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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'>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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',
|
||||
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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> </td>
|
||||
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
|
||||
<td> </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') {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>);
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
@import 'tooltip';
|
||||
@import 'buttons';
|
||||
@import 'error';
|
||||
@import 'shipselector';
|
||||
@import 'sortable';
|
||||
@import 'loader';
|
||||
@import 'pips';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user