468 lines
19 KiB
C#
468 lines
19 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace IdleSpectator;
|
|
|
|
/// <summary>The only ActorAsset → prose voice classifier.</summary>
|
|
public static class ActivityVoiceResolver
|
|
{
|
|
private static readonly Dictionary<string, int> FlavorOrdinals =
|
|
new Dictionary<string, int>(StringComparer.Ordinal);
|
|
private static int _ordinalLibraryCount = -1;
|
|
|
|
public static ProseVoice Resolve(string speciesId)
|
|
{
|
|
ActorAsset asset = ActivityAssetCatalog.TryGetActorAsset(speciesId);
|
|
return asset != null ? Resolve(asset) : ProseVoice.Unknown(speciesId);
|
|
}
|
|
|
|
public static ProseVoice Resolve(ActorAsset liveAsset)
|
|
{
|
|
if (liveAsset == null)
|
|
{
|
|
return ProseVoice.Unknown();
|
|
}
|
|
|
|
string liveId = liveAsset.id ?? "";
|
|
int flavorOrdinal = FlavorOrdinal(liveId);
|
|
|
|
if (IsVehicle(liveId))
|
|
{
|
|
return Build(
|
|
liveAsset,
|
|
liveAsset,
|
|
ActivityAssetKind.Vehicle,
|
|
ActivityModifier.None,
|
|
ActivityBodyTag.Vehicle,
|
|
ActivityEcologyTag.Water,
|
|
ActivityVoiceFamilies.Special,
|
|
flavorOrdinal,
|
|
false,
|
|
true);
|
|
}
|
|
|
|
ActivityModifier modifier = ResolveModifier(liveAsset);
|
|
bool recoveryExpected = modifier == ActivityModifier.Undead
|
|
&& liveId.StartsWith("zombie_", StringComparison.OrdinalIgnoreCase);
|
|
ActorAsset baseAsset = ResolveBaseAsset(liveAsset, modifier, out bool recoverySucceeded);
|
|
if (baseAsset == null)
|
|
{
|
|
baseAsset = liveAsset;
|
|
}
|
|
|
|
ActivityBodyTag body = ResolveBody(baseAsset, liveAsset, modifier);
|
|
ActivityEcologyTag ecology = ResolveEcology(baseAsset, body, modifier);
|
|
string family = ResolveFamily(baseAsset, body);
|
|
ActivityBodyTag effective = EffectiveTag(modifier, body);
|
|
ActivityAssetKind kind = IsExplicitSpecial(liveId)
|
|
? ActivityAssetKind.Special
|
|
: ActivityAssetKind.Creature;
|
|
|
|
return Build(
|
|
liveAsset,
|
|
baseAsset,
|
|
kind,
|
|
modifier,
|
|
effective,
|
|
ecology,
|
|
family,
|
|
flavorOrdinal,
|
|
recoveryExpected,
|
|
recoverySucceeded);
|
|
}
|
|
|
|
public static void ApplyToContext(ActivityContext ctx, ActorAsset liveAsset)
|
|
{
|
|
if (ctx == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ProseVoice voice = Resolve(liveAsset);
|
|
ctx.Voice = voice;
|
|
ctx.SpeciesId = voice.LiveSpeciesId;
|
|
ctx.BaseSpeciesId = voice.BaseSpeciesId;
|
|
ctx.Family = voice.BaseFamily;
|
|
ctx.MannerTag = TagName(voice.EffectiveActionTag);
|
|
ctx.ModifierTag = ModifierName(voice.Modifier);
|
|
ctx.IsCiv = voice.BaseIsCiv;
|
|
ctx.IsHumanoid = voice.BaseIsHumanoid;
|
|
ctx.IsAnimal = voice.BaseIsAnimal;
|
|
|
|
if (liveAsset != null)
|
|
{
|
|
ctx.TaxonomicKingdom = liveAsset.name_taxonomic_kingdom ?? "";
|
|
ctx.TaxonomicPhylum = liveAsset.name_taxonomic_phylum ?? "";
|
|
ctx.TaxonomicClass = liveAsset.name_taxonomic_class ?? "";
|
|
ctx.TaxonomicOrder = liveAsset.name_taxonomic_order ?? "";
|
|
ctx.TaxonomicFamily = liveAsset.name_taxonomic_family ?? "";
|
|
ctx.TaxonomicGenus = liveAsset.name_taxonomic_genus ?? "";
|
|
ctx.TaxonomicSpecies = liveAsset.name_taxonomic_species ?? "";
|
|
}
|
|
}
|
|
|
|
public static string TagName(ActivityBodyTag tag)
|
|
{
|
|
return tag.ToString().ToLowerInvariant();
|
|
}
|
|
|
|
public static string ModifierName(ActivityModifier modifier)
|
|
{
|
|
return modifier == ActivityModifier.None ? "" : modifier.ToString().ToLowerInvariant();
|
|
}
|
|
|
|
private static ProseVoice Build(
|
|
ActorAsset liveAsset,
|
|
ActorAsset baseAsset,
|
|
ActivityAssetKind kind,
|
|
ActivityModifier modifier,
|
|
ActivityBodyTag effective,
|
|
ActivityEcologyTag ecology,
|
|
string family,
|
|
int flavorOrdinal,
|
|
bool recoveryExpected,
|
|
bool recoverySucceeded)
|
|
{
|
|
string liveId = liveAsset?.id ?? "";
|
|
string baseId = baseAsset?.id ?? liveId;
|
|
ActivityBodyTag baseBody = kind == ActivityAssetKind.Vehicle
|
|
? ActivityBodyTag.Vehicle
|
|
: ResolveBody(baseAsset, liveAsset, modifier);
|
|
return new ProseVoice(
|
|
liveId,
|
|
baseId,
|
|
family,
|
|
baseBody,
|
|
ecology,
|
|
modifier,
|
|
effective,
|
|
kind,
|
|
baseAsset != null && baseAsset.civ,
|
|
baseAsset != null && (baseAsset.is_humanoid || baseAsset.civ),
|
|
baseAsset != null && baseAsset.default_animal,
|
|
liveId,
|
|
flavorOrdinal,
|
|
ActivityVoiceProfiles.Resolve(baseId),
|
|
recoveryExpected,
|
|
recoverySucceeded);
|
|
}
|
|
|
|
private static ActivityModifier ResolveModifier(ActorAsset asset)
|
|
{
|
|
string id = asset?.id ?? "";
|
|
string wild = asset?.kingdom_id_wild ?? "";
|
|
|
|
if (EqualsIgnore(wild, "undead")
|
|
|| id.StartsWith("zombie_", StringComparison.OrdinalIgnoreCase)
|
|
|| EqualsIgnore(id, "zombie")
|
|
|| EqualsIgnore(id, "skeleton")
|
|
|| EqualsIgnore(id, "ghost"))
|
|
{
|
|
return ActivityModifier.Undead;
|
|
}
|
|
|
|
if (EqualsIgnore(wild, "fire_elemental")
|
|
|| id.StartsWith("fire_elemental", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return ActivityModifier.Elemental;
|
|
}
|
|
|
|
if (EqualsIgnore(wild, "mush") || id.StartsWith("mush_", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return ActivityModifier.Mush;
|
|
}
|
|
|
|
if (EqualsIgnore(wild, "tumor") || id.StartsWith("tumor_", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return ActivityModifier.Tumor;
|
|
}
|
|
|
|
if (EqualsIgnore(wild, "aliens") || EqualsIgnore(id, "alien") || EqualsIgnore(id, "ufo"))
|
|
{
|
|
return ActivityModifier.Alien;
|
|
}
|
|
|
|
if (EqualsIgnore(wild, "living_houses")
|
|
|| EqualsIgnore(id, "living_house")
|
|
|| EqualsIgnore(id, "assimilator")
|
|
|| EqualsIgnore(id, "printer")
|
|
|| EqualsIgnore(id, "god_finger")
|
|
|| EqualsIgnore(id, "crystal_sword"))
|
|
{
|
|
return ActivityModifier.Construct;
|
|
}
|
|
|
|
return ActivityModifier.None;
|
|
}
|
|
|
|
private static ActorAsset ResolveBaseAsset(
|
|
ActorAsset liveAsset,
|
|
ActivityModifier modifier,
|
|
out bool recoverySucceeded)
|
|
{
|
|
recoverySucceeded = true;
|
|
if (liveAsset == null || modifier != ActivityModifier.Undead)
|
|
{
|
|
return liveAsset;
|
|
}
|
|
|
|
string id = liveAsset.id ?? "";
|
|
string candidate = "";
|
|
if (id.StartsWith("zombie_civ_", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
candidate = id.Substring("zombie_".Length);
|
|
}
|
|
else if (id.StartsWith("zombie_animal_", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
candidate = id.Substring("zombie_animal_".Length);
|
|
}
|
|
else if (id.StartsWith("zombie_", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
candidate = id.Substring("zombie_".Length);
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(candidate))
|
|
{
|
|
return liveAsset;
|
|
}
|
|
|
|
ActorAsset baseAsset = ActivityAssetCatalog.TryGetActorAsset(candidate);
|
|
recoverySucceeded = baseAsset != null;
|
|
return baseAsset ?? liveAsset;
|
|
}
|
|
|
|
private static ActivityBodyTag ResolveBody(
|
|
ActorAsset baseAsset,
|
|
ActorAsset liveAsset,
|
|
ActivityModifier modifier)
|
|
{
|
|
string id = baseAsset?.id ?? liveAsset?.id ?? "";
|
|
string liveId = liveAsset?.id ?? id;
|
|
string taxClass = baseAsset?.name_taxonomic_class ?? "";
|
|
string taxOrder = baseAsset?.name_taxonomic_order ?? "";
|
|
string taxFamily = baseAsset?.name_taxonomic_family ?? "";
|
|
|
|
if (IsVehicle(liveId)) return ActivityBodyTag.Vehicle;
|
|
if (EqualsIgnore(id, "dragon")) return ActivityBodyTag.Dragon;
|
|
if (EqualsIgnore(id, "crabzilla")) return ActivityBodyTag.Crab;
|
|
if (EqualsIgnore(id, "crystal_sword")) return ActivityBodyTag.Crystal;
|
|
if (EqualsIgnore(id, "snowman") || EqualsIgnore(id, "cold_one")) return ActivityBodyTag.Cold;
|
|
if (EqualsIgnore(id, "living_plants")) return ActivityBodyTag.Plant;
|
|
if (EqualsIgnore(id, "living_house") || EqualsIgnore(id, "god_finger")) return ActivityBodyTag.Construct;
|
|
if (EqualsIgnore(id, "assimilator") || EqualsIgnore(id, "printer")) return ActivityBodyTag.Machina;
|
|
if (EqualsIgnore(id, "sand_spider")) return ActivityBodyTag.Arachnid;
|
|
if (EqualsIgnore(id, "worm")) return ActivityBodyTag.Crawler;
|
|
if (id.StartsWith("ant_", StringComparison.OrdinalIgnoreCase)) return ActivityBodyTag.Insect;
|
|
|
|
if (modifier == ActivityModifier.Elemental)
|
|
{
|
|
if (liveId.EndsWith("_horse", StringComparison.OrdinalIgnoreCase)) return ActivityBodyTag.Livestock;
|
|
if (liveId.EndsWith("_snake", StringComparison.OrdinalIgnoreCase)) return ActivityBodyTag.Reptile;
|
|
if (liveId.EndsWith("_slug", StringComparison.OrdinalIgnoreCase)) return ActivityBodyTag.Crawler;
|
|
if (liveId.EndsWith("_blob", StringComparison.OrdinalIgnoreCase)) return ActivityBodyTag.Protist;
|
|
return ActivityBodyTag.Mythic;
|
|
}
|
|
|
|
if (modifier == ActivityModifier.Mush) return ActivityBodyTag.Fungi;
|
|
if (modifier == ActivityModifier.Tumor) return ActivityBodyTag.Tumor;
|
|
if (modifier == ActivityModifier.Alien) return ActivityBodyTag.Alien;
|
|
|
|
if (baseAsset != null && baseAsset.civ) return ActivityBodyTag.Humanoid;
|
|
if (EqualsIgnore(taxClass, "aves")) return ActivityBodyTag.Bird;
|
|
if (EqualsIgnore(taxClass, "insecta")
|
|
|| EqualsIgnore(taxOrder, "hymenoptera")
|
|
|| EqualsIgnore(taxOrder, "coleoptera")
|
|
|| EqualsIgnore(taxOrder, "lepidoptera")
|
|
|| EqualsIgnore(taxOrder, "diptera")
|
|
|| EqualsIgnore(taxOrder, "orthoptera")) return ActivityBodyTag.Insect;
|
|
if (EqualsIgnore(taxClass, "arachnida")
|
|
|| EqualsIgnore(taxOrder, "scorpiones")
|
|
|| EqualsIgnore(taxOrder, "araneae")) return ActivityBodyTag.Arachnid;
|
|
if (EqualsIgnore(taxClass, "amphibia")) return ActivityBodyTag.Amphibian;
|
|
if (EqualsIgnore(taxClass, "malacostraca")) return ActivityBodyTag.Crab;
|
|
if (EqualsIgnore(taxClass, "actinopterygii")
|
|
|| EqualsIgnore(taxClass, "chondrichthyes")
|
|
|| EqualsIgnore(taxClass, "cephalopoda")
|
|
|| EqualsIgnore(taxOrder, "testudines")) return ActivityBodyTag.Aquatic;
|
|
if (EqualsIgnore(taxClass, "reptilia")
|
|
|| EqualsIgnore(taxOrder, "squamata")
|
|
|| EqualsIgnore(taxOrder, "crocodilia")) return ActivityBodyTag.Reptile;
|
|
if (EqualsIgnore(taxFamily, "canidae")) return ActivityBodyTag.Canine;
|
|
if (EqualsIgnore(taxFamily, "felidae")) return ActivityBodyTag.Feline;
|
|
if (EqualsIgnore(taxFamily, "ursidae")) return ActivityBodyTag.Ursid;
|
|
if (EqualsIgnore(taxFamily, "phocidae")) return ActivityBodyTag.Seal;
|
|
if (EqualsIgnore(taxFamily, "procyonidae")) return ActivityBodyTag.Procyonid;
|
|
if (EqualsIgnore(taxFamily, "hyaenidae")) return ActivityBodyTag.Hyaenid;
|
|
if (EqualsIgnore(taxOrder, "primates")) return ActivityBodyTag.Primate;
|
|
if (EqualsIgnore(taxOrder, "rodentia")) return ActivityBodyTag.Rodent;
|
|
if (EqualsIgnore(taxOrder, "cingulata") || EqualsIgnore(taxFamily, "dasypodidae"))
|
|
return ActivityBodyTag.Armadillo;
|
|
if (EqualsIgnore(taxOrder, "artiodactyla")
|
|
|| EqualsIgnore(taxOrder, "perissodactyla")
|
|
|| EqualsIgnore(taxFamily, "bovidae")
|
|
|| EqualsIgnore(taxFamily, "suidae")
|
|
|| EqualsIgnore(taxFamily, "leporidae")
|
|
|| EqualsIgnore(taxFamily, "elephantidae")
|
|
|| EqualsIgnore(taxFamily, "rhinocerotidae")
|
|
|| EqualsIgnore(taxFamily, "camelidae")
|
|
|| EqualsIgnore(taxFamily, "equidae")) return ActivityBodyTag.Livestock;
|
|
|
|
string kingdom = baseAsset?.name_taxonomic_kingdom ?? "";
|
|
if (EqualsIgnore(kingdom, "plantae")) return ActivityBodyTag.Plant;
|
|
if (EqualsIgnore(kingdom, "fungi")) return ActivityBodyTag.Fungi;
|
|
if (EqualsIgnore(kingdom, "protista")) return ActivityBodyTag.Protist;
|
|
if (EqualsIgnore(kingdom, "machina")) return ActivityBodyTag.Machina;
|
|
if (EqualsIgnore(kingdom, "mythoria")) return ActivityBodyTag.Mythic;
|
|
if (EqualsIgnore(kingdom, "gregalia")) return ActivityBodyTag.Gregalia;
|
|
|
|
// All remaining current animalia assets are terrestrial mammals or explicit specials.
|
|
if (EqualsIgnore(taxClass, "mammalia")) return ActivityBodyTag.Livestock;
|
|
if (!string.IsNullOrEmpty(id)) return ActivityBodyTag.Mythic;
|
|
return ActivityBodyTag.Default;
|
|
}
|
|
|
|
private static string ResolveFamily(ActorAsset asset, ActivityBodyTag body)
|
|
{
|
|
if (asset != null && asset.civ) return ActivityVoiceFamilies.Humanoid;
|
|
string kingdom = asset?.name_taxonomic_kingdom ?? "";
|
|
if (EqualsIgnore(kingdom, "plantae")) return ActivityVoiceFamilies.Plant;
|
|
if (EqualsIgnore(kingdom, "fungi")) return ActivityVoiceFamilies.Fungi;
|
|
if (EqualsIgnore(kingdom, "protista")) return ActivityVoiceFamilies.Protist;
|
|
if (EqualsIgnore(kingdom, "machina")) return ActivityVoiceFamilies.Machina;
|
|
if (EqualsIgnore(kingdom, "mythoria")) return ActivityVoiceFamilies.Mythic;
|
|
if (EqualsIgnore(kingdom, "gregalia")) return ActivityVoiceFamilies.Gregalia;
|
|
|
|
return body switch
|
|
{
|
|
ActivityBodyTag.Canine => ActivityVoiceFamilies.Canine,
|
|
ActivityBodyTag.Feline => ActivityVoiceFamilies.Feline,
|
|
ActivityBodyTag.Bird => ActivityVoiceFamilies.Bird,
|
|
ActivityBodyTag.Insect => ActivityVoiceFamilies.Insect,
|
|
ActivityBodyTag.Arachnid => ActivityVoiceFamilies.Arachnid,
|
|
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,
|
|
ActivityBodyTag.Fungi => ActivityVoiceFamilies.Fungi,
|
|
ActivityBodyTag.Protist => ActivityVoiceFamilies.Protist,
|
|
ActivityBodyTag.Machina => ActivityVoiceFamilies.Machina,
|
|
ActivityBodyTag.Mythic => ActivityVoiceFamilies.Mythic,
|
|
ActivityBodyTag.Dragon => ActivityVoiceFamilies.Mythic,
|
|
ActivityBodyTag.Cold => ActivityVoiceFamilies.Mythic,
|
|
ActivityBodyTag.Crystal => ActivityVoiceFamilies.Special,
|
|
ActivityBodyTag.Alien => ActivityVoiceFamilies.Special,
|
|
ActivityBodyTag.Construct => ActivityVoiceFamilies.Special,
|
|
ActivityBodyTag.Tumor => ActivityVoiceFamilies.Special,
|
|
ActivityBodyTag.Gregalia => ActivityVoiceFamilies.Gregalia,
|
|
ActivityBodyTag.Humanoid => ActivityVoiceFamilies.Humanoid,
|
|
ActivityBodyTag.Vehicle => ActivityVoiceFamilies.Special,
|
|
ActivityBodyTag.Default => ActivityVoiceFamilies.Default,
|
|
_ => ActivityVoiceFamilies.Mammal
|
|
};
|
|
}
|
|
|
|
private static ActivityEcologyTag ResolveEcology(
|
|
ActorAsset asset,
|
|
ActivityBodyTag body,
|
|
ActivityModifier modifier)
|
|
{
|
|
if (modifier == ActivityModifier.Undead) return ActivityEcologyTag.Undead;
|
|
if (modifier == ActivityModifier.Alien) return ActivityEcologyTag.Alien;
|
|
if (modifier == ActivityModifier.Construct) return ActivityEcologyTag.Machine;
|
|
if (body == ActivityBodyTag.Vehicle || body == ActivityBodyTag.Aquatic || body == ActivityBodyTag.Seal)
|
|
return ActivityEcologyTag.Water;
|
|
if (body == ActivityBodyTag.Bird || body == ActivityBodyTag.Insect) return ActivityEcologyTag.Air;
|
|
if (body == ActivityBodyTag.Plant) return ActivityEcologyTag.Plant;
|
|
if (body == ActivityBodyTag.Fungi || modifier == ActivityModifier.Mush) return ActivityEcologyTag.Fungal;
|
|
if (body == ActivityBodyTag.Machina) return ActivityEcologyTag.Machine;
|
|
if (body == ActivityBodyTag.Cold) return ActivityEcologyTag.Cold;
|
|
if (asset != null && asset.civ) return ActivityEcologyTag.Civilized;
|
|
if (body == ActivityBodyTag.Mythic || body == ActivityBodyTag.Dragon
|
|
|| modifier == ActivityModifier.Elemental) return ActivityEcologyTag.Mythic;
|
|
return ActivityEcologyTag.Land;
|
|
}
|
|
|
|
private static ActivityBodyTag EffectiveTag(ActivityModifier modifier, ActivityBodyTag body)
|
|
{
|
|
return modifier switch
|
|
{
|
|
ActivityModifier.Undead => ActivityBodyTag.Undead,
|
|
ActivityModifier.Elemental => ActivityBodyTag.Elemental,
|
|
ActivityModifier.Mush => ActivityBodyTag.Mush,
|
|
ActivityModifier.Tumor => ActivityBodyTag.Tumor,
|
|
ActivityModifier.Alien => ActivityBodyTag.Alien,
|
|
ActivityModifier.Construct => ActivityBodyTag.Construct,
|
|
_ => body
|
|
};
|
|
}
|
|
|
|
private static bool IsVehicle(string id)
|
|
{
|
|
return !string.IsNullOrEmpty(id)
|
|
&& id.StartsWith("boat_", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
private static bool IsExplicitSpecial(string id)
|
|
{
|
|
return EqualsIgnore(id, "crabzilla")
|
|
|| EqualsIgnore(id, "god_finger")
|
|
|| EqualsIgnore(id, "living_plants")
|
|
|| EqualsIgnore(id, "living_house")
|
|
|| EqualsIgnore(id, "printer")
|
|
|| EqualsIgnore(id, "assimilator")
|
|
|| EqualsIgnore(id, "crystal_sword");
|
|
}
|
|
|
|
private static int FlavorOrdinal(string id)
|
|
{
|
|
EnsureFlavorOrdinals();
|
|
if (!string.IsNullOrEmpty(id) && FlavorOrdinals.TryGetValue(id, out int ordinal))
|
|
{
|
|
return ordinal;
|
|
}
|
|
|
|
return Math.Abs(StableHash(id)) % 1024;
|
|
}
|
|
|
|
private static void EnsureFlavorOrdinals()
|
|
{
|
|
List<string> ids = ActivityAssetCatalog.EnumerateLiveActorAssetIds();
|
|
if (ids.Count == _ordinalLibraryCount && FlavorOrdinals.Count == ids.Count)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ids.Sort(StringComparer.Ordinal);
|
|
FlavorOrdinals.Clear();
|
|
for (int i = 0; i < ids.Count; i++)
|
|
{
|
|
FlavorOrdinals[ids[i]] = i;
|
|
}
|
|
|
|
_ordinalLibraryCount = ids.Count;
|
|
}
|
|
|
|
private static int StableHash(string value)
|
|
{
|
|
unchecked
|
|
{
|
|
uint h = 2166136261;
|
|
string s = value ?? "";
|
|
for (int i = 0; i < s.Length; i++)
|
|
{
|
|
h ^= s[i];
|
|
h *= 16777619;
|
|
}
|
|
|
|
return (int)(h & 0x7fffffff);
|
|
}
|
|
}
|
|
|
|
private static bool EqualsIgnore(string a, string b)
|
|
{
|
|
return !string.IsNullOrEmpty(a) && a.Equals(b, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
}
|