From 6c34a2627341badcc7c6155234798058849d7fe1 Mon Sep 17 00:00:00 2001 From: Alex Williams Date: Fri, 24 May 2024 18:23:04 +0100 Subject: [PATCH] Issue 754 imports need to be more graceful (#5) * Adds valid module checking to all types of modules on import * Changes as per comments on the PR --- src/app/components/AvailableModulesMenu.jsx | 27 ++++- src/app/components/HardpointSlot.jsx | 1 + src/app/components/InternalSlot.jsx | 1 + src/app/components/Slot.jsx | 15 ++- src/app/components/StandardSlot.jsx | 10 +- src/app/i18n/en.json | 4 + src/app/pages/ErrorDetails.jsx | 3 + src/app/shipyard/Module.js | 9 ++ src/app/utils/JournalUtils.js | 123 +++++++++++++++++--- 9 files changed, 167 insertions(+), 26 deletions(-) diff --git a/src/app/components/AvailableModulesMenu.jsx b/src/app/components/AvailableModulesMenu.jsx index 1ae53a44..e5f54030 100644 --- a/src/app/components/AvailableModulesMenu.jsx +++ b/src/app/components/AvailableModulesMenu.jsx @@ -217,16 +217,30 @@ export default class AvailableModulesMenu extends TranslatedComponent { if (categories.length === 1) { // Show category header instead of group header if (m && grp == m.grp) { - list.push(
this.groupElem = elem} key={category} + // If this is a missing module/weapon, skip it + if (m.grp == "mh" || m.grp == "mm"){ + continue; + } else { + list.push(
this.groupElem = elem} key={category} className={'select-category upp'}>{translate(category)}
); + } } else { - list.push(
{translate(category)}
); + if (category == "mh"){ + continue; + } else { + list.push(
{translate(category)}
); + } } } else { // Show category header as well as group header if (!categoryHeader) { - list.push(
{translate(category)}
); - categoryHeader = true; + if (category == "mh"){ + continue; + } + else { + list.push(
{translate(category)}
); + categoryHeader = true; + } } if (m && grp == m.grp) { list.push(
this.groupElem = elem} key={grp} @@ -302,6 +316,10 @@ export default class AvailableModulesMenu extends TranslatedComponent { let itemsOnThisRow = 0; for (let i = 0; i < sortedModules.length; i++) { let m = sortedModules[i]; + if (m.grp == 'mh' || m.grp == 'mm') { + // If this is a missing module, skip it + continue; + } let mount = null; let disabled = false; prevName = m.name; @@ -316,6 +334,7 @@ export default class AvailableModulesMenu extends TranslatedComponent { disabled = 1 <= ship.internal.filter(o => o.m && o.m.grp === 'mlc').length; } let active = mountedModule && mountedModule.id === m.id; + let classes = cn(m.name ? 'lc' : 'c', { warning: !disabled && warningFunc && warningFunc(m), active, diff --git a/src/app/components/HardpointSlot.jsx b/src/app/components/HardpointSlot.jsx index 724d9fd5..e9edda43 100644 --- a/src/app/components/HardpointSlot.jsx +++ b/src/app/components/HardpointSlot.jsx @@ -136,6 +136,7 @@ export default class HardpointSlot extends Slot { {showModuleResistances && m.getThermalResistance() ?
{translate('thermres')}: {formats.pct(m.getThermalResistance())}
: null} {m.getIntegrity() ?
{translate('integrity')}: {formats.int(m.getIntegrity())}
: null} + {m.getInfo() ?
{translate(m.getInfo())}
: null} {m && validMods.length > 0 ?
this.modButton = modButton}>
: null }
; diff --git a/src/app/components/Slot.jsx b/src/app/components/Slot.jsx index 8c04dbab..1594cad6 100644 --- a/src/app/components/Slot.jsx +++ b/src/app/components/Slot.jsx @@ -99,6 +99,7 @@ export default class Slot extends TranslatedComponent { let translate = language.translate; let { ship, m, enabled, dropClass, dragOver, onOpen, onChange, selected, eligible, onSelect, warning, availableModules } = this.props; let slotDetails, modificationsMarker, menu; + let missing = false; if (!selected) { // If not selected then sure that modifications flag is unset @@ -108,6 +109,11 @@ export default class Slot extends TranslatedComponent { if (m) { slotDetails = this._getSlotDetails(m, enabled, translate, language.formats, language.units); // Must be implemented by sub classes modificationsMarker = JSON.stringify(m); + if(typeof m.grp !== 'undefined' || m.grp !== null) { + if(m.grp == "mh" || m.grp == "mm") { + missing = true; + } + } } else { slotDetails =
{translate(eligible ? 'emptyrestricted' : 'empty')}
; modificationsMarker = ''; @@ -138,13 +144,16 @@ export default class Slot extends TranslatedComponent { } // TODO: implement touch dragging - + return (
this.slotDiv = slotDiv}> -
+ { + // If missing module/hardpoint, set the div container to warning status. + } +
{this._getMaxClassLabel(translate)}
{slotDetails} -
+
{menu}
); diff --git a/src/app/components/StandardSlot.jsx b/src/app/components/StandardSlot.jsx index 1e810982..09819009 100644 --- a/src/app/components/StandardSlot.jsx +++ b/src/app/components/StandardSlot.jsx @@ -93,6 +93,11 @@ export default class StandardSlot extends TranslatedComponent { this._modificationsSelected = false; } + // If this is a missing module, therefore has the 'info' field, set the warning value on the module to be true when loaded. + if (m.info) { + warning = () => true; + } + const modificationsMarker = JSON.stringify(m); if (selected) { @@ -124,7 +129,7 @@ export default class StandardSlot extends TranslatedComponent {
{m.grp == 'bh' ? m.name.charAt(0) : slot.maxClass}
-
{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? : null }
+
{classRating} {m.getInfo() ? translate(m.ukName) : translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? : null }
{formats.round(mass)}{units.T}
@@ -144,7 +149,8 @@ export default class StandardSlot extends TranslatedComponent { { showModuleResistances && m.getKineticResistance() ?
{translate('kinres')}: {formats.pct(m.getKineticResistance())}
: null } { showModuleResistances && m.getThermalResistance() ?
{translate('thermres')}: {formats.pct(m.getThermalResistance())}
: null } { m.getIntegrity() ?
{translate('integrity')}: {formats.int(m.getIntegrity())}
: null } - { validMods.length > 0 ?
this.modButton = modButton }>
: null } + { m.getInfo() ?
{translate(m.getInfo())}
: null } + { m.getInfo() ?
: validMods.length > 0 ?
this.modButton = modButton }>
: null }
diff --git a/src/app/i18n/en.json b/src/app/i18n/en.json index e232373f..db5c6e4f 100644 --- a/src/app/i18n/en.json +++ b/src/app/i18n/en.json @@ -83,6 +83,7 @@ "HELP_MODIFICATIONS_MENU": "Click on a number to enter a new value, or drag along the bar for small changes", "PHRASE_FAIL_EDENGINEER": "Failed to send to EDEngineer (Launch EDEngineer and make sure the API is started then refresh the page.)", "PHRASE_FIREFOX_EDENGINEER": "Sending to EDEngineer is not compatible with Firefox's security settings. Please try again with Chrome.", + "MISSING_MODULES": "Missing Modules", "am": "Auto Field-Maintenance Unit", "bh": "Bulkheads", "bl": "Beam Laser", @@ -109,6 +110,8 @@ "kw": "Kill Warrant Scanner", "ls": "Life Support", "mc": "Multi-cannon", + "mh": "Missing Weapon/Utility", + "mm": "Missing Module", "advmc": "Multi-cannon (Advanced)", "axmc": "AX Multi-cannon", "axmce": "AX Multi-cannon (Enhanced)", @@ -216,6 +219,7 @@ "boost interval": "Boost interval", "total": "Total", "ammo": "Ammunition maximum", + "info": "Info", "boot": "Boot time", "hacktime": "Hack time", "brokenregen": "Broken regeneration rate", diff --git a/src/app/pages/ErrorDetails.jsx b/src/app/pages/ErrorDetails.jsx index 5c68b8c9..4d96f93e 100644 --- a/src/app/pages/ErrorDetails.jsx +++ b/src/app/pages/ErrorDetails.jsx @@ -45,6 +45,9 @@ export default class ErrorDetails extends React.Component { return

Jameson, we have a problem..

{error.message}

+ Import Error handling has been improved, but still isn't perfect.
MOST Import failures are a result of missing modules in Coriolis,
OR incorrect import strings generated by third party apps. If you're seeing this page, we may have failed to handle the errors in your import correctly. Please see the data output below, specifically the 'scriptUrl:' section if it's there and then if you feel confident enough, please check the github issues page linked below and see if there is a similar issue already logged. If not, please create a new issue with the data below. If you're not confident, please ask for help on the Coriolis Channel of the EDCD Discord server. +
+

{importerror ?
If you are attempting to import a ship from EDDI or EDMC and are seeing a 'Z_BUF_ERROR' it means that the URL has not been provided correctly. This is a common problem when using Microsoft Internet Explorer or Microsoft Edge, and you should use another browser instead.
: null }
diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js index dbbdc09c..7df7f44e 100755 --- a/src/app/shipyard/Module.js +++ b/src/app/shipyard/Module.js @@ -439,6 +439,15 @@ export default class Module { return this.get('integrity', modified); } + /** + * Get the info of this module + * @param {Boolean} [modified=false] Whether to take modifications into account + * @return {String} the info of this module + */ + getInfo(modified = false) { + return (modified && this.getModValue('info')) || this.info; + } + /** * Get the mass of this module * @param {Boolean} [modified=true] Whether to take modifications into account diff --git a/src/app/utils/JournalUtils.js b/src/app/utils/JournalUtils.js index bb78993d..5b7cf0f5 100644 --- a/src/app/utils/JournalUtils.js +++ b/src/app/utils/JournalUtils.js @@ -6,6 +6,22 @@ import { Modules } from 'coriolis-data/dist'; import { Modifications } from 'coriolis-data/dist'; import { getBlueprint, setQualityCB } from './BlueprintFunctions'; +/** + * Check if an imported module is valid + * @param {Object} module the module to check + * @param {Object} moduleType the type of module to check + * @return {boolean} true if the module is valid + */ +function _isValidImportedModule(module, moduleType) { + // First of all, has the _moduleFromFdName function returned 'null'? + if (!module){ + return false + } + else { + return true + } +} + /** * Obtain a module given its FD Name * @param {string} fdname the FD Name of the module @@ -98,49 +114,90 @@ export function shipFromLoadoutJSON(json) { if (module.Engineering) _addModifications(ship.bulkheads.m, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect); break; case 'powerplant': - const powerplant = _moduleFromFdName(module.Item); + let powerplant = _moduleFromFdName(module.Item); + // Check the powerplant returned is valid + if (!_isValidImportedModule(powerplant, 'powerplant')) + { + powerplant = _moduleFromFdName('Int_Missing_Powerplant'); + module.Engineering = null; + } ship.use(ship.standard[0], powerplant, true); ship.standard[0].enabled = module.On; ship.standard[0].priority = module.Priority; if (module.Engineering) _addModifications(powerplant, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect); break; case 'mainengines': - const thrusters = _moduleFromFdName(module.Item); + let thrusters = _moduleFromFdName(module.Item); + // Check the thrusters returned is valid + if (!_isValidImportedModule(thrusters, 'thrusters')) + { + thrusters = _moduleFromFdName('Int_Missing_Engine'); + module.Engineering = null; + } ship.use(ship.standard[1], thrusters, true); ship.standard[1].enabled = module.On; ship.standard[1].priority = module.Priority; if (module.Engineering) _addModifications(thrusters, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect); break; case 'frameshiftdrive': - const frameshiftdrive = _moduleFromFdName(module.Item); + let frameshiftdrive = _moduleFromFdName(module.Item); + // Check the frameshiftdrive returned is valid + if (!_isValidImportedModule(frameshiftdrive, 'frameshiftdrive')) + { + frameshiftdrive = _moduleFromFdName('Int_Missing_Hyperdrive'); + module.Engineering = null; + } ship.use(ship.standard[2], frameshiftdrive, true); ship.standard[2].enabled = module.On; ship.standard[2].priority = module.Priority; if (module.Engineering) _addModifications(frameshiftdrive, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect); break; case 'lifesupport': - const lifesupport = _moduleFromFdName(module.Item); + let lifesupport = _moduleFromFdName(module.Item); + // Check the lifesupport returned is valid + if (!_isValidImportedModule(lifesupport, 'lifesupport')) + { + lifesupport = _moduleFromFdName('Int_Missing_LifeSupport'); + module.Engineering = null; + } ship.use(ship.standard[3], lifesupport, true); ship.standard[3].enabled = module.On === true; ship.standard[3].priority = module.Priority; if (module.Engineering) _addModifications(lifesupport, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect); break; case 'powerdistributor': - const powerdistributor = _moduleFromFdName(module.Item); + let powerdistributor = _moduleFromFdName(module.Item); + // Check the powerdistributor returned is valid + if (!_isValidImportedModule(powerdistributor, 'powerdistributor')) + { + powerdistributor = _moduleFromFdName('Int_Missing_PowerDistributor'); + module.Engineering = null; + } ship.use(ship.standard[4], powerdistributor, true); ship.standard[4].enabled = module.On; ship.standard[4].priority = module.Priority; if (module.Engineering) _addModifications(powerdistributor, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect); break; case 'radar': - const sensors = _moduleFromFdName(module.Item); + let sensors = _moduleFromFdName(module.Item); + // Check the sensors returned is valid + if (!_isValidImportedModule(sensors, 'sensors')) + { + sensors = _moduleFromFdName('Int_Missing_Sensors'); + module.Engineering = null; + } ship.use(ship.standard[5], sensors, true); ship.standard[5].enabled = module.On; ship.standard[5].priority = module.Priority; if (module.Engineering) _addModifications(sensors, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect); break; case 'fueltank': - const fueltank = _moduleFromFdName(module.Item); + let fueltank = _moduleFromFdName(module.Item); + // Check the fueltank returned is valid + if (!_isValidImportedModule(fueltank, 'fueltank')) + { + fueltank = _moduleFromFdName('Int_Missing_FuelTank'); + } ship.use(ship.standard[6], fueltank, true); ship.standard[6].enabled = true; ship.standard[6].priority = 0; @@ -170,10 +227,27 @@ export function shipFromLoadoutJSON(json) { // This can happen with old imports that don't contain new hardpoints } else { hardpoint = _moduleFromFdName(hardpointSlot.Item); - ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true); - ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On; - ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority; - modsToAdd.push({ coriolisMod: hardpoint, json: hardpointSlot }); + // Check the hardpoint module returned is valid + if (!_isValidImportedModule(hardpoint, 'hardpoint')){ + // Check if it's a Utility or Hardpoint + if (hardpointSlot.Slot.toLowerCase().search(/tiny/)) + { + // Use the missing_hardpoint module 'Missing Hardpoint' which will inform the user that the module is missing + hardpoint = _moduleFromFdName('Hpt_Missing_Hardpoint'); + } + else { + // Use the missing_hardpoint module 'Missing Utility' which will inform the user that the module is missing + hardpoint = _moduleFromFdName('Hpt_Missing_Utility'); + } + ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true); + ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On; + ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority; + } else { + ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true); + ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On; + ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority; + modsToAdd.push({ coriolisMod: hardpoint, json: hardpointSlot }); + } } hardpointArrayNum++; } @@ -187,13 +261,17 @@ export function shipFromLoadoutJSON(json) { continue; } const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name == 'Military' : false; + const isPlanetary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name == 'PlanetaryApproachSuite' : false; - // The internal slot might be a standard or a military slot. Military slots have a different naming system + // The internal slot might be a standard or a military slot, or a planetary slot. Military and Planetary slots have a different naming system let internalSlot = null; if (isMilitary) { const internalName = 'Military0' + militarySlotNum; internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase()); militarySlotNum++; + } else if (isPlanetary) { + const internalName = 'PlanetaryApproachSuite'; + internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase()); } else { // Slot numbers are not contiguous so handle skips. for (; internalSlot === null && internalSlotNum < 99; internalSlotNum++) { @@ -212,11 +290,22 @@ export function shipFromLoadoutJSON(json) { // This can happen with old imports that don't contain new slots } else { const internalJson = internalSlot; - const internal = _moduleFromFdName(internalJson.Item); - ship.use(ship.internal[i], internal, true); - ship.internal[i].enabled = internalJson.On === true; - ship.internal[i].priority = internalJson.Priority; - modsToAdd.push({ coriolisMod: internal, json: internalSlot }); + let internal = _moduleFromFdName(internalJson.Item); + // Check the internal module returned is valid + if (!_isValidImportedModule(internal, 'internal')) + { + internal = _moduleFromFdName('Int_Missing_Module'); + ship.use(ship.internal[i], internal, true); + ship.internal[i].enabled = internalJson.On === true; + ship.internal[i].priority = internalJson.Priority; + //throw 'Unknown internal module: "' + module.Item + '"'; + } + else { + ship.use(ship.internal[i], internal, true); + ship.internal[i].enabled = internalJson.On === true; + ship.internal[i].priority = internalJson.Priority; + modsToAdd.push({ coriolisMod: internal, json: internalSlot }); + } } }