using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using NeoModLoader.services;
namespace IdleSpectator;
/// One decision row from event-catalog.json (action phrase + EventStrength).
public sealed class DecisionCatalogRow
{
public string Id = "";
public string Action = "";
public float Strength;
}
/// Library dial defaults from event-catalog.json libraries.
public sealed class LibraryCatalogDefaults
{
public float AmbientStrength = 40f;
public float SignalStrength = 80f;
public string Category = "Event";
public string Label = "{a} · {id}";
public bool AmbientCreatesInterest = true;
public readonly HashSet SignalIds = new HashSet(StringComparer.OrdinalIgnoreCase);
}
///
/// Loads event-catalog.json — SoT for per-event strength/prose overlays.
/// action is decision-only (clause after "decides to"); other domains use label / happiness variants.
///
public static class EventCatalogConfig
{
public const string FileName = "event-catalog.json";
public const float DefaultDecisionStrength = 86f;
private static readonly Dictionary Decisions =
new Dictionary(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary SpeciesBonuses =
new Dictionary(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary CharacterBonuses =
new Dictionary(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary Happiness =
new Dictionary(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary Status =
new Dictionary(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary WorldLog =
new Dictionary(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary Plots =
new Dictionary(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary Relationship =
new Dictionary(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary Story =
new Dictionary(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary Traits =
new Dictionary(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary Books =
new Dictionary(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary Eras =
new Dictionary(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary Disasters =
new Dictionary(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary WarTypes =
new Dictionary(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary Libraries =
new Dictionary(StringComparer.OrdinalIgnoreCase);
private static string _loadedPath = "";
private static string _loadedVersion = "";
private static bool _loadedFromFile;
private static int _generation;
public static bool LoadedFromFile => _loadedFromFile;
public static string LoadedPath => _loadedPath;
public static string LoadedVersion => _loadedVersion;
public static int Generation => _generation;
public static IEnumerable DecisionIds => Decisions.Keys;
public static int DecisionCount => Decisions.Count;
public static int SpeciesCount => SpeciesBonuses.Count;
public static int CharacterCount => CharacterBonuses.Count;
public static int HappinessCount => Happiness.Count;
public static int StatusCount => Status.Count;
public static int WorldLogCount => WorldLog.Count;
public static int PlotCount => Plots.Count;
public static int RelationshipCount => Relationship.Count;
public static int StoryCount => Story.Count;
public static int TraitCount => Traits.Count;
public static int BookCount => Books.Count;
public static int EraCount => Eras.Count;
public static int DisasterCount => Disasters.Count;
public static int WarTypeCount => WarTypes.Count;
public static int LibraryCount => Libraries.Count;
public static void LoadFromModFolder(string modFolder)
{
ClearAll();
_loadedFromFile = false;
_loadedPath = "";
_loadedVersion = "";
if (string.IsNullOrEmpty(modFolder))
{
LogService.LogInfo("[IdleSpectator] event-catalog.json: no mod folder, using C# fallbacks");
BumpAndInvalidate();
return;
}
string path = Path.Combine(modFolder, FileName);
if (!File.Exists(path))
{
LogService.LogInfo($"[IdleSpectator] event-catalog.json missing at {path}, using C# fallbacks");
BumpAndInvalidate();
return;
}
try
{
string json = File.ReadAllText(path);
if (!TryParseDocument(json, out string version))
{
LogService.LogInfo("[IdleSpectator] event-catalog.json parse failed, using C# fallbacks");
ClearAll();
BumpAndInvalidate();
return;
}
_loadedFromFile = Happiness.Count > 0 || Decisions.Count > 0 || Status.Count > 0;
_loadedPath = path;
_loadedVersion = version ?? "";
BumpAndInvalidate();
LogService.LogInfo(
$"[IdleSpectator] event-catalog.json loaded v={_loadedVersion} "
+ $"happiness={Happiness.Count} status={Status.Count} worldLog={WorldLog.Count} "
+ $"plots={Plots.Count} relationship={Relationship.Count} story={Story.Count} traits={Traits.Count} "
+ $"books={Books.Count} eras={Eras.Count} disasters={Disasters.Count} "
+ $"warTypes={WarTypes.Count} decisions={Decisions.Count} libraries={Libraries.Count} "
+ $"species={SpeciesBonuses.Count} character={CharacterBonuses.Count}");
}
catch (Exception ex)
{
ClearAll();
LogService.LogInfo($"[IdleSpectator] event-catalog.json failed ({ex.Message}), using C# fallbacks");
BumpAndInvalidate();
}
}
private static void ClearAll()
{
Decisions.Clear();
SpeciesBonuses.Clear();
CharacterBonuses.Clear();
Happiness.Clear();
Status.Clear();
WorldLog.Clear();
Plots.Clear();
Relationship.Clear();
Story.Clear();
Traits.Clear();
Books.Clear();
Eras.Clear();
Disasters.Clear();
WarTypes.Clear();
Libraries.Clear();
}
private static void BumpAndInvalidate()
{
_generation++;
EventCatalog.InvalidateAllOverlays();
}
internal static bool TryParseDocument(string json, out string version)
{
version = "";
if (string.IsNullOrEmpty(json))
{
return false;
}
version = ExtractJsonString(json, "modVersion") ?? "";
if (TryExtractArray(json, "decisions", out string a)) IngestDecisionObjects(a);
if (TryExtractArray(json, "species", out a)) IngestBonusObjects(a, SpeciesBonuses);
if (TryExtractArray(json, "character", out a)) IngestBonusObjects(a, CharacterBonuses);
if (TryExtractArray(json, "happiness", out a)) IngestHappiness(a);
if (TryExtractArray(json, "status", out a)) IngestStatus(a);
if (TryExtractArray(json, "worldLog", out a)) IngestWorldLog(a);
if (TryExtractArray(json, "plots", out a)) IngestDiscrete(a, Plots);
if (TryExtractArray(json, "relationship", out a)) IngestDiscrete(a, Relationship);
if (TryExtractArray(json, "story", out a)) IngestDiscrete(a, Story);
if (TryExtractArray(json, "traits", out a)) IngestDiscrete(a, Traits);
if (TryExtractArray(json, "books", out a)) IngestDiscrete(a, Books);
if (TryExtractArray(json, "eras", out a)) IngestDiscrete(a, Eras);
if (TryExtractArray(json, "disasters", out a)) IngestDisasters(a);
if (TryExtractArray(json, "warTypes", out a)) IngestWarTypes(a);
IngestLibraries(json);
return Happiness.Count > 0 || Decisions.Count > 0 || Status.Count > 0 || Plots.Count > 0;
}
public static int FillHappiness(Dictionary into)
{
into.Clear();
foreach (KeyValuePair kv in Happiness)
{
into[kv.Key] = kv.Value;
}
return into.Count;
}
public static int FillStatus(Dictionary into)
{
into.Clear();
foreach (KeyValuePair kv in Status)
{
into[kv.Key] = kv.Value;
}
return into.Count;
}
public static int FillWorldLog(Dictionary into)
{
into.Clear();
foreach (KeyValuePair kv in WorldLog)
{
into[kv.Key] = kv.Value;
}
return into.Count;
}
public static int FillPlots(Dictionary into) => FillDiscrete(Plots, into);
public static int FillRelationship(Dictionary into) => FillDiscrete(Relationship, into);
public static int FillStory(Dictionary into) => FillDiscrete(Story, into);
public static int FillTraits(Dictionary into) => FillDiscrete(Traits, into);
public static int FillBooks(Dictionary into) => FillDiscrete(Books, into);
public static int FillEras(Dictionary into) => FillDiscrete(Eras, into);
public static int FillDisasters(Dictionary into)
{
into.Clear();
foreach (KeyValuePair kv in Disasters)
{
into[kv.Key] = kv.Value;
}
return into.Count;
}
public static int FillWarTypes(Dictionary into)
{
into.Clear();
foreach (KeyValuePair kv in WarTypes)
{
into[kv.Key] = kv.Value;
}
return into.Count;
}
private static int FillDiscrete(
Dictionary src,
Dictionary into)
{
into.Clear();
foreach (KeyValuePair kv in src)
{
into[kv.Key] = kv.Value;
}
return into.Count;
}
public static bool TryGetLibraryDefaults(string key, out LibraryCatalogDefaults defaults)
{
defaults = null;
if (string.IsNullOrEmpty(key))
{
return false;
}
return Libraries.TryGetValue(key.Trim(), out defaults) && defaults != null;
}
private static void IngestHappiness(string arrayJson)
{
foreach (string obj in EnumerateObjects(arrayJson))
{
string id = ExtractJsonString(obj, "id");
if (string.IsNullOrEmpty(id)) continue;
var entry = new HappinessCatalogEntry
{
Id = id.Trim(),
EventStrength = ExtractJsonFloat(obj, "strength", 40f),
Presentation = ParseEnum(obj, "presentation", HappinessPresentationTier.Signal),
Context = ParseEnum(obj, "context", HappinessContextRequirement.None),
Life = ParseEnum(obj, "life", HappinessLifePolicy.ActivityOnly),
Overlap = ParseEnum(obj, "overlap", HappinessOverlapPolicy.Independent),
RelationRole = ParseEnum(obj, "relationRole", HappinessRelationRole.None),
InterestCategory = ExtractJsonString(obj, "category") ?? "",
CanonicalMilestoneKey = ExtractJsonString(obj, "canonicalMilestoneKey") ?? "",
StatusOverlapId = ExtractJsonString(obj, "statusOverlapId") ?? "",
StatusOverlapKind = ParseStatusOverlapKind(obj, ExtractJsonString(obj, "statusOverlapId")),
VariantsWithRelated = ExtractStringArray(obj, "variantsWithRelated"),
VariantsWithoutRelated = ExtractStringArray(obj, "variantsWithoutRelated")
};
Happiness[entry.Id] = entry;
}
}
private static void IngestStatus(string arrayJson)
{
foreach (string obj in EnumerateObjects(arrayJson))
{
string id = ExtractJsonString(obj, "id");
if (string.IsNullOrEmpty(id)) continue;
Status[id.Trim()] = new StatusInterestEntry
{
Id = id.Trim(),
EventStrength = ExtractJsonFloat(obj, "strength", 40f),
Category = ExtractJsonString(obj, "category") ?? "Status",
CreatesInterest = ExtractJsonBool(obj, "camera", false),
ExtendsGrief = ExtractJsonBool(obj, "extendsGrief", false)
};
}
}
private static void IngestWorldLog(string arrayJson)
{
foreach (string obj in EnumerateObjects(arrayJson))
{
string id = ExtractJsonString(obj, "id");
if (string.IsNullOrEmpty(id)) continue;
bool camera = ExtractJsonBool(obj, "camera", true);
float strength = ExtractJsonFloat(obj, "strength", 40f);
bool chronicle = ExtractJsonBool(obj, "chronicle", camera && strength >= 65f);
WorldLog[id.Trim()] = new WorldLogEventEntry
{
Id = id.Trim(),
EventStrength = strength,
Category = ExtractJsonString(obj, "category") ?? "WorldLog",
CreatesInterest = camera,
ChronicleEligible = chronicle,
LabelTemplate = ExtractJsonString(obj, "label") ?? "{id}: {a}",
MinWatch = ExtractJsonFloat(obj, "minWatch", 6f),
MaxWatch = ExtractJsonFloat(obj, "maxWatch", 28f)
};
}
}
private static void IngestDiscrete(string arrayJson, Dictionary into)
{
foreach (string obj in EnumerateObjects(arrayJson))
{
string id = ExtractJsonString(obj, "id");
if (string.IsNullOrEmpty(id)) continue;
into[id.Trim()] = new DiscreteEventEntry
{
Id = id.Trim(),
EventStrength = ExtractJsonFloat(obj, "strength", 40f),
Category = ExtractJsonString(obj, "category") ?? "Event",
CreatesInterest = ExtractJsonBool(obj, "camera", true),
LabelTemplate = ExtractJsonString(obj, "label") ?? "{a} · {id}",
ActionPhrase = ExtractJsonString(obj, "action") ?? ""
};
}
}
private static void IngestDisasters(string arrayJson)
{
foreach (string obj in EnumerateObjects(arrayJson))
{
string id = ExtractJsonString(obj, "id");
if (string.IsNullOrEmpty(id)) continue;
Disasters[id.Trim()] = new DisasterInterestEntry
{
Id = id.Trim(),
EventStrength = ExtractJsonFloat(obj, "strength", 90f),
Category = ExtractJsonString(obj, "category") ?? "Disaster",
CreatesInterest = ExtractJsonBool(obj, "camera", true),
WorldLogId = ExtractJsonString(obj, "worldLogId") ?? ""
};
}
}
private static void IngestWarTypes(string arrayJson)
{
foreach (string obj in EnumerateObjects(arrayJson))
{
string id = ExtractJsonString(obj, "id");
if (string.IsNullOrEmpty(id)) continue;
WarTypes[id.Trim()] = new WarTypeInterestEntry
{
Id = id.Trim(),
EventStrength = ExtractJsonFloat(obj, "strength", 85f),
Category = ExtractJsonString(obj, "category") ?? "Politics",
CreatesInterest = ExtractJsonBool(obj, "camera", true),
LabelTemplate = ExtractJsonString(obj, "label") ?? "War ({id})"
};
}
}
private static void IngestLibraries(string json)
{
if (!TryExtractObject(json, "libraries", out string libsJson))
{
return;
}
string[] keys =
{
"spell", "power", "item", "gene", "phenotype", "worldLaw", "subspeciesTrait", "biome",
"cultureTrait", "religionTrait", "clanTrait", "languageTrait"
};
for (int i = 0; i < keys.Length; i++)
{
string key = keys[i];
if (!TryExtractObject(libsJson, key, out string obj))
{
continue;
}
var d = new LibraryCatalogDefaults
{
AmbientStrength = ExtractJsonFloat(obj, "ambientStrength", 40f),
SignalStrength = ExtractJsonFloat(obj, "signalStrength", 80f),
Category = ExtractJsonString(obj, "category") ?? "Event",
Label = ExtractJsonString(obj, "label") ?? "{a} · {id}",
// Large libraries default Signal-only; JSON must opt into ambient A.
AmbientCreatesInterest = ExtractJsonBool(obj, "ambientCreatesInterest", false)
};
string[] ids = ExtractStringArray(obj, "signalIds");
for (int j = 0; j < ids.Length; j++)
{
if (!string.IsNullOrEmpty(ids[j]))
{
d.SignalIds.Add(ids[j]);
}
}
Libraries[key] = d;
}
}
private static int IngestDecisionObjects(string arrayJson)
{
int n = 0;
foreach (string obj in EnumerateObjects(arrayJson))
{
string id = ExtractJsonString(obj, "id");
string action = ExtractJsonString(obj, "action");
if (string.IsNullOrEmpty(id) || string.IsNullOrEmpty(action)) continue;
float strength = ExtractJsonFloat(obj, "strength", DefaultDecisionStrength);
Decisions[id.Trim()] = new DecisionCatalogRow
{
Id = id.Trim(),
Action = action,
Strength = strength > 0f ? strength : DefaultDecisionStrength
};
n++;
}
return n;
}
private static int IngestBonusObjects(string arrayJson, Dictionary into)
{
int n = 0;
foreach (string obj in EnumerateObjects(arrayJson))
{
string id = ExtractJsonString(obj, "id");
if (string.IsNullOrEmpty(id)) continue;
into[id.Trim()] = ExtractJsonFloat(obj, "bonus", 0f);
n++;
}
return n;
}
private static TEnum ParseEnum(string json, string key, TEnum fallback) where TEnum : struct
{
string raw = ExtractJsonString(json, key);
if (string.IsNullOrEmpty(raw)) return fallback;
return Enum.TryParse(raw, true, out TEnum value) ? value : fallback;
}
private static HappinessStatusOverlapKind ParseStatusOverlapKind(string json, string statusOverlapId)
{
if (string.IsNullOrEmpty(statusOverlapId))
{
return HappinessStatusOverlapKind.None;
}
HappinessStatusOverlapKind parsed = ParseEnum(
json, "statusOverlapKind", HappinessStatusOverlapKind.None);
if (parsed != HappinessStatusOverlapKind.None)
{
return parsed;
}
// Legacy rows without statusOverlapKind: offset end-pings vs onset holds.
string id = ExtractJsonString(json, "id") ?? "";
if (IsLegacyOffsetOverlap(id))
{
return HappinessStatusOverlapKind.Offset;
}
return HappinessStatusOverlapKind.Hold;
}
private static bool IsLegacyOffsetOverlap(string effectId)
{
if (string.IsNullOrEmpty(effectId))
{
return false;
}
switch (effectId.Trim().ToLowerInvariant())
{
case "just_laughed":
case "just_sang":
case "just_swore":
case "just_cried":
case "just_had_tantrum":
case "just_inspired":
return true;
default:
return false;
}
}
private static string[] ExtractStringArray(string json, string key)
{
if (!TryExtractArray(json, key, out string arr) || string.IsNullOrEmpty(arr))
{
return Array.Empty();
}
var list = new List();
int i = 0;
while (i < arr.Length)
{
int q = arr.IndexOf('"', i);
if (q < 0) break;
int end = q + 1;
var sb = new StringBuilder();
while (end < arr.Length)
{
char c = arr[end++];
if (c == '\\' && end < arr.Length) { sb.Append(arr[end++]); continue; }
if (c == '"') break;
sb.Append(c);
}
list.Add(sb.ToString());
i = end;
}
return list.ToArray();
}
private static bool ExtractJsonBool(string json, string key, bool fallback)
{
string needle = "\"" + key + "\"";
int keyAt = json.IndexOf(needle, StringComparison.Ordinal);
if (keyAt < 0) return fallback;
int colon = json.IndexOf(':', keyAt + needle.Length);
if (colon < 0) return fallback;
int i = colon + 1;
while (i < json.Length && char.IsWhiteSpace(json[i])) i++;
if (i + 4 <= json.Length && string.Compare(json, i, "true", 0, 4, StringComparison.OrdinalIgnoreCase) == 0)
return true;
if (i + 5 <= json.Length && string.Compare(json, i, "false", 0, 5, StringComparison.OrdinalIgnoreCase) == 0)
return false;
return fallback;
}
private static IEnumerable EnumerateObjects(string arrayJson)
{
if (string.IsNullOrEmpty(arrayJson)) yield break;
int i = 0;
while (i < arrayJson.Length)
{
int start = arrayJson.IndexOf('{', i);
if (start < 0) yield break;
int depth = 0;
bool inString = false;
bool escape = false;
for (int j = start; j < arrayJson.Length; j++)
{
char c = arrayJson[j];
if (inString)
{
if (escape) { escape = false; continue; }
if (c == '\\') { escape = true; continue; }
if (c == '"') inString = false;
continue;
}
if (c == '"') { inString = true; continue; }
if (c == '{') depth++;
else if (c == '}')
{
depth--;
if (depth == 0)
{
yield return arrayJson.Substring(start, j - start + 1);
i = j + 1;
break;
}
}
}
if (depth != 0) yield break;
}
}
private static string ExtractJsonString(string json, string key)
{
string needle = "\"" + key + "\"";
int keyAt = json.IndexOf(needle, StringComparison.Ordinal);
if (keyAt < 0) return null;
int colon = json.IndexOf(':', keyAt + needle.Length);
if (colon < 0) return null;
int i = colon + 1;
while (i < json.Length && char.IsWhiteSpace(json[i])) i++;
if (i >= json.Length || json[i] != '"') return null;
i++;
var sb = new StringBuilder();
while (i < json.Length)
{
char c = json[i++];
if (c == '\\' && i < json.Length) { sb.Append(json[i++]); continue; }
if (c == '"') break;
sb.Append(c);
}
return sb.ToString();
}
private static float ExtractJsonFloat(string json, string key, float fallback)
{
string needle = "\"" + key + "\"";
int keyAt = json.IndexOf(needle, StringComparison.Ordinal);
if (keyAt < 0) return fallback;
int colon = json.IndexOf(':', keyAt + needle.Length);
if (colon < 0) return fallback;
int i = colon + 1;
while (i < json.Length && char.IsWhiteSpace(json[i])) i++;
int start = i;
while (i < json.Length && (char.IsDigit(json[i]) || json[i] == '.' || json[i] == '-' || json[i] == '+'))
i++;
if (i <= start) return fallback;
string token = json.Substring(start, i - start);
return float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out float value)
? value : fallback;
}
private static bool TryExtractArray(string json, string key, out string arrayJson)
{
arrayJson = null;
string keyColon = "\"" + key + "\"";
int searchFrom = 0;
int keyAt = -1;
while (true)
{
int at = json.IndexOf(keyColon, searchFrom, StringComparison.Ordinal);
if (at < 0) break;
int after = at + keyColon.Length;
while (after < json.Length && char.IsWhiteSpace(json[after])) after++;
if (after < json.Length && json[after] == ':') { keyAt = at; break; }
searchFrom = at + 1;
}
if (keyAt < 0) return false;
int bracket = json.IndexOf('[', keyAt + keyColon.Length);
if (bracket < 0) return false;
int depth = 0;
bool inString = false;
bool escape = false;
for (int i = bracket; i < json.Length; i++)
{
char c = json[i];
if (inString)
{
if (escape) { escape = false; continue; }
if (c == '\\') { escape = true; continue; }
if (c == '"') inString = false;
continue;
}
if (c == '"') { inString = true; continue; }
if (c == '[') depth++;
else if (c == ']')
{
depth--;
if (depth == 0)
{
arrayJson = json.Substring(bracket, i - bracket + 1);
return true;
}
}
}
return false;
}
private static bool TryExtractObject(string json, string key, out string objectJson)
{
objectJson = null;
string keyColon = "\"" + key + "\"";
int searchFrom = 0;
int keyAt = -1;
while (true)
{
int at = json.IndexOf(keyColon, searchFrom, StringComparison.Ordinal);
if (at < 0) break;
int after = at + keyColon.Length;
while (after < json.Length && char.IsWhiteSpace(json[after])) after++;
if (after < json.Length && json[after] == ':') { keyAt = at; break; }
searchFrom = at + 1;
}
if (keyAt < 0) return false;
int brace = json.IndexOf('{', keyAt + keyColon.Length);
if (brace < 0) return false;
int depth = 0;
bool inString = false;
bool escape = false;
for (int i = brace; i < json.Length; i++)
{
char c = json[i];
if (inString)
{
if (escape) { escape = false; continue; }
if (c == '\\') { escape = true; continue; }
if (c == '"') inString = false;
continue;
}
if (c == '"') { inString = true; continue; }
if (c == '{') depth++;
else if (c == '}')
{
depth--;
if (depth == 0)
{
objectJson = json.Substring(brace, i - brace + 1);
return true;
}
}
}
return false;
}
public static bool TryGetDecision(string id, out DecisionCatalogRow row)
{
row = null;
if (string.IsNullOrEmpty(id)) return false;
return Decisions.TryGetValue(id.Trim(), out row) && row != null;
}
public static bool TryGetDecisionAction(string id, out string action)
{
action = "";
if (!TryGetDecision(id, out DecisionCatalogRow row)) return false;
action = row.Action ?? "";
return !string.IsNullOrEmpty(action);
}
public static float DecisionStrengthOrDefault(string id, float fallback = DefaultDecisionStrength)
{
if (TryGetDecision(id, out DecisionCatalogRow row) && row.Strength > 0f) return row.Strength;
return fallback;
}
public static float SpeciesBonus(string speciesId)
{
if (string.IsNullOrEmpty(speciesId)) return 0f;
return SpeciesBonuses.TryGetValue(speciesId.Trim(), out float bonus) ? bonus : 0f;
}
public static float CharacterBonus(string roleId, float scoringModelFallback)
{
if (string.IsNullOrEmpty(roleId)) return scoringModelFallback;
if (CharacterBonuses.TryGetValue(roleId.Trim(), out float bonus)) return bonus;
return scoringModelFallback;
}
public static bool Reload()
{
string folder = ModClass.ModFolder;
if (string.IsNullOrEmpty(folder)) return false;
LoadFromModFolder(folder);
return _loadedFromFile;
}
}