Changes for mods

This commit is contained in:
Cmdr McDonald
2016-10-30 20:54:36 +00:00
parent 0df051e40f
commit 4b14f617ec
9 changed files with 270 additions and 73 deletions

View File

@@ -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 <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
@@ -62,7 +64,7 @@ export default class HardpointSlot extends Slot {
{ m.range && !m.dps ? <div className={'l'}>{translate('Range')} : {formats.round(m.range / 1000)}{u.km}</div> : null }
{ m.shieldmul ? <div className={'l'}>+{formats.rPct(m.shieldmul)}</div> : null }
{ m.ammo >= 0 ? <div className={'l'}>{translate('ammo')}: {formats.int(m.clip)}/{formats.int(m.ammo)}</div> : null }
{ validMods.length > 0 ? <div className='r' ><ListModifications /></div> : null }
{ m && validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
</div>
</div>;
} else {

View File

@@ -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 <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
@@ -44,7 +46,8 @@ export default class InternalSlot extends Slot {
{ m.rangeLS === null ? <div className={'l'}>{u.Ls}</div> : null }
{ m.rangeRating ? <div className={'l'}>{translate('range')}: {m.rangeRating}</div> : null }
{ m.armouradd ? <div className={'l'}>+{m.armouradd} <u className='cap'>{translate('armour')}</u></div> : null }
{ validMods.length > 0 ? <div className='r' ><ListModifications /></div> : null }
{ m.passengers ? <div className={'l'}>{translate('passengers')}: {m.passengers}</div> : null }
{ m && validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
</div>
</div>;
} else {

View File

@@ -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}

View File

@@ -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,7 +95,15 @@ export default class Slot extends TranslatedComponent {
slotDetails = <div className={'empty'}>{translate('empty')}</div>;
}
if (this.props.selected) {
if (selected) {
if (this._modificationsSelected) {
menu = <ModificationsMenu
className={this._getClassNames()}
onChange={onChange}
ship={ship}
m={m}
/>;
} else {
menu = <AvailableModulesMenu
className={this._getClassNames()}
modules={availableModules()}
@@ -94,13 +114,8 @@ export default class Slot extends TranslatedComponent {
diffDetails={diffDetails.bind(ship, this.context.language)}
/>;
}
if (this.props.selected) {
menu = <ModificationsMenu
className={this._getClassNames()}
m={m}
/>;
}
// TODO: implement touch dragging
return (
@@ -113,4 +128,8 @@ export default class Slot extends TranslatedComponent {
</div>
);
}
_toggleModifications(event) {
this._modificationsSelected = !this._modificationsSelected;
}
}

View File

@@ -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,13 +37,26 @@ 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) {
if (!selected) {
// If not selected then sure that modifications flag is unset
this._modificationsSelected = false;
}
if (selected) {
if (this._modificationsSelected) {
menu = <ModificationsMenu
className='standard'
onChange={onChange}
ship={ship}
m={m}
/>;
} else {
menu = <AvailableModulesMenu
className='standard'
modules={modules}
@@ -49,14 +67,6 @@ export default class StandardSlot extends TranslatedComponent {
diffDetails={diffDetails.bind(ship, this.context.language)}
/>;
}
if (this.props.selected) {
menu = <ModificationsMenu
className='standard'
onChange={onChange}
ship={ship}
m={m}
/>;
}
return (
@@ -74,17 +84,22 @@ export default class StandardSlot extends TranslatedComponent {
{ m.getRange() ? <div className='l'>{translate('range')}: {m.getRange()}{units.km}</div> : null }
{ m.time ? <div className='l'>{translate('time')}: {formats.time(m.time)}</div> : null }
{ m.getThermalEfficiency() ? <div className='l'>{translate('efficiency')}: {m.getThermalEfficiency()}</div> : null }
{ m.getPowerGeneration() > 0 ? <div className='l'>{translate('power')}: {formats.round(m.getPowerGeneration())}{units.MW}</div> : null }
{ m.getPowerGeneration() > 0 ? <div className='l'>{translate('pGen')}: {formats.round(m.getPowerGeneration())}{units.MW}</div> : null }
{ m.getMaxFuelPerJump() ? <div className='l'>{translate('max')} {translate('fuel')}: {m.getMaxFuelPerJump()}{units.T}</div> : null }
{ m.getWeaponsCapacity() ? <div className='l'>{translate('WEP')}: {m.getWeaponsCapacity()}{units.MJ} / {m.getWeaponsRechargeRate()}{units.MW}</div> : null }
{ m.getSystemsCapacity() ? <div className='l'>{translate('SYS')}: {m.getSystemsCapacity()}{units.MJ} / {m.getSystemsRechargeRate()}{units.MW}</div> : null }
{ m.getEnginesCapacity() ? <div className='l'>{translate('ENG')}: {m.getEnginesCapacity()}{units.MJ} / {m.getEnginesRechargeRate()}{units.MW}</div> : null }
{ validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
</div>
</div>
</div>
{menu}
</div>
);
// { validMods.length > 0 ? <div className='r' ><button onClick={this._showModificationsMenu.bind(this, m)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
}
_toggleModifications() {
this._modificationsSelected = !this._modificationsSelected;
}
}

View File

@@ -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'
};

View File

@@ -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;
};
@@ -37,28 +42,29 @@ 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 {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
};
}

View File

@@ -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;
}
}

View File

@@ -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