Enhance activity action handling by introducing companion verb checks in ActivityClauseAssembler and updating related methods for improved context management. Refactor ActivityLog to utilize new parameters for action logging, ensuring clarity in actor and place interactions. Increment version in mod.json to reflect these changes.

This commit is contained in:
DazedAnon 2026-07-15 12:08:38 -05:00
parent 76e91385f6
commit 72a4a2e164
6 changed files with 128 additions and 34 deletions

View file

@ -48,20 +48,34 @@ public static class ActivityClauseAssembler
action = StripToken(action, "{job}");
string subject = BuildSubject(ctx);
action = BindSingletonCompanions(ctx, action);
string verb = ActivityVerbMap.Resolve(key);
if (IsCompanionVerb(verb))
{
action = BindSingletonCompanions(ctx, action);
}
// Traits stay on the dossier chips - they only salt variety below, never named here.
string target = BuildObservedTargetClause(ctx, action);
string target = BuildObservedTargetClause(ctx, action, verb);
string carrying = BuildCarryingClause(ctx, action);
string pressure = BuildPressureClause(ctx, action + target);
string line = subject + " " + action + target + carrying + pressure;
return CollapseSpaces(line).Trim();
}
private static string BuildObservedTargetClause(ActivityContext ctx, string action)
private static bool IsCompanionVerb(string verb)
{
return verb == "social"
|| verb == "lover"
|| verb == "cry"
|| verb == "swear"
|| verb == "group";
}
private static string BuildObservedTargetClause(ActivityContext ctx, string action, string verb)
{
if (ctx == null
|| !IsCompanionVerb(verb)
|| !ctx.TargetIsActor
|| string.IsNullOrEmpty(ctx.TargetName)
|| ctx.InCombat

View file

@ -218,6 +218,20 @@ public static class ActivityInterestTable
return !string.IsNullOrEmpty(name) ? name : place;
}
/// <summary>Living actor interactee name only - never a building/place label.</summary>
public static string ActorTargetName(Actor actor)
{
ResolveTarget(actor, out string name, out bool isActor, out _);
return isActor ? name : "";
}
/// <summary>Building/tile/settlement label only - never an actor name.</summary>
public static string PlaceOrObjectLabel(Actor actor)
{
ResolveTarget(actor, out _, out bool isActor, out string place);
return isActor ? "" : (place ?? "");
}
/// <summary>
/// Prefer live actor targets (by name). Places/buildings stay as type labels.
/// </summary>

View file

@ -237,7 +237,16 @@ public static class ActivityLog
NoteTask(actor, taskId);
}
public static void NoteActionBeat(Actor actor, string actionKey, string rawFact, string target)
/// <summary>
/// Curated behaviour beat. <paramref name="actorTarget"/> is a living unit name only;
/// <paramref name="placeHint"/> is a building/place/object label and never becomes a companion.
/// </summary>
public static void NoteActionBeat(
Actor actor,
string actionKey,
string rawFact,
string actorTarget = "",
string placeHint = "")
{
if (actor == null || !actor.isAlive() || string.IsNullOrEmpty(actionKey))
{
@ -267,14 +276,19 @@ public static class ActivityLog
LastBeatAt[id] = now;
ActivityContext ctx = ActivityInterestTable.BuildContext(actor, actionKey);
string taskId = ActivityInterestTable.SafeTaskId(actor);
string jobId = ctx.JobId;
if (string.IsNullOrEmpty(ctx.TargetName) && !string.IsNullOrEmpty(target))
if (string.IsNullOrEmpty(ctx.TargetName) && !string.IsNullOrEmpty(actorTarget))
{
ctx.TargetName = target;
ctx.TargetName = actorTarget;
ctx.TargetIsActor = true;
}
if (string.IsNullOrEmpty(ctx.PlaceLabel) && !string.IsNullOrEmpty(placeHint))
{
ctx.PlaceLabel = placeHint;
}
if (actionKey == "BehUnloadResources" || actionKey == "BehThrowResources")
{
if (string.IsNullOrEmpty(ctx.CarryingLabel))

View file

@ -32,13 +32,18 @@ public static class ActorActivityPatches
return;
}
string target = ActivityInterestTable.TargetLabel(pActor);
if (string.IsNullOrEmpty(target))
string obj = ActivityInterestTable.PlaceOrObjectLabel(pActor);
if (string.IsNullOrEmpty(obj))
{
target = "flower";
obj = "flower";
}
ActivityLog.NoteActionBeat(pActor, "BehPollinate", "Pollinates " + target, target);
ActivityLog.NoteActionBeat(
pActor,
"BehPollinate",
"Pollinates " + obj,
actorTarget: "",
placeHint: obj);
}
[HarmonyPatch(typeof(BehUnloadResources), nameof(BehUnloadResources.execute))]
@ -69,7 +74,8 @@ public static class ActorActivityPatches
string fact = string.IsNullOrEmpty(carrying)
? "Unloads resources"
: "Unloads " + carrying;
ActivityLog.NoteActionBeat(pActor, "BehUnloadResources", fact, "town");
// Place comes from live context / city - never as a companion target.
ActivityLog.NoteActionBeat(pActor, "BehUnloadResources", fact);
}
[HarmonyPatch(typeof(BehAttackActorHuntingTarget), nameof(BehAttackActorHuntingTarget.execute))]
@ -81,12 +87,12 @@ public static class ActorActivityPatches
return;
}
string target = ActivityInterestTable.TargetLabel(pActor);
string actorTarget = ActivityInterestTable.ActorTargetName(pActor);
ActivityLog.NoteActionBeat(
pActor,
"BehAttackActorHuntingTarget",
string.IsNullOrEmpty(target) ? "Hunts prey" : "Hunts " + target,
target);
string.IsNullOrEmpty(actorTarget) ? "Hunts prey" : "Hunts " + actorTarget,
actorTarget: actorTarget);
}
[HarmonyPatch(typeof(BehCityActorRemoveFire), nameof(BehCityActorRemoveFire.execute))]
@ -98,7 +104,12 @@ public static class ActorActivityPatches
return;
}
ActivityLog.NoteActionBeat(pActor, "BehCityActorRemoveFire", "Fights fire", "blaze");
ActivityLog.NoteActionBeat(
pActor,
"BehCityActorRemoveFire",
"Fights fire",
actorTarget: "",
placeHint: "blaze");
}
[HarmonyPatch(typeof(BehTryToSocialize), nameof(BehTryToSocialize.execute))]
@ -110,12 +121,12 @@ public static class ActorActivityPatches
return;
}
string target = ActivityInterestTable.TargetLabel(pActor);
string actorTarget = ActivityInterestTable.ActorTargetName(pActor);
ActivityLog.NoteActionBeat(
pActor,
"BehTryToSocialize",
string.IsNullOrEmpty(target) ? "Tries to socialize" : "Tries to socialize with " + target,
target);
string.IsNullOrEmpty(actorTarget) ? "Tries to socialize" : "Tries to socialize with " + actorTarget,
actorTarget: actorTarget);
}
[HarmonyPatch(typeof(BehPlantCrops), nameof(BehPlantCrops.execute))]
@ -127,7 +138,7 @@ public static class ActorActivityPatches
return;
}
ActivityLog.NoteActionBeat(pActor, "BehPlantCrops", "Plants crops", "");
ActivityLog.NoteActionBeat(pActor, "BehPlantCrops", "Plants crops");
}
[HarmonyPatch(typeof(BehCityActorFertilizeCrop), nameof(BehCityActorFertilizeCrop.execute))]
@ -139,7 +150,7 @@ public static class ActorActivityPatches
return;
}
ActivityLog.NoteActionBeat(pActor, "BehCityActorFertilizeCrop", "Fertilizes crops", "");
ActivityLog.NoteActionBeat(pActor, "BehCityActorFertilizeCrop", "Fertilizes crops");
}
[HarmonyPatch(typeof(BehThrowResources), nameof(BehThrowResources.execute))]
@ -170,7 +181,7 @@ public static class ActorActivityPatches
string fact = string.IsNullOrEmpty(carrying)
? "Throws resources"
: "Throws " + carrying;
ActivityLog.NoteActionBeat(pActor, "BehThrowResources", fact, "");
ActivityLog.NoteActionBeat(pActor, "BehThrowResources", fact);
}
[HarmonyPatch(typeof(BehBuildTarget), nameof(BehBuildTarget.execute))]
@ -182,12 +193,13 @@ public static class ActorActivityPatches
return;
}
string target = ActivityInterestTable.TargetLabel(pActor);
string obj = ActivityInterestTable.PlaceOrObjectLabel(pActor);
ActivityLog.NoteActionBeat(
pActor,
"BehBuildTarget",
string.IsNullOrEmpty(target) ? "Works a build" : "Builds " + target,
target);
string.IsNullOrEmpty(obj) ? "Works a build" : "Builds " + obj,
actorTarget: "",
placeHint: obj);
}
[HarmonyPatch(typeof(BehPoopOutside), nameof(BehPoopOutside.execute))]
@ -199,7 +211,7 @@ public static class ActorActivityPatches
return;
}
ActivityLog.NoteActionBeat(pActor, "BehPoopOutside", "Private moment outdoors", "");
ActivityLog.NoteActionBeat(pActor, "BehPoopOutside", "Private moment outdoors");
}
[HarmonyPatch(typeof(BehPoopInside), nameof(BehPoopInside.execute))]
@ -211,7 +223,7 @@ public static class ActorActivityPatches
return;
}
ActivityLog.NoteActionBeat(pActor, "BehPoopInside", "Private moment indoors", "");
ActivityLog.NoteActionBeat(pActor, "BehPoopInside", "Private moment indoors");
}
[HarmonyPatch(typeof(BehBoatFishing), nameof(BehBoatFishing.execute))]
@ -223,7 +235,7 @@ public static class ActorActivityPatches
return;
}
ActivityLog.NoteActionBeat(pActor, "BehBoatFishing", "Fishes from the boat", "");
ActivityLog.NoteActionBeat(pActor, "BehBoatFishing", "Fishes from the boat");
}
[HarmonyPatch(typeof(BehBoatCollectFish), nameof(BehBoatCollectFish.execute))]
@ -235,7 +247,7 @@ public static class ActorActivityPatches
return;
}
ActivityLog.NoteActionBeat(pActor, "BehBoatCollectFish", "Collects the catch", "");
ActivityLog.NoteActionBeat(pActor, "BehBoatCollectFish", "Collects the catch");
}
[HarmonyPatch(typeof(BehClaimZoneForCityActorBorder), nameof(BehClaimZoneForCityActorBorder.execute))]
@ -264,6 +276,7 @@ public static class ActorActivityPatches
pActor,
"BehClaimZoneForCityActorBorder",
string.IsNullOrEmpty(place) ? "Claims border land" : "Claims border land for " + place,
place);
actorTarget: "",
placeHint: place);
}
}

View file

@ -2296,6 +2296,43 @@ public static class AgentHarness
socialBindOk = socialBindOk && withTomi <= 1;
// Building/place labels must never become companion "with …" on work/unload.
ActivityContext bonfireCtx = ActivityContext.ForHarness(
actorName,
"bonfire",
"human",
place: "bonfire");
ActivityProse.Format(
ActivityKind.TaskStart,
"type_bonfire",
bonfireCtx,
"Works a bonfire",
12,
1,
out string bonfireLine,
out _);
bool bonfireOk = !string.IsNullOrEmpty(bonfireLine)
&& bonfireLine.IndexOf("with bonfire", System.StringComparison.OrdinalIgnoreCase) < 0;
ActivityContext unloadTownCtx = ActivityContext.ForHarness(
actorName,
"town",
"human",
place: "Riverhold",
carrying: "wheat");
ActivityProse.Format(
ActivityKind.ActionBeat,
"BehUnloadResources",
unloadTownCtx,
"Unloads wheat",
13,
1,
out string unloadTownLine,
out _);
bool unloadTownOk = !string.IsNullOrEmpty(unloadTownLine)
&& unloadTownLine.IndexOf("wheat", System.StringComparison.OrdinalIgnoreCase) >= 0
&& unloadTownLine.IndexOf("with town", System.StringComparison.OrdinalIgnoreCase) < 0;
// Taxonomy kingdoms like "insect" must never become place clauses.
string insectPlace = ActivityInterestTable.PickPlaceLabel(
"insect",
@ -2377,17 +2414,19 @@ public static class AgentHarness
&& rhinoLine.IndexOf(" the rhino", System.StringComparison.OrdinalIgnoreCase) < 0;
pass = dogOk && catOk && humanOk && unloadOk && distinct && placeRejectOk && placeKeepOk
&& insectRejectOk && frogOk && frogJumpOk && liveTaxOk && rhinoOk && socialBindOk;
&& insectRejectOk && frogOk && frogJumpOk && liveTaxOk && rhinoOk && socialBindOk
&& bonfireOk && unloadTownOk;
detail =
$"dogOk={dogOk} catOk={catOk} humanOk={humanOk} unloadOk={unloadOk} distinct={distinct} "
+ $"placeRejectOk={placeRejectOk} placeKeepOk={placeKeepOk} insectRejectOk={insectRejectOk} "
+ $"frogOk={frogOk} frogJumpOk={frogJumpOk} liveTaxOk={liveTaxOk} rhinoOk={rhinoOk} "
+ $"socialBindOk={socialBindOk} "
+ $"socialBindOk={socialBindOk} bonfireOk={bonfireOk} unloadTownOk={unloadTownOk} "
+ $"frogFamily={frogCtx.Family} frogClass={frogCtx.TaxonomicClass} "
+ $"dogFamily={dogCtxLive.Family} dogTaxFamily={dogCtxLive.TaxonomicFamily} "
+ $"dog='{dogLine}' cat='{catLine}' human='{humanLine}' unload='{unloadLine}' "
+ $"buffalo='{buffaloLine}' buffaloTown='{buffaloTownLine}' beetle='{beetleLine}' "
+ $"frog='{frogLine}' frogJump='{frogJumpLine}' rhino='{rhinoLine}' social='{socialLine}'";
+ $"frog='{frogLine}' frogJump='{frogJumpLine}' rhino='{rhinoLine}' social='{socialLine}' "
+ $"bonfire='{bonfireLine}' unloadTown='{unloadTownLine}'";
break;
}
case "activity_taxonomy_audit":

View file

@ -1,7 +1,7 @@
{
"name": "IdleSpectator",
"author": "dazed",
"version": "0.12.90",
"version": "0.12.91",
"description": "AFK Idle Spectator (I) + Lore (L). Detailed living activity prose by default.",
"GUID": "com.dazed.idlespectator"
}