dw2 build work

This commit is contained in:
William Blythe
2018-11-20 12:18:16 +11:00
parent 1f3c3726f2
commit 318d06d9f9
3 changed files with 646 additions and 444 deletions

View File

@@ -20,16 +20,25 @@ export default class StandardSlotSection extends SlotSection {
super(props, context, 'standard', 'core internal'); super(props, context, 'standard', 'core internal');
this._optimizeStandard = this._optimizeStandard.bind(this); this._optimizeStandard = this._optimizeStandard.bind(this);
this._selectBulkhead = this._selectBulkhead.bind(this); this._selectBulkhead = this._selectBulkhead.bind(this);
this._showDW2Menu = this._showDW2Menu.bind(this);
this._dw2 = this._dw2.bind(this);
this.selectedRefId = null; this.selectedRefId = null;
this.firstRefId = 'maxjump'; this.firstRefId = 'maxjump';
this.lastRefId = 'racer'; this.lastRefId = 'dw2';
this.state = {
showDW2: false,
DW2Tier: -1,
DW2Eng: -1,
DW2Role: ''
};
} }
/** /**
* Handle focus if the component updates * Handle focus if the component updates
* @param {Object} prevProps React Component properties * @param {Object} prevProps React Component properties
*/ */
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
this._handleSectionFocus(prevProps,this.firstRefId, this.lastRefId); this._handleSectionFocus(prevProps, this.firstRefId, this.lastRefId);
} }
/** /**
@@ -72,6 +81,91 @@ export default class StandardSlotSection extends SlotSection {
this._close(); this._close();
} }
/**
* DW2 Build
* @param tier
* @param engineeringLevel
* @param role
*/
_dw2() {
this.selectedRefId = 'dw2';
this.setState({ showDW2: false });
ShipRoles.dw2Build(this.props.ship, this.state.DW2Tier, this.state.DW2Eng, this.state.DW2Role);
this.props.ship.updateModificationsString()
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
_showDW2Menu(translate) {
return (
<div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<div className='select-group cap'>{translate('Tier')}</div>
<ul id={'tier'}>
<li className={cn({ active: this.state.DW2Tier === 1 }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Tier: 1 })} onKeyDown={this._keyDown}
>{translate('1- Hardcore exploration, fully maximize jump range')}</li>
<li className={cn({ active: this.state.DW2Tier === 2 }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Tier: 2 })} onKeyDown={this._keyDown}
>{translate('2- Classical shielded exploration')}</li>
<li className={cn({ active: this.state.DW2Tier === 3 }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Tier: 3 })} onKeyDown={this._keyDown}
>{translate('3- Surface exploration, improved shield')}</li>
<li className={cn({ active: this.state.DW2Tier === 4 }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Tier: 4 })} onKeyDown={this._keyDown}
>{translate('4- Surface flight, improved shield and thrusters')}</li>
</ul>
<hr/>
<div className='select-group cap'>{translate('Engineering Level')}</div>
<ul id={'engLevel'}>
<li className={cn({ active: this.state.DW2Eng === 1 }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Eng: 1 })} onKeyDown={this._keyDown}
>{translate('No engineering')}</li>
<li className={cn({ active: this.state.DW2Eng === 2 }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Eng: 2 })} onKeyDown={this._keyDown}
>{translate('Only Felicity Farseer and Elvira Martuuk')}</li>
<li className={cn({ active: this.state.DW2Eng === 3 }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Eng: 3 })} onKeyDown={this._keyDown}
>{translate('All exploration related engineers')}</li>
</ul>
<hr/>
<div className='select-group cap'>{translate('Role')}</div>
<ul id={'role'}>
<li className={cn({ active: this.state.DW2Role === 'exploration' }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Role: 'exploration' })}
onKeyDown={this._keyDown}
>{translate('Exploration')}</li>
<li className={cn({ active: this.state.DW2Role === 'surface' }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Role: 'surface' })}
onKeyDown={this._keyDown}
>{translate('Surface exploration')}</li>
<li className={cn({ active: this.state.DW2Role === 'fullmining' }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Role: 'fullmining' })}
onKeyDown={this._keyDown}
>{translate('Big Rig, full mining')}</li>
<li className={cn({ active: this.state.DW2Role === 'prospector' }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Role: 'prospector' })}
onKeyDown={this._keyDown}
>{translate('Saper / Prospector mining')}</li>
<li className={cn({ active: this.state.DW2Role === 'rat' }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Role: 'rat' })} onKeyDown={this._keyDown}
>{translate('Fuel rat')}</li>
<li className={cn({ active: this.state.DW2Role === 'repair' }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Role: 'repair' })} onKeyDown={this._keyDown}
>{translate('Repair rat')}</li>
</ul>
<hr/>
<ul>
<li onClick={this._dw2} className={cn('lc')} tabIndex="0"
onKeyDown={this._keyDown}>
<button className="button">Apply</button>
</li>
</ul>
</div>
);
}
/** /**
* Miner Build * Miner Build
* @param {Boolean} shielded True if shield generator should be included * @param {Boolean} shielded True if shield generator should be included
@@ -232,7 +326,7 @@ export default class StandardSlotSection extends SlotSection {
selected={currentMenu == st[6]} selected={currentMenu == st[6]}
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 warning={m => m.fuel < st[2].m.maxfuel} // Show warning when fuel tank is smaller than FSD Max Fuel
/>; />;
return slots; return slots;
@@ -245,19 +339,34 @@ export default class StandardSlotSection extends SlotSection {
*/ */
_getSectionMenu(translate) { _getSectionMenu(translate) {
let planetaryDisabled = this.props.ship.internal.length < 4; let planetaryDisabled = this.props.ship.internal.length < 4;
if (this.state.showDW2 === true) {
return this._showDW2Menu(translate);
}
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}> return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul> <ul>
<li className='lc' tabIndex="0" onClick={this._optimizeStandard} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['maxjump'] = smRef}>{translate('Maximize Jump Range')}</li> <li className='lc' tabIndex="0" onClick={this._optimizeStandard} onKeyDown={this._keyDown}
ref={smRef => this.sectionRefArr['maxjump'] = smRef}>{translate('Maximize Jump Range')}</li>
</ul> </ul>
<div className='select-group cap'>{translate('roles')}</div> <div className='select-group cap'>{translate('roles')}</div>
<ul> <ul>
<li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, false, 0)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['multipurpose'] = smRef}>{translate('Multi-purpose')}</li> <li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, false, 0)} onKeyDown={this._keyDown}
<li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, true, 2)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['combat'] = smRef}>{translate('Combat')}</li> ref={smRef => this.sectionRefArr['multipurpose'] = smRef}>{translate('Multi-purpose')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeCargo.bind(this, true)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['trader'] = smRef}>{translate('Trader')}</li> <li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, true, 2)} onKeyDown={this._keyDown}
<li className='lc' tabIndex="0" onClick={this._optimizeExplorer.bind(this, false)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['explorer'] = smRef}>{translate('Explorer')}</li> ref={smRef => this.sectionRefArr['combat'] = smRef}>{translate('Combat')}</li>
<li className={cn('lc', { disabled: planetaryDisabled })} tabIndex={planetaryDisabled ? '' : '0'} onClick={!planetaryDisabled && this._optimizeExplorer.bind(this, true)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['planetary'] = smRef}>{translate('Planetary Explorer')}</li> <li className='lc' tabIndex="0" onClick={this._optimizeCargo.bind(this, true)} onKeyDown={this._keyDown}
<li className='lc' tabIndex="0" onClick={this._optimizeMiner.bind(this, true)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['miner'] = smRef}>{translate('Miner')}</li> ref={smRef => this.sectionRefArr['trader'] = smRef}>{translate('Trader')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeRacer.bind(this)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['racer'] = smRef}>{translate('Racer')}</li> <li className='lc' tabIndex="0" onClick={this._optimizeExplorer.bind(this, false)} onKeyDown={this._keyDown}
ref={smRef => this.sectionRefArr['explorer'] = smRef}>{translate('Explorer')}</li>
<li className={cn('lc', { disabled: planetaryDisabled })} tabIndex={planetaryDisabled ? '' : '0'}
onClick={!planetaryDisabled && this._optimizeExplorer.bind(this, true)} onKeyDown={this._keyDown}
ref={smRef => this.sectionRefArr['planetary'] = smRef}>{translate('Planetary Explorer')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeMiner.bind(this, true)} onKeyDown={this._keyDown}
ref={smRef => this.sectionRefArr['miner'] = smRef}>{translate('Miner')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeRacer.bind(this)} onKeyDown={this._keyDown}
ref={smRef => this.sectionRefArr['racer'] = smRef}>{translate('Racer')}</li>
<li className='lc' tabIndex="0" onClick={() => this.setState({ showDW2: !this.state.showDW2 })}
onKeyDown={this._keyDown}
ref={smRef => this.sectionRefArr['dw2'] = smRef}>{translate('DW2')}</li>
</ul> </ul>
</div>; </div>;
} }

View File

@@ -1,5 +1,8 @@
import * as ModuleUtils from './ModuleUtils'; import * as ModuleUtils from './ModuleUtils';
import {Modifications} from 'coriolis-data/dist';
import { canMount } from '../utils/SlotFunctions'; import { canMount } from '../utils/SlotFunctions';
import { getBlueprint, setPercent } from '../utils/BlueprintFunctions';
/** /**
* Standard / typical role for multi-purpose or combat (if shielded with better bulkheads) * Standard / typical role for multi-purpose or combat (if shielded with better bulkheads)
@@ -14,7 +17,7 @@ export function multiPurpose(ship, shielded, bulkheadIndex) {
.useBulkhead(bulkheadIndex); .useBulkhead(bulkheadIndex);
if (shielded) { if (shielded) {
ship.internal.some(function(slot) { ship.internal.some(function (slot) {
if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield
ship.use(slot, ModuleUtils.findInternal('sg', slot.maxClass, 'A')); ship.use(slot, ModuleUtils.findInternal('sg', slot.maxClass, 'A'));
ship.setSlotEnabled(slot, true); ship.setSlotEnabled(slot, true);
@@ -24,6 +27,96 @@ export function multiPurpose(ship, shielded, bulkheadIndex) {
} }
} }
/**
* Distant Worlds 2 role
* Tiers:
* 1- Hardcore exploration, fully maximize jump range
* 2- Classical shielded exploration
* 3- Surface exploration, improved shield
* 4- Surface flight, improved shield and thrusters
*
* Engineering level:
* No engineering
* Only Felicity Farseer and Elvira Martuuk
* All exploration related engineers
*
* Role
* Exploration
* Surface exploration
* Big Rig, full mining
* Saper / Prospector mining
* Fuel rat
* Repair rat
*
* @param ship {Ship} Ship instance
* @param tier {Number}
* @param engineeringLevel {Number}
* @param role {String}
*/
export function dw2Build(ship, tier, engineeringLevel, role) {
let standardOpts = { ppRating: 'D', pd: 'd3' };
ship
.emptyInternal()
.emptyHardpoints()
.emptyUtility();
ship.use(ship.standard[2], ModuleUtils.findStandard('fsd', ship.standard[2].maxClass, 'A'))
ship.use(ship.standard[3], ModuleUtils.findStandard('ls', ship.standard[3].maxClass, 'D'))
ship.use(ship.standard[4], ModuleUtils.findStandard('pd', 1, 'D'))
ship.use(ship.standard[5], ModuleUtils.findStandard('s', ship.standard[5].maxClass, 'D'))
const fuelNeeded = ship.standard[2].m.maxfuel * 2;
const fuelTank = ship.availCS.standard[6]
.filter(e => e.fuel)
.filter(e => e.fuel >= fuelNeeded)
ship.use(ship.standard[6], fuelTank[0])
if (engineeringLevel === 2) {
const bp = getBlueprint('FSD_LongRange', ship.standard[2]);
bp.grade = 5
bp.special = Modifications.specials['special_fsd_heavy']
ship.standard[2].m.blueprint = bp;
setPercent(ship, ship.standard[2].m, 100);
} else if (engineeringLevel === 3) {
// Armour G5 HD + Deep Plating
const armourBP = getBlueprint('Armour_HeavyDuty', ship.bulkheads);
armourBP.grade = 5
armourBP.special = Modifications.specials['special_armour_chunky']
ship.bulkheads.m.blueprint = armourBP;
setPercent(ship, ship.bulkheads.m, 100);
// FSD G5 IR + Mass Manager
const fsdBP = getBlueprint('FSD_LongRange', ship.standard[2]);
fsdBP.grade = 5
fsdBP.special = Modifications.specials['special_fsd_heavy']
ship.standard[2].m.blueprint = fsdBP;
setPercent(ship, ship.standard[2].m, 100);
// LS G4 LW
const lsBP = getBlueprint('LifeSupport_LightWeight', ship.standard[3]);
lsBP.grade = 4
ship.standard[3].m.blueprint = lsBP;
setPercent(ship, ship.standard[3].m, 100);
// Sensors G5 LW
const sBP = getBlueprint('Sensor_Sensor_LightWeight', ship.standard[5]);
sBP.grade = 5
ship.standard[5].m.blueprint = sBP;
setPercent(ship, ship.standard[5].m, 100);
}
ship.useBulkhead(0, false);
ship.use(ship.standard[0], ship.availCS.lightestPowerPlant(Math.max(ship.powerRetracted, ship.powerDeployed), 'D'))
// ship.useLightestStandard(standardOpts);
ship.updatePowerGenerated()
.updatePowerUsed()
.recalculateMass()
.updateJumpStats()
.recalculateShield()
.recalculateShieldCells()
.recalculateArmour()
.recalculateDps()
.recalculateEps()
.recalculateHps()
.updateMovement()
.updateModificationsString();
}
/** /**
* Trader Role * Trader Role
* @param {Ship} ship Ship instance * @param {Ship} ship Ship instance
@@ -45,7 +138,7 @@ export function trader(ship, shielded, standardOpts) {
.filter(a => (!a.eligible) || a.eligible.sg) .filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class) .filter(a => a.maxClass >= sg.class)
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass)); .sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
shieldInternals.some(function(slot) { shieldInternals.some(function (slot) {
if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield
const shield = ModuleUtils.findInternal('sg', slot.maxClass, 'A'); const shield = ModuleUtils.findInternal('sg', slot.maxClass, 'A');
if (shield && shield.maxmass > ship.hullMass) { if (shield && shield.maxmass > ship.hullMass) {
@@ -87,11 +180,11 @@ export function trader(ship, shielded, standardOpts) {
*/ */
export function explorer(ship, planetary) { export function explorer(ship, planetary) {
let standardOpts = { ppRating: 'A' }, let standardOpts = { ppRating: 'A' },
heatSinkCount = 2, // Fit 2 heat sinks if possible heatSinkCount = 2, // Fit 2 heat sinks if possible
usedSlots = [], usedSlots = [],
sgSlot, sgSlot,
fuelScoopSlot, fuelScoopSlot,
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass); sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
if (!planetary) { // Non-planetary explorers don't really need to boost if (!planetary) { // Non-planetary explorers don't really need to boost
standardOpts.pd = '1D'; standardOpts.pd = '1D';
@@ -216,9 +309,9 @@ export function explorer(ship, planetary) {
export function miner(ship, shielded) { export function miner(ship, shielded) {
shielded = true; shielded = true;
let standardOpts = { ppRating: 'A' }, let standardOpts = { ppRating: 'A' },
miningLaserCount = 2, miningLaserCount = 2,
usedSlots = [], usedSlots = [],
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass); sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
// Cargo hatch should be enabled // Cargo hatch should be enabled
ship.setSlotEnabled(ship.cargoHatch, true); ship.setSlotEnabled(ship.cargoHatch, true);
@@ -269,7 +362,7 @@ export function miner(ship, shielded) {
// Dual mining lasers of highest possible class; remove anything else // Dual mining lasers of highest possible class; remove anything else
const miningLaserOrder = [2, 3, 4, 1, 0]; const miningLaserOrder = [2, 3, 4, 1, 0];
const miningLaserHardpoints = ship.hardpoints.concat().sort(function(a, b) { const miningLaserHardpoints = ship.hardpoints.concat().sort(function (a, b) {
return miningLaserOrder.indexOf(a.maxClass) - miningLaserOrder.indexOf(b.maxClass); return miningLaserOrder.indexOf(a.maxClass) - miningLaserOrder.indexOf(b.maxClass);
}); });
for (let s of miningLaserHardpoints) { for (let s of miningLaserHardpoints) {
@@ -283,7 +376,7 @@ export function miner(ship, shielded) {
// Number of collector limpets required to be active is a function of the size of the ship and the power of the lasers // Number of collector limpets required to be active is a function of the size of the ship and the power of the lasers
const miningLaserDps = ship.hardpoints.filter(h => h.m != null) const miningLaserDps = ship.hardpoints.filter(h => h.m != null)
.reduce(function(a, b) { .reduce(function (a, b) {
return a + b.m.getDps(); return a + b.m.getDps();
}, 0); }, 0);
// Find out how many internal slots we have, and their potential cargo size // Find out how many internal slots we have, and their potential cargo size
@@ -314,7 +407,7 @@ export function miner(ship, shielded) {
// Power distributor to power the mining lasers indefinitely // Power distributor to power the mining lasers indefinitely
const wepRateRequired = ship.hardpoints.filter(h => h.m != null) const wepRateRequired = ship.hardpoints.filter(h => h.m != null)
.reduce(function(a, b) { .reduce(function (a, b) {
return a + b.m.getEps(); return a + b.m.getEps();
}, 0); }, 0);
standardOpts.pd = ship.getAvailableModules().matchingPowerDist({ weprate: wepRateRequired }).id; standardOpts.pd = ship.getAvailableModules().matchingPowerDist({ weprate: wepRateRequired }).id;
@@ -336,9 +429,9 @@ export function miner(ship, shielded) {
*/ */
export function racer(ship) { export function racer(ship) {
let standardOpts = {}, let standardOpts = {},
usedSlots = [], usedSlots = [],
sgSlot, sgSlot,
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass); sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
// Cargo hatch can be disabled // Cargo hatch can be disabled
ship.setSlotEnabled(ship.cargoHatch, false); ship.setSlotEnabled(ship.cargoHatch, false);

View File

@@ -1,417 +1,417 @@
import React from 'react'; import React from 'react';
import { Modifications } from 'coriolis-data/dist'; import { Modifications } from 'coriolis-data/dist';
/** /**
* Generate a tooltip with details of a blueprint's specials * Generate a tooltip with details of a blueprint's specials
* @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 {string} grp The group of the module * @param {string} grp The group of the module
* @param {Object} m The module to compare with * @param {Object} m The module to compare with
* @param {string} specialName The name of the special * @param {string} specialName The name of the special
* @returns {Object} The react components * @returns {Object} The react components
*/ */
export function specialToolTip(translate, blueprint, grp, m, specialName) { export function specialToolTip(translate, blueprint, grp, m, specialName) {
const effects = []; const effects = [];
if (!blueprint || !blueprint.features) { if (!blueprint || !blueprint.features) {
return undefined; return undefined;
} }
if (m) { if (m) {
// We also add in any benefits from specials that aren't covered above // We also add in any benefits from specials that aren't covered above
if (m.blueprint) { if (m.blueprint) {
for (const feature in Modifications.modifierActions[specialName]) { for (const feature in Modifications.modifierActions[specialName]) {
// if (!blueprint.features[feature] && !m.mods.feature) { // if (!blueprint.features[feature] && !m.mods.feature) {
const featureDef = Modifications.modifications[feature]; const featureDef = Modifications.modifications[feature];
if (featureDef && !featureDef.hidden) { if (featureDef && !featureDef.hidden) {
let symbol = ''; let symbol = '';
if (feature === 'jitter') { if (feature === 'jitter') {
symbol = '°'; symbol = '°';
} else if (featureDef.type === 'percentage') { } else if (featureDef.type === 'percentage') {
symbol = '%'; symbol = '%';
} }
let current = m.getModValue(feature) - m.getModValue(feature, true); let current = m.getModValue(feature) - m.getModValue(feature, true);
if (featureDef.type === 'percentage') { if (featureDef.type === 'percentage') {
current = Math.round(current / 10) / 10; current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') { } else if (featureDef.type === 'numeric') {
current /= 100; current /= 100;
} }
const currentIsBeneficial = isValueBeneficial(feature, current); const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push( effects.push(
<tr key={feature + '_specialTT'}> <tr key={feature + '_specialTT'}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td> <td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td> <td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} <td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'}
style={{ textAlign: 'right' }}>{current}{symbol}</td> style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td> <td>&nbsp;</td>
</tr> </tr>
); );
} }
} }
} }
} }
return ( return (
<div> <div>
<table width='100%'> <table width='100%'>
<tbody> <tbody>
{effects} {effects}
</tbody> </tbody>
</table> </table>
</div> </div>
); );
} }
/** /**
* 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 {Array} engineers The engineers supplying this blueprint
* @param {string} grp The group of the module * @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, engineers, grp, m) {
const effects = []; const effects = [];
if (!blueprint || !blueprint.features) { if (!blueprint || !blueprint.features) {
return undefined; return undefined;
} }
for (const feature in blueprint.features) { for (const feature in blueprint.features) {
const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]); const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]);
const featureDef = Modifications.modifications[feature]; const featureDef = Modifications.modifications[feature];
if (!featureDef.hidden) { if (!featureDef.hidden) {
let symbol = ''; let symbol = '';
if (feature === 'jitter') { if (feature === 'jitter') {
symbol = '°'; symbol = '°';
} else if (featureDef.type === 'percentage') { } else if (featureDef.type === 'percentage') {
symbol = '%'; symbol = '%';
} }
let lowerBound = blueprint.features[feature][0]; let lowerBound = blueprint.features[feature][0];
let upperBound = blueprint.features[feature][1]; let upperBound = blueprint.features[feature][1];
if (featureDef.type === 'percentage') { if (featureDef.type === 'percentage') {
lowerBound = Math.round(lowerBound * 1000) / 10; lowerBound = Math.round(lowerBound * 1000) / 10;
upperBound = Math.round(upperBound * 1000) / 10; upperBound = Math.round(upperBound * 1000) / 10;
} }
const lowerIsBeneficial = isValueBeneficial(feature, lowerBound); const lowerIsBeneficial = isValueBeneficial(feature, lowerBound);
const upperIsBeneficial = isValueBeneficial(feature, upperBound); const upperIsBeneficial = isValueBeneficial(feature, upperBound);
if (m) { if (m) {
// We have a module - add in the current value // We have a module - add in the current value
let current = m.getModValue(feature); let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10; current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') { } else if (featureDef.type === 'numeric') {
current /= 100; current /= 100;
} }
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, grp)}</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>
</tr> </tr>
); );
} else { } else {
// 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, grp)}</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>
); );
} }
} }
} }
if (m) { if (m) {
// Because we have a module add in any benefits that aren't part of the primary blueprint // Because we have a module add in any benefits that aren't part of the primary blueprint
for (const feature in m.mods) { for (const feature in m.mods) {
if (!blueprint.features[feature]) { if (!blueprint.features[feature]) {
const featureDef = Modifications.modifications[feature]; const featureDef = Modifications.modifications[feature];
if (featureDef && !featureDef.hidden) { if (featureDef && !featureDef.hidden) {
let symbol = ''; let symbol = '';
if (feature === 'jitter') { if (feature === 'jitter') {
symbol = '°'; symbol = '°';
} else if (featureDef.type === 'percentage') { } else if (featureDef.type === 'percentage') {
symbol = '%'; symbol = '%';
} }
let current = m.getModValue(feature); let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10; current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') { } else if (featureDef.type === 'numeric') {
current /= 100; current /= 100;
} }
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, grp)}</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>
</tr> </tr>
); );
} }
} }
} }
// We also add in any benefits from specials that aren't covered above // We also add in any benefits from specials that aren't covered above
if (m.blueprint && m.blueprint.special) { if (m.blueprint && m.blueprint.special) {
for (const feature in Modifications.modifierActions[m.blueprint.special.edname]) { for (const feature in Modifications.modifierActions[m.blueprint.special.edname]) {
if (!blueprint.features[feature] && !m.mods.feature) { if (!blueprint.features[feature] && !m.mods.feature) {
const featureDef = Modifications.modifications[feature]; const featureDef = Modifications.modifications[feature];
if (featureDef && !featureDef.hidden) { if (featureDef && !featureDef.hidden) {
let symbol = ''; let symbol = '';
if (feature === 'jitter') { if (feature === 'jitter') {
symbol = '°'; symbol = '°';
} else if (featureDef.type === 'percentage') { } else if (featureDef.type === 'percentage') {
symbol = '%'; symbol = '%';
} }
let current = m.getModValue(feature); let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10; current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') { } else if (featureDef.type === 'numeric') {
current /= 100; current /= 100;
} }
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, grp)}</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>
</tr> </tr>
); );
} }
} }
} }
} }
} }
let components; let components;
if (!m) { if (!m) {
components = []; components = [];
for (const component in blueprint.components) { for (const component in blueprint.components) {
components.push( components.push(
<tr key={component}> <tr key={component}>
<td style={{ textAlign: 'left' }}>{translate(component)}</td> <td style={{ textAlign: 'left' }}>{translate(component)}</td>
<td style={{ textAlign: 'right' }}>{blueprint.components[component]}</td> <td style={{ textAlign: 'right' }}>{blueprint.components[component]}</td>
</tr> </tr>
); );
} }
} }
let engineersList; let engineersList;
if (engineers) { if (engineers) {
engineersList = []; engineersList = [];
for (const engineer of engineers) { for (const engineer of engineers) {
engineersList.push( engineersList.push(
<tr key={engineer}> <tr key={engineer}>
<td style={{ textAlign: 'left' }}>{engineer}</td> <td style={{ textAlign: 'left' }}>{engineer}</td>
</tr> </tr>
); );
} }
} }
return ( return (
<div> <div>
<table width='100%'> <table width='100%'>
<thead> <thead>
<tr> <tr>
<td>{translate('feature')}</td> <td>{translate('feature')}</td>
<td>{translate('worst')}</td> <td>{translate('worst')}</td>
{m ? <td>{translate('current')}</td> : null } {m ? <td>{translate('current')}</td> : null }
<td>{translate('best')}</td> <td>{translate('best')}</td>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{effects} {effects}
</tbody> </tbody>
</table> </table>
{ components ? <table width='100%'> { components ? <table width='100%'>
<thead> <thead>
<tr> <tr>
<td>{translate('component')}</td> <td>{translate('component')}</td>
<td>{translate('amount')}</td> <td>{translate('amount')}</td>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{components} {components}
</tbody> </tbody>
</table> : null } </table> : null }
{ engineersList ? <table width='100%'> { engineersList ? <table width='100%'>
<thead> <thead>
<tr> <tr>
<td>{translate('engineers')}</td> <td>{translate('engineers')}</td>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{engineersList} {engineersList}
</tbody> </tbody>
</table> : null } </table> : null }
</div> </div>
); );
} }
/** /**
* Is this blueprint feature beneficial? * Is this blueprint feature beneficial?
* @param {string} feature The name of the feature * @param {string} feature The name of the feature
* @param {array} values The value of the feature * @param {array} values The value of the feature
* @returns {boolean} True if this feature is beneficial * @returns {boolean} True if this feature is beneficial
*/ */
export function isBeneficial(feature, values) { export function isBeneficial(feature, values) {
const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0)); const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0));
if (Modifications.modifications[feature].higherbetter) { if (Modifications.modifications[feature].higherbetter) {
return !fact; return !fact;
} else { } else {
return fact; return fact;
} }
} }
/** /**
* Is this feature value beneficial? * Is this feature value 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 * @param {number} value The value of the feature
* @returns {boolean} True if this value is beneficial * @returns {boolean} True if this value is beneficial
*/ */
export function isValueBeneficial(feature, value) { export function isValueBeneficial(feature, value) {
if (Modifications.modifications[feature].higherbetter) { if (Modifications.modifications[feature].higherbetter) {
return value > 0; return value > 0;
} else { } else {
return value < 0; return value < 0;
} }
} }
/** /**
* Get a blueprint with a given name and an optional module * Get a blueprint with a given name and an optional module
* @param {string} name The name of the blueprint * @param {string} name The name of the blueprint
* @param {Object} module The module for which to obtain this blueprint * @param {Object} module The module for which to obtain this blueprint
* @returns {Object} The matching blueprint * @returns {Object} The matching blueprint
*/ */
export function getBlueprint(name, module) { export function getBlueprint(name, module) {
// Start with a copy of the blueprint // Start with a copy of the blueprint
const findMod = val => Object.keys(Modifications.blueprints).find(elem => elem.toString().toLowerCase().search(val.toString().toLowerCase().replace(/(OutfittingFieldType_|persecond)/igm, '')) >= 0); const findMod = val => Object.keys(Modifications.blueprints).find(elem => elem.toString().toLowerCase().search(val.toString().toLowerCase().replace(/(OutfittingFieldType_|persecond)/igm, '')) >= 0);
const found = Modifications.blueprints[findMod(name)]; const found = Modifications.blueprints[findMod(name)];
if (!found || !found.fdname) { if (!found || !found.fdname) {
return {}; return {};
} }
const blueprint = JSON.parse(JSON.stringify(found)); const blueprint = JSON.parse(JSON.stringify(found));
return blueprint; return blueprint;
} }
/** /**
* Provide 'percent' primary modifications * Provide 'percent' primary modifications
* @param {Object} ship The ship for which to perform the modifications * @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications * @param {Object} m The module for which to perform the modifications
* @param {Number} percent The percent to set values to of full. * @param {Number} percent The percent to set values to of full.
*/ */
export function setPercent(ship, m, percent) { export function setPercent(ship, m, percent) {
ship.clearModifications(m); ship.clearModifications(m);
// Pick given value as multiplier // Pick given value as multiplier
const mult = percent / 100; const mult = percent / 100;
const features = m.blueprint.grades[m.blueprint.grade].features; const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) { for (const featureName in features) {
let value; let value;
if (Modifications.modifications[featureName].higherbetter) { if (Modifications.modifications[featureName].higherbetter) {
// Higher is better, but is this making it better or worse? // Higher is better, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult); value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
} else { } else {
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult); value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
} }
} else { } else {
// Higher is worse, but is this making it better or worse? // Higher is worse, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult); value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
} else { } else {
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult); value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
} }
} }
_setValue(ship, m, featureName, value); _setValue(ship, m, featureName, value);
} }
} }
/** /**
* Provide 'random' primary modifications * Provide 'random' primary modifications
* @param {Object} ship The ship for which to perform the modifications * @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications * @param {Object} m The module for which to perform the modifications
*/ */
export function setRandom(ship, m) { export function setRandom(ship, m) {
// Pick a single value for our randomness // Pick a single value for our randomness
setPercent(ship, m, Math.random() * 100); setPercent(ship, m, Math.random() * 100);
} }
/** /**
* Set a modification feature value * Set a modification feature value
* @param {Object} ship The ship for which to perform the modifications * @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications * @param {Object} m The module for which to perform the modifications
* @param {string} featureName The feature being set * @param {string} featureName The feature being set
* @param {number} value The value being set for the feature * @param {number} value The value being set for the feature
*/ */
function _setValue(ship, m, featureName, value) { function _setValue(ship, m, featureName, value) {
if (Modifications.modifications[featureName].type == 'percentage') { if (Modifications.modifications[featureName].type == 'percentage') {
ship.setModification(m, featureName, value * 10000); ship.setModification(m, featureName, value * 10000);
} else if (Modifications.modifications[featureName].type == 'numeric') { } else if (Modifications.modifications[featureName].type == 'numeric') {
ship.setModification(m, featureName, value * 100); ship.setModification(m, featureName, value * 100);
} else { } else {
ship.setModification(m, featureName, value); ship.setModification(m, featureName, value);
} }
} }
/** /**
* Provide 'percent' primary query * Provide 'percent' primary query
* @param {Object} m The module for which to perform the query * @param {Object} m The module for which to perform the query
* @returns {Number} percent The percentage indicator of current applied values. * @returns {Number} percent The percentage indicator of current applied values.
*/ */
export function getPercent(m) { export function getPercent(m) {
let result = null; let result = null;
const features = m.blueprint.grades[m.blueprint.grade].features; const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) { for (const featureName in features) {
if (features[featureName][0] === features[featureName][1]) { if (features[featureName][0] === features[featureName][1]) {
continue; continue;
} }
let value = _getValue(m, featureName); let value = _getValue(m, featureName);
let mult; let mult;
if (featureName == 'shieldboost') { if (featureName == 'shieldboost') {
mult = ((1 + value) * (1 + m.shieldboost)) - 1 - m.shieldboost; mult = ((1 + value) * (1 + m.shieldboost)) - 1 - m.shieldboost;
} else if (Modifications.modifications[featureName].higherbetter) { } else if (Modifications.modifications[featureName].higherbetter) {
// Higher is better, but is this making it better or worse? // Higher is better, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100); mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100);
} else { } else {
mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100); mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100);
} }
} else { } else {
// Higher is worse, but is this making it better or worse? // Higher is worse, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100); mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100);
} else { } else {
mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100); mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100);
} }
} }
if (result && result != mult) { if (result && result != mult) {
return null; return null;
} else if (result != mult) { } else if (result != mult) {
result = mult; result = mult;
} }
} }
return result; return result;
} }
/** /**
* Query a feature value * Query a feature value
* @param {Object} m The module for which to perform the query * @param {Object} m The module for which to perform the query
* @param {string} featureName The feature being queried * @param {string} featureName The feature being queried
* @returns {number} The value of the modification as a % * @returns {number} The value of the modification as a %
*/ */
function _getValue(m, featureName) { function _getValue(m, featureName) {
if (Modifications.modifications[featureName].type == 'percentage') { if (Modifications.modifications[featureName].type == 'percentage') {
return m.getModValue(featureName, true) / 10000; return m.getModValue(featureName, true) / 10000;
} else if (Modifications.modifications[featureName].type == 'numeric') { } else if (Modifications.modifications[featureName].type == 'numeric') {
return m.getModValue(featureName, true) / 100; return m.getModValue(featureName, true) / 100;
} else { } else {
return m.getModValue(featureName, true); return m.getModValue(featureName, true);
} }
} }