using System; using System.Collections.Generic; namespace IdleSpectator; public sealed class WorldLogEventEntry { public string Id = ""; public float EventStrength = 40f; public string Category = "WorldLog"; public bool CreatesInterest = true; public bool ChronicleEligible; public float MinWatch = 6f; public float MaxWatch = 28f; public string LabelTemplate = "{id}"; public bool IsFallback; public string MakeLabel(string special1, string special2) { string a = (special1 ?? "").Trim(); string b = (special2 ?? "").Trim(); string template = string.IsNullOrEmpty(LabelTemplate) ? "{id}" : LabelTemplate; string displayId = EventReason.HumanizeId(Id); if (string.IsNullOrEmpty(displayId)) { displayId = (Id ?? "").Trim(); } string label = template .Replace("{id}", displayId) .Replace("{a}", a) .Replace("{b}", b) .Replace("{special1}", a) .Replace("{special2}", b); return CollapseSparseLabel(label, displayId); } public string MakeLabel(WorldLogMessage message) { if (message == null) { return MakeLabel("", ""); } string a = message.special1 ?? ""; string b = message.special2 ?? ""; // Many disaster WorldLogs leave special1 empty - prefer the follow unit name, // else CollapseSparseLabel drops hanging "Tornado: " tails. if (string.IsNullOrWhiteSpace(a) && message.unit != null) { a = EventFeedUtil.SafeName(message.unit) ?? ""; } return MakeLabel(a, b); } /// /// Drop empty-slot leftovers ("Tornado: ", "Event: ") so tips never show a bare colon. /// public static string CollapseSparseLabel(string label, string displayIdFallback) { if (string.IsNullOrWhiteSpace(label)) { return string.IsNullOrEmpty(displayIdFallback) ? "Event" : displayIdFallback; } string s = label.Trim(); while (s.IndexOf(" ", StringComparison.Ordinal) >= 0) { s = s.Replace(" ", " "); } // Trailing separator after an empty {a}/{b}. while (s.Length > 0) { char last = s[s.Length - 1]; if (last == ':' || last == '-' || last == '·' || last == '|') { s = s.Substring(0, s.Length - 1).TrimEnd(); continue; } break; } // Leading separator when {a} was first and empty. while (s.Length > 0) { char first = s[0]; if (first == ':' || first == '-' || first == '·' || first == '|') { s = s.Substring(1).TrimStart(); continue; } break; } if (string.IsNullOrWhiteSpace(s)) { return string.IsNullOrEmpty(displayIdFallback) ? "Event" : displayIdFallback; } return s; } /// True when a rendered tip would look empty or end with a hanging colon. public static bool IsHangingLabel(string label) { if (string.IsNullOrWhiteSpace(label)) { return true; } string s = label.TrimEnd(); return s.EndsWith(":", StringComparison.Ordinal) || s.EndsWith(": ", StringComparison.Ordinal); } } /// WorldLog dials from event-catalog.json worldLog. public static partial class EventCatalog { public static class WorldLog { private static readonly Dictionary Entries = new Dictionary(StringComparer.OrdinalIgnoreCase); private static int _appliedGeneration = -1; public static void InvalidateOverlays() { Entries.Clear(); _appliedGeneration = -1; } private static void Ensure() { if (_appliedGeneration == EventCatalogConfig.Generation) { return; } _appliedGeneration = EventCatalogConfig.Generation; EventCatalogConfig.FillWorldLog(Entries); } public static IEnumerable AuthoredIds { get { Ensure(); return Entries.Keys; } } public static bool HasAuthored(string assetId) { Ensure(); return !string.IsNullOrEmpty(assetId) && Entries.ContainsKey(assetId.Trim()); } public static bool TryGet(string assetId, out WorldLogEventEntry entry) { Ensure(); entry = null; if (string.IsNullOrEmpty(assetId)) { return false; } return Entries.TryGetValue(assetId.Trim(), out entry); } public static WorldLogEventEntry GetOrFallback(string assetId) { if (TryGet(assetId, out WorldLogEventEntry entry)) { return entry; } string id = string.IsNullOrEmpty(assetId) ? "unknown" : assetId.Trim(); float strength = 40f; string category = "WorldLog"; // Live disaster library / authored disaster overlays - not tip-string matching. if (Disaster.IsLiveOrAuthored(id)) { DisasterInterestEntry d = Disaster.GetOrFallback(id); strength = d.EventStrength > 0f ? d.EventStrength : 95f; category = "Disaster"; } return new WorldLogEventEntry { Id = id, EventStrength = strength, Category = category, CreatesInterest = true, ChronicleEligible = strength >= 65f, LabelTemplate = "{id}: {a}", MinWatch = strength >= 90f ? 8f : 6f, MaxWatch = strength >= 90f ? 35f : 28f, IsFallback = true }; } /// WorldLog / disaster tips classified as Disaster via catalog or live library. public static bool IsDisasterCategory(string assetId) { if (string.IsNullOrEmpty(assetId)) { return false; } if (TryGet(assetId, out WorldLogEventEntry entry) && string.Equals(entry.Category, "Disaster", StringComparison.OrdinalIgnoreCase)) { return true; } return Disaster.IsLiveOrAuthored(assetId); } public static bool TryGetEventStrength(string assetId, out float eventStrength) { WorldLogEventEntry entry = GetOrFallback(assetId); eventStrength = entry.EventStrength; return !string.IsNullOrEmpty(assetId); } public static string MakeLabel(WorldLogMessage message) { if (message == null) { return "event"; } return GetOrFallback(message.asset_id).MakeLabel(message); } } }