Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of DvergrAllies v1.0.9
plugins/DvergrAllies.dll
Decompiled a month ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.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