From 318d06d9f94bfbdc4f593b3f92a18ec6e60200c1 Mon Sep 17 00:00:00 2001 From: William Blythe Date: Tue, 20 Nov 2018 12:18:16 +1100 Subject: [PATCH] dw2 build work --- src/app/components/StandardSlotSection.jsx | 131 +++- src/app/shipyard/ShipRoles.js | 125 ++- src/app/utils/BlueprintFunctions.js | 834 ++++++++++----------- 3 files changed, 646 insertions(+), 444 deletions(-) diff --git a/src/app/components/StandardSlotSection.jsx b/src/app/components/StandardSlotSection.jsx index 7ed5cb55..c880816a 100644 --- a/src/app/components/StandardSlotSection.jsx +++ b/src/app/components/StandardSlotSection.jsx @@ -20,16 +20,25 @@ export default class StandardSlotSection extends SlotSection { super(props, context, 'standard', 'core internal'); this._optimizeStandard = this._optimizeStandard.bind(this); this._selectBulkhead = this._selectBulkhead.bind(this); + this._showDW2Menu = this._showDW2Menu.bind(this); + this._dw2 = this._dw2.bind(this); this.selectedRefId = null; this.firstRefId = 'maxjump'; - this.lastRefId = 'racer'; + this.lastRefId = 'dw2'; + this.state = { + showDW2: false, + DW2Tier: -1, + DW2Eng: -1, + DW2Role: '' + }; } + /** * Handle focus if the component updates * @param {Object} prevProps React Component properties */ 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(); } + /** + * 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 ( +
e.stopPropagation()} onContextMenu={stopCtxPropagation}> +
{translate('Tier')}
+ +
+
{translate('Engineering Level')}
+ +
+
{translate('Role')}
+ +
+ +
+ ); + } + /** * Miner Build * @param {Boolean} shielded True if shield generator should be included @@ -232,7 +326,7 @@ export default class StandardSlotSection extends SlotSection { selected={currentMenu == st[6]} onChange={this.props.onChange} ship={ship} - warning= {m => m.fuel < st[2].m.maxfuel} // Show warning when fuel tank is smaller than FSD Max Fuel + warning={m => m.fuel < st[2].m.maxfuel} // Show warning when fuel tank is smaller than FSD Max Fuel />; return slots; @@ -245,19 +339,34 @@ export default class StandardSlotSection extends SlotSection { */ _getSectionMenu(translate) { let planetaryDisabled = this.props.ship.internal.length < 4; + if (this.state.showDW2 === true) { + return this._showDW2Menu(translate); + } return
e.stopPropagation()} onContextMenu={stopCtxPropagation}>
{translate('roles')}
; } diff --git a/src/app/shipyard/ShipRoles.js b/src/app/shipyard/ShipRoles.js index 62c4e00c..179300c4 100644 --- a/src/app/shipyard/ShipRoles.js +++ b/src/app/shipyard/ShipRoles.js @@ -1,5 +1,8 @@ import * as ModuleUtils from './ModuleUtils'; +import {Modifications} from 'coriolis-data/dist'; import { canMount } from '../utils/SlotFunctions'; +import { getBlueprint, setPercent } from '../utils/BlueprintFunctions'; + /** * 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); 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 ship.use(slot, ModuleUtils.findInternal('sg', slot.maxClass, 'A')); 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 * @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.maxClass >= sg.class) .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 const shield = ModuleUtils.findInternal('sg', slot.maxClass, 'A'); if (shield && shield.maxmass > ship.hullMass) { @@ -87,11 +180,11 @@ export function trader(ship, shielded, standardOpts) { */ export function explorer(ship, planetary) { let standardOpts = { ppRating: 'A' }, - heatSinkCount = 2, // Fit 2 heat sinks if possible - usedSlots = [], - sgSlot, - fuelScoopSlot, - sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass); + heatSinkCount = 2, // Fit 2 heat sinks if possible + usedSlots = [], + sgSlot, + fuelScoopSlot, + sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass); if (!planetary) { // Non-planetary explorers don't really need to boost standardOpts.pd = '1D'; @@ -216,9 +309,9 @@ export function explorer(ship, planetary) { export function miner(ship, shielded) { shielded = true; let standardOpts = { ppRating: 'A' }, - miningLaserCount = 2, - usedSlots = [], - sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass); + miningLaserCount = 2, + usedSlots = [], + sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass); // Cargo hatch should be enabled ship.setSlotEnabled(ship.cargoHatch, true); @@ -269,7 +362,7 @@ export function miner(ship, shielded) { // Dual mining lasers of highest possible class; remove anything else const miningLaserOrder = [2, 3, 4, 1, 0]; - const miningLaserHardpoints = ship.hardpoints.concat().sort(function(a, b) { + const miningLaserHardpoints = ship.hardpoints.concat().sort(function (a, b) { return miningLaserOrder.indexOf(a.maxClass) - miningLaserOrder.indexOf(b.maxClass); }); 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 const miningLaserDps = ship.hardpoints.filter(h => h.m != null) - .reduce(function(a, b) { + .reduce(function (a, b) { return a + b.m.getDps(); }, 0); // 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 const wepRateRequired = ship.hardpoints.filter(h => h.m != null) - .reduce(function(a, b) { + .reduce(function (a, b) { return a + b.m.getEps(); }, 0); standardOpts.pd = ship.getAvailableModules().matchingPowerDist({ weprate: wepRateRequired }).id; @@ -336,9 +429,9 @@ export function miner(ship, shielded) { */ export function racer(ship) { let standardOpts = {}, - usedSlots = [], - sgSlot, - sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass); + usedSlots = [], + sgSlot, + sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass); // Cargo hatch can be disabled ship.setSlotEnabled(ship.cargoHatch, false); diff --git a/src/app/utils/BlueprintFunctions.js b/src/app/utils/BlueprintFunctions.js index 2c38449d..e49e9d18 100644 --- a/src/app/utils/BlueprintFunctions.js +++ b/src/app/utils/BlueprintFunctions.js @@ -1,417 +1,417 @@ -import React from 'react'; -import { Modifications } from 'coriolis-data/dist'; - -/** - * Generate a tooltip with details of a blueprint's specials - * @param {Object} translate The translate object - * @param {Object} blueprint The blueprint at the required grade - * @param {string} grp The group of the module - * @param {Object} m The module to compare with - * @param {string} specialName The name of the special - * @returns {Object} The react components - */ -export function specialToolTip(translate, blueprint, grp, m, specialName) { - const effects = []; - if (!blueprint || !blueprint.features) { - return undefined; - } - if (m) { - // We also add in any benefits from specials that aren't covered above - if (m.blueprint) { - for (const feature in Modifications.modifierActions[specialName]) { - // if (!blueprint.features[feature] && !m.mods.feature) { - const featureDef = Modifications.modifications[feature]; - if (featureDef && !featureDef.hidden) { - let symbol = ''; - if (feature === 'jitter') { - symbol = '°'; - } else if (featureDef.type === 'percentage') { - symbol = '%'; - } - let current = m.getModValue(feature) - m.getModValue(feature, true); - if (featureDef.type === 'percentage') { - current = Math.round(current / 10) / 10; - } else if (featureDef.type === 'numeric') { - current /= 100; - } - const currentIsBeneficial = isValueBeneficial(feature, current); - - effects.push( - - {translate(feature, grp)} -   - {current}{symbol} -   - - ); - } - } - } - } - - return ( -
- - - {effects} - -
-
- ); -} - -/** - * Generate a tooltip with details of a blueprint's effects - * @param {Object} translate The translate object - * @param {Object} blueprint The blueprint at the required grade - * @param {Array} engineers The engineers supplying this blueprint - * @param {string} grp The group of the module - * @param {Object} m The module to compare with - * @returns {Object} The react components - */ -export function blueprintTooltip(translate, blueprint, engineers, grp, m) { - const effects = []; - if (!blueprint || !blueprint.features) { - return undefined; - } - for (const feature in blueprint.features) { - const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]); - const featureDef = Modifications.modifications[feature]; - if (!featureDef.hidden) { - let symbol = ''; - if (feature === 'jitter') { - symbol = '°'; - } else if (featureDef.type === 'percentage') { - symbol = '%'; - } - let lowerBound = blueprint.features[feature][0]; - let upperBound = blueprint.features[feature][1]; - if (featureDef.type === 'percentage') { - lowerBound = Math.round(lowerBound * 1000) / 10; - upperBound = Math.round(upperBound * 1000) / 10; - } - const lowerIsBeneficial = isValueBeneficial(feature, lowerBound); - const upperIsBeneficial = isValueBeneficial(feature, upperBound); - if (m) { - // We have a module - add in the current value - let current = m.getModValue(feature); - if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { - current = Math.round(current / 10) / 10; - } else if (featureDef.type === 'numeric') { - current /= 100; - } - const currentIsBeneficial = isValueBeneficial(feature, current); - effects.push( - - {translate(feature, grp)} - {lowerBound}{symbol} - {current}{symbol} - {upperBound}{symbol} - - ); - } else { - // We do not have a module, no value - effects.push( - - {translate(feature, grp)} - {lowerBound}{symbol} - {upperBound}{symbol} - - ); - } - } - } - if (m) { - // Because we have a module add in any benefits that aren't part of the primary blueprint - for (const feature in m.mods) { - if (!blueprint.features[feature]) { - const featureDef = Modifications.modifications[feature]; - if (featureDef && !featureDef.hidden) { - let symbol = ''; - if (feature === 'jitter') { - symbol = '°'; - } else if (featureDef.type === 'percentage') { - symbol = '%'; - } - let current = m.getModValue(feature); - if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { - current = Math.round(current / 10) / 10; - } else if (featureDef.type === 'numeric') { - current /= 100; - } - const currentIsBeneficial = isValueBeneficial(feature, current); - effects.push( - - {translate(feature, grp)} -   - {current}{symbol} -   - - ); - } - } - } - - // We also add in any benefits from specials that aren't covered above - if (m.blueprint && m.blueprint.special) { - for (const feature in Modifications.modifierActions[m.blueprint.special.edname]) { - if (!blueprint.features[feature] && !m.mods.feature) { - const featureDef = Modifications.modifications[feature]; - if (featureDef && !featureDef.hidden) { - let symbol = ''; - if (feature === 'jitter') { - symbol = '°'; - } else if (featureDef.type === 'percentage') { - symbol = '%'; - } - let current = m.getModValue(feature); - if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { - current = Math.round(current / 10) / 10; - } else if (featureDef.type === 'numeric') { - current /= 100; - } - const currentIsBeneficial = isValueBeneficial(feature, current); - effects.push( - - {translate(feature, grp)} -   - {current}{symbol} -   - - ); - } - } - } - } - } - - let components; - if (!m) { - components = []; - for (const component in blueprint.components) { - components.push( - - {translate(component)} - {blueprint.components[component]} - - ); - } - } - - let engineersList; - if (engineers) { - engineersList = []; - for (const engineer of engineers) { - engineersList.push( - - {engineer} - - ); - } - } - - return ( -
- - - - - - {m ? : null } - - - - - {effects} - -
{translate('feature')}{translate('worst')}{translate('current')}{translate('best')}
- { components ? - - - - - - - - {components} - -
{translate('component')}{translate('amount')}
: null } - { engineersList ? - - - - - - - {engineersList} - -
{translate('engineers')}
: null } -
- ); -} - -/** - * Is this blueprint feature beneficial? - * @param {string} feature The name of the feature - * @param {array} values The value of the feature - * @returns {boolean} True if this feature is beneficial - */ -export function isBeneficial(feature, values) { - const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0)); - if (Modifications.modifications[feature].higherbetter) { - return !fact; - } else { - return fact; - } -} - -/** - * Is this feature value beneficial? - * @param {string} feature The name of the feature - * @param {number} value The value of the feature - * @returns {boolean} True if this value is beneficial - */ -export function isValueBeneficial(feature, value) { - if (Modifications.modifications[feature].higherbetter) { - return value > 0; - } else { - return value < 0; - } -} - -/** - * Get a blueprint with a given name and an optional module - * @param {string} name The name of the blueprint - * @param {Object} module The module for which to obtain this blueprint - * @returns {Object} The matching blueprint - */ -export function getBlueprint(name, module) { - // Start with a copy of the blueprint - const findMod = val => Object.keys(Modifications.blueprints).find(elem => elem.toString().toLowerCase().search(val.toString().toLowerCase().replace(/(OutfittingFieldType_|persecond)/igm, '')) >= 0); - const found = Modifications.blueprints[findMod(name)]; - if (!found || !found.fdname) { - return {}; - } - const blueprint = JSON.parse(JSON.stringify(found)); - return blueprint; -} - -/** - * Provide 'percent' primary modifications - * @param {Object} ship The ship 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. - */ -export function setPercent(ship, m, percent) { - ship.clearModifications(m); - // Pick given value as multiplier - const mult = percent / 100; - const features = m.blueprint.grades[m.blueprint.grade].features; - for (const featureName in features) { - let value; - if (Modifications.modifications[featureName].higherbetter) { - // Higher is better, but is this making it better or worse? - if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { - value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult); - } else { - value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult); - } - } else { - // Higher is worse, but is this making it better or worse? - if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { - value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult); - } else { - value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult); - } - } - - _setValue(ship, m, featureName, value); - } -} - -/** - * Provide 'random' primary modifications - * @param {Object} ship The ship for which to perform the modifications - * @param {Object} m The module for which to perform the modifications - */ -export function setRandom(ship, m) { - // Pick a single value for our randomness - setPercent(ship, m, Math.random() * 100); -} - -/** - * Set a modification feature value - * @param {Object} ship The ship 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 {number} value The value being set for the feature - */ -function _setValue(ship, m, featureName, value) { - if (Modifications.modifications[featureName].type == 'percentage') { - ship.setModification(m, featureName, value * 10000); - } else if (Modifications.modifications[featureName].type == 'numeric') { - ship.setModification(m, featureName, value * 100); - } else { - ship.setModification(m, featureName, value); - } -} - -/** - * Provide 'percent' primary query - * @param {Object} m The module for which to perform the query - * @returns {Number} percent The percentage indicator of current applied values. - */ -export function getPercent(m) { - let result = null; - const features = m.blueprint.grades[m.blueprint.grade].features; - for (const featureName in features) { - if (features[featureName][0] === features[featureName][1]) { - continue; - } - - let value = _getValue(m, featureName); - let mult; - if (featureName == 'shieldboost') { - mult = ((1 + value) * (1 + m.shieldboost)) - 1 - m.shieldboost; - } else if (Modifications.modifications[featureName].higherbetter) { - // Higher is better, but is this making it better or worse? - 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); - } else { - mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100); - } - } else { - // Higher is worse, but is this making it better or worse? - 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); - } else { - mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100); - } - } - - if (result && result != mult) { - return null; - } else if (result != mult) { - result = mult; - } - } - - return result; -} - -/** - * Query a feature value - * @param {Object} m The module for which to perform the query - * @param {string} featureName The feature being queried - * @returns {number} The value of the modification as a % - */ -function _getValue(m, featureName) { - if (Modifications.modifications[featureName].type == 'percentage') { - return m.getModValue(featureName, true) / 10000; - } else if (Modifications.modifications[featureName].type == 'numeric') { - return m.getModValue(featureName, true) / 100; - } else { - return m.getModValue(featureName, true); - } -} +import React from 'react'; +import { Modifications } from 'coriolis-data/dist'; + +/** + * Generate a tooltip with details of a blueprint's specials + * @param {Object} translate The translate object + * @param {Object} blueprint The blueprint at the required grade + * @param {string} grp The group of the module + * @param {Object} m The module to compare with + * @param {string} specialName The name of the special + * @returns {Object} The react components + */ +export function specialToolTip(translate, blueprint, grp, m, specialName) { + const effects = []; + if (!blueprint || !blueprint.features) { + return undefined; + } + if (m) { + // We also add in any benefits from specials that aren't covered above + if (m.blueprint) { + for (const feature in Modifications.modifierActions[specialName]) { + // if (!blueprint.features[feature] && !m.mods.feature) { + const featureDef = Modifications.modifications[feature]; + if (featureDef && !featureDef.hidden) { + let symbol = ''; + if (feature === 'jitter') { + symbol = '°'; + } else if (featureDef.type === 'percentage') { + symbol = '%'; + } + let current = m.getModValue(feature) - m.getModValue(feature, true); + if (featureDef.type === 'percentage') { + current = Math.round(current / 10) / 10; + } else if (featureDef.type === 'numeric') { + current /= 100; + } + const currentIsBeneficial = isValueBeneficial(feature, current); + + effects.push( + + {translate(feature, grp)} +   + {current}{symbol} +   + + ); + } + } + } + } + + return ( +
+ + + {effects} + +
+
+ ); +} + +/** + * Generate a tooltip with details of a blueprint's effects + * @param {Object} translate The translate object + * @param {Object} blueprint The blueprint at the required grade + * @param {Array} engineers The engineers supplying this blueprint + * @param {string} grp The group of the module + * @param {Object} m The module to compare with + * @returns {Object} The react components + */ +export function blueprintTooltip(translate, blueprint, engineers, grp, m) { + const effects = []; + if (!blueprint || !blueprint.features) { + return undefined; + } + for (const feature in blueprint.features) { + const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]); + const featureDef = Modifications.modifications[feature]; + if (!featureDef.hidden) { + let symbol = ''; + if (feature === 'jitter') { + symbol = '°'; + } else if (featureDef.type === 'percentage') { + symbol = '%'; + } + let lowerBound = blueprint.features[feature][0]; + let upperBound = blueprint.features[feature][1]; + if (featureDef.type === 'percentage') { + lowerBound = Math.round(lowerBound * 1000) / 10; + upperBound = Math.round(upperBound * 1000) / 10; + } + const lowerIsBeneficial = isValueBeneficial(feature, lowerBound); + const upperIsBeneficial = isValueBeneficial(feature, upperBound); + if (m) { + // We have a module - add in the current value + let current = m.getModValue(feature); + if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { + current = Math.round(current / 10) / 10; + } else if (featureDef.type === 'numeric') { + current /= 100; + } + const currentIsBeneficial = isValueBeneficial(feature, current); + effects.push( + + {translate(feature, grp)} + {lowerBound}{symbol} + {current}{symbol} + {upperBound}{symbol} + + ); + } else { + // We do not have a module, no value + effects.push( + + {translate(feature, grp)} + {lowerBound}{symbol} + {upperBound}{symbol} + + ); + } + } + } + if (m) { + // Because we have a module add in any benefits that aren't part of the primary blueprint + for (const feature in m.mods) { + if (!blueprint.features[feature]) { + const featureDef = Modifications.modifications[feature]; + if (featureDef && !featureDef.hidden) { + let symbol = ''; + if (feature === 'jitter') { + symbol = '°'; + } else if (featureDef.type === 'percentage') { + symbol = '%'; + } + let current = m.getModValue(feature); + if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { + current = Math.round(current / 10) / 10; + } else if (featureDef.type === 'numeric') { + current /= 100; + } + const currentIsBeneficial = isValueBeneficial(feature, current); + effects.push( + + {translate(feature, grp)} +   + {current}{symbol} +   + + ); + } + } + } + + // We also add in any benefits from specials that aren't covered above + if (m.blueprint && m.blueprint.special) { + for (const feature in Modifications.modifierActions[m.blueprint.special.edname]) { + if (!blueprint.features[feature] && !m.mods.feature) { + const featureDef = Modifications.modifications[feature]; + if (featureDef && !featureDef.hidden) { + let symbol = ''; + if (feature === 'jitter') { + symbol = '°'; + } else if (featureDef.type === 'percentage') { + symbol = '%'; + } + let current = m.getModValue(feature); + if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { + current = Math.round(current / 10) / 10; + } else if (featureDef.type === 'numeric') { + current /= 100; + } + const currentIsBeneficial = isValueBeneficial(feature, current); + effects.push( + + {translate(feature, grp)} +   + {current}{symbol} +   + + ); + } + } + } + } + } + + let components; + if (!m) { + components = []; + for (const component in blueprint.components) { + components.push( + + {translate(component)} + {blueprint.components[component]} + + ); + } + } + + let engineersList; + if (engineers) { + engineersList = []; + for (const engineer of engineers) { + engineersList.push( + + {engineer} + + ); + } + } + + return ( +
+ + + + + + {m ? : null } + + + + + {effects} + +
{translate('feature')}{translate('worst')}{translate('current')}{translate('best')}
+ { components ? + + + + + + + + {components} + +
{translate('component')}{translate('amount')}
: null } + { engineersList ? + + + + + + + {engineersList} + +
{translate('engineers')}
: null } +
+ ); +} + +/** + * Is this blueprint feature beneficial? + * @param {string} feature The name of the feature + * @param {array} values The value of the feature + * @returns {boolean} True if this feature is beneficial + */ +export function isBeneficial(feature, values) { + const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0)); + if (Modifications.modifications[feature].higherbetter) { + return !fact; + } else { + return fact; + } +} + +/** + * Is this feature value beneficial? + * @param {string} feature The name of the feature + * @param {number} value The value of the feature + * @returns {boolean} True if this value is beneficial + */ +export function isValueBeneficial(feature, value) { + if (Modifications.modifications[feature].higherbetter) { + return value > 0; + } else { + return value < 0; + } +} + +/** + * Get a blueprint with a given name and an optional module + * @param {string} name The name of the blueprint + * @param {Object} module The module for which to obtain this blueprint + * @returns {Object} The matching blueprint + */ +export function getBlueprint(name, module) { + // Start with a copy of the blueprint + const findMod = val => Object.keys(Modifications.blueprints).find(elem => elem.toString().toLowerCase().search(val.toString().toLowerCase().replace(/(OutfittingFieldType_|persecond)/igm, '')) >= 0); + const found = Modifications.blueprints[findMod(name)]; + if (!found || !found.fdname) { + return {}; + } + const blueprint = JSON.parse(JSON.stringify(found)); + return blueprint; +} + +/** + * Provide 'percent' primary modifications + * @param {Object} ship The ship 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. + */ +export function setPercent(ship, m, percent) { + ship.clearModifications(m); + // Pick given value as multiplier + const mult = percent / 100; + const features = m.blueprint.grades[m.blueprint.grade].features; + for (const featureName in features) { + let value; + if (Modifications.modifications[featureName].higherbetter) { + // Higher is better, but is this making it better or worse? + if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { + value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult); + } else { + value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult); + } + } else { + // Higher is worse, but is this making it better or worse? + if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { + value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult); + } else { + value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult); + } + } + + _setValue(ship, m, featureName, value); + } +} + +/** + * Provide 'random' primary modifications + * @param {Object} ship The ship for which to perform the modifications + * @param {Object} m The module for which to perform the modifications + */ +export function setRandom(ship, m) { + // Pick a single value for our randomness + setPercent(ship, m, Math.random() * 100); +} + +/** + * Set a modification feature value + * @param {Object} ship The ship 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 {number} value The value being set for the feature + */ +function _setValue(ship, m, featureName, value) { + if (Modifications.modifications[featureName].type == 'percentage') { + ship.setModification(m, featureName, value * 10000); + } else if (Modifications.modifications[featureName].type == 'numeric') { + ship.setModification(m, featureName, value * 100); + } else { + ship.setModification(m, featureName, value); + } +} + +/** + * Provide 'percent' primary query + * @param {Object} m The module for which to perform the query + * @returns {Number} percent The percentage indicator of current applied values. + */ +export function getPercent(m) { + let result = null; + const features = m.blueprint.grades[m.blueprint.grade].features; + for (const featureName in features) { + if (features[featureName][0] === features[featureName][1]) { + continue; + } + + let value = _getValue(m, featureName); + let mult; + if (featureName == 'shieldboost') { + mult = ((1 + value) * (1 + m.shieldboost)) - 1 - m.shieldboost; + } else if (Modifications.modifications[featureName].higherbetter) { + // Higher is better, but is this making it better or worse? + 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); + } else { + mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100); + } + } else { + // Higher is worse, but is this making it better or worse? + 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); + } else { + mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100); + } + } + + if (result && result != mult) { + return null; + } else if (result != mult) { + result = mult; + } + } + + return result; +} + +/** + * Query a feature value + * @param {Object} m The module for which to perform the query + * @param {string} featureName The feature being queried + * @returns {number} The value of the modification as a % + */ +function _getValue(m, featureName) { + if (Modifications.modifications[featureName].type == 'percentage') { + return m.getModValue(featureName, true) / 10000; + } else if (Modifications.modifications[featureName].type == 'numeric') { + return m.getModValue(featureName, true) / 100; + } else { + return m.getModValue(featureName, true); + } +}