Compare commits

...

2 commits

11 changed files with 265 additions and 87 deletions

View file

@ -109,7 +109,7 @@ public static class ActivityActionComposer
verb = string.IsNullOrEmpty(humanized) ? "move" : humanized.ToLowerInvariant();
}
string[] bag = ActivityActionLexicon.GetBag(voice, verb, hasTarget);
string[] bag = ActivityActionLexicon.GetBag(voice, verb, hasTarget, ctx.Terrain);
return Pick(bag, voice, verb, subjectId, lineIndex);
}

View file

@ -5,7 +5,11 @@ namespace IdleSpectator;
/// <summary>Phrase fragments keyed by the already-resolved voice. No ActorAsset lookup.</summary>
public static class ActivityActionLexicon
{
public static string[] GetBag(ProseVoice voice, string verb, bool hasTarget)
public static string[] GetBag(
ProseVoice voice,
string verb,
bool hasTarget,
ActivityTerrain terrain = ActivityTerrain.Unknown)
{
voice ??= ProseVoice.Unknown();
string v = string.IsNullOrEmpty(verb) ? "move" : verb;
@ -24,7 +28,7 @@ public static class ActivityActionLexicon
string[] profile = ProfileBag(voice.ProfileTag, v, hasTarget);
if (profile != null) return profile;
string[] body = BodyBag(voice.BaseBodyTag, v, hasTarget);
string[] body = BodyBag(voice.BaseBodyTag, v, hasTarget, terrain);
return body ?? GenericBag(v, hasTarget);
}
@ -39,7 +43,7 @@ public static class ActivityActionLexicon
{
"move" => new[] { "surges ahead in a trail of flame{place_at}", "flows forward in a rolling blaze{place_at}" },
"wait" => new[] { "flickers in place{place_at}", "burns steadily while waiting{place_at}" },
"play" => new[] { "flares and darts through a wild game{place_at}", "dances in playful arcs of flame{place_at}" },
"play" => new[] { "flickers into quick spirals and sudden bursts{place_at}", "loops in bright arcs of flame{place_at}" },
"eat" => new[] { "feeds the inner flame{place_at}", "draws fuel into the flames{place_at}" },
"sleep" => new[] { "dims into a banked rest{place_at}", "settles into a low, steady glow{place_at}" },
"hunt" => Targeted(hasTarget, "surges after {target}{place_at}", "hunts {target} in a trail of heat{place_at}", "searches for prey in flickering sweeps{place_at}", "hunts as flames flicker and rise{place_at}"),
@ -52,9 +56,9 @@ public static class ActivityActionLexicon
{
"move" => new[] { "shuffles ahead in soft, uneven steps{place_at}", "creeps forward as spores drift{place_at}" },
"wait" => new[] { "rests while spores drift around it{place_at}", "waits while faint spores drift{place_at}" },
"play" => new[] { "puffs through an odd little game{place_at}", "bobs about in playful bursts of spores{place_at}" },
"eat" => new[] { "feeds through the damp earth{place_at}", "draws a slow meal from decay{place_at}" },
"sleep" => new[] { "settles into a damp, quiet rest{place_at}", "folds down among drifting spores{place_at}" },
"play" => new[] { "bobs and puffs spores in short bursts{place_at}", "wobbles about, shedding little clouds of spores{place_at}" },
"eat" => new[] { "feeds slowly on decaying matter{place_at}", "draws a slow meal from decay{place_at}" },
"sleep" => new[] { "settles into a soft, quiet rest{place_at}", "folds down among drifting spores{place_at}" },
_ => GenericBag(verb, hasTarget)
};
case ActivityModifier.Tumor:
@ -62,7 +66,7 @@ public static class ActivityActionLexicon
{
"move" => new[] { "pulses forward as its mass shifts{place_at}", "lurches ahead in uneven surges{place_at}" },
"wait" => new[] { "throbs in unsettling stillness{place_at}", "quivers without moving on{place_at}" },
"play" => new[] { "writhes through a grotesque game{place_at}", "pulses with uncanny playfulness{place_at}" },
"play" => new[] { "writhes and recoils in restless loops{place_at}", "pulses in an oddly buoyant rhythm{place_at}" },
"eat" => new[] { "feeds with consuming hunger{place_at}", "absorbs food into the growing mass{place_at}" },
"fight" => Targeted(hasTarget, "lashes into {target} with terrible force{place_at}", "surges against {target}{place_at}", "fights in a thrashing fury{place_at}", "clashes in a violent rush{place_at}"),
_ => GenericBag(verb, hasTarget)
@ -72,7 +76,7 @@ public static class ActivityActionLexicon
{
"move" => new[] { "glides ahead without a sound{place_at}", "tracks a precise, unfamiliar course{place_at}" },
"wait" => new[] { "observes in unreadable stillness{place_at}", "stands motionless and watches{place_at}" },
"play" => new[] { "tests each movement like a game{place_at}", "experiments with a strange little game{place_at}" },
"play" => new[] { "repeats curious patterns of motion{place_at}", "darts, pauses, and changes direction without warning{place_at}" },
"hunt" => Targeted(hasTarget, "tracks {target} without a sound{place_at}", "closes on {target} in eerie silence{place_at}", "scans for prey{place_at}", "hunts by unfamiliar instinct{place_at}"),
"fight" => Targeted(hasTarget, "strikes at {target} with uncanny precision{place_at}", "clashes with {target} in sudden bursts{place_at}", "fights with unreadable intent{place_at}", "attacks in a precise rush{place_at}"),
_ => GenericBag(verb, hasTarget)
@ -82,7 +86,7 @@ public static class ActivityActionLexicon
{
"move" => new[] { "moves in measured, jointed steps{place_at}", "shifts forward with deliberate precision{place_at}" },
"wait" => new[] { "idles in rigid watchfulness{place_at}", "stays motionless, mechanisms quiet{place_at}" },
"play" => new[] { "tests a playful sequence of motions{place_at}", "turns precise movement into a game{place_at}" },
"play" => new[] { "repeats a sequence of precise motions{place_at}", "spins and resets with mechanical exactness{place_at}" },
"eat" => new[] { "takes in fuel with mechanical care{place_at}", "replenishes what keeps it moving{place_at}" },
"sleep" => new[] { "powers down into quiet rest{place_at}", "settles into a dormant stillness{place_at}" },
_ => GenericBag(verb, hasTarget)
@ -98,20 +102,20 @@ public static class ActivityActionLexicon
{
return body switch
{
ActivityBodyTag.Canine => new[] { "pads ahead with a stiff, uneven gait{place_at}", "sniffs the ground and shambles on{place_at}" },
ActivityBodyTag.Canine => new[] { "pads ahead with a stiff, uneven gait{place_at}", "sniffs ahead and shambles on{place_at}" },
ActivityBodyTag.Feline => new[] { "slinks ahead in eerie silence{place_at}", "pads forward with stiff precision{place_at}" },
ActivityBodyTag.Insect => new[] { "skitters ahead on stiff little legs{place_at}", "crawls forward in twitching bursts{place_at}" },
ActivityBodyTag.Plant => new[] { "creeps ahead in withered motion{place_at}", "drags dead growth across the ground{place_at}" },
ActivityBodyTag.Plant => new[] { "creeps ahead in withered motion{place_at}", "drags dead growth behind it{place_at}" },
ActivityBodyTag.Dragon => new[] { "lumbers ahead with the weight of a fallen giant{place_at}", "advances like ruin given motion{place_at}" },
ActivityBodyTag.Bird => new[] { "flutters ahead on lifeless wings{place_at}", "hops forward in ragged, uneven motions{place_at}" },
_ => new[] { "shambles forward over uneven ground{place_at}", "drifts along in restless undeath{place_at}" }
_ => new[] { "shambles forward in uneven steps{place_at}", "drifts along in restless undeath{place_at}" }
};
}
return verb switch
{
"wait" => new[] { "waits in hollow stillness{place_at}", "stands in restless silence{place_at}" },
"play" => new[] { "lurches through a strange game{place_at}", "mimics a game with jerking motions{place_at}" },
"play" => new[] { "lurches in clumsy circles{place_at}", "repeats jerking motions with eerie persistence{place_at}" },
"eat" => new[] { "feeds with deathless hunger{place_at}", "tears into a meal with endless hunger{place_at}" },
"sleep" => new[] { "slumps into a hollow rest{place_at}", "goes still in restless undeath{place_at}" },
"hunt" => Targeted(hasTarget, "shambles after {target}{place_at}", "hunts {target} with hollow hunger{place_at}", "hunts with deathless hunger{place_at}", "searches for prey without rest{place_at}"),
@ -133,7 +137,7 @@ public static class ActivityActionLexicon
"sleep" => new[] { "coils into a guarded sleep{place_at}", "rests with one eye on the world{place_at}" },
"eat" => new[] { "feeds with terrifying appetite{place_at}", "devours a meal at leisure{place_at}" },
"laugh" => new[] { "rumbles with deep laughter{place_at}", "laughs in a dangerous rolling growl{place_at}" },
"hunt" => Targeted(hasTarget, "descends on {target}{place_at}", "watches {target} from above{place_at}", "circles in search of prey{place_at}", "hunts across a wide range{place_at}"),
"hunt" => Targeted(hasTarget, "descends on {target}{place_at}", "keeps {target} in sight{place_at}", "circles in search of prey{place_at}", "hunts across a wide range{place_at}"),
_ => null
};
}
@ -143,7 +147,7 @@ public static class ActivityActionLexicon
return verb switch
{
"move" => new[] { "flits onward in bright little arcs{place_at}", "drifts ahead on glittering wings{place_at}" },
"play" => new[] { "dances through a sparkling game{place_at}", "flutters about with mischievous delight{place_at}" },
"play" => new[] { "loops and twirls in sparkling arcs{place_at}", "flutters about mischievously{place_at}" },
"laugh" => new[] { "chimes with tiny laughter{place_at}", "laughs in a bright little trill{place_at}" },
_ => null
};
@ -154,7 +158,7 @@ public static class ActivityActionLexicon
return verb switch
{
"move" => new[] { "glides ahead at an unhurried pace{place_at}", "follows a slow, winding trail{place_at}" },
"play" => new[] { "turns in slow, playful circles{place_at}", "sways through a gentle game{place_at}" },
"play" => new[] { "turns in slow, winding circles{place_at}", "sways gently from side to side{place_at}" },
"eat" => new[] { "nibbles through a meal slowly{place_at}", "takes a slow, careful meal{place_at}" },
_ => null
};
@ -165,7 +169,7 @@ public static class ActivityActionLexicon
return verb switch
{
"move" => new[] { "pads softly along{place_at}", "slinks ahead, watching the way{place_at}" },
"play" => new[] { "bats and twists through a private game{place_at}", "springs into sudden playful leaps{place_at}" },
"play" => new[] { "bats, twists, and springs aside{place_at}", "springs into sudden leaps{place_at}" },
_ => null
};
}
@ -175,7 +179,7 @@ public static class ActivityActionLexicon
return verb switch
{
"move" => new[] { "trudges onward with steady weight{place_at}", "ambles ahead in slow, heavy steps{place_at}" },
"play" => new[] { "charges about in rough play{place_at}", "stomps through a clumsy game{place_at}" },
"play" => new[] { "charges, stops, and wheels around{place_at}", "stomps and bounds with clumsy enthusiasm{place_at}" },
_ => null
};
}
@ -193,27 +197,36 @@ public static class ActivityActionLexicon
return null;
}
private static string[] BodyBag(ActivityBodyTag body, string verb, bool hasTarget)
private static string[] BodyBag(
ActivityBodyTag body,
string verb,
bool hasTarget,
ActivityTerrain terrain)
{
if (verb == "move")
{
return body switch
{
ActivityBodyTag.Humanoid => new[] { "walks along, taking in the surroundings{place_at}", "wanders ahead at an easy pace{place_at}" },
ActivityBodyTag.Canine => new[] { "trots along without hurry{place_at}", "roams ahead with nose to the ground{place_at}" },
ActivityBodyTag.Canine => new[] { "trots along without hurry{place_at}", "roams ahead, following a scent{place_at}" },
ActivityBodyTag.Feline => new[] { "pads softly along{place_at}", "slinks ahead at an easy pace{place_at}" },
ActivityBodyTag.Bird => new[] { "flutters onward through the air{place_at}", "hops and flaps along nearby{place_at}" },
ActivityBodyTag.Insect => new[] { "scuttles along without hurry{place_at}", "skitters across the ground{place_at}" },
ActivityBodyTag.Bird => new[] { "flutters steadily onward{place_at}", "hops and flaps along nearby{place_at}" },
ActivityBodyTag.Insect => new[] { "scuttles along without hurry{place_at}", "skitters quickly forward{place_at}" },
ActivityBodyTag.Arachnid => new[] { "skitters onward on many legs{place_at}", "creeps ahead with careful steps{place_at}" },
ActivityBodyTag.Amphibian => new[] { "hops from patch to patch{place_at}", "bounces along in damp, springy steps{place_at}" },
ActivityBodyTag.Aquatic => new[] { "glides through the water{place_at}", "swims onward with easy strokes{place_at}" },
ActivityBodyTag.Reptile => new[] { "slides along without hurry{place_at}", "creeps forward close to the ground{place_at}" },
ActivityBodyTag.Amphibian => new[] { "hops from patch to patch{place_at}", "bounces along in springy steps{place_at}" },
ActivityBodyTag.Aquatic => AquaticTerrainBag(
terrain,
"glides through the water{place_at}",
"swims onward with easy strokes{place_at}",
"moves forward in a smooth, winding motion{place_at}",
"twists steadily onward{place_at}"),
ActivityBodyTag.Reptile => new[] { "slides along without hurry{place_at}", "creeps forward in a low crawl{place_at}" },
ActivityBodyTag.Livestock => new[] { "ambles along without hurry{place_at}", "wanders in slow, heavy steps{place_at}" },
ActivityBodyTag.Plant => new[] { "creeps along as stems and leaves sway{place_at}", "eases forward in slow, leafy steps{place_at}" },
ActivityBodyTag.Fungi => new[] { "drifts along in soft, uneven steps{place_at}", "spreads forward as spores trail behind{place_at}" },
ActivityBodyTag.Protist => new[] { "oozes onward without hurry{place_at}", "pulses along in slow ripples{place_at}" },
ActivityBodyTag.Machina => new[] { "moves ahead in measured steps{place_at}", "tracks a precise course{place_at}" },
ActivityBodyTag.Mythic => new[] { "drifts forward as if barely touching the ground{place_at}", "moves with an uncanny grace{place_at}" },
ActivityBodyTag.Mythic => new[] { "drifts forward without a sound{place_at}", "moves with an uncanny grace{place_at}" },
ActivityBodyTag.Gregalia => new[] { "lopes onward with an uneven gait{place_at}", "wanders in twitchy, restless steps{place_at}" },
ActivityBodyTag.Primate => new[] { "lopes onward with nimble curiosity{place_at}", "moves ahead in quick clever steps{place_at}" },
ActivityBodyTag.Rodent => new[] { "scurries onward without hurry{place_at}", "pads ahead in quick little steps{place_at}" },
@ -221,11 +234,11 @@ public static class ActivityActionLexicon
ActivityBodyTag.Seal => new[] { "slides forward on its belly{place_at}", "bounds ahead with a sleek, rolling motion{place_at}" },
ActivityBodyTag.Procyonid => new[] { "pads onward, nosing around{place_at}", "scampers ahead in nimble steps{place_at}" },
ActivityBodyTag.Hyaenid => new[] { "lopes onward with rough confidence{place_at}", "trots ahead in rangy strides{place_at}" },
ActivityBodyTag.Armadillo => new[] { "trundles onward close to the ground{place_at}", "scurries ahead in armored little steps{place_at}" },
ActivityBodyTag.Crawler => new[] { "writhes forward across the ground{place_at}", "crawls ahead without hurry{place_at}" },
ActivityBodyTag.Armadillo => new[] { "trundles onward with its body held low{place_at}", "scurries ahead in armored little steps{place_at}" },
ActivityBodyTag.Crawler => new[] { "writhes steadily forward{place_at}", "crawls ahead without hurry{place_at}" },
ActivityBodyTag.Cold => new[] { "moves onward in a breath of cold{place_at}", "drifts ahead with wintry calm{place_at}" },
ActivityBodyTag.Crystal => new[] { "glides onward with crystalline precision{place_at}", "moves in stiff, glittering steps{place_at}" },
ActivityBodyTag.Crab => new[] { "scuttles sideways across the ground{place_at}", "sidesteps onward behind a hard shell{place_at}" },
ActivityBodyTag.Crab => new[] { "scuttles sideways{place_at}", "sidesteps onward behind a hard shell{place_at}" },
_ => GenericBag(verb, hasTarget)
};
}
@ -234,21 +247,27 @@ public static class ActivityActionLexicon
{
return body switch
{
ActivityBodyTag.Canine => new[] { "tumbles about in lively play{place_at}", "bounds and wrestles through play{place_at}" },
ActivityBodyTag.Feline => new[] { "bats and twists through a private game{place_at}", "plays with sudden leaps{place_at}" },
ActivityBodyTag.Bird => new[] { "flutters through playful arcs{place_at}", "hops and wings about in play{place_at}" },
ActivityBodyTag.Insect => new[] { "scuttles through a tiny game{place_at}", "darts about in quick bursts of play{place_at}" },
ActivityBodyTag.Arachnid => new[] { "skitters through a restless game{place_at}", "darts about on many legs{place_at}" },
ActivityBodyTag.Amphibian => new[] { "hops about in damp play{place_at}", "bounces through a lively game{place_at}" },
ActivityBodyTag.Aquatic => new[] { "splashes through play in the water{place_at}", "darts and circles in play{place_at}" },
ActivityBodyTag.Reptile => new[] { "slides and coils in play{place_at}", "plays in slow, winding motions{place_at}" },
ActivityBodyTag.Livestock => new[] { "stomps about in clumsy play{place_at}", "frolics in heavy, eager bursts{place_at}" },
ActivityBodyTag.Plant => new[] { "sways through a slow game{place_at}", "twists and rustles in play{place_at}" },
ActivityBodyTag.Primate => new[] { "clambers through a clever game{place_at}", "plays with nimble mischief{place_at}" },
ActivityBodyTag.Rodent => new[] { "scampers through a tiny game{place_at}", "darts about in quick play{place_at}" },
ActivityBodyTag.Ursid => new[] { "rolls through a heavy game{place_at}", "plays with lumbering cheer{place_at}" },
ActivityBodyTag.Crawler => new[] { "loops through a low little game{place_at}", "writhes about in restless play{place_at}" },
_ => new[] { "finds a way to amuse itself{place_at}", "plays with restless enthusiasm{place_at}" }
ActivityBodyTag.Canine => new[] { "tumbles, bounds, and wheels around{place_at}", "bounds, feints, and wrestles about{place_at}" },
ActivityBodyTag.Feline => new[] { "bats, twists, and springs aside{place_at}", "pounces and leaps without warning{place_at}" },
ActivityBodyTag.Bird => new[] { "flutters in looping arcs{place_at}", "hops and flaps about{place_at}" },
ActivityBodyTag.Insect => new[] { "scuttles in quick zigzags{place_at}", "darts about in short bursts{place_at}" },
ActivityBodyTag.Arachnid => new[] { "skitters in quick loops{place_at}", "darts about on many legs{place_at}" },
ActivityBodyTag.Amphibian => new[] { "hops about in lively bursts{place_at}", "bounces back and forth{place_at}" },
ActivityBodyTag.Aquatic => AquaticTerrainBag(
terrain,
"splashes and darts through the water{place_at}",
"weaves quick circles through the water{place_at}",
"twists and darts about{place_at}",
"wriggles in quick loops{place_at}"),
ActivityBodyTag.Crab => new[] { "scuttles in quick sideways loops{place_at}", "sidesteps and spins about{place_at}" },
ActivityBodyTag.Reptile => new[] { "slides, coils, and doubles back{place_at}", "winds about in slow loops{place_at}" },
ActivityBodyTag.Livestock => new[] { "stomps and bounds clumsily{place_at}", "frolics in heavy, eager bursts{place_at}" },
ActivityBodyTag.Plant => new[] { "sways and twists in slow arcs{place_at}", "twists and rustles energetically{place_at}" },
ActivityBodyTag.Primate => new[] { "clambers, swings, and doubles back{place_at}", "scrambles about with nimble mischief{place_at}" },
ActivityBodyTag.Rodent => new[] { "scampers in tight circles{place_at}", "darts about in quick zigzags{place_at}" },
ActivityBodyTag.Ursid => new[] { "rolls over and lumbers upright again{place_at}", "lumbers about with clumsy cheer{place_at}" },
ActivityBodyTag.Crawler => new[] { "loops around and doubles back{place_at}", "writhes about in restless circles{place_at}" },
_ => new[] { "paces, turns, and doubles back{place_at}", "moves in quick, restless bursts{place_at}" }
};
}
@ -256,11 +275,16 @@ public static class ActivityActionLexicon
{
return body switch
{
ActivityBodyTag.Insect => new[] { "holds still in a tiny pause{place_at}", "waits with antennae alert{place_at}" },
ActivityBodyTag.Insect => new[] { "holds still with its antennae raised{place_at}", "waits while its antennae twitch{place_at}" },
ActivityBodyTag.Amphibian => new[] { "crouches low and waits{place_at}", "sits still, ready to spring{place_at}" },
ActivityBodyTag.Feline => new[] { "settles into a watchful crouch{place_at}", "waits without looking away{place_at}" },
ActivityBodyTag.Canine => new[] { "waits with alert ears{place_at}", "holds a restless pause{place_at}" },
ActivityBodyTag.Aquatic => new[] { "hovers in the water and waits{place_at}", "drifts almost motionless{place_at}" },
ActivityBodyTag.Aquatic => AquaticTerrainBag(
terrain,
"hovers in the water and waits{place_at}",
"drifts almost motionless in the water{place_at}",
"rests almost motionless{place_at}",
"waits with only a faint twitch{place_at}"),
ActivityBodyTag.Bird => new[] { "perches and waits{place_at}", "holds a light watchful pause{place_at}" },
ActivityBodyTag.Plant => new[] { "holds a rooted pause{place_at}", "stands still with leaves barely stirring{place_at}" },
ActivityBodyTag.Machina => new[] { "idles in a watchful hold{place_at}", "stays still and scans the surroundings{place_at}" },
@ -272,11 +296,11 @@ public static class ActivityActionLexicon
{
return body switch
{
ActivityBodyTag.Canine => Targeted(hasTarget, "hunts {target} with nose to the ground{place_at}", "follows {target}'s trail{place_at}", "follows the scent of prey{place_at}", "hunts with nose to the ground{place_at}"),
ActivityBodyTag.Feline => Targeted(hasTarget, "hunts {target} in silence{place_at}", "stalks {target} from cover{place_at}", "hunts without making a sound{place_at}", "stalks prey from cover{place_at}"),
ActivityBodyTag.Bird => Targeted(hasTarget, "hunts {target} from above{place_at}", "circles after {target}{place_at}", "hunts from above{place_at}", "circles after prey{place_at}"),
ActivityBodyTag.Canine => Targeted(hasTarget, "hunts {target} by scent{place_at}", "follows {target}'s trail{place_at}", "follows the scent of prey{place_at}", "hunts by scent{place_at}"),
ActivityBodyTag.Feline => Targeted(hasTarget, "hunts {target} in silence{place_at}", "stalks {target} without a sound{place_at}", "hunts without making a sound{place_at}", "stalks prey in silence{place_at}"),
ActivityBodyTag.Bird => Targeted(hasTarget, "tracks {target} with sharp eyes{place_at}", "circles after {target}{place_at}", "searches keenly for prey{place_at}", "circles after prey{place_at}"),
ActivityBodyTag.Insect => Targeted(hasTarget, "scuttles after {target}{place_at}", "darts toward {target}{place_at}", "searches for prey in quick bursts{place_at}", "scuttles after prey{place_at}"),
ActivityBodyTag.Reptile => Targeted(hasTarget, "slides after {target}{place_at}", "creeps closer to {target}{place_at}", "waits low for prey to approach{place_at}", "stalks prey close to the ground{place_at}"),
ActivityBodyTag.Reptile => Targeted(hasTarget, "slides after {target}{place_at}", "creeps closer to {target}{place_at}", "waits low for prey to approach{place_at}", "stalks prey in a low crawl{place_at}"),
_ => Targeted(hasTarget, "pursues {target}{place_at}", "closes in on {target}{place_at}", "searches for prey{place_at}", "moves quietly on the hunt{place_at}")
};
}
@ -298,7 +322,12 @@ public static class ActivityActionLexicon
ActivityBodyTag.Canine => new[] { "curls into sleep{place_at}", "settles into deep rest{place_at}" },
ActivityBodyTag.Feline => new[] { "curls into a neat sleep{place_at}", "settles into quiet rest{place_at}" },
ActivityBodyTag.Bird => new[] { "tucks into sleep{place_at}", "roosts into rest{place_at}" },
ActivityBodyTag.Aquatic => new[] { "drifts into rest in the water{place_at}", "settles into wet quiet{place_at}" },
ActivityBodyTag.Aquatic => AquaticTerrainBag(
terrain,
"drifts into rest in the water{place_at}",
"settles almost motionless in the water{place_at}",
"goes still to rest{place_at}",
"settles into a quiet sleep{place_at}"),
ActivityBodyTag.Plant => new[] { "settles into leafy rest{place_at}", "goes completely still{place_at}" },
_ => new[] { "settles into deep sleep{place_at}", "rests after the day's demands{place_at}" }
},
@ -344,7 +373,7 @@ public static class ActivityActionLexicon
"gather_life" => new[] { "gathers with kin{place_at}", "moves among the group{place_at}" },
"move" => new[] { "wanders without hurry{place_at}", "roams nearby, looking around{place_at}" },
"wait" => new[] { "waits in watchful quiet{place_at}", "stands still and watches{place_at}" },
"play" => new[] { "finds a way to amuse itself{place_at}", "turns idle time into a game{place_at}" },
"play" => new[] { "paces, turns, and doubles back{place_at}", "moves in quick, restless bursts{place_at}" },
"eat" => new[] { "sits down to eat{place_at}", "finishes a meal{place_at}" },
"sleep" => new[] { "settles into deep sleep{place_at}", "rests after the day's demands{place_at}" },
"hunt" => Targeted(hasTarget, "pursues {target}{place_at}", "closes in on {target}{place_at}", "searches for prey{place_at}", "moves quietly on the hunt{place_at}"),
@ -367,4 +396,16 @@ public static class ActivityActionLexicon
? new[] { withTargetA, withTargetB }
: new[] { noTargetA, noTargetB };
}
private static string[] AquaticTerrainBag(
ActivityTerrain terrain,
string liquidA,
string liquidB,
string nonLiquidA,
string nonLiquidB)
{
return terrain == ActivityTerrain.Liquid
? new[] { liquidA, liquidB }
: new[] { nonLiquidA, nonLiquidB };
}
}

View file

@ -1,5 +1,12 @@
namespace IdleSpectator;
public enum ActivityTerrain
{
Unknown,
Land,
Liquid
}
/// <summary>
/// Snapshot of live actor facts for activity prose. Only observed fields - never invent events.
/// Species / taxonomy / flags come from <see cref="Actor.asset"/> for every unit in the game.
@ -41,6 +48,8 @@ public sealed class ActivityContext
public bool TargetIsActor;
/// <summary>Building, city, kingdom, or soft place label for clauses.</summary>
public string PlaceLabel = "";
/// <summary>Observed terrain under the actor when this snapshot was captured.</summary>
public ActivityTerrain Terrain;
public string CarryingLabel = "";
public bool InCombat;
public bool HasAttackTarget;
@ -68,7 +77,8 @@ public sealed class ActivityContext
bool inCombat = false,
bool isKing = false,
bool isLeader = false,
bool isWarrior = false)
bool isWarrior = false,
ActivityTerrain terrain = ActivityTerrain.Unknown)
{
ActivityContext ctx = new ActivityContext
{
@ -81,6 +91,7 @@ public sealed class ActivityContext
JobId = jobId ?? "",
TaskKey = taskKey ?? "",
TopTraitId = traitId ?? "",
Terrain = terrain,
IsChild = isChild,
InCombat = inCombat,
HasAttackTarget = inCombat && !string.IsNullOrEmpty(targetName),

View file

@ -382,6 +382,7 @@ public static class ActivityInterestTable
ctx.TaskKey = taskKeyHint ?? SafeTaskId(actor);
ctx.JobId = SafeJobId(actor);
ctx.Terrain = ResolveTerrain(actor);
try
{
@ -463,6 +464,34 @@ public static class ActivityInterestTable
return ctx;
}
private static ActivityTerrain ResolveTerrain(Actor actor)
{
try
{
WorldTile tile = actor?.current_tile;
if (tile?.Type == null)
{
return ActivityTerrain.Unknown;
}
if (tile.Type.liquid)
{
return ActivityTerrain.Liquid;
}
if (tile.Type.ground)
{
return ActivityTerrain.Land;
}
}
catch
{
// A missing or changing tile is not safe evidence for environmental prose.
}
return ActivityTerrain.Unknown;
}
public static string DeriveRoleLabel(bool isKing, bool isLeader, bool isWarrior)
{
if (isKing)

View file

@ -324,33 +324,33 @@ public static class ActivityProse
},
["task_unit_play"] = new[]
{
"{actor} finds a way to amuse itself{place_at}",
"{actor} turns idle time into play{place_at}",
"{actor} plays with restless enthusiasm{place_at}"
"{actor} moves about in quick, restless bursts{place_at}",
"{actor} darts back and forth for a while{place_at}",
"{actor} bounds around with eager energy{place_at}"
},
["just_played"] = new[]
{
"{actor} just finished playing and settles again{place_at}",
"{actor} wraps up playtime and looks outward{place_at}",
"{actor} steps away from play with leftover cheer{place_at}"
"{actor} slows down after playing{place_at}",
"{actor} stops to catch a breath{place_at}",
"{actor} settles after a burst of activity{place_at}"
},
["child_play_at_one_spot"] = new[]
{
"{actor} plays in place with busy hands and feet{place_at}",
"{actor} keeps busy with play without wandering far{place_at}",
"{actor} amuses themselves right where they stand{place_at}"
"{actor} hops and pivots without wandering far{place_at}",
"{actor} keeps busy in one small spot{place_at}",
"{actor} turns and bounces in place{place_at}"
},
["child_random_flips"] = new[]
{
"{actor} flips about in a burst of play{place_at}",
"{actor} tumbles through playful motion{place_at}",
"{actor} shows off flips for whoever watches{place_at}"
"{actor} flips and tumbles about{place_at}",
"{actor} rolls forward and springs upright{place_at}",
"{actor} tries one quick flip after another{place_at}"
},
["child_random_jump"] = new[]
{
"{actor} jumps about with bright energy{place_at}",
"{actor} leaps in play across the open ground{place_at}",
"{actor} bounces around as if the day were light{place_at}"
"{actor} jumps about in quick bursts{place_at}",
"{actor} leaps forward and doubles back{place_at}",
"{actor} bounces around without settling{place_at}"
},
["child_follow_parent"] = new[]
{
@ -360,15 +360,15 @@ public static class ActivityProse
},
["random_fun_move"] = new[]
{
"{actor} frolics about with no errand but joy{place_at}",
"{actor} moves for fun through the open moment{place_at}",
"{actor} skips along playfully without hurry{place_at}"
"{actor} frolics and wheels around{place_at}",
"{actor} darts off and circles back{place_at}",
"{actor} skips along without hurry{place_at}"
},
["godfinger_random_fun_move"] = new[]
{
"{actor} frolics under a strange divine whim{place_at}",
"{actor} is nudged into playful motion by unseen will{place_at}",
"{actor} moves about for fun as if guided lightly{place_at}"
"{actor} moves in sudden, unpredictable turns{place_at}",
"{actor} darts one way, then abruptly another{place_at}",
"{actor} loops around in restless motion{place_at}"
},
["try_to_read"] = new[]
{

View file

@ -163,6 +163,7 @@ public static class ActivityVoiceFamilies
public const string Arachnid = "arachnid";
public const string Amphibian = "amphibian";
public const string Aquatic = "aquatic";
public const string Crustacean = "crustacean";
public const string Reptile = "reptile";
public const string Livestock = "livestock";
public const string Plant = "plant";

View file

@ -39,7 +39,25 @@ public static class ActivityVoiceHarness
"deathly little steps",
"mythic calm",
"otherworldly purpose",
"patient citrus"
"patient citrus",
"game",
"through play",
"in play"
};
private static readonly string[] UngroundedTerrainFragments =
{
"through the water",
"in the water",
"through the air",
"across the ground",
"over uneven ground",
"from above",
"from cover",
"close to the ground",
"nose to the ground",
"damp play",
"wet quiet"
};
public static ActivityVoiceAuditResult RunAudit(string outputDirectory)
@ -58,6 +76,7 @@ public static class ActivityVoiceHarness
int collisions = 0;
int vehicleMismatch = 0;
int awkwardProse = 0;
int terrainLeaks = 0;
string sample = "";
string vehicleFingerprint = null;
var fingerprints = new Dictionary<string, string>(StringComparer.Ordinal);
@ -114,6 +133,14 @@ public static class ActivityVoiceHarness
awkwardProse++;
AddSample(ref sample, id + ":awkward=" + awkward);
}
string terrainLeak = voice.AssetKind == ActivityAssetKind.Vehicle
? ""
: FindPhrase(fingerprint, UngroundedTerrainFragments);
if (!string.IsNullOrEmpty(terrainLeak))
{
terrainLeaks++;
AddSample(ref sample, id + ":terrain=" + terrainLeak);
}
if (voice.AssetKind == ActivityAssetKind.Vehicle)
{
@ -173,6 +200,7 @@ public static class ActivityVoiceHarness
&& collisions == 0
&& vehicleMismatch == 0
&& awkwardProse == 0
&& terrainLeaks == 0
&& pairFail == 0
&& liveContextOk;
return new ActivityVoiceAuditResult
@ -189,6 +217,7 @@ public static class ActivityVoiceHarness
+ " collisions=" + collisions
+ " vehicleMismatch=" + vehicleMismatch
+ " awkwardProse=" + awkwardProse
+ " terrainLeaks=" + terrainLeaks
+ " pairFail=" + pairFail
+ " liveContextOk=" + liveContextOk
+ " sample='" + sample + "'"
@ -196,11 +225,16 @@ public static class ActivityVoiceHarness
}
private static string FindAwkwardPhrase(string fingerprint)
{
return FindPhrase(fingerprint, AwkwardProseFragments);
}
private static string FindPhrase(string fingerprint, string[] phrases)
{
string text = fingerprint ?? "";
for (int i = 0; i < AwkwardProseFragments.Length; i++)
for (int i = 0; i < phrases.Length; i++)
{
string phrase = AwkwardProseFragments[i];
string phrase = phrases[i];
if (text.IndexOf(phrase, StringComparison.OrdinalIgnoreCase) >= 0)
{
return phrase;
@ -222,6 +256,13 @@ public static class ActivityVoiceHarness
}
ActivityContext ctx = ActivityInterestTable.BuildContext(actor, "move");
ActivityTerrain expectedTerrain = actor.current_tile?.Type == null
? ActivityTerrain.Unknown
: actor.current_tile.Type.liquid
? ActivityTerrain.Liquid
: actor.current_tile.Type.ground
? ActivityTerrain.Land
: ActivityTerrain.Unknown;
ActivityProse.Format(
ActivityKind.TaskStart,
"move",
@ -234,6 +275,7 @@ public static class ActivityVoiceHarness
bool ok = ctx.Voice != null
&& ctx.Voice.LiveSpeciesId == (actor.asset.id ?? "")
&& ctx.Voice.EffectiveActionTag != ActivityBodyTag.Default
&& ctx.Terrain == expectedTerrain
&& !string.IsNullOrEmpty(line);
if (!ok)
{

View file

@ -279,8 +279,8 @@ public static class ActivityVoiceResolver
|| EqualsIgnore(taxOrder, "scorpiones")
|| EqualsIgnore(taxOrder, "araneae")) return ActivityBodyTag.Arachnid;
if (EqualsIgnore(taxClass, "amphibia")) return ActivityBodyTag.Amphibian;
if (EqualsIgnore(taxClass, "malacostraca")
|| EqualsIgnore(taxClass, "actinopterygii")
if (EqualsIgnore(taxClass, "malacostraca")) return ActivityBodyTag.Crab;
if (EqualsIgnore(taxClass, "actinopterygii")
|| EqualsIgnore(taxClass, "chondrichthyes")
|| EqualsIgnore(taxClass, "cephalopoda")
|| EqualsIgnore(taxOrder, "testudines")) return ActivityBodyTag.Aquatic;
@ -342,6 +342,7 @@ public static class ActivityVoiceResolver
ActivityBodyTag.Amphibian => ActivityVoiceFamilies.Amphibian,
ActivityBodyTag.Aquatic => ActivityVoiceFamilies.Aquatic,
ActivityBodyTag.Seal => ActivityVoiceFamilies.Aquatic,
ActivityBodyTag.Crab => ActivityVoiceFamilies.Crustacean,
ActivityBodyTag.Reptile => ActivityVoiceFamilies.Reptile,
ActivityBodyTag.Livestock => ActivityVoiceFamilies.Livestock,
ActivityBodyTag.Plant => ActivityVoiceFamilies.Plant,
@ -352,7 +353,6 @@ public static class ActivityVoiceResolver
ActivityBodyTag.Dragon => ActivityVoiceFamilies.Mythic,
ActivityBodyTag.Cold => ActivityVoiceFamilies.Mythic,
ActivityBodyTag.Crystal => ActivityVoiceFamilies.Special,
ActivityBodyTag.Crab => ActivityVoiceFamilies.Special,
ActivityBodyTag.Alien => ActivityVoiceFamilies.Special,
ActivityBodyTag.Construct => ActivityVoiceFamilies.Special,
ActivityBodyTag.Tumor => ActivityVoiceFamilies.Special,

View file

@ -2252,12 +2252,13 @@ public static class AgentHarness
case "activity_prose_showcase":
{
// Taxonomy must map to real buckets - never fantasy kingdoms -> monster.
string[] speciesIds = { "zombie", "angle", "lemon_snail", "dragon" };
string[] speciesIds = { "zombie", "angle", "lemon_snail", "dragon", "fairy" };
string[] wantFamily =
{
ActivityVoiceFamilies.Mammal,
ActivityVoiceFamilies.Mythic,
ActivityVoiceFamilies.Plant,
ActivityVoiceFamilies.Mythic,
ActivityVoiceFamilies.Mythic
};
ActivityBodyTag[] wantManner =
@ -2265,7 +2266,8 @@ public static class AgentHarness
ActivityBodyTag.Undead,
ActivityBodyTag.Mythic,
ActivityBodyTag.Plant,
ActivityBodyTag.Dragon
ActivityBodyTag.Dragon,
ActivityBodyTag.Insect
};
var verbs = new (string key, string fact, bool withTarget)[]
{
@ -2523,6 +2525,45 @@ public static class AgentHarness
1,
out string rhinoPlay,
out _);
ActivityProse.Format(
ActivityKind.TaskStart,
"task_unit_play",
ActivityContext.ForHarness(
"Crob",
"",
"crab",
terrain: ActivityTerrain.Land),
"Playing",
108,
1,
out string crabLandPlay,
out _);
ActivityProse.Format(
ActivityKind.TaskStart,
"task_unit_play",
ActivityContext.ForHarness(
"Pip",
"",
"piranha",
terrain: ActivityTerrain.Land),
"Playing",
109,
1,
out string fishLandPlay,
out _);
ActivityProse.Format(
ActivityKind.TaskStart,
"task_unit_play",
ActivityContext.ForHarness(
"Pip",
"",
"piranha",
terrain: ActivityTerrain.Liquid),
"Playing",
109,
1,
out string fishWaterPlay,
out _);
bool taxDiverge = !string.IsNullOrEmpty(beetlePlay)
&& !string.IsNullOrEmpty(frogPlay)
&& !string.IsNullOrEmpty(rhinoPlay)
@ -2543,13 +2584,26 @@ public static class AgentHarness
|| frogPlay.IndexOf("splash", System.StringComparison.OrdinalIgnoreCase) >= 0
|| frogPlay.IndexOf("damp", System.StringComparison.OrdinalIgnoreCase) >= 0
|| frogPlay.IndexOf("jump", System.StringComparison.OrdinalIgnoreCase) >= 0);
bool crabLandOk = !string.IsNullOrEmpty(crabLandPlay)
&& crabLandPlay.IndexOf("water", System.StringComparison.OrdinalIgnoreCase) < 0
&& crabLandPlay.IndexOf("wet", System.StringComparison.OrdinalIgnoreCase) < 0
&& (crabLandPlay.IndexOf("scuttl", System.StringComparison.OrdinalIgnoreCase) >= 0
|| crabLandPlay.IndexOf("sidestep", System.StringComparison.OrdinalIgnoreCase) >= 0);
bool fishTerrainOk = !string.IsNullOrEmpty(fishLandPlay)
&& !string.IsNullOrEmpty(fishWaterPlay)
&& fishLandPlay.IndexOf("water", System.StringComparison.OrdinalIgnoreCase) < 0
&& fishLandPlay.IndexOf("wet", System.StringComparison.OrdinalIgnoreCase) < 0
&& fishWaterPlay.IndexOf("water", System.StringComparison.OrdinalIgnoreCase) >= 0;
pass = farmerOk && minerOk && divergeOk && kingOk && huntPressureOk && taxDiverge;
pass = farmerOk && minerOk && divergeOk && kingOk && huntPressureOk && taxDiverge
&& crabLandOk && fishTerrainOk;
detail =
$"farmerOk={farmerOk} minerOk={minerOk} divergeOk={divergeOk} kingOk={kingOk} "
+ $"huntPressureOk={huntPressureOk} taxDiverge={taxDiverge} "
+ $"crabLandOk={crabLandOk} fishTerrainOk={fishTerrainOk} "
+ $"farmer='{farmerLine}' miner='{minerLine}' king='{kingCombatLine}' hunt='{huntCombatLine}' "
+ $"beetle='{beetlePlay}' frog='{frogPlay}' rhino='{rhinoPlay}'";
+ $"beetle='{beetlePlay}' frog='{frogPlay}' rhino='{rhinoPlay}' "
+ $"crabLand='{crabLandPlay}' fishLand='{fishLandPlay}' fishWater='{fishWaterPlay}'";
break;
}
case "activity_prose_library":

View file

@ -492,7 +492,7 @@ internal static class HarnessScenarios
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("act16b", "activity_force", asset: "task_unit_play", label: "Playing", value: "dog"),
Step("act16c", "assert", expect: "activity_log_contains", value: "play"),
Step("act16c", "assert", expect: "activity_log_contains", value: "bounds"),
Step("act16d", "activity_force", asset: "task_unit_play", label: "Playing", value: "cat"),
Step("act16e", "assert", expect: "activity_prose_variety", value: "2"),
Step("act16f", "activity_force", asset: "BehUnloadResources", label: "wheat@Riverhold", tier: "beat", value: "human"),

View file

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