- Keep LifeSagaSession across Clear; wipe on map load/clear with bind key - Soft-bias Prefer/cross-MC/cast and prefer roster MCs in aftermath/recover/park - Resolve war/plot principals plus discrete/WorldLog labels from live libraries - Amortize roster world scans, gate Relayout, and add hitch probe/save-slot helpers
221 lines
6.3 KiB
C#
221 lines
6.3 KiB
C#
using HarmonyLib;
|
|
|
|
namespace IdleSpectator;
|
|
|
|
/// <summary>Plot lifecycle patches from mutation discovery.</summary>
|
|
[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 "";
|
|
}
|
|
}
|