worldbox-observer-mod/IdleSpectator/ActivityAssetCatalog.cs

687 lines
18 KiB
C#

using System;
using System.Collections.Generic;
namespace IdleSpectator;
/// <summary>Live asset/task lookup and species-token hygiene. No voice classification.</summary>
public static class ActivityAssetCatalog
{
private static HashSet<string> _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());
}
/// <summary>Title Case species for dossier nametag: <c>Unicorn</c>, not <c>unicorn</c>.</summary>
public static string SpeciesNametagLabel(string speciesId)
{
string raw = SpeciesDisplayLabel(speciesId);
return TitleCaseWords(string.IsNullOrEmpty(raw) ? speciesId : raw);
}
/// <summary>
/// Live prefixes like <c>gatherer_*</c> where ≥2 jobs share <c>P_*</c> and bare <c>P</c>
/// is not itself a job. Rebuilt from <see cref="EnumerateLiveCitizenJobIds"/>.
/// </summary>
private static HashSet<string> _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<string> EnumerateLiveCitizenJobIds()
{
var ids = new List<string>();
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;
}
/// <summary>
/// Live citizen-job display for dossier from <c>citizen_job_library</c> ids.
/// Resource-role families discovered from the library
/// (<c>gatherer_herbs</c> → <c>Herb Gatherer</c>); everything else is Title Case id
/// (<c>miner_deposit</c> → <c>Miner Deposit</c>). Never probes invented locale keys.
/// </summary>
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);
}
/// <summary>
/// Title Case + discovered resource-role reorder for a citizen job id.
/// Public for harness audits over the full live library.
/// </summary>
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<string> ids = EnumerateLiveCitizenJobIds();
if (_resourceRoleJobPrefixes != null && _resourceRoleJobPrefixesCount == ids.Count)
{
return;
}
var idSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var prefixCounts = new Dictionary<string, int>(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<string>(StringComparer.OrdinalIgnoreCase);
foreach (KeyValuePair<string, int> 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;
}
/// <summary>Title-case whitespace-separated words for nametag identity tags.</summary>
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<string> EnumerateLiveActorAssetIds()
{
var ids = new List<string>();
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<string> EnumerateLiveStatusIds()
{
var ids = new List<string>();
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<string> EnumerateLiveHappinessIds()
{
var ids = new List<string>();
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<string> EnumerateLiveTaskIds()
{
return EnumerateLibraryIds("tasks_actor");
}
public static List<string> 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<string> 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<string> 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<string> EnumerateLivePlotIds()
{
return EnumerateLibraryIds("plots_library");
}
public static List<string> EnumerateLiveEraIds()
{
return EnumerateLibraryIds("era_library");
}
public static List<string> EnumerateLiveBookTypeIds()
{
return EnumerateLibraryIds("book_types");
}
public static List<string> EnumerateLiveTraitIds()
{
return EnumerateLibraryIds("traits");
}
public static List<string> EnumerateLiveBuildingIds()
{
return EnumerateLibraryIds("buildings");
}
public static List<string> EnumerateLiveSpellIds() => EnumerateLibraryIds("spells");
public static List<string> EnumerateLivePowerIds() => EnumerateLibraryIds("powers");
public static List<string> EnumerateLiveDecisionIds() => EnumerateLibraryIds("decisions_library");
public static List<string> EnumerateLiveItemIds() => EnumerateLibraryIds("items");
public static List<string> EnumerateLiveSubspeciesTraitIds() => EnumerateLibraryIds("subspecies_traits");
public static List<string> EnumerateLiveGeneIds() => EnumerateLibraryIds("gene_library");
public static List<string> EnumerateLivePhenotypeIds() => EnumerateLibraryIds("phenotype_library");
public static List<string> EnumerateLiveWorldLawIds() => EnumerateLibraryIds("world_laws_library");
public static List<string> EnumerateLiveBiomeIds() => EnumerateLibraryIds("biome_library");
public static List<string> EnumerateLiveCultureTraitIds() => EnumerateLibraryIds("culture_traits");
public static List<string> EnumerateLiveReligionTraitIds() => EnumerateLibraryIds("religion_traits");
public static List<string> EnumerateLiveClanTraitIds() => EnumerateLibraryIds("clan_traits");
public static List<string> EnumerateLiveLanguageTraitIds() => EnumerateLibraryIds("language_traits");
private static List<string> EnumerateLibraryIds(string assetManagerField)
{
var ids = new List<string>();
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<string>(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<string> tokens, string value)
{
if (!string.IsNullOrEmpty(value))
{
tokens.Add(value.Trim());
}
}
}