using HarmonyLib; namespace IdleSpectator; /// Plot lifecycle patches from mutation discovery. [HarmonyPatch] public static class PlotEventPatches { [HarmonyPatch(typeof(PlotManager), nameof(PlotManager.newPlot))] [HarmonyPostfix] public static void PostfixNewPlot(Plot __result) { if (__result == null || !AgentHarness.LiveFeedsAllowed) { return; } PlotInterestFeed.EmitFromPlot(__result, "new"); try { LifeSagaMemory.RecordPlotMembers(__result, finished: false, provenance: "newPlot"); } catch { // ignore } } [HarmonyPatch(typeof(PlotManager), nameof(PlotManager.cancelPlot))] [HarmonyPostfix] public static void PostfixCancelPlot(Plot pPlot) { if (pPlot == null || !AgentHarness.LiveFeedsAllowed) { return; } PlotInterestFeed.EmitFromPlot(pPlot, "cancel"); try { LifeSagaMemory.RecordPlotMembers(pPlot, finished: true, provenance: "cancelPlot"); } catch { try { string plotKey = pPlot.getID().ToString(); LifeSagaMemory.ResolvePlotJoins(plotKey); } catch { // ignore } } } [HarmonyPatch(typeof(Actor), nameof(Actor.setPlot))] [HarmonyPostfix] public static void PostfixSetPlot(Actor __instance, Plot pObject) { if (__instance == null || !__instance.isAlive() || pObject == null || !AgentHarness.LiveFeedsAllowed) { return; } PlotInterestFeed.EmitFromPlot(pObject, "join"); try { // Joiner only - full member snapshot on new/finish/cancel. // Re-recording every co-member on each setPlot was O(joins^2) on busy plots. string plotKey = pObject.getID().ToString(); string assetId = ReadPlotAssetId(pObject); Actor author = null; try { author = pObject.getAuthor(); } catch { author = null; } Actor other = author != null && author != __instance ? author : null; LifeSagaMemory.RecordPlot( __instance, plotKey, assetId, finished: false, provenance: "setPlot", other: other); } catch { // ignore } } [HarmonyPatch(typeof(Actor), nameof(Actor.leavePlot))] [HarmonyPrefix] public static void PrefixLeavePlot(Actor __instance, out string __state) { __state = ""; if (!AgentHarness.LiveFeedsAllowed) { return; } try { object plot = __instance?.GetType().GetField("plot")?.GetValue(__instance) ?? __instance?.GetType().GetProperty("plot")?.GetValue(__instance, null); if (plot != null) { __state = ReadPlotAssetId(plot); try { string plotKey = ""; if (plot is Plot typed) { plotKey = typed.getID().ToString(); } if (!string.IsNullOrEmpty(plotKey)) { // Leaving resolves this actor's join; full cancel/finish still resolves all. LifeSagaMemory.RecordPlot( __instance, plotKey, __state, finished: true, provenance: "leavePlot"); } } catch { // ignore } } } catch { __state = ""; } } [HarmonyPatch(typeof(Actor), nameof(Actor.leavePlot))] [HarmonyPostfix] public static void PostfixLeavePlot(Actor __instance, string __state) { if (__instance == null || !__instance.isAlive() || string.IsNullOrEmpty(__state) || !AgentHarness.LiveFeedsAllowed) { return; } PlotInterestFeed.Emit(__state, __instance, "leave"); } [HarmonyPatch(typeof(Plot), nameof(Plot.finishPlot))] [HarmonyPostfix] public static void PostfixFinishPlot(Plot __instance, PlotState pState, Actor pActor) { if (__instance == null || pState != PlotState.Finished || !AgentHarness.LiveFeedsAllowed) { return; } // Prefer finishing actor; fall back to plot author inside EmitFromPlot. if (pActor != null && pActor.isAlive()) { string assetId = ReadPlotAssetId(__instance); if (string.IsNullOrEmpty(assetId)) { PlotInterestFeed.EmitFromPlot(__instance, "complete"); } else { PlotInterestFeed.Emit(assetId, pActor, "complete"); } } else { PlotInterestFeed.EmitFromPlot(__instance, "complete"); } try { LifeSagaMemory.RecordPlotMembers(__instance, finished: true, provenance: "finishPlot"); } catch { // ignore } } private static string ReadPlotAssetId(object plot) { try { object asset = plot.GetType().GetField("asset")?.GetValue(plot) ?? plot.GetType().GetProperty("asset")?.GetValue(plot, null) ?? plot.GetType().GetMethod("getAsset")?.Invoke(plot, null) ?? plot.GetType().GetField("plot_asset")?.GetValue(plot); if (asset != null) { object id = asset.GetType().GetField("id")?.GetValue(asset) ?? asset.GetType().GetProperty("id")?.GetValue(asset, null); if (id != null) { return id.ToString(); } } } catch { // ignore } return ""; } }