Merge branch 'release/2.2.18'

This commit is contained in:
Cmdr McDonald
2017-02-22 08:29:43 +00:00
36 changed files with 12402 additions and 241 deletions

View File

@@ -1,3 +1,13 @@
#2.2.18
* Change methodology for calculating explorer role; can result in lighter builds
* Tidy up layout for module selection and lay everything out in a consistent best-to-worst for both class and grade
* Make integrity for module reinforcement packages visible
* Clean up breakpoints for modules in available modules list; stops 7- or 8- module long lines
* Add damager/range graphs to damage dealt
* Reorder panels
* Use coriolis-data 2.2.18:
* Correct lower efficiency value to be better, not worse
#2.2.17
* Use in-game terminology for shield generator optmul and optmass items
* Add crew to shipyard and outfitting page information

11
d3-funcs.js vendored Normal file
View File

@@ -0,0 +1,11 @@
export {
axisBottom,
axisLeft,
axisTop,
formatLocale,
line,
scaleBand,
scaleLinear,
scaleOrdinal,
select
} from 'd3';

11675
d3.js vendored Normal file

File diff suppressed because it is too large Load Diff

4
d3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
{
"name": "coriolis_shipyard",
"version": "2.2.17",
"version": "2.2.18",
"repository": {
"type": "git",
"url": "https://github.com/EDCD/coriolis"
@@ -11,6 +11,7 @@
"engine": "node >= 4.0.0",
"license": "MIT",
"scripts": {
"prepublish": "rollup -c && uglifyjs d3.js -c -m -o d3.min.js",
"extract-translations": "grep -hroE \"(translate\\('[^']+'\\))|(tip.bind\\(null, '[^']+')\" src/* | grep -oE \"'[^']+'\" | grep -oE \"[^']+\" | sort -u -f",
"clean": "rimraf build",
"start": "node devServer.js",
@@ -62,6 +63,7 @@
"babel-preset-react": "*",
"babel-preset-stage-0": "*",
"css-loader": "^0.23.0",
"d3-selection": "1",
"eslint": "2.2.0",
"eslint-plugin-react": "^4.0.0",
"expose-loader": "^0.7.1",
@@ -77,6 +79,8 @@
"react-addons-test-utils": "^15.0.1",
"react-testutils-additions": "^15.1.0",
"rimraf": "^2.4.3",
"rollup": "0.36",
"rollup-plugin-node-resolve": "2",
"style-loader": "^0.13.0",
"url-loader": "^0.5.6",
"webpack": "^1.9.6",
@@ -87,7 +91,7 @@
"classnames": "^2.2.0",
"browserify-zlib": "ipfs/browserify-zlib",
"coriolis-data": "EDCD/coriolis-data",
"d3": "3.5.16",
"d3": "4.6.0",
"fbemitter": "^2.0.0",
"lodash": "^4.15.0",
"lz-string": "^1.4.4",

9
rollup.config.js Normal file
View File

@@ -0,0 +1,9 @@
import nodeResolve from "rollup-plugin-node-resolve";
export default {
entry: "d3-funcs.js",
format: "umd",
moduleName: "d3",
plugins: [nodeResolve({jsnext: true})],
dest: "d3.js"
};

View File

@@ -11,7 +11,6 @@ import ModalHelp from './components/ModalHelp';
import ModalImport from './components/ModalImport';
import ModalPermalink from './components/ModalPermalink';
import * as CompanionApiUtils from './utils/CompanionApiUtils';
import { outfitURL } from './utils/UrlGenerators';
import AboutPage from './pages/AboutPage';
import NotFoundPage from './pages/NotFoundPage';

View File

@@ -7,6 +7,74 @@ import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
const PRESS_THRESHOLD = 500; // mouse/touch down threshold
/*
* Categorisation of module groups
*/
const GRPCAT = {
'sg': 'shields',
'bsg': 'shields',
'psg': 'shields',
'scb': 'shields',
'cc': 'limpet controllers',
'fx': 'limpet controllers',
'hb': 'limpet controllers',
'pc': 'limpet controllers',
'pce': 'passenger cabins',
'pci': 'passenger cabins',
'pcm': 'passenger cabins',
'pcq': 'passenger cabins',
'fh': 'hangars',
'pv': 'hangars',
'fs': 'fuel',
'ft': 'fuel',
'hr': 'structural reinforcement',
'mrp': 'structural reinforcement',
'bl': 'lasers',
'pl': 'lasers',
'ul': 'lasers',
'ml': 'lasers',
'c': 'projectiles',
'mc': 'projectiles',
'fc': 'projectiles',
'pa': 'projectiles',
'rg': 'projectiles',
'mr': 'ordnance',
'tp': 'ordnance',
'nl': 'ordnance',
// Utilities
'cs': 'scanners',
'kw': 'scanners',
'ws': 'scanners',
'ch': 'defence',
'po': 'defence',
'ec': 'defence',
};
// Order here is the order in which items will be shown in the modules menu
const CATEGORIES = {
// Internals
'am': ['am'],
'cr': ['cr'],
'fi': ['fi'],
'fuel': ['ft', 'fs'],
'hangars': ['fh', 'pv'],
'limpet controllers': ['cc', 'fx', 'hb', 'pc'],
'passenger cabins': ['pce', 'pci', 'pcm', 'pcq'],
'rf': ['rf'],
'sc': ['sc'],
'shields': ['sg', 'bsg', 'psg', 'scb'],
'structural reinforcement': ['hr', 'mrp'],
'dc': ['dc'],
// Hardpoints
'lasers': ['pl', 'ul', 'bl', 'ml'],
'projectiles': ['mc', 'c', 'fc', 'pa', 'rg'],
'ordnance': ['mr', 'tp', 'nl'],
// Utilities
'sb': ['sb'],
'hs': ['hs'],
'defence': ['ch', 'po', 'ec'],
'scanners': ['cs', 'kw', 'ws'],
};
/**
* Available modules menu
*/
@@ -63,15 +131,55 @@ export default class AvailableModulesMenu extends TranslatedComponent {
} else {
list = [];
// At present time slots with grouped options (Hardpoints and Internal) can be empty
list.push(<div className='empty-c upp' key='empty' onClick={onSelect.bind(null, null)} >{translate('empty')}</div>);
for (let g in modules) {
if (m && g == m.grp) {
list.push(<div ref={(elem) => this.groupElem = elem} key={g} className={'select-group cap'}>{translate(g)}</div>);
} else {
list.push(<div key={g} className={'select-group cap'}>{translate(g)}</div>);
}
if (m) {
list.push(<div className='empty-c upp' key='empty' onClick={onSelect.bind(null, null)} >{translate('empty')}</div>);
}
list.push(buildGroup(g, modules[g]));
// Need to regroup the modules by our own categorisation
let catmodules = {};
// Pre-create to preserve ordering
for (let cat in CATEGORIES) {
catmodules[cat] = [];
}
for (let g in modules) {
const moduleCategory = GRPCAT[g] || g;
const existing = catmodules[moduleCategory] || [];
catmodules[moduleCategory] = existing.concat(modules[g]);
}
for (let category in catmodules) {
let categoryHeader = false;
// Order through CATEGORIES if present
const categories = CATEGORIES[category] || [category];
if (categories && categories.length) {
for (let n in categories) {
const grp = categories[n];
// We now have the group and the category. We might not have any modules, though...
if (modules[grp]) {
// Decide if we need a category header as well as a group header
if (categories.length === 1) {
// Show category header instead of group header
if (m && grp == m.grp) {
list.push(<div ref={(elem) => this.groupElem = elem} key={category} className={'select-category upp'}>{translate(category)}</div>);
} else {
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
}
} else {
// Show category header as well as group header
if (!categoryHeader) {
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
categoryHeader = true;
}
if (m && grp == m.grp) {
list.push(<div ref={(elem) => this.groupElem = elem} key={grp} className={'select-group cap'}>{translate(grp)}</div>);
} else {
list.push(<div key={grp} className={'select-group cap'}>{translate(grp)}</div>);
}
}
list.push(buildGroup(grp, modules[grp]));
}
}
}
}
}
@@ -95,6 +203,12 @@ export default class AvailableModulesMenu extends TranslatedComponent {
const sortedModules = modules.sort(this._moduleOrder);
// Calculate the number of items per class. Used so we don't have long lists with only a few items in each row
const tmp = sortedModules.map((v, i) => v['class']).reduce((count, cls) => { count[cls] = ++count[cls] || 1; return count; }, {});
const itemsPerClass = Math.max.apply(null, Object.keys(tmp).map(key => tmp[key]));
let itemsOnThisRow = 0;
for (let i = 0; i < sortedModules.length; i++) {
let m = sortedModules[i];
let mount = null;
@@ -128,8 +242,9 @@ export default class AvailableModulesMenu extends TranslatedComponent {
case 'T': mount = <MountTurret className={'lg'}/>; break;
}
if (i > 0 && sortedModules.length > 3 && m.class != prevClass && (m.rating != prevRating || m.mount) && m.grp != 'pa') {
if (itemsOnThisRow == 6 || i > 0 && sortedModules.length > 3 && itemsPerClass > 2 && m.class != prevClass && (m.rating != prevRating || m.mount)) {
elems.push(<br key={'b' + m.grp + i} />);
itemsOnThisRow = 0;
}
elems.push(
@@ -138,6 +253,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
{(mount ? ' ' : '') + m.class + m.rating + (m.missile ? '/' + m.missile : '') + (m.name ? ' ' + translate(m.name) : '')}
</li>
);
itemsOnThisRow++;
prevClass = m.class;
prevRating = m.rating;
}
@@ -232,12 +348,12 @@ export default class AvailableModulesMenu extends TranslatedComponent {
return 1;
}
}
// Rating ordered from lowest (E) to highest (A)
// Rating ordered from highest (A) to lowest (E)
if (a.rating < b.rating) {
return 1;
return -1;
}
if (a.rating > b.rating) {
return -1;
return 1;
}
// Do not attempt to order by name at this point, as that mucks up the order of armour
return 0;

View File

@@ -1,5 +1,5 @@
import React from 'react';
import d3 from 'd3';
import * as d3 from 'd3';
import TranslatedComponent from './TranslatedComponent';
const MARGIN = { top: 15, right: 20, bottom: 40, left: 150 };
@@ -68,13 +68,13 @@ export default class BarChart extends TranslatedComponent {
this._updateDimensions = this._updateDimensions.bind(this);
this._hideTip = this._hideTip.bind(this);
let scale = d3.scale.linear();
let y0 = d3.scale.ordinal();
let y1 = d3.scale.ordinal();
let scale = d3.scaleLinear();
let y0 = d3.scaleBand();
let y1 = d3.scaleBand();
this.xAxis = d3.svg.axis().scale(scale).ticks(5).outerTickSize(0).orient('bottom').tickFormat(context.language.formats.s2);
this.yAxis = d3.svg.axis().scale(y0).outerTickSize(0).orient('left');
this.state = { scale, y0, y1, color: d3.scale.ordinal().range(props.colors) };
this.xAxis = d3.axisBottom(scale).ticks(5).tickSizeOuter(0).tickFormat(context.language.formats.s2);
this.yAxis = d3.axisLeft(y0).tickSizeOuter(0);
this.state = { scale, y0, y1, color: d3.scaleOrdinal().range(props.colors) };
}
/**
@@ -131,8 +131,8 @@ export default class BarChart extends TranslatedComponent {
let max = data.reduce((max, build) => (properties.reduce(((m, p) => (m > build[p] ? m : build[p])), max)), 0);
this.state.scale.range([0, innerWidth]).domain([0, max]);
this.state.y0.domain(data.map(bName)).rangeRoundBands([0, innerHeight], 0.3);
this.state.y1.domain(properties).rangeRoundBands([0, this.state.y0.rangeBand()]);
this.state.y0.domain(data.map(bName)).range([0, innerHeight], 0.3).padding(0.4);
this.state.y1.domain(properties).range([0, this.state.y0.bandwidth()]).padding(0.1);
this.setState({
barHeight,
@@ -192,7 +192,7 @@ export default class BarChart extends TranslatedComponent {
x={0}
y={y1(p)}
width={scale(build[p])}
height={y1.rangeBand()}
height={y1.bandwidth()}
fill={color(p)}
onMouseOver={this._showTip.bind(this, build, p, propIndex)}
onMouseOut={this._hideTip}

View File

@@ -3,7 +3,6 @@ import TranslatedComponent from './TranslatedComponent';
import Link from './Link';
import cn from 'classnames';
import { outfitURL } from '../utils/UrlGenerators';
import { SizeMap } from '../shipyard/Constants';
/**

View File

@@ -4,7 +4,12 @@ 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
@@ -47,6 +52,7 @@ export function weaponComparator(translate, propComparator, desc) {
export default class DamageDealt extends TranslatedComponent {
static PropTypes = {
ship: React.PropTypes.object.isRequired,
chartWidth: React.PropTypes.number.isRequired,
code: React.PropTypes.string.isRequired
};
@@ -55,21 +61,33 @@ export default class DamageDealt extends TranslatedComponent {
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props) {
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: DamageDealt.DEFAULT_AGAINST,
against,
expanded: false,
range: 0.1667,
maxRange: 6000
range,
maxRange,
maxDps,
weaponNames,
calcHullDpsFunc: this._calcDps.bind(this, context, ship, weaponNames, against, true),
calcShieldsDpsFunc: this._calcDps.bind(this, context, ship, weaponNames, against, false)
};
}
@@ -77,7 +95,7 @@ export default class DamageDealt extends TranslatedComponent {
* Set the initial weapons state
*/
componentWillMount() {
const data = this._calcWeapons(this.props.ship, this.state.against, this.state.range * this.state.maxRange);
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 });
}
@@ -89,20 +107,165 @@ export default class DamageDealt extends TranslatedComponent {
*/
componentWillReceiveProps(nextProps, nextContext) {
if (nextProps.code != this.props.code) {
const data = this._calcWeapons(this.props.ship, this.state.against, this.state.range * this.state.maxRange);
this.setState({ weapons: data.weapons, totals: data.totals });
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 {boolean} Returns the per-weapon damage
* @return {object} Returns the per-weapon damage
*/
_calcWeapons(ship, against, range) {
_calcWeaponsDps(ship, against, range) {
const translate = this.context.language.translate;
// Tidy up the range so that it's to 4 decimal places
@@ -120,7 +283,7 @@ export default class DamageDealt extends TranslatedComponent {
let weapons = [];
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
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;
@@ -146,10 +309,42 @@ export default class DamageDealt extends TranslatedComponent {
engineering += ', ' + translate(m.blueprint.special.name);
}
}
const effectivenessShields = dropoff;
// 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);
const effectivenessHull = (m.getPiercing() >= against.properties.hardness ? 1 : m.getPiercing() / against.properties.hardness) * dropoff;
// 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;
@@ -158,7 +353,6 @@ export default class DamageDealt extends TranslatedComponent {
totals.effectiveSDpsHull += effectiveSDpsHull;
totalDps += m.getDps();
weapons.push({ id: i,
mount: m.mount,
name: m.name || m.grp,
@@ -192,8 +386,12 @@ export default class DamageDealt extends TranslatedComponent {
*/
_onShipChange(s) {
const against = Ships[s];
const data = this._calcWeapons(this.props.ship, against, this.state.range * this.state.maxRange);
this.setState({ against, weapons: data.weapons, totals: data.totals });
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) });
}
/**
@@ -259,11 +457,11 @@ export default class DamageDealt extends TranslatedComponent {
{weapon.classRating} {translate(weapon.name)}
{weapon.engineering ? ' (' + weapon.engineering + ')' : null }
</td>
<td className='ri'>{formats.round1(weapon.effectiveDpsShields)}</td>
<td className='ri'>{formats.round1(weapon.effectiveSDpsShields)}</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.round1(weapon.effectiveDpsHull)}</td>
<td className='ri'>{formats.round1(weapon.effectiveSDpsHull)}</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>);
}
@@ -277,8 +475,10 @@ export default class DamageDealt extends TranslatedComponent {
* @param {number} range Range 0-1
*/
_rangeChange(range) {
const data = this._calcWeapons(this.props.ship, this.state.against, this.state.range * this.state.maxRange);
this.setState({ range, weapons: data.weapons, totals: data.totals });
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 });
}
/**
@@ -288,22 +488,25 @@ export default class DamageDealt extends TranslatedComponent {
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { expanded, maxRange, range, totals } = this.state;
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() + '.' + against.properties.name;
return (
<span>
<h1>{translate('damage dealt against')} {expanded ? <span onClick={onCollapseExpand}><CollapseSection className='summary'/></span> : <span onClick={onCollapseExpand}><ExpandSection className='summary'/></span>}</h1>
<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={this.state.against} currentMenu={this.props.currentMenu} onChange={this._onShipChange} />
<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('shields')}</th>
<th colSpan='3'>{translate('armour')}</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>
@@ -320,11 +523,11 @@ export default class DamageDealt extends TranslatedComponent {
<tfoot>
<tr className='main'>
<td className='ri'><i>{translate('total')}</i></td>
<td className='ri'><i>{formats.round1(totals.effectiveDpsShields)}</i></td>
<td className='ri'><i>{formats.round1(totals.effectiveSDpsShields)}</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.round1(totals.effectiveDpsHull)}</i></td>
<td className='ri'><i>{formats.round1(totals.effectiveSDpsHull)}</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>
@@ -349,7 +552,40 @@ export default class DamageDealt extends TranslatedComponent {
</td>
</tr>
</tbody>
</table></span> : null }
</table>
<div className='group half'>
<h1>{translate('sustained dps against standard shields')}</h1>
<LineChart
width={this.props.chartWidth}
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
width={this.props.chartWidth}
xMax={maxRange}
yMax={this.state.maxDps}
xLabel={translate('range')}
xUnit={translate('m')}
yLabel={translate('sdps')}
series={this.state.weaponNames}
colors={DAMAGE_DEALT_COLORS}
func={this.state.calcHullDpsFunc}
points={200}
code={code}
/>
</div>
</span> : null }
</span>
);
}

View File

@@ -278,7 +278,7 @@ export default class DamageReceived extends TranslatedComponent {
return (
<span>
<h1>{translate('damage received by')} {expanded ? <span onClick={onCollapseExpand}><CollapseSection className='summary'/></span> : <span onClick={onCollapseExpand}><ExpandSection className='summary'/></span>}</h1>
<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>

View File

@@ -39,8 +39,8 @@ export default class InternalSlot extends Slot {
<div className={'r'}>{formats.round(mass)}{u.T}</div>
</div>
<div className={'cb'}>
{ m.getOptMass() ? <div className={'l'}>{translate('optmass', m.grp)}: {formats.int(m.getOptMass())}{u.T}</div> : null }
{ m.getMaxMass() ? <div className={'l'}>{translate('maxmass', m.grp)}: {formats.int(m.getMaxMass())}{u.T}</div> : null }
{ m.getOptMass() ? <div className={'l'}>{translate('optmass', 'sg')}: {formats.int(m.getOptMass())}{u.T}</div> : null }
{ m.getMaxMass() ? <div className={'l'}>{translate('maxmass', 'sg')}: {formats.int(m.getMaxMass())}{u.T}</div> : null }
{ m.bins ? <div className={'l'}>{m.bins} <u>{translate('bins')}</u></div> : null }
{ m.bays ? <div className={'l'}>{translate('bays')}: {m.bays}</div> : null }
{ m.rebuildsperbay ? <div className={'l'}>{translate('rebuildsperbay')}: {m.rebuildsperbay}</div> : null }
@@ -60,6 +60,7 @@ export default class InternalSlot extends Slot {
{ m.rangeRating ? <div className={'l'}>{translate('range')}: {m.rangeRating}</div> : null }
{ m.getHullReinforcement() ? <div className={'l'}>+{formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost') / 10000)} <u className='cap'>{translate('armour')}</u></div> : null }
{ m.getProtection() ? <div className={'l'}>{formats.rPct(m.getProtection())} <u className='cap'>{translate('protection')}</u></div> : null }
{ m.getIntegrity() && m.grp === 'mrp' ? <div className={'l'}>{formats.int(m.getIntegrity())} <u className='cap'>{translate('integrity')}</u></div> : null }
{ m.passengers ? <div className={'l'}>{translate('passengers')}: {m.passengers}</div> : null }
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null }
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }

View File

@@ -1,8 +1,7 @@
import React from 'react';
import d3 from 'd3';
import * as d3 from 'd3';
import TranslatedComponent from './TranslatedComponent';
const RENDER_POINTS = 20; // Only render 20 points on the graph
const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
/**
@@ -11,8 +10,10 @@ const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
export default class LineChart extends TranslatedComponent {
static defaultProps = {
code: '',
xMin: 0,
yMin: 0,
points: 20,
colors: ['#ff8c0d']
};
@@ -26,9 +27,11 @@ export default class LineChart extends TranslatedComponent {
yLabel: React.PropTypes.string.isRequired,
yMin: React.PropTypes.number,
yMax: React.PropTypes.number.isRequired,
yUnit: React.PropTypes.string.isRequired,
yUnit: React.PropTypes.string,
series: React.PropTypes.array,
colors: React.PropTypes.array,
points: React.PropTypes.number,
code: React.PropTypes.string,
};
/**
@@ -40,37 +43,25 @@ export default class LineChart extends TranslatedComponent {
super(props);
this._updateDimensions = this._updateDimensions.bind(this);
this._updateSeriesData = this._updateSeriesData.bind(this);
this._updateSeries = this._updateSeries.bind(this);
this._tooltip = this._tooltip.bind(this);
this._showTip = this._showTip.bind(this);
this._hideTip = this._hideTip.bind(this);
this._moveTip = this._moveTip.bind(this);
let markerElems = [];
let detailElems = [<text key='lbl' className='text-tip x' y='1.25em'/>];
let xScale = d3.scale.linear();
let xAxisScale = d3.scale.linear();
let yScale = d3.scale.linear();
let series = props.series;
let seriesLines = [];
const series = props.series;
this.xAxis = d3.svg.axis().scale(xAxisScale).outerTickSize(0).orient('bottom');
this.yAxis = d3.svg.axis().scale(yScale).ticks(6).outerTickSize(0).orient('left');
let xScale = d3.scaleLinear();
let yScale = d3.scaleLinear();
let xAxisScale = d3.scaleLinear();
for(let i = 0, l = series ? series.length : 1; i < l; i++) {
let yAccessor = series ? function(d) { return yScale(d[1][this]); }.bind(series[i]) : (d) => yScale(d[1]);
seriesLines.push(d3.svg.line().x((d) => xScale(d[0])).y(yAccessor));
detailElems.push(<text key={i} className='text-tip y' y={1.25 * (i + 2) + 'em'}/>);
markerElems.push(<circle key={i} className='marker' r='4' />);
}
this.xAxis = d3.axisBottom(xAxisScale).tickSizeOuter(0);
this.yAxis = d3.axisLeft(yScale).ticks(6).tickSizeOuter(0);
this.state = {
xScale,
xAxisScale,
yScale,
seriesLines,
detailElems,
markerElems,
tipHeight: 2 + (1.2 * (series ? series.length : 0.8))
};
}
@@ -98,7 +89,7 @@ export default class LineChart extends TranslatedComponent {
let yVal = series ? y0[series[i]] : y0;
yTotal += yVal;
return (series ? translate(series[i]) : '') + ' ' + formats.f2(yVal);
}).append('tspan').attr('class', 'metric').text(' ' + yUnit);
}).append('tspan').attr('class', 'metric').text(yUnit ? ' ' + yUnit : '');
tips.selectAll('text').each(function() {
if (this.getBBox().width > tipWidth) {
@@ -129,7 +120,7 @@ export default class LineChart extends TranslatedComponent {
this.state.xScale.range([0, innerWidth]).domain([xMin, xMax || 1]).clamp(true);
this.state.xAxisScale.range([0, innerWidth]).domain([xMin, xMax]).clamp(true);
this.state.yScale.range([innerHeight, 0]).domain([yMin, yMax]);
this.state.yScale.range([innerHeight, 0]).domain([yMin, yMax * 1.1]); // 10% higher than maximum value for tooltip visibility
this.setState({ innerWidth, outerHeight, innerHeight });
}
@@ -164,27 +155,39 @@ export default class LineChart extends TranslatedComponent {
}
/**
* Update series data generated from props
* @param {Object} props React Component properties
* Update series generated from props
* @param {Object} props React Component properties
* @param {Object} state React Component state
*/
_updateSeriesData(props) {
let { func, xMin, xMax, series } = props;
let delta = (xMax - xMin) / RENDER_POINTS;
let seriesData = new Array(RENDER_POINTS);
_updateSeries(props, state) {
let { func, xMin, xMax, series, points } = props;
let delta = (xMax - xMin) / points;
let seriesData = new Array(points);
if (delta) {
seriesData = new Array(RENDER_POINTS);
for (let i = 0, x = xMin; i < RENDER_POINTS; i++) {
seriesData = new Array(points);
for (let i = 0, x = xMin; i < points; i++) {
seriesData[i] = [x, func(x)];
x += delta;
}
seriesData[RENDER_POINTS - 1] = [xMax, func(xMax)];
seriesData[points - 1] = [xMax, func(xMax)];
} else {
let yVal = func(xMin);
seriesData = [[0, yVal], [1, yVal]];
}
this.setState({ seriesData });
const markerElems = [];
const detailElems = [<text key='lbl' className='text-tip x' y='1.25em'/>];
const seriesLines = [];
for (let i = 0, l = series ? series.length : 1; i < l; i++) {
const yAccessor = series ? function(d) { return state.yScale(d[1][this]); }.bind(series[i]) : (d) => state.yScale(d[1]);
seriesLines.push(d3.line().x((d, i) => this.state.xScale(d[0])).y(yAccessor));
detailElems.push(<text key={i} className='text-tip y' stroke={props.colors[i]} y={1.25 * (i + 2) + 'em'}/>);
markerElems.push(<circle key={i} className='marker' r='4' />);
}
this.setState({ markerElems, detailElems, seriesLines, seriesData });
}
/**
@@ -192,7 +195,7 @@ export default class LineChart extends TranslatedComponent {
*/
componentWillMount() {
this._updateDimensions(this.props, this.context.sizeRatio);
this._updateSeriesData(this.props);
this._updateSeries(this.props, this.state);
}
/**
@@ -210,8 +213,8 @@ export default class LineChart extends TranslatedComponent {
this._updateDimensions(nextProps, nextContext.sizeRatio);
}
if (domainChanged) {
this._updateSeriesData(nextProps);
if (props.code != nextProps.code) {
this._updateSeries(nextProps, this.state);
}
}
@@ -227,7 +230,7 @@ export default class LineChart extends TranslatedComponent {
let { xLabel, yLabel, xUnit, yUnit, colors } = this.props;
let { innerWidth, outerHeight, innerHeight, tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state;
let line = this.line;
let lines = seriesLines.map((line, i) => <path key={i} className='line' stroke={colors[i]} strokeWidth='2' d={line(seriesData)} />);
let lines = seriesLines.map((line, i) => <path key={i} className='line' fill='none' stroke={colors[i]} strokeWidth='1' d={line(seriesData)} />).reverse();
return <svg style={{ width: '100%', height: outerHeight }}>
<g transform={`translate(${MARGIN.left},${MARGIN.top})`}>
@@ -241,7 +244,7 @@ export default class LineChart extends TranslatedComponent {
<g className='y axis' ref={(elem) => d3.select(elem).call(this.yAxis)}>
<text className='cap' transform='rotate(-90)' y='-50' dy='.1em' x={innerHeight / -2} style={{ textAnchor: 'middle' }}>
<tspan>{yLabel}</tspan>
<tspan className='metric'> ({yUnit})</tspan>
{ yUnit && <tspan className='metric'> ({yUnit})</tspan> }
</text>
</g>
<g ref={(g) => this.tipContainer = d3.select(g)} style={{ display: 'none' }}>

View File

@@ -1,5 +1,4 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames';
import NumberEditor from 'react-number-editor';

View File

@@ -1,10 +1,8 @@
import React from 'react';
import * as _ from 'lodash';
import { findDOMNode } from 'react-dom';
import TranslatedComponent from './TranslatedComponent';
import { isEmpty, stopCtxPropagation } from '../utils/UtilityFunctions';
import cn from 'classnames';
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
import { Modifications } from 'coriolis-data/dist';
import Modification from './Modification';
@@ -44,8 +42,8 @@ export default class ModificationsMenu extends TranslatedComponent {
* @return {Object} list: Array of React Components
*/
_initState(props, context) {
let { m, onChange, ship } = props;
const { language, tooltip, termtip } = context;
let { m } = props;
const { language } = context;
const translate = language.translate;
// Set up the blueprints
@@ -220,7 +218,23 @@ export default class ModificationsMenu extends TranslatedComponent {
const { m, ship } = this.props;
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
const value = Modifications.modifications[featureName].higherbetter ? features[featureName][1] : features[featureName][0];
let value;
if (Modifications.modifications[featureName].higherbetter) {
// Higher is better, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
value = features[featureName][0];
} else {
value = features[featureName][1];
}
} else {
// Higher is worse, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
value = features[featureName][1];
} else {
value = features[featureName][0];
}
}
this._setRollResult(ship, m, featureName, value);
}
this.setState({ modifications: this._setModifications(this.props) });
@@ -267,7 +281,6 @@ export default class ModificationsMenu extends TranslatedComponent {
}
let specialLabel;
let haveSpecial = false;
if (m.blueprint && m.blueprint.special) {
specialLabel = m.blueprint.special.name;
} else {

View File

@@ -26,7 +26,6 @@ export default class MovementSummary extends TranslatedComponent {
let ship = this.props.ship;
let { language, tooltip, termtip } = this.context;
let { formats, translate, units } = language;
let hide = tooltip.bind(null, null);
let boostMultiplier = ship.topBoost / ship.topSpeed;
return (

View File

@@ -1,5 +1,5 @@
import React from 'react';
import d3 from 'd3';
import * as d3 from 'd3';
import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent';
import { wrapCtxMenu } from '../utils/UtilityFunctions';
@@ -46,10 +46,10 @@ export default class PowerBands extends TranslatedComponent {
*/
constructor(props, context) {
super(props);
this.wattScale = d3.scale.linear();
this.pctScale = d3.scale.linear().domain([0, 1]);
this.wattAxis = d3.svg.axis().scale(this.wattScale).outerTickSize(0).orient('top').tickFormat(context.language.formats.r2);
this.pctAxis = d3.svg.axis().scale(this.pctScale).outerTickSize(0).orient('bottom').tickFormat(context.language.formats.rPct);
this.wattScale = d3.scaleLinear();
this.pctScale = d3.scaleLinear().domain([0, 1]);
this.wattAxis = d3.axisTop(this.wattScale).tickSizeOuter(0).tickFormat(context.language.formats.r2);
this.pctAxis = d3.axisBottom(this.pctScale).tickSizeOuter(0).tickFormat(context.language.formats.rPct);
this._updateDimensions = this._updateDimensions.bind(this);
this._updateScales = this._updateScales.bind(this);
@@ -186,9 +186,9 @@ export default class PowerBands extends TranslatedComponent {
let { wattScale, pctScale, context, props, state } = this;
let { translate, formats } = context.language;
let { f2, pct1, rPct, r2 } = formats; // wattFmt, pctFmt, pctAxis, wattAxis
let { available, bands, width } = props;
let { innerWidth, maxPwr, ret, dep } = state;
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 deployed = [];
let retracted = [];

View File

@@ -29,7 +29,6 @@ export default class ShipSelector extends TranslatedComponent {
*/
_getShipsMenu() {
const _selectShip = this._selectShip;
const _openMenu = this._openMenu;
let shipList = [];

View File

@@ -1,7 +1,6 @@
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames';
import { SizeMap } from '../shipyard/Constants';
import { Warning } from './SvgIcons';
/**
@@ -23,7 +22,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
let translate = language.translate;
let u = language.units;
let formats = language.formats;
let { time, int, round, f1, f2, pct } = formats;
let { time, int, round, f1, f2 } = formats;
let sgClassNames = cn({ warning: ship.findInternalByGroup('sg') && !ship.shield, muted: !ship.findInternalByGroup('sg') });
let sgRecover = '-';
let sgRecharge = '-';

View File

@@ -3,8 +3,6 @@ import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames';
import AvailableModulesMenu from './AvailableModulesMenu';
import ModificationsMenu from './ModificationsMenu';
import { Modifications } from 'coriolis-data/dist';
import { ListModifications } from './SvgIcons';
import { diffDetails } from '../utils/SlotFunctions';
import { wrapCtxMenu } from '../utils/UtilityFunctions';
import { stopCtxPropagation } from '../utils/UtilityFunctions';

View File

@@ -2,7 +2,6 @@ import React from 'react';
import cn from 'classnames';
import Persist from '../stores/Persist';
import TranslatedComponent from './TranslatedComponent';
import { jumpRange } from '../shipyard/Calculations';
import { diffDetails } from '../utils/SlotFunctions';
import AvailableModulesMenu from './AvailableModulesMenu';
import ModificationsMenu from './ModificationsMenu';

View File

@@ -3,7 +3,6 @@ import cn from 'classnames';
import SlotSection from './SlotSection';
import StandardSlot from './StandardSlot';
import Module from '../shipyard/Module';
import { diffDetails } from '../utils/SlotFunctions';
import * as ShipRoles from '../shipyard/ShipRoles';
import { stopCtxPropagation } from '../utils/UtilityFunctions';

View File

@@ -99,11 +99,11 @@ export default class UtilitySlotSection extends SlotSection {
</ul>
<div className='select-group cap'>{translate('sb')}</div>
<ul>
<li className='c' onClick={_use.bind(this, 'sb', 'E', null)}>E</li>
<li className='c' onClick={_use.bind(this, 'sb', 'D', null)}>D</li>
<li className='c' onClick={_use.bind(this, 'sb', 'C', null)}>C</li>
<li className='c' onClick={_use.bind(this, 'sb', 'B', null)}>B</li>
<li className='c' onClick={_use.bind(this, 'sb', 'A', null)}>A</li>
<li className='c' onClick={_use.bind(this, 'sb', 'B', null)}>B</li>
<li className='c' onClick={_use.bind(this, 'sb', 'C', null)}>C</li>
<li className='c' onClick={_use.bind(this, 'sb', 'D', null)}>D</li>
<li className='c' onClick={_use.bind(this, 'sb', 'E', null)}>E</li>
</ul>
<div className='select-group cap'>{translate('hs')}</div>
<ul>

View File

@@ -6,7 +6,7 @@ import * as FR from './fr';
import * as IT from './it';
import * as RU from './ru';
import * as PL from './pl';
import d3 from 'd3';
import * as d3 from 'd3';
let fallbackTerms = EN.terms;
@@ -30,8 +30,9 @@ export function getLanguage(langCode) {
}
let currentTerms = lang.terms;
let d3Locale = d3.locale(lang.formats);
let gen = d3Locale.numberFormat('n');
let d3Locale = d3.formatLocale(lang.formats);
let gen = d3Locale.format('');
const round = function(x, n) { const ten_n = Math.pow(10,n); return Math.round(x * ten_n) / ten_n; };
if(lang === EN) {
translate = (t, x) => { return currentTerms[t + '_' + x] || currentTerms[t] || t; };
@@ -41,18 +42,18 @@ export function getLanguage(langCode) {
return {
formats: {
gen, // General number format (.e.g 1,001,001.1234)
int: d3Locale.numberFormat(',.0f'), // Fixed to 0 decimal places (.e.g 1,001)
f1: d3Locale.numberFormat(',.1f'), // Fixed to 1 decimal place (.e.g 1,001.1)
f2: d3Locale.numberFormat(',.2f'), // Fixed to 2 decimal places (.e.g 1,001.10)
s2: d3Locale.numberFormat('.2s'), // SI Format to 2 decimal places (.e.g 1.1k)
pct: d3Locale.numberFormat('.2%'), // % to 2 decimal places (.e.g 5.40%)
pct1: d3Locale.numberFormat('.1%'), // % to 1 decimal places (.e.g 5.4%)
r1: d3Locale.numberFormat('.1r'), // Rounded to 1 significant number (.e.g 512 => 500, 4.122 => 4)
r2: d3Locale.numberFormat('.2r'), // Rounded to 2 significant numbers (.e.g 512 => 510, 4.122 => 4.1)
rPct: d3.format('%'), // % to 0 decimal places (.e.g 5%)
round1: (d) => gen(d3.round(d, 1)), // Rounded to 0-1 decimal places (.e.g 5.1, 4)
round: (d) => gen(d3.round(d, 2)), // Rounded to 0-2 decimal places (.e.g 5.12, 4.1)
gen, // General number format (.e.g 1,001,001.1234)
int: d3Locale.format(',.0f'), // Fixed to 0 decimal places (.e.g 1,001)
f1: d3Locale.format(',.1f'), // Fixed to 1 decimal place (.e.g 1,001.1)
f2: d3Locale.format(',.2f'), // Fixed to 2 decimal places (.e.g 1,001.10)
s2: d3Locale.format('.2s'), // SI Format to 2 decimal places (.e.g 1.1k)
pct: d3Locale.format('.2%'), // % to 2 decimal places (.e.g 5.40%)
pct1: d3Locale.format('.1%'), // % to 1 decimal places (.e.g 5.4%)
r1: d3Locale.format('.1r'), // Rounded to 1 significant number (.e.g 512 => 500, 4.122 => 4)
r2: d3Locale.format('.2r'), // Rounded to 2 significant numbers (.e.g 512 => 510, 4.122 => 4.1)
rPct: d3Locale.format('.0%'), // % to 0 decimal places (.e.g 5%)
round1: (d) => gen(round(d, 1)), // Round to 0-1 decimal places (e.g. 5.1, 4)
round: (d) => gen(round(d, 2)), // Rounded to 0-2 decimal places (.e.g 5.12, 4.1)
time: (d) => (d < 0 ? '-' : '') + Math.floor(Math.abs(d) / 60) + ':' + ('00' + Math.floor(Math.abs(d) % 60)).substr(-2, 2)
},
translate,

View File

@@ -99,8 +99,8 @@ export const terms = {
// Items on the outfitting page
// Notification of restricted slot
emptyrestricted: 'empty (restricted)',
'damage dealt against': 'Damage dealt against',
'damage received by': 'Damage received by',
'damage dealt to': 'Damage dealt to',
'damage received from': 'Damage received from',
'against shields': 'Against shields',
'against hull': 'Against hull',
// 'ammo' was overloaded for outfitting page and modul info, so changed to ammunition for outfitting page
@@ -212,6 +212,8 @@ Once you have a working companion API connection go to the &apos;Shipyard&apos;
Note that Internet Explorer and Edge might not import correctly, due to their internal restrictions on URL length. If you find that this is the case then please change your default browser to Chrome. </p>
Also, the imported information does not provide any data on the power priority or enabled status of your cargo hatch. Coriolis sets this item to have a power priority of "5" and to be disabled by default. You can change this after import in the Power Management section. </p>
<h2>Importing Your Ship From EDMC</h2>
To import your ship from EDMC once your connection to the Frontier servers&apos; companion API is working go to &apos;Settings -&gt;Configuration&apos; and set the &apos;Preferred Shipyard&apos; to &apos;Coriolis&apos;. Once this is set up clicking on your ship in the main window will open your default web browser in Coriolis with the ship&apos;s build.</p>
@@ -238,16 +240,6 @@ To move a module from one slot to another drag it. If you instead want to copy
Clicking on the headings for each set of modules gives you the ability to either select an overall role for your ship (when clicking the core internal header) or a specific module with which you want to fill all applicable slots (when clicking the other headers). </p>
<h2>Power Management</h2>
The power management panel provides information about power usage and priorities. It allows you to enable and disable individual modules, as well as set power priorities for each module.</p>
<h2>Costs</h2>
The costs panel provides information about the costs for each of your modules, and the total cost and insurance for your build. By default Coriolis uses the standard costs, however discounts for your ship, modules and insurance can be altered in the &apos;Settings&apos; at the top-right of the page.</p>
The retrofit costs provides information about the costs of changing the base build for your ship, or your saved build, to the current build.</p>
The reload costs provides information about the costs of reloading your current build.</p>
<h2>Offence Summary</h2>
The offence summary panel provides information about the damage that you deal with your weapons.</p>
@@ -279,15 +271,25 @@ Along the top of this panel are the number of pips you put in to your ENG capaci
<dt>Yaw</dt><dd>The fastest the ship can turn its nose left or right, in degrees per second</dd>
</dl>
<h2>Power Management</h2>
The power management panel provides information about power usage and priorities. It allows you to enable and disable individual modules, as well as set power priorities for each module.</p>
<h2>Costs</h2>
The costs panel provides information about the costs for each of your modules, and the total cost and insurance for your build. By default Coriolis uses the standard costs, however discounts for your ship, modules and insurance can be altered in the &apos;Settings&apos; at the top-right of the page.</p>
The retrofit costs provides information about the costs of changing the base build for your ship, or your saved build, to the current build.</p>
The reload costs provides information about the costs of reloading your current build.</p>
<h2>Jump Range</h2>
The jump range panel provides information about the build&apos; jump range. The graph shows how the build&apos;s jump range changes with the amount of cargo on-board. The slider can be altered to change the amount of fuel you have on-board.
The jump range panel provides information about the build&apos; jump range. The graph shows how the build&apos;s jump range changes with the amount of cargo on-board. The slider can be altered to change the amount of fuel you have on-board.</p>
<h2>Damage Dealt</h2>
The damage dealt panel provides information about the effectiveness of your build&apos;s weapons against opponents&apos; shields and hull at different engagement distances.</p>
The ship against which you want to check damage dealt can be selected by clicking on the red ship icon or the red ship name at the top of this panel.</p>
The main section of this panel is a table showing your weapons and their effectiveness. Effectiveness against shields takes in to account the weapon and its engagement range, but does not consider the target ship&apos;s resistances. Effectiveness against hull takes in to account the weapon and, its engagement range and the target&apos;s hardness, but does not consider the target ship&apos;s resistances. This is because resistances will alter significantly depending on the target&apos;s build.</p>
The main section of this panel is a table showing your weapons and their effectiveness. Effectiveness against shields takes in to account the weapon and its engagement range, and assumes standard shield resistances. Effectiveness against hull takes in to account the weapon and, its engagement range and the target&apos;s hardness, and assumes military grade armour resistances.</p>
Effective DPS and effective SDPS are the equivalent of DPS and SDPS for the weapon. Effectiveness is a percentage value that shows how effective the DPS of the weapon is compared in reality against the given target compared to the weapon&apos;s stated DPS. Effectiveness can never go above 100%.</p>
@@ -295,7 +297,9 @@ Total effective DPS, SDPS and effectiveness against both shields and hull are pr
At the bottom of this panel you can change your engagement range. The engagement range is the distance between your ship and your target. Many weapons suffer from what is known as damage falloff, where their effectiveness decreases the further the distance between your ship and your target. This allows you to model the effect of engaging at different ranges.
Note that this panel only shows enabled weapons, so if you want to see your overall effectiveness for a subset of your weapons you can disable the undesired weapons in the power management panel.
Note that this panel only shows enabled weapons, so if you want to see your overall effectiveness for a subset of your weapons you can disable the undesired weapons in the power management panel.</p>
At the bottom of this panel are two graphs showing how your sustained DPS changes with engagement range. This shows at a glance how effective each weapon is at different distances.</p>
<h2>Damage Received</h2>
The damage received panel provides information about the effectiveness of your build&apos;s defences against opponent&apos;s weapons at different engagement range. Features and functions are the same as the damage dealt panel, except that it does take in to account your build&apos;s resistances.</p>

View File

@@ -239,7 +239,8 @@ export default class OutfittingPage extends Page {
if (elem) {
this.setState({
chartWidth: findDOMNode(this.refs.chartThird).offsetWidth
thirdChartWidth: findDOMNode(this.refs.chartThird).offsetWidth,
halfChartWidth: findDOMNode(this.refs.chartHalf).offsetWidth
});
}
}
@@ -322,7 +323,7 @@ export default class OutfittingPage extends Page {
let state = this.state,
{ language, termtip, tooltip, sizeRatio, onWindowResize } = this.context,
{ translate, units, formats } = language,
{ ship, code, savedCode, buildName, newBuildName, chartWidth, fuelCapacity, fuelLevel } = state,
{ ship, code, savedCode, buildName, newBuildName, halfChartWidth, thirdChartWidth, fuelCapacity, fuelLevel } = state,
hide = tooltip.bind(null, null),
menu = this.props.currentMenu,
shipUpdated = this._shipUpdated,
@@ -370,25 +371,27 @@ export default class OutfittingPage extends Page {
<InternalSlotSection ship={ship} code={iStr} onChange={shipUpdated} currentMenu={menu} />
<HardpointsSlotSection ship={ship} code={hStr || ''} onChange={shipUpdated} currentMenu={menu} />
<UtilitySlotSection ship={ship} code={hStr || ''} onChange={shipUpdated} currentMenu={menu} />
<PowerManagement ship={ship} code={code || ''} onChange={shipUpdated} />
<CostSection ship={ship} buildName={buildName} code={code || ''} />
<div className='group third'>
<OffenceSummary ship={ship} code={code}/>
</div>
<div className='group third'>
<DefenceSummary ship={ship} code={code}/>
</div>
<div className='group third'>
<MovementSummary ship={ship} code={code}/>
</div>
<PowerManagement ship={ship} code={code || ''} onChange={shipUpdated} />
<CostSection ship={ship} buildName={buildName} code={code || ''} />
<div ref='chartHalf' className='group half' />
<div className='group half' />
<div ref='chartThird' className='group third'>
<h1>{translate('jump range')}</h1>
<LineChart
width={chartWidth}
width={thirdChartWidth}
xMax={ship.cargoCapacity}
yMax={ship.unladenRange}
xUnit={translate('T')}
@@ -423,7 +426,7 @@ export default class OutfittingPage extends Page {
</div>
<div>
<DamageDealt ship={ship} code={code} currentMenu={menu}/>
<DamageDealt ship={ship} code={code} chartWidth={halfChartWidth} currentMenu={menu}/>
</div>
<div>

View File

@@ -1,5 +1,4 @@
import * as ModuleUtils from './ModuleUtils';
import * as _ from 'lodash';
import { Modifications } from 'coriolis-data/dist';
/**

View File

@@ -1,15 +1,10 @@
import { ModuleGroupToName, MountMap, BulkheadNames } from './Constants';
import { Ships } from 'coriolis-data/dist';
import Ship from './Ship';
import * as ModuleUtils from './ModuleUtils';
import * as Utils from '../utils/UtilityFunctions';
import LZString from 'lz-string';
import { outfitURL } from '../utils/UrlGenerators';
const STANDARD = ['powerPlant', 'thrusters', 'frameShiftDrive', 'lifeSupport', 'powerDistributor', 'sensors', 'fuelTank'];
const STANDARD_GROUPS = { 'powerPlant': 'pp', 'thrusters': 't', 'frameShiftDrive': 'fsd', 'lifeSupport': 'ls', 'powerDistributor': 'pd', 'sensors': 's', 'fuelTank': 'ft' };
/**
* Generates ship-loadout JSON Schema standard object
* @param {Object} standard model

View File

@@ -835,7 +835,6 @@ export default class Ship {
*/
setSlotPriority(slot, newPriority) {
if (newPriority >= 0 && newPriority < this.priorityBands.length) {
let oldPriority = slot.priority;
slot.priority = newPriority;
this.updatePowerPrioritesString();
@@ -1437,7 +1436,6 @@ export default class Ship {
let bulkheadMods = new Array();
let bulkheadBlueprint = null;
let bulkheadBlueprintGrade = null;
if (this.bulkheads.m && this.bulkheads.m.mods) {
for (let modKey in this.bulkheads.m.mods) {
// Filter out invalid modifications

View File

@@ -31,20 +31,38 @@ export function multiPurpose(ship, shielded, bulkheadIndex) {
* @param {Object} standardOpts [Optional] Standard module optional overrides
*/
export function trader(ship, shielded, standardOpts) {
let sg = shielded ? ship.getAvailableModules().lightestShieldGenerator(ship.hullMass) : null;
for (let i = ship.internal.length; i--;) {
let slot = ship.internal[i];
if (sg && canMount(ship, slot, 'sg', sg.class)) {
ship.use(slot, sg);
sg = null;
} else {
if (canMount(ship, slot, 'cr')) {
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
let usedSlots = [],
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
// Shield generator if required
if (shielded) {
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class)
.sort((a,b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
for (let i = 0; i < shieldInternals.length; i++) {
if (canMount(ship, shieldInternals[i], 'sg')) {
ship.use(shieldInternals[i], sg);
usedSlots.push(shieldInternals[i]);
break;
}
}
}
// Fill the empty internals with cargo racks
for (let i = ship.internal.length; i--;) {
let slot = ship.internal[i];
if (usedSlots.indexOf(slot) == -1 && canMount(ship, slot, 'cr')) {
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
}
}
// Empty the hardpoints
for (let s of ship.hardpoints) {
ship.use(s, null);
}
ship.useLightestStandard(standardOpts);
}
@@ -55,53 +73,102 @@ export function trader(ship, shielded, standardOpts) {
*/
export function explorer(ship, planetary) {
let standardOpts = { ppRating: 'A' },
intLength = ship.internal.length,
heatSinkCount = 2, // Fit 2 heat sinks if possible
afmUnitCount = 2, // Fit 2 AFM Units if possible
shieldNext = planetary,
usedSlots = [],
sgSlot,
fuelScoopSlot,
pvhSlot,
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
if (!planetary) { // Non-planetary explorers don't really need to boost
standardOpts.pd = '1D';
}
ship.setSlotEnabled(ship.cargoHatch, false)
.use(ship.internal[--intLength], ModuleUtils.internal('2f')); // Advanced Discovery Scanner
// Cargo hatch can be disabled
ship.setSlotEnabled(ship.cargoHatch, false);
if (!planetary || intLength > 3) { // Don't mount a DDS on planetary explorer ships too small for both a PVH and DDS
ship.use(ship.internal[--intLength], ModuleUtils.internal('2i')); // Detailed Surface Scanner
// Advanced Discovery Scanner - class 1 or higher
const adsOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const adsInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sc)
.sort((a,b) => adsOrder.indexOf(a.maxClass) - adsOrder.indexOf(b.maxClass));
for (let i = 0; i < adsInternals.length; i++) {
if (canMount(ship, adsInternals[i], 'sc')) {
ship.use(adsInternals[i], ModuleUtils.internal('2f'));
usedSlots.push(adsInternals[i]);
break;
}
}
for (let i = 0; i < intLength; i++) {
let slot = ship.internal[i];
let nextSlot = (i + 1) < intLength ? ship.internal[i + 1] : null;
// Fit best possible Fuel Scoop
if (!fuelScoopSlot && canMount(ship, slot, 'fs')) {
fuelScoopSlot = slot;
ship.use(slot, ModuleUtils.findInternal('fs', slot.maxClass, 'A'));
ship.setSlotEnabled(slot, true);
// Mount a Shield generator if possible AND an AFM Unit has been mounted already (Guarantees at least 1 AFM Unit)
} else if (!sgSlot && shieldNext && canMount(ship, slot, 'sg', sg.class) && !canMount(ship, nextSlot, 'sg', sg.class)) {
sgSlot = slot;
shieldNext = false;
ship.use(slot, sg);
ship.setSlotEnabled(slot, true);
// if planetary explorer and the next slot cannot mount a PVH or the next modul to mount is a SG
} else if (planetary && !pvhSlot && canMount(ship, slot, 'pv') && (shieldNext || !canMount(ship, nextSlot, 'pv', 2))) {
pvhSlot = slot;
ship.use(slot, ModuleUtils.findInternal('pv', Math.min(Math.floor(pvhSlot.maxClass / 2) * 2, 6), 'G'));
ship.setSlotEnabled(slot, false); // Disabled power for PVH
shieldNext = !sgSlot;
} else if (afmUnitCount > 0 && canMount(ship, slot, 'am')) {
afmUnitCount--;
ship.use(slot, ModuleUtils.findInternal('am', slot.maxClass, 'A'));
ship.setSlotEnabled(slot, false); // Disabled power for AFM Unit
shieldNext = !sgSlot;
} else {
ship.use(slot, null);
if (planetary) {
// Planetary Vehicle Hangar - class 2 or higher
const pvhOrder = [2, 3, 4, 5, 6, 7, 8, 1];
const pvhInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.pv)
.sort((a,b) => pvhOrder.indexOf(a.maxClass) - pvhOrder.indexOf(b.maxClass));
for (let i = 0; i < pvhInternals.length; i++) {
if (canMount(ship, pvhInternals[i], 'pv')) {
// Planetary Vehical Hangar only has even classes
const pvhClass = pvhInternals[i].maxClass % 2 === 1 ? pvhInternals[i].maxClass - 1 : pvhInternals[i].maxClass;
ship.use(pvhInternals[i], ModuleUtils.findInternal('pv', pvhClass, 'G')); // G is lower mass
ship.setSlotEnabled(pvhInternals[i], false); // Disable power for Planetary Vehical Hangar
usedSlots.push(pvhInternals[i]);
break;
}
}
}
// Shield generator
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class)
.sort((a,b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
for (let i = 0; i < shieldInternals.length; i++) {
if (canMount(ship, shieldInternals[i], 'sg')) {
ship.use(shieldInternals[i], sg);
usedSlots.push(shieldInternals[i]);
sgSlot = shieldInternals[i];
break;
}
}
// Detailed Surface Scanner
const dssOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const dssInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sc)
.sort((a,b) => dssOrder.indexOf(a.maxClass) - dssOrder.indexOf(b.maxClass));
for (let i = 0; i < dssInternals.length; i++) {
if (canMount(ship, dssInternals[i], 'sc')) {
ship.use(dssInternals[i], ModuleUtils.internal('2i'));
usedSlots.push(dssInternals[i]);
break;
}
}
// Fuel scoop - best possible
const fuelScoopOrder = [8, 7, 6, 5, 4, 3, 2, 1];
const fuelScoopInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.fs)
.sort((a,b) => fuelScoopOrder.indexOf(a.maxClass) - fuelScoopOrder.indexOf(b.maxClass));
for (let i = 0; i < fuelScoopInternals.length; i++) {
if (canMount(ship, fuelScoopInternals[i], 'fs')) {
ship.use(fuelScoopInternals[i], ModuleUtils.findInternal('fs', fuelScoopInternals[i].maxClass, 'A'));
usedSlots.push(fuelScoopInternals[i]);
fuelScoopSlot = fuelScoopInternals[i];
break;
}
}
// AFMUs - fill as they are 0-weight
const afmuOrder = [8, 7, 6, 5, 4, 3, 2, 1];
const afmuInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.pc)
.sort((a,b) => afmuOrder.indexOf(a.maxClass) - afmuOrder.indexOf(b.maxClass));
for (let i = 0; i < afmuInternals.length; i++) {
if (canMount(ship, afmuInternals[i], 'am')) {
ship.use(afmuInternals[i], ModuleUtils.findInternal('am', afmuInternals[i].maxClass, 'A'));
usedSlots.push(afmuInternals[i]);
ship.setSlotEnabled(afmuInternals[i], false); // Disable power for AFM Unit
}
}
@@ -115,7 +182,7 @@ export function explorer(ship, planetary) {
}
}
if (sgSlot) {
if (sgSlot && fuelScoopSlot) {
// The SG and Fuel scoop to not need to be powered at the same time
if (sgSlot.m.getPowerUsage() > fuelScoopSlot.m.getPowerUsage()) { // The Shield generator uses the most power
ship.setSlotEnabled(fuelScoopSlot, false);
@@ -173,7 +240,7 @@ export function miner(ship, shielded) {
if (shielded) {
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.pc)
.filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class)
.sort((a,b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
for (let i = 0; i < shieldInternals.length; i++) {

View File

@@ -243,7 +243,6 @@ export function shipFromJson(json) {
let internalSlotNum = 1;
let militarySlotNum = 1;
for (let i in shipTemplate.slots.internal) {
const internalClassNum = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].class : shipTemplate.slots.internal[i];
const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name == 'Military' : false;
// The internal slot might be a standard or a military slot. Military slots have a different naming system

View File

@@ -143,13 +143,7 @@ export function diffDetails(language, m, mm) {
let { formats, translate, units } = language;
let propDiffs = [];
let mCost = m.cost || 0;
let mmCost = mm ? mm.cost : 0;
if (mCost != mmCost) propDiffs.push(<div key='cost'>{translate('cost')}: <span className={diffClass(mCost, mmCost, true) }>{mCost ? Math.round(mCost * (1 - Persist.getModuleDiscount())) : 0}{units.CR}</span></div>);
let mMass = m.mass || 0;
let mmMass = mm ? mm.getMass() : 0;
if (mMass != mmMass) propDiffs.push(<div key='mass'>{translate('mass')}: <span className={diffClass(mMass, mmMass, true)}>{diff(formats.round, mMass, mmMass)}{units.T}</span></div>);
// Module-specific items
if (m.grp === 'pp') {
let mPowerGeneration = m.pgen || 0;
@@ -157,7 +151,7 @@ export function diffDetails(language, m, mm) {
if (mPowerGeneration != mmPowerGeneration) propDiffs.push(<div key='pgen'>{translate('pgen')}: <span className={diffClass(mPowerGeneration, mmPowerGeneration)}>{diff(formats.round, mPowerGeneration, mmPowerGeneration)}{units.MJ}</span></div>);
} else {
let mPowerUsage = m.power || 0;
let mmPowerUsage = mm ? mm.getPowerUsage() : 0;
let mmPowerUsage = mm ? mm.getPowerUsage() || 0 : 0;
if (mPowerUsage != mmPowerUsage) propDiffs.push(<div key='power'>{translate('power')}: <span className={diffClass(mPowerUsage, mmPowerUsage, true)}>{diff(formats.round, mPowerUsage, mmPowerUsage)}{units.MJ}</span></div>);
}
@@ -187,6 +181,20 @@ export function diffDetails(language, m, mm) {
propDiffs.push(<div key='shields'>{translate('shields')}: <span className={sgDiffClass}>{diff(formats.int, newShield, shield)}{units.MJ}</span></div>);
}
if (m.grp === 'mrp') {
let mProtection = m.protection;
let mmProtection = mm ? mm.getProtection() || 0 : 0;
if (mProtection != mmProtection) {
propDiffs.push(<div key='protection'>{translate('protection')}: <span className={diffClass(mmProtection, mProtection, true)}>{diff(formats.pct, mProtection, mmProtection)}</span></div>);
}
let mIntegrity = m.integrity;
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>);
}
}
if (m.grp == 'pd') {
propDiffs.push(<div key='wep'>
{`${translate('WEP')}: `}
@@ -208,6 +216,16 @@ export function diffDetails(language, m, mm) {
</div>);
}
// Common items
let mCost = m.cost || 0;
let mmCost = mm ? mm.cost : 0;
if (mCost != mmCost) propDiffs.push(<div key='cost'>{translate('cost')}: <span className={diffClass(mCost, mmCost, true) }>{mCost ? Math.round(mCost * (1 - Persist.getModuleDiscount())) : 0}{units.CR}</span></div>);
let mMass = m.mass || 0;
let mmMass = mm ? mm.getMass() : 0;
if (mMass != mmMass) propDiffs.push(<div key='mass'>{translate('mass')}: <span className={diffClass(mMass, mmMass, true)}>{diff(formats.round, mMass, mmMass)}{units.T}</span></div>);
let massDiff = mMass - mmMass;
let mCap = m.fuel || m.cargo || 0;
let mmCap = mm ? mm.fuel || mm.cargo || 0 : 0;

View File

@@ -35,7 +35,8 @@ svg {
}
text {
font-size: 0.8em;
font-size: 1.2em;
font-family: @fStandard;
fill: @primary-disabled;
}
@@ -55,7 +56,7 @@ svg {
}
.label {
font-size: 0.75em;
font-size: 1.1em;
}
.text-tip {

View File

@@ -55,10 +55,23 @@ select {
background-color: @primary-disabled;
}
.select-category {
white-space: nowrap;
line-height: 2em;
font-size: 1.2em;
text-align: center;
margin: 0.5em 0;
padding-left: 5px;
border-top: 3px solid @primary-disabled;
border-bottom: 3px solid @primary-disabled;
overflow: hidden;
text-overflow: ellipsis;
}
.select-group {
white-space: nowrap;
line-height: 1.5em;
text-align: left;
text-align: center;
margin: 0.5em 0;
padding-left: 5px;
border-top: 1px solid @primary-disabled;
@@ -145,15 +158,6 @@ select {
width: 4.5em;
padding: 0.1em 0.2em;
}
ul {
width: 17em;
}
}
&.standard {
ul {
width: 16.25em;
}
}
}

View File

@@ -7,7 +7,7 @@ var ExtractTextPlugin = require("extract-text-webpack-plugin");
var AppCachePlugin = require('appcache-webpack-plugin');
var node_modules_dir = path.resolve(__dirname, 'node_modules');
var d3Path = path.resolve(node_modules_dir, 'd3/d3.min.js');
var d3Path = path.resolve(__dirname, 'd3.min.js');
var reactPath = path.resolve(node_modules_dir, 'react/dist/react.min.js');
var reactDomPath = path.resolve(node_modules_dir, 'react-dom/dist/react-dom.min.js');
var lzStringPath = path.resolve(node_modules_dir, 'lz-string/libs/lz-string.min.js');