diff --git a/ChangeLog.md b/ChangeLog.md index bc86add2..be4421ab 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,21 @@ +#2.2.11 + * Add help system and initial help file + * Make absolute damage visible + * Add 'average' roll for blueprints + * Update spacing for movement summary to make it more readable + * Provide damage dealt statistics for both shields and hull + * Damage dealt panel only shows enabled weapons + * Add engagement range to damage received panel + * Handle burst rate of fire as an absolute number rather than a perentage modification + * Ensure that clip values are always rounded up + * Ensure that focused weapon mod uses range modifier to increase falloff as well + * Use coriolis-data 2.2.11: + * Remove non-existent chaff launcher capacity blueprint grades + * Fix incorrect values for charge enhanced power distributor + * Remove incorrect AFMU blueprints + * Correct fragment cannon Double Shot blueprint information + * Correct Focused weapon blueprint information + #2.2.10 * Fix detailed export of module reinforcement packages * Use damagedist for exact breakdown of weapons that have more than one type of damage diff --git a/__tests__/fixtures/anaconda-test-detailed-export-v4.json b/__tests__/fixtures/anaconda-test-detailed-export-v4.json index 402c67f2..638dfd51 100644 --- a/__tests__/fixtures/anaconda-test-detailed-export-v4.json +++ b/__tests__/fixtures/anaconda-test-detailed-export-v4.json @@ -25,7 +25,7 @@ "priority": 1, "modifications": { "pgen": 1000 - } + } }, "thrusters": { "class": 6, @@ -275,6 +275,9 @@ "totalExplDpe": 0, "totalExplDps": 0, "totalExplSDps": 0, + "totalAbsDpe": 3.57, + "totalAbsDps": 18.78, + "totalAbsSDps": 14.45, "totalHps": 33.62, "totalKinDpe": 117.48, "totalKinDps": 24.94, diff --git a/package.json b/package.json index 880c6da2..c4a26791 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coriolis_shipyard", - "version": "2.2.10", + "version": "2.2.11", "repository": { "type": "git", "url": "https://github.com/EDCD/coriolis" diff --git a/src/app/Coriolis.jsx b/src/app/Coriolis.jsx index 0e796d23..49d76485 100644 --- a/src/app/Coriolis.jsx +++ b/src/app/Coriolis.jsx @@ -6,7 +6,10 @@ import Persist from './stores/Persist'; import Header from './components/Header'; import Tooltip from './components/Tooltip'; +import ModalExport from './components/ModalExport'; +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'; @@ -159,14 +162,25 @@ export default class Coriolis extends React.Component { this._hideModal(); this._closeMenu(); break; + case 72: // 'h' + if (e.ctrlKey || e.metaKey) { // CTRL/CMD + h + e.preventDefault(); + this._showModal(); + } + break; case 73: // 'i' if (e.ctrlKey || e.metaKey) { // CTRL/CMD + i e.preventDefault(); this._showModal(); } break; - case 101010: // 's' - if (e.ctrlKey || e.metaKey) { // CTRL/CMD + i + case 76: // 'l' + if (e.ctrlKey || e.metaKey) { // CTRL/CMD + l + e.preventDefault(); + this._showModal(); + } + case 83: // 's' + if (e.ctrlKey || e.metaKey) { // CTRL/CMD + s e.preventDefault(); this.emitter.emit('command', 'save'); } diff --git a/src/app/components/DamageDealt.jsx b/src/app/components/DamageDealt.jsx index 5d0f27b2..3f06f87d 100644 --- a/src/app/components/DamageDealt.jsx +++ b/src/app/components/DamageDealt.jsx @@ -108,14 +108,17 @@ export default class DamageDealt extends TranslatedComponent { // Track totals let totals = {}; - totals.effectiveness = 0; - totals.effectiveDps = 0; - totals.effectiveSDps = 0; + 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].m) { + if (ship.hardpoints[i].m && ship.hardpoints[i].enabled) { const m = ship.hardpoints[i].m; if (m.getDamage() && m.grp !== 'po') { let dropoff = 1; @@ -134,24 +137,33 @@ export default class DamageDealt extends TranslatedComponent { } } const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`; - const effectiveness = (m.getPiercing() >= against.properties.hardness ? 1 : m.getPiercing() / against.properties.hardness) * dropoff; - const effectiveDps = m.getDps() * effectiveness * dropoff; - const effectiveSDps = (m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectiveness : effectiveDps) * dropoff; - totals.effectiveDps += effectiveDps; - totals.effectiveSDps += effectiveSDps; + const effectivenessShields = dropoff; + const effectiveDpsShields = m.getDps() * effectivenessShields * dropoff; + const effectiveSDpsShields = (m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectivenessShields : effectiveDpsShields) * dropoff; + const effectivenessHull = (m.getPiercing() >= against.properties.hardness ? 1 : m.getPiercing() / against.properties.hardness) * dropoff; + const effectiveDpsHull = m.getDps() * effectivenessHull * dropoff; + const effectiveSDpsHull = (m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectivenessHull : effectiveDpsHull) * dropoff; + 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, - effectiveDps, - effectiveSDps, - effectiveness }); + effectiveDpsShields, + effectiveSDpsShields, + effectivenessShields, + effectiveDpsHull, + effectiveSDpsHull, + effectivenessHull }); } } } - totals.effectiveness = totals.effectiveDps / totalDps; + totals.effectivenessShields = totalDps == 0 ? 0 : totals.effectiveDpsShields / totalDps; + totals.effectivenessHull = totalDps == 0 ? 0 : totals.effectiveDpsHull / totalDps; return { weapons, totals }; } @@ -169,7 +181,7 @@ export default class DamageDealt extends TranslatedComponent { */ _onShipChange(s) { const against = Ships[s]; - const data = this._calcWeapons(this.props.ship, against); + const data = this._calcWeapons(this.props.ship, against, this.state.range * this.state.maxRange); this.setState({ against, weapons: data.weapons, totals: data.totals }); } @@ -201,9 +213,12 @@ export default class DamageDealt extends TranslatedComponent { switch (predicate) { case 'n': comp = comp(null, desc); break; - case 'edps': comp = comp((a, b) => a.effectiveDps - b.effectiveDps, desc); break; - case 'esdps': comp = comp((a, b) => a.effectiveSDps - b.effectiveSDps, desc); break; - case 'e': comp = comp((a, b) => a.effectiveness - b.effectiveness, 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); @@ -232,9 +247,12 @@ export default class DamageDealt extends TranslatedComponent { {weapon.mount == 'T' ? : null} {weapon.classRating} {translate(weapon.name)} - {formats.round1(weapon.effectiveDps)} - {formats.round1(weapon.effectiveSDps)} - {formats.pct(weapon.effectiveness)} + {formats.round1(weapon.effectiveDpsShields)} + {formats.round1(weapon.effectiveSDpsShields)} + {formats.pct(weapon.effectivenessShields)} + {formats.round1(weapon.effectiveDpsHull)} + {formats.round1(weapon.effectiveSDpsHull)} + {formats.pct(weapon.effectivenessHull)} ); } } @@ -271,10 +289,17 @@ export default class DamageDealt extends TranslatedComponent { - - - - + + + + + + + + + + + @@ -283,9 +308,12 @@ export default class DamageDealt extends TranslatedComponent { - - - + + + + + +
{translate('weapon')}{translate('effective dps')}{translate('effective sdps')}{translate('effectiveness')}{translate('weapon')}{translate('shields')}{translate('armour')}
{translate('effective dps')}{translate('effective sdps')}{translate('effectiveness')}{translate('effective dps')}{translate('effective sdps')}{translate('effectiveness')}
{translate('total')}{formats.round1(totals.effectiveDps)}{formats.round1(totals.effectiveSDps)}{formats.pct(totals.effectiveness)}{formats.round1(totals.effectiveDpsShields)}{formats.round1(totals.effectiveSDpsShields)}{formats.pct(totals.effectivenessShields)}{formats.round1(totals.effectiveDpsHull)}{formats.round1(totals.effectiveSDpsHull)}{formats.pct(totals.effectivenessHull)}
diff --git a/src/app/components/DamageReceived.jsx b/src/app/components/DamageReceived.jsx index 3b254025..3d96c7f3 100644 --- a/src/app/components/DamageReceived.jsx +++ b/src/app/components/DamageReceived.jsx @@ -4,6 +4,7 @@ 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 @@ -62,7 +63,9 @@ export default class DamageReceived extends TranslatedComponent { this.state = { predicate: 'n', desc: true, - expanded: false + expanded: false, + range: 0.1667, + maxRange: 6000 }; } @@ -70,7 +73,7 @@ export default class DamageReceived extends TranslatedComponent { * Set the initial weapons state */ componentWillMount() { - this.setState({ weapons: this._calcWeapons(this.props.ship) }); + this.setState({ weapons: this._calcWeapons(this.props.ship, this.state.range * this.state.maxRange) }); } /** @@ -80,27 +83,47 @@ export default class DamageReceived extends TranslatedComponent { * @return {boolean} Returns true if the component should be rerendered */ componentWillReceiveProps(nextProps, nextContext) { - this.setState({ weapons: this._calcWeapons(nextProps.ship) }); + 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) { - let weapons = []; + _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(); - const baseSDps = m.getClip() ? (m.getClip() * baseDps / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : baseDps; + 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; @@ -116,6 +139,7 @@ export default class DamageReceived extends TranslatedComponent { if (m.getDamageDist().A) { effectivenessShields += m.getDamageDist().A; } + effectivenessShields *= dropoff; const effectiveDpsShields = baseDps * effectivenessShields; const effectiveSDpsShields = baseSDps * effectivenessShields; @@ -133,7 +157,7 @@ export default class DamageReceived extends TranslatedComponent { if (m.getDamageDist().A) { effectivenessHull += m.getDamageDist().A; } - effectivenessHull *= Math.min(m.getPiercing() / ship.hardness, 1); + effectivenessHull *= Math.min(m.getPiercing() / ship.hardness, 1) * dropoff; const effectiveDpsHull = baseDps * effectivenessHull; const effectiveSDpsHull = baseSDps * effectivenessHull; @@ -232,14 +256,22 @@ export default class DamageReceived extends TranslatedComponent { 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, tooltip, termtip } = this.context; - const { formats, translate } = language; - const { expanded } = this.state; + 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; @@ -267,6 +299,27 @@ export default class DamageReceived extends TranslatedComponent { {this._renderRows(translate, formats)} + + + + + + + + +
{translate('engagement range')} + + + {formats.f2(range * maxRange / 1000)}{units.km} +
: null } ); diff --git a/src/app/components/HardpointSlot.jsx b/src/app/components/HardpointSlot.jsx index f6232568..826bc7c2 100644 --- a/src/app/components/HardpointSlot.jsx +++ b/src/app/components/HardpointSlot.jsx @@ -1,7 +1,7 @@ import React from 'react'; import Slot from './Slot'; import Persist from '../stores/Persist'; -import { DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret, ListModifications, Modified } from './SvgIcons'; +import { DamageAbsolute, DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret, ListModifications, Modified } from './SvgIcons'; import { Modifications } from 'coriolis-data/dist'; import { stopCtxPropagation } from '../utils/UtilityFunctions'; @@ -62,6 +62,7 @@ export default class HardpointSlot extends Slot { {m.getDamageDist() && m.getDamageDist().K ? : ''} {m.getDamageDist() && m.getDamageDist().T ? : ''} {m.getDamageDist() && m.getDamageDist().E ? : ''} + {m.getDamageDist() && m.getDamageDist().A ? : ''} {classRating} {translate(m.name || m.grp)}{ m.mods && Object.keys(m.mods).length > 0 ? : null } @@ -74,7 +75,7 @@ export default class HardpointSlot extends Slot { { m.getDps() && m.getEps() ?
{translate('DPE')}: {formats.f1(m.getDps() / m.getEps())}
: null } { m.getRoF() ?
{translate('ROF')}: {formats.f1(m.getRoF())}{u.ps}
: null } { m.getRange() ?
{translate('range')} {formats.f1(m.getRange() / 1000)}{u.km}
: null } - { m.getFalloff() ?
{translate('falloff')} {formats.f1(m.getFalloff() / 1000)}{u.km}
: null } + { m.getFalloff() ?
{translate('falloff')} {formats.round(m.getFalloff() / 1000)}{u.km}
: null } { m.getShieldBoost() ?
+{formats.pct1(m.getShieldBoost())}
: null } { m.getAmmo() ?
{translate('ammunition')}: {formats.int(m.getClip())}/{formats.int(m.getAmmo())}
: null } { m.getShotSpeed() ?
{translate('shotspeed')}: {formats.int(m.getShotSpeed())}{u.mps}
: null } diff --git a/src/app/components/Header.jsx b/src/app/components/Header.jsx index 8b679a6b..ff880f1b 100644 --- a/src/app/components/Header.jsx +++ b/src/app/components/Header.jsx @@ -5,12 +5,13 @@ import { Insurance } from '../shipyard/Constants'; import Link from './Link'; import ActiveLink from './ActiveLink'; import cn from 'classnames'; -import { Cogs, CoriolisLogo, Hammer, Rocket, StatsBars } from './SvgIcons'; +import { Cogs, CoriolisLogo, Hammer, Help, Rocket, StatsBars } from './SvgIcons'; import { Ships } from 'coriolis-data/dist'; import Persist from '../stores/Persist'; import { toDetailedExport } from '../shipyard/Serializer'; import ModalDeleteAll from './ModalDeleteAll'; import ModalExport from './ModalExport'; +import ModalHelp from './ModalHelp'; import ModalImport from './ModalImport'; import Slider from './Slider'; import { outfitURL } from '../utils/UrlGenerators'; @@ -53,11 +54,11 @@ function selectAll(e) { */ export default class Header extends TranslatedComponent { - /** - * Constructor - * @param {Object} props React Component properties - * @param {Object} context React Component context - */ + /** + * Constructor + * @param {Object} props React Component properties + * @param {Object} context React Component context + */ constructor(props, context) { super(props); this.shipOrder = Object.keys(Ships).sort(); @@ -74,6 +75,7 @@ export default class Header extends TranslatedComponent { this._openBuilds = this._openMenu.bind(this, 'b'); this._openComp = this._openMenu.bind(this, 'comp'); this._openSettings = this._openMenu.bind(this, 'settings'); + this._showHelp = this._showHelp.bind(this); this.languageOptions = []; this.insuranceOptions = []; this.state = { @@ -248,6 +250,17 @@ export default class Header extends TranslatedComponent { />); } + /** + * Show help modal + * @param {SyntheticEvent} e Event + */ + _showHelp(e) { + let translate = this.context.language.translate; + e.preventDefault(); + + this.context.showModal(); + } + /** * Show import modal * @param {SyntheticEvent} e Event @@ -521,6 +534,12 @@ export default class Header extends TranslatedComponent { {openedMenu == 'settings' ? this._getSettingsMenu() : null} + +
+
+ +
+
); } diff --git a/src/app/components/ModalHelp.jsx b/src/app/components/ModalHelp.jsx new file mode 100644 index 00000000..89b2454a --- /dev/null +++ b/src/app/components/ModalHelp.jsx @@ -0,0 +1,48 @@ +/* eslint react/no-danger: 0 */ +import React from 'react'; +import { findDOMNode } from 'react-dom'; +import TranslatedComponent from './TranslatedComponent'; + +/** + * Help Modal + */ +export default class ModalHelp extends TranslatedComponent { + + static propTypes = { + title: React.PropTypes.string + }; + + /** + * Constructor + * @param {Object} props React Component properties + */ + constructor(props) { + super(props); + } + + /** + * Focus on textarea and select all + */ + componentDidMount() { + const e = findDOMNode(this.refs.exportField); + if (e) { + e.focus(); + e.select(); + } + } + + /** + * Render the modal + * @return {React.Component} Modal Content + */ + render() { + const translate = this.context.language.translate; + const text = translate('HELP_TEXT'); + + return
e.stopPropagation() }> +

{translate(this.props.title || 'Help')}

+
+ +
; + } +} diff --git a/src/app/components/Modification.jsx b/src/app/components/Modification.jsx index e6d2bc29..c999ff35 100644 --- a/src/app/components/Modification.jsx +++ b/src/app/components/Modification.jsx @@ -72,7 +72,7 @@ export default class Modification extends TranslatedComponent { let symbol; if (name === 'jitter') { symbol = '°'; - } else if (name !== 'burst') { + } else if (name !== 'burst' && name != 'burstrof') { symbol = '%'; } if (symbol) { diff --git a/src/app/components/ModificationsMenu.jsx b/src/app/components/ModificationsMenu.jsx index 1e05deaa..3e0f558d 100644 --- a/src/app/components/ModificationsMenu.jsx +++ b/src/app/components/ModificationsMenu.jsx @@ -31,6 +31,7 @@ export default class ModificationsMenu extends TranslatedComponent { this._toggleBlueprintsMenu = this._toggleBlueprintsMenu.bind(this); this._rollWorst = this._rollWorst.bind(this); this._rollRandom = this._rollRandom.bind(this); + this._rollAverage = this._rollAverage.bind(this); this._rollBest = this._rollBest.bind(this); this._reset = this._reset.bind(this); } @@ -133,6 +134,34 @@ export default class ModificationsMenu extends TranslatedComponent { this.props.onChange(); } + /** + * Provide an 'average' roll within the information we have + */ + _rollAverage() { + const { m, ship } = this.props; + const features = m.blueprint.features[m.blueprint.grade]; + for (const featureName in features) { + if (Modifications.modifications[featureName].method == 'overwrite') { + ship.setModification(m, featureName, (features[featureName][0] + features[featureName][1]) / 2); + } else { + let value = (features[featureName][0] + features[featureName][1]) / 2; + if (m.grp == 'sb' && featureName == 'shieldboost') { + // Shield boosters are a special case. Their boost is dependent on their base so we need to calculate the value here + value = ((1 + m.shieldboost) * (1 + value) - 1) / m.shieldboost - 1; + } + + if (Modifications.modifications[featureName].type == 'percentage') { + ship.setModification(m, featureName, value * 10000); + } else if (Modifications.modifications[featureName].type == 'numeric') { + ship.setModification(m, featureName, value * 100); + } + } + } + + this.setState({ modifications: this._setModifications(this.props) }); + this.props.onChange(); + } + /** * Provide a random roll within the information we have */ @@ -215,6 +244,7 @@ export default class ModificationsMenu extends TranslatedComponent { const _toggleBlueprintsMenu = this._toggleBlueprintsMenu; const _rollBest = this._rollBest; const _rollWorst = this._rollWorst; + const _rollAverage = this._rollAverage; const _rollRandom = this._rollRandom; const _reset = this._reset; @@ -241,8 +271,9 @@ export default class ModificationsMenu extends TranslatedComponent { { translate('roll') }: { translate('worst') } - { translate('random') } + { translate('average') } { translate('best') } + { translate('random') } { translate('reset') } diff --git a/src/app/components/MovementSummary.jsx b/src/app/components/MovementSummary.jsx index f5b31699..4b6f25df 100644 --- a/src/app/components/MovementSummary.jsx +++ b/src/app/components/MovementSummary.jsx @@ -1,7 +1,6 @@ import React from 'react'; import cn from 'classnames'; import TranslatedComponent from './TranslatedComponent'; -import { DamageKinetic, DamageThermal, DamageExplosive } from './SvgIcons'; /** * Movement summary @@ -33,7 +32,7 @@ export default class MovementSummary extends TranslatedComponent { return (

{translate('movement summary')}

- +
diff --git a/src/app/components/OffenceSummary.jsx b/src/app/components/OffenceSummary.jsx index 9123ce5b..1aac8b8c 100644 --- a/src/app/components/OffenceSummary.jsx +++ b/src/app/components/OffenceSummary.jsx @@ -1,7 +1,7 @@ import React from 'react'; import cn from 'classnames'; import TranslatedComponent from './TranslatedComponent'; -import { DamageKinetic, DamageThermal, DamageExplosive } from './SvgIcons'; +import { DamageAbsolute, DamageKinetic, DamageThermal, DamageExplosive } from './SvgIcons'; /** * Offence summary @@ -34,30 +34,33 @@ export default class OffenceSummary extends TranslatedComponent {
 
- + + - + + - + + diff --git a/src/app/components/SvgIcons.jsx b/src/app/components/SvgIcons.jsx index 6093b050..34d84118 100644 --- a/src/app/components/SvgIcons.jsx +++ b/src/app/components/SvgIcons.jsx @@ -319,6 +319,24 @@ export class Warning extends SvgIcon { } } +/** + * Absolute damage + */ +export class DamageAbsolute extends SvgIcon { + /** + * Overriden view box + * @return {String} view box + */ + viewBox() { return '0 0 200 200'; } + /** + * Generate the SVG + * @return {React.Component} SVG Contents + */ + svg() { + return ; + } +} + /** * Thermal damage */ @@ -534,6 +552,25 @@ export class Rocket extends SvgIcon { } } +/** + * Help + */ +export class Help extends SvgIcon { + /** + * Overriden view box + * @return {String} view box + */ + viewBox() { return '0 0 200 200'; } + + /** + * Generate the SVG + * @return {React.Component} SVG Contents + */ + svg() { + return ; + } +} + /** * ListModifications (engineers) */ diff --git a/src/app/i18n/en.js b/src/app/i18n/en.js index 03c96387..8dd9f0ca 100644 --- a/src/app/i18n/en.js +++ b/src/app/i18n/en.js @@ -31,6 +31,7 @@ export const terms = { PHRASE_ENGAGEMENT_RANGE: 'The distance between your ship and its target', PHRASE_SELECT_BLUEPRINT: 'Click to select a blueprint', PHRASE_BLUEPRINT_WORST: 'Worst primary values for this blueprint', + PHRASE_BLUEPRINT_AVERAGE: 'Average primary values for this blueprint', PHRASE_BLUEPRINT_RANDOM: 'Random selection between worst and best primary values for this blueprint', PHRASE_BLUEPRINT_BEST: 'Best primary values for this blueprint', PHRASE_BLUEPRINT_RESET: 'Remove all modifications and blueprint', @@ -109,6 +110,7 @@ export const terms = { // Blueprint rolls worst: 'Worst', + average: 'Average', random: 'Random', best: 'Best', reset: 'Reset', @@ -182,4 +184,130 @@ export const terms = { thermres: 'Thermal resistance', wepcap: 'Weapons capacity', weprate: 'Weapons recharge rate', + + // Help text + HELP_TEXT: ` +

Introduction

+Coriolis is a ship builder for Elite: Dangerous. This help file provides you with the information you need to use Coriolis. + +

Importing Your Ship Into Coriolis

+Often, you will want to start with your existing ship in Coriolis and see how particular changes might affect it, for example upgrading your FSD. There are a number of tools that can be used to import your ship without you having to create it manually. This has the added benefit of copying over any engineering modifications that have taken place as well.

+ +

Importing Your Ship From EDDI

+To import your ship from EDDI first ensure that your connection to the Frontier servers' companion API is working. To do this check the 'Companion App' tab where you should see "Your connection to the companion app is operational". If not then follow the instructions in the companion app tab in EDDI to connect to the Frontier servers.

+ +Once you have a working companion API connection go to the 'Shipyard' tab. At the right-hand side of each ship is an 'Export to Coriolis' button that will open your default web browser in Coriolis with the ship's build.

+ +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.

+ +

Importing Your Ship From EDMC

+To import your ship from EDMC once your connection to the Frontier servers' companion API is working go to 'Settings ->Configuration' and set the 'Preferred Shipyard' to 'Coriolis'. Once this is set up clicking on your ship in the main window will open your default web browser in Coriolis with the ship's build.

+ +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.

+ +

Understanding And Using The Outfitting Panels

+The outfitting page is where you will spend most of your time, and contains the information for your ship. Information on each of the panels is provided below.

+ +

Key Values

+Along the top of the screen are some of the key values for your build. This is a handy reference for the values, but more information is provided for the values in the further panels.

+ +Here, along with most places in Coriolis, acronyms will have tooltips explaining what they mean. Hover over the acronym to obtain more detail, or look in the glossary at the end of this help.

+ +

Modules

+The next set of panels laid out horizontally across the screen contain the modules you have put in your build. From left to right these are the core modules, the internal modules, the hardpoints and the utility mounts. These represent the available slots in your ship and cannot be altered. Each slot has a class, or size, and in general any module up to a given size can fit in a given slot (exceptions being bulkheads, life support and sensors in core modules and restricted internal slots, which can only take a subset of module depending on their restrictions).

+ +To add a module to a slot left-click on the slot and select the required module. Only the modules capable of fitting in the selected slot will be shown.

+ +To remove a module from a slot right-click on the module.

+ +To move a module from one slot to another drag it. If you instead want to copy the module drag it whilst holding down the 'Alt' key.

+ +

Power Management

+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.

+ +

Costs

+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 'Settings' at the top-right of the page.

+ +The retrofit costs provides information about the costs of changing the base build for your ship, or your saved build, to the current build.

+ +The reload costs provides information about the costs of reloading your current build.

+ +

Offence Summary

+The offence summary panel provides information about the damage that you deal with your weapons.

+ +The first headline gives an overall damage per second rating: this is the optimum amount of damage the build will do per second according to weapon statistics. After that is a breakdown of the damage per second the build will do for each type of damage: absolute, explosive, kinetic, and thermal.

+ +The next headline gives an overall sustained damage per second rating: this is the optimum amount of damage the build will do per second over a longer period of time, taking in to account ammunition clip capacities and reload times. After that is a breakdown of the sustained damage per second the build will do for each type of damage: absolute, explosive, kinetic, and thermal.

+ +The final headline gives an overall damage per energy rating: this is the amount of damage the build will do per unit of weapon capacitor energy expended. After that is a breakdown of the damage per energy the build will do for each type of damage: absolute, explosive, kinetic, and thermal.

+ +

Defence Summary

+The defence summary panel provides information about the strength of your defences and the damage that you receive from opponents.

+ +The first headline gives your total shield strength (if you have shields), taking in to account your base shield plus boosters. After that are the details of how long it will take for your shields to recover from 0 to 50% (recovery time) and from 50% to 100% (recharge time). The next line provides a breakdown of the shield damage taken from different damage types. For example, if you damage from kinetic is 60% then it means that a weapon usually dealing 10 points of damage will only deal 6, the rest being resisted by the shield. Note that this does not include any resistance alterations due to pips in your SYS capacitor.

+ +The second headline gives your total shield cell strength (if you have shield cell banks). This is the sum of the recharge of all of equipped shield cell banks.

+ +The third headline gives your total armour strength, taking in to account your base armour plus hull reinforcement packages. The next line provides a breakdown of the hull damage taken from different damage types. For example, if you damage from kinetic is 120% then it means that a weapon usually dealing 10 points of damage will deal 12.

+ +The fourth headline gives your total module protection strength from module reinforcement packages. The next line provides a breakdown of the protection for both internal and external modules whilst all module reinforcement packages are functioning. For example, if external module protection is 20% then 10 points of damage will 2 points of damage to the module reinforcement packages and 8 points of damage to the module

+ +

Movement Summary

+The movement summary panel provides information about the build's speed and agility.

+ +Along the top of this panel are the number of pips you put in to your ENG capacitor, from 0 to 4 and also include 4 pips and boost (4b). Along the side of this panel are the names of the metrics. These are: +
+
Speed
The fastest the ship can move, in metres per second
+
Pitch
The fastest the ship can raise or lower its nose, in degrees per second
+
Roll
The fastest the ship can roll its body, in degrees per second
+
Yaw
The fastest the ship can turn its nose left or right, in degrees per second
+
+ +

Jump Range

+The jump range panel provides information about the build' jump range. The graph shows how the build'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. + +

Damage Dealt

+The damage dealt panel provides information about the effectiveness of your build's weapons against opponents' shields and hull at different engagement distances.

+ +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.

+ +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's resistances. Effectiveness against hull takes in to account the weapon and, its engagement range and the target's hardness, but does not consider the target ship's resistances. This is because resistances will alter significantly depending on the target's build.

+ +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's stated DPS. Effectiveness can never go above 100%.

+ +Total effective DPS, SDPS and effectiveness against both shields and hull are provided at the bottom of the table.

+ +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. + +

Damage Received

+The damage received panel provides information about the effectiveness of your build's defences against opponent'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's resistances.

+ +

Keyboard Shortcuts

+
+
Ctrl-e
open export dialogue (outfitting page only)
+
Ctrl-h
open help dialogue
+
Ctrl-i
open import dialogue
+
Ctrl-l
open shortlink dialogue
+
Esc
close any open dialogue
+
+

Glossary

+
+
Absolute damage
A type of damage, without any protection. Absolute damage is always dealt at 100% regardless of if the damage is to shields, hull or modules, and irrespective of resistances
+
DPS
Damage per second; the amount of damage that a weapon or a ship can deal per second to a target under optimum conditions
+
EPS
Energy per second; the amount of energy that a weapon or a ship drains from the weapons capacitor per second when firing
+
HPS
Heat per second; the amount of heat that a weapon or a ship generates per second when firing
+
Effectivness
A comparison of the maximum DPS of a given weapon to the actual DPS of the given weapon in a specific situation. DPS can be reduced by range to the target, the target's hull and shield resistances, and the target's hardness
+
Explosive damage
A type of damage, protected against by explosive resistance
+
Hardness
The inherent resistance to damage of a ship's hull. Hardness is defined on a per-ship basis and there is currently nothing that can be done to change it. Hardness of a ship's hull is compared to the piercing of weapons: if piercing is higher than hardness the weapon does 100% damage, otherwise it does a fraction of its damage calculated as piercing/hardness
+
Falloff
The distance at which a weapons starts to do less damage than its stated DPS
+
Kinetic damage
A type of damage, protected against by kinetic resistance
+
SDPS
Sustained damage per second; the amount of damage that a weapon or a ship can deal per second to a target, taking in to account ammunition reload
+
SEPS
Sustained energy per second; the amount of energy that a weapon or a ship drains from the weapons capacitor per second when firing, taking in to account ammunition reload
+
SHPS
Sustained heat per second; the amount of heat that a weapon or a ship generates per second when firing, taking in to account ammunition reload
+
Thermal damage
A type of damage, protected against by thermal resistance
+
+ + `, }; diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx index 1edadf1c..e2345fbc 100644 --- a/src/app/pages/OutfittingPage.jsx +++ b/src/app/pages/OutfittingPage.jsx @@ -52,6 +52,8 @@ export default class OutfittingPage extends Page { constructor(props, context) { super(props, context); this.state = this._initState(context); + this._keyDown = this._keyDown.bind(this); + this._exportBuild = this._exportBuild.bind(this); } /** @@ -181,7 +183,7 @@ export default class OutfittingPage extends Page { let translate = this.context.language.translate; let { buildName, ship } = this.state; this.context.showModal(); @@ -258,6 +260,7 @@ export default class OutfittingPage extends Page { */ componentWillMount() { this.resizeListener = this.context.onWindowResize(this._updateDimensions); + document.addEventListener('keydown', this._keyDown); } /** @@ -281,6 +284,22 @@ export default class OutfittingPage extends Page { this.context.showModal(); } + /** + * Handle Key Down + * @param {Event} e Keyboard Event + */ + _keyDown(e) { + // .keyCode will eventually be replaced with .key + switch (e.keyCode) { + case 69: // 'e' + if (e.ctrlKey || e.metaKey) { // CTRL/CMD + e + e.preventDefault(); + this._exportBuild(); + } + break; + } + } + /** * Render the Page * @return {React.Component} The page contents diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js index 7b58e6b4..4ca5d84d 100755 --- a/src/app/shipyard/Module.js +++ b/src/app/shipyard/Module.js @@ -78,6 +78,7 @@ export default class Module { if (!modification) { return result; } + // We store percentages as decimals, so to get them back we need to divide by 10000. Otherwise // we divide by 100. Both ways we end up with a value with two decimal places let modValue; @@ -292,11 +293,19 @@ export default class Module { */ getFalloff() { if (this.getModValue('fallofffromrange')) { + // Falloff from range means what it says, so use range instead of falloff return this.getRange(); } else { - const falloff = this._getModifiedValue('falloff'); - const range = this.getRange(); - return (falloff > range ? range : falloff); + // Need to find out if we have a focused modification, in which case our falloff is scaled to range + if (this.blueprint && this.blueprint.name === 'Focused') { + const rangeMod = this.getModValue('range') / 10000; + return this.falloff * (1 + rangeMod); + } else { + // Standard falloff calculation + const range = this.getRange(); + const falloff = this._getModifiedValue('falloff'); + return (falloff > range ? range : falloff); + } } } @@ -534,7 +543,10 @@ export default class Module { * @return {Number} the clip size of this module */ getClip() { - return this._getModifiedValue('clip'); + // Clip size is always rounded up + let result = this._getModifiedValue('clip'); + if (result) { result = Math.ceil(result); } + return result; } /** diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index 88ffb9b0..2d2e0f13 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -533,14 +533,17 @@ export default class Ship { this.totalCost = this.m.incCost ? this.m.discountedCost : 0; this.unladenMass = this.hullMass; this.totalDpe = 0; + this.totalAbsDpe = 0; this.totalExplDpe = 0; this.totalKinDpe = 0; this.totalThermDpe = 0; this.totalDps = 0; + this.totalAbsDps = 0; this.totalExplDps = 0; this.totalKinDps = 0; this.totalThermDps = 0; this.totalSDps = 0; + this.totalAbsSDps = 0; this.totalExplSDps = 0; this.totalKinSDps = 0; this.totalThermSDps = 0; @@ -958,14 +961,17 @@ export default class Ship { */ recalculateDps() { let totalDpe = 0; + let totalAbsDpe = 0; let totalExplDpe = 0; let totalKinDpe = 0; let totalThermDpe = 0; let totalDps = 0; + let totalAbsDps = 0; let totalExplDps = 0; let totalKinDps = 0; let totalThermDps = 0; let totalSDps = 0; + let totalAbsSDps = 0; let totalExplSDps = 0; let totalKinSDps = 0; let totalThermSDps = 0; @@ -981,6 +987,11 @@ export default class Ship { totalDps += dps; totalSDps += sdps; if (slot.m.getDamageDist()) { + if (slot.m.getDamageDist().A) { + totalAbsDpe += dpe * slot.m.getDamageDist().A; + totalAbsDps += dps * slot.m.getDamageDist().A; + totalAbsSDps += sdps * slot.m.getDamageDist().A; + } if (slot.m.getDamageDist().E) { totalExplDpe += dpe * slot.m.getDamageDist().E; totalExplDps += dps * slot.m.getDamageDist().E; @@ -1001,14 +1012,17 @@ export default class Ship { } this.totalDpe = totalDpe; + this.totalAbsDpe = totalAbsDpe; this.totalExplDpe = totalExplDpe; this.totalKinDpe = totalKinDpe; this.totalThermDpe = totalThermDpe; this.totalDps = totalDps; + this.totalAbsDps = totalAbsDps; this.totalExplDps = totalExplDps; this.totalKinDps = totalKinDps; this.totalThermDps = totalThermDps; this.totalSDps = totalSDps; + this.totalAbsSDps = totalAbsSDps; this.totalExplSDps = totalExplSDps; this.totalKinSDps = totalKinSDps; this.totalThermSDps = totalThermSDps; diff --git a/src/app/utils/CompanionApiUtils.js b/src/app/utils/CompanionApiUtils.js index 234ed48d..e1cf0d1a 100644 --- a/src/app/utils/CompanionApiUtils.js +++ b/src/app/utils/CompanionApiUtils.js @@ -303,7 +303,7 @@ function _addModifications(module, modifiers, blueprint, grade) { // This is an absolute number that acts as an override module.setModValue('burst', modifiers.modifiers[i].value * 100); } else if (modifiers.modifiers[i].name === 'mod_weapon_burst_rof') { - // For some reason this is a non-normalised percentage (i.e. 12.23% is 12.23 value rather than 0.1223 as everywhere else), so fix that here + // This is an absolute number that acts as an override module.setModValue('burstrof', modifiers.modifiers[i].value * 100); } else if (modifiers.modifiers[i].name === 'mod_weapon_falloffrange_from_range') { // Obtain the falloff value directly from the range

{translate('dps')}: {formats.f1(ship.totalDps)}

{translate('dps')}: {formats.f1(ship.totalDps)}

{translate('damage by')} {formats.f1(ship.totalAbsDps)} {formats.f1(ship.totalExplDps)} {formats.f1(ship.totalKinDps)} {formats.f1(ship.totalThermDps)}

{translate('sdps')}: {formats.f1(ship.totalSDps)}

{translate('sdps')}: {formats.f1(ship.totalSDps)}

{translate('damage by')} {formats.f1(ship.totalAbsSDps)} {formats.f1(ship.totalExplSDps)} {formats.f1(ship.totalKinSDps)} {formats.f1(ship.totalThermSDps)}

{translate('dpe')}: {formats.f1(ship.totalDpe)}

{translate('dpe')}: {formats.f1(ship.totalDpe)}

{translate('damage by')} {formats.f1(ship.totalAbsDpe)} {formats.f1(ship.totalExplDpe)} {formats.f1(ship.totalKinDpe)} {formats.f1(ship.totalThermDpe)}