Repository: RkShaRkz/ExoticaTechnologies
Pull request #46: Fix the real problem with HullmodExotics that was broken in #26 - installing on correct mods, and a few more bugfixes
Author: login: RkShaRkz
Created: 2025-04-19T21:00:09Z | Merged: 2025-04-19T22:32:05Z

Besides reverting the revert of commit that brought the biggest "value" (yet unfortunately also broke HullmodExotics) - eab0661 which brought conditional ExoticaTech hullmod adding/removing as well as HullmodExotic installation (to avoid wiping the module once removed)  and 726246a which introduced "limiting" to at most "exotica limit" on each module, this PR also figured out a decent workaround to resolve all the outlying problems with hullmod exotics:
- Installing AlphaSubcore on main module while some children modules are "full" of exotics should **not** install on full modules
- removing AlphaSubcore from main module while children have/don't have it installed should neither wipe them clean or mix up their exotics/upgrades
- i forgot the third issue but it's also been taken care of before.
- Installing/removing from "Exotica Technologies" colony screen works super-reliable, installing/removing from refit is flaky.

All in all, this PR finishes what I started out last august.


Diff:
diff --git a/src/exoticatechnologies/hullmods/exotics/HullmodExoticHandler.kt b/src/exoticatechnologies/hullmods/exotics/HullmodExoticHandler.kt
index 4ae12c3b..97a03234 100644
--- a/src/exoticatechnologies/hullmods/exotics/HullmodExoticHandler.kt
+++ b/src/exoticatechnologies/hullmods/exotics/HullmodExoticHandler.kt
@@ -580,6 +580,27 @@ object HullmodExoticHandler {
         }
     }
 
+    /**
+     * To avoid installing on the wrong list of [ShipModifications] from the [ShipModLoader], we'll use this method
+     * to try and pinch out the mods from the concrete variant by using [ShipModLoader.getFromVariant] - if no such
+     * mods are found, we'll fall back to [ShipModLoader.get]
+     *
+     * @return the found ship modifications list
+     *
+     * @see ShipModLoader.get
+     * @see ShipModLoader.getFromVariant
+     */
+    internal fun getCorrectMods(fleetMember: FleetMemberAPI, variant: ShipVariantAPI): ShipModifications? {
+        val fleetMemberMods = ShipModLoader.get(fleetMember, variant)
+        val variantMods = ShipModLoader.getFromVariant(variant)
+
+        return if (variantMods != null) {
+            variantMods
+        } else {
+            fleetMemberMods
+        }
+    }
+
     /**
      * Inner "class" holding certain "flows" which are a somewhat long list of steps/actions to perform, such as:
      * - checking and installing on all child modules' variants
@@ -658,7 +679,7 @@ object HullmodExoticHandler {
 
                     // Iterate through each slot, getting their variant and trying to install there
                     for (moduleVariant in childModuleVariants) {
-                        val mods = ShipModLoader.get(fleetMember, moduleVariant)
+                        val mods = getCorrectMods(fleetMember, moduleVariant)
                         logIfOverMinLogLevel("onInstall()\tmods: ${mods}", Level.INFO)
                         mods?.let { nonNullMods ->
                             val shouldInstallOnModuleVariant = HullmodExoticHandler.shouldInstallHullmodExoticToVariant(
@@ -668,9 +689,11 @@ object HullmodExoticHandler {
                                     variantList = Optional.of(allVariantsList),
                                     workMode = workMode
                             )
-                            onShouldCallback.execute(shouldInstallOnModuleVariant, moduleVariant)
-                            logIfOverMinLogLevel("onInstall()\tshouldInstallOnModuleVariant: ${shouldInstallOnModuleVariant}, variant: ${moduleVariant}", Level.INFO)
-                            if (shouldInstallOnModuleVariant) {
+                            val underExoticLimit = nonNullMods.isUnderExoticLimit(fleetMember)
+                            val shouldProceedWithInstallation = shouldInstallOnModuleVariant && underExoticLimit
+                            onShouldCallback.execute(shouldProceedWithInstallation, moduleVariant)
+                            logIfOverMinLogLevel("onInstall()\tshouldInstallOnModuleVariant: ${shouldInstallOnModuleVariant}, underExoticLimit: ${underExoticLimit}, variant: ${moduleVariant}", Level.INFO)
+                            if (shouldProceedWithInstallation) {
                                 // Lets try starting from the HullmodExoticHandler installation
                                 val installResult = HullmodExoticHandler.installHullmodExoticToVariant(
                                         hullmodExotic = hullmodExotic,
@@ -759,7 +782,7 @@ object HullmodExoticHandler {
                 }
 
                 // Now, get the mods
-                val mods = ShipModLoader.get(member, memberVariant)
+                val mods = getCorrectMods(member, memberVariant)
                 mods?.let { nonNullMods ->
                     val shouldInstallOnMemberVariant = HullmodExoticHandler.shouldInstallHullmodExoticToVariant(
                             hullmodExotic = hullmodExotic,
@@ -768,9 +791,11 @@ object HullmodExoticHandler {
                             variantList = variantListOptional,
                             workMode = workMode
                     )
-                    onShouldCallback.execute(shouldInstallOnMemberVariant, memberVariant)
-                    logIfOverMinLogLevel("onInstall()\tshouldInstallOnMemberVariant: ${shouldInstallOnMemberVariant}, variant: ${memberVariant}", Level.INFO)
-                    if (shouldInstallOnMemberVariant) {
+                    val underExoticLimit = nonNullMods.isUnderExoticLimit(member)
+                    val shouldProceedWithInstallation = shouldInstallOnMemberVariant && underExoticLimit
+                    onShouldCallback.execute(shouldProceedWithInstallation, memberVariant)
+                    logIfOverMinLogLevel("onInstall()\tshouldInstallOnMemberVariant: ${shouldInstallOnMemberVariant}, underExoticLimit: ${underExoticLimit}, variant: ${memberVariant}", Level.INFO)
+                    if (shouldProceedWithInstallation) {
                         val installResult = HullmodExoticHandler.installHullmodExoticToVariant(
                                 hullmodExotic = hullmodExotic,
                                 parentFleetMember = member,
@@ -828,7 +853,7 @@ object HullmodExoticHandler {
 
                 // If we have slots, start doing what needs to be done
                 for (installedOnVariant in installedOnVariantsList) {
-                    val mods = ShipModLoader.get(fleetMember, installedOnVariant)
+                    val mods = getCorrectMods(fleetMember, installedOnVariant)
                     mods?.let { nonNullMods ->
                         // Since this can be *any* HullmodExotic referencing their own ExoticHullmods, we should first
                         // check the ExoticHullmodLookup map for any instances of the exotic hullmod.
@@ -893,7 +918,7 @@ object HullmodExoticHandler {
                 }
 
                 // Carry on
-                val mods = ShipModLoader.get(fleetMember, fleetMemberVariant)
+                val mods = getCorrectMods(fleetMember, fleetMemberVariant)
                 mods?.let { nonNullMods ->
                     val shouldRemoveFromMemberVariant = HullmodExoticHandler.shouldRemoveHullmodExoticFromVariant(
                             hullmodExotic = hullmodExotic,
diff --git a/src/exoticatechnologies/modifications/ShipModLoader.kt b/src/exoticatechnologies/modifications/ShipModLoader.kt
index 3e67f6c9..4ff559a3 100644
--- a/src/exoticatechnologies/modifications/ShipModLoader.kt
+++ b/src/exoticatechnologies/modifications/ShipModLoader.kt
@@ -64,6 +64,11 @@ class ShipModLoader {
 
             return null
         }
+
+        @JvmStatic
+        fun getFromVariant(variant: ShipVariantAPI): ShipModifications? {
+            return VariantTagProvider.inst.getFromVariant(variant)
+        }
     }
 
     interface Provider {
@@ -75,4 +80,4 @@ class ShipModLoader {
             return true
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/exoticatechnologies/modifications/VariantTagProvider.kt b/src/exoticatechnologies/modifications/VariantTagProvider.kt
index 79cb07bc..1b32ce44 100644
--- a/src/exoticatechnologies/modifications/VariantTagProvider.kt
+++ b/src/exoticatechnologies/modifications/VariantTagProvider.kt
@@ -30,7 +30,7 @@ open class VariantTagProvider : ShipModLoader.Provider {
         }
 
         val cacheMods: ShipModifications? = cache.keys
-            .filter { it == member }
+            .filter { it == member && it.specId == member.specId }
             .firstNotNullOfOrNull { cache[it] }
 
         if (cacheMods != null) {
@@ -81,7 +81,8 @@ open class VariantTagProvider : ShipModLoader.Provider {
 
         if (exoticaTag != null) {
             val jsonStr = exoticaTag.replace(EXOTICA_INDICATOR, "")
-            return convertFromJson(jsonStr)
+            val mods = convertFromJson(jsonStr)
+            return mods
         }
 
         return null
@@ -103,4 +104,4 @@ open class VariantTagProvider : ShipModLoader.Provider {
             throw RuntimeException("Failed to save Exotica object.")
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/exoticatechnologies/modifications/exotics/impl/HullmodExotic.kt b/src/exoticatechnologies/modifications/exotics/impl/HullmodExotic.kt
index 4be9de6f..e95722ca 100644
--- a/src/exoticatechnologies/modifications/exotics/impl/HullmodExotic.kt
+++ b/src/exoticatechnologies/modifications/exotics/impl/HullmodExotic.kt
@@ -11,7 +11,6 @@ import exoticatechnologies.hullmods.exotics.ExoticHullmod
 import exoticatechnologies.hullmods.exotics.ExoticHullmodLookup
 import exoticatechnologies.hullmods.exotics.HullmodExoticHandler
 import exoticatechnologies.modifications.ShipModLoader
-import exoticatechnologies.modifications.ShipModLoader.Companion.get
 import exoticatechnologies.modifications.ShipModifications
 import exoticatechnologies.modifications.exotics.Exotic
 import exoticatechnologies.modifications.exotics.ExoticData
@@ -32,11 +31,11 @@ import java.awt.Color
  * It's a bridge between a concrete [Exotic] implementation and it's corresponding [ExoticHullmod]
  */
 open class HullmodExotic(
-    key: String,
-    settingsObj: JSONObject,
-    private val hullmodId: String,
-    private val statDescriptionKey: String,
-    override var color: Color,
+        key: String,
+        settingsObj: JSONObject,
+        private val hullmodId: String,
+        private val statDescriptionKey: String,
+        override var color: Color,
 ) : Exotic(key, settingsObj) {
     private val logger: Logger = Logger.getLogger(HullmodExotic::class.java)
     private val exoticHullmod: ExoticHullmod
@@ -206,20 +205,31 @@ open class HullmodExotic(
      */
     private fun unapplyExoticHullmodAndRemoveExoticaAndHullmod(member: FleetMemberAPI, moduleVariant: ShipVariantAPI, optionalMemberMods: Optional<ShipModifications>) {
         // If optional is there
-        if (optionalMemberMods.isPresent()) {
+        val hasAnyMods = if (optionalMemberMods.isPresent()) {
             val memberMods = optionalMemberMods.get()
             memberMods.removeExotic(this@HullmodExotic)
             ShipModLoader.set(member, moduleVariant, memberMods)
+
+            // And return if we have any mods (exotics or upgrades) left
+            memberMods.hasAnyModifications()
         } else {
             // Otherwise, extract them manually and use them
-            val memberMods = get(member, moduleVariant)
+            val memberMods = ShipModLoader.getFromVariant(moduleVariant)
             memberMods?.let { nonNullMods ->
                 nonNullMods.removeExotic(this@HullmodExotic)
-                ShipModLoader.set(member, moduleVariant, memberMods)
-            }
+                ShipModLoader.set(member, moduleVariant, nonNullMods)
+
+                // And return if we have any modifications (exotics or upgrades) left
+                nonNullMods.hasAnyModifications()
+            } ?: false
+            // In case we didn't have any non-null mods, let's default to 'false' for having more exotics
         }
         // And the shared common part for both that has nothing to do with the mods
-        ExoticaTechHM.addToFleetMember(member, moduleVariant)
+        // Remove "exoticatech" hullmod only if we ran out of exoticas on the member
+        if (hasAnyMods.not()) {
+            // If we don't have any more exotics, remove the "exoticatech" hullmod
+            ExoticaTechHM.addToFleetMember(member, moduleVariant)
+        }
         removeHullmodFromVariant(moduleVariant)
         // grab stats to use
         val stats = HullmodExoticHandler.getNonNullStatsToUse(member, moduleVariant)
@@ -255,36 +265,37 @@ open class HullmodExotic(
     }
 
     override fun applyExoticToStats(
-        id: String,
-        stats: MutableShipStatsAPI,
-        member: FleetMemberAPI,
-        mods: ShipModifications,
-        exoticData: ExoticData
+            id: String,
+            stats: MutableShipStatsAPI,
+            member: FleetMemberAPI,
+            mods: ShipModifications,
+            exoticData: ExoticData
     ) {
         onInstall(member)
     }
 
     override fun applyToShip(
-        id: String,
-        member: FleetMemberAPI,
-        ship: ShipAPI,
-        mods: ShipModifications,
-        exoticData: ExoticData
+            id: String,
+            member: FleetMemberAPI,
+            ship: ShipAPI,
+            mods: ShipModifications,
+            exoticData: ExoticData
     ) {
         onInstall(member)
     }
 
     override fun modifyToolTip(
-        tooltip: TooltipMakerAPI,
-        title: UIComponentAPI,
-        member: FleetMemberAPI,
-        mods: ShipModifications,
-        exoticData: ExoticData,
-        expand: Boolean
+            tooltip: TooltipMakerAPI,
+            title: UIComponentAPI,
+            member: FleetMemberAPI,
+            mods: ShipModifications,
+            exoticData: ExoticData,
+            expand: Boolean
     ) {
         if (expand) {
-            StringUtils.getTranslation(key, statDescriptionKey)
-                .addToTooltip(tooltip, title)
+            StringUtils
+                    .getTranslation(key, statDescriptionKey)
+                    .addToTooltip(tooltip, title)
         }
     }
 
@@ -302,15 +313,28 @@ open class HullmodExotic(
      * - and finally adds the ExoticaTech hullmod by calling [ExoticaTechHM.addToFleetMember]
      */
     private fun installThisHullmodExoticToFleetMembersVariant(member: FleetMemberAPI, moduleVariant: ShipVariantAPI, moduleVariantMods: ShipModifications) {
-        installHullmodOnVariant(moduleVariant)
+        val hasAnyMods = moduleVariantMods.hasAnyModifications()
 
         // This is the installWorkaround code
-        //TODO check if we have it first before adding
-        moduleVariantMods.putExotic(ExoticData(this@HullmodExotic.key))
+        if (moduleVariantMods.isUnderExoticLimit(member)) {
+            // Install the hullmod since we're under the exotic limit, this could have been done outside
+            // but somehow feels cleaner to do here
+            installHullmodOnVariant(moduleVariant)
+
+            // And proceed to install the HullmodExotic if we don't have it in our variant's ShipModifications
+            if (moduleVariantMods.hasExotic(this@HullmodExotic.key).not()) {
+                // Install the HullmodExotic into the ShipModifications moduleVariant's "mods"
+                moduleVariantMods.putExotic(ExoticData(this@HullmodExotic.key))
 
-        ShipModLoader.set(member, moduleVariant, moduleVariantMods)
-        // Install the exoticatech hullmod to show the thing we just installed
-        ExoticaTechHM.addToFleetMember(member, moduleVariant)
+                ShipModLoader.set(member, moduleVariant, moduleVariantMods)
+            }
+        } else {
+            logIfOverMinLogLevel("Bailing out - not installing HullmodExotic ${this} on module variant ${moduleVariant} due to already being at Max Exoticas Limit !!!", Level.ERROR)
+        }
+        // Install the exoticatech hullmod to show the thing we just installed - but only if we don't have mods alerady
+        if (hasAnyMods.not()) {
+            ExoticaTechHM.addToFleetMember(member, moduleVariant)
+        }
     }
 
     private fun logIfOverMinLogLevel(logMsg: String, logLevel: Level) {
diff --git a/src/exoticatechnologies/util/Extensions.kt b/src/exoticatechnologies/util/Extensions.kt
index 313c839c..105ed16f 100644
--- a/src/exoticatechnologies/util/Extensions.kt
+++ b/src/exoticatechnologies/util/Extensions.kt
@@ -662,7 +662,7 @@ fun List<String>.containsIgnoreCase(string: String?): Boolean {
 }
 
 /**
- * Method that returns a list of all child module variants belonging to the passed-in [fleetMemberAPI]
+ * Method that returns a sorted list of all child module variants belonging to the passed-in [fleetMemberAPI]
  *
  * **NOTE:** Does **not** include the root module's variant in the list.
  *
@@ -673,7 +673,7 @@ fun List<String>.containsIgnoreCase(string: String?): Boolean {
 fun getChildModuleVariantList(fleetMemberAPI: FleetMemberAPI): List<ShipVariantAPI> {
     val retVal = mutableListOf<ShipVariantAPI>()
     if (fleetMemberAPI.variant.moduleSlots == null || fleetMemberAPI.variant.moduleSlots.isEmpty()) return emptyList()
-    val moduleSlotList = fleetMemberAPI.variant.moduleSlots
+    val moduleSlotList = fleetMemberAPI.variant.moduleSlots.asSortedStringList()
     for (slot in moduleSlotList) {
         val moduleVariant = fleetMemberAPI.variant.getModuleVariant(slot)
         retVal.add(moduleVariant)
@@ -681,6 +681,15 @@ fun getChildModuleVariantList(fleetMemberAPI: FleetMemberAPI): List<ShipVariantA
     return retVal.toList()
 }
 
+/**
+ * Returns a sorted list of List<String>
+ */
+fun List<String>.asSortedStringList(): List<String> {
+    val temp = this.toMutableList()
+    temp.sort()
+    return temp.toList()
+}
+
 /**
  * Method that checks whether we're currently located in the Refit screen or not.
  */


Reviews:
user: login: RkShaRkz
body: LGTM-ish.

retest what needs to be retested though
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-19T21:03:34Z
user: login: RkShaRkz
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-19T21:14:05Z
user: login: RkShaRkz
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-19T21:35:23Z
user: login: RkShaRkz
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-19T22:11:50Z
user: login: RkShaRkz
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-19T22:12:06Z
user: login: RkShaRkz
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-19T22:12:11Z
user: login: RkShaRkz
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-19T22:13:06Z
user: login: RkShaRkz
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-19T22:13:15Z

Review comments:
diff_hunk: @@ -206,20 +205,32 @@ open class HullmodExotic(
      */
     private fun unapplyExoticHullmodAndRemoveExoticaAndHullmod(member: FleetMemberAPI, moduleVariant: ShipVariantAPI, optionalMemberMods: Optional<ShipModifications>) {
         // If optional is there
-        if (optionalMemberMods.isPresent()) {
+        val hasAnyMods = if (optionalMemberMods.isPresent()) {
             val memberMods = optionalMemberMods.get()
             memberMods.removeExotic(this@HullmodExotic)
             ShipModLoader.set(member, moduleVariant, memberMods)
+
+            // And return if we have any mods (exotics or upgrades) left
+            memberMods.hasAnyModifications()
         } else {
             // Otherwise, extract them manually and use them
-            val memberMods = get(member, moduleVariant)
+//            val memberMods = ShipModLoader.get(member, moduleVariant)   //TODO fix
path: src/exoticatechnologies/modifications/exotics/impl/HullmodExotic.kt
user: login: RkShaRkz
body: retest again. when everything works fine, get rid of this.
created_at: 2025-04-19T21:01:08Z
author_association: OWNER
diff_hunk: @@ -206,20 +205,32 @@ open class HullmodExotic(
      */
     private fun unapplyExoticHullmodAndRemoveExoticaAndHullmod(member: FleetMemberAPI, moduleVariant: ShipVariantAPI, optionalMemberMods: Optional<ShipModifications>) {
         // If optional is there
-        if (optionalMemberMods.isPresent()) {
+        val hasAnyMods = if (optionalMemberMods.isPresent()) {
             val memberMods = optionalMemberMods.get()
             memberMods.removeExotic(this@HullmodExotic)
             ShipModLoader.set(member, moduleVariant, memberMods)
+
+            // And return if we have any mods (exotics or upgrades) left
+            memberMods.hasAnyModifications()
         } else {
             // Otherwise, extract them manually and use them
-            val memberMods = get(member, moduleVariant)
+//            val memberMods = ShipModLoader.get(member, moduleVariant)   //TODO fix
+            val memberMods = ShipModLoader.getFromVariant(moduleVariant)
path: src/exoticatechnologies/modifications/exotics/impl/HullmodExotic.kt
user: login: RkShaRkz
body: then consider using `HullmodExoticHandler.getCorrectMods(member, moduleVariant)` here, however i remember that one not working(?).

needs two separate test runs.
created_at: 2025-04-19T21:01:46Z
author_association: OWNER
diff_hunk: @@ -302,15 +314,26 @@ open class HullmodExotic(
      * - and finally adds the ExoticaTech hullmod by calling [ExoticaTechHM.addToFleetMember]
      */
     private fun installThisHullmodExoticToFleetMembersVariant(member: FleetMemberAPI, moduleVariant: ShipVariantAPI, moduleVariantMods: ShipModifications) {
-        installHullmodOnVariant(moduleVariant)
+        val hasAnyMods = moduleVariantMods.hasAnyModifications()
 
         // This is the installWorkaround code
-        //TODO check if we have it first before adding
-        moduleVariantMods.putExotic(ExoticData(this@HullmodExotic.key))
+        if (moduleVariantMods.isUnderExoticLimit(member)) {
+            // Install the hullmod since we're under the exotic limit, this could have been done outside
+            // but somehow feels cleaner to do here
+            installHullmodOnVariant(moduleVariant)
+            if (moduleVariantMods.hasExotic(this@HullmodExotic.key).not()) {
path: src/exoticatechnologies/modifications/exotics/impl/HullmodExotic.kt
user: login: RkShaRkz
body: empty line somewhere in here
created_at: 2025-04-19T21:02:30Z
author_association: OWNER
diff_hunk: @@ -673,14 +673,23 @@ fun List<String>.containsIgnoreCase(string: String?): Boolean {
 fun getChildModuleVariantList(fleetMemberAPI: FleetMemberAPI): List<ShipVariantAPI> {
     val retVal = mutableListOf<ShipVariantAPI>()
     if (fleetMemberAPI.variant.moduleSlots == null || fleetMemberAPI.variant.moduleSlots.isEmpty()) return emptyList()
-    val moduleSlotList = fleetMemberAPI.variant.moduleSlots
+    var moduleSlotList = fleetMemberAPI.variant.moduleSlots.asSortedStringList()
path: src/exoticatechnologies/util/Extensions.kt
user: login: RkShaRkz
body: could be `val` again, since we're not doing anything here - it's already changed, sorted, then changed back to immutable list in the extension method.
created_at: 2025-04-19T21:03:17Z
author_association: OWNER
diff_hunk: @@ -206,20 +205,32 @@ open class HullmodExotic(
      */
     private fun unapplyExoticHullmodAndRemoveExoticaAndHullmod(member: FleetMemberAPI, moduleVariant: ShipVariantAPI, optionalMemberMods: Optional<ShipModifications>) {
         // If optional is there
-        if (optionalMemberMods.isPresent()) {
+        val hasAnyMods = if (optionalMemberMods.isPresent()) {
             val memberMods = optionalMemberMods.get()
             memberMods.removeExotic(this@HullmodExotic)
             ShipModLoader.set(member, moduleVariant, memberMods)
+
+            // And return if we have any mods (exotics or upgrades) left
+            memberMods.hasAnyModifications()
         } else {
             // Otherwise, extract them manually and use them
-            val memberMods = get(member, moduleVariant)
+//            val memberMods = ShipModLoader.get(member, moduleVariant)   //TODO fix
path: src/exoticatechnologies/modifications/exotics/impl/HullmodExotic.kt
user: login: RkShaRkz
body: everything works fine, deleting.
created_at: 2025-04-19T21:14:05Z
author_association: OWNER
diff_hunk: @@ -206,20 +205,32 @@ open class HullmodExotic(
      */
     private fun unapplyExoticHullmodAndRemoveExoticaAndHullmod(member: FleetMemberAPI, moduleVariant: ShipVariantAPI, optionalMemberMods: Optional<ShipModifications>) {
         // If optional is there
-        if (optionalMemberMods.isPresent()) {
+        val hasAnyMods = if (optionalMemberMods.isPresent()) {
             val memberMods = optionalMemberMods.get()
             memberMods.removeExotic(this@HullmodExotic)
             ShipModLoader.set(member, moduleVariant, memberMods)
+
+            // And return if we have any mods (exotics or upgrades) left
+            memberMods.hasAnyModifications()
         } else {
             // Otherwise, extract them manually and use them
-            val memberMods = get(member, moduleVariant)
+//            val memberMods = ShipModLoader.get(member, moduleVariant)   //TODO fix
+            val memberMods = ShipModLoader.getFromVariant(moduleVariant)
path: src/exoticatechnologies/modifications/exotics/impl/HullmodExotic.kt
user: login: RkShaRkz
body: Nope, as mentioned - it breaks uninstalling AlphaSubcore, the children modules are left with un-restored OP costs and have a lower OP instead of full.

should not change.
created_at: 2025-04-19T21:35:23Z
author_association: OWNER
diff_hunk: @@ -206,20 +205,32 @@ open class HullmodExotic(
      */
     private fun unapplyExoticHullmodAndRemoveExoticaAndHullmod(member: FleetMemberAPI, moduleVariant: ShipVariantAPI, optionalMemberMods: Optional<ShipModifications>) {
         // If optional is there
-        if (optionalMemberMods.isPresent()) {
+        val hasAnyMods = if (optionalMemberMods.isPresent()) {
             val memberMods = optionalMemberMods.get()
             memberMods.removeExotic(this@HullmodExotic)
             ShipModLoader.set(member, moduleVariant, memberMods)
+
+            // And return if we have any mods (exotics or upgrades) left
+            memberMods.hasAnyModifications()
         } else {
             // Otherwise, extract them manually and use them
-            val memberMods = get(member, moduleVariant)
+//            val memberMods = ShipModLoader.get(member, moduleVariant)   //TODO fix
+            val memberMods = ShipModLoader.getFromVariant(moduleVariant)
path: src/exoticatechnologies/modifications/exotics/impl/HullmodExotic.kt
user: login: RkShaRkz
body: nope, not touching this. it's flaky enough as it is from the Refit screen already.
created_at: 2025-04-19T22:11:50Z
author_association: OWNER
diff_hunk: @@ -206,20 +205,32 @@ open class HullmodExotic(
      */
     private fun unapplyExoticHullmodAndRemoveExoticaAndHullmod(member: FleetMemberAPI, moduleVariant: ShipVariantAPI, optionalMemberMods: Optional<ShipModifications>) {
         // If optional is there
-        if (optionalMemberMods.isPresent()) {
+        val hasAnyMods = if (optionalMemberMods.isPresent()) {
             val memberMods = optionalMemberMods.get()
             memberMods.removeExotic(this@HullmodExotic)
             ShipModLoader.set(member, moduleVariant, memberMods)
+
+            // And return if we have any mods (exotics or upgrades) left
+            memberMods.hasAnyModifications()
         } else {
             // Otherwise, extract them manually and use them
-            val memberMods = get(member, moduleVariant)
+//            val memberMods = ShipModLoader.get(member, moduleVariant)   //TODO fix
path: src/exoticatechnologies/modifications/exotics/impl/HullmodExotic.kt
user: login: RkShaRkz
body: resolving.
created_at: 2025-04-19T22:12:06Z
author_association: OWNER
diff_hunk: @@ -302,15 +314,26 @@ open class HullmodExotic(
      * - and finally adds the ExoticaTech hullmod by calling [ExoticaTechHM.addToFleetMember]
      */
     private fun installThisHullmodExoticToFleetMembersVariant(member: FleetMemberAPI, moduleVariant: ShipVariantAPI, moduleVariantMods: ShipModifications) {
-        installHullmodOnVariant(moduleVariant)
+        val hasAnyMods = moduleVariantMods.hasAnyModifications()
 
         // This is the installWorkaround code
-        //TODO check if we have it first before adding
-        moduleVariantMods.putExotic(ExoticData(this@HullmodExotic.key))
+        if (moduleVariantMods.isUnderExoticLimit(member)) {
+            // Install the hullmod since we're under the exotic limit, this could have been done outside
+            // but somehow feels cleaner to do here
+            installHullmodOnVariant(moduleVariant)
+            if (moduleVariantMods.hasExotic(this@HullmodExotic.key).not()) {
path: src/exoticatechnologies/modifications/exotics/impl/HullmodExotic.kt
user: login: RkShaRkz
body: resolving.
created_at: 2025-04-19T22:12:11Z
author_association: OWNER
diff_hunk: @@ -673,14 +673,23 @@ fun List<String>.containsIgnoreCase(string: String?): Boolean {
 fun getChildModuleVariantList(fleetMemberAPI: FleetMemberAPI): List<ShipVariantAPI> {
     val retVal = mutableListOf<ShipVariantAPI>()
     if (fleetMemberAPI.variant.moduleSlots == null || fleetMemberAPI.variant.moduleSlots.isEmpty()) return emptyList()
-    val moduleSlotList = fleetMemberAPI.variant.moduleSlots
+    var moduleSlotList = fleetMemberAPI.variant.moduleSlots.asSortedStringList()
path: src/exoticatechnologies/util/Extensions.kt
user: login: RkShaRkz
body: done.
created_at: 2025-04-19T22:13:06Z
author_association: OWNER
diff_hunk: @@ -673,14 +673,23 @@ fun List<String>.containsIgnoreCase(string: String?): Boolean {
 fun getChildModuleVariantList(fleetMemberAPI: FleetMemberAPI): List<ShipVariantAPI> {
     val retVal = mutableListOf<ShipVariantAPI>()
     if (fleetMemberAPI.variant.moduleSlots == null || fleetMemberAPI.variant.moduleSlots.isEmpty()) return emptyList()
-    val moduleSlotList = fleetMemberAPI.variant.moduleSlots
+    var moduleSlotList = fleetMemberAPI.variant.moduleSlots.asSortedStringList()
path: src/exoticatechnologies/util/Extensions.kt
user: login: RkShaRkz
body: resolving.
created_at: 2025-04-19T22:13:15Z
author_association: OWNER

PR comments:
user: login: RkShaRkz
created_at: 2025-04-19T22:27:02Z
body: Well... somehow, out of nowhere, a new problem.
Installing just AlphaSubcore on the mainmodule doesn't make the ExoticaTech hullmod show up on the child modules... *sigh*
author_association: OWNER
user: login: RkShaRkz
created_at: 2025-04-19T22:31:24Z
body: Hmm... Added NanotechArmor on child module, followed by AlphaSubcore from exoticatech colony menu on the main module... I couldn't get the child module to reset it's OP back to original...
author_association: OWNER