Decompiled source of CustomSimFramework v0.9.7
CustomSimFramework.dll
Decompiled 3 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Erenshor.CustomSimFramework.Data; using HarmonyLib; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("CustomSimFramework")] [assembly: AssemblyDescription("Framework for adding custom SimPlayers and dialogue to Erenshor")] [assembly: AssemblyProduct("CustomSimFramework")] [assembly: AssemblyFileVersion("0.9.4.0")] [assembly: ComVisible(false)] [assembly: Guid("3b2a1c5d-9f41-4e83-a7d6-1c0e58b2f914")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("0.9.4.0")] namespace Erenshor.CustomSimFramework { [BepInPlugin("erenshor.customsimframework", "Custom Sim Framework", "0.9.4")] public class CustomSimFrameworkPlugin : BaseUnityPlugin { public const string PluginGuid = "erenshor.customsimframework"; public const string PluginName = "Custom Sim Framework"; public const string PluginVersion = "0.9.4"; internal static ManualLogSource Log; internal static ConfigEntry<bool> CfgDumpVanillaData; internal static ConfigEntry<bool> CfgVerboseLogging; internal static ConfigEntry<float> CfgGuildChatFrequency; internal static ConfigEntry<float> CfgGuildQuestFrequency; internal static bool DumpEnabled; internal static bool Verbose; internal static float GuildChatFactor = 1f; internal static float GuildQuestFactor = 1f; private Harmony _harmony; private static CustomSimFrameworkPlugin _instance; private static bool _postLoadDumped; private static bool _postLoadFixupsDone; private void Awake() { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Expected O, but got Unknown //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Expected O, but got Unknown _instance = this; Log = ((BaseUnityPlugin)this).Logger; CfgDumpVanillaData = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "DumpVanillaData", false, "Writes the vanilla sim data (global dialogue pools, pre-authored sims, zone comments, timing checks) to BepInEx/plugins/CustomSimFramework/VanillaDump. Captured BEFORE framework injection, on the FIRST login of the app run, so it reflects the unmodified game. Only needed for development / pack authoring."); CfgVerboseLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "VerboseLogging", false, "Logs detailed framework activity: per-sim language fill statistics, spawn fix-ups, zone chatter matching. Useful when developing a pack or hunting a bug; noisy otherwise. Zero cost when disabled."); CfgGuildChatFrequency = ((BaseUnityPlugin)this).Config.Bind<float>("Dialogue", "GuildChatFrequency", 1f, new ConfigDescription("How often guild sims start QUESTIONS and TOPIC CONVERSATIONS in guild chat. 1 = vanilla (roughly every 2-4 minutes, with occasional quick follow-ups); 2 = twice as often; 0.5 = half as often. Takes effect from the next timer cycle; also applies (rarely, harmlessly) while at character select.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 10f), Array.Empty<object>())); CfgGuildQuestFrequency = ((BaseUnityPlugin)this).Config.Bind<float>("Dialogue", "GuildQuestFrequency", 1f, new ConfigDescription("How often guild sims post fetch-quest asks (\"anyone have a spare X?\"). 1 = vanilla (roughly every 5-27 minutes); 2 = twice as often; 0.5 = half.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 10f), Array.Empty<object>())); DumpEnabled = CfgDumpVanillaData.Value; Verbose = CfgVerboseLogging.Value; GuildChatFactor = ClampFactor(CfgGuildChatFrequency.Value); GuildQuestFactor = ClampFactor(CfgGuildQuestFrequency.Value); CfgDumpVanillaData.SettingChanged += delegate { DumpEnabled = CfgDumpVanillaData.Value; ((Behaviour)this).enabled = true; }; CfgVerboseLogging.SettingChanged += delegate { Verbose = CfgVerboseLogging.Value; }; CfgGuildChatFrequency.SettingChanged += delegate { GuildChatFactor = ClampFactor(CfgGuildChatFrequency.Value); }; CfgGuildQuestFrequency.SettingChanged += delegate { GuildQuestFactor = ClampFactor(CfgGuildQuestFrequency.Value); }; PackLoader.LoadAll(); _harmony = new Harmony("erenshor.customsimframework"); _harmony.PatchAll(typeof(CustomSimFrameworkPlugin).Assembly); Log.LogInfo((object)("Custom Sim Framework 0.9.4 loaded. Sims=" + PackLoader.Sims.Count + " DumpVanillaData=" + DumpEnabled + " VerboseLogging=" + Verbose)); } internal static void OnManagerStart() { _postLoadFixupsDone = false; GlobalDialogueInjector.ResetPerLogin(); if ((Object)(object)_instance != (Object)null) { ((Behaviour)_instance).enabled = true; } } private void Update() { TryPostLoadWork(); if (_postLoadFixupsDone && (_postLoadDumped || !DumpEnabled)) { ((Behaviour)this).enabled = false; } } private static float ClampFactor(float value) { if (value < 0.1f) { return 0.1f; } if (value > 10f) { return 10f; } return value; } internal static void LogDebug(string msg) { if (Verbose) { Log.LogInfo((object)("[Debug] " + msg)); } } internal static void TryPostLoadWork() { bool flag = !_postLoadFixupsDone; bool flag2 = DumpEnabled && !_postLoadDumped; if (!flag && !flag2) { return; } SimPlayerMngr simMngr = GameData.SimMngr; if ((Object)(object)simMngr == (Object)null || !simMngr.LoadedSimplayers) { return; } if (flag2) { _postLoadDumped = true; VanillaDumper.DumpPostLoad(simMngr); } if (!flag) { return; } _postLoadFixupsDone = true; SimTemplateBuilder.ApplyPostLoadFixups(simMngr); try { GlobalDialogueInjector.ApplyGroupingPools(); } catch (Exception ex) { Log.LogError((object)("[Global] Grouping pool injection failed: " + ex)); } try { GlobalDialogueInjector.ApplyGuildPools(); } catch (Exception ex2) { Log.LogError((object)("[Global] Guild pool injection failed: " + ex2)); } try { GlobalDialogueInjector.ValidateZoneTargets(); } catch (Exception ex3) { Log.LogError((object)("[Packs] Zone-target validation failed: " + ex3)); } } } internal static class VanillaDumper { internal static string DumpRoot { get { string directoryName = Path.GetDirectoryName(typeof(VanillaDumper).Assembly.Location); return Path.Combine(string.IsNullOrEmpty(directoryName) ? Paths.PluginPath : directoryName, "VanillaDump"); } } internal static void DumpAtManagerStart(SimPlayerMngr mngr) { DumpEnvironment(mngr); DumpManagerPools(mngr); DumpGlobalLanguage(mngr); DumpActualSims(mngr); DumpGuildPools(); } internal static void DumpPostLoad(SimPlayerMngr mngr) { try { StringBuilder sb = new StringBuilder(); sb.AppendLine("== POST-LOAD STATE (LoadedSimplayers == true) =="); sb.AppendLine("Captured: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); sb.AppendLine(); Section(sb, "GameData.SimLang identity", delegate { SimPlayerLanguage component = ((Component)mngr).GetComponent<SimPlayerLanguage>(); sb.AppendLine("GameData.SimLang is null: " + ((Object)(object)GameData.SimLang == (Object)null)); sb.AppendLine("GameData.SimLang == manager's own SimPlayerLanguage component: " + (GameData.SimLang == component)); }); Section(sb, "Zone reference (scene name -> display name; scene names are the keys for zones.dialogue.json)", delegate { if (ZoneAtlas.Atlas == null) { sb.AppendLine("ZoneAtlas.Atlas is NULL"); } else { sb.AppendLine("ZoneAtlas.Atlas entries: " + ZoneAtlas.Atlas.Length); ZoneAtlasEntry[] atlas = ZoneAtlas.Atlas; foreach (ZoneAtlasEntry val in atlas) { if ((Object)(object)val == (Object)null) { sb.AppendLine(" (null entry)"); } else { string zoneTerm = GetCommonTerms.GetZoneTerm(val.ZoneName); sb.AppendLine(" " + val.ZoneName + ((zoneTerm != val.ZoneName) ? (" -> \"" + zoneTerm + "\"") : "") + " | levels " + val.LevelRangeLow + "-" + val.LevelRangeHigh + " | dungeon=" + val.Dungeon + " | neighbors: " + JoinList(val.NeighboringZones)); } } sb.AppendLine(); sb.AppendLine("NOTE: each zone's ZoneComments (ambient chatter) are stored inside that"); sb.AppendLine("zone's scene file, so they can only be dumped on entering the zone"); sb.AppendLine("(see zones/ folder). Zone NAMES above need no visit."); } }); Section(sb, "SimPlayerGrouping pools (group-context chat: join/leave/targeting/pain)", delegate { SimPlayerGrouping simPlayerGrouping = GameData.SimPlayerGrouping; if ((Object)(object)simPlayerGrouping == (Object)null) { sb.AppendLine("GameData.SimPlayerGrouping is NULL"); } else { DumpAllStringListFields(sb, simPlayerGrouping); } }); Section(sb, "Guilds (RecruitmentStrings are shouted by members as ambient chatter)", delegate { GuildManager guildManager = GameData.GuildManager; if ((Object)(object)guildManager == (Object)null || guildManager.Guilds == null) { sb.AppendLine("GuildManager/Guilds unavailable"); return; } sb.AppendLine("Guilds: " + guildManager.Guilds.Count); foreach (LiveGuildData guild in guildManager.Guilds) { if (guild != null) { sb.AppendLine(" " + guild.GuildName + " | id=" + guild.Id + " | leader=" + guild.GuildLeader + " | members=" + ((guild.GuildMembers != null) ? guild.GuildMembers.Count : 0) + " | skill=" + guild.GuildSkill); if (guild.RecruitmentStrings != null && guild.RecruitmentStrings.Count > 0) { foreach (string recruitmentString in guild.RecruitmentStrings) { sb.AppendLine(" recruit: " + recruitmentString); } } } } }); Section(sb, "Sim roster (" + mngr.Sims.Count + " sims)", delegate { sb.AppendLine("name | class | level | gender | scene | personality | bioIndex | rival | tiedToSlot | gearScore | guild"); foreach (SimPlayerTracking sim in mngr.Sims) { if (sim == null) { sb.AppendLine("(null tracking entry)"); } else { sb.AppendLine(sim.SimName + " | " + sim.ClassName + " | " + sim.Level + " | " + sim.Gender + " | " + sim.CurScene + " | " + sim.Personality + " | " + sim.BioIndex + " | " + sim.Rival + " | " + sim.TiedToSlot + " | " + sim.GearScore + " | " + sim.GuildID); } } }); Write("05_post_load.txt", sb.ToString()); DumpGuildTopics(); } catch (Exception ex) { CustomSimFrameworkPlugin.Log.LogError((object)("[Dump] post-load dump failed: " + ex)); } } internal static void DumpZone(ZoneAnnounce zone) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) StringBuilder sb = new StringBuilder(); Scene val = ((Component)zone).gameObject.scene; object obj; if (!((Scene)(ref val)).IsValid()) { obj = "(no scene)"; } else { val = ((Component)zone).gameObject.scene; obj = ((Scene)(ref val)).name; } string text = (string)obj; sb.AppendLine("== ZONE =="); sb.AppendLine("ZoneName (display): " + zone.ZoneName); sb.AppendLine("Scene (of ZoneAnnounce object): " + text); StringBuilder stringBuilder = sb; val = SceneManager.GetActiveScene(); stringBuilder.AppendLine("Active scene: " + ((Scene)(ref val)).name); sb.AppendLine("isDungeon: " + zone.isDungeon + " RaidCapable: " + zone.RaidCapable); sb.AppendLine(); Section(sb, "ZoneComments (ambient sim chatter pool)", delegate { if (zone.ZoneComments == null) { sb.AppendLine("NULL"); return; } sb.AppendLine("count: " + zone.ZoneComments.Count); foreach (string zoneComment in zone.ZoneComments) { sb.AppendLine(" " + zoneComment); } }); Write(Path.Combine("zones", SanitizeFileName(text) + ".txt"), sb.ToString()); } private static void DumpEnvironment(SimPlayerMngr mngr) { StringBuilder sb = new StringBuilder(); sb.AppendLine("== ENVIRONMENT / TIMING CHECKS (captured in SimPlayerMngr.Start PREFIX) =="); sb.AppendLine("Captured: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); sb.AppendLine(); Section(sb, "Versions", delegate { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) sb.AppendLine("Unity version: " + Application.unityVersion); sb.AppendLine("Game version (Application.version): " + Application.version); StringBuilder stringBuilder = sb; Scene activeScene = SceneManager.GetActiveScene(); stringBuilder.AppendLine("Active scene: " + ((Scene)(ref activeScene)).name); }); Section(sb, "Timing: is GameData ready at our injection point?", delegate { sb.AppendLine("GameData.ClassDB is null: " + ((Object)(object)GameData.ClassDB == (Object)null)); if ((Object)(object)GameData.ClassDB != (Object)null) { sb.AppendLine(" Paladin: " + ClassNameOf(GameData.ClassDB.Paladin)); sb.AppendLine(" Arcanist: " + ClassNameOf(GameData.ClassDB.Arcanist)); sb.AppendLine(" Druid: " + ClassNameOf(GameData.ClassDB.Druid)); sb.AppendLine(" Duelist: " + ClassNameOf(GameData.ClassDB.Duelist)); sb.AppendLine(" Stormcaller: " + ClassNameOf(GameData.ClassDB.Stormcaller)); sb.AppendLine(" Reaver: " + ClassNameOf(GameData.ClassDB.Reaver)); } sb.AppendLine("GameData.SimLang is null: " + ((Object)(object)GameData.SimLang == (Object)null)); sb.AppendLine("GameData.ServerPop: " + GameData.ServerPop); sb.AppendLine("mngr.ServerPop (pre-Start value): " + mngr.ServerPop); sb.AppendLine("ZoneAtlas.Atlas: " + ((ZoneAtlas.Atlas == null) ? "NULL" : (ZoneAtlas.Atlas.Length + " entries"))); }); Section(sb, "Appearance pools", delegate { sb.AppendLine("HairColors: " + CountOf(mngr.HairColors) + " -> " + JoinColors(mngr.HairColors)); sb.AppendLine("SkinColors: " + CountOf(mngr.SkinColors) + " -> " + JoinColors(mngr.SkinColors)); }); Section(sb, "Emoji pool (appended by LovesEmojis quirk)", delegate { FieldInfo field = typeof(SimPlayerMngr).GetField("emojis", BindingFlags.Static | BindingFlags.NonPublic); if (field == null) { sb.AppendLine("private field 'emojis' not found (renamed in a game update?)"); } else { List<string> list = field.GetValue(null) as List<string>; sb.AppendLine((list == null) ? "NULL" : JoinList(list)); } }); Section(sb, "Hair style names (objects under BlankSPTemplate; valid HairName values)", delegate { if ((Object)(object)mngr.BlankSPTemplate == (Object)null) { sb.AppendLine("BlankSPTemplate is NULL"); } else { List<string> list = new List<string>(); Transform[] componentsInChildren = mngr.BlankSPTemplate.GetComponentsInChildren<Transform>(true); foreach (Transform val in componentsInChildren) { if (((Object)val).name.StartsWith("Chr_Hair") && !list.Contains(((Object)val).name)) { list.Add(((Object)val).name); } } list.Sort(); sb.AppendLine((list.Count == 0) ? "none found (hair may live on a different object; Chr_Hair_01..38 observed in vanilla data)" : (list.Count + " styles: " + string.Join(", ", list.ToArray()))); } }); Section(sb, "BlankSPTemplate (spawn template for every sim body)", delegate { DescribeGameObject(sb, mngr.BlankSPTemplate, "BlankSPTemplate"); if ((Object)(object)mngr.BlankSPTemplate != (Object)null) { SimPlayer component = mngr.BlankSPTemplate.GetComponent<SimPlayer>(); sb.AppendLine(" SimPlayer.LootWanted: " + (((Object)(object)component == (Object)null) ? "no SimPlayer" : ((component.LootWanted == null) ? "NULL" : (component.LootWanted.Count + " item(s)")))); SimPlayerLanguage component2 = mngr.BlankSPTemplate.GetComponent<SimPlayerLanguage>(); sb.AppendLine(" has SimPlayerLanguage: " + ((Object)(object)component2 != (Object)null)); if ((Object)(object)component2 != (Object)null) { sb.AppendLine(" SimPlayerLanguage.Public: " + component2.Public); sb.AppendLine(" language list counts: " + SummarizeStringListCounts(component2)); sb.AppendLine(); sb.AppendLine(" FULL CONTENTS - these serialized lists are the LIVE lines for any"); sb.AppendLine(" list LoadSPChat does not overwrite on generic sims (Aggro, Denials,"); sb.AppendLine(" LFGPublic, Goodnight, WantsDrop, ...), and the framework's final"); sb.AppendLine(" fill fallback for custom sims:"); DumpAllStringListFields(sb, component2); } } }); Section(sb, "SPTemplate list", delegate { if (mngr.SPTemplate == null) { sb.AppendLine("NULL"); return; } sb.AppendLine("count: " + mngr.SPTemplate.Count); foreach (GameObject item in mngr.SPTemplate) { DescribeGameObject(sb, item, " entry"); } }); Section(sb, "ActualSims overview (pre-authored sims)", delegate { if (mngr.ActualSims == null) { sb.AppendLine("NULL"); } else { sb.AppendLine("count: " + mngr.ActualSims.Count); foreach (GameObject actualSim in mngr.ActualSims) { sb.AppendLine(" " + (((Object)(object)actualSim == (Object)null) ? "(null)" : ((Object)actualSim).name)); } sb.AppendLine("(full per-sim detail in 03_actual_sims/)"); } }); Write("00_environment.txt", sb.ToString()); } private static void DumpManagerPools(SimPlayerMngr mngr) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("== SimPlayerMngr GLOBAL POOLS =="); stringBuilder.AppendLine("Every public List<string> field on the manager, via reflection,"); stringBuilder.AppendLine("so nothing is missed and names match the shipped game exactly."); stringBuilder.AppendLine(); DumpAllStringListFields(stringBuilder, mngr); Write("01_manager_pools.txt", stringBuilder.ToString()); } private static void DumpGuildPools() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("== GuildManager DIALOGUE POOLS =="); stringBuilder.AppendLine("Every public List<string> field on GameData.GuildManager, via reflection."); stringBuilder.AppendLine("Guild chat questions/asks, topic-conversation support lines, membership"); stringBuilder.AppendLine("and recruitment reactions. Several are fragments composed with live data"); stringBuilder.AppendLine("(see Packs/CATEGORY_REFERENCE.md)."); stringBuilder.AppendLine(); GuildManager guildManager = GameData.GuildManager; if ((Object)(object)guildManager == (Object)null) { stringBuilder.AppendLine("GameData.GuildManager is NULL at SimPlayerMngr.Start prefix time."); } else { DumpAllStringListFields(stringBuilder, guildManager); } Write("06_guild_pools.txt", stringBuilder.ToString()); } private static void DumpGuildTopics() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("== GuildTopic ASSETS (Resources/GuildConvoTopics) =="); stringBuilder.AppendLine("Keyword-activated guild-chat conversations, shared by all guilds."); stringBuilder.AppendLine("ActivationWords: matched against guild-chat input (yours or a sim's own"); stringBuilder.AppendLine("SimPlayerActivations opener). Responses: verbatim replies, optionally"); stringBuilder.AppendLine("wrapped '<Preceed> <response>' or '<response> <End>'. RelevantScene gates"); stringBuilder.AppendLine("answers to that zone (elsewhere: OutOfZoneAnswers + scene name)."); stringBuilder.AppendLine(); AppendTopicFolder(stringBuilder, "GuildConvoTopics"); AppendTopicFolder(stringBuilder, "GuildTutorialTopics"); Write("07_guild_topics.txt", stringBuilder.ToString()); } private static void AppendTopicFolder(StringBuilder sb, string folder) { GuildTopic[] array = Resources.LoadAll<GuildTopic>(folder); sb.AppendLine("---- Resources/" + folder + ": " + array.Length + " topic(s) ----"); sb.AppendLine(); GuildTopic[] array2 = array; foreach (GuildTopic topic in array2) { if (!((Object)(object)topic == (Object)null)) { Section(sb, "Topic: " + ((Object)topic).name + " (" + folder + ")", delegate { sb.AppendLine("RequiredLevelToKnow: " + topic.RequiredLevelToKnow + " MaxLevelToAsk: " + topic.MaxLevelToAsk + " forceLotsOfResponses: " + topic.forceLotsOfResponses); DumpAllStringListFields(sb, topic); }); } } } private static void DumpGlobalLanguage(SimPlayerMngr mngr) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("== GLOBAL SimPlayerLanguage (component on the manager object) =="); SimPlayerLanguage component = ((Component)mngr).GetComponent<SimPlayerLanguage>(); if ((Object)(object)component == (Object)null) { stringBuilder.AppendLine("NOT PRESENT on the manager GameObject!"); } else { stringBuilder.AppendLine("Public flag: " + component.Public); stringBuilder.AppendLine(); DumpAllStringListFields(stringBuilder, component); } Write("02_global_language.txt", stringBuilder.ToString()); } private static void DumpActualSims(SimPlayerMngr mngr) { if (mngr.ActualSims == null) { return; } foreach (GameObject actualSim in mngr.ActualSims) { if (!((Object)(object)actualSim == (Object)null)) { try { StringBuilder stringBuilder = new StringBuilder(); DumpOneActualSim(stringBuilder, actualSim); Write(Path.Combine("03_actual_sims", SanitizeFileName(((Object)actualSim).name) + ".txt"), stringBuilder.ToString()); } catch (Exception ex) { CustomSimFrameworkPlugin.Log.LogError((object)("[Dump] failed for actual sim '" + ((Object)actualSim).name + "': " + ex)); } } } } private static void DumpOneActualSim(StringBuilder sb, GameObject go) { sb.AppendLine("== PRE-AUTHORED SIM: " + ((Object)go).name + " =="); sb.AppendLine(); Section(sb, "GameObject", delegate { DescribeGameObject(sb, go, ((Object)go).name); }); Section(sb, "NPC", delegate { NPC component = go.GetComponent<NPC>(); sb.AppendLine(((Object)(object)component == (Object)null) ? "no NPC component" : ("NPCName: " + component.NPCName)); }); Section(sb, "Stats", delegate { Stats component = go.GetComponent<Stats>(); if ((Object)(object)component == (Object)null) { sb.AppendLine("no Stats component"); } else { sb.AppendLine("Level: " + component.Level); sb.AppendLine("CharacterClass: " + (((Object)(object)component.CharacterClass == (Object)null) ? "NULL" : component.CharacterClass.ClassName)); } }); Section(sb, "Inventory", delegate { Inventory component = go.GetComponent<Inventory>(); if ((Object)(object)component == (Object)null) { sb.AppendLine("no Inventory component"); } else { sb.AppendLine("isMale: " + component.isMale); if (component.EquippedItems != null) { sb.AppendLine("EquippedItems (" + component.EquippedItems.Count + "):"); { foreach (Item equippedItem in component.EquippedItems) { sb.AppendLine(" " + (((Object)(object)equippedItem == (Object)null) ? "(null)" : (((BaseScriptableObject)equippedItem).Id + " : " + equippedItem.ItemName))); } return; } } sb.AppendLine("EquippedItems: NULL"); } }); Section(sb, "SimPlayer (personality / quirks)", delegate { SimPlayer component = go.GetComponent<SimPlayer>(); if ((Object)(object)component == (Object)null) { sb.AppendLine("no SimPlayer component"); } else { sb.AppendLine("SkillLevel: " + component.SkillLevel); sb.AppendLine("Bio: " + component.Bio); sb.AppendLine("PersonalityType: " + component.PersonalityType + " BioIndex: " + component.BioIndex); sb.AppendLine("HairName: " + component.HairName + " HairColor(idx): " + component.HairColor + " SkinColor(idx): " + component.SkinColor); sb.AppendLine("TypesInAllCaps: " + component.TypesInAllCaps); sb.AppendLine("TypesInAllLowers: " + component.TypesInAllLowers); sb.AppendLine("TypesInThirdPerson: " + component.TypesInThirdPerson); sb.AppendLine("RefersToSelfAs: '" + component.RefersToSelfAs + "'"); sb.AppendLine("LovesEmojis: " + component.LovesEmojis); sb.AppendLine("Abbreviates: " + component.Abbreviates); sb.AppendLine("TypoRate: " + component.TypoRate + " TypoChance: " + component.TypoChance); sb.AppendLine("SignOffLine: " + JoinList(component.SignOffLine)); sb.AppendLine("Greed: " + component.Greed + " Patience: " + component.Patience); sb.AppendLine("LoreChase: " + component.LoreChase + " GearChase: " + component.GearChase + " SocialChase: " + component.SocialChase + " Troublemaker: " + component.Troublemaker); sb.AppendLine("DedicationLevel: " + component.DedicationLevel + " Dedication: " + component.Dedication); sb.AppendLine("TiedToSlot: " + component.TiedToSlot); sb.AppendLine("Rival: " + component.Rival + " IsGMCharacter: " + component.IsGMCharacter + " InTutorial: " + component.InTutorial); sb.AppendLine("LootWanted: " + ((component.LootWanted == null) ? "NULL" : (component.LootWanted.Count + " item(s)"))); } }); Section(sb, "Per-sim SimPlayerLanguage (custom dialogue)", delegate { SimPlayerLanguage component = go.GetComponent<SimPlayerLanguage>(); if ((Object)(object)component == (Object)null) { sb.AppendLine("no SimPlayerLanguage component -> uses randomized global dialogue"); } else { sb.AppendLine("Public flag: " + component.Public); sb.AppendLine(); DumpAllStringListFields(sb, component); } }); } private static void Section(StringBuilder sb, string title, Action body) { sb.AppendLine("---- " + title + " ----"); try { body(); } catch (Exception ex) { sb.AppendLine("!! SECTION FAILED: " + ex.GetType().Name + ": " + ex.Message); } sb.AppendLine(); } private static void DumpAllStringListFields(StringBuilder sb, object target) { FieldInfo[] fields = target.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType != typeof(List<string>)) { continue; } List<string> list = (List<string>)fieldInfo.GetValue(target); sb.AppendLine("### " + fieldInfo.Name + " (" + ((list == null) ? "NULL" : list.Count.ToString()) + ")"); if (list != null) { foreach (string item in list) { sb.AppendLine(" " + item); } } sb.AppendLine(); } } private static string SummarizeStringListCounts(object target) { StringBuilder stringBuilder = new StringBuilder(); FieldInfo[] fields = target.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public); foreach (FieldInfo fieldInfo in fields) { if (!(fieldInfo.FieldType != typeof(List<string>))) { List<string> list = (List<string>)fieldInfo.GetValue(target); stringBuilder.Append(fieldInfo.Name + "=" + ((list == null) ? "NULL" : list.Count.ToString()) + " "); } } return stringBuilder.ToString(); } private static void DescribeGameObject(StringBuilder sb, GameObject go, string label) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)go == (Object)null) { sb.AppendLine(label + ": NULL"); return; } string[] obj = new string[9] { label, ": name='", ((Object)go).name, "' activeSelf=", go.activeSelf.ToString(), " activeInHierarchy=", go.activeInHierarchy.ToString(), " scene=", null }; Scene scene = go.scene; object obj2; if (!((Scene)(ref scene)).IsValid()) { obj2 = "(none - prefab asset)"; } else { scene = go.scene; obj2 = ((Scene)(ref scene)).name; } obj[8] = (string)obj2; sb.AppendLine(string.Concat(obj)); Component[] components = go.GetComponents<Component>(); StringBuilder stringBuilder = new StringBuilder(); Component[] array = components; foreach (Component val in array) { stringBuilder.Append(((Object)(object)val == (Object)null) ? "(null/missing script)" : ((object)val).GetType().Name); stringBuilder.Append(", "); } sb.AppendLine(" components: " + stringBuilder); ModularPar componentInChildren = go.GetComponentInChildren<ModularPar>(true); sb.AppendLine(" ModularPar in children: " + ((Object)(object)componentInChildren != (Object)null)); } private static string ClassNameOf(Class cls) { if (!((Object)(object)cls == (Object)null)) { return cls.ClassName; } return "NULL"; } private static string CountOf<T>(List<T> list) { if (list != null) { return list.Count.ToString(); } return "NULL"; } private static string JoinList(List<string> list) { if (list == null) { return "NULL"; } return "[" + string.Join(" | ", list.ToArray()) + "]"; } private static string JoinColors(List<Color> list) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) if (list == null) { return "NULL"; } StringBuilder stringBuilder = new StringBuilder(); foreach (Color item in list) { stringBuilder.Append("#" + ColorUtility.ToHtmlStringRGBA(item) + " "); } return stringBuilder.ToString(); } private static string SanitizeFileName(string name) { if (string.IsNullOrEmpty(name)) { return "unnamed"; } char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char oldChar in invalidFileNameChars) { name = name.Replace(oldChar, '_'); } return name; } private static void Write(string relativePath, string content) { string text = Path.Combine(DumpRoot, relativePath); Directory.CreateDirectory(Path.GetDirectoryName(text)); File.WriteAllText(text, content); CustomSimFrameworkPlugin.Log.LogInfo((object)("[Dump] wrote " + text)); } } internal static class PackLoader { private static readonly Regex NamePattern = new Regex("^[A-Za-z0-9-]+$", RegexOptions.Compiled); internal static readonly List<SimDefinition> Sims = new List<SimDefinition>(); internal static readonly List<GlobalDialogueDefinition> GlobalDialogues = new List<GlobalDialogueDefinition>(); internal static readonly List<ZoneDialogueDefinition> ZoneDialogues = new List<ZoneDialogueDefinition>(); internal static readonly List<TopicsDefinition> TopicPacks = new List<TopicsDefinition>(); private const string ShippedPackPrefix = "ShippedPack::"; private static Dictionary<string, List<string>> _schemaLocations; internal static string PacksRoot => Path.Combine(Path.Combine(Paths.ConfigPath, "CustomSimFramework"), "Packs"); private static Dictionary<string, List<string>> SchemaLocations { get { if (_schemaLocations == null) { _schemaLocations = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase); AddSchemaLocation(typeof(SimDefinition), "a *.sim.json file"); AddSchemaLocation(typeof(GlobalDialogueDefinition), "the top level of global.dialogue.json"); AddSchemaLocation(typeof(ManagerPoolAdditions), "ManagerPools"); AddSchemaLocation(typeof(GlobalLanguageAdditions), "GlobalLanguage"); AddSchemaLocation(typeof(GroupingPoolAdditions), "GroupingPools"); AddSchemaLocation(typeof(GuildPoolAdditions), "GuildPools"); AddSchemaLocation(typeof(TemplateLanguageAdditions), "TemplateLanguage"); AddSchemaLocation(typeof(ZoneDialogueDefinition), "the top level of zones.dialogue.json"); AddSchemaLocation(typeof(ZoneLines), "a Zones entry"); AddSchemaLocation(typeof(TopicsDefinition), "the top level of topics.dialogue.json"); AddSchemaLocation(typeof(TopicDefinition), "a Topics entry"); } return _schemaLocations; } } private static void SeedShippedPacks() { try { Assembly assembly = typeof(PackLoader).Assembly; HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); HashSet<string> hashSet2 = new HashSet<string>(StringComparer.OrdinalIgnoreCase); string[] manifestResourceNames = assembly.GetManifestResourceNames(); foreach (string text in manifestResourceNames) { if (!text.StartsWith("ShippedPack::", StringComparison.Ordinal)) { continue; } string[] array = text.Substring("ShippedPack::".Length).Split(new string[1] { "::" }, StringSplitOptions.None); if (array.Length != 2) { continue; } string text2 = array[0]; string text3 = array[1]; if (hashSet.Contains(text2)) { continue; } string text4 = Path.Combine(PacksRoot, text2); if (!hashSet2.Contains(text2)) { if (Directory.Exists(text4)) { hashSet.Add(text2); continue; } Directory.CreateDirectory(text4); hashSet2.Add(text2); CustomSimFrameworkPlugin.Log.LogInfo((object)("[Packs] Installed the bundled '" + text2 + "' example pack to " + text4 + " (first run; your packs live here, outside the mod manager's folder, so mod updates never touch them).")); } try { using Stream stream = assembly.GetManifestResourceStream(text); using FileStream destination = new FileStream(Path.Combine(text4, text3), FileMode.Create, FileAccess.Write); stream.CopyTo(destination); } catch (Exception ex) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] Could not extract '" + text3 + "' of bundled pack '" + text2 + "': " + ex.Message)); } } } catch (Exception ex2) { CustomSimFrameworkPlugin.LogDebug("Pack seeding skipped: " + ex2.Message); } } internal static void LoadAll() { Sims.Clear(); GlobalDialogues.Clear(); ZoneDialogues.Clear(); TopicPacks.Clear(); RunJsonSelfTest(); SeedShippedPacks(); if (!Directory.Exists(PacksRoot)) { Directory.CreateDirectory(PacksRoot); CustomSimFrameworkPlugin.Log.LogInfo((object)("[Packs] No Packs folder found - created empty one at " + PacksRoot)); return; } string[] directories = Directory.GetDirectories(PacksRoot); string[] array = directories; foreach (string text in array) { string fileName = Path.GetFileName(text); try { LoadPack(text, fileName); } catch (Exception ex) { CustomSimFrameworkPlugin.Log.LogError((object)("[Packs] Failed loading pack '" + fileName + "': " + ex)); } } CustomSimFrameworkPlugin.Log.LogInfo((object)("[Packs] Loaded " + directories.Length + " pack(s): " + Sims.Count + " sim(s), " + GlobalDialogues.Count + " global dialogue file(s), " + ZoneDialogues.Count + " zone dialogue file(s), " + TopicPacks.Count + " topic file(s).")); } private static void LoadPack(string packDir, string packName) { string[] files = Directory.GetFiles(packDir, "*.sim.json"); foreach (string path in files) { SimDefinition simDefinition = ParseJson<SimDefinition>(path); if (simDefinition == null) { continue; } simDefinition.SourceFile = packName + "/" + Path.GetFileName(path); if (string.IsNullOrEmpty(simDefinition.Name) || simDefinition.Name.Trim().Length == 0) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] " + simDefinition.SourceFile + " has no Name - skipped.")); continue; } simDefinition.Name = simDefinition.Name.Trim(); if (!NamePattern.IsMatch(simDefinition.Name)) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] Sim name '" + simDefinition.Name + "' in " + simDefinition.SourceFile + " contains unsupported characters (allowed: A-Z, a-z, 0-9, hyphen; no spaces) - skipped.")); continue; } if (FindSimByName(simDefinition.Name) != null) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] Duplicate sim name '" + simDefinition.Name + "' in " + simDefinition.SourceFile + " - skipped (first definition wins).")); continue; } if (simDefinition.TiedToSlot != -1 && simDefinition.Rival) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] " + simDefinition.SourceFile + ": TiedToSlot is ignored for Rival sims (the game forces rivals to 99).")); simDefinition.TiedToSlot = -1; } if (simDefinition.TiedToSlot != -1 && (simDefinition.TiedToSlot < 0 || simDefinition.TiedToSlot > 10) && simDefinition.TiedToSlot != 12 && simDefinition.TiedToSlot != 99) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] " + simDefinition.SourceFile + ": TiedToSlot=" + simDefinition.TiedToSlot + " is not valid (-1, 0-10, 12 or 99) - using -1.")); simDefinition.TiedToSlot = -1; } simDefinition.Gender = (simDefinition.Gender ?? "").Trim(); if (!string.Equals(simDefinition.Gender, "Male", StringComparison.OrdinalIgnoreCase) && !string.Equals(simDefinition.Gender, "Female", StringComparison.OrdinalIgnoreCase)) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] " + simDefinition.SourceFile + ": Gender '" + simDefinition.Gender + "' is not 'Male' or 'Female' - defaulting to Male.")); } simDefinition.RefersToSelfAs = (simDefinition.RefersToSelfAs ?? "").Trim(); Sims.Add(simDefinition); CustomSimFrameworkPlugin.Log.LogInfo((object)("[Packs] Sim '" + simDefinition.Name + "' loaded from " + simDefinition.SourceFile)); } string path2 = Path.Combine(packDir, "global.dialogue.json"); if (File.Exists(path2)) { GlobalDialogueDefinition globalDialogueDefinition = ParseGlobalDialogue(path2); if (globalDialogueDefinition != null) { globalDialogueDefinition.SourceFile = packName + "/global.dialogue.json"; GlobalDialogues.Add(globalDialogueDefinition); } } string path3 = Path.Combine(packDir, "zones.dialogue.json"); if (File.Exists(path3)) { ZoneDialogueDefinition zoneDialogueDefinition = ParseZoneDialogue(path3); if (zoneDialogueDefinition != null) { zoneDialogueDefinition.SourceFile = packName + "/zones.dialogue.json"; ZoneDialogues.Add(zoneDialogueDefinition); } } string path4 = Path.Combine(packDir, "topics.dialogue.json"); if (File.Exists(path4)) { TopicsDefinition topicsDefinition = ParseTopics(path4); if (topicsDefinition != null) { topicsDefinition.SourceFile = packName + "/topics.dialogue.json"; TopicPacks.Add(topicsDefinition); } } } private static TopicsDefinition ParseTopics(string path) { string text = Path.GetFileName(Path.GetDirectoryName(path)) + "/" + Path.GetFileName(path); try { string text2 = File.ReadAllText(path); TopicsDefinition topicsDefinition = JsonUtility.FromJson<TopicsDefinition>(text2); if (topicsDefinition == null) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] " + path + " parsed to null (malformed JSON?) - skipped.")); return null; } if (TopLevelJsonSections(text2).TryGetValue("Topics", out var value)) { topicsDefinition.Topics = new List<TopicDefinition>(); foreach (string item in TopLevelArrayElements(value)) { TopicDefinition topicDefinition = ParseSection<TopicDefinition>(item, "Topics entry", text); if (topicDefinition != null) { CheckKeysAtLevel(item, typeof(TopicDefinition), "a Topics entry", text); topicsDefinition.Topics.Add(topicDefinition); } } } CheckKeysAtLevel(text2, typeof(TopicsDefinition), "the top level of topics.dialogue.json", text); CleanStringLists(topicsDefinition, path); ValidateTopics(topicsDefinition, text); int num = ((topicsDefinition.Topics != null) ? topicsDefinition.Topics.Count : 0); int num2 = 0; if (topicsDefinition.Topics != null) { foreach (TopicDefinition topic in topicsDefinition.Topics) { num2 += CountLines(topic); } } CustomSimFrameworkPlugin.Log.LogInfo((object)("[Packs] " + text + " parsed: " + num + " topic(s) with " + num2 + " line(s).")); return topicsDefinition; } catch (Exception ex) { CustomSimFrameworkPlugin.Log.LogError((object)("[Packs] Could not parse " + path + ": " + ex.Message)); return null; } } private static void ValidateTopics(TopicsDefinition def, string displayName) { if (def.Topics == null) { return; } HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); for (int num = def.Topics.Count - 1; num >= 0; num--) { TopicDefinition topicDefinition = def.Topics[num]; topicDefinition.Name = (topicDefinition.Name ?? "").Trim(); if (topicDefinition.Name.Length == 0) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] " + displayName + ": a Topics entry has no Name - skipped.")); def.Topics.RemoveAt(num); } else if (topicDefinition.Responses == null || topicDefinition.Responses.Count == 0) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] " + displayName + ": topic '" + topicDefinition.Name + "' has no Responses - skipped (a matched topic without responses would crash the game).")); def.Topics.RemoveAt(num); } else { bool num2 = topicDefinition.SimPlayerActivations == null || topicDefinition.SimPlayerActivations.Count == 0; bool flag = topicDefinition.ActivationWords == null || topicDefinition.ActivationWords.Count == 0; if (num2 && flag) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] " + displayName + ": topic '" + topicDefinition.Name + "' has neither SimPlayerActivations nor ActivationWords - unreachable, skipped.")); def.Topics.RemoveAt(num); } else { if (!hashSet.Add(topicDefinition.Name)) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] " + displayName + ": duplicate topic name '" + topicDefinition.Name + "' - both are injected, but logs will be ambiguous.")); } if (topicDefinition.Responses.Count < 3) { CustomSimFrameworkPlugin.Log.LogInfo((object)("[Packs] " + displayName + ": topic '" + topicDefinition.Name + "' has only " + topicDefinition.Responses.Count + " response(s) - conversations will repeat (and the game's dedup loop idles); 3+ recommended.")); } if (topicDefinition.ActivationWords != null) { foreach (string activationWord in topicDefinition.ActivationWords) { string[] array = activationWord.Split(new char[1] { ' ' }); foreach (string text in array) { if (text.Length > 0 && (!char.IsLetterOrDigit(text[0]) || !char.IsLetterOrDigit(text[text.Length - 1]))) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] " + displayName + ": topic '" + topicDefinition.Name + "' trigger \"" + activationWord + "\" contains a punctuation-edged word (\"" + text + "\") - the game's word-boundary matching can never match it (write \"join discord\", not \"join discord?\").")); break; } } } } } } } } private static GlobalDialogueDefinition ParseGlobalDialogue(string path) { string text = Path.GetFileName(Path.GetDirectoryName(path)) + "/" + Path.GetFileName(path); try { string text2 = File.ReadAllText(path); GlobalDialogueDefinition globalDialogueDefinition = JsonUtility.FromJson<GlobalDialogueDefinition>(text2); if (globalDialogueDefinition == null) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] " + path + " parsed to null (malformed JSON?) - skipped.")); return null; } Dictionary<string, string> dictionary = TopLevelJsonSections(text2); if (dictionary.TryGetValue("ManagerPools", out var value)) { globalDialogueDefinition.ManagerPools = ParseSection<ManagerPoolAdditions>(value, "ManagerPools", text); } if (dictionary.TryGetValue("GlobalLanguage", out value)) { globalDialogueDefinition.GlobalLanguage = ParseSection<GlobalLanguageAdditions>(value, "GlobalLanguage", text); } if (dictionary.TryGetValue("GroupingPools", out value)) { globalDialogueDefinition.GroupingPools = ParseSection<GroupingPoolAdditions>(value, "GroupingPools", text); } if (dictionary.TryGetValue("GuildPools", out value)) { globalDialogueDefinition.GuildPools = ParseSection<GuildPoolAdditions>(value, "GuildPools", text); } if (dictionary.TryGetValue("TemplateLanguage", out value)) { globalDialogueDefinition.TemplateLanguage = ParseSection<TemplateLanguageAdditions>(value, "TemplateLanguage", text); } CheckKeysAtLevel(text2, typeof(GlobalDialogueDefinition), LevelLabelFor(typeof(GlobalDialogueDefinition)), text); if (dictionary.TryGetValue("ManagerPools", out value)) { CheckKeysAtLevel(value, typeof(ManagerPoolAdditions), "ManagerPools", text); } if (dictionary.TryGetValue("GlobalLanguage", out value)) { CheckKeysAtLevel(value, typeof(GlobalLanguageAdditions), "GlobalLanguage", text); } if (dictionary.TryGetValue("GroupingPools", out value)) { CheckKeysAtLevel(value, typeof(GroupingPoolAdditions), "GroupingPools", text); } if (dictionary.TryGetValue("GuildPools", out value)) { CheckKeysAtLevel(value, typeof(GuildPoolAdditions), "GuildPools", text); } if (dictionary.TryGetValue("TemplateLanguage", out value)) { CheckKeysAtLevel(value, typeof(TemplateLanguageAdditions), "TemplateLanguage", text); } CleanStringLists(globalDialogueDefinition, path); ValidateNameAdditions(globalDialogueDefinition, text); WarnIntraPackGenderDuplicates(globalDialogueDefinition, text); WarnEdgePunctuationInListenPools(globalDialogueDefinition.ManagerPools, text); CustomSimFrameworkPlugin.Log.LogInfo((object)("[Packs] " + text + " parsed: ManagerPools=" + CountLines(globalDialogueDefinition.ManagerPools) + ", GlobalLanguage=" + CountLines(globalDialogueDefinition.GlobalLanguage) + ", GroupingPools=" + CountLines(globalDialogueDefinition.GroupingPools) + ", GuildPools=" + CountLines(globalDialogueDefinition.GuildPools) + ", TemplateLanguage=" + CountLines(globalDialogueDefinition.TemplateLanguage) + ", Names=" + (ListCount(globalDialogueDefinition.MaleNames) + ListCount(globalDialogueDefinition.FemaleNames)) + ", Bios=" + (ListCount(globalDialogueDefinition.NiceBios) + ListCount(globalDialogueDefinition.TryhardBios) + ListCount(globalDialogueDefinition.MeanBios)) + " line(s).")); return globalDialogueDefinition; } catch (Exception ex) { CustomSimFrameworkPlugin.Log.LogError((object)("[Packs] Could not parse " + path + ": " + ex.Message)); return null; } } private static ZoneDialogueDefinition ParseZoneDialogue(string path) { string text = Path.GetFileName(Path.GetDirectoryName(path)) + "/" + Path.GetFileName(path); try { string text2 = File.ReadAllText(path); ZoneDialogueDefinition zoneDialogueDefinition = JsonUtility.FromJson<ZoneDialogueDefinition>(text2); if (zoneDialogueDefinition == null) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] " + path + " parsed to null (malformed JSON?) - skipped.")); return null; } if (TopLevelJsonSections(text2).TryGetValue("Zones", out var value)) { zoneDialogueDefinition.Zones = new List<ZoneLines>(); foreach (string item in TopLevelArrayElements(value)) { CheckKeysAtLevel(item, typeof(ZoneLines), "a Zones entry", text); ZoneLines zoneLines = ParseSection<ZoneLines>(item, "Zones entry", text); if (zoneLines != null) { zoneLines.Zone = (zoneLines.Zone ?? "").Trim(); zoneDialogueDefinition.Zones.Add(zoneLines); } } } CheckKeysAtLevel(text2, typeof(ZoneDialogueDefinition), LevelLabelFor(typeof(ZoneDialogueDefinition)), text); CleanStringLists(zoneDialogueDefinition, path); int num = 0; if (zoneDialogueDefinition.Zones != null) { foreach (ZoneLines zone in zoneDialogueDefinition.Zones) { num += ((zone != null) ? ListCount(zone.Lines) : 0); } } CustomSimFrameworkPlugin.Log.LogInfo((object)("[Packs] " + text + " parsed: AllZones=" + ListCount(zoneDialogueDefinition.AllZones) + ", Zones=" + ((zoneDialogueDefinition.Zones != null) ? zoneDialogueDefinition.Zones.Count : 0) + " zone entr(y/ies) with " + num + " line(s).")); return zoneDialogueDefinition; } catch (Exception ex) { CustomSimFrameworkPlugin.Log.LogError((object)("[Packs] Could not parse " + path + ": " + ex.Message)); return null; } } private static T ParseSection<T>(string sectionJson, string sectionName, string displayName) where T : class { try { return JsonUtility.FromJson<T>(sectionJson); } catch (Exception ex) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] " + displayName + ": section '" + sectionName + "' could not be parsed (" + ex.Message + ") - section skipped.")); return null; } } private static int CountLines(object section) { if (section == null) { return 0; } int num = 0; FieldInfo[] fields = section.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType == typeof(List<string>)) { num += ListCount((List<string>)fieldInfo.GetValue(section)); } } return num; } private static int ListCount(List<string> list) { return list?.Count ?? 0; } private static void RunJsonSelfTest() { ManagerPoolAdditions managerPoolAdditions = null; try { managerPoolAdditions = JsonUtility.FromJson<ManagerPoolAdditions>("{\"GroupRequest\":[\"selftest\"]}"); } catch (Exception) { } if (managerPoolAdditions == null || managerPoolAdditions.GroupRequest == null || managerPoolAdditions.GroupRequest.Count != 1) { CustomSimFrameworkPlugin.Log.LogError((object)"[Packs] JSON self-test FAILED - this environment cannot parse dialogue sections at all; global/zone dialogue additions will not work this session."); return; } GlobalDialogueDefinition globalDialogueDefinition = null; try { globalDialogueDefinition = JsonUtility.FromJson<GlobalDialogueDefinition>("{\"ManagerPools\":{\"GroupRequest\":[\"selftest\"]}}"); } catch (Exception) { } if (globalDialogueDefinition != null && globalDialogueDefinition.ManagerPools != null && globalDialogueDefinition.ManagerPools.GroupRequest != null && globalDialogueDefinition.ManagerPools.GroupRequest.Count == 1) { CustomSimFrameworkPlugin.Log.LogInfo((object)"[Packs] Note: the engine now parses nested plugin-class JSON (engine change?). Two-stage parsing remains in use and is unaffected."); } } private static Dictionary<string, string> TopLevelJsonSections(string json) { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal); int i = 0; int length; for (length = json.Length; i < length && json[i] != '{'; i++) { } if (i >= length) { return dictionary; } i++; while (i < length) { for (; i < length && (char.IsWhiteSpace(json[i]) || json[i] == ','); i++) { } if (i >= length || json[i] == '}' || json[i] != '"') { break; } i++; StringBuilder stringBuilder = new StringBuilder(); while (i < length && json[i] != '"') { if (json[i] == '\\' && i + 1 < length) { stringBuilder.Append(json[i + 1]); i += 2; } else { stringBuilder.Append(json[i]); i++; } } for (i++; i < length && char.IsWhiteSpace(json[i]); i++) { } if (i >= length || json[i] != ':') { break; } for (i++; i < length && char.IsWhiteSpace(json[i]); i++) { } if (i >= length) { break; } int num = i; SkipJsonValue(json, ref i); dictionary[stringBuilder.ToString()] = json.Substring(num, i - num); } return dictionary; } private static List<string> TopLevelArrayElements(string arrayJson) { List<string> list = new List<string>(); int i = 0; int length; for (length = arrayJson.Length; i < length && arrayJson[i] != '['; i++) { } if (i >= length) { return list; } i++; while (i < length) { for (; i < length && (char.IsWhiteSpace(arrayJson[i]) || arrayJson[i] == ','); i++) { } if (i >= length || arrayJson[i] == ']') { break; } int num = i; SkipJsonValue(arrayJson, ref i); list.Add(arrayJson.Substring(num, i - num)); } return list; } private static void SkipJsonValue(string json, ref int i) { int length = json.Length; switch (json[i]) { case '[': case '{': { int num = 0; bool flag = false; while (i < length) { char c2 = json[i]; if (flag) { switch (c2) { case '\\': i += 2; continue; case '"': flag = false; break; } } else { switch (c2) { case '"': flag = true; break; case '[': case '{': num++; break; case ']': case '}': num--; if (num == 0) { i++; return; } break; } } i++; } return; } case '"': i++; while (i < length) { char c = json[i]; if (c == '\\') { i += 2; continue; } i++; if (c != '"') { continue; } break; } return; } while (i < length && json[i] != ',' && json[i] != '}' && json[i] != ']') { i++; } } private static void ValidateNameAdditions(GlobalDialogueDefinition def, string displayName) { int num = RemoveInvalidNames(def.MaleNames) + RemoveInvalidNames(def.FemaleNames); if (num > 0) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] " + displayName + ": removed " + num + " generated-name entr(y/ies) with unsupported characters (allowed: A-Z, a-z, 0-9, hyphen; no spaces - names become save filenames and whisper targets).")); } } private static int RemoveInvalidNames(List<string> names) { return names?.RemoveAll((string name) => !NamePattern.IsMatch(name)) ?? 0; } private static void WarnEdgePunctuationInListenPools(ManagerPoolAdditions pools, string displayName) { if (pools == null) { return; } FieldInfo[] fields = typeof(ManagerPoolAdditions).GetFields(BindingFlags.Instance | BindingFlags.Public); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType != typeof(List<string>) || !CategoryInfo.ListenManagerPools.Contains(fieldInfo.Name)) { continue; } List<string> list = (List<string>)fieldInfo.GetValue(pools); if (list == null) { continue; } foreach (string item in list) { if (item.Length != 0 && (!char.IsLetterOrDigit(item[0]) || !char.IsLetterOrDigit(item[item.Length - 1]))) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] " + displayName + ": listen line \"" + item + "\" in ManagerPools." + fieldInfo.Name + " starts/ends with punctuation - the game's word-boundary matching can never match it at message edges (write \"wanna duo\", not \"wanna duo?\").")); } } } } internal static SimDefinition FindSimByName(string name) { foreach (SimDefinition sim in Sims) { if (string.Equals(sim.Name, name, StringComparison.OrdinalIgnoreCase)) { return sim; } } return null; } private static T ParseJson<T>(string path) where T : class { try { string text = File.ReadAllText(path); T val = JsonUtility.FromJson<T>(text); if (val == null) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] " + path + " parsed to null (malformed JSON?) - skipped.")); } else { CheckKeysAtLevel(text, typeof(T), LevelLabelFor(typeof(T)), Path.GetFileName(path)); CleanStringLists(val, path); } return val; } catch (Exception ex) { CustomSimFrameworkPlugin.Log.LogError((object)("[Packs] Could not parse " + path + ": " + ex.Message)); return null; } } private static void AddSchemaLocation(Type type, string label) { FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public); foreach (FieldInfo fieldInfo in fields) { if (!fieldInfo.IsNotSerialized) { if (!_schemaLocations.TryGetValue(fieldInfo.Name, out var value)) { value = new List<string>(); _schemaLocations[fieldInfo.Name] = value; } if (!value.Contains(label)) { value.Add(label); } } } } private static string LevelLabelFor(Type type) { if (type == typeof(SimDefinition)) { return "a *.sim.json file"; } if (type == typeof(GlobalDialogueDefinition)) { return "the top level of global.dialogue.json"; } if (type == typeof(ZoneDialogueDefinition)) { return "the top level of zones.dialogue.json"; } if (type == typeof(TopicsDefinition)) { return "the top level of topics.dialogue.json"; } return type.Name; } private static void CheckKeysAtLevel(string jsonObjectText, Type levelType, string levelLabel, string displayName) { foreach (string key in TopLevelJsonSections(jsonObjectText).Keys) { if (key.StartsWith("_", StringComparison.Ordinal)) { continue; } FieldInfo field = levelType.GetField(key, BindingFlags.Instance | BindingFlags.Public); if (!(field != null) || field.IsNotSerialized) { string text = FindFieldCaseInsensitive(levelType, key); List<string> value; if (text != null) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] " + displayName + ": unknown key \"" + key + "\" is ignored by the game - did you mean \"" + text + "\"?")); } else if (SchemaLocations.TryGetValue(key, out value)) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] " + displayName + ": key \"" + key + "\" is not valid in " + levelLabel + " - its content is silently dead there. Valid location(s): " + string.Join(", ", value.ToArray()) + ".")); } else { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] " + displayName + ": unknown key \"" + key + "\" is ignored by the game (typo? see Packs/README.md for valid keys).")); } } } } private static string FindFieldCaseInsensitive(Type type, string key) { FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public); foreach (FieldInfo fieldInfo in fields) { if (!fieldInfo.IsNotSerialized && string.Equals(fieldInfo.Name, key, StringComparison.OrdinalIgnoreCase)) { return fieldInfo.Name; } } return null; } private static void WarnIntraPackGenderDuplicates(GlobalDialogueDefinition def, string displayName) { WarnSameListCaseVariants(def.MaleNames, "MaleNames", displayName); WarnSameListCaseVariants(def.FemaleNames, "FemaleNames", displayName); if (def.MaleNames == null || def.FemaleNames == null) { return; } foreach (string maleName in def.MaleNames) { foreach (string femaleName in def.FemaleNames) { if (string.Equals(maleName, femaleName, StringComparison.OrdinalIgnoreCase)) { if (string.Equals(maleName, femaleName, StringComparison.Ordinal)) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] " + displayName + ": \"" + maleName + "\" is in both MaleNames and FemaleNames - sims with this name will ALWAYS resolve male (the game's male-list check wins).")); } else { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] " + displayName + ": MaleNames \"" + maleName + "\" and FemaleNames \"" + femaleName + "\" differ only by casing - two minted sims would share one save file (Windows filenames are case-insensitive).")); } } } } } private static void WarnSameListCaseVariants(List<string> names, string listName, string displayName) { if (names == null) { return; } for (int i = 0; i < names.Count; i++) { for (int j = i + 1; j < names.Count; j++) { if (string.Equals(names[i], names[j], StringComparison.OrdinalIgnoreCase) && !string.Equals(names[i], names[j], StringComparison.Ordinal)) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] " + displayName + ": " + listName + " entries \"" + names[i] + "\" and \"" + names[j] + "\" differ only by casing - two minted sims would share one save file (Windows filenames are case-insensitive).")); } } } } private static void CleanStringLists(object obj, string path) { int num = RemoveEmptyEntries(obj, 0); if (num > 0) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] " + Path.GetFileName(path) + ": removed " + num + " empty line(s) from dialogue lists (an empty line would make sims say \"lol\").")); } } private static int RemoveEmptyEntries(object obj, int depth) { if (obj == null || depth > 3) { return 0; } int num = 0; FieldInfo[] fields = obj.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public); foreach (FieldInfo fieldInfo in fields) { object value = fieldInfo.GetValue(obj); if (value == null) { continue; } if (value is List<string> list) { for (int j = 0; j < list.Count; j++) { if (list[j] != null) { list[j] = list[j].Trim(); } } num += list.RemoveAll(string.IsNullOrWhiteSpace); continue; } Type fieldType = fieldInfo.FieldType; if (fieldType.Namespace != null && fieldType.Namespace.StartsWith("Erenshor.CustomSimFramework")) { num += RemoveEmptyEntries(value, depth + 1); } else { if (!(value is IEnumerable) || !fieldType.IsGenericType || !(fieldType.GetGenericTypeDefinition() == typeof(List<>)) || fieldType.GetGenericArguments()[0].Namespace == null || !fieldType.GetGenericArguments()[0].Namespace.StartsWith("Erenshor.CustomSimFramework")) { continue; } foreach (object item in (IEnumerable)value) { num += RemoveEmptyEntries(item, depth + 1); } } } return num; } } internal static class SimTemplateBuilder { internal static readonly Dictionary<string, SimDefinition> BuiltSims = new Dictionary<string, SimDefinition>(StringComparer.OrdinalIgnoreCase); private static GameObject _holder; private static readonly Dictionary<string, GameObject> _templates = new Dictionary<string, GameObject>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, string> ManagerPoolFillMap = new Dictionary<string, string> { { "Greetings", "GenericGreeting" }, { "ReturnGreeting", "GenericGreeting" }, { "Invites", "Invites" }, { "Justifications", "InviteEnd" }, { "GenericLines", "SmallTalk" }, { "Confirms", "Affirmations" }, { "DeclineGroup", "DenyGroup" }, { "OTW", "OTW" }, { "AcknowledgeGratitude", "AcknowledgeGratitude" }, { "Died", "Died" }, { "Impressed", "Impressed" }, { "ImpressedEnd", "ImpressedEnd" }, { "LevelUpCelebration", "LevelUpCongratulations" } }; internal static void InjectAll(SimPlayerMngr mngr) { if (PackLoader.Sims.Count == 0) { return; } if ((Object)(object)mngr.BlankSPTemplate == (Object)null) { CustomSimFrameworkPlugin.Log.LogError((object)"[Inject] BlankSPTemplate is null - cannot build custom sims."); return; } if ((Object)(object)GameData.ClassDB == (Object)null) { CustomSimFrameworkPlugin.Log.LogError((object)"[Inject] GameData.ClassDB is null - custom sims skipped this login."); return; } EnsureHolder(); foreach (SimDefinition sim in PackLoader.Sims) { try { InjectOne(mngr, sim); } catch (Exception ex) { CustomSimFrameworkPlugin.Log.LogError((object)("[Inject] Failed to inject sim '" + sim.Name + "': " + ex)); } } } private static void InjectOne(SimPlayerMngr mngr, SimDefinition def) { foreach (GameObject actualSim in mngr.ActualSims) { if ((Object)(object)actualSim != (Object)null && string.Equals(((Object)actualSim).name, def.Name, StringComparison.OrdinalIgnoreCase)) { if (!_templates.ContainsKey(def.Name)) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Inject] '" + def.Name + "' collides with an existing sim - skipped (" + def.SourceFile + ").")); } return; } } string text = FindCaseVariantSaveFile(def.Name); if (text != null) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Inject] '" + def.Name + "' (" + def.SourceFile + ") clashes with the existing sim save '" + text + "' (same name, different casing) - skipped. Rename the sim, or use the exact same casing to take that sim over.")); return; } RemoveCaseVariantNames(mngr, def.Name); if (!_templates.TryGetValue(def.Name, out var value) || (Object)(object)value == (Object)null) { value = BuildTemplate(mngr, def); _templates[def.Name] = value; BuiltSims[def.Name] = def; } mngr.ActualSims.Add(value); CustomSimFrameworkPlugin.Log.LogInfo((object)("[Inject] Custom sim '" + def.Name + "' (" + def.Class + " lvl " + def.Level + ") registered from " + def.SourceFile)); } private static void EnsureHolder() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown if (!((Object)(object)_holder != (Object)null)) { _holder = new GameObject("CustomSimFramework_Templates"); _holder.SetActive(false); Object.DontDestroyOnLoad((Object)(object)_holder); } } private static GameObject BuildTemplate(SimPlayerMngr mngr, SimDefinition def) { GameObject val = Object.Instantiate<GameObject>(mngr.BlankSPTemplate, _holder.transform); ((Object)val).name = def.Name; val.GetComponent<NPC>().NPCName = def.Name; Stats component = val.GetComponent<Stats>(); component.Level = Mathf.Clamp(def.Level, 1, 35); component.CharacterClass = ResolveClass(def.Class, def.Name); Inventory component2 = val.GetComponent<Inventory>(); component2.isMale = !string.Equals(def.Gender, "Female", StringComparison.OrdinalIgnoreCase); if (component2.EquippedItems == null) { component2.EquippedItems = new List<Item>(); } SimPlayer component3 = val.GetComponent<SimPlayer>(); ApplyQuirks(component3, def); component3.SkillLevel = def.SkillLevel; component3.Bio = def.Bio ?? ""; component3.PersonalityType = def.PersonalityType; component3.BioIndex = -1; component3.Rival = def.Rival; component3.IsGMCharacter = false; component3.InTutorial = false; component3.TiedToSlot = 99; component3.Greed = def.Greed; component3.Patience = def.Patience; component3.GearChase = def.GearChase; component3.LoreChase = def.LoreChase; component3.SocialChase = def.SocialChase; component3.Troublemaker = def.Troublemaker; component3.DedicationLevel = def.DedicationLevel; ResolveAppearance(mngr, def, component3); component3.SignOffLine = new List<string>(); if (def.SignOffLines != null) { component3.SignOffLine.AddRange(def.SignOffLines); } BuildLanguage(mngr, val, def); return val; } internal static void ApplyPostLoadFixups(SimPlayerMngr mngr) { foreach (KeyValuePair<string, SimDefinition> builtSim in BuiltSims) { SimDefinition value = builtSim.Value; try { SimPlayerTracking value2 = null; if (mngr.SimDict != null) { mngr.SimDict.TryGetValue(value.Name, out value2); } if (value2 != null) { if (value.PersonalityType != 0) { value2.Personality = value.PersonalityType; } if (value.TiedToSlot != -1 && !value2.Rival) { value2.TiedToSlot = value.TiedToSlot; } } SimPlayerSaveData val = FindLatestSaveRecord(value.Name); if (val != null) { bool flag = false; int num = ((!string.Equals(value.Gender, "Female", StringComparison.OrdinalIgnoreCase)) ? 1 : 0); if (val.Male != num) { val.Male = num; flag = true; } if (value.PersonalityType != 0 && val.PersonalityType != value.PersonalityType) { val.PersonalityType = value.PersonalityType; flag = true; } if (value.TiedToSlot != -1 && !value.Rival && val.TiedToSlot != value.TiedToSlot) { val.TiedToSlot = value.TiedToSlot; flag = true; } if (flag) { SimPlayerDataManager.SaveSimData(val); CustomSimFrameworkPlugin.LogDebug("Post-load fixup saved for '" + value.Name + "' (Male=" + val.Male + ", PersonalityType=" + val.PersonalityType + ", TiedToSlot=" + val.TiedToSlot + ")"); } } } catch (Exception ex) { CustomSimFrameworkPlugin.Log.LogError((object)("[Inject] Post-load fixup failed for '" + value.Name + "': " + ex)); } } } private static SimPlayerSaveData FindLatestSaveRecord(string simName) { List<SimPlayerSaveData> simPlayerData = SimPlayerDataManager.SimPlayerData; if (simPlayerData == null) { return null; } for (int num = simPlayerData.Count - 1; num >= 0; num--) { SimPlayerSaveData val = simPlayerData[num]; if (val != null && val.NPCName == simName) { return val; } } return null; } internal static void ApplyQuirks(SimPlayer sp, SimDefinition def) { sp.TypesInAllCaps = def.TypesInAllCaps; sp.TypesInAllLowers = def.TypesInAllLowers; sp.TypesInThirdPerson = def.TypesInThirdPerson; sp.RefersToSelfAs = def.RefersToSelfAs ?? ""; sp.LovesEmojis = def.LovesEmojis; sp.Abbreviates = def.Abbreviates; sp.TypoRate = def.TypoRate; sp.TypoChance = def.TypoChance; } private static void BuildLanguage(SimPlayerMngr mngr, GameObject go, SimDefinition def) { SimPlayerLanguage component = go.GetComponent<SimPlayerLanguage>(); if ((Object)(object)component == (Object)null) { CustomSimFrameworkPlugin.Log.LogError((object)("[Inject] Template for '" + def.Name + "' has no SimPlayerLanguage component!")); return; } component.Public = false; SimPlayerLanguage component2 = ((Component)mngr).GetComponent<SimPlayerLanguage>(); SimPlayerLanguage component3 = mngr.BlankSPTemplate.GetComponent<SimPlayerLanguage>(); int num = 0; int num2 = 0; List<string> list = null; FieldInfo[] fields = typeof(SimPlayerLanguage).GetFields(BindingFlags.Instance | BindingFlags.Public); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType != typeof(List<string>)) { continue; } List<string> definitionList = GetDefinitionList(def, fieldInfo.Name); List<string> list2 = new List<string>(); if (definitionList != null && definitionList.Count > 0) { if (CategoryInfo.DeadSimListAlternatives.TryGetValue(fieldInfo.Name, out var value)) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Inject] '" + def.Name + "' (" + def.SourceFile + "): list '" + fieldInfo.Name + "' is never read by the game - these lines will not appear. Use instead: " + value)); } list2.AddRange(definitionList); num++; } else { List<string> list3 = ResolveFillSource(mngr, component2, component3, fieldInfo); if (list3 != null) { list2.AddRange(list3); num2++; } else { if (list == null) { list = new List<string>(); } list.Add(fieldInfo.Name); } } fieldInfo.SetValue(component, list2); } if (CustomSimFrameworkPlugin.Verbose) { CustomSimFrameworkPlugin.LogDebug("'" + def.Name + "' language: " + num + " custom list(s), " + num2 + " filled from game pools" + ((list != null) ? (", EMPTY: " + string.Join(", ", list.ToArray())) : "")); } } private static List<string> GetDefinitionList(SimDefinition def, string fieldName) { FieldInfo field = typeof(SimDefinition).GetField(fieldName, BindingFlags.Instance | BindingFlags.Public); if (field == null || field.FieldType != typeof(List<string>)) { return null; } return (List<string>)field.GetValue(def); } private static List<string> ResolveFillSource(SimPlayerMngr mngr, SimPlayerLanguage globalLang, SimPlayerLanguage blankLang, FieldInfo langField) { if (ManagerPoolFillMap.TryGetValue(langField.Name, out var value)) { FieldInfo field = typeof(SimPlayerMngr).GetField(value, BindingFlags.Instance | BindingFlags.Public); if (field != null && field.FieldType == typeof(List<string>)) { List<string> list = (List<string>)field.GetValue(mngr); if (list != null && list.Count > 0) { return list; } } } if ((Object)(object)globalLang != (Object)null) { List<string> list2 = (List<string>)langField.GetValue(globalLang); if (list2 != null && list2.Count > 0) { return list2; } } if ((Object)(object)blankLang != (Object)null) { List<string> list3 = (List<string>)langField.GetValue(blankLang); if (list3 != null && list3.Count > 0) { return list3; } } return null; } private static Class ResolveClass(string className, string simName) { ClassDB classDB = GameData.ClassDB; if ((Object)(object)classDB == (Object)null) { CustomSimFrameworkPlugin.Log.LogError((object)("[Inject] GameData.ClassDB unavailable for '" + simName + "'.")); return null; } switch ((className ?? "").Trim().ToLowerInvariant()) { case "paladin": return classDB.Paladin; case "arcanist": return classDB.Arcanist; case "druid": return classDB.Druid; case "duelist": return classDB.Duelist; case "stormcaller": return classDB.Stormcaller; case "reaver": return classDB.Reaver; default: CustomSimFrameworkPlugin.Log.LogWarning((object)("[Inject] Unknown class '" + className + "' for sim '" + simName + "' - defaulting to Duelist.")); return classDB.Duelist; } } private static void ResolveAppearance(SimPlayerMngr mngr, SimDefinition def, SimPlayer sp) { int num = ((mngr.HairColors != null) ? mngr.HairColors.Count : 0); int num2 = ((mngr.SkinColors != null) ? mngr.SkinColors.Count : 0); string text = def.HairName; int hairColorIndex = def.HairColorIndex; int skinColorIndex = def.SkinColorIndex; if (!string.IsNullOrEmpty(text) && !IsValidHairName(text)) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Inject] HairName='" + text + "' for sim '" + def.Name + "' is not a real style (valid: Chr_Hair_01..Chr_Hair_38, except 03 which does not exist) - using a random style instead.")); text = ""; } if (string.IsNullOrEmpty(text) || hairColorIndex < 0 || skinColorIndex < 0) { SimPlayerSaveData val = TryReadExistingSave(def.Name); if (val != null) { if (string.IsNullOrEmpty(text) && IsValidHairName(val.hairName)) { text = val.hairName; } if (hairColorIndex < 0 && val.HairColorIndex >= 0 && val.HairColorIndex < num) { hairColorIndex = val.HairColorIndex; } if (skinColorIndex < 0 && val.SkinColorIndex >= 0 && val.SkinColorIndex < num2) { skinColorIndex = val.SkinColorIndex; } } } sp.HairName = (string.IsNullOrEmpty(text) ? RandomHairName() : text); sp.HairColor = ClampOrRandomIndex(hairColorIndex, num, def.Name, "HairColorIndex"); sp.SkinColor = ClampOrRandomIndex(skinColorIndex, num2, def.Name, "SkinColorIndex"); } private static SimPlayerSaveData TryReadExistingSave(string simName) { try { string path = Path.Combine(Path.Combine(Application.persistentDataPath, "ESSaveData"), "Sims" + simName); if (!File.Exists(path)) { return null; } return JsonUtility.FromJson<SimPlayerSaveData>(File.ReadAllText(path)); } catch (Exception ex) { CustomSimFrameworkPlugin.LogDebug("Could not read existing save for '" + simName + "': " + ex.Message); return null; } } private static int ClampOrRandomIndex(int index, int count, string simName, string fieldName) { if (count <= 0) { return 0; } if (index < 0) { return Random.Range(0, count); } if (index >= count) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Inject] " + fieldName + "=" + index + " out of range (max " + (count - 1) + ") for sim '" + simName + "' - clamping.")); return count - 1; } return index; } private static string RandomHairName() { int num; do { num = Random.Range(1, 39); } while (num == 3); if (num < 10) { return "Chr_Hair_0" + num; } return "Chr_Hair_" + num; } private static bool IsValidHairName(string name) { if (string.IsNullOrEmpty(name) || name.Length != "Chr_Hair_".Length + 2 || !name.StartsWith("Chr_Hair_", StringComparison.Ordinal)) { return false; } if (!int.TryParse(name.Substring("Chr_Hair_".Length), out var result)) { return false; } if (result >= 1 && result <= 38) { return result != 3; } return false; } private static string FindCaseVariantSaveFile(string simName) { try { string path = Path.Combine(Application.persistentDataPath, "ESSaveData"); if (!Directory.Exists(path)) { return null; } string b = "Sims" + simName; string[] files = Directory.GetFiles(path, "Sims*"); foreach (string path2 in files) { if (!(Path.GetExtension(path2) != "")) { string fileName = Path.GetFileName(path2); if (string.Equals(fileName, b, StringComparison.OrdinalIgnoreCase) && !string.Equals(fileName, b, StringComparison.Ordinal)) { return fileName.Substring("Sims".Length); } } } } catch (Exception ex) { CustomSimFrameworkPlugin.LogDebug("Case-variant save scan failed for '" + simName + "': " + ex.Message); } return null; } private static void RemoveCaseVariantNames(SimPlayerMngr mngr, string simName) { RemoveCaseVariantsFrom(mngr.NameDatabaseMale, simName); RemoveCaseVariantsFrom(mngr.NameDatabaseFemale, simName); } private static void RemoveCaseVariantsFrom(List<string> names, string simName) { if (names == null) { return; } for (int num = names.Count - 1; num >= 0; num--) { if (string.Equals(names[num], simName, StringComparison.OrdinalIgnoreCase) && !string.Equals(names[num], simName, StringComparison.Ordinal)) { CustomSimFrameworkPlugin.LogDebug("Removed '" + names[num] + "' from the generated-name pool (case-variant of custom sim '" + simName + "')."); names.RemoveAt(num); } } } } internal static class GlobalDialogueInjector { private static bool _groupingApplied; private static bool _guildApplied; private static readonly HashSet<string> _nameWarningsIssued = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static bool _zoneTargetsChecked; internal static void ResetPerLogin() { _groupingApplied = false; _guildApplied = false; } internal static void ApplyGroupingPools() { if (_groupingApplied || PackLoader.GlobalDialogues.Count == 0) { return; } SimPlayerGrouping simPlayerGrouping = GameData.SimPlayerGrouping; if ((Object)(object)simPlayerGrouping == (Object)null) { return; } _groupingApplied = true; int num = 0; foreach (GlobalDialogueDefinition globalDialogue in PackLoader.GlobalDialogues) { if (globalDialogue.GroupingPools != null) { num += AppendByFieldNames(globalDialogue.GroupingPools, simPlayerGrouping); } } if (num > 0) { CustomSimFrameworkPlugin.Log.LogInfo((object)("[Global] " + num + " group-chat line(s) appended to SimPlayerGrouping.")); } } internal static void ApplyGuildPools() { if (_guildApplied || PackLoader.GlobalDialogues.Count == 0) { return; } GuildManager guildManager = GameData.GuildManager; if ((Object)(object)guildManager == (Object)null) { return; } _guildApplied = true; int num = 0; foreach (GlobalDialogueDefinition globalDialogue in PackLoader.GlobalDialogues) { if (globalDialogue.GuildPools != null) { num += AppendByFieldNames(globalDialogue.GuildPools, guildManager); } } if (num > 0) { CustomSimFrameworkPlugin.Log.LogInfo((object)("[Global] " + num + " guild-chat line(s) appended to GuildManager.")); } } internal static void Apply(SimPlayerMngr mngr) { if (PackLoader.GlobalDialogues.Count == 0) { return; } ApplyGroupingPools(); ApplyGuildPools(); SimPlayerLanguage component = ((Component)mngr).GetComponent<SimPlayerLanguage>(); SimPlayerLanguage val = (((Object)(object)mngr.BlankSPTemplate != (Object)null) ? mngr.BlankSPTemplate.GetComponent<SimPlayerLanguage>() : null); foreach (GlobalDialogueDefinition globalDialogue in PackLoader.GlobalDialogues) { int num = 0; try { if (globalDialogue.ManagerPools != null) { WarnDeadLists(globalDialogue.ManagerPools, CategoryInfo.DeadManagerPools, globalDialogue.SourceFile); num += AppendByFieldNames(globalDialogue.ManagerPools, mngr); } if (globalDialogue.GlobalLanguage != null && (Object)(object)component != (Object)null) { WarnDeadLists(globalDialogue.GlobalLanguage, CategoryInfo.DeadGlobalLanguageLists, globalDialogue.SourceFile); num += AppendByFieldNames(globalDialogue.GlobalLanguage, component); } if (globalDialogue.TemplateLanguage != null && (Object)(object)val != (Object)null) { num += AppendByFieldNames(globalDialogue.TemplateLanguage, val); } WarnNameCollisions(mngr, globalDialogue); num += AppendLines(mngr.NameDatabaseMale, globalDialogue.MaleNames); num += AppendLines(mngr.NameDatabaseFemale, globalDialogue.FemaleNames); num += AppendLines(mngr.NiceDesciptions, globalDialogue.NiceBios); num += AppendLines(mngr.TryhardDescriptions, globalDialogue.TryhardBios); num += AppendLines(mngr.MeanDescriptions, globalDialogue.MeanBios); CustomSimFrameworkPlugin.Log.LogInfo((object)("[Global] " + globalDialogue.SourceFile + ": " + num + " line(s) appended.")); } catch (Exception ex) { CustomSimFrameworkPlugin.Log.LogError((object)("[Global] Failed applying " + globalDialogue.SourceFile + ": " + ex)); } } } private static int AppendByFieldNames(object additions, object target) { int num = 0; FieldInfo[] fields = additions.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public); Type type = target.GetType(); FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { if (fieldInfo.FieldType != typeof(List<string>)) { continue; } List<string> list = (List<string>)fieldInfo.GetValue(additions); if (list == null || list.Count == 0) { continue; } FieldInfo field = type.GetField(fieldInfo.Name, BindingFlags.Instance | BindingFlags.Public); if (field == null || field.FieldType != typeof(List<string>)) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Global] No pool named '" + fieldInfo.Name + "' on " + type.Name + " - lines ignored.")); } else { List<string> list2 = (List<string>)field.GetValue(target); if (list2 == null) { list2 = new List<string>(); field.SetValue(target, list2); } num += AppendLines(list2, list); } } return num; } private static void WarnDeadLists(object additions, HashSet<string> deadNames, string sourceFile) { FieldInfo[] fields = additions.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public); foreach (FieldInfo fieldInfo in fields) { if (!(fieldInfo.FieldType != typeof(List<string>)) && deadNames.Contains(fieldInfo.Name)) { List<string> list = (List<string>)fieldInfo.GetValue(additions); if (list != null && list.Count > 0) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Global] " + sourceFile + ": '" + fieldInfo.Name + "' is never read by the game - these lines will not appear. See Packs/CATEGORY_REFERENCE.md for the live alternative.")); } } } } private static void WarnNameCollisions(SimPlayerMngr mngr, GlobalDialogueDefinition def) { Dictionary<string, string> saves = ListSimSaveFiles(); CheckNameAdditions(def.MaleNames, mngr.NameDatabaseFemale, "female", saves, def.SourceFile); CheckNameAdditions(def.FemaleNames, mngr.NameDatabaseMale, "male", saves, def.SourceFile); } private static void CheckNameAdditions(List<string> names, List<string> oppositeDb, string oppositeLabel, Dictionary<string, string> saves, string sourceFile) { if (names == null) { return; } foreach (string name in names) { if (string.IsNullOrEmpty(name) || _nameWarningsIssued.Contains(name)) { continue; } SimDefinition simDefinition = PackLoader.FindSimByName(name); string value; if (simDefinition != null && !string.Equals(simDefinition.Name, name, StringComparison.Ordinal)) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Global] " + sourceFile + ": generated name \"" + name + "\" is a case-variant of custom sim '" + simDefinition.Name + "' - it will be removed from the name pool.")); _nameWarningsIssued.Add(name); } else if (saves != null && saves.TryGetValue(name, out value) && !string.Equals(value, name, StringComparison.Ordinal)) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Global] " + sourceFile + ": generated name \"" + name + "\" is a case-variant of the existing sim save '" + value + "' - a sim minted with this name would share that save file (Windows filenames are case-insensitive).")); _nameWarningsIssued.Add(name); } else { if (oppositeDb == null) { continue; } foreach (string item in oppositeDb) { if (string.Equals(item, name, StringComparison.OrdinalIgnoreCase)) { if (string.Equals(item, name, StringComparison.Ordinal)) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Global] " + sourceFile + ": generated name \"" + name + "\" is also in the " + oppositeLabel + " name database - sims with this name will ALWAYS resolve male (the game's male-list check wins).")); } else { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Global] " + sourceFile + ": generated name \"" + name + "\" has the case-variant \"" + item + "\" in the " + oppositeLabel + " name database - two minted sims would share one save file.")); } _nameWarningsIssued.Add(name); break; } } } } } private static Dictionary<string, string> ListSimSaveFiles() { try { string path = Path.Combine(Application.persistentDataPath, "ESSaveData"); if (!Directory.Exists(path)) { return null; } Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); string[] files = Directory.GetFiles(path, "Sims*"); foreach (string path2 in files) { if (!(Path.GetExtension(path2) != "")) { string text = Path.GetFileName(path2).Substring("Sims".Length); if (text.Length > 0 && !dictionary.ContainsKey(text)) { dictionary[text] = text; } } } return dictionary; } catch (Exception ex) { CustomSimFrameworkPlugin.LogDebug("Save-name scan failed: " + ex.Message); return null; } } internal static void ValidateZoneTargets() { if (_zoneTargetsChecked || PackLoader.ZoneDialogues.Count == 0) { return; } ZoneAtlasEntry[] atlas = ZoneAtlas.Atlas; if (atlas == null) { return; } _zoneTargetsChecked = true; HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); ZoneAtlasEntry[] array = atlas; foreach (ZoneAtlasEntry val in array) { if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(val.ZoneName)) { hashSet.Add(val.ZoneName); string zoneTerm = GetCommonTerms.GetZoneTerm(val.ZoneName); if (!string.IsNullOrEmpty(zoneTerm)) { hashSet.Add(zoneTerm); } } } foreach (ZoneDialogueDefinition zoneDialogue in PackLoader.ZoneDialogues) { if (zoneDialogue.Zones == null) { continue; } foreach (ZoneLines zone in zoneDialogue.Zones) { if (zone != null && !string.IsNullOrEmpty(zone.Zone) && !hashSet.Contains(zone.Zone)) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Packs] " + zoneDialogue.SourceFile + ": zone \"" + zone.Zone + "\" matches no zone in the game's atlas - check the spelling (special/event scenes may not be listed).")); } } } } internal static int AppendLines(List<string> pool, List<string> lines) { if (pool == null || lines == null) { return 0; } int num = 0; foreach (string line in lines) { if (!string.IsNullOrEmpty(line) && !pool.Contains(line)) { pool.Add(line); num++; } } return num; } } internal static class GuildTopicInjector { private static List<GuildTopic> _built; private static bool _warningsDone; private static bool _scenesValidated; internal static void ApplyAll() { if (PackLoader.TopicPacks.Count == 0) { return; } GuildManager guildManager = GameData.GuildManager; if ((Object)(object)guildManager == (Object)null || guildManager.Guilds == null) { return; } EnsureBuilt(); if (_built.Count == 0) { return; } int num = 0; int num2 = 0; foreach (LiveGuildData guild in guildManager.Guilds) { if (guild == null || guild.Conversations == null) { continue; } bool flag = false; foreach (GuildTopic item in _built) { if (!guild.Conversations.Contains(item)) { guild.Conversations.Add(item); num++; flag = true; } } if (flag) { num2++; } } if (num > 0) { CustomSimFrameworkPlugin.Log.LogInfo((object)("[Topics] " + _built.Count + " pack topic(s) ensured in " + num2 + " guild(s) (" + num + " list insert(s)).")); } TryWarnShadowingOnce(guildManager); TryValidateScenesOnce(); } private static void EnsureBuilt() { if (_built != null) { return; } _built = new List<GuildTopic>(); foreach (TopicsDefinition topicPack in PackLoader.TopicPacks) { if (topicPack.Topics == null) { continue; } foreach (TopicDefinition topic in topicPack.Topics) { if (topic != null && topic.Responses != null && topic.Responses.Count != 0) { GuildTopic val = ScriptableObject.CreateInstance<GuildTopic>(); ((Object)val).name = "CSF_" + topic.Name; val.SimPlayerActivations = CopyList(topic.SimPlayerActivations); val.ActivationWords = CopyList(topic.ActivationWords); val.Responses = CopyList(topic.Responses); val.Preceed = CopyList(topic.Preceed); val.End = CopyList(topic.End); val.RelevantScene = CopyList(topic.RelevantScene); val.RequiredLevelToKnow = topic.RequiredLevelToKnow; val.MaxLevelToAsk = topic.MaxLevelToAsk; val.forceLotsOfResponses = topic.forceLotsOfResponses; _built.Add(val); CustomSimFrameworkPlugin.LogDebug("Built guild topic '" + topic.Name + "' (" + val.SimPlayerActivations.Count + " opener(s), " + val.ActivationWords.Count + " trigger(s), " + val.Responses.Count + " response(s)) from " + topicPack.SourceFile); } } } WarnPackTopicCrossChecks(); } private static void WarnPackTopicCrossChecks() { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (GuildTopic item in _built) { if (!hashSet.Add(((Object)item).name)) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Topics] duplicate topic name '" + ((Object)item).name + "' across packs - both are injected, but logs and warnings will be ambiguous.")); } foreach (string activationWord in item.ActivationWords) { string[] array = activationWord.Split(new char[1] { ' ' }); foreach (string text in array) { if (string.Equals(text, "item", StringComparison.OrdinalIgnoreCase) || string.Equals(text, "npc", StringComparison.OrdinalIgnoreCase) || string.Equals(text, "level", StringComparison.OrdinalIgnoreCase)) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Topics] '" + ((Object)item).name + "' trigger \"" + activationWord + "\" contains the word \"" + text + "\" - the game routes sanitized knowledge questions (\"<name> item \", \"<name> npc \", \" level N\") through topic matching, so this trigger may eat item/NPC/level answers.")); break; } } } } for (int j = 0; j < _built.Count; j++) { for (int k = j + 1; k < _built.Count; k++) { foreach (string activationWord2 in _built[k].ActivationWords) { foreach (string activationWord3 in _built[j].ActivationWords) { if (GuildManager.ActivationMatches(activationWord2, activationWord3)) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Topics] '" + ((Object)_built[k]).name + "' trigger \"" + activationWord2 + "\" is shadowed by '" + ((Object)_built[j]).name + "' (\"" + activationWord3 + "\") - earlier-injected topics match first, so this trigger may never reach its own topic.")); } } } } } } private static List<string> CopyList(List<string> source) { if (source == null) { return new List<string>(); } return new List<string>(source); } private static void TryWarnShadowingOnce(GuildManager gm) { if (_warningsDone) { return; } SimPlayerMngr simMngr = GameData.SimMngr; List<GuildTopic> list = FindVanillaTopics(gm); if ((Object)(object)simMngr == (Object)null || list == null) { return; } _warningsDone = true; foreach (GuildTopic item in _built) { foreach (string activationWord in item.ActivationWords) { foreach (GuildTopic item2 in list) { foreach (string activationWord2 in item2.ActivationWords) { if (GuildManager.ActivationMatches(activationWord, activationWord2)) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Topics] '" + ((Object)item).name + "' trigger \"" + activationWord + "\" is shadowed by the vanilla topic '" + ((Object)item2).name + "' (\"" + activationWord2 + "\") - vanilla topics match first, so this trigger may never reach your topic.")); } } } WarnWaveOverlap(activationWord, ((Object)item).name, simMngr.GenericGreeting, "greeting"); WarnWaveOverlap(activationWord, ((Object)item).name, simMngr.Goodnight, "goodnight"); WarnWaveOverlap(activationWord, ((Object)item).name, simMngr.LevelUpCelebrations, "level-up (ding)"); } } } private static void WarnWaveOverlap(string phrase, string topicName, List<string> listenPool, string waveName) { if (listenPool == null) { return; } foreach (string item in listenPool) { if (GuildManager.ActivationMatches(item, phrase)) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Topics] '" + topicName + "' trigger \"" + phrase + "\" also matches the " + waveName + " listen phrase \"" + item + "\" - guild messages like that will fire your topic INSTEAD of the " + waveName + " wave. Use a more specific trigger.")); break; } } } private static List<GuildTopic> FindVanillaTopics(GuildManager gm) { foreach (LiveGuildData guild in gm.Guilds) { if (guild == null || guild.Conversations == null || guild.Conversations.Count == 0) { continue; } List<GuildTopic> list = new List<GuildTopic>(); foreach (GuildTopic conversation in guild.Conversations) { if ((Object)(object)conversation != (Object)null && !_built.Contains(conversation)) { list.Add(conversation); } } if (list.Count > 0) { return list; } } return null; } private static void TryValidateScenesOnce() { if (_scenesValidated) { return; } ZoneAtlasEntry[] atlas = ZoneAtlas.Atlas; if (atlas == null) { return; } _scenesValidated = true; HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); ZoneAtlasEntry[] array = atlas; foreach (ZoneAtlasEntry val in array) { if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(val.ZoneName)) { string zoneTerm = GetCommonTerms.GetZoneTerm(val.ZoneName); hashSet.Add(zoneTerm); dictionary[zoneTerm] = zoneTerm; } } foreach (GuildTopic item in _built) { foreach (string item2 in item.RelevantScene) { if (!hashSet.Contains(item2)) { if (dictionary.TryGetValue(item2, out var value)) { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Topics] '" + ((Object)item).name + "': RelevantScene \"" + item2 + "\" differs from the atlas display name \"" + value + "\" only by casing - the game compares CASE-SENSITIVELY, so this gate would treat every zone (including the right one) as out-of-zone. Use \"" + value + "\".")); } else { CustomSimFrameworkPlugin.Log.LogWarning((object)("[Topics] '" + ((Object)item).name + "': RelevantScene \"" + item2 + "\" matches no zone DISPLAY name in the game's atlas (write e.g. \"Stowaway's Step\", not the scene name; special/event scenes may not be listed) - the zone gate would treat every zone as out-of-zone.")); } } } } } } } namespace Erenshor.CustomSimFramework.Patches { [HarmonyPatch(typeof(SimPlayerMngr), "Start")] internal static class SimPlayerMngr_Start_Dump { private static bool _dumpedThisLaunch; [HarmonyPrefix] [HarmonyPriority(600)] private static void Prefix(SimPlayerMngr __instance) { if (!CustomSimFrameworkPlugin.DumpEnabled || _dumpedThisLaunch) { return; } _dumpedThisLaunch = true; try { VanillaDumper.DumpAtManagerStart(__instance); } catch (Exception ex) { CustomSimFrameworkPlugin.Log.LogError((object)("[Dump] manager dump failed: " + ex)); } } } [HarmonyPatch(typeof(ZoneAnnounce), "Start")] internal static class ZoneAnnounce_Start_Dump { [HarmonyPrefix] [HarmonyPriority(600)] private static void Prefix(ZoneAnnounce __instance) { if (!CustomSimFrameworkPlugin.DumpEnabled) { return; } try { VanillaDumper.DumpZone(__instance); } catch (Exception ex) { CustomSimFrameworkPlugin.Log.LogError((object)("[Dump] zone dump failed: " + ex)); } } } [HarmonyPatch(typeof(SimPlayerMngr), "Start")] internal static class SimPlayerMngr_Start_Inject { [HarmonyPrefix] [HarmonyPriority(200)] private static void Prefix(SimPlayerMngr __instance) { CustomSimFrameworkPlugin.OnManagerStart(); try { GlobalDialogueInjector.Apply(__instance); } catch (Exception ex) { CustomSimFrameworkPlugin.Log.LogError((object)("[Inject] Global dialogue injection failed: " + ex)); } try { SimTemplateBuilder.InjectAll(__instance); } catch (Exception ex2) { CustomSimFrameworkPlugin.Log.LogError((object)("[Inject] Sim injection failed: " + ex2)); } } } [HarmonyPatch(typeof(SimPlayerTracking), "SpawnMeInGame")] internal static class SpawnMeInGame_QuirkFix { [HarmonyPostfix] private static void Postfix(SimPlayer __result, SimPlayerTracking _sim) { if ((Object)(object)__result == (Object)null || _sim == null || string.IsNullOrEmpty(_sim.SimName) || !SimTemplateBuilder.BuiltSims.TryGetValue(_sim.SimName, out var value)) { return; } try { SimTemplateBuilder.ApplyQuirks(__result, value); __result.SkillLevel = value.SkillLevel; if (value.Person