worldbox-observer-mod/IdleSpectator/ActivityVoiceHarness.cs

837 lines
31 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
namespace IdleSpectator;
public sealed class ActivityVoiceAuditResult
{
public bool Passed;
public string Detail = "";
}
/// <summary>Exhaustive live-library gates for voice classification and prose fingerprints.</summary>
public static class ActivityVoiceHarness
{
private static readonly string[] FingerprintKeys = ActivitySpeciesVoice.AuthoredVerbs;
private static readonly string[] AwkwardProseFragments =
{
"under combat pressure",
"through the living world",
"through the living moment",
"in a living rush",
"living energy",
"proper meal",
"focused appetite",
"careful focus",
"constructed steps",
"spores about",
"gregs onward",
"through the wet",
"quiet purpose",
"deathly little steps",
"mythic calm",
"otherworldly purpose",
"patient citrus",
"game",
"through play",
"in play",
"blank persistence",
"seared meal",
"sugared meal",
"sweet meal",
"raw meal",
"tough meal",
"patient care",
"with determination",
"playfully",
"with delight",
"cheerful",
"eagerly",
"contentedly",
"stubborn focus",
"stubborn persistence",
"pollinating work",
"fishing task",
"healing task",
"hauling task",
"stealing motion",
"tends the healing",
"works the farm",
"waits over the fishing"
};
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",
" in water",
" through water",
" underwater",
" on the shore",
" across the field",
" in the field",
" tests the ground",
" gaining ground",
" grounded "
};
public static ActivityVoiceAuditResult RunAudit(string outputDirectory)
{
List<string> ids = ActivityAssetCatalog.EnumerateLiveActorAssetIds();
var rows = new StringBuilder(32768);
rows.AppendLine("id\tbase\tkind\tmodifier\tfamily\tbody\teffective\tecology\tsource\tvoice_key\trecovery\tfingerprint");
int missing = 0;
int missingAuthored = 0;
int orphanAuthored = 0;
int defaults = 0;
int emptyProse = 0;
int leaks = 0;
int recoveryFail = 0;
int modifierFail = 0;
int aquaticFalsePositive = 0;
int collisions = 0;
int vehicleMismatch = 0;
int awkwardProse = 0;
int terrainLeaks = 0;
int variantBaseFail = 0;
int authoredPhraseFail = 0;
int targetBindFail = 0;
int unloadPlaceFail = 0;
int unmappedTasks = 0;
int missingTaskBags = 0;
int routedTasks = 0;
string sample = "";
string vehicleFingerprint = null;
var fingerprints = new Dictionary<string, string>(StringComparer.Ordinal);
var fingerprintsById = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var voicesById = new Dictionary<string, ProseVoice>(StringComparer.OrdinalIgnoreCase);
var liveBases = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var validatedBases = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < ids.Count; i++)
{
string id = ids[i];
ActorAsset asset = ActivityAssetCatalog.TryGetActorAsset(id);
if (asset == null)
{
missing++;
AddSample(ref sample, id + ":missing");
continue;
}
ProseVoice voice = ActivityVoiceResolver.Resolve(asset);
voicesById[id] = voice;
if (voice.AssetKind != ActivityAssetKind.Vehicle)
{
liveBases.Add(voice.BaseSpeciesId);
if (voice.Source != ActivityVoiceSource.AuthoredSpecies
|| !ActivitySpeciesVoiceCatalog.HasCompleteVoice(voice.BaseSpeciesId))
{
missingAuthored++;
AddSample(ref sample, id + ":voice-source=" + voice.Source);
}
else if (validatedBases.Add(voice.BaseSpeciesId))
{
authoredPhraseFail += ValidateAuthoredVoice(voice.BaseSpeciesId, ref sample);
}
}
if (voice.EffectiveActionTag == ActivityBodyTag.Default
|| string.IsNullOrEmpty(voice.BaseFamily)
|| voice.BaseFamily == ActivityVoiceFamilies.Default)
{
defaults++;
AddSample(ref sample, id + ":default");
}
if (voice.BaseRecoveryExpected && !voice.BaseRecoverySucceeded)
{
recoveryFail++;
AddSample(ref sample, id + ":base?");
}
if (!ExpectedModifierMatches(asset, voice))
{
modifierFail++;
AddSample(ref sample, id + ":modifier=" + voice.Modifier);
}
if (MustNotBeAquatic(id)
&& (voice.BaseBodyTag == ActivityBodyTag.Aquatic
|| voice.EcologyTag == ActivityEcologyTag.Water
&& voice.AssetKind != ActivityAssetKind.Vehicle))
{
aquaticFalsePositive++;
AddSample(ref sample, id + ":aquatic");
}
int localEmpty;
int localLeaks;
string fingerprint = Fingerprint(id, out localEmpty, out localLeaks);
fingerprintsById[id] = fingerprint;
emptyProse += localEmpty;
leaks += localLeaks;
string awkward = FindAwkwardPhrase(fingerprint);
if (!string.IsNullOrEmpty(awkward))
{
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)
{
if (vehicleFingerprint == null)
{
vehicleFingerprint = fingerprint;
}
else if (!vehicleFingerprint.Equals(fingerprint, StringComparison.Ordinal))
{
vehicleMismatch++;
AddSample(ref sample, id + ":vehicle-fingerprint");
}
}
else if (fingerprints.TryGetValue(fingerprint, out string prior))
{
collisions++;
AddSample(ref sample, prior + "=" + id);
}
else
{
fingerprints[fingerprint] = id;
}
rows.Append(id).Append('\t')
.Append(voice.BaseSpeciesId).Append('\t')
.Append(voice.AssetKind).Append('\t')
.Append(voice.Modifier).Append('\t')
.Append(voice.BaseFamily).Append('\t')
.Append(voice.BaseBodyTag).Append('\t')
.Append(voice.EffectiveActionTag).Append('\t')
.Append(voice.EcologyTag).Append('\t')
.Append(voice.Source).Append('\t')
.Append(voice.VoiceKey).Append('\t')
.Append(voice.BaseRecoveryExpected ? voice.BaseRecoverySucceeded.ToString() : "n/a").Append('\t')
.Append(fingerprint.Replace('\t', ' ')).Append('\n');
}
foreach (KeyValuePair<string, ProseVoice> pair in voicesById)
{
ProseVoice voice = pair.Value;
if (voice.AssetKind == ActivityAssetKind.Vehicle
|| pair.Key.Equals(voice.BaseSpeciesId, StringComparison.OrdinalIgnoreCase))
{
continue;
}
if (!fingerprintsById.TryGetValue(voice.BaseSpeciesId, out string baseFingerprint)
|| !fingerprintsById.TryGetValue(pair.Key, out string variantFingerprint)
|| variantFingerprint.Equals(baseFingerprint, StringComparison.Ordinal))
{
variantBaseFail++;
AddSample(ref sample, pair.Key + ":same-as-base=" + voice.BaseSpeciesId);
}
}
List<string> authoredIds = ActivitySpeciesVoiceCatalog.AuthoredIds();
for (int i = 0; i < authoredIds.Count; i++)
{
if (!liveBases.Contains(authoredIds[i]))
{
orphanAuthored++;
AddSample(ref sample, authoredIds[i] + ":orphan-voice");
}
}
List<string> taskKeys = ActivityVerbMap.EnumerateLiveTaskIds();
string[] beatKeys = ActivityVerbMap.EnumerateBehaviorBeatKeys();
for (int source = 0; source < 2; source++)
{
int count = source == 0 ? taskKeys.Count : beatKeys.Length;
for (int i = 0; i < count; i++)
{
string taskKey = source == 0 ? taskKeys[i] : beatKeys[i];
string verb = ActivityVerbMap.Resolve(taskKey);
routedTasks++;
if (string.IsNullOrEmpty(verb))
{
unmappedTasks++;
AddSample(ref sample, taskKey + ":unmapped-task");
continue;
}
foreach (string baseId in liveBases)
{
string[] bag = ActivitySpeciesVoiceCatalog.GetBag(
baseId,
verb,
ActivityTerrain.Unknown);
if (bag == null || bag.Length < 2)
{
missingTaskBags++;
AddSample(ref sample, baseId + ":" + taskKey + ":missing-bag=" + verb);
}
}
}
}
int pairFail = PairwiseFailures(ref sample);
targetBindFail = ValidateTargetBinding(ref sample);
unloadPlaceFail = ValidateUnloadPlace(ref sample);
bool liveContextOk = VerifyLiveContext(ref sample);
try
{
Directory.CreateDirectory(outputDirectory);
File.WriteAllText(Path.Combine(outputDirectory, "activity-voice-audit.tsv"), rows.ToString());
}
catch (Exception ex)
{
AddSample(ref sample, "write:" + ex.Message);
}
bool passed = ids.Count > 50
&& missing == 0
&& missingAuthored == 0
&& orphanAuthored == 0
&& defaults == 0
&& emptyProse == 0
&& leaks == 0
&& recoveryFail == 0
&& modifierFail == 0
&& aquaticFalsePositive == 0
&& collisions == 0
&& vehicleMismatch == 0
&& awkwardProse == 0
&& terrainLeaks == 0
&& variantBaseFail == 0
&& authoredPhraseFail == 0
&& unmappedTasks == 0
&& missingTaskBags == 0
&& pairFail == 0
&& targetBindFail == 0
&& unloadPlaceFail == 0
&& liveContextOk;
return new ActivityVoiceAuditResult
{
Passed = passed,
Detail = "total=" + ids.Count
+ " missing=" + missing
+ " missingAuthored=" + missingAuthored
+ " orphanAuthored=" + orphanAuthored
+ " defaults=" + defaults
+ " empty=" + emptyProse
+ " leaks=" + leaks
+ " recoveryFail=" + recoveryFail
+ " modifierFail=" + modifierFail
+ " aquaticFalsePositive=" + aquaticFalsePositive
+ " collisions=" + collisions
+ " vehicleMismatch=" + vehicleMismatch
+ " awkwardProse=" + awkwardProse
+ " terrainLeaks=" + terrainLeaks
+ " variantBaseFail=" + variantBaseFail
+ " authoredPhraseFail=" + authoredPhraseFail
+ " routedTasks=" + routedTasks
+ " unmappedTasks=" + unmappedTasks
+ " missingTaskBags=" + missingTaskBags
+ " pairFail=" + pairFail
+ " targetBindFail=" + targetBindFail
+ " unloadPlaceFail=" + unloadPlaceFail
+ " liveContextOk=" + liveContextOk
+ " sample='" + sample + "'"
};
}
private static int ValidateAuthoredVoice(string baseSpeciesId, ref string sample)
{
int failures = 0;
string label = ActivityAssetCatalog.SpeciesDisplayLabel(baseSpeciesId);
for (int i = 0; i < ActivitySpeciesVoice.AuthoredVerbs.Length; i++)
{
string verb = ActivitySpeciesVoice.AuthoredVerbs[i];
string[] bag = ActivitySpeciesVoiceCatalog.GetBag(
baseSpeciesId,
verb,
ActivityTerrain.Unknown);
if (bag == null || bag.Length < 2)
{
failures++;
AddSample(ref sample, baseSpeciesId + ":" + verb + ":incomplete");
continue;
}
bool wantsPlace = verb == "farm" || verb == "trade" || verb == "settle";
for (int p = 0; p < bag.Length; p++)
{
string phrase = bag[p] ?? "";
bool targetMissing = (verb == "hunt" || verb == "fight")
&& phrase.IndexOf("{target}", StringComparison.Ordinal) < 0;
bool hasJob = phrase.IndexOf("{job_as}", StringComparison.Ordinal) >= 0
|| phrase.IndexOf("{job}", StringComparison.Ordinal) >= 0;
bool hasPlaceAt = phrase.IndexOf("{place_at}", StringComparison.Ordinal) >= 0;
bool placePolicyOk = wantsPlace
? phrase.EndsWith("{place_at}", StringComparison.Ordinal)
: !hasPlaceAt;
bool jobPolicyOk = !hasJob;
string standalone = CollapseStandalone(phrase);
string dangling = FindDanglingStandalone(standalone);
bool incomplete = standalone.Length < 12 || standalone.IndexOf(' ') < 0;
bool actorLeak = phrase.IndexOf("{actor}", StringComparison.Ordinal) >= 0;
bool speciesLeak = !string.IsNullOrEmpty(label)
&& Regex.IsMatch(
phrase,
@"\b" + Regex.Escape(label) + @"\b",
RegexOptions.IgnoreCase);
string awkward = FindAwkwardPhrase(phrase);
string terrain = FindPhrase(phrase, UngroundedTerrainFragments);
if (targetMissing || !placePolicyOk || !jobPolicyOk || incomplete
|| !string.IsNullOrEmpty(dangling)
|| actorLeak || speciesLeak
|| !string.IsNullOrEmpty(awkward) || !string.IsNullOrEmpty(terrain))
{
failures++;
AddSample(
ref sample,
baseSpeciesId + ":" + verb + ":" + p
+ ":target=" + !targetMissing
+ ":place=" + placePolicyOk
+ ":job=" + jobPolicyOk
+ ":standalone=" + !incomplete
+ ":dangling=" + dangling
+ ":actor=" + actorLeak
+ ":species=" + speciesLeak
+ ":awkward=" + awkward
+ ":terrain=" + terrain
+ ":text='" + standalone + "'");
}
}
}
return failures;
}
private static string CollapseStandalone(string phrase)
{
string t = phrase ?? "";
t = t.Replace("{place_at}", "");
t = t.Replace("{job_as}", "");
t = t.Replace("{job}", "");
t = t.Replace("{with}", "");
t = t.Replace("{place}", "");
t = t.Replace("{carrying}", "goods");
t = t.Replace("{target}", "Tomi");
t = t.Replace("{actor}", "Actor");
t = t.Replace("{species}", "creature");
while (t.IndexOf(" ", StringComparison.Ordinal) >= 0)
{
t = t.Replace(" ", " ");
}
return t.Trim();
}
private static string FindDanglingStandalone(string standalone)
{
if (string.IsNullOrEmpty(standalone))
{
return "empty";
}
if (Regex.IsMatch(
standalone,
@"\b(in|as|at|with|to|from|near|into|onto|for|of|by)\s*$",
RegexOptions.IgnoreCase))
{
return "dangling-prep";
}
if (Regex.IsMatch(standalone, @"\b(the|a|an|and|or)\s*$", RegexOptions.IgnoreCase))
{
return "dangling-article";
}
if (standalone.IndexOf(" ", StringComparison.Ordinal) >= 0
|| standalone.IndexOf(",,", StringComparison.Ordinal) >= 0)
{
return "dup-sep";
}
return "";
}
private static int ValidateTargetBinding(ref string sample)
{
int failures = 0;
string[,] cases =
{
{ "human", "BehSocializeTalk" },
{ "dog", "BehSocializeTalk" },
{ "human", "lover" }
};
for (int i = 0; i < cases.GetLength(0); i++)
{
string species = cases[i, 0];
string key = cases[i, 1];
ActivityContext ctx = ActivityContext.ForHarness(
"Woliek",
"Tomi",
species,
place: "Celuford");
ActivityProse.Format(
ActivityKind.ActionBeat,
key,
ctx,
"Talks",
77,
1,
out string line,
out _);
if (string.IsNullOrEmpty(line))
{
failures++;
AddSample(ref sample, "target-bind:" + species + ":" + key + ":empty");
continue;
}
bool hasName = line.IndexOf("Tomi", StringComparison.OrdinalIgnoreCase) >= 0;
bool anonLeft = line.IndexOf("with another", StringComparison.OrdinalIgnoreCase) >= 0
|| line.IndexOf("nearby company", StringComparison.OrdinalIgnoreCase) >= 0
|| line.IndexOf("another presence", StringComparison.OrdinalIgnoreCase) >= 0;
int withName = 0;
int idx = 0;
while (idx >= 0)
{
idx = line.IndexOf("with Tomi", idx, StringComparison.OrdinalIgnoreCase);
if (idx < 0)
{
break;
}
withName++;
idx += "with Tomi".Length;
}
// Plural group wording must stay plural (no personal-name rewrite).
bool groupOk = true;
if (line.IndexOf("among the others", StringComparison.OrdinalIgnoreCase) >= 0
&& line.IndexOf("among Tomi", StringComparison.OrdinalIgnoreCase) >= 0)
{
groupOk = false;
}
if (!hasName || anonLeft || withName > 1 || !groupOk)
{
failures++;
AddSample(
ref sample,
"target-bind:" + species + ":" + key
+ ":name=" + hasName
+ ":anon=" + anonLeft
+ ":withDup=" + withName
+ ":group=" + groupOk
+ ":line='" + line + "'");
}
}
return failures;
}
private static int ValidateUnloadPlace(ref string sample)
{
ActivityContext ctx = ActivityContext.ForHarness(
"Porter",
"",
"human",
place: "Riverhold",
carrying: "wheat");
ActivityProse.Format(
ActivityKind.ActionBeat,
"BehUnloadResources",
ctx,
"Unloads wheat",
88,
1,
out string line,
out _);
bool ok = !string.IsNullOrEmpty(line)
&& line.IndexOf("wheat", StringComparison.OrdinalIgnoreCase) >= 0
&& line.IndexOf("Riverhold", StringComparison.OrdinalIgnoreCase) >= 0
&& line.IndexOf("while carrying", StringComparison.OrdinalIgnoreCase) < 0;
if (!ok)
{
AddSample(ref sample, "unload-place:line='" + line + "'");
return 1;
}
return 0;
}
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 < phrases.Length; i++)
{
string phrase = phrases[i];
if (text.IndexOf(phrase, StringComparison.OrdinalIgnoreCase) >= 0)
{
return phrase;
}
}
return "";
}
private static bool VerifyLiveContext(ref string sample)
{
try
{
Actor actor = MoveCamera.hasFocusUnit() ? MoveCamera._focus_unit : null;
if (actor == null || actor.asset == null)
{
AddSample(ref sample, "live-context:no-focus");
return false;
}
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",
ctx,
"Moving",
actor.getID(),
1,
out string line,
out _);
bool ok = ctx.Voice != null
&& ctx.Voice.LiveSpeciesId == (actor.asset.id ?? "")
&& ctx.Voice.EffectiveActionTag != ActivityBodyTag.Default
&& (ctx.Voice.AssetKind == ActivityAssetKind.Vehicle
|| ctx.Voice.Source == ActivityVoiceSource.AuthoredSpecies)
&& ctx.Terrain == expectedTerrain
&& !string.IsNullOrEmpty(line);
if (!ok)
{
AddSample(ref sample, "live-context:" + (actor.asset.id ?? "") + ":" + line);
}
return ok;
}
catch (Exception ex)
{
AddSample(ref sample, "live-context:" + ex.Message);
return false;
}
}
public static string Fingerprint(string speciesId, out int empty, out int leaks)
{
empty = 0;
leaks = 0;
var corpus = new StringBuilder();
ActivityContext probe = ActivityContext.ForHarness("VoiceActor", "VoiceTarget", speciesId);
string speciesLabel = probe.SpeciesLabel ?? "";
for (int i = 0; i < FingerprintKeys.Length; i++)
{
string key = FingerprintKeys[i];
bool target = key == "task_hunt" || key == "task_attack" || key == "social";
ActivityContext ctx = ActivityContext.ForHarness(
"VoiceActor",
target ? "VoiceTarget" : "",
speciesId,
place: "VoicePlace",
carrying: "VoiceCargo",
jobId: "VoiceJob",
taskKey: key,
traitId: "VoiceTrait");
ActivityProse.Format(
ActivityKind.TaskStart,
key,
ctx,
key,
4242,
i + 1,
out string line,
out _);
if (string.IsNullOrEmpty(line))
{
empty++;
}
if (ContainsSpeciesOrTrait(line, speciesId, speciesLabel, "VoiceTrait"))
{
leaks++;
}
corpus.Append(Normalize(line, speciesId, speciesLabel)).Append('|');
}
return corpus.ToString();
}
private static int PairwiseFailures(ref string sample)
{
string[,] pairs =
{
{ "human", "zombie_human" },
{ "dog", "zombie_animal_dog" },
{ "lemon_snail", "zombie_animal_lemon_snail" },
{ "dragon", "zombie_dragon" },
{ "fairy", "zombie_animal_fairy" },
{ "fire_elemental_horse", "fire_elemental_snake" }
};
int failures = 0;
for (int i = 0; i < pairs.GetLength(0); i++)
{
string a = pairs[i, 0];
string b = pairs[i, 1];
if (!ActivityAssetCatalog.IsLiveActorAssetId(a)
|| !ActivityAssetCatalog.IsLiveActorAssetId(b))
{
failures++;
AddSample(ref sample, a + "/" + b + ":missing-pair");
continue;
}
string fa = Fingerprint(a, out _, out _);
string fb = Fingerprint(b, out _, out _);
if (fa.Equals(fb, StringComparison.Ordinal))
{
failures++;
AddSample(ref sample, a + "=" + b);
}
}
List<string> ids = ActivityAssetCatalog.EnumerateLiveActorAssetIds();
string boat = null;
for (int i = 0; i < ids.Count; i++)
{
if (!ids[i].StartsWith("boat_", StringComparison.OrdinalIgnoreCase)) continue;
string fp = Fingerprint(ids[i], out _, out _);
if (boat == null) boat = fp;
else if (!boat.Equals(fp, StringComparison.Ordinal))
{
failures++;
AddSample(ref sample, ids[i] + ":boat-pair");
break;
}
}
return failures;
}
private static string Normalize(string line, string speciesId, string speciesLabel)
{
string text = Regex.Replace(line ?? "", "<[^>]+>", "");
text = ReplaceIgnoreCase(text, "VoiceActor", "");
text = ReplaceIgnoreCase(text, "VoiceTarget", "");
text = ReplaceIgnoreCase(text, "VoicePlace", "");
text = ReplaceIgnoreCase(text, "VoiceCargo", "");
text = ReplaceIgnoreCase(text, "VoiceJob", "");
text = ReplaceIgnoreCase(text, "VoiceTrait", "");
text = ReplaceIgnoreCase(text, speciesLabel, "");
text = ReplaceIgnoreCase(text, (speciesId ?? "").Replace('_', ' '), "");
text = Regex.Replace(text.ToLowerInvariant(), "[^a-z0-9]+", " ").Trim();
return text;
}
private static bool ContainsSpeciesOrTrait(
string line,
string speciesId,
string speciesLabel,
string trait)
{
if (string.IsNullOrEmpty(line)) return false;
if (!string.IsNullOrEmpty(trait)
&& line.IndexOf(trait, StringComparison.OrdinalIgnoreCase) >= 0) return true;
if (!string.IsNullOrEmpty(speciesLabel)
&& line.IndexOf(" the " + speciesLabel, StringComparison.OrdinalIgnoreCase) >= 0) return true;
string idWords = (speciesId ?? "").Replace('_', ' ');
return !string.IsNullOrEmpty(idWords)
&& line.IndexOf(" the " + idWords, StringComparison.OrdinalIgnoreCase) >= 0;
}
private static bool ExpectedModifierMatches(ActorAsset asset, ProseVoice voice)
{
string id = asset.id ?? "";
string wild = asset.kingdom_id_wild ?? "";
if (id.StartsWith("boat_", StringComparison.OrdinalIgnoreCase))
return voice.AssetKind == ActivityAssetKind.Vehicle;
if (wild.Equals("undead", StringComparison.OrdinalIgnoreCase)
|| id.StartsWith("zombie_", StringComparison.OrdinalIgnoreCase))
return voice.Modifier == ActivityModifier.Undead;
if (wild.Equals("fire_elemental", StringComparison.OrdinalIgnoreCase)
|| id.StartsWith("fire_elemental", StringComparison.OrdinalIgnoreCase))
return voice.Modifier == ActivityModifier.Elemental;
if (id.StartsWith("mush_", StringComparison.OrdinalIgnoreCase))
return voice.Modifier == ActivityModifier.Mush;
if (id.StartsWith("tumor_", StringComparison.OrdinalIgnoreCase))
return voice.Modifier == ActivityModifier.Tumor;
if (wild.Equals("aliens", StringComparison.OrdinalIgnoreCase))
return voice.Modifier == ActivityModifier.Alien;
if (wild.Equals("living_houses", StringComparison.OrdinalIgnoreCase))
return voice.Modifier == ActivityModifier.Construct;
if (id.Equals("assimilator", StringComparison.OrdinalIgnoreCase)
|| id.Equals("printer", StringComparison.OrdinalIgnoreCase)
|| id.Equals("god_finger", StringComparison.OrdinalIgnoreCase)
|| id.Equals("crystal_sword", StringComparison.OrdinalIgnoreCase))
return voice.Modifier == ActivityModifier.Construct;
return true;
}
private static bool MustNotBeAquatic(string id)
{
return id.StartsWith("ant_", StringComparison.OrdinalIgnoreCase)
|| id.IndexOf("spider", StringComparison.OrdinalIgnoreCase) >= 0
|| id.Equals("worm", StringComparison.OrdinalIgnoreCase)
|| id.Equals("printer", StringComparison.OrdinalIgnoreCase);
}
private static string ReplaceIgnoreCase(string text, string value, string replacement)
{
if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(value)) return text;
return Regex.Replace(text, Regex.Escape(value), replacement ?? "", RegexOptions.IgnoreCase);
}
private static void AddSample(ref string sample, string value)
{
if (sample.Length >= 500) return;
if (sample.Length > 0) sample += ";";
sample += value;
}
}