worldbox-observer-mod/IdleSpectator/Events/Patches/ActorHappinessPatches.cs
2026-07-16 15:29:28 -05:00

247 lines
7.9 KiB
C#

using ai.behaviours;
using HarmonyLib;
namespace IdleSpectator;
/// <summary>
/// Authoritative happiness hooks. Universal changeHappiness covers every effect id;
/// apply-site prefixes supply related-unit context the history struct lacks.
/// </summary>
[HarmonyPatch]
internal static class ActorHappinessPatches
{
[HarmonyPatch(typeof(Actor), nameof(Actor.changeHappiness))]
[HarmonyPostfix]
public static void PostfixChangeHappiness(Actor __instance, string pID, int pValue, bool __result)
{
if (__instance == null || string.IsNullOrEmpty(pID))
{
return;
}
if (!__result)
{
HappinessEventRouter.PublishSuppressed(
__instance, pID, HappinessSourceHook.ChangeHappiness);
return;
}
Actor related = null;
HappinessRelationRole role = HappinessRelationRole.None;
HappinessSourceHook hook = HappinessSourceHook.ChangeHappiness;
if (HappinessContextBag.TryGetRelated(__instance, out Actor bagRelated, out HappinessRelationRole bagRole))
{
related = bagRelated;
role = bagRole != HappinessRelationRole.None
? bagRole
: HappinessContextBag.RoleForDeathEffect(pID);
if (pID != null && pID.StartsWith("death_"))
{
hook = HappinessSourceHook.DeathEvent;
}
}
else if (pID != null && pID.StartsWith("death_"))
{
// Death event without bag - still log survivor-centered subject-only if required fails.
role = HappinessContextBag.RoleForDeathEffect(pID);
hook = HappinessSourceHook.DeathEvent;
}
int amount = HappinessGameApi.ResolveAmount(pID, pValue);
HappinessEventRouter.PublishApplied(__instance, pID, amount, hook, related, role);
}
[HarmonyPatch(typeof(Actor), "checkHappinessChangeFromDeathEvent")]
[HarmonyPrefix]
public static void PrefixDeathHappiness(Actor __instance)
{
HappinessContextBag.BeginDeathEvent(__instance);
}
[HarmonyPatch(typeof(Actor), "checkHappinessChangeFromDeathEvent")]
[HarmonyPostfix]
public static void PostfixDeathHappiness()
{
HappinessContextBag.EndDeathEvent();
}
[HarmonyPatch(typeof(Actor), nameof(Actor.birthEvent))]
[HarmonyPrefix]
public static void PrefixBirthEvent(Actor __instance)
{
// birthEvent runs on the parent; related newborn is not passed.
// Mark role so prose can stay subject-aware even without a name.
HappinessContextBag.SetRelated(null, HappinessRelationRole.Newborn, "just_had_child");
}
[HarmonyPatch(typeof(Actor), nameof(Actor.birthEvent))]
[HarmonyPostfix]
public static void PostfixBirthEvent()
{
HappinessContextBag.ClearRelated();
}
[HarmonyPatch(typeof(Actor), nameof(Actor.becomeLoversWith))]
[HarmonyPrefix]
public static void PrefixLovers(Actor __instance, Actor pTarget)
{
// Context for status fell_in_love → changeHappiness("fallen_in_love") during the body.
// Do not NoteCanonicalMilestone here - that would merge the ring before it writes.
HappinessContextBag.SetPair(__instance, pTarget, HappinessRelationRole.Lover, "fallen_in_love");
}
/// <summary>
/// Live path: status fell_in_love action_on_receive → changeHappiness("fallen_in_love").
/// Bridge still publishes for units that suppress changeHappiness (no emotions / egg),
/// and ensures related names when the status path is skipped. Self-merge within 0.35s
/// collapses the double for emotion units.
/// </summary>
[HarmonyPatch(typeof(Actor), nameof(Actor.becomeLoversWith))]
[HarmonyPostfix]
[HarmonyPriority(Priority.First)]
public static void PostfixLovers(Actor __instance, Actor pTarget)
{
try
{
BridgeFallenInLove(__instance, pTarget);
BridgeFallenInLove(pTarget, __instance);
}
finally
{
HappinessContextBag.ClearRelated();
}
}
private static void BridgeFallenInLove(Actor subject, Actor related)
{
if (subject == null || !subject.isAlive())
{
return;
}
Actor relatedAlive = related != null && related.isAlive() ? related : null;
int amount = HappinessGameApi.ResolveAmount("fallen_in_love", 0);
HappinessEventRouter.PublishApplied(
subject,
"fallen_in_love",
amount,
HappinessSourceHook.ChangeHappiness,
relatedAlive,
HappinessRelationRole.Lover);
}
[HarmonyPatch(typeof(Actor), nameof(Actor.setBestFriend))]
[HarmonyPrefix]
public static void PrefixBestFriend(Actor __instance, Actor pActor, bool pNew)
{
if (!pNew)
{
return;
}
HappinessContextBag.SetRelated(pActor, HappinessRelationRole.BestFriend, "just_made_friend");
try
{
if (__instance != null)
{
HappinessEventRouter.NoteCanonicalMilestone(__instance.getID(), "milestone_friend");
}
}
catch
{
// ignore
}
}
[HarmonyPatch(typeof(Actor), nameof(Actor.setBestFriend))]
[HarmonyPostfix]
public static void PostfixBestFriend()
{
HappinessContextBag.ClearRelated();
}
[HarmonyPatch(typeof(Actor), nameof(Actor.newKillAction))]
[HarmonyPrefix]
public static void PrefixKillAction(Actor __instance, Actor pDeadUnit)
{
HappinessContextBag.SetRelated(pDeadUnit, HappinessRelationRole.Victim, "just_killed");
}
[HarmonyPatch(typeof(Actor), nameof(Actor.newKillAction))]
[HarmonyPostfix]
public static void PostfixKillAction()
{
HappinessContextBag.ClearRelated();
}
[HarmonyPatch(typeof(Actor), nameof(Actor.stealActionFrom))]
[HarmonyPrefix]
public static void PrefixSteal(Actor __instance, Actor pTarget)
{
// Victim gets got_robbed; related = thief.
HappinessContextBag.SetRelated(__instance, HappinessRelationRole.Robber, "got_robbed");
}
[HarmonyPatch(typeof(Actor), nameof(Actor.stealActionFrom))]
[HarmonyPostfix]
public static void PostfixSteal()
{
HappinessContextBag.ClearRelated();
}
[HarmonyPatch(typeof(BehCheckForBabiesFromSexualReproduction), nameof(BehCheckForBabiesFromSexualReproduction.execute))]
[HarmonyPrefix]
public static void PrefixKissed(Actor pActor)
{
Actor lover = null;
try
{
if (pActor != null && pActor.hasLover())
{
lover = pActor.lover;
}
}
catch
{
lover = null;
}
HappinessContextBag.SetPair(pActor, lover, HappinessRelationRole.Lover, "just_kissed");
}
[HarmonyPatch(typeof(BehCheckForBabiesFromSexualReproduction), nameof(BehCheckForBabiesFromSexualReproduction.execute))]
[HarmonyPostfix]
public static void PostfixKissed()
{
HappinessContextBag.ClearRelated();
}
[HarmonyPatch(typeof(BehFinishTalk), "finishTalk")]
[HarmonyPrefix]
public static void PrefixFinishTalk(Actor pActor, Actor pTarget)
{
HappinessContextBag.SetPair(pActor, pTarget, HappinessRelationRole.ConversationPartner, "just_talked");
}
[HarmonyPatch(typeof(BehFinishTalk), "finishTalk")]
[HarmonyPostfix]
public static void PostfixFinishTalk()
{
HappinessContextBag.ClearRelated();
}
[HarmonyPatch(typeof(BehFinishTalk), "makeGift")]
[HarmonyPrefix]
public static void PrefixMakeGift(Actor pActor, Actor pTarget)
{
HappinessContextBag.SetPair(pActor, pTarget, HappinessRelationRole.GiftPartner, "just_gave_gift");
}
[HarmonyPatch(typeof(BehFinishTalk), "makeGift")]
[HarmonyPostfix]
public static void PostfixMakeGift()
{
HappinessContextBag.ClearRelated();
}
}