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", "coriolis-data": "../coriolis-data",
"d3": "^5.7.0", "d3": "^5.7.0",
"detect-browser": "^3.0.1", "detect-browser": "^3.0.1",
"ed-forge": "github:EDCD/ed-forge",
"fbemitter": "^2.1.1", "fbemitter": "^2.1.1",
"lodash": "^4.17.11", "lodash": "^4.17.11",
"lz-string": "^1.4.4", "lz-string": "^1.4.4",

View File

@@ -5,16 +5,14 @@ import { register } from 'register-service-worker';
import { EventEmitter } from 'fbemitter'; import { EventEmitter } from 'fbemitter';
import { getLanguage } from './i18n/Language'; import { getLanguage } from './i18n/Language';
import Persist from './stores/Persist'; import Persist from './stores/Persist';
import { Ship } from 'ed-forge';
import Announcement from './components/Announcement'; import Announcement from './components/Announcement';
import Header from './components/Header'; import Header from './components/Header';
import Tooltip from './components/Tooltip'; import Tooltip from './components/Tooltip';
import ModalExport from './components/ModalExport';
import ModalHelp from './components/ModalHelp'; import ModalHelp from './components/ModalHelp';
import ModalImport from './components/ModalImport'; import ModalImport from './components/ModalImport';
import ModalPermalink from './components/ModalPermalink'; import ModalPermalink from './components/ModalPermalink';
import * as CompanionApiUtils from './utils/CompanionApiUtils';
import * as JournalUtils from './utils/JournalUtils';
import AboutPage from './pages/AboutPage'; import AboutPage from './pages/AboutPage';
import NotFoundPage from './pages/NotFoundPage'; import NotFoundPage from './pages/NotFoundPage';
import OutfittingPage from './pages/OutfittingPage'; import OutfittingPage from './pages/OutfittingPage';
@@ -22,7 +20,6 @@ import ComparisonPage from './pages/ComparisonPage';
import ShipyardPage from './pages/ShipyardPage'; import ShipyardPage from './pages/ShipyardPage';
import ErrorDetails from './pages/ErrorDetails'; import ErrorDetails from './pages/ErrorDetails';
const zlib = require('pako');
const request = require('superagent'); const request = require('superagent');
/** /**
@@ -61,7 +58,6 @@ export default class Coriolis extends React.Component {
this._onLanguageChange = this._onLanguageChange.bind(this); this._onLanguageChange = this._onLanguageChange.bind(this);
this._onSizeRatioChange = this._onSizeRatioChange.bind(this); this._onSizeRatioChange = this._onSizeRatioChange.bind(this);
this._keyDown = this._keyDown.bind(this); this._keyDown = this._keyDown.bind(this);
this._importBuild = this._importBuild.bind(this);
this.emitter = new EventEmitter(); this.emitter = new EventEmitter();
this.state = { this.state = {
@@ -93,19 +89,10 @@ export default class Coriolis extends React.Component {
_importBuild(r) { _importBuild(r) {
try { try {
// Need to decode and gunzip the data, then build the ship // Need to decode and gunzip the data, then build the ship
const data = zlib.inflate(new Buffer(r.params.data, 'base64'), { to: 'string' }); let ship = new Ship(r.params.data);
const json = JSON.parse(data); r.params.ship = ship.getShipType();
console.info('Ship import data: '); r.params.code = ship.compress();
console.info(json); this._setPage(OutfittingPage, r);
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)
} catch (err) { } catch (err) {
this._onError('Failed to import ship', r.path, 0, 0, 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 { export default class AvailableModulesMenu extends TranslatedComponent {
static propTypes = { static propTypes = {
modules: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
onSelect: PropTypes.func.isRequired, onSelect: PropTypes.func.isRequired,
diffDetails: PropTypes.func, diffDetails: PropTypes.func,
m: PropTypes.object, m: PropTypes.object,
@@ -155,7 +154,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
*/ */
_initState(props, context) { _initState(props, context) {
let translate = context.language.translate; let translate = context.language.translate;
let { m, warning, onSelect, modules, ship } = props; let { m, warning, onSelect, ship } = props;
let list, currentGroup; let list, currentGroup;
let buildGroup = this._buildGroup.bind( let buildGroup = this._buildGroup.bind(
@@ -170,6 +169,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
} }
); );
let fuzzy = []; let fuzzy = [];
let modules = m.getApplicableItems();
if (modules instanceof Array) { if (modules instanceof Array) {
list = buildGroup(modules[0].grp, modules); list = buildGroup(modules[0].grp, modules);
} else { } else {

View File

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

View File

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

View File

@@ -15,36 +15,39 @@ export default class InternalSlot extends Slot {
/** /**
* Generate the slot contents * Generate the slot contents
* @param {Object} m Mounted Module * @param {Object} m Mounted Module
* @param {Boolean} enabled Slot enabled
* @param {Function} translate Translate function * @param {Function} translate Translate function
* @param {Object} formats Localized Formats map * @param {Object} formats Localized Formats map
* @param {Object} u Localized Units Map * @param {Object} u Localized Units Map
* @return {React.Component} Slot contents * @return {React.Component} Slot contents
*/ */
_getSlotDetails(m, enabled, translate, formats, u) { _getSlotDetails(m, translate, formats, u) {
if (m) { if (m) {
let classRating = m.class + m.rating; let classRating = String(m.getSize()) + m.getRating();
let { drag, drop, ship } = this.props; let { drag, drop, ship } = this.props;
let { termtip, tooltip } = this.context; let { termtip, tooltip } = this.context;
let validMods = (Modifications.modules[m.grp] ? Modifications.modules[m.grp].modifications : []); let validMods = m.getApplicableBlueprints();
let showModuleResistances = Persist.showModuleResistances(); let showModuleResistances = Persist.showModuleResistances();
// Modifications tooltip shows blueprint and grade, if available // Modifications tooltip shows blueprint and grade, if available
let modTT = translate('modified'); // let modTT = translate('modified');
if (m && m.blueprint && m.blueprint.name) { // const blueprint = m.getBlueprint();
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade; // const experimental = m.getExperimental();
if (m.blueprint.special && m.blueprint.special.id >= 0) { // const grade = m.getGrade();
modTT += ', ' + translate(m.blueprint.special.name); // if (blueprint) {
} // modTT = translate(blueprint) + ' ' + translate('grade') + ' ' + grade;
modTT = ( // if (experimental) {
<div> // modTT += ', ' + translate(experimental);
<div>{modTT}</div> // }
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)} // modTT = (
</div> // <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'); const className = cn('details', enabled ? '' : 'disabled');
return <div className={className} draggable='true' onDragStart={drag} onDragEnd={drop}> 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 className={'r'}>{formats.round(mass)}{u.T}</div>
</div> </div>
<div className={'cb'}> <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.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.bins ? <div className={'l'}>{m.bins} <u>{translate('bins')}</u></div> : null }
{ m.bays ? <div className={'l'}>{translate('bays')}: {m.bays}</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.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.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.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>
</div>; </div>;
} else { } else {

View File

@@ -9,7 +9,6 @@ import { canMount } from '../utils/SlotFunctions';
* Internal slot section * Internal slot section
*/ */
export default class InternalSlotSection extends SlotSection { export default class InternalSlotSection extends SlotSection {
/** /**
* Constructor * Constructor
* @param {Object} props React Component properties * @param {Object} props React Component properties
@@ -221,29 +220,24 @@ export default class InternalSlotSection extends SlotSection {
let slots = []; let slots = [];
let { currentMenu, ship } = this.props; let { currentMenu, ship } = this.props;
let { originSlot, targetSlot } = this.state; let { originSlot, targetSlot } = this.state;
let { internal, fuelCapacity } = ship; let { fuelCapacity } = ship;
let availableModules = ship.getAvailableModules();
for (let i = 0, l = internal.length; i < l; i++) {
let s = internal[i];
for (const slot of ship.getInternals(undefined, true)) {
slots.push(<InternalSlot slots.push(<InternalSlot
key={i} key={slot.object.Slot}
maxClass={s.maxClass} maxClass={slot.getSize()}
availableModules={() => availableModules.getInts(ship, s.maxClass, s.eligible)} onOpen={this._openMenu.bind(this, slot)}
onOpen={this._openMenu.bind(this,s)} onChange={this.props.onChange}
onChange={this.props.onChange} onSelect={this._selectModule.bind(this, slot)}
onSelect={this._selectModule.bind(this, s)} selected={currentMenu == slot}
selected={currentMenu == s} slot={slot}
eligible={s.eligible} drag={this._drag.bind(this, slot)}
m={s.m} dragOver={this._dragOverSlot.bind(this, slot)}
drag={this._drag.bind(this, s)}
dragOver={this._dragOverSlot.bind(this, s)}
drop={this._drop} drop={this._drop}
dropClass={this._dropClass(s, originSlot, targetSlot)} dropClass={this._dropClass(slot, originSlot, targetSlot)}
fuel={fuelCapacity} fuel={fuelCapacity}
ship={ship} ship={ship}
enabled={s.enabled ? true : false} enabled={slot.isEnabled()}
/>); />);
} }
@@ -268,10 +262,9 @@ export default class InternalSlotSection extends SlotSection {
<li className='lc' tabIndex='0' onClick={this._fillWithEconomyClassCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pce'] = smRef}>{translate('pce')}</li> <li className='lc' tabIndex='0' onClick={this._fillWithEconomyClassCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pce'] = smRef}>{translate('pce')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithBusinessClassCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pci'] = smRef}>{translate('pci')}</li> <li className='lc' tabIndex='0' onClick={this._fillWithBusinessClassCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pci'] = smRef}>{translate('pci')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithFirstClassCabins} onKeyDown={ship.luxuryCabins ? '' : this._keyDown} ref={smRef => this.sectionRefArr['pcm'] = smRef}>{translate('pcm')}</li> <li className='lc' tabIndex='0' onClick={this._fillWithFirstClassCabins} onKeyDown={ship.luxuryCabins ? '' : this._keyDown} ref={smRef => this.sectionRefArr['pcm'] = smRef}>{translate('pcm')}</li>
{ ship.luxuryCabins ? <li className='lc' tabIndex='0' onClick={this._fillWithLuxuryCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pcq'] = smRef}>{translate('pcq')}</li> : ''} { ship.luxuryCabins ? <li className='lc' tabIndex='0' onClick={this._fillWithLuxuryCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pcq'] = smRef}>{translate('pcq')}</li> : ''}
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li> <li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul> </ul>
</div>; </div>;
} }
} }

View File

@@ -85,7 +85,7 @@ export default class ModificationsMenu extends TranslatedComponent {
}); });
const close = this._blueprintSelected.bind(this, blueprintName, grade); const close = this._blueprintSelected.bind(this, blueprintName, grade);
const key = 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; 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>); 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]) { 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; blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
haveBlueprint = true; 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); blueprintCv = getPercent(m);
} }

View File

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

View File

@@ -6,22 +6,19 @@ import AvailableModulesMenu from './AvailableModulesMenu';
import ModificationsMenu from './ModificationsMenu'; import ModificationsMenu from './ModificationsMenu';
import { diffDetails } from '../utils/SlotFunctions'; import { diffDetails } from '../utils/SlotFunctions';
import { wrapCtxMenu } from '../utils/UtilityFunctions'; import { wrapCtxMenu } from '../utils/UtilityFunctions';
import { Ship, Module } from 'ed-forge';
import { REG_MILITARY_SLOT } from 'ed-forge/lib/data/slots';
/** /**
* Abstract Slot * Abstract Slot
*/ */
export default class Slot extends TranslatedComponent { export default class Slot extends TranslatedComponent {
static propTypes = { static propTypes = {
availableModules: PropTypes.func.isRequired,
onSelect: PropTypes.func.isRequired, onSelect: PropTypes.func.isRequired,
onOpen: PropTypes.func.isRequired, onOpen: PropTypes.func.isRequired,
maxClass: PropTypes.number.isRequired,
selected: PropTypes.bool, selected: PropTypes.bool,
m: PropTypes.object, slot: PropTypes.instanceOf(Module),
enabled: PropTypes.bool.isRequired, ship: PropTypes.instanceOf(Ship),
ship: PropTypes.object.isRequired,
eligible: PropTypes.object,
warning: PropTypes.func, warning: PropTypes.func,
drag: PropTypes.func, drag: PropTypes.func,
drop: PropTypes.func, drop: PropTypes.func,
@@ -61,7 +58,7 @@ export default class Slot extends TranslatedComponent {
* @return {string} label * @return {string} label
*/ */
_getMaxClassLabel() { _getMaxClassLabel() {
return this.props.maxClass; return this.props.slot.getSize();
} }
/** /**
@@ -97,7 +94,7 @@ export default class Slot extends TranslatedComponent {
render() { render() {
let language = this.context.language; let language = this.context.language;
let translate = language.translate; 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; let slotDetails, modificationsMarker, menu;
if (!selected) { if (!selected) {
@@ -105,36 +102,37 @@ export default class Slot extends TranslatedComponent {
this._modificationsSelected = false; this._modificationsSelected = false;
} }
if (m) { if (!slot.isEmpty()) {
slotDetails = this._getSlotDetails(m, enabled, translate, language.formats, language.units); // Must be implemented by sub classes slotDetails = this._getSlotDetails(slot, translate, language.formats, language.units); // Must be implemented by sub classes
modificationsMarker = JSON.stringify(m);
} else { } else {
slotDetails = <div className={'empty'}>{translate(eligible ? 'emptyrestricted' : 'empty')}</div>; slotDetails = <div className={'empty'}>
modificationsMarker = ''; {translate(
slot.getSlot().match(REG_MILITARY_SLOT) ? 'emptyrestricted' : 'empty'
)}
</div>;
} }
if (selected) { if (selected) {
if (this._modificationsSelected) { // if (this._modificationsSelected) {
menu = <ModificationsMenu // menu = <ModificationsMenu
className={this._getClassNames()} // className={this._getClassNames()}
onChange={onChange} // onChange={onChange}
ship={ship} // ship={ship}
m={m} // m={m}
marker={modificationsMarker} // marker={modificationsMarker}
modButton = {this.modButton} // modButton = {this.modButton}
/>; // />;
} else { // } else {
menu = <AvailableModulesMenu menu = <AvailableModulesMenu
className={this._getClassNames()} className={this._getClassNames()}
modules={availableModules()} m={slot}
m={m} ship={ship}
ship={ship} onSelect={onSelect}
onSelect={onSelect} warning={warning}
warning={warning} diffDetails={diffDetails.bind(ship, this.context.language)}
diffDetails={diffDetails.bind(ship, this.context.language)} slotDiv = {this.slotDiv}
slotDiv = {this.slotDiv} />;
/>; // }
}
} }
// TODO: implement touch dragging // TODO: implement touch dragging
@@ -143,14 +141,13 @@ export default class Slot extends TranslatedComponent {
<div className={cn('slot', dropClass, { selected })} onClick={onOpen} onKeyDown={this._keyDown} onContextMenu={this._contextMenu} onDragOver={dragOver} tabIndex="0" ref={slotDiv => this.slotDiv = slotDiv}> <div className={cn('slot', dropClass, { selected })} onClick={onOpen} onKeyDown={this._keyDown} onContextMenu={this._contextMenu} onDragOver={dragOver} tabIndex="0" ref={slotDiv => this.slotDiv = slotDiv}>
<div className='details-container'> <div className='details-container'>
<div className='sz'>{this._getMaxClassLabel(translate)}</div> <div className='sz'>{this._getMaxClassLabel(translate)}</div>
{slotDetails} {slotDetails}
</div> </div>
{menu} {menu}
</div> </div>
); );
} }
/** /**
* Toggle the modifications flag when selecting the modifications icon * 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 { canMount } from '../utils/SlotFunctions';
import { Equalizer } from '../components/SvgIcons'; import { Equalizer } from '../components/SvgIcons';
import cn from 'classnames'; import cn from 'classnames';
import { Ship } from 'ed-forge';
const browser = require('detect-browser'); const browser = require('detect-browser');
/** /**
* Abstract Slot Section * Abstract Slot Section
*/ */
export default class SlotSection extends TranslatedComponent { export default class SlotSection extends TranslatedComponent {
static propTypes = { static propTypes = {
ship: PropTypes.object.isRequired, ship: PropTypes.instanceOf(Ship),
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
onCargoChange: PropTypes.func.isRequired, // code: PropTypes.string.isRequired,
onFuelChange: PropTypes.func.isRequired,
code: PropTypes.string.isRequired,
togglePwr: PropTypes.func, togglePwr: PropTypes.func,
sectionMenuRefs: PropTypes.object sectionMenuRefs: PropTypes.object
}; };
@@ -106,6 +104,7 @@ export default class SlotSection extends TranslatedComponent {
this.sectionRefArr['ssHeadRef'].focus(); this.sectionRefArr['ssHeadRef'].focus();
} }
} }
/** /**
* Open a menu * Open a menu
* @param {string} menu Menu name * @param {string} menu Menu name
@@ -126,7 +125,7 @@ export default class SlotSection extends TranslatedComponent {
* @param {Object} m Selected module * @param {Object} m Selected module
*/ */
_selectModule(slot, m) { _selectModule(slot, m) {
this.props.ship.use(slot, m, false); slot.setItem(m);
this.props.onChange(); this.props.onChange();
this._close(); this._close();
} }

View File

@@ -6,24 +6,22 @@ import TranslatedComponent from './TranslatedComponent';
import { diffDetails } from '../utils/SlotFunctions'; import { diffDetails } from '../utils/SlotFunctions';
import AvailableModulesMenu from './AvailableModulesMenu'; import AvailableModulesMenu from './AvailableModulesMenu';
import ModificationsMenu from './ModificationsMenu'; import ModificationsMenu from './ModificationsMenu';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import { ListModifications, Modified } from './SvgIcons'; import { ListModifications, Modified } from './SvgIcons';
import { Modifications } from 'coriolis-data/dist'; import { Modifications } from 'coriolis-data/dist';
import { stopCtxPropagation } from '../utils/UtilityFunctions'; import { stopCtxPropagation } from '../utils/UtilityFunctions';
import { blueprintTooltip } from '../utils/BlueprintFunctions'; import { blueprintTooltip } from '../utils/BlueprintFunctions';
import { Ship, Module } from 'ed-forge';
/** /**
* Standard Slot * Standard Slot
*/ */
export default class StandardSlot extends TranslatedComponent { export default class StandardSlot extends TranslatedComponent {
static propTypes = { static propTypes = {
slot: PropTypes.object, slot: PropTypes.instanceOf(Module),
modules: PropTypes.array.isRequired,
onSelect: PropTypes.func.isRequired, onSelect: PropTypes.func.isRequired,
onOpen: PropTypes.func.isRequired, onOpen: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
ship: PropTypes.object.isRequired, ship: PropTypes.instanceOf(Ship),
selected: PropTypes.bool, selected: PropTypes.bool,
warning: PropTypes.func, warning: PropTypes.func,
}; };
@@ -59,33 +57,28 @@ export default class StandardSlot extends TranslatedComponent {
render() { render() {
let { termtip, tooltip } = this.context; let { termtip, tooltip } = this.context;
let { translate, formats, units } = this.context.language; let { translate, formats, units } = this.context.language;
let { modules, slot, selected, warning, onSelect, onChange, ship } = this.props; let { slot, selected, warning, onSelect, onChange, ship } = this.props;
let m = slot.m; let classRating = String(slot.getClass()) + (slot.getRating() || '');
let classRating = m.class + m.rating;
let menu; let menu;
let validMods = m == null || !Modifications.modules[m.grp] ? [] : (Modifications.modules[m.grp].modifications || []); let validMods = slot.getApplicableBlueprints();
if (m && m.name && m.name === 'Guardian Hybrid Power Plant') {
validMods = [];
}
if (m && m.name && m.name === 'Guardian Power Distributor') {
validMods = [];
}
let showModuleResistances = Persist.showModuleResistances(); 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 // Modifications tooltip shows blueprint and grade, if available
let modTT = translate('modified'); let modTT = translate('modified');
if (m && m.blueprint && m.blueprint.name) { const appliedBlueprint = slot.getBlueprint();
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade; const appliedExperimental = slot.getExperimental();
if (m.blueprint.special && m.blueprint.special.id >= 0) { if (appliedBlueprint) {
modTT += ', ' + translate(m.blueprint.special.name); modTT = translate(appliedBlueprint) + ' ' + translate('grade') + ' ' + slot.getBlueprintGrade();
if (appliedExperimental) {
modTT += ', ' + translate(appliedExperimental);
} }
modTT = ( modTT = (
<div> <div>
<div>{modTT}</div> <div>{modTT}</div>
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)} {blueprintTooltip(translate, slot)}
</div> </div>
); );
} }
if (!selected) { if (!selected) {
@@ -93,23 +86,19 @@ export default class StandardSlot extends TranslatedComponent {
this._modificationsSelected = false; this._modificationsSelected = false;
} }
const modificationsMarker = JSON.stringify(m);
if (selected) { if (selected) {
if (this._modificationsSelected) { if (this._modificationsSelected) {
menu = <ModificationsMenu menu = <ModificationsMenu
className='standard' className='standard'
onChange={onChange} onChange={onChange}
ship={ship} ship={ship}
m={m} m={slot}
marker={modificationsMarker}
modButton = {this.modButton} modButton = {this.modButton}
/>; />;
} else { } else {
menu = <AvailableModulesMenu menu = <AvailableModulesMenu
className='standard' className='standard'
modules={modules} m={slot}
m={m}
ship={ship} ship={ship}
onSelect={onSelect} onSelect={onSelect}
warning={warning} warning={warning}
@@ -121,30 +110,30 @@ export default class StandardSlot extends TranslatedComponent {
return ( 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('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={cn('details-container', { warning: warning && warning(slot), disabled: slot.isEnabled() })}>
<div className={'sz'}>{m.grp == 'bh' ? m.name.charAt(0) : slot.maxClass}</div> <div className={'sz'}>{slot.getSize()}</div>
<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 className={'r'}>{formats.round(mass)}{units.T}</div>
<div/> <div/>
<div className={'cb'}> <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.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.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 } { m.getOptMul() ? <div className='l'>{translate('optimal multiplier')}: {formats.rPct(m.getOptMul())}</div> : null }
{ m.getRange() ? <div className='l'>{translate('range', m.grp)}: {formats.f2(m.getRange())}{units.km}</div> : null } { m.getRange() ? <div className='l'>{translate('range', m.grp)}: {formats.f2(m.getRange())}{units.km}</div> : null }
{ m.time ? <div className='l'>{translate('time')}: {formats.time(m.time)}</div> : null } { m.time ? <div className='l'>{translate('time')}: {formats.time(m.time)}</div> : null }
{ m.getThermalEfficiency() ? <div className='l'>{translate('efficiency')}: {formats.f2(m.getThermalEfficiency())}</div> : null } { m.getThermalEfficiency() ? <div className='l'>{translate('efficiency')}: {formats.f2(m.getThermalEfficiency())}</div> : null }
{ m.getPowerGeneration() > 0 ? <div className='l'>{translate('pgen')}: {formats.f1(m.getPowerGeneration())}{units.MW}</div> : null } { m.getPowerGeneration() > 0 ? <div className='l'>{translate('pgen')}: {formats.f1(m.getPowerGeneration())}{units.MW}</div> : null }
{ m.getMaxFuelPerJump() ? <div className='l'>{translate('max')} {translate('fuel')}: {formats.f1(m.getMaxFuelPerJump())}{units.T}</div> : null } { m.getMaxFuelPerJump() ? <div className='l'>{translate('max')} {translate('fuel')}: {formats.f1(m.getMaxFuelPerJump())}{units.T}</div> : null }
{ m.getWeaponsCapacity() ? <div className='l'>{translate('WEP')}: {formats.f1(m.getWeaponsCapacity())}{units.MJ} / {formats.f1(m.getWeaponsRechargeRate())}{units.MW}</div> : null } { m.getWeaponsCapacity() ? <div className='l'>{translate('WEP')}: {formats.f1(m.getWeaponsCapacity())}{units.MJ} / {formats.f1(m.getWeaponsRechargeRate())}{units.MW}</div> : null }
{ m.getSystemsCapacity() ? <div className='l'>{translate('SYS')}: {formats.f1(m.getSystemsCapacity())}{units.MJ} / {formats.f1(m.getSystemsRechargeRate())}{units.MW}</div> : null } { m.getSystemsCapacity() ? <div className='l'>{translate('SYS')}: {formats.f1(m.getSystemsCapacity())}{units.MJ} / {formats.f1(m.getSystemsRechargeRate())}{units.MW}</div> : null }
{ m.getEnginesCapacity() ? <div className='l'>{translate('ENG')}: {formats.f1(m.getEnginesCapacity())}{units.MJ} / {formats.f1(m.getEnginesRechargeRate())}{units.MW}</div> : null } { m.getEnginesCapacity() ? <div className='l'>{translate('ENG')}: {formats.f1(m.getEnginesCapacity())}{units.MJ} / {formats.f1(m.getEnginesRechargeRate())}{units.MW}</div> : null }
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null } { showModuleResistances && m.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.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 } { 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 } { 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 } {/* { 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> </div>
</div> </div>

View File

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

View File

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

View File

@@ -6,7 +6,7 @@ import Page from './Page';
import Router from '../Router'; import Router from '../Router';
import Persist from '../stores/Persist'; import Persist from '../stores/Persist';
import * as Utils from '../utils/UtilityFunctions'; import * as Utils from '../utils/UtilityFunctions';
import Ship from '../shipyard/Ship'; import { Factory, Ship } from 'ed-forge';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { toDetailedBuild } from '../shipyard/Serializer'; import { toDetailedBuild } from '../shipyard/Serializer';
import { outfitURL } from '../utils/UrlGenerators'; import { outfitURL } from '../utils/UrlGenerators';
@@ -64,10 +64,6 @@ export default class OutfittingPage extends Page {
this.state = this._initState(props, context); this.state = this._initState(props, context);
this._keyDown = this._keyDown.bind(this); this._keyDown = this._keyDown.bind(this);
this._exportBuild = this._exportBuild.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._opponentUpdated = this._opponentUpdated.bind(this);
this._engagementRangeUpdated = this._engagementRangeUpdated.bind(this); this._engagementRangeUpdated = this._engagementRangeUpdated.bind(this);
this._sectionMenuRefs = {}; this._sectionMenuRefs = {};
@@ -82,40 +78,14 @@ export default class OutfittingPage extends Page {
_initState(props, context) { _initState(props, context) {
let params = context.route.params; let params = context.route.params;
let shipId = params.ship; let shipId = params.ship;
let code = params.code;
let buildName = params.bn; let buildName = params.bn;
let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults
let savedCode = Persist.getBuild(shipId, buildName); let savedCode = Persist.getBuild(shipId, buildName);
if (!data) { let code = params.code || savedCode;
return { error: { message: 'Ship not found: ' + shipId } }; let ship = code ? new Ship(code) : Factory.newShip(shipId); // Create a new Ship instance
} code = ship.compress();
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
}
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 { return {
error: null, error: null,
title: this._getTitle(buildName), title: this._getTitle(buildName),
@@ -126,21 +96,6 @@ export default class OutfittingPage extends Page {
ship, ship,
code, code,
savedCode, 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 * Update the control part of the route
*/ */
_updateRouteOnControlChange() { _updateRoute() {
const { ship, shipId, buildName } = this.state; const { ship, shipId, buildName } = this.state;
const code = this._fullCode(ship); const code = ship.compress();
this._updateRoute(shipId, buildName, code); this._updateRoute(shipId, buildName, code);
this.setState({ 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 * Triggered when engagement range has been updated
* @param {number} engagementRange the engagement range, in m * @param {number} engagementRange the engagement range, in m
@@ -387,36 +185,10 @@ export default class OutfittingPage extends Page {
opponentEng, opponentEng,
opponentWep 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 * Save the current build
*/ */
@@ -424,7 +196,7 @@ export default class OutfittingPage extends Page {
const { ship, buildName, newBuildName, shipId } = this.state; 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 // 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); Persist.saveBuild(shipId, newBuildName, code);
this._updateRoute(shipId, newBuildName, code); this._updateRoute(shipId, newBuildName, code);
@@ -497,39 +269,9 @@ export default class OutfittingPage extends Page {
// Rebuild ship // Rebuild ship
ship.buildWith(Ships[shipId].defaults); ship.buildWith(Ships[shipId].defaults);
// Reset controls // Reset controls
const code = ship.toString(); const code = ship.compress();
const {
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
engagementRange
} = this._obtainControlFromCode(ship, code);
// Update state, and refresh the ship // Update state, and refresh the ship
this.setState( this._updateRoute(shipId, buildName, code);
{
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
engagementRange
},
() => 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 * Called when the code for the ship has been updated, to synchronise the rest of the data
*/ */
_codeUpdated() { _codeUpdated() {
const { code, ship, shipId, buildName } = this.state; const { code, shipId, buildName } = this.state;
// Rebuild ship from the code this.setState({
this.state.ship.buildFrom(code); ship: new Ship(code),
}, () => this._updateRoute(shipId, buildName, 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)
);
} }
/** /**
* Called when the ship has been updated, to set the code and then update accordingly * Called when the ship has been updated, to set the code and then update accordingly
*/ */
_shipUpdated() { _shipUpdated() {
let { ship, shipId, buildName, cargo, fuel } = this.state; let { ship, shipId, buildName } = this.state;
if (cargo > ship.cargoCapacity) { const code = ship.compress();
cargo = ship.cargoCapacity;
}
if (fuel > ship.fuelCapacity) {
fuel = ship.fuelCapacity;
}
const code = this._fullCode(ship, fuel, cargo);
// Only update the state if this really has been updated // Only update the state if this really has been updated
if ( if (this.state.code != code) {
this.state.code != code || this.setState({ code }, () =>
this.state.cargo != cargo ||
this.state.fuel != fuel
) {
this.setState({ code, cargo, fuel }, () =>
this._updateRoute(shipId, buildName, code) this._updateRoute(shipId, buildName, code)
); );
} }
@@ -747,29 +446,20 @@ export default class OutfittingPage extends Page {
*/ */
renderPage() { renderPage() {
let state = this.state, let state = this.state,
{ language, termtip, tooltip, sizeRatio, onWindowResize } = this.context, { language, termtip, tooltip, sizeRatio } = this.context,
{ translate, units, formats } = language, { translate } = language,
{ {
ship, ship,
code, code,
savedCode, savedCode,
buildName, buildName,
newBuildName, newBuildName,
sys, // opponent,
eng, // opponentBuild,
wep, // opponentSys,
mcSys, // opponentEng,
mcEng, // opponentWep,
mcWep, // engagementRange
boost,
fuel,
cargo,
opponent,
opponentBuild,
opponentSys,
opponentEng,
opponentWep,
engagementRange
} = state, } = state,
hide = tooltip.bind(null, null), hide = tooltip.bind(null, null),
menu = this.props.currentMenu, menu = this.props.currentMenu,
@@ -782,88 +472,88 @@ export default class OutfittingPage extends Page {
code = ship.name + (code || ''); 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 // 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 _sStr = ship.getStandardString();
const _iStr = ship.getInternalString(); // const _iStr = ship.getInternalString();
const _hStr = ship.getHardpointsString(); // const _hStr = ship.getHardpointsString();
const _pStr = `${ship.getPowerEnabledString()}${ship.getPowerPrioritiesString()}`; // const _pStr = `${ship.getPowerEnabledString()}${ship.getPowerPrioritiesString()}`;
const _mStr = ship.getModificationsString(); // const _mStr = ship.getModificationsString();
const standardSlotMarker = `${ship.name}${_sStr}${_pStr}${_mStr}${ // const standardSlotMarker = `${ship.name}${_sStr}${_pStr}${_mStr}${
ship.ladenMass // ship.ladenMass
}${cargo}${fuel}`; // }${cargo}${fuel}`;
const internalSlotMarker = `${ship.name}${_iStr}${_pStr}${_mStr}`; // const internalSlotMarker = `${ship.name}${_iStr}${_pStr}${_mStr}`;
const hardpointsSlotMarker = `${ship.name}${_hStr}${_pStr}${_mStr}`; // const hardpointsSlotMarker = `${ship.name}${_hStr}${_pStr}${_mStr}`;
const boostMarker = `${ship.canBoost(cargo, fuel)}`; // const boostMarker = `${ship.canBoost(cargo, fuel)}`;
const shipSummaryMarker = `${ // const shipSummaryMarker = `${
ship.name // ship.name
}${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`; // }${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`;
const requirements = Ships[ship.id].requirements; // const requirements = Ships[ship.id].requirements;
let requirementElements = []; // let requirementElements = [];
/** // /**
* Render the requirements for a ship / etc // * Render the requirements for a ship / etc
* @param {string} className Class names // * @param {string} className Class names
* @param {string} textKey The key for translating // * @param {string} textKey The key for translating
* @param {String} tooltipTextKey Tooltip key // * @param {String} tooltipTextKey Tooltip key
*/ // */
function renderRequirement(className, textKey, tooltipTextKey) { // function renderRequirement(className, textKey, tooltipTextKey) {
if (textKey.startsWith('empire') || textKey.startsWith('federation')) { // if (textKey.startsWith('empire') || textKey.startsWith('federation')) {
requirementElements.push( // requirementElements.push(
<div // <div
key={textKey} // key={textKey}
className={className} // className={className}
onMouseEnter={termtip.bind(null, tooltipTextKey)} // onMouseEnter={termtip.bind(null, tooltipTextKey)}
onMouseLeave={hide} // onMouseLeave={hide}
> // >
<a // <a
href={ // href={
textKey.startsWith('empire') ? // textKey.startsWith('empire') ?
'http://elite-dangerous.wikia.com/wiki/Empire/Ranks' : // 'http://elite-dangerous.wikia.com/wiki/Empire/Ranks' :
'http://elite-dangerous.wikia.com/wiki/Federation/Ranks' // 'http://elite-dangerous.wikia.com/wiki/Federation/Ranks'
} // }
target="_blank" // target="_blank"
rel="noopener" // rel="noopener"
> // >
{translate(textKey)} // {translate(textKey)}
</a> // </a>
</div> // </div>
); // );
} else { // } else {
requirementElements.push( // requirementElements.push(
<div // <div
key={textKey} // key={textKey}
className={className} // className={className}
onMouseEnter={termtip.bind(null, tooltipTextKey)} // onMouseEnter={termtip.bind(null, tooltipTextKey)}
onMouseLeave={hide} // onMouseLeave={hide}
> // >
{translate(textKey)} // {translate(textKey)}
</div> // </div>
); // );
} // }
} // }
if (requirements) { // if (requirements) {
requirements.federationRank && // requirements.federationRank &&
renderRequirement( // renderRequirement(
'federation', // 'federation',
'federation rank ' + requirements.federationRank, // 'federation rank ' + requirements.federationRank,
'federation rank required' // 'federation rank required'
); // );
requirements.empireRank && // requirements.empireRank &&
renderRequirement( // renderRequirement(
'empire', // 'empire',
'empire rank ' + requirements.empireRank, // 'empire rank ' + requirements.empireRank,
'empire rank required' // 'empire rank required'
); // );
requirements.horizons && // requirements.horizons &&
renderRequirement('horizons', 'horizons', 'horizons required'); // renderRequirement('horizons', 'horizons', 'horizons required');
requirements.horizonsEarlyAdoption && // requirements.horizonsEarlyAdoption &&
renderRequirement( // renderRequirement(
'horizons', // 'horizons',
'horizons early adoption', // 'horizons early adoption',
'horizons early adoption required' // 'horizons early adoption required'
); // );
} // }
return ( return (
<div <div
@@ -872,8 +562,8 @@ export default class OutfittingPage extends Page {
style={{ fontSize: sizeRatio * 0.9 + 'em' }} style={{ fontSize: sizeRatio * 0.9 + 'em' }}
> >
<div id="overview"> <div id="overview">
<h1>{ship.name}</h1> <h1>{ship.getShipType()}</h1>
<div id="requirements">{requirementElements}</div> {/* <div id="requirements">{requirementElements}</div> */}
<div id="build"> <div id="build">
<input <input
value={newBuildName || ''} value={newBuildName || ''}
@@ -933,7 +623,7 @@ export default class OutfittingPage extends Page {
<Download className="lg" /> <Download className="lg" />
</button> </button>
<button <button
onClick={this._eddbShoppingList} // onClick={this._eddbShoppingList}
onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_LIST')} onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_LIST')}
onMouseOut={hide} onMouseOut={hide}
> >
@@ -954,7 +644,7 @@ export default class OutfittingPage extends Page {
<OrbisIcon className="lg" /> <OrbisIcon className="lg" />
</button> </button>
<button <button
onClick={this._genShoppingList} // onClick={this._genShoppingList}
onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_MATS')} onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_MATS')}
onMouseOut={hide} onMouseOut={hide}
> >
@@ -964,22 +654,13 @@ export default class OutfittingPage extends Page {
</div> </div>
{/* Main tables */} {/* Main tables */}
<ShipSummaryTable {/* <ShipSummaryTable
ship={ship} ship={ship}
fuel={fuel}
cargo={cargo}
marker={shipSummaryMarker} marker={shipSummaryMarker}
pips={{ /> */}
sys: this.state.sys,
wep: this.state.wep,
eng: this.state.eng
}}
/>
<StandardSlotSection <StandardSlotSection
ship={ship} ship={ship}
fuel={fuel} // code={standardSlotMarker}
cargo={cargo}
code={standardSlotMarker}
onChange={shipUpdated} onChange={shipUpdated}
onCargoChange={this._cargoUpdated} onCargoChange={this._cargoUpdated}
onFuelChange={this._fuelUpdated} onFuelChange={this._fuelUpdated}
@@ -988,7 +669,7 @@ export default class OutfittingPage extends Page {
/> />
<InternalSlotSection <InternalSlotSection
ship={ship} ship={ship}
code={internalSlotMarker} // code={internalSlotMarker}
onChange={shipUpdated} onChange={shipUpdated}
onCargoChange={this._cargoUpdated} onCargoChange={this._cargoUpdated}
onFuelChange={this._fuelUpdated} onFuelChange={this._fuelUpdated}
@@ -997,7 +678,7 @@ export default class OutfittingPage extends Page {
/> />
<HardpointSlotSection <HardpointSlotSection
ship={ship} ship={ship}
code={hardpointsSlotMarker} // code={hardpointsSlotMarker}
onChange={shipUpdated} onChange={shipUpdated}
onCargoChange={this._cargoUpdated} onCargoChange={this._cargoUpdated}
onFuelChange={this._fuelUpdated} onFuelChange={this._fuelUpdated}
@@ -1006,7 +687,7 @@ export default class OutfittingPage extends Page {
/> />
<UtilitySlotSection <UtilitySlotSection
ship={ship} ship={ship}
code={hardpointsSlotMarker} // code={hardpointsSlotMarker}
onChange={shipUpdated} onChange={shipUpdated}
onCargoChange={this._cargoUpdated} onCargoChange={this._cargoUpdated}
onFuelChange={this._fuelUpdated} onFuelChange={this._fuelUpdated}
@@ -1015,7 +696,7 @@ export default class OutfittingPage extends Page {
/> />
{/* Control of ship and opponent */} {/* Control of ship and opponent */}
<div className="group quarter"> {/* <div className="group quarter">
<div className="group half"> <div className="group half">
<h2 style={{ verticalAlign: 'middle', textAlign: 'left' }}> <h2 style={{ verticalAlign: 'middle', textAlign: 'left' }}>
{translate('ship control')} {translate('ship control')}
@@ -1044,7 +725,6 @@ export default class OutfittingPage extends Page {
<div className="group quarter"> <div className="group quarter">
<Fuel <Fuel
fuelCapacity={ship.fuelCapacity} fuelCapacity={ship.fuelCapacity}
fuel={fuel}
onChange={this._fuelUpdated} onChange={this._fuelUpdated}
/> />
</div> </div>
@@ -1052,7 +732,6 @@ export default class OutfittingPage extends Page {
{ship.cargoCapacity > 0 ? ( {ship.cargoCapacity > 0 ? (
<Cargo <Cargo
cargoCapacity={ship.cargoCapacity} cargoCapacity={ship.cargoCapacity}
cargo={cargo}
onChange={this._cargoUpdated} onChange={this._cargoUpdated}
/> />
) : null} ) : null}
@@ -1077,10 +756,10 @@ export default class OutfittingPage extends Page {
engagementRange={engagementRange} engagementRange={engagementRange}
onChange={this._engagementRangeUpdated} onChange={this._engagementRangeUpdated}
/> />
</div> </div> */}
{/* Tabbed subpages */} {/* Tabbed subpages */}
<OutfittingSubpages {/* <OutfittingSubpages
ship={ship} ship={ship}
code={code} code={code}
buildName={buildName} buildName={buildName}
@@ -1097,7 +776,7 @@ export default class OutfittingPage extends Page {
opponentSys={opponentSys} opponentSys={opponentSys}
opponentEng={opponentEng} opponentEng={opponentEng}
opponentWep={opponentWep} opponentWep={opponentWep}
/> /> */}
</div> </div>
); );
} }

View File

@@ -1,21 +1,11 @@
import React from 'react'; import React from 'react';
import Page from './Page'; import Page from './Page';
import { Ships } from 'coriolis-data/dist';
import cn from 'classnames'; import cn from 'classnames';
import Ship from '../shipyard/Ship'; import { Factory } from 'ed-forge';
import * as ModuleUtils from '../shipyard/ModuleUtils'; import * as ModuleUtils from '../shipyard/ModuleUtils';
import { SizeMap } from '../shipyard/Constants'; import { SizeMap } from '../shipyard/Constants';
import Link from '../components/Link'; 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 * Counts the internal slots and aggregated properties
* @param {Object} slot Internal Slots * @param {Object} slot Internal Slots
@@ -53,50 +43,55 @@ function countInt(slot) {
/** /**
* Generate Ship summary and aggregated properties * Generate Ship summary and aggregated properties
* @param {String} shipId Ship Id * @param {String} shipId Ship Id
* @param {Object} shipData Ship Default Data
* @return {Object} Ship summary and aggregated properties * @return {Object} Ship summary and aggregated properties
*/ */
function shipSummary(shipId, shipData) { function shipSummary(shipId) {
// Build Ship
let ship = Factory.newShip(shipId);
let summary = { let summary = {
id: shipId, id: shipId,
hpCount: 0, hpCount: 0,
intCount: 0, intCount: 0,
beta: shipData.beta,
maxCargo: 0, maxCargo: 0,
maxPassengers: 0, maxPassengers: 0,
hp: [0, 0, 0, 0, 0], // Utility, Small, Medium, Large, Huge hp: [0, 0, 0, 0, 0], // Utility, Small, Medium, Large, Huge
int: [0, 0, 0, 0, 0, 0, 0, 0], // Sizes 1 - 8 int: [0, 0, 0, 0, 0, 0, 0, 0], // Sizes 1 - 8
standard: shipData.slots.standard, standard: ship.readMeta('coreSizes'),
agility: agility:
shipData.properties.pitch + ship.getBaseProperty('pitch') +
shipData.properties.yaw + ship.getBaseProperty('yaw') +
shipData.properties.roll ship.getBaseProperty('roll')
}; };
Object.assign(summary, shipData.properties);
let ship = new Ship(shipId, shipData.properties, shipData.slots);
// Build Ship // Count Hardpoints by class
ship.buildWith(shipData.defaults); // Populate with stock/default components ship.getHardpoints(undefined, true).forEach(hardpoint => {
ship.hardpoints.forEach(countHp.bind(summary)); // Count Hardpoints by class summary.hp[hardpoint.getSize()]++;
ship.internal.forEach(countInt.bind(summary)); // Count Internal Compartments by class summary.hpCount++;
summary.retailCost = ship.totalCost; // Record Stock/Default/retail cost });
ship.optimizeMass({ pd: '1D' }); // Optimize Mass with 1D PD for maximum possible jump range // Count Internal Compartments by class
summary.maxJumpRange = ship.unladenRange; // Record Jump Range 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 // Best thrusters
let th; // let th;
if (ship.standard[1].maxClass === 3) { // if (ship.standard[1].maxClass === 3) {
th = 'tz'; // th = 'tz';
} else if (ship.standard[1].maxClass === 2) { // } else if (ship.standard[1].maxClass === 2) {
th = 'u0'; // th = 'u0';
} else { // } else {
th = ship.standard[1].maxClass + 'A'; // th = ship.standard[1].maxClass + 'A';
} // }
ship.optimizeMass({ th, fsd: '2D', ft: '1C' }); // Optmize mass with Max Thrusters // ship.optimizeMass({ th, fsd: '2D', ft: '1C' }); // Optmize mass with Max Thrusters
summary.topSpeed = ship.topSpeed; summary.topSpeed = -1; // ship.topSpeed;
summary.topBoost = ship.topBoost; summary.topBoost = -1; // ship.topBoost;
summary.baseArmour = ship.armour; summary.baseArmour = -1; // ship.armour;
return summary; return summary;
} }
@@ -117,8 +112,8 @@ export default class ShipyardPage extends Page {
if (!ShipyardPage.cachedShipSummaries) { if (!ShipyardPage.cachedShipSummaries) {
ShipyardPage.cachedShipSummaries = []; ShipyardPage.cachedShipSummaries = [];
for (let s in Ships) { for (let s of Factory.getAllShipTypes()) {
ShipyardPage.cachedShipSummaries.push(shipSummary(s, Ships[s])); ShipyardPage.cachedShipSummaries.push(shipSummary(s));
} }
} }
@@ -335,7 +330,7 @@ export default class ShipyardPage extends Page {
onClick={() => this._toggleCompare(s.id)} onClick={() => this._toggleCompare(s.id)}
> >
<td className="le"> <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> </td>
</tr> </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 * Generate a tooltip with details of a blueprint's effects
* @param {Object} translate The translate object * @param {Object} translate The translate object
* @param {Object} blueprint The blueprint at the required grade * @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 * @param {Object} m The module to compare with
* @returns {Object} The react components * @returns {Object} The react components
*/ */
export function blueprintTooltip(translate, blueprint, engineers, grp, m) { export function blueprintTooltip(translate, blueprint, m) {
const effects = []; const effects = [];
if (!blueprint || !blueprint.features) { if (!blueprint || !blueprint.features) {
return undefined; return undefined;
@@ -105,7 +103,7 @@ export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
const currentIsBeneficial = isValueBeneficial(feature, current); const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push( effects.push(
<tr key={feature}> <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={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={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{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 // We do not have a module, no value
effects.push( effects.push(
<tr key={feature}> <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={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td> <td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
</tr> </tr>
@@ -144,7 +142,7 @@ export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
const currentIsBeneficial = isValueBeneficial(feature, current); const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push( effects.push(
<tr key={feature}> <tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td> <td style={{ textAlign: 'left' }}>{translate(feature)}</td>
<td>&nbsp;</td> <td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td> <td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td> <td>&nbsp;</td>
@@ -175,7 +173,7 @@ export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
const currentIsBeneficial = isValueBeneficial(feature, current); const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push( effects.push(
<tr key={feature}> <tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td> <td style={{ textAlign: 'left' }}>{translate(feature)}</td>
<td>&nbsp;</td> <td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td> <td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td> <td>&nbsp;</td>
@@ -200,17 +198,18 @@ export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
} }
} }
let engineersList; let engineersList = [];
if (engineers) { // TODO:
engineersList = []; // if (engineers) {
for (const engineer of engineers) { // engineersList = [];
engineersList.push( // for (const engineer of engineers) {
<tr key={engineer}> // engineersList.push(
<td style={{ textAlign: 'left' }}>{engineer}</td> // <tr key={engineer}>
</tr> // <td style={{ textAlign: 'left' }}>{engineer}</td>
); // </tr>
} // );
} // }
// }
return ( return (
<div> <div>
@@ -285,7 +284,7 @@ export function isValueBeneficial(feature, value) {
* Is the change as shown beneficial? * Is the change as shown beneficial?
* @param {string} feature The name of the feature * @param {string} feature The name of the feature
* @param {number} value The value of the feature as percentage change * @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) { export function isChangeValueBeneficial(feature, value) {
let changeHigherBetter = STATS_FORMATTING[feature].higherbetter; let changeHigherBetter = STATS_FORMATTING[feature].higherbetter;