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 * @param {object} ship Ship instance */ export function jumpRange(mass, fsd, fuel, ship) { const fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel; const fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass; let jumpAddition = 0; if (ship) { mass += ship.reserveFuelCapacity || 0; for (const module of ship.internal) { if (module && module.m && module.m.grp === 'gfsb' && ship.getSlotStatus(module) == 3) { jumpAddition += module.m.getJumpBoost(); } } } return (Math.pow(Math.min(fuel === undefined ? fsdMaxFuelPerJump : fuel, fsdMaxFuelPerJump) / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass) + jumpAddition; } /** * 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 * @param {object} ship Ship instance */ export function totalJumpRange(mass, fsd, fuel, ship) { 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, ship); // 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 pip multiplier for speed. * @param {number} baseSpeed The base speed of ship in data * @param {number} topSpeed The top speed of ship in data * @return {number} The multiplier that pips affect speed. */ export function calcPipSpeed(baseSpeed, topSpeed) { return (topSpeed - baseSpeed) / (4 * topSpeed); } /** * 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 {object} 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 {object} 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 // Diminishing returns not currently in-game // boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5); // Remove base shield generator strength boost -= 1; let shieldAddition = 0; if (ship) { for (const module of ship.internal) { if (module && module.m && module.m.grp === 'gsrp' && module.enabled) { shieldAddition += module.m.getShieldAddition(); } } } let 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 + shieldAddition) / 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() * shieldGenerator.getDistDraw()) - 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.Infinity; } else { // Recover remaining shields at the rate of the power distributor's recharge recover += remainingShieldToRecover / (sysRechargeRate / shieldGenerator.getDistDraw()); } } // Recharge time is the time taken to go from 50% to 100% const shieldToRecharge = (generatorStrength + boostersStrength + shieldAddition) / 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() * shieldGenerator.getDistDraw()) - 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 / shieldGenerator.getDistDraw()); } } shield = { generator: generatorStrength, boosters: boostersStrength, addition: shieldAddition, cells: ship.shieldCells, summary: generatorStrength + boostersStrength + shieldAddition, total: generatorStrength + boostersStrength + ship.shieldCells + shieldAddition, 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 }; /** * An object that stores a selection of difference damage multipliers that * deal with a ship's shield strength. * @typedef {Object} ShieldDamageMults * @property {number} generator Base damage multiplier of the shield * contributing it's base resistance. * @property {number} boosters Damage multiplier contributed by all * boosters, i.e. `rawMj / (generator * boosters)` equals shield strength * with 0 pips to sys. * @property {number} sys Damage multiplier contributed by pips to sys. * @property {number} base Damage multiplier with 0 pips to sys; just * boosters and shield generator. Equals `generator * boosters`. * @property {number} total Damage multiplier with current pip settings. * @property {number} max Damage multiplier with 4 pips to sys. */ let sgExplosiveDmg = 1 - shieldGenerator.getExplosiveResistance(); let sgSbExplosiveDmg = diminishingReturnsShields(sgExplosiveDmg, sgExplosiveDmg * boosterExplDmg); /** @type {ShieldDamageMults} */ shield.explosive = { generator: sgExplosiveDmg, boosters: sgSbExplosiveDmg / sgExplosiveDmg, sys: (1 - sysResistance), base: sgSbExplosiveDmg, total: sgSbExplosiveDmg * (1 - sysResistance), max: sgSbExplosiveDmg * (1 - maxSysResistance), }; let sgKineticDmg = 1 - shieldGenerator.getKineticResistance(); let sgSbKineticDmg = diminishingReturnsShields(sgKineticDmg, sgKineticDmg * boosterKinDmg); /** @type {ShieldDamageMults} */ shield.kinetic = { generator: sgKineticDmg, boosters: sgSbKineticDmg / sgKineticDmg, sys: (1 - sysResistance), base: sgSbKineticDmg, total: sgSbKineticDmg * (1 - sysResistance), max: sgSbKineticDmg * (1 - maxSysResistance), }; let sgThermalDmg = 1 - shieldGenerator.getThermalResistance(); let sgSbThermalDmg = diminishingReturnsShields(sgThermalDmg , sgThermalDmg * boosterThermDmg); /** @type {ShieldDamageMults} */ shield.thermal = { generator: sgThermalDmg, boosters: sgSbThermalDmg / sgThermalDmg, sys: (1 - sysResistance), base: sgSbThermalDmg, total: sgSbThermalDmg * (1 - sysResistance), max: sgSbThermalDmg * (1 - maxSysResistance), }; } return shield; } /** * Calculate time from one boost to another * @return {number} Boost frequency in seconds * @param {Ship} ship Ship object */ export function calcBoost(ship) { if (!ship.boostEnergy || !ship.standard[4] || !ship.standard[4].m) { return undefined; } return ship.boostEnergy / ship.standard[4].m.getEnginesRechargeRate(); } /** * 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; const bulkheads = ship.bulkheads.m; let hullExplDmgs = []; let hullKinDmgs = []; let hullThermDmgs = []; let hullCausDmgs = []; // Armour from HRPs and module armour from MRPs for (let slot of ship.internal) { if (slot.m && slot.enabled && (slot.m.grp === 'hr' || slot.m.grp === 'ghrp' || slot.m.grp == 'mahr')) { armourReinforcement += slot.m.getHullReinforcement(); // Hull boost for HRPs is applied against the ship's base armour armourReinforcement += ship.baseArmour * slot.m.getModValue('hullboost') / 10000; hullExplDmgs.push(1 - slot.m.getExplosiveResistance()); hullKinDmgs.push(1 - slot.m.getKineticResistance()); hullThermDmgs.push(1 - slot.m.getThermalResistance()); hullCausDmgs.push(1 - slot.m.getCausticResistance()); } if (slot.m && slot.enabled && (slot.m.grp == 'mrp' || slot.m.grp == 'gmrp')) { moduleArmour += slot.m.getIntegrity(); moduleProtection = moduleProtection * (1 - slot.m.getProtection()); } } moduleProtection = 1 - moduleProtection; 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 }; let armourExplDmg = 1 - ship.bulkheads.m.getExplosiveResistance(); let armourReinforcedExplDmg = diminishingReturnsArmour(armourExplDmg, ...hullExplDmgs); armour.explosive = { bulkheads: armourExplDmg, reinforcement: armourReinforcedExplDmg / armourExplDmg, total: armourReinforcedExplDmg, res: 1 - armourReinforcedExplDmg }; let armourKinDmg = 1 - ship.bulkheads.m.getKineticResistance(); let armourReinforcedKinDmg = diminishingReturnsArmour(armourKinDmg, ...hullKinDmgs); armour.kinetic = { bulkheads: armourKinDmg, reinforcement: armourReinforcedKinDmg / armourKinDmg, total: armourReinforcedKinDmg, res: 1 - armourReinforcedKinDmg }; let armourThermDmg = 1 - ship.bulkheads.m.getThermalResistance(); let armourReinforcedThermDmg = diminishingReturnsArmour(armourThermDmg, ...hullThermDmgs); armour.thermal = { bulkheads: armourThermDmg, reinforcement: armourReinforcedThermDmg / armourThermDmg, total: armourReinforcedThermDmg, res: 1 - armourReinforcedThermDmg }; let armourCausDmg = 1 - ship.bulkheads.m.getCausticResistance(); let armourReinforcedCausDmg = diminishingReturnsArmour(armourCausDmg, ...hullCausDmgs); armour.caustic = { bulkheads: armourCausDmg, reinforcement: armourReinforcedCausDmg / armourCausDmg, total: armourReinforcedCausDmg, res: 1 - armourReinforcedCausDmg, }; 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} opponentWep The pips to pponent's WEP * @param {int} engagementrange The range between the ship and opponent * @returns {Object} Defence metrics */ export function defenceMetrics(ship, opponent, sys, opponentWep, 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} opponentSys The pips to opponent's SYS * @param {int} engagementrange The range between the ship and opponent * @returns {array} Offence metrics */ export function offenceMetrics(ship, opponent, wep, opponentSys, engagementrange) { // Per-weapon and total damage const damage = []; // Obtain the opponent's shield and armour metrics const opponentShields = this.shieldMetrics(opponent, opponentSys); 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; } /** * 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 }; } /** * Stores SDPS split up by type. * @typedef {Object} SDps * @property {number} absolute Damage of type absolute * @property {number} explosive Damage of type explosive * @property {number} kinetic Damage of type kinetic * @property {number} thermal Damage of type thermal * @property {number} [total] Sum of all damage types */ /** * An object that holds information about SDPS for a given weapon and opponent. * @typedef {Object} WeaponDamage * @property {number} eps Energy per second * @property {Object} damage An object that stores damage inflicted by * the weapon. * @property {Object} effectiveness An object that stores the effectiveness of * the weapon against the opponent given. */ /** * Stores overall SDPS and against a given opponent's shields and armour. * @typedef {Object} WeaponDamage~damage * @property {SDps} base Overall SDPS. * @property {SDps} shields SDPS against the given opponent's shields. * @property {SDps} armour SDPS against the given opponent's armour. */ /** * 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 {WeaponDamage} 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: { base: { absolute: 0, explosive: 0, kinetic: 0, thermal: 0, total: 0, }, 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, dpe: 1 }, armour: { range: 1, hardness: 1, resistance: 1, dpe: 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.getSDps(); // 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; } weapon.damage.base.absolute = sDps * m.getDamageDist().A; weapon.damage.base.explosive = sDps * m.getDamageDist().E; weapon.damage.base.kinetic = sDps * m.getDamageDist().K; weapon.damage.base.thermal = sDps * m.getDamageDist().T; weapon.damage.base.total = sDps; // 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; weapon.effectiveness.shields.dpe = weapon.damage.shields.total / m.getEps() / m.getSustainedFactor(); weapon.effectiveness.armour.dpe = weapon.damage.armour.total / m.getEps() / m.getSustainedFactor(); 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 there is nothing to remove, we're don instantly if (!amount) { return 0; } 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); } } } /** * Checks whether diminishing returns should be applied to shield damage * multipliers and does so if necessary. * @param {number} shieldMult Damage multiplier of shield generator * @param {number} combinedMult Damage multiplier of shields and shield boosters * @returns {number} Overall damage multiplier */ export function diminishingReturnsShields(shieldMult, combinedMult) { let max = shieldMult * 0.7; if (combinedMult < max) { return mapIntoDiminishingRange(max / 2, max, combinedMult); } else { return combinedMult; } } /** * Checks whether diminishing returns should be applied to armour damage * multipliers and does so if necessary. * @param {...any} mults Damage multipliers of alloys and hull reinforcement * packages * @returns {number} Overall damage multiplier */ export function diminishingReturnsArmour(...mults) { let max = Math.min(0.7, ...mults); let combined = mults.reduce((aggr, v) => aggr * v); let diminished = mapIntoDiminishingRange(0.35, max, combined); if (diminished < 0.7) { return diminished; } else { return combined; } } /** * Applies diminishing returns to a damage multiplier. Effictively, the range * [`0`, `max`]` is mapped into the range [`min`, `max`] for the value `now`. * It can also happen, that `now` is outside of the range [`min`, `max`], then * `now` is actually improved, i.e. enlarged. * @param {number} min Best theoretical damage multiplier * @param {number} max Damage multiplier from which diminishing returns start to * be applied * @param {number} now The current damage multiplier * @returns {number} Remapped damage multiplier */ export function mapIntoDiminishingRange(min, max, now) { return min + (max - min) * (now / max); }