Drop calculations made redundant by ed-forge

This commit is contained in:
Felix Linker
2021-12-26 21:44:22 +01:00
parent d2c3787165
commit 23c264e321
26 changed files with 88 additions and 6609 deletions

View File

@@ -16,7 +16,7 @@ import ModalPermalink from './components/ModalPermalink';
import AboutPage from './pages/AboutPage';
import NotFoundPage from './pages/NotFoundPage';
import OutfittingPage from './pages/OutfittingPage';
import ComparisonPage from './pages/ComparisonPage';
// import ComparisonPage from './pages/ComparisonPage';
import ShipyardPage from './pages/ShipyardPage';
import ErrorDetails from './pages/ErrorDetails';
@@ -76,9 +76,9 @@ export default class Coriolis extends React.Component {
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('/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));
}

View File

@@ -1,7 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import * as Calc from '../shipyard/Calculations';
import PieChart from './PieChart';
import VerticalBarChart from './VerticalBarChart';
import autoBind from 'auto-bind';

View File

@@ -2,7 +2,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import LineChart from '../components/LineChart';
import * as Calc from '../shipyard/Calculations';
import { calculateJumpRange } from 'ed-forge/lib/stats/JumpRangeProfile';
import { ShipProps } from 'ed-forge';
const { LADEN_MASS } = ShipProps;
@@ -18,18 +17,6 @@ export default class FSDProfile extends TranslatedComponent {
fuel: PropTypes.number.isRequired,
};
/**
* Calculate the maximum range for this ship across its applicable mass
* @param {Object} ship The ship
* @param {Object} fuel The fuel on the ship
* @param {Object} mass The mass at which to calculate the maximum range
* @return {number} The maximum range
*/
_calcMaxRange(ship, fuel, mass) {
// Obtain the maximum range
return Calc.jumpRange(mass, ship.standard[2].m, Math.min(fuel, ship.standard[2].m.getMaxFuelPerJump()), ship);
}
/**
* Render FSD profile
* @return {React.Component} contents

View File

@@ -8,7 +8,6 @@ import cn from 'classnames';
import { Cogs, CoriolisLogo, Hammer, Help, Rocket, StatsBars } from './SvgIcons';
import Persist from '../stores/Persist';
import { toDetailedExport } from '../shipyard/Serializer';
import Ship from '../shipyard/Ship';
import ModalBatchOrbis from './ModalBatchOrbis';
import ModalDeleteAll from './ModalDeleteAll';
import ModalExport from './ModalExport';

View File

@@ -1,9 +1,7 @@
import React from 'react';
import SlotSection from './SlotSection';
import Slot from './Slot';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import { canMount } from '../utils/SlotFunctions';
import autoBind from 'auto-bind';
/**

View File

@@ -2,84 +2,16 @@ import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent';
import Router from '../Router';
import Persist from '../stores/Persist';
import { Ships } from 'coriolis-data/dist';
import Ship from '../shipyard/Ship';
import { ModuleNameToGroup, Insurance } from '../shipyard/Constants';
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';
import autoBind from 'auto-bind';
import { isArray } from 'lodash';
import { Ship } from 'ed-forge';
const zlib = require('pako');
const textBuildRegex = new RegExp('^\\[([\\w \\-]+)\\]\n');
const lineRegex = new RegExp('^([\\dA-Z]{1,2}): (\\d)([A-I])[/]?([FGT])?([SD])? ([\\w\\- ]+)');
const mountMap = { 'H': 4, 'L': 3, 'M': 2, 'S': 1, 'U': 0 };
const standardMap = { 'RB': 0, 'TM': 1, 'FH': 2, 'EC': 3, 'PC': 4, 'SS': 5, 'FS': 6 };
const bhMap = { 'lightweight alloy': 0, 'reinforced alloy': 1, 'military grade composite': 2, 'mirrored surface composite': 3, 'reactive surface composite': 4 };
/**
* Check is slot is empty
* @param {Object} slot Slot model
* @return {Boolean} True if empty
*/
function isEmptySlot(slot) {
return slot.maxClass == this && slot.m === null;
}
/**
* Determine if a build is valid
* @param {string} shipId Ship ID
* @param {string} code Serialzied ship build 'code'
* @param {string} name Build name
* @throws {string} If build is not valid
*/
function validateBuild(shipId, code, name) {
let shipData = Ships[shipId];
if (!shipData) {
throw '"' + shipId + '" is not a valid Ship Id!';
}
if (typeof name != 'string' || name.length == 0) {
throw shipData.properties.name + ' build "' + name + '" must be a string at least 1 character long!';
}
if (typeof code != 'string' || code.length < 10) {
throw shipData.properties.name + ' build "' + name + '" is not valid!';
}
try {
let ship = new Ship(shipId, shipData.properties, shipData.slots);
ship.buildFrom(code);
} catch (e) {
throw shipData.properties.name + ' build "' + name + '" is not valid!';
}
}
/**
* Convert a ship-loadout JSON object to a Coriolis build
* @param {Object} detailedBuild ship-loadout
* @return {Object} Coriolis build
*/
function detailedJsonToBuild(detailedBuild) {
let ship;
if (!detailedBuild.name) {
throw 'Build Name missing!';
}
if (!detailedBuild.name.trim()) {
throw 'Build Name must be a string at least 1 character long!';
}
try {
ship = fromDetailedBuild(detailedBuild);
} catch (e) {
throw detailedBuild.ship + ' Build "' + detailedBuild.name + '": Invalid data';
}
return { shipId: ship.id, name: detailedBuild.name, code: ship.toString() };
}
const STATE = {
READY: 0,
PARSED: 1,
ERROR: 2,
};
/**
* Import Modal
@@ -95,228 +27,12 @@ export default class ModalImport extends TranslatedComponent {
*/
constructor(props) {
super(props);
autoBind(this);
this.state = {
builds: props.builds,
canEdit: !props.builds,
loadoutEvent: null,
comparisons: null,
shipDiscount: null,
moduleDiscount: null,
errorMsg: null,
importString: null,
importValid: false,
insurance: null
status: STATE.READY,
builds: props.builds || [],
};
this._process = this._process.bind(this);
this._import = this._import.bind(this);
this._importBackup = this._importBackup.bind(this);
this._importLoadout = this._importLoadout.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);
}
/**
* Import a Loadout event from Elite: Dangerous journal files
* @param {Object} data Loadout event
* @throws {string} If import fails
*/
_importLoadout(data) {
if (data && data.Ship && data.Modules) {
const deflated = zlib.deflate(JSON.stringify(data), { to: 'string' });
let compressed = btoa(deflated);
this.setState({ loadoutEvent: compressed });
} else {
throw 'Loadout event must contain Ship and Modules';
}
}
/**
* Import a Coriolis backup
* @param {Object} importData Backup Data
* @throws {string} If import fails
*/
_importBackup(importData) {
if (importData.builds && typeof importData.builds == 'object') {
for (let shipId in importData.builds) {
for (let buildName in importData.builds[shipId]) {
try {
validateBuild(shipId, importData.builds[shipId][buildName], buildName);
} catch (err) {
delete importData.builds[shipId][buildName];
}
}
}
this.setState({ builds: importData.builds });
} else {
throw 'builds must be an object!';
}
if (importData.comparisons) {
for (let compName in importData.comparisons) {
let comparison = importData.comparisons[compName];
for (let i = 0, l = comparison.builds.length; i < l; i++) {
let build = comparison.builds[i];
if (!importData.builds[build.shipId] || !importData.builds[build.shipId][build.buildName]) {
throw build.shipId + ' build "' + build.buildName + '" data is missing!';
}
}
}
this.setState({ comparisons: importData.comparisons });
}
// Check for old/deprecated discounts
if (importData.discounts instanceof Array && importData.discounts.length == 2) {
this.setState({ shipDiscount: importData.discounts[0], moduleDiscount: importData.discounts[1] });
}
// Check for ship discount
if (!isNaN(importData.shipDiscount)) {
this.setState({ shipDiscount: importData.shipDiscount * 1 });
}
// Check for module discount
if (!isNaN(importData.moduleDiscount)) {
this.setState({ moduleDiscount: importData.moduleDiscount * 1 });
}
if (typeof importData.insurance == 'string') {
let insurance = importData.insurance.toLowerCase();
if (Insurance[insurance] !== undefined) {
this.setState({ insurance });
} else {
throw 'Invalid insurance type: ' + insurance;
}
}
}
/**
* Import an array of ship-loadout objects / builds
* @param {Array} importArr Array of ship-loadout JSON Schema builds
*/
_importDetailedArray(importArr) {
let builds = {};
for (let i = 0, l = importArr.length; i < l; i++) {
let build = detailedJsonToBuild(importArr[i]);
if (!builds[build.shipId]) {
builds[build.shipId] = {};
}
builds[build.shipId][build.name] = build.code;
}
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
* @throws {string} If parse / import fails
*/
_importTextBuild(buildStr) {
let buildName = textBuildRegex.exec(buildStr)[1].trim();
let shipName = buildName.toLowerCase();
let shipId = null;
for (let sId in Ships) {
if (Ships[sId].properties.name.toLowerCase() == shipName) {
shipId = sId;
break;
}
}
if (!shipId) {
throw 'No such ship found: "' + buildName + '"';
}
let lines = buildStr.split('\n');
let ship = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots);
ship.buildWith(null);
for (let i = 1; i < lines.length; i++) {
let line = lines[i].trim();
if (!line) { continue; }
if (line.substring(0, 3) == '---') { break; }
let parts = lineRegex.exec(line);
if (!parts) { throw 'Error parsing: "' + line + '"'; }
let typeSize = parts[1];
let cl = parts[2];
let rating = parts[3];
let mount = parts[4];
let missile = parts[5];
let name = parts[6].trim();
let slot, group;
if (isNaN(typeSize)) { // Standard or Hardpoint
if (typeSize.length == 1) { // Hardpoint
let slotClass = mountMap[typeSize];
if (cl > slotClass) { throw cl + rating + ' ' + name + ' exceeds slot size: "' + line + '"'; }
slot = ship.hardpoints.find(isEmptySlot, slotClass);
if (!slot) { throw 'No hardpoint slot available for: "' + line + '"'; }
group = ModuleNameToGroup[name.toLowerCase()];
let hp = ModuleUtils.findHardpoint(group, cl, rating, group ? null : name, mount, missile);
if (!hp) { throw 'Unknown component: "' + line + '"'; }
ship.use(slot, hp, true);
} else if (typeSize == 'BH') {
let bhId = bhMap[name.toLowerCase()];
if (bhId === undefined) { throw 'Unknown bulkhead: "' + line + '"'; }
ship.useBulkhead(bhId, true);
} else if (standardMap[typeSize] != undefined) {
let standardIndex = standardMap[typeSize];
if (ship.standard[standardIndex].maxClass < cl) { throw name + ' exceeds max class for the ' + ship.name; }
ship.use(ship.standard[standardIndex], ModuleUtils.standard(standardIndex, cl + rating), true);
} else {
throw 'Unknown component: "' + line + '"';
}
} else {
if (cl > typeSize) { throw cl + rating + ' ' + name + ' exceeds slot size: "' + line + '"'; }
slot = ship.internal.find(isEmptySlot, typeSize);
if (!slot) { throw 'No internal slot available for: "' + line + '"'; }
group = ModuleNameToGroup[name.toLowerCase()];
let intComp = ModuleUtils.findInternal(group, cl, rating, group ? null : name);
if (!intComp) { throw 'Unknown component: "' + line + '"'; }
ship.use(slot, intComp);
}
}
let builds = {};
builds[shipId] = {};
builds[shipId]['Imported ' + buildName] = ship.toString();
this.setState({ builds, singleBuild: true });
}
/**
@@ -324,164 +40,50 @@ export default class ModalImport extends TranslatedComponent {
* @param {SyntheticEvent} event Event
* @throws {string} If validation fails
*/
_validateImport(event) {
let importData = null;
let importString = event.target.value.trim();
this.setState({
builds: null,
comparisons: null,
shipDiscount: null,
moduleDiscount: null,
errorMsg: null,
importValid: false,
insurance: null,
singleBuild: false,
importString,
});
if (!importString) {
return;
}
_parse(event) {
const importString = event.target.value.trim();
try {
if (textBuildRegex.test(importString)) { // E:D Shipyard build text
this._importTextBuild(importString);
} else { // JSON Build data
importData = JSON.parse(importString);
if (!importData || typeof importData != 'object') {
throw 'Must be an object or array!';
}
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
this.setState({ singleBuild: true });
} else if (importData.Modules != null && importData.Modules[0] != null) {
this._importLoadout(importData);
} else { // Using Backup JSON
this._importBackup(importData);
}
let data = JSON.parse(importString);
if (!isArray(data)) {
data = [data];
}
} catch (e) {
console.log(e);
this.setState({ errorMsg: (typeof e == 'string') ? e : 'Cannot Parse the data!' });
return;
}
this.setState({ importValid: true });
};
const ships = data.map((item) => {
try {
return new Ship(item.data ? item.data : item);
} catch (err) {
return err;
}
});
this.setState({ ships, status: STATE.PARSED });
} catch (err) {
this.setState({ err, status: STATE.ERROR });
}
}
/**
* Process imported data
*/
_process() {
let builds = null, comparisons = null;
if (this.state.loadoutEvent) {
return Router.go(`/import?data=${this.state.loadoutEvent}`);
}
// If only importing a single build go straight to the outfitting page
if (this.state.singleBuild) {
builds = this.state.builds;
let shipId = Object.keys(builds)[0];
let name = Object.keys(builds[shipId])[0];
Router.go(outfitURL(shipId, builds[shipId][name], name));
return;
}
if (this.state.builds) {
builds = {}; // Create new builds object such that orginal name retained, but can be renamed
for (let shipId in this.state.builds) {
let shipbuilds = this.state.builds[shipId];
builds[shipId] = {};
for (let buildName in shipbuilds) {
builds[shipId][buildName] = {
code: shipbuilds[buildName],
useName: buildName
};
}
for (const build of this.state.builds) {
if (!build instanceof Error) {
Persist.saveBuild(build.Ship, build.CoriolisBuildName || build.ShipName, build.compress());
}
}
if (this.state.comparisons) {
comparisons = {};
for (let name in this.state.comparisons) {
comparisons[name] = Object.assign({ useName: name }, this.state.comparisons[name]);
}
}
this.setState({ processed: true, builds, comparisons });
};
/**
* Import parsed, processed data and save
*/
_import() {
let state = this.state;
if (state.builds) {
let builds = state.builds;
for (let shipId in builds) {
for (let buildName in builds[shipId]) {
let build = builds[shipId][buildName];
let name = build.useName.trim();
if (name) {
Persist.saveBuild(shipId, name, build.code);
}
}
}
}
if (state.comparisons) {
let comparisons = state.comparisons;
for (let comp in comparisons) {
let comparison = comparisons[comp];
let useName = comparison.useName.trim();
if (useName) {
Persist.saveComparison(useName, comparison.builds, comparison.facets);
}
}
}
if (state.shipDiscount !== undefined) {
Persist.setShipDiscount(state.shipDiscount);
}
if (state.moduleDiscount !== undefined) {
Persist.setModuleDiscount(state.moduleDiscount);
}
if (state.insurance) {
Persist.setInsurance(state.insurance);
}
this.context.hideModal();
};
this.setState({ builds: [], status: STATE.READY });
}
/**
* Capture build name changes
* @param {Object} item Build/Comparison import object
* @param {SyntheticEvent} e Event
* @param {Object} index Build/Comparison import object
* @param {SyntheticEvent} event Event
*/
_changeName(item, e) {
item.useName = e.target.value;
this.forceUpdate();
_changeName(index, event) {
const { builds } = this.state;
builds[index].CoriolisBuildName = event.target.value.trim();
this.setState({ builds });
}
/**
* If imported data is already provided process immediately on mount
*/
componentWillMount() {
if (this.props.builds) {
this._process();
}
}
/**
* If textarea is shown focus on mount
*/
@@ -496,100 +98,54 @@ export default class ModalImport extends TranslatedComponent {
* @return {React.Component} Modal contents
*/
render() {
let translate = this.context.language.translate;
let state = this.state;
let importStage;
const { translate } = this.context.language;
const { status, builds, err } = this.state;
if (!state.processed) {
importStage = (
<div>
<textarea spellCheck={false} className='cb json' ref={node => this.importField = node} onChange={this._validateImport} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
<button id='proceed' className='l cap' onClick={this._process} disabled={!state.importValid} >{translate('proceed')}</button>
<div className='l warning' style={{ marginLeft:'3em' }}>{state.errorMsg}</div>
</div>
);
} else {
let comparisonTable, edit, buildRows = [];
if (state.comparisons) {
let comparisonRows = [];
for (let name in state.comparisons) {
let comparison = state.comparisons[name];
let hasComparison = Persist.hasComparison(comparison.useName);
comparisonRows.push(
<tr key={name} className='cb'>
<td>
<input type='text' onChange={this._changeName.bind(this, comparison)} value={comparison.useName}/>
</td>
<td style={{ textAlign:'center' }} className={ cn('cap', { warning: hasComparison, disabled: comparison.useName == '' }) }>
{translate(comparison.useName == '' ? 'skip' : (hasComparison ? 'overwrite' : 'create'))}
</td>
</tr>
);
}
comparisonTable = (
<table className='l' style={{ overflow:'hidden', margin: '1em 0', width: '100%' }} >
<thead>
<tr>
<th style={{ textAlign:'left' }}>{translate('comparison')}</th>
<th>{translate('action')}</th>
</tr>
</thead>
<tbody>
{comparisonRows}
</tbody>
</table>
);
const buildRows = builds.map((build, i) => {
if (build instanceof Error) {
return <tr key={i} className='cb'>
<td colSpan={3} className='warning'>Error: {build.name}</td>
</tr>;
}
if(this.state.canEdit) {
edit = <button className='l cap' style={{ marginLeft: '2em' }} onClick={() => this.setState({ processed: false })}>{translate('edit data')}</button>;
}
let builds = this.state.builds;
for (let shipId in builds) {
let shipBuilds = builds[shipId];
for (let buildName in shipBuilds) {
let b = shipBuilds[buildName];
let hasBuild = Persist.hasBuild(shipId, b.useName);
buildRows.push(
<tr key={shipId + buildName} className='cb'>
<td>{Ships[shipId].properties.name}</td>
<td><input type='text' onChange={this._changeName.bind(this, b)} value={b.useName}/></td>
<td style={{ textAlign: 'center' }} className={cn('cap', { warning: hasBuild, disabled: b.useName == '' })}>
{translate(b.useName == '' ? 'skip' : (hasBuild ? 'overwrite' : 'create'))}
</td>
</tr>
);
}
}
importStage = (
<div>
<table className='l' style={{ overflow:'hidden', margin: '1em 0', width: '100%' }}>
<thead>
<tr>
<th style={{ textAlign: 'left' }} >{translate('ship')}</th>
<th style={{ textAlign: 'left' }} >{translate('build name')}</th>
<th>{translate('action')}</th>
</tr>
</thead>
<tbody>
{buildRows}
</tbody>
</table>
{comparisonTable}
<button id='import' className='cl l' onClick={this._import}><Download/> {translate('import')}</button>
{edit}
</div>
);
}
const exists = Persist.hasBuild(build.Ship, build.CoriolisBuildName);
const saveName = build.CoriolisBuildName || build.ShipName;
return <tr key={i} className='cb'>
<td>{translate(build.Ship)}</td>
<td><input type='text' onChange={this._changeName.bind(this, i)} value={saveName}/></td>
<td style={{ textAlign: 'center' }} className={cn('cap', { warning: exists, disabled: saveName === '' })}>
{translate(saveName === '' ? 'skip' : (exists ? 'overwrite' : 'create'))}
</td>
</tr>;
});
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
<h2 >{translate('import')}</h2>
{importStage}
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
<div>
<textarea spellCheck={false} className='cb json' ref={node => this.importField = node} onChange={this._parse} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
{status === STATE.ERROR && <div className='l warning' style={{ marginLeft:'3em' }}>{err.toString()}</div>}
</div>
{builds.length && <div>
<table className='l' style={{ overflow:'hidden', margin: '1em 0', width: '100%' }}>
<thead>
<tr>
<th style={{ textAlign: 'left' }} >{translate('ship')}</th>
<th style={{ textAlign: 'left' }} >{translate('build name')}</th>
<th>{translate('action')}</th>
</tr>
</thead>
<tbody>
{buildRows}
</tbody>
</table>
</div>}
<button id='proceed' className='l cap' onClick={this._process}
disabled={status !== STATE.PARSED} >
{translate('proceed')}
</button>
<button className={'r dismiss cap'} onClick={this.context.hideModal}>
{translate('close')}
</button>
</div>;
}
}

View File

@@ -1,7 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import * as Calc from '../shipyard/Calculations';
import PieChart from './PieChart';
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
import { Ship } from 'ed-forge';

View File

@@ -5,7 +5,6 @@ import cn from 'classnames';
import { ListModifications, Modified } from './SvgIcons';
import AvailableModulesMenu from './AvailableModulesMenu';
import ModificationsMenu from './ModificationsMenu';
import { diffDetails } from '../utils/SlotFunctions';
import { stopCtxPropagation, wrapCtxMenu } from '../utils/UtilityFunctions';
import { blueprintTooltip } from '../utils/BlueprintFunctions';
import { Module } from 'ed-forge';

View File

@@ -2,7 +2,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { wrapCtxMenu } from '../utils/UtilityFunctions';
import { canMount } from '../utils/SlotFunctions';
import { Equalizer } from '../components/SvgIcons';
import cn from 'classnames';
import { Ship } from 'ed-forge';

View File

@@ -2,11 +2,9 @@ import React from 'react';
import cn from 'classnames';
import SlotSection from './SlotSection';
import Slot from './Slot';
import Module from '../shipyard/Module';
import * as ShipRoles from '../shipyard/ShipRoles';
import autoBind from 'auto-bind';
import { stopCtxPropagation, moduleGet } from '../utils/UtilityFunctions';
import { ShipProps } from 'ed-forge';
import { ShipProps, Module } from 'ed-forge';
const { CONSUMED_RETR, LADEN_MASS } = ShipProps;
/**

View File

@@ -2,7 +2,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import LineChart from '../components/LineChart';
import * as Calc from '../shipyard/Calculations';
import { moduleReduce } from 'ed-forge/lib/helper';
import { chain, keys, mapValues, values } from 'lodash';

View File

@@ -3,7 +3,6 @@ import Page from './Page';
import Router from '../Router';
import cn from 'classnames';
import { Ships } from 'coriolis-data/dist';
import Ship from '../shipyard/Ship';
import { fromComparison, toComparison } from '../shipyard/Serializer';
import Persist from '../stores/Persist';
import { SizeMap, ShipFacets } from '../shipyard/Constants';

View File

@@ -217,8 +217,8 @@ export default class OutfittingPage extends Page {
const { ship, buildName, newBuildName } = this.state;
const shipId = ship.getShipType();
// If this is a stock ship the code won't be set, so ensure that we have it
const code = this.state.code || ship.compress();
ship.write('CoriolisBuildName', buildName);
const code = ship.compress();
Persist.saveBuild(shipId, newBuildName, code);
this._setRoute();

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +0,0 @@
/**
* Modification - a modification and its value
*/
export default class Modification {
/**
* @param {String} id Unique modification ID
* @param {Number} value Value of the modification
*/
constructor(id, value) {
this.id = id;
this.value = value;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,197 +0,0 @@
import Module from './Module';
import { BulkheadNames } from './Constants';
/**
* Filter eligble modules based on parameters
* @param {Array} arr Available modules array
* @param {number} maxClass Max class
* @param {number} minClass Minimum class
* @param {number} mass Mass
* @return {Array} Fitlered module subset
*/
function filter(arr, maxClass, minClass, mass) {
return arr.filter(m => m.class <= maxClass && m.class >= minClass && (m.maxmass === undefined || mass <= m.maxmass));
}
/**
* The available module set for a specific ship
*/
export default class ModuleSet {
/**
* Instantiate the module set
* @param {Object} modules All Modules
* @param {Object} shipData Ship Specifications Data (see coriolis-data/Ships)
*/
constructor(modules, shipData) {
let maxInternal = isNaN(shipData.slots.internal[0]) ? shipData.slots.internal[0].class : shipData.slots.internal[0];
let mass = shipData.properties.hullMass + 6.5;
let maxStandardArr = shipData.slots.standard;
let maxHardPoint = shipData.slots.hardpoints[0];
let stnd = modules.standard;
this.mass = mass;
this.standard = {};
this.internal = {};
this.hardpoints = {};
this.hpClass = {};
this.intClass = {};
this.bulkheads = shipData.bulkheads.map((b, i) => {
return Object.assign(new Module(), { grp: 'bh', id: i, name: BulkheadNames[i], index: i, class: '', rating: '' }, b);
});
this.standard[0] = filter(stnd.pp, maxStandardArr[0], 0, mass); // Power Plant
this.standard[2] = filter(stnd.fsd, maxStandardArr[2], 0, mass); // FSD
this.standard[4] = filter(stnd.pd, maxStandardArr[4], 0, mass); // Power Distributor
this.standard[6] = filter(stnd.ft, maxStandardArr[6], 0, mass); // Fuel Tank
// Thrusters, filter modules by class only (to show full list of ratings for that class)
let minThrusterClass = stnd.t.reduce((clazz, th) => (th.maxmass >= mass && th.class < clazz) ? th.class : clazz, maxStandardArr[1]);
this.standard[1] = filter(stnd.t, maxStandardArr[1], minThrusterClass, 0); // Thrusters
// Slots where module class must be equal to slot class
this.standard[3] = filter(stnd.ls, maxStandardArr[3], maxStandardArr[3], 0); // Life Supprt
this.standard[5] = filter(stnd.s, maxStandardArr[5], maxStandardArr[5], mass); // Sensors
for (let h in modules.hardpoints) {
this.hardpoints[h] = filter(modules.hardpoints[h], maxHardPoint, 0, mass);
}
for (let g in modules.internal) {
this.internal[g] = filter(modules.internal[g], maxInternal, 0, mass);
}
}
/**
* Get the specified bulkhead
* @param {integer} index Bulkhead index
* @return {Object} Bulkhead module details
*/
getBulkhead(index) {
return this.bulkheads[index] ? new Module({ template: this.bulkheads[index] }) : null;
}
/**
* Determine the modules that areeligible for an internal slot
* @param {Object} ship The ship
* @param {integer} c The max class module that can be mounted in the slot
* @param {Object} eligible) The map of eligible internal groups
* @return {object} A map of all eligible modules by group
*/
getInts(ship, c, eligible) {
let o = {};
for (let key in this.internal) {
if (eligible && !eligible[key]) {
continue;
}
if (key == 'pcq' && !(ship.luxuryCabins && ship.luxuryCabins === true)) {
continue;
}
if (key == 'fh' && !(ship.fighterHangars && ship.fighterHangars === true)) {
continue;
}
let data = filter(this.internal[key], c, 0, this.mass);
if (data.length) { // If group is not empty
o[key] = data;
}
}
return o;
}
/**
* Determining the modules that are eligible for an hardpoint slot
* @param {integer} c The max class module that can be mounted in the slot
* @param {Object} eligible) The map of eligible hardpoint groups
* @return {object} A map of all eligible modules by group
*/
getHps(c, eligible) {
let o = {};
for (let key in this.hardpoints) {
if (eligible && !eligible[key]) {
continue;
}
let data = filter(this.hardpoints[key], c, c ? 1 : 0, this.mass);
if (data.length) { // If group is not empty
o[key] = data;
}
}
return o;
}
/**
* Find the lightest Power Distributor that provides sufficient
* energy to boost.
* @param {number} boostEnergy The energy that is required to boost
* @return {Object} Power Distributor
*/
lightestPowerDist(boostEnergy) {
let pd = this.standard[4][0];
for (let p of this.standard[4]) {
if (p.mass < pd.mass && p.engcap > boostEnergy) {
pd = p;
}
}
return new Module({ template: pd });
};
/** Find the power distributor that matches the requirements
* @param {Object} requirements The requirements to be met (currently only support 'weprate')
* @return {Object} Power distributor
*/
matchingPowerDist(requirements) {
let pd = this.standard[4][0];
for (let p of this.standard[4]) {
if (p.weprate >= requirements.weprate || p.weprate >= pd.weprate) {
pd = p;
}
}
return new Module({ template: pd });
}
/**
* Finds the lightest Thruster that can handle the specified tonnage
* @param {number} ladenMass Ship laden mass (mass + cargo + fuel)
* @return {Object} Thruster
*/
lightestThruster(ladenMass) {
let th = this.standard[1][0];
for (let t of this.standard[1]) {
if (t.mass < th.mass && t.maxmass >= ladenMass) {
th = t;
}
}
return new Module({ template: th });
};
/**
* Finds the lightest usable Shield Generator
* @param {number} hullMass Ship hull mass
* @return {Object} Thruster
*/
lightestShieldGenerator(hullMass) {
let sg = this.internal.sg[0];
for (let s of this.internal.sg) {
if (s.mass < sg.mass && s.maxmass > hullMass) {
sg = s;
}
}
return new Module({ template: sg });
};
/**
* Find the lightest Power Plant that provides sufficient power
* @param {number} powerNeeded Power requirements in MJ
* @param {string} rating The optional rating of the power plant
* @return {Object} Power Plant
*/
lightestPowerPlant(powerNeeded, rating) {
let pp = this.standard[0][0];
for (let p of this.standard[0]) {
// Provides enough power, is lighter or the same mass as current power plant but better output/efficiency
if (p.pgen >= powerNeeded && (p.mass < pp.mass || (p.mass == pp.mass && p.pgen > pp.pgen)) && (!rating || rating == p.rating)) {
pp = p;
}
}
return new Module({ template: pp });
}
}

View File

@@ -1,336 +0,0 @@
import { ModuleNameToGroup, BulkheadNames, StandardArray } from './Constants';
import ModuleSet from './ModuleSet';
import Module from './Module';
import { Ships, Modules } from 'coriolis-data/dist';
/*
* All functions below must return a fresh Module rather than a definition or existing module, as
* the resultant object can be altered with modifications.
*/
/**
* Created a cargo hatch model
* @return {Object} Cargo hatch model
*/
export function cargoHatch() {
let hatch = new Module();
Object.assign(hatch, { name: 'Cargo Hatch', class: 1, rating: 'H', power: 0.6 });
return hatch;
};
/**
* Finds the module with the specific group and ID
* @param {String} grp Module group (pp - power plant, pl - pulse laser etc)
* @param {String} id The module ID
* @return {Object} The module or null
*/
export function findModule(grp, id) {
// See if it's a standard module
if (Modules.standard[grp]) {
let standardmod = Modules.standard[grp].find(e => e.id == id);
if (standardmod != null) {
return new Module({ template: standardmod });
}
}
// See if it's an internal module
if (Modules.internal[grp]) {
let internalmod = Modules.internal[grp].find(e => e.id == id);
if (internalmod != null) {
return new Module({ template: internalmod });
}
}
// See if it's a hardpoint module
if (Modules.hardpoints[grp]) {
let hardpointmod = Modules.hardpoints[grp].find(e => e.id == id);
if (hardpointmod != null) {
return new Module({ template: hardpointmod });
}
}
return null;
}
/**
* Finds the standard module type with the specified ID
* @param {String|Number} type Standard Module Type (0/pp - Power Plant, 1/t - Thrusters, etc)
* @param {String} id The module ID or '[Class][Rating]'
* @return {Object} The standard module or null
*/
export function standard(type, id) {
if (!isNaN(type)) {
type = StandardArray[type];
}
let s = Modules.standard[type].find(e => e.id === id);
if (!s) {
s = Modules.standard[type].find(e => (e.class == id.charAt(0) && e.rating == id.charAt(1)));
}
if (s) {
s = new Module({ template: s });
}
return s || null;
};
/**
* Finds the hardpoint with the specified ID
* @param {String} id Hardpoint ID
* @return {Object} Hardpoint module or null
*/
export function hardpoints(id) {
for (let n in Modules.hardpoints) {
let group = Modules.hardpoints[n];
for (let i = 0; i < group.length; i++) {
if (group[i].id == id) {
return new Module({ template: group[i] });
}
}
}
return null;
};
/**
* Finds the internal module with the specified ID
* @param {String} id Internal module ID
* @return {Object} Internal module or null
*/
export function internal(id) {
for (let n in Modules.internal) {
let group = Modules.internal[n];
for (let i = 0; i < group.length; i++) {
if (group[i].id == id) {
return new Module({ template: group[i] });
}
}
}
return null;
};
/**
* Finds a standard module based on Class, Rating, Group and/or name.
* At least one of Group name or unique module name must be provided
*
* @param {String} groupName [Optional] Full name or abbreviated name for module group
* @param {integer} clss module Class
* @param {String} rating module Rating
* @param {String} name [Optional] Long/unique name for module -e.g. 'Advanced Discover Scanner'
* @return {Object} The module if found, null if not found
*/
export function findStandard(groupName, clss, rating, name) {
let groups = {};
if (groupName) {
if (Modules.standard[groupName]) {
groups[groupName] = Modules.standard[groupName];
} else {
let grpCode = ModuleNameToGroup[groupName.toLowerCase()];
if (grpCode && Modules.standard[grpCode]) {
groups[grpCode] = Modules.standard[grpCode];
}
}
} else if (name) {
groups = Modules.standard;
}
for (let g in groups) {
let group = groups[g];
for (let i = 0, l = group.length; i < l; i++) {
if (group[i].class == clss && group[i].rating == rating && ((!name && !group[i].name) || group[i].name == name)) {
return group[i];
}
}
}
return null;
}
/**
* Finds a standard Module ID based on Class, Rating, Group and/or name.
* At least one of Group name or unique module name must be provided
*
* @param {String} groupName [Optional] Full name or abbreviated name for module group
* @param {integer} clss module Class
* @param {String} rating Module Rating
* @param {String} name [Optional] Long/unique name for module -e.g. 'Advanced Discover Scanner'
* @return {String} The id of the module if found, null if not found
*/
export function findStandardId(groupName, clss, rating, name) {
let i = this.findStandard(groupName, clss, rating, name);
return i ? i.id : 0;
}
/**
* Finds an internal module based on Class, Rating, Group and/or name.
* At least one ofGroup name or unique module name must be provided
*
* @param {String} groupName [Optional] Full name or abbreviated name for module group
* @param {integer} clss module Class
* @param {String} rating module Rating
* @param {String} name [Optional] Long/unique name for module -e.g. 'Advanced Discover Scanner'
* @return {Object} The module if found, null if not found
*/
export function findInternal(groupName, clss, rating, name) {
let groups = {};
if (groupName) {
if (Modules.internal[groupName]) {
groups[groupName] = Modules.internal[groupName];
} else {
let grpCode = ModuleNameToGroup[groupName.toLowerCase()];
if (grpCode && Modules.internal[grpCode]) {
groups[grpCode] = Modules.internal[grpCode];
}
}
} else if (name) {
groups = Modules.internal;
}
for (let g in groups) {
let group = groups[g];
for (let i = 0, l = group.length; i < l; i++) {
if (group[i].class == clss && group[i].rating == rating && ((!name && !group[i].name) || group[i].name == name)) {
return group[i];
}
}
}
return null;
}
/**
* Finds an internal module based on Class, Rating, Group and/or name.
* At least one of Group name or unique module name must be provided.
* will start searching at specified class and proceed lower until a
* module is found or 0 is hit
* Uses findInternal internally
*
* @param {String} groupName [Optional] Full name or abbreviated name for module group
* @param {integer} clss module Class
* @param {String} rating module Rating
* @param {String} name [Optional] Long/unique name for module -e.g. 'Advanced Discover Scanner'
* @return {Object} The module if found, null if not found
*/
export function findMaxInternal(groupName, clss, rating, name) {
let foundModule = null;
let currentClss = clss;
while (currentClss > 0 && foundModule == null) {
foundModule = findInternal(groupName, currentClss, rating, name);
currentClss = currentClss - 1;
}
return foundModule;
}
/**
* Finds an internal Module ID based on Class, Rating, Group and/or name.
* At least one ofGroup name or unique module name must be provided
*
* @param {String} groupName [Optional] Full name or abbreviated name for module group
* @param {integer} clss module Class
* @param {String} rating Module Rating
* @param {String} name [Optional] Long/unique name for module -e.g. 'Advanced Discover Scanner'
* @return {String} The id of the module if found, null if not found
*/
export function findInternalId(groupName, clss, rating, name) {
let i = this.findInternal(groupName, clss, rating, name);
return i ? i.id : 0;
}
/**
* Finds a hardpoint Module based on Class, Rating, Group and/or name.
* At least one ofGroup name or unique module name must be provided
*
* @param {String} groupName [Optional] Full name or abbreviated name for module group
* @param {integer} clss Module Class
* @param {String} rating [Optional] module Rating
* @param {String} name [Optional] Long/unique name for module -e.g. 'Heat Sink Launcher'
* @param {String} mount Mount type - [F]ixed, [G]imballed, [T]urret
* @param {String} missile [Optional] Missile type - [D]umbfire, [S]eeker
* @return {String} The id of the module if found, null if not found
*/
export function findHardpoint(groupName, clss, rating, name, mount, missile) {
let groups = {};
if (groupName) {
if (Modules.hardpoints[groupName]) {
groups[groupName] = Modules.hardpoints[groupName];
} else {
let grpCode = ModuleNameToGroup[groupName.toLowerCase()];
if (grpCode && Modules.hardpoints[grpCode]) {
groups[grpCode] = Modules.hardpoints[grpCode];
}
}
} else if (name) {
groups = Modules.hardpoints;
}
for (let g in groups) {
let group = groups[g];
for (let h of group) {
if (h.class == clss && (!rating || h.rating == rating) && h.mount == mount && h.name == name && h.missile == missile) {
return h;
}
}
}
return null;
}
/**
* Finds a hardpoint module ID based on Class, Rating, Group and/or name.
* At least one of Group name or unique module name must be provided
*
* @param {String} groupName [Optional] Full name or abbreviated name for module group
* @param {integer} clss module Class
* @param {String} rating module Rating
* @param {String} name [Optional] Long/unique name for module -e.g. 'Heat Sink Launcher'
* @param {String} mount Mount type - [F]ixed, [G]imballed, [T]urret
* @param {String} missile [Optional] Missile type - [D]umbfire, [S]eeker
* @return {String} The id of the module if found, null if not found
*/
export function findHardpointId(groupName, clss, rating, name, mount, missile) {
let h = this.findHardpoint(groupName, clss, rating, name, mount, missile);
if (h) {
return h.id;
}
// Countermeasures used to be lumped in a single group but have been broken, out. If we have been given a groupName of 'Countermeasure' then
// rely on the unique name to find it
if (groupName === 'cm' || groupName === 'Countermeasure') {
h = this.findHardpoint(null, clss, rating, name, mount, missile);
}
return h ? h.id : 0;
}
/**
* Get the bulkhead index for the given bulkhead name
* @param {String} bulkheadName Bulkhead name in english
* @return {number} Bulkhead index
*/
export function bulkheadIndex(bulkheadName) {
return BulkheadNames.indexOf(bulkheadName);
}
/**
* Determine if a module group is a shield generator
* @param {String} g Module Group name
* @return {Boolean} True if the group is a shield generator
*/
export function isShieldGenerator(g) {
return g == 'sg' || g == 'psg' || g == 'bsg';
}
/**
* Creates a new ModuleSet that contains all available modules
* that the specified ship is eligible to use.
*
* 6.5 T is the lightest possible mass of standard components that any ship can use
*
* @param {String} shipId Unique ship Id/Key
* @return {ModuleSet} The set of modules the ship can install
*/
export function forShip(shipId) {
return new ModuleSet(Modules, Ships[shipId]);
}

View File

@@ -1,6 +1,4 @@
import { ModuleGroupToName, MountMap, BulkheadNames } from './Constants';
import { Ships } from 'coriolis-data/dist';
import Ship from './Ship';
import * as Utils from '../utils/UtilityFunctions';
import LZString from 'lz-string';
import { outfitURL } from '../utils/UrlGenerators';
@@ -72,49 +70,6 @@ function slotToSchema(slot) {
return null;
}
/**
* Instantiates a ship from a ship-loadout object
* @param {Object} detailedBuild ship-loadout object
* @return {Ship} Ship instance
*/
export function fromDetailedBuild(detailedBuild) {
let shipId = Object.keys(Ships).find((shipId) => Ships[shipId].properties.name.toLowerCase() == detailedBuild.ship.toLowerCase());
if (!shipId) {
throw 'No such ship: ' + detailedBuild.ship;
}
let shipData = Ships[shipId];
let ship = new Ship(shipId, shipData.properties, shipData.slots);
if (!detailedBuild.references[0] || !detailedBuild.references[0].code) {
throw 'Missing reference code';
}
ship.buildFrom(detailedBuild.references[0].code);
return ship;
}
/**
* Generates an array of ship-loadout JSON Schema object for export
* @param {Array} builds Array of ship builds
* @return {Array} Array of of ship-loadout objects
*/
export function toDetailedExport(builds) {
let data = [];
for (let shipId in builds) {
for (let buildName in builds[shipId]) {
let code = builds[shipId][buildName];
let shipData = Ships[shipId];
let ship = new Ship(shipId, shipData.properties, shipData.slots);
ship.buildFrom(code);
data.push(toDetailedBuild(buildName, ship, code));
}
}
return data;
};
/**
* Serializes a comparion and all of the ships to zipped
* Base 64 encoded JSON.

File diff suppressed because it is too large Load Diff

View File

@@ -1,415 +0,0 @@
import * as ModuleUtils from './ModuleUtils';
import { canMount } from '../utils/SlotFunctions';
/**
* Standard / typical role for multi-purpose or combat (if shielded with better bulkheads)
* @param {Ship} ship Ship instance
* @param {Boolean} shielded True if shield generator should be included
* @param {integer} bulkheadIndex Bulkhead to use see Constants.BulkheadNames
*/
export function multiPurpose(ship, shielded, bulkheadIndex) {
ship.useStandard('A')
.use(ship.standard[3], ModuleUtils.standard(3, ship.standard[3].maxClass + 'D')) // D Life Support
.use(ship.standard[5], ModuleUtils.standard(5, ship.standard[5].maxClass + 'D')) // D Sensors
.useBulkhead(bulkheadIndex);
if (shielded) {
ship.internal.some(function(slot) {
if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield
ship.use(slot, ModuleUtils.findInternal('sg', slot.maxClass, 'A'));
ship.setSlotEnabled(slot, true);
return true;
}
});
}
}
/**
* Trader Role
* @param {Ship} ship Ship instance
* @param {Boolean} shielded True if shield generator should be included
* @param {Object} standardOpts [Optional] Standard module optional overrides
*/
export function trader(ship, shielded, standardOpts) {
let usedSlots = [];
let bstCount = 2;
let sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
ship.useStandard('A')
.use(ship.standard[3], ModuleUtils.standard(3, ship.standard[3].maxClass + 'D')) // D Life Support
.use(ship.standard[1], ModuleUtils.standard(1, ship.standard[1].maxClass + 'D')) // D Life Support
.use(ship.standard[4], ModuleUtils.standard(4, ship.standard[4].maxClass + 'D')) // D Life Support
.use(ship.standard[5], ModuleUtils.standard(5, ship.standard[5].maxClass + 'D')); // D Sensors
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class)
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
shieldInternals.some(function(slot) {
if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield
const shield = ModuleUtils.findInternal('sg', slot.maxClass, 'A');
if (shield && shield.maxmass > ship.hullMass) {
ship.use(slot, shield);
ship.setSlotEnabled(slot, true);
usedSlots.push(slot);
return true;
}
}
});
// Fill the empty internals with cargo racks
for (let i = ship.internal.length; i--;) {
let slot = ship.internal[i];
if (usedSlots.indexOf(slot) == -1 && canMount(ship, slot, 'cr')) {
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
}
}
// Empty the hardpoints
for (let s of ship.hardpoints) {
ship.use(s, null);
}
for (let s of ship.hardpoints) {
if (s.maxClass == 0 && bstCount) { // Mount up to 2 boosters
ship.use(s, ModuleUtils.hardpoints('04'));
bstCount--;
} else {
ship.use(s, null);
}
}
// ship.useLightestStandard(standardOpts);
}
/**
* Explorer Role
* @param {Ship} ship Ship instance
* @param {Boolean} planetary True if Planetary Vehicle Hangar (PVH) should be included
*/
export function explorer(ship, planetary) {
let standardOpts = { ppRating: 'A' },
heatSinkCount = 2, // Fit 2 heat sinks if possible
usedSlots = [],
sgSlot,
fuelScoopSlot,
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
if (!planetary) { // Non-planetary explorers don't really need to boost
standardOpts.pd = '1D';
}
// Cargo hatch can be disabled
ship.setSlotEnabled(ship.cargoHatch, false);
// Advanced Discovery Scanner - class 1 or higher
const adsOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const adsInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sc)
.sort((a, b) => adsOrder.indexOf(a.maxClass) - adsOrder.indexOf(b.maxClass));
for (let i = 0; i < adsInternals.length; i++) {
if (canMount(ship, adsInternals[i], 'sc')) {
ship.use(adsInternals[i], ModuleUtils.internal('2f'));
usedSlots.push(adsInternals[i]);
break;
}
}
if (planetary) {
// Planetary Vehicle Hangar - class 2 or higher
const pvhOrder = [2, 3, 4, 5, 6, 7, 8, 1];
const pvhInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.pv)
.sort((a, b) => pvhOrder.indexOf(a.maxClass) - pvhOrder.indexOf(b.maxClass));
for (let i = 0; i < pvhInternals.length; i++) {
if (canMount(ship, pvhInternals[i], 'pv')) {
// Planetary Vehical Hangar only has even classes
const pvhClass = pvhInternals[i].maxClass % 2 === 1 ? pvhInternals[i].maxClass - 1 : pvhInternals[i].maxClass;
ship.use(pvhInternals[i], ModuleUtils.findInternal('pv', pvhClass, 'G')); // G is lower mass
ship.setSlotEnabled(pvhInternals[i], false); // Disable power for Planetary Vehical Hangar
usedSlots.push(pvhInternals[i]);
break;
}
}
}
// Shield generator
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class)
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
for (let i = 0; i < shieldInternals.length; i++) {
if (canMount(ship, shieldInternals[i], 'sg')) {
ship.use(shieldInternals[i], sg);
usedSlots.push(shieldInternals[i]);
sgSlot = shieldInternals[i];
break;
}
}
// Detailed Surface Scanner
const dssOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const dssInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sc)
.sort((a, b) => dssOrder.indexOf(a.maxClass) - dssOrder.indexOf(b.maxClass));
for (let i = 0; i < dssInternals.length; i++) {
if (canMount(ship, dssInternals[i], 'sc')) {
ship.use(dssInternals[i], ModuleUtils.internal('2i'));
usedSlots.push(dssInternals[i]);
break;
}
}
// Fuel scoop - best possible
const fuelScoopOrder = [8, 7, 6, 5, 4, 3, 2, 1];
const fuelScoopInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.fs)
.sort((a, b) => fuelScoopOrder.indexOf(a.maxClass) - fuelScoopOrder.indexOf(b.maxClass));
for (let i = 0; i < fuelScoopInternals.length; i++) {
if (canMount(ship, fuelScoopInternals[i], 'fs')) {
ship.use(fuelScoopInternals[i], ModuleUtils.findInternal('fs', fuelScoopInternals[i].maxClass, 'A'));
usedSlots.push(fuelScoopInternals[i]);
fuelScoopSlot = fuelScoopInternals[i];
break;
}
}
// AFMUs - fill as they are 0-weight
const afmuOrder = [8, 7, 6, 5, 4, 3, 2, 1];
const afmuInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.pc)
.sort((a, b) => afmuOrder.indexOf(a.maxClass) - afmuOrder.indexOf(b.maxClass));
for (let i = 0; i < afmuInternals.length; i++) {
if (canMount(ship, afmuInternals[i], 'am')) {
ship.use(afmuInternals[i], ModuleUtils.findInternal('am', afmuInternals[i].maxClass, 'A'));
usedSlots.push(afmuInternals[i]);
ship.setSlotEnabled(afmuInternals[i], false); // Disable power for AFM Unit
}
}
for (let s of ship.hardpoints) {
if (s.maxClass == 0 && heatSinkCount) { // Mount up to 2 heatsinks
ship.use(s, ModuleUtils.hardpoints('02'));
ship.setSlotEnabled(s, heatSinkCount == 2); // Only enable a single Heatsink
heatSinkCount--;
} else {
ship.use(s, null);
}
}
if (sgSlot && fuelScoopSlot) {
// The SG and Fuel scoop to not need to be powered at the same time
if (sgSlot.m.getPowerUsage() > fuelScoopSlot.m.getPowerUsage()) { // The Shield generator uses the most power
ship.setSlotEnabled(fuelScoopSlot, false);
} else { // The Fuel scoop uses the most power
ship.setSlotEnabled(sgSlot, false);
}
}
ship.useLightestStandard(standardOpts);
}
/**
* Miner Role
* @param {Ship} ship Ship instance
* @param {Boolean} shielded True if shield generator should be included
*/
export function miner(ship, shielded) {
shielded = true;
let standardOpts = { ppRating: 'A' },
miningLaserCount = 2,
usedSlots = [],
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
// Cargo hatch should be enabled
ship.setSlotEnabled(ship.cargoHatch, true);
// Largest possible refinery
const refineryOrder = [4, 5, 6, 7, 8, 3, 2, 1];
const refineryInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.rf)
.sort((a, b) => refineryOrder.indexOf(a.maxClass) - refineryOrder.indexOf(b.maxClass));
for (let i = 0; i < refineryInternals.length; i++) {
if (canMount(ship, refineryInternals[i], 'rf')) {
ship.use(refineryInternals[i], ModuleUtils.findInternal('rf', Math.min(refineryInternals[i].maxClass, 4), 'A'));
usedSlots.push(refineryInternals[i]);
break;
}
}
// Prospector limpet controller - 3A if possible
const prospectorOrder = [3, 4, 5, 6, 7, 8, 2, 1];
const prospectorInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.pc)
.sort((a, b) => prospectorOrder.indexOf(a.maxClass) - prospectorOrder.indexOf(b.maxClass));
for (let i = 0; i < prospectorInternals.length; i++) {
if (canMount(ship, prospectorInternals[i], 'pc')) {
// Prospector only has odd classes
const prospectorClass = prospectorInternals[i].maxClass % 2 === 0 ? prospectorInternals[i].maxClass - 1 : prospectorInternals[i].maxClass;
ship.use(prospectorInternals[i], ModuleUtils.findInternal('pc', prospectorClass, 'A'));
usedSlots.push(prospectorInternals[i]);
break;
}
}
// Shield generator if required
if (shielded) {
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class)
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
for (let i = 0; i < shieldInternals.length; i++) {
if (canMount(ship, shieldInternals[i], 'sg')) {
ship.use(shieldInternals[i], sg);
usedSlots.push(shieldInternals[i]);
break;
}
}
}
// Dual mining lasers of highest possible class; remove anything else
const miningLaserOrder = [2, 3, 4, 1, 0];
const miningLaserHardpoints = ship.hardpoints.concat().sort(function(a, b) {
return miningLaserOrder.indexOf(a.maxClass) - miningLaserOrder.indexOf(b.maxClass);
});
for (let s of miningLaserHardpoints) {
if (s.maxClass >= 1 && miningLaserCount) {
ship.use(s, ModuleUtils.hardpoints(s.maxClass >= 2 ? '2m' : '2l'));
miningLaserCount--;
} else {
ship.use(s, null);
}
}
// Number of collector limpets required to be active is a function of the size of the ship and the power of the lasers
const miningLaserDps = ship.hardpoints.filter(h => h.m != null)
.reduce(function(a, b) {
return a + b.m.getDps();
}, 0);
// Find out how many internal slots we have, and their potential cargo size
const potentialCargo = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.cr)
.map(b => Math.pow(2, b.maxClass));
// One collector for each 1.25 DPS, multiply by 1.25 for medium ships and 1.5 for large ships as they have further to travel
// 0 if we only have 1 cargo slot, otherwise minium of 1 and maximum of 6 (excluding size modifier)
const sizeModifier = ship.class == 2 ? 1.2 : ship.class == 3 ? 1.5 : 1;
let collectorLimpetsRequired = potentialCargo.length == 1 ? 0 : Math.ceil(sizeModifier * Math.min(6, Math.floor(miningLaserDps / 1.25)));
if (collectorLimpetsRequired > 0) {
const collectorOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const collectorInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.cc)
.sort((a, b) => collectorOrder.indexOf(a.maxClass) - collectorOrder.indexOf(b.maxClass));
// Always keep at least 2 slots free for cargo racks (1 for shielded)
for (let i = 0; i < collectorInternals.length - (shielded ? 1 : 2) && collectorLimpetsRequired > 0; i++) {
if (canMount(ship, collectorInternals[i], 'cc')) {
// Collector only has odd classes
const collectorClass = collectorInternals[i].maxClass % 2 === 0 ? collectorInternals[i].maxClass - 1 : collectorInternals[i].maxClass;
ship.use(collectorInternals[i], ModuleUtils.findInternal('cc', collectorClass, 'D'));
usedSlots.push(collectorInternals[i]);
collectorLimpetsRequired -= collectorInternals[i].m.maximum;
}
}
}
// Power distributor to power the mining lasers indefinitely
const wepRateRequired = ship.hardpoints.filter(h => h.m != null)
.reduce(function(a, b) {
return a + b.m.getEps();
}, 0);
standardOpts.pd = ship.getAvailableModules().matchingPowerDist({ weprate: wepRateRequired }).id;
// Fill the empty internals with cargo racks
for (let i = ship.internal.length; i--;) {
let slot = ship.internal[i];
if (usedSlots.indexOf(slot) == -1 && canMount(ship, slot, 'cr')) {
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
}
}
ship.useLightestStandard(standardOpts);
}
/**
* Racer Role
* @param {Ship} ship Ship instance
*/
export function racer(ship) {
let standardOpts = {},
usedSlots = [],
sgSlot,
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
// Cargo hatch can be disabled
ship.setSlotEnabled(ship.cargoHatch, false);
// Shield generator
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class)
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
for (let i = 0; i < shieldInternals.length; i++) {
if (canMount(ship, shieldInternals[i], 'sg')) {
ship.use(shieldInternals[i], sg);
usedSlots.push(shieldInternals[i]);
sgSlot = shieldInternals[i];
break;
}
}
// Empty the hardpoints
for (let s of ship.hardpoints) {
ship.use(s, null);
}
// Empty the internals
for (let i = ship.internal.length; i--;) {
let slot = ship.internal[i];
if (usedSlots.indexOf(slot) == -1) {
ship.use(slot, null);
}
}
// Best thrusters
if (ship.standard[1].maxClass === 3) {
standardOpts.th = 'tz';
} else if (ship.standard[1].maxClass === 2) {
standardOpts.th = 'u0';
} else {
standardOpts.th = ship.standard[1].maxClass + 'A';
}
// Best power distributor for more boosting
standardOpts.pd = ship.standard[4].maxClass + 'A';
// Smallest possible FSD drive
standardOpts.fsd = '2D';
// Minimal fuel tank
standardOpts.ft = '1C';
// Disable nearly everything
standardOpts.fsdDisabled = true;
standardOpts.sDisabled = true;
standardOpts.pdDisabled = true;
standardOpts.lsDisabled = true;
ship.useLightestStandard(standardOpts);
// Apply engineering to each module
// ship.standard[1].m.blueprint = getBlueprint('Engine_Dirty', ship.standard[0]);
// ship.standard[1].m.blueprint.grade = 5;
// setBest(ship, ship.standard[1].m);
// ship.standard[3].m.blueprint = getBlueprint('LifeSupport_LightWeight', ship.standard[3]);
// ship.standard[3].m.blueprint.grade = 4;
// setBest(ship, ship.standard[3].m);
// ship.standard[4].m.blueprint = getBlueprint('PowerDistributor_PriorityEngines', ship.standard[4]);
// ship.standard[4].m.blueprint.grade = 3;
// setBest(ship, ship.standard[4].m);
// ship.standard[5].m.blueprint = getBlueprint('Sensor_Sensor_LightWeight', ship.standard[5]);
// ship.standard[5].m.blueprint.grade = 5;
// setBest(ship, ship.standard[5].m);
}

View File

@@ -1,83 +0,0 @@
export const SI_PREFIXES = {
'Y': 1e+24, // Yotta
'Z': 1e+21, // Zetta
'E': 1e+18, // Peta
'P': 1e+15, // Peta
'T': 1e+12, // Tera
'G': 1e+9, // Giga
'M': 1e+6, // Mega
'k': 1e+3, // Kilo
'h': 1e+2, // Hekto
'da': 1e+1, // Deka
'': 1,
'd': 1e-1, // Dezi
'c': 1e-2, // Zenti
'm': 1e-3, // Milli
'μ': 1e-6, // mikro not supported due to charset
'n': 10e-9, // Nano
'p': 1e-12, // Nano
'f': 1e-15, // Femto
'a': 1e-18, // Atto
'z': 1e-21, // Zepto
'y': 1e-24 // Yokto
};
export const STATS_FORMATTING = {
'ammo': { 'format': 'int', },
'boot': { 'format': 'int', 'unit': 'secs' },
'brokenregen': { 'format': 'round1', 'unit': 'ps' },
'burst': { 'format': 'int', 'change': 'additive' },
'burstrof': { 'format': 'round1', 'unit': 'ps', 'change': 'additive' },
'causres': { 'format': 'pct' },
'clip': { 'format': 'int' },
'damage': { 'format': 'round' },
'dps': { 'format': 'round', 'units': 'ps', 'synthetic': 'getDps' },
'dpe': { 'format': 'round', 'units': 'ps', 'synthetic': 'getDpe' },
'distdraw': { 'format': 'round', 'unit': 'MW' },
'duration': { 'format': 'round1', 'unit': 's' },
'eff': { 'format': 'round2' },
'engcap': { 'format': 'round1', 'unit': 'MJ' },
'engrate': { 'format': 'round1', 'unit': 'MW' },
'eps': { 'format': 'round', 'units': 'ps', 'synthetic': 'getEps' },
'explres': { 'format': 'pct' },
'facinglimit': { 'format': 'round1', 'unit': 'ang' },
'falloff': { 'format': 'round', 'unit': 'km', 'storedUnit': 'm' },
'fallofffromrange': { 'format': 'round', 'unit': 'km', 'storedUnit': 'm', 'synthetic': 'getFalloff' },
'hps': { 'format': 'round', 'units': 'ps', 'synthetic': 'getHps' },
'hullboost': { 'format': 'pct1', 'change': 'additive' },
'hullreinforcement': { 'format': 'int' },
'integrity': { 'format': 'round1' },
'jitter': { 'format': 'round', 'unit': 'ang' },
'kinres': { 'format': 'pct' },
'mass': { 'format': 'round1', 'unit': 'T' },
'maxfuel': { 'format': 'round1', 'unit': 'T' },
'optmass': { 'format': 'int', 'unit': 'T' },
'optmul': { 'format': 'pct', 'change': 'additive' },
'pgen': { 'format': 'round1', 'unit': 'MW' },
'piercing': { 'format': 'int' },
'power': { 'format': 'round', 'unit': 'MW' },
'protection': { 'format': 'pct' },
'range': { 'format': 'f2', 'unit': 'km', 'storedUnit': 'm' },
'ranget': { 'format': 'f1', 'unit': 's' },
'regen': { 'format': 'round1', 'unit': 'ps' },
'reload': { 'format': 'int', 'unit': 's' },
'rof': { 'format': 'round1', 'unit': 'ps', 'synthetic': 'getRoF', 'higherbetter': true },
'angle': { 'format': 'round1', 'unit': 'ang' },
'scanrate': { 'format': 'int' },
'scantime': { 'format': 'round1', 'unit': 's' },
'sdps': { 'format': 'round1', 'units': 'ps', 'synthetic': 'getSDps' },
'shield': { 'format': 'int', 'unit': 'MJ' },
'shieldaddition': { 'format': 'round1', 'unit': 'MJ' },
'shieldboost': { 'format': 'pct1', 'change': 'additive' },
'shieldreinforcement': { 'format': 'round1', 'unit': 'MJ' },
'shotspeed': { 'format': 'int', 'unit': 'm/s' },
'spinup': { 'format': 'round1', 'unit': 's' },
'syscap': { 'format': 'round1', 'unit': 'MJ' },
'sysrate': { 'format': 'round1', 'unit': 'MW' },
'thermload': { 'format': 'round1' },
'thermres': { 'format': 'pct' },
'wepcap': { 'format': 'round1', 'unit': 'MJ' },
'weprate': { 'format': 'round1', 'unit': 'MW' },
'jumpboost': { 'format': 'round1', 'unit': 'LY' },
'proberadius': { 'format': 'pct1', 'unit': 'pct' },
};

View File

@@ -1,5 +1,4 @@
import React from 'react';
import { Modifications } from 'coriolis-data/dist';
import { Module } from 'ed-forge';
import { getBlueprintInfo, getExperimentalInfo } from 'ed-forge/lib/data/blueprints';
import { fromPairs, keys, uniq } from 'lodash';
@@ -116,20 +115,3 @@ export function blueprintTooltip(language, m, previewBP, previewGrade) {
</div>
);
}
/**
* Get a blueprint with a given name and an optional module
* @param {string} name The name of the blueprint
* @param {Object} module The module for which to obtain this blueprint
* @returns {Object} The matching blueprint
*/
export function getBlueprint(name, module) {
// Start with a copy of the blueprint
const findMod = val => Object.keys(Modifications.blueprints).find(elem => elem.toString().toLowerCase().search(val.toString().toLowerCase().replace(/(OutfittingFieldType_|persecond)/igm, '')) >= 0);
const found = Modifications.blueprints[findMod(name)];
if (!found || !found.fdname) {
return {};
}
const blueprint = JSON.parse(JSON.stringify(found));
return blueprint;
}

View File

@@ -1,470 +0,0 @@
import React from 'react';
import { Modifications, Modules, Ships } from 'coriolis-data/dist';
import Module from '../shipyard/Module';
import Ship from '../shipyard/Ship';
import { getBlueprint } from '../utils/BlueprintFunctions';
import * as ModuleUtils from '../shipyard/ModuleUtils';
// mapping from fd's ship model names to coriolis'
export 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',
'DiamondBackXL': 'diamondback_explorer',
'DiamondBack': 'diamondback',
'Dolphin': 'dolphin',
'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',
'Krait_MkII': 'krait_mkii',
'Mamba': 'mamba',
'Krait_Light': 'krait_phantom',
'Orca': 'orca',
'Python': 'python',
'SideWinder': 'sidewinder',
'Type6': 'type_6_transporter',
'Type7': 'type_7_transport',
'Type9': 'type_9_heavy',
'Type9_Military': 'type_10_defender',
'TypeX': 'alliance_chieftain',
'TypeX_2': 'alliance_crusader',
'TypeX_3': 'alliance_challenger',
'Viper': 'viper',
'Viper_MkIV': 'viper_mk_iv',
'Vulture': 'vulture'
};
// Mapping from hardpoint class to name in companion API
export 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[Object.keys(SHIP_FD_NAME_TO_CORIOLIS_NAME).find(elem => elem.toLowerCase() === edName.toLowerCase())];
}
/**
* 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 || json.Ship);
}
/**
* 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
if (json.modules.CargoHatch) {
ship.cargoHatch.enabled = json.modules.CargoHatch.module.on == true;
ship.cargoHatch.priority = json.modules.CargoHatch.module.priority;
} else {
// We don't have any information on it so guess it's priority 5 and disabled
ship.cargoHatch.enabled = false;
ship.cargoHatch.priority = 4;
}
let rootModule;
// Add the bulkheads
const armourJson = json.modules.Armour.module;
if (armourJson.name.toLowerCase().endsWith('_armour_grade1')) {
ship.useBulkhead(0, true);
} else if (armourJson.name.toLowerCase().endsWith('_armour_grade2')) {
ship.useBulkhead(1, true);
} else if (armourJson.name.toLowerCase().endsWith('_armour_grade3')) {
ship.useBulkhead(2, true);
} else if (armourJson.name.toLowerCase().endsWith('_armour_mirrored')) {
ship.useBulkhead(3, true);
} else if (armourJson.name.toLowerCase().endsWith('_armour_reactive')) {
ship.useBulkhead(4, true);
} else {
throw 'Unknown bulkheads "' + armourJson.name + '"';
}
ship.bulkheads.enabled = true;
rootModule = json.modules.Armour;
if (rootModule.WorkInProgress_modifications) _addModifications(ship.bulkheads.m, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel);
// Add the standard modules
// Power plant
const powerplantJson = json.modules.PowerPlant.module;
const powerplant = _moduleFromEdId(powerplantJson.id);
rootModule = json.modules.PowerPlant;
if (rootModule.WorkInProgress_modifications) _addModifications(powerplant, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel);
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);
rootModule = json.modules.MainEngines;
if (rootModule.WorkInProgress_modifications) _addModifications(thrusters, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel);
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);
rootModule = json.modules.FrameShiftDrive;
if (rootModule.WorkInProgress_modifications) _addModifications(frameshiftdrive, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel);
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);
rootModule = json.modules.LifeSupport;
if (rootModule.WorkInProgress_modifications) _addModifications(lifesupport, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel);
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);
rootModule = json.modules.PowerDistributor;
if (rootModule.WorkInProgress_modifications) _addModifications(powerdistributor, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel);
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);
rootModule = json.modules.Radar;
if (rootModule.WorkInProgress_modifications) _addModifications(sensors, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel);
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) {
// This can happen with old imports that don't contain new hardpoints
} else if (!hardpointSlot.module) {
// No module
} else {
const hardpointJson = hardpointSlot.module;
const hardpoint = _moduleFromEdId(hardpointJson.id);
rootModule = hardpointSlot;
if (rootModule.WorkInProgress_modifications) _addModifications(hardpoint, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel, rootModule.specialModifications);
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;
let militarySlotNum = 1;
for (let i in shipTemplate.slots.internal) {
const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name == 'Military' : false;
// The internal slot might be a standard or a military slot. Military slots have a different naming system
let internalSlot = null;
if (isMilitary) {
const internalName = 'Military0' + militarySlotNum;
internalSlot = json.modules[internalName];
militarySlotNum++;
} else {
// Slot numbers are not contiguous so handle skips.
while (internalSlot === null && internalSlotNum < 99) {
// Slot sizes have no relationship to the actual size, either, so check all possibilities
for (let slotsize = 0; slotsize < 9; slotsize++) {
const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '') + internalSlotNum + '_Size' + slotsize;
if (json.modules[internalName]) {
internalSlot = json.modules[internalName];
break;
}
}
internalSlotNum++;
}
}
if (!internalSlot) {
// This can happen with old imports that don't contain new slots
} else if (!internalSlot.module) {
// No module
} else {
const internalJson = internalSlot.module;
const internal = _moduleFromEdId(internalJson.id);
rootModule = internalSlot;
if (rootModule.WorkInProgress_modifications) _addModifications(internal, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel);
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
* @param {Object} blueprint the blueprint of the modification
* @param {Object} grade the grade of the modification
* @param {Object} specialModifications special modification
*/
function _addModifications(module, modifiers, blueprint, grade, specialModifications) {
if (!modifiers) return;
let special;
if (specialModifications) {
special = Modifications.specials[Object.keys(specialModifications)[0]];
}
for (const i in modifiers) {
// Some special modifications
if (modifiers[i].name === 'mod_weapon_clip_size_override') {
// This is a numeric addition to the clip size, but we need to work it out in terms of being a percentage so
// that it works the same as other modifications
const origClip = module.clip || 1;
module.setModValue('clip', ((modifiers[i].value - origClip) / origClip) * 10000);
} else if (modifiers[i].name === 'mod_weapon_burst_size') {
// This is an absolute number that acts as an override
module.setModValue('burst', modifiers[i].value * 100);
} else if (modifiers[i].name === 'mod_weapon_burst_rof') {
// This is an absolute number that acts as an override
module.setModValue('burstrof', modifiers[i].value * 100);
} else if (modifiers[i].name === 'mod_weapon_falloffrange_from_range') {
// Obtain the falloff value directly from the range
module.setModValue('fallofffromrange', 1);
} else if (modifiers[i].name && modifiers[i].name.startsWith('special_')) {
// We don't add special effects directly, but keep a note of them so they can be added when fetching values
special = Modifications.specials[modifiers[i].name];
} else {
// Look up the modifiers to find what we need to do
const modifierActions = Modifications.modifierActions[i];
let value;
if (i === 'OutfittingFieldType_DefenceModifierShieldMultiplier') {
value = modifiers[i].value - 1;
} else if (i === 'OutfittingFieldType_DefenceModifierHealthMultiplier' && blueprint.startsWith('Armour_')) {
value = (modifiers[i].value - module.hullboost) / module.hullboost;
} else if (i === 'OutfittingFieldType_DefenceModifierHealthMultiplier') {
value = modifiers[i].value / module.hullboost;
} else if (i === 'OutfittingFieldType_RateOfFire') {
value = (1 / Math.abs(modifiers[i].value));
} else {
value = modifiers[i].value - 1;
}
// Carry out the required changes
for (const action in modifierActions) {
if (isNaN(modifierActions[action])) {
module.setModValue(action, modifierActions[action]);
} else {
const actionValue = modifierActions[action] * value;
let mod = module.getModValue(action) / 10000;
if (!mod) {
mod = 0;
}
module.setModValue(action, ((1 + mod) * (1 + actionValue) - 1) * 10000);
}
}
}
}
// Add the blueprint definition, grade and special
if (blueprint) {
module.blueprint = getBlueprint(blueprint, module);
if (grade) {
module.blueprint.grade = Number(grade);
}
if (special) {
module.blueprint.special = special;
}
}
// 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 (ModuleUtils.isShieldGenerator(module.grp)) {
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.
// In addition, the modification is based off the inherent resistance of the module
if (module.grp === 'hr') {
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);
}
}
// 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);
}
}
// Bulkhead boost is based off the inherent boost of the module
if (module.grp == 'bh') {
const alteredBoost = (1 + module.hullboost) * (1 + module.getModValue('hullboost') / 10000) - 1;
module.setModValue('hullboost', (alteredBoost / module.hullboost - 1) * 1000);
}
// Jitter is an absolute number, so we need to divide it by 100
if (module.getModValue('jitter')) {
module.setModValue('jitter', module.getModValue('jitter') / 100);
}
// Clip size is rounded up so that the result is a whole number
if (module.getModValue('clip')) {
const individual = 1 / (module.clip || 1);
module.setModValue('clip', Math.ceil((module.getModValue('clip') / 10000) / individual) * individual * 10000);
}
}

View File

@@ -1,303 +0,0 @@
import Ship from '../shipyard/Ship';
import { HARDPOINT_NUM_TO_CLASS, shipModelFromJson } from './CompanionApiUtils';
import { Ships } from 'coriolis-data/dist';
import Module from '../shipyard/Module';
import { Modules } from 'coriolis-data/dist';
import { Modifications } from 'coriolis-data/dist';
import { getBlueprint } from './BlueprintFunctions';
/**
* Obtain a module given its FD Name
* @param {string} fdname the FD Name of the module
* @return {Module} the module
*/
function _moduleFromFdName(fdname) {
if (!fdname) return null;
fdname = fdname.toLowerCase();
// 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].symbol && Modules.standard[grp][i].symbol.toLowerCase() === fdname) {
// 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].symbol && Modules.hardpoints[grp][i].symbol.toLowerCase() === fdname) {
// 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].symbol && Modules.internal[grp][i].symbol.toLowerCase() === fdname) {
// Found it
return new Module({ template: Modules.internal[grp][i] });
}
}
}
}
// Not found
return null;
}
/**
* Build a ship from the journal Loadout event JSON
* @param {object} json the Loadout event JSON
* @return {Ship} the built ship
*/
export function shipFromLoadoutJSON(json) {
// Start off building a basic ship
const shipModel = shipModelFromJson(json);
if (!shipModel) {
throw 'No such ship found: "' + json.Ship + '"';
}
const shipTemplate = Ships[shipModel];
let ship = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots);
ship.buildWith(null);
// Initial Ship building, don't do engineering yet.
let modsToAdd = [];
for (const module of json.Modules) {
switch (module.Slot.toLowerCase()) {
// Cargo Hatch.
case 'cargohatch':
ship.cargoHatch.enabled = module.On;
ship.cargoHatch.priority = module.Priority;
break;
// Add the bulkheads
case 'armour':
if (module.Item.toLowerCase().endsWith('_armour_grade1')) {
ship.useBulkhead(0, true);
} else if (module.Item.toLowerCase().endsWith('_armour_grade2')) {
ship.useBulkhead(1, true);
} else if (module.Item.toLowerCase().endsWith('_armour_grade3')) {
ship.useBulkhead(2, true);
} else if (module.Item.toLowerCase().endsWith('_armour_mirrored')) {
ship.useBulkhead(3, true);
} else if (module.Item.toLowerCase().endsWith('_armour_reactive')) {
ship.useBulkhead(4, true);
} else {
throw 'Unknown bulkheads "' + module.Item + '"';
}
ship.bulkheads.enabled = true;
if (module.Engineering) _addModifications(ship.bulkheads.m, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break;
case 'powerplant':
const powerplant = _moduleFromFdName(module.Item);
ship.use(ship.standard[0], powerplant, true);
ship.standard[0].enabled = module.On;
ship.standard[0].priority = module.Priority;
if (module.Engineering) _addModifications(powerplant, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break;
case 'mainengines':
const thrusters = _moduleFromFdName(module.Item);
ship.use(ship.standard[1], thrusters, true);
ship.standard[1].enabled = module.On;
ship.standard[1].priority = module.Priority;
if (module.Engineering) _addModifications(thrusters, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break;
case 'frameshiftdrive':
const frameshiftdrive = _moduleFromFdName(module.Item);
ship.use(ship.standard[2], frameshiftdrive, true);
ship.standard[2].enabled = module.On;
ship.standard[2].priority = module.Priority;
if (module.Engineering) _addModifications(frameshiftdrive, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break;
case 'lifesupport':
const lifesupport = _moduleFromFdName(module.Item);
ship.use(ship.standard[3], lifesupport, true);
ship.standard[3].enabled = module.On === true;
ship.standard[3].priority = module.Priority;
if (module.Engineering) _addModifications(lifesupport, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break;
case 'powerdistributor':
const powerdistributor = _moduleFromFdName(module.Item);
ship.use(ship.standard[4], powerdistributor, true);
ship.standard[4].enabled = module.On;
ship.standard[4].priority = module.Priority;
if (module.Engineering) _addModifications(powerdistributor, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break;
case 'radar':
const sensors = _moduleFromFdName(module.Item);
ship.use(ship.standard[5], sensors, true);
ship.standard[5].enabled = module.On;
ship.standard[5].priority = module.Priority;
if (module.Engineering) _addModifications(sensors, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break;
case 'fueltank':
const fueltank = _moduleFromFdName(module.Item);
ship.use(ship.standard[6], fueltank, true);
ship.standard[6].enabled = true;
ship.standard[6].priority = 0;
break;
default:
}
if (module.Slot.toLowerCase().search(/hardpoint/) !== -1) {
// Add hardpoints
let hardpoint;
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.find(elem => elem.Slot.toLowerCase() === hardpointName.toLowerCase());
if (!hardpointSlot) {
// This can happen with old imports that don't contain new hardpoints
} else if (!hardpointSlot) {
// No module
} else {
hardpoint = _moduleFromFdName(hardpointSlot.Item);
ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true);
ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On;
ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority;
modsToAdd.push({ coriolisMod: hardpoint, json: hardpointSlot });
}
hardpointArrayNum++;
}
}
}
let internalSlotNum = 0;
let militarySlotNum = 1;
for (let i in shipTemplate.slots.internal) {
if (!shipTemplate.slots.internal.hasOwnProperty(i)) {
continue;
}
const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name == 'Military' : false;
// The internal slot might be a standard or a military slot. Military slots have a different naming system
let internalSlot = null;
if (isMilitary) {
const internalName = 'Military0' + militarySlotNum;
internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
militarySlotNum++;
} else {
// Slot numbers are not contiguous so handle skips.
for (; internalSlot === null && internalSlotNum < 99; internalSlotNum++) {
// Slot sizes have no relationship to the actual size, either, so check all possibilities
for (let slotsize = 0; slotsize < 9; slotsize++) {
const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '') + internalSlotNum + '_Size' + slotsize;
if (json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase())) {
internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
break;
}
}
}
}
if (!internalSlot) {
// This can happen with old imports that don't contain new slots
} else {
const internalJson = internalSlot;
const internal = _moduleFromFdName(internalJson.Item);
ship.use(ship.internal[i], internal, true);
ship.internal[i].enabled = internalJson.On === true;
ship.internal[i].priority = internalJson.Priority;
modsToAdd.push({ coriolisMod: internal, json: internalSlot });
}
}
for (const i of modsToAdd) {
if (i.json.Engineering) {
_addModifications(i.coriolisMod, i.json.Engineering.Modifiers, i.json.Engineering.BlueprintName, i.json.Engineering.Level, i.json.Engineering.ExperimentalEffect);
}
}
// We don't have any information on it so guess it's priority 5 and disabled
if (!ship.cargoHatch) {
ship.cargoHatch.enabled = false;
ship.cargoHatch.priority = 4;
}
// 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
* @param {Object} blueprint the blueprint of the modification
* @param {Object} grade the grade of the modification
* @param {Object} specialModifications special modification
*/
function _addModifications(module, modifiers, blueprint, grade, specialModifications) {
if (!modifiers) return;
let special;
if (specialModifications) {
if (specialModifications == 'special_plasma_slug') {
if (module.symbol.match(/PlasmaAccelerator/i)) {
specialModifications = 'special_plasma_slug_pa';
} else {
specialModifications = 'special_plasma_slug_cooled';
}
}
special = Modifications.specials[specialModifications];
}
// Add the blueprint definition, grade and special
if (blueprint) {
module.blueprint = getBlueprint(blueprint, module);
if (grade) {
module.blueprint.grade = Number(grade);
}
if (special) {
module.blueprint.special = special;
}
}
for (const i in modifiers) {
// Some special modifications
// Look up the modifiers to find what we need to do
const findMod = val => Object.keys(Modifications.modifierActions).find(elem => elem.toString().toLowerCase().replace(/(outfittingfieldtype_|persecond)/igm, '') === val.toString().toLowerCase().replace(/(outfittingfieldtype_|persecond)/igm, ''));
const modifierActions = Modifications.modifierActions[findMod(modifiers[i].Label)];
// TODO: Figure out how to scale this value.
if (!!modifiers[i].LessIsGood) {
}
let value = (modifiers[i].Value / modifiers[i].OriginalValue * 100 - 100) * 100;
if (value === Infinity) {
value = modifiers[i].Value * 100;
}
if (modifiers[i].Label.search('DamageFalloffRange') >= 0) {
value = (modifiers[i].Value / module.range - 1) * 100;
}
if (modifiers[i].Label.search('Resistance') >= 0) {
value = (modifiers[i].Value * 100) - (modifiers[i].OriginalValue * 100);
}
if (modifiers[i].Label.search('ShieldMultiplier') >= 0 || modifiers[i].Label.search('DefenceModifierHealthMultiplier') >= 0) {
value = ((100 + modifiers[i].Value) / (100 + modifiers[i].OriginalValue) * 100 - 100) * 100;
}
// Carry out the required changes
for (const action in modifierActions) {
if (isNaN(modifierActions[action])) {
module.setModValue(action, modifierActions[action]);
} else {
module.setModValue(action, value, true);
}
}
}
}

View File

@@ -1,252 +0,0 @@
import React from 'react';
import Persist from '../stores/Persist';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import Module from '../shipyard/Module';
/**
* Determine if a slot on a ship can mount a module of a particular class and group
* @param {Object} ship Ship object
* @param {Object} slot Slot object
* @param {String} group Module group/type abbrivation/code
* @param {Integer} clazz [Optional] Module Class/Size
* @return {Boolean} True if the slot can mount the module
*/
export function canMount(ship, slot, group, clazz) {
if (slot &&
(!slot.eligible || slot.eligible[group]) &&
(group != 'pcq' || (ship.luxuryCabins && ship.luxuryCabins === true)) &&
(group != 'fh' || (ship.fighterHangars && ship.fighterHangars === true)) &&
(clazz === undefined || slot.maxClass >= clazz)) {
return true;
}
return false;
}
/**
* Returns the translate name for the module mounted in the specified
* slot.
* @param {function} translate Translation function
* @param {object} slot Slot object
* @return {string} The translated name
*/
export function slotName(translate, slot) {
return slot.m ? translate(slot.m.name || slot.m.grp) : '';
}
/**
* Slot name comparator
* @param {Function} translate Translate function
* @param {Object} a Slot object
* @param {Object} b Slot object
* @return {Number} 1, 0, -1
*/
export function nameComparator(translate, a, b) {
return translate(a.name || a.grp).localeCompare(translate(b.name || b.grp));
}
/**
* Generates an internationalization friendly slot comparator that will
* sort by specified property (if provided) then by name/group, class, rating
* @param {function} translate Tranlation function
* @param {function} propComparator Optional property comparator
* @param {boolean} desc Use descending order
* @return {function} Comparator function for slot names
*/
export function slotComparator(translate, propComparator, desc) {
return (a, b) => {
// retain descending order when sorting sorting by name/group/class/rating
let am = a.m; // Slot A's mounted module
let bm = b.m; // Slot B's mounted module
if (!desc) { // Flip A and B if ascending order
let t = a;
a = b;
b = t;
}
// Check for empty slots first
if (a.m && !b.m) {
return 1;
} else if (!a.m && b.m) {
return -1;
} else if (!a.m && !b.m) {
return 0;
}
// If a property comparator is provided use it first
let diff = propComparator ? propComparator(a, b) : nameComparator(translate, a.m, b.m);
if (diff) {
return diff;
}
// Property matches so sort by name / group, then class, rating
if (am.name === bm.name && am.grp === bm.grp) {
if(am.class == bm.class) {
return am.rating > bm.rating ? 1 : -1;
}
return am.class - bm.class;
}
return nameComparator(translate, am, bm);
};
}
/**
* Determine the appropriate class based on diff value
* @param {Number} a Potential Module (cannot be null)
* @param {Number} b Currently mounted module (optional - null)
* @param {Boolean} negative A positive diff has a negative implication
* @return {String} CSS Class name
*/
function diffClass(a, b, negative) {
if (b === undefined || a === b) {
return 'muted';
} else if (a > b) {
return negative ? 'warning' : 'secondary';
}
return negative ? 'secondary' : 'warning';
}
/**
* Determine the displayable diff of a module's proprty
* @param {Function} format Formatter
* @param {Number} mVal Potential Module property value
* @param {Number} mmVal Currently mounted module property value
* @return {string | React.Component} Component to be rendered
*/
function diff(format, mVal, mmVal) {
if (mVal == Infinity) {
return '∞';
} else {
let diff = mVal - mmVal;
if (!diff || mVal === undefined || diff == mVal || Math.abs(diff) == Infinity) {
return format(mVal);
}
return `${format(mVal)} (${diff > 0 ? '+' : ''}${format(diff)})`;
}
}
/**
* Returns a a summary and diff of the potential module
* versus the currently mounted module if there is one. Must be bound
* to a ship instance
*
* @this {Ship}
* @param {Object} language Current language
* @param {Object} m Potential Module (cannot be null)
* @param {Object} mm Currently mounted module (optional - null if empty)
* @return {React.Component} Component to be rendered
*/
export function diffDetails(language, m, mm) {
let { formats, translate, units } = language;
let propDiffs = [];
m = new Module(m);
// Module-specific items
if (m.grp === 'pp') {
let mPowerGeneration = m.getPowerGeneration() || 0;
let mmPowerGeneration = mm ? mm.getPowerGeneration() : 0;
if (mPowerGeneration != mmPowerGeneration) propDiffs.push(<div key='pgen'>{translate('pgen')}: <span className={diffClass(mPowerGeneration, mmPowerGeneration)}>{diff(formats.round, mPowerGeneration, mmPowerGeneration)}{units.MW}</span></div>);
} else {
let mPowerUsage = m.getPowerUsage() || 0;
let mmPowerUsage = mm ? mm.getPowerUsage() || 0 : 0;
if (mPowerUsage != mmPowerUsage) propDiffs.push(<div key='power'>{translate('power')}: <span className={diffClass(mPowerUsage, mmPowerUsage, true)}>{diff(formats.round, mPowerUsage, mmPowerUsage)}{units.MW}</span></div>);
}
let mDps = m.getDps() || 0;
let mmDps = mm ? mm.getDps() || 0 : 0;
if (mDps && mDps != mmDps) propDiffs.push(<div key='dps'>{translate('dps')}: <span className={diffClass(mmDps, mDps, true)}>{diff(formats.round, mDps, mmDps)}</span></div>);
let mAffectsShield = ModuleUtils.isShieldGenerator(m.grp) || m.grp == 'sb';
let mmAffectsShield = mm ? ModuleUtils.isShieldGenerator(m.grp) || mm.grp == 'sb' : false;
if (mAffectsShield || mmAffectsShield) {
let shield = this.calcShieldStrengthWith(); // Get shield strength regardless of slot active / inactive
let newShield = 0;
if (mAffectsShield) {
if (m.grp == 'sb') { // Both m and mm must be utility modules if this is true
newShield = this.calcShieldStrengthWith(null, m.getShieldBoost() - (mm ? mm.getShieldBoost() || 0 : 0));
} else {
newShield = this.calcShieldStrengthWith(m);
}
} else {
// Old module must be a shield booster
newShield = this.calcShieldStrengthWith(null, -mm.getShieldBoost());
}
let sgDiffClass = Math.round((newShield - shield) * 100) / 100 == 0 ? 'muted' : (newShield > shield ? 'secondary' : 'warning');
propDiffs.push(<div key='shields'>{translate('shields')}: <span className={sgDiffClass}>{diff(formats.int, newShield, shield)}{units.MJ}</span></div>);
}
if (m.grp === 'mrp') {
let mProtection = m.getProtection();
let mmProtection = mm ? mm.getProtection() || 0 : 0;
if (mProtection != mmProtection) {
propDiffs.push(<div key='protection'>{translate('protection')}: <span className={diffClass(mmProtection, mProtection, true)}>{diff(formats.pct, mProtection, mmProtection)}</span></div>);
}
}
if (m.grp === 'hr') {
let mHullReinforcement = m.getHullReinforcement();
let mmHullReinforcement = mm ? mm.getHullReinforcement() || 0 : 0;
if (mHullReinforcement && mHullReinforcement != mmHullReinforcement) propDiffs.push(<div key='hullreinforcement'>{translate('hullreinforcement')}: <span className={diffClass(mmHullReinforcement, mHullReinforcement, true)}>{diff(formats.round, mHullReinforcement, mmHullReinforcement)}</span></div>);
}
if (m.grp == 'pd') {
propDiffs.push(<div key='wep'>
{`${translate('WEP')}: `}
<span className={diffClass(m.wepcap, mm.getWeaponsCapacity())}>{m.wepcap}{units.MJ}</span>
{' / '}
<span className={diffClass(m.weprate, mm.getWeaponsRechargeRate())}>{m.weprate}{units.MW}</span>
</div>);
propDiffs.push(<div key='sys'>
{`${translate('SYS')}: `}
<span className={diffClass(m.syscap, mm.getSystemsCapacity())}>{m.syscap}{units.MJ}</span>
{' / '}
<span className={diffClass(m.sysrate, mm.getSystemsRechargeRate())}>{m.sysrate}{units.MW}</span>
</div>);
propDiffs.push(<div key='eng'>
{`${translate('ENG')}: `}
<span className={diffClass(m.engcap, mm.getEnginesCapacity())}>{m.engcap}{units.MJ}</span>
{' / '}
<span className={diffClass(m.engrate, mm.getEnginesRechargeRate())}>{m.engrate}{units.MW}</span>
</div>);
}
// Common items
let mCost = m.cost || 0;
let mmCost = mm ? mm.cost : 0;
if (mCost != mmCost) propDiffs.push(<div key='cost'>{translate('cost')}: <span className={diffClass(mCost, mmCost, true) }>{formats.int(mCost ? Math.round(mCost * (1 - Persist.getModuleDiscount())) : 0)}{units.CR}</span></div>);
let mMass = m.getMass() || 0;
let mmMass = mm ? mm.getMass() : 0;
if (mMass != mmMass) propDiffs.push(<div key='mass'>{translate('mass')}: <span className={diffClass(mMass, mmMass, true)}>{diff(formats.round, mMass, mmMass)}{units.T}</span></div>);
let massDiff = mMass - mmMass;
let mCap = m.fuel || m.cargo || 0;
let mmCap = mm ? mm.fuel || mm.cargo || 0 : 0;
let capDiff = mCap - mmCap;
if (m.grp == 'fsd' || massDiff || capDiff) {
let fsd = m.grp == 'fsd' ? m : null;
let maxRange = this.calcUnladenRange(massDiff, m.fuel, fsd);
let ladenRange = this.calcLadenRange(massDiff + capDiff, m.fuel, fsd);
if (maxRange != this.unladenRange) {
propDiffs.push(<div key='maxRange'>{translate('max')} {translate('jump range')}: <span className={maxRange > this.unladenRange ? 'secondary' : 'warning'}>{formats.round(maxRange)}{units.LY}</span></div>);
}
if (ladenRange != this.ladenRange) {
propDiffs.push(<div key='unladenRange'>{translate('laden')} {translate('jump range')}: <span className={ladenRange > this.ladenRange ? 'secondary' : 'warning'}>{formats.round(ladenRange)}{units.LY}</span></div>);
}
}
let mIntegrity = m.getIntegrity() || 0;
let mmIntegrity = mm ? mm.getIntegrity() || 0 : 0;
if (mIntegrity != mmIntegrity) {
propDiffs.push(<div key='integrity'>{translate('integrity')}: <span className={diffClass(mmIntegrity, mIntegrity, true)}>{diff(formats.round, mIntegrity, mmIntegrity)}</span></div>);
}
return propDiffs.length > 0 ? <div className='cap' style={{ whiteSpace: 'nowrap' }}>{propDiffs}</div> : null;
}