mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-08 22:33:24 +00:00
864 lines
39 KiB
JavaScript
864 lines
39 KiB
JavaScript
import Module from './Module';
|
|
|
|
/**
|
|
* Calculate the maximum single jump range based on mass and a specific FSD
|
|
*
|
|
* @param {number} mass Mass of a ship: laden, unlanden, partially laden, etc
|
|
* @param {object} fsd The FDS object/component with maxfuel, fuelmul, fuelpower, optmass
|
|
* @param {number} fuel Optional - The fuel consumed during the jump
|
|
* @return {number} Distance in Light Years
|
|
*/
|
|
export function jumpRange(mass, fsd, fuel) {
|
|
const fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
|
|
const 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;
|
|
}
|
|
|
|
/**
|
|
* Calculate the total jump range based on mass and a specific FSD, and all fuel available
|
|
*
|
|
* @param {number} mass Mass of a ship: laden, unlanden, partially laden, etc
|
|
* @param {object} fsd The FDS object/component with maxfuel, fuelmul, fuelpower, optmass
|
|
* @param {number} fuel The total fuel available
|
|
* @return {number} Distance in Light Years
|
|
*/
|
|
export function totalJumpRange(mass, fsd, fuel) {
|
|
const fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
|
|
const fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass;
|
|
|
|
let fuelRemaining = fuel;
|
|
let totalRange = 0;
|
|
while (fuelRemaining > 0) {
|
|
const fuelForThisJump = Math.min(fuelRemaining, fsdMaxFuelPerJump);
|
|
totalRange += this.jumpRange(mass, fsd, fuelForThisJump);
|
|
// Mass is reduced
|
|
mass -= fuelForThisJump;
|
|
fuelRemaining -= fuelForThisJump;
|
|
}
|
|
return totalRange;
|
|
};
|
|
|
|
/**
|
|
* Calculate the a ships shield strength based on mass, shield generator and shield boosters used.
|
|
*
|
|
* @param {number} mass Current mass of the ship
|
|
* @param {number} baseShield Base Shield strength MJ for ship
|
|
* @param {object} sg The shield generator used
|
|
* @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any)
|
|
* @return {number} Approximate shield strengh in MJ
|
|
*/
|
|
export function shieldStrength(mass, baseShield, sg, multiplier) {
|
|
// sg might be a module or a template; handle either here
|
|
let minMass = sg instanceof Module ? sg.getMinMass() : sg.minmass;
|
|
let optMass = sg instanceof Module ? sg.getOptMass() : sg.optmass;
|
|
let maxMass = sg instanceof Module ? sg.getMaxMass() : sg.maxmass;
|
|
let minMul = sg instanceof Module ? sg.getMinMul() : sg.minmul;
|
|
let optMul = sg instanceof Module ? sg.getOptMul() : sg.optmul;
|
|
let maxMul = sg instanceof Module ? sg.getMaxMul() : sg.maxmul;
|
|
|
|
let xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass));
|
|
let exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass)));
|
|
let ynorm = Math.pow(xnorm, exponent);
|
|
let mul = minMul + ynorm * (maxMul - minMul);
|
|
|
|
return baseShield * mul * multiplier;
|
|
}
|
|
|
|
/**
|
|
* Calculate the a ships speed based on mass, and thrusters.
|
|
*
|
|
* @param {number} mass the mass of the ship
|
|
* @param {number} baseSpeed base speed m/s for ship
|
|
* @param {object} thrusters The ship's thrusters
|
|
* @param {number} engpip the multiplier per pip to engines
|
|
* @return {array} Speed by pips
|
|
*/
|
|
export function speed(mass, baseSpeed, thrusters, engpip) {
|
|
// thrusters might be a module or a template; handle either here
|
|
const minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass;
|
|
const optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass;
|
|
const maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass;
|
|
const minMul = thrusters instanceof Module ? thrusters.getMinMul('speed') : (thrusters.minmulspeed ? thrusters.minmulspeed : thrusters.minmul);
|
|
const optMul = thrusters instanceof Module ? thrusters.getOptMul('speed') : (thrusters.optmulspeed ? thrusters.minmulspeed : thrusters.minmul);
|
|
const maxMul = thrusters instanceof Module ? thrusters.getMaxMul('speed') : (thrusters.maxmulspeed ? thrusters.minmulspeed : thrusters.minmul);
|
|
|
|
let results = normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseSpeed, engpip);
|
|
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* Calculate pitch of a ship based on mass and thrusters
|
|
* @param {number} mass the mass of the ship
|
|
* @param {number} basePitch base pitch of the ship
|
|
* @param {object} thrusters the ship's thrusters
|
|
* @param {number} engpip the multiplier per pip to engines
|
|
* @return {array} Pitch by pips
|
|
*/
|
|
export function pitch(mass, basePitch, thrusters, engpip) {
|
|
// thrusters might be a module or a template; handle either here
|
|
let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass;
|
|
let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass;
|
|
let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass;
|
|
let minMul = thrusters instanceof Module ? thrusters.getMinMul('rotation') : (thrusters.minmulrotation ? thrusters.minmulrotation : thrusters.minmul);
|
|
let optMul = thrusters instanceof Module ? thrusters.getOptMul('rotation') : (thrusters.optmulrotation ? thrusters.optmulrotation : thrusters.optmul);
|
|
let maxMul = thrusters instanceof Module ? thrusters.getMaxMul('rotation') : (thrusters.maxmulrotation ? thrusters.maxmulrotation : thrusters.maxmul);
|
|
|
|
return normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, basePitch, engpip);
|
|
}
|
|
|
|
/**
|
|
* Calculate yaw of a ship based on mass and thrusters
|
|
* @param {number} mass the mass of the ship
|
|
* @param {number} baseYaw base yaw of the ship
|
|
* @param {object} thrusters the ship's thrusters
|
|
* @param {number} engpip the multiplier per pip to engines
|
|
* @return {array} Yaw by pips
|
|
*/
|
|
export function yaw(mass, baseYaw, thrusters, engpip) {
|
|
// thrusters might be a module or a template; handle either here
|
|
let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass;
|
|
let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass;
|
|
let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass;
|
|
let minMul = thrusters instanceof Module ? thrusters.getMinMul('rotation') : (thrusters.minmulrotation ? thrusters.minmulrotation : thrusters.minmul);
|
|
let optMul = thrusters instanceof Module ? thrusters.getOptMul('rotation') : (thrusters.optmulrotation ? thrusters.optmulrotation : thrusters.optmul);
|
|
let maxMul = thrusters instanceof Module ? thrusters.getMaxMul('rotation') : (thrusters.maxmulrotation ? thrusters.maxmulrotation : thrusters.maxmul);
|
|
|
|
return normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseYaw, engpip);
|
|
}
|
|
|
|
/**
|
|
* Calculate roll of a ship based on mass and thrusters
|
|
* @param {number} mass the mass of the ship
|
|
* @param {number} baseRoll base roll of the ship
|
|
* @param {object} thrusters the ship's thrusters
|
|
* @param {number} engpip the multiplier per pip to engines
|
|
* @return {array} Roll by pips
|
|
*/
|
|
export function roll(mass, baseRoll, thrusters, engpip) {
|
|
// thrusters might be a module or a template; handle either here
|
|
let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass;
|
|
let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass;
|
|
let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass;
|
|
let minMul = thrusters instanceof Module ? thrusters.getMinMul('rotation') : (thrusters.minmulrotation ? thrusters.minmulrotation : thrusters.minmul);
|
|
let optMul = thrusters instanceof Module ? thrusters.getOptMul('rotation') : (thrusters.optmulrotation ? thrusters.optmulrotation : thrusters.optmul);
|
|
let maxMul = thrusters instanceof Module ? thrusters.getMaxMul('rotation') : (thrusters.maxmulrotation ? thrusters.maxmulrotation : thrusters.maxmul);
|
|
|
|
return normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseRoll, engpip);
|
|
}
|
|
|
|
/**
|
|
* Normalise according to FD's calculations and return suitable values
|
|
* @param {number} minMass the minimum mass of the thrusters
|
|
* @param {number} optMass the optimum mass of the thrusters
|
|
* @param {number} maxMass the maximum mass of the thrusters
|
|
* @param {number} minMul the minimum multiplier of the thrusters
|
|
* @param {number} optMul the optimum multiplier of the thrusters
|
|
* @param {number} maxMul the maximum multiplier of the thrusters
|
|
* @param {number} mass the mass of the ship
|
|
* @param {base} base the base value from which to calculate
|
|
* @param {number} engpip the multiplier per pip to engines
|
|
* @return {array} values by pips
|
|
*/
|
|
function normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, base, engpip) {
|
|
const xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass));
|
|
const exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass)));
|
|
const ynorm = Math.pow(xnorm, exponent);
|
|
const mul = minMul + ynorm * (maxMul - minMul);
|
|
const res = base * mul;
|
|
|
|
return [res * (1 - (engpip * 4)),
|
|
res * (1 - (engpip * 3)),
|
|
res * (1 - (engpip * 2)),
|
|
res * (1 - (engpip * 1)),
|
|
res];
|
|
}
|
|
|
|
/**
|
|
* Calculate a single value
|
|
* @param {number} minMass the minimum mass of the thrusters
|
|
* @param {number} optMass the optimum mass of the thrusters
|
|
* @param {number} maxMass the maximum mass of the thrusters
|
|
* @param {number} minMul the minimum multiplier of the thrusters
|
|
* @param {number} optMul the optimum multiplier of the thrusters
|
|
* @param {number} maxMul the maximum multiplier of the thrusters
|
|
* @param {number} mass the mass of the ship
|
|
* @param {base} base the base value from which to calculate
|
|
* @param {number} engpip the multiplier per pip to engines
|
|
* @param {number} eng the pips to engines
|
|
* @returns {number} the resultant value
|
|
*/
|
|
function calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, base, engpip, eng) {
|
|
const xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass));
|
|
const exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass)));
|
|
const ynorm = Math.pow(xnorm, exponent);
|
|
const mul = minMul + ynorm * (maxMul - minMul);
|
|
const res = base * mul;
|
|
|
|
return res * (1 - (engpip * (4 - eng)));
|
|
}
|
|
|
|
/**
|
|
* Calculate speed for a given setup
|
|
* @param {number} mass the mass of the ship
|
|
* @param {number} baseSpeed the base speed of the ship
|
|
* @param {ojbect} thrusters the thrusters of the ship
|
|
* @param {number} engpip the multiplier per pip to engines
|
|
* @param {number} eng the pips to engines
|
|
* @param {number} boostFactor the boost factor for ths ship
|
|
* @param {boolean} boost true if the boost is activated
|
|
* @returns {number} the resultant speed
|
|
*/
|
|
export function calcSpeed(mass, baseSpeed, thrusters, engpip, eng, boostFactor, boost) {
|
|
// thrusters might be a module or a template; handle either here
|
|
const minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass;
|
|
const optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass;
|
|
const maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass;
|
|
const minMul = thrusters instanceof Module ? thrusters.getMinMul('speed') : (thrusters.minmulspeed ? thrusters.minmulspeed : thrusters.minmul);
|
|
const optMul = thrusters instanceof Module ? thrusters.getOptMul('speed') : (thrusters.optmulspeed ? thrusters.minmulspeed : thrusters.minmul);
|
|
const maxMul = thrusters instanceof Module ? thrusters.getMaxMul('speed') : (thrusters.maxmulspeed ? thrusters.minmulspeed : thrusters.minmul);
|
|
|
|
let result = calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseSpeed, engpip, eng);
|
|
if (boost == true) {
|
|
result *= boostFactor;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Calculate pitch for a given setup
|
|
* @param {number} mass the mass of the ship
|
|
* @param {number} basePitch the base pitch of the ship
|
|
* @param {ojbect} thrusters the thrusters of the ship
|
|
* @param {number} engpip the multiplier per pip to engines
|
|
* @param {number} eng the pips to engines
|
|
* @param {number} boostFactor the boost factor for ths ship
|
|
* @param {boolean} boost true if the boost is activated
|
|
* @returns {number} the resultant pitch
|
|
*/
|
|
export function calcPitch(mass, basePitch, thrusters, engpip, eng, boostFactor, boost) {
|
|
// thrusters might be a module or a template; handle either here
|
|
let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass;
|
|
let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass;
|
|
let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass;
|
|
let minMul = thrusters instanceof Module ? thrusters.getMinMul('rotation') : (thrusters.minmulrotation ? thrusters.minmulrotation : thrusters.minmul);
|
|
let optMul = thrusters instanceof Module ? thrusters.getOptMul('rotation') : (thrusters.optmulrotation ? thrusters.optmulrotation : thrusters.optmul);
|
|
let maxMul = thrusters instanceof Module ? thrusters.getMaxMul('rotation') : (thrusters.maxmulrotation ? thrusters.maxmulrotation : thrusters.maxmul);
|
|
|
|
let result = calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, basePitch, engpip, eng);
|
|
if (boost == true) {
|
|
result *= boostFactor;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Calculate roll for a given setup
|
|
* @param {number} mass the mass of the ship
|
|
* @param {number} baseRoll the base roll of the ship
|
|
* @param {ojbect} thrusters the thrusters of the ship
|
|
* @param {number} engpip the multiplier per pip to engines
|
|
* @param {number} eng the pips to engines
|
|
* @param {number} boostFactor the boost factor for ths ship
|
|
* @param {boolean} boost true if the boost is activated
|
|
* @returns {number} the resultant roll
|
|
*/
|
|
export function calcRoll(mass, baseRoll, thrusters, engpip, eng, boostFactor, boost) {
|
|
// thrusters might be a module or a template; handle either here
|
|
let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass;
|
|
let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass;
|
|
let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass;
|
|
let minMul = thrusters instanceof Module ? thrusters.getMinMul('rotation') : (thrusters.minmulrotation ? thrusters.minmulrotation : thrusters.minmul);
|
|
let optMul = thrusters instanceof Module ? thrusters.getOptMul('rotation') : (thrusters.optmulrotation ? thrusters.optmulrotation : thrusters.optmul);
|
|
let maxMul = thrusters instanceof Module ? thrusters.getMaxMul('rotation') : (thrusters.maxmulrotation ? thrusters.maxmulrotation : thrusters.maxmul);
|
|
|
|
let result = calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseRoll, engpip, eng);
|
|
if (boost == true) {
|
|
result *= boostFactor;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Calculate yaw for a given setup
|
|
* @param {number} mass the mass of the ship
|
|
* @param {number} baseYaw the base yaw of the ship
|
|
* @param {ojbect} thrusters the thrusters of the ship
|
|
* @param {number} engpip the multiplier per pip to engines
|
|
* @param {number} eng the pips to engines
|
|
* @param {number} boostFactor the boost factor for ths ship
|
|
* @param {boolean} boost true if the boost is activated
|
|
* @returns {number} the resultant yaw
|
|
*/
|
|
export function calcYaw(mass, baseYaw, thrusters, engpip, eng, boostFactor, boost) {
|
|
// thrusters might be a module or a template; handle either here
|
|
let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass;
|
|
let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass;
|
|
let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass;
|
|
let minMul = thrusters instanceof Module ? thrusters.getMinMul('rotation') : (thrusters.minmulrotation ? thrusters.minmulrotation : thrusters.minmul);
|
|
let optMul = thrusters instanceof Module ? thrusters.getOptMul('rotation') : (thrusters.optmulrotation ? thrusters.optmulrotation : thrusters.optmul);
|
|
let maxMul = thrusters instanceof Module ? thrusters.getMaxMul('rotation') : (thrusters.maxmulrotation ? thrusters.maxmulrotation : thrusters.maxmul);
|
|
|
|
let result = calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseYaw, engpip, eng);
|
|
if (boost == true) {
|
|
result *= boostFactor;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Calculate shield metrics
|
|
* @param {Object} ship The ship
|
|
* @param {int} sys The pips to SYS
|
|
* @returns {Object} Shield metrics
|
|
*/
|
|
export function shieldMetrics(ship, sys) {
|
|
const sysResistance = this.sysResistance(sys);
|
|
const maxSysResistance = this.sysResistance(4);
|
|
|
|
let shield = {};
|
|
|
|
const shieldGeneratorSlot = ship.findInternalByGroup('sg');
|
|
if (shieldGeneratorSlot && shieldGeneratorSlot.enabled && shieldGeneratorSlot.m) {
|
|
const shieldGenerator = shieldGeneratorSlot.m;
|
|
|
|
// Boosters
|
|
let boost = 1;
|
|
let boosterExplDmg = 1;
|
|
let boosterKinDmg = 1;
|
|
let boosterThermDmg = 1;
|
|
for (let slot of ship.hardpoints) {
|
|
if (slot.enabled && slot.m && slot.m.grp == 'sb') {
|
|
boost += slot.m.getShieldBoost();
|
|
boosterExplDmg = boosterExplDmg * (1 - slot.m.getExplosiveResistance());
|
|
boosterKinDmg = boosterKinDmg * (1 - slot.m.getKineticResistance());
|
|
boosterThermDmg = boosterThermDmg * (1 - slot.m.getThermalResistance());
|
|
}
|
|
}
|
|
|
|
// Calculate diminishing returns for boosters
|
|
boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5);
|
|
// Remove base shield generator strength
|
|
boost -= 1;
|
|
// Apply diminishing returns
|
|
boosterExplDmg = boosterExplDmg > 0.7 ? boosterExplDmg : 0.7 - (0.7 - boosterExplDmg) / 2;
|
|
boosterKinDmg = boosterKinDmg > 0.7 ? boosterKinDmg : 0.7 - (0.7 - boosterKinDmg) / 2;
|
|
boosterThermDmg = boosterThermDmg > 0.7 ? boosterThermDmg : 0.7 - (0.7 - boosterThermDmg) / 2;
|
|
|
|
const generatorStrength = this.shieldStrength(ship.hullMass, ship.baseShieldStrength, shieldGenerator, 1);
|
|
const boostersStrength = generatorStrength * boost;
|
|
|
|
// Recover time is the time taken to go from 0 to 50%. It includes a 16-second wait before shields start to recover
|
|
const shieldToRecover = (generatorStrength + boostersStrength) / 2;
|
|
const powerDistributor = ship.standard[4].m;
|
|
const sysRechargeRate = this.sysRechargeRate(powerDistributor, sys);
|
|
|
|
// Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes
|
|
// 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration
|
|
let capacitorDrain = (shieldGenerator.getBrokenRegenerationRate() * 0.6) - sysRechargeRate;
|
|
let capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain;
|
|
|
|
let recover = 16;
|
|
if (capacitorDrain <= 0 || shieldToRecover < capacitorLifetime * shieldGenerator.getBrokenRegenerationRate()) {
|
|
// We can recover the entire shield from the capacitor store
|
|
recover += shieldToRecover / shieldGenerator.getBrokenRegenerationRate();
|
|
} else {
|
|
// We can recover some of the shield from the capacitor store
|
|
recover += capacitorLifetime;
|
|
const remainingShieldToRecover = shieldToRecover - capacitorLifetime * shieldGenerator.getBrokenRegenerationRate();
|
|
if (sys === 0) {
|
|
// No system pips so will never recover shields
|
|
recover = Math.Inf;
|
|
} else {
|
|
// Recover remaining shields at the rate of the power distributor's recharge
|
|
recover += remainingShieldToRecover / (sysRechargeRate / 0.6);
|
|
}
|
|
}
|
|
|
|
// Recharge time is the time taken to go from 50% to 100%
|
|
const shieldToRecharge = (generatorStrength + boostersStrength) / 2;
|
|
|
|
// Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes
|
|
// 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration
|
|
capacitorDrain = (shieldGenerator.getRegenerationRate() * 0.6) - sysRechargeRate;
|
|
capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain;
|
|
|
|
let recharge = 0;
|
|
if (capacitorDrain <= 0 || shieldToRecharge < capacitorLifetime * shieldGenerator.getRegenerationRate()) {
|
|
// We can recharge the entire shield from the capacitor store
|
|
recharge += shieldToRecharge / shieldGenerator.getRegenerationRate();
|
|
} else {
|
|
// We can recharge some of the shield from the capacitor store
|
|
recharge += capacitorLifetime;
|
|
const remainingShieldToRecharge = shieldToRecharge - capacitorLifetime * shieldGenerator.getRegenerationRate();
|
|
if (sys === 0) {
|
|
// No system pips so will never recharge shields
|
|
recharge = Math.Inf;
|
|
} else {
|
|
// Recharge remaining shields at the rate of the power distributor's recharge
|
|
recharge += remainingShieldToRecharge / (sysRechargeRate / 0.6);
|
|
}
|
|
}
|
|
|
|
shield = {
|
|
generator: generatorStrength,
|
|
boosters: boostersStrength,
|
|
cells: ship.shieldCells,
|
|
total: generatorStrength + boostersStrength + ship.shieldCells,
|
|
recover,
|
|
recharge,
|
|
};
|
|
|
|
// Shield resistances have three components: the shield generator, the shield boosters and the SYS pips.
|
|
// We re-cast these as damage percentages
|
|
shield.absolute = {
|
|
generator: 1,
|
|
boosters: 1,
|
|
sys: 1 - sysResistance,
|
|
total: 1 - sysResistance,
|
|
max: 1 - maxSysResistance
|
|
};
|
|
|
|
shield.explosive = {
|
|
generator: 1 - shieldGenerator.getExplosiveResistance(),
|
|
boosters: boosterExplDmg,
|
|
sys: (1 - sysResistance),
|
|
total: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - sysResistance),
|
|
max: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - maxSysResistance)
|
|
};
|
|
|
|
shield.kinetic = {
|
|
generator: 1 - shieldGenerator.getKineticResistance(),
|
|
boosters: boosterKinDmg,
|
|
sys: (1 - sysResistance),
|
|
total: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - sysResistance),
|
|
max: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - maxSysResistance)
|
|
};
|
|
|
|
shield.thermal = {
|
|
generator: 1 - shieldGenerator.getThermalResistance(),
|
|
boosters: boosterThermDmg,
|
|
sys: (1 - sysResistance),
|
|
total: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - sysResistance),
|
|
max: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - maxSysResistance)
|
|
};
|
|
}
|
|
|
|
return shield;
|
|
}
|
|
|
|
/**
|
|
* Calculate armour metrics
|
|
* @param {Object} ship The ship
|
|
* @returns {Object} Armour metrics
|
|
*/
|
|
export function armourMetrics(ship) {
|
|
// Armour from bulkheads
|
|
const armourBulkheads = ship.baseArmour + (ship.baseArmour * ship.bulkheads.m.getHullBoost());
|
|
let armourReinforcement = 0;
|
|
|
|
let moduleArmour = 0;
|
|
let moduleProtection = 1;
|
|
|
|
let hullExplDmg = 1;
|
|
let hullKinDmg = 1;
|
|
let hullThermDmg = 1;
|
|
|
|
// Armour from HRPs and module armour from MRPs
|
|
for (let slot of ship.internal) {
|
|
if (slot.m && slot.m.grp == 'hr') {
|
|
armourReinforcement += slot.m.getHullReinforcement();
|
|
// Hull boost for HRPs is applied against the ship's base armour
|
|
armourReinforcement += ship.baseArmour * slot.m.getModValue('hullboost') / 10000;
|
|
|
|
hullExplDmg = hullExplDmg * (1 - slot.m.getExplosiveResistance());
|
|
hullKinDmg = hullKinDmg * (1 - slot.m.getKineticResistance());
|
|
hullThermDmg = hullThermDmg * (1 - slot.m.getThermalResistance());
|
|
}
|
|
if (slot.m && slot.m.grp == 'mrp') {
|
|
moduleArmour += slot.m.getIntegrity();
|
|
moduleProtection = moduleProtection * (1 - slot.m.getProtection());
|
|
}
|
|
}
|
|
moduleProtection = 1 - moduleProtection;
|
|
|
|
// Apply diminishing returns
|
|
hullExplDmg = hullExplDmg > 0.7 ? hullExplDmg : 0.7 - (0.7 - hullExplDmg) / 2;
|
|
hullKinDmg = hullKinDmg > 0.7 ? hullKinDmg : 0.7 - (0.7 - hullKinDmg) / 2;
|
|
hullThermDmg = hullThermDmg > 0.7 ? hullThermDmg : 0.7 - (0.7 - hullThermDmg) / 2;
|
|
|
|
const armour = {
|
|
bulkheads: armourBulkheads,
|
|
reinforcement: armourReinforcement,
|
|
modulearmour: moduleArmour,
|
|
moduleprotection: moduleProtection,
|
|
total: armourBulkheads + armourReinforcement
|
|
};
|
|
|
|
// Armour resistances have two components: bulkheads and HRPs
|
|
// We re-cast these as damage percentages
|
|
armour.absolute = {
|
|
bulkheads: 1,
|
|
reinforcement: 1,
|
|
total: 1
|
|
};
|
|
|
|
armour.explosive = {
|
|
bulkheads: 1 - ship.bulkheads.m.getExplosiveResistance(),
|
|
reinforcement: hullExplDmg,
|
|
total: (1 - ship.bulkheads.m.getExplosiveResistance()) * hullExplDmg
|
|
};
|
|
|
|
armour.kinetic = {
|
|
bulkheads: 1 - ship.bulkheads.m.getKineticResistance(),
|
|
reinforcement: hullKinDmg,
|
|
total: (1 - ship.bulkheads.m.getKineticResistance()) * hullKinDmg
|
|
};
|
|
|
|
armour.thermal = {
|
|
bulkheads: 1 - ship.bulkheads.m.getThermalResistance(),
|
|
reinforcement: hullThermDmg,
|
|
total: (1 - ship.bulkheads.m.getThermalResistance()) * hullThermDmg
|
|
};
|
|
|
|
return armour;
|
|
}
|
|
|
|
/**
|
|
* Calculate defence metrics for a ship
|
|
* @param {Object} ship The ship
|
|
* @param {Object} opponent The opponent ship
|
|
* @param {int} sys The pips to SYS
|
|
* @param {int} engagementrange The range between the ship and opponent
|
|
* @returns {Object} Defence metrics
|
|
*/
|
|
export function defenceMetrics(ship, opponent, sys, engagementrange) {
|
|
// Obtain the shield metrics
|
|
const shield = this.shieldMetrics(ship, sys);
|
|
|
|
// Obtain the armour metrics
|
|
const armour = this.armourMetrics(ship);
|
|
|
|
// Obtain the opponent's sustained DPS on us
|
|
const sustainedDps = this.sustainedDps(opponent, ship, sys, engagementrange);
|
|
|
|
const shielddamage = shield.generator ? {
|
|
absolutesdps: sustainedDps.shieldsdps.absolute,
|
|
explosivesdps: sustainedDps.shieldsdps.explosive,
|
|
kineticsdps: sustainedDps.shieldsdps.kinetic,
|
|
thermalsdps: sustainedDps.shieldsdps.thermal,
|
|
totalsdps: sustainedDps.shieldsdps.absolute + sustainedDps.shieldsdps.explosive + sustainedDps.shieldsdps.kinetic + sustainedDps.shieldsdps.thermal,
|
|
totalseps: sustainedDps.eps
|
|
} : {};
|
|
|
|
const armourdamage = {
|
|
absolutesdps: sustainedDps.armoursdps.absolute,
|
|
explosivesdps: sustainedDps.armoursdps.explosive,
|
|
kineticsdps: sustainedDps.armoursdps.kinetic,
|
|
thermalsdps: sustainedDps.armoursdps.thermal,
|
|
totalsdps: sustainedDps.armoursdps.absolute + sustainedDps.armoursdps.explosive + sustainedDps.armoursdps.kinetic + sustainedDps.armoursdps.thermal,
|
|
totalseps: sustainedDps.eps
|
|
};
|
|
|
|
return { shield, armour, shielddamage, armourdamage };
|
|
}
|
|
|
|
/**
|
|
* Calculate offence metrics for a ship
|
|
* @param {Object} ship The ship
|
|
* @param {Object} opponent The opponent ship
|
|
* @param {int} wep The pips to WEP
|
|
* @param {int} engagementrange The range between the ship and opponent
|
|
* @returns {array} Offence metrics
|
|
*/
|
|
export function offenceMetrics(ship, opponent, wep, engagementrange) {
|
|
// Per-weapon and total damage
|
|
const damage = [];
|
|
|
|
// Obtain the opponent's shield and armour metrics
|
|
const opponentShields = this.shieldMetrics(opponent, 4);
|
|
const opponentArmour = this.armourMetrics(opponent);
|
|
|
|
// Per-weapon and total damage to shields
|
|
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;
|
|
|
|
const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
|
|
let engineering;
|
|
if (m.blueprint && m.blueprint.name) {
|
|
engineering = m.blueprint.name + ' ' + 'grade' + ' ' + m.blueprint.grade;
|
|
if (m.blueprint.special && m.blueprint.special.id >= 0) {
|
|
engineering += ', ' + m.blueprint.special.name;
|
|
}
|
|
}
|
|
|
|
const weaponSustainedDps = this._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange);
|
|
damage.push({
|
|
id: i,
|
|
mount: m.mount,
|
|
name: m.name || m.grp,
|
|
classRating,
|
|
engineering,
|
|
sdps: weaponSustainedDps.damage,
|
|
seps: weaponSustainedDps.eps,
|
|
effectiveness: weaponSustainedDps.effectiveness
|
|
});
|
|
}
|
|
}
|
|
|
|
return damage;
|
|
|
|
const shielddamage = opponentShields.generator ? {
|
|
absolute: {
|
|
weapon1: 10,
|
|
weapon2: 10,
|
|
weapon3: 10,
|
|
weapon4: 10,
|
|
total: 40
|
|
}
|
|
} : {};
|
|
|
|
return damage;
|
|
}
|
|
|
|
/**
|
|
* Calculate the resistance provided by SYS pips
|
|
* @param {integer} sys the value of the SYS pips
|
|
* @returns {integer} the resistance for the given pips
|
|
*/
|
|
export function sysResistance(sys) {
|
|
return Math.pow(sys,0.85) * 0.6 / Math.pow(4,0.85);
|
|
}
|
|
|
|
/**
|
|
* Obtain the recharge rate of the SYS capacitor of a power distributor given pips
|
|
* @param {Object} pd The power distributor
|
|
* @param {number} sys The number of pips to SYS
|
|
* @returns {number} The recharge rate in MJ/s
|
|
*/
|
|
export function sysRechargeRate(pd, sys) {
|
|
return pd.getSystemsRechargeRate() * Math.pow(sys, 1.1) / Math.pow(4, 1.1);
|
|
}
|
|
|
|
/**
|
|
* Calculate the sustained DPS for a ship against an opponent at a given range
|
|
* @param {Object} ship The ship
|
|
* @param {Object} opponent The opponent ship
|
|
* @param {number} sys Pips to opponent's SYS
|
|
* @param {int} engagementrange The range between the ship and opponent
|
|
* @returns {Object} Sustained DPS for shield and armour
|
|
*/
|
|
export function sustainedDps(ship, opponent, sys, engagementrange) {
|
|
// Obtain the opponent's shield and armour metrics
|
|
const opponentShields = this.shieldMetrics(opponent, sys);
|
|
const opponentArmour = this.armourMetrics(opponent);
|
|
|
|
return this._sustainedDps(ship, opponent, opponentShields, opponentArmour, engagementrange);
|
|
}
|
|
|
|
/**
|
|
* Calculate the sustained DPS for a ship against an opponent at a given range
|
|
* @param {Object} ship The ship
|
|
* @param {Object} opponent The opponent ship
|
|
* @param {Object} opponentShields The opponent's shield resistances
|
|
* @param {Object} opponentArmour The opponent's armour resistances
|
|
* @param {int} engagementrange The range between the ship and opponent
|
|
* @returns {Object} Sustained DPS for shield and armour
|
|
*/
|
|
export function _sustainedDps(ship, opponent, opponentShields, opponentArmour, engagementrange) {
|
|
const shieldsdps = {
|
|
absolute: 0,
|
|
explosive: 0,
|
|
kinetic: 0,
|
|
thermal: 0
|
|
};
|
|
|
|
const armoursdps = {
|
|
absolute: 0,
|
|
explosive: 0,
|
|
kinetic: 0,
|
|
thermal: 0
|
|
};
|
|
|
|
let eps = 0;
|
|
|
|
for (let i = 0; i < ship.hardpoints.length; i++) {
|
|
if (ship.hardpoints[i].m && ship.hardpoints[i].enabled && ship.hardpoints[i].maxClass > 0) {
|
|
const m = ship.hardpoints[i].m;
|
|
const sustainedDps = this._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange);
|
|
shieldsdps.absolute += sustainedDps.damage.shields.absolute;
|
|
shieldsdps.explosive += sustainedDps.damage.shields.explosive;
|
|
shieldsdps.kinetic += sustainedDps.damage.shields.kinetic;
|
|
shieldsdps.thermal += sustainedDps.damage.shields.thermal;
|
|
armoursdps.absolute += sustainedDps.damage.armour.absolute;
|
|
armoursdps.explosive += sustainedDps.damage.armour.explosive;
|
|
armoursdps.kinetic += sustainedDps.damage.armour.kinetic;
|
|
armoursdps.thermal += sustainedDps.damage.armour.thermal;
|
|
eps += sustainedDps.eps;
|
|
}
|
|
}
|
|
|
|
return { shieldsdps, armoursdps, eps };
|
|
}
|
|
|
|
/**
|
|
* Calculate the sustained DPS for a weapon at a given range
|
|
* @param {Object} m The weapon
|
|
* @param {Object} opponent The opponent ship
|
|
* @param {Object} opponentShields The opponent's shield resistances
|
|
* @param {Object} opponentArmour The opponent's armour resistances
|
|
* @param {int} engagementrange The range between the ship and opponent
|
|
* @returns {Object} Sustained DPS for shield and armour
|
|
*/
|
|
export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange) {
|
|
const opponentHasShields = opponentShields.generator ? true : false;
|
|
const weapon = {
|
|
eps: 0,
|
|
damage: {
|
|
shields: {
|
|
absolute: 0,
|
|
explosive: 0,
|
|
kinetic: 0,
|
|
thermal: 0,
|
|
total: 0
|
|
},
|
|
armour: {
|
|
absolute: 0,
|
|
explosive: 0,
|
|
kinetic: 0,
|
|
thermal: 0,
|
|
total: 0
|
|
},
|
|
},
|
|
effectiveness: {
|
|
shields: {
|
|
range: 1,
|
|
sys: opponentHasShields ? opponentShields.absolute.sys : 1,
|
|
resistance: 1
|
|
},
|
|
armour: {
|
|
range: 1,
|
|
hardness: 1,
|
|
resistance: 1
|
|
}
|
|
}
|
|
};
|
|
|
|
// EPS
|
|
weapon.eps = m.getClip() ? (m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getEps();
|
|
|
|
// Initial sustained DPS
|
|
let sDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getDps();
|
|
|
|
// Take fall-off in to account
|
|
const falloff = m.getFalloff();
|
|
if (falloff && engagementrange > falloff) {
|
|
const dropoffRange = m.getRange() - falloff;
|
|
const dropoff = 1 - Math.min((engagementrange - falloff) / dropoffRange, 1);
|
|
weapon.effectiveness.shields.range = weapon.effectiveness.armour.range = dropoff;
|
|
sDps *= dropoff;
|
|
}
|
|
|
|
// Piercing/hardness modifier (for armour only)
|
|
const armourMultiple = m.getPiercing() >= opponent.hardness ? 1 : m.getPiercing() / opponent.hardness;
|
|
weapon.effectiveness.armour.hardness = armourMultiple;
|
|
|
|
// Break out the damage according to type
|
|
let shieldsResistance = 0;
|
|
let armourResistance = 0;
|
|
if (m.getDamageDist().A) {
|
|
weapon.damage.shields.absolute += sDps * m.getDamageDist().A * (opponentHasShields ? opponentShields.absolute.total : 1);
|
|
weapon.damage.armour.absolute += sDps * m.getDamageDist().A * armourMultiple * opponentArmour.absolute.total;
|
|
shieldsResistance += m.getDamageDist().A * (opponentHasShields ? opponentShields.absolute.generator * opponentShields.absolute.boosters : 1);
|
|
armourResistance += m.getDamageDist().A * opponentArmour.absolute.bulkheads * opponentArmour.absolute.reinforcement;
|
|
}
|
|
if (m.getDamageDist().E) {
|
|
weapon.damage.shields.explosive += sDps * m.getDamageDist().E * (opponentHasShields ? opponentShields.explosive.total : 1);
|
|
weapon.damage.armour.explosive += sDps * m.getDamageDist().E * armourMultiple * opponentArmour.explosive.total;
|
|
shieldsResistance += m.getDamageDist().E * (opponentHasShields ? opponentShields.explosive.generator * opponentShields.explosive.boosters : 1);
|
|
armourResistance += m.getDamageDist().E * opponentArmour.explosive.bulkheads * opponentArmour.explosive.reinforcement;
|
|
}
|
|
if (m.getDamageDist().K) {
|
|
weapon.damage.shields.kinetic += sDps * m.getDamageDist().K * (opponentHasShields ? opponentShields.kinetic.total : 1);
|
|
weapon.damage.armour.kinetic += sDps * m.getDamageDist().K * armourMultiple * opponentArmour.kinetic.total;
|
|
shieldsResistance += m.getDamageDist().K * (opponentHasShields ? opponentShields.kinetic.generator * opponentShields.kinetic.boosters : 1);
|
|
armourResistance += m.getDamageDist().K * opponentArmour.kinetic.bulkheads * opponentArmour.kinetic.reinforcement;
|
|
}
|
|
if (m.getDamageDist().T) {
|
|
weapon.damage.shields.thermal += sDps * m.getDamageDist().T * (opponentHasShields ? opponentShields.thermal.total : 1);
|
|
weapon.damage.armour.thermal += sDps * m.getDamageDist().T * armourMultiple * opponentArmour.thermal.total;
|
|
shieldsResistance += m.getDamageDist().T * (opponentHasShields ? opponentShields.thermal.generator * opponentShields.thermal.boosters : 1);
|
|
armourResistance += m.getDamageDist().T * opponentArmour.thermal.bulkheads * opponentArmour.thermal.reinforcement;
|
|
}
|
|
weapon.damage.shields.total = weapon.damage.shields.absolute + weapon.damage.shields.explosive + weapon.damage.shields.kinetic + weapon.damage.shields.thermal;
|
|
weapon.damage.armour.total = weapon.damage.armour.absolute + weapon.damage.armour.explosive + weapon.damage.armour.kinetic + weapon.damage.armour.thermal;
|
|
|
|
weapon.effectiveness.shields.resistance *= shieldsResistance;
|
|
weapon.effectiveness.armour.resistance *= armourResistance;
|
|
|
|
weapon.effectiveness.shields.total = weapon.effectiveness.shields.range * weapon.effectiveness.shields.sys * weapon.effectiveness.shields.resistance;
|
|
weapon.effectiveness.armour.total = weapon.effectiveness.armour.range * weapon.effectiveness.armour.resistance * weapon.effectiveness.armour.hardness;
|
|
return weapon;
|
|
}
|
|
|
|
/**
|
|
* Calculate time to drain WEP capacitor
|
|
* @param {object} ship The ship
|
|
* @param {number} wep Pips to WEP
|
|
* @returns {number} The time to drain the WEP capacitor, in seconds
|
|
*/
|
|
export function timeToDrainWep(ship, wep) {
|
|
let totalSEps = 0;
|
|
|
|
for (let slotNum in ship.hardpoints) {
|
|
const slot = ship.hardpoints[slotNum];
|
|
if (slot.maxClass > 0 && slot.m && slot.enabled && slot.type === 'WEP' && slot.m.getDps()) {
|
|
totalSEps += slot.m.getClip() ? (slot.m.getClip() * slot.m.getEps() / slot.m.getRoF()) / ((slot.m.getClip() / slot.m.getRoF()) + slot.m.getReload()) : slot.m.getEps();
|
|
}
|
|
}
|
|
|
|
// Calculate the drain time
|
|
const drainPerSecond = totalSEps - ship.standard[4].m.getWeaponsRechargeRate() * wep / 4;
|
|
if (drainPerSecond <= 0) {
|
|
// Can fire forever
|
|
return Infinity;
|
|
} else {
|
|
const initialCharge = ship.standard[4].m.getWeaponsCapacity();
|
|
return initialCharge / drainPerSecond;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate the time to deplete an amount of shields or armour
|
|
* @param {number} amount The amount to be depleted
|
|
* @param {number} dps The depletion per second
|
|
* @param {number} eps The energy drained per second
|
|
* @param {number} capacity The initial energy capacity
|
|
* @param {number} recharge The energy recharged per second
|
|
* @returns {number} The number of seconds to deplete to 0
|
|
*/
|
|
export function timeToDeplete(amount, dps, eps, capacity, recharge) {
|
|
const drainPerSecond = eps - recharge;
|
|
if (drainPerSecond <= 0) {
|
|
// Simple result
|
|
return amount / dps;
|
|
} else {
|
|
// We are draining the capacitor, but can we deplete before we run out
|
|
const timeToDrain = capacity / drainPerSecond;
|
|
const depletedBeforeDrained = dps * timeToDrain;
|
|
if (depletedBeforeDrained >= amount) {
|
|
return amount / dps;
|
|
} else {
|
|
const restToDeplete = amount - depletedBeforeDrained;
|
|
// We delete the rest at the reduced rate
|
|
const reducedDps = dps * (recharge / eps);
|
|
return timeToDrain + (restToDeplete / reducedDps);
|
|
}
|
|
}
|
|
}
|