From 645da002ad7afa9dad5b3bb7e48615bd63c26eaa Mon Sep 17 00:00:00 2001 From: tmp64 Date: Wed, 6 Aug 2025 21:29:39 +0700 Subject: [PATCH 1/8] ChairManager: Log if config fails to save --- Src/ChairManager/ChairManager.cpp | 9 +++++++++ Src/ChairManager/ChairManager.h | 4 +--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Src/ChairManager/ChairManager.cpp b/Src/ChairManager/ChairManager.cpp index 88bde325..8c7c8e14 100644 --- a/Src/ChairManager/ChairManager.cpp +++ b/Src/ChairManager/ChairManager.cpp @@ -1024,6 +1024,15 @@ fs::path ChairManager::getConfigPath(std::string &modName) { fs::path ChairManager::getDefaultConfigPath(std::string &modName) { return GetGamePath() / "Mods" / modName / fs::u8path(modName + "_default.xml"); } + +void ChairManager::saveChairloaderConfigFile() +{ + bool result = ChairloaderConfigFile.save_file((GetGamePath().wstring() + L"/Mods/config/Chairloader.xml").c_str()); + + if (!result) + log(severityLevel::error, "Failed to save Chairloader config"); +} + bool ChairManager::LoadModInfoFile(fs::path directory, Mod *mod, bool allowDifferentDirectory) { std::string directoryName = directory.filename().u8string(); pugi::xml_document result; diff --git a/Src/ChairManager/ChairManager.h b/Src/ChairManager/ChairManager.h index b77fe85a..d5a431fe 100644 --- a/Src/ChairManager/ChairManager.h +++ b/Src/ChairManager/ChairManager.h @@ -176,9 +176,7 @@ class ChairManager final : public IChairManager { fs::path getDefaultConfigPath(std::string &modName); pugi::xml_document ChairloaderConfigFile, ChairManagerConfigFile; pugi::xml_node ModListNode; - inline bool saveChairloaderConfigFile() { - return ChairloaderConfigFile.save_file((GetGamePath().wstring() + L"/Mods/config/Chairloader.xml").c_str()); - }; + void saveChairloaderConfigFile(); inline bool saveModManagerConfigFile(){ return ChairManagerConfigFile.save_file(ChairManagerConfigPath.string().c_str()); } From d377a3914b9419c1d100c002cbcc11d452e6a404 Mon Sep 17 00:00:00 2001 From: tmp64 Date: Fri, 8 Aug 2025 20:00:19 +0700 Subject: [PATCH 2/8] ChairMerger: Fix finalization of nodes with text --- Src/ChairMerger/Private/XmlFinalizer3.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Src/ChairMerger/Private/XmlFinalizer3.cpp b/Src/ChairMerger/Private/XmlFinalizer3.cpp index b542546e..c02a2005 100644 --- a/Src/ChairMerger/Private/XmlFinalizer3.cpp +++ b/Src/ChairMerger/Private/XmlFinalizer3.cpp @@ -143,6 +143,9 @@ void XmlFinalizer3::FinalizeNode( for (pugi::xml_node childNode : node.children()) { + if (childNode.type() != pugi::node_element) + continue; + XmlErrorStack childErrorStack = errorStack.GetChild(childNode); childErrorStack.SetIndex(i); From cff60c69e079da45f5c706605a87d98242520874 Mon Sep 17 00:00:00 2001 From: tmp64 Date: Fri, 8 Aug 2025 20:01:14 +0700 Subject: [PATCH 3/8] Preditor: Assets: Fix wrong file being used as base --- Src/Preditor/Assets/Merging/Mergers/XmlAssetMerger.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/Preditor/Assets/Merging/Mergers/XmlAssetMerger.cpp b/Src/Preditor/Assets/Merging/Mergers/XmlAssetMerger.cpp index 745a427d..12a5b314 100644 --- a/Src/Preditor/Assets/Merging/Mergers/XmlAssetMerger.cpp +++ b/Src/Preditor/Assets/Merging/Mergers/XmlAssetMerger.cpp @@ -97,7 +97,7 @@ void Assets::XmlAssetMerger::DoMerge(const std::vector& inputFiles) CRY_ASSERT(!inputFiles.empty()); // First file is the base - baseDoc = ReadFile(*inputFiles.rbegin()); + baseDoc = ReadFile(*inputFiles.begin()); // Since it's already "merged", skip it inputFileIdx = 1; From 8cc5aa677f82e5df2d2d9146ad619dab8e381a0f Mon Sep 17 00:00:00 2001 From: tmp64 Date: Fri, 8 Aug 2025 20:01:35 +0700 Subject: [PATCH 4/8] MergingLibrary: Add EntitySpawnList --- .../Chairloader/Trainer/EntitySpawnList.xml | 64 +++++++++---------- .../Chairloader/Trainer/EntitySpawnList.xml | 7 ++ Data/XmlTypeLibrary.xml | 30 +++++++++ 3 files changed, 69 insertions(+), 32 deletions(-) create mode 100644 Data/MergingLibrary/Libs/Chairloader/Trainer/EntitySpawnList.xml diff --git a/Data/ChairloaderPatch/Libs/Chairloader/Trainer/EntitySpawnList.xml b/Data/ChairloaderPatch/Libs/Chairloader/Trainer/EntitySpawnList.xml index f275ffa3..a7082fb6 100644 --- a/Data/ChairloaderPatch/Libs/Chairloader/Trainer/EntitySpawnList.xml +++ b/Data/ChairloaderPatch/Libs/Chairloader/Trainer/EntitySpawnList.xml @@ -1,44 +1,44 @@ - - - - Mimic - Greater Mimic + + + + Mimic + Greater Mimic - - Normal - Thermal - Etheric - Voltaic + + Normal + Thermal + Etheric + Voltaic - Nightmare - Poltergeist - Telepath - Technopath + Nightmare + Poltergeist + Telepath + Technopath - - Medical - Engineering - Science - Military + + Medical + Engineering + Science + Military - Turret + Turret - - Wrench - Shotgun - Shotgun Ammo - Pistol - Pistol Ammo - Gloo Gun - Gloo Gun Ammo - Q Beam - Q Beam Ammo - Stun Gun - Stun Gun Ammo + + Wrench + Shotgun + Shotgun Ammo + Pistol + Pistol Ammo + Gloo Gun + Gloo Gun Ammo + Q Beam + Q Beam Ammo + Stun Gun + Stun Gun Ammo diff --git a/Data/MergingLibrary/Libs/Chairloader/Trainer/EntitySpawnList.xml b/Data/MergingLibrary/Libs/Chairloader/Trainer/EntitySpawnList.xml new file mode 100644 index 00000000..708ea66e --- /dev/null +++ b/Data/MergingLibrary/Libs/Chairloader/Trainer/EntitySpawnList.xml @@ -0,0 +1,7 @@ + + + + diff --git a/Data/XmlTypeLibrary.xml b/Data/XmlTypeLibrary.xml index a744253b..539e2b62 100644 --- a/Data/XmlTypeLibrary.xml +++ b/Data/XmlTypeLibrary.xml @@ -1826,5 +1826,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 1043b55baad8e810f58dd7687c9e063a26c8c565 Mon Sep 17 00:00:00 2001 From: tmp64 Date: Sat, 9 Aug 2025 14:21:13 +0700 Subject: [PATCH 5/8] ChairMerger: Fix merging if file is added by Chairloader --- Src/ChairMerger/Private/ChairMerger.cpp | 68 ++++++++++++++++--------- 1 file changed, 43 insertions(+), 25 deletions(-) diff --git a/Src/ChairMerger/Private/ChairMerger.cpp b/Src/ChairMerger/Private/ChairMerger.cpp index 63155354..628e9639 100644 --- a/Src/ChairMerger/Private/ChairMerger.cpp +++ b/Src/ChairMerger/Private/ChairMerger.cpp @@ -322,23 +322,41 @@ void ChairMerger::Merge() SetDeployPhase(DeployPhase::Merge); SetDeployStep(DeployStep::MergingMods); - m_pBaseFileCache = std::make_unique(); - m_pBaseFileCache->SetRootDir(m_PreyFilesPath); - - for (size_t i = 0; i < m_Mods.size(); i++) + try { - if (!m_ModXmlCaches[i]) + m_pBaseFileCache = std::make_unique(); + m_pBaseFileCache->SetRootDir(m_PreyFilesPath); + + for (size_t i = 0; i < m_Mods.size(); i++) { - m_pLog->Log(severityLevel::debug, "Skipping mod without Data: %s", m_Mods[i].modName); - continue; + if (!m_ModXmlCaches[i]) + { + m_pLog->Log(severityLevel::debug, "Skipping mod without Data: %s", m_Mods[i].modName); + continue; + } + + ProcessMod(i); + WaitForPendingTasks(); + m_pLog->Log(severityLevel::trace, "Finished merging mod: %s", m_Mods[i].modName); } - ProcessMod(i); - WaitForPendingTasks(); - m_pLog->Log(severityLevel::trace, "Finished merging mod: %s", m_Mods[i].modName); + FinalizeFiles(); + } + catch (const std::exception&) + { + // Save files to disk for debugging + try + { + m_pBaseFileCache->ExportModifiedFiles(m_OutputPath); + m_pBaseFileCache.reset(); + } + catch (const std::exception& e) + { + m_pLog->Log(severityLevel::error, "Failed to save modified files: %s", e.what()); + } + + throw; } - - FinalizeFiles(); // Save files to disk m_pBaseFileCache->ExportModifiedFiles(m_OutputPath); @@ -494,17 +512,6 @@ void ChairMerger::ProcessXMLFile( if (mod.type == EModType::Native) ResolveFileWildcards(mod, modDoc.first_child()); - // Load the original file - IXmlCache::ReadLock originalXmlLock; - const pugi::xml_document* originalDoc = nullptr; - - { - IXmlCache::EOpenResult result = m_pOriginalFileCache->TryOpenXmlForReading(relativePath, &originalDoc, originalXmlLock, parseTags); - - if (result != IXmlCache::EOpenResult::NotFound) - IXmlCache::ThrowForResult(result); - } - // if we have an original result, we can actually do some merging if (fileMergingPolicy.GetMethod() == FileMergingPolicy3::EMethod::Replace) { @@ -519,6 +526,17 @@ void ChairMerger::ProcessXMLFile( // Convert legacy mod into a Chairloader mod if (mod.type == EModType::Legacy) { + // Load the original file + IXmlCache::ReadLock originalXmlLock; + const pugi::xml_document* originalDoc = nullptr; + + { + IXmlCache::EOpenResult result = m_pOriginalFileCache->TryOpenXmlForReading(relativePath, &originalDoc, originalXmlLock, parseTags); + + if (result != IXmlCache::EOpenResult::NotFound) + IXmlCache::ThrowForResult(result); + } + // Move mod into a temp doc // modDoc will be replaced with converted mod pugi::xml_document legacyModDoc = std::move(modDoc); @@ -621,8 +639,8 @@ void ChairMerger::ProcessXMLFile( modDoc = std::move(legacyModDoc); } - // If original file doesn't exist, overwrite the base file. - if (!originalDoc) + // If base file is empty, overwrite the base file. + if (!baseDoc.first_child()) { // Replace the entire file baseDoc = std::move(modDoc); From bcd62be3c24510ace7ebba10d6f2f51a9f40c38b Mon Sep 17 00:00:00 2001 From: tmp64 Date: Sun, 14 Sep 2025 08:12:47 +0700 Subject: [PATCH 6/8] ChairManager: Return mod path from mod info --- Src/ChairManager/ChairManager.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Src/ChairManager/ChairManager.cpp b/Src/ChairManager/ChairManager.cpp index 8c7c8e14..4d91a1e5 100644 --- a/Src/ChairManager/ChairManager.cpp +++ b/Src/ChairManager/ChairManager.cpp @@ -2634,7 +2634,13 @@ fs::path ChairManager::GetConfigPath() fs::path ChairManager::GetModPath(const std::string& modName) { - return GetGamePath() / "Mods" / fs::u8path(modName); + for (const auto& i : GetMods()) + { + if (i.modName == modName) + return i.path; + } + + return std::string(); } std::vector ChairManager::GetModNames() From b18eb8a2b51c699c5c0678304089331dc26cf2c0 Mon Sep 17 00:00:00 2001 From: tmp64 Date: Sun, 14 Sep 2025 08:38:06 +0700 Subject: [PATCH 7/8] ChairMerger: Add test for custom file merge --- .../FullTestCustomFileMerge/Configs/Mod1.xml | 4 ++ .../Expected/Main/.chairloader | 0 .../Chairloader/Trainer/EntitySpawnList.xml | 55 +++++++++++++++++++ .../Expected/Main/UI/texture.dds | 1 + .../Chairloader/Trainer/EntitySpawnList.xml | 15 +++++ .../Chairloader/Trainer/EntitySpawnList.xml | 44 +++++++++++++++ Src/ChairMerger/Tests/ModMerging.cpp | 2 + 7 files changed, 121 insertions(+) create mode 100644 Data/Testing/ChairMerger/FullTestCustomFileMerge/Configs/Mod1.xml create mode 100644 Data/Testing/ChairMerger/FullTestCustomFileMerge/Expected/Main/.chairloader create mode 100644 Data/Testing/ChairMerger/FullTestCustomFileMerge/Expected/Main/Libs/Chairloader/Trainer/EntitySpawnList.xml create mode 100644 Data/Testing/ChairMerger/FullTestCustomFileMerge/Expected/Main/UI/texture.dds create mode 100644 Data/Testing/ChairMerger/FullTestCustomFileMerge/Mod1/Data/Libs/Chairloader/Trainer/EntitySpawnList.xml create mode 100644 Data/Testing/ChairMerger/_ChairloaderPatch/Libs/Chairloader/Trainer/EntitySpawnList.xml diff --git a/Data/Testing/ChairMerger/FullTestCustomFileMerge/Configs/Mod1.xml b/Data/Testing/ChairMerger/FullTestCustomFileMerge/Configs/Mod1.xml new file mode 100644 index 00000000..f9fdd14c --- /dev/null +++ b/Data/Testing/ChairMerger/FullTestCustomFileMerge/Configs/Mod1.xml @@ -0,0 +1,4 @@ + + + String from mod 1 + diff --git a/Data/Testing/ChairMerger/FullTestCustomFileMerge/Expected/Main/.chairloader b/Data/Testing/ChairMerger/FullTestCustomFileMerge/Expected/Main/.chairloader new file mode 100644 index 00000000..e69de29b diff --git a/Data/Testing/ChairMerger/FullTestCustomFileMerge/Expected/Main/Libs/Chairloader/Trainer/EntitySpawnList.xml b/Data/Testing/ChairMerger/FullTestCustomFileMerge/Expected/Main/Libs/Chairloader/Trainer/EntitySpawnList.xml new file mode 100644 index 00000000..7b7062e5 --- /dev/null +++ b/Data/Testing/ChairMerger/FullTestCustomFileMerge/Expected/Main/Libs/Chairloader/Trainer/EntitySpawnList.xml @@ -0,0 +1,55 @@ + + + + + Mimic + Greater Mimic + + + + Beanie + Bucket + Crown + Rice + Sombrero + Striped Hat + Top Hat + Witch Hat + + + + Normal + Thermal + Etheric + Voltaic + + + Nightmare + Poltergeist + Telepath + Technopath + + + + Medical + Engineering + Science + Military + + + Turret + + + Wrench + Shotgun + Shotgun Ammo + Pistol + Pistol Ammo + Gloo Gun + Gloo Gun Ammo + Q Beam + Q Beam Ammo + Stun Gun + Stun Gun Ammo + + diff --git a/Data/Testing/ChairMerger/FullTestCustomFileMerge/Expected/Main/UI/texture.dds b/Data/Testing/ChairMerger/FullTestCustomFileMerge/Expected/Main/UI/texture.dds new file mode 100644 index 00000000..e59d0d10 --- /dev/null +++ b/Data/Testing/ChairMerger/FullTestCustomFileMerge/Expected/Main/UI/texture.dds @@ -0,0 +1 @@ +I'm a texture! \ No newline at end of file diff --git a/Data/Testing/ChairMerger/FullTestCustomFileMerge/Mod1/Data/Libs/Chairloader/Trainer/EntitySpawnList.xml b/Data/Testing/ChairMerger/FullTestCustomFileMerge/Mod1/Data/Libs/Chairloader/Trainer/EntitySpawnList.xml new file mode 100644 index 00000000..5f19f6b9 --- /dev/null +++ b/Data/Testing/ChairMerger/FullTestCustomFileMerge/Mod1/Data/Libs/Chairloader/Trainer/EntitySpawnList.xml @@ -0,0 +1,15 @@ + + + + + Beanie + Bucket + Crown + Rice + Sombrero + Striped Hat + Top Hat + Witch Hat + + + diff --git a/Data/Testing/ChairMerger/_ChairloaderPatch/Libs/Chairloader/Trainer/EntitySpawnList.xml b/Data/Testing/ChairMerger/_ChairloaderPatch/Libs/Chairloader/Trainer/EntitySpawnList.xml new file mode 100644 index 00000000..a7082fb6 --- /dev/null +++ b/Data/Testing/ChairMerger/_ChairloaderPatch/Libs/Chairloader/Trainer/EntitySpawnList.xml @@ -0,0 +1,44 @@ + + + + + Mimic + Greater Mimic + + + + Normal + Thermal + Etheric + Voltaic + + + Nightmare + Poltergeist + Telepath + Technopath + + + + Medical + Engineering + Science + Military + + + Turret + + + Wrench + Shotgun + Shotgun Ammo + Pistol + Pistol Ammo + Gloo Gun + Gloo Gun Ammo + Q Beam + Q Beam Ammo + Stun Gun + Stun Gun Ammo + + diff --git a/Src/ChairMerger/Tests/ModMerging.cpp b/Src/ChairMerger/Tests/ModMerging.cpp index 32427f9c..e47238af 100644 --- a/Src/ChairMerger/Tests/ModMerging.cpp +++ b/Src/ChairMerger/Tests/ModMerging.cpp @@ -115,6 +115,7 @@ class ChairMergerTestFullMerging TEST_P(ChairMergerTestFullMerging, FullTest) { + CrySleep(5000); InitTest(GetParam()); LoadMods(); CreateMerger(); @@ -168,6 +169,7 @@ TEST_P(ChairMergerTestFullMerging, FullTest) } const auto FULL_TEST_NAMES = testing::Values( + "FullTestCustomFileMerge", "FullTestDllOnly", "FullTestMain", "FullTestLocalization", From 6e15e3db156d13fbbcfbd0d31cf8a870a2ae2b1b Mon Sep 17 00:00:00 2001 From: tmp64 Date: Sun, 14 Sep 2025 09:03:19 +0700 Subject: [PATCH 8/8] ChairMerger: Fix tests --- .../Chairloader/Trainer/EntitySpawnList.xml | 44 +++++++++++++++++++ .../Chairloader/Trainer/EntitySpawnList.xml | 44 +++++++++++++++++++ .../Chairloader/Trainer/EntitySpawnList.xml | 44 +++++++++++++++++++ .../Chairloader/Trainer/EntitySpawnList.xml | 44 +++++++++++++++++++ .../Chairloader/Trainer/EntitySpawnList.xml | 44 +++++++++++++++++++ 5 files changed, 220 insertions(+) create mode 100644 Data/Testing/ChairMerger/FullTestDllOnly/Expected/Main/Libs/Chairloader/Trainer/EntitySpawnList.xml create mode 100644 Data/Testing/ChairMerger/FullTestEverything/Expected/Main/Libs/Chairloader/Trainer/EntitySpawnList.xml create mode 100644 Data/Testing/ChairMerger/FullTestLevels/Expected/Main/Libs/Chairloader/Trainer/EntitySpawnList.xml create mode 100644 Data/Testing/ChairMerger/FullTestLocalization/Expected/Main/Libs/Chairloader/Trainer/EntitySpawnList.xml create mode 100644 Data/Testing/ChairMerger/FullTestMain/Expected/Main/Libs/Chairloader/Trainer/EntitySpawnList.xml diff --git a/Data/Testing/ChairMerger/FullTestDllOnly/Expected/Main/Libs/Chairloader/Trainer/EntitySpawnList.xml b/Data/Testing/ChairMerger/FullTestDllOnly/Expected/Main/Libs/Chairloader/Trainer/EntitySpawnList.xml new file mode 100644 index 00000000..64a92d35 --- /dev/null +++ b/Data/Testing/ChairMerger/FullTestDllOnly/Expected/Main/Libs/Chairloader/Trainer/EntitySpawnList.xml @@ -0,0 +1,44 @@ + + + + + Mimic + Greater Mimic + + + + Normal + Thermal + Etheric + Voltaic + + + Nightmare + Poltergeist + Telepath + Technopath + + + + Medical + Engineering + Science + Military + + + Turret + + + Wrench + Shotgun + Shotgun Ammo + Pistol + Pistol Ammo + Gloo Gun + Gloo Gun Ammo + Q Beam + Q Beam Ammo + Stun Gun + Stun Gun Ammo + + diff --git a/Data/Testing/ChairMerger/FullTestEverything/Expected/Main/Libs/Chairloader/Trainer/EntitySpawnList.xml b/Data/Testing/ChairMerger/FullTestEverything/Expected/Main/Libs/Chairloader/Trainer/EntitySpawnList.xml new file mode 100644 index 00000000..64a92d35 --- /dev/null +++ b/Data/Testing/ChairMerger/FullTestEverything/Expected/Main/Libs/Chairloader/Trainer/EntitySpawnList.xml @@ -0,0 +1,44 @@ + + + + + Mimic + Greater Mimic + + + + Normal + Thermal + Etheric + Voltaic + + + Nightmare + Poltergeist + Telepath + Technopath + + + + Medical + Engineering + Science + Military + + + Turret + + + Wrench + Shotgun + Shotgun Ammo + Pistol + Pistol Ammo + Gloo Gun + Gloo Gun Ammo + Q Beam + Q Beam Ammo + Stun Gun + Stun Gun Ammo + + diff --git a/Data/Testing/ChairMerger/FullTestLevels/Expected/Main/Libs/Chairloader/Trainer/EntitySpawnList.xml b/Data/Testing/ChairMerger/FullTestLevels/Expected/Main/Libs/Chairloader/Trainer/EntitySpawnList.xml new file mode 100644 index 00000000..64a92d35 --- /dev/null +++ b/Data/Testing/ChairMerger/FullTestLevels/Expected/Main/Libs/Chairloader/Trainer/EntitySpawnList.xml @@ -0,0 +1,44 @@ + + + + + Mimic + Greater Mimic + + + + Normal + Thermal + Etheric + Voltaic + + + Nightmare + Poltergeist + Telepath + Technopath + + + + Medical + Engineering + Science + Military + + + Turret + + + Wrench + Shotgun + Shotgun Ammo + Pistol + Pistol Ammo + Gloo Gun + Gloo Gun Ammo + Q Beam + Q Beam Ammo + Stun Gun + Stun Gun Ammo + + diff --git a/Data/Testing/ChairMerger/FullTestLocalization/Expected/Main/Libs/Chairloader/Trainer/EntitySpawnList.xml b/Data/Testing/ChairMerger/FullTestLocalization/Expected/Main/Libs/Chairloader/Trainer/EntitySpawnList.xml new file mode 100644 index 00000000..64a92d35 --- /dev/null +++ b/Data/Testing/ChairMerger/FullTestLocalization/Expected/Main/Libs/Chairloader/Trainer/EntitySpawnList.xml @@ -0,0 +1,44 @@ + + + + + Mimic + Greater Mimic + + + + Normal + Thermal + Etheric + Voltaic + + + Nightmare + Poltergeist + Telepath + Technopath + + + + Medical + Engineering + Science + Military + + + Turret + + + Wrench + Shotgun + Shotgun Ammo + Pistol + Pistol Ammo + Gloo Gun + Gloo Gun Ammo + Q Beam + Q Beam Ammo + Stun Gun + Stun Gun Ammo + + diff --git a/Data/Testing/ChairMerger/FullTestMain/Expected/Main/Libs/Chairloader/Trainer/EntitySpawnList.xml b/Data/Testing/ChairMerger/FullTestMain/Expected/Main/Libs/Chairloader/Trainer/EntitySpawnList.xml new file mode 100644 index 00000000..64a92d35 --- /dev/null +++ b/Data/Testing/ChairMerger/FullTestMain/Expected/Main/Libs/Chairloader/Trainer/EntitySpawnList.xml @@ -0,0 +1,44 @@ + + + + + Mimic + Greater Mimic + + + + Normal + Thermal + Etheric + Voltaic + + + Nightmare + Poltergeist + Telepath + Technopath + + + + Medical + Engineering + Science + Military + + + Turret + + + Wrench + Shotgun + Shotgun Ammo + Pistol + Pistol Ammo + Gloo Gun + Gloo Gun Ammo + Q Beam + Q Beam Ammo + Stun Gun + Stun Gun Ammo + +