From 4b14f617ec229c09f5510ab0df647e494335f2a4 Mon Sep 17 00:00:00 2001 From: Cmdr McDonald Date: Sun, 30 Oct 2016 20:54:36 +0000 Subject: [PATCH] Changes for mods --- src/app/components/HardpointSlot.jsx | 4 +- src/app/components/InternalSlot.jsx | 5 +- src/app/components/InternalSlotSection.jsx | 1 + src/app/components/Slot.jsx | 57 ++++++++----- src/app/components/StandardSlot.jsx | 55 ++++++++----- src/app/i18n/en.js | 40 ++++++++- src/app/shipyard/Calculations.js | 77 +++++++++++------- src/app/shipyard/Module.js | 94 ++++++++++++++++++++++ src/app/shipyard/Ship.js | 10 ++- 9 files changed, 270 insertions(+), 73 deletions(-) diff --git a/src/app/components/HardpointSlot.jsx b/src/app/components/HardpointSlot.jsx index c9fc6ece..18436ec1 100644 --- a/src/app/components/HardpointSlot.jsx +++ b/src/app/components/HardpointSlot.jsx @@ -2,6 +2,7 @@ import React from 'react'; import Slot from './Slot'; import { DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret, ListModifications } from './SvgIcons'; import { Modifications } from 'coriolis-data/dist'; +import { stopCtxPropagation } from '../utils/UtilityFunctions'; /** @@ -38,6 +39,7 @@ export default class HardpointSlot extends Slot { if (m) { let classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`; let { drag, drop } = this.props; + let { termtip, tooltip } = this.context; let validMods = Modifications.validity[m.grp] || []; return
@@ -62,7 +64,7 @@ export default class HardpointSlot extends Slot { { m.range && !m.dps ?
{translate('Range')} : {formats.round(m.range / 1000)}{u.km}
: null } { m.shieldmul ?
+{formats.rPct(m.shieldmul)}
: null } { m.ammo >= 0 ?
{translate('ammo')}: {formats.int(m.clip)}/{formats.int(m.ammo)}
: null } - { validMods.length > 0 ?
: null } + { m && validMods.length > 0 ?
: null }
; } else { diff --git a/src/app/components/InternalSlot.jsx b/src/app/components/InternalSlot.jsx index 6dcc25f3..84fbc1c5 100644 --- a/src/app/components/InternalSlot.jsx +++ b/src/app/components/InternalSlot.jsx @@ -2,6 +2,7 @@ import React from 'react'; import Slot from './Slot'; import { ListModifications } from './SvgIcons'; import { Modifications } from 'coriolis-data/dist'; +import { stopCtxPropagation } from '../utils/UtilityFunctions'; /** * Internal Slot @@ -20,6 +21,7 @@ export default class InternalSlot extends Slot { if (m) { let classRating = m.class + m.rating; let { drag, drop } = this.props; + let { termtip, tooltip } = this.context; let validMods = Modifications.validity[m.grp] || []; return
@@ -44,7 +46,8 @@ export default class InternalSlot extends Slot { { m.rangeLS === null ?
∞{u.Ls}
: null } { m.rangeRating ?
{translate('range')}: {m.rangeRating}
: null } { m.armouradd ?
+{m.armouradd} {translate('armour')}
: null } - { validMods.length > 0 ?
: null } + { m.passengers ?
{translate('passengers')}: {m.passengers}
: null } + { m && validMods.length > 0 ?
: null }
; } else { diff --git a/src/app/components/InternalSlotSection.jsx b/src/app/components/InternalSlotSection.jsx index c33cc451..66beab58 100644 --- a/src/app/components/InternalSlotSection.jsx +++ b/src/app/components/InternalSlotSection.jsx @@ -110,6 +110,7 @@ export default class InternalSlotSection extends SlotSection { maxClass={s.maxClass} availableModules={() => availableModules.getInts(ship, s.maxClass, s.eligible)} onOpen={this._openMenu.bind(this,s)} + onChange={this.props.onChange} onSelect={this._selectModule.bind(this, s)} selected={currentMenu == s} enabled={s.enabled} diff --git a/src/app/components/Slot.jsx b/src/app/components/Slot.jsx index 0453e23a..6310ac15 100644 --- a/src/app/components/Slot.jsx +++ b/src/app/components/Slot.jsx @@ -3,8 +3,11 @@ import TranslatedComponent from './TranslatedComponent'; import cn from 'classnames'; import AvailableModulesMenu from './AvailableModulesMenu'; import ModificationsMenu from './ModificationsMenu'; +import { Modifications } from 'coriolis-data/dist'; +import { ListModifications } from './SvgIcons'; import { diffDetails } from '../utils/SlotFunctions'; import { wrapCtxMenu } from '../utils/UtilityFunctions'; +import { stopCtxPropagation } from '../utils/UtilityFunctions'; /** * Abstract Slot @@ -32,6 +35,8 @@ export default class Slot extends TranslatedComponent { constructor(props) { super(props); + this._modificationsSelected = false; + this._contextMenu = wrapCtxMenu(this._contextMenu.bind(this)); this._getMaxClassLabel = this._getMaxClassLabel.bind(this); } @@ -73,9 +78,16 @@ export default class Slot extends TranslatedComponent { */ render() { let language = this.context.language; + let { termtip, tooltip } = this.context; let translate = language.translate; - let { ship, m, dropClass, dragOver, onOpen, selected, onSelect, warning, shipMass, availableModules } = this.props; + let { ship, m, dropClass, dragOver, onOpen, onChange, selected, onSelect, warning, shipMass, availableModules } = this.props; let slotDetails, menu; + let validMods = m == null ? [] : (Modifications.validity[m.grp] || []); + + if (!selected) { + // If not selected then sure that modifications flag is unset + this._modificationsSelected = false; + } if (m) { slotDetails = this._getSlotDetails(m, translate, language.formats, language.units); // Must be implemented by sub classes @@ -83,34 +95,41 @@ export default class Slot extends TranslatedComponent { slotDetails =
{translate('empty')}
; } - if (this.props.selected) { - menu = ; + if (selected) { + if (this._modificationsSelected) { + menu = ; + } else { + menu = ; + } } - if (this.props.selected) { - menu = ; - } // TODO: implement touch dragging return (
{this._getMaxClassLabel(translate)}
- {slotDetails} -
+ {slotDetails} +
{menu} ); } + + _toggleModifications(event) { + this._modificationsSelected = !this._modificationsSelected; + } } diff --git a/src/app/components/StandardSlot.jsx b/src/app/components/StandardSlot.jsx index 534c1c38..49ae73fd 100644 --- a/src/app/components/StandardSlot.jsx +++ b/src/app/components/StandardSlot.jsx @@ -25,6 +25,11 @@ export default class StandardSlot extends TranslatedComponent { warning: React.PropTypes.func, }; + constructor(props) { + super(props); + this._modificationsSelected = false; + } + /** * Render the slot * @return {React.Component} Slot component @@ -32,31 +37,36 @@ export default class StandardSlot extends TranslatedComponent { render() { let { termtip, tooltip } = this.context; let { translate, formats, units } = this.context.language; - let { modules, slot, warning, onSelect, onChange, ladenMass, ship } = this.props; + let { modules, slot, selected, warning, onSelect, onChange, ladenMass, ship } = this.props; let m = slot.m; let classRating = m.class + m.rating; let menu; let validMods = m == null ? [] : (Modifications.validity[m.grp] || []); - if (this.props.selected) { - menu = ; + if (!selected) { + // If not selected then sure that modifications flag is unset + this._modificationsSelected = false; } - if (this.props.selected) { - menu = ; + if (selected) { + if (this._modificationsSelected) { + menu = ; + } else { + menu = ; + } } return ( @@ -74,17 +84,22 @@ export default class StandardSlot extends TranslatedComponent { { m.getRange() ?
{translate('range')}: {m.getRange()}{units.km}
: null } { m.time ?
{translate('time')}: {formats.time(m.time)}
: null } { m.getThermalEfficiency() ?
{translate('efficiency')}: {m.getThermalEfficiency()}
: null } - { m.getPowerGeneration() > 0 ?
{translate('power')}: {formats.round(m.getPowerGeneration())}{units.MW}
: null } + { m.getPowerGeneration() > 0 ?
{translate('pGen')}: {formats.round(m.getPowerGeneration())}{units.MW}
: null } { m.getMaxFuelPerJump() ?
{translate('max')} {translate('fuel')}: {m.getMaxFuelPerJump()}{units.T}
: null } { m.getWeaponsCapacity() ?
{translate('WEP')}: {m.getWeaponsCapacity()}{units.MJ} / {m.getWeaponsRechargeRate()}{units.MW}
: null } { m.getSystemsCapacity() ?
{translate('SYS')}: {m.getSystemsCapacity()}{units.MJ} / {m.getSystemsRechargeRate()}{units.MW}
: null } { m.getEnginesCapacity() ?
{translate('ENG')}: {m.getEnginesCapacity()}{units.MJ} / {m.getEnginesRechargeRate()}{units.MW}
: null } + + { validMods.length > 0 ?
: null } {menu} ); - // { validMods.length > 0 ?
: null } + } + + _toggleModifications() { + this._modificationsSelected = !this._modificationsSelected; } } diff --git a/src/app/i18n/en.js b/src/app/i18n/en.js index f35115bb..ce8a535f 100644 --- a/src/app/i18n/en.js +++ b/src/app/i18n/en.js @@ -78,5 +78,43 @@ export const terms = { t: 'thrusters', tp: 'Torpedo Pylon', ul: 'Burst Laser', - ws: 'Frame Shift Wake Scanner' + ws: 'Frame Shift Wake Scanner', + + // Modifications + mass: 'Mass', + integrity: 'Integrity', + pGen: 'Power generation', + eff: 'Efficiency', + power: 'Power draw', + optmass: 'Optimal mass', + optmul: 'Optimal multiplier', + dps: 'Damage per second', + eps: 'Energy per second', + thermload: 'Thermal load', + boot: 'Boot time', + maxfuel: 'Maximum fuel per jump', + syscap: 'Systems capacity', + engcap: 'Engines capacity', + wepcap: 'Weapons capacity', + sysrate: 'Systems recharge rate', + engrate: 'Engines recharge rate', + weprate: 'Weapons recharge rate', + kinres: 'Kinetic resistance', + thermres: 'Thermal resistance', + explres: 'Explosive resistance', + regen: 'Regeneration rate', + brokenregen: 'Broken regeneration rate', + delay: 'Delay', + duration: 'Duration', + shield: 'Shield', + shieldboost: 'Shield boost', + distdraw: 'Distributor draw', + damage: 'Damage', + armourpen: 'Armour penetration', + range: 'Range', + rof: 'Rate of fire', + clip: 'Ammunition clip', + ammo: 'Ammunition maximum', + jitter: 'Jitter', + reload: 'Reload time' }; diff --git a/src/app/shipyard/Calculations.js b/src/app/shipyard/Calculations.js index 16beefb4..6fa9c88d 100644 --- a/src/app/shipyard/Calculations.js +++ b/src/app/shipyard/Calculations.js @@ -1,3 +1,4 @@ +import Module from './Module'; /** * Calculate the maximum single jump range based on mass and a specific FSD @@ -8,7 +9,9 @@ * @return {number} Distance in Light Years */ export function jumpRange(mass, fsd, fuel) { - return Math.pow(Math.min(fuel === undefined ? fsd.getMaxFuelPerJump() : fuel, fsd.getMaxFuelPerJump()) / fsd.fuelmul, 1 / fsd.fuelpower) * fsd.getOptimalMass() / mass; + let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel; + let fsdOptimalMass = fsd instanceof Module ? fsd.getOptimalMass() : fsd.optmass; + return Math.pow(Math.min(fuel === undefined ? fsdMaxFuelPerJump : fuel, fsdMaxFuelPerJump) / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass; } /** @@ -20,15 +23,17 @@ export function jumpRange(mass, fsd, fuel) { * @return {number} Distance in Light Years */ export function fastestRange(mass, fsd, fuel) { - let fuelRemaining = fuel % fsd.getMaxFuelPerJump(); // Fuel left after making N max jumps - let jumps = Math.floor(fuel / fsd.getMaxFuelPerJump()); + let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel; + let fsdOptimalMass = fsd instanceof Module ? fsd.getOptimalMass() : fsd.optmass; + let fuelRemaining = fuel % fsdMaxFuelPerJump; // Fuel left after making N max jumps + let jumps = Math.floor(fuel / fsdMaxFuelPerJump); mass += fuelRemaining; // Going backwards, start with the last jump using the remaining fuel - let fastestRange = fuelRemaining > 0 ? Math.pow(fuelRemaining / fsd.fuelmul, 1 / fsd.fuelpower) * fsd.getOptimalMass() / mass : 0; + let fastestRange = fuelRemaining > 0 ? Math.pow(fuelRemaining / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass : 0; // For each max fuel jump, calculate the max jump range based on fuel mass left in the tank for (let j = 0; j < jumps; j++) { mass += fsd.maxfuel; - fastestRange += Math.pow(fsd.getMaxFuelPerJump() / fsd.fuelmul, 1 / fsd.fuelpower) * fsd.getOptimalMass() / mass; + fastestRange += Math.pow(fsdMaxFuelPerJump / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass; } return fastestRange; }; @@ -36,29 +41,30 @@ export function fastestRange(mass, fsd, fuel) { /** * 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} shields 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 + * @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, shields, sg, multiplier) { - let opt; - if (mass < sg.minmass) { - return shields * multiplier * sg.minmul; - } - if (mass > sg.maxmass) { - return shields * multiplier * sg.maxmul; - } - if (mass < sg.optmass) { - opt = (sg.optmass - mass) / (sg.optmass - sg.minmass); - opt = 1 - Math.pow(1 - opt, 0.87); - return shields * multiplier * ((opt * sg.minmul) + ((1 - opt) * sg.optmul)); - } else { - opt = (sg.optmass - mass) / (sg.maxmass - sg.optmass); - opt = -1 + Math.pow(1 + opt, 2.425); - return shields * multiplier * ((-1 * opt * sg.maxmul) + ((1 + opt) * sg.optmul)); - } +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); + let strength = baseShield * mul; + + // TODO handle multiplier + + return strength; } /** @@ -72,13 +78,24 @@ export function shieldStrength(mass, shields, sg, multiplier) { * @return {object} Approximate speed by pips */ export function speed(mass, baseSpeed, baseBoost, thrusters, pipSpeed) { - let multiplier = mass > thrusters.maxmass ? 0 : ((1 - thrusters.M) + (thrusters.M * Math.pow(3 - (2 * Math.max(0.5, mass / thrusters.optmass)), thrusters.P))); - let speed = baseSpeed * multiplier; + // 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() : thrusters.minmul; + let optMul = thrusters instanceof Module ? thrusters.getOptMul() : thrusters.optmul; + let maxMul = thrusters instanceof Module ? thrusters.getMaxMul() : thrusters.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); + let speed = baseSpeed * mul; return { '0 Pips': speed * (1 - (pipSpeed * 4)), '2 Pips': speed * (1 - (pipSpeed * 2)), '4 Pips': speed, - 'boost': baseBoost * multiplier + 'boost': baseBoost * mul }; } diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js index 9010e61c..efae87ca 100755 --- a/src/app/shipyard/Module.js +++ b/src/app/shipyard/Module.js @@ -150,7 +150,17 @@ export default class Module { * @return {Number} the heat per second of this module */ getHeatPerSecond() { + // Modifier for hps is thermload return this._getModifiedValue('hps'); + let result = 0; + if (this['hps']) { + result = this['hps']; + if (result) { + let mult = this.getModValue('thermload'); + if (mult) { result = result * (1 + mult); } + } + } + return result; } /** @@ -296,4 +306,88 @@ export default class Module { getShieldReinforcement() { return this._getModifiedValue('shieldreinforcement'); } + + /** + * Get the minimum mass for this module, taking in to account modifications + * @return {Number} the minimum mass of this module + */ + getMinMass() { + // Modifier is optmass + let result = 0; + if (this['minmass']) { + result = this['minmass']; + if (result) { + let mult = this.getModValue('optmass'); + if (mult) { result = result * (1 + mult); } + } + } + return result; + } + + /** + * Get the optimum mass for this module, taking in to account modifications + * @return {Number} the optimum mass of this module + */ + getOptMass() { + return this._getModifiedValue('optmass'); + } + + /** + * Get the maximum mass for this module, taking in to account modifications + * @return {Number} the maximum mass of this module + */ + getMaxMass() { + // Modifier is optmass + let result = 0; + if (this['maxmass']) { + result = this['maxmass']; + if (result) { + let mult = this.getModValue('optmass'); + if (mult) { result = result * (1 + mult); } + } + } + return result; + } + + /** + * Get the minimum multiplier for this module, taking in to account modifications + * @return {Number} the minimum multiplier of this module + */ + getMinMul() { + // Modifier is optmul + let result = 0; + if (this['minmul']) { + result = this['minmul']; + if (result) { + let mult = this.getModValue('optmul'); + if (mult) { result = result * (1 + mult); } + } + } + return result; + } + + /** + * Get the optimum multiplier for this module, taking in to account modifications + * @return {Number} the optimum multiplier of this module + */ + getOptMul() { + return this._getModifiedValue('optmul'); + } + + /** + * Get the maximum multiplier for this module, taking in to account modifications + * @return {Number} the maximum multiplier of this module + */ + getMaxMul() { + // Modifier is optmul + let result = 0; + if (this['maxmul']) { + result = this['maxmul']; + if (result) { + let mult = this.getModValue('optmul'); + if (mult) { result = result * (1 + mult); } + } + } + return result; + } } diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js index 9074f96f..09085b9a 100755 --- a/src/app/shipyard/Ship.js +++ b/src/app/shipyard/Ship.js @@ -179,7 +179,8 @@ export default class Ship { */ calcUnladenRange(massDelta, fuel, fsd) { fsd = fsd || this.standard[2].m; - return Calc.jumpRange(this.unladenMass + (massDelta || 0) + Math.min(fsd.getMaxFuelPerJump(), fuel || this.fuelCapacity), fsd || this.standard[2].m, fuel); + let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel; + return Calc.jumpRange(this.unladenMass + (massDelta || 0) + Math.min(fsdMaxFuelPerJump, fuel || this.fuelCapacity), fsd || this.standard[2].m, fuel); } /** @@ -432,6 +433,13 @@ export default class Ship { this.updateJumpStats(); } else if (name == 'optmass') { m.setModValue(name, value); + // Could be for either thrusters or FSD + this.updateTopSpeed(); + this.updateJumpStats(); + } else if (name == 'optmul') { + m.setModValue(name, value); + // Could be for either thrusters or FSD + this.updateTopSpeed(); this.updateJumpStats(); } else { // Generic