Add detailed activity prose for various actions in ActivityProse class
Expanded the ActivityProse class with new templates for actions such as laughing, singing, and playing. Enhanced the formatting logic to ensure proper sentence structure for activities, including mood and task labels. Updated AgentHarness to utilize the new prose formatting for activity sentences and adjusted test scenarios in HarnessScenarios to validate these changes. Incremented version in mod.json to reflect the update.
This commit is contained in:
parent
63bf44cae5
commit
0dfe3ab832
4 changed files with 455 additions and 39 deletions
|
|
@ -295,6 +295,96 @@ public static class ActivityProse
|
|||
"{actor} gathers loot",
|
||||
"{actor} claims spoils",
|
||||
"{actor} picks through loot"
|
||||
},
|
||||
["laughing"] = new[]
|
||||
{
|
||||
"{actor} laughs",
|
||||
"{actor} bursts into laughter",
|
||||
"{actor} is laughing"
|
||||
},
|
||||
["happy_laughing"] = new[]
|
||||
{
|
||||
"{actor} laughs with joy",
|
||||
"{actor} is laughing happily",
|
||||
"{actor} chuckles aloud"
|
||||
},
|
||||
["just_laughed"] = new[]
|
||||
{
|
||||
"{actor} just finished laughing",
|
||||
"{actor} settles after a laugh",
|
||||
"{actor} catches their breath from laughing"
|
||||
},
|
||||
["singing"] = new[]
|
||||
{
|
||||
"{actor} sings",
|
||||
"{actor} lifts a song",
|
||||
"{actor} is singing"
|
||||
},
|
||||
["task_unit_play"] = new[]
|
||||
{
|
||||
"{actor} plays",
|
||||
"{actor} is at play",
|
||||
"{actor} amuses themselves"
|
||||
},
|
||||
["just_played"] = new[]
|
||||
{
|
||||
"{actor} just finished playing",
|
||||
"{actor} wraps up playtime",
|
||||
"{actor} steps away from play"
|
||||
},
|
||||
["child_play_at_one_spot"] = new[]
|
||||
{
|
||||
"{actor} plays in place",
|
||||
"{actor} keeps busy with play",
|
||||
"{actor} amuses themselves on the spot"
|
||||
},
|
||||
["child_random_flips"] = new[]
|
||||
{
|
||||
"{actor} flips about",
|
||||
"{actor} tumbles in play",
|
||||
"{actor} shows off flips"
|
||||
},
|
||||
["child_random_jump"] = new[]
|
||||
{
|
||||
"{actor} jumps about",
|
||||
"{actor} leaps in play",
|
||||
"{actor} bounces around"
|
||||
},
|
||||
["child_follow_parent"] = new[]
|
||||
{
|
||||
"{actor} follows a parent",
|
||||
"{actor} stays close to {target}",
|
||||
"{actor} trails after kin"
|
||||
},
|
||||
["random_fun_move"] = new[]
|
||||
{
|
||||
"{actor} frolics about",
|
||||
"{actor} moves for fun",
|
||||
"{actor} skips along playfully"
|
||||
},
|
||||
["godfinger_random_fun_move"] = new[]
|
||||
{
|
||||
"{actor} frolics under divine whim",
|
||||
"{actor} is nudged into playful motion",
|
||||
"{actor} moves about for fun"
|
||||
},
|
||||
["try_to_read"] = new[]
|
||||
{
|
||||
"{actor} tries to read",
|
||||
"{actor} opens a book",
|
||||
"{actor} settles in to read"
|
||||
},
|
||||
["try_to_poop"] = new[]
|
||||
{
|
||||
"{actor} looks for a private moment",
|
||||
"{actor} tends to nature's call",
|
||||
"{actor} steps aside briefly"
|
||||
},
|
||||
["try_to_launch_fireworks"] = new[]
|
||||
{
|
||||
"{actor} tries to launch fireworks",
|
||||
"{actor} readies a firework",
|
||||
"{actor} prepares a celebration blast"
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -325,13 +415,25 @@ public static class ActivityProse
|
|||
rich = "";
|
||||
string actor = string.IsNullOrEmpty(actorName) ? "Someone" : actorName;
|
||||
string targetPlain = ResolveTargetPlain(targetName, targetIsActor, placeOrFallback);
|
||||
string template = PickTemplate(key, subjectId, lineIndex, !string.IsNullOrEmpty(targetPlain));
|
||||
bool hasTarget = !string.IsNullOrEmpty(targetPlain);
|
||||
string template = PickTemplate(key, subjectId, lineIndex, hasTarget);
|
||||
if (string.IsNullOrEmpty(template))
|
||||
{
|
||||
template = PatternTemplate(key, hasTarget);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(template))
|
||||
{
|
||||
plain = BuildFallbackPlain(actor, targetPlain, rawFact, key);
|
||||
rich = BuildFallbackRich(actor, targetPlain, targetIsActor, rawFact, key);
|
||||
return;
|
||||
template = LocalizedTemplate(rawFact, key, hasTarget);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(template))
|
||||
{
|
||||
template = "{actor} " + LowerFirst(HumanizeKey(key));
|
||||
if (string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(rawFact))
|
||||
{
|
||||
template = GerundSentence(rawFact);
|
||||
}
|
||||
}
|
||||
|
||||
plain = Apply(template, actor, targetPlain);
|
||||
|
|
@ -404,43 +506,327 @@ public static class ActivityProse
|
|||
return t.Trim();
|
||||
}
|
||||
|
||||
private static string BuildFallbackPlain(string actor, string target, string rawFact, string key)
|
||||
private static string PatternTemplate(string key, bool hasTarget)
|
||||
{
|
||||
string action = !string.IsNullOrEmpty(rawFact) ? rawFact : HumanizeKey(key);
|
||||
if (string.IsNullOrEmpty(action))
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return actor;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(target) && action.IndexOf(target, System.StringComparison.OrdinalIgnoreCase) < 0)
|
||||
string k = key.ToLowerInvariant();
|
||||
|
||||
if (k.StartsWith("try_to_"))
|
||||
{
|
||||
return actor + " " + LowerFirst(action) + " → " + target;
|
||||
return "{actor} tries to " + HumanizeKey(k.Substring("try_to_".Length));
|
||||
}
|
||||
|
||||
return actor + " " + LowerFirst(action);
|
||||
if (k.StartsWith("check_"))
|
||||
{
|
||||
return "{actor} checks " + HumanizeKey(k.Substring("check_".Length));
|
||||
}
|
||||
|
||||
if (k.StartsWith("find_"))
|
||||
{
|
||||
return "{actor} looks for " + HumanizeKey(k.Substring("find_".Length));
|
||||
}
|
||||
|
||||
if (k.StartsWith("decide_"))
|
||||
{
|
||||
return "{actor} decides " + HumanizeKey(k.Substring("decide_".Length));
|
||||
}
|
||||
|
||||
if (k.StartsWith("build_"))
|
||||
{
|
||||
return "{actor} builds " + HumanizeKey(k.Substring("build_".Length));
|
||||
}
|
||||
|
||||
if (k.StartsWith("boat_"))
|
||||
{
|
||||
return "{actor} " + BoatVerb(k.Substring("boat_".Length));
|
||||
}
|
||||
|
||||
if (k.StartsWith("warrior_"))
|
||||
{
|
||||
return WarriorTemplate(k, hasTarget);
|
||||
}
|
||||
|
||||
if (k.StartsWith("socialize_"))
|
||||
{
|
||||
return hasTarget
|
||||
? "{actor} socializes with {target}"
|
||||
: "{actor} socializes";
|
||||
}
|
||||
|
||||
if (k.StartsWith("sexual_reproduction") || k.StartsWith("asexual_reproduction"))
|
||||
{
|
||||
return hasTarget
|
||||
? "{actor} tries to start a family with {target}"
|
||||
: "{actor} tries to reproduce";
|
||||
}
|
||||
|
||||
if (k.StartsWith("child_"))
|
||||
{
|
||||
return "{actor} " + LowerFirst(HumanizeKey(k.Substring("child_".Length)));
|
||||
}
|
||||
|
||||
if (k.StartsWith("ant_") || k.StartsWith("bee_") || k.StartsWith("ufo_")
|
||||
|| k.StartsWith("dragon_") || k.StartsWith("worm_"))
|
||||
{
|
||||
return "{actor} " + LowerFirst(HumanizeKey(StripCreaturePrefix(k)));
|
||||
}
|
||||
|
||||
if (k.StartsWith("task_unit_"))
|
||||
{
|
||||
return LocalizedStyleFromId(k.Substring("task_unit_".Length), hasTarget);
|
||||
}
|
||||
|
||||
if (k.Contains("laugh"))
|
||||
{
|
||||
return "{actor} laughs";
|
||||
}
|
||||
|
||||
if (k.Contains("sing"))
|
||||
{
|
||||
return "{actor} sings";
|
||||
}
|
||||
|
||||
if (k.Contains("play") && !k.Contains("display"))
|
||||
{
|
||||
return hasTarget ? "{actor} plays with {target}" : "{actor} plays";
|
||||
}
|
||||
|
||||
if (k.Contains("wait"))
|
||||
{
|
||||
return "{actor} waits";
|
||||
}
|
||||
|
||||
if (k.Contains("sleep"))
|
||||
{
|
||||
return "{actor} sleeps";
|
||||
}
|
||||
|
||||
if (k.Contains("idle") || k.Contains("random_move") || k.EndsWith("_move")
|
||||
|| k.Contains("walking"))
|
||||
{
|
||||
return "{actor} wanders";
|
||||
}
|
||||
|
||||
if (k.Contains("fight") || k.Contains("attack") || k.Contains("hunt"))
|
||||
{
|
||||
return hasTarget ? "{actor} strikes at {target}" : "{actor} fights";
|
||||
}
|
||||
|
||||
if (k.Contains("heal") || k.Contains("cure"))
|
||||
{
|
||||
return hasTarget ? "{actor} tends to {target}" : "{actor} tends to wounds";
|
||||
}
|
||||
|
||||
if (k.Contains("eat") || k.Contains("food"))
|
||||
{
|
||||
return "{actor} seeks a meal";
|
||||
}
|
||||
|
||||
if (k.Contains("farm") || k.Contains("harvest") || k.Contains("forag")
|
||||
|| k.Contains("gather") || k.Contains("chop") || k.Contains("mine"))
|
||||
{
|
||||
return "{actor} " + LowerFirst(HumanizeKey(k));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string BuildFallbackRich(
|
||||
string actor,
|
||||
string target,
|
||||
bool targetIsActor,
|
||||
string rawFact,
|
||||
string key)
|
||||
private static string WarriorTemplate(string k, bool hasTarget)
|
||||
{
|
||||
string action = !string.IsNullOrEmpty(rawFact) ? rawFact : HumanizeKey(key);
|
||||
string a = ColorName(actor);
|
||||
if (string.IsNullOrEmpty(action))
|
||||
if (k.Contains("follow"))
|
||||
{
|
||||
return a;
|
||||
return hasTarget ? "{actor} follows {target}" : "{actor} follows the army";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(target) && action.IndexOf(target, System.StringComparison.OrdinalIgnoreCase) < 0)
|
||||
if (k.Contains("train"))
|
||||
{
|
||||
string t = targetIsActor ? ColorName(target) : target;
|
||||
return a + " " + LowerFirst(action) + " → " + t;
|
||||
return "{actor} trains for war";
|
||||
}
|
||||
|
||||
return a + " " + LowerFirst(action);
|
||||
if (k.Contains("join"))
|
||||
{
|
||||
return "{actor} tries to join the army";
|
||||
}
|
||||
|
||||
if (k.Contains("attack"))
|
||||
{
|
||||
return hasTarget ? "{actor} marches on {target}" : "{actor} marches to attack";
|
||||
}
|
||||
|
||||
if (k.Contains("wait") || k.Contains("idle"))
|
||||
{
|
||||
return "{actor} holds formation";
|
||||
}
|
||||
|
||||
return "{actor} " + LowerFirst(HumanizeKey(k.Replace("warrior_", "")));
|
||||
}
|
||||
|
||||
private static string BoatVerb(string rest)
|
||||
{
|
||||
if (rest.Contains("fish"))
|
||||
{
|
||||
return "fishes from the boat";
|
||||
}
|
||||
|
||||
if (rest.Contains("trade"))
|
||||
{
|
||||
return "trades by boat";
|
||||
}
|
||||
|
||||
if (rest.Contains("dock") || rest.Contains("return"))
|
||||
{
|
||||
return "returns to dock";
|
||||
}
|
||||
|
||||
if (rest.Contains("load"))
|
||||
{
|
||||
return "loads the boat";
|
||||
}
|
||||
|
||||
if (rest.Contains("unload"))
|
||||
{
|
||||
return "unloads the boat";
|
||||
}
|
||||
|
||||
if (rest.Contains("idle"))
|
||||
{
|
||||
return "idles aboard";
|
||||
}
|
||||
|
||||
return LowerFirst(HumanizeKey(rest)) + " aboard";
|
||||
}
|
||||
|
||||
private static string StripCreaturePrefix(string k)
|
||||
{
|
||||
int idx = k.IndexOf('_');
|
||||
return idx > 0 && idx < k.Length - 1 ? k.Substring(idx + 1) : k;
|
||||
}
|
||||
|
||||
private static string LocalizedStyleFromId(string rest, bool hasTarget)
|
||||
{
|
||||
if (rest == "play")
|
||||
{
|
||||
return "{actor} plays";
|
||||
}
|
||||
|
||||
if (rest == "wait")
|
||||
{
|
||||
return "{actor} waits";
|
||||
}
|
||||
|
||||
if (rest == "walk")
|
||||
{
|
||||
return "{actor} walks";
|
||||
}
|
||||
|
||||
if (rest.Contains("social"))
|
||||
{
|
||||
return hasTarget ? "{actor} socializes with {target}" : "{actor} socializes";
|
||||
}
|
||||
|
||||
return "{actor} " + LowerFirst(HumanizeKey(rest));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turn game localized labels like "Laughing" / "Job Seeking" into real sentences.
|
||||
/// </summary>
|
||||
private static string LocalizedTemplate(string rawFact, string key, bool hasTarget)
|
||||
{
|
||||
string loc = ExtractLocalizedBody(rawFact, key);
|
||||
if (string.IsNullOrEmpty(loc))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return GerundSentence(loc);
|
||||
}
|
||||
|
||||
private static string ExtractLocalizedBody(string rawFact, string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(rawFact))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string body = rawFact;
|
||||
int arrow = body.IndexOf('→');
|
||||
if (arrow < 0)
|
||||
{
|
||||
arrow = body.IndexOf("->", System.StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
if (arrow >= 0)
|
||||
{
|
||||
body = body.Substring(0, arrow).Trim();
|
||||
}
|
||||
|
||||
if (body.StartsWith("Task:", System.StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Ignore if it's just the raw key.
|
||||
if (!string.IsNullOrEmpty(key)
|
||||
&& body.Equals(key, System.StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return body.Trim();
|
||||
}
|
||||
|
||||
private static string GerundSentence(string loc)
|
||||
{
|
||||
if (string.IsNullOrEmpty(loc))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string body = loc.Trim();
|
||||
// "Laughing" / "Job Seeking" / "Holding Still"
|
||||
if (IsGerundPhrase(body))
|
||||
{
|
||||
return "{actor} is " + LowerFirst(body);
|
||||
}
|
||||
|
||||
// Already a short verb phrase
|
||||
return "{actor} " + LowerFirst(body);
|
||||
}
|
||||
|
||||
private static bool IsGerundPhrase(string body)
|
||||
{
|
||||
if (string.IsNullOrEmpty(body))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string[] parts = body.Split(new[] { ' ' }, System.StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length == 0 || parts.Length > 4)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Last token ends with -ing (Laughing, Seeking, Holding...)
|
||||
string last = parts[parts.Length - 1];
|
||||
return last.Length > 4
|
||||
&& last.EndsWith("ing", System.StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static string HumanizeKey(string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
string s = key.Replace("Beh", "").Replace('_', ' ').Trim();
|
||||
// Collapse common noise words for readability.
|
||||
s = s.Replace("task unit ", "").Replace("type ", "");
|
||||
return s;
|
||||
}
|
||||
|
||||
private static string LowerFirst(string s)
|
||||
|
|
@ -458,16 +844,6 @@ public static class ActivityProse
|
|||
return char.ToLowerInvariant(s[0]) + s.Substring(1);
|
||||
}
|
||||
|
||||
private static string HumanizeKey(string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
return key.Replace('_', ' ').Replace("Beh", "").Trim();
|
||||
}
|
||||
|
||||
private static string PickTemplate(string key, long subjectId, int lineIndex, bool hasTarget)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
|
|
@ -528,7 +904,6 @@ public static class ActivityProse
|
|||
|
||||
if (suited.Count == 0)
|
||||
{
|
||||
// Fall back to any template; Apply() will scrub missing targets.
|
||||
suited.AddRange(bag);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1918,6 +1918,44 @@ public static class AgentHarness
|
|||
detail = $"hit={hit} needle='{needle}' sample='{sample}' count={entries.Count}";
|
||||
break;
|
||||
}
|
||||
case "activity_prose_sentence":
|
||||
{
|
||||
// Ensure mood/task labels become sentences, not "{name} laughing".
|
||||
long id = WatchCaption.CurrentUnitId;
|
||||
string actorName = "";
|
||||
try
|
||||
{
|
||||
if (MoveCamera.hasFocusUnit() && MoveCamera._focus_unit != null)
|
||||
{
|
||||
actorName = MoveCamera._focus_unit.getName() ?? "";
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
actorName = "";
|
||||
}
|
||||
|
||||
ActivityProse.Format(
|
||||
ActivityKind.TaskStart,
|
||||
"laughing",
|
||||
actorName,
|
||||
"",
|
||||
false,
|
||||
"",
|
||||
"Laughing",
|
||||
id,
|
||||
99,
|
||||
out string plain,
|
||||
out _);
|
||||
bool ok = !string.IsNullOrEmpty(plain)
|
||||
&& plain.IndexOf("laugh", System.StringComparison.OrdinalIgnoreCase) >= 0
|
||||
&& !plain.Equals(
|
||||
(string.IsNullOrEmpty(actorName) ? "Someone" : actorName) + " laughing",
|
||||
System.StringComparison.Ordinal);
|
||||
pass = ok;
|
||||
detail = $"plain='{plain}' actor='{actorName}'";
|
||||
break;
|
||||
}
|
||||
case "lore_detail_pane":
|
||||
{
|
||||
string want = (cmd.value ?? "life").Trim().ToLowerInvariant();
|
||||
|
|
|
|||
|
|
@ -452,7 +452,10 @@ internal static class HarnessScenarios
|
|||
Step("act11", "wait", wait: 0.35f),
|
||||
Step("act12", "activity_probe"),
|
||||
Step("act13", "assert", expect: "activity_first_scoring"),
|
||||
Step("act14", "activity_force", asset: "eating", label: "Taking a meal", count: 1),
|
||||
Step("act13b", "assert", expect: "activity_prose_sentence"),
|
||||
Step("act14", "activity_force", asset: "laughing", label: "Laughing", count: 1),
|
||||
Step("act14b", "assert", expect: "activity_log_contains", value: "laugh"),
|
||||
Step("act14c", "activity_force", asset: "eating", label: "Taking a meal", count: 1),
|
||||
Step("act15", "activity_force", asset: "farm", label: "Tends the fields", count: 1),
|
||||
Step("act16", "activity_force", asset: "hunt", label: "Hunts", count: 1, expect: "Barkley"),
|
||||
Step("act17", "activity_force", asset: "BehPollinate", label: "Works the flower", count: 3, tier: "beat"),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "IdleSpectator",
|
||||
"author": "dazed",
|
||||
"version": "0.12.29",
|
||||
"description": "AFK Idle Spectator (I) + Lore (L). Detailed activity prose, Activity/Life tabs.",
|
||||
"version": "0.12.30",
|
||||
"description": "AFK Idle Spectator (I) + Lore (L). Full activity prose coverage for actor tasks.",
|
||||
"GUID": "com.dazed.idlespectator"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue