diff --git a/AbsorbentSoilMod.cs b/AbsorbentSoil/AbsorbentSoilMod.cs similarity index 63% rename from AbsorbentSoilMod.cs rename to AbsorbentSoil/AbsorbentSoilMod.cs index 4fb8f14..936251a 100644 --- a/AbsorbentSoilMod.cs +++ b/AbsorbentSoil/AbsorbentSoilMod.cs @@ -4,12 +4,11 @@ using System.Linq; using System.Reflection; using HarmonyLib; using MelonLoader; -using ScheduleOne.Growing; -using ScheduleOne.ItemFramework; -using ScheduleOne.ObjectScripts; -using UnityEngine; +using Il2CppScheduleOne.Growing; +using Il2CppScheduleOne.ItemFramework; +using Il2CppScheduleOne.ObjectScripts; -[assembly: MelonInfo(typeof(AbsorbentSoil.AbsorbentSoilMod), "Absorbent Soil", "0.1.0", "AttilaG")] +[assembly: MelonInfo(typeof(AbsorbentSoil.AbsorbentSoilMod), "Absorbent Soil", "0.1.1", "AttilaG")] [assembly: MelonGame(null, "Schedule I")] namespace AbsorbentSoil @@ -23,16 +22,15 @@ namespace AbsorbentSoil MelonLogger.Msg("Absorbent Soil loaded. Additive retention is active for long-life soils."); } - // This keeps slot/session bleed low if the player backs out and loads another save without restarting. - // It does NOT persist anything to disk. It only tracks pots currently seen in the running game session. public override void OnSceneWasLoaded(int buildIndex, string sceneName) { + // Prevent save-slot/session bleed if the player returns to menu and loads another save. if (!string.IsNullOrWhiteSpace(sceneName) && (sceneName.IndexOf("menu", StringComparison.OrdinalIgnoreCase) >= 0 || sceneName.IndexOf("main", StringComparison.OrdinalIgnoreCase) >= 0 || sceneName.IndexOf("load", StringComparison.OrdinalIgnoreCase) >= 0)) { - AdditiveMemory.Clear(); + AdditiveMemory.ClearAll(); MelonLogger.Msg($"Cleared additive memory on scene load: {sceneName}"); } } @@ -80,12 +78,22 @@ namespace AbsorbentSoil if (pot == null) return; - string key = PotKeyHelper.GetPotKey(pot); - if (!string.IsNullOrWhiteSpace(key)) - AdditivesByPotKey.Remove(key); + Forget(PotKeyHelper.GetPotKey(pot)); } - public static void Clear() => AdditivesByPotKey.Clear(); + public static void Forget(string potKey) + { + if (string.IsNullOrWhiteSpace(potKey)) + return; + + if (AdditivesByPotKey.Remove(potKey) && AbsorbentSoilMod.VerboseLogging) + MelonLogger.Msg($"Cleared retained additives for pot '{potKey}'."); + } + + public static void ClearAll() + { + AdditivesByPotKey.Clear(); + } } internal static class PotKeyHelper @@ -95,13 +103,10 @@ namespace AbsorbentSoil if (pot == null) return string.Empty; - // Most Schedule I buildable/grid items have a GUID inherited from a base class. - // Use reflection so this survives minor EA API shifts. object guid = ReadMember(pot, "GUID") ?? ReadMember(pot, "Guid") ?? ReadMember(pot, "guid"); if (guid != null && !string.IsNullOrWhiteSpace(guid.ToString())) return guid.ToString(); - // Fallback for testing only. Not stable across reloads, but better than hard failing. return $"scene:{pot.GetInstanceID()}"; } @@ -111,92 +116,6 @@ namespace AbsorbentSoil return null; Type type = instance.GetType(); - - PropertyInfo prop = AccessTools.Property(type, name); - if (prop != null) - { - try { return prop.GetValue(instance); } catch { } - } - - FieldInfo field = AccessTools.Field(type, name); - if (field != null) - { - try { return field.GetValue(instance); } catch { } - } - - return null; - } - } - - internal static class SoilHelper - { - // Adjust these after checking the actual IDs/names from item definitions or Melon logs. - // The helper checks IDs/names case-insensitively and allows partial matches. - private static readonly string[] RetainingSoilTokens = - { - "longlife", - "long_life", - "long-life", - "long life", - "extralonglife", - "extra_long_life", - "extra-long-life", - "extra long life" - }; - - public static bool IsRetainingSoil(Pot pot) - { - if (pot == null) - return false; - - // In current Pot.cs, soil identity is probably on GrowContainer/PotData/base state rather than Pot itself. - // Try several likely member names so the mod remains resilient to EA renames. - foreach (string memberName in new[] - { - "SoilID", "soilID", "SoilId", "soilId", - "SoilDefinition", "soilDefinition", "SoilItem", "soilItem", - "Soil", "soil", "CurrentSoil", "currentSoil" - }) - { - object value = ReadMemberRecursive(pot, memberName); - if (MatchesRetainingSoil(value)) - return true; - } - - // Last resort: inspect chunks/transforms/names. This is intentionally broad but only affects whether - // additives are remembered/reapplied; if it is too broad, tighten RetainingSoilTokens above. - string potName = SafeLower(pot.name); - if (ContainsAnyToken(potName)) - return true; - - return false; - } - - private static bool MatchesRetainingSoil(object value) - { - if (value == null) - return false; - - if (ContainsAnyToken(SafeLower(value.ToString()))) - return true; - - foreach (string memberName in new[] { "ID", "Id", "Name", "name", "ItemID", "itemID", "Definition", "definition" }) - { - object nested = ReadMemberRecursive(value, memberName); - if (nested != null && ContainsAnyToken(SafeLower(nested.ToString()))) - return true; - } - - return false; - } - - private static object ReadMemberRecursive(object instance, string name) - { - if (instance == null || string.IsNullOrWhiteSpace(name)) - return null; - - Type type = instance.GetType(); - while (type != null) { PropertyInfo prop = AccessTools.Property(type, name); @@ -216,22 +135,61 @@ namespace AbsorbentSoil return null; } + } - private static bool ContainsAnyToken(string text) + internal static class SoilHelper + { + public static bool IsRetainingSoil(Pot pot) { - if (string.IsNullOrWhiteSpace(text)) + string soilId = GetSoilId(pot); + if (string.IsNullOrWhiteSpace(soilId)) return false; - return RetainingSoilTokens.Any(token => text.Contains(token)); + soilId = soilId.ToLowerInvariant(); + return soilId.Contains("longlifesoil") || soilId.Contains("extralonglifesoil"); } - private static string SafeLower(string value) => value == null ? string.Empty : value.ToLowerInvariant(); + public static string GetSoilId(Pot pot) + { + if (pot == null) + return string.Empty; + + object soilId = ReadMember(pot, "SoilID") ?? ReadMember(pot, "soilID") ?? ReadMember(pot, "SoilId") ?? ReadMember(pot, "soilId"); + return soilId?.ToString() ?? string.Empty; + } + + private static object ReadMember(object instance, string name) + { + if (instance == null || string.IsNullOrWhiteSpace(name)) + return null; + + Type type = instance.GetType(); + while (type != null) + { + PropertyInfo prop = AccessTools.Property(type, name); + if (prop != null) + { + try { return prop.GetValue(instance); } catch { } + } + + FieldInfo field = AccessTools.Field(type, name); + if (field != null) + { + try { return field.GetValue(instance); } catch { } + } + + type = type.BaseType; + } + + return null; + } } [HarmonyPatch(typeof(Pot), "ApplyAdditive")] internal static class Pot_ApplyAdditive_Patch { private static bool _suppressCapture; + private static readonly MethodInfo ApplyAdditiveMethod = AccessTools.Method(typeof(Pot), "ApplyAdditive"); public static void Postfix(Pot __instance, AdditiveDefinition __result, string additiveID, bool isInitialApplication) { @@ -259,8 +217,7 @@ namespace AbsorbentSoil if (pot == null || string.IsNullOrWhiteSpace(additiveID)) return; - MethodInfo applyAdditive = AccessTools.Method(typeof(Pot), "ApplyAdditive"); - if (applyAdditive == null) + if (ApplyAdditiveMethod == null) { MelonLogger.Warning("Could not find Pot.ApplyAdditive via reflection."); return; @@ -269,7 +226,7 @@ namespace AbsorbentSoil try { _suppressCapture = true; - applyAdditive.Invoke(pot, new object[] { additiveID, true }); + ApplyAdditiveMethod.Invoke(pot, new object[] { additiveID, true }); } catch (Exception ex) { @@ -293,8 +250,12 @@ namespace AbsorbentSoil return; Pot pot = __instance.Pot; + if (!SoilHelper.IsRetainingSoil(pot)) + { + AdditiveMemory.Forget(pot); return; + } IReadOnlyList additiveIds = AdditiveMemory.Get(pot); if (additiveIds.Count == 0) @@ -317,8 +278,20 @@ namespace AbsorbentSoil { public static void Postfix(Pot __instance) { - // Intentionally do nothing. This patch exists as a reminder that retained additives should survive harvest. - // Do NOT clear AdditiveMemory here, because long-life/extra-long-life soil should carry additives forward. + try + { + if (__instance == null) + return; + + // After the game's harvest logic runs, if soil was consumed/removed or is no longer retaining soil, + // clear the retained additives so freshly re-poured soil starts clean. + if (!SoilHelper.IsRetainingSoil(__instance)) + AdditiveMemory.Forget(__instance); + } + catch (Exception ex) + { + MelonLogger.Warning($"OnPlantFullyHarvested postfix failed: {ex}"); + } } } @@ -327,7 +300,6 @@ namespace AbsorbentSoil { public static void Prefix(Pot __instance) { - // If the pot itself is destroyed/sold, stop tracking its additives. try { AdditiveMemory.Forget(__instance); } catch { } } diff --git a/AbsorbentSoil/bin/Release/net6.0/AbsorbentSoil.pdb b/AbsorbentSoil/bin/Release/net6.0/AbsorbentSoil.pdb index d181b8d..4e949ea 100644 Binary files a/AbsorbentSoil/bin/Release/net6.0/AbsorbentSoil.pdb and b/AbsorbentSoil/bin/Release/net6.0/AbsorbentSoil.pdb differ diff --git a/AbsorbentSoil/obj/Release/net6.0/AbsorbentSoil.AssemblyInfo.cs b/AbsorbentSoil/obj/Release/net6.0/AbsorbentSoil.AssemblyInfo.cs index 972d1a6..9d98a04 100644 --- a/AbsorbentSoil/obj/Release/net6.0/AbsorbentSoil.AssemblyInfo.cs +++ b/AbsorbentSoil/obj/Release/net6.0/AbsorbentSoil.AssemblyInfo.cs @@ -13,7 +13,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("AbsorbentSoil")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Release")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+1374b2e929263849b9362bc2032e73ac19079563")] [assembly: System.Reflection.AssemblyProductAttribute("AbsorbentSoil")] [assembly: System.Reflection.AssemblyTitleAttribute("AbsorbentSoil")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/AbsorbentSoil/obj/Release/net6.0/AbsorbentSoil.AssemblyInfoInputs.cache b/AbsorbentSoil/obj/Release/net6.0/AbsorbentSoil.AssemblyInfoInputs.cache index 9e8ac38..de68f8b 100644 --- a/AbsorbentSoil/obj/Release/net6.0/AbsorbentSoil.AssemblyInfoInputs.cache +++ b/AbsorbentSoil/obj/Release/net6.0/AbsorbentSoil.AssemblyInfoInputs.cache @@ -1 +1 @@ -54ce364b284ea9f7c7e326b3938578be61e76fb507bfb64c59c1c67e28888f74 +c2488a0a4e0149be33c85611dadce8edca6655bd5fdedcb3c238565f85b2d0ec diff --git a/AbsorbentSoil/obj/Release/net6.0/AbsorbentSoil.dll b/AbsorbentSoil/obj/Release/net6.0/AbsorbentSoil.dll index a199e3e..454561f 100644 Binary files a/AbsorbentSoil/obj/Release/net6.0/AbsorbentSoil.dll and b/AbsorbentSoil/obj/Release/net6.0/AbsorbentSoil.dll differ diff --git a/AbsorbentSoil/obj/Release/net6.0/AbsorbentSoil.pdb b/AbsorbentSoil/obj/Release/net6.0/AbsorbentSoil.pdb index d181b8d..4e949ea 100644 Binary files a/AbsorbentSoil/obj/Release/net6.0/AbsorbentSoil.pdb and b/AbsorbentSoil/obj/Release/net6.0/AbsorbentSoil.pdb differ diff --git a/AbsorbentSoil/obj/Release/net6.0/ref/AbsorbentSoil.dll b/AbsorbentSoil/obj/Release/net6.0/ref/AbsorbentSoil.dll index bf946c7..d376aeb 100644 Binary files a/AbsorbentSoil/obj/Release/net6.0/ref/AbsorbentSoil.dll and b/AbsorbentSoil/obj/Release/net6.0/ref/AbsorbentSoil.dll differ diff --git a/AbsorbentSoil/obj/Release/net6.0/refint/AbsorbentSoil.dll b/AbsorbentSoil/obj/Release/net6.0/refint/AbsorbentSoil.dll index bf946c7..d376aeb 100644 Binary files a/AbsorbentSoil/obj/Release/net6.0/refint/AbsorbentSoil.dll and b/AbsorbentSoil/obj/Release/net6.0/refint/AbsorbentSoil.dll differ