diff --git a/IdleSpectator/ActivityActionComposer.cs b/IdleSpectator/ActivityActionComposer.cs
index a714833..9663f0e 100644
--- a/IdleSpectator/ActivityActionComposer.cs
+++ b/IdleSpectator/ActivityActionComposer.cs
@@ -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);
}
diff --git a/IdleSpectator/ActivityActionLexicon.cs b/IdleSpectator/ActivityActionLexicon.cs
index e3e3241..c9c25c9 100644
--- a/IdleSpectator/ActivityActionLexicon.cs
+++ b/IdleSpectator/ActivityActionLexicon.cs
@@ -5,7 +5,11 @@ namespace IdleSpectator;
/// Phrase fragments keyed by the already-resolved voice. No ActorAsset lookup.
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);
}
@@ -53,8 +57,8 @@ 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}" },
+ "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:
@@ -98,13 +102,13 @@ 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}" }
};
}
@@ -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
};
}
@@ -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)
};
}
@@ -239,8 +252,14 @@ public static class ActivityActionLexicon
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.Amphibian => new[] { "hops about in lively play{place_at}", "bounces through a lively game{place_at}" },
+ ActivityBodyTag.Aquatic => AquaticTerrainBag(
+ terrain,
+ "splashes through play in the water{place_at}",
+ "darts and circles through the water{place_at}",
+ "twists and darts through play{place_at}",
+ "wriggles through an energetic game{place_at}"),
+ ActivityBodyTag.Crab => new[] { "scuttles through a lively game{place_at}", "sidesteps and spins 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}" },
@@ -260,7 +279,12 @@ public static class ActivityActionLexicon
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}" }
},
@@ -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 };
+ }
}
diff --git a/IdleSpectator/ActivityContext.cs b/IdleSpectator/ActivityContext.cs
index 0ff6a22..f52e02c 100644
--- a/IdleSpectator/ActivityContext.cs
+++ b/IdleSpectator/ActivityContext.cs
@@ -1,5 +1,12 @@
namespace IdleSpectator;
+public enum ActivityTerrain
+{
+ Unknown,
+ Land,
+ Liquid
+}
+
///
/// Snapshot of live actor facts for activity prose. Only observed fields - never invent events.
/// Species / taxonomy / flags come from for every unit in the game.
@@ -41,6 +48,8 @@ public sealed class ActivityContext
public bool TargetIsActor;
/// Building, city, kingdom, or soft place label for clauses.
public string PlaceLabel = "";
+ /// Observed terrain under the actor when this snapshot was captured.
+ 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),
diff --git a/IdleSpectator/ActivityInterestTable.cs b/IdleSpectator/ActivityInterestTable.cs
index 9ec2ad2..c8f0863 100644
--- a/IdleSpectator/ActivityInterestTable.cs
+++ b/IdleSpectator/ActivityInterestTable.cs
@@ -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)
diff --git a/IdleSpectator/ActivityVoice.cs b/IdleSpectator/ActivityVoice.cs
index 924a35a..102ebf8 100644
--- a/IdleSpectator/ActivityVoice.cs
+++ b/IdleSpectator/ActivityVoice.cs
@@ -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";
diff --git a/IdleSpectator/ActivityVoiceHarness.cs b/IdleSpectator/ActivityVoiceHarness.cs
index d67f2a8..2742a5d 100644
--- a/IdleSpectator/ActivityVoiceHarness.cs
+++ b/IdleSpectator/ActivityVoiceHarness.cs
@@ -42,6 +42,21 @@ public static class ActivityVoiceHarness
"patient citrus"
};
+ 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)
{
List ids = ActivityAssetCatalog.EnumerateLiveActorAssetIds();
@@ -58,6 +73,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(StringComparer.Ordinal);
@@ -114,6 +130,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 +197,7 @@ public static class ActivityVoiceHarness
&& collisions == 0
&& vehicleMismatch == 0
&& awkwardProse == 0
+ && terrainLeaks == 0
&& pairFail == 0
&& liveContextOk;
return new ActivityVoiceAuditResult
@@ -189,6 +214,7 @@ public static class ActivityVoiceHarness
+ " collisions=" + collisions
+ " vehicleMismatch=" + vehicleMismatch
+ " awkwardProse=" + awkwardProse
+ + " terrainLeaks=" + terrainLeaks
+ " pairFail=" + pairFail
+ " liveContextOk=" + liveContextOk
+ " sample='" + sample + "'"
@@ -196,11 +222,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 +253,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 +272,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)
{
diff --git a/IdleSpectator/ActivityVoiceResolver.cs b/IdleSpectator/ActivityVoiceResolver.cs
index 4d4736c..3fdd36e 100644
--- a/IdleSpectator/ActivityVoiceResolver.cs
+++ b/IdleSpectator/ActivityVoiceResolver.cs
@@ -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,
diff --git a/IdleSpectator/AgentHarness.cs b/IdleSpectator/AgentHarness.cs
index f6820b8..e2d7a23 100644
--- a/IdleSpectator/AgentHarness.cs
+++ b/IdleSpectator/AgentHarness.cs
@@ -2523,6 +2523,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 +2582,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":
diff --git a/IdleSpectator/mod.json b/IdleSpectator/mod.json
index 7d0371b..1462626 100644
--- a/IdleSpectator/mod.json
+++ b/IdleSpectator/mod.json
@@ -1,7 +1,7 @@
{
"name": "IdleSpectator",
"author": "dazed",
- "version": "0.12.72",
+ "version": "0.12.73",
"description": "AFK Idle Spectator (I) + Lore (L). Detailed living activity prose by default.",
"GUID": "com.dazed.idlespectator"
}