mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-10 15:15:34 +00:00
Merge branch 'feature/res' into develop
This commit is contained in:
@@ -6,8 +6,15 @@
|
||||
* Add initial loadout passenger cabins for Beluga
|
||||
* Add initial loadout passenger cabins for Orca
|
||||
* Update costs and initial loadouts for Keelback and Type-7
|
||||
* Add resistances for hull reinforcement packages
|
||||
* Added modifier actions to create modifications from raw data
|
||||
* Show modification icon for modified modules
|
||||
* Take modifications in to account when deciding whether to issue a warning on a standard module
|
||||
* Fix hardpoint comparison DPS number when selecting an alternate module
|
||||
* Ensure that retrofit tab only shows changed modules
|
||||
* Fix import and export of ships with modifications, bump schema version to 4
|
||||
* Enable boost display even if power distributor is disabled
|
||||
* Calculate breakdown of ship offensive and defensive stats
|
||||
* Add 'Offence summary' and 'Defence summary' components
|
||||
* Add ability to import from companion API output through import feature
|
||||
* Add ability to import from companion API output through URL
|
||||
|
||||
@@ -264,16 +264,28 @@
|
||||
"topBoost": 248.62,
|
||||
"topSpeed": 186.46,
|
||||
"totalCost": 882362058,
|
||||
"totalDpe": 127.26,
|
||||
"totalDps": 97.74,
|
||||
"totalEps": 22.71,
|
||||
"totalHps": 677.29,
|
||||
"totalExplDpe": 0,
|
||||
"totalExplDps": 0,
|
||||
"totalExplSDps": 0,
|
||||
"totalHps": 33.28,
|
||||
"totalKinDpe": 103.97,
|
||||
"totalKinDps": 28.92,
|
||||
"totalKinSDps": 21.23,
|
||||
"totalSDps": 85.77,
|
||||
"totalThermDpe": 23.29,
|
||||
"totalThermDps": 68.82,
|
||||
"totalThermSDps": 64.53,
|
||||
"agility": 2,
|
||||
"baseShieldStrength": 350,
|
||||
"baseArmour": 945,
|
||||
"hullExplRes": 0,
|
||||
"hullKinRes": 0,
|
||||
"hullExplRes": 0.78,
|
||||
"hullKinRes": 0.73,
|
||||
"hullMass": 400,
|
||||
"hullThermRes": 0,
|
||||
"hullThermRes": 1.37,
|
||||
"masslock": 23,
|
||||
"pipSpeed": 0.14,
|
||||
"moduleCostMultiplier": 1,
|
||||
@@ -293,8 +305,9 @@
|
||||
"ladenFastestRange": 66.15,
|
||||
"maxJumpCount": 4,
|
||||
"shield": 833,
|
||||
"shieldExplRes": 0,
|
||||
"shieldKinRes": 0,
|
||||
"shieldThermRes": 0
|
||||
"shieldCells": 1840,
|
||||
"shieldExplRes": 0.5,
|
||||
"shieldKinRes": 0.6,
|
||||
"shieldThermRes": 1.2
|
||||
}
|
||||
}
|
||||
|
||||
1288
__tests__/fixtures/companion-api-import-1.json
Normal file
1288
__tests__/fixtures/companion-api-import-1.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -196,6 +196,23 @@ describe('Import Modal', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Import Companion API Build', function() {
|
||||
|
||||
beforeEach(reset);
|
||||
|
||||
it('imports a valid v4 build', function() {
|
||||
const importData = require('./fixtures/companion-api-import-1');
|
||||
pasteText(JSON.stringify(importData));
|
||||
|
||||
expect(modal.state.importValid).toBeTruthy();
|
||||
expect(modal.state.errorMsg).toEqual(null);
|
||||
expect(modal.state.singleBuild).toBe(true);
|
||||
clickProceed();
|
||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette/2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifhv66g2f.AwRj4zNaKA==.CwRgDBldUExuBiQqA===.H4sIAAAAAAAAAx2Rzy4DURTGz7TuzHRu47ZjWreKlg5iQ9KFZ9CENyBWtWo8gIUFsamteAIJi0qEWIhdN11ZEN1IwyNYVKRpcXzH5su553f_XyfvKiLTYma-TkScyHVcokoYEdmbBNDsiDla-WUOT5LgyfAshHdvyGyjFFHUQCSrBU8TLT4gYq4DNL_LhNTFN3PwiqdZQyX2C-sekep-Mrs1RIbnDppsIogD1UAtN7JEM9eIzZg8hmhsEU32gFmrdgB_UARvjYEr4QMUMffoxGnV-M8X3hZ_lAO-gmWq2Eq2IVtDOzZ2Hbbuws6KxCKmKUUydgRb3woSiUXMs6Cs7Qt6FCQSi5hxkNKhj6qhfcPU_kU4wYrFMseSOmFXMKbuwZsViUWMlq1sbhvJ_lKyfqTqEJGJyoC5eIpU9x2TRnUswYXyF77BW4Z3qQuv05GDTpfvcDzvSbxJ5DtV_aHS1I4clyB2A5_b-pAL8x_enn626gEAAA==?bn=Imported%20Federal%20Corvette');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Import E:D Shipyard Builds', function() {
|
||||
|
||||
it('imports a valid builds', function() {
|
||||
|
||||
@@ -84,12 +84,13 @@
|
||||
"dependencies": {
|
||||
"babel-polyfill": "*",
|
||||
"classnames": "^2.2.0",
|
||||
"browserify-zlib": "ipfs/browserify-zlib",
|
||||
"coriolis-data": "EDCD/coriolis-data",
|
||||
"d3": "3.5.16",
|
||||
"fbemitter": "^2.0.0",
|
||||
"lodash": "^4.15.0",
|
||||
"lz-string": "^1.4.4",
|
||||
"react-number-editor": "^4.0.2",
|
||||
"react-number-editor": "Athanasius/react-number-editor.git#miggy",
|
||||
"react": "^15.0.1",
|
||||
"react-dom": "^15.0.1",
|
||||
"superagent": "^1.4.0"
|
||||
|
||||
@@ -7,6 +7,8 @@ import Persist from './stores/Persist';
|
||||
import Header from './components/Header';
|
||||
import Tooltip from './components/Tooltip';
|
||||
import ModalImport from './components/ModalImport';
|
||||
import * as CompanionApiUtils from './utils/CompanionApiUtils';
|
||||
import { outfitURL } from './utils/UrlGenerators'
|
||||
|
||||
import AboutPage from './pages/AboutPage';
|
||||
import NotFoundPage from './pages/NotFoundPage';
|
||||
@@ -15,6 +17,8 @@ import ComparisonPage from './pages/ComparisonPage';
|
||||
import ShipyardPage from './pages/ShipyardPage';
|
||||
import ErrorDetails from './pages/ErrorDetails';
|
||||
|
||||
const zlib = require('zlib');
|
||||
|
||||
/**
|
||||
* Coriolis App
|
||||
*/
|
||||
@@ -52,6 +56,7 @@ export default class Coriolis extends React.Component {
|
||||
this._onLanguageChange = this._onLanguageChange.bind(this);
|
||||
this._onSizeRatioChange = this._onSizeRatioChange.bind(this);
|
||||
this._keyDown = this._keyDown.bind(this);
|
||||
this._importBuild = this._importBuild.bind(this);
|
||||
|
||||
this.emitter = new EventEmitter();
|
||||
this.state = {
|
||||
@@ -63,13 +68,36 @@ export default class Coriolis extends React.Component {
|
||||
};
|
||||
|
||||
Router('', (r) => this._setPage(ShipyardPage, r));
|
||||
Router('/import?', (r) => this._importBuild(r));
|
||||
Router('/import/:data', (r) => this._importBuild(r));
|
||||
Router('/outfit/?', (r) => this._setPage(OutfittingPage, r));
|
||||
Router('/outfit/:ship/?', (r) => this._setPage(OutfittingPage, r));
|
||||
Router('/outfit/:ship/:code?', (r) => this._setPage(OutfittingPage, r));
|
||||
Router('/compare/:name?', (r) => this._setPage(ComparisonPage, r));
|
||||
Router('/comparison?', (r) => this._setPage(ComparisonPage, r));
|
||||
Router('/comparison/:code', (r) => this._setPage(ComparisonPage, r));
|
||||
Router('/about', (r) => this._setPage(AboutPage, r));
|
||||
Router('*', (r) => this._setPage(null, r));
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a build directly
|
||||
* @param {Object} r The current route
|
||||
*/
|
||||
_importBuild(r) {
|
||||
try {
|
||||
// Need to decode and gunzip the data, then build the ship
|
||||
const data = zlib.gunzipSync(new Buffer(r.params.data, 'base64'));
|
||||
const json = JSON.parse(data);
|
||||
const ship = CompanionApiUtils.shipFromJson(json);
|
||||
r.params.ship = ship.id;
|
||||
r.params.code = ship.toString();
|
||||
this._setPage(OutfittingPage, r);
|
||||
} catch (err) {
|
||||
this._onError('Failed to import ship', r.path, 0, 0, err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates / Sets the page and route context
|
||||
* @param {[type]} page The page to be shown
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import Link from './Link';
|
||||
import cn from 'classnames';
|
||||
import { outfitURL } from '../utils/UrlGenerators';
|
||||
import { SizeMap } from '../shipyard/Constants';
|
||||
|
||||
|
||||
@@ -71,7 +72,7 @@ export default class ComparisonTable extends TranslatedComponent {
|
||||
* @return {React.Component} Table row
|
||||
*/
|
||||
_buildRow(build, facets, formats, units) {
|
||||
let url = `/outfit/${build.id}/${build.toString()}?bn=${build.buildName}`;
|
||||
let url = outfitURL(build.id, build.toString(), build.buildName)
|
||||
let cells = [
|
||||
<td key='s' className='tl'><Link href={url}>{build.name}</Link></td>,
|
||||
<td key='bn' className='tl'><Link href={url}>{build.buildName}</Link></td>
|
||||
|
||||
@@ -507,19 +507,19 @@ export default class CostSection extends TranslatedComponent {
|
||||
scoop = true;
|
||||
break;
|
||||
case 'scb':
|
||||
q = slotGroup[i].m.cells;
|
||||
q = slotGroup[i].m.getCells();
|
||||
break;
|
||||
case 'am':
|
||||
q = slotGroup[i].m.ammo;
|
||||
q = slotGroup[i].m.getAmmo();
|
||||
break;
|
||||
case 'pv':
|
||||
srvs += slotGroup[i].m.vehicles;
|
||||
srvs += slotGroup[i].m.getBays();
|
||||
break;
|
||||
case 'fx': case 'hb': case 'cc': case 'pc':
|
||||
limpets = ship.cargoCapacity;
|
||||
break;
|
||||
default:
|
||||
q = slotGroup[i].m.clip + slotGroup[i].m.ammo;
|
||||
q = slotGroup[i].m.getClip() + slotGroup[i].m.getAmmo();
|
||||
}
|
||||
// Calculate ammo costs only if a cost is specified
|
||||
if (slotGroup[i].m.ammocost > 0) {
|
||||
@@ -532,6 +532,17 @@ export default class CostSection extends TranslatedComponent {
|
||||
ammoCosts.push(item);
|
||||
ammoTotal += item.total;
|
||||
}
|
||||
// Add fighters
|
||||
if (slotGroup[i].m.grp === 'fh') {
|
||||
item = {
|
||||
m: slotGroup[i].m,
|
||||
max: slotGroup[i].m.getRebuildsPerBay() * slotGroup[i].m.getBays(),
|
||||
cost: slotGroup[i].m.fightercost,
|
||||
total: slotGroup[i].m.getRebuildsPerBay() * slotGroup[i].m.getBays() * slotGroup[i].m.fightercost
|
||||
};
|
||||
ammoCosts.push(item);
|
||||
ammoTotal += item.total;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -552,12 +563,13 @@ export default class CostSection extends TranslatedComponent {
|
||||
item = {
|
||||
m: { name: 'SRVs', class: '', rating: '' },
|
||||
max: srvs,
|
||||
cost: 6005,
|
||||
total: srvs * 6005
|
||||
cost: 1030,
|
||||
total: srvs * 1030
|
||||
};
|
||||
ammoCosts.push(item);
|
||||
ammoTotal += item.total;
|
||||
}
|
||||
|
||||
// Calculate refuel costs if no scoop present
|
||||
if (!scoop) {
|
||||
item = {
|
||||
|
||||
75
src/app/components/DefenceSummary.jsx
Normal file
75
src/app/components/DefenceSummary.jsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { DamageKinetic, DamageThermal, DamageExplosive } from './SvgIcons';
|
||||
|
||||
/**
|
||||
* Defence summary
|
||||
*/
|
||||
export default class DefenceSummary extends TranslatedComponent {
|
||||
static PropTypes = {
|
||||
ship: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render defence summary
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
let ship = this.props.ship;
|
||||
let { language, tooltip, termtip } = this.context;
|
||||
let { formats, translate, units } = language;
|
||||
let hide = tooltip.bind(null, null);
|
||||
|
||||
return (
|
||||
<span>
|
||||
<h1>{translate('defence summary')}</h1>
|
||||
<table className='summary' style={{ marginLeft: 'auto', marginRight: 'auto', lineHeight: '1em', backgroundColor: 'transparent' }}>
|
||||
<tbody>
|
||||
{ship.shield ?
|
||||
<tr>
|
||||
<td colSpan='4' className='summary'><h2>{translate('shields')}: {formats.int(ship.shield)} {units.MJ}</h2></td>
|
||||
</tr> : null }
|
||||
{ship.shield ?
|
||||
<tr>
|
||||
<td className='ri' onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECOVER', { cap: 0 })} onMouseLeave={hide}>{translate('recovery')}</td>
|
||||
<td className='le'>{formats.time(ship.calcShieldRecovery())}</td>
|
||||
<td className='ri' onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECHARGE', { cap: 0 })} onMouseLeave={hide}>{translate('recharge')}</td>
|
||||
<td className='le'>{formats.time(ship.calcShieldRecharge())}</td>
|
||||
</tr> : null }
|
||||
{ship.shield ?
|
||||
<tr>
|
||||
<td className='le'>{translate('damage from')}</td>
|
||||
<td className='ri' onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /> {formats.pct1(ship.shieldExplRes || 1)}</td>
|
||||
<td className='ri' onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /> {formats.pct1(ship.shieldKinRes || 1)}</td>
|
||||
<td className='ri' onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /> {formats.pct1(ship.shieldThermRes || 1)}</td>
|
||||
</tr> : null }
|
||||
|
||||
{ ship.shield && ship.shieldCells ?
|
||||
<tr>
|
||||
<td colSpan='4'><h2>{translate('shield cells')}: {formats.int(ship.shieldCells)} {units.MJ}</h2></td>
|
||||
</tr> : null }
|
||||
|
||||
<tr>
|
||||
<td colSpan='4'><h2>{translate('armour')}: {formats.int(ship.armour)}</h2></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='le'>{translate('damage from')}</td>
|
||||
<td className='ri' onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /> {formats.pct1(ship.hullExplRes || 1)}</td>
|
||||
<td className='ri' onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /> {formats.pct1(ship.hullKinRes || 1)}</td>
|
||||
<td className='ri' onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /> {formats.pct1(ship.hullThermRes || 1)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import Slot from './Slot';
|
||||
import Persist from '../stores/Persist';
|
||||
import { DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret, ListModifications, Modified } from './SvgIcons';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
@@ -41,6 +42,7 @@ export default class HardpointSlot extends Slot {
|
||||
let { drag, drop } = this.props;
|
||||
let { termtip, tooltip } = this.context;
|
||||
let validMods = Modifications.validity[m.grp] || [];
|
||||
let showModuleResistances = Persist.showModuleResistances();
|
||||
|
||||
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
|
||||
<div className={'cb'}>
|
||||
@@ -62,10 +64,15 @@ export default class HardpointSlot extends Slot {
|
||||
{ m.getHps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'hpsshps' : 'hps')} onMouseOut={tooltip.bind(null, null)}>{translate('HPS')}: {formats.round1(m.getHps())} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getHps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null }
|
||||
{ m.getDps() && m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, 'dpe')} onMouseOut={tooltip.bind(null, null)}>{translate('DPE')}: {formats.f1(m.getDps() / m.getEps())}</div> : null }
|
||||
{ m.getRoF() ? <div className={'l'} onMouseOver={termtip.bind(null, 'rof')} onMouseOut={tooltip.bind(null, null)}>{translate('ROF')}: {formats.f1(m.getRoF())}{u.ps}</div> : null }
|
||||
{ m.getRange() && !m.getDps() ? <div className={'l'}>{translate('Range')} : {formats.round(m.getRange() / 1000)}{u.km}</div> : null }
|
||||
{ m.getRange() ? <div className={'l'}>{translate('range')} {formats.f1(m.getRange() / 1000)}{u.km}</div> : null }
|
||||
{ m.getShieldBoost() ? <div className={'l'}>+{formats.pct1(m.getShieldBoost())}</div> : null }
|
||||
{ m.getAmmo() ? <div className={'l'}>{translate('ammunition')}: {formats.int(m.getClip())}/{formats.int(m.getAmmo())}</div> : null }
|
||||
{ m.getJitter() ? <div className={'l'}>{translate('jitter')}: {formats.f2(m.getJitter())}°</div> : null }
|
||||
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null }
|
||||
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
|
||||
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</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 {
|
||||
|
||||
@@ -203,6 +203,13 @@ export default class Header extends TranslatedComponent {
|
||||
Persist.showTooltips(!Persist.showTooltips());
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle module resistances setting
|
||||
*/
|
||||
_toggleModuleResistances() {
|
||||
Persist.showModuleResistances(!Persist.showModuleResistances());
|
||||
}
|
||||
|
||||
/**
|
||||
* Show delete all modal
|
||||
* @param {SyntheticEvent} e Event
|
||||
@@ -359,6 +366,7 @@ export default class Header extends TranslatedComponent {
|
||||
_getSettingsMenu() {
|
||||
let translate = this.context.language.translate;
|
||||
let tips = Persist.showTooltips();
|
||||
let moduleResistances = Persist.showModuleResistances();
|
||||
|
||||
return (
|
||||
<div className='menu-list no-wrap cap' onClick={ (e) => e.stopPropagation() }>
|
||||
@@ -376,6 +384,10 @@ export default class Header extends TranslatedComponent {
|
||||
<td>{translate('tooltips')}</td>
|
||||
<td className={cn('ri', { disabled: !tips, 'primary-disabled': tips })}>{(tips ? '✓' : '✗')}</td>
|
||||
</tr>
|
||||
<tr className='cap ptr' onClick={this._toggleModuleResistances} >
|
||||
<td>{translate('module resistances')}</td>
|
||||
<td className={cn('ri', { disabled: !moduleResistances, 'primary-disabled': moduleResistances })}>{(moduleResistances ? '✓' : '✗')}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{translate('insurance')}</td>
|
||||
<td className='ri'>
|
||||
@@ -438,6 +450,7 @@ export default class Header extends TranslatedComponent {
|
||||
Persist.addListener('deletedAll', update);
|
||||
Persist.addListener('builds', update);
|
||||
Persist.addListener('tooltips', update);
|
||||
Persist.addListener('moduleresistances', update);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import Slot from './Slot';
|
||||
import Persist from '../stores/Persist';
|
||||
import { ListModifications, Modified } from './SvgIcons';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
@@ -23,6 +24,7 @@ export default class InternalSlot extends Slot {
|
||||
let { drag, drop, ship } = this.props;
|
||||
let { termtip, tooltip } = this.context;
|
||||
let validMods = Modifications.validity[m.grp] || [];
|
||||
let showModuleResistances = Persist.showModuleResistances();
|
||||
|
||||
let mass = m.getMass() || m.cargo || m.fuel || 0;
|
||||
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
|
||||
@@ -38,7 +40,7 @@ export default class InternalSlot extends Slot {
|
||||
{ m.rate ? <div className={'l'}>{translate('rate')}: {m.rate}{u.kgs} {translate('refuel time')}: {formats.time(this.props.fuel * 1000 / m.rate)}</div> : null }
|
||||
{ m.getAmmo() ? <div className={'l'}>{translate('ammunition')}: {formats.gen(m.getAmmo())}</div> : null }
|
||||
{ m.cells ? <div className={'l'}>{translate('cells')}: {m.cells}</div> : null }
|
||||
{ m.recharge ? <div className={'l'}>{translate('recharge')}: {m.recharge} <u>MJ</u> {translate('total')}: {m.cells * m.recharge}{u.MJ}</div> : null }
|
||||
{ m.shieldreinforcement ? <div className={'l'}>{translate('shieldreinforcement')}: {formats.int(m.getShieldReinforcement())} <u>MJ</u> {translate('total')}: {formats.int(m.cells * m.getShieldReinforcement())}{u.MJ}</div> : null }
|
||||
{ m.repair ? <div className={'l'}>{translate('repair')}: {m.repair}</div> : null }
|
||||
{ m.getFacingLimit() ? <div className={'l'}>{translate('facinglimit')} {formats.f1(m.getFacingLimit())}°</div> : null }
|
||||
{ m.getRange() ? <div className={'l'}>{translate('range')} {formats.f2(m.getRange())}{u.km}</div> : null }
|
||||
@@ -49,8 +51,12 @@ export default class InternalSlot extends Slot {
|
||||
{ m.rangeLS ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null }
|
||||
{ m.rangeLS === null ? <div className={'l'}>∞{u.Ls}</div> : null }
|
||||
{ m.rangeRating ? <div className={'l'}>{translate('range')}: {m.rangeRating}</div> : null }
|
||||
{ m.getHullReinforcement() ? <div className={'l'}>+{formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost'))} <u className='cap'>{translate('armour')}</u></div> : null }
|
||||
{ m.getHullReinforcement() ? <div className={'l'}>+{formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost') / 10000)} <u className='cap'>{translate('armour')}</u></div> : null }
|
||||
{ m.passengers ? <div className={'l'}>{translate('passengers')}: {m.passengers}</div> : null }
|
||||
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null }
|
||||
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
|
||||
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</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>
|
||||
|
||||
@@ -11,6 +11,7 @@ import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import { fromDetailedBuild } from '../shipyard/Serializer';
|
||||
import { Download } from './SvgIcons';
|
||||
import { outfitURL } from '../utils/UrlGenerators';
|
||||
import * as CompanionApiUtils from '../utils/CompanionApiUtils';
|
||||
|
||||
const textBuildRegex = new RegExp('^\\[([\\w \\-]+)\\]\n');
|
||||
const lineRegex = new RegExp('^([\\dA-Z]{1,2}): (\\d)([A-I])[/]?([FGT])?([SD])? ([\\w\\- ]+)');
|
||||
@@ -112,6 +113,7 @@ export default class ModalImport extends TranslatedComponent {
|
||||
this._importBackup = this._importBackup.bind(this);
|
||||
this._importDetailedArray = this._importDetailedArray.bind(this);
|
||||
this._importTextBuild = this._importTextBuild.bind(this);
|
||||
this._importCompanionApiBuild = this._importCompanionApiBuild.bind(this);
|
||||
this._validateImport = this._validateImport.bind(this);
|
||||
}
|
||||
|
||||
@@ -183,6 +185,21 @@ export default class ModalImport extends TranslatedComponent {
|
||||
this.setState({ builds });
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a build direct from the companion API
|
||||
* @param {string} build JSON from the companion API information
|
||||
* @throws {string} if parse/import fails
|
||||
*/
|
||||
_importCompanionApiBuild(build) {
|
||||
const shipModel = CompanionApiUtils.shipModelFromJson(build);
|
||||
const ship = CompanionApiUtils.shipFromJson(build);
|
||||
|
||||
let builds = {};
|
||||
builds[shipModel] = {};
|
||||
builds[shipModel]['Imported ' + Ships[shipModel].properties.name] = ship.toString();
|
||||
this.setState({ builds, singleBuild: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a text build from ED Shipyard
|
||||
* @param {string} buildStr Build string
|
||||
@@ -315,7 +332,11 @@ export default class ModalImport extends TranslatedComponent {
|
||||
throw 'Must be an object or array!';
|
||||
}
|
||||
|
||||
if (importData instanceof Array) { // Must be detailed export json
|
||||
if (importData.modules != null && importData.modules.Armour != null) { // Only the companion API has this information
|
||||
this._importCompanionApiBuild(importData); // Single sihp definition
|
||||
} else if (importData.ship != null && importData.ship.modules != null && importData.ship.modules.Armour != null) { // Only the companion API has this information
|
||||
this._importCompanionApiBuild(importData.ship); // Complete API dump
|
||||
} else if (importData instanceof Array) { // Must be detailed export json
|
||||
this._importDetailedArray(importData);
|
||||
} else if (importData.ship && typeof importData.name !== undefined) { // Using JSON from a single ship build export
|
||||
this._importDetailedArray([importData]); // Convert to array with singleobject
|
||||
|
||||
@@ -7,7 +7,7 @@ import NumberEditor from 'react-number-editor';
|
||||
/**
|
||||
* Modification
|
||||
*/
|
||||
export default class ModificationsMenu extends TranslatedComponent {
|
||||
export default class Modification extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
ship: React.PropTypes.object.isRequired,
|
||||
@@ -24,26 +24,30 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
this.state.value = this.props.m.getModValue(this.props.name) * 100 || 0;
|
||||
this.state.value = this.props.m.getModValue(this.props.name) / 100 || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update modification given a value.
|
||||
* @param {Number} value The value to set
|
||||
* @param {Number} value The value to set. This comes in as a string and must be stored in state as a string,
|
||||
* because it needs to allow illegal 'numbers' ('-', '1.', etc) when the user is typing
|
||||
* in a value by hand
|
||||
*/
|
||||
_updateValue(value) {
|
||||
let scaledValue = Math.floor(Number(value) * 100) / 10000;
|
||||
const name = this.props.name;
|
||||
|
||||
let scaledValue = Math.round(Number(value) * 100);
|
||||
// Limit to +1000% / -100%
|
||||
if (scaledValue > 10) {
|
||||
scaledValue = 10;
|
||||
if (scaledValue > 100000) {
|
||||
scaledValue = 100000;
|
||||
value = 1000;
|
||||
}
|
||||
if (scaledValue < -1) {
|
||||
scaledValue = -1;
|
||||
if (scaledValue < -10000) {
|
||||
scaledValue = -10000;
|
||||
value = -100;
|
||||
}
|
||||
|
||||
let m = this.props.m;
|
||||
let name = this.props.name;
|
||||
let ship = this.props.ship;
|
||||
ship.setModification(m, name, scaledValue);
|
||||
|
||||
@@ -62,7 +66,7 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
return (
|
||||
<div className={'cb'} key={name}>
|
||||
<div className={'cb'}>{translate(name)}{name === 'jitter' ? ' (°)' : ' (%)'}</div>
|
||||
<NumberEditor className={'cb'} style={{ width: '100%', textAlign: 'center' }} step={0.01} stepModifier={1} decimals={2} value={this.state.value} onValueChange={this._updateValue.bind(this)} />
|
||||
<NumberEditor className={'cb'} style={{ width: '90%', textAlign: 'center' }} step={0.01} stepModifier={1} decimals={2} value={this.state.value} onValueChange={this._updateValue.bind(this)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,11 +50,13 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
* @return {React.Component} List
|
||||
*/
|
||||
render() {
|
||||
let { tooltip, termtip } = this.context;
|
||||
return (
|
||||
<div
|
||||
className={cn('select', this.props.className)}
|
||||
onClick={(e) => e.stopPropagation() }
|
||||
onContextMenu={stopCtxPropagation}
|
||||
onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')} onMouseOut={tooltip.bind(null, null)}
|
||||
>
|
||||
{this.state.list}
|
||||
</div>
|
||||
|
||||
70
src/app/components/OffenceSummary.jsx
Normal file
70
src/app/components/OffenceSummary.jsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { DamageKinetic, DamageThermal, DamageExplosive } from './SvgIcons';
|
||||
|
||||
/**
|
||||
* Offence summary
|
||||
*/
|
||||
export default class OffenceSummary extends TranslatedComponent {
|
||||
static PropTypes = {
|
||||
ship: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render offence summary
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
let ship = this.props.ship;
|
||||
let { language, tooltip, termtip } = this.context;
|
||||
let { formats, translate } = language;
|
||||
|
||||
return (
|
||||
<span>
|
||||
<h1>{translate('offence summary')}</h1>
|
||||
<table className='summary' style={{ marginLeft: 'auto', marginRight: 'auto', lineHeight: '1em', backgroundColor: 'transparent' }}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colSpan='4' className='summary'><h2>{translate('dps')}: {formats.f1(ship.totalDps)}</h2></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='le'>{translate('damage by')}</td>
|
||||
<td className='ri' onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /> {formats.f1(ship.totalExplDps)}</td>
|
||||
<td className='ri' onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /> {formats.f1(ship.totalKinDps)}</td>
|
||||
<td className='ri' onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /> {formats.f1(ship.totalThermDps)}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colSpan='4' className='summary'><h2>{translate('sdps')}: {formats.f1(ship.totalSDps)}</h2></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='le'>{translate('damage by')}</td>
|
||||
<td className='ri' onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /> {formats.f1(ship.totalExplSDps)}</td>
|
||||
<td className='ri' onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /> {formats.f1(ship.totalKinSDps)}</td>
|
||||
<td className='ri' onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /> {formats.f1(ship.totalThermSDps)}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colSpan='4' className='summary'><h2>{translate('dpe')}: {formats.f1(ship.totalDpe)}</h2></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='le'>{translate('damage by')}</td>
|
||||
<td className='ri' onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /> {formats.f1(ship.totalExplDpe)}</td>
|
||||
<td className='ri' onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /> {formats.f1(ship.totalKinDpe)}</td>
|
||||
<td className='ri' onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /> {formats.f1(ship.totalThermDpe)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -34,20 +34,6 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
||||
sgRecharge = time(ship.calcShieldRecharge());
|
||||
}
|
||||
|
||||
// <th colSpan={3}>{translate('shield resistance')}</th>
|
||||
// <th colSpan={3}>{translate('hull resistance')}</th>
|
||||
// <th className='lft'>{translate('explosive')}</th>
|
||||
// <th className='lft'>{translate('kinetic')}</th>
|
||||
// <th className='lft'>{translate('thermal')}</th>
|
||||
// <th className='lft'>{translate('explosive')}</th>
|
||||
// <th className='lft'>{translate('kinetic')}</th>
|
||||
// <th className='lft'>{translate('thermal')}</th>
|
||||
// <td>{pct(ship.shieldExplRes)}</td>
|
||||
// <td>{pct(ship.shieldKinRes)}</td>
|
||||
// <td>{pct(ship.shieldThermRes)}</td>
|
||||
// <td>{pct(ship.hullExplRes)}</td>
|
||||
// <td>{pct(ship.hullKinRes)}</td>
|
||||
// <td>{pct(ship.hullThermRes)}</td>
|
||||
return <div id='summary'>
|
||||
<table id='summaryTable'>
|
||||
<thead>
|
||||
@@ -58,7 +44,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
||||
<th onMouseEnter={termtip.bind(null, 'energy per second')} onMouseLeave={hide} rowSpan={2}>{translate('EPS')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th>
|
||||
<th rowSpan={2}>{translate('armour')}</th>
|
||||
<th colSpan={3}>{translate('shields')}</th>
|
||||
<th rowSpan={2}>{translate('shields')}</th>
|
||||
<th colSpan={3}>{translate('mass')}</th>
|
||||
<th rowSpan={2}>{translate('cargo')}</th>
|
||||
<th rowSpan={2}>{translate('fuel')}</th>
|
||||
@@ -67,9 +53,6 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
||||
<th onMouseEnter={termtip.bind(null, 'mass lock factor')} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className='lft'>{translate('strength')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECOVER', { cap: 0 })} onMouseLeave={hide}>{translate('recovery')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECHARGE', { cap: 0 })} onMouseLeave={hide}>{translate('recharge')}</th>
|
||||
<th className='lft'>{translate('hull')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'PHRASE_UNLADEN', { cap: 0 })} onMouseLeave={hide}>{translate('unladen')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'PHRASE_LADEN', { cap: 0 })} onMouseLeave={hide}>{translate('laden')}</th>
|
||||
@@ -90,8 +73,6 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
||||
<td>{f1(ship.totalHps)}</td>
|
||||
<td>{int(ship.armour)}</td>
|
||||
<td className={sgClassNames}>{int(ship.shield)} {u.MJ}</td>
|
||||
<td className={sgClassNames}>{sgRecover}</td>
|
||||
<td className={sgClassNames}>{sgRecharge}</td>
|
||||
<td>{ship.hullMass} {u.T}</td>
|
||||
<td>{int(ship.unladenMass)} {u.T}</td>
|
||||
<td>{int(ship.ladenMass)} {u.T}</td>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import Persist from '../stores/Persist';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { jumpRange } from '../shipyard/Calculations';
|
||||
import { diffDetails } from '../utils/SlotFunctions';
|
||||
@@ -46,6 +47,7 @@ export default class StandardSlot extends TranslatedComponent {
|
||||
let classRating = m.class + m.rating;
|
||||
let menu;
|
||||
let validMods = m == null ? [] : (Modifications.validity[m.grp] || []);
|
||||
let showModuleResistances = Persist.showModuleResistances();
|
||||
let mass = m.getMass() || m.cargo || m.fuel || 0;
|
||||
|
||||
if (!selected) {
|
||||
@@ -79,11 +81,10 @@ export default class StandardSlot extends TranslatedComponent {
|
||||
<div className={cn('details-container', { warning: warning && warning(slot.m) })}>
|
||||
<div className={'sz'}>{slot.maxClass}</div>
|
||||
<div>
|
||||
<div className={'l'}>{classRating} {translate(m.grp == 'bh' ? m.grp : m.name || m.grp)}{ m.mods && Object.keys(m.mods).length > 0 ? <span className='r' onMouseOver={termtip.bind(null, 'modified')} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : null }</div>
|
||||
<div className={'l'}>{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span className='r' onMouseOver={termtip.bind(null, 'modified')} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : null }</div>
|
||||
<div className={'r'}>{formats.round(mass)}{units.T}</div>
|
||||
<div/>
|
||||
<div className={'cb'}>
|
||||
{ m.grp == 'bh' && m.name ? <div className='l'>{translate(m.name)}</div> : null }
|
||||
{ m.getOptimalMass() ? <div className='l'>{translate('optimal mass')}: {formats.int(m.getOptimalMass())}{units.T}</div> : null }
|
||||
{ m.getMaxMass() ? <div className='l'>{translate('max mass')}: {formats.int(m.getMaxMass())}{units.T}</div> : null }
|
||||
{ m.getRange() ? <div className='l'>{translate('range')}: {formats.f2(m.getRange())}{units.km}</div> : null }
|
||||
@@ -94,6 +95,9 @@ export default class StandardSlot extends TranslatedComponent {
|
||||
{ m.getWeaponsCapacity() ? <div className='l'>{translate('WEP')}: {formats.f1(m.getWeaponsCapacity())}{units.MJ} / {formats.f1(m.getWeaponsRechargeRate())}{units.MW}</div> : null }
|
||||
{ m.getSystemsCapacity() ? <div className='l'>{translate('SYS')}: {formats.f1(m.getSystemsCapacity())}{units.MJ} / {formats.f1(m.getSystemsRechargeRate())}{units.MW}</div> : null }
|
||||
{ m.getEnginesCapacity() ? <div className='l'>{translate('ENG')}: {formats.f1(m.getEnginesCapacity())}{units.MJ} / {formats.f1(m.getEnginesRechargeRate())}{units.MW}</div> : null }
|
||||
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null }
|
||||
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
|
||||
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</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>
|
||||
|
||||
@@ -4,7 +4,6 @@ import SlotSection from './SlotSection';
|
||||
import StandardSlot from './StandardSlot';
|
||||
import Module from '../shipyard/Module';
|
||||
import { diffDetails } from '../utils/SlotFunctions';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import * as ShipRoles from '../shipyard/ShipRoles';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ export const terms = {
|
||||
PHRASE_UNLADEN: 'Ship mass excluding fuel and cargo',
|
||||
PHRASE_UPDATE_RDY: 'Update Available! Click to refresh',
|
||||
|
||||
HELP_MODIFICATIONS_MENU: 'Click on a number to enter a new value, or drag along the bar for small changes',
|
||||
|
||||
// Other languages fallback to these values
|
||||
// Only Translate to other languages if the name is different in-game
|
||||
am: 'Auto Field-Maintenance Unit',
|
||||
@@ -92,14 +94,18 @@ export const terms = {
|
||||
// Unit for seconds
|
||||
secs: 's',
|
||||
|
||||
// Hardpoint abbreviations
|
||||
// Weapon, offence and defence
|
||||
dpe: 'Damage per MJ of energy',
|
||||
dps: 'Damage per second',
|
||||
sdps: 'Sustained damage per second',
|
||||
dpssdps: 'Damage per second (sustained damage per second)',
|
||||
eps: 'Energy per second',
|
||||
epsseps: 'Energy per second (sustained energy per second)',
|
||||
hps: 'Heat per second',
|
||||
hpsshps: 'Heat per second (sustained heat per second)',
|
||||
'damage by': 'Damage by',
|
||||
'damage from': 'Damage from',
|
||||
'shield cells': 'Shield cells',
|
||||
|
||||
// Modifications
|
||||
ammo: 'Ammunition maximum',
|
||||
@@ -134,6 +140,7 @@ export const terms = {
|
||||
rof: 'Rate of fire',
|
||||
shield: 'Shield',
|
||||
shieldboost: 'Shield boost',
|
||||
shieldreinforcement: 'Shield reinforcement',
|
||||
spinup: 'Spin up time',
|
||||
syscap: 'Systems capacity',
|
||||
sysrate: 'Systems recharge rate',
|
||||
|
||||
@@ -345,7 +345,7 @@ export default class ComparisonPage extends Page {
|
||||
|
||||
let code = fromComparison(name, builds, selectedFacets, predicate, desc);
|
||||
let loc = window.location;
|
||||
return `${loc.protocol}//${loc.host}/comparison/${code}`;
|
||||
return loc.protocol + '//' + loc.host + '/comparison?code=' + encodeURIComponent(code);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,7 +26,7 @@ export default class ErrorDetails extends React.Component {
|
||||
if (ed) {
|
||||
content = <div style={{ textAlign:'left', fontSize:'0.8em', width: '43em', margin: '0 auto' }}>
|
||||
<div className='cen'>
|
||||
<a href='https://github.com/cmmcleod/coriolis/issues' target='_blank' title='Coriolis Github Project'>Create an issue on Github</a>
|
||||
<a href='https://github.com/edcd/coriolis/issues' target='_blank' title='Coriolis Github Project'>Create an issue on Github</a>
|
||||
{' if this keeps happening. Add these details:'}
|
||||
</div>
|
||||
<div style={{ marginTop: '2em' }}>
|
||||
|
||||
@@ -8,13 +8,14 @@ import Persist from '../stores/Persist';
|
||||
import Ship from '../shipyard/Ship';
|
||||
import { toDetailedBuild } from '../shipyard/Serializer';
|
||||
import { outfitURL } from '../utils/UrlGenerators';
|
||||
|
||||
import { FloppyDisk, Bin, Switch, Download, Reload, Fuel } from '../components/SvgIcons';
|
||||
import ShipSummaryTable from '../components/ShipSummaryTable';
|
||||
import StandardSlotSection from '../components/StandardSlotSection';
|
||||
import HardpointsSlotSection from '../components/HardpointsSlotSection';
|
||||
import InternalSlotSection from '../components/InternalSlotSection';
|
||||
import UtilitySlotSection from '../components/UtilitySlotSection';
|
||||
import OffenceSummary from '../components/OffenceSummary';
|
||||
import DefenceSummary from '../components/DefenceSummary';
|
||||
import LineChart from '../components/LineChart';
|
||||
import PowerManagement from '../components/PowerManagement';
|
||||
import CostSection from '../components/CostSection';
|
||||
@@ -288,12 +289,14 @@ export default class OutfittingPage extends Page {
|
||||
sStr = ship.getStandardString() + '.' + ship.getModificationsString(),
|
||||
iStr = ship.getInternalString() + '.' + ship.getModificationsString();
|
||||
|
||||
Router.replace(outfitURL(ship.id, code, buildName));
|
||||
|
||||
return (
|
||||
<div id='outfit' className={'page'} style={{ fontSize: (sizeRatio * 0.9) + 'em' }}>
|
||||
<div id='overview'>
|
||||
<h1>{ship.name}</h1>
|
||||
<div id='build'>
|
||||
<input value={newBuildName} onChange={this._buildNameChange} placeholder={translate('Enter Name')} maxLength={50} />
|
||||
<input value={newBuildName || ''} onChange={this._buildNameChange} placeholder={translate('Enter Name')} maxLength={50} />
|
||||
<button onClick={canSave && this._saveBuild} disabled={!canSave} onMouseOver={termtip.bind(null, 'save')} onMouseOut={hide}>
|
||||
<FloppyDisk className='lg' />
|
||||
</button>
|
||||
@@ -324,31 +327,11 @@ export default class OutfittingPage extends Page {
|
||||
<CostSection ship={ship} buildName={buildName} code={sStr + hStr + iStr} />
|
||||
|
||||
<div ref='chartThird' className='group third'>
|
||||
<h1>{translate('jump range')}</h1>
|
||||
<LineChart
|
||||
width={chartWidth}
|
||||
xMax={ship.cargoCapacity}
|
||||
yMax={ship.unladenRange}
|
||||
xUnit={translate('T')}
|
||||
yUnit={translate('LY')}
|
||||
yLabel={translate('jump range')}
|
||||
xLabel={translate('cargo')}
|
||||
func={state.jumpRangeChartFunc}
|
||||
/>
|
||||
<OffenceSummary ship={ship} code={code}/>
|
||||
</div>
|
||||
|
||||
<div className='group third'>
|
||||
<h1>{translate('total range')}</h1>
|
||||
<LineChart
|
||||
width={chartWidth}
|
||||
xMax={ship.cargoCapacity}
|
||||
yMax={ship.unladenFastestRange}
|
||||
xUnit={translate('T')}
|
||||
yUnit={translate('LY')}
|
||||
yLabel={translate('fastest range')}
|
||||
xLabel={translate('cargo')}
|
||||
func={state.fastestRangeChartFunc}
|
||||
/>
|
||||
<DefenceSummary ship={ship} code={code}/>
|
||||
</div>
|
||||
|
||||
<div className='group third'>
|
||||
@@ -397,3 +380,16 @@ export default class OutfittingPage extends Page {
|
||||
);
|
||||
}
|
||||
}
|
||||
// <div ref='chartThird' className='group third'>
|
||||
// <h1>{translate('jump range')}</h1>
|
||||
// <LineChart
|
||||
// width={chartWidth}
|
||||
// xMax={ship.cargoCapacity}
|
||||
// yMax={ship.unladenRange}
|
||||
// xUnit={translate('T')}
|
||||
// yUnit={translate('LY')}
|
||||
// yLabel={translate('jump range')}
|
||||
// xLabel={translate('cargo')}
|
||||
// func={state.jumpRangeChartFunc}
|
||||
// />
|
||||
// </div>
|
||||
|
||||
@@ -55,6 +55,7 @@ function shipSummary(shipId, shipData) {
|
||||
ship.optimizeMass({ th: ship.standard[1].maxClass + 'A', fsd: '2D', ft: '1C' }); // Optmize mass with Max Thrusters
|
||||
summary.topSpeed = ship.topSpeed;
|
||||
summary.topBoost = ship.topBoost;
|
||||
summary.baseArmour = ship.armour;
|
||||
|
||||
return summary;
|
||||
}
|
||||
@@ -141,7 +142,7 @@ export default class ShipyardPage extends Page {
|
||||
<td>{s.agility}</td>
|
||||
<td className='ri'>{fInt(s.speed)}{u['m/s']}</td>
|
||||
<td className='ri'>{fInt(s.boost)}{u['m/s']}</td>
|
||||
<td className='ri'>{s.baseArmour}</td>
|
||||
<td className='ri'>{fInt(s.baseArmour)}</td>
|
||||
<td className='ri'>{fInt(s.baseShieldStrength)}{u.MJ}</td>
|
||||
<td className='ri'>{fInt(s.topSpeed)}{u['m/s']}</td>
|
||||
<td className='ri'>{fInt(s.topBoost)}{u['m/s']}</td>
|
||||
|
||||
@@ -125,7 +125,7 @@ export const ShipFacets = [
|
||||
},
|
||||
{ // 3
|
||||
title: 'shields',
|
||||
props: ['shieldStrength'],
|
||||
props: ['shield'],
|
||||
unit: 'MJ',
|
||||
fmt: 'int',
|
||||
i: 3
|
||||
|
||||
@@ -28,16 +28,16 @@ export default class Module {
|
||||
/**
|
||||
* Get a value for a given modification
|
||||
* @param {Number} name The name of the modification
|
||||
* @return {Number} The value of the modification, as a decimal value where 1 is 100%
|
||||
* @return {Number} The value of the modification, as an integer value scaled so that 1.23% == 123
|
||||
*/
|
||||
getModValue(name) {
|
||||
return this.mods && this.mods[name] ? this.mods[name] / 10000 : null;
|
||||
return this.mods && this.mods[name] ? this.mods[name] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value for a given modification ID
|
||||
* @param {Number} name The name of the modification
|
||||
* @param {Number} value The value of the modification, as a decimal value where 1 is 100%
|
||||
* @param {Number} value The value of the modification, as an integer scaled so that -2.34% == -234
|
||||
*/
|
||||
setModValue(name, value) {
|
||||
if (!this.mods) {
|
||||
@@ -47,27 +47,41 @@ export default class Module {
|
||||
if (value == null || value == 0) {
|
||||
delete this.mods[name];
|
||||
} else {
|
||||
// Store value with 2dp
|
||||
this.mods[name] = Math.round(value * 10000);
|
||||
// Round just to be sure
|
||||
this.mods[name] = Math.round(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to obtain a modified value using standard multipliers
|
||||
* @param {String} name the name of the modifier to obtain
|
||||
* @param {Boolean} additive Optional true if the value is additive rather than multiplicative
|
||||
* @return {Number} the mass of this module
|
||||
*/
|
||||
_getModifiedValue(name) {
|
||||
let result = 0;
|
||||
if (this[name]) {
|
||||
result = this[name];
|
||||
if (result) {
|
||||
let mult = this.getModValue(name);
|
||||
if (mult) { result = result * (1 + mult); }
|
||||
_getModifiedValue(name, additive) {
|
||||
let result = this[name] || (additive ? 0 : null); // Additive NULL === 0
|
||||
if (result != null) {
|
||||
// Jitter is special, being the only non-percentage value (it is in fact degrees)
|
||||
const modValue = name === 'jitter' ? this.getModValue(name) / 100 : this.getModValue(name) / 10000;
|
||||
if (modValue) {
|
||||
if (additive) {
|
||||
result = result + modValue;
|
||||
} else {
|
||||
result = result * (1 + modValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is a shield generator
|
||||
* @return {Boolean} if this is a shield generator
|
||||
*/
|
||||
isShieldGenerator() {
|
||||
return (this.grp === 'sg' || this.grp === 'psg' || this.grp === 'bsg');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the power generation of this module, taking in to account modifications
|
||||
* @return {Number} the power generation of this module
|
||||
@@ -193,7 +207,7 @@ export default class Module {
|
||||
* @return {Number} the kinetic resistance of this module
|
||||
*/
|
||||
getKineticResistance() {
|
||||
return this._getModifiedValue('kinres');
|
||||
return this._getModifiedValue('kinres', true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,7 +215,7 @@ export default class Module {
|
||||
* @return {Number} the thermal resistance of this module
|
||||
*/
|
||||
getThermalResistance() {
|
||||
return this._getModifiedValue('thermres');
|
||||
return this._getModifiedValue('thermres', true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,7 +223,7 @@ export default class Module {
|
||||
* @return {Number} the explosive resistance of this module
|
||||
*/
|
||||
getExplosiveResistance() {
|
||||
return this._getModifiedValue('explres');
|
||||
return this._getModifiedValue('explres', true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -294,7 +308,7 @@ export default class Module {
|
||||
if (this['minmass']) {
|
||||
result = this['minmass'];
|
||||
if (result) {
|
||||
let mult = this.getModValue('optmass');
|
||||
let mult = this.getModValue('optmass') / 10000;
|
||||
if (mult) { result = result * (1 + mult); }
|
||||
}
|
||||
}
|
||||
@@ -319,7 +333,7 @@ export default class Module {
|
||||
if (this['maxmass']) {
|
||||
result = this['maxmass'];
|
||||
if (result) {
|
||||
let mult = this.getModValue('optmass');
|
||||
let mult = this.getModValue('optmass') / 10000;
|
||||
if (mult) { result = result * (1 + mult); }
|
||||
}
|
||||
}
|
||||
@@ -336,7 +350,7 @@ export default class Module {
|
||||
if (this['minmul']) {
|
||||
result = this['minmul'];
|
||||
if (result) {
|
||||
let mult = this.getModValue('optmul');
|
||||
let mult = this.getModValue('optmul') / 10000;
|
||||
if (mult) { result = result * (1 + mult); }
|
||||
}
|
||||
}
|
||||
@@ -361,7 +375,7 @@ export default class Module {
|
||||
if (this['maxmul']) {
|
||||
result = this['maxmul'];
|
||||
if (result) {
|
||||
let mult = this.getModValue('optmul');
|
||||
let mult = this.getModValue('optmul') / 10000;
|
||||
if (mult) { result = result * (1 + mult); }
|
||||
}
|
||||
}
|
||||
@@ -487,4 +501,44 @@ export default class Module {
|
||||
return this._getModifiedValue('hullboost');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the shield reinforcement for this module, taking in to account modifications
|
||||
* @return {Number} the shield reinforcement for this module
|
||||
*/
|
||||
getShieldReinforcement() {
|
||||
return this._getModifiedValue('shieldreinforcement');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bays for this module, taking in to account modifications
|
||||
* @return {Number} the bays for this module
|
||||
*/
|
||||
getBays() {
|
||||
return this._getModifiedValue('bays');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the rebuilds per bay for this module, taking in to account modifications
|
||||
* @return {Number} the rebuilds per bay for this module
|
||||
*/
|
||||
getRebuildsPerBay() {
|
||||
return this._getModifiedValue('rebuildsperbay');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cells for this module, taking in to account modifications
|
||||
* @return {Number} the cells for this module
|
||||
*/
|
||||
getCells() {
|
||||
return this._getModifiedValue('cells');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the jitter for this module, taking in to account modifications
|
||||
* @return {Number} the jitter for this module
|
||||
*/
|
||||
getJitter() {
|
||||
return this._getModifiedValue('jitter', true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ import { ModuleGroupToName, MountMap, BulkheadNames } from './Constants';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import Ship from './Ship';
|
||||
import * as ModuleUtils from './ModuleUtils';
|
||||
import * as Utils from '../utils/UtilityFunctions';
|
||||
import LZString from 'lz-string';
|
||||
import { outfitURL } from '../utils/UrlGenerators';
|
||||
|
||||
const STANDARD = ['powerPlant', 'thrusters', 'frameShiftDrive', 'lifeSupport', 'powerDistributor', 'sensors', 'fuelTank'];
|
||||
|
||||
@@ -83,7 +85,7 @@ export function toDetailedBuild(buildName, ship) {
|
||||
ship: ship.name,
|
||||
references: [{
|
||||
name: 'Coriolis.io',
|
||||
url: `https://coriolis.edcd.io/outfit/${ship.id}/${code}?bn=${encodeURIComponent(buildName)}`,
|
||||
url: 'https://coriolis.edcd.io' + outfitURL(ship.id, code, buildName),
|
||||
code,
|
||||
shipId: ship.id
|
||||
}],
|
||||
@@ -215,7 +217,7 @@ export function fromComparison(name, builds, facets, predicate, desc) {
|
||||
f: facets,
|
||||
p: predicate,
|
||||
d: desc ? 1 : 0
|
||||
})).replace(/\//g, '-');
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -224,5 +226,5 @@ export function fromComparison(name, builds, facets, predicate, desc) {
|
||||
* @return {Object} Comparison data object
|
||||
*/
|
||||
export function toComparison(code) {
|
||||
return JSON.parse(LZString.decompressFromBase64(code.replace(/-/g, '/')));
|
||||
return JSON.parse(LZString.decompressFromBase64(Utils.fromUrlSafe(code)));
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as Calc from './Calculations';
|
||||
import * as ModuleUtils from './ModuleUtils';
|
||||
import * as Utils from '../utils/UtilityFunctions';
|
||||
import Module from './Module';
|
||||
import LZString from 'lz-string';
|
||||
import isEqual from 'lodash/lang';
|
||||
@@ -126,7 +127,6 @@ export default class Ship {
|
||||
*/
|
||||
canBoost() {
|
||||
return this.canThrust() && // Thrusters operational
|
||||
this.getSlotStatus(this.standard[4]) == 3 && // Power distributor operational
|
||||
this.boostEnergy <= this.standard[4].m.getEnginesCapacity(); // PD capacitor is sufficient for boost
|
||||
}
|
||||
|
||||
@@ -194,11 +194,13 @@ export default class Ship {
|
||||
*/
|
||||
calcShieldRecovery() {
|
||||
if (this.shield > 0) {
|
||||
let sgSlot = this.findInternalByGroup('sg');
|
||||
let brokenRegenRate = 1 + sgSlot.m.getModValue('brokenregen');
|
||||
const sgSlot = this.findInternalByGroup('sg');
|
||||
if (sgSlot != null) {
|
||||
let brokenRegenRate = 1 + sgSlot.m.getModValue('brokenregen') / 10000;
|
||||
// 50% of shield strength / recovery recharge rate + 15 second delay before recharge starts
|
||||
return ((this.shield / 2) / (sgSlot.m.recover * brokenRegenRate)) + 15;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -210,11 +212,13 @@ export default class Ship {
|
||||
*/
|
||||
calcShieldRecharge() {
|
||||
if (this.shield > 0) {
|
||||
let sgSlot = this.findInternalByGroup('sg');
|
||||
let regenRate = 1 + sgSlot.m.getModValue('regen');
|
||||
const sgSlot = this.findInternalByGroup('sg');
|
||||
if (sgSlot != null) {
|
||||
let regenRate = 1 + sgSlot.m.getModValue('regen') / 10000;
|
||||
// 50% -> 100% recharge time, Bi-Weave shields charge at 1.8 MJ/s
|
||||
return (this.shield / 2) / ((sgSlot.m.grp == 'bsg' ? 1.8 : 1) * regenRate);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -233,9 +237,8 @@ export default class Ship {
|
||||
sg = sgSlot.m;
|
||||
}
|
||||
|
||||
// TODO obtain shield boost
|
||||
// return Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sg, this.shieldMultiplier + (multiplierDelta || 0));
|
||||
return Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sg, 0 + (multiplierDelta || 0));
|
||||
// TODO Not accurate if the ship has modified shield boosters
|
||||
return Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sg, 1 + (multiplierDelta || 0));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -397,19 +400,24 @@ export default class Ship {
|
||||
* Set a modification value
|
||||
* @param {Object} m The module to change
|
||||
* @param {Object} name The name of the modification to change
|
||||
* @param {Number} value The new value of the modification
|
||||
* @param {Number} value The new value of the modification. The value of the modification is scaled to provide two decimal places of precision in an integer. For example 1.23% is stored as 123
|
||||
*/
|
||||
setModification(m, name, value) {
|
||||
if (isNaN(value)) {
|
||||
// Value passed is invalid; reset it to 0
|
||||
value = 0;
|
||||
}
|
||||
|
||||
// Handle special cases
|
||||
if (name == 'pgen') {
|
||||
if (name === 'pgen') {
|
||||
// Power generation
|
||||
m.setModValue(name, value);
|
||||
this.updatePowerGenerated();
|
||||
} else if (name == 'power') {
|
||||
} else if (name === 'power') {
|
||||
// Power usage
|
||||
m.setModValue(name, value);
|
||||
this.updatePowerUsed();
|
||||
} else if (name == 'mass') {
|
||||
} else if (name === 'mass') {
|
||||
// Mass
|
||||
let oldMass = m.getMass();
|
||||
m.setModValue(name, value);
|
||||
@@ -418,32 +426,40 @@ export default class Ship {
|
||||
this.ladenMass = this.ladenMass - oldMass + newMass;
|
||||
this.updateTopSpeed();
|
||||
this.updateJumpStats();
|
||||
} else if (name == 'maxfuel') {
|
||||
} else if (name === 'maxfuel') {
|
||||
m.setModValue(name, value);
|
||||
this.updateJumpStats();
|
||||
} else if (name == 'optmass') {
|
||||
} else if (name === 'optmass') {
|
||||
m.setModValue(name, value);
|
||||
// Could be for any of thrusters, FSD or shield
|
||||
this.updateTopSpeed();
|
||||
this.updateJumpStats();
|
||||
this.updateShield();
|
||||
} else if (name == 'optmul') {
|
||||
this.recalculateShield();
|
||||
} else if (name === 'optmul') {
|
||||
m.setModValue(name, value);
|
||||
// Could be for any of thrusters, FSD or shield
|
||||
this.updateTopSpeed();
|
||||
this.updateJumpStats();
|
||||
this.updateShield();
|
||||
} else if (name == 'shieldboost') {
|
||||
this.recalculateShield();
|
||||
} else if (name === 'shieldboost') {
|
||||
m.setModValue(name, value);
|
||||
this.updateShield();
|
||||
} else if (name == 'hullboost') {
|
||||
this.recalculateShield();
|
||||
} else if (name === 'hullboost' || name === 'hullreinforcement') {
|
||||
m.setModValue(name, value);
|
||||
this.updateArmour();
|
||||
} else if (name == 'burst' || name == 'clip' || name == 'damage' || name == 'distdraw' || name == 'jitter' || name == 'piercing' || name == 'range' || name == 'reload' || name == 'rof' || name == 'thermload') {
|
||||
this.recalculateArmour();
|
||||
} else if (name === 'shieldreinforcement') {
|
||||
m.setModValue(name, value);
|
||||
this.recalculateShieldCells();
|
||||
} else if (name === 'burst' || name === 'clip' || name === 'damage' || name === 'distdraw' || name === 'jitter' || name === 'piercing' || name === 'range' || name === 'reload' || name === 'rof' || name === 'thermload') {
|
||||
m.setModValue(name, value);
|
||||
this.recalculateDps();
|
||||
this.recalculateHps();
|
||||
this.recalculateEps();
|
||||
} else if (name === 'explres' || name === 'kinres' || name === 'thermres') {
|
||||
m.setModValue(name, value);
|
||||
// Could be for shields or armour
|
||||
this.recalculateArmour();
|
||||
this.recalculateShield();
|
||||
} else {
|
||||
// Generic
|
||||
m.setModValue(name, value);
|
||||
@@ -471,9 +487,21 @@ export default class Ship {
|
||||
this.ladenMass = 0;
|
||||
this.armour = this.baseArmour;
|
||||
this.shield = this.baseShieldStrength;
|
||||
this.shieldCells = 0;
|
||||
this.totalCost = this.m.incCost ? this.m.discountedCost : 0;
|
||||
this.unladenMass = this.hullMass;
|
||||
this.totalDpe = 0;
|
||||
this.totalExplDpe = 0;
|
||||
this.totalKinDpe = 0;
|
||||
this.totalThermDpe = 0;
|
||||
this.totalDps = 0;
|
||||
this.totalExplDps = 0;
|
||||
this.totalKinDps = 0;
|
||||
this.totalThermDps = 0;
|
||||
this.totalSDps = 0;
|
||||
this.totalExplSDps = 0;
|
||||
this.totalKinSDps = 0;
|
||||
this.totalThermSDps = 0;
|
||||
this.totalEps = 0;
|
||||
this.totalHps = 0;
|
||||
this.shieldExplRes = 0;
|
||||
@@ -544,8 +572,12 @@ export default class Ship {
|
||||
this.updatePowerGenerated()
|
||||
.updatePowerUsed()
|
||||
.updateJumpStats()
|
||||
.updateShield()
|
||||
.updateArmour()
|
||||
.recalculateShield()
|
||||
.recalculateShieldCells()
|
||||
.recalculateArmour()
|
||||
.recalculateDps()
|
||||
.recalculateEps()
|
||||
.recalculateHps()
|
||||
.updateTopSpeed();
|
||||
}
|
||||
|
||||
@@ -570,20 +602,20 @@ export default class Ship {
|
||||
code = parts[0];
|
||||
|
||||
if (parts[1]) {
|
||||
enabled = LZString.decompressFromBase64(parts[1].replace(/-/g, '/')).split('');
|
||||
enabled = LZString.decompressFromBase64(Utils.fromUrlSafe(parts[1])).split('');
|
||||
}
|
||||
|
||||
if (parts[2]) {
|
||||
priorities = LZString.decompressFromBase64(parts[2].replace(/-/g, '/')).split('');
|
||||
priorities = LZString.decompressFromBase64(Utils.fromUrlSafe(parts[2])).split('');
|
||||
}
|
||||
|
||||
if (parts[3]) {
|
||||
const modstr = parts[3].replace(/-/g, '/');
|
||||
const modstr = parts[3];
|
||||
if (modstr.match(':')) {
|
||||
this.decodeModificationsString(modstr, modifications);
|
||||
} else {
|
||||
try {
|
||||
this.decodeModificationsStruct(zlib.gunzipSync(new Buffer(modstr, 'base64')), modifications);
|
||||
this.decodeModificationsStruct(zlib.gunzipSync(new Buffer(Utils.fromUrlSafe(modstr), 'base64')), modifications);
|
||||
} catch (err) {
|
||||
// Could be out-of-date URL; ignore
|
||||
}
|
||||
@@ -687,21 +719,27 @@ export default class Ship {
|
||||
if (slot.enabled != enabled) { // Enabled state is changing
|
||||
slot.enabled = enabled;
|
||||
if (slot.m) {
|
||||
if (ModuleUtils.isShieldGenerator(slot.m.grp) || slot.m.grp == 'sb') {
|
||||
this.updateShield();
|
||||
if (ModuleUtils.isShieldGenerator(slot.m.grp) || slot.m.grp === 'sb') {
|
||||
this.recalculateShield();
|
||||
}
|
||||
if (slot.m.getDps()) {
|
||||
this.totalDps += slot.m.getDps() * (enabled ? 1 : -1);
|
||||
}
|
||||
if (slot.m.getEps()) {
|
||||
this.totalEps += slot.m.getEps() * (enabled ? 1 : -1);
|
||||
}
|
||||
if (slot.m.getHps()) {
|
||||
this.totalHps += slot.m.getHps() * (enabled ? 1 : -1);
|
||||
if (slot.m.grp === 'scb') {
|
||||
this.recalculateShieldCells();
|
||||
}
|
||||
|
||||
this.updatePowerUsed();
|
||||
this.updatePowerEnabledString();
|
||||
|
||||
if (slot.m.getDps()) {
|
||||
this.recalculateDps();
|
||||
}
|
||||
|
||||
if (slot.m.getHps()) {
|
||||
this.recalculateHps();
|
||||
}
|
||||
|
||||
if (slot.m.getEps()) {
|
||||
this.recalculateEps();
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
@@ -738,10 +776,15 @@ export default class Ship {
|
||||
updateStats(slot, n, old, preventUpdate) {
|
||||
let powerGeneratedChange = slot == this.standard[0];
|
||||
let powerUsedChange = false;
|
||||
let dpsChanged = n && n.getDps() || old && old.getDps();
|
||||
let epsChanged = n && n.getEps() || old && old.getEps();
|
||||
let hpsChanged = n && n.getHps() || old && old.getHps();
|
||||
|
||||
let armourChange = (slot == this.bulkheads) || (n && n.grp == 'hr') || (old && old.grp == 'hr');
|
||||
let armourChange = (slot === this.bulkheads) || (n && n.grp === 'hr') || (old && old.grp === 'hr');
|
||||
|
||||
let shieldChange = (n && n.grp == 'bsg') || (old && old.grp == 'bsg') || (n && n.grp == 'psg') || (old && old.grp == 'psg') || (n && n.grp == 'sg') || (old && old.grp == 'sg') || (n && n.grp == 'sb') || (old && old.grp == 'sb');
|
||||
let shieldChange = (n && n.grp === 'bsg') || (old && old.grp === 'bsg') || (n && n.grp === 'psg') || (old && old.grp === 'psg') || (n && n.grp === 'sg') || (old && old.grp === 'sg') || (n && n.grp === 'sb') || (old && old.grp === 'sb');
|
||||
|
||||
let shieldCellsChange = (n && n.grp === 'scb') || (old && old.grp === 'scb');
|
||||
|
||||
if (old) { // Old modul now being removed
|
||||
switch (old.grp) {
|
||||
@@ -761,16 +804,6 @@ export default class Ship {
|
||||
powerUsedChange = true;
|
||||
}
|
||||
|
||||
if (old.getDps()) {
|
||||
this.totalDps -= old.getDps();
|
||||
}
|
||||
if (old.getEps()) {
|
||||
this.totalEps -= old.getEps();
|
||||
}
|
||||
if (old.getHps()) {
|
||||
this.totalHps -= old.getHps();
|
||||
}
|
||||
|
||||
this.unladenMass -= old.getMass() || 0;
|
||||
}
|
||||
|
||||
@@ -792,22 +825,21 @@ export default class Ship {
|
||||
powerUsedChange = true;
|
||||
}
|
||||
|
||||
if (n.getDps()) {
|
||||
this.totalDps += n.getDps();
|
||||
}
|
||||
if (n.getEps()) {
|
||||
this.totalEps += n.getEps();
|
||||
}
|
||||
if (n.getHps()) {
|
||||
this.totalHps += n.getHps();
|
||||
}
|
||||
|
||||
this.unladenMass += n.getMass() || 0;
|
||||
}
|
||||
|
||||
this.ladenMass = this.unladenMass + this.cargoCapacity + this.fuelCapacity;
|
||||
|
||||
if (!preventUpdate) {
|
||||
if (dpsChanged) {
|
||||
this.recalculateDps();
|
||||
}
|
||||
if (epsChanged) {
|
||||
this.recalculateEps();
|
||||
}
|
||||
if (hpsChanged) {
|
||||
this.recalculateHps();
|
||||
}
|
||||
if (powerGeneratedChange) {
|
||||
this.updatePowerGenerated();
|
||||
}
|
||||
@@ -815,10 +847,13 @@ export default class Ship {
|
||||
this.updatePowerUsed();
|
||||
}
|
||||
if (armourChange) {
|
||||
this.updateArmour();
|
||||
this.recalculateArmour();
|
||||
}
|
||||
if (shieldChange) {
|
||||
this.updateShield();
|
||||
this.recalculateShield();
|
||||
}
|
||||
if (shieldCellsChange) {
|
||||
this.recalculateShieldCells();
|
||||
}
|
||||
this.updateTopSpeed();
|
||||
this.updateJumpStats();
|
||||
@@ -826,20 +861,108 @@ export default class Ship {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate diminishing returns value, where values below a given limit are returned
|
||||
* as-is, and values between the lower and upper limit of the diminishing returns are
|
||||
* given at half value.
|
||||
* Commonly used for resistances.
|
||||
* @param {Number} val The value
|
||||
* @param {Number} drll The lower limit for diminishing returns
|
||||
* @param {Number} drul The upper limit for diminishing returns
|
||||
* @return {this} The ship instance (for chaining operations)
|
||||
*/
|
||||
diminishingReturns(val, drll, drul) {
|
||||
if (val > drll) {
|
||||
val = drll + (val - drll) / 2;
|
||||
}
|
||||
if (val > drul) {
|
||||
val = drul;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate damage per second for weapons
|
||||
* @return {this} The ship instance (for chaining operations)
|
||||
*/
|
||||
recalculateDps() {
|
||||
let totalDpe = 0;
|
||||
let totalExplDpe = 0;
|
||||
let totalKinDpe = 0;
|
||||
let totalThermDpe = 0;
|
||||
let totalDps = 0;
|
||||
let totalExplDps = 0;
|
||||
let totalKinDps = 0;
|
||||
let totalThermDps = 0;
|
||||
let totalSDps = 0;
|
||||
let totalExplSDps = 0;
|
||||
let totalKinSDps = 0;
|
||||
let totalThermSDps = 0;
|
||||
|
||||
for (let slotNum in this.hardpoints) {
|
||||
const slot = this.hardpoints[slotNum];
|
||||
if (slot.m && slot.enabled && slot.m.getDps()) {
|
||||
totalDps += slot.m.getDps();
|
||||
const dpe = slot.m.getDps() / slot.m.getEps();
|
||||
const dps = slot.m.getDps();
|
||||
const sdps = slot.m.getClip() ? (slot.m.getClip() * slot.m.getDps() / slot.m.getRoF()) / ((slot.m.getClip() / slot.m.getRoF()) + slot.m.getReload()) : dps;
|
||||
|
||||
totalDpe += dpe;
|
||||
totalDps += dps;
|
||||
totalSDps += sdps;
|
||||
if (slot.m.type === 'E') {
|
||||
totalExplDpe += dpe;
|
||||
totalExplDps += dps;
|
||||
totalExplSDps += sdps;
|
||||
}
|
||||
if (slot.m.type === 'K') {
|
||||
totalKinDpe += dpe;
|
||||
totalKinDps += dps;
|
||||
totalKinSDps += sdps;
|
||||
}
|
||||
if (slot.m.type === 'T') {
|
||||
totalThermDpe += dpe;
|
||||
totalThermDps += dps;
|
||||
totalThermSDps += sdps;
|
||||
}
|
||||
if (slot.m.type === 'EK') {
|
||||
totalExplDpe += dpe / 2;
|
||||
totalKinDpe += dpe / 2;
|
||||
totalExplDps += dps / 2;
|
||||
totalKinDps += dps / 2;
|
||||
totalExplSDps += sdps / 2;
|
||||
totalKinSDps += sdps / 2;
|
||||
}
|
||||
if (slot.m.type === 'ET') {
|
||||
totalExplDpe += dpe / 2;
|
||||
totalThermDpe += dpe / 2;
|
||||
totalExplDps += dps / 2;
|
||||
totalThermDps += dps / 2;
|
||||
totalExplSDps += sdps / 2;
|
||||
totalThermSDps += sdps / 2;
|
||||
}
|
||||
if (slot.m.type === 'KT') {
|
||||
totalKinDpe += dpe / 2;
|
||||
totalThermDpe += dpe / 2;
|
||||
totalKinDps += dps / 2;
|
||||
totalThermDps += dps / 2;
|
||||
totalKinSDps += sdps / 2;
|
||||
totalThermSDps += sdps / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.totalDpe = totalDpe;
|
||||
this.totalExplDpe = totalExplDpe;
|
||||
this.totalKinDpe = totalKinDpe;
|
||||
this.totalThermDpe = totalThermDpe;
|
||||
this.totalDps = totalDps;
|
||||
this.totalExplDps = totalExplDps;
|
||||
this.totalKinDps = totalKinDps;
|
||||
this.totalThermDps = totalThermDps;
|
||||
this.totalSDps = totalSDps;
|
||||
this.totalExplSDps = totalExplSDps;
|
||||
this.totalKinSDps = totalKinSDps;
|
||||
this.totalThermSDps = totalThermSDps;
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -958,44 +1081,88 @@ export default class Ship {
|
||||
* Update shield
|
||||
* @return {this} The ship instance (for chaining operations)
|
||||
*/
|
||||
updateShield() {
|
||||
// Base shield from generator
|
||||
let baseShield = 0;
|
||||
let sgSlot = this.findInternalByGroup('sg');
|
||||
if (sgSlot && sgSlot.enabled) {
|
||||
baseShield = Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sgSlot.m, 1);
|
||||
}
|
||||
recalculateShield() {
|
||||
let shield = 0;
|
||||
let shieldExplRes = null;
|
||||
let shieldKinRes = null;
|
||||
let shieldThermRes = null;
|
||||
|
||||
let shield = baseShield;
|
||||
const sgSlot = this.findInternalByGroup('sg');
|
||||
if (sgSlot && sgSlot.enabled) {
|
||||
// Shield from generator
|
||||
const baseShield = Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sgSlot.m, 1);
|
||||
shield = baseShield;
|
||||
shieldExplRes = 1 - sgSlot.m.getExplosiveResistance();
|
||||
shieldKinRes = 1 - sgSlot.m.getKineticResistance();
|
||||
shieldThermRes = 1 - sgSlot.m.getThermalResistance();
|
||||
|
||||
// Shield from boosters
|
||||
for (let slot of this.hardpoints) {
|
||||
if (slot.m && slot.m.grp == 'sb') {
|
||||
shield += baseShield * slot.m.getShieldBoost();
|
||||
shieldExplRes *= (1 - slot.m.getExplosiveResistance());
|
||||
shieldKinRes *= (1 - slot.m.getKineticResistance());
|
||||
shieldThermRes *= (1 - slot.m.getThermalResistance());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.shield = shield;
|
||||
this.shieldExplRes = shieldExplRes ? 1 - this.diminishingReturns(1 - shieldExplRes, 0.5, 0.75) : null;
|
||||
this.shieldKinRes = shieldKinRes ? 1 - this.diminishingReturns(1 - shieldKinRes, 0.5, 0.75) : null;
|
||||
this.shieldThermRes = shieldThermRes ? 1 - this.diminishingReturns(1 - shieldThermRes, 0.5, 0.75) : null;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update armour
|
||||
* Update shield cells
|
||||
* @return {this} The ship instance (for chaining operations)
|
||||
*/
|
||||
updateArmour() {
|
||||
recalculateShieldCells() {
|
||||
let shieldCells = 0;
|
||||
|
||||
for (let slot of this.internal) {
|
||||
if (slot.m && slot.m.grp == 'scb') {
|
||||
shieldCells += slot.m.getShieldReinforcement() * slot.m.getCells();
|
||||
}
|
||||
}
|
||||
|
||||
this.shieldCells = shieldCells;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update armour and hull resistances
|
||||
* @return {this} The ship instance (for chaining operations)
|
||||
*/
|
||||
recalculateArmour() {
|
||||
// Armour from bulkheads
|
||||
let bulkhead = this.bulkheads.m;
|
||||
let armour = this.baseArmour + (this.baseArmour * bulkhead.getHullBoost());
|
||||
let hullExplRes = 1 - bulkhead.getExplosiveResistance();
|
||||
let hullKinRes = 1 - bulkhead.getKineticResistance();
|
||||
let hullThermRes = 1 - bulkhead.getThermalResistance();
|
||||
|
||||
// Armour from HRPs
|
||||
for (let slot of this.internal) {
|
||||
if (slot.m && slot.m.grp == 'hr') {
|
||||
armour += slot.m.getHullReinforcement();
|
||||
// Hull boost for HRPs is applied against the ship's base armour
|
||||
armour += this.baseArmour * slot.m.getModValue('hullboost');
|
||||
armour += this.baseArmour * slot.m.getModValue('hullboost') / 10000;
|
||||
|
||||
hullExplRes *= (1 - slot.m.getExplosiveResistance());
|
||||
hullKinRes *= (1 - slot.m.getKineticResistance());
|
||||
hullThermRes *= (1 - slot.m.getThermalResistance());
|
||||
}
|
||||
}
|
||||
|
||||
this.armour = armour;
|
||||
this.hullExplRes = 1 - this.diminishingReturns(1 - hullExplRes, 0.5, 0.75);
|
||||
this.hullKinRes = 1 - this.diminishingReturns(1 - hullKinRes, 0.5, 0.75);
|
||||
this.hullThermRes = 1 - this.diminishingReturns(1 - hullThermRes, 0.5, 0.75);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1032,7 +1199,7 @@ export default class Ship {
|
||||
priorities.push(slot.priority);
|
||||
}
|
||||
|
||||
this.serialized.priorities = LZString.compressToBase64(priorities.join('')).replace(/\//g, '-');
|
||||
this.serialized.priorities = LZString.compressToBase64(priorities.join(''));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1053,7 +1220,7 @@ export default class Ship {
|
||||
enabled.push(slot.enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
this.serialized.enabled = LZString.compressToBase64(enabled.join('')).replace(/\//g, '-');
|
||||
this.serialized.enabled = LZString.compressToBase64(enabled.join(''));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1067,7 +1234,7 @@ export default class Ship {
|
||||
let bulkheadMods = new Array();
|
||||
if (this.bulkheads.m && this.bulkheads.m.mods) {
|
||||
for (let modKey in this.bulkheads.m.mods) {
|
||||
bulkheadMods.push(Modifications.modifiers.indexOf(modKey) + ':' + Math.round(this.bulkheads.m.getModValue(modKey) * 10000));
|
||||
bulkheadMods.push(Modifications.modifications.indexOf(modKey) + ':' + this.bulkheads.m.getModValue(modKey));
|
||||
}
|
||||
}
|
||||
allMods.push(bulkheadMods.join(';'));
|
||||
@@ -1076,7 +1243,7 @@ export default class Ship {
|
||||
let slotMods = new Array();
|
||||
if (slot.m && slot.m.mods) {
|
||||
for (let modKey in slot.m.mods) {
|
||||
slotMods.push(Modifications.modifiers.indexOf(modKey) + ':' + Math.round(slot.m.getModValue(modKey) * 10000));
|
||||
slotMods.push(Modifications.modifications.indexOf(modKey) + ':' + slot.m.getModValue(modKey));
|
||||
}
|
||||
}
|
||||
allMods.push(slotMods.join(';'));
|
||||
@@ -1085,7 +1252,7 @@ export default class Ship {
|
||||
let slotMods = new Array();
|
||||
if (slot.m && slot.m.mods) {
|
||||
for (let modKey in slot.m.mods) {
|
||||
slotMods.push(Modifications.modifiers.indexOf(modKey) + ':' + Math.round(slot.m.getModValue(modKey) * 10000));
|
||||
slotMods.push(Modifications.modifications.indexOf(modKey) + ':' + slot.m.getModValue(modKey));
|
||||
}
|
||||
}
|
||||
allMods.push(slotMods.join(';'));
|
||||
@@ -1094,12 +1261,12 @@ export default class Ship {
|
||||
let slotMods = new Array();
|
||||
if (slot.m && slot.m.mods) {
|
||||
for (let modKey in slot.m.mods) {
|
||||
slotMods.push(Modifications.modifiers.indexOf(modKey) + ':' + Math.round(slot.m.getModValue(modKey) * 10000));
|
||||
slotMods.push(Modifications.modifications.indexOf(modKey) + ':' + slot.m.getModValue(modKey));
|
||||
}
|
||||
}
|
||||
allMods.push(slotMods.join(';'));
|
||||
}
|
||||
this.serialized.modifications = LZString.compressToBase64(allMods.join(',').replace(/,+$/, '')).replace(/\//g, '-');
|
||||
this.serialized.modifications = LZString.compressToBase64(allMods.join(',').replace(/,+$/, ''));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1117,7 +1284,7 @@ export default class Ship {
|
||||
for (let j = 0; j < mods.length; j++) {
|
||||
let modElements = mods[j].split(':');
|
||||
if (modElements[0].match('[0-9]+')) {
|
||||
arr[i][Modifications.modifiers[modElements[0]]] = Number(modElements[1]);
|
||||
arr[i][Modifications.modifications[modElements[0]]] = Number(modElements[1]);
|
||||
} else {
|
||||
arr[i][modElements[0]] = Number(modElements[1]);
|
||||
}
|
||||
@@ -1131,7 +1298,7 @@ export default class Ship {
|
||||
* This is a binary structure. It starts with a byte that identifies a slot, with bulkheads being ID 0 and moving through
|
||||
* standard modules, hardpoints, and finally internal modules. It then contains one or more modifications, with each
|
||||
* modification being a one-byte modification ID and at two-byte modification value. Modification IDs are based on the array
|
||||
* in Modifications.modifiers. The list of modifications is terminated by a modification ID of -1. The structure then repeats
|
||||
* in Modifications.modifications. The list of modifications is terminated by a modification ID of -1. The structure then repeats
|
||||
* for the next module, and the next, and is terminated by a slot ID of -1.
|
||||
* @return {this} The ship instance (for chaining operations)
|
||||
*/
|
||||
@@ -1142,7 +1309,10 @@ export default class Ship {
|
||||
let bulkheadMods = new Array();
|
||||
if (this.bulkheads.m && this.bulkheads.m.mods) {
|
||||
for (let modKey in this.bulkheads.m.mods) {
|
||||
bulkheadMods.push({ id: Modifications.modifiers.indexOf(modKey), value: Math.round(this.bulkheads.m.getModValue(modKey) * 10000) });
|
||||
// Filter out invalid modifications
|
||||
if (Modifications.validity['bh'] && Modifications.validity['bh'].indexOf(modKey) != -1) {
|
||||
bulkheadMods.push({ id: Modifications.modifications.indexOf(modKey), value: this.bulkheads.m.getModValue(modKey) });
|
||||
}
|
||||
}
|
||||
}
|
||||
slots.push(bulkheadMods);
|
||||
@@ -1151,7 +1321,10 @@ export default class Ship {
|
||||
let slotMods = new Array();
|
||||
if (slot.m && slot.m.mods) {
|
||||
for (let modKey in slot.m.mods) {
|
||||
slotMods.push({ id: Modifications.modifiers.indexOf(modKey), value: Math.round(slot.m.getModValue(modKey) * 10000) });
|
||||
// Filter out invalid modifications
|
||||
if (Modifications.validity[slot.m.grp] && Modifications.validity[slot.m.grp].indexOf(modKey) != -1) {
|
||||
slotMods.push({ id: Modifications.modifications.indexOf(modKey), value: slot.m.getModValue(modKey) });
|
||||
}
|
||||
}
|
||||
}
|
||||
slots.push(slotMods);
|
||||
@@ -1161,7 +1334,10 @@ export default class Ship {
|
||||
let slotMods = new Array();
|
||||
if (slot.m && slot.m.mods) {
|
||||
for (let modKey in slot.m.mods) {
|
||||
slotMods.push({ id: Modifications.modifiers.indexOf(modKey), value: Math.round(slot.m.getModValue(modKey) * 10000) });
|
||||
// Filter out invalid modifications
|
||||
if (Modifications.validity[slot.m.grp] && Modifications.validity[slot.m.grp].indexOf(modKey) != -1) {
|
||||
slotMods.push({ id: Modifications.modifications.indexOf(modKey), value: slot.m.getModValue(modKey) });
|
||||
}
|
||||
}
|
||||
}
|
||||
slots.push(slotMods);
|
||||
@@ -1171,7 +1347,10 @@ export default class Ship {
|
||||
let slotMods = new Array();
|
||||
if (slot.m && slot.m.mods) {
|
||||
for (let modKey in slot.m.mods) {
|
||||
slotMods.push({ id: Modifications.modifiers.indexOf(modKey), value: Math.round(slot.m.getModValue(modKey) * 10000) });
|
||||
// Filter out invalid modifications
|
||||
if (Modifications.validity[slot.m.grp] && Modifications.validity[slot.m.grp].indexOf(modKey) != -1) {
|
||||
slotMods.push({ id: Modifications.modifications.indexOf(modKey), value: slot.m.getModValue(modKey) });
|
||||
}
|
||||
}
|
||||
}
|
||||
slots.push(slotMods);
|
||||
@@ -1197,6 +1376,7 @@ export default class Ship {
|
||||
for (let slotMod of slot) {
|
||||
buffer.writeInt8(slotMod.id, curpos++);
|
||||
buffer.writeInt32LE(slotMod.value, curpos);
|
||||
// console.log('ENCODE Slot ' + i + ': ' + Modifications.modifications[slotMod.id] + ' = ' + slotMod.value);
|
||||
curpos += 4;
|
||||
}
|
||||
buffer.writeInt8(-1, curpos++);
|
||||
@@ -1207,7 +1387,7 @@ export default class Ship {
|
||||
buffer.writeInt8(-1, curpos++);
|
||||
}
|
||||
|
||||
this.serialized.modifications = zlib.gzipSync(buffer).toString('base64').replace(/\//g, '-');
|
||||
this.serialized.modifications = zlib.gzipSync(buffer).toString('base64');
|
||||
} else {
|
||||
this.serialized.modifications = null;
|
||||
}
|
||||
@@ -1229,7 +1409,8 @@ export default class Ship {
|
||||
while (modificationId != -1) {
|
||||
let modificationValue = buffer.readInt32LE(curpos);
|
||||
curpos += 4;
|
||||
modifications[Modifications.modifiers[modificationId]] = modificationValue;
|
||||
// console.log('DECODE Slot ' + slot + ': ' + Modifications.modifications[modificationId] + ' = ' + modificationValue);
|
||||
modifications[Modifications.modifications[modificationId]] = modificationValue;
|
||||
modificationId = buffer.readInt8(curpos++);
|
||||
}
|
||||
arr[slot] = modifications;
|
||||
|
||||
@@ -11,6 +11,7 @@ const LS_KEY_MOD_DISCOUNT = 'moduleDiscount';
|
||||
const LS_KEY_STATE = 'state';
|
||||
const LS_KEY_SIZE_RATIO = 'sizeRatio';
|
||||
const LS_KEY_TOOLTIPS = 'tooltips';
|
||||
const LS_KEY_MODULE_RESISTANCES = 'moduleResistances';
|
||||
|
||||
let LS;
|
||||
|
||||
@@ -81,6 +82,7 @@ export class Persist extends EventEmitter {
|
||||
LS = null;
|
||||
}
|
||||
|
||||
let moduleResistances = _get(LS_KEY_MODULE_RESISTANCES);
|
||||
let tips = _get(LS_KEY_TOOLTIPS);
|
||||
let insurance = _getString(LS_KEY_INSURANCE);
|
||||
let shipDiscount = _get(LS_KEY_SHIP_DISCOUNT);
|
||||
@@ -99,6 +101,7 @@ export class Persist extends EventEmitter {
|
||||
this.state = _get(LS_KEY_STATE);
|
||||
this.sizeRatio = _get(LS_KEY_SIZE_RATIO) || 1;
|
||||
this.tooltipsEnabled = tips === null ? true : tips;
|
||||
this.moduleResistancesEnabled = moduleResistances === null ? true : moduleResistances;
|
||||
|
||||
if (LS) {
|
||||
window.addEventListener('storage', this.onStorageChange);
|
||||
@@ -143,6 +146,10 @@ export class Persist extends EventEmitter {
|
||||
this.tooltipsEnabled = !!newValue && newValue.toLowerCase() == 'true';
|
||||
this.emit('tooltips', this.tooltipsEnabled);
|
||||
break;
|
||||
case LS_KEY_MODULE_RESISTANCES:
|
||||
this.moduleResistancesEnabled = !!newValue && newValue.toLowerCase() == 'true';
|
||||
this.emit('moduleresistances', this.moduleResistancesEnabled);
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
// On JSON.Parse Error - don't sync or do anything
|
||||
@@ -183,6 +190,21 @@ export class Persist extends EventEmitter {
|
||||
return this.tooltipsEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show module resistances setting
|
||||
* @param {boolean} show Optional - update setting
|
||||
* @return {boolean} True if module resistances should be shown
|
||||
*/
|
||||
showModuleResistances(show) {
|
||||
if (show !== undefined) {
|
||||
this.moduleResistancesEnabled = !!show;
|
||||
_put(LS_KEY_MODULE_RESISTANCES, this.moduleResistancesEnabled);
|
||||
this.emit('moduleresistances', this.moduleResistancesEnabled);
|
||||
}
|
||||
|
||||
return this.moduleResistancesEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist a ship build in local storage.
|
||||
*
|
||||
|
||||
367
src/app/utils/CompanionApiUtils.js
Normal file
367
src/app/utils/CompanionApiUtils.js
Normal file
@@ -0,0 +1,367 @@
|
||||
import React from 'react';
|
||||
import { Modifications, Modules, Ships } from 'coriolis-data/dist';
|
||||
import Module from '../shipyard/Module';
|
||||
import Ship from '../shipyard/Ship';
|
||||
|
||||
// mapping from fd's ship model names to coriolis'
|
||||
const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
|
||||
'Adder': 'adder',
|
||||
'Anaconda': 'anaconda',
|
||||
'Asp': 'asp',
|
||||
'Asp_Scout': 'asp_scout',
|
||||
'BelugaLiner': 'beluga',
|
||||
'CobraMkIII': 'cobra_mk_iii',
|
||||
'CobraMkIV': 'cobra_mk_iv',
|
||||
'Cutter': 'imperial_cutter',
|
||||
'DiamondBack': 'diamondback_explorer',
|
||||
'DiamondBackXL': 'diamondback',
|
||||
'Eagle': 'eagle',
|
||||
'Empire_Courier': 'imperial_courier',
|
||||
'Empire_Eagle': 'imperial_eagle',
|
||||
'Empire_Trader': 'imperial_clipper',
|
||||
'Federation_Corvette': 'federal_corvette',
|
||||
'Federation_Dropship': 'federal_dropship',
|
||||
'Federation_Dropship_MkII': 'federal_assault_ship',
|
||||
'Federation_Gunship': 'federal_gunship',
|
||||
'FerDeLance': 'fer_de_lance',
|
||||
'Hauler': 'hauler',
|
||||
'Independant_Trader': 'keelback',
|
||||
'Orca': 'orca',
|
||||
'Python': 'python',
|
||||
'SideWinder': 'sidewinder',
|
||||
'Type6': 'type_6_transporter',
|
||||
'Type7': 'type_7_transport',
|
||||
'Type9': 'type_9_heavy',
|
||||
'Viper': 'viper',
|
||||
'Viper_MKIV': 'viper_mk_iv',
|
||||
'Vulture': 'vulture'
|
||||
};
|
||||
|
||||
// Mapping from hardpoint class to name in companion API
|
||||
const HARDPOINT_NUM_TO_CLASS = {
|
||||
0: 'Tiny',
|
||||
1: 'Small',
|
||||
2: 'Medium',
|
||||
3: 'Large',
|
||||
4: 'Huge'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Obtain a module given its ED ID
|
||||
* @param {Integer} edId the Elite ID of the module
|
||||
* @return {Module} the module
|
||||
*/
|
||||
function _moduleFromEdId(edId) {
|
||||
if (!edId) return null;
|
||||
|
||||
// Check standard modules
|
||||
for (const grp in Modules.standard) {
|
||||
if (Modules.standard.hasOwnProperty(grp)) {
|
||||
for (const i in Modules.standard[grp]) {
|
||||
if (Modules.standard[grp][i].edID === edId) {
|
||||
// Found it
|
||||
return new Module({ template: Modules.standard[grp][i] });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check hardpoint modules
|
||||
for (const grp in Modules.hardpoints) {
|
||||
if (Modules.hardpoints.hasOwnProperty(grp)) {
|
||||
for (const i in Modules.hardpoints[grp]) {
|
||||
if (Modules.hardpoints[grp][i].edID === edId) {
|
||||
// Found it
|
||||
return new Module({ template: Modules.hardpoints[grp][i] });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check internal modules
|
||||
for (const grp in Modules.internal) {
|
||||
if (Modules.internal.hasOwnProperty(grp)) {
|
||||
for (const i in Modules.internal[grp]) {
|
||||
if (Modules.internal[grp][i].edID === edId) {
|
||||
// Found it
|
||||
return new Module({ template: Modules.internal[grp][i] });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not found
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the model of a ship given its ED name
|
||||
* @param {string} edName the Elite name of the ship
|
||||
* @return {string} the Coriolis model of the ship
|
||||
*/
|
||||
function _shipModelFromEDName(edName) {
|
||||
return SHIP_FD_NAME_TO_CORIOLIS_NAME[edName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a ship's model from the companion API JSON
|
||||
* @param {object} json the companion API JSON
|
||||
* @return {string} the Coriolis model of the ship
|
||||
*/
|
||||
export function shipModelFromJson(json) {
|
||||
return _shipModelFromEDName(json.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a ship from the companion API JSON
|
||||
* @param {object} json the companion API JSON
|
||||
* @return {Ship} the built ship
|
||||
*/
|
||||
export function shipFromJson(json) {
|
||||
// Start off building a basic ship
|
||||
const shipModel = shipModelFromJson(json);
|
||||
if (!shipModel) {
|
||||
throw 'No such ship found: "' + json.name + '"';
|
||||
}
|
||||
const shipTemplate = Ships[shipModel];
|
||||
|
||||
let ship = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots);
|
||||
ship.buildWith(null);
|
||||
|
||||
// Set the cargo hatch. We don't have any information on it so guess it's priority 5 and disabled
|
||||
ship.cargoHatch.enabled = false;
|
||||
ship.cargoHatch.priority = 4;
|
||||
|
||||
// Add the bulkheads
|
||||
const armourJson = json.modules.Armour.module;
|
||||
if (armourJson.name.endsWith('_Armour_Grade1')) {
|
||||
ship.useBulkhead(0, true);
|
||||
} else if (armourJson.name.endsWith('_Armour_Grade2')) {
|
||||
ship.useBulkhead(1, true);
|
||||
} else if (armourJson.name.endsWith('_Armour_Grade3')) {
|
||||
ship.useBulkhead(2, true);
|
||||
} else if (armourJson.name.endsWith('_Armour_Mirrored')) {
|
||||
ship.useBulkhead(3, true);
|
||||
} else if (armourJson.name.endsWith('_Armour_Reactive')) {
|
||||
ship.useBulkhead(4, true);
|
||||
} else {
|
||||
throw 'Unknown bulkheads "' + armourJson.name + '"';
|
||||
}
|
||||
ship.bulkheads.enabled = true;
|
||||
if (armourJson.modifiers) _addModifications(ship.bulkheads.m, armourJson.modifiers);
|
||||
|
||||
// Add the standard modules
|
||||
// Power plant
|
||||
const powerplantJson = json.modules.PowerPlant.module;
|
||||
const powerplant = _moduleFromEdId(powerplantJson.id);
|
||||
if (powerplantJson.modifiers) _addModifications(powerplant, powerplantJson.modifiers);
|
||||
ship.use(ship.standard[0], powerplant, true);
|
||||
ship.standard[0].enabled = powerplantJson.on === true;
|
||||
ship.standard[0].priority = powerplantJson.priority;
|
||||
|
||||
// Thrusters
|
||||
const thrustersJson = json.modules.MainEngines.module;
|
||||
const thrusters = _moduleFromEdId(thrustersJson.id);
|
||||
if (thrustersJson.modifiers) _addModifications(thrusters, thrustersJson.modifiers);
|
||||
ship.use(ship.standard[1], thrusters, true);
|
||||
ship.standard[1].enabled = thrustersJson.on === true;
|
||||
ship.standard[1].priority = thrustersJson.priority;
|
||||
|
||||
// FSD
|
||||
const frameshiftdriveJson = json.modules.FrameShiftDrive.module;
|
||||
const frameshiftdrive = _moduleFromEdId(frameshiftdriveJson.id);
|
||||
if (frameshiftdriveJson.modifiers) _addModifications(frameshiftdrive, frameshiftdriveJson.modifiers);
|
||||
ship.use(ship.standard[2], frameshiftdrive, true);
|
||||
ship.standard[2].enabled = frameshiftdriveJson.on === true;
|
||||
ship.standard[2].priority = frameshiftdriveJson.priority;
|
||||
|
||||
// Life support
|
||||
const lifesupportJson = json.modules.LifeSupport.module;
|
||||
const lifesupport = _moduleFromEdId(lifesupportJson.id);
|
||||
if (lifesupportJson.modifiers)_addModifications(lifesupport, lifesupportJson.modifiers);
|
||||
ship.use(ship.standard[3], lifesupport, true);
|
||||
ship.standard[3].enabled = lifesupportJson.on === true;
|
||||
ship.standard[3].priority = lifesupportJson.priority;
|
||||
|
||||
// Power distributor
|
||||
const powerdistributorJson = json.modules.PowerDistributor.module;
|
||||
const powerdistributor = _moduleFromEdId(powerdistributorJson.id);
|
||||
if (powerdistributorJson.modifiers) _addModifications(powerdistributor, powerdistributorJson.modifiers);
|
||||
ship.use(ship.standard[4], powerdistributor, true);
|
||||
ship.standard[4].enabled = powerdistributorJson.on === true;
|
||||
ship.standard[4].priority = powerdistributorJson.priority;
|
||||
|
||||
// Sensors
|
||||
const sensorsJson = json.modules.Radar.module;
|
||||
const sensors = _moduleFromEdId(sensorsJson.id);
|
||||
if (sensorsJson.modifiers) _addModifications(sensors, sensorsJson.modifiers);
|
||||
ship.use(ship.standard[5], sensors, true);
|
||||
ship.standard[5].enabled = sensorsJson.on === true;
|
||||
ship.standard[5].priority = sensorsJson.priority;
|
||||
|
||||
// Fuel tank
|
||||
const fueltankJson = json.modules.FuelTank.module;
|
||||
const fueltank = _moduleFromEdId(fueltankJson.id);
|
||||
ship.use(ship.standard[6], fueltank, true);
|
||||
ship.standard[6].enabled = true;
|
||||
ship.standard[6].priority = 0;
|
||||
|
||||
// Add hardpoints
|
||||
let hardpointClassNum = -1;
|
||||
let hardpointSlotNum = -1;
|
||||
let hardpointArrayNum = 0;
|
||||
for (let i in shipTemplate.slots.hardpoints) {
|
||||
if (shipTemplate.slots.hardpoints[i] === hardpointClassNum) {
|
||||
// Another slot of the same class
|
||||
hardpointSlotNum++;
|
||||
} else {
|
||||
// The first slot of a new class
|
||||
hardpointClassNum = shipTemplate.slots.hardpoints[i];
|
||||
hardpointSlotNum = 1;
|
||||
}
|
||||
|
||||
// Now that we know what we're looking for, find it
|
||||
const hardpointName = HARDPOINT_NUM_TO_CLASS[hardpointClassNum] + 'Hardpoint' + hardpointSlotNum;
|
||||
const hardpointSlot = json.modules[hardpointName];
|
||||
if (!hardpointSlot.module) {
|
||||
// No module
|
||||
} else {
|
||||
const hardpointJson = hardpointSlot.module;
|
||||
const hardpoint = _moduleFromEdId(hardpointJson.id);
|
||||
if (hardpointJson.modifiers) _addModifications(hardpoint, hardpointJson.modifiers);
|
||||
ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true);
|
||||
ship.hardpoints[hardpointArrayNum].enabled = hardpointJson.on === true;
|
||||
ship.hardpoints[hardpointArrayNum].priority = hardpointJson.priority;
|
||||
}
|
||||
hardpointArrayNum++;
|
||||
}
|
||||
|
||||
// Add internal compartments
|
||||
let internalSlotNum = 1;
|
||||
for (let i in shipTemplate.slots.internal) {
|
||||
const internalClassNum = shipTemplate.slots.internal[i];
|
||||
|
||||
let internalSlot = null;
|
||||
while (internalSlot === null && internalSlotNum < 99) {
|
||||
// Slot numbers are not contiguous so handle skips
|
||||
const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '') + internalSlotNum + '_Size' + internalClassNum;
|
||||
if (json.modules[internalName]) {
|
||||
internalSlot = json.modules[internalName];
|
||||
}
|
||||
internalSlotNum++;
|
||||
}
|
||||
if (!internalSlot.module) {
|
||||
// No module
|
||||
} else {
|
||||
const internalJson = internalSlot.module;
|
||||
const internal = _moduleFromEdId(internalJson.id);
|
||||
if (internalJson.modifiers) _addModifications(internal, internalJson.modifiers);
|
||||
ship.use(ship.internal[i], internal, true);
|
||||
ship.internal[i].enabled = internalJson.on === true;
|
||||
ship.internal[i].priority = internalJson.priority;
|
||||
}
|
||||
}
|
||||
|
||||
// Now update the ship's codes before returning it
|
||||
return ship.updatePowerPrioritesString().updatePowerEnabledString().updateModificationsString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the modifications for a module
|
||||
* @param {Module} module the module
|
||||
* @param {Object} modifiers the modifiers
|
||||
*/
|
||||
function _addModifications(module, modifiers) {
|
||||
if (!modifiers || !modifiers.modifiers) return;
|
||||
|
||||
for (const i in modifiers.modifiers) {
|
||||
// Look up the modifiers to find what we need to do
|
||||
const modifierActions = Modifications.modifierActions[modifiers.modifiers[i].name];
|
||||
const value = modifiers.modifiers[i].value;
|
||||
|
||||
// Carry out the required changes
|
||||
for (const action in modifierActions) {
|
||||
const actionValue = modifierActions[action] * value;
|
||||
let mod = module.getModValue(action) / 10000;
|
||||
if (!mod) {
|
||||
mod = 0;
|
||||
}
|
||||
module.setModValue(action, ((1 + mod) * (1 + actionValue) - 1) * 10000);
|
||||
}
|
||||
}
|
||||
|
||||
// Need to fix up a few items
|
||||
|
||||
// Shield boosters are treated internally as straight modifiers, so rather than (for example)
|
||||
// being a 4% boost they are a 104% multiplier. Unfortunately this means that our % modification
|
||||
// is incorrect so we fix it
|
||||
if (module.grp === 'sb' && module.getModValue('shieldboost')) {
|
||||
const alteredBoost = (1 + module.shieldboost) * (module.getModValue('shieldboost') / 10000);
|
||||
module.setModValue('shieldboost', alteredBoost * 10000 / module.shieldboost);
|
||||
}
|
||||
|
||||
// Shield booster resistance is actually a damage modifier, so needs to be inverted.
|
||||
if (module.grp === 'sb') {
|
||||
if (module.getModValue('explres')) {
|
||||
module.setModValue('explres', ((module.getModValue('explres') / 10000) * -1) * 10000);
|
||||
}
|
||||
if (module.getModValue('kinres')) {
|
||||
module.setModValue('kinres', ((module.getModValue('kinres') / 10000) * -1) * 10000);
|
||||
}
|
||||
if (module.getModValue('thermres')) {
|
||||
module.setModValue('thermres', ((module.getModValue('thermres') / 10000) * -1) * 10000);
|
||||
}
|
||||
}
|
||||
|
||||
// Shield generator resistance is actually a damage modifier, so needs to be inverted.
|
||||
// In addition, the modification is based off the inherent resistance of the module
|
||||
if (module.isShieldGenerator()) {
|
||||
if (module.getModValue('explres')) {
|
||||
module.setModValue('explres', ((1 - (1 - module.explres) * (1 + module.getModValue('explres') / 10000)) - module.explres) * 10000);
|
||||
}
|
||||
if (module.getModValue('kinres')) {
|
||||
module.setModValue('kinres', ((1 - (1 - module.kinres) * (1 + module.getModValue('kinres') / 10000)) - module.kinres) * 10000);
|
||||
}
|
||||
if (module.getModValue('thermres')) {
|
||||
module.setModValue('thermres', ((1 - (1 - module.thermres) * (1 + module.getModValue('thermres') / 10000)) - module.thermres) * 10000);
|
||||
}
|
||||
}
|
||||
|
||||
// Hull reinforcement package resistance is actually a damage modifier, so needs to be inverted.
|
||||
if (module.grp === 'hr') {
|
||||
if (module.getModValue('explres')) {
|
||||
module.setModValue('explres', ((module.getModValue('explres') / 10000) * -1) * 10000);
|
||||
}
|
||||
if (module.getModValue('kinres')) {
|
||||
module.setModValue('kinres', ((module.getModValue('kinres') / 10000) * -1) * 10000);
|
||||
}
|
||||
if (module.getModValue('thermres')) {
|
||||
module.setModValue('thermres', ((module.getModValue('thermres') / 10000) * -1) * 10000);
|
||||
}
|
||||
}
|
||||
|
||||
// Bulkhead resistance is actually a damage modifier, so needs to be inverted.
|
||||
// In addition, the modification is based off the inherent resistance of the module
|
||||
if (module.grp == 'bh') {
|
||||
if (module.getModValue('explres')) {
|
||||
module.setModValue('explres', ((1 - (1 - module.explres) * (1 + module.getModValue('explres') / 10000)) - module.explres) * 10000);
|
||||
}
|
||||
if (module.getModValue('kinres')) {
|
||||
module.setModValue('kinres', ((1 - (1 - module.kinres) * (1 + module.getModValue('kinres') / 10000)) - module.kinres) * 10000);
|
||||
}
|
||||
if (module.getModValue('thermres')) {
|
||||
module.setModValue('thermres', ((1 - (1 - module.thermres) * (1 + module.getModValue('thermres') / 10000)) - module.thermres) * 10000);
|
||||
}
|
||||
}
|
||||
|
||||
// Jitter is an absolute number, so we need to divide it by 100
|
||||
if (module.getModValue('jitter')) {
|
||||
module.setModValue('jitter', module.getModValue('jitter') / 100);
|
||||
}
|
||||
|
||||
// FD uses interval between bursts internally, so we need to translate this to a real rate of fire
|
||||
if (module.getModValue('rof')) {
|
||||
module.setModValue('rof', ((1 / (1 + module.getModValue('rof') / 10000)) - 1) * 10000);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
/**
|
||||
* Generates a URL for the outiffing page
|
||||
* @param {String} shipId Ship Id
|
||||
@@ -7,15 +6,18 @@
|
||||
* @return {String} URL
|
||||
*/
|
||||
export function outfitURL(shipId, code, buildName) {
|
||||
let parts = ['/outfit/', shipId];
|
||||
let path = '/outfit/' + shipId;
|
||||
|
||||
let sepChar = '?';
|
||||
|
||||
if (code) {
|
||||
parts.push('/', code);
|
||||
path = path + sepChar + 'code=' + encodeURIComponent(code);
|
||||
sepChar = '&';
|
||||
}
|
||||
|
||||
if (buildName) {
|
||||
parts.push('?bn=', encodeURIComponent(buildName));
|
||||
}
|
||||
return parts.join('');
|
||||
path = path + sepChar + 'bn=' + encodeURIComponent(buildName);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
@@ -58,3 +58,16 @@ export function shallowEqual(objA, objB) {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn a URL-safe base-64 encoded string in to a normal version.
|
||||
* Coriolis used to use a different encoding system, and some old
|
||||
* data might be bookmarked or on local storage, so we keep this
|
||||
* around and use it when decoding data from the old-style URLs to
|
||||
* be safe.
|
||||
* @param {string} data the string
|
||||
* @return {string} the converted string
|
||||
*/
|
||||
export function fromUrlSafe(data) {
|
||||
return data ? data.replace(/-/g, '/').replace(/_/g, '+') : null;
|
||||
}
|
||||
|
||||
@@ -56,3 +56,9 @@
|
||||
height: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
.summary {
|
||||
stroke: @fg;
|
||||
stroke-width: 10;
|
||||
fill: @fg;
|
||||
}
|
||||
|
||||
@@ -275,15 +275,15 @@
|
||||
"minimum": 1
|
||||
},
|
||||
"hullExplRes": {
|
||||
"description": "Resistance of the hull to explosive attacks",
|
||||
"description": "Multiplier for explosive damage to hull",
|
||||
"type": "number"
|
||||
},
|
||||
"hullKinRes": {
|
||||
"description": "Resistance of the hull to kinetic attacks",
|
||||
"description": "Multiplier for kinetic damage to hull",
|
||||
"type": "number"
|
||||
},
|
||||
"hullThermRes": {
|
||||
"description": "Resistance of the hull to thermal attacks",
|
||||
"description": "Multiplier for thermal damage to hull",
|
||||
"type": "number"
|
||||
},
|
||||
"fuelCapacity": {
|
||||
@@ -311,20 +311,20 @@
|
||||
"minimum": 1
|
||||
},
|
||||
"shield": {
|
||||
"description": "Shield strengh in Mega Joules (Mj)",
|
||||
"description": "Shield strength in Mega Joules (Mj)",
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
},
|
||||
"shieldExplRes": {
|
||||
"description": "Resistance of the shield to explosive attacks",
|
||||
"description": "Multiplier for explosive damage to shields",
|
||||
"type": "number"
|
||||
},
|
||||
"shieldKinRes": {
|
||||
"description": "Resistance of the shield to kinetic attacks",
|
||||
"description": "Multiplier for kinetic damage to shields",
|
||||
"type": "number"
|
||||
},
|
||||
"shieldThermRes": {
|
||||
"description": "Resistance of the shield to thermal attacks",
|
||||
"description": "Multiplier for thermal damage to shields",
|
||||
"type": "number"
|
||||
},
|
||||
"speed": {
|
||||
@@ -333,8 +333,8 @@
|
||||
"minimum": 1
|
||||
},
|
||||
"totalCost": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
"description": "Total cost of the loadout, including discounts",
|
||||
"type": "number"
|
||||
},
|
||||
"unladenRange": {
|
||||
"description": "Single Jump range when unladen, see unladenMass",
|
||||
|
||||
Reference in New Issue
Block a user