diff --git a/IdleSpectator/ActivityClauseAssembler.cs b/IdleSpectator/ActivityClauseAssembler.cs
index 0370889..09ef87c 100644
--- a/IdleSpectator/ActivityClauseAssembler.cs
+++ b/IdleSpectator/ActivityClauseAssembler.cs
@@ -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
diff --git a/IdleSpectator/ActivityInterestTable.cs b/IdleSpectator/ActivityInterestTable.cs
index c8f0863..52c586a 100644
--- a/IdleSpectator/ActivityInterestTable.cs
+++ b/IdleSpectator/ActivityInterestTable.cs
@@ -218,6 +218,20 @@ public static class ActivityInterestTable
return !string.IsNullOrEmpty(name) ? name : place;
}
+ /// Living actor interactee name only - never a building/place label.
+ public static string ActorTargetName(Actor actor)
+ {
+ ResolveTarget(actor, out string name, out bool isActor, out _);
+ return isActor ? name : "";
+ }
+
+ /// Building/tile/settlement label only - never an actor name.
+ public static string PlaceOrObjectLabel(Actor actor)
+ {
+ ResolveTarget(actor, out _, out bool isActor, out string place);
+ return isActor ? "" : (place ?? "");
+ }
+
///
/// Prefer live actor targets (by name). Places/buildings stay as type labels.
///
diff --git a/IdleSpectator/ActivityLog.cs b/IdleSpectator/ActivityLog.cs
index 41bad56..368e17a 100644
--- a/IdleSpectator/ActivityLog.cs
+++ b/IdleSpectator/ActivityLog.cs
@@ -237,7 +237,16 @@ public static class ActivityLog
NoteTask(actor, taskId);
}
- public static void NoteActionBeat(Actor actor, string actionKey, string rawFact, string target)
+ ///
+ /// Curated behaviour beat. is a living unit name only;
+ /// is a building/place/object label and never becomes a companion.
+ ///
+ 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))
diff --git a/IdleSpectator/ActorActivityPatches.cs b/IdleSpectator/ActorActivityPatches.cs
index c6edfb2..beae0d0 100644
--- a/IdleSpectator/ActorActivityPatches.cs
+++ b/IdleSpectator/ActorActivityPatches.cs
@@ -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);
}
}
diff --git a/IdleSpectator/AgentHarness.cs b/IdleSpectator/AgentHarness.cs
index 2c5fd2e..1b5b729 100644
--- a/IdleSpectator/AgentHarness.cs
+++ b/IdleSpectator/AgentHarness.cs
@@ -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":
diff --git a/IdleSpectator/mod.json b/IdleSpectator/mod.json
index 8b1f024..210f830 100644
--- a/IdleSpectator/mod.json
+++ b/IdleSpectator/mod.json
@@ -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"
}