);
+ }
+ }
+
+ // Set up the special effects
+ let specials = [];
+ if (Modifications.modules[m.grp].specials && Modifications.modules[m.grp].specials.length > 0) {
+ const close = this._specialSelected.bind(this, null);
+ specials.push(
{translate('PHRASE_NO_SPECIAL')}
);
+ for (const specialName of Modifications.modules[m.grp].specials) {
+ const close = this._specialSelected.bind(this, specialName);
+ specials.push(
: null }
diff --git a/src/app/i18n/en.js b/src/app/i18n/en.js
index 8dd9f0ca..5062f672 100644
--- a/src/app/i18n/en.js
+++ b/src/app/i18n/en.js
@@ -35,6 +35,8 @@ export const terms = {
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',
+ PHRASE_SELECT_SPECIAL: 'Click to select an experimental effect',
+ PHRASE_NO_SPECIAL: 'No experimental effect',
HELP_MODIFICATIONS_MENU: 'Click on a number to enter a new value, or drag along the bar for small changes',
@@ -289,7 +291,7 @@ The damage received panel provides information about the effectiveness of your b
This is Coriolis EDCD Edition - a temporary clone of https://coriolis.io/ with added support for E:D 2.2. For more info see Settings / About
diff --git a/src/app/shipyard/Calculations.js b/src/app/shipyard/Calculations.js
index f9ab11e4..47b74b7f 100644
--- a/src/app/shipyard/Calculations.js
+++ b/src/app/shipyard/Calculations.js
@@ -10,7 +10,7 @@ import Module from './Module';
*/
export function jumpRange(mass, fsd, fuel) {
let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
- let fsdOptimalMass = fsd instanceof Module ? fsd.getOptimalMass() : fsd.optmass;
+ let fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass;
return Math.pow(Math.min(fuel === undefined ? fsdMaxFuelPerJump : fuel, fsdMaxFuelPerJump) / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass;
}
@@ -24,7 +24,7 @@ export function jumpRange(mass, fsd, fuel) {
*/
export function fastestRange(mass, fsd, fuel) {
let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
- let fsdOptimalMass = fsd instanceof Module ? fsd.getOptimalMass() : fsd.optmass;
+ let fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass;
let fuelRemaining = fuel % fsdMaxFuelPerJump; // Fuel left after making N max jumps
let jumps = Math.floor(fuel / fsdMaxFuelPerJump);
mass += fuelRemaining;
diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js
index 4ca5d84d..b47dec02 100755
--- a/src/app/shipyard/Module.js
+++ b/src/app/shipyard/Module.js
@@ -36,75 +36,126 @@ export default class Module {
/**
* Get a value for a given modification
- * @param {Number} name The name of the modification
- * @return {object} The value of the modification. If it is a numeric value then it is returned as an integer value scaled so that 1.23% == 123
+ * @param {Number} name The name of the modification
+ * @param {Number} raw True if the value returned should be raw i.e. without the influence of special effects
+ * @return {object} The value of the modification. If it is a numeric value then it is returned as an integer value scaled so that 1.23% == 123
*/
- getModValue(name) {
- return this.mods && this.mods[name] ? this.mods[name] : null;
+ getModValue(name, raw) {
+ let result = this.mods && this.mods[name] ? this.mods[name] : null;
+ if ((!raw) && this.blueprint && this.blueprint.special) {
+ // This module has a special effect, see if we need to alter our returned value
+ const modifierActions = Modifications.modifierActions[this.blueprint.special.edname];
+ if (modifierActions && modifierActions[name]) {
+ // this special effect modifies our returned value
+ const modification = Modifications.modifications[name];
+ if (modification.method === 'additive') {
+ result = result + modifierActions[name];
+ } else if (modification.method === 'overwrite') {
+ result = modifierActions[name];
+ } else {
+ // rate of fire is special, as it's really burst interval. Handle that here
+ let mod = null;
+ if (name === 'rof') {
+ mod = 1 / (1 + modifierActions[name]) - 1;
+ } else {
+ mod = modifierActions[name];
+ }
+ result = (((1 + result / 10000) * (1 + mod)) - 1) * 10000;
+ }
+ }
+ }
+
+ // Sanitise the resultant value to 4dp equivalent
+ return isNaN(result) ? result : Math.round(result);
}
/**
* Set a value for a given modification ID
- * @param {Number} name The name of the modification
+ * @param {Number} name The name of the modification
* @param {object} value The value of the modification. If it is a numeric value then it should be an integer scaled so that -2.34% == -234
+ * @param {bool} valueiswithspecial true if the value includes the special effect (when coming from a UI component)
*/
- setModValue(name, value) {
+ setModValue(name, value, valueiswithspecial) {
if (!this.mods) {
this.mods = {};
}
+ if (valueiswithspecial && this.blueprint && this.blueprint.special) {
+ // This module has a special effect, see if we need to alter the stored value
+ const modifierActions = Modifications.modifierActions[this.blueprint.special.edname];
+ if (modifierActions && modifierActions[name]) {
+ // This special effect modifies the value being set, so we need to revert it prior to storing the value
+ const modification = Modifications.modifications[name];
+ if (modification.method === 'additive') {
+ value = value - modifierActions[name];
+ } else if (modification.method === 'overwrite') {
+ value = null;
+ } else {
+ // rate of fire is special, as it's really burst interval. Handle that here
+ let mod = null;
+ if (name === 'rof') {
+ mod = 1 / (1 + modifierActions[name]) - 1;
+ } else {
+ mod = modifierActions[name];
+ }
+ value = ((value / 10000 + 1) / (1 + mod) - 1) * 10000;
+ }
+ }
+ }
if (value == null || value == 0) {
delete this.mods[name];
} else {
- if (isNaN(value)) {
- this.mods[name] = value;
- } else {
- // Round just to be sure
- this.mods[name] = Math.round(value);
- }
+ this.mods[name] = value;
}
}
/**
* Helper to obtain a modified value using standard multipliers
* @param {String} name the name of the modifier to obtain
- * @param {Boolean} additive Optional true if the value is additive rather than multiplicative
* @return {Number} the mass of this module
*/
- _getModifiedValue(name, additive) {
- let result = this[name] || (additive ? 0 : null); // Additive NULL === 0
- if (result != null) {
- const modification = Modifications.modifications[name];
- if (!modification) {
- return result;
- }
+ _getModifiedValue(name) {
+ const modification = Modifications.modifications[name];
+ let result = this[name];
- // 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;
- if (modification.type === 'percentage') {
- modValue = this.getModValue(name) / 10000;
- } else if (modification.type === 'numeric') {
- modValue = this.getModValue(name) / 100;
+ if (!result) {
+ if (modification && modification.method === 'additive') {
+ // Additive modifications start at 0 rather than NULL
+ result = 0;
} else {
- modValue = this.getModValue(name);
+ result = null;
}
- if (modValue) {
- if (additive) {
- result = result + modValue;
+ }
+
+ if (result != null) {
+ if (modification) {
+ // 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;
+ if (modification.type === 'percentage') {
+ modValue = this.getModValue(name) / 10000;
+ } else if (modification.type === 'numeric') {
+ modValue = this.getModValue(name) / 100;
} else {
- result = result * (1 + modValue);
+ modValue = this.getModValue(name);
+ }
+ if (modValue) {
+ if (modification.method === 'additive') {
+ result = result + modValue;
+ } else if (modification.method === 'overwrite') {
+ result = modValue;
+ } else {
+ result = result * (1 + modValue);
+ }
}
}
} else {
if (name === 'burst') {
// Burst is special, as if it can not exist but have a modification
- const modValue = this.getModValue(name) / 100;
- return modValue;
+ result = this.getModValue(name) / 100;
} else if (name === 'burstrof') {
// Burst rate of fire is special, as if it can not exist but have a modification
- const modValue = this.getModValue(name) / 100;
- return modValue;
+ result = this.getModValue(name) / 100;
}
}
@@ -159,30 +210,6 @@ export default class Module {
return this._getModifiedValue('eff');
}
- /**
- * Get the maximum mass of this module, taking in to account modifications
- * @return {Number} the maximum mass of this module
- */
- getMaxMass() {
- return this._getModifiedValue('maxmass');
- }
-
- /**
- * Get the optimal mass of this module, taking in to account modifications
- * @return {Number} the optimal mass of this module
- */
- getOptimalMass() {
- return this._getModifiedValue('optmass');
- }
-
- /**
- * Get the optimal multiplier of this module, taking in to account modifications
- * @return {Number} the optimal multiplier of this module
- */
- getOptimalMultiplier() {
- return this._getModifiedValue('optmult');
- }
-
/**
* Get the maximum fuel per jump for this module, taking in to account modifications
* @return {Number} the maximum fuel per jump of this module
@@ -244,7 +271,7 @@ export default class Module {
* @return {Number} the kinetic resistance of this module
*/
getKineticResistance() {
- return this._getModifiedValue('kinres', true);
+ return this._getModifiedValue('kinres');
}
/**
@@ -252,7 +279,7 @@ export default class Module {
* @return {Number} the thermal resistance of this module
*/
getThermalResistance() {
- return this._getModifiedValue('thermres', true);
+ return this._getModifiedValue('thermres');
}
/**
@@ -260,7 +287,7 @@ export default class Module {
* @return {Number} the explosive resistance of this module
*/
getExplosiveResistance() {
- return this._getModifiedValue('explres', true);
+ return this._getModifiedValue('explres');
}
/**
@@ -671,9 +698,25 @@ export default class Module {
/**
* Get the shot speed for this module, taking in to account modifications
- * @return {string} the damage distribution for this module
+ * @return {string} the shot speed for this module
*/
getShotSpeed() {
return this._getModifiedValue('shotspeed');
}
+
+ /**
+ * Get the spinup for this module, taking in to account modifications
+ * @return {string} the spinup for this module
+ */
+ getSpinup() {
+ return this._getModifiedValue('spinup');
+ }
+
+ /**
+ * Get the time for this module, taking in to account modifications
+ * @return {string} the time for this module
+ */
+ getTime() {
+ return this._getModifiedValue('time');
+ }
}
diff --git a/src/app/shipyard/Serializer.js b/src/app/shipyard/Serializer.js
index 0e60c305..c71122f0 100644
--- a/src/app/shipyard/Serializer.js
+++ b/src/app/shipyard/Serializer.js
@@ -90,7 +90,7 @@ export function toDetailedBuild(buildName, ship) {
code = ship.toString();
let data = {
- $schema: 'http://cdn.coriolis.io/schemas/ship-loadout/4.json#',
+ $schema: 'https://coriolis.edcd.io/schemas/ship-loadout/4.json#',
name: buildName,
ship: ship.name,
references: [{
diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js
index 2d2e0f13..aa4803bc 100755
--- a/src/app/shipyard/Ship.js
+++ b/src/app/shipyard/Ship.js
@@ -438,12 +438,13 @@ export default class Ship {
}
/**
- * Set a modification value
- * @param {Object} m The module to change
- * @param {Object} name The name of the modification to change
+ * Set a modification value and update ship stats
+ * @param {Object} m The module to change
+ * @param {Object} name The name of the modification to change
* @param {Number} value The new value of the modification. The value of the modification is scaled to provide two decimal places of precision in an integer. For example 1.23% is stored as 123
+ * @param {bool} sentfromui True if this update was sent from the UI
*/
- setModification(m, name, value) {
+ setModification(m, name, value, sentfromui) {
if (isNaN(value)) {
// Value passed is invalid; reset it to 0
value = 0;
@@ -452,58 +453,58 @@ export default class Ship {
// Handle special cases
if (name === 'pgen') {
// Power generation
- m.setModValue(name, value);
+ m.setModValue(name, value, sentfromui);
this.updatePowerGenerated();
} else if (name === 'power') {
// Power usage
- m.setModValue(name, value);
+ m.setModValue(name, value, sentfromui);
this.updatePowerUsed();
} else if (name === 'mass') {
// Mass
let oldMass = m.getMass();
- m.setModValue(name, value);
+ m.setModValue(name, value, sentfromui);
let newMass = m.getMass();
this.unladenMass = this.unladenMass - oldMass + newMass;
this.ladenMass = this.ladenMass - oldMass + newMass;
this.updateMovement();
this.updateJumpStats();
} else if (name === 'maxfuel') {
- m.setModValue(name, value);
+ m.setModValue(name, value, sentfromui);
this.updateJumpStats();
} else if (name === 'optmass') {
- m.setModValue(name, value);
+ m.setModValue(name, value, sentfromui);
// Could be for any of thrusters, FSD or shield
this.updateMovement();
this.updateJumpStats();
this.recalculateShield();
} else if (name === 'optmul') {
- m.setModValue(name, value);
+ m.setModValue(name, value, sentfromui);
// Could be for any of thrusters, FSD or shield
this.updateMovement();
this.updateJumpStats();
this.recalculateShield();
} else if (name === 'shieldboost') {
- m.setModValue(name, value);
+ m.setModValue(name, value, sentfromui);
this.recalculateShield();
} else if (name === 'hullboost' || name === 'hullreinforcement' || name === 'modulereinforcement') {
- m.setModValue(name, value);
+ m.setModValue(name, value, sentfromui);
this.recalculateArmour();
} else if (name === 'shieldreinforcement') {
- m.setModValue(name, value);
+ m.setModValue(name, value, sentfromui);
this.recalculateShieldCells();
} else if (name === 'burst' || name == 'burstrof' || name === 'clip' || name === 'damage' || name === 'distdraw' || name === 'jitter' || name === 'piercing' || name === 'range' || name === 'reload' || name === 'rof' || name === 'thermload') {
- m.setModValue(name, value);
+ m.setModValue(name, value, sentfromui);
this.recalculateDps();
this.recalculateHps();
this.recalculateEps();
} else if (name === 'explres' || name === 'kinres' || name === 'thermres') {
- m.setModValue(name, value);
+ m.setModValue(name, value, sentfromui);
// Could be for shields or armour
this.recalculateArmour();
this.recalculateShield();
} else {
// Generic
- m.setModValue(name, value);
+ m.setModValue(name, value, sentfromui);
}
}
@@ -1376,7 +1377,7 @@ export default class Ship {
for (let modKey in this.bulkheads.m.mods) {
// Filter out invalid modifications
if (Modifications.modules['bh'] && Modifications.modules['bh'].modifications.indexOf(modKey) != -1) {
- bulkheadMods.push({ id: Modifications.modifications[modKey].id, value: this.bulkheads.m.getModValue(modKey) });
+ bulkheadMods.push({ id: Modifications.modifications[modKey].id, value: this.bulkheads.m.getModValue(modKey, true) });
}
}
bulkheadBlueprint = this.bulkheads.m.blueprint;
@@ -1391,7 +1392,7 @@ export default class Ship {
for (let modKey in slot.m.mods) {
// Filter out invalid modifications
if (Modifications.modules[slot.m.grp] && Modifications.modules[slot.m.grp].modifications.indexOf(modKey) != -1) {
- slotMods.push({ id: Modifications.modifications[modKey].id, value: slot.m.getModValue(modKey) });
+ slotMods.push({ id: Modifications.modifications[modKey].id, value: slot.m.getModValue(modKey, true) });
}
}
}
@@ -1406,7 +1407,7 @@ export default class Ship {
for (let modKey in slot.m.mods) {
// Filter out invalid modifications
if (Modifications.modules[slot.m.grp] && Modifications.modules[slot.m.grp].modifications.indexOf(modKey) != -1) {
- slotMods.push({ id: Modifications.modifications[modKey].id, value: slot.m.getModValue(modKey) });
+ slotMods.push({ id: Modifications.modifications[modKey].id, value: slot.m.getModValue(modKey, true) });
}
}
}
@@ -1421,7 +1422,7 @@ export default class Ship {
for (let modKey in slot.m.mods) {
// Filter out invalid modifications
if (Modifications.modules[slot.m.grp] && Modifications.modules[slot.m.grp].modifications.indexOf(modKey) != -1) {
- slotMods.push({ id: Modifications.modifications[modKey].id, value: slot.m.getModValue(modKey) });
+ slotMods.push({ id: Modifications.modifications[modKey].id, value: slot.m.getModValue(modKey, true) });
}
}
}
diff --git a/src/app/utils/CompanionApiUtils.js b/src/app/utils/CompanionApiUtils.js
index e1cf0d1a..674ef674 100644
--- a/src/app/utils/CompanionApiUtils.js
+++ b/src/app/utils/CompanionApiUtils.js
@@ -308,6 +308,9 @@ function _addModifications(module, modifiers, blueprint, grade) {
} else if (modifiers.modifiers[i].name === 'mod_weapon_falloffrange_from_range') {
// Obtain the falloff value directly from the range
module.setModValue('fallofffromrange', 1);
+ } else if (modifiers.modifiers[i].name && modifiers.modifiers[i].name.startsWith('special_')) {
+ // We don't add special effects directly, but keep a note of them so they can be added when fetching values
+ special = Modifications.specials[modifiers.modifiers[i].name];
} else {
// Look up the modifiers to find what we need to do
const modifierActions = Modifications.modifierActions[modifiers.modifiers[i].name];
@@ -327,11 +330,6 @@ function _addModifications(module, modifiers, blueprint, grade) {
}
}
}
-
- // Note the special if present
- if (modifiers.modifiers[i].name && modifiers.modifiers[i].name.startsWith('special_')) {
- special = Modifications.specials[modifiers.modifiers[i].name];
- }
}
// Add the blueprint ID, grade and special
diff --git a/src/app/utils/SlotFunctions.js b/src/app/utils/SlotFunctions.js
index 1c064c67..feff8a5b 100644
--- a/src/app/utils/SlotFunctions.js
+++ b/src/app/utils/SlotFunctions.js
@@ -151,13 +151,15 @@ export function diffDetails(language, m, mm) {
let mmMass = mm ? mm.getMass() : 0;
if (mMass != mmMass) propDiffs.push(