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 {
-
- | {translate('weapon')} |
- {translate('effective dps')} |
- {translate('effective sdps')} |
- {translate('effectiveness')} |
-
+
+ | {translate('weapon')} |
+ {translate('effective dps')} |
+ {translate('effective sdps')} |
+ {translate('effectiveness')} |
+
{this._renderRows(translate, formats)}
+
+
+ | {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