0.1.1
This commit is contained in:
@@ -0,0 +1,307 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using MelonLoader;
|
||||
using Il2CppScheduleOne.Growing;
|
||||
using Il2CppScheduleOne.ItemFramework;
|
||||
using Il2CppScheduleOne.ObjectScripts;
|
||||
|
||||
[assembly: MelonInfo(typeof(AbsorbentSoil.AbsorbentSoilMod), "Absorbent Soil", "0.1.1", "AttilaG")]
|
||||
[assembly: MelonGame(null, "Schedule I")]
|
||||
|
||||
namespace AbsorbentSoil
|
||||
{
|
||||
public sealed class AbsorbentSoilMod : MelonMod
|
||||
{
|
||||
internal static bool VerboseLogging = false;
|
||||
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
MelonLogger.Msg("Absorbent Soil loaded. Additive retention is active for long-life soils.");
|
||||
}
|
||||
|
||||
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.ClearAll();
|
||||
MelonLogger.Msg($"Cleared additive memory on scene load: {sceneName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class AdditiveMemory
|
||||
{
|
||||
private static readonly Dictionary<string, HashSet<string>> AdditivesByPotKey = new();
|
||||
|
||||
public static void Remember(Pot pot, string additiveId)
|
||||
{
|
||||
if (pot == null || string.IsNullOrWhiteSpace(additiveId))
|
||||
return;
|
||||
|
||||
string key = PotKeyHelper.GetPotKey(pot);
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
return;
|
||||
|
||||
if (!AdditivesByPotKey.TryGetValue(key, out HashSet<string> set))
|
||||
{
|
||||
set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
AdditivesByPotKey[key] = set;
|
||||
}
|
||||
|
||||
if (set.Add(additiveId) && AbsorbentSoilMod.VerboseLogging)
|
||||
MelonLogger.Msg($"Remembered additive '{additiveId}' for pot '{key}'.");
|
||||
}
|
||||
|
||||
public static IReadOnlyList<string> Get(Pot pot)
|
||||
{
|
||||
if (pot == null)
|
||||
return Array.Empty<string>();
|
||||
|
||||
string key = PotKeyHelper.GetPotKey(pot);
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
return Array.Empty<string>();
|
||||
|
||||
return AdditivesByPotKey.TryGetValue(key, out HashSet<string> set)
|
||||
? set.ToArray()
|
||||
: Array.Empty<string>();
|
||||
}
|
||||
|
||||
public static void Forget(Pot pot)
|
||||
{
|
||||
if (pot == null)
|
||||
return;
|
||||
|
||||
Forget(PotKeyHelper.GetPotKey(pot));
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
public static string GetPotKey(Pot pot)
|
||||
{
|
||||
if (pot == null)
|
||||
return string.Empty;
|
||||
|
||||
object guid = ReadMember(pot, "GUID") ?? ReadMember(pot, "Guid") ?? ReadMember(pot, "guid");
|
||||
if (guid != null && !string.IsNullOrWhiteSpace(guid.ToString()))
|
||||
return guid.ToString();
|
||||
|
||||
return $"scene:{pot.GetInstanceID()}";
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class SoilHelper
|
||||
{
|
||||
public static bool IsRetainingSoil(Pot pot)
|
||||
{
|
||||
string soilId = GetSoilId(pot);
|
||||
if (string.IsNullOrWhiteSpace(soilId))
|
||||
return false;
|
||||
|
||||
soilId = soilId.ToLowerInvariant();
|
||||
return soilId.Contains("longlifesoil") || soilId.Contains("extralonglifesoil");
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_suppressCapture)
|
||||
return;
|
||||
|
||||
if (__instance == null || __result == null || string.IsNullOrWhiteSpace(additiveID))
|
||||
return;
|
||||
|
||||
if (!SoilHelper.IsRetainingSoil(__instance))
|
||||
return;
|
||||
|
||||
AdditiveMemory.Remember(__instance, additiveID);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MelonLogger.Warning($"ApplyAdditive postfix failed: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReapplyWithoutRecapture(Pot pot, string additiveID)
|
||||
{
|
||||
if (pot == null || string.IsNullOrWhiteSpace(additiveID))
|
||||
return;
|
||||
|
||||
if (ApplyAdditiveMethod == null)
|
||||
{
|
||||
MelonLogger.Warning("Could not find Pot.ApplyAdditive via reflection.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_suppressCapture = true;
|
||||
ApplyAdditiveMethod.Invoke(pot, new object[] { additiveID, true });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MelonLogger.Warning($"Failed to reapply additive '{additiveID}': {ex}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_suppressCapture = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(Plant), nameof(Plant.Initialize))]
|
||||
internal static class Plant_Initialize_Patch
|
||||
{
|
||||
public static void Postfix(Plant __instance)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (__instance == null || __instance.Pot == null)
|
||||
return;
|
||||
|
||||
Pot pot = __instance.Pot;
|
||||
|
||||
if (!SoilHelper.IsRetainingSoil(pot))
|
||||
{
|
||||
AdditiveMemory.Forget(pot);
|
||||
return;
|
||||
}
|
||||
|
||||
IReadOnlyList<string> additiveIds = AdditiveMemory.Get(pot);
|
||||
if (additiveIds.Count == 0)
|
||||
return;
|
||||
|
||||
foreach (string additiveId in additiveIds)
|
||||
Pot_ApplyAdditive_Patch.ReapplyWithoutRecapture(pot, additiveId);
|
||||
|
||||
MelonLogger.Msg($"Reapplied {additiveIds.Count} retained additive(s) to new plant in pot '{PotKeyHelper.GetPotKey(pot)}'.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MelonLogger.Warning($"Plant.Initialize postfix failed: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(Pot), "OnPlantFullyHarvested")]
|
||||
internal static class Pot_OnPlantFullyHarvested_Patch
|
||||
{
|
||||
public static void Postfix(Pot __instance)
|
||||
{
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(Pot), "Destroy")]
|
||||
internal static class Pot_Destroy_Patch
|
||||
{
|
||||
public static void Prefix(Pot __instance)
|
||||
{
|
||||
try { AdditiveMemory.Forget(__instance); }
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user