using System;
using System.Collections.Generic;
namespace IdleSpectator;
/// Live asset/task lookup and species-token hygiene. No voice classification.
public static class ActivityAssetCatalog
{
private static HashSet _liveSpeciesPlaceTokens;
private static int _liveSpeciesPlaceTokensCount = -1;
public static ActorAsset TryGetActorAsset(string speciesId)
{
if (string.IsNullOrEmpty(speciesId))
{
return null;
}
try
{
return AssetManager.actor_library?.get(speciesId.Trim());
}
catch
{
return null;
}
}
public static bool IsLiveActorAssetId(string speciesId)
{
if (string.IsNullOrEmpty(speciesId))
{
return false;
}
try
{
return AssetManager.actor_library != null
&& AssetManager.actor_library.has(speciesId.Trim());
}
catch
{
return false;
}
}
public static string SpeciesDisplayLabel(ActorAsset asset)
{
if (asset == null)
{
return "";
}
try
{
string translated = asset.getTranslatedName();
if (!string.IsNullOrEmpty(translated))
{
string localeId = asset.getLocaleID() ?? "";
if (!translated.Equals(localeId, StringComparison.Ordinal)
&& translated.IndexOf('_') < 0)
{
return translated.Trim().ToLowerInvariant();
}
}
}
catch
{
// Localization may not be ready during early harness setup.
}
if (!string.IsNullOrEmpty(asset.name_locale))
{
return asset.name_locale.Trim().ToLowerInvariant();
}
return string.IsNullOrEmpty(asset.id) ? "" : asset.id.Replace('_', ' ').Trim();
}
public static string SpeciesDisplayLabel(string speciesId)
{
ActorAsset asset = TryGetActorAsset(speciesId);
return asset != null
? SpeciesDisplayLabel(asset)
: (string.IsNullOrEmpty(speciesId) ? "" : speciesId.Replace('_', ' ').Trim());
}
/// Title Case species for dossier nametag: Unicorn, not unicorn.
public static string SpeciesNametagLabel(string speciesId)
{
string raw = SpeciesDisplayLabel(speciesId);
return TitleCaseWords(string.IsNullOrEmpty(raw) ? speciesId : raw);
}
///
/// Live prefixes like gatherer_* where ≥2 jobs share P_* and bare P
/// is not itself a job. Rebuilt from .
///
private static HashSet _resourceRoleJobPrefixes;
private static int _resourceRoleJobPrefixesCount = -1;
public static CitizenJobAsset TryGetCitizenJobAsset(string jobId)
{
if (string.IsNullOrEmpty(jobId))
{
return null;
}
try
{
return AssetManager.citizen_job_library?.get(jobId.Trim());
}
catch
{
return null;
}
}
public static List EnumerateLiveCitizenJobIds()
{
var ids = new List();
try
{
var lib = AssetManager.citizen_job_library;
if (lib?.list == null)
{
return ids;
}
for (int i = 0; i < lib.list.Count; i++)
{
CitizenJobAsset asset = lib.list[i];
if (asset != null && !string.IsNullOrEmpty(asset.id))
{
ids.Add(asset.id);
}
}
}
catch
{
// Library can be unavailable during early boot.
}
return ids;
}
///
/// Live citizen-job display for dossier from citizen_job_library ids.
/// Resource-role families discovered from the library
/// (gatherer_herbs → Herb Gatherer); everything else is Title Case id
/// (miner_deposit → Miner Deposit). Never probes invented locale keys.
///
public static string JobDisplayLabel(string jobId)
{
if (string.IsNullOrEmpty(jobId))
{
return "";
}
string id = jobId.Trim();
CitizenJobAsset asset = TryGetCitizenJobAsset(id);
if (asset != null && !string.IsNullOrEmpty(asset.id))
{
id = asset.id;
}
return FormatCitizenJobIdLabel(id);
}
public static string JobDisplayLabel(CitizenJobAsset asset)
{
if (asset == null || string.IsNullOrEmpty(asset.id))
{
return "";
}
return FormatCitizenJobIdLabel(asset.id);
}
///
/// Stable identity role for the dossier nametag. Simulation variants such as
/// miner_deposit describe the current assignment, not a distinct profession.
/// Activity/task prose may still use the full job label.
///
public static string IdentityJobDisplayLabel(string jobId)
{
if (string.IsNullOrEmpty(jobId))
{
return "";
}
string id = jobId.Trim();
const string depositSuffix = "_deposit";
if (id.EndsWith(depositSuffix, StringComparison.OrdinalIgnoreCase)
&& id.Length > depositSuffix.Length)
{
id = id.Substring(0, id.Length - depositSuffix.Length);
}
return JobDisplayLabel(id);
}
public static string IdentityJobDisplayLabel(CitizenJobAsset asset) =>
asset == null ? "" : IdentityJobDisplayLabel(asset.id);
///
/// Title Case + discovered resource-role reorder for a citizen job id.
/// Public for harness audits over the full live library.
///
public static string FormatCitizenJobIdLabel(string jobId)
{
if (string.IsNullOrEmpty(jobId))
{
return "";
}
string id = jobId.Trim();
if (id.StartsWith("job_", StringComparison.OrdinalIgnoreCase))
{
id = id.Substring(4);
}
EnsureResourceRoleJobPrefixes();
int us = id.IndexOf('_');
if (us > 0
&& us < id.Length - 1
&& _resourceRoleJobPrefixes != null
&& _resourceRoleJobPrefixes.Contains(id.Substring(0, us)))
{
string resource = SingularizeResourcePhrase(id.Substring(us + 1).Replace('_', ' '));
if (!string.IsNullOrEmpty(resource))
{
return TitleCaseWords(resource + " " + id.Substring(0, us));
}
}
return TitleCaseWords(id.Replace('_', ' '));
}
private static void EnsureResourceRoleJobPrefixes()
{
List ids = EnumerateLiveCitizenJobIds();
if (_resourceRoleJobPrefixes != null && _resourceRoleJobPrefixesCount == ids.Count)
{
return;
}
var idSet = new HashSet(StringComparer.OrdinalIgnoreCase);
var prefixCounts = new Dictionary(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < ids.Count; i++)
{
string id = ids[i];
if (string.IsNullOrEmpty(id))
{
continue;
}
idSet.Add(id);
int us = id.IndexOf('_');
if (us <= 0 || us >= id.Length - 1)
{
continue;
}
string prefix = id.Substring(0, us);
if (prefixCounts.TryGetValue(prefix, out int count))
{
prefixCounts[prefix] = count + 1;
}
else
{
prefixCounts[prefix] = 1;
}
}
var families = new HashSet(StringComparer.OrdinalIgnoreCase);
foreach (KeyValuePair kv in prefixCounts)
{
// Family: several P_* jobs and no bare job id P (avoids miner + miner_deposit → Deposit Miner).
if (kv.Value >= 2 && !idSet.Contains(kv.Key))
{
families.Add(kv.Key);
}
}
_resourceRoleJobPrefixes = families;
_resourceRoleJobPrefixesCount = ids.Count;
}
private static string SingularizeResourcePhrase(string phrase)
{
if (string.IsNullOrEmpty(phrase))
{
return "";
}
string s = phrase.Trim();
int lastSpace = s.LastIndexOf(' ');
if (lastSpace < 0)
{
return SingularizeResourceWord(s);
}
string head = s.Substring(0, lastSpace).Trim();
string last = SingularizeResourceWord(s.Substring(lastSpace + 1).Trim());
return string.IsNullOrEmpty(last) ? head : head + " " + last;
}
private static string SingularizeResourceWord(string word)
{
if (string.IsNullOrEmpty(word))
{
return "";
}
string w = word.Trim();
if (w.EndsWith("hes", StringComparison.OrdinalIgnoreCase) && w.Length > 3)
{
// bushes → bush
return w.Substring(0, w.Length - 2);
}
if (w.EndsWith("s", StringComparison.OrdinalIgnoreCase)
&& !w.EndsWith("ss", StringComparison.OrdinalIgnoreCase)
&& w.Length > 2)
{
// herbs → herb; honey stays honey
return w.Substring(0, w.Length - 1);
}
return w;
}
/// Title-case whitespace-separated words for nametag identity tags.
public static string TitleCaseWords(string raw)
{
if (string.IsNullOrEmpty(raw))
{
return "";
}
string s = raw.Trim().Replace('_', ' ');
while (s.IndexOf(" ", StringComparison.Ordinal) >= 0)
{
s = s.Replace(" ", " ");
}
if (string.IsNullOrEmpty(s))
{
return "";
}
char[] chars = s.ToCharArray();
bool newWord = true;
for (int i = 0; i < chars.Length; i++)
{
if (char.IsWhiteSpace(chars[i]) || chars[i] == '-' || chars[i] == '/')
{
newWord = true;
continue;
}
if (newWord)
{
chars[i] = char.ToUpperInvariant(chars[i]);
newWord = false;
}
else
{
chars[i] = char.ToLowerInvariant(chars[i]);
}
}
return new string(chars);
}
public static List EnumerateLiveActorAssetIds()
{
var ids = new List();
try
{
ActorAssetLibrary lib = AssetManager.actor_library;
if (lib?.list == null)
{
return ids;
}
for (int i = 0; i < lib.list.Count; i++)
{
ActorAsset asset = lib.list[i];
if (asset != null && !string.IsNullOrEmpty(asset.id))
{
ids.Add(asset.id);
}
}
}
catch
{
// Library can be unavailable during early boot.
}
return ids;
}
public static List EnumerateLiveStatusIds()
{
var ids = new List();
try
{
StatusLibrary lib = AssetManager.status;
if (lib?.list == null)
{
return ids;
}
for (int i = 0; i < lib.list.Count; i++)
{
StatusAsset asset = lib.list[i];
if (asset != null && !string.IsNullOrEmpty(asset.id))
{
ids.Add(asset.id);
}
}
}
catch
{
// Library can be unavailable during early boot.
}
return ids;
}
public static StatusAsset TryGetStatusAsset(string statusId)
{
if (string.IsNullOrEmpty(statusId))
{
return null;
}
try
{
return AssetManager.status?.get(statusId.Trim());
}
catch
{
return null;
}
}
public static List EnumerateLiveHappinessIds()
{
var ids = new List();
try
{
HappinessLibrary lib = AssetManager.happiness_library;
if (lib?.list == null)
{
return ids;
}
for (int i = 0; i < lib.list.Count; i++)
{
HappinessAsset asset = lib.list[i];
if (asset != null && !string.IsNullOrEmpty(asset.id))
{
ids.Add(asset.id);
}
}
}
catch
{
// Library can be unavailable during early boot.
}
return ids;
}
public static HappinessAsset TryGetHappinessAsset(string effectId)
{
if (string.IsNullOrEmpty(effectId))
{
return null;
}
try
{
return AssetManager.happiness_library?.get(effectId.Trim());
}
catch
{
return null;
}
}
public static List EnumerateLiveTaskIds()
{
return EnumerateLibraryIds("tasks_actor");
}
public static List EnumerateLiveWorldLogIds()
{
return EnumerateLibraryIds("world_log_library");
}
public static WorldLogAsset TryGetWorldLogAsset(string assetId)
{
if (string.IsNullOrEmpty(assetId))
{
return null;
}
try
{
return AssetManager.world_log_library?.get(assetId.Trim());
}
catch
{
return null;
}
}
public static List EnumerateLiveDisasterIds()
{
return EnumerateLibraryIds("disasters");
}
public static DisasterAsset TryGetDisasterAsset(string disasterId)
{
if (string.IsNullOrEmpty(disasterId))
{
return null;
}
try
{
return AssetManager.disasters?.get(disasterId.Trim());
}
catch
{
return null;
}
}
public static List EnumerateLiveWarTypeIds()
{
return EnumerateLibraryIds("war_types_library");
}
public static WarTypeAsset TryGetWarTypeAsset(string warTypeId)
{
if (string.IsNullOrEmpty(warTypeId))
{
return null;
}
try
{
return AssetManager.war_types_library?.get(warTypeId.Trim());
}
catch
{
return null;
}
}
public static List EnumerateLivePlotIds()
{
return EnumerateLibraryIds("plots_library");
}
public static List EnumerateLiveEraIds()
{
return EnumerateLibraryIds("era_library");
}
public static List EnumerateLiveBookTypeIds()
{
return EnumerateLibraryIds("book_types");
}
public static List EnumerateLiveTraitIds()
{
return EnumerateLibraryIds("traits");
}
public static List EnumerateLiveBuildingIds()
{
return EnumerateLibraryIds("buildings");
}
public static List EnumerateLiveSpellIds() => EnumerateLibraryIds("spells");
public static List EnumerateLivePowerIds() => EnumerateLibraryIds("powers");
public static List EnumerateLiveDecisionIds() => EnumerateLibraryIds("decisions_library");
public static List EnumerateLiveItemIds() => EnumerateLibraryIds("items");
public static List EnumerateLiveSubspeciesTraitIds() => EnumerateLibraryIds("subspecies_traits");
public static List EnumerateLiveGeneIds() => EnumerateLibraryIds("gene_library");
public static List EnumerateLivePhenotypeIds() => EnumerateLibraryIds("phenotype_library");
public static List EnumerateLiveWorldLawIds() => EnumerateLibraryIds("world_laws_library");
public static List EnumerateLiveBiomeIds() => EnumerateLibraryIds("biome_library");
public static List EnumerateLiveCultureTraitIds() => EnumerateLibraryIds("culture_traits");
public static List EnumerateLiveReligionTraitIds() => EnumerateLibraryIds("religion_traits");
public static List EnumerateLiveClanTraitIds() => EnumerateLibraryIds("clan_traits");
public static List EnumerateLiveLanguageTraitIds() => EnumerateLibraryIds("language_traits");
private static List EnumerateLibraryIds(string assetManagerField)
{
var ids = new List();
try
{
object lib = typeof(AssetManager).GetField(assetManagerField)?.GetValue(null);
object listObj = lib?.GetType().GetField("list")?.GetValue(lib)
?? lib?.GetType().GetProperty("list")?.GetValue(lib, null);
System.Collections.IList list = listObj as System.Collections.IList;
if (list == null)
{
return ids;
}
for (int i = 0; i < list.Count; i++)
{
object asset = list[i];
object idObj = asset?.GetType().GetField("id")?.GetValue(asset)
?? asset?.GetType().GetProperty("id")?.GetValue(asset, null);
string id = idObj as string;
if (!string.IsNullOrEmpty(id))
{
ids.Add(id);
}
}
}
catch
{
// Library can be unavailable during early boot.
}
return ids;
}
public static bool IsLiveSpeciesPlaceToken(string label)
{
if (string.IsNullOrEmpty(label))
{
return false;
}
EnsureLiveSpeciesPlaceTokens();
return _liveSpeciesPlaceTokens != null
&& _liveSpeciesPlaceTokens.Contains(label.Trim());
}
private static void EnsureLiveSpeciesPlaceTokens()
{
try
{
ActorAssetLibrary lib = AssetManager.actor_library;
if (lib?.list == null)
{
return;
}
int count = lib.list.Count;
if (_liveSpeciesPlaceTokens != null && _liveSpeciesPlaceTokensCount == count)
{
return;
}
var tokens = new HashSet(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < count; i++)
{
ActorAsset asset = lib.list[i];
if (asset == null)
{
continue;
}
Add(tokens, asset.id);
Add(tokens, asset.kingdom_id_wild);
Add(tokens, asset.name_locale);
if (!string.IsNullOrEmpty(asset.id))
{
Add(tokens, asset.id.Replace('_', ' '));
}
}
_liveSpeciesPlaceTokens = tokens;
_liveSpeciesPlaceTokensCount = count;
}
catch
{
// Keep prior cache if any.
}
}
private static void Add(HashSet tokens, string value)
{
if (!string.IsNullOrEmpty(value))
{
tokens.Add(value.Trim());
}
}
}