diff --git a/ChangeLog.md b/ChangeLog.md index 1bb56d58..9bf715ac 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,11 @@ +#2.2.9 + * Use SSL-enabled server for shortlinks + * Add falloff for weapons + * Use falloff when calculating weapon effectiveness in damage dealt + * Add engagement range slider to 'Damage Dealt' section to allow user to see change in weapon effectiveness with range + * Use better DPE calculation methodology + * Add total DPS and effectiveness information to 'Damage Dealt' section + #2.2.8 * Fix issue where filling all internals with cargo racks would include restricted slots * Use coriolis-data 2.2.8: diff --git a/src/app/components/DamageDealt.jsx b/src/app/components/DamageDealt.jsx index 01ba84b2..eb27dc20 100644 --- a/src/app/components/DamageDealt.jsx +++ b/src/app/components/DamageDealt.jsx @@ -4,6 +4,7 @@ import { Ships } from 'coriolis-data/dist'; import ShipSelector from './ShipSelector'; import { nameComparator } from '../utils/SlotFunctions'; import { CollapseSection, ExpandSection, MountFixed, MountGimballed, MountTurret } from './SvgIcons'; +import Slider from '../components/Slider'; /** * Generates an internationalization friendly weapon comparator that will @@ -66,7 +67,9 @@ export default class DamageDealt extends TranslatedComponent { predicate: 'n', desc: true, against: DamageDealt.DEFAULT_AGAINST, - expanded: false + expanded: false, + range: 0.1667, + maxRange: 6000 }; } @@ -74,8 +77,8 @@ export default class DamageDealt extends TranslatedComponent { * Set the initial weapons state */ componentWillMount() { - const weapons = this._calcWeapons(this.props.ship, this.state.against); - this.setState({ weapons }); + const data = this._calcWeapons(this.props.ship, this.state.against, this.state.range * this.state.maxRange); + this.setState({ weapons: data.weapons, totals: data.totals }); } /** @@ -86,8 +89,8 @@ export default class DamageDealt extends TranslatedComponent { */ componentWillReceiveProps(nextProps, nextContext) { if (nextProps.code != this.props.code) { - const weapons = this._calcWeapons(this.props.ship, this.state.against); - this.setState({ weapons }); + const data = this._calcWeapons(this.props.ship, this.state.against, this.state.range * this.state.maxRange); + this.setState({ weapons: data.weapons, totals: data.totals }); } return true; } @@ -96,30 +99,61 @@ export default class DamageDealt extends TranslatedComponent { * 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 */ - _calcWeapons(ship, against) { - let weapons = []; + _calcWeapons(ship, against, range) { + // Tidy up the range so that it's to 4 decimal places + range = Math.round(10000 * range) / 10000; + // Track totals + let totals = {}; + totals.effectiveness = 0; + totals.effectiveDps = 0; + totals.effectiveSDps = 0; + let totalDps = 0; + + let weapons = []; for (let i = 0; i < ship.hardpoints.length; i++) { if (ship.hardpoints[i].m) { const m = ship.hardpoints[i].m; - const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`; - const effectiveness = m.getPiercing() >= against.properties.hardness ? 1 : m.getPiercing() / against.properties.hardness; - const effectiveDps = m.getDps() * effectiveness; - const effectiveSDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectiveness : effectiveDps; + if (m.getDamage() && m.grp !== 'po') { + let dropoff = 1; + if (m.getFalloff()) { + // Calculate the dropoff % due to range + if (range > m.getRange()) { + // Weapon is out of range + dropoff = 0; + } else { + const falloff = m.getFalloff(); + if (range > falloff) { + const dropoffRange = m.getRange() - falloff; + // Assuming straight-line falloff + dropoff = 1 - (range - falloff) / dropoffRange; + } + } + } + const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`; + 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; + totalDps += m.getDps(); - weapons.push({ id: i, - mount: m.mount, - name: m.name || m.grp, - classRating, - effectiveDps, - effectiveSDps, - effectiveness }); + weapons.push({ id: i, + mount: m.mount, + name: m.name || m.grp, + classRating, + effectiveDps, + effectiveSDps, + effectiveness }); + } } } - - return weapons; + totals.effectiveness = totals.effectiveDps / totalDps; + + return {weapons: weapons, totals: totals}; } /** @@ -135,8 +169,8 @@ export default class DamageDealt extends TranslatedComponent { */ _onShipChange(s) { const against = Ships[s]; - const weapons = this._calcWeapons(this.props.ship, against); - this.setState({ against, weapons }); + const data = this._calcWeapons(this.props.ship, against); + this.setState({ against, weapons: data.weapons, totals: data.totals }); } /** @@ -208,14 +242,23 @@ export default class DamageDealt extends TranslatedComponent { return rows; } + /** + * Update current range + * @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 }); + } + /** * Render damage dealt * @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, totals } = this.state; const sortOrder = this._sortOrder; const onCollapseExpand = this._onCollapseExpand; @@ -227,16 +270,45 @@ export default class DamageDealt extends TranslatedComponent { - - - - - - + + + + + + {this._renderRows(translate, formats)} + + + + + + + + +
{translate('weapon')}{translate('effective dps')}{translate('effective sdps')}{translate('effectiveness')}
{translate('weapon')}{translate('effective dps')}{translate('effective sdps')}{translate('effectiveness')}
{translate('total')}{formats.round1(totals.effectiveDps)}{formats.round1(totals.effectiveSDps)}{formats.pct(totals.effectiveness)}
+ + + + + + + +
{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 cc1d80ac..e4d75d6e 100644 --- a/src/app/components/HardpointSlot.jsx +++ b/src/app/components/HardpointSlot.jsx @@ -74,6 +74,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.getShieldBoost() ?
+{formats.pct1(m.getShieldBoost())}
: null } { m.getAmmo() ?
{translate('ammunition')}: {formats.int(m.getClip())}/{formats.int(m.getAmmo())}
: null } { m.getPiercing() ?
{translate('piercing')}: {formats.int(m.getPiercing())}
: null } diff --git a/src/app/components/ModificationsMenu.jsx b/src/app/components/ModificationsMenu.jsx index f38c8b1e..1a56180f 100644 --- a/src/app/components/ModificationsMenu.jsx +++ b/src/app/components/ModificationsMenu.jsx @@ -39,7 +39,9 @@ export default class ModificationsMenu extends TranslatedComponent { let list = []; for (let modName of Modifications.validity[m.grp]) { - list.push(); + if (Modifications.modifications[modName].type != 'hidden') { + list.push(); + } } return { list }; diff --git a/src/app/i18n/en.js b/src/app/i18n/en.js index 0d0496de..091cb560 100644 --- a/src/app/i18n/en.js +++ b/src/app/i18n/en.js @@ -28,6 +28,7 @@ export const terms = { PHRASE_SG_RECOVER: 'Recovery (to 50%) after collapse', PHRASE_UNLADEN: 'Ship mass excluding fuel and cargo', PHRASE_UPDATE_RDY: 'Update Available! Click to refresh', + PHRASE_ENGAGEMENT_RANGE: 'The distance between your ship and its target', HELP_MODIFICATIONS_MENU: 'Click on a number to enter a new value, or drag along the bar for small changes', @@ -123,6 +124,8 @@ export const terms = { 'yaw': 'Yaw', 'internal protection': 'Internal protection', 'external protection': 'External protection', + 'engagement range': 'Engagement range', + 'total': 'Total', // Modifications ammo: 'Ammunition maximum', diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js index 37879de6..36911b33 100755 --- a/src/app/shipyard/Module.js +++ b/src/app/shipyard/Module.js @@ -286,6 +286,20 @@ export default class Module { return this._getModifiedValue('range'); } + /** + * Get the falloff for this module, taking in to account modifications + * @return {Number} the falloff of this module + */ + getFalloff() { + if (this.getModValue('fallofffromrange')) { + return this.getRange(); + } else { + const falloff = this._getModifiedValue('falloff'); + const range = this.getRange(); + return (falloff > range ? range : falloff); + } + } + /** * Get the range (in terms of seconds, for FSDI) for this module, taking in to account modifications * @return {Number} the range of this module diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index 7a5313a5..1342ff20 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -947,51 +947,27 @@ export default class Ship { for (let slotNum in this.hardpoints) { const slot = this.hardpoints[slotNum]; if (slot.m && slot.enabled && slot.m.getDps()) { - const dpe = slot.m.getDps() / slot.m.getEps(); + const dpe = slot.m.getEps() === 0 ? 0 : slot.m.getDps() / slot.m.getEps(); const dps = slot.m.getDps(); const sdps = slot.m.getClip() ? (slot.m.getClip() * slot.m.getDps() / slot.m.getRoF()) / ((slot.m.getClip() / slot.m.getRoF()) + slot.m.getReload()) : dps; totalDpe += dpe; totalDps += dps; totalSDps += sdps; - if (slot.m.getDamageType() === 'E') { - totalExplDpe += dpe; - totalExplDps += dps; - totalExplSDps += sdps; + if (slot.m.getDamageType().indexOf('E') != -1) { + totalExplDpe += dpe / slot.m.getDamageType().length; + totalExplDps += dps / slot.m.getDamageType().length; + totalExplSDps += sdps / slot.m.getDamageType().length; } - if (slot.m.getDamageType() === 'K') { - totalKinDpe += dpe; - totalKinDps += dps; - totalKinSDps += sdps; + if (slot.m.getDamageType().indexOf('K') != -1) { + totalKinDpe += dpe / slot.m.getDamageType().length; + totalKinDps += dps / slot.m.getDamageType().length; + totalKinSDps += sdps / slot.m.getDamageType().length; } - if (slot.m.getDamageType() === 'T') { - totalThermDpe += dpe; - totalThermDps += dps; - totalThermSDps += sdps; - } - if (slot.m.getDamageType() === 'EK') { - totalExplDpe += dpe / 2; - totalKinDpe += dpe / 2; - totalExplDps += dps / 2; - totalKinDps += dps / 2; - totalExplSDps += sdps / 2; - totalKinSDps += sdps / 2; - } - if (slot.m.getDamageType() === 'ET') { - totalExplDpe += dpe / 2; - totalThermDpe += dpe / 2; - totalExplDps += dps / 2; - totalThermDps += dps / 2; - totalExplSDps += sdps / 2; - totalThermSDps += sdps / 2; - } - if (slot.m.getDamageType() === 'KT') { - totalKinDpe += dpe / 2; - totalThermDpe += dpe / 2; - totalKinDps += dps / 2; - totalThermDps += dps / 2; - totalKinSDps += sdps / 2; - totalThermSDps += sdps / 2; + if (slot.m.getDamageType().indexOf('T') != -1) { + totalThermDpe += dpe / slot.m.getDamageType().length; + totalThermDps += dps / slot.m.getDamageType().length; + totalThermSDps += sdps / slot.m.getDamageType().length; } } } diff --git a/src/app/utils/CompanionApiUtils.js b/src/app/utils/CompanionApiUtils.js index 11030548..234ed48d 100644 --- a/src/app/utils/CompanionApiUtils.js +++ b/src/app/utils/CompanionApiUtils.js @@ -305,6 +305,9 @@ function _addModifications(module, modifiers, blueprint, grade) { } 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 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 + module.setModValue('fallofffromrange', 1); } else { // Look up the modifiers to find what we need to do const modifierActions = Modifications.modifierActions[modifiers.modifiers[i].name]; diff --git a/src/app/utils/ShortenUrl.js b/src/app/utils/ShortenUrl.js index ca7f05b4..e2f59614 100644 --- a/src/app/utils/ShortenUrl.js +++ b/src/app/utils/ShortenUrl.js @@ -38,7 +38,7 @@ function shortenUrlGoogle(url, success, error) { } } -const SHORTEN_API_EDDP = 'http://eddp.co/u'; +const SHORTEN_API_EDDP = 'https://eddp.co/u'; /** * Shorten a URL using EDDP's URL shortener API * @param {string} url The URL to shorten