Decompiled source of DvergrAllies v1.0.9

plugins/DvergrAllies.dll

Decompiled a month ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using HarmonyLib;
using Jotunn;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using TMPro;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: IgnoresAccessChecksTo("assembly_guiutils")]
[assembly: IgnoresAccessChecksTo("assembly_utils")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: AssemblyCompany("DvergrAllies")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("A Valheim mod that allows taming and crossbreeding Dvergrs.")]
[assembly: AssemblyFileVersion("1.0.9.0")]
[assembly: AssemblyInformationalVersion("1.0.9+30f59d215d6966f5160d8e27c3bd26de0ae1af8b")]
[assembly: AssemblyProduct("DvergrAllies")]
[assembly: AssemblyTitle("DvergrAllies")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.9.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace DvergrAllies
{
	public class CustomDvergrDef
	{
		public string PrefabName;

		public string BasePrefab;

		public string DisplayName;

		public string[] Loadout;

		public float HealthMultiplier = 1f;

		public bool StripArmor;

		public bool AddRage;

		public bool IsMeleeAI;

		public bool IsRangedAI;

		public bool IsClericAI;
	}
	public static class AllyPrefabManager
	{
		public static List<CustomDvergrDef> CustomVariants = new List<CustomDvergrDef>
		{
			new CustomDvergrDef
			{
				PrefabName = "AllyDvergrMageElemental",
				BasePrefab = "DvergerMageFire",
				DisplayName = "Dvergr Elemental Mage",
				Loadout = new string[0],
				IsRangedAI = true
			},
			new CustomDvergrDef
			{
				PrefabName = "AllyDvergrSpellswordFire",
				BasePrefab = "DvergerMageFire",
				DisplayName = "Dvergr Fire Spellsword",
				Loadout = new string[1] { "AllyDvergr_SwordBronze" },
				IsMeleeAI = true
			},
			new CustomDvergrDef
			{
				PrefabName = "AllyDvergrSpellswordIce",
				BasePrefab = "DvergerMageIce",
				DisplayName = "Dvergr Ice Spellsword",
				Loadout = new string[1] { "AllyDvergr_SwordBronze" },
				IsMeleeAI = true
			},
			new CustomDvergrDef
			{
				PrefabName = "AllyDvergrCleric",
				BasePrefab = "DvergerMageSupport",
				DisplayName = "Dvergr Cleric",
				Loadout = new string[3] { "AllyDvergr_MaceBronze", "DvergerStaffHeal_heal", "DvergerStaffSupport_buff" },
				IsClericAI = true
			},
			new CustomDvergrDef
			{
				PrefabName = "AllyDvergrWarrior",
				BasePrefab = "Dverger",
				DisplayName = "Dvergr Warrior",
				Loadout = new string[2] { "AllyDvergr_SwordBronze", "ShieldBanded" },
				HealthMultiplier = 1.5f,
				IsMeleeAI = true
			},
			new CustomDvergrDef
			{
				PrefabName = "AllyDvergrBerserker",
				BasePrefab = "Dverger",
				DisplayName = "Dvergr Berserker",
				Loadout = new string[1] { "AllyDvergr_Battleaxe" },
				StripArmor = false,
				AddRage = true,
				IsMeleeAI = true
			}
		};

		public static void Setup()
		{
			PrefabManager.OnVanillaPrefabsAvailable -= Setup;
			Logger.LogInfo((object)"Setting up Ally Dvergr prefabs...");
			CreateAllyVariant("Dverger", "AllyDvergrRogue", "Dvergr Rogue");
			CreateAllyVariant("DvergerMage", "AllyDvergrMage", "Dvergr Mage");
			CreateAllyVariant("DvergerMageFire", "AllyDvergrMageFire", "Dvergr Fire Mage");
			CreateAllyVariant("DvergerMageIce", "AllyDvergrMageIce", "Dvergr Ice Mage");
			CreateAllyVariant("DvergerMageSupport", "AllyDvergrMageSupport", "Dvergr Support Mage");
			foreach (CustomDvergrDef customVariant in CustomVariants)
			{
				CreateCustomVariant(customVariant);
			}
			GetOrCloneWeaponWithMeleeAnim("AllyDvergr_SwordIron");
			GetOrCloneWeaponWithMeleeAnim("AllyDvergr_SwordBlackmetal");
			GetOrCloneWeaponWithMeleeAnim("AllyDvergr_MaceIron");
			GetOrCloneWeaponWithMeleeAnim("AllyDvergr_MaceBlackmetal");
			GetOrCloneWeaponWithMeleeAnim("AllyDvergr_BattleaxeBlackmetal");
			Logger.LogInfo((object)"Ally Dvergr prefabs created.");
		}

		public static void MakePrefabTamable(GameObject prefab)
		{
			if ((Object)(object)prefab == (Object)null)
			{
				return;
			}
			Tameable component = prefab.GetComponent<Tameable>();
			if ((Object)(object)component != (Object)null)
			{
				Object.DestroyImmediate((Object)(object)component);
			}
			DvergrTameable dvergrTameable = prefab.GetComponent<DvergrTameable>();
			if ((Object)(object)dvergrTameable == (Object)null)
			{
				dvergrTameable = prefab.AddComponent<DvergrTameable>();
			}
			((Tameable)dvergrTameable).m_tamingTime = ConfigManager.TamingTime.Value;
			((Tameable)dvergrTameable).m_fedDuration = ConfigManager.FedDuration.Value;
			((Tameable)dvergrTameable).m_commandable = true;
			Procreation component2 = prefab.GetComponent<Procreation>();
			if ((Object)(object)component2 != (Object)null)
			{
				Object.DestroyImmediate((Object)(object)component2);
			}
			if ((Object)(object)prefab.GetComponent<DvergrProcreation>() == (Object)null)
			{
				prefab.AddComponent<DvergrProcreation>();
			}
			if ((Object)(object)prefab.GetComponent<DvergrGenetics>() == (Object)null)
			{
				prefab.AddComponent<DvergrGenetics>();
			}
			MonsterAI component3 = prefab.GetComponent<MonsterAI>();
			if (!((Object)(object)component3 != (Object)null))
			{
				return;
			}
			((BaseAI)component3).m_passiveAggresive = false;
			component3.m_fleeIfNotAlerted = false;
			component3.m_fleeIfLowHealth = 0f;
			component3.m_attackPlayerObjects = false;
			((BaseAI)component3).m_avoidFire = false;
			if (component3.m_consumeItems == null)
			{
				component3.m_consumeItems = new List<ItemDrop>();
			}
			string[] array = ConfigManager.TamingItems.Value.Split(new char[1] { ',' });
			foreach (string text in array)
			{
				GameObject prefab2 = PrefabManager.Instance.GetPrefab(text.Trim());
				if ((Object)(object)prefab2 != (Object)null)
				{
					ItemDrop component4 = prefab2.GetComponent<ItemDrop>();
					if ((Object)(object)component4 != (Object)null && !component3.m_consumeItems.Contains(component4))
					{
						component3.m_consumeItems.Add(component4);
					}
				}
			}
			if (component3.m_consumeSearchRange < 10f)
			{
				component3.m_consumeSearchRange = 10f;
			}
			if (component3.m_consumeSearchInterval < 10f)
			{
				component3.m_consumeSearchInterval = 10f;
			}
			component3.m_consumeRange = ConfigManager.ConsumeRange.Value;
		}

		public static GameObject GetOrCloneWeaponWithMeleeAnim(string itemName)
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Expected O, but got Unknown
			if (itemName.StartsWith("AllyDvergr_"))
			{
				string text = itemName.Replace("AllyDvergr_", "");
				GameObject prefab = PrefabManager.Instance.GetPrefab(itemName);
				if ((Object)(object)prefab != (Object)null)
				{
					return prefab;
				}
				if ((Object)(object)PrefabManager.Instance.GetPrefab(text) == (Object)null)
				{
					return null;
				}
				GameObject obj = PrefabManager.Instance.CreateClonedPrefab(itemName, text);
				CustomItem val = new CustomItem(obj, true);
				ItemDrop itemDrop = val.ItemDrop;
				if ((Object)(object)itemDrop != (Object)null && itemDrop.m_itemData != null && itemDrop.m_itemData.m_shared != null)
				{
					if (itemName.Contains("Sword") || itemName.Contains("Mace") || itemName.Contains("Battleaxe") || itemName.Contains("Shield"))
					{
						GameObject prefab2 = PrefabManager.Instance.GetPrefab("Dverger_melee");
						if ((Object)(object)prefab2 != (Object)null)
						{
							ItemDrop component = prefab2.GetComponent<ItemDrop>();
							if ((Object)(object)component != (Object)null && component.m_itemData.m_shared != null)
							{
								itemDrop.m_itemData.m_shared.m_attack = component.m_itemData.m_shared.m_attack.Clone();
							}
						}
						itemDrop.m_itemData.m_shared.m_aiAttackInterval = 1.2f;
						itemDrop.m_itemData.m_shared.m_aiAttackRange = 3.5f;
						itemDrop.m_itemData.m_shared.m_attack.m_attackRange = 3.5f;
						itemDrop.m_itemData.m_shared.m_attack.m_attackStamina = 0f;
						itemDrop.m_itemData.m_shared.m_attack.m_attackHealth = 0f;
						itemDrop.m_itemData.m_shared.m_attack.m_attackHealthPercentage = 0f;
						itemDrop.m_itemData.m_shared.m_attack.m_attackEitr = 0f;
						itemDrop.m_itemData.m_shared.m_useDurability = false;
					}
					if (itemName.Contains("Battleaxe"))
					{
						itemDrop.m_itemData.m_shared.m_damages.m_slash *= 1.5f;
					}
					float value = ConfigManager.AllyDamageMultiplier.Value;
					if (value != 1f)
					{
						itemDrop.m_itemData.m_shared.m_damages.m_damage *= value;
						itemDrop.m_itemData.m_shared.m_damages.m_blunt *= value;
						itemDrop.m_itemData.m_shared.m_damages.m_slash *= value;
						itemDrop.m_itemData.m_shared.m_damages.m_pierce *= value;
						itemDrop.m_itemData.m_shared.m_damages.m_chop *= value;
						itemDrop.m_itemData.m_shared.m_damages.m_pickaxe *= value;
						itemDrop.m_itemData.m_shared.m_damages.m_fire *= value;
						itemDrop.m_itemData.m_shared.m_damages.m_frost *= value;
						itemDrop.m_itemData.m_shared.m_damages.m_lightning *= value;
						itemDrop.m_itemData.m_shared.m_damages.m_poison *= value;
						itemDrop.m_itemData.m_shared.m_damages.m_spirit *= value;
					}
				}
				ItemManager.Instance.AddItem(val);
				return obj;
			}
			return PrefabManager.Instance.GetPrefab(itemName);
		}

		private static string GetSuitName(string basePrefabName)
		{
			return basePrefabName switch
			{
				"Dverger" => "DvergerSuitArbalest", 
				"DvergerMageFire" => "DvergerSuitFire", 
				"DvergerMageIce" => "DvergerSuitIce", 
				"DvergerMageSupport" => "DvergerSuitSupport", 
				_ => null, 
			};
		}

		private static void CreateCustomVariant(CustomDvergrDef def)
		{
			CreateAllyVariant(def.BasePrefab, def.PrefabName, def.DisplayName, def.HealthMultiplier, def.IsMeleeAI, def.IsRangedAI, def.IsClericAI);
			GameObject prefab = PrefabManager.Instance.GetPrefab(def.PrefabName);
			if ((Object)(object)prefab == (Object)null)
			{
				return;
			}
			Humanoid component = prefab.GetComponent<Humanoid>();
			if ((Object)(object)component == (Object)null)
			{
				return;
			}
			component.m_randomSets = (ItemSet[])(object)new ItemSet[0];
			component.m_randomWeapon = (GameObject[])(object)new GameObject[0];
			component.m_randomShield = (GameObject[])(object)new GameObject[0];
			if (def.StripArmor)
			{
				component.m_randomArmor = (GameObject[])(object)new GameObject[0];
			}
			List<GameObject> list = new List<GameObject>();
			if (!def.StripArmor)
			{
				string suitName = GetSuitName(def.BasePrefab);
				if (suitName != null)
				{
					GameObject prefab2 = PrefabManager.Instance.GetPrefab(suitName);
					if ((Object)(object)prefab2 != (Object)null)
					{
						list.Add(prefab2);
						ConfigManager.LogEquipment("[CustomVariant] " + def.PrefabName + ": Added suit: " + suitName);
					}
				}
			}
			string[] loadout = def.Loadout;
			foreach (string text in loadout)
			{
				GameObject orCloneWeaponWithMeleeAnim = GetOrCloneWeaponWithMeleeAnim(text);
				if ((Object)(object)orCloneWeaponWithMeleeAnim != (Object)null)
				{
					list.Add(orCloneWeaponWithMeleeAnim);
					ConfigManager.LogEquipment("[CustomVariant] " + def.PrefabName + ": Added: " + text);
					continue;
				}
				Logger.LogWarning((object)("[CustomVariant] " + def.PrefabName + ": Could not find prefab '" + text + "'!"));
			}
			component.m_defaultItems = list.ToArray();
			component.m_unarmedWeapon = null;
			ConfigManager.LogEquipment($"[CustomVariant] {def.PrefabName}: Final m_defaultItems count: {list.Count}");
			if (def.AddRage)
			{
				prefab.AddComponent<DvergrBerserkerRage>();
			}
			DvergrCombatAI component2 = prefab.GetComponent<DvergrCombatAI>();
			if ((Object)(object)component2 != (Object)null && def.PrefabName == "AllyDvergrMageElemental")
			{
				component2.IsElementalMage = true;
			}
		}

		private static void CreateAllyVariant(string basePrefabName, string newPrefabName, string displayName, float healthMultiplier = 1f, bool isMelee = false, bool isRanged = false, bool isCleric = false)
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Expected O, but got Unknown
			//IL_028f: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)PrefabManager.Instance.GetPrefab(basePrefabName) == (Object)null)
			{
				Logger.LogWarning((object)("Base prefab " + basePrefabName + " not found!"));
				return;
			}
			GameObject val = PrefabManager.Instance.CreateClonedPrefab(newPrefabName, basePrefabName);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			CustomPrefab val2 = new CustomPrefab(val, true);
			StripBalrondComponents(val);
			Tameable component = val.GetComponent<Tameable>();
			if ((Object)(object)component != (Object)null)
			{
				Object.DestroyImmediate((Object)(object)component);
			}
			DvergrTameable dvergrTameable = val.GetComponent<DvergrTameable>();
			if ((Object)(object)dvergrTameable == (Object)null)
			{
				dvergrTameable = val.AddComponent<DvergrTameable>();
			}
			((Tameable)dvergrTameable).m_tamingTime = ConfigManager.TamingTime.Value;
			((Tameable)dvergrTameable).m_fedDuration = ConfigManager.FedDuration.Value;
			((Tameable)dvergrTameable).m_commandable = true;
			MonsterAI component2 = val.GetComponent<MonsterAI>();
			if ((Object)(object)component2 != (Object)null)
			{
				string[] array = ConfigManager.TamingItems.Value.Split(new char[1] { ',' });
				component2.m_consumeItems = new List<ItemDrop>();
				string[] array2 = array;
				foreach (string text in array2)
				{
					GameObject prefab = PrefabManager.Instance.GetPrefab(text.Trim());
					if ((Object)(object)prefab != (Object)null)
					{
						ItemDrop component3 = prefab.GetComponent<ItemDrop>();
						if ((Object)(object)component3 != (Object)null)
						{
							component2.m_consumeItems.Add(component3);
						}
						else
						{
							Logger.LogWarning((object)("Item " + text + " is missing ItemDrop component!"));
						}
					}
					else
					{
						Logger.LogWarning((object)("Could not find taming item prefab: " + text));
					}
				}
				component2.m_consumeSearchRange = 10f;
				component2.m_consumeSearchInterval = 10f;
				component2.m_consumeRange = ConfigManager.ConsumeRange.Value;
				((BaseAI)component2).m_aggravatable = false;
				((BaseAI)component2).m_passiveAggresive = false;
				component2.m_fleeIfLowHealth = 0f;
				component2.m_fleeIfNotAlerted = false;
				component2.m_circulateWhileCharging = false;
				component2.m_circulateWhileChargingFlying = false;
				((BaseAI)component2).m_avoidFire = false;
				((BaseAI)component2).m_avoidWater = false;
				component2.m_interceptTimeMax = 2f;
				component2.m_interceptTimeMin = 0f;
				component2.m_maxChaseDistance = 0f;
				component2.m_alertRange = 30f;
				component2.m_attackPlayerObjects = false;
			}
			Procreation component4 = val.GetComponent<Procreation>();
			if ((Object)(object)component4 != (Object)null)
			{
				Object.DestroyImmediate((Object)(object)component4);
			}
			if ((Object)(object)val.GetComponent<DvergrProcreation>() == (Object)null)
			{
				val.AddComponent<DvergrProcreation>();
			}
			val.AddComponent<DvergrGenetics>();
			val.AddComponent<DvergrWeaponScaler>();
			DvergrCombatAI dvergrCombatAI = val.AddComponent<DvergrCombatAI>();
			dvergrCombatAI.IsMeleeClass = isMelee;
			dvergrCombatAI.IsRangedClass = isRanged;
			dvergrCombatAI.IsClericClass = isCleric;
			Character component5 = val.GetComponent<Character>();
			if ((Object)(object)component5 != (Object)null)
			{
				component5.m_health *= ConfigManager.AllyHealthMultiplier.Value * healthMultiplier;
				component5.m_faction = (Faction)0;
				component5.m_runSpeed = Mathf.Max(component5.m_runSpeed, 6.5f);
				component5.m_walkSpeed = Mathf.Max(component5.m_walkSpeed, 3f);
			}
			Humanoid component6 = val.GetComponent<Humanoid>();
			if ((Object)(object)component6 != (Object)null)
			{
				((Character)component6).m_name = displayName;
			}
			PrefabManager.Instance.AddPrefab(val2);
		}

		private static void StripBalrondComponents(GameObject prefab)
		{
			if (!Plugin.HasBalrondIdleActors)
			{
				return;
			}
			string[] array = new string[3] { "BalrondActor", "BalrondActorTalker", "BalrondActorNamePicker" };
			foreach (string text in array)
			{
				Component component = prefab.GetComponent(text);
				if ((Object)(object)component != (Object)null)
				{
					Object.DestroyImmediate((Object)(object)component);
					Logger.LogInfo((object)("[DvergrAllies] Stripped " + text + " from cloned " + ((Object)prefab).name));
					ConfigManager.LogDebug("[Compat] Removed " + text + " from " + ((Object)prefab).name + " to prevent Balrond Idle Actors conflict.");
				}
			}
		}
	}
	[HarmonyPatch(typeof(ZNetScene), "Awake")]
	public static class Patch_ZNetScene_Awake_Dvergrs
	{
		[HarmonyPostfix]
		public static void Postfix(ZNetScene __instance)
		{
			if ((Object)(object)__instance == (Object)null || __instance.m_prefabs == null)
			{
				return;
			}
			if (Plugin.HasBalrondIdleActors)
			{
				Debug.LogError((object)"=======================================================================");
				Debug.LogError((object)"[DvergrAllies] BalrondIdleActors detected! Skipping wild Dvergr injection!");
				Debug.LogError((object)"[DvergrAllies] Enabling compatibility mode: Wild Dvergr modifications are disabled.");
				Debug.LogError((object)"=======================================================================");
				return;
			}
			Logger.LogInfo((object)"Scanning ZNetScene for wild Dvergr prefabs to make tamable...");
			int num = 0;
			foreach (GameObject prefab in __instance.m_prefabs)
			{
				if (!((Object)(object)prefab == (Object)null))
				{
					string text = ((Object)prefab).name.ToLower();
					if ((text.Contains("dverg") || text.Contains("dverger")) && !text.Contains("ally") && (Object)(object)prefab.GetComponent<Humanoid>() != (Object)null && (Object)(object)prefab.GetComponent<MonsterAI>() != (Object)null)
					{
						AllyPrefabManager.MakePrefabTamable(prefab);
						num++;
					}
				}
			}
			Logger.LogInfo((object)$"Successfully injected Taming/Breeding components into {num} wild Dvergr prefabs!");
		}
	}
	[HarmonyPatch(typeof(MonsterAI), "UpdateAI")]
	public static class Patch_MonsterAI_UpdateAI_Dvergrs
	{
		[HarmonyPrefix]
		public static void Prefix(MonsterAI __instance)
		{
			if (!((Object)(object)__instance == (Object)null) && !((Object)(object)((Component)__instance).gameObject == (Object)null))
			{
				string text = ((Object)((Component)__instance).gameObject).name.ToLower();
				if ((text.Contains("dverg") || text.Contains("dverger")) && !text.Contains("ally") && !Plugin.HasBalrondIdleActors && !ValkyriesCargoCompat.IsExcluded(((Component)__instance).gameObject))
				{
					((BaseAI)__instance).m_passiveAggresive = false;
					__instance.m_attackPlayerObjects = false;
					((BaseAI)__instance).m_avoidFire = false;
					__instance.m_fleeIfNotAlerted = false;
				}
			}
		}
	}
	public static class ConfigManager
	{
		public static ConfigEntry<float> TamingTime;

		public static ConfigEntry<string> TamingItems;

		public static ConfigEntry<float> FedDuration;

		public static ConfigEntry<float> ConsumeRange;

		public static ConfigEntry<float> PregnancyDuration;

		public static ConfigEntry<int> BreedingLimit;

		public static ConfigEntry<float> SpecialComboChance;

		public static ConfigEntry<float> PregnancyChance;

		public static ConfigEntry<float> PartnerCheckRange;

		public static ConfigEntry<float> UpdateInterval;

		public static ConfigEntry<float> LevelUpChance;

		public static ConfigEntry<int> MaxBreedingLevel;

		public static ConfigEntry<float> MaxFollowLeash;

		public static ConfigEntry<int> ContractCost;

		public static ConfigEntry<float> AllyHealthMultiplier;

		public static ConfigEntry<float> AllyDamageMultiplier;

		public static ConfigEntry<bool> EnableStatsExport;

		public static ConfigEntry<float> StatsExportInterval;

		public static ConfigEntry<bool> ForceCensusUnavailable;

		public static ConfigEntry<bool> EnableDebugLogs;

		public static ConfigEntry<bool> DebugFull;

		public static ConfigEntry<bool> DebugAI;

		public static ConfigEntry<bool> DebugBreeding;

		public static ConfigEntry<bool> DebugEquipment;

		public static void LogAI(string message)
		{
			if (EnableDebugLogs != null && EnableDebugLogs.Value && (DebugFull.Value || DebugAI.Value))
			{
				Logger.LogInfo((object)("[AI] " + message));
			}
		}

		public static void LogBreeding(string message)
		{
			if (EnableDebugLogs != null && EnableDebugLogs.Value && (DebugFull.Value || DebugBreeding.Value))
			{
				Logger.LogInfo((object)("[Breeding] " + message));
			}
		}

		public static void LogEquipment(string message)
		{
			if (EnableDebugLogs != null && EnableDebugLogs.Value && (DebugFull.Value || DebugEquipment.Value))
			{
				Logger.LogInfo((object)("[Equipment] " + message));
			}
		}

		public static void LogDebug(string message)
		{
			if (EnableDebugLogs != null && EnableDebugLogs.Value && DebugFull.Value)
			{
				Logger.LogInfo((object)("[General] " + message));
			}
		}

		private static ConfigDescription SyncedConfig(string description)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Expected O, but got Unknown
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Expected O, but got Unknown
			return new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} });
		}

		public static void Init(ConfigFile config)
		{
			TamingTime = config.Bind<float>("1 - Taming", "Taming Time", 1800f, SyncedConfig("Time in seconds required to tame a Dvergr."));
			TamingItems = config.Bind<string>("1 - Taming", "Taming Items", "CookedMeat,Coins,Sausages,YggdrasilWood", SyncedConfig("Comma-separated list of item prefabs Dvergrs will eat to tame/breed."));
			FedDuration = config.Bind<float>("1 - Taming", "Fed Duration", 600f, SyncedConfig("Time in seconds a Dvergr stays fed (not hungry) after eating."));
			ConsumeRange = config.Bind<float>("1 - Taming", "Consume Range", 4f, SyncedConfig("Distance from which they can consume food. Increase if they get stuck pushing each other."));
			PregnancyDuration = config.Bind<float>("2 - Breeding", "Pregnancy Duration", 600f, SyncedConfig("Time in seconds before a pregnant Dvergr gives birth."));
			BreedingLimit = config.Bind<int>("2 - Breeding", "Breeding Limit", 4, SyncedConfig("Maximum number of Dvergrs in a 10m radius before they stop breeding."));
			SpecialComboChance = config.Bind<float>("2 - Breeding", "Special Combo Chance", 25f, SyncedConfig("Percentage chance (0-100) to breed a special combo variant if parents are compatible."));
			PregnancyChance = config.Bind<float>("2 - Breeding", "Pregnancy Chance", 0.33f, SyncedConfig("Chance (0.0 to 1.0) of getting pregnant when conditions are met."));
			PartnerCheckRange = config.Bind<float>("2 - Breeding", "Partner Check Range", 10f, SyncedConfig("Distance within which to look for a partner."));
			UpdateInterval = config.Bind<float>("2 - Breeding", "Update Interval", 10f, SyncedConfig("How often (in seconds) the breeding logic checks for partners/pregnancy."));
			LevelUpChance = config.Bind<float>("2 - Breeding", "Level Up Chance", 10f, SyncedConfig("Percentage chance (0-100) for offspring to gain +1 level (star) above their highest level parent."));
			MaxBreedingLevel = config.Bind<int>("2 - Breeding", "Max Breeding Level", 3, SyncedConfig("Maximum level (3 = 2 stars) a Dvergr can reach through breeding."));
			MaxFollowLeash = config.Bind<float>("2.5 - AI", "Max Follow Leash", 40f, SyncedConfig("Max distance (meters) a Dvergr can chase an enemy before dropping aggro to return to the player they are following."));
			ContractCost = config.Bind<int>("3 - Economy", "Contract Cost", 999, SyncedConfig("Cost in coins to purchase a Dvergr Contract from Haldor or Recruiter."));
			AllyHealthMultiplier = config.Bind<float>("4 - Stats", "Health Multiplier", 1f, SyncedConfig("Multiplier applied to base health of Ally Dvergrs."));
			AllyDamageMultiplier = config.Bind<float>("4 - Stats", "Damage Multiplier", 1f, SyncedConfig("Multiplier applied to base damage of Ally Dvergrs."));
			EnableStatsExport = config.Bind<bool>("6 - BarrkBOT Export", "Enable Stats Export", true, "Periodically writes Dvergr population/ownership stats to BepInEx/config/DvergrAllies/ as JSON for BarrkBOT to read. Dedicated server / world host only; local-only setting, not synced.");
			StatsExportInterval = config.Bind<float>("6 - BarrkBOT Export", "Export Interval", 60f, "Seconds between stats exports. BarrkBOT only sweeps its watch folder every 20 minutes, so going below 60s buys nothing but disk churn.");
			ForceCensusUnavailable = config.Bind<bool>("6 - BarrkBOT Export", "Force Census Unavailable", false, "TESTING ONLY. Forces the export to report 'census_unavailable' as if the world could not be scanned, so the failure path can be exercised on a live server without breaking anything. Population counts are written as null (not zero); lifetime history is unaffected and keeps recording. The export says plainly that it was forced, so this cannot be mistaken for a real fault. Leave this off in normal play.");
			EnableDebugLogs = config.Bind<bool>("5 - Debug", "Enable Debug Logs", false, SyncedConfig("Master switch to enable debug logging."));
			DebugFull = config.Bind<bool>("5 - Debug", "Debug Full", false, SyncedConfig("If true, overrides sub-categories and logs absolutely everything."));
			DebugAI = config.Bind<bool>("5 - Debug", "Debug AI", false, SyncedConfig("Log AI behavior overrides, targeting, and behavior resets."));
			DebugBreeding = config.Bind<bool>("5 - Debug", "Debug Breeding", false, SyncedConfig("Log partner finding, pregnancy, birth, and genetic scaling."));
			DebugEquipment = config.Bind<bool>("5 - Debug", "Debug Equipment", false, SyncedConfig("Log prefab creation, loadout setup, and weapon scaling/upgrades."));
		}
	}
	public static class CustomStavesManager
	{
		public static void Setup()
		{
			PrefabManager.OnVanillaPrefabsAvailable -= Setup;
			CreateEarthStaff();
			CreateSpiritStaff();
		}

		private static void CreateEarthStaff()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Expected O, but got Unknown
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Expected O, but got Unknown
			if ((Object)(object)PrefabManager.Instance.GetPrefab("DvergerStaffFire") == (Object)null)
			{
				return;
			}
			CustomItem val = new CustomItem("AllyDvergrStaffEarth", "DvergerStaffFire");
			ItemDrop itemDrop = val.ItemDrop;
			itemDrop.m_itemData.m_shared.m_name = "Earth Staff";
			if ((Object)(object)PrefabManager.Instance.GetPrefab("DvergerStaffIce_projectile") != (Object)null)
			{
				GameObject val2 = PrefabManager.Instance.CreateClonedPrefab("AllyDvergrStaffEarth_projectile", "DvergerStaffIce_projectile");
				Projectile component = val2.GetComponent<Projectile>();
				if ((Object)(object)component != (Object)null)
				{
					component.m_damage.m_frost = 0f;
					component.m_damage.m_blunt = 20f;
					component.m_damage.m_poison = 40f;
					component.m_statusEffect = "Tar";
					GameObject prefab = PrefabManager.Instance.GetPrefab("vfx_blob_attack");
					if ((Object)(object)prefab != (Object)null)
					{
						component.m_hitEffects.m_effectPrefabs = (EffectData[])(object)new EffectData[1]
						{
							new EffectData
							{
								m_prefab = prefab,
								m_enabled = true
							}
						};
					}
				}
				itemDrop.m_itemData.m_shared.m_attack.m_attackProjectile = val2;
			}
			ItemManager.Instance.AddItem(val);
		}

		private static void CreateSpiritStaff()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Expected O, but got Unknown
			if ((Object)(object)PrefabManager.Instance.GetPrefab("DvergerStaffFire") == (Object)null)
			{
				return;
			}
			CustomItem val = new CustomItem("AllyDvergrStaffSpirit", "DvergerStaffFire");
			ItemDrop itemDrop = val.ItemDrop;
			itemDrop.m_itemData.m_shared.m_name = "Spirit Staff";
			if ((Object)(object)PrefabManager.Instance.GetPrefab("DvergerStaffIce_projectile") != (Object)null)
			{
				GameObject val2 = PrefabManager.Instance.CreateClonedPrefab("AllyDvergrStaffSpirit_projectile", "DvergerStaffIce_projectile");
				Projectile component = val2.GetComponent<Projectile>();
				if ((Object)(object)component != (Object)null)
				{
					component.m_damage.m_frost = 0f;
					component.m_damage.m_blunt = 20f;
					component.m_damage.m_spirit = 60f;
				}
				itemDrop.m_itemData.m_shared.m_attack.m_attackProjectile = val2;
			}
			ItemManager.Instance.AddItem(val);
		}
	}
	public class DvergrBerserkerRage : MonoBehaviour
	{
		private Character m_character;

		private void Start()
		{
			m_character = ((Component)this).GetComponent<Character>();
			((MonoBehaviour)this).InvokeRepeating("ApplyRage", 1f, 5f);
		}

		private void ApplyRage()
		{
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)m_character == (Object)null) && !m_character.IsDead())
			{
				SEMan sEMan = m_character.GetSEMan();
				if (sEMan != null && !sEMan.HaveStatusEffect(StringExtensionMethods.GetStableHashCode("BerserkerRage")))
				{
					SE_Stats val = ScriptableObject.CreateInstance<SE_Stats>();
					((Object)val).name = "BerserkerRage";
					((StatusEffect)val).m_name = "Rage";
					val.m_speedModifier = 1.15f;
					val.m_staminaRegenMultiplier = 1.3f;
					val.m_healthRegenMultiplier = 1.2f;
					val.m_raiseSkill = (SkillType)7;
					val.m_raiseSkillModifier = 15f;
					sEMan.AddStatusEffect((StatusEffect)(object)val, false, 0, 0f, (short)(-1));
				}
			}
		}
	}
	public class DvergrCombatAI : MonoBehaviour
	{
		public bool IsMeleeClass;

		public bool IsRangedClass;

		public bool IsClericClass;

		public bool IsElementalMage;

		private MonsterAI m_monsterAI;

		private Character m_character;

		private bool m_initialized;

		private bool m_isConfigured;

		private float m_clericHealTimer = 15f;

		private float m_healScanTimer;

		private float m_elementRotateTimer;

		private List<ItemData> m_elementalStaves = new List<ItemData>();

		private int m_elementSetIndex;

		private void Awake()
		{
			m_monsterAI = ((Component)this).GetComponent<MonsterAI>();
			m_character = ((Component)this).GetComponent<Character>();
			m_isConfigured = false;
			if (!Plugin.HasBalrondIdleActors)
			{
				return;
			}
			string[] array = new string[3] { "BalrondActor", "BalrondActorTalker", "BalrondActorNamePicker" };
			foreach (string text in array)
			{
				Component component = ((Component)this).GetComponent(text);
				if ((Object)(object)component != (Object)null)
				{
					Object.Destroy((Object)(object)component);
					ConfigManager.LogDebug("[Compat] Runtime stripped " + text + " from existing active " + ((Object)((Component)this).gameObject).name);
				}
			}
		}

		private void Start()
		{
			ForceAggressiveAI();
			((MonoBehaviour)this).Invoke("ForceAggressiveAI", 1f);
			((MonoBehaviour)this).Invoke("ForceAggressiveAI", 3f);
			((MonoBehaviour)this).Invoke("InjectMagicWeapons", 0.5f);
		}

		private void ForceAggressiveAI()
		{
			if (!((Object)(object)m_monsterAI == (Object)null))
			{
				((BaseAI)m_monsterAI).m_aggravatable = false;
				((BaseAI)m_monsterAI).m_passiveAggresive = false;
				m_monsterAI.m_fleeIfNotAlerted = false;
				((BaseAI)m_monsterAI).m_avoidFire = false;
				((BaseAI)m_monsterAI).m_avoidWater = false;
				m_monsterAI.m_fleeIfLowHealth = 0f;
				m_monsterAI.m_attackPlayerObjects = false;
				if (IsMeleeClass)
				{
					m_monsterAI.m_circulateWhileCharging = false;
					m_monsterAI.m_circulateWhileChargingFlying = false;
					((BaseAI)m_monsterAI).m_randomMoveInterval = 0f;
					((BaseAI)m_monsterAI).m_randomCircleInterval = 0f;
					m_monsterAI.m_alertRange = 30f;
				}
				else if (IsRangedClass)
				{
					m_monsterAI.m_circulateWhileCharging = false;
					m_monsterAI.m_circulateWhileChargingFlying = false;
					((BaseAI)m_monsterAI).m_randomMoveInterval = 10f;
				}
				else if (IsClericClass)
				{
					m_monsterAI.m_circulateWhileCharging = false;
					((BaseAI)m_monsterAI).m_randomMoveInterval = 5f;
					((BaseAI)m_monsterAI).m_randomCircleInterval = 5f;
					m_monsterAI.m_alertRange = 30f;
				}
				if (!m_initialized)
				{
					m_initialized = true;
					ConfigManager.LogAI($"[CombatAI] Forced aggressive AI on {((Object)((Component)this).gameObject).name} (Melee:{IsMeleeClass}, Ranged:{IsRangedClass})");
				}
			}
		}

		private void FixedUpdate()
		{
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)m_monsterAI == (Object)null)
			{
				return;
			}
			if (Time.frameCount % 60 == 0 && (((BaseAI)m_monsterAI).m_aggravatable || ((BaseAI)m_monsterAI).m_passiveAggresive || m_monsterAI.m_fleeIfNotAlerted))
			{
				ConfigManager.LogAI("[CombatAI] AI flags were reset on " + ((Object)((Component)this).gameObject).name + ", re-forcing!");
				ForceAggressiveAI();
			}
			if (IsMeleeClass || IsClericClass)
			{
				((BaseAI)m_monsterAI).m_randomCircleInterval = 9999f;
				m_monsterAI.m_circulateWhileCharging = false;
			}
			GameObject followTarget = m_monsterAI.GetFollowTarget();
			if ((Object)(object)followTarget != (Object)null && (Object)(object)((BaseAI)m_monsterAI).GetTargetCreature() != (Object)null)
			{
				float num = Vector3.Distance(((Component)this).transform.position, followTarget.transform.position);
				if (num > ConfigManager.MaxFollowLeash.Value)
				{
					m_monsterAI.SetTarget((Character)null);
					ConfigManager.LogAI($"[Leash] {((Object)((Component)this).gameObject).name} strayed too far ({num:F1}m > {ConfigManager.MaxFollowLeash.Value}m), dropping aggro to return to leader.");
				}
			}
			if (IsClericClass)
			{
				m_clericHealTimer += Time.fixedDeltaTime;
				m_healScanTimer += Time.fixedDeltaTime;
				if (m_healScanTimer >= 2f)
				{
					m_healScanTimer = 0f;
					UpdateClericAI();
				}
			}
			if (IsElementalMage)
			{
				UpdateElementalMageAI();
			}
		}

		private void UpdateClericAI()
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_018e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0193: Unknown result type (might be due to invalid IL or missing references)
			//IL_0198: Unknown result type (might be due to invalid IL or missing references)
			if (((Object)(object)((BaseAI)m_monsterAI).m_character != (Object)null && ((BaseAI)m_monsterAI).m_character.InAttack()) || m_clericHealTimer < 15f)
			{
				return;
			}
			bool flag = false;
			int level = ((BaseAI)m_monsterAI).m_character.GetLevel();
			Collider[] array = Physics.OverlapSphere(((Component)this).transform.position, 15f, LayerMask.GetMask(new string[3] { "character", "character_net", "character_ghost" }));
			for (int i = 0; i < array.Length; i++)
			{
				Character component = ((Component)array[i]).GetComponent<Character>();
				if (!((Object)(object)component != (Object)null) || component.IsDead() || (!((Object)(object)component == (Object)(object)((BaseAI)m_monsterAI).m_character) && !component.IsPlayer() && !component.IsTamed()) || !(component.GetHealthPercentage() < 0.8f))
				{
					continue;
				}
				if (!flag)
				{
					((BaseAI)m_monsterAI).m_character.GetZAnim().SetTrigger("StaffCast");
					flag = true;
					m_clericHealTimer = 0f;
					ConfigManager.LogAI($"[Cleric] Triggered AoE HoT Pulse (Level {level})");
					if ((Object)(object)ZNetScene.instance != (Object)null)
					{
						GameObject prefab = ZNetScene.instance.GetPrefab("vfx_Potion_health_medium");
						if ((Object)(object)prefab != (Object)null)
						{
							Object.Instantiate<GameObject>(prefab, ((Component)this).transform.position, Quaternion.identity);
						}
						GameObject prefab2 = ZNetScene.instance.GetPrefab("vfx_creature_soothed");
						if ((Object)(object)prefab2 != (Object)null)
						{
							Object.Instantiate<GameObject>(prefab2, ((Component)this).transform.position + Vector3.up, Quaternion.identity);
						}
					}
				}
				DvergrHoT[] components = ((Component)component).gameObject.GetComponents<DvergrHoT>();
				for (int j = 0; j < components.Length; j++)
				{
					Object.Destroy((Object)(object)components[j]);
				}
				((Component)component).gameObject.AddComponent<DvergrHoT>().Setup(level);
			}
		}

		private ItemData CreateNativeVariant(string baseWeaponName, string newProjectileName = null)
		{
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Expected O, but got Unknown
			if ((Object)(object)ObjectDB.instance == (Object)null || (Object)(object)ZNetScene.instance == (Object)null)
			{
				return null;
			}
			GameObject val = ObjectDB.instance.GetItemPrefab(baseWeaponName) ?? ZNetScene.instance.GetPrefab(baseWeaponName);
			if ((Object)(object)val == (Object)null)
			{
				ConfigManager.LogAI("[NativeVariant] Base weapon " + baseWeaponName + " not found!");
				return null;
			}
			ItemData val2 = val.GetComponent<ItemDrop>().m_itemData.Clone();
			val2.m_dropPrefab = val;
			SharedData shared = val2.m_shared;
			val2.m_shared = new SharedData();
			FieldInfo[] fields = typeof(SharedData).GetFields(BindingFlags.Instance | BindingFlags.Public);
			foreach (FieldInfo fieldInfo in fields)
			{
				fieldInfo.SetValue(val2.m_shared, fieldInfo.GetValue(shared));
			}
			val2.m_shared.m_attack = shared.m_attack.Clone();
			if (shared.m_secondaryAttack != null)
			{
				val2.m_shared.m_secondaryAttack = shared.m_secondaryAttack.Clone();
			}
			val2.m_shared.m_attack.m_attackStamina = 0f;
			val2.m_shared.m_attack.m_attackHealth = 0f;
			val2.m_shared.m_attack.m_attackEitr = 0f;
			float value = ConfigManager.AllyDamageMultiplier.Value;
			if (value != 1f)
			{
				val2.m_shared.m_damages.m_damage *= value;
				val2.m_shared.m_damages.m_blunt *= value;
				val2.m_shared.m_damages.m_slash *= value;
				val2.m_shared.m_damages.m_pierce *= value;
				val2.m_shared.m_damages.m_chop *= value;
				val2.m_shared.m_damages.m_pickaxe *= value;
				val2.m_shared.m_damages.m_fire *= value;
				val2.m_shared.m_damages.m_frost *= value;
				val2.m_shared.m_damages.m_lightning *= value;
				val2.m_shared.m_damages.m_poison *= value;
				val2.m_shared.m_damages.m_spirit *= value;
			}
			if (newProjectileName != null)
			{
				GameObject val3 = ObjectDB.instance.GetItemPrefab(newProjectileName) ?? ZNetScene.instance.GetPrefab(newProjectileName);
				if ((Object)(object)val3 != (Object)null)
				{
					val2.m_shared.m_attack.m_attackProjectile = val3;
				}
			}
			return val2;
		}

		private void InjectMagicWeapons()
		{
			if ((Object)(object)m_monsterAI == (Object)null || (Object)(object)((BaseAI)m_monsterAI).m_character == (Object)null)
			{
				return;
			}
			Character character = ((BaseAI)m_monsterAI).m_character;
			Humanoid val = (Humanoid)(object)((character is Humanoid) ? character : null);
			if ((Object)(object)val == (Object)null || val.GetInventory() == null)
			{
				return;
			}
			ConfigManager.LogAI($"[RuntimeBuilder] {((Object)((Component)this).gameObject).name} - inventory has {val.GetInventory().GetAllItems().Count} items");
			foreach (ItemData allItem in val.GetInventory().GetAllItems())
			{
				if (allItem.IsWeapon() && (Object)(object)allItem.m_dropPrefab != (Object)null && ((Object)allItem.m_dropPrefab).name.Contains("Heal"))
				{
					allItem.m_shared.m_aiAttackInterval = 30f;
					ConfigManager.LogAI("[RuntimeBuilder] NERFED heal staff: " + ((Object)allItem.m_dropPrefab).name + " -> interval=30s");
				}
			}
			if (((Object)((Component)this).gameObject).name.Contains("SpellswordFire"))
			{
				ItemData val2 = CreateNativeVariant("DvergerStaffFire_fireball");
				if (val2 != null)
				{
					val2.m_shared.m_aiAttackInterval = 15f;
					val.GetInventory().AddItem(val2);
					ConfigManager.LogAI("[SpellswordFire] Injected Native Fireball into memory inventory!");
				}
			}
			else if (((Object)((Component)this).gameObject).name.Contains("SpellswordIce"))
			{
				ItemData val3 = CreateNativeVariant("DvergerStaffIce_icebolt");
				if (val3 != null)
				{
					val3.m_shared.m_aiAttackInterval = 15f;
					val.GetInventory().AddItem(val3);
					ConfigManager.LogAI("[SpellswordIce] Injected Native Icebolt into memory inventory!");
				}
			}
			if (!IsElementalMage)
			{
				return;
			}
			m_elementalStaves.Clear();
			m_elementalStaves.Add(CreateNativeVariant("DvergerStaffFire_fireball"));
			m_elementalStaves.Add(CreateNativeVariant("DvergerStaffIce_icebolt"));
			m_elementalStaves.Add(CreateNativeVariant("DvergerStaffFire_fireball", "GoblinShaman_projectile_fireball"));
			m_elementalStaves.RemoveAll((ItemData x) => x == null);
			List<ItemData> list = new List<ItemData>();
			foreach (ItemData allItem2 in val.GetInventory().GetAllItems())
			{
				if (allItem2.IsWeapon())
				{
					val.UnequipItem(allItem2, true);
					list.Add(allItem2);
				}
			}
			foreach (ItemData item in list)
			{
				val.GetInventory().RemoveItem(item);
			}
			if (m_elementalStaves.Count > 0)
			{
				val.GetInventory().AddItem(m_elementalStaves[0]);
				val.EquipItem(m_elementalStaves[0], true);
			}
			ConfigManager.LogAI($"[ElementalMage] Loaded {m_elementalStaves.Count} elements into memory cache.");
			m_elementRotateTimer = 0f;
		}

		private void UpdateElementalMageAI()
		{
			if ((Object)(object)((BaseAI)m_monsterAI).m_character == (Object)null)
			{
				return;
			}
			Character character = ((BaseAI)m_monsterAI).m_character;
			Humanoid val = (Humanoid)(object)((character is Humanoid) ? character : null);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			Inventory inventory = val.GetInventory();
			if (inventory == null || m_elementalStaves.Count == 0)
			{
				return;
			}
			m_elementRotateTimer += Time.fixedDeltaTime;
			if (!(m_elementRotateTimer >= 10f))
			{
				return;
			}
			m_elementRotateTimer = 0f;
			m_elementSetIndex = (m_elementSetIndex + 1) % m_elementalStaves.Count;
			ItemData val2 = m_elementalStaves[m_elementSetIndex];
			List<ItemData> list = new List<ItemData>();
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (allItem.IsWeapon())
				{
					val.UnequipItem(allItem, true);
					list.Add(allItem);
				}
			}
			foreach (ItemData item in list)
			{
				inventory.RemoveItem(item);
			}
			inventory.AddItem(val2);
			val.EquipItem(val2, true);
			ConfigManager.LogAI($"[ElementalMage] Swapped to element {m_elementSetIndex}");
		}
	}
	public class DvergrGenetics : MonoBehaviour
	{
		public enum Gender
		{
			Male,
			Female
		}

		public const string StealthExemptKey = "SoMStealthExempt";

		private ZNetView m_nview;

		private Humanoid m_humanoid;

		public static void RefreshStealthExemption(Character c, ZNetView nview)
		{
			if (!((Object)(object)c == (Object)null))
			{
				RefreshStealthExemption(c, nview, c.IsTamed());
			}
		}

		public static void RefreshStealthExemption(Character c, ZNetView nview, bool tamed)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Invalid comparison between Unknown and I4
			if (!((Object)(object)c == (Object)null) && !((Object)(object)nview == (Object)null) && nview.IsValid())
			{
				ZDO zDO = nview.GetZDO();
				if (zDO != null)
				{
					bool flag = tamed || (int)c.m_faction == 0;
					zDO.Set("SoMStealthExempt", flag ? 1 : 0);
				}
			}
		}

		private void Awake()
		{
			if (ValkyriesCargoCompat.ExcludeIfIngvar(((Component)this).gameObject, "DvergrGenetics"))
			{
				return;
			}
			m_nview = ((Component)this).GetComponent<ZNetView>();
			m_humanoid = ((Component)this).GetComponent<Humanoid>();
			if (!((Object)(object)m_nview == (Object)null) && m_nview.IsValid() && m_nview.IsOwner())
			{
				RefreshStealthExemption(((Component)this).GetComponent<Character>(), m_nview);
				if (m_nview.GetZDO().GetInt("dvergr_gender", -1) == -1)
				{
					Gender gender = ((!(Random.value > 0.5f)) ? Gender.Female : Gender.Male);
					m_nview.GetZDO().Set("dvergr_gender", (int)gender);
					ConfigManager.LogBreeding($"[Genetics] {((Object)((Component)this).gameObject).name} randomly assigned gender: {gender}");
				}
			}
		}

		private void LateUpdate()
		{
			if ((Object)(object)m_nview == (Object)null || !m_nview.IsValid() || !((Object)(object)Player.m_localPlayer != (Object)null) || !((Object)(object)((Humanoid)Player.m_localPlayer).GetHoverObject() != (Object)null))
			{
				return;
			}
			GameObject hoverObject = ((Humanoid)Player.m_localPlayer).GetHoverObject();
			if ((!((Object)(object)hoverObject == (Object)(object)((Component)this).gameObject) && !hoverObject.transform.IsChildOf(((Component)this).transform)) || !((Object)(object)Hud.instance != (Object)null) || !((Object)(object)Hud.instance.m_hoverName != (Object)null))
			{
				return;
			}
			int num = m_nview.GetZDO().GetInt("dvergr_gender", -1);
			if (num != -1)
			{
				Gender gender = (Gender)num;
				string text = $"\n({gender})";
				DvergrProcreation component = ((Component)this).GetComponent<DvergrProcreation>();
				if ((Object)(object)component != (Object)null && component.IsPregnant())
				{
					int pregnancyTimeLeft = component.GetPregnancyTimeLeft();
					string text2 = ((pregnancyTimeLeft > 0) ? $"{pregnancyTimeLeft}s" : "Imminent");
					text = text + "\n<color=yellow>Pregnant: " + text2 + "</color>";
				}
				DvergrTameable component2 = ((Component)this).GetComponent<DvergrTameable>();
				if ((Object)(object)component2 != (Object)null && ((Tameable)component2).IsHungry())
				{
					text += "\n<color=red>Hungry</color>";
				}
				TextMeshProUGUI hoverName = Hud.instance.m_hoverName;
				((TMP_Text)hoverName).text = ((TMP_Text)hoverName).text + text;
			}
		}

		public Gender GetGender()
		{
			if ((Object)(object)m_nview == (Object)null || !m_nview.IsValid())
			{
				return Gender.Male;
			}
			return (Gender)m_nview.GetZDO().GetInt("dvergr_gender", 0);
		}
	}
	[HarmonyPatch(typeof(Character), "SetTamed")]
	public static class Patch_Character_SetTamed_StealthExemption
	{
		[HarmonyPostfix]
		public static void Postfix(Character __instance, bool tamed)
		{
			if (!((Object)(object)__instance == (Object)null) && !((Object)(object)((Component)__instance).GetComponent<DvergrGenetics>() == (Object)null))
			{
				ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
				if (!((Object)(object)component == (Object)null) && component.IsValid() && component.IsOwner() && !ValkyriesCargoCompat.IsIngvar(component))
				{
					DvergrGenetics.RefreshStealthExemption(__instance, component, tamed);
				}
			}
		}
	}
	public class DvergrHoT : MonoBehaviour
	{
		private Character m_target;

		private int m_ticksRemaining = 5;

		private float m_healAmountPerTick = 10f;

		private float m_tickTimer;

		public void Setup(int clericLevel)
		{
			m_target = ((Component)this).GetComponent<Character>();
			m_healAmountPerTick = 5f + 5f * (float)clericLevel;
			m_ticksRemaining = 5;
			m_tickTimer = 1f;
			object[] array = new object[4];
			Character target = m_target;
			array[0] = ((target != null) ? ((Object)target).name : null);
			array[1] = clericLevel;
			array[2] = m_healAmountPerTick;
			array[3] = m_ticksRemaining;
			ConfigManager.LogAI(string.Format("[Cleric] Applied HoT to {0} (Lvl {1}): {2} HP/s for {3}s", array));
		}

		private void FixedUpdate()
		{
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)m_target == (Object)null || m_target.IsDead())
			{
				Object.Destroy((Object)(object)this);
				return;
			}
			m_tickTimer += Time.fixedDeltaTime;
			if (!(m_tickTimer >= 1f))
			{
				return;
			}
			m_tickTimer = 0f;
			m_ticksRemaining--;
			m_target.Heal(m_healAmountPerTick, true);
			if ((Object)(object)ZNetScene.instance != (Object)null)
			{
				GameObject prefab = ZNetScene.instance.GetPrefab("vfx_Potion_health_medium");
				if ((Object)(object)prefab != (Object)null)
				{
					Object.Instantiate<GameObject>(prefab, ((Component)m_target).transform.position, Quaternion.identity);
				}
			}
			if (m_ticksRemaining <= 0)
			{
				Object.Destroy((Object)(object)this);
			}
		}
	}
	public class PlayerLifetimeStats
	{
		[JsonProperty("player_id")]
		public string PlayerId;

		[JsonProperty("name")]
		public string Name = "Unknown";

		[JsonProperty("tamed")]
		public long Tamed;

		[JsonProperty("births")]
		public long Births;

		[JsonProperty("deaths")]
		public long Deaths;

		[JsonProperty("contracts_consumed")]
		public long ContractsConsumed;

		[JsonProperty("elite_pulls")]
		public long ElitePulls;
	}
	public class DvergrLifetimeStats
	{
		[JsonProperty("world_uid")]
		public long WorldUid;

		[JsonProperty("world_name")]
		public string WorldName = "";

		[JsonProperty("tracking_since")]
		public string TrackingSince;

		[JsonProperty("tamed")]
		public long Tamed;

		[JsonProperty("births")]
		public long Births;

		[JsonProperty("births_special_combo")]
		public long BirthsSpecialCombo;

		[JsonProperty("deaths")]
		public long Deaths;

		[JsonProperty("star_ups_at_birth")]
		public long StarUpsAtBirth;

		[JsonProperty("highest_level_bred")]
		public int HighestLevelBred;

		[JsonProperty("contracts_consumed")]
		public long ContractsConsumed;

		[JsonProperty("births_by_class")]
		public Dictionary<string, long> BirthsByClass = new Dictionary<string, long>();

		[JsonProperty("deaths_by_class")]
		public Dictionary<string, long> DeathsByClass = new Dictionary<string, long>();

		[JsonProperty("gacha_pulls_by_class")]
		public Dictionary<string, long> GachaPullsByClass = new Dictionary<string, long>();

		[JsonProperty("players")]
		public Dictionary<string, PlayerLifetimeStats> Players = new Dictionary<string, PlayerLifetimeStats>();

		public static DvergrLifetimeStats CreateNew(long worldUid, string worldName, DateTime nowUtc)
		{
			return new DvergrLifetimeStats
			{
				WorldUid = worldUid,
				WorldName = (worldName ?? ""),
				TrackingSince = DvergrStatsSchema.FormatTimestamp(nowUtc)
			};
		}

		public void NormalizeAfterLoad()
		{
			if (WorldName == null)
			{
				WorldName = "";
			}
			if (BirthsByClass == null)
			{
				BirthsByClass = new Dictionary<string, long>();
			}
			if (DeathsByClass == null)
			{
				DeathsByClass = new Dictionary<string, long>();
			}
			if (GachaPullsByClass == null)
			{
				GachaPullsByClass = new Dictionary<string, long>();
			}
			if (Players == null)
			{
				Players = new Dictionary<string, PlayerLifetimeStats>();
			}
			foreach (KeyValuePair<string, PlayerLifetimeStats> player in Players)
			{
				if (player.Value != null)
				{
					if (string.IsNullOrEmpty(player.Value.PlayerId))
					{
						player.Value.PlayerId = player.Key;
					}
					if (string.IsNullOrEmpty(player.Value.Name))
					{
						player.Value.Name = "Unknown";
					}
				}
			}
		}

		public PlayerLifetimeStats GetOrCreatePlayer(string playerId, string name)
		{
			if (string.IsNullOrEmpty(playerId) || playerId == "0")
			{
				return null;
			}
			if (!Players.TryGetValue(playerId, out var value) || value == null)
			{
				value = new PlayerLifetimeStats
				{
					PlayerId = playerId,
					Name = (string.IsNullOrEmpty(name) ? "Unknown" : name)
				};
				Players[playerId] = value;
			}
			else if (!string.IsNullOrEmpty(name) && name != "Unknown")
			{
				value.Name = name;
			}
			return value;
		}

		public static void Bump(Dictionary<string, long> dict, string key, long amount = 1L)
		{
			if (dict != null && !string.IsNullOrEmpty(key))
			{
				dict.TryGetValue(key, out var value);
				dict[key] = value + amount;
			}
		}

		public string ToJson()
		{
			return JsonConvert.SerializeObject((object)this, (Formatting)1);
		}

		public static DvergrLifetimeStats FromJson(string json)
		{
			DvergrLifetimeStats dvergrLifetimeStats = JsonConvert.DeserializeObject<DvergrLifetimeStats>(json);
			if (dvergrLifetimeStats != null)
			{
				dvergrLifetimeStats.NormalizeAfterLoad();
				return dvergrLifetimeStats;
			}
			return dvergrLifetimeStats;
		}
	}
	public class DvergrProcreation : MonoBehaviour
	{
		private ZNetView m_nview;

		private Character m_character;

		private BaseAI m_baseAI;

		private DvergrGenetics m_genetics;

		private Coroutine m_birthingCoroutine;

		public EffectList m_birthEffects = new EffectList();

		public float m_spawnOffset = 1f;

		private void Awake()
		{
			if (!ValkyriesCargoCompat.ExcludeIfIngvar(((Component)this).gameObject, "DvergrProcreation"))
			{
				m_nview = ((Component)this).GetComponent<ZNetView>();
				m_character = ((Component)this).GetComponent<Character>();
				m_baseAI = ((Component)this).GetComponent<BaseAI>();
				m_genetics = ((Component)this).GetComponent<DvergrGenetics>();
				if (!((Object)(object)m_nview == (Object)null) && m_nview.IsValid())
				{
					((MonoBehaviour)this).InvokeRepeating("Procreate", Random.Range(0f, ConfigManager.UpdateInterval.Value), ConfigManager.UpdateInterval.Value);
				}
			}
		}

		public bool IsPregnant()
		{
			if ((Object)(object)m_nview != (Object)null && m_nview.IsValid())
			{
				return m_nview.GetZDO().GetLong("pregnant", 0L) != 0;
			}
			return false;
		}

		public int GetPregnancyTimeLeft()
		{
			if (!IsPregnant())
			{
				return 0;
			}
			long num = m_nview.GetZDO().GetLong("pregnant", 0L);
			if (num == 0L)
			{
				return 0;
			}
			DateTime dateTime = new DateTime(num);
			double totalSeconds = (ZNet.instance.GetTime() - dateTime).TotalSeconds;
			int num2 = Mathf.CeilToInt((float)((double)ConfigManager.PregnancyDuration.Value - totalSeconds));
			return Mathf.Max(0, num2);
		}

		private void MakePregnant(string partnerPrefab, int partnerLevel)
		{
			if (!((Object)(object)m_nview == (Object)null) && m_nview.IsValid())
			{
				m_nview.GetZDO().Set("pregnant", ZNet.instance.GetTime().Ticks);
				m_nview.GetZDO().Set("dvergr_partner_prefab", partnerPrefab);
				m_nview.GetZDO().Set("dvergr_partner_level", partnerLevel);
			}
		}

		private void ResetPregnancy()
		{
			if (!((Object)(object)m_nview == (Object)null) && m_nview.IsValid())
			{
				m_nview.GetZDO().Set("pregnant", 0L);
				m_nview.GetZDO().Set("dvergr_partner_prefab", "");
			}
		}

		private bool ReadyForProcreation()
		{
			if (IsPregnant())
			{
				return false;
			}
			if ((Object)(object)m_character == (Object)null || !m_character.IsTamed())
			{
				return false;
			}
			if ((Object)(object)m_baseAI != (Object)null && m_baseAI.IsAlerted())
			{
				return false;
			}
			DvergrTameable component = ((Component)this).GetComponent<DvergrTameable>();
			if ((Object)(object)component != (Object)null && ((Tameable)component).IsHungry())
			{
				return false;
			}
			return true;
		}

		private void Procreate()
		{
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			if (!m_nview.IsValid() || !m_nview.IsOwner())
			{
				return;
			}
			if (IsPregnant())
			{
				long num = m_nview.GetZDO().GetLong("pregnant", 0L);
				if (num != 0L)
				{
					DateTime dateTime = new DateTime(num);
					if ((ZNet.instance.GetTime() - dateTime).TotalSeconds > (double)ConfigManager.PregnancyDuration.Value && m_birthingCoroutine == null)
					{
						m_birthingCoroutine = ((MonoBehaviour)this).StartCoroutine(BirthingProcess());
					}
				}
			}
			else
			{
				if (!ReadyForProcreation())
				{
					return;
				}
				string text = ((Object)((Component)this).gameObject).name.Replace("(Clone)", "").Trim();
				int num2 = 0;
				Collider[] array = Physics.OverlapSphere(((Component)this).transform.position, 10f, LayerMask.GetMask(new string[3] { "character", "character_net", "character_ghost" }));
				for (int i = 0; i < array.Length; i++)
				{
					Character component = ((Component)array[i]).GetComponent<Character>();
					if ((Object)(object)component != (Object)null && component.IsTamed() && (Object)(object)((Component)component).GetComponent<DvergrGenetics>() != (Object)null)
					{
						num2++;
					}
				}
				if (num2 >= ConfigManager.BreedingLimit.Value)
				{
					ConfigManager.LogBreeding($"[Procreation] {text} halted breeding: Population limit reached ({num2}/{ConfigManager.BreedingLimit.Value}) tamed Dvergrs within 10m.");
					return;
				}
				array = Physics.OverlapSphere(((Component)this).transform.position, ConfigManager.PartnerCheckRange.Value, LayerMask.GetMask(new string[3] { "character", "character_net", "character_ghost" }));
				for (int i = 0; i < array.Length; i++)
				{
					Character component2 = ((Component)array[i]).GetComponent<Character>();
					if (!((Object)(object)component2 != (Object)null) || !((Object)(object)((Component)component2).gameObject != (Object)(object)((Component)this).gameObject))
					{
						continue;
					}
					DvergrGenetics component3 = ((Component)component2).GetComponent<DvergrGenetics>();
					if (!((Object)(object)component3 != (Object)null) || !((Object)(object)m_genetics != (Object)null) || m_genetics.GetGender() == component3.GetGender())
					{
						continue;
					}
					DvergrProcreation component4 = ((Component)component2).GetComponent<DvergrProcreation>();
					if (!((Object)(object)component4 != (Object)null) || !component4.ReadyForProcreation())
					{
						continue;
					}
					ConfigManager.LogBreeding($"[Procreation] {text} ({m_genetics.GetGender()}) found compatible partner {((Object)((Component)component2).gameObject).name} ({component3.GetGender()})");
					if (Random.value <= ConfigManager.PregnancyChance.Value)
					{
						string text2 = ((Object)((Component)component2).gameObject).name.Replace("(Clone)", "").Trim();
						int level = component2.GetLevel();
						if (m_genetics.GetGender() == DvergrGenetics.Gender.Female)
						{
							MakePregnant(text2, level);
							component4.ResetPregnancy();
							ConfigManager.LogBreeding("[Procreation] " + text + " is now PREGNANT!");
						}
						else
						{
							string partnerPrefab = ((Object)((Component)this).gameObject).name.Replace("(Clone)", "").Trim();
							int level2 = m_character.GetLevel();
							component4.MakePregnant(partnerPrefab, level2);
							ResetPregnancy();
							ConfigManager.LogBreeding("[Procreation] Partner " + text2 + " is now PREGNANT!");
						}
					}
					else
					{
						ConfigManager.LogBreeding("[Procreation] " + text + " failed pregnancy chance roll.");
					}
					break;
				}
			}
		}

		private IEnumerator BirthingProcess()
		{
			string text = m_nview.GetZDO().GetString("dvergr_partner_prefab", "");
			int num = m_nview.GetZDO().GetInt("dvergr_partner_level", 1);
			ResetPregnancy();
			string text2 = ((Object)((Component)this).gameObject).name.Replace("(Clone)", "").Trim();
			bool wasSpecialCombo;
			string text3 = DetermineOffspring(text2, text, out wasSpecialCombo);
			ConfigManager.LogBreeding($"[Procreation] BIRTHING PROCESS START: ParentA={text2}(Lvl {m_character.GetLevel()}), ParentB={text}(Lvl {num}) -> Outcome={text3}");
			GameObject prefab = PrefabManager.Instance.GetPrefab(text3);
			if ((Object)(object)prefab != (Object)null)
			{
				Vector3 val = ((Component)this).transform.position + ((Component)this).transform.forward * m_spawnOffset;
				GameObject val2 = Object.Instantiate<GameObject>(prefab, val, Quaternion.LookRotation(((Component)this).transform.forward));
				Character component = val2.GetComponent<Character>();
				if ((Object)(object)component != (Object)null && (Object)(object)m_character != (Object)null)
				{
					long playerId = 0L;
					string playerName = "Unknown";
					ZNetView component2 = val2.GetComponent<ZNetView>();
					if ((Object)(object)component2 != (Object)null && component2.IsValid() && (Object)(object)m_nview != (Object)null && m_nview.IsValid())
					{
						ZDO zDO = component2.GetZDO();
						zDO.Set("dvergr_tame_counted", 1);
						if (DvergrStatsExporter.TryGetOwner(m_nview.GetZDO(), out playerId, out playerName))
						{
							DvergrStatsExporter.StampOwner(zDO, playerId, playerName);
						}
					}
					component.SetTamed(m_character.IsTamed());
					int num2 = Mathf.Max(m_character.GetLevel(), num);
					bool starUp = false;
					if ((float)Random.Range(0, 100) < ConfigManager.LevelUpChance.Value)
					{
						num2++;
						starUp = true;
						ConfigManager.LogBreeding("[Procreation] " + text3 + " offspring triggered LEVEL UP CHANCE! Level increased.");
					}
					num2 = Mathf.Clamp(num2, 1, ConfigManager.MaxBreedingLevel.Value);
					component.SetLevel(num2);
					ConfigManager.LogBreeding($"[Procreation] Birthed {text3} at Level {num2} (Max {ConfigManager.MaxBreedingLevel.Value})");
					DvergrStatsStore.ReportBirth(DvergrStatsSchema.ClassifyPrefabName(text3), playerId, playerName, wasSpecialCombo, starUp, num2);
				}
				if (m_birthEffects != null && m_birthEffects.HasEffects())
				{
					m_birthEffects.Create(((Component)this).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID));
				}
			}
			m_birthingCoroutine = null;
			yield break;
		}

		private string DetermineOffspring(string parentA, string parentB, out bool wasSpecialCombo)
		{
			bool flag = parentA.Contains("Fire") || parentB.Contains("Fire");
			bool flag2 = parentA.Contains("Ice") || parentB.Contains("Ice");
			bool flag3 = parentA.Contains("Support") || parentB.Contains("Support");
			bool flag4 = parentA.Contains("Rogue") || parentB.Contains("Rogue");
			bool flag5 = parentA.Contains("Warrior") || parentB.Contains("Warrior");
			bool flag6 = parentA.Contains("Cleric") || parentB.Contains("Cleric");
			wasSpecialCombo = true;
			if ((float)Random.Range(0, 100) < ConfigManager.SpecialComboChance.Value)
			{
				if (flag4 && !flag && !flag2 && !flag3 && !flag5 && !flag6)
				{
					return "AllyDvergrWarrior";
				}
				if (flag3 && !flag4 && !flag && !flag2 && !flag5 && !flag6)
				{
					return "AllyDvergrCleric";
				}
				if (flag && flag2)
				{
					return "AllyDvergrMageElemental";
				}
				if (flag4 && flag && !flag2)
				{
					return "AllyDvergrSpellswordFire";
				}
				if (flag4 && flag2 && !flag)
				{
					return "AllyDvergrSpellswordIce";
				}
				if (flag5 && flag5)
				{
					return "AllyDvergrBerserker";
				}
			}
			wasSpecialCombo = false;
			if (!(Random.value > 0.5f))
			{
				return parentB;
			}
			return parentA;
		}
	}
	public class DvergrStatsExporter : MonoBehaviour
	{
		public const string OwnerIdKey = "dvergr_owner_id";

		public const string OwnerNameKey = "dvergr_owner_name";

		public const string TameCountedKey = "dvergr_tame_counted";

		private const float OwnerSearchRadius = 15f;

		private const int ZdosPerFrame = 2000;

		private DateTime m_sessionStart;

		private string m_exportDir;

		private Dictionary<int, string> m_classByPrefabHash;

		private int m_prefabMapBuiltFromCount = -1;

		private string m_lastStatus;

		private void Awake()
		{
			m_sessionStart = DateTime.UtcNow;
			m_exportDir = Path.Combine(Paths.ConfigPath, "DvergrAllies");
			Logger.LogInfo((object)($"[StatsExport] Armed. Enable Stats Export = {ConfigManager.EnableStatsExport.Value}; " + "writing to " + m_exportDir + " once this process is confirmed to be the server."));
			((MonoBehaviour)this).StartCoroutine(ExportLoop());
		}

		private void LogStatus(string status)
		{
			if (!(status == m_lastStatus))
			{
				m_lastStatus = status;
				Logger.LogInfo((object)("[StatsExport] " + status));
			}
		}

		private IEnumerator ExportLoop()
		{
			while (true)
			{
				float interval = Mathf.Max(30f, ConfigManager.StatsExportInterval.Value);
				yield return (object)new WaitForSeconds(interval);
				if (!ConfigManager.EnableStatsExport.Value)
				{
					LogStatus("Idle: 'Enable Stats Export' is off, so no files are being written.");
					continue;
				}
				bool flag = false;
				string text = null;
				try
				{
					DvergrStatsStore.EnsureReady();
					flag = DvergrStatsStore.IsServer();
				}
				catch (Exception ex)
				{
					text = ex.ToString();
				}
				if (text != null)
				{
					Logger.LogWarning((object)$"[StatsExport] Could not prepare the counter store this cycle; retrying in {interval:0}s: {text}");
					continue;
				}
				if (!flag)
				{
					LogStatus("Waiting: no world is loaded yet, or this process is not the server. Nothing is written until it is.");
					continue;
				}
				LogStatus($"Running: exporting every {interval:0}s to {m_exportDir}.");
				yield return ((MonoBehaviour)this).StartCoroutine(CensusAndWrite());
			}
		}

		private void EnsurePrefabMap()
		{
			ZNetScene instance = ZNetScene.instance;
			if ((Object)(object)instance == (Object)null || instance.m_prefabs == null || (m_classByPrefabHash != null && m_prefabMapBuiltFromCount == instance.m_prefabs.Count))
			{
				return;
			}
			Dictionary<int, string> dictionary = new Dictionary<int, string>();
			foreach (GameObject prefab in instance.m_prefabs)
			{
				if (!((Object)(object)prefab == (Object)null) && ((Object)prefab).name.IndexOf("dverg", StringComparison.OrdinalIgnoreCase) >= 0 && !((Object)(object)prefab.GetComponent<Humanoid>() == (Object)null))
				{
					dictionary[StringExtensionMethods.GetStableHashCode(((Object)prefab).name)] = DvergrStatsSchema.ClassifyPrefabName(((Object)prefab).name);
				}
			}
			m_classByPrefabHash = dictionary;
			m_prefabMapBuiltFromCount = instance.m_prefabs.Count;
		}

		private IEnumerator CensusAndWrite()
		{
			EnsurePrefabMap();
			string text = null;
			if (ConfigManager.ForceCensusUnavailable.Value)
			{
				text = "forced by the 'Force Census Unavailable' config switch - this is a deliberate test of the failure path, not a real fault";
			}
			else if ((Object)(object)ZNetScene.instance == (Object)null)
			{
				text = "world scene is not loaded yet";
			}
			else if (m_classByPrefabHash == null || m_classByPrefabHash.Count == 0)
			{
				text = "no Dvergr creature prefabs are registered - prefab setup has not run, or another mod replaced them";
			}
			else if (ZDOMan.instance == null)
			{
				text = "the object database is not available";
			}
			if (text != null)
			{
				Logger.LogWarning((object)("[StatsExport] Census unavailable: " + text + ". Writing exports with census fields nulled."));
				Write(new DvergrWorldSnapshot(), new List<DvergrPlayerSnapshot>(), text);
				yield break;
			}
			DvergrWorldSnapshot world = new DvergrWorldSnapshot();
			Dictionary<string, DvergrPlayerSnapshot> byPlayer = new Dictionary<string, DvergrPlayerSnapshot>();
			List<ZDO> zdos;
			try
			{
				zdos = ZDOMan.instance.m_objectsByID.Values.ToList();
			}
			catch (Exception ex)
			{
				zdos = null;
				Logger.LogWarning((object)("[StatsExport] Could not snapshot the object database this cycle: " + ex.Message));
			}
			if (zdos == null)
			{
				Write(new DvergrWorldSnapshot(), new List<DvergrPlayerSnapshot>(), "the object database could not be read this cycle");
				yield break;
			}
			for (int i = 0; i < zdos.Count; i++)
			{
				if (i > 0 && i % 2000 == 0)
				{
					yield return null;
				}
				ZDO val = zdos[i];
				if (val == null || !m_classByPrefabHash.TryGetValue(val.GetPrefab(), out var value) || !val.GetBool(ZDOVars.s_tamed, false) || ValkyriesCargoCompat.IsIngvar(val))
				{
					continue;
				}
				bool flag = val.GetLong("pregnant", 0L) != 0;
				bool flag2 = IsHungry(val);
				int num = val.GetInt("dvergr_gender", -1);
				world.AliveTamed++;
				if (flag)
				{
					world.Pregnant++;
				}
				if (flag2)
				{
					world.Hungry++;
				}
				switch (num)
				{
				case 0:
					world.Male++;
					break;
				case 1:
					world.Female++;
					break;
				default:
					world.GenderUnknown++;
					break;
				}
				Bump(world.ClassCounts, value);
				long num2 = val.GetLong("dvergr_owner_id", 0L);
				if (num2 != 0L)
				{
					string text2 = num2.ToString();
					if (!byPlayer.TryGetValue(text2, out var value2))
					{
						value2 = (byPlayer[text2] = new DvergrPlayerSnapshot
						{
							PlayerId = text2,
							Name = val.GetString("dvergr_owner_name", "Unknown")
						});
					}
					value2.AliveTamed++;
					if (flag)
					{
						value2.PregnantOwned++;
					}
					if (flag2)
					{
						value2.HungryOwned++;
					}
					Bump(value2.ClassCounts, value);
				}
			}
			Write(world, byPlayer.Values, null);
		}

		private static bool IsHungry(ZDO zdo)
		{
			ZNetView val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.FindInstance(zdo) : null);
			if ((Object)(object)val != (Object)null)
			{
				Tameable component = ((Component)val).GetComponent<Tameable>();
				if ((Object)(object)component != (Object)null)
				{
					return component.IsHungry();
				}
			}
			long num = zdo.GetLong(ZDOVars.s_tameLastFeeding, 0L);
			if (num <= 0)
			{
				return true;
			}
			try
			{
				return (ZNet.instance.GetTime() - new DateTime(num)).TotalSeconds > (double)ConfigManager.FedDuration.Value;
			}
			catch (ArgumentOutOfRangeException)
			{
				return true;
			}
		}

		private void Write(DvergrWorldSnapshot world, IEnumerable<DvergrPlayerSnapshot> players, string censusUnavailableReason)
		{
			try
			{
				string source = "DvergrAllies 1.0.9";
				DateTime utcNow = DateTime.UtcNow;
				DvergrLifetimeStats current = DvergrStatsStore.Current;
				string content = DvergrStatsSchema.BuildWorldJson(world, current, source, utcNow, m_sessionStart, Mathf.RoundToInt(ConfigManager.StatsExportInterval.Value), censusUnavailableReason);
				string content2 = DvergrStatsSchema.BuildPlayersJson(players, current, source, utcNow, m_sessionStart, censusUnavailableReason);
				Directory.CreateDirectory(m_exportDir);
				DvergrStatsStore.WriteAtomic(Path.Combine(m_exportDir, "barrkbot_dvergr_world.json"), content);
				DvergrStatsStore.WriteAtomic(Path.Combine(m_exportDir, "barrkbot_dvergr_players.json"), content2);
				DvergrStatsStore.Save();
				ConfigManager.LogDebug($"[StatsExport] {world.AliveTamed} alive tamed across the world; " + $"lifetime {current?.Tamed ?? 0} tamed / {current?.Deaths ?? 0} deaths.");
			}
			catch (Exception arg)
			{
				Logger.LogWarning((object)$"[StatsExport] Export failed, skipping this cycle: {arg}");
			}
		}

		private static void Bump(Dictionary<string, int> dict, string key)
		{
			dict.TryGetValue(key, out var value);
			dict[key] = value + 1;
		}

		public static void StampOwner(ZDO zdo, long playerId, string playerName)
		{
			if (zdo != null && playerId != 0L)
			{
				zdo.Set("dvergr_owner_id", playerId);
				zdo.Set("dvergr_owner_name", string.IsNullOrEmpty(playerName) ? "Unknown" : playerName);
			}
		}

		public static bool TryGetOwner(ZDO zdo, out long playerId, out string playerName)
		{
			playerId = 0L;
			playerName = "Unknown";
			if (zdo == null)
			{
				return false;
			}
			playerId = zdo.GetLong("dvergr_owner_id", 0L);
			if (playerId == 0L)
			{
				return false;
			}
			playerName = zdo.GetString("dvergr_owner_name", "Unknown");
			return true;
		}

		public static void AssignOwnerOnTame(Character character, ZNetView nview)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			ZDO zDO = nview.GetZDO();
			if (zDO.GetLong("dvergr_owner_id", 0L) != 0L)
			{
				return;
			}
			Player val = null;
			float num = 15f;
			foreach (Player allPlayer in Player.GetAllPlayers())
			{
				if (!((Object)(object)allPlayer == (Object)null))
				{
					float num2 = Vector3.Distance(((Component)allPlayer).transform.position, ((Component)character).transform.position);
					if (num2 <= num)
					{
						val = allPlayer;
						num = num2;
					}
				}
			}
			if (!((Object)(object)val == (Object)null))
			{
				StampOwner(zDO, val.GetPlayerID(), val.GetPlayerName());
			}
		}
	}
	[HarmonyPatch(typeof(Character), "SetTamed")]
	public static class Patch_Character_SetTamed_OwnerTracking
	{
		[HarmonyPostfix]
		public static void Postfix(Character __instance, bool tamed)
		{
			if (!tamed || (Object)(object)__instance == (Object)null || (Object)(object)((Component)__instance).GetComponent<DvergrGenetics>() == (Object)null)
			{
				return;
			}
			ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
			if (!((Object)(object)component == (Object)null) && component.IsValid() && component.IsOwner() && !ValkyriesCargoCompat.IsIngvar(component))
			{
				DvergrStatsExporter.AssignOwnerOnTame(__instance, component);
				ZDO zDO = component.GetZDO();
				if (zDO.GetInt("dvergr_tame_counted", 0) == 0)
				{
					zDO.Set("dvergr_tame_counted", 1);
					DvergrStatsExporter.TryGetOwner(zDO, out var playerId, out var playerName);
					DvergrStatsStore.ReportTame(DvergrStatsSchema.ClassifyPrefabName(((Object)((Component)__instance).gameObject).name.Replace("(Clone)", "").Trim()), playerId, playerName);
				}
			}
		}
	}
	[HarmonyPatch(typeof(Character), "OnDeath")]
	public static class Patch_Humanoid_OnDeath_Stats
	{
		[HarmonyPrefix]
		public static void Prefix(Character __instance)
		{
			if (!((Object)(object)__instance == (Object)null) && !((Object)(object)((Component)__instance).GetComponent<DvergrGenetics>() == (Object)null) && __instance.IsTamed())
			{
				ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
				if (!((Object)(object)component == (Object)null) && component.IsValid() && component.IsOwner() && !ValkyriesCargoCompat.IsIngvar(component))
				{
					DvergrStatsExporter.TryGetOwner(component.GetZDO(), out var playerId, out var playerName);
					DvergrStatsStore.ReportDeath(DvergrStatsSchema.ClassifyPrefabName(((Object)((Component)__instance).gameObject).name.Replace("(Clone)", "").Trim()), playerId, playerName);
				}
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "Shutdown")]
	public static class Patch_ZNet_Shutdown_SaveStats
	{
		[HarmonyPrefix]
		public static void Prefix()
		{
			DvergrStatsStore.Save(force: true);
		}
	}
	public class DvergrWorldSnapshot
	{
		public int AliveTamed;

		public int Pregnant;

		public int Hungry;

		public int Male;

		public int Female;

		public int GenderUnknown;

		public Dictionary<string, int> ClassCounts = new Dictionary<string, int>();
	}
	public class DvergrPlayerSnapshot
	{
		public string PlayerId;

		public string Name;

		public int AliveTamed;

		public int PregnantOwned;

		public int HungryOwned;

		public Dictionary<string, int> ClassCounts = new Dictionary<string, int>();
	}
	public static class DvergrStatsSchema
	{
		public const int SchemaVersion = 3;

		public static readonly string[] AllClassNames = new string[11]
		{
			"Rogue", "Mage", "Fire Mage", "Ice Mage", "Support Mage", "Elemental Mage", "Fire Spellsword", "Ice Spellsword", "Cleric", "Warrior",
			"Berserker"
		};

		private static readonly HashSet<string> BaseClasses = new HashSet<string> { "Rogue", "Mage", "Fire Mage", "Ice Mage", "Support Mage" };

		private const string CensusUnavailableNote = "The live census did NOT run this cycle, so every census field is null rather than a number - that means 'not measured', not 'none'. Do not report population, class counts or any alive_* figure from this file; say the count is unavailable and give the reason in 'status_detail'. The 'lifetime' block and every '*_alltime' field come from the counter file rather than the world scan and are still accurate.";

		private const string IncomparableNote = "Never subtract or compare a live count against a lifetime one. 'population', 'class_counts' and every 'alive_*' field count Dvergr existing right now; the 'lifetime' block and every '*_alltime' field are cumulative since 'tracking_since'. A player with 2 alive and 9 tamed_alltime has not lost 7 - use 'deaths_alltime' for losses.";

		public static bool IsEliteClass(string className)
		{
			if (!string.IsNullOrEmpty(className))
			{
				return !BaseClasses.Contains(className);
			}
			return false;
		}

		public static string ClassifyPrefabName(string prefabName)
		{
			if (string.IsNullOrEmpty(prefabName))
			{
				return "Rogue";
			}
			if (prefabName.Contains("Berserker"))
			{
				return "Berserker";
			}
			if (prefabName.Contains("SpellswordFire"))
			{
				return "Fire Spellsword";
			}
			if (prefabName.Contains("SpellswordIce"))
			{
				return "Ice Spellsword";
			}
			if (prefabName.Contains("MageElemental"))
			{
				return "Elemental Mage";
			}
			if (prefabName.Contains("Cleric"))
			{
				return "Cleric";
			}
			if (prefabName.Contains("Warrior"))
			{
				return "Warrior";
			}
			if (prefabName.Contains("MageFire"))
			{
				return "Fire Mage";
			}
			if (prefabName.Contains("MageIce"))
			{
				return "Ice Mage";
			}
			if (prefabName.Contains("MageSupport"))
			{
				return "Support Mage";
			}
			if (prefabName.Contains("Mage"))
			{
				return "Mage";
			}
			return "Rogue";
		}

		private static JObject FullClassCounts<T>(Dictionary<string, T> partial)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			JObject val = new JObject();
			string[] allClassNames = AllClassNames;
			foreach (string text in allClassNames)
			{
				T value = default(T);
				partial?.TryGetValue(text, out value);
				val[text] = JToken.op_Implicit(Convert.ToInt64(value));
			}
			return val;
		}

		public static string FormatTimestamp(DateTime dt)
		{
			return dt.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture);
		}

		private static JToken CensusValue(bool measured, int value)
		{
			if (!measured)
			{
				return (JToken)(object)JValue.CreateNull();
			}
			return JToken.op_Implicit(value);
		}

		public static string BuildWorldJson(DvergrWorldSnapshot snapshot, DvergrLifetimeStats lifetime, string source, DateTime generatedAt, DateTime sessionStartedAt, int exportIntervalSeconds, string censusUnavailableReason = null)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Expected O, but got Unknown
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: Unknown result type (might be due to invalid IL or missing references)
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: Expected O, but got Unknown
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0205: Unknown result type (might be due to invalid IL or missing references)
			//IL_0222: Unknown result type (might be due to invalid IL or missing references)
			//IL_023f: Unknown result type (might be due to invalid IL or missing references)
			//IL_025b: Unknown result type (might be due to invalid IL or missing references)
			//IL_027d: Expected O, but got Unknown
			//IL_027d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0299: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fc: Expected O, but got Unknown
			bool flag = censusUnavailableReason == null;
			JObject val = new JObject
			{
				["schema_version"] = JToken.op_Implicit(3),
				["generated_at"] = JToken.op_Implicit(FormatTimestamp(generatedAt)),
				["source"] = JToken.op_Implicit(source),
				["status"] = JToken.op_Implicit(flag ? "ok" : "census_unavailable"),
				["status_detail"] = JToken.op_Implicit(censusUnavailableReason),
				["session_started_at"] = JToken.op_Implicit(FormatTimestamp(sessionStartedAt)),
				["tracking_since"] = JToken.op_Implicit(lifetime?.TrackingSince),
				["world_name"] = JToken.op_Implicit((lifetime != null) ? lifetime.WorldName : ""),
				["intervals"] = (JToken)new JObject { ["write_seconds"] = JToken.op_Implicit(exportIntervalSeconds) },
				["population"] = (JToken)new JObject
				{
					["alive_tamed"] = CensusValue(flag, snapshot.AliveTamed),
					["pregnant"] = CensusValue(flag, snapshot.Pregnant),
					["hungry"] = CensusValue(flag, snapshot.Hungry),
					["male"] = CensusValue(flag, snapshot.Male),
					["female"] = CensusValue(flag, snapshot.Female),
					["gender_unknown"] = CensusValue(flag, snapshot.GenderUnknown)
				},
				["class_counts"] = (JToken)(flag ? ((object)FullClassCounts(snapshot.ClassCounts)) : ((object)JValue.CreateNull())),
				["lifetime"] = (JToken)new JObject
				{
					["tamed_alltime"] = JToken.op_Implicit(lifetime?.Tamed ?? 0),
					["births_alltime"] = JToken.op_Implicit(lifetime?.Births ?? 0),
					["births_special_combo"] = JToken.op_Implicit(lifetime?.BirthsSpecialCombo ?? 0),
					["deaths_alltime"] = JToken.op_Implicit(lifetime?.Deaths ?? 0),
					["star_ups_at_birth"] = JToken.op_Implicit(lifetime?.StarUpsAtBirth ?? 0),
					["highest_level_bred"] = JToken.op_Implicit(lifetime?.HighestLevelBred ?? 0),
					["contracts_consumed"] = JToken.op_Implicit(lifetime?.ContractsConsumed ?? 0)
				},
				["lifetime_births_by_class"] = (JToken)(object)FullClassCounts(lifetime?.BirthsByClass),
				["lifetime_deaths_by_class"] = (JToken)(object)FullClassCounts(lifetime?.DeathsByClass),
				["gacha_pulls_by_class"] = (JToken)(object)FullClassCounts(lifetime?.GachaPullsByClass),
				["notes"] = JToken.op_Implicit("The census reads the save database directly, so it includes Dvergr in zones no player has loaded. Only TAMED Dvergr are counted; wild Mistlands ones are ignored. 'gender_unknown' means it has never been near a player since spawning, so its gender was never rolled."),
				["counter_notes"] = JToken.op_Implicit("Never subtract or compare a live count against a lifetime one. 'population', 'class_counts' and every 'alive_*' field count Dvergr existing right now; the 'lifetime' block and every '*_alltime' field are cumulative since 'tracking_since'. A player with 2 alive and 9 tamed_alltime has not lost 7 - use 'deaths_alltime' for losses.")
			};
			if (!flag)
			{
				val["status_notes"] = JToken.op_Implicit("The live census did NOT run this cycle, so every census field is null rather than a number - that means 'not measured', not 'none'. Do not report population, class counts or any alive_* figure from this file; say the count is unavailable and give the reason in 'status_detail'. The 'lifetime' block and every '*_alltime' field come from the counter file rather than the world scan and are still accurate.");
			}
			return ((JToken)val).ToString((Formatting)1, Array.Empty<JsonConverter>());
		}

		public static string BuildPlayersJson(IEnumerable<DvergrPlayerSnapshot> livePlayers, DvergrLifetimeStats lifetime, string source, DateTime generatedAt, DateTime sessionStartedAt, string censusUnavailableReason = null)
		{
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Expected O, but got Unknown
			//IL_02cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_030c: Unknown result type (might be due to invalid IL or missing references)
			//IL_032b: Unknown result type (might be due to invalid IL or missing references)
			//IL_033d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0354: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0181: Unknown result type (might be due to invalid IL or missing references)
			//IL_0198: Unknown result type (might be due to invalid IL or missing references)
			//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01da: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0370: Unknown result type (might be due to invalid IL or missing references)
			//IL_0385: Unknown result type (might be due to invalid IL or missing references)
			//IL_039a: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a7: Expected O, but got Unknown
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			//IL_0230: Unknown result type (might be due to invalid IL or missing references)
			//IL_024f: Unknown result type (might be due to invalid IL or missing references)
			//IL_026e: Unknown result type (might be due to invalid IL or missing references)
			//IL_028d: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b3: Expected O, but got Unknown
			bool flag = censusUnavailableReason == null;
			Dictionary<string, DvergrPlayerSnapshot> dictionary = new Dictionary<string, DvergrPlayerSnapshot>();
			if (livePlayers != null)
			{
				foreach (DvergrPlayerSnapshot livePlayer in livePlayers)
				{
					if (livePlayer != null && !string.IsNullOrEmpty(livePlayer.PlayerId))
					{
						dictionary[livePlayer.PlayerId] = livePlayer;
					}
				}
			}
			if (lifetime?.Players != null)
			{
				foreach (KeyValuePair<string, PlayerLifetimeStats> player in lifetime.Players)
				{
					if (player.Value != null && !dictionary.ContainsKey(player.Key))
					{
						dictionary[player.Key] = new DvergrPlayerSnapshot
						{
							PlayerId = player.Key,
							Name = player.Value.Name
						};
					}
				}
			}
			JObject val = new JObject();
			foreach (KeyValuePair<string, DvergrPlayerSnapshot> item in dictionary)
			{
				DvergrPlayerSnapshot value = item.Value;
				PlayerLifetimeStats value2 = null;
				lifetime?.Players?.TryGetValue(item.Key, out value2);
				string text = ((value2 != null && !string.IsNullOrEmpty(value2.Name) && value2.Name != "Unknown") ? value2.Name : (string.IsNullOrEmpty(value.Name) ? "Unknown" : value.Name));
				val[item.Key] = (JToken)new JObject
				{
					["player_id"] = JToken.op_Implicit(item.Key),
					["name"] = JToken.op_Implicit(text),
					["alive_tamed"] = CensusValue(flag, value.AliveTamed),
					["pregnant_owned"] = CensusValue(flag, value.PregnantOwned),
					["hungry_owned"] = CensusValue(flag, value.HungryOwned),
					["tamed_alltime"] = JToken.op_Implicit(value2?.Tamed ?? 0),
					["births_alltime"] = JToken.op_Implicit(value2?.Births ?? 0),
					["deaths_alltime"] = JToken.op_Implicit(value2?.Deaths ?? 0),
					["contracts_consumed"] = JToken.op_Implicit(value2?.ContractsConsumed ?? 0),
					["elite_pulls"] = JToken.op_Implicit(value2?.ElitePulls ?? 0),
					["class_counts"] = (JToken)(flag ? ((object)FullClassCounts(value.ClassCounts)) : ((object)JValue.CreateNull()))
				};
			}
			JObject val2 = new JObject
			{
				["schema_version"] = JToken.op_Implicit(3),
				["generated_at"] = JToken.op_Implicit(FormatTimestamp(generatedAt)),
				["source"] = JToken.op_Implicit(source),
				["status"] = JToken.op_Implicit(flag ? "ok" : "census_unavailable"),
				["status_detail"] = JToken.op_Implicit(censusUnavailableReason),
				["session_started_at"] = JToken.op_Implicit(FormatTimestamp(sessionStartedAt)),
				["tracking_since"] = JToken.op_Implicit(lifetime?.TrackingSince),
				["notes"] = JToken.op_Implicit("A player appears here if they own a living Dvergr OR have recorded history on this world. Ownership is stamped at tame/summon and inherited by offspring at birth; Dvergr tamed before tracking existed belong to nobody and appear only in the world file's totals. 'elite_pulls' counts contracts that produced a bred-tier class."),
				["counter_notes"] = JToken.op_Implicit("Never subtract or compare a live count against a lifetime one. 'population', 'class_counts' and every 'alive_*' field count Dvergr existing right now; the 'lifetime' block and every '*_alltime' field are cumulative since 'tracking_since'. A player with 2 alive and 9 tamed_alltime has not lost 7 - use 'deaths_alltime' for losses."),
				["players"] = (JToken)(object)val
			};
			if (!flag)
			{
				val2["status_notes"] = JToken.op_Implicit("The live census did NOT run this cycle, so every census field is null rather than a number - that means 'not measured', not 'none'. Do not report population, class counts or any alive_* figure from this file; say the count is unavailable and give the reason in 'status_detail'. The 'lifetime' block and every '*_alltime' field come from the counter file rather than the world scan and are still accurate.");
			}
			return ((JToken)val2).ToString((Formatting)1, Array.Empty<JsonConverter>());
		}
	}
	public static class DvergrStatsStore
	{
		private const string RpcName = "DvergrAllies_StatEvent";

		public static DvergrLifetimeStats Current;

		private static ZRoutedRpc s_registeredOn;

		private static long s_loadedWorldUid;

		private static bool s_dirty;

		public static bool IsServer()
		{
			if ((Object)(object)ZNet.instance != (Object)null)
			{
				return ZNet.instance.IsServer();
			}
			return false;
		}

		public static void EnsureReady()
		{
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				return;
			}
			if (ZRoutedRpc.instance != null && s_registeredOn != ZRoutedRpc.instance)
			{
				ZRoutedRpc.instance.Register<string>("DvergrAllies_StatEvent", (Action<long, string>)RPC_StatEvent);
				s_registeredOn = ZRoutedRpc.instance;
			}
			if (IsServer())
			{
				long worldUID = ZNet.instance.GetWorldUID();
				if (Current == null || s_loadedWorldUid != worldUID)
				{
					Load(worldUID);
				}
			}
		}

		private static string StateFilePath(long worldUid)
		{
			return Path.Combine(Path.Combine(Paths.ConfigPath, "DvergrAllies"), $"dvergr_lifetime_{worldUid}.json");
		}

		private static void Load(long worldUid)
		{
			string text = StateFilePath(worldUid);
			string worldName = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetWorldName() : "");
			try
			{
				if (File.Exists(text))
				{
					DvergrLifetimeStats dvergrLifetimeStats = DvergrLifetimeStats.FromJson(File.ReadAllText(text));
					if (dvergrLifetimeStats != null && dvergrLifetimeStats.WorldUid == worldUid)
					{
						Current = dvergrLifetimeStats;
						s_loadedWorldUid = worldUid;
						Current.WorldName = worldName;
						ConfigManager.LogDebug($"[StatsStore] Loaded lifetime stats for world {worldUid}: {Current.Tamed} tamed, {Current.Births} births, {Current.Deaths} deaths.");
						return;
					}
					Logger.LogWarning((object)$"[StatsStore] {Path.GetFileName(text)} is for world {dvergrLifetimeStats?.WorldUid}, not {worldUid}. Starting fresh.");
				}
			}
			catch (Exception ex)
			{
				Logger.LogWarning((object)("[StatsStore] Could not read " + text + " (" + ex.Message + "); starting fresh."));
				TryBackupCorruptFile(text);
			}
			Current = DvergrLifetimeStats.CreateNew(worldUid, worldName, DateTime.UtcNow);
			s_loadedWorldUid = worldUid;
			s_dirty = true;
		}

		private static void TryBackupCorruptFile(string path)
		{
			try
			{
				if (File.Exists(path))
				{
					File.Move(path, path + ".corrupt");
				}
			}
			catch (Exception ex)
			{
				Logger.LogWarning((object)("[StatsStore] Could not preserve corrupt state file: " + ex.Message));
			}
		}

		public static void Save(bool force = false)
		{
			if (Current == null || (!s_dirty && !force))
			{
				return;
			}
			try
			{
				string path = StateFilePath(Current.WorldUid);
				Directory.CreateDirectory(Path.GetDirectoryName(path));
				WriteAtomic(path, Current.ToJson());
				s_dirty = false;
			}
			catch (Exception arg)
			{
				Logger.LogWarning((object)$"[StatsStore] Failed to save lifetime stats: {arg}");
			}
		}

		public static void WriteAtomic(string path, string content)
		{
			string text = path + ".tmp";
			File.WriteAllText(text, content);
			if (File.Exists(path))
			{
				File.Delete(path);
			}
			File.Move(text, path);
		}

		private static void Report(JObject payload)
		{
			if (ZRoutedRpc.instance != null)
			{
				if (IsServer())
				{
					Apply(payload);
					return;
				}
				ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "DvergrAllies_StatEvent", new object[1] { ((JToken)payload).ToString((Formatting)0, Array.Empty<JsonConverter>()) });
			}
		}

		public static void ReportTame(string className, long playerId, string playerName)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Expected O, but got Unknown
			Report(new JObject
			{
				["kind"] = JToken.op_Implicit("tame"),
				["cls"] = JToken.op_Implicit(className),
				["pid"] = JToken.op_Implicit(playerId.ToString()),
				["pname"] = JToken.op_Implicit(playerName ?? "")
			});
		}

		public static void ReportBirth(string className, long playerId, string playerName, bool specialCombo, bool starUp, int level)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Expected O, but got Unknown
			Report(new JObject
			{
				["kind"] = JToken.op_Implicit("birth"),
				["cls"] = JToken.op_Implicit(className),
				["pid"] = JToken.op_Implicit(playerId.ToString()),
				["pname"] = JToken.op_Implicit(playerName ?? ""),
				["combo"] = JToken.op_Implicit(specialCombo),
				["starup"] = JToken.op_Implicit(starUp),
				["level"] = JToken.op_Implicit(level)
			});
		}

		public static void ReportDeath(string className, long playerId, string playerName)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Expected O, but got Unknown
			Report(new JObject
			{
				["kind"] = JToken.op_Implicit("death"),
				["cls"] = JToken.op_Implicit(className),
				["pid"] = JToken.op_Implicit(playerId.ToString()),
				["pname"] = JToken.op_Implicit(playerName ?? "")
			});
		}

		public static void ReportContract(string pulledClass, long playerId, string playerName)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Expected O, but got Unknown
			Report(new JObject
			{
				["kind"] = JToken.op_Implicit("contract"),
				["cls"] = JToken.op_Implicit(pulledClass),
				["pid"] = JToken.op_Implicit(playerId.ToString()),
				["pname"] = JToken.op_Implicit(playerName ?? "")
			});
		}

		private static void RPC_StatEvent(long sender, string payload)
		{
			if (!IsServer() || string.IsNullOrEmpty(payload))
			{
				return;
			}
			try
			{
				Apply(JObject.Parse(payload));
			}
			catch (Exception ex)
			{
				ConfigManager.LogDebug($"[StatsStore] Ignored malformed stat event from {sender}: {ex.Message}");
			}
		}

		private static void Apply(JObject payload)
		{
			if (Current == null)
			{
				return;
			}
			string text = (string)payload["kind"];
			if (string.IsNullOrEmpty(text))
			{
				return;
			}
			string text2 = (string)payload["cls"];
			if (string.IsNullOrEmpty(text2))
			{
				text2 = "Rogue";
			}
			string playerId = (string)payload["pid"];
			string name = (string)payload["pname"];
			PlayerLifetimeStats orCreatePlayer = Current.GetOrCreatePlayer(playerId, name);
			switch (text)
			{
			default:
				return;
			case "tame":
				Current.Tamed++;
				if (orCreatePlayer != null)
				{
					orCreatePlayer.Tamed++;
				}
				break;
			case "birth":
				Current.Births++;
				DvergrLifetimeStats.Bump(Current.BirthsByClass, text2, 1L);
				if (orCreatePlayer != null)
				{
					orCreatePlayer.Births++;
				}
				if (payload["combo"] != null && (bool)payload["combo"])
				{
					Current.BirthsSpecialCombo++;
				}
				if (payload["starup"] != null && (bool)payload["starup"])
				{
					Current.StarUpsAtBirth++;
				}
				if (payload["level"] != null)
				{
					int num = (int)payload["level"];
					int num2 = ((ConfigManager.MaxBreedingLevel != null) ? ConfigManager.MaxBreedingLevel.Value : 3);
					if (num > Current.HighestLevelBred && num <= num2)
					{
						Current.HighestLevelBred = num;
					}
				}
				break;
			case "death":
				Current.Deaths++;
				DvergrLifetimeStats.Bump(Current.DeathsByClass, text2, 1L);
				if (orCreatePlayer != null)
				{
					orCreatePlayer.Deaths++;
				}
				break;
			case "contract":
				Current.ContractsConsumed++;
				DvergrLifetimeStats.Bump(Current.GachaPullsByClass, text2, 1L);
				if (orCreatePlayer != null)
				{
					orCreatePlayer.ContractsConsumed++;
					if (DvergrStatsSchema.IsEliteClass(text2))
					{
						orCreatePlayer.ElitePulls++;
					}
				}
				break;
			}
			s_dirty = true;
		}
	}
	public class DvergrTameable : Tameable, Hoverable
	{
		private Character _hoverCharacter;

		public string GetHoverText()
		{
			str