First steps towards ed-forge rewrite

This commit is contained in:
Felix Linker
2019-09-17 01:41:17 +02:00
committed by felixlinker
parent cee4c32551
commit 9797a8d781
17 changed files with 421 additions and 792 deletions

View File

@@ -128,6 +128,7 @@
"coriolis-data": "../coriolis-data",
"d3": "^5.7.0",
"detect-browser": "^3.0.1",
"ed-forge": "github:EDCD/ed-forge",
"fbemitter": "^2.1.1",
"lodash": "^4.17.11",
"lz-string": "^1.4.4",

View File

@@ -5,16 +5,14 @@ import { register } from 'register-service-worker';
import { EventEmitter } from 'fbemitter';
import { getLanguage } from './i18n/Language';
import Persist from './stores/Persist';
import { Ship } from 'ed-forge';
import Announcement from './components/Announcement';
import Header from './components/Header';
import Tooltip from './components/Tooltip';
import ModalExport from './components/ModalExport';
import ModalHelp from './components/ModalHelp';
import ModalImport from './components/ModalImport';
import ModalPermalink from './components/ModalPermalink';
import * as CompanionApiUtils from './utils/CompanionApiUtils';
import * as JournalUtils from './utils/JournalUtils';
import AboutPage from './pages/AboutPage';
import NotFoundPage from './pages/NotFoundPage';
import OutfittingPage from './pages/OutfittingPage';
@@ -22,7 +20,6 @@ import ComparisonPage from './pages/ComparisonPage';
import ShipyardPage from './pages/ShipyardPage';
import ErrorDetails from './pages/ErrorDetails';
const zlib = require('pako');
const request = require('superagent');
/**
@@ -61,7 +58,6 @@ export default class Coriolis extends React.Component {
this._onLanguageChange = this._onLanguageChange.bind(this);
this._onSizeRatioChange = this._onSizeRatioChange.bind(this);
this._keyDown = this._keyDown.bind(this);
this._importBuild = this._importBuild.bind(this);
this.emitter = new EventEmitter();
this.state = {
@@ -93,19 +89,10 @@ export default class Coriolis extends React.Component {
_importBuild(r) {
try {
// Need to decode and gunzip the data, then build the ship
const data = zlib.inflate(new Buffer(r.params.data, 'base64'), { to: 'string' });
const json = JSON.parse(data);
console.info('Ship import data: ');
console.info(json);
let ship;
if (json && json.modules) {
ship = CompanionApiUtils.shipFromJson(json);
} else if (json && json.Modules) {
ship = JournalUtils.shipFromLoadoutJSON(json);
}
r.params.ship = ship.id;
r.params.code = ship.toString();
this._setPage(OutfittingPage, r)
let ship = new Ship(r.params.data);
r.params.ship = ship.getShipType();
r.params.code = ship.compress();
this._setPage(OutfittingPage, r);
} catch (err) {
this._onError('Failed to import ship', r.path, 0, 0, err);
}

View File

@@ -122,7 +122,6 @@ const CATEGORIES = {
*/
export default class AvailableModulesMenu extends TranslatedComponent {
static propTypes = {
modules: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
onSelect: PropTypes.func.isRequired,
diffDetails: PropTypes.func,
m: PropTypes.object,
@@ -155,7 +154,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
*/
_initState(props, context) {
let translate = context.language.translate;
let { m, warning, onSelect, modules, ship } = props;
let { m, warning, onSelect, ship } = props;
let list, currentGroup;
let buildGroup = this._buildGroup.bind(
@@ -170,6 +169,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
}
);
let fuzzy = [];
let modules = m.getApplicableItems();
if (modules instanceof Array) {
list = buildGroup(modules[0].grp, modules);
} else {

View File

@@ -21,7 +21,6 @@ import { blueprintTooltip } from '../utils/BlueprintFunctions';
* Hardpoint / Utility Slot
*/
export default class HardpointSlot extends Slot {
/**
* Get the CSS class name for the slot.
* @return {string} CSS Class name
@@ -42,40 +41,38 @@ export default class HardpointSlot extends Slot {
/**
* Generate the slot contents
* @param {Object} m Mounted Module
* @param {Boolean} enabled Slot enabled
* @param {Function} translate Translate function
* @param {Object} formats Localized Formats map
* @param {Object} u Localized Units Map
* @return {React.Component} Slot contents
*/
_getSlotDetails(m, enabled, translate, formats, u) {
_getSlotDetails(m, translate, formats, u) {
if (m) {
let classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
let { drag, drop } = this.props;
let { termtip, tooltip } = this.context;
let validMods = Modifications.modules[m.grp].modifications || [];
let showModuleResistances = Persist.showModuleResistances();
// Modifications tooltip shows blueprint and grade, if available
let modTT = translate('modified');
if (m && m.blueprint && m.blueprint.name) {
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
if (m.blueprint.special && m.blueprint.special.id >= 0) {
modTT += ', ' + translate(m.blueprint.special.name);
}
modTT = (
<div>
<div>{modTT}</div>
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
</div>
);
// modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
// if (m.blueprint.special && m.blueprint.special.id >= 0) {
// modTT += ', ' + translate(m.blueprint.special.name);
// }
// modTT = (
// <div>
// <div>{modTT}</div>
// {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], m)}
// </div>
// );
}
const className = cn('details', enabled ? '' : 'disabled');
const className = cn('details', m.isEnabled() ? '' : 'disabled');
return <div className={className} draggable='true' onDragStart={drag} onDragEnd={drop}>
<div className={'cb'}>
<div className={'l'}>
{m.mount && m.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')}
{/* {m.mount && m.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')}
onMouseOut={tooltip.bind(null, null)}><MountFixed/></span> : ''}
{m.mount && m.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')}
onMouseOut={tooltip.bind(null, null)}><MountGimballed/></span> : ''}
@@ -91,13 +88,13 @@ export default class HardpointSlot extends Slot {
onMouseOut={tooltip.bind(null, null)}><DamageAbsolute/></span> : ''}
{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span className='r'
onMouseOver={termtip.bind(null, modTT)}
onMouseOut={tooltip.bind(null, null)}><Modified/></span> : null}
onMouseOut={tooltip.bind(null, null)}><Modified/></span> : null} */}
</div>
<div className={'r'}>{formats.round(m.getMass())}{u.T}</div>
<div className={'r'}>{formats.round(m.get('mass'))}{u.T}</div>
</div>
<div className={'cb'}>
{m.getDps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'dpssdps' : 'dps')}
{/* {m.getDps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'dpssdps' : 'dps')}
onMouseOut={tooltip.bind(null, null)}>{translate('DPS')}: {formats.round1(m.getDps())} {m.getClip() ?
<span>({formats.round1(m.getSDps())})</span> : null}</div> : null}
{m.getDamage() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getDamage() ? 'shotdmg' : 'shotdmg')}
@@ -140,12 +137,11 @@ export default class HardpointSlot extends Slot {
<button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation}
onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}>
<ListModifications/></button>
</div> : null}
</div> : null} */}
</div>
</div>;
} else {
return <div className={'empty'}>{translate('empty')}</div>;
}
}
}

View File

@@ -68,16 +68,11 @@ export default class HardpointSlotSection extends SlotSection {
let { ship, currentMenu } = this.props;
let { originSlot, targetSlot } = this.state;
let slots = [];
let hardpoints = ship.hardpoints;
let availableModules = ship.getAvailableModules();
for (let i = 0, l = hardpoints.length; i < l; i++) {
let h = hardpoints[i];
if (h.maxClass) {
for (let h of ship.getHardpoints(undefined, true)) {
slots.push(<HardpointSlot
key={i}
maxClass={h.maxClass}
availableModules={() => availableModules.getHps(h.maxClass)}
key={h.object.Slot}
maxClass={h.getSize()}
onOpen={this._openMenu.bind(this, h)}
onSelect={this._selectModule.bind(this, h)}
onChange={this.props.onChange}
@@ -87,11 +82,10 @@ export default class HardpointSlotSection extends SlotSection {
drop={this._drop}
dropClass={this._dropClass(h, originSlot, targetSlot)}
ship={ship}
m={h.m}
slot={h}
enabled={h.enabled ? true : false}
/>);
}
}
return slots;
}

View File

@@ -15,36 +15,39 @@ export default class InternalSlot extends Slot {
/**
* Generate the slot contents
* @param {Object} m Mounted Module
* @param {Boolean} enabled Slot enabled
* @param {Function} translate Translate function
* @param {Object} formats Localized Formats map
* @param {Object} u Localized Units Map
* @return {React.Component} Slot contents
*/
_getSlotDetails(m, enabled, translate, formats, u) {
_getSlotDetails(m, translate, formats, u) {
if (m) {
let classRating = m.class + m.rating;
let classRating = String(m.getSize()) + m.getRating();
let { drag, drop, ship } = this.props;
let { termtip, tooltip } = this.context;
let validMods = (Modifications.modules[m.grp] ? Modifications.modules[m.grp].modifications : []);
let validMods = m.getApplicableBlueprints();
let showModuleResistances = Persist.showModuleResistances();
// Modifications tooltip shows blueprint and grade, if available
let modTT = translate('modified');
if (m && m.blueprint && m.blueprint.name) {
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
if (m.blueprint.special && m.blueprint.special.id >= 0) {
modTT += ', ' + translate(m.blueprint.special.name);
}
modTT = (
<div>
<div>{modTT}</div>
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
</div>
);
}
// let modTT = translate('modified');
// const blueprint = m.getBlueprint();
// const experimental = m.getExperimental();
// const grade = m.getGrade();
// if (blueprint) {
// modTT = translate(blueprint) + ' ' + translate('grade') + ' ' + grade;
// if (experimental) {
// modTT += ', ' + translate(experimental);
// }
// modTT = (
// <div>
// <div>{modTT}</div>
// {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], m)}
// </div>
// );
// }
let mass = m.getMass() || m.cargo || m.fuel || 0;
let mass = m.get('mass') || m.get('cargo') || m.get('fuel') || 0;
const enabled = m.isEnabled();
const className = cn('details', enabled ? '' : 'disabled');
return <div className={className} draggable='true' onDragStart={drag} onDragEnd={drop}>
@@ -53,7 +56,7 @@ export default class InternalSlot extends Slot {
<div className={'r'}>{formats.round(mass)}{u.T}</div>
</div>
<div className={'cb'}>
{ m.getOptMass() ? <div className={'l'}>{translate('optmass', 'sg')}: {formats.int(m.getOptMass())}{u.T}</div> : null }
{/* { m.getOptMass() ? <div className={'l'}>{translate('optmass', 'sg')}: {formats.int(m.getOptMass())}{u.T}</div> : null }
{ m.getMaxMass() ? <div className={'l'}>{translate('maxmass', 'sg')}: {formats.int(m.getMaxMass())}{u.T}</div> : null }
{ m.bins ? <div className={'l'}>{m.bins} <u>{translate('bins')}</u></div> : null }
{ m.bays ? <div className={'l'}>{translate('bays')}: {m.bays}</div> : null }
@@ -88,7 +91,7 @@ export default class InternalSlot extends Slot {
{ m.getHullReinforcement() ? <div className='l'>{translate('armour')}: {formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost') / 10000)}</div> : null }
{ m.getProtection() ? <div className='l'>{translate('protection')}: {formats.rPct(m.getProtection())}</div> : null }
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
{ m && validMods.length > 0 ? <div className='r' tabIndex="0" ref={ modButton => this.modButton = modButton }><button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
{ m && validMods.length > 0 ? <div className='r' tabIndex="0" ref={ modButton => this.modButton = modButton }><button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null } */}
</div>
</div>;
} else {

View File

@@ -9,7 +9,6 @@ import { canMount } from '../utils/SlotFunctions';
* Internal slot section
*/
export default class InternalSlotSection extends SlotSection {
/**
* Constructor
* @param {Object} props React Component properties
@@ -221,29 +220,24 @@ export default class InternalSlotSection extends SlotSection {
let slots = [];
let { currentMenu, ship } = this.props;
let { originSlot, targetSlot } = this.state;
let { internal, fuelCapacity } = ship;
let availableModules = ship.getAvailableModules();
for (let i = 0, l = internal.length; i < l; i++) {
let s = internal[i];
let { fuelCapacity } = ship;
for (const slot of ship.getInternals(undefined, true)) {
slots.push(<InternalSlot
key={i}
maxClass={s.maxClass}
availableModules={() => availableModules.getInts(ship, s.maxClass, s.eligible)}
onOpen={this._openMenu.bind(this,s)}
key={slot.object.Slot}
maxClass={slot.getSize()}
onOpen={this._openMenu.bind(this, slot)}
onChange={this.props.onChange}
onSelect={this._selectModule.bind(this, s)}
selected={currentMenu == s}
eligible={s.eligible}
m={s.m}
drag={this._drag.bind(this, s)}
dragOver={this._dragOverSlot.bind(this, s)}
onSelect={this._selectModule.bind(this, slot)}
selected={currentMenu == slot}
slot={slot}
drag={this._drag.bind(this, slot)}
dragOver={this._dragOverSlot.bind(this, slot)}
drop={this._drop}
dropClass={this._dropClass(s, originSlot, targetSlot)}
dropClass={this._dropClass(slot, originSlot, targetSlot)}
fuel={fuelCapacity}
ship={ship}
enabled={s.enabled ? true : false}
enabled={slot.isEnabled()}
/>);
}
@@ -273,5 +267,4 @@ export default class InternalSlotSection extends SlotSection {
</ul>
</div>;
}
}

View File

@@ -85,7 +85,7 @@ export default class ModificationsMenu extends TranslatedComponent {
});
const close = this._blueprintSelected.bind(this, blueprintName, grade);
const key = blueprintName + ':' + grade;
const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers, m.grp);
const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade]);
if (classes.indexOf('active') >= 0) this.selectedModId = key;
blueprintGrades.unshift(<li key={key} tabIndex="0" data-id={key} className={classes} style={{ width: '2em' }} onMouseOver={termtip.bind(null, tooltipContent)} onMouseOut={tooltip.bind(null, null)} onClick={close} onKeyDown={this._keyDown} ref={modItem => this.modItems[key] = modItem}>{grade}</li>);
}
@@ -429,7 +429,7 @@ export default class ModificationsMenu extends TranslatedComponent {
if (m.blueprint && m.blueprint.name && Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade]) {
blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
haveBlueprint = true;
blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade].engineers, m.grp);
blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade]);
blueprintCv = getPercent(m);
}

View File

@@ -5,6 +5,12 @@ import cn from 'classnames';
import { Warning } from './SvgIcons';
import * as Calc from '../shipyard/Calculations';
import { ShipProps } from 'ed-forge';
const {
SPEED, JUMP_RANGE, TOTAL_RANGE, SHIELD_METRICS, ARMOUR_METRICS, MODULE_ARMOUR,
MODULE_PROTECTION
} = ShipProps;
/**
* Ship Summary Table / Stats
*/
@@ -12,10 +18,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired,
cargo: PropTypes.number.isRequired,
fuel: PropTypes.number.isRequired,
marker: PropTypes.string.isRequired,
pips: PropTypes.object.isRequired
};
/**
@@ -35,14 +38,14 @@ export default class ShipSummaryTable extends TranslatedComponent {
* @return {React.Component} Summary table
*/
render() {
const { ship, cargo, fuel, pips } = this.props;
const { ship } = this.props;
let { language, tooltip, termtip } = this.context;
let translate = language.translate;
let u = language.units;
let formats = language.formats;
let { time, int, round, f1, f2 } = formats;
let hide = tooltip.bind(null, null);
const shieldGenerator = ship.findInternalByGroup('sg') || ship.findInternalByGroup('psg');
const shieldGenerator = ship.getShieldGenerator();
const sgClassNames = cn({ warning: shieldGenerator && !ship.shield, muted: !shieldGenerator });
const sgTooltip = shieldGenerator ? 'TT_SUMMARY_SHIELDS' : 'TT_SUMMARY_SHIELDS_NONFUNCTIONAL';
const timeToDrain = Calc.timeToDrainWep(ship, 4);
@@ -64,15 +67,19 @@ export default class ShipSummaryTable extends TranslatedComponent {
this.state = {
shieldColour
};
let speed = ship.get(SPEED);
let jumpRange = ship.get(JUMP_RANGE);
return <div id='summary'>
<div style={{display: "table", width: "100%"}}>
<div style={{display: "table-row"}}>
<table className={'summaryTable'}>
<thead>
<tr className='main'>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canThrust }) }>{translate('speed')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': speed == 0 }) }>{translate('speed')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost')}</th>
<th colSpan={5} className={ cn({ 'bg-warning-disabled': !canJump }) }>{translate('jump range')}</th>
<th colSpan={5} className={ cn({ 'bg-warning-disabled': jumpRange == 0 }) }>{translate('jump range')}</th>
<th rowSpan={2}>{translate('shield')}</th>
<th rowSpan={2}>{translate('integrity')}</th>
<th rowSpan={2}>{translate('DPS')}</th>

View File

@@ -6,22 +6,19 @@ import AvailableModulesMenu from './AvailableModulesMenu';
import ModificationsMenu from './ModificationsMenu';
import { diffDetails } from '../utils/SlotFunctions';
import { wrapCtxMenu } from '../utils/UtilityFunctions';
import { Ship, Module } from 'ed-forge';
import { REG_MILITARY_SLOT } from 'ed-forge/lib/data/slots';
/**
* Abstract Slot
*/
export default class Slot extends TranslatedComponent {
static propTypes = {
availableModules: PropTypes.func.isRequired,
onSelect: PropTypes.func.isRequired,
onOpen: PropTypes.func.isRequired,
maxClass: PropTypes.number.isRequired,
selected: PropTypes.bool,
m: PropTypes.object,
enabled: PropTypes.bool.isRequired,
ship: PropTypes.object.isRequired,
eligible: PropTypes.object,
slot: PropTypes.instanceOf(Module),
ship: PropTypes.instanceOf(Ship),
warning: PropTypes.func,
drag: PropTypes.func,
drop: PropTypes.func,
@@ -61,7 +58,7 @@ export default class Slot extends TranslatedComponent {
* @return {string} label
*/
_getMaxClassLabel() {
return this.props.maxClass;
return this.props.slot.getSize();
}
/**
@@ -97,7 +94,7 @@ export default class Slot extends TranslatedComponent {
render() {
let language = this.context.language;
let translate = language.translate;
let { ship, m, enabled, dropClass, dragOver, onOpen, onChange, selected, eligible, onSelect, warning, availableModules } = this.props;
let { ship, slot, dropClass, dragOver, onOpen, onChange, selected, onSelect, warning } = this.props;
let slotDetails, modificationsMarker, menu;
if (!selected) {
@@ -105,36 +102,37 @@ export default class Slot extends TranslatedComponent {
this._modificationsSelected = false;
}
if (m) {
slotDetails = this._getSlotDetails(m, enabled, translate, language.formats, language.units); // Must be implemented by sub classes
modificationsMarker = JSON.stringify(m);
if (!slot.isEmpty()) {
slotDetails = this._getSlotDetails(slot, translate, language.formats, language.units); // Must be implemented by sub classes
} else {
slotDetails = <div className={'empty'}>{translate(eligible ? 'emptyrestricted' : 'empty')}</div>;
modificationsMarker = '';
slotDetails = <div className={'empty'}>
{translate(
slot.getSlot().match(REG_MILITARY_SLOT) ? 'emptyrestricted' : 'empty'
)}
</div>;
}
if (selected) {
if (this._modificationsSelected) {
menu = <ModificationsMenu
className={this._getClassNames()}
onChange={onChange}
ship={ship}
m={m}
marker={modificationsMarker}
modButton = {this.modButton}
/>;
} else {
// if (this._modificationsSelected) {
// menu = <ModificationsMenu
// className={this._getClassNames()}
// onChange={onChange}
// ship={ship}
// m={m}
// marker={modificationsMarker}
// modButton = {this.modButton}
// />;
// } else {
menu = <AvailableModulesMenu
className={this._getClassNames()}
modules={availableModules()}
m={m}
m={slot}
ship={ship}
onSelect={onSelect}
warning={warning}
diffDetails={diffDetails.bind(ship, this.context.language)}
slotDiv = {this.slotDiv}
/>;
}
// }
}
// TODO: implement touch dragging
@@ -150,7 +148,6 @@ export default class Slot extends TranslatedComponent {
);
}
/**
* Toggle the modifications flag when selecting the modifications icon
*/

View File

@@ -5,19 +5,17 @@ import { wrapCtxMenu } from '../utils/UtilityFunctions';
import { canMount } from '../utils/SlotFunctions';
import { Equalizer } from '../components/SvgIcons';
import cn from 'classnames';
import { Ship } from 'ed-forge';
const browser = require('detect-browser');
/**
* Abstract Slot Section
*/
export default class SlotSection extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired,
ship: PropTypes.instanceOf(Ship),
onChange: PropTypes.func.isRequired,
onCargoChange: PropTypes.func.isRequired,
onFuelChange: PropTypes.func.isRequired,
code: PropTypes.string.isRequired,
// code: PropTypes.string.isRequired,
togglePwr: PropTypes.func,
sectionMenuRefs: PropTypes.object
};
@@ -106,6 +104,7 @@ export default class SlotSection extends TranslatedComponent {
this.sectionRefArr['ssHeadRef'].focus();
}
}
/**
* Open a menu
* @param {string} menu Menu name
@@ -126,7 +125,7 @@ export default class SlotSection extends TranslatedComponent {
* @param {Object} m Selected module
*/
_selectModule(slot, m) {
this.props.ship.use(slot, m, false);
slot.setItem(m);
this.props.onChange();
this._close();
}

View File

@@ -6,24 +6,22 @@ import TranslatedComponent from './TranslatedComponent';
import { diffDetails } from '../utils/SlotFunctions';
import AvailableModulesMenu from './AvailableModulesMenu';
import ModificationsMenu from './ModificationsMenu';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import { ListModifications, Modified } from './SvgIcons';
import { Modifications } from 'coriolis-data/dist';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import { blueprintTooltip } from '../utils/BlueprintFunctions';
import { Ship, Module } from 'ed-forge';
/**
* Standard Slot
*/
export default class StandardSlot extends TranslatedComponent {
static propTypes = {
slot: PropTypes.object,
modules: PropTypes.array.isRequired,
slot: PropTypes.instanceOf(Module),
onSelect: PropTypes.func.isRequired,
onOpen: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
ship: PropTypes.object.isRequired,
ship: PropTypes.instanceOf(Ship),
selected: PropTypes.bool,
warning: PropTypes.func,
};
@@ -59,31 +57,26 @@ export default class StandardSlot extends TranslatedComponent {
render() {
let { termtip, tooltip } = this.context;
let { translate, formats, units } = this.context.language;
let { modules, slot, selected, warning, onSelect, onChange, ship } = this.props;
let m = slot.m;
let classRating = m.class + m.rating;
let { slot, selected, warning, onSelect, onChange, ship } = this.props;
let classRating = String(slot.getClass()) + (slot.getRating() || '');
let menu;
let validMods = m == null || !Modifications.modules[m.grp] ? [] : (Modifications.modules[m.grp].modifications || []);
if (m && m.name && m.name === 'Guardian Hybrid Power Plant') {
validMods = [];
}
if (m && m.name && m.name === 'Guardian Power Distributor') {
validMods = [];
}
let validMods = slot.getApplicableBlueprints();
let showModuleResistances = Persist.showModuleResistances();
let mass = m.getMass() || m.cargo || m.fuel || 0;
let mass = slot.get('cargo') || slot.get('fuel') || slot.get('mass') || 0;
// Modifications tooltip shows blueprint and grade, if available
let modTT = translate('modified');
if (m && m.blueprint && m.blueprint.name) {
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
if (m.blueprint.special && m.blueprint.special.id >= 0) {
modTT += ', ' + translate(m.blueprint.special.name);
const appliedBlueprint = slot.getBlueprint();
const appliedExperimental = slot.getExperimental();
if (appliedBlueprint) {
modTT = translate(appliedBlueprint) + ' ' + translate('grade') + ' ' + slot.getBlueprintGrade();
if (appliedExperimental) {
modTT += ', ' + translate(appliedExperimental);
}
modTT = (
<div>
<div>{modTT}</div>
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
{blueprintTooltip(translate, slot)}
</div>
);
}
@@ -93,23 +86,19 @@ export default class StandardSlot extends TranslatedComponent {
this._modificationsSelected = false;
}
const modificationsMarker = JSON.stringify(m);
if (selected) {
if (this._modificationsSelected) {
menu = <ModificationsMenu
className='standard'
onChange={onChange}
ship={ship}
m={m}
marker={modificationsMarker}
m={slot}
modButton = {this.modButton}
/>;
} else {
menu = <AvailableModulesMenu
className='standard'
modules={modules}
m={m}
m={slot}
ship={ship}
onSelect={onSelect}
warning={warning}
@@ -121,14 +110,14 @@ export default class StandardSlot extends TranslatedComponent {
return (
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen} onKeyDown={this._keyDown} onContextMenu={stopCtxPropagation} tabIndex="0" ref={ slotDiv => this.slotDiv = slotDiv }>
<div className={cn('details-container', { warning: warning && warning(slot.m), disabled: m.grp !== 'bh' && !slot.enabled })}>
<div className={'sz'}>{m.grp == 'bh' ? m.name.charAt(0) : slot.maxClass}</div>
<div className={cn('details-container', { warning: warning && warning(slot), disabled: slot.isEnabled() })}>
<div className={'sz'}>{slot.getSize()}</div>
<div>
<div className={'l'}>{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span className='r' onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : null }</div>
<div className={'l'}>{classRating} {translate(slot.getItem())}{appliedBlueprint ? <span className='r' onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : null }</div>
<div className={'r'}>{formats.round(mass)}{units.T}</div>
<div/>
<div className={'cb'}>
{ m.getMinMass() ? <div className='l'>{translate('minimum mass')}: {formats.int(m.getMinMass())}{units.T}</div> : null }
{/* { m.getMinMass() ? <div className='l'>{translate('minimum mass')}: {formats.int(m.getMinMass())}{units.T}</div> : null }
{ m.getOptMass() ? <div className='l'>{translate('optimal mass')}: {formats.int(m.getOptMass())}{units.T}</div> : null }
{ m.getMaxMass() ? <div className='l'>{translate('max mass')}: {formats.int(m.getMaxMass())}{units.T}</div> : null }
{ m.getOptMul() ? <div className='l'>{translate('optimal multiplier')}: {formats.rPct(m.getOptMul())}</div> : null }
@@ -143,8 +132,8 @@ export default class StandardSlot extends TranslatedComponent {
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null }
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
{ validMods.length > 0 ? <div className='r' tabIndex="0" ref={ modButton => this.modButton = modButton }><button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null } */}
{/* { validMods.length > 0 ? <div className='r' tabIndex="0" ref={ modButton => this.modButton = modButton }><button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null } */}
</div>
</div>
</div>

View File

@@ -5,12 +5,12 @@ import StandardSlot from './StandardSlot';
import Module from '../shipyard/Module';
import * as ShipRoles from '../shipyard/ShipRoles';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import { ShipProps } from 'ed-forge';
/**
* Standard Slot section
*/
export default class StandardSlotSection extends SlotSection {
/**
* Constructor
* @param {Object} props React Component properties
@@ -39,8 +39,6 @@ export default class StandardSlotSection extends SlotSection {
this.selectedRefId = 'maxjump';
this.props.ship.useLightestStandard();
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
@@ -54,8 +52,6 @@ export default class StandardSlotSection extends SlotSection {
if (bulkheadIndex === 2) this.selectedRefId = 'combat';
ShipRoles.multiPurpose(this.props.ship, shielded, bulkheadIndex);
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
@@ -67,8 +63,6 @@ export default class StandardSlotSection extends SlotSection {
this.selectedRefId = 'trader';
ShipRoles.trader(this.props.ship, shielded);
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
@@ -80,8 +74,6 @@ export default class StandardSlotSection extends SlotSection {
this.selectedRefId = 'miner';
ShipRoles.miner(this.props.ship, shielded);
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
@@ -94,8 +86,6 @@ export default class StandardSlotSection extends SlotSection {
if (planetary) this.selectedRefId = 'planetary';
ShipRoles.explorer(this.props.ship, planetary);
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
@@ -106,8 +96,6 @@ export default class StandardSlotSection extends SlotSection {
this.selectedRefId = 'racer';
ShipRoles.racer(this.props.ship);
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
@@ -134,105 +122,114 @@ export default class StandardSlotSection extends SlotSection {
* @return {Array} Array of Slots
*/
_getSlots() {
let { ship, currentMenu, cargo, fuel } = this.props;
let { ship, currentMenu } = this.props;
let slots = new Array(8);
let open = this._openMenu;
let select = this._selectModule;
let st = ship.standard;
let avail = ship.getAvailableModules().standard;
let bh = ship.bulkheads;
// let st = ship.standard;
// let avail = ship.getAvailableModules().standard;
// let bh = ship.bulkheads;
let armour = ship.getAlloys();
slots[0] = <StandardSlot
key='bh'
slot={bh}
modules={ship.getAvailableModules().bulkheads}
onOpen={open.bind(this, bh)}
slot={armour}
modules={armour.getApplicableItems()}
onOpen={open.bind(this, armour)}
onSelect={this._selectBulkhead}
selected={currentMenu == bh}
selected={currentMenu == armour}
onChange={this.props.onChange}
ship={ship}
/>;
const powerPlant = ship.getPowerPlant();
slots[1] = <StandardSlot
key='pp'
slot={st[0]}
modules={avail[0]}
onOpen={open.bind(this, st[0])}
onSelect={select.bind(this, st[0])}
selected={currentMenu == st[0]}
slot={powerPlant}
modules={powerPlant.getApplicableItems()}
onOpen={open.bind(this, powerPlant)}
onSelect={select.bind(this, powerPlant)}
selected={currentMenu == powerPlant}
onChange={this.props.onChange}
ship={ship}
warning={m => m instanceof Module ? m.getPowerGeneration() < ship.powerRetracted : m.pgen < ship.powerRetracted}
warning={m => ship.get(ShipProps.CONSUMED_RETR) < m.get('powercapacity')}
/>;
const thrusters = ship.getThrusters();
slots[2] = <StandardSlot
key='th'
slot={st[1]}
modules={avail[1]}
onOpen={open.bind(this, st[1])}
onSelect={select.bind(this, st[1])}
selected={currentMenu == st[1]}
slot={thrusters}
modules={thrusters.getApplicableItems()}
onOpen={open.bind(this, thrusters)}
onSelect={select.bind(this, thrusters)}
selected={currentMenu == thrusters}
onChange={this.props.onChange}
ship={ship}
warning={m => m instanceof Module ? m.getMaxMass() < (ship.unladenMass + cargo + fuel - st[1].m.mass + m.mass) : m.maxmass < (ship.unladenMass + cargo + fuel - st[1].m.mass + m.mass)}
warning={m => m.get('enginemaximalmass') < ship.get(ShipProps.LADEN_MASS)}
/>;
const fsd = ship.getFSD();
slots[3] = <StandardSlot
key='fsd'
slot={st[2]}
modules={avail[2]}
onOpen={open.bind(this, st[2])}
onSelect={select.bind(this, st[2])}
slot={fsd}
modules={fsd.getApplicableItems()}
onOpen={open.bind(this, fsd)}
onSelect={select.bind(this, fsd)}
onChange={this.props.onChange}
ship={ship}
selected={currentMenu == st[2]}
selected={currentMenu == fsd}
/>;
const lifeSupport = ship.getLifeSupport();
slots[4] = <StandardSlot
key='ls'
slot={st[3]}
modules={avail[3]}
onOpen={open.bind(this, st[3])}
onSelect={select.bind(this, st[3])}
slot={lifeSupport}
modules={lifeSupport.getApplicableItems()}
onOpen={open.bind(this, lifeSupport)}
onSelect={select.bind(this, lifeSupport)}
onChange={this.props.onChange}
ship={ship}
selected={currentMenu == st[3]}
selected={currentMenu == lifeSupport}
/>;
const powerDistributor = ship.getPowerDistributor();
slots[5] = <StandardSlot
key='pd'
slot={st[4]}
modules={avail[4]}
onOpen={open.bind(this, st[4])}
onSelect={select.bind(this, st[4])}
selected={currentMenu == st[4]}
slot={powerDistributor}
modules={powerDistributor.getApplicableItems()}
onOpen={open.bind(this, powerDistributor)}
onSelect={select.bind(this, powerDistributor)}
selected={currentMenu == powerDistributor}
onChange={this.props.onChange}
ship={ship}
warning={m => m instanceof Module ? m.getEnginesCapacity() <= ship.boostEnergy : m.engcap <= ship.boostEnergy}
/>;
const sensors = ship.getSensors();
slots[6] = <StandardSlot
key='ss'
slot={st[5]}
modules={avail[5]}
onOpen={open.bind(this, st[5])}
onSelect={select.bind(this, st[5])}
selected={currentMenu == st[5]}
slot={sensors}
modules={sensors.getApplicableItems()}
onOpen={open.bind(this, sensors)}
onSelect={select.bind(this, sensors)}
selected={currentMenu == sensors}
onChange={this.props.onChange}
ship={ship}
/>;
const fuelTank = ship.getCoreFuelTank();
slots[7] = <StandardSlot
key='ft'
slot={st[6]}
modules={avail[6]}
onOpen={open.bind(this, st[6])}
onSelect={select.bind(this, st[6])}
selected={currentMenu == st[6]}
slot={fuelTank}
modules={fuelTank.getApplicableItems()}
onOpen={open.bind(this, fuelTank)}
onSelect={select.bind(this, fuelTank)}
selected={currentMenu == fuelTank}
onChange={this.props.onChange}
ship={ship}
warning= {m => m.fuel < st[2].m.maxfuel} // Show warning when fuel tank is smaller than FSD Max Fuel
// Show warning when fuel tank is smaller than FSD Max Fuel
warning= {m => m.get('fuel') < fsd.get('maxfuel')}
/>;
return slots;
@@ -261,5 +258,4 @@ export default class StandardSlotSection extends SlotSection {
</ul>
</div>;
}
}

View File

@@ -67,17 +67,12 @@ export default class UtilitySlotSection extends SlotSection {
_getSlots() {
let slots = [];
let { ship, currentMenu } = this.props;
let hardpoints = ship.hardpoints;
let { originSlot, targetSlot } = this.state;
let availableModules = ship.getAvailableModules();
for (let i = 0, l = hardpoints.length; i < l; i++) {
let h = hardpoints[i];
if (h.maxClass === 0) {
for (let h of ship.getUtilities(undefined, true)) {
slots.push(<HardpointSlot
key={i}
maxClass={h.maxClass}
availableModules={() => availableModules.getHps(h.maxClass)}
key={h.object.Slot}
maxClass={h.getSize()}
onOpen={this._openMenu.bind(this,h)}
onSelect={this._selectModule.bind(this, h)}
onChange={this.props.onChange}
@@ -87,11 +82,10 @@ export default class UtilitySlotSection extends SlotSection {
drop={this._drop}
dropClass={this._dropClass(h, originSlot, targetSlot)}
ship={ship}
m={h.m}
slot={h}
enabled={h.enabled ? true : false}
/>);
}
}
return slots;
}

View File

@@ -6,7 +6,7 @@ import Page from './Page';
import Router from '../Router';
import Persist from '../stores/Persist';
import * as Utils from '../utils/UtilityFunctions';
import Ship from '../shipyard/Ship';
import { Factory, Ship } from 'ed-forge';
import * as _ from 'lodash';
import { toDetailedBuild } from '../shipyard/Serializer';
import { outfitURL } from '../utils/UrlGenerators';
@@ -64,10 +64,6 @@ export default class OutfittingPage extends Page {
this.state = this._initState(props, context);
this._keyDown = this._keyDown.bind(this);
this._exportBuild = this._exportBuild.bind(this);
this._pipsUpdated = this._pipsUpdated.bind(this);
this._boostUpdated = this._boostUpdated.bind(this);
this._cargoUpdated = this._cargoUpdated.bind(this);
this._fuelUpdated = this._fuelUpdated.bind(this);
this._opponentUpdated = this._opponentUpdated.bind(this);
this._engagementRangeUpdated = this._engagementRangeUpdated.bind(this);
this._sectionMenuRefs = {};
@@ -82,40 +78,14 @@ export default class OutfittingPage extends Page {
_initState(props, context) {
let params = context.route.params;
let shipId = params.ship;
let code = params.code;
let buildName = params.bn;
let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults
let savedCode = Persist.getBuild(shipId, buildName);
if (!data) {
return { error: { message: 'Ship not found: ' + shipId } };
}
let ship = new Ship(shipId, data.properties, data.slots); // Create a new Ship instance
if (code) {
ship.buildFrom(code); // Populate modules from serialized 'code' URL param
} else {
ship.buildWith(data.defaults); // Populate with default components
}
let code = params.code || savedCode;
let ship = code ? new Ship(code) : Factory.newShip(shipId); // Create a new Ship instance
code = ship.compress();
this._getTitle = getTitle.bind(this, data.properties.name);
this._getTitle = getTitle.bind(this, ship.getShipType());
// Obtain ship control from code
const {
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
opponentSys,
opponentEng,
opponentWep,
engagementRange
} = this._obtainControlFromCode(ship, code);
return {
error: null,
title: this._getTitle(buildName),
@@ -126,21 +96,6 @@ export default class OutfittingPage extends Page {
ship,
code,
savedCode,
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
opponentSys,
opponentEng,
opponentWep,
engagementRange
};
}
@@ -168,170 +123,13 @@ export default class OutfittingPage extends Page {
/**
* Update the control part of the route
*/
_updateRouteOnControlChange() {
_updateRoute() {
const { ship, shipId, buildName } = this.state;
const code = this._fullCode(ship);
const code = ship.compress();
this._updateRoute(shipId, buildName, code);
this.setState({ code });
}
/**
* Provide a full code for this ship, including any additions due to the outfitting page
* @param {Object} ship the ship
* @param {number} fuel the fuel carried by the ship (if different from that in state)
* @param {number} cargo the cargo carried by the ship (if different from that in state)
* @returns {string} the code for this ship
*/
_fullCode(ship, fuel, cargo) {
return `${ship.toString()}.${LZString.compressToBase64(
this._controlCode(fuel, cargo)
)}`;
}
/**
* Obtain the control information from the build code
* @param {Object} ship The ship
* @param {string} code The build code
* @returns {Object} The control information
*/
_obtainControlFromCode(ship, code) {
// Defaults
let sys = 2;
let eng = 2;
let wep = 2;
let mcSys = 0;
let mcEng = 0;
let mcWep = 0;
let boost = false;
let fuel = ship.fuelCapacity;
let cargo = ship.cargoCapacity;
let opponent = new Ship(
'eagle',
Ships['eagle'].properties,
Ships['eagle'].slots
).buildWith(Ships['eagle'].defaults);
let opponentSys = 2;
let opponentEng = 2;
let opponentWep = 2;
let opponentBuild;
let engagementRange = 1000;
// Obtain updates from code, if available
if (code) {
const parts = code.split('.');
if (parts.length >= 5) {
// We have control information in the code
const control = LZString.decompressFromBase64(
Utils.fromUrlSafe(parts[4])
).split('/');
sys = parseFloat(control[0]);
eng = parseFloat(control[1]);
wep = parseFloat(control[2]);
if (sys + eng + wep > 6) {
sys = eng = wep = 2;
}
boost = control[3] == 1 ? true : false;
fuel = parseFloat(control[4]) || fuel;
cargo = parseInt(control[5]) || cargo;
if (control[6]) {
const shipId = control[6];
opponent = new Ship(
shipId,
Ships[shipId].properties,
Ships[shipId].slots
);
if (control[7] && Persist.getBuild(shipId, control[7])) {
// Ship is a particular build
const opponentCode = Persist.getBuild(shipId, control[7]);
opponent.buildFrom(opponentCode);
opponentBuild = control[7];
if (opponentBuild) {
// Obtain opponent's sys/eng/wep pips from their code
const opponentParts = opponentCode.split('.');
if (opponentParts.length >= 5) {
const opponentControl = LZString.decompressFromBase64(
Utils.fromUrlSafe(opponentParts[4])
).split('/');
opponentSys = parseFloat(opponentControl[0]) || opponentSys;
opponentEng = parseFloat(opponentControl[1]) || opponentEng;
opponentWep = parseFloat(opponentControl[2]) || opponentWep;
}
}
} else {
// Ship is a stock build
opponent.buildWith(Ships[shipId].defaults);
}
}
engagementRange = parseInt(control[8]) || engagementRange;
// Multi-crew pips were introduced later on so assign default values
// because those values might not be present.
mcSys = parseInt(control[9]) || mcSys;
mcEng = parseInt(control[10]) || mcEng;
mcWep = parseInt(control[11]) || mcWep;
}
}
return {
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
opponentSys,
opponentEng,
opponentWep,
engagementRange
};
}
/**
* Triggered when pips have been updated. Multi-crew pips are already included
* in sys, eng and wep but mcSys, mcEng and mcWep make clear where each pip
* comes from.
* @param {number} sys SYS pips
* @param {number} eng ENG pips
* @param {number} wep WEP pips
* @param {number} mcSys SYS pips from multi-crew
* @param {number} mcEng ENG pips from multi-crew
* @param {number} mcWep WEP pips from multi-crew
*/
_pipsUpdated(sys, eng, wep, mcSys, mcEng, mcWep) {
this.setState({ sys, eng, wep, mcSys, mcEng, mcWep }, () =>
this._updateRouteOnControlChange()
);
}
/**
* Triggered when boost has been updated
* @param {boolean} boost true if boosting
*/
_boostUpdated(boost) {
this.setState({ boost }, () => this._updateRouteOnControlChange());
}
/**
* Triggered when fuel has been updated
* @param {number} fuel the amount of fuel, in T
*/
_fuelUpdated(fuel) {
this.setState({ fuel }, () => this._updateRouteOnControlChange());
}
/**
* Triggered when cargo has been updated
* @param {number} cargo the amount of cargo, in T
*/
_cargoUpdated(cargo) {
this.setState({ cargo }, () => this._updateRouteOnControlChange());
}
/**
* Triggered when engagement range has been updated
* @param {number} engagementRange the engagement range, in m
@@ -387,36 +185,10 @@ export default class OutfittingPage extends Page {
opponentEng,
opponentWep
},
() => this._updateRouteOnControlChange()
() => this._updateRoute()
);
}
/**
* Set the control code for this outfitting page
* @param {number} fuel the fuel carried by the ship (if different from that in state)
* @param {number} cargo the cargo carried by the ship (if different from that in state)
* @returns {string} The control code
*/
_controlCode(fuel, cargo) {
const {
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
opponent,
opponentBuild,
engagementRange
} = this.state;
const code = `${sys}/${eng}/${wep}/${boost ? 1 : 0}/${fuel ||
this.state.fuel}/${cargo || this.state.cargo}/${opponent.id}/${
opponentBuild ? opponentBuild : ''
}/${engagementRange}/${mcSys}/${mcEng}/${mcWep}`;
return code;
}
/**
* Save the current build
*/
@@ -424,7 +196,7 @@ export default class OutfittingPage extends Page {
const { ship, buildName, newBuildName, shipId } = this.state;
// If this is a stock ship the code won't be set, so ensure that we have it
const code = this.state.code || ship.toString();
const code = this.state.code || ship.compress();
Persist.saveBuild(shipId, newBuildName, code);
this._updateRoute(shipId, newBuildName, code);
@@ -497,39 +269,9 @@ export default class OutfittingPage extends Page {
// Rebuild ship
ship.buildWith(Ships[shipId].defaults);
// Reset controls
const code = ship.toString();
const {
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
engagementRange
} = this._obtainControlFromCode(ship, code);
const code = ship.compress();
// Update state, and refresh the ship
this.setState(
{
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
engagementRange
},
() => this._updateRoute(shipId, buildName, code)
);
this._updateRoute(shipId, buildName, code);
}
/**
@@ -573,65 +315,22 @@ export default class OutfittingPage extends Page {
* Called when the code for the ship has been updated, to synchronise the rest of the data
*/
_codeUpdated() {
const { code, ship, shipId, buildName } = this.state;
const { code, shipId, buildName } = this.state;
// Rebuild ship from the code
this.state.ship.buildFrom(code);
// Obtain controls from the code
const {
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
engagementRange
} = this._obtainControlFromCode(ship, code);
// Update state, and refresh the route when complete
this.setState(
{
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
engagementRange
},
() => this._updateRoute(shipId, buildName, code)
);
this.setState({
ship: new Ship(code),
}, () => this._updateRoute(shipId, buildName, code));
}
/**
* Called when the ship has been updated, to set the code and then update accordingly
*/
_shipUpdated() {
let { ship, shipId, buildName, cargo, fuel } = this.state;
if (cargo > ship.cargoCapacity) {
cargo = ship.cargoCapacity;
}
if (fuel > ship.fuelCapacity) {
fuel = ship.fuelCapacity;
}
const code = this._fullCode(ship, fuel, cargo);
let { ship, shipId, buildName } = this.state;
const code = ship.compress();
// Only update the state if this really has been updated
if (
this.state.code != code ||
this.state.cargo != cargo ||
this.state.fuel != fuel
) {
this.setState({ code, cargo, fuel }, () =>
if (this.state.code != code) {
this.setState({ code }, () =>
this._updateRoute(shipId, buildName, code)
);
}
@@ -747,29 +446,20 @@ export default class OutfittingPage extends Page {
*/
renderPage() {
let state = this.state,
{ language, termtip, tooltip, sizeRatio, onWindowResize } = this.context,
{ translate, units, formats } = language,
{ language, termtip, tooltip, sizeRatio } = this.context,
{ translate } = language,
{
ship,
code,
savedCode,
buildName,
newBuildName,
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
opponentSys,
opponentEng,
opponentWep,
engagementRange
// opponent,
// opponentBuild,
// opponentSys,
// opponentEng,
// opponentWep,
// engagementRange
} = state,
hide = tooltip.bind(null, null),
menu = this.props.currentMenu,
@@ -782,88 +472,88 @@ export default class OutfittingPage extends Page {
code = ship.name + (code || '');
// Markers are used to propagate state changes without requiring a deep comparison of the ship, as that takes a long time
const _sStr = ship.getStandardString();
const _iStr = ship.getInternalString();
const _hStr = ship.getHardpointsString();
const _pStr = `${ship.getPowerEnabledString()}${ship.getPowerPrioritiesString()}`;
const _mStr = ship.getModificationsString();
// const _sStr = ship.getStandardString();
// const _iStr = ship.getInternalString();
// const _hStr = ship.getHardpointsString();
// const _pStr = `${ship.getPowerEnabledString()}${ship.getPowerPrioritiesString()}`;
// const _mStr = ship.getModificationsString();
const standardSlotMarker = `${ship.name}${_sStr}${_pStr}${_mStr}${
ship.ladenMass
}${cargo}${fuel}`;
const internalSlotMarker = `${ship.name}${_iStr}${_pStr}${_mStr}`;
const hardpointsSlotMarker = `${ship.name}${_hStr}${_pStr}${_mStr}`;
const boostMarker = `${ship.canBoost(cargo, fuel)}`;
const shipSummaryMarker = `${
ship.name
}${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`;
// const standardSlotMarker = `${ship.name}${_sStr}${_pStr}${_mStr}${
// ship.ladenMass
// }${cargo}${fuel}`;
// const internalSlotMarker = `${ship.name}${_iStr}${_pStr}${_mStr}`;
// const hardpointsSlotMarker = `${ship.name}${_hStr}${_pStr}${_mStr}`;
// const boostMarker = `${ship.canBoost(cargo, fuel)}`;
// const shipSummaryMarker = `${
// ship.name
// }${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`;
const requirements = Ships[ship.id].requirements;
let requirementElements = [];
/**
* Render the requirements for a ship / etc
* @param {string} className Class names
* @param {string} textKey The key for translating
* @param {String} tooltipTextKey Tooltip key
*/
function renderRequirement(className, textKey, tooltipTextKey) {
if (textKey.startsWith('empire') || textKey.startsWith('federation')) {
requirementElements.push(
<div
key={textKey}
className={className}
onMouseEnter={termtip.bind(null, tooltipTextKey)}
onMouseLeave={hide}
>
<a
href={
textKey.startsWith('empire') ?
'http://elite-dangerous.wikia.com/wiki/Empire/Ranks' :
'http://elite-dangerous.wikia.com/wiki/Federation/Ranks'
}
target="_blank"
rel="noopener"
>
{translate(textKey)}
</a>
</div>
);
} else {
requirementElements.push(
<div
key={textKey}
className={className}
onMouseEnter={termtip.bind(null, tooltipTextKey)}
onMouseLeave={hide}
>
{translate(textKey)}
</div>
);
}
}
// const requirements = Ships[ship.id].requirements;
// let requirementElements = [];
// /**
// * Render the requirements for a ship / etc
// * @param {string} className Class names
// * @param {string} textKey The key for translating
// * @param {String} tooltipTextKey Tooltip key
// */
// function renderRequirement(className, textKey, tooltipTextKey) {
// if (textKey.startsWith('empire') || textKey.startsWith('federation')) {
// requirementElements.push(
// <div
// key={textKey}
// className={className}
// onMouseEnter={termtip.bind(null, tooltipTextKey)}
// onMouseLeave={hide}
// >
// <a
// href={
// textKey.startsWith('empire') ?
// 'http://elite-dangerous.wikia.com/wiki/Empire/Ranks' :
// 'http://elite-dangerous.wikia.com/wiki/Federation/Ranks'
// }
// target="_blank"
// rel="noopener"
// >
// {translate(textKey)}
// </a>
// </div>
// );
// } else {
// requirementElements.push(
// <div
// key={textKey}
// className={className}
// onMouseEnter={termtip.bind(null, tooltipTextKey)}
// onMouseLeave={hide}
// >
// {translate(textKey)}
// </div>
// );
// }
// }
if (requirements) {
requirements.federationRank &&
renderRequirement(
'federation',
'federation rank ' + requirements.federationRank,
'federation rank required'
);
requirements.empireRank &&
renderRequirement(
'empire',
'empire rank ' + requirements.empireRank,
'empire rank required'
);
requirements.horizons &&
renderRequirement('horizons', 'horizons', 'horizons required');
requirements.horizonsEarlyAdoption &&
renderRequirement(
'horizons',
'horizons early adoption',
'horizons early adoption required'
);
}
// if (requirements) {
// requirements.federationRank &&
// renderRequirement(
// 'federation',
// 'federation rank ' + requirements.federationRank,
// 'federation rank required'
// );
// requirements.empireRank &&
// renderRequirement(
// 'empire',
// 'empire rank ' + requirements.empireRank,
// 'empire rank required'
// );
// requirements.horizons &&
// renderRequirement('horizons', 'horizons', 'horizons required');
// requirements.horizonsEarlyAdoption &&
// renderRequirement(
// 'horizons',
// 'horizons early adoption',
// 'horizons early adoption required'
// );
// }
return (
<div
@@ -872,8 +562,8 @@ export default class OutfittingPage extends Page {
style={{ fontSize: sizeRatio * 0.9 + 'em' }}
>
<div id="overview">
<h1>{ship.name}</h1>
<div id="requirements">{requirementElements}</div>
<h1>{ship.getShipType()}</h1>
{/* <div id="requirements">{requirementElements}</div> */}
<div id="build">
<input
value={newBuildName || ''}
@@ -933,7 +623,7 @@ export default class OutfittingPage extends Page {
<Download className="lg" />
</button>
<button
onClick={this._eddbShoppingList}
// onClick={this._eddbShoppingList}
onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_LIST')}
onMouseOut={hide}
>
@@ -954,7 +644,7 @@ export default class OutfittingPage extends Page {
<OrbisIcon className="lg" />
</button>
<button
onClick={this._genShoppingList}
// onClick={this._genShoppingList}
onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_MATS')}
onMouseOut={hide}
>
@@ -964,22 +654,13 @@ export default class OutfittingPage extends Page {
</div>
{/* Main tables */}
<ShipSummaryTable
{/* <ShipSummaryTable
ship={ship}
fuel={fuel}
cargo={cargo}
marker={shipSummaryMarker}
pips={{
sys: this.state.sys,
wep: this.state.wep,
eng: this.state.eng
}}
/>
/> */}
<StandardSlotSection
ship={ship}
fuel={fuel}
cargo={cargo}
code={standardSlotMarker}
// code={standardSlotMarker}
onChange={shipUpdated}
onCargoChange={this._cargoUpdated}
onFuelChange={this._fuelUpdated}
@@ -988,7 +669,7 @@ export default class OutfittingPage extends Page {
/>
<InternalSlotSection
ship={ship}
code={internalSlotMarker}
// code={internalSlotMarker}
onChange={shipUpdated}
onCargoChange={this._cargoUpdated}
onFuelChange={this._fuelUpdated}
@@ -997,7 +678,7 @@ export default class OutfittingPage extends Page {
/>
<HardpointSlotSection
ship={ship}
code={hardpointsSlotMarker}
// code={hardpointsSlotMarker}
onChange={shipUpdated}
onCargoChange={this._cargoUpdated}
onFuelChange={this._fuelUpdated}
@@ -1006,7 +687,7 @@ export default class OutfittingPage extends Page {
/>
<UtilitySlotSection
ship={ship}
code={hardpointsSlotMarker}
// code={hardpointsSlotMarker}
onChange={shipUpdated}
onCargoChange={this._cargoUpdated}
onFuelChange={this._fuelUpdated}
@@ -1015,7 +696,7 @@ export default class OutfittingPage extends Page {
/>
{/* Control of ship and opponent */}
<div className="group quarter">
{/* <div className="group quarter">
<div className="group half">
<h2 style={{ verticalAlign: 'middle', textAlign: 'left' }}>
{translate('ship control')}
@@ -1044,7 +725,6 @@ export default class OutfittingPage extends Page {
<div className="group quarter">
<Fuel
fuelCapacity={ship.fuelCapacity}
fuel={fuel}
onChange={this._fuelUpdated}
/>
</div>
@@ -1052,7 +732,6 @@ export default class OutfittingPage extends Page {
{ship.cargoCapacity > 0 ? (
<Cargo
cargoCapacity={ship.cargoCapacity}
cargo={cargo}
onChange={this._cargoUpdated}
/>
) : null}
@@ -1077,10 +756,10 @@ export default class OutfittingPage extends Page {
engagementRange={engagementRange}
onChange={this._engagementRangeUpdated}
/>
</div>
</div> */}
{/* Tabbed subpages */}
<OutfittingSubpages
{/* <OutfittingSubpages
ship={ship}
code={code}
buildName={buildName}
@@ -1097,7 +776,7 @@ export default class OutfittingPage extends Page {
opponentSys={opponentSys}
opponentEng={opponentEng}
opponentWep={opponentWep}
/>
/> */}
</div>
);
}

View File

@@ -1,21 +1,11 @@
import React from 'react';
import Page from './Page';
import { Ships } from 'coriolis-data/dist';
import cn from 'classnames';
import Ship from '../shipyard/Ship';
import { Factory } from 'ed-forge';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import { SizeMap } from '../shipyard/Constants';
import Link from '../components/Link';
/**
* Counts the hardpoints by class/size
* @param {Object} slot Hardpoint Slot model
*/
function countHp(slot) {
this.hp[slot.maxClass]++;
this.hpCount++;
}
/**
* Counts the internal slots and aggregated properties
* @param {Object} slot Internal Slots
@@ -53,50 +43,55 @@ function countInt(slot) {
/**
* Generate Ship summary and aggregated properties
* @param {String} shipId Ship Id
* @param {Object} shipData Ship Default Data
* @return {Object} Ship summary and aggregated properties
*/
function shipSummary(shipId, shipData) {
function shipSummary(shipId) {
// Build Ship
let ship = Factory.newShip(shipId);
let summary = {
id: shipId,
hpCount: 0,
intCount: 0,
beta: shipData.beta,
maxCargo: 0,
maxPassengers: 0,
hp: [0, 0, 0, 0, 0], // Utility, Small, Medium, Large, Huge
int: [0, 0, 0, 0, 0, 0, 0, 0], // Sizes 1 - 8
standard: shipData.slots.standard,
standard: ship.readMeta('coreSizes'),
agility:
shipData.properties.pitch +
shipData.properties.yaw +
shipData.properties.roll
ship.getBaseProperty('pitch') +
ship.getBaseProperty('yaw') +
ship.getBaseProperty('roll')
};
Object.assign(summary, shipData.properties);
let ship = new Ship(shipId, shipData.properties, shipData.slots);
// Build Ship
ship.buildWith(shipData.defaults); // Populate with stock/default components
ship.hardpoints.forEach(countHp.bind(summary)); // Count Hardpoints by class
ship.internal.forEach(countInt.bind(summary)); // Count Internal Compartments by class
summary.retailCost = ship.totalCost; // Record Stock/Default/retail cost
ship.optimizeMass({ pd: '1D' }); // Optimize Mass with 1D PD for maximum possible jump range
summary.maxJumpRange = ship.unladenRange; // Record Jump Range
// Count Hardpoints by class
ship.getHardpoints(undefined, true).forEach(hardpoint => {
summary.hp[hardpoint.getSize()]++;
summary.hpCount++;
});
// Count Internal Compartments by class
ship.getInternals(undefined, true).forEach(internal => {
summary.int[internal.getSize()]++;
summary.intCount++;
});
summary.retailCost = ship.readMeta('retailCost'); // Record Stock/Default/retail cost
// ship.optimizeMass({ pd: '1D' }); // Optimize Mass with 1D PD for maximum possible jump range
summary.maxJumpRange = -1; // ship.unladenRange; // Record Jump Range
// Best thrusters
let th;
if (ship.standard[1].maxClass === 3) {
th = 'tz';
} else if (ship.standard[1].maxClass === 2) {
th = 'u0';
} else {
th = ship.standard[1].maxClass + 'A';
}
// let th;
// if (ship.standard[1].maxClass === 3) {
// th = 'tz';
// } else if (ship.standard[1].maxClass === 2) {
// th = 'u0';
// } else {
// th = ship.standard[1].maxClass + 'A';
// }
ship.optimizeMass({ th, fsd: '2D', ft: '1C' }); // Optmize mass with Max Thrusters
summary.topSpeed = ship.topSpeed;
summary.topBoost = ship.topBoost;
summary.baseArmour = ship.armour;
// ship.optimizeMass({ th, fsd: '2D', ft: '1C' }); // Optmize mass with Max Thrusters
summary.topSpeed = -1; // ship.topSpeed;
summary.topBoost = -1; // ship.topBoost;
summary.baseArmour = -1; // ship.armour;
return summary;
}
@@ -117,8 +112,8 @@ export default class ShipyardPage extends Page {
if (!ShipyardPage.cachedShipSummaries) {
ShipyardPage.cachedShipSummaries = [];
for (let s in Ships) {
ShipyardPage.cachedShipSummaries.push(shipSummary(s, Ships[s]));
for (let s of Factory.getAllShipTypes()) {
ShipyardPage.cachedShipSummaries.push(shipSummary(s));
}
}
@@ -335,7 +330,7 @@ export default class ShipyardPage extends Page {
onClick={() => this._toggleCompare(s.id)}
>
<td className="le">
<Link href={'/outfit/' + s.id}>{s.name} {s.beta === true ? '(Beta)' : null}</Link>
<Link href={'/outfit/' + s.id}>{s.id} {s.beta === true ? '(Beta)' : null}</Link>
</td>
</tr>
);

View File

@@ -66,12 +66,10 @@ export function specialToolTip(translate, blueprint, grp, m, specialName) {
* Generate a tooltip with details of a blueprint's effects
* @param {Object} translate The translate object
* @param {Object} blueprint The blueprint at the required grade
* @param {Array} engineers The engineers supplying this blueprint
* @param {string} grp The group of the module
* @param {Object} m The module to compare with
* @returns {Object} The react components
*/
export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
export function blueprintTooltip(translate, blueprint, m) {
const effects = [];
if (!blueprint || !blueprint.features) {
return undefined;
@@ -105,7 +103,7 @@ export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td style={{ textAlign: 'left' }}>{translate(feature)}</td>
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
@@ -115,7 +113,7 @@ export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
// We do not have a module, no value
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td style={{ textAlign: 'left' }}>{translate(feature)}</td>
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
</tr>
@@ -144,7 +142,7 @@ export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td style={{ textAlign: 'left' }}>{translate(feature)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td>
@@ -175,7 +173,7 @@ export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td style={{ textAlign: 'left' }}>{translate(feature)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td>
@@ -200,17 +198,18 @@ export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
}
}
let engineersList;
if (engineers) {
engineersList = [];
for (const engineer of engineers) {
engineersList.push(
<tr key={engineer}>
<td style={{ textAlign: 'left' }}>{engineer}</td>
</tr>
);
}
}
let engineersList = [];
// TODO:
// if (engineers) {
// engineersList = [];
// for (const engineer of engineers) {
// engineersList.push(
// <tr key={engineer}>
// <td style={{ textAlign: 'left' }}>{engineer}</td>
// </tr>
// );
// }
// }
return (
<div>
@@ -285,7 +284,7 @@ export function isValueBeneficial(feature, value) {
* Is the change as shown beneficial?
* @param {string} feature The name of the feature
* @param {number} value The value of the feature as percentage change
* @returns True if the value is beneficial
* @returns {boolean} True if the value is beneficial
*/
export function isChangeValueBeneficial(feature, value) {
let changeHigherBetter = STATS_FORMATTING[feature].higherbetter;