Decompiled source of CustomSimFramework v0.9.7

CustomSimFramework.dll

Decompiled 3 hours ago
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