worldbox-observer-mod/IdleSpectator/ActorActivityPatches.cs
DazedAnon 90d5b1604f Enhance dossier interactions and improve Lore click handling.
- Add related unit tracking for activity logs and dossiers
- Implement colorized names for related units in reason lines
- Update harness scenarios to validate new interactions
- Increment version to 0.26.1 in mod.json
2026-07-17 13:36:33 -05:00

284 lines
8.4 KiB
C#

using ai.behaviours;
using HarmonyLib;
namespace IdleSpectator;
/// <summary>
/// Feeds <see cref="ActivityLog"/> from task changes and curated behaviour beats.
/// Only patch Beh types that declare <c>execute</c> - Harmony fails with "method null"
/// when the method is inherited-only (e.g. BehSocializeTalk).
/// </summary>
[HarmonyPatch]
public static class ActorActivityPatches
{
[HarmonyPatch(typeof(Actor), nameof(Actor.setTask))]
[HarmonyPostfix]
public static void PostfixSetTask(Actor __instance, string pTaskId)
{
if (__instance == null || string.IsNullOrEmpty(pTaskId))
{
return;
}
ActivityLog.NoteTask(__instance, pTaskId);
}
[HarmonyPatch(typeof(BehPollinate), nameof(BehPollinate.execute))]
[HarmonyPostfix]
public static void PostfixPollinate(Actor pActor, BehResult __result)
{
if (pActor == null || __result == BehResult.Stop)
{
return;
}
string obj = ActivityInterestTable.PlaceOrObjectLabel(pActor);
if (string.IsNullOrEmpty(obj))
{
obj = "flower";
}
ActivityLog.NoteActionBeat(
pActor,
"BehPollinate",
"Pollinates " + obj,
actorTarget: "",
placeHint: obj);
}
[HarmonyPatch(typeof(BehUnloadResources), nameof(BehUnloadResources.execute))]
[HarmonyPrefix]
public static void PrefixUnload(Actor pActor, out bool __state)
{
__state = false;
try
{
__state = pActor != null && pActor.isCarryingResources();
}
catch
{
__state = false;
}
}
[HarmonyPatch(typeof(BehUnloadResources), nameof(BehUnloadResources.execute))]
[HarmonyPostfix]
public static void PostfixUnload(Actor pActor, bool __state)
{
if (pActor == null || !__state)
{
return;
}
string carrying = ActivityInterestTable.TryGetCarryingLabel(pActor);
string fact = string.IsNullOrEmpty(carrying)
? "Unloads resources"
: "Unloads " + carrying;
// Place comes from live context / city - never as a companion target.
ActivityLog.NoteActionBeat(pActor, "BehUnloadResources", fact);
}
[HarmonyPatch(typeof(BehAttackActorHuntingTarget), nameof(BehAttackActorHuntingTarget.execute))]
[HarmonyPostfix]
public static void PostfixHuntAttack(Actor pActor, BehResult __result)
{
if (pActor == null)
{
return;
}
string actorTarget = ActivityInterestTable.ActorTargetName(pActor);
ActivityLog.NoteActionBeat(
pActor,
"BehAttackActorHuntingTarget",
string.IsNullOrEmpty(actorTarget) ? "Hunts prey" : "Hunts " + actorTarget,
actorTarget: actorTarget,
relatedId: ActivityInterestTable.ActorTargetId(pActor));
}
[HarmonyPatch(typeof(BehCityActorRemoveFire), nameof(BehCityActorRemoveFire.execute))]
[HarmonyPostfix]
public static void PostfixRemoveFire(Actor pActor, BehResult __result)
{
if (pActor == null)
{
return;
}
ActivityLog.NoteActionBeat(
pActor,
"BehCityActorRemoveFire",
"Fights fire",
actorTarget: "",
placeHint: "blaze");
}
[HarmonyPatch(typeof(BehTryToSocialize), nameof(BehTryToSocialize.execute))]
[HarmonyPostfix]
public static void PostfixTrySocialize(Actor pActor, BehResult __result)
{
if (pActor == null || __result == BehResult.Stop)
{
return;
}
string actorTarget = ActivityInterestTable.ActorTargetName(pActor);
ActivityLog.NoteActionBeat(
pActor,
"BehTryToSocialize",
string.IsNullOrEmpty(actorTarget) ? "Tries to socialize" : "Tries to socialize with " + actorTarget,
actorTarget: actorTarget,
relatedId: ActivityInterestTable.ActorTargetId(pActor));
}
[HarmonyPatch(typeof(BehPlantCrops), nameof(BehPlantCrops.execute))]
[HarmonyPostfix]
public static void PostfixPlantCrops(Actor pActor, BehResult __result)
{
if (pActor == null || __result == BehResult.Stop)
{
return;
}
ActivityLog.NoteActionBeat(pActor, "BehPlantCrops", "Plants crops");
}
[HarmonyPatch(typeof(BehCityActorFertilizeCrop), nameof(BehCityActorFertilizeCrop.execute))]
[HarmonyPostfix]
public static void PostfixFertilize(Actor pActor, BehResult __result)
{
if (pActor == null || __result == BehResult.Stop)
{
return;
}
ActivityLog.NoteActionBeat(pActor, "BehCityActorFertilizeCrop", "Fertilizes crops");
}
[HarmonyPatch(typeof(BehThrowResources), nameof(BehThrowResources.execute))]
[HarmonyPrefix]
public static void PrefixThrow(Actor pActor, out bool __state)
{
__state = false;
try
{
__state = pActor != null && pActor.isCarryingResources();
}
catch
{
__state = false;
}
}
[HarmonyPatch(typeof(BehThrowResources), nameof(BehThrowResources.execute))]
[HarmonyPostfix]
public static void PostfixThrow(Actor pActor, bool __state)
{
if (pActor == null || !__state)
{
return;
}
string carrying = ActivityInterestTable.TryGetCarryingLabel(pActor);
string fact = string.IsNullOrEmpty(carrying)
? "Throws resources"
: "Throws " + carrying;
ActivityLog.NoteActionBeat(pActor, "BehThrowResources", fact);
}
[HarmonyPatch(typeof(BehBuildTarget), nameof(BehBuildTarget.execute))]
[HarmonyPostfix]
public static void PostfixBuild(Actor pActor, BehResult __result)
{
if (pActor == null || __result == BehResult.Stop)
{
return;
}
string obj = ActivityInterestTable.PlaceOrObjectLabel(pActor);
ActivityLog.NoteActionBeat(
pActor,
"BehBuildTarget",
string.IsNullOrEmpty(obj) ? "Works a build" : "Builds " + obj,
actorTarget: "",
placeHint: obj);
}
[HarmonyPatch(typeof(BehPoopOutside), nameof(BehPoopOutside.execute))]
[HarmonyPostfix]
public static void PostfixPoopOutside(Actor pActor, BehResult __result)
{
if (pActor == null || __result == BehResult.Stop)
{
return;
}
ActivityLog.NoteActionBeat(pActor, "BehPoopOutside", "Private moment outdoors");
}
[HarmonyPatch(typeof(BehPoopInside), nameof(BehPoopInside.execute))]
[HarmonyPostfix]
public static void PostfixPoopInside(Actor pActor, BehResult __result)
{
if (pActor == null || __result == BehResult.Stop)
{
return;
}
ActivityLog.NoteActionBeat(pActor, "BehPoopInside", "Private moment indoors");
}
[HarmonyPatch(typeof(BehBoatFishing), nameof(BehBoatFishing.execute))]
[HarmonyPostfix]
public static void PostfixBoatFish(Actor pActor, BehResult __result)
{
if (pActor == null || __result == BehResult.Stop)
{
return;
}
ActivityLog.NoteActionBeat(pActor, "BehBoatFishing", "Fishes from the boat");
}
[HarmonyPatch(typeof(BehBoatCollectFish), nameof(BehBoatCollectFish.execute))]
[HarmonyPostfix]
public static void PostfixBoatCollectFish(Actor pActor, BehResult __result)
{
if (pActor == null || __result == BehResult.Stop)
{
return;
}
ActivityLog.NoteActionBeat(pActor, "BehBoatCollectFish", "Collects the catch");
}
[HarmonyPatch(typeof(BehClaimZoneForCityActorBorder), nameof(BehClaimZoneForCityActorBorder.execute))]
[HarmonyPostfix]
public static void PostfixClaimBorder(Actor pActor, BehResult __result)
{
if (pActor == null || __result == BehResult.Stop)
{
return;
}
string place = "";
try
{
if (pActor.city != null)
{
place = pActor.city.name ?? "";
}
}
catch
{
place = "";
}
ActivityLog.NoteActionBeat(
pActor,
"BehClaimZoneForCityActorBorder",
string.IsNullOrEmpty(place) ? "Claims border land" : "Claims border land for " + place,
actorTarget: "",
placeHint: place);
}
}