mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-11 16:53:02 +00:00
Rework cost section
This commit is contained in:
@@ -1,13 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import Persist from '../stores/Persist';
|
import Persist from '../stores/Persist';
|
||||||
import Ship from '../shipyard/Ship';
|
import Ship from '../shipyard/Ship';
|
||||||
import { Insurance } from '../shipyard/Constants';
|
import { Insurance } from '../shipyard/Constants';
|
||||||
import { slotName, slotComparator } from '../utils/SlotFunctions';
|
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { ShoppingIcon } from '../components/SvgIcons';
|
import { ShoppingIcon } from '../components/SvgIcons';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
import { assign, differenceBy, sortBy, reverse } from 'lodash';
|
||||||
|
import { COST, FUEL_CAPACITY } from 'ed-forge/lib/ship-stats';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cost Section
|
* Cost Section
|
||||||
@@ -16,7 +17,7 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
code: PropTypes.string.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
buildName: PropTypes.string
|
buildName: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,71 +26,53 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this._costsTab = this._costsTab.bind(this);
|
autoBind(this);
|
||||||
this._sortCost = this._sortCost.bind(this);
|
|
||||||
this._sortAmmo = this._sortAmmo.bind(this);
|
|
||||||
this._sortRetrofit = this._sortRetrofit.bind(this);
|
|
||||||
this._buildRetrofitShip = this._buildRetrofitShip.bind(this);
|
|
||||||
this._onBaseRetrofitChange = this._onBaseRetrofitChange.bind(this);
|
|
||||||
this._defaultRetrofitName = this._defaultRetrofitName.bind(this);
|
|
||||||
this._eddbShoppingList = this._eddbShoppingList.bind(this);
|
|
||||||
|
|
||||||
let data = Ships[props.ship.id]; // Retrieve the basic ship properties, slots and defaults
|
|
||||||
let retrofitName = this._defaultRetrofitName(props.ship.id, props.buildName);
|
|
||||||
let retrofitShip = this._buildRetrofitShip(props.ship.id, retrofitName);
|
|
||||||
let shipDiscount = Persist.getShipDiscount();
|
|
||||||
let moduleDiscount = Persist.getModuleDiscount();
|
|
||||||
|
|
||||||
this.props.ship.applyDiscounts(shipDiscount, moduleDiscount);
|
|
||||||
retrofitShip.applyDiscounts(shipDiscount, moduleDiscount);
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
retrofitShip,
|
retrofitName: this._defaultRetrofitName(props.ship, props.buildName),
|
||||||
retrofitName,
|
shipDiscount: Persist.getShipDiscount(),
|
||||||
shipDiscount,
|
moduleDiscount: Persist.getModuleDiscount(),
|
||||||
moduleDiscount,
|
|
||||||
insurance: Insurance[Persist.getInsurance()],
|
insurance: Insurance[Persist.getInsurance()],
|
||||||
tab: Persist.getCostTab(),
|
tab: Persist.getCostTab(),
|
||||||
buildOptions: Persist.getBuildsNamesFor(props.ship.id),
|
buildOptions: Persist.getBuildsNamesFor(props.ship.getShipType()),
|
||||||
ammoPredicate: 'cr',
|
predicate: 'cr',
|
||||||
ammoDesc: true,
|
desc: true,
|
||||||
costPredicate: 'cr',
|
excluded: {},
|
||||||
costDesc: true,
|
|
||||||
retroPredicate: 'cr',
|
|
||||||
retroDesc: true
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a ship instance to base/reference retrofit changes from
|
* Create a ship instance to base/reference retrofit changes from
|
||||||
* @param {string} shipId Ship Id
|
* @param {string} ship Ship
|
||||||
* @param {string} name Build name
|
* @param {string} name Build name
|
||||||
* @param {Ship} retrofitShip Existing retrofit ship
|
* @param {Ship} retrofitShip Existing retrofit ship
|
||||||
* @return {Ship} Retrofit ship
|
* @return {Ship} Retrofit ship
|
||||||
*/
|
*/
|
||||||
_buildRetrofitShip(shipId, name, retrofitShip) {
|
_buildRetrofitShip(ship, name, retrofitShip) {
|
||||||
let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults
|
// TODO: once ships have been persisted, this can be fixed
|
||||||
|
return ship;
|
||||||
|
// let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults
|
||||||
|
|
||||||
if (!retrofitShip) { // Don't create a new instance unless needed
|
// if (!retrofitShip) { // Don't create a new instance unless needed
|
||||||
retrofitShip = new Ship(shipId, data.properties, data.slots); // Create a new Ship for retrofit comparison
|
// retrofitShip = new Ship(shipId, data.properties, data.slots); // Create a new Ship for retrofit comparison
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (Persist.hasBuild(shipId, name)) {
|
// if (Persist.hasBuild(shipId, name)) {
|
||||||
retrofitShip.buildFrom(Persist.getBuild(shipId, name)); // Populate modules from existing build
|
// retrofitShip.buildFrom(Persist.getBuild(shipId, name)); // Populate modules from existing build
|
||||||
} else {
|
// } else {
|
||||||
retrofitShip.buildWith(data.defaults); // Populate with default components
|
// retrofitShip.buildWith(data.defaults); // Populate with default components
|
||||||
}
|
// }
|
||||||
return retrofitShip;
|
// return retrofitShip;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the default retrofit build name if it exists
|
* Get the default retrofit build name if it exists
|
||||||
* @param {string} shipId Ship Id
|
* @param {string} ship Ship
|
||||||
* @param {string} name Build name
|
* @param {string} name Build name
|
||||||
* @return {string} Build name or null
|
* @return {string} Build name or null
|
||||||
*/
|
*/
|
||||||
_defaultRetrofitName(shipId, name) {
|
_defaultRetrofitName(ship, name) {
|
||||||
return Persist.hasBuild(shipId, name) ? name : null;
|
return Persist.hasBuild(ship.getShipType(), name) ? name : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -107,9 +90,6 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
_onDiscountChanged() {
|
_onDiscountChanged() {
|
||||||
let shipDiscount = Persist.getShipDiscount();
|
let shipDiscount = Persist.getShipDiscount();
|
||||||
let moduleDiscount = Persist.getModuleDiscount();
|
let moduleDiscount = Persist.getModuleDiscount();
|
||||||
this.props.ship.applyDiscounts(shipDiscount, moduleDiscount);
|
|
||||||
this.state.retrofitShip.applyDiscounts(shipDiscount, moduleDiscount);
|
|
||||||
this._updateRetrofit(this.props.ship, this.state.retrofitShip);
|
|
||||||
this.setState({ shipDiscount, moduleDiscount });
|
this.setState({ shipDiscount, moduleDiscount });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,156 +106,33 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
* @param {SyntheticEvent} event Build name to base the retrofit ship on
|
* @param {SyntheticEvent} event Build name to base the retrofit ship on
|
||||||
*/
|
*/
|
||||||
_onBaseRetrofitChange(event) {
|
_onBaseRetrofitChange(event) {
|
||||||
let retrofitName = event.target.value;
|
this.setState({ retrofitName: event.target.value });
|
||||||
let ship = this.props.ship;
|
|
||||||
|
|
||||||
if (retrofitName) {
|
|
||||||
this.state.retrofitShip.buildFrom(Persist.getBuild(ship.id, retrofitName));
|
|
||||||
} else {
|
|
||||||
this.state.retrofitShip.buildWith(Ships[ship.id].defaults); // Retrofit ship becomes stock build
|
|
||||||
}
|
|
||||||
this._updateRetrofit(ship, this.state.retrofitShip);
|
|
||||||
this.setState({ retrofitName });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On builds changed check to see if the retrofit ship needs
|
* Toggle item cost inclusion
|
||||||
* to be updated
|
* @param {String} key Key of the row to toggle
|
||||||
*/
|
*/
|
||||||
_onBuildsChanged() {
|
_toggleExcluded(key) {
|
||||||
let update = false;
|
let { excluded } = this.state;
|
||||||
let ship = this.props.ship;
|
excluded = assign({}, excluded);
|
||||||
let { retrofitName, retrofitShip } = this.state;
|
const slotExcluded = excluded[key];
|
||||||
|
excluded[key] = (slotExcluded === undefined ? true : !slotExcluded);
|
||||||
if(!Persist.hasBuild(ship.id, retrofitName)) {
|
this.setState({ excluded });
|
||||||
retrofitShip.buildWith(Ships[ship.id].defaults); // Retrofit ship becomes stock build
|
|
||||||
this.setState({ retrofitName: null });
|
|
||||||
update = true;
|
|
||||||
} else if (Persist.getBuild(ship.id, retrofitName) != retrofitShip.toString()) {
|
|
||||||
retrofitShip.buildFrom(Persist.getBuild(ship.id, retrofitName)); // Repopulate modules from saved build
|
|
||||||
update = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (update) { // Update retrofit comparison
|
|
||||||
this._updateRetrofit(ship, retrofitShip);
|
|
||||||
}
|
|
||||||
// Update list of retrofit base build options
|
|
||||||
this.setState({ buildOptions: Persist.getBuildsNamesFor(ship.id) });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle item cost inclusion in overall total
|
* Set list sort predicate
|
||||||
* @param {Object} item Cost item
|
* @param {string} newPredicate sort predicate
|
||||||
*/
|
*/
|
||||||
_toggleCost(item) {
|
_sortBy(newPredicate) {
|
||||||
this.props.ship.setCostIncluded(item, !item.incCost);
|
let { predicate, desc } = this.state;
|
||||||
this.forceUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (newPredicate == predicate) {
|
||||||
* Toggle item cost inclusion in retrofit total
|
desc = !desc;
|
||||||
* @param {Object} item Cost item
|
|
||||||
*/
|
|
||||||
_toggleRetrofitCost(item) {
|
|
||||||
let retrofitTotal = this.state.retrofitTotal;
|
|
||||||
item.retroItem.incCost = !item.retroItem.incCost;
|
|
||||||
retrofitTotal += item.netCost * (item.retroItem.incCost ? 1 : -1);
|
|
||||||
this.setState({ retrofitTotal });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set cost list sort predicate
|
|
||||||
* @param {string} predicate sort predicate
|
|
||||||
*/
|
|
||||||
_sortCostBy(predicate) {
|
|
||||||
let { costPredicate, costDesc } = this.state;
|
|
||||||
|
|
||||||
if (costPredicate == predicate) {
|
|
||||||
costDesc = !costDesc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ costPredicate: predicate, costDesc });
|
this.setState({ predicate: newPredicate, desc });
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort cost list
|
|
||||||
* @param {Ship} ship Ship instance
|
|
||||||
* @param {string} predicate Sort predicate
|
|
||||||
* @param {Boolean} desc Sort descending
|
|
||||||
*/
|
|
||||||
_sortCost(ship, predicate, desc) {
|
|
||||||
let costList = ship.costList;
|
|
||||||
let translate = this.context.language.translate;
|
|
||||||
|
|
||||||
if (predicate == 'm') {
|
|
||||||
costList.sort(slotComparator(translate, null, desc));
|
|
||||||
} else {
|
|
||||||
costList.sort(slotComparator(translate, (a, b) => (a.m.cost || 0) - (b.m.cost || 0), desc));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set ammo list sort predicate
|
|
||||||
* @param {string} predicate sort predicate
|
|
||||||
*/
|
|
||||||
_sortAmmoBy(predicate) {
|
|
||||||
let { ammoPredicate, ammoDesc } = this.state;
|
|
||||||
|
|
||||||
if (ammoPredicate == predicate) {
|
|
||||||
ammoDesc = !ammoDesc;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ ammoPredicate: predicate, ammoDesc });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort ammo cost list
|
|
||||||
* @param {Array} ammoCosts Ammo cost list
|
|
||||||
* @param {string} predicate Sort predicate
|
|
||||||
* @param {Boolean} desc Sort descending
|
|
||||||
*/
|
|
||||||
_sortAmmo(ammoCosts, predicate, desc) {
|
|
||||||
let translate = this.context.language.translate;
|
|
||||||
|
|
||||||
if (predicate == 'm') {
|
|
||||||
ammoCosts.sort(slotComparator(translate, null, desc));
|
|
||||||
} else {
|
|
||||||
ammoCosts.sort(slotComparator(translate, (a, b) => a[predicate] - b[predicate], desc));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set retrofit list sort predicate
|
|
||||||
* @param {string} predicate sort predicate
|
|
||||||
*/
|
|
||||||
_sortRetrofitBy(predicate) {
|
|
||||||
let { retroPredicate, retroDesc } = this.state;
|
|
||||||
|
|
||||||
if (retroPredicate == predicate) {
|
|
||||||
retroDesc = !retroDesc;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ retroPredicate: predicate, retroDesc });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort retrofit cost list
|
|
||||||
* @param {Array} retrofitCosts Retrofit cost list
|
|
||||||
* @param {string} predicate Sort predicate
|
|
||||||
* @param {Boolean} desc Sort descending
|
|
||||||
*/
|
|
||||||
_sortRetrofit(retrofitCosts, predicate, desc) {
|
|
||||||
let translate = this.context.language.translate;
|
|
||||||
|
|
||||||
if (predicate == 'cr') {
|
|
||||||
retrofitCosts.sort((a, b) => a.netCost - b.netCost);
|
|
||||||
} else {
|
|
||||||
retrofitCosts.sort((a , b) => (a[predicate] ? translate(a[predicate]).toLowerCase() : '').localeCompare(b[predicate] ? translate(b[predicate]).toLowerCase() : ''));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!desc) {
|
|
||||||
retrofitCosts.reverse();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -284,43 +141,54 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
_costsTab() {
|
_costsTab() {
|
||||||
let { ship } = this.props;
|
let { ship } = this.props;
|
||||||
let { shipDiscount, moduleDiscount, insurance } = this.state;
|
let {
|
||||||
|
excluded, shipDiscount, moduleDiscount, insurance, desc, predicate
|
||||||
|
} = this.state;
|
||||||
let { translate, formats, units } = this.context.language;
|
let { translate, formats, units } = this.context.language;
|
||||||
let rows = [];
|
let rows = [];
|
||||||
|
|
||||||
for (let i = 0, l = ship.costList.length; i < l; i++) {
|
let modules = sortBy(
|
||||||
let item = ship.costList[i];
|
ship.getModules(),
|
||||||
if (item.m && item.m.cost) {
|
(predicate === 'm' ? (m) => m.getItem() : (m) => m.readMeta('cost'))
|
||||||
let toggle = this._toggleCost.bind(this, item);
|
);
|
||||||
rows.push(<tr key={i} className={cn('highlight', { disabled: !item.incCost })}>
|
if (desc) {
|
||||||
<td className='ptr' style={{ width: '1em' }} onClick={toggle}>{item.m.class + item.m.rating}</td>
|
reverse(modules);
|
||||||
<td className='le ptr shorten cap' onClick={toggle}>{slotName(translate, item)}</td>
|
}
|
||||||
<td className='ri ptr' onClick={toggle}>{formats.int(item.discountedCost)}{units.CR}</td>
|
for (let module of modules) {
|
||||||
|
const cost = module.readMeta('cost');
|
||||||
|
const slot = module.getSlot();
|
||||||
|
if (cost) {
|
||||||
|
let toggle = this._toggleExcluded.bind(this, slot);
|
||||||
|
rows.push(<tr key={slot} className={cn('highlight', { disabled: excluded[slot] })}>
|
||||||
|
<td className='ptr' style={{ width: '1em' }} onClick={toggle}>{module.getClassRating()}</td>
|
||||||
|
<td className='le ptr shorten cap' onClick={toggle}>{translate(module.readMeta('type'))}</td>
|
||||||
|
<td className='ri ptr' onClick={toggle}>{formats.int(cost * (1 - moduleDiscount))}{units.CR}</td>
|
||||||
</tr>);
|
</tr>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const totalCost = ship.get(COST);
|
||||||
return <div>
|
return <div>
|
||||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className='main'>
|
<tr className='main'>
|
||||||
<th colSpan='2' className='sortable le' onClick={this._sortCostBy.bind(this,'m')}>
|
<th colSpan='2' className='sortable le' onClick={() => this._sortBy('m')}>
|
||||||
{translate('module')}
|
{translate('module')}
|
||||||
{shipDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} -${formats.pct(shipDiscount)}]`}</u> : null}
|
{shipDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} -${formats.pct(shipDiscount)}]`}</u> : null}
|
||||||
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
|
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
|
||||||
</th>
|
</th>
|
||||||
<th className='sortable le' onClick={this._sortCostBy.bind(this, 'cr')} >{translate('credits')}</th>
|
<th className='sortable le' onClick={() => this._sortBy('cr')} >{translate('credits')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{rows}
|
{rows}
|
||||||
<tr className='ri'>
|
<tr className='ri'>
|
||||||
<td colSpan='2' className='lbl' >{translate('total')}</td>
|
<td colSpan='2' className='lbl' >{translate('total')}</td>
|
||||||
<td className='val'>{formats.int(ship.totalCost)}{units.CR}</td>
|
<td className='val'>{formats.int(totalCost)}{units.CR}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className='ri'>
|
<tr className='ri'>
|
||||||
<td colSpan='2' className='lbl'>{translate('insurance')}</td>
|
<td colSpan='2' className='lbl'>{translate('insurance')}</td>
|
||||||
<td className='val'>{formats.int(ship.totalCost * insurance)}{units.CR}</td>
|
<td className='val'>{formats.int(totalCost * insurance)}{units.CR}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -331,14 +199,62 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
* Open up a window for EDDB with a shopping list of our retrofit components
|
* Open up a window for EDDB with a shopping list of our retrofit components
|
||||||
*/
|
*/
|
||||||
_eddbShoppingList() {
|
_eddbShoppingList() {
|
||||||
const { retrofitCosts } = this.state;
|
const {} = this.state;
|
||||||
const { ship } = this.props;
|
const { ship } = this.props;
|
||||||
|
|
||||||
// Provide unique list of non-PP module EDDB IDs to buy
|
// Provide unique list of non-PP module EDDB IDs to buy
|
||||||
const modIds = retrofitCosts.filter(item => item.retroItem.incCost && item.buyId && !item.buyPp).map(item => item.buyId).filter((v, i, a) => a.indexOf(v) === i);
|
// const modIds = retrofitCosts.filter(item => item.retroItem.incCost && item.buyId && !item.buyPp).map(item => item.buyId).filter((v, i, a) => a.indexOf(v) === i);
|
||||||
|
|
||||||
// Open up the relevant URL
|
// Open up the relevant URL
|
||||||
window.open('https://eddb.io/station?m=' + modIds.join(','));
|
// TODO:
|
||||||
|
// window.open('https://eddb.io/station?m=' + modIds.join(','));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
_retrofitInfo() {
|
||||||
|
const { ship } = this.props;
|
||||||
|
const { desc, moduleDiscount, predicate, retrofitName } = this.state;
|
||||||
|
const retrofitShip = this._buildRetrofitShip(ship, retrofitName);
|
||||||
|
|
||||||
|
const currentModules = ship.getModules();
|
||||||
|
const oldModules = retrofitShip.getModules();
|
||||||
|
const buyModules = differenceBy(currentModules, oldModules, (m) => m.getItem());
|
||||||
|
const sellModules = differenceBy(oldModules, currentModules, (m) => m.getItem());
|
||||||
|
|
||||||
|
let modules = [];
|
||||||
|
for (let m of buyModules) {
|
||||||
|
const key = `buy_${m.getSlot()}`;
|
||||||
|
modules.push({
|
||||||
|
key,
|
||||||
|
cost: m.readMeta('cost') * (1 - moduleDiscount),
|
||||||
|
buyRating: m.getClassRating(),
|
||||||
|
buyItem: m.readMeta('type'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (let m of sellModules) {
|
||||||
|
const key = `sell_${m.getSlot()}`;
|
||||||
|
modules.push({
|
||||||
|
key,
|
||||||
|
cost: -1 * m.readMeta('cost') * moduleDiscount,
|
||||||
|
sellRating: m.getClassRating(),
|
||||||
|
sellItem: m.readMeta('type'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let _sortF = undefined;
|
||||||
|
switch (predicate) {
|
||||||
|
case 'cr': _sortF = (o) => o.cost;
|
||||||
|
case 'm':
|
||||||
|
default: _sortF = (o) => o.buyItem || o.sellItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
modules = sortBy(modules, _sortF);
|
||||||
|
if (desc) {
|
||||||
|
reverse(modules);
|
||||||
|
}
|
||||||
|
return modules;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -346,28 +262,36 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
* @return {React.Component} Tab contents
|
* @return {React.Component} Tab contents
|
||||||
*/
|
*/
|
||||||
_retrofitTab() {
|
_retrofitTab() {
|
||||||
let { retrofitTotal, retrofitCosts, moduleDiscount, retrofitName } = this.state;
|
let { excluded, moduleDiscount, retrofitName } = this.state;
|
||||||
const { termtip, tooltip } = this.context;
|
const { termtip, tooltip } = this.context;
|
||||||
let { translate, formats, units } = this.context.language;
|
let { translate, formats, units } = this.context.language;
|
||||||
let int = formats.int;
|
let int = formats.int;
|
||||||
let rows = [], options = [<option key='stock' value=''>{translate('Stock')}</option>];
|
let options = [<option key='stock' value=''>{translate('Stock')}</option>];
|
||||||
|
|
||||||
for (let opt of this.state.buildOptions) {
|
for (let opt of this.state.buildOptions) {
|
||||||
options.push(<option key={opt} value={opt}>{opt}</option>);
|
options.push(<option key={opt} value={opt}>{opt}</option>);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (retrofitCosts.length) {
|
const retrofitInfo = this._retrofitInfo();
|
||||||
for (let i = 0, l = retrofitCosts.length; i < l; i++) {
|
const retrofitTotal = 0;
|
||||||
let item = retrofitCosts[i];
|
let rows = [];
|
||||||
rows.push(<tr key={i} className={cn('highlight', { disabled: !item.retroItem.incCost })} onClick={this._toggleRetrofitCost.bind(this, item)}>
|
for (let i of retrofitInfo) {
|
||||||
<td className='ptr' style={{ width: '1em' }}>{item.sellClassRating}</td>
|
const disabled = excluded[i.key];
|
||||||
<td className='le ptr shorten cap'>{translate(item.sellName)}</td>
|
rows.push(
|
||||||
<td className='ptr' style={{ width: '1em' }}>{item.buyClassRating}</td>
|
<tr key={i.key} className={cn('highlight', { disabled })}
|
||||||
<td className='le ptr shorten cap'>{translate(item.buyName)}</td>
|
onClick={() => this._toggleExcluded(i.key)}>
|
||||||
<td colSpan='2' className={cn('ri ptr', item.retroItem.incCost ? item.netCost > 0 ? 'warning' : 'secondary-disabled' : 'disabled')}>{int(item.netCost)}{units.CR}</td>
|
<td className='ptr' style={{ width: '1em' }}>{i.buyRating}</td>
|
||||||
</tr>);
|
<td className='le ptr shorten cap'>{translate(i.buyItem)}</td>
|
||||||
}
|
<td className='ptr' style={{ width: '1em' }}>{i.sellRating}</td>
|
||||||
} else {
|
<td className='le ptr shorten cap'>{translate(i.sellItem)}</td>
|
||||||
|
<td colSpan='2' className={cn('ri ptr', disabled ? 'disabled' : (i.cost < 0 ? 'secondary-disabled' : 'warning'))}>
|
||||||
|
{int(i.cost)}{units.CR}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
retrofitTotal += disabled ? 0 : i.cost;
|
||||||
|
}
|
||||||
|
if (!rows.length) {
|
||||||
rows = <tr><td colSpan='7' style={{ padding: '3em 0' }}>{translate('PHRASE_NO_RETROCH')}</td></tr>;
|
rows = <tr><td colSpan='7' style={{ padding: '3em 0' }}>{translate('PHRASE_NO_RETROCH')}</td></tr>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,9 +300,9 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
<table style={{ width: '100%' }}>
|
<table style={{ width: '100%' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className='main'>
|
<tr className='main'>
|
||||||
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'sellName')}>{translate('sell')}</th>
|
<th colSpan='2' className='sortable le' onClick={() => this._sortBy('m')}>{translate('sell')}</th>
|
||||||
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'buyName')}>{translate('buy')}</th>
|
<th colSpan='2' className='sortable le' onClick={() => this._sortBy('m')}>{translate('buy')}</th>
|
||||||
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'cr')}>
|
<th colSpan='2' className='sortable le' onClick={() => this._sortBy('cr')}>
|
||||||
{translate('net cost')}
|
{translate('net cost')}
|
||||||
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
|
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
|
||||||
</th>
|
</th>
|
||||||
@@ -408,63 +332,50 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update retrofit costs
|
*
|
||||||
* @param {Ship} ship Ship instance
|
* @param {*} modules
|
||||||
* @param {Ship} retrofitShip Retrofit Ship instance
|
|
||||||
*/
|
*/
|
||||||
_updateRetrofit(ship, retrofitShip) {
|
_ammoInfo() {
|
||||||
let retrofitCosts = [];
|
const { ship } = this.props;
|
||||||
let retrofitTotal = 0, i, l, item;
|
const { desc, predicate } = this.state;
|
||||||
|
|
||||||
if (ship.bulkheads.m.index != retrofitShip.bulkheads.m.index) {
|
let info = [{
|
||||||
item = {
|
key: 'fuel',
|
||||||
buyClassRating: ship.bulkheads.m.class + ship.bulkheads.m.rating,
|
item: 'Fuel',
|
||||||
buyId: ship.bulkheads.m.eddbID,
|
qty: ship.get(FUEL_CAPACITY),
|
||||||
buyPp: ship.bulkheads.m.pp,
|
unitCost: 50,
|
||||||
buyName: ship.bulkheads.m.name,
|
cost: 50 * ship.get(FUEL_CAPACITY),
|
||||||
sellClassRating: retrofitShip.bulkheads.m.class + retrofitShip.bulkheads.m.rating,
|
}];
|
||||||
sellName: retrofitShip.bulkheads.m.name,
|
for (let m of ship.getModules()) {
|
||||||
netCost: ship.bulkheads.discountedCost - retrofitShip.bulkheads.discountedCost,
|
const rebuilds = m.get('bays') * m.get('rebuildsperbay');
|
||||||
retroItem: retrofitShip.bulkheads
|
const ammo = (m.get('ammomaximum') + m.get('ammoclipsize')) || rebuilds;
|
||||||
};
|
if (ammo) {
|
||||||
retrofitCosts.push(item);
|
const unitCost = m.readMeta('ammocost');
|
||||||
if (retrofitShip.bulkheads.incCost) {
|
info.push({
|
||||||
retrofitTotal += item.netCost;
|
key: `restock_${m.getSlot()}`,
|
||||||
|
rating: m.getClassRating(),
|
||||||
|
item: m.readMeta('type'),
|
||||||
|
qty: ammo,
|
||||||
|
unitCost, cost: unitCost * ammo,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let g in { standard: 1, internal: 1, hardpoints: 1 }) {
|
let _sortF = undefined;
|
||||||
let retroSlotGroup = retrofitShip[g];
|
switch (predicate) {
|
||||||
let slotGroup = ship[g];
|
case 'cr': _sortF = (o) => o.cost; break;
|
||||||
for (i = 0, l = slotGroup.length; i < l; i++) {
|
case 'qty': _sortF = (o) => o.qty; break;
|
||||||
const modId = slotGroup[i].m ? slotGroup[i].m.eddbID : null;
|
case 'cost': _sortF = (o) => o.unitCost; break;
|
||||||
const retroModId = retroSlotGroup[i].m ? retroSlotGroup[i].m.eddbID : null;
|
case 'm':
|
||||||
if (modId != retroModId) {
|
default: _sortF = (o) => o.item;
|
||||||
item = { netCost: 0, retroItem: retroSlotGroup[i] };
|
}
|
||||||
if (slotGroup[i].m) {
|
info = sortBy(info, _sortF);
|
||||||
item.buyId = slotGroup[i].m.eddbID,
|
if (desc) {
|
||||||
item.buyPp = slotGroup[i].m.pp,
|
reverse(info);
|
||||||
item.buyName = slotGroup[i].m.name || slotGroup[i].m.grp;
|
|
||||||
item.buyClassRating = slotGroup[i].m.class + slotGroup[i].m.rating;
|
|
||||||
item.netCost = slotGroup[i].discountedCost;
|
|
||||||
}
|
|
||||||
if (retroSlotGroup[i].m) {
|
|
||||||
item.sellName = retroSlotGroup[i].m.name || retroSlotGroup[i].m.grp;
|
|
||||||
item.sellClassRating = retroSlotGroup[i].m.class + retroSlotGroup[i].m.rating;
|
|
||||||
item.netCost -= retroSlotGroup[i].discountedCost;
|
|
||||||
}
|
|
||||||
retrofitCosts.push(item);
|
|
||||||
if (retroSlotGroup[i].incCost) {
|
|
||||||
retrofitTotal += item.netCost;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ retrofitCosts, retrofitTotal });
|
return info;
|
||||||
this._sortRetrofit(retrofitCosts, this.state.retroPredicate, this.state.retroDesc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -472,20 +383,24 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
* @return {React.Component} Tab contents
|
* @return {React.Component} Tab contents
|
||||||
*/
|
*/
|
||||||
_ammoTab() {
|
_ammoTab() {
|
||||||
let { ammoTotal, ammoCosts } = this.state;
|
const { excluded } = this.state;
|
||||||
let { translate, formats, units } = this.context.language;
|
const { translate, formats, units } = this.context.language;
|
||||||
let int = formats.int;
|
const int = formats.int;
|
||||||
let rows = [];
|
const rows = [];
|
||||||
|
|
||||||
for (let i = 0, l = ammoCosts.length; i < l; i++) {
|
const ammoInfo = this._ammoInfo();
|
||||||
let item = ammoCosts[i];
|
let total = 0;
|
||||||
rows.push(<tr key={i} className='highlight'>
|
for (let i of ammoInfo) {
|
||||||
<td style={{ width: '1em' }}>{item.m.class + item.m.rating}</td>
|
const disabled = excluded[i.key];
|
||||||
<td className='le shorten cap'>{slotName(translate, item)}</td>
|
rows.push(<tr key={i.key} onClick={() => this._toggleExcluded(i.key)}
|
||||||
<td className='ri'>{int(item.max)}</td>
|
className={cn('highlight', { disabled })}>
|
||||||
<td className='ri'>{int(item.cost)}{units.CR}</td>
|
<td style={{ width: '1em' }}>{i.rating}</td>
|
||||||
<td className='ri'>{int(item.total)}{units.CR}</td>
|
<td className='le shorten cap'>{translate(i.item)}</td>
|
||||||
|
<td className='ri'>{int(i.qty)}</td>
|
||||||
|
<td className='ri'>{int(i.unitCost)}{units.CR}</td>
|
||||||
|
<td className='ri'>{int(i.cost)}{units.CR}</td>
|
||||||
</tr>);
|
</tr>);
|
||||||
|
total += disabled ? 0 : i.cost;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
@@ -493,17 +408,17 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
<table style={{ width: '100%' }}>
|
<table style={{ width: '100%' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className='main'>
|
<tr className='main'>
|
||||||
<th colSpan='2' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'm')} >{translate('module')}</th>
|
<th colSpan='2' className='sortable le' onClick={() => this._sortBy('m')}>{translate('module')}</th>
|
||||||
<th colSpan='1' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'max')} >{translate('qty')}</th>
|
<th colSpan='1' className='sortable le' onClick={() => this._sortBy('qty')}>{translate('qty')}</th>
|
||||||
<th colSpan='1' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'cost')} >{translate('unit cost')}</th>
|
<th colSpan='1' className='sortable le' onClick={() => this._sortBy('cost')}>{translate('unit cost')}</th>
|
||||||
<th className='sortable le' onClick={this._sortAmmoBy.bind(this, 'total')}>{translate('subtotal')}</th>
|
<th className='sortable le' onClick={() => this._sortBy('cr')}>{translate('subtotal')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{rows}
|
{rows}
|
||||||
<tr className='ri'>
|
<tr className='ri'>
|
||||||
<td colSpan='4' className='lbl' >{translate('total')}</td>
|
<td colSpan='4' className='lbl' >{translate('total')}</td>
|
||||||
<td className='val'>{int(ammoTotal)}{units.CR}</td>
|
<td className='val'>{int(total)}{units.CR}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -511,103 +426,6 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Recalculate all ammo costs
|
|
||||||
* @param {Ship} ship Ship instance
|
|
||||||
*/
|
|
||||||
_updateAmmoCosts(ship) {
|
|
||||||
let ammoCosts = [], ammoTotal = 0, item, q, limpets = 0, srvs = 0, scoop = false;
|
|
||||||
|
|
||||||
for (let g in { standard: 1, internal: 1, hardpoints: 1 }) {
|
|
||||||
let slotGroup = ship[g];
|
|
||||||
for (let i = 0, l = slotGroup.length; i < l; i++) {
|
|
||||||
if (slotGroup[i].m) {
|
|
||||||
// Special cases needed for SCB, AFMU, and limpet controllers since they don't use standard ammo/clip
|
|
||||||
q = 0;
|
|
||||||
switch (slotGroup[i].m.grp) {
|
|
||||||
case 'fs': // Skip fuel calculation if scoop present
|
|
||||||
scoop = true;
|
|
||||||
break;
|
|
||||||
case 'scb':
|
|
||||||
q = slotGroup[i].m.getAmmo() + 1;
|
|
||||||
break;
|
|
||||||
case 'am':
|
|
||||||
q = slotGroup[i].m.getAmmo();
|
|
||||||
break;
|
|
||||||
case 'pv':
|
|
||||||
srvs += slotGroup[i].m.getBays();
|
|
||||||
break;
|
|
||||||
case 'fx': case 'hb': case 'cc': case 'pc':
|
|
||||||
limpets = ship.cargoCapacity;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
q = slotGroup[i].m.getClip() + slotGroup[i].m.getAmmo();
|
|
||||||
}
|
|
||||||
// Calculate ammo costs only if a cost is specified
|
|
||||||
if (slotGroup[i].m.ammocost > 0) {
|
|
||||||
item = {
|
|
||||||
m: slotGroup[i].m,
|
|
||||||
max: q,
|
|
||||||
cost: slotGroup[i].m.ammocost,
|
|
||||||
total: q * slotGroup[i].m.ammocost
|
|
||||||
};
|
|
||||||
ammoCosts.push(item);
|
|
||||||
ammoTotal += item.total;
|
|
||||||
}
|
|
||||||
// Add fighters
|
|
||||||
if (slotGroup[i].m.grp === 'fh') {
|
|
||||||
item = {
|
|
||||||
m: slotGroup[i].m,
|
|
||||||
max: slotGroup[i].m.getRebuildsPerBay() * slotGroup[i].m.getBays(),
|
|
||||||
cost: slotGroup[i].m.fightercost,
|
|
||||||
total: slotGroup[i].m.getRebuildsPerBay() * slotGroup[i].m.getBays() * slotGroup[i].m.fightercost
|
|
||||||
};
|
|
||||||
ammoCosts.push(item);
|
|
||||||
ammoTotal += item.total;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limpets if controllers exist and cargo space available
|
|
||||||
if (limpets > 0) {
|
|
||||||
item = {
|
|
||||||
m: { name: 'limpets', class: '', rating: '' },
|
|
||||||
max: ship.cargoCapacity,
|
|
||||||
cost: 101,
|
|
||||||
total: ship.cargoCapacity * 101
|
|
||||||
};
|
|
||||||
ammoCosts.push(item);
|
|
||||||
ammoTotal += item.total;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (srvs > 0) {
|
|
||||||
item = {
|
|
||||||
m: { name: 'SRVs', class: '', rating: '' },
|
|
||||||
max: srvs,
|
|
||||||
cost: 1030,
|
|
||||||
total: srvs * 1030
|
|
||||||
};
|
|
||||||
ammoCosts.push(item);
|
|
||||||
ammoTotal += item.total;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate refuel costs if no scoop present
|
|
||||||
if (!scoop) {
|
|
||||||
item = {
|
|
||||||
m: { name: 'fuel', class: '', rating: '' },
|
|
||||||
max: ship.fuelCapacity,
|
|
||||||
cost: 50,
|
|
||||||
total: ship.fuelCapacity * 50
|
|
||||||
};
|
|
||||||
ammoCosts.push(item);
|
|
||||||
ammoTotal += item.total;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ ammoTotal, ammoCosts });
|
|
||||||
this._sortAmmo(ammoCosts, this.state.ammoPredicate, this.state.ammoDesc);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add listeners on mount and update costs
|
* Add listeners on mount and update costs
|
||||||
*/
|
*/
|
||||||
@@ -615,64 +433,7 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
this.listeners = [
|
this.listeners = [
|
||||||
Persist.addListener('discounts', this._onDiscountChanged.bind(this)),
|
Persist.addListener('discounts', this._onDiscountChanged.bind(this)),
|
||||||
Persist.addListener('insurance', this._onInsuranceChanged.bind(this)),
|
Persist.addListener('insurance', this._onInsuranceChanged.bind(this)),
|
||||||
Persist.addListener('builds', this._onBuildsChanged.bind(this)),
|
|
||||||
];
|
];
|
||||||
this._updateAmmoCosts(this.props.ship);
|
|
||||||
this._updateRetrofit(this.props.ship, this.state.retrofitShip);
|
|
||||||
this._sortCost(this.props.ship);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update state based on property and context changes
|
|
||||||
* @param {Object} nextProps Incoming/Next properties
|
|
||||||
* @param {Object} nextContext Incoming/Next context
|
|
||||||
*/
|
|
||||||
componentWillReceiveProps(nextProps, nextContext) {
|
|
||||||
let retrofitShip = this.state.retrofitShip;
|
|
||||||
|
|
||||||
if (nextProps.ship != this.props.ship) { // Ship has changed
|
|
||||||
let nextId = nextProps.ship.id;
|
|
||||||
let retrofitName = this._defaultRetrofitName(nextId, nextProps.buildName);
|
|
||||||
retrofitShip = this._buildRetrofitShip(nextId, retrofitName, nextId == this.props.ship.id ? retrofitShip : null);
|
|
||||||
this.setState({
|
|
||||||
retrofitShip,
|
|
||||||
retrofitName,
|
|
||||||
buildOptions: Persist.getBuildsNamesFor(nextId)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextProps.ship != this.props.ship || nextProps.code != this.props.code) {
|
|
||||||
nextProps.ship.applyDiscounts(Persist.getShipDiscount(), Persist.getModuleDiscount());
|
|
||||||
this._updateAmmoCosts(nextProps.ship);
|
|
||||||
this._updateRetrofit(nextProps.ship, retrofitShip);
|
|
||||||
this._sortCost(nextProps.ship);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort lists before render
|
|
||||||
* @param {Object} nextProps Incoming/Next properties
|
|
||||||
* @param {Object} nextState Incoming/Next state
|
|
||||||
*/
|
|
||||||
componentWillUpdate(nextProps, nextState) {
|
|
||||||
let state = this.state;
|
|
||||||
|
|
||||||
switch (nextState.tab) {
|
|
||||||
case 'ammo':
|
|
||||||
if (state.ammoPredicate != nextState.ammoPredicate || state.ammoDesc != nextState.ammoDesc) {
|
|
||||||
this._sortAmmo(nextState.ammoCosts, nextState.ammoPredicate, nextState.ammoDesc);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'retrofit':
|
|
||||||
if (state.retroPredicate != nextState.retroPredicate || state.retroDesc != nextState.retroDesc) {
|
|
||||||
this._sortRetrofit(nextState.retrofitCosts, nextState.retroPredicate, nextState.retroDesc);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (state.costPredicate != nextState.costPredicate || state.costDesc != nextState.costDesc) {
|
|
||||||
this._sortCost(nextProps.ship, nextState.costPredicate, nextState.costDesc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user