import React from 'react'; import { Modifications, Modules, Ships } from 'coriolis-data/dist'; import Module from '../shipyard/Module'; import Ship from '../shipyard/Ship'; import { getBlueprint } from '../utils/BlueprintFunctions'; import * as ModuleUtils from '../shipyard/ModuleUtils'; // mapping from fd's ship model names to coriolis' export const SHIP_FD_NAME_TO_CORIOLIS_NAME = { 'Adder': 'adder', 'Anaconda': 'anaconda', 'Asp': 'asp', 'Asp_Scout': 'asp_scout', 'BelugaLiner': 'beluga', 'CobraMkIII': 'cobra_mk_iii', 'CobraMkIV': 'cobra_mk_iv', 'Cutter': 'imperial_cutter', 'DiamondBackXL': 'diamondback_explorer', 'DiamondBack': 'diamondback', 'Dolphin': 'dolphin', 'Eagle': 'eagle', 'Empire_Courier': 'imperial_courier', 'Empire_Eagle': 'imperial_eagle', 'Empire_Trader': 'imperial_clipper', 'Federation_Corvette': 'federal_corvette', 'Federation_Dropship': 'federal_dropship', 'Federation_Dropship_MkII': 'federal_assault_ship', 'Federation_Gunship': 'federal_gunship', 'FerDeLance': 'fer_de_lance', 'Hauler': 'hauler', 'Independant_Trader': 'keelback', 'Krait_MkII': 'krait_mkii', 'Mamba': 'mamba', 'Mandalay': 'mandalay', 'Krait_Light': 'krait_phantom', 'Orca': 'orca', 'Python': 'python', 'Python_nx': 'python_nx', 'SideWinder': 'sidewinder', 'Type6': 'type_6_transporter', 'Type7': 'type_7_transport', 'Type8': 'type_8_transport', 'Type9': 'type_9_heavy', 'Type9_Military': 'type_10_defender', 'TypeX': 'alliance_chieftain', 'TypeX_2': 'alliance_crusader', 'TypeX_3': 'alliance_challenger', 'Viper': 'viper', 'Viper_MkIV': 'viper_mk_iv', 'Vulture': 'vulture' }; // Mapping from hardpoint class to name in companion API export const HARDPOINT_NUM_TO_CLASS = { 0: 'Tiny', 1: 'Small', 2: 'Medium', 3: 'Large', 4: 'Huge' }; /** * Obtain a module given its ED ID * @param {Integer} edId the Elite ID of the module * @return {Module} the module */ function _moduleFromEdId(edId) { if (!edId) return null; // Check standard modules for (const grp in Modules.standard) { if (Modules.standard.hasOwnProperty(grp)) { for (const i in Modules.standard[grp]) { if (Modules.standard[grp][i].edID === edId) { // Found it return new Module({ template: Modules.standard[grp][i] }); } } } } // Check hardpoint modules for (const grp in Modules.hardpoints) { if (Modules.hardpoints.hasOwnProperty(grp)) { for (const i in Modules.hardpoints[grp]) { if (Modules.hardpoints[grp][i].edID === edId) { // Found it return new Module({ template: Modules.hardpoints[grp][i] }); } } } } // Check internal modules for (const grp in Modules.internal) { if (Modules.internal.hasOwnProperty(grp)) { for (const i in Modules.internal[grp]) { if (Modules.internal[grp][i].edID === edId) { // Found it return new Module({ template: Modules.internal[grp][i] }); } } } } // Not found return null; } /** * Obtain the model of a ship given its ED name * @param {string} edName the Elite name of the ship * @return {string} the Coriolis model of the ship */ function _shipModelFromEDName(edName) { return SHIP_FD_NAME_TO_CORIOLIS_NAME[Object.keys(SHIP_FD_NAME_TO_CORIOLIS_NAME).find(elem => elem.toLowerCase() === edName.toLowerCase())]; } /** * Obtain a ship's model from the companion API JSON * @param {object} json the companion API JSON * @return {string} the Coriolis model of the ship */ export function shipModelFromJson(json) { return _shipModelFromEDName(json.name || json.Ship); } /** * Build a ship from the companion API JSON * @param {object} json the companion API JSON * @return {Ship} the built ship */ export function shipFromJson(json) { // Start off building a basic ship const shipModel = shipModelFromJson(json); if (!shipModel) { throw 'No such ship found: "' + json.name + '"'; } const shipTemplate = Ships[shipModel]; let ship = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots); ship.buildWith(null); // Set the cargo hatch if (json.modules.CargoHatch) { ship.cargoHatch.enabled = json.modules.CargoHatch.module.on == true; ship.cargoHatch.priority = json.modules.CargoHatch.module.priority; } else { // We don't have any information on it so guess it's priority 5 and disabled ship.cargoHatch.enabled = false; ship.cargoHatch.priority = 4; } let rootModule; // Add the bulkheads const armourJson = json.modules.Armour.module; if (armourJson.name.toLowerCase().endsWith('_armour_grade1')) { ship.useBulkhead(0, true); } else if (armourJson.name.toLowerCase().endsWith('_armour_grade2')) { ship.useBulkhead(1, true); } else if (armourJson.name.toLowerCase().endsWith('_armour_grade3')) { ship.useBulkhead(2, true); } else if (armourJson.name.toLowerCase().endsWith('_armour_mirrored')) { ship.useBulkhead(3, true); } else if (armourJson.name.toLowerCase().endsWith('_armour_reactive')) { ship.useBulkhead(4, true); } else { throw 'Unknown bulkheads "' + armourJson.name + '"'; } ship.bulkheads.enabled = true; rootModule = json.modules.Armour; if (rootModule.WorkInProgress_modifications) _addModifications(ship.bulkheads.m, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel); // Add the standard modules // Power plant const powerplantJson = json.modules.PowerPlant.module; const powerplant = _moduleFromEdId(powerplantJson.id); rootModule = json.modules.PowerPlant; if (rootModule.WorkInProgress_modifications) _addModifications(powerplant, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel); ship.use(ship.standard[0], powerplant, true); ship.standard[0].enabled = powerplantJson.on === true; ship.standard[0].priority = powerplantJson.priority; // Thrusters const thrustersJson = json.modules.MainEngines.module; const thrusters = _moduleFromEdId(thrustersJson.id); rootModule = json.modules.MainEngines; if (rootModule.WorkInProgress_modifications) _addModifications(thrusters, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel); ship.use(ship.standard[1], thrusters, true); ship.standard[1].enabled = thrustersJson.on === true; ship.standard[1].priority = thrustersJson.priority; // FSD const frameshiftdriveJson = json.modules.FrameShiftDrive.module; const frameshiftdrive = _moduleFromEdId(frameshiftdriveJson.id); rootModule = json.modules.FrameShiftDrive; if (rootModule.WorkInProgress_modifications) _addModifications(frameshiftdrive, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel); ship.use(ship.standard[2], frameshiftdrive, true); ship.standard[2].enabled = frameshiftdriveJson.on === true; ship.standard[2].priority = frameshiftdriveJson.priority; // Life support const lifesupportJson = json.modules.LifeSupport.module; const lifesupport = _moduleFromEdId(lifesupportJson.id); rootModule = json.modules.LifeSupport; if (rootModule.WorkInProgress_modifications) _addModifications(lifesupport, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel); ship.use(ship.standard[3], lifesupport, true); ship.standard[3].enabled = lifesupportJson.on === true; ship.standard[3].priority = lifesupportJson.priority; // Power distributor const powerdistributorJson = json.modules.PowerDistributor.module; const powerdistributor = _moduleFromEdId(powerdistributorJson.id); rootModule = json.modules.PowerDistributor; if (rootModule.WorkInProgress_modifications) _addModifications(powerdistributor, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel); ship.use(ship.standard[4], powerdistributor, true); ship.standard[4].enabled = powerdistributorJson.on === true; ship.standard[4].priority = powerdistributorJson.priority; // Sensors const sensorsJson = json.modules.Radar.module; const sensors = _moduleFromEdId(sensorsJson.id); rootModule = json.modules.Radar; if (rootModule.WorkInProgress_modifications) _addModifications(sensors, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel); ship.use(ship.standard[5], sensors, true); ship.standard[5].enabled = sensorsJson.on === true; ship.standard[5].priority = sensorsJson.priority; // Fuel tank const fueltankJson = json.modules.FuelTank.module; const fueltank = _moduleFromEdId(fueltankJson.id); ship.use(ship.standard[6], fueltank, true); ship.standard[6].enabled = true; ship.standard[6].priority = 0; // Add hardpoints let hardpointClassNum = -1; let hardpointSlotNum = -1; let hardpointArrayNum = 0; for (let i in shipTemplate.slots.hardpoints) { if (shipTemplate.slots.hardpoints[i] === hardpointClassNum) { // Another slot of the same class hardpointSlotNum++; } else { // The first slot of a new class hardpointClassNum = shipTemplate.slots.hardpoints[i]; hardpointSlotNum = 1; } // Now that we know what we're looking for, find it const hardpointName = HARDPOINT_NUM_TO_CLASS[hardpointClassNum] + 'Hardpoint' + hardpointSlotNum; const hardpointSlot = json.modules[hardpointName]; if (!hardpointSlot) { // This can happen with old imports that don't contain new hardpoints } else if (!hardpointSlot.module) { // No module } else { const hardpointJson = hardpointSlot.module; const hardpoint = _moduleFromEdId(hardpointJson.id); rootModule = hardpointSlot; if (rootModule.WorkInProgress_modifications) _addModifications(hardpoint, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel, rootModule.specialModifications); ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true); ship.hardpoints[hardpointArrayNum].enabled = hardpointJson.on === true; ship.hardpoints[hardpointArrayNum].priority = hardpointJson.priority; } hardpointArrayNum++; } // Add internal compartments let internalSlotNum = 1; let militarySlotNum = 1; for (let i in shipTemplate.slots.internal) { const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name == 'Military' : false; // The internal slot might be a standard or a military slot. Military slots have a different naming system let internalSlot = null; if (isMilitary) { const internalName = 'Military0' + militarySlotNum; internalSlot = json.modules[internalName]; militarySlotNum++; } else { // Slot numbers are not contiguous so handle skips. while (internalSlot === null && internalSlotNum < 99) { // Slot sizes have no relationship to the actual size, either, so check all possibilities for (let slotsize = 0; slotsize < 9; slotsize++) { const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '') + internalSlotNum + '_Size' + slotsize; if (json.modules[internalName]) { internalSlot = json.modules[internalName]; break; } } internalSlotNum++; } } if (!internalSlot) { // This can happen with old imports that don't contain new slots } else if (!internalSlot.module) { // No module } else { const internalJson = internalSlot.module; const internal = _moduleFromEdId(internalJson.id); rootModule = internalSlot; if (rootModule.WorkInProgress_modifications) _addModifications(internal, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel); ship.use(ship.internal[i], internal, true); ship.internal[i].enabled = internalJson.on === true; ship.internal[i].priority = internalJson.priority; } } // Now update the ship's codes before returning it return ship.updatePowerPrioritesString().updatePowerEnabledString().updateModificationsString(); } /** * Add the modifications for a module * @param {Module} module the module * @param {Object} modifiers the modifiers * @param {Object} blueprint the blueprint of the modification * @param {Object} grade the grade of the modification * @param {Object} specialModifications special modification */ function _addModifications(module, modifiers, blueprint, grade, specialModifications) { if (!modifiers) return; let special; if (specialModifications) { special = Modifications.specials[Object.keys(specialModifications)[0]]; } for (const i in modifiers) { // Some special modifications if (modifiers[i].name === 'mod_weapon_clip_size_override') { // This is a numeric addition to the clip size, but we need to work it out in terms of being a percentage so // that it works the same as other modifications const origClip = module.clip || 1; module.setModValue('clip', ((modifiers[i].value - origClip) / origClip) * 10000); } else if (modifiers[i].name === 'mod_weapon_burst_size') { // This is an absolute number that acts as an override module.setModValue('burst', modifiers[i].value * 100); } else if (modifiers[i].name === 'mod_weapon_burst_rof') { // This is an absolute number that acts as an override module.setModValue('burstrof', modifiers[i].value * 100); } else if (modifiers[i].name === 'mod_weapon_falloffrange_from_range') { // Obtain the falloff value directly from the range module.setModValue('fallofffromrange', 1); } else if (modifiers[i].name && modifiers[i].name.startsWith('special_')) { // We don't add special effects directly, but keep a note of them so they can be added when fetching values special = Modifications.specials[modifiers[i].name]; } else { // Look up the modifiers to find what we need to do const modifierActions = Modifications.modifierActions[i]; let value; if (i === 'OutfittingFieldType_DefenceModifierShieldMultiplier') { value = modifiers[i].value - 1; } else if (i === 'OutfittingFieldType_DefenceModifierHealthMultiplier' && blueprint.startsWith('Armour_')) { value = (modifiers[i].value - module.hullboost) / module.hullboost; } else if (i === 'OutfittingFieldType_DefenceModifierHealthMultiplier') { value = modifiers[i].value / module.hullboost; } else if (i === 'OutfittingFieldType_RateOfFire') { value = (1 / Math.abs(modifiers[i].value)); } else { value = modifiers[i].value - 1; } // Carry out the required changes for (const action in modifierActions) { if (isNaN(modifierActions[action])) { module.setModValue(action, modifierActions[action]); } else { const actionValue = modifierActions[action] * value; let mod = module.getModValue(action) / 10000; if (!mod) { mod = 0; } module.setModValue(action, ((1 + mod) * (1 + actionValue) - 1) * 10000); } } } } // Add the blueprint definition, grade and special if (blueprint) { module.blueprint = getBlueprint(blueprint, module); if (grade) { module.blueprint.grade = Number(grade); } if (special) { module.blueprint.special = special; } } // Need to fix up a few items // Shield boosters are treated internally as straight modifiers, so rather than (for example) // being a 4% boost they are a 104% multiplier. Unfortunately this means that our % modification // is incorrect so we fix it if (module.grp === 'sb' && module.getModValue('shieldboost')) { const alteredBoost = (1 + module.shieldboost) * (module.getModValue('shieldboost') / 10000); module.setModValue('shieldboost', alteredBoost * 10000 / module.shieldboost); } // Shield booster resistance is actually a damage modifier, so needs to be inverted. if (module.grp === 'sb') { if (module.getModValue('explres')) { module.setModValue('explres', ((module.getModValue('explres') / 10000) * -1) * 10000); } if (module.getModValue('kinres')) { module.setModValue('kinres', ((module.getModValue('kinres') / 10000) * -1) * 10000); } if (module.getModValue('thermres')) { module.setModValue('thermres', ((module.getModValue('thermres') / 10000) * -1) * 10000); } } // Shield generator resistance is actually a damage modifier, so needs to be inverted. // In addition, the modification is based off the inherent resistance of the module if (ModuleUtils.isShieldGenerator(module.grp)) { if (module.getModValue('explres')) { module.setModValue('explres', ((1 - (1 - module.explres) * (1 + module.getModValue('explres') / 10000)) - module.explres) * 10000); } if (module.getModValue('kinres')) { module.setModValue('kinres', ((1 - (1 - module.kinres) * (1 + module.getModValue('kinres') / 10000)) - module.kinres) * 10000); } if (module.getModValue('thermres')) { module.setModValue('thermres', ((1 - (1 - module.thermres) * (1 + module.getModValue('thermres') / 10000)) - module.thermres) * 10000); } } // Hull reinforcement package resistance is actually a damage modifier, so needs to be inverted. // In addition, the modification is based off the inherent resistance of the module if (module.grp === 'hr') { if (module.getModValue('explres')) { module.setModValue('explres', ((1 - (1 - module.explres) * (1 + module.getModValue('explres') / 10000)) - module.explres) * 10000); } if (module.getModValue('kinres')) { module.setModValue('kinres', ((1 - (1 - module.kinres) * (1 + module.getModValue('kinres') / 10000)) - module.kinres) * 10000); } if (module.getModValue('thermres')) { module.setModValue('thermres', ((1 - (1 - module.thermres) * (1 + module.getModValue('thermres') / 10000)) - module.thermres) * 10000); } } // Bulkhead resistance is actually a damage modifier, so needs to be inverted. // In addition, the modification is based off the inherent resistance of the module if (module.grp == 'bh') { if (module.getModValue('explres')) { module.setModValue('explres', ((1 - (1 - module.explres) * (1 + module.getModValue('explres') / 10000)) - module.explres) * 10000); } if (module.getModValue('kinres')) { module.setModValue('kinres', ((1 - (1 - module.kinres) * (1 + module.getModValue('kinres') / 10000)) - module.kinres) * 10000); } if (module.getModValue('thermres')) { module.setModValue('thermres', ((1 - (1 - module.thermres) * (1 + module.getModValue('thermres') / 10000)) - module.thermres) * 10000); } } // Bulkhead boost is based off the inherent boost of the module if (module.grp == 'bh') { const alteredBoost = (1 + module.hullboost) * (1 + module.getModValue('hullboost') / 10000) - 1; module.setModValue('hullboost', (alteredBoost / module.hullboost - 1) * 1000); } // Jitter is an absolute number, so we need to divide it by 100 if (module.getModValue('jitter')) { module.setModValue('jitter', module.getModValue('jitter') / 100); } // Clip size is rounded up so that the result is a whole number if (module.getModValue('clip')) { const individual = 1 / (module.clip || 1); module.setModValue('clip', Math.ceil((module.getModValue('clip') / 10000) / individual) * individual * 10000); } }