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" }