using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Linq.Expressions; using System.Net.Security; using System.Net.Sockets; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Authentication; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using EpicLoot; using EpicLoot.Abilities; using EpicLoot.Adventure; using EpicLoot.CraftingV2; using EpicLootAPI; using EpicLootLeslieAlphaTest.src; using EpicLootLeslieAlphaTest.src.MagicEffectss; using EpicLootLeslieAlphaTest.src.StatusEffects; using EpicLootLeslieAlphaTest.src.StatusEffects.VFX; using EpicLootLeslieAlphaTest.src.Utilities; using EpicLoot_UnityLib; using GUIFramework; using HarmonyLib; using JetBrains.Annotations; using Jotunn; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using PraetorisClient.CreatureOwnership; using PraetorisClient.ServerChestFeature; using Splatform; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.Networking; using UnityEngine.UI; using ValheimRcon; using ValheimRcon.Commands; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("PraetorisClient")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("PraetorisClient")] [assembly: AssemblyCopyright("Copyright © 2023")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("3A303D15-4E60-4110-8EDF-EDF38560FBE2")] [assembly: AssemblyFileVersion("0.1.55")] [assembly: TargetFramework(".NETFramework,Version=v4.8.1", FrameworkDisplayName = ".NET Framework 4.8.1")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.55.0")] [module: UnverifiableCode] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace EpicLootLeslieAlphaTest.src { [HarmonyPatch(typeof(EnchantingUIController), "BuildEnchantedRune")] internal class EnchantingUIControllerMod { [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown FieldInfo fieldInfo = AccessTools.Field(typeof(MagicItemEffectDefinitions), "AllDefinitions"); FieldInfo fieldInfo2 = AccessTools.Field(typeof(MagicItemEffect), "EffectValue"); CodeMatcher val = new CodeMatcher(instructions, (ILGenerator)null); val.MatchStartForward((CodeMatch[])(object)new CodeMatch[1] { new CodeMatch((OpCode?)OpCodes.Ldsfld, (object)fieldInfo, (string)null) }); int pos = val.Pos; val.MatchStartForward((CodeMatch[])(object)new CodeMatch[1] { new CodeMatch((OpCode?)OpCodes.Stfld, (object)fieldInfo2, (string)null) }); int pos2 = val.Pos; val.Start().Advance(pos).RemoveInstructions(pos2 - pos + 1); return val.InstructionEnumeration(); } [HarmonyPostfix] private static void Postfix(ref ItemData __result) { if (__result == null) { return; } MagicItem magicItem = ItemDataExtensions.GetMagicItem(__result); if (magicItem == null) { return; } foreach (MagicItemEffect effect in magicItem.Effects) { effect.EffectValue = (float)Math.Round(effect.EffectValue, 2); } ItemDataExtensions.SaveMagicItem(__result, magicItem); } } public static class EnchantingHelper { internal static bool AwaitingConfirmation; internal static float PendingDestroyChance; public static float RuneTooPowerfulEtchDestructionChance(ItemData item, ItemData rune) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) MagicItemEffect val = ItemDataExtensions.GetMagicItem(rune).Effects[0]; if (MagicItemEffectDefinitions.AllDefinitions[val.EffectType].ValuesPerRarity.GetValueDefForRarity(ItemDataExtensions.GetRarity(item)) != null) { float maxValue = MagicItemEffectDefinitions.AllDefinitions[val.EffectType].ValuesPerRarity.GetValueDefForRarity(ItemDataExtensions.GetRarity(item)).MaxValue; bool flag = val.EffectValue >= maxValue * 1.1f; float num = val.EffectValue / maxValue - 1f; Debug.LogWarning((object)$"[EpicLootAlpha] effectValue: {val.EffectValue}, maxDefault: {maxValue}, tooPowerful: {flag}"); if (flag) { return Mathf.Round(Mathf.Clamp(num * 100f, 10f, 90f) / 5f) * 5f; } } return 0f; } } [HarmonyPatch(typeof(EnchantingUIController), "RuneEnhanceItemAndReturnSuccess")] internal class RuneEnhanceItem_DestroyChance_Patch { [HarmonyPrefix] private static bool Prefix(ItemData item, ItemData rune, int enchantment, ref GameObject __result) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) MagicItemEffect val = ItemDataExtensions.GetMagicItem(rune).Effects[0]; if (MagicItemEffectDefinitions.AllDefinitions[val.EffectType].ValuesPerRarity.GetValueDefForRarity(ItemDataExtensions.GetRarity(item)) != null) { float num = EnchantingHelper.RuneTooPowerfulEtchDestructionChance(item, rune); if (num > 0f) { float num2 = Random.value * 100f; if (num > num2) { ((Humanoid)Player.m_localPlayer).UnequipItem(item, true); ((Humanoid)Player.m_localPlayer).GetInventory().RemoveItem(item); ((Humanoid)Player.m_localPlayer).GetInventory().RemoveItem(rune); ((Character)Player.m_localPlayer).Message((MessageType)2, $"You rolled {num2:F1} You are not worthy of the rune's power. The item and rune has been destroyed.", 0, (Sprite)null); __result = null; return false; } } } return true; } } public static class MagicEffects { public static void Init() { MagicItemEffectDefinition magicItemEffectDefinition = new MagicItemEffectDefinition("AncestralSlam", "Ancestral Slam", "Summons ancestral spirit to mirror your attacks for each enemy hit."); magicItemEffectDefinition.Requirements.AllowedItemTypes.Add("TwoHandedWeapon"); magicItemEffectDefinition.Requirements.AllowedSkillTypes.Add((SkillType)3); Helpers.Add(magicItemEffectDefinition.Requirements.AllowedRarities, ItemRarity.Epic, ItemRarity.Legendary, ItemRarity.Mythic); magicItemEffectDefinition.SelectionWeight = 1f; magicItemEffectDefinition.Register(); MagicItemEffectDefinition magicItemEffectDefinition2 = new MagicItemEffectDefinition("GlancingBlows", "Glancing Blows", "Increases your block value by 50%. Take addtional damage equal to 50% of all blocked or parried hits. This additional damage is only mitigate by player armor and is not reduced from player resistances."); magicItemEffectDefinition2.Requirements.ItemHasBlockPower = true; Helpers.Add(magicItemEffectDefinition2.Requirements.AllowedRarities, ItemRarity.Magic, ItemRarity.Rare, ItemRarity.Epic, ItemRarity.Legendary, ItemRarity.Mythic); magicItemEffectDefinition2.ValuesPerRarity.Magic.Set(1f, 1f); magicItemEffectDefinition2.ValuesPerRarity.Rare = new ValueDef(1f, 1f, 1f); magicItemEffectDefinition2.ValuesPerRarity.Epic = new ValueDef(1f, 1f, 1f); magicItemEffectDefinition2.ValuesPerRarity.Legendary = new ValueDef(1f, 1f, 1f); magicItemEffectDefinition2.ValuesPerRarity.Mythic = new ValueDef(1f, 1f, 1f); magicItemEffectDefinition2.SelectionWeight = 3f; magicItemEffectDefinition2.Register(); MagicItemEffectDefinition magicItemEffectDefinition3 = new MagicItemEffectDefinition("Retaliation", "Retaliation +{0}", "On block gain a stack of Relatliation. On Attack consumes a stack of Relataliation to increase the attack speed of your next attack string. All stacks of Retaliation expire after 5 seconds. Consuming or gaining a stack of Retaliation resets the expiration timer."); magicItemEffectDefinition3.Requirements.ItemHasBlockPower = true; Helpers.Add(magicItemEffectDefinition3.Requirements.AllowedRarities, ItemRarity.Magic, ItemRarity.Rare, ItemRarity.Epic, ItemRarity.Legendary, ItemRarity.Mythic); magicItemEffectDefinition3.ValuesPerRarity.Magic.Set(1f, 1f); magicItemEffectDefinition3.ValuesPerRarity.Rare = new ValueDef(2f, 2f, 1f); magicItemEffectDefinition3.ValuesPerRarity.Epic = new ValueDef(3f, 3f, 1f); magicItemEffectDefinition3.ValuesPerRarity.Legendary = new ValueDef(4f, 4f, 1f); magicItemEffectDefinition3.ValuesPerRarity.Mythic = new ValueDef(5f, 5f, 1f); magicItemEffectDefinition3.SelectionWeight = 5f; magicItemEffectDefinition3.Register(); MagicItemEffectDefinition magicItemEffectDefinition4 = new MagicItemEffectDefinition("Infusion", "Infusion +{0}%", "Cleanses certain status effects and adds X% of weapon damage as their respective type. Certain cleansed status effects grant addittional bonuses."); Helpers.Add(magicItemEffectDefinition4.Requirements.AllowedItemTypes, "OneHandedWeapon", "TwoHandedWeapon"); List allowedSkillTypes = magicItemEffectDefinition4.Requirements.AllowedSkillTypes; SkillType[] array = new SkillType[8]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); Helpers.Add(allowedSkillTypes, (SkillType[])(object)array); Helpers.Add(magicItemEffectDefinition4.Requirements.AllowedRarities, ItemRarity.Magic, ItemRarity.Rare, ItemRarity.Epic, ItemRarity.Legendary, ItemRarity.Mythic); magicItemEffectDefinition4.ValuesPerRarity.Magic.Set(2f, 4f); magicItemEffectDefinition4.ValuesPerRarity.Rare = new ValueDef(3f, 6f, 1f); magicItemEffectDefinition4.ValuesPerRarity.Epic = new ValueDef(4f, 8f, 1f); magicItemEffectDefinition4.ValuesPerRarity.Legendary = new ValueDef(5f, 10f, 1f); magicItemEffectDefinition4.ValuesPerRarity.Mythic = new ValueDef(6f, 12f, 1f); magicItemEffectDefinition4.SelectionWeight = 2f; magicItemEffectDefinition4.Ability = "Infusion"; AbilityProxyDefinition abilityProxyDefinition = new AbilityProxyDefinition("Infusion", AbilityActivationMode.Activated, typeof(Infusion.InfusionAbilityProxy)); abilityProxyDefinition.Ability.Cooldown = 120f; PrefabManager.OnPrefabsRegistered += delegate { EpicLoot.RegisterAsset("InfusionIcon", ObjectDB.instance.GetStatusEffect(StringExtensionMethods.GetStableHashCode("SetEffect_MageArmor"))?.m_icon); }; abilityProxyDefinition.Ability.IconAsset = "InfusionIcon"; magicItemEffectDefinition4.Register(); MagicItemEffectDefinition magicItemEffectDefinition5 = new MagicItemEffectDefinition("Whirlwind", "Whirlwind", "Spin"); magicItemEffectDefinition5.Requirements.AllowedItemTypes.Add("TwoHandedWeapon"); magicItemEffectDefinition5.Requirements.AllowedSkillTypes.Add((SkillType)4); Helpers.Add(magicItemEffectDefinition5.Requirements.AllowedRarities, ItemRarity.Epic, ItemRarity.Legendary, ItemRarity.Mythic); magicItemEffectDefinition5.SelectionWeight = 1f; magicItemEffectDefinition5.Register(); MagicItemEffectDefinition magicItemEffectDefinition6 = new MagicItemEffectDefinition("Onslaught", "Onslaught +{0}%", "Grants Onslaught on last hit of an attack string and when hitting a staggered enemy. While in last hit of attack string during Onslaught you continue attacking using the last hit animation of your attack string. Each consecutive Onslaught hit deals {0}% increased damage per hit. Onslaught expires if an enemy has not been struck with an attack in last hit animation for 2 seconds."); Helpers.Add(magicItemEffectDefinition6.Requirements.AllowedItemTypes, "OneHandedWeapon", "TwoHandedWeapon"); magicItemEffectDefinition6.Requirements.AllowedSkillTypes.Add((SkillType)3); Helpers.Add(magicItemEffectDefinition6.Requirements.AllowedRarities, ItemRarity.Magic, ItemRarity.Rare, ItemRarity.Epic, ItemRarity.Legendary, ItemRarity.Mythic); magicItemEffectDefinition6.ValuesPerRarity.Magic.Set(5f, 8f); magicItemEffectDefinition6.ValuesPerRarity.Rare = new ValueDef(6f, 10f, 1f); magicItemEffectDefinition6.ValuesPerRarity.Epic = new ValueDef(7f, 12f, 1f); magicItemEffectDefinition6.ValuesPerRarity.Legendary = new ValueDef(8f, 14f, 1f); magicItemEffectDefinition6.ValuesPerRarity.Mythic = new ValueDef(9f, 16f, 1f); magicItemEffectDefinition6.SelectionWeight = 5f; magicItemEffectDefinition6.Register(); MagicItemEffectDefinition magicItemEffectDefinition7 = new MagicItemEffectDefinition("PerfectStrike", "Perfect Strike +{0}%", "Allows Perfect Strikes to be performed. When attack is repeated with perfect timing your next strike will deal {0}% increased damage."); magicItemEffectDefinition7.Requirements.AllowedItemTypes.Add("TwoHandedWeapon"); magicItemEffectDefinition7.Requirements.AllowedSkillTypes.Add((SkillType)7); Helpers.Add(magicItemEffectDefinition7.Requirements.AllowedRarities, ItemRarity.Magic, ItemRarity.Rare, ItemRarity.Epic, ItemRarity.Legendary, ItemRarity.Mythic); magicItemEffectDefinition7.ValuesPerRarity.Magic.Set(30f, 50f, 2f); magicItemEffectDefinition7.ValuesPerRarity.Rare = new ValueDef(40f, 60f, 2f); magicItemEffectDefinition7.ValuesPerRarity.Epic = new ValueDef(50f, 70f, 2f); magicItemEffectDefinition7.ValuesPerRarity.Legendary = new ValueDef(50f, 80f, 2f); magicItemEffectDefinition7.ValuesPerRarity.Mythic = new ValueDef(50f, 100f, 1f); magicItemEffectDefinition7.SelectionWeight = 5f; magicItemEffectDefinition7.Register(); } } [HarmonyPatch(typeof(EnchantingTableUIPanelBase), "OnMainButtonClicked")] internal class RuneUIMod_OnMainButtonClicked_Patch { private static bool Prefix(EnchantingTableUIPanelBase __instance) { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) if (!(__instance is RuneUI)) { return true; } if ((bool)AccessTools.Field(typeof(EnchantingTableUIPanelBase), "_inProgress").GetValue(__instance)) { return true; } if ((int)AccessTools.Field(typeof(RuneUI), "_runeAction").GetValue(__instance) != 1) { return true; } if (EnchantingHelper.AwaitingConfirmation) { EnchantingHelper.AwaitingConfirmation = false; EnchantingHelper.PendingDestroyChance = 0f; return true; } Tuple singleSelectedItem = ((MultiSelectItemList)AccessTools.Field(typeof(EnchantingTableUIPanelBase), "AvailableItems").GetValue(__instance)).GetSingleSelectedItem(); if (((singleSelectedItem != null) ? singleSelectedItem.Item1.GetItem() : null) == null) { return true; } Tuple singleSelectedItem2 = ((MultiSelectItemList)AccessTools.Field(typeof(RuneUI), "AvailableRunes").GetValue(__instance)).GetSingleSelectedItem(); if (((singleSelectedItem2 != null) ? singleSelectedItem2.Item1.GetItem() : null) == null) { return true; } float num = EnchantingHelper.RuneTooPowerfulEtchDestructionChance(singleSelectedItem.Item1.GetItem(), singleSelectedItem2.Item1.GetItem()); if (num > 0f) { EnchantingHelper.AwaitingConfirmation = true; EnchantingHelper.PendingDestroyChance = num; Text val = (Text)AccessTools.Field(typeof(RuneUI), "Warning").GetValue(__instance); val.text = $"WARNING: {num:0}% chance to DESTROY your item!! Click Etch again to confirm."; ((Graphic)val).color = Color.red; return false; } return true; } } [HarmonyPatch(typeof(RuneUI), "DoMainAction")] internal class RuneUI_DoMainACtion_Patch { private static bool Prefix(RuneUI __instance) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Expected O, but got Unknown //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) EnchantingHelper.AwaitingConfirmation = false; if ((int)AccessTools.Field(typeof(RuneUI), "_runeAction").GetValue(__instance) != 1) { return true; } Tuple tuple = ((MultiSelectItemList)AccessTools.Field(typeof(EnchantingTableUIPanelBase), "AvailableItems").GetValue(__instance)).GetSelectedItems().FirstOrDefault(); ((EnchantingTableUIPanelBase)__instance).Cancel(); if (((tuple != null) ? tuple.Item1.GetItem() : null) == null) { return false; } MultiSelectItemList val = (MultiSelectItemList)AccessTools.Field(typeof(RuneUI), "AvailableRunes").GetValue(__instance); ItemData item = val.GetSingleSelectedItem().Item1.GetItem(); ItemData item2 = tuple.Item1.GetItem(); int num = (int)AccessTools.Field(typeof(RuneUI), "_selectedEnchantmentIndex").GetValue(__instance); FieldInfo fieldInfo = AccessTools.Field(typeof(RuneUI), "_successDialog"); GameObject val2 = (GameObject)fieldInfo.GetValue(__instance); if ((Object)(object)val2 != (Object)null) { Object.Destroy((Object)(object)val2); } GameObject val3 = RuneUI.RuneEnchancedItem.Invoke(item2, item, num); if ((Object)(object)val3 != (Object)null) { fieldInfo.SetValue(__instance, val3); val3.SetActive(true); InventoryManagement.Instance.RemoveExactItem(item, 1); } else { fieldInfo.SetValue(__instance, null); AccessTools.Method(typeof(RuneUI), "Unlock", (Type[])null, (Type[])null).Invoke(__instance, null); } MultiSelectItemList val4 = (MultiSelectItemList)AccessTools.Field(typeof(RuneUI), "CostList").GetValue(__instance); val4.SetItems(new List()); ((EnchantingTableUIPanelBase)__instance).DeselectAll(); AccessTools.Method(typeof(RuneUI), "RefreshAvailableItems", (Type[])null, (Type[])null).Invoke(__instance, null); AccessTools.Field(typeof(RuneUI), "_selectedEnchantmentIndex").SetValue(__instance, -1); val4.SetItems(new List()); val.SetItems(new List()); return false; } } [HarmonyPatch(typeof(RuneUI), "Lock")] internal class RuneUI_Lock_Patch { private static void Postfix(RuneUI __instance) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown Button val = (Button)AccessTools.Field(typeof(EnchantingTableUIPanelBase), "MainButton").GetValue(__instance); if ((Object)(object)val != (Object)null) { ((Selectable)val).interactable = true; } } } } namespace EpicLootLeslieAlphaTest.src.Utilities { public static class Help { public static void CopyFieldsFrom(this T target, V source) where T : Humanoid where V : Humanoid { Dictionary dictionary = typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).ToDictionary((FieldInfo f) => f.Name); FieldInfo[] fields = typeof(V).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (dictionary.TryGetValue(fieldInfo.Name, out var value) && value.FieldType.IsAssignableFrom(fieldInfo.FieldType)) { value.SetValue(target, fieldInfo.GetValue(source)); } } } public static void Add(this List list, params T[] values) { list.AddRange(values); } public static void Remove(this GameObject prefab) where T : Component { T val = default(T); if (prefab.TryGetComponent(ref val)) { Object.DestroyImmediate((Object)(object)val); } } } public class HumanoidFactory { public static GameObject playerAncestor; private static bool Loaded; public static void Create() { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_0207: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Unknown result type (might be due to invalid IL or missing references) if (Loaded) { return; } GameObject playerPrefab = Game.instance.m_playerPrefab; bool activeSelf = playerPrefab.activeSelf; playerPrefab.SetActive(false); Player component = playerPrefab.GetComponent(); playerAncestor = Object.Instantiate(playerPrefab); playerAncestor.Remove(); playerAncestor.Remove(); playerAncestor.Remove(); playerAncestor.Remove(); playerAncestor.Remove(); playerAncestor.Remove(); playerAncestor.Remove(); ((Object)playerAncestor).name = "playerAncestorHashString"; playerAncestor.GetComponent().m_persistent = false; playerAncestor.GetComponent().m_type = (ObjectType)0; Humanoid obj = playerAncestor.AddComponent(); obj.CopyFieldsFrom(component); ((Character)obj).m_animator = playerAncestor.GetComponentInChildren(); ((Character)obj).m_zanim = playerAncestor.GetComponent(); ((Character)obj).m_body = playerAncestor.GetComponent(); ((Character)obj).m_collider = playerAncestor.GetComponent(); obj.m_visEquipment = playerAncestor.GetComponent(); ((Character)obj).m_turnSpeed = 0f; ((Character)obj).m_animEvent = playerAncestor.GetComponentInChildren(); ((Character)obj).m_eye = Utils.FindChild(playerAncestor.transform, "EyePos", (IterativeSearchType)0); if ((Object)(object)playerAncestor.GetComponent() != (Object)null) { playerAncestor.GetComponent().m_animator = playerAncestor.GetComponent(); playerAncestor.GetComponent().m_nview = playerAncestor.GetComponent(); } Rigidbody component2 = playerAncestor.GetComponent(); if ((Object)(object)component2 != (Object)null) { component2.isKinematic = false; component2.useGravity = false; component2.constraints = (RigidbodyConstraints)126; } Shader shader = Shader.Find("Sprites/Default"); Renderer[] componentsInChildren = playerAncestor.GetComponentsInChildren(true); foreach (Renderer obj2 in componentsInChildren) { Material[] materials = obj2.materials; materials[0].shader = shader; materials[0].mainTexture = null; materials[0].color = new Color(0.3f, 0.5f, 0.9f, 0.03f); materials[1].shader = shader; materials[1].mainTexture = null; materials[1].color = new Color(0.3f, 0.5f, 0.9f, 0f); materials[0].SetColor("_SkinColor", new Color(0.3f, 0.5f, 0.9f, 0.03f)); _ = materials[0].shader; obj2.materials = materials; } componentsInChildren = playerAncestor.GetComponentsInChildren(true); foreach (Renderer val in componentsInChildren) { SkinnedMeshRenderer val2 = (SkinnedMeshRenderer)(object)((val is SkinnedMeshRenderer) ? val : null); if (val2 != null && (Object)(object)val2.sharedMesh != (Object)null) { _ = ((Object)val2.sharedMesh).name; } else if (val is MeshRenderer) { MeshFilter component3 = ((Component)val).GetComponent(); if (component3 != null && (Object)(object)component3.sharedMesh != (Object)null) { _ = ((Object)component3.sharedMesh).name; } } } Collider[] componentsInChildren2 = playerAncestor.GetComponentsInChildren(); for (int i = 0; i < componentsInChildren2.Length; i++) { componentsInChildren2[i].enabled = false; } ZNetScene.instance.m_namedPrefabs[StringExtensionMethods.GetStableHashCode("playerAncestorHashString")] = playerAncestor; playerPrefab.SetActive(activeSelf); playerAncestor.SetActive(false); Loaded = true; } } public static class WA { public static bool IsHeavyWeapon(ItemData item) { //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_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Invalid comparison between Unknown and I4 AnimationState animationState = item.m_shared.m_animationState; if ((int)animationState != 8 && (int)animationState != 2 && (int)animationState != 7) { return (int)animationState == 13; } return true; } public static bool IsRangedWeapon(ItemData item) { //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_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Invalid comparison between Unknown and I4 //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Invalid comparison between Unknown and I4 //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Invalid comparison between Unknown and I4 AnimationState animationState = item.m_shared.m_animationState; if ((int)animationState != 3 && (int)animationState != 10 && (int)animationState != 12) { return (int)animationState == 14; } return true; } public static bool IsTwoHanded(ItemData item) { //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_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Invalid comparison between Unknown and I4 //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Invalid comparison between Unknown and I4 //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Invalid comparison between Unknown and I4 //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Invalid comparison between Unknown and I4 AnimationState animationState = item.m_shared.m_animationState; if ((int)animationState != 8 && (int)animationState != 2 && (int)animationState != 7 && (int)animationState != 13 && (int)animationState != 15 && (int)animationState != 11) { return (int)animationState == 0; } return true; } } } namespace EpicLootLeslieAlphaTest.src.StatusEffects { public static class SERegistry { public static void RegisterStatusEffects() { Register("SE_Retaliation"); Register("SE_Onslaught"); Register("SE_FireInfused"); Register("SE_FrostInfused"); Register("SE_LightningInfused"); Register("SE_PoisonInfused"); Register("SE_WindInfused"); } private static void Register(string effectName) where T : StatusEffect { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown T val = ScriptableObject.CreateInstance(); ((Object)(object)val).name = effectName; ((StatusEffect)val).m_name = effectName; ItemManager.Instance.AddStatusEffect(new CustomStatusEffect((StatusEffect)(object)val, false)); } } public class SE_FireInfused : SE_Stats { public const string EffectName = "SE_FireInfused"; private GameObject m_vfxInstance; private GameObject m_vfxInstance2; private ItemData weapon; private float infusedFireDamage; public float Efficacy { get; set; } public override void Setup(Character character) { //IL_00a2: 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) ((SE_Stats)this).Setup(character); if (!((Object)(object)Player.m_localPlayer == (Object)null)) { Efficacy = Infusion.Efficacy; GameObject itemPrefab = ObjectDB.instance.GetItemPrefab("SwordDyrnwyn"); object icon; if (itemPrefab == null) { icon = null; } else { ItemDrop component = itemPrefab.GetComponent(); icon = ((component != null) ? component.m_itemData.m_shared.m_icons[0] : null); } ((StatusEffect)this).m_icon = (Sprite)icon; ((StatusEffect)this).m_name = "Fire Infused"; ((StatusEffect)this).m_ttl = 120f; Player localPlayer = Player.m_localPlayer; weapon = ((localPlayer != null) ? ((Humanoid)localPlayer).GetCurrentWeapon() : null); if (weapon != null && !WA.IsRangedWeapon(weapon)) { DamageTypes damage = weapon.GetDamage(); float totalDamage = ((DamageTypes)(ref damage)).GetTotalDamage(); float num = (WA.IsHeavyWeapon(weapon) ? 2f : 1f); Logger.LogWarning((object)string.Format("[FireInfused] weapon base damage:{0} efficacy:{1} weapon multiplier:{2} added:{3} + {4} * {5}", totalDamage, Efficacy, num, totalDamage * Player.m_localPlayer.GetTotalActiveMagicEffectValue("Infusion", 0.01f), Player.m_localPlayer.GetTotalActiveMagicEffectValue("Infusion", 0.01f) * Efficacy, num)); infusedFireDamage += totalDamage * Player.m_localPlayer.GetTotalActiveMagicEffectValue("Infusion", 0.01f) + Efficacy * Player.m_localPlayer.GetTotalActiveMagicEffectValue("Infusion", 0.1f) * num; weapon.m_shared.m_damages.m_fire += infusedFireDamage; Logger.LogWarning((object)$"Added {infusedFireDamage} fire damage"); } } } public override void Stop() { ((StatusEffect)this).Stop(); if (weapon != null) { weapon.m_shared.m_damages.m_fire -= infusedFireDamage; } if ((Object)(object)m_vfxInstance != (Object)null) { Object.Destroy((Object)(object)m_vfxInstance); } if ((Object)(object)m_vfxInstance2 != (Object)null) { Object.Destroy((Object)(object)m_vfxInstance2); } } public override void UpdateStatusEffect(float dt) { ((SE_Stats)this).UpdateStatusEffect(dt); if (!((Object)(object)Player.m_localPlayer == (Object)null)) { if (((Humanoid)Player.m_localPlayer).GetCurrentWeapon() == weapon && (Object)(object)m_vfxInstance == (Object)null) { CreateVFX(); } else if (((Humanoid)Player.m_localPlayer).GetCurrentWeapon() != weapon && (Object)(object)m_vfxInstance != (Object)null) { DestroyVFX(); } } } private void CreateVFX() { VisEquipment visEquipment = ((Humanoid)Player.m_localPlayer).m_visEquipment; if (!WA.IsTwoHanded(weapon)) { m_vfxInstance = Object.Instantiate(InfusionVFX.FireInfusionVFX, visEquipment.m_rightHand); m_vfxInstance.SetActive(true); return; } m_vfxInstance = Object.Instantiate(InfusionVFX.FireInfusionVFX, visEquipment.m_rightHand); m_vfxInstance2 = Object.Instantiate(InfusionVFX.FireInfusionVFX, visEquipment.m_leftHand); m_vfxInstance.SetActive(true); m_vfxInstance2.SetActive(true); } private void DestroyVFX() { if ((Object)(object)m_vfxInstance != (Object)null) { Object.Destroy((Object)(object)m_vfxInstance); m_vfxInstance = null; } if ((Object)(object)m_vfxInstance2 != (Object)null) { Object.Destroy((Object)(object)m_vfxInstance2); m_vfxInstance2 = null; } } } public class SE_FrostInfused : SE_Stats { public const string EffectName = "SE_FrostInfused"; private GameObject m_vfxInstance; private GameObject m_vfxInstance2; private ItemData weapon; private float infusedFrostDamage; public float Efficacy { get; set; } public override void Setup(Character character) { //IL_00a2: 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) ((SE_Stats)this).Setup(character); if (!((Object)(object)Player.m_localPlayer == (Object)null)) { Efficacy = Infusion.Efficacy; GameObject itemPrefab = ObjectDB.instance.GetItemPrefab("SwordMistwalker"); object icon; if (itemPrefab == null) { icon = null; } else { ItemDrop component = itemPrefab.GetComponent(); icon = ((component != null) ? component.m_itemData.m_shared.m_icons[0] : null); } ((StatusEffect)this).m_icon = (Sprite)icon; ((StatusEffect)this).m_name = "Frost Infused"; ((StatusEffect)this).m_ttl = 120f; Player localPlayer = Player.m_localPlayer; weapon = ((localPlayer != null) ? ((Humanoid)localPlayer).GetCurrentWeapon() : null); if (weapon != null && !WA.IsRangedWeapon(weapon)) { DamageTypes damage = weapon.GetDamage(); float totalDamage = ((DamageTypes)(ref damage)).GetTotalDamage(); float num = (WA.IsHeavyWeapon(weapon) ? 2f : 1f); Logger.LogWarning((object)string.Format("[FrostInfused] weapon base damage:{0} efficacy:{1} weapon multiplier:{2} added:{3} + {4} * {5}", totalDamage, Efficacy, num, totalDamage * Player.m_localPlayer.GetTotalActiveMagicEffectValue("Infusion", 0.01f), Player.m_localPlayer.GetTotalActiveMagicEffectValue("Infusion", 0.01f) * Efficacy, num)); infusedFrostDamage += totalDamage * Player.m_localPlayer.GetTotalActiveMagicEffectValue("Infusion", 0.01f) + Efficacy * Player.m_localPlayer.GetTotalActiveMagicEffectValue("Infusion", 0.1f) * num; weapon.m_shared.m_damages.m_frost += infusedFrostDamage; Logger.LogWarning((object)$"Added {infusedFrostDamage} frost damage"); } } } public override void Stop() { ((StatusEffect)this).Stop(); if (weapon != null) { weapon.m_shared.m_damages.m_frost -= infusedFrostDamage; } if ((Object)(object)m_vfxInstance != (Object)null) { Object.Destroy((Object)(object)m_vfxInstance); } if ((Object)(object)m_vfxInstance2 != (Object)null) { Object.Destroy((Object)(object)m_vfxInstance2); } } public override void UpdateStatusEffect(float dt) { ((SE_Stats)this).UpdateStatusEffect(dt); if (!((Object)(object)Player.m_localPlayer == (Object)null)) { if (((Humanoid)Player.m_localPlayer).GetCurrentWeapon() == weapon && (Object)(object)m_vfxInstance == (Object)null) { CreateVFX(); } else if (((Humanoid)Player.m_localPlayer).GetCurrentWeapon() != weapon && (Object)(object)m_vfxInstance != (Object)null) { DestroyVFX(); } } } private void CreateVFX() { VisEquipment visEquipment = ((Humanoid)Player.m_localPlayer).m_visEquipment; if (!WA.IsTwoHanded(weapon)) { m_vfxInstance = Object.Instantiate(InfusionVFX.FrostInfusionVFX, visEquipment.m_rightHand); m_vfxInstance.SetActive(true); return; } m_vfxInstance = Object.Instantiate(InfusionVFX.FrostInfusionVFX, visEquipment.m_rightHand); m_vfxInstance2 = Object.Instantiate(InfusionVFX.FrostInfusionVFX, visEquipment.m_leftHand); m_vfxInstance.SetActive(true); m_vfxInstance2.SetActive(true); } private void DestroyVFX() { if ((Object)(object)m_vfxInstance != (Object)null) { Object.Destroy((Object)(object)m_vfxInstance); m_vfxInstance = null; } if ((Object)(object)m_vfxInstance2 != (Object)null) { Object.Destroy((Object)(object)m_vfxInstance2); m_vfxInstance2 = null; } } } public class SE_LightningInfused : SE_Stats { public const string EffectName = "SE_LightningInfused"; private GameObject m_vfxInstance; private GameObject m_vfxInstance2; private ItemData weapon; private float infusedLightningDamage; public float Efficacy { get; set; } public override void Setup(Character character) { //IL_00a2: 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) ((SE_Stats)this).Setup(character); if (!((Object)(object)Player.m_localPlayer == (Object)null)) { Efficacy = Infusion.Efficacy; GameObject itemPrefab = ObjectDB.instance.GetItemPrefab("SwordNiedhoggLightning"); object icon; if (itemPrefab == null) { icon = null; } else { ItemDrop component = itemPrefab.GetComponent(); icon = ((component != null) ? component.m_itemData.m_shared.m_icons[0] : null); } ((StatusEffect)this).m_icon = (Sprite)icon; ((StatusEffect)this).m_name = "Lightning Infused"; ((StatusEffect)this).m_ttl = 120f; Player localPlayer = Player.m_localPlayer; weapon = ((localPlayer != null) ? ((Humanoid)localPlayer).GetCurrentWeapon() : null); if (weapon != null && !WA.IsRangedWeapon(weapon)) { DamageTypes damage = weapon.GetDamage(); float totalDamage = ((DamageTypes)(ref damage)).GetTotalDamage(); float num = (WA.IsHeavyWeapon(weapon) ? 2f : 1f); Logger.LogWarning((object)string.Format("[LightningInfused] weapon base damage:{0} efficacy:{1} weapon multiplier:{2} added:{3} + {4} * {5}", totalDamage, Efficacy, num, totalDamage * Player.m_localPlayer.GetTotalActiveMagicEffectValue("Infusion", 0.01f), Player.m_localPlayer.GetTotalActiveMagicEffectValue("Infusion", 0.01f) * Efficacy, num)); infusedLightningDamage += totalDamage * Player.m_localPlayer.GetTotalActiveMagicEffectValue("Infusion", 0.01f) + Efficacy * Player.m_localPlayer.GetTotalActiveMagicEffectValue("Infusion", 0.1f) * num; weapon.m_shared.m_damages.m_lightning += infusedLightningDamage; Logger.LogWarning((object)$"Added {infusedLightningDamage} lightning damage"); } } } public override void Stop() { ((StatusEffect)this).Stop(); if (weapon != null) { weapon.m_shared.m_damages.m_lightning -= infusedLightningDamage; } if ((Object)(object)m_vfxInstance != (Object)null) { Object.Destroy((Object)(object)m_vfxInstance); } if ((Object)(object)m_vfxInstance2 != (Object)null) { Object.Destroy((Object)(object)m_vfxInstance2); } } public override void UpdateStatusEffect(float dt) { ((SE_Stats)this).UpdateStatusEffect(dt); if (!((Object)(object)Player.m_localPlayer == (Object)null)) { if (((Humanoid)Player.m_localPlayer).GetCurrentWeapon() == weapon && (Object)(object)m_vfxInstance == (Object)null) { CreateVFX(); } else if (((Humanoid)Player.m_localPlayer).GetCurrentWeapon() != weapon && (Object)(object)m_vfxInstance != (Object)null) { DestroyVFX(); } } } private void CreateVFX() { VisEquipment visEquipment = ((Humanoid)Player.m_localPlayer).m_visEquipment; if (!WA.IsTwoHanded(weapon)) { m_vfxInstance = Object.Instantiate(InfusionVFX.LightningInfusionVFX, visEquipment.m_rightHand); m_vfxInstance.SetActive(true); return; } m_vfxInstance = Object.Instantiate(InfusionVFX.LightningInfusionVFX, visEquipment.m_rightHand); m_vfxInstance2 = Object.Instantiate(InfusionVFX.LightningInfusionVFX, visEquipment.m_leftHand); m_vfxInstance.SetActive(true); m_vfxInstance2.SetActive(true); } private void DestroyVFX() { if ((Object)(object)m_vfxInstance != (Object)null) { Object.Destroy((Object)(object)m_vfxInstance); m_vfxInstance = null; } if ((Object)(object)m_vfxInstance2 != (Object)null) { Object.Destroy((Object)(object)m_vfxInstance2); m_vfxInstance2 = null; } } } public class SE_Onslaught : SE_Stats { public const string EffectName = "SE_Onslaught"; public int m_stacks; public int m_maxStacks = 9999; private float m_onslaughtHits; private const float m_expire = 2f; public override void Setup(Character character) { ((SE_Stats)this).Setup(character); if ((Object)(object)character.m_animator != (Object)null) { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab("MaceIron"); object icon; if (itemPrefab == null) { icon = null; } else { ItemDrop component = itemPrefab.GetComponent(); icon = ((component != null) ? component.m_itemData.m_shared.m_icons[0] : null); } ((StatusEffect)this).m_icon = (Sprite)icon; ((StatusEffect)this).m_name = "Onslaught Hits"; ((StatusEffect)this).m_flashIcon = true; m_stacks++; } } public void AddStack() { ((StatusEffect)this).m_ttl = 2f; if (m_stacks < m_maxStacks) { m_stacks++; ((StatusEffect)this).m_time = 0f; } } public void ConsumeStack() { if (m_stacks > 0) { m_stacks--; ((StatusEffect)this).m_time = 0f; } } public override void UpdateStatusEffect(float dt) { ((SE_Stats)this).UpdateStatusEffect(dt); Player localPlayer = Player.m_localPlayer; if (!WA.IsRangedWeapon((localPlayer != null) ? ((Humanoid)localPlayer).GetCurrentWeapon() : null)) { if (m_stacks <= 0 && !((StatusEffect)this).m_character.InAttack()) { ((StatusEffect)this).m_character.GetSEMan().RemoveStatusEffect((StatusEffect)(object)this, false); } if (!((Character)Player.m_localPlayer).InAttack() && ((StatusEffect)this).m_time > 1.5f) { m_onslaughtHits = 0f; } } } public override void ModifyAttack(SkillType skill, ref HitData hitData) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) ((SE_Stats)this).ModifyAttack(skill, ref hitData); Logger.LogWarning((object)$"stacks{m_stacks}"); Character character = ((StatusEffect)this).m_character; int? num = ((Humanoid)(((character is Humanoid) ? character : null)?)).m_currentAttack?.m_currentAttackCainLevel; if (m_stacks > 0 && !(num < 2)) { m_onslaughtHits += 1f; Logger.LogWarning((object)$"{m_onslaughtHits}"); float num2 = 1f + m_onslaughtHits * Player.m_localPlayer.GetTotalActiveMagicEffectValue("Onslaught", 0.01f); hitData.ApplyModifier(num2); } } public override string GetIconText() { return $"{m_onslaughtHits}"; } } public class SE_PoisonInfused : SE_Stats { public const string EffectName = "SE_PoisonInfused"; private GameObject m_vfxInstance; private GameObject m_vfxInstance2; private ItemData weapon; private float infusedPoisonDamage; public float Efficacy { get; set; } public override void Setup(Character character) { //IL_00a2: 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) ((SE_Stats)this).Setup(character); if (!((Object)(object)Player.m_localPlayer == (Object)null)) { Efficacy = Infusion.Efficacy; GameObject itemPrefab = ObjectDB.instance.GetItemPrefab("SwordNiedhoggNature"); object icon; if (itemPrefab == null) { icon = null; } else { ItemDrop component = itemPrefab.GetComponent(); icon = ((component != null) ? component.m_itemData.m_shared.m_icons[0] : null); } ((StatusEffect)this).m_icon = (Sprite)icon; ((StatusEffect)this).m_name = "Poison Infused"; ((StatusEffect)this).m_ttl = 120f; Player localPlayer = Player.m_localPlayer; weapon = ((localPlayer != null) ? ((Humanoid)localPlayer).GetCurrentWeapon() : null); if (weapon != null && !WA.IsRangedWeapon(weapon)) { DamageTypes damage = weapon.GetDamage(); float totalDamage = ((DamageTypes)(ref damage)).GetTotalDamage(); float num = (WA.IsHeavyWeapon(weapon) ? 2f : 1f); Logger.LogWarning((object)string.Format("[PoisonInfused] weapon base damage:{0} efficacy:{1} weapon multiplier:{2} added:{3} + {4} * {5}", totalDamage, Efficacy, num, totalDamage * Player.m_localPlayer.GetTotalActiveMagicEffectValue("Infusion", 0.01f), Player.m_localPlayer.GetTotalActiveMagicEffectValue("Infusion", 0.01f) * Efficacy, num)); infusedPoisonDamage += totalDamage * Player.m_localPlayer.GetTotalActiveMagicEffectValue("Infusion", 0.01f) + Efficacy * Player.m_localPlayer.GetTotalActiveMagicEffectValue("Infusion", 0.1f) * num; weapon.m_shared.m_damages.m_poison += infusedPoisonDamage; Logger.LogWarning((object)$"Added {infusedPoisonDamage} poison damage"); } } } public override void Stop() { ((StatusEffect)this).Stop(); if (weapon != null) { weapon.m_shared.m_damages.m_poison -= infusedPoisonDamage; } if ((Object)(object)m_vfxInstance != (Object)null) { Object.Destroy((Object)(object)m_vfxInstance); } if ((Object)(object)m_vfxInstance2 != (Object)null) { Object.Destroy((Object)(object)m_vfxInstance2); } } public override void UpdateStatusEffect(float dt) { ((SE_Stats)this).UpdateStatusEffect(dt); if (!((Object)(object)Player.m_localPlayer == (Object)null)) { if (((Humanoid)Player.m_localPlayer).GetCurrentWeapon() == weapon && (Object)(object)m_vfxInstance == (Object)null) { CreateVFX(); } else if (((Humanoid)Player.m_localPlayer).GetCurrentWeapon() != weapon && (Object)(object)m_vfxInstance != (Object)null) { DestroyVFX(); } } } private void CreateVFX() { VisEquipment visEquipment = ((Humanoid)Player.m_localPlayer).m_visEquipment; if (!WA.IsTwoHanded(weapon)) { m_vfxInstance = Object.Instantiate(InfusionVFX.PoisonInfusionVFX, visEquipment.m_rightHand); m_vfxInstance.SetActive(true); return; } m_vfxInstance = Object.Instantiate(InfusionVFX.PoisonInfusionVFX, visEquipment.m_rightHand); m_vfxInstance2 = Object.Instantiate(InfusionVFX.PoisonInfusionVFX, visEquipment.m_leftHand); m_vfxInstance.SetActive(true); m_vfxInstance2.SetActive(true); } private void DestroyVFX() { if ((Object)(object)m_vfxInstance != (Object)null) { Object.Destroy((Object)(object)m_vfxInstance); m_vfxInstance = null; } if ((Object)(object)m_vfxInstance2 != (Object)null) { Object.Destroy((Object)(object)m_vfxInstance2); m_vfxInstance2 = null; } } } public class SE_Retaliation : SE_Stats { public const string EffectName = "SE_Retaliation"; public int m_stacks; public int m_maxStacks = 1; private const float m_expire = 5f; private float baseSpeed = 1f; private bool addedRetaliationSpeed; private bool amAttacking; private static readonly Dictionary _animSpeeds = new Dictionary { { "atgeir_attack", 1.2f }, { "atgeir_secondary", 2f }, { "knife_stab", 1.2f }, { "knife_secondary", 1.1f }, { "battleaxe_attack", 1.6f }, { "swing_axe", 1.2f }, { "swing_longsword", 1.2f }, { "mace_secondary", 1.35f }, { "spear_poke", 1.2f }, { "sword_secondary", 0.5f }, { "dualaxes", 1.1f }, { "dualaxes_secondary", 1.1f }, { "dual_knives", 1.1f }, { "dual_knives_secondary", 1.2f }, { "greatsword", 1.2f }, { "greatsword_secondary", 1.2f }, { "swing_sledge", 1.4f }, { "unarmed_attack", 2.2f } }; public override void Setup(Character character) { ((SE_Stats)this).Setup(character); if ((Object)(object)character.m_animator != (Object)null) { m_maxStacks = Mathf.RoundToInt(Player.m_localPlayer.GetTotalActiveMagicEffectValue("Retaliation")); baseSpeed = character.m_animator.speed; character.m_animator.speed = ((Retaliation.AttackType != null && _animSpeeds.TryGetValue(Retaliation.AttackType, out var value)) ? value : 1.2f); GameObject itemPrefab = ObjectDB.instance.GetItemPrefab("ShieldBanded"); object icon; if (itemPrefab == null) { icon = null; } else { ItemDrop component = itemPrefab.GetComponent(); icon = ((component != null) ? component.m_itemData.m_shared.m_icons[3] : null); } ((StatusEffect)this).m_icon = (Sprite)icon; ((StatusEffect)this).m_name = "Retaliation"; ((StatusEffect)this).m_flashIcon = true; } } public void AddStack() { ((StatusEffect)this).m_ttl = 5f; if (m_stacks < m_maxStacks) { m_stacks++; ((StatusEffect)this).m_time = 0f; } } public void ConsumeStack() { if (m_stacks > 0) { m_stacks--; ((StatusEffect)this).m_time = 0f; } } public override void UpdateStatusEffect(float dt) { ((SE_Stats)this).UpdateStatusEffect(dt); Player localPlayer = Player.m_localPlayer; if (!WA.IsRangedWeapon((localPlayer != null) ? ((Humanoid)localPlayer).GetCurrentWeapon() : null)) { if (!((StatusEffect)this).m_character.InAttack() && amAttacking) { ConsumeStack(); } amAttacking = ((StatusEffect)this).m_character.InAttack(); if ((Object)(object)((StatusEffect)this).m_character.m_animator != (Object)null && ((StatusEffect)this).m_character.m_animator.speed >= 5f && !addedRetaliationSpeed) { Animator animator = ((StatusEffect)this).m_character.m_animator; animator.speed += ((Retaliation.AttackType != null && _animSpeeds.TryGetValue(Retaliation.AttackType, out var value)) ? value : 1.2f); addedRetaliationSpeed = true; } if ((Object)(object)((StatusEffect)this).m_character.m_animator != (Object)null && ((StatusEffect)this).m_character.m_animator.speed < 5f) { ((StatusEffect)this).m_character.m_animator.speed = ((Retaliation.AttackType != null && _animSpeeds.TryGetValue(Retaliation.AttackType, out var value2)) ? value2 : 1.2f); } if (m_stacks > 0 && !((StatusEffect)this).m_character.InAttack()) { ((StatusEffect)this).m_character.m_animator.speed = baseSpeed; } if (m_stacks <= 0 && !((StatusEffect)this).m_character.InAttack()) { ((StatusEffect)this).m_character.GetSEMan().RemoveStatusEffect((StatusEffect)(object)this, false); } } } public override void Stop() { ((StatusEffect)this).Stop(); if ((Object)(object)((StatusEffect)this).m_character?.m_animator != (Object)null) { ((StatusEffect)this).m_character.m_animator.speed = baseSpeed; } } public override string GetIconText() { return $"{m_stacks}"; } } public class SE_WindInfused : SE_Stats { public const string EffectName = "SE_WindInfused"; private GameObject m_vfxInstance; private GameObject m_vfxInstance2; private ItemData weapon; private float infusedWindValue; private float baseSpeed = 1f; public override void Setup(Character character) { ((SE_Stats)this).Setup(character); if (!((Object)(object)Player.m_localPlayer == (Object)null)) { weapon = ((Humanoid)Player.m_localPlayer).GetCurrentWeapon(); baseSpeed = ((StatusEffect)this).m_character.m_animator.speed; ((StatusEffect)this).m_name = "Wind Infused"; ((StatusEffect)this).m_ttl = 120f; } } public override void Stop() { ((StatusEffect)this).Stop(); ((StatusEffect)this).m_character.m_animator.speed = baseSpeed; if ((Object)(object)m_vfxInstance != (Object)null) { Object.Destroy((Object)(object)m_vfxInstance); } if ((Object)(object)m_vfxInstance2 != (Object)null) { Object.Destroy((Object)(object)m_vfxInstance2); } } public override void UpdateStatusEffect(float dt) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) ((SE_Stats)this).UpdateStatusEffect(dt); if (!((Object)(object)Player.m_localPlayer == (Object)null)) { _ = ((Humanoid)Player.m_localPlayer).m_visEquipment.m_rightItemInstance; _ = weapon.m_shared.m_animationState; _ = ((Humanoid)Player.m_localPlayer).m_visEquipment.m_rightHand; _ = ((Humanoid)Player.m_localPlayer).m_visEquipment.m_leftHand; if (((Character)Player.m_localPlayer).InAttack() && ((Humanoid)Player.m_localPlayer).GetCurrentWeapon() == weapon && baseSpeed <= 3f * baseSpeed) { Animator animator = ((StatusEffect)this).m_character.m_animator; float num = (animator.speed += Player.m_localPlayer.GetTotalActiveMagicEffectValue("Infusion", 0.01f)); } if (((Humanoid)Player.m_localPlayer).GetCurrentWeapon() == weapon && (Object)(object)m_vfxInstance == (Object)null) { CreateVFX(); } else if (((Humanoid)Player.m_localPlayer).GetCurrentWeapon() != weapon && (Object)(object)m_vfxInstance != (Object)null) { DestroyVFX(); } } } private void CreateVFX() { VisEquipment visEquipment = ((Humanoid)Player.m_localPlayer).m_visEquipment; if (!WA.IsTwoHanded(weapon)) { m_vfxInstance = Object.Instantiate(InfusionVFX.WindInfusionVFX, visEquipment.m_rightHand); m_vfxInstance.SetActive(true); PlayAll(m_vfxInstance); return; } m_vfxInstance = Object.Instantiate(InfusionVFX.WindInfusionVFX, visEquipment.m_rightHand); m_vfxInstance2 = Object.Instantiate(InfusionVFX.WindInfusionVFX, visEquipment.m_leftHand); m_vfxInstance.SetActive(true); m_vfxInstance2.SetActive(true); PlayAll(m_vfxInstance); PlayAll(m_vfxInstance2); } private void PlayAll(GameObject vfx) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) Light componentInChildren = vfx.GetComponentInChildren(); if ((Object)(object)componentInChildren != (Object)null) { ((Component)componentInChildren).gameObject.SetActive(false); } ParticleSystem[] componentsInChildren = vfx.GetComponentsInChildren(); foreach (ParticleSystem val in componentsInChildren) { if ((Object)(object)((Component)val).transform == (Object)(object)vfx.transform) { val.Stop(); continue; } MainModule main = val.main; ((MainModule)(ref main)).loop = true; ((MainModule)(ref main)).duration = 2f; ((MainModule)(ref main)).startColor = MinMaxGradient.op_Implicit(new Color(0.7607843f, 1f, 0.57254905f, 1f)); val.Play(); } } private void DestroyVFX() { if ((Object)(object)m_vfxInstance != (Object)null) { Object.Destroy((Object)(object)m_vfxInstance); m_vfxInstance = null; } if ((Object)(object)m_vfxInstance2 != (Object)null) { Object.Destroy((Object)(object)m_vfxInstance2); m_vfxInstance2 = null; } } } } namespace EpicLootLeslieAlphaTest.src.StatusEffects.VFX { public static class InfusionVFX { public static GameObject FireInfusionVFX; public static GameObject FrostInfusionVFX; public static GameObject LightningInfusionVFX; public static GameObject PoisonInfusionVFX; public static GameObject WindInfusionVFX; public static void Init() { FireInfusionVFX = BuildVFX(ZNetScene.instance.GetPrefab("Torch"), 0.1f); FrostInfusionVFX = BuildVFX(ZNetScene.instance.GetPrefab("vfx_Freezing"), 0.3f); LightningInfusionVFX = BuildVFX(ZNetScene.instance.GetPrefab("fx_Lightning"), 0.2f); PoisonInfusionVFX = BuildVFX(ZNetScene.instance.GetPrefab("vfx_Poison"), 0.2f); WindInfusionVFX = BuildVFX(ZNetScene.instance.GetPrefab("vfx_MeadSwimmer"), 0.08f); } private static GameObject BuildVFX(GameObject source, float scale) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: 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) if ((Object)(object)source == (Object)null) { return null; } GameObject val = new GameObject("InfusionVFX_" + ((Object)source).name); val.SetActive(false); Object.DontDestroyOnLoad((Object)(object)val); if ((Object)(object)val.GetComponent() != (Object)null) { Object.Destroy((Object)(object)val.GetComponent()); } ParticleSystem[] componentsInChildren = source.GetComponentsInChildren(true); foreach (ParticleSystem val2 in componentsInChildren) { if (!((Object)(object)((Component)val2).transform == (Object)(object)source.transform)) { GameObject obj = Object.Instantiate(((Component)val2).gameObject, val.transform); obj.transform.localPosition = Vector3.zero; obj.transform.localRotation = Quaternion.identity; Transform transform = obj.transform; transform.localScale *= scale; } } return val; } } } namespace EpicLootLeslieAlphaTest.src.MagicEffectss { public class AncestralSlam { [HarmonyPatch(typeof(Attack), "OnAttackTrigger")] public class Attack_Trigger { public static void Prefix(Attack __instance) { if (__instance.m_character is Player) { _lastAttackCainLevel = __instance.m_currentAttackCainLevel; _lastAttackAnim = __instance.m_attackAnimation; _lastAttackRange = __instance.m_attackRange; _lastAttack = __instance; } } } [HarmonyPatch(typeof(Character), "Damage")] public class Character_Damage_SpawnClone_Patch { public static void Postfix(Character __instance, HitData hit) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0086: 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) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) if (((Humanoid)Player.m_localPlayer).GetCurrentWeapon() == null) { return; } ItemData currentWeapon = ((Humanoid)Player.m_localPlayer).GetCurrentWeapon(); if (currentWeapon != null && ItemDataExtensions.HasMagicEffect(currentWeapon, "AncestralSlam")) { Character attacker = hit.GetAttacker(); if (!((Object)(object)attacker == (Object)null) && !((Object)(object)attacker != (Object)(object)Player.m_localPlayer) && !ancestralClonesHASH.Contains(((Component)attacker).gameObject)) { Vector3 position = ((Component)__instance).transform.position; Vector3 val = position - ((Component)Player.m_localPlayer).transform.position; Vector3 normalized = ((Vector3)(ref val)).normalized; SpawnAncestralSlam(rotation: (normalized != Vector3.zero) ? Quaternion.LookRotation(normalized) : ((Component)Player.m_localPlayer).transform.rotation, position: position - normalized * 1.5f, weapon: currentWeapon); } } } } [HarmonyPatch(typeof(Character), "Damage")] public class Character_Clone_Damage_Patch { public static void Prefix(Character __instance, HitData hit) { //IL_0059: 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_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Player.m_localPlayer == (Object)null) { return; } ItemData currentWeapon = ((Humanoid)Player.m_localPlayer).GetCurrentWeapon(); if (currentWeapon == null || !ItemDataExtensions.HasMagicEffect(currentWeapon, "AncestralSlam")) { return; } Character attacker = hit.GetAttacker(); if (!((Object)(object)attacker == (Object)null) && ancestralClonesHASH.Contains(((Component)attacker).gameObject)) { float skillFactor = ((Character)Player.m_localPlayer).GetSkillFactor(currentWeapon.m_shared.m_skillType); float num = Random.Range(0.96f, 1.04f); DamageTypes damage = ((DamageTypes)(ref currentWeapon.m_shared.m_damages)).Clone(); if (_lastAttackCainLevel >= 2) { ((DamageTypes)(ref damage)).Modify((skillFactor * 0.75f + 0.25f) * 2f * num); } else { ((DamageTypes)(ref damage)).Modify((skillFactor * 0.75f + 0.25f) * num); } hit.m_damage = damage; } } } [HarmonyPatch(typeof(EnemyHud), "ShowHud")] internal class EnemyHud_ShowHud_Patch { private static bool Prefix(Character c) { return !ancestralClonesHASH.Contains(((Component)c).gameObject); } } private const float damageModifier = 1f; private static int _lastAttackCainLevel = 0; private static string _lastAttackAnim = ""; private static Attack _lastAttack = null; private static float _lastAttackRange = 2f; public static readonly HashSet ancestralClonesHASH = new HashSet(); internal static void SpawnAncestralSlam(Vector3 position, Quaternion rotation, ItemData weapon) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)HumanoidFactory.playerAncestor == (Object)null)) { GameObject val = Object.Instantiate(HumanoidFactory.playerAncestor, position, rotation); ancestralClonesHASH.Add(val); val.SetActive(true); bool flag = weapon.m_shared.m_secondaryAttack.m_attackAnimation == _lastAttackAnim; Humanoid component = val.GetComponent(); component.m_rightItem = weapon.Clone(); val.GetComponent(); ((Character)component).m_lookDir = rotation * Vector3.forward; ((Character)component).StartAttack((Character)null, flag); if (component.m_currentAttack != null) { Attack currentAttack = component.m_currentAttack; currentAttack.m_attackRange *= 1.2f; component.m_currentAttack.m_currentAttackCainLevel = _lastAttackCainLevel; } ((MonoBehaviour)component).StartCoroutine(DelayedDestruction(val.GetComponent(), val, 2.5f)); } } private static IEnumerator DelayedDestruction(ZNetView zview, GameObject go, float attackTime) { yield return (object)new WaitForSeconds(attackTime + 0.5f); Character component = go.GetComponent(); if ((Object)(object)component != (Object)null) { EnemyHud instance = EnemyHud.instance; if (instance != null) { instance.RemoveCharacterHud(component); } Character.Instances.Remove((IMonoUpdater)(object)component); } ancestralClonesHASH.Remove(go); if ((Object)(object)zview != (Object)null && zview.IsOwner()) { ZNetScene.instance.Destroy(((Component)zview).gameObject); } } } public static class GlancingBlows { [HarmonyPatch(typeof(Humanoid), "BlockAttack")] public class Block_Attack_GlancingBlowsBlockAttackPrefix_Patch { public static void Prefix(HitData hit) { initialHitValue = hit.GetTotalBlockableDamage(); } } [HarmonyPatch(typeof(Humanoid), "BlockAttack")] public class Block_Attack_GlancingBlowsBlockAttackPostfix_Patch { public static void Postfix(HitData hit, Character attacker) { //IL_0082: Unknown result type (might be due to invalid IL or missing references) if (Object.op_Implicit((Object)(object)Player.m_localPlayer) && Player.m_localPlayer.HasActiveMagicEffect("GlancingBlows", out var _)) { float num = initialHitValue * 0.5f; float health = ((Character)Player.m_localPlayer).GetHealth(); float num2 = (float)Math.Round(num - ((Character)Player.m_localPlayer).GetBodyArmor(), 2); if (!((Character)Player.m_localPlayer).IsDead() && num2 > 0f) { ((Character)Player.m_localPlayer).SetHealth(health - num2); DamageText.instance.ShowText((TextType)0, ((Component)Player.m_localPlayer).transform.position, num2.ToString(), true); } } } } [HarmonyPatch(typeof(ItemData), "GetBlockPower", new Type[] { typeof(int), typeof(float) })] public class GlancingBlowsDamage_Patch { public static void Postfix(float skillFactor, ref float __result) { if (Object.op_Implicit((Object)(object)Player.m_localPlayer) && Player.m_localPlayer.HasActiveMagicEffect("GlancingBlows", out var _)) { __result *= 1.5f; } } } private static float effectValue = 1f; private static float initialHitValue = 0f; } public static class Infusion { public enum InfusionElement { Fire, Frost, Lightning, Poison, Wind, Blood } public class InfusionAbilityProxy : Proxy { public static IEnumerator RechargeAnimation(Player player, float duration) { ((Character)player).m_zanim.SetBool("recharge_lightningstaff", true); yield return (object)new WaitForSeconds(duration); ((Character)player).m_zanim.SetBool("recharge_lightningstaff", false); ((Character)player).m_zanim.SetTrigger("recharge_lightningstaff_done"); } public override void Activate() { if (IsOnCooldown() || (Object)(object)Player.m_localPlayer == (Object)null) { return; } SetCooldownEndTime(Proxy.GetTime() + Cooldown); List list = new List(((Character)Player.m_localPlayer).GetSEMan().GetStatusEffects()); isInfused = false; Dictionary dictionary = new Dictionary(); List list2 = new List(); for (int num = list.Count - 1; num >= 0; num--) { string text = ((Object)list[num]).name.ToLower(); foreach (var (value, key) in elementMap) { if (text.Contains(value)) { float efficacy = GetEfficacy(list[num]); list2.Add(list[num]); if (!dictionary.ContainsKey(key) || efficacy > dictionary[key]) { dictionary[key] = efficacy; } break; } } } ((MonoBehaviour)Player.m_localPlayer).StartCoroutine(DeferRemoveAndApply(list2, dictionary)); static IEnumerator DeferRemoveAndApply(List toRemove, Dictionary bestEfficacy) { foreach (StatusEffect item in toRemove) { ((Character)Player.m_localPlayer).GetSEMan().RemoveStatusEffect(item, false); } yield return null; foreach (KeyValuePair item2 in bestEfficacy) { Efficacy = item2.Value; ((Character)Player.m_localPlayer).GetSEMan().AddStatusEffect(StringExtensionMethods.GetStableHashCode($"SE_{item2.Key}Infused"), false, 0, 0f); isInfused = true; } if (isInfused) { ((MonoBehaviour)Player.m_localPlayer).StartCoroutine(RechargeAnimation(Player.m_localPlayer, 1f)); } if (!isInfused) { ((Character)Player.m_localPlayer).m_zanim.SetTrigger("emote_shrug"); } } } } private static bool isInfused = false; private static readonly List<(string keyword, InfusionElement element)> elementMap = new List<(string, InfusionElement)> { ("fire", InfusionElement.Fire), ("burn", InfusionElement.Fire), ("frost", InfusionElement.Frost), ("freez", InfusionElement.Frost), ("lightning", InfusionElement.Lightning), ("shock", InfusionElement.Lightning), ("poison", InfusionElement.Poison), ("wind", InfusionElement.Wind), ("aero", InfusionElement.Wind), ("moder", InfusionElement.Wind), ("leaf", InfusionElement.Wind), ("petal", InfusionElement.Wind), ("blood", InfusionElement.Blood) }; private static readonly Dictionary> efficacyType = new Dictionary> { { "Burning", (StatusEffect se) => ((SE_Burning)se).m_fireDamageLeft }, { "Frost", (StatusEffect se) => ((StatusEffect)(SE_Frost)se).m_ttl * 10f }, { "Lightning", (StatusEffect se) => (!(se.m_ttl > 0f) || ((Character)Player.m_localPlayer).m_lastHit == null) ? 0f : ((Character)Player.m_localPlayer).m_lastHit.m_damage.m_lightning }, { "Poison", (StatusEffect se) => ((SE_Poison)se).m_damageLeft } }; public static float Efficacy; private static float GetEfficacy(StatusEffect se) { Logger.LogWarning((object)$"GetEfficacy: {((Object)se).name} found {efficacyType.ContainsKey(((Object)se).name)}"); if (efficacyType.TryGetValue(((Object)se).name, out Func value)) { return value(se); } return 0f; } } public static class Onslaught { [HarmonyPatch(typeof(Character), "Damage")] public class Attack_Trigger_Patch { public static void Prefix(Character __instance, HitData hit) { Character attacker = hit.GetAttacker(); Humanoid val = (Humanoid)(object)((attacker is Humanoid) ? attacker : null); if (!((Object)(object)val == (Object)null) && !((Object)(object)Player.m_localPlayer != (Object)(object)val) && ItemDataExtensions.HasMagicEffect(((Humanoid)Player.m_localPlayer).GetCurrentWeapon(), "Onslaught") && ((val.m_currentAttack?.m_currentAttackCainLevel ?? (-1)) >= 2 || __instance.IsStaggering())) { SEMan sEMan = ((Character)Player.m_localPlayer).GetSEMan(); SE_Onslaught sE_Onslaught = sEMan.GetStatusEffect(StringExtensionMethods.GetStableHashCode("SE_Onslaught")) as SE_Onslaught; if ((Object)(object)sE_Onslaught != (Object)null) { sE_Onslaught.AddStack(); } else { (sEMan.AddStatusEffect(StringExtensionMethods.GetStableHashCode("SE_Onslaught"), false, 0, 0f) as SE_Onslaught).AddStack(); } } } } [HarmonyPatch(typeof(Attack), "Start")] public class Attack_Modify_Chain_Patch { public static void Postfix(Attack __instance, Humanoid character, ref bool __result) { if (!((Object)(object)character != (Object)(object)Player.m_localPlayer)) { SE_Onslaught sE_Onslaught = ((Character)character).GetSEMan().GetStatusEffect(StringExtensionMethods.GetStableHashCode("SE_Onslaught")) as SE_Onslaught; if (!((Object)(object)sE_Onslaught == (Object)null) && sE_Onslaught.m_stacks > 0 && ItemDataExtensions.HasMagicEffect(character.GetCurrentWeapon(), "Onslaught")) { __instance.m_currentAttackCainLevel = 2; __instance.m_zanim.SetTrigger(__instance.m_attackAnimation + "2"); sE_Onslaught.ConsumeStack(); } } } } } public static class PerfectStrike { [HarmonyPatch(typeof(Attack), "Start")] public static class Attack_PerfectStrikeAnimation_Patch { public static void Postfix(Attack __instance, Humanoid character) { if (!((Object)(object)character != (Object)(object)Player.m_localPlayer) && Player.m_localPlayer.HasActiveMagicEffect("PerfectStrike", out var _)) { if (isPerfectStrike) { int lastPerfectChain = ((_lastPerfectChain == 2) ? 1 : 2); __instance.m_currentAttackCainLevel = 2; __instance.m_zanim.SetTrigger(__instance.m_attackAnimation + lastPerfectChain); _lastPerfectChain = lastPerfectChain; _clicked = false; isPerfectStrike = false; _perfectDamagePending = true; } _currentAttack = __instance; _currentAnim = __instance.m_attackAnimation; _currentChain = __instance.m_currentAttackCainLevel; _currentAnimSpeed = __instance.m_speedFactor; _attackStartTime = Time.time; } } } [HarmonyPatch(typeof(PlayerController), "FixedUpdate")] public static class Input_AntiSpam_Patch { public static void Postfix(PlayerController __instance) { //IL_0295: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance.m_character != (Object)(object)Player.m_localPlayer || !Player.m_localPlayer.HasActiveMagicEffect("PerfectStrike", out var _)) { return; } if (!((Character)Player.m_localPlayer).InAttack()) { _clicked = false; _windowSFXplayed = false; if ((Object)(object)_windowVFXInstance != (Object)null) { Object.Destroy((Object)(object)_windowVFXInstance); _windowVFXInstance = null; } return; } float value = 0f; float num = 0f; float num2 = 0f; bool flag = _currentAnim != null && _attackDeltas.TryGetValue((_currentAnim, _currentChain, "default"), out value); if (flag) { num = value * 0.5f; num2 = value * 0.75f; } float num3 = Time.time - _attackStartTime; bool flag2 = _currentChain == 0 && _currentAnim != null && !_currentAnim.Contains("secondary"); if (flag && !flag2) { bool flag3 = num3 >= num && num3 <= num2; if (flag3 && (Object)(object)_windowVFXInstance == (Object)null) { if ((Object)(object)_cleanVFXPrefab == (Object)null) { GameObject prefab = ZNetScene.instance.GetPrefab("vfx_kiln_addore"); if ((Object)(object)prefab != (Object)null) { bool activeSelf = prefab.activeSelf; prefab.SetActive(false); _cleanVFXPrefab = Object.Instantiate(prefab); prefab.SetActive(activeSelf); Object.DestroyImmediate((Object)(object)_cleanVFXPrefab.GetComponent()); } } if ((Object)(object)_cleanVFXPrefab != (Object)null) { GameObject rightItemInstance = ((Humanoid)Player.m_localPlayer).m_visEquipment.m_rightItemInstance; Transform val = (((Object)(object)rightItemInstance != (Object)null) ? rightItemInstance.transform : ((Humanoid)Player.m_localPlayer).m_visEquipment.m_rightHand); _windowVFXInstance = Object.Instantiate(_cleanVFXPrefab, val); _windowVFXInstance.SetActive(true); _windowVFXInstance.transform.localPosition = Vector3.zero; } } else if (!flag3 && (Object)(object)_windowVFXInstance != (Object)null) { Object.Destroy((Object)(object)_windowVFXInstance); _windowVFXInstance = null; } } if (ZInput.GetButtonDown("Attack")) { _nowClicked = Time.time; if (!_clicked) { _clicked = true; if (flag) { isPerfectStrike = num3 >= num && num3 <= num2; } } else { _ = isPerfectStrike; } } if (isPerfectStrike && !_windowSFXplayed && !flag2) { GameObject prefab2 = ZNetScene.instance.GetPrefab("sfx_perfectblock"); if ((Object)(object)prefab2 != (Object)null) { Object.Instantiate(prefab2, ((Component)Player.m_localPlayer).transform).transform.localPosition = Vector3.zero; _windowSFXplayed = true; } } if (Time.time - _nowClicked >= _clickSpam) { _clicked = false; } } } [HarmonyPatch(typeof(Character), "Damage")] public static class PerfectStrike_Damage_Patch { public static void Postfix(Character __instance, HitData hit) { if (_perfectDamagePending) { ((DamageTypes)(ref hit.m_damage)).Modify(Player.m_localPlayer.GetTotalActiveMagicEffectValue("PerfectStrike", 0.01f)); _perfectDamagePending = false; _windowSFXplayed = false; } } } private static float _attackStartTime; private static float _clickSpam = 0.25f; private static float _nowClicked; private static bool _clicked = false; private static bool _windowSFXplayed = false; private static int _lastPerfectChain = 0; private static bool isPerfectStrike = false; private static bool _perfectDamagePending = false; private static Attack _currentAttack; private static string _currentAnim; private static int _currentChain; private static float _currentAnimSpeed; private static GameObject _cleanVFXPrefab; private static GameObject _windowVFXInstance; private static readonly Dictionary<(string, int, string), float> _attackDeltas = new Dictionary<(string, int, string), float> { { ("battleaxe_attack", 0, "default"), 0.878f }, { ("battleaxe_attack", 0, "fast"), 0.599f }, { ("battleaxe_attack", 1, "default"), 1.04f }, { ("battleaxe_attack", 1, "fast"), 0.761f }, { ("battleaxe_attack", 2, "default"), 1.304f }, { ("battleaxe_attack", 2, "fast"), 1.078f }, { ("battleaxe_secondary", 0, "default"), 0.843f }, { ("greatsword", 0, "default"), 1.642f }, { ("greatsword", 0, "fast"), 1.258f }, { ("greatsword", 1, "default"), 0.839f }, { ("greatsword", 1, "fast"), 0.579f }, { ("greatsword", 2, "default"), 0.958f }, { ("greatsword", 2, "fast"), 0.682f }, { ("greatsword_secondary", 0, "default"), 2.125f }, { ("greatsword_secondary", 0, "fast"), 1.703f } }; } public static class Retaliation { [HarmonyPatch(typeof(Humanoid), "BlockAttack")] public class Block_Attack_Retaliate_Patch { public static void Postfix(Humanoid __instance, bool __result) { if (Player.m_localPlayer.HasActiveMagicEffect("Retaliation", out var _) && __result && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { SEMan sEMan = ((Character)__instance).GetSEMan(); SE_Retaliation sE_Retaliation = sEMan.GetStatusEffect(StringExtensionMethods.GetStableHashCode("SE_Retaliation")) as SE_Retaliation; if ((Object)(object)sE_Retaliation != (Object)null) { sE_Retaliation.AddStack(); } else { (sEMan.AddStatusEffect(StringExtensionMethods.GetStableHashCode("SE_Retaliation"), false, 0, 0f) as SE_Retaliation).AddStack(); } } } } [HarmonyPatch(typeof(Attack), "Start")] public class Humanoid_Attack_Patch { private static void Prefix(Attack __instance, Humanoid character) { if (!((Object)(object)character != (Object)(object)Player.m_localPlayer) && Player.m_localPlayer.HasActiveMagicEffect("Retaliation", out var _)) { AttackType = __instance.m_attackAnimation; } } } public static string AttackType; } public static class Whirlwind { [HarmonyPatch(typeof(CharacterAnimEvent), "CustomFixedUpdate")] [HarmonyPriority(0)] public static class Whirlwind_CharAnim_CFU_Patch { public static void Postfix(CharacterAnimEvent __instance) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance.m_character == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)__instance.m_character != (Object)(object)Player.m_localPlayer) { return; } InWhirlwind = ItemDataExtensions.HasMagicEffect(((Humanoid)Player.m_localPlayer).GetCurrentWeapon(), "Whirlwind") && (Object)(object)__instance.m_character != (Object)null && __instance.m_character.InAttack() && __instance.m_character.m_secondaryAttackHold && ((Humanoid)__instance.m_character).m_currentAttack.m_attackAnimation == "atgeir_secondary"; if (InWhirlwind) { if (__instance.m_animator.speed > 1f && __instance.m_animator.speed != 10f) { overFlowAnimSpeed = Mathf.Clamp(__instance.m_animator.speed - 2f, 0f, 0.4f); } __instance.m_animator.speed = 10f; WhirlwindDamage = true; __instance.m_character.m_attack = false; __instance.m_character.m_attackHold = false; } else if (!__instance.m_character.InAttack()) { WhirlwindDamage = false; overFlowAnimSpeed = 0f; } } } [HarmonyPatch(typeof(Character), "Damage")] public static class Whirlwind_Damage_Patch { public static void Prefix(Character __instance, HitData hit) { if (!((Object)(object)hit.GetAttacker() != (Object)(object)Player.m_localPlayer) && InWhirlwind) { ((DamageTypes)(ref hit.m_damage)).Modify(0.4f * (1f + overFlowAnimSpeed)); hit.m_staggerMultiplier = 1f; } } } [HarmonyPatch(typeof(Attack), "GetAttackStamina")] public static class Whirlwind_Stamina_Patch { public static void Postfix(ref float __result) { if (InWhirlwind) { __result *= 0.3f; } } } private static bool InWhirlwind; private static bool WhirlwindDamage; private static float overFlowAnimSpeed; } } namespace PraetorisClient { internal static class BotApiClient { public static IEnumerator PostLinkRoutine(LinkRequest link, Action sendResult) { string linkApiUrl = PraetorisClientPlugin.GetLinkApiUrl(); string botApiKey = PraetorisClientPlugin.GetBotApiKey(); if (string.IsNullOrWhiteSpace(linkApiUrl)) { sendResult(link.Sender, link.RequestId, arg3: false, "Link API URL is not configured on the server."); yield break; } if (string.IsNullOrWhiteSpace(botApiKey)) { sendResult(link.Sender, link.RequestId, arg3: false, "Bot API key is not configured on the server."); yield break; } string s = JsonLinkRequest(link); byte[] bytes = Encoding.UTF8.GetBytes(s); UnityWebRequest request = new UnityWebRequest(linkApiUrl, "POST") { uploadHandler = (UploadHandler)new UploadHandlerRaw(bytes), downloadHandler = (DownloadHandler)new DownloadHandlerBuffer() }; try { request.SetRequestHeader("Content-Type", "application/json"); request.SetRequestHeader("X-API-Key", botApiKey); request.SetRequestHeader("User-Agent", "PraetorisClient/0.1"); yield return request.SendWebRequest(); string text = ((request.downloadHandler != null) ? request.downloadHandler.text : ""); if ((int)request.result != 1 || request.responseCode < 200 || request.responseCode >= 300) { string text2 = ((!string.IsNullOrWhiteSpace(text)) ? text : (string.IsNullOrWhiteSpace(request.error) ? ("HTTP " + request.responseCode) : (request.error + " (HTTP " + request.responseCode + ")"))); PraetorisClientPlugin.Log.LogWarning((object)("Discord link API failed for " + link.PlayerId + ": " + text2)); sendResult(link.Sender, link.RequestId, arg3: false, text2); } else { string arg = (string.IsNullOrWhiteSpace(text) ? "Discord link complete." : text); PraetorisClientPlugin.Log.LogInfo((object)("Discord link API accepted " + link.PlayerId + ".")); sendResult(link.Sender, link.RequestId, arg3: true, arg); } } finally { ((IDisposable)request)?.Dispose(); } } private static string JsonLinkRequest(LinkRequest link) { return "{\"requestId\":\"" + EscapeJson(link.RequestId) + "\",\"code\":\"" + EscapeJson(link.Code) + "\",\"playerId\":\"" + EscapeJson(link.PlayerId) + "\",\"playerName\":\"" + EscapeJson(link.PlayerName) + "\",\"endpoint\":\"" + EscapeJson(link.Endpoint) + "\",\"platformDisplayName\":\"" + EscapeJson(link.PlatformDisplayName) + "\",\"receivedAtUtc\":\"" + EscapeJson(link.ReceivedAtUtc.ToString("O", CultureInfo.InvariantCulture)) + "\"}"; } private static string EscapeJson(string value) { return (value ?? "").Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\r", "\\r") .Replace("\n", "\\n"); } } internal static class LinkCommandHandler { private static readonly Regex CodePattern = new Regex("^[A-Za-z0-9_-]{4,64}$", RegexOptions.Compiled); public static bool TryHandle(Chat chat) { string text = (((Object)(object)((Terminal)chat).m_input != (Object)null) ? ((TMP_InputField)((Terminal)chat).m_input).text : ""); if (string.IsNullOrWhiteSpace(text)) { return false; } string text2 = PraetorisClientPlugin.LinkCommand.Value.Trim(); if (string.IsNullOrWhiteSpace(text2)) { text2 = "!link"; } string text3 = text.Trim(); if (!text3.Equals(text2, StringComparison.OrdinalIgnoreCase) && !text3.StartsWith(text2 + " ", StringComparison.OrdinalIgnoreCase)) { return false; } string text4 = ((text3.Length > text2.Length) ? text3.Substring(text2.Length).Trim() : ""); if (!CodePattern.IsMatch(text4)) { ((Terminal)chat).AddString("Usage: " + text2 + " CODE"); ClearInput(chat); return true; } if (!LinkRpc.TrySendRequest(text4, out string message)) { ((Terminal)chat).AddString(message); ClearInput(chat); return true; } ((Terminal)chat).AddString("Sending Discord link code to the server."); ClearInput(chat); return true; } private static void ClearInput(Chat chat) { if (!((Object)(object)((Terminal)chat).m_input == (Object)null)) { ((TMP_InputField)((Terminal)chat).m_input).text = ""; ((Component)((Terminal)chat).m_input).gameObject.SetActive(false); } } } internal static class LinkRpc { public static bool TrySendRequest(string code, out string message) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Invalid comparison between Unknown and I4 //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown message = ""; if ((Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null || ZNet.instance.IsServer() || (int)ZNet.GetConnectionStatus() != 2) { message = "You must be connected to a server before linking Discord."; return false; } string text = Guid.NewGuid().ToString("N"); ZPackage val = new ZPackage(); val.Write(text); val.Write(code); ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "DiscordTools_LinkRequest", new object[1] { val }); return true; } public static void OnRequest(long sender, ZPackage pkg) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } string requestId = pkg.ReadString(); string code = pkg.ReadString(); ZNetPeer val = PlayerResolver.FindPeerBySender(sender); if (val == null) { SendResult(sender, requestId, success: false, "The server could not identify your Valheim connection."); return; } PraetorisClientPlugin instance = PraetorisClientPlugin.Instance; if ((Object)(object)instance == (Object)null) { SendResult(sender, requestId, success: false, "PraetorisClient is not ready on the server."); return; } LinkRequest link = new LinkRequest { Sender = sender, RequestId = requestId, Code = code, PlayerId = PlayerResolver.StablePlayerId(val), PlayerName = (val.m_playerName ?? ""), Endpoint = PlayerResolver.SafeEndPoint(val), PlatformDisplayName = PlayerResolver.PlatformDisplayName(val), ReceivedAtUtc = DateTime.UtcNow }; PraetorisClientPlugin.Log.LogInfo((object)("Received Discord link code from " + PlayerResolver.DescribePeer(val) + ".")); ((MonoBehaviour)instance).StartCoroutine(BotApiClient.PostLinkRoutine(link, SendResult)); } public static void OnResult(long sender, ZPackage pkg) { if (!((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer()) { string text = pkg.ReadString(); bool flag = pkg.ReadBool(); string text2 = pkg.ReadString(); PraetorisClientPlugin.Log.LogInfo((object)("Discord link result " + text + ": " + text2)); Chat instance = Chat.instance; if (instance != null) { ((Terminal)instance).AddString(flag ? text2 : ("Discord link failed: " + text2)); } } } private static void SendResult(long target, string requestId, bool success, string message) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(requestId); val.Write(success); val.Write(message); ZRoutedRpc.instance.InvokeRoutedRPC(target, "DiscordTools_LinkResult", new object[1] { val }); } } internal sealed class LinkRequest { public long Sender; public string RequestId = ""; public string Code = ""; public string PlayerId = ""; public string PlayerName = ""; public string Endpoint = ""; public string PlatformDisplayName = ""; public DateTime ReceivedAtUtc; } internal static class PlayerResolver { public static List FindPeers(string query) { List list = new List(); if ((Object)(object)ZNet.instance == (Object)null) { return list; } string text = Normalize(query); foreach (ZNetPeer connectedPeer in ZNet.instance.GetConnectedPeers()) { if (connectedPeer.IsReady()) { string value = SafeHostName(connectedPeer); string value2 = StablePlayerId(connectedPeer); if (Normalize(connectedPeer.m_playerName) == text || Normalize(value) == text || Normalize(value2) == text || (DigitsOnly(value) == DigitsOnly(query) && DigitsOnly(query).Length > 0)) { list.Add(connectedPeer); } } } if (list.Count > 0) { return list; } foreach (ZNetPeer connectedPeer2 in ZNet.instance.GetConnectedPeers()) { if (connectedPeer2.IsReady() && Normalize(connectedPeer2.m_playerName).Contains(text)) { list.Add(connectedPeer2); } } return list; } public static ZNetPeer? FindPeerBySender(long sender) { if ((Object)(object)ZNet.instance == (Object)null) { return null; } return ((IEnumerable)ZNet.instance.GetConnectedPeers()).FirstOrDefault((Func)((ZNetPeer peer) => peer.m_uid == sender)); } public static bool TryGetPeerPlayerId(ZNetPeer peer, out long playerId) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) playerId = 0L; if (peer == null || ZDOMan.instance == null || ((ZDOID)(ref peer.m_characterID)).IsNone()) { return false; } ZDO zDO = ZDOMan.instance.GetZDO(peer.m_characterID); if (zDO == null) { return false; } playerId = zDO.GetLong(ZDOVars.s_playerID, 0L); return playerId != 0; } public static bool TryGetSenderPlayerId(long sender, out long playerId, out ZNetPeer? peer) { playerId = 0L; peer = FindPeerBySender(sender); if (peer != null) { return TryGetPeerPlayerId(peer, out playerId); } if (sender == ZNet.GetUID() && (Object)(object)Game.instance != (Object)null) { playerId = Game.instance.GetPlayerProfile().GetPlayerID(); return playerId != 0; } return false; } public static string DescribePeer(ZNetPeer peer) { return peer.m_playerName + " (" + StablePlayerId(peer) + ")"; } public static string StablePlayerId(ZNetPeer peer) { string text = SafeHostName(peer); if (!string.IsNullOrWhiteSpace(text)) { return text; } return peer.m_uid.ToString(CultureInfo.InvariantCulture); } public static string SafeHostName(ZNetPeer peer) { try { ISocket socket = peer.m_socket; return ((socket != null) ? socket.GetHostName() : null) ?? ""; } catch { return ""; } } public static string SafeEndPoint(ZNetPeer peer) { try { ISocket socket = peer.m_socket; return ((socket != null) ? socket.GetEndPointString() : null) ?? ""; } catch { return ""; } } public static string PlatformDisplayName(ZNetPeer peer) { try { string value; return (peer.m_serverSyncedPlayerData != null && peer.m_serverSyncedPlayerData.TryGetValue("platformDisplayName", out value)) ? value : ""; } catch { return ""; } } private static string Normalize(string value) { return (value ?? "").Trim().ToLowerInvariant(); } private static string DigitsOnly(string value) { return new string((value ?? "").Where(char.IsDigit).ToArray()); } } [BepInPlugin("warpalicious.PraetorisClient", "PraetorisClient", "0.1.55")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class PraetorisClientPlugin : BaseUnityPlugin { private const string ModName = "PraetorisClient"; private const string ModVersion = "0.1.55"; private const string Author = "warpalicious"; private const string ModGUID = "warpalicious.PraetorisClient"; private const string LinkApiUrlEnv = "PRAETORISCLIENT_LINK_API_URL"; private const string BotApiKeyEnv = "PRAETORISCLIENT_BOT_API_KEY"; private readonly Harmony _harmony = new Harmony("warpalicious.PraetorisClient"); private DateTime _lastReloadTime; private FileSystemWatcher? _configWatcher; private const long ReloadDelayTicks = 10000000L; private static bool Loaded = false; public static readonly ManualLogSource Log = Logger.CreateLogSource("PraetorisClient"); internal static ConfigEntry LinkApiUrl = null; internal static ConfigEntry BotApiKey = null; internal static ConfigEntry LinkCommand = null; internal static ConfigEntry MetricMaxBatchRows = null; internal static ConfigEntry MetricBatchIntervalSeconds = null; internal static ConfigEntry NetworkMetricHttpUploadPreferred = null; internal static ConfigEntry SuppressEnvironmentDamageText = null; internal static ConfigEntry FrameMetricsEnabled = null; internal static ConfigEntry FrameMetricsSummaryIntervalSeconds = null; internal static ConfigEntry FrameMetricsLongFrameThresholdMs = null; internal static ConfigEntry FrameMetricsLogLongFrames = null; internal static ConfigEntry SocketMetricsEnabled = null; internal static ConfigEntry SocketMetricsSampleIntervalSeconds = null; internal static ConfigEntry SocketMetricsSendQueueBudgetBytes = null; internal static ConfigEntry RpcProbeEnabled = null; internal static ConfigEntry RpcProbeIntervalSeconds = null; internal static ConfigEntry RpcProbePayloadBytes = null; internal static ConfigEntry RpcProbeTimeoutSeconds = null; internal static ConfigEntry MeasurementDisableNetworkMetrics = null; internal static ConfigEntry MeasurementDisableNetworkMetricHttpUpload = null; internal static ConfigEntry DisableBoatWaterImpactDamage = null; internal static ConfigEntry CreatureOwnerWardRadius = null; internal static ConfigEntry CreatureOwnerWardUpdateIntervalSeconds = null; internal static ConfigEntry DebugCreatureOwnerWard = null; internal static ConfigEntry DebugServerChest = null; internal static ConfigEntry BlockPeerServerSyncConfigSync = null; internal static string TraceModGuid => "warpalicious.PraetorisClient"; internal static string TraceModName => "PraetorisClient"; internal static string TraceModVersion => "0.1.55"; public static PraetorisClientPlugin? Instance { get; private set; } internal static string GetLinkApiUrl() { string environmentVariable = Environment.GetEnvironmentVariable("PRAETORISCLIENT_LINK_API_URL"); if (!string.IsNullOrWhiteSpace(environmentVariable)) { return environmentVariable.Trim(); } return LinkApiUrl.Value; } internal static string GetBotApiKey() { string environmentVariable = Environment.GetEnvironmentVariable("PRAETORISCLIENT_BOT_API_KEY"); if (!string.IsNullOrWhiteSpace(environmentVariable)) { return environmentVariable.Trim(); } return BotApiKey.Value; } public void Awake() { PrefabManager.OnPrefabsRegistered += delegate { if (!Loaded) { HumanoidFactory.Create(); Loaded = true; } }; PrefabManager.OnPrefabsRegistered += delegate { InfusionVFX.Init(); }; MagicEffects.Init(); SERegistry.RegisterStatusEffects(); EpicLoot.RegisterAll(); Instance = this; BindConfig(); SynchronizationManager.OnConfigurationSynchronized += OnConfigurationSynchronized; CreatureOwnerWardPiece.Initialize(); CreatureOwnerWardCommand.Register(); ServerChestPiece.Initialize(); ServerChestCommand.Register(); if (Chainloader.PluginInfos.ContainsKey("org.tristan.rcon")) { ServerChestRconCommand.Register(); } SiegePortalTestCommand.Register(); FrameTimeMonitor.Initialize(); RpcTraceTelemetry.Initialize(); _harmony.PatchAll(Assembly.GetExecutingAssembly()); SocketMetricPatches.ApplyManualPatches(_harmony); SetupWatcher(); } private void Update() { if ((Object)(object)Game.instance == (Object)null) { RpcTraceTelemetry.BackgroundUpdate(); } } private void OnDestroy() { SynchronizationManager.OnConfigurationSynchronized -= OnConfigurationSynchronized; CreatureOwnerWardPiece.Shutdown(); ServerChestPiece.Shutdown(); try { _configWatcher?.Dispose(); _configWatcher = null; } catch (Exception ex) { Log.LogWarning((object)("Failed to dispose configuration watcher: " + ex.Message)); } try { RpcTraceTelemetry.Shutdown(); FrameTimeMonitor.Shutdown(); } catch (Exception ex2) { Log.LogWarning((object)("Failed to shut down telemetry: " + ex2.Message)); } try { ((BaseUnityPlugin)this).Config.Save(); } catch (Exception ex3) { Log.LogWarning((object)("Failed to save configuration during shutdown: " + ex3.Message)); } try { _harmony.UnpatchSelf(); } catch (Exception ex4) { Log.LogWarning((object)("Failed to unpatch PraetorisClient during shutdown: " + ex4.Message)); } if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } } private void BindConfig() { LinkApiUrl = ((BaseUnityPlugin)this).Config.Bind("BotApi", "LinkApiUrl", "", "Compatible bot Valheim link endpoint. Prefer the PRAETORISCLIENT_LINK_API_URL environment variable on dedicated servers."); BotApiKey = ((BaseUnityPlugin)this).Config.Bind("BotApi", "ApiKey", "", "API key sent to the bot in the X-API-Key header. Prefer the PRAETORISCLIENT_BOT_API_KEY environment variable on dedicated servers."); LinkCommand = ((BaseUnityPlugin)this).Config.Bind("Linking", "LinkCommand", "!link", "In-game chat command consumed before it is sent as chat."); MetricMaxBatchRows = ((BaseUnityPlugin)this).Config.Bind("NetworkMetrics", "MaxBatchRows", 250, SyncedDescription("Maximum probe/socket metric rows to write to one local gzip file before rotating it.")); MetricBatchIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind("NetworkMetrics", "BatchIntervalSeconds", 10f, SyncedDescription("Maximum seconds to keep a local probe/socket metric gzip file open before rotating it.")); NetworkMetricHttpUploadPreferred = ((BaseUnityPlugin)this).Config.Bind("NetworkMetrics", "HttpUploadPreferred", true, SyncedDescription("Uses ValheimTracer-issued HTTP upload tokens to deliver probe/socket metric batches when the server supports it.")); SuppressEnvironmentDamageText = ((BaseUnityPlugin)this).Config.Bind("Network", "SuppressEnvironmentDamageText", true, "Suppresses low-value environment damage text from AoE damage to pieces and non-player vegetation damage while preserving character combat damage text."); FrameMetricsEnabled = ((BaseUnityPlugin)this).Config.Bind("FrameMetrics", "Enabled", true, "Writes client frame-time summaries to BepInEx/logs/PraetorisClient/FrameMetrics."); FrameMetricsSummaryIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind("FrameMetrics", "SummaryIntervalSeconds", 30f, "Seconds per frame metrics summary window."); FrameMetricsLongFrameThresholdMs = ((BaseUnityPlugin)this).Config.Bind("FrameMetrics", "LongFrameThresholdMs", 150f, "Frame duration counted as a long frame."); FrameMetricsLogLongFrames = ((BaseUnityPlugin)this).Config.Bind("FrameMetrics", "LogLongFrames", true, "Writes individual long-frame rows to CSV."); SocketMetricsEnabled = ((BaseUnityPlugin)this).Config.Bind("SocketMetrics", "Enabled", true, SyncedDescription("Writes client socket queue and connection-quality metric samples to local metric files.")); SocketMetricsSampleIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind("SocketMetrics", "SampleIntervalSeconds", 5f, "Seconds per client socket metrics sample window."); SocketMetricsSendQueueBudgetBytes = ((BaseUnityPlugin)this).Config.Bind("SocketMetrics", "SendQueueBudgetBytes", 0, SyncedDescription("Socket send queue budget in bytes for skip/headroom metrics. Set to 0 to auto-detect VBNetTweaks ZDOQueueLimit, falling back to Valheim's vanilla 10240 bytes.")); RpcProbeEnabled = ((BaseUnityPlugin)this).Config.Bind("RpcProbe", "Enabled", true, SyncedDescription("Enables active client-to-server-to-client RPC latency probes.")); RpcProbeIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind("RpcProbe", "IntervalSeconds", 2f, "Seconds between active RPC probe requests from this client."); RpcProbePayloadBytes = ((BaseUnityPlugin)this).Config.Bind("RpcProbe", "PayloadBytes", 128, "Synthetic payload bytes included in each active RPC probe."); RpcProbeTimeoutSeconds = ((BaseUnityPlugin)this).Config.Bind("RpcProbe", "TimeoutSeconds", 10f, "Seconds before a pending active RPC probe is recorded as timed out."); MeasurementDisableNetworkMetrics = ((BaseUnityPlugin)this).Config.Bind("Measurement", "DisableNetworkMetrics", false, "Local measurement override. When true, disables PraetorisClient RPC probe and socket metric capture even if synced config enables it."); MeasurementDisableNetworkMetricHttpUpload = ((BaseUnityPlugin)this).Config.Bind("Measurement", "DisableNetworkMetricHttpUpload", false, "Local measurement override. When true, keeps network metrics on disk and does not upload them over HTTP."); DisableBoatWaterImpactDamage = ((BaseUnityPlugin)this).Config.Bind("Ships", "DisableBoatWaterImpactDamage", true, SyncedDescription("Prevents boats from losing health when Valheim's water-force impact handling applies boat impact damage. Other boat damage sources still apply normally.")); BlockPeerServerSyncConfigSync = ((BaseUnityPlugin)this).Config.Bind("ServerSyncProtection", "BlockPeerServerSyncConfigSync", true, SyncedDescription("Blocks outgoing ServerSync config packets so Praetoris clients do not publish client-to-client config changes.")); CreatureOwnerWardRadius = ((BaseUnityPlugin)this).Config.Bind("CreatureOwnerWard", "Radius", 40f, SyncedDescription("Meters around an active Creature Owner Ward where monster ZDO ownership is assigned to the configured connected player.")); CreatureOwnerWardUpdateIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind("CreatureOwnerWard", "UpdateIntervalSeconds", 2f, SyncedDescription("Seconds between active Creature Owner Ward reassignment checks.")); DebugCreatureOwnerWard = ((BaseUnityPlugin)this).Config.Bind("CreatureOwnerWard", "Debug", false, SyncedDescription("When true, logs Creature Owner Ward owner resolution and creature ownership changes.")); DebugServerChest = ((BaseUnityPlugin)this).Config.Bind("ServerChest", "Debug", false, SyncedDescription("When true, logs ServerChest registration, delivery, command, and ZDO save details.")); } private static ConfigDescription SyncedDescription(string description) { //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_000d: Expected O, but got Unknown //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown ConfigurationManagerAttributes val = new ConfigurationManagerAttributes { IsAdminOnly = true }; return new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { val }); } private static void OnConfigurationSynchronized(object sender, ConfigurationSynchronizationEventArgs args) { if (args.UpdatedPluginGUIDs != null && args.UpdatedPluginGUIDs.Contains("warpalicious.PraetorisClient")) { string text = (args.InitialSynchronization ? "initial" : "updated"); Log.LogInfo((object)("Jotunn synchronized PraetorisClient configuration (" + text + ").")); } } private void SetupWatcher() { try { _lastReloadTime = DateTime.Now; _configWatcher?.Dispose(); _configWatcher = new FileSystemWatcher(Paths.ConfigPath, "warpalicious.PraetorisClient.cfg"); _configWatcher.Changed += ReadConfigValues; _configWatcher.Created += ReadConfigValues; _configWatcher.Renamed += ReadConfigValues; _configWatcher.IncludeSubdirectories = true; _configWatcher.EnableRaisingEvents = true; } catch (Exception ex) { Log.LogWarning((object)("Failed to start configuration watcher: " + ex.Message)); } } private void ReadConfigValues(object sender, FileSystemEventArgs e) { DateTime now = DateTime.Now; long num = now.Ticks - _lastReloadTime.Ticks; if (File.Exists(Path.Combine(Paths.ConfigPath, "warpalicious.PraetorisClient.cfg")) && num >= 10000000) { try { Log.LogInfo((object)"Reloading configuration."); ((BaseUnityPlugin)this).Config.Reload(); } catch (Exception ex) { Log.LogError((object)("Failed to reload configuration: " + ex.Message)); } _lastReloadTime = now; } } } internal static class CreativeBiomeOverride { private sealed class OverrideZone { public Vector3 Center { get; } public float Radius { get; } public float RadiusSquared { get; } public float TerrainOuterRadius { get; } public float TerrainOuterRadiusSquared { get; } public float TerrainEdgeFalloffWidth { get; } public float TerrainEdgeFloorHeight { get; } public float VisualRadius { get; } public float VisualRadiusSquared { get; } public float TerrainPatchHalfSize { get; } public float GrassResetRadius { get; } public Biome Biome { get; } public bool SuppressSpawns { get; } public bool UseTerrainSource { get; } public Vector3 TerrainSourceCenter { get; } public bool SuppressVegetationDrops { get; } public OverrideZone(Vector3 center, float radius, Biome biome, bool suppressSpawns, bool useTerrainSource, Vector3 terrainSourceCenter, float terrainPatchHalfSize, float terrainEdgeFalloffWidth, float terrainEdgeFloorHeight, bool suppressVegetationDrops) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) Center = center; Radius = radius; RadiusSquared = radius * radius; TerrainOuterRadius = radius + Mathf.Max(0f, terrainEdgeFalloffWidth); TerrainOuterRadiusSquared = TerrainOuterRadius * TerrainOuterRadius; TerrainEdgeFalloffWidth = Mathf.Max(0f, terrainEdgeFalloffWidth); TerrainEdgeFloorHeight = terrainEdgeFloorHeight; VisualRadius = radius + 16f; VisualRadiusSquared = VisualRadius * VisualRadius; TerrainPatchHalfSize = (useTerrainSource ? Mathf.Max(32f, terrainPatchHalfSize) : radius); GrassResetRadius = (useTerrainSource ? (Mathf.Sqrt(2f) * (TerrainPatchHalfSize + 32f)) : radius); Biome = biome; SuppressSpawns = suppressSpawns; UseTerrainSource = useTerrainSource; TerrainSourceCenter = terrainSourceCenter; SuppressVegetationDrops = suppressVegetationDrops; } public bool Contains(float x, float z) { return ContainsRadius(x, z, RadiusSquared); } public bool ContainsTerrain(float x, float z) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) if (!UseTerrainSource) { return ContainsRadius(x, z, RadiusSquared); } Vector3 zonePos = ZoneSystem.GetZonePos(ZoneSystem.GetZone(new Vector3(x, 0f, z))); if (Mathf.Abs(zonePos.x - Center.x) <= TerrainPatchHalfSize && Mathf.Abs(zonePos.z - Center.z) <= TerrainPatchHalfSize) { return ContainsRadius(x, z, TerrainOuterRadiusSquared); } return false; } public bool IntersectsTerrain(Heightmap heightmap) { //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_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003e: 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_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) Vector3 position = ((Component)heightmap).transform.position; Vector3 zonePos = ZoneSystem.GetZonePos(ZoneSystem.GetZone(position)); if (Mathf.Abs(zonePos.x - Center.x) > TerrainPatchHalfSize || Mathf.Abs(zonePos.z - Center.z) > TerrainPatchHalfSize) { return false; } float num = (float)heightmap.m_width * heightmap.m_scale * 0.5f; float num2 = Math.Max(Math.Abs(position.x - Center.x) - num, 0f); float num3 = Math.Max(Math.Abs(position.z - Center.z) - num, 0f); return num2 * num2 + num3 * num3 <= TerrainOuterRadiusSquared; } private bool ContainsRadius(float x, float z, float radiusSquared) { //IL_0002: 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) float num = x - Center.x; float num2 = z - Center.z; return num * num + num2 * num2 <= radiusSquared; } public Vector2 MapToSource(float x, float z) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000d: 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_0026: 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) return new Vector2(TerrainSourceCenter.x + (x - Center.x), TerrainSourceCenter.z + (z - Center.z)); } public TerrainSample CreateTerrainSample(float x, float z) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) float num = DistanceFromCenter(x, z); float sourceWeight = 1f; if (TerrainEdgeFalloffWidth > 0f && num > Radius) { float num2 = Mathf.Clamp01((num - Radius) / TerrainEdgeFalloffWidth); sourceWeight = 1f - Mathf.SmoothStep(0f, 1f, num2); } return new TerrainSample(MapToSource(x, z), sourceWeight, TerrainEdgeFloorHeight); } public bool ContainsVisual(float x, float z) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) if (UseTerrainSource) { return ContainsTerrain(x, z); } float num = x - Center.x; float num2 = z - Center.z; return num * num + num2 * num2 <= VisualRadiusSquared; } public bool HasSameState(OverrideZone other) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) if (Approximately(Center, other.Center) && Mathf.Approximately(Radius, other.Radius) && Biome == other.Biome && SuppressSpawns == other.SuppressSpawns && UseTerrainSource == other.UseTerrainSource && Approximately(TerrainSourceCenter, other.TerrainSourceCenter) && Mathf.Approximately(TerrainPatchHalfSize, other.TerrainPatchHalfSize) && Mathf.Approximately(TerrainEdgeFalloffWidth, other.TerrainEdgeFalloffWidth) && Mathf.Approximately(TerrainEdgeFloorHeight, other.TerrainEdgeFloorHeight)) { return SuppressVegetationDrops == other.SuppressVegetationDrops; } return false; } private float DistanceFromCenter(float x, float z) { //IL_0002: 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) float num = x - Center.x; float num2 = z - Center.z; return Mathf.Sqrt(num * num + num2 * num2); } private static bool Approximately(Vector3 left, Vector3 right) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (Mathf.Approximately(left.x, right.x) && Mathf.Approximately(left.y, right.y)) { return Mathf.Approximately(left.z, right.z); } return false; } } private readonly struct TerrainSample { public Vector2 Source { get; } public float SourceWeight { get; } public float FloorHeight { get; } public TerrainSample(Vector2 source, float sourceWeight, float floorHeight) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) Source = source; SourceWeight = sourceWeight; FloorHeight = floorHeight; } public float ApplyHeight(float sourceHeight) { return Mathf.Lerp(FloorHeight, sourceHeight, SourceWeight); } } [HarmonyPatch(typeof(WorldGenerator), "GetBiome", new Type[] { typeof(float), typeof(float), typeof(float), typeof(bool) })] private static class WorldGeneratorGetBiomePatch { private static bool Prefix(float wx, float wy, ref Biome __result) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected I4, but got Unknown if (!TryGetBiome(wx, wy, out var biome)) { return true; } __result = (Biome)(int)biome; return false; } } [HarmonyPatch(typeof(Heightmap), "GetBiome", new Type[] { typeof(Vector3), typeof(float), typeof(bool) })] private static class HeightmapGetBiomePatch { private static bool Prefix(Vector3 point, ref Biome __result) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected I4, but got Unknown if (!TryGetBiome(point.x, point.z, out var biome)) { return true; } __result = (Biome)(int)biome; return false; } } [HarmonyPatch(typeof(WorldGenerator), "GetBiomeHeight")] private static class WorldGeneratorGetBiomeHeightPatch { private static bool Prefix(ref Biome biome, float wx, float wy, ref Color mask, bool preGeneration, ref float __result) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected I4, but got Unknown //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) if (!TryGetTerrainSample(wx, wy, out var sample) || WorldGenerator.instance == null) { return true; } _samplingSourceTerrain = true; try { biome = (Biome)(int)WorldGenerator.instance.GetBiome(sample.Source.x, sample.Source.y, 0.02f, false); __result = sample.ApplyHeight(WorldGenerator.instance.GetBiomeHeight(biome, sample.Source.x, sample.Source.y, ref mask, preGeneration)); } finally { _samplingSourceTerrain = false; } return false; } } [HarmonyPatch(typeof(WorldGenerator), "GetHeight", new Type[] { typeof(float), typeof(float) })] private static class WorldGeneratorGetHeightPatch { private static bool Prefix(float wx, float wy, ref float __result) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) if (!TryGetTerrainSample(wx, wy, out var sample) || WorldGenerator.instance == null) { return true; } _samplingSourceTerrain = true; try { __result = sample.ApplyHeight(WorldGenerator.instance.GetHeight(sample.Source.x, sample.Source.y)); } finally { _samplingSourceTerrain = false; } return false; } } [HarmonyPatch] private static class WorldGeneratorGetHeightWithMaskPatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(WorldGenerator), "GetHeight", new Type[3] { typeof(float), typeof(float), typeof(Color).MakeByRefType() }, (Type[])null); } private static bool Prefix(float wx, float wy, ref Color mask, ref float __result) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) if (!TryGetTerrainSample(wx, wy, out var sample) || WorldGenerator.instance == null) { return true; } _samplingSourceTerrain = true; try { __result = sample.ApplyHeight(WorldGenerator.instance.GetHeight(sample.Source.x, sample.Source.y, ref mask)); } finally { _samplingSourceTerrain = false; } return false; } } [HarmonyPatch(typeof(Heightmap), "GetBiomeColor", new Type[] { typeof(float), typeof(float) })] private static class HeightmapGetBiomeColorPatch { private static bool Prefix(Heightmap __instance, float ix, float iy, ref Color __result) { //IL_0026: 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_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null) { return true; } float num = (float)__instance.m_width * __instance.m_scale * 0.5f; Vector3 position = ((Component)__instance).transform.position; float x = position.x + (ix - 0.5f) * num * 2f; float z = position.z + (iy - 0.5f) * num * 2f; if (!TryGetVisualBiome(x, z, out var biome)) { return true; } __result = Color32.op_Implicit(Heightmap.GetBiomeColor(biome)); return false; } } [HarmonyPatch(typeof(WorldGenerator), "IsAshlands")] private static class WorldGeneratorIsAshlandsPatch { private static bool Prefix(float x, float y, ref bool __result) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 if (!TryGetBiome(x, y, out var biome)) { return true; } __result = (int)biome == 32; return false; } } [HarmonyPatch(typeof(WorldGenerator), "IsDeepnorth")] private static class WorldGeneratorIsDeepnorthPatch { private static bool Prefix(float x, float y, ref bool __result) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 if (!TryGetBiome(x, y, out var biome)) { return true; } __result = (int)biome == 64; return false; } } [HarmonyPatch(typeof(SpawnSystem), "IsSpawnPointGood")] private static class SpawnSystemIsSpawnPointGoodPatch { private static bool Prefix(ref Vector3 spawnPoint, ref bool __result) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) if (!ContainsSpawnBlockedZone(spawnPoint)) { return true; } __result = false; return false; } } private const int ProtocolVersion = 5; private const string ZdoVegetationMarker = "valheimCreative.vegetation"; private const string ZdoVegetationSlotId = "valheimCreative.vegetationSlot"; private const float VisualBiomeMargin = 16f; private static readonly Dictionary Zones = new Dictionary(); private static readonly FieldInfo? HeightmapBuildDataField = AccessTools.Field(typeof(Heightmap), "m_buildData"); [ThreadStatic] private static bool _samplingSourceTerrain; public static void OnOverride(long sender, ZPackage pkg) { //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: 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_01b0: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer()) { return; } try { int num = pkg.ReadInt(); if (num < 1 || num > 5) { PraetorisClientPlugin.Log.LogWarning((object)$"Ignoring creative biome override version {num}; expected 1-{5}."); return; } int num2 = pkg.ReadInt(); if (num2 <= 0) { ClearAll(); return; } for (int i = 0; i < num2; i++) { string text = pkg.ReadString(); bool num3 = pkg.ReadBool(); Vector3 center = pkg.ReadVector3(); float num4 = pkg.ReadSingle(); Biome val = (Biome)pkg.ReadInt(); bool suppressSpawns = ((num2 == 1 && pkg.GetPos() < pkg.Size()) ? pkg.ReadBool() : (!text.StartsWith("siege_", StringComparison.OrdinalIgnoreCase))); bool useTerrainSource = false; Vector3 terrainSourceCenter = Vector3.zero; if (num >= 2 && pkg.GetPos() < pkg.Size()) { useTerrainSource = pkg.ReadBool(); terrainSourceCenter = pkg.ReadVector3(); } float terrainPatchHalfSize = CalculateTerrainPatchHalfSize(num4); if (num >= 3 && pkg.GetPos() < pkg.Size()) { float num5 = pkg.ReadSingle(); if (num5 > 0f) { terrainPatchHalfSize = num5; } } float terrainEdgeFalloffWidth = 0f; float terrainEdgeFloorHeight = 0f; if (num >= 4 && pkg.GetPos() < pkg.Size()) { terrainEdgeFalloffWidth = Mathf.Max(0f, pkg.ReadSingle()); } if (num >= 4 && pkg.GetPos() < pkg.Size()) { terrainEdgeFloorHeight = pkg.ReadSingle(); } bool suppressVegetationDrops = !text.StartsWith("siege_", StringComparison.OrdinalIgnoreCase); if (num >= 5 && pkg.GetPos() < pkg.Size()) { suppressVegetationDrops = pkg.ReadBool(); } if (!num3 || (int)val == 0 || num4 <= 0f) { Remove(text); } else { Set(text, center, num4, val, suppressSpawns, useTerrainSource, terrainSourceCenter, terrainPatchHalfSize, terrainEdgeFalloffWidth, terrainEdgeFloorHeight, suppressVegetationDrops); } } } catch (Exception arg) { PraetorisClientPlugin.Log.LogWarning((object)$"Failed to apply creative biome override: {arg}"); } } public static bool TryGetBiome(float x, float z, out Biome biome) { //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected I4, but got Unknown //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected I4, but got Unknown if (_samplingSourceTerrain) { biome = (Biome)0; return false; } foreach (OverrideZone value in Zones.Values) { if (!value.ContainsTerrain(x, z)) { continue; } if (value.UseTerrainSource && WorldGenerator.instance != null) { Vector2 val = value.MapToSource(x, z); _samplingSourceTerrain = true; try { biome = (Biome)(int)WorldGenerator.instance.GetBiome(val.x, val.y, 0.02f, false); } finally { _samplingSourceTerrain = false; } return true; } if (!value.UseTerrainSource) { biome = (Biome)(int)value.Biome; return true; } } biome = (Biome)0; return false; } private static bool TryGetVisualBiome(float x, float z, out Biome biome) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected I4, but got Unknown foreach (OverrideZone value in Zones.Values) { if (value.ContainsVisual(x, z)) { biome = (Biome)(int)value.Biome; return true; } } biome = (Biome)0; return false; } private static bool TryGetTerrainSample(float x, float z, out TerrainSample sample) { sample = default(TerrainSample); if (_samplingSourceTerrain) { return false; } foreach (OverrideZone value in Zones.Values) { if (value.UseTerrainSource && value.ContainsTerrain(x, z)) { sample = value.CreateTerrainSample(x, z); return true; } } return false; } public static bool ContainsSpawnBlockedZone(Vector3 point) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) foreach (OverrideZone value in Zones.Values) { if (value.SuppressSpawns && value.Contains(point.x, point.z)) { return true; } } return false; } public static bool ShouldSuppressVegetationDrops(Component component) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)component == (Object)null) { return false; } return ShouldSuppressVegetationDrops(component, component.transform.position); } public static bool ShouldSuppressVegetationDrops(Component component, Vector3 point) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)component == (Object)null) { return false; } foreach (OverrideZone value in Zones.Values) { if (value.SuppressVegetationDrops && value.Contains(point.x, point.z)) { ZNetView val = component.GetComponent() ?? component.GetComponentInParent(); ZDO val2 = (((Object)(object)val != (Object)null) ? val.GetZDO() : null); if (val2 != null && IsMarkedCreativeVegetation(val2)) { return true; } return (Object)(object)component.GetComponent() != (Object)null || (Object)(object)component.GetComponent() != (Object)null || (Object)(object)component.GetComponent() != (Object)null || (Object)(object)component.GetComponent() != (Object)null || (Object)(object)component.GetComponent() != (Object)null || (Object)(object)component.GetComponent() != (Object)null; } } return false; } public static bool ContainsTerrainOverride(Vector3 point) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) foreach (OverrideZone value in Zones.Values) { if (value.UseTerrainSource && value.ContainsTerrain(point.x, point.z)) { return true; } } return false; } private static bool IsMarkedCreativeVegetation(ZDO zdo) { if (!zdo.GetBool("valheimCreative.vegetation", false)) { return !string.IsNullOrWhiteSpace(zdo.GetString("valheimCreative.vegetationSlot", "")); } return true; } private static void Set(string zoneId, Vector3 center, float radius, Biome biome, bool suppressSpawns, bool useTerrainSource, Vector3 terrainSourceCenter, float terrainPatchHalfSize, float terrainEdgeFalloffWidth, float terrainEdgeFloorHeight, bool suppressVegetationDrops) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) zoneId = NormalizeZoneId(zoneId); OverrideZone value; bool flag = Zones.TryGetValue(zoneId, out value); OverrideZone overrideZone = new OverrideZone(center, radius, biome, suppressSpawns, useTerrainSource, terrainSourceCenter, terrainPatchHalfSize, terrainEdgeFalloffWidth, terrainEdgeFloorHeight, suppressVegetationDrops); if (!flag || !value.HasSameState(overrideZone)) { Zones[zoneId] = overrideZone; if (flag) { RefreshTerrain(value); } RefreshTerrain(overrideZone); string text = (useTerrainSource ? $", terrainSource={terrainSourceCenter.x:0.##},{terrainSourceCenter.z:0.##}" : string.Empty); PraetorisClientPlugin.Log.LogInfo((object)$"Creative biome override {zoneId}: {biome} at {center.x:0.##},{center.z:0.##} radius {radius:0.##}, suppressSpawns={suppressSpawns}, suppressVegetationDrops={suppressVegetationDrops}{text}."); } } private static void Remove(string zoneId) { zoneId = NormalizeZoneId(zoneId); if (Zones.TryGetValue(zoneId, out OverrideZone value)) { Zones.Remove(zoneId); RefreshTerrain(value); PraetorisClientPlugin.Log.LogInfo((object)("Removed creative biome override " + zoneId + ".")); } } private static void ClearAll() { if (Zones.Count == 0) { return; } List list = new List(Zones.Values); Zones.Clear(); foreach (OverrideZone item in list) { RefreshTerrain(item); } PraetorisClientPlugin.Log.LogInfo((object)"Cleared creative biome overrides."); } private static void RefreshTerrain(OverrideZone zone) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) Heightmap[] array = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (Heightmap val in array) { if (!((Object)(object)val == (Object)null) && Intersects(val, zone)) { HeightmapBuildDataField?.SetValue(val, null); val.Poke(false); } } if ((Object)(object)ClutterSystem.instance != (Object)null) { ClutterSystem.instance.ResetGrass(zone.Center, zone.GrassResetRadius); } } private static bool Intersects(Heightmap heightmap, OverrideZone zone) { //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_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0055: 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) Vector3 position = ((Component)heightmap).transform.position; if (zone.UseTerrainSource) { return zone.IntersectsTerrain(heightmap); } float num = (float)heightmap.m_width * heightmap.m_scale * 0.5f; float num2 = Math.Max(Math.Abs(position.x - zone.Center.x) - num, 0f); float num3 = Math.Max(Math.Abs(position.z - zone.Center.z) - num, 0f); return num2 * num2 + num3 * num3 <= zone.RadiusSquared; } private static float CalculateTerrainPatchHalfSize(float radius) { float num = Mathf.Max(1f, radius); float num2 = Mathf.Ceil(Mathf.Max(0f, num - 32f) / 64f); return 32f + num2 * 64f; } private static string NormalizeZoneId(string zoneId) { if (!string.IsNullOrWhiteSpace(zoneId)) { return zoneId.Trim(); } return "creative"; } } internal static class CreativeCommandZoneState { private const int ProtocolVersion = 1; private const string DeniedMessage = "Creative commands can only be used inside your creative zone."; private static bool _active; private static Vector3 _center; private static float _radius; private static long _ownerPlayerId; private static long _playerId; private static string _slotId = string.Empty; private static bool _guardEnabled; private static string _protectedCommandPrefixes = string.Empty; internal static void OnState(long sender, ZPackage package) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer()) { return; } try { int num = package.ReadInt(); if (num != 1) { PraetorisClientPlugin.Log.LogWarning((object)$"Ignoring creative command zone state protocol {num}; expected {1}."); return; } bool flag = package.ReadBool(); Vector3 val = package.ReadVector3(); float num2 = package.ReadSingle(); long num3 = package.ReadLong(); long num4 = package.ReadLong(); string text = package.ReadString(); bool flag2 = package.GetPos() < package.Size() && package.ReadBool(); string text2 = ((package.GetPos() < package.Size()) ? package.ReadString() : string.Empty); if (!flag || !(num2 > 0f) || num4 == 0L || !_active || !Approximately(_center, val) || !Mathf.Approximately(_radius, num2) || _ownerPlayerId != num3 || _playerId != num4 || !string.Equals(_slotId, text ?? string.Empty, StringComparison.Ordinal) || _guardEnabled != flag2 || !string.Equals(_protectedCommandPrefixes, text2 ?? string.Empty, StringComparison.Ordinal)) { _guardEnabled = flag2; _protectedCommandPrefixes = text2 ?? string.Empty; if (!flag || num2 <= 0f || num4 == 0L) { Clear(); return; } _active = true; _center = val; _radius = num2; _ownerPlayerId = num3; _playerId = num4; _slotId = text ?? string.Empty; PraetorisClientPlugin.Log.LogInfo((object)$"Creative command zone active: {_slotId} at {_center.x:0.##},{_center.z:0.##} radius {_radius:0.##}."); } } catch (Exception arg) { Clear(); PraetorisClientPlugin.Log.LogWarning((object)$"Failed to apply creative command zone state: {arg}"); } } internal static void Clear() { //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) _active = false; _center = Vector3.zero; _radius = 0f; _ownerPlayerId = 0L; _playerId = 0L; _slotId = string.Empty; } internal static bool CanRunCommand(ConsoleEventArgs args) { if (args == null || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || !IsProtectedCommand(args.FullLine)) { return true; } if (IsLocalPlayerInsideActiveZone()) { return true; } object obj = ((object)args.Context) ?? ((object)Console.instance); if (obj != null) { ((Terminal)obj).AddString("Creative commands can only be used inside your creative zone."); } return false; } private static bool IsProtectedCommand(string rawCommand) { if (!_guardEnabled) { return false; } string text = NormalizeCommand(rawCommand); if (text.Length == 0) { return false; } foreach (string protectedCommandPrefix in GetProtectedCommandPrefixes()) { if (text.StartsWith(protectedCommandPrefix, StringComparison.Ordinal)) { return true; } } return false; } internal static bool IsLocalPlayerInsideActiveZone() { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if (!_active || (Object)(object)localPlayer == (Object)null || _radius <= 0f) { return false; } if (_playerId != 0L && localPlayer.GetPlayerID() != _playerId) { return false; } Vector3 position = ((Component)localPlayer).transform.position; float num = position.x - _center.x; float num2 = position.z - _center.z; return num * num + num2 * num2 <= _radius * _radius; } private static IEnumerable GetProtectedCommandPrefixes() { return from value in _protectedCommandPrefixes.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries) select value.Trim().ToLowerInvariant() into value where value.Length > 0 select value; } private static string NormalizeCommand(string rawCommand) { if (string.IsNullOrWhiteSpace(rawCommand)) { return string.Empty; } string[] array = rawCommand.Trim().Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length != 0) { return array[0].ToLowerInvariant(); } return string.Empty; } private static bool Approximately(Vector3 left, Vector3 right) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (Mathf.Approximately(left.x, right.x) && Mathf.Approximately(left.y, right.y)) { return Mathf.Approximately(left.z, right.z); } return false; } } internal static class CreativeInventoryRpc { private sealed class CreativeInventorySnapshot { public bool Available { get; set; } public string Error { get; set; } = string.Empty; public long PlayerId { get; set; } public string PlayerName { get; set; } = string.Empty; public int PlayerInventoryCount { get; set; } public bool ExtraSlotsLoaded { get; set; } public bool ExtraSlotsAvailable { get; set; } public int ExtraSlotsCount { get; set; } public int TotalUniqueCount { get; set; } public List Items { get; } = new List(); public static CreativeInventorySnapshot Unavailable(string error) { return new CreativeInventorySnapshot { Available = false, Error = error }; } } private sealed class CreativeInventoryItem { public string Source { get; set; } = string.Empty; public string PrefabName { get; set; } = string.Empty; public string SharedName { get; set; } = string.Empty; public int Stack { get; set; } public int Quality { get; set; } public bool Equipped { get; set; } public int GridX { get; set; } public int GridY { get; set; } public static CreativeInventoryItem FromItem(string source, ItemData item) { return new CreativeInventoryItem { Source = source, PrefabName = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : string.Empty), SharedName = (item.m_shared?.m_name ?? string.Empty), Stack = item.m_stack, Quality = item.m_quality, Equipped = item.m_equipped, GridX = item.m_gridPos.x, GridY = item.m_gridPos.y }; } } private sealed class ExtraSlotsReadResult { public bool ModLoaded { get; private set; } public bool Available { get; private set; } public string Error { get; private set; } = string.Empty; public List Items { get; } = new List(); public static ExtraSlotsReadResult NotLoaded() { return new ExtraSlotsReadResult { ModLoaded = false, Available = true }; } public static ExtraSlotsReadResult Loaded(IEnumerable items) { ExtraSlotsReadResult extraSlotsReadResult = new ExtraSlotsReadResult(); extraSlotsReadResult.ModLoaded = true; extraSlotsReadResult.Available = true; extraSlotsReadResult.Items.AddRange(items); return extraSlotsReadResult; } public static ExtraSlotsReadResult Failed(string error) { return new ExtraSlotsReadResult { ModLoaded = true, Available = false, Error = error }; } } private const int ProtocolVersion = 1; private const string ExtraSlotsApiTypeName = "ExtraSlots.API"; public static void OnRequest(long sender, ZPackage pkg) { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: 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_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer()) { return; } string text = string.Empty; ZDOID val = ZDOID.None; bool flag = true; try { int num = pkg.ReadInt(); if (num != 1) { SendUnavailable(sender, text, val, $"Unsupported creative inventory protocol version {num}."); return; } text = pkg.ReadString(); val = pkg.ReadZDOID(); flag = pkg.ReadBool(); CreativeInventorySnapshot snapshot = BuildSnapshot(val, flag); SendResponse(sender, text, val, snapshot); } catch (Exception arg) { PraetorisClientPlugin.Log.LogWarning((object)$"Failed to answer creative inventory request {text}: {arg}"); SendUnavailable(sender, text, val, "Failed to read client inventory."); } } private static CreativeInventorySnapshot BuildSnapshot(ZDOID expectedCharacterId, bool includeItems) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)((Character)localPlayer).m_nview == (Object)null || !((Character)localPlayer).m_nview.IsValid()) { return CreativeInventorySnapshot.Unavailable("Local player is not available."); } ZDO zDO = ((Character)localPlayer).m_nview.GetZDO(); if (zDO == null) { return CreativeInventorySnapshot.Unavailable("Local player character is not available."); } if (!((ZDOID)(ref expectedCharacterId)).IsNone() && zDO.m_uid != expectedCharacterId) { return CreativeInventorySnapshot.Unavailable("Local player character did not match the requested character."); } Inventory inventory = ((Humanoid)localPlayer).GetInventory(); if (inventory == null) { return CreativeInventorySnapshot.Unavailable("Local player inventory is not available."); } List list = (from item in inventory.GetAllItems() where item != null select item).ToList(); ExtraSlotsReadResult extraSlotsReadResult = ReadExtraSlotsItems(); if (!extraSlotsReadResult.Available && extraSlotsReadResult.ModLoaded) { return CreativeInventorySnapshot.Unavailable(extraSlotsReadResult.Error); } List list2 = new List(); AddUnique(list2, list); AddUnique(list2, extraSlotsReadResult.Items); CreativeInventorySnapshot creativeInventorySnapshot = new CreativeInventorySnapshot { Available = true, Error = string.Empty, PlayerId = localPlayer.GetPlayerID(), PlayerName = localPlayer.GetPlayerName(), PlayerInventoryCount = list.Count, ExtraSlotsAvailable = extraSlotsReadResult.Available, ExtraSlotsLoaded = extraSlotsReadResult.ModLoaded, ExtraSlotsCount = extraSlotsReadResult.Items.Count, TotalUniqueCount = list2.Count }; if (includeItems) { creativeInventorySnapshot.Items.AddRange(list.Select((ItemData item) => CreativeInventoryItem.FromItem("player", item))); creativeInventorySnapshot.Items.AddRange(extraSlotsReadResult.Items.Select((ItemData item) => CreativeInventoryItem.FromItem("extraSlots", item))); } return creativeInventorySnapshot; } private static ExtraSlotsReadResult ReadExtraSlotsItems() { Type type = (from assembly in AppDomain.CurrentDomain.GetAssemblies() select assembly.GetType("ExtraSlots.API", throwOnError: false)).FirstOrDefault((Type type2) => type2 != null); if (type == null) { return ExtraSlotsReadResult.NotLoaded(); } MethodInfo method = type.GetMethod("GetAllExtraSlotsItems", BindingFlags.Static | BindingFlags.Public); if (method == null) { return ExtraSlotsReadResult.Failed("ExtraSlots API did not expose GetAllExtraSlotsItems."); } try { if (!(method.Invoke(null, Array.Empty()) is IEnumerable enumerable)) { return ExtraSlotsReadResult.Failed("ExtraSlots API returned no item list."); } List list = new List(); foreach (object item in enumerable) { ItemData val = (ItemData)((item is ItemData) ? item : null); if (val != null) { list.Add(val); } } return ExtraSlotsReadResult.Loaded(list); } catch (Exception ex) { return ExtraSlotsReadResult.Failed("ExtraSlots API read failed: " + ex.Message); } } private static void AddUnique(List target, IEnumerable items) { foreach (ItemData item in items) { if (!target.Contains(item)) { target.Add(item); } } } private static void SendUnavailable(long target, string requestId, ZDOID characterId, string error) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) SendResponse(target, requestId, characterId, CreativeInventorySnapshot.Unavailable(error)); } private static void SendResponse(long target, string requestId, ZDOID characterId, CreativeInventorySnapshot snapshot) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //IL_0015: Unknown result type (might be due to invalid IL or missing references) ZPackage val = new ZPackage(); val.Write(1); val.Write(requestId); val.Write(characterId); val.Write(snapshot.Available); val.Write(snapshot.Error); val.Write(snapshot.PlayerId); val.Write(snapshot.PlayerName); val.Write(snapshot.PlayerInventoryCount); val.Write(snapshot.ExtraSlotsLoaded); val.Write(snapshot.ExtraSlotsAvailable); val.Write(snapshot.ExtraSlotsCount); val.Write(snapshot.TotalUniqueCount); val.Write(snapshot.Items.Count); foreach (CreativeInventoryItem item in snapshot.Items) { val.Write(item.Source); val.Write(item.PrefabName); val.Write(item.SharedName); val.Write(item.Stack); val.Write(item.Quality); val.Write(item.Equipped); val.Write(item.GridX); val.Write(item.GridY); } ZRoutedRpc.instance.InvokeRoutedRPC(target, "DiscordTools_CreativeInventoryResponse", new object[1] { val }); } } internal static class CreativeVegetationDropPatches { [HarmonyPatch] private static class TreeBaseRpcDamagePatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(TreeBase), "RPC_Damage", (Type[])null, (Type[])null); } private static void Prefix(TreeBase __instance, ref DropPatchState? __state) { __state = ReplaceDropsIfSuppressed((Component)(object)__instance, __instance.m_dropWhenDestroyed); if (__state != null) { __instance.m_dropWhenDestroyed = EmptyDropTable; } } private static void Postfix(TreeBase __instance, DropPatchState? __state) { if (__state != null) { __instance.m_dropWhenDestroyed = __state.DropTable; } } } [HarmonyPatch] private static class TreeBaseSpawnLogPatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(TreeBase), "SpawnLog", (Type[])null, (Type[])null); } private static bool Prefix(TreeBase __instance) { return !CreativeBiomeOverride.ShouldSuppressVegetationDrops((Component)(object)__instance); } } [HarmonyPatch] private static class TreeLogDestroyPatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(TreeLog), "Destroy", (Type[])null, (Type[])null); } private static void Prefix(TreeLog __instance, ref TreeLogPatchState? __state) { if (!CreativeBiomeOverride.ShouldSuppressVegetationDrops((Component)(object)__instance)) { __state = null; return; } __state = new TreeLogPatchState(__instance.m_dropWhenDestroyed, __instance.m_subLogPrefab); __instance.m_dropWhenDestroyed = EmptyDropTable; __instance.m_subLogPrefab = null; } private static void Postfix(TreeLog __instance, TreeLogPatchState? __state) { if (__state != null) { __instance.m_dropWhenDestroyed = __state.DropTable; __instance.m_subLogPrefab = __state.SubLogPrefab; } } } [HarmonyPatch] private static class PickableRpcPickPatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(Pickable), "RPC_Pick", (Type[])null, (Type[])null); } private static bool Prefix(Pickable __instance) { if (!CreativeBiomeOverride.ShouldSuppressVegetationDrops((Component)(object)__instance)) { return true; } ZNetView component = ((Component)__instance).GetComponent(); if ((Object)(object)component == (Object)null || !component.IsOwner()) { return true; } __instance.SetPicked(true); return false; } } [HarmonyPatch] private static class DropOnDestroyedPatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(DropOnDestroyed), "OnDestroyed", (Type[])null, (Type[])null); } private static bool Prefix(DropOnDestroyed __instance) { return !CreativeBiomeOverride.ShouldSuppressVegetationDrops((Component)(object)__instance); } } [HarmonyPatch] private static class MineRockRpcHitPatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(MineRock), "RPC_Hit", (Type[])null, (Type[])null); } private static void Prefix(MineRock __instance, HitData hit, ref DropPatchState? __state) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) __state = ReplaceDropsIfSuppressed((Component)(object)__instance, __instance.m_dropItems, hit.m_point); if (__state != null) { __instance.m_dropItems = EmptyDropTable; } } private static void Postfix(MineRock __instance, DropPatchState? __state) { if (__state != null) { __instance.m_dropItems = __state.DropTable; } } } [HarmonyPatch] private static class MineRock5DamageAreaPatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(MineRock5), "DamageArea", (Type[])null, (Type[])null); } private static void Prefix(MineRock5 __instance, HitData hit, ref DropPatchState? __state) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) __state = ReplaceDropsIfSuppressed((Component)(object)__instance, __instance.m_dropItems, hit.m_point); if (__state != null) { __instance.m_dropItems = EmptyDropTable; } } private static void Postfix(MineRock5 __instance, DropPatchState? __state) { if (__state != null) { __instance.m_dropItems = __state.DropTable; } } } private sealed class DropPatchState { internal DropTable DropTable { get; } internal DropPatchState(DropTable dropTable) { DropTable = dropTable; } } private sealed class TreeLogPatchState { internal DropTable DropTable { get; } internal GameObject SubLogPrefab { get; } internal TreeLogPatchState(DropTable dropTable, GameObject subLogPrefab) { DropTable = dropTable; SubLogPrefab = subLogPrefab; } } private static readonly DropTable EmptyDropTable = new DropTable { m_drops = new List(), m_dropMin = 0, m_dropMax = 0, m_dropChance = 0f }; private static DropPatchState? ReplaceDropsIfSuppressed(Component component, DropTable dropTable) { if (!CreativeBiomeOverride.ShouldSuppressVegetationDrops(component)) { return null; } return new DropPatchState(dropTable); } private static DropPatchState? ReplaceDropsIfSuppressed(Component component, DropTable dropTable, Vector3 point) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) if (!CreativeBiomeOverride.ShouldSuppressVegetationDrops(component, point)) { return null; } return new DropPatchState(dropTable); } } internal static class PraetorisClientRpc { private static ZRoutedRpc? _registeredRpc; public static void Register() { if (ZRoutedRpc.instance != null && _registeredRpc != ZRoutedRpc.instance) { _registeredRpc = ZRoutedRpc.instance; ZRoutedRpc.instance.Register("DiscordTools_LinkRequest", (Action)LinkRpc.OnRequest); ZRoutedRpc.instance.Register("DiscordTools_LinkResult", (Action)LinkRpc.OnResult); ZRoutedRpc.instance.Register("DiscordTools_CreativeInventoryRequest", (Action)CreativeInventoryRpc.OnRequest); ZRoutedRpc.instance.Register("DiscordTools_CreativeBiomeOverride", (Action)CreativeBiomeOverride.OnOverride); ZRoutedRpc.instance.Register("PraetorisClient_CreativeCommandZoneState", (Action)CreativeCommandZoneState.OnState); ZRoutedRpc.instance.Register("PraetorisClient_RpcTraceUploadTokenResponse", (Action)RpcTraceUploadTokenClient.OnTokenResponse); CreatureOwnerWardRpc.Register(ZRoutedRpc.instance); ServerChestRpc.Register(ZRoutedRpc.instance); PraetorisClientPlugin.Log.LogInfo((object)"Registered PraetorisClient RPC handlers."); } } } internal static class RpcNames { public const string LinkRequest = "DiscordTools_LinkRequest"; public const string LinkResult = "DiscordTools_LinkResult"; public const string CreativeInventoryRequest = "DiscordTools_CreativeInventoryRequest"; public const string CreativeInventoryResponse = "DiscordTools_CreativeInventoryResponse"; public const string CreativeBiomeOverride = "DiscordTools_CreativeBiomeOverride"; public const string CreativeCommandZoneState = "PraetorisClient_CreativeCommandZoneState"; public const string SiegePortalEnter = "DiscordTools_SiegePortalEnter"; public const string RpcProbeRequest = "PraetorisClient_RpcProbe_Request"; public const string RpcProbeForward = "PraetorisClient_RpcProbe_Forward"; public const string RpcProbeReply = "PraetorisClient_RpcProbe_Reply"; public const string RpcProbeAck = "PraetorisClient_RpcProbe_Ack"; public const string RpcTraceUploadTokenRequest = "PraetorisClient_RpcTraceUploadTokenRequest"; public const string RpcTraceUploadTokenResponse = "PraetorisClient_RpcTraceUploadTokenResponse"; public const string CreatureOwnerWardSetOwner = "PraetorisClient_CreatureOwnerWard_SetOwner"; public const string CreatureOwnerWardSetEnabled = "PraetorisClient_CreatureOwnerWard_SetEnabled"; public const string ServerChestRegisterRequest = "PraetorisClient_ServerChest_RegisterRequest"; public const string ServerChestRegisterResponse = "PraetorisClient_ServerChest_RegisterResponse"; public const string ServerChestCommandRequest = "PraetorisClient_ServerChest_CommandRequest"; public const string ServerChestCommandResponse = "PraetorisClient_ServerChest_CommandResponse"; } internal readonly struct SendZdoMetricState { internal long PeerUid { get; } internal string PlayerName { get; } internal int SentBefore { get; } internal long StartTicks { get; } internal bool IsValid { get; } internal SendZdoMetricState(long peerUid, string playerName, int sentBefore, long startTicks) { PeerUid = peerUid; PlayerName = playerName; SentBefore = sentBefore; StartTicks = startTicks; IsValid = true; } } internal static class ServerSyncProtection { private const string ServerSyncConfigSyncMethodSuffix = " ConfigSync"; internal static bool IsEnabledOnClient { get { ConfigEntry blockPeerServerSyncConfigSync = PraetorisClientPlugin.BlockPeerServerSyncConfigSync; if (blockPeerServerSyncConfigSync != null && blockPeerServerSyncConfigSync.Value && (Object)(object)ZNet.instance != (Object)null) { return !ZNet.instance.IsServer(); } return false; } } internal static bool ShouldBlockOutgoing(string methodName) { if (!IsEnabledOnClient || !IsServerSyncConfigSyncMethod(methodName)) { return false; } PraetorisClientPlugin.Log.LogInfo((object)("Blocked outgoing peer ServerSync config packet: " + methodName)); return true; } private static bool IsServerSyncConfigSyncMethod(string methodName) { if (!string.IsNullOrEmpty(methodName)) { return methodName.EndsWith(" ConfigSync", StringComparison.Ordinal); } return false; } } [HarmonyPatch(typeof(ZRoutedRpc), "InvokeRoutedRPC", new Type[] { typeof(long), typeof(string), typeof(object[]) })] internal static class ServerSyncProtectionOutgoingPatch { private static bool Prefix(string methodName) { return !ServerSyncProtection.ShouldBlockOutgoing(methodName); } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class ZNetAwakePatch { private static void Postfix() { CreativeCommandZoneState.Clear(); PraetorisClientRpc.Register(); } } [HarmonyPatch(typeof(Chat), "SendInput")] internal static class ChatSendInputPatch { private static bool Prefix(Chat __instance) { return !LinkCommandHandler.TryHandle(__instance); } } internal static class DamageTextSuppression { [ThreadStatic] private static int _suppressDepth; [ThreadStatic] private static int _aoeDamageDepth; [ThreadStatic] private static int _aoePieceDamageDepth; internal static bool IsSuppressing => _suppressDepth > 0; internal static bool IsAoeDamage => _aoeDamageDepth > 0; internal static bool IsAoePieceDamage => _aoePieceDamageDepth > 0; internal static void BeginSuppress() { _suppressDepth++; } internal static void EndSuppress() { if (_suppressDepth > 0) { _suppressDepth--; } } internal static void BeginAoeDamage() { _aoeDamageDepth++; } internal static void EndAoeDamage() { if (_aoeDamageDepth > 0) { _aoeDamageDepth--; } } internal static void BeginAoePieceDamage() { _aoePieceDamageDepth++; } internal static void EndAoePieceDamage() { if (_aoePieceDamageDepth > 0) { _aoePieceDamageDepth--; } } internal static bool BeginIf(bool shouldSuppress) { if (!shouldSuppress) { return false; } BeginSuppress(); return true; } internal static void EndIf(bool shouldSuppress) { if (shouldSuppress) { EndSuppress(); } } internal static bool ShouldSuppressAoePieceDamageText(HitData? hit) { if (PraetorisClientPlugin.SuppressEnvironmentDamageText.Value && hit != null) { if (!IsAoePieceDamage) { return hit.m_radius > 0f; } return true; } return false; } internal static bool ShouldSuppressNonPlayerVegetationDamageText(HitData? hit) { if (PraetorisClientPlugin.SuppressEnvironmentDamageText.Value && hit != null) { return !IsPlayerDamage(hit); } return false; } internal static bool BeginAoePieceDamageIfNeeded(HitData? hit) { if (!PraetorisClientPlugin.SuppressEnvironmentDamageText.Value || !IsAoeDamage || hit == null || hit.m_radius > 0f) { return false; } BeginAoePieceDamage(); return true; } private static bool IsPlayerDamage(HitData hit) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 if ((int)hit.m_hitType == 2) { return true; } Character attacker = hit.GetAttacker(); if ((Object)(object)attacker != (Object)null) { return attacker.IsPlayer(); } return false; } } [HarmonyPatch(typeof(DamageText), "ShowText", new Type[] { typeof(TextType), typeof(Vector3), typeof(string), typeof(bool) })] internal static class DamageTextShowTextEnvironmentSuppressionPatch { private static bool Prefix() { return !DamageTextSuppression.IsSuppressing; } } [HarmonyPatch(typeof(Projectile), "DoAOE")] internal static class ProjectileDoAoeDamageTextContextPatch { private static void Prefix(ref bool __state) { DamageTextSuppression.BeginAoeDamage(); __state = true; } private static void Finalizer(bool __state) { if (__state) { DamageTextSuppression.EndAoeDamage(); } } } [HarmonyPatch(typeof(Attack), "DoAreaAttack")] internal static class AttackDoAreaAttackDamageTextContextPatch { private static void Prefix(ref bool __state) { DamageTextSuppression.BeginAoeDamage(); __state = true; } private static void Finalizer(bool __state) { if (__state) { DamageTextSuppression.EndAoeDamage(); } } } [HarmonyPatch(typeof(WearNTear), "Damage")] internal static class WearNTearDamageAoeMarkerPatch { private static void Prefix(HitData hit, ref bool __state) { __state = DamageTextSuppression.BeginAoePieceDamageIfNeeded(hit); } private static void Finalizer(bool __state) { if (__state) { DamageTextSuppression.EndAoePieceDamage(); } } } [HarmonyPatch(typeof(WearNTear), "RPC_Damage")] internal static class WearNTearRpcDamageTextSuppressionPatch { private static void Prefix(HitData hit, ref bool __state) { __state = DamageTextSuppression.BeginIf(DamageTextSuppression.ShouldSuppressAoePieceDamageText(hit)); } private static void Finalizer(bool __state) { DamageTextSuppression.EndIf(__state); } } [HarmonyPatch(typeof(TreeBase), "RPC_Damage")] internal static class TreeBaseRpcDamageTextSuppressionPatch { private static void Prefix(HitData hit, ref bool __state) { __state = DamageTextSuppression.BeginIf(DamageTextSuppression.ShouldSuppressNonPlayerVegetationDamageText(hit)); } private static void Finalizer(bool __state) { DamageTextSuppression.EndIf(__state); } } [HarmonyPatch(typeof(TreeLog), "RPC_Damage")] internal static class TreeLogRpcDamageTextSuppressionPatch { private static void Prefix(HitData hit, ref bool __state) { __state = DamageTextSuppression.BeginIf(DamageTextSuppression.ShouldSuppressNonPlayerVegetationDamageText(hit)); } private static void Finalizer(bool __state) { DamageTextSuppression.EndIf(__state); } } internal static class BoatWaterImpactDamage { internal static bool ShouldBlock(WearNTear wearNTear, HitData hit) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) if (!PraetorisClientPlugin.DisableBoatWaterImpactDamage.Value || (Object)(object)wearNTear == (Object)null || hit == null) { return false; } Ship component = ((Component)wearNTear).GetComponent(); if ((Object)(object)component == (Object)null || (Object)(object)hit.GetAttacker() != (Object)null || (int)hit.m_hitType != 0) { return false; } if (hit.m_damage.m_blunt <= 0f || !Mathf.Approximately(hit.m_damage.m_blunt, component.m_waterImpactDamage)) { return false; } if (hit.m_damage.m_slash != 0f || hit.m_damage.m_pierce != 0f || hit.m_damage.m_chop != 0f || hit.m_damage.m_pickaxe != 0f || hit.m_damage.m_fire != 0f || hit.m_damage.m_frost != 0f || hit.m_damage.m_lightning != 0f || hit.m_damage.m_poison != 0f || hit.m_damage.m_spirit != 0f) { return false; } Vector3 val = hit.m_dir - Vector3.up; if (((Vector3)(ref val)).sqrMagnitude > 0.001f) { return false; } return true; } } [HarmonyPatch(typeof(WearNTear), "Damage")] internal static class WearNTearDamageBoatWaterImpactPatch { [HarmonyPriority(800)] private static bool Prefix(WearNTear __instance, HitData hit) { return !BoatWaterImpactDamage.ShouldBlock(__instance, hit); } } [HarmonyPatch(typeof(Game), "Update")] internal static class GameUpdateTelemetryPatch { private static void Postfix() { FrameTimeMonitor.Update(); RpcTraceTelemetry.Update(); } } [HarmonyPatch(typeof(ConsoleCommand), "RunAction")] internal static class ConsoleCommandRunActionCreativeZoneGuardPatch { private static bool Prefix(ConsoleEventArgs args) { return CreativeCommandZoneState.CanRunCommand(args); } } [HarmonyPatch(typeof(Skills), "RaiseSkill")] internal static class SkillsRaiseSkillCreativeZonePatch { private static bool Prefix(Skills __instance) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && !((Object)(object)__instance != (Object)(object)((Character)localPlayer).GetSkills())) { return !CreativeCommandZoneState.IsLocalPlayerInsideActiveZone(); } return true; } } [HarmonyPatch(typeof(Player), "EdgeOfWorldKill")] internal static class PlayerEdgeOfWorldKillCreativePatch { private static bool Prefix(Player __instance) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)__instance == (Object)null)) { return !CreativeBiomeOverride.ContainsTerrainOverride(((Component)__instance).transform.position); } return true; } } internal static class SiegePortalBridge { private const int ProtocolVersion = 1; internal const string SiegeIdZdoKey = "valheimCreativeSiegeId"; internal const string SiegeTagPrefix = "siege:"; internal static bool TryHandle(TeleportWorld portal, Player player) { //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Expected O, but got Unknown //IL_00d4: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || ZRoutedRpc.instance == null) { return false; } if ((Object)(object)player == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer) { return false; } ZNetView component = ((Component)portal).GetComponent(); ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); if (val == null || !TryGetSiegeId(val, out string siegeId)) { return false; } if ((Object)(object)((Character)player).m_nview == (Object)null || !((Character)player).m_nview.IsValid()) { ((Character)player).Message((MessageType)2, "Siege portal failed: character was not ready.", 0, (Sprite)null); return true; } ZDO zDO = ((Character)player).m_nview.GetZDO(); if (zDO == null) { ((Character)player).Message((MessageType)2, "Siege portal failed: character was not ready.", 0, (Sprite)null); return true; } ZPackage val2 = new ZPackage(); val2.Write(1); val2.Write(zDO.m_uid); val2.Write(siegeId); ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "DiscordTools_SiegePortalEnter", new object[1] { val2 }); ((Character)player).Message((MessageType)2, "Entering siege: " + siegeId, 0, (Sprite)null); PraetorisClientPlugin.Log.LogInfo((object)("Requested siege portal enter for " + siegeId + ".")); return true; } private static bool TryGetSiegeId(ZDO portalZdo, out string siegeId) { siegeId = portalZdo.GetString("valheimCreativeSiegeId", "").Trim(); if (!string.IsNullOrWhiteSpace(siegeId)) { return true; } string text = portalZdo.GetString(ZDOVars.s_tag, "").Trim(); if (text.StartsWith("siege:", StringComparison.OrdinalIgnoreCase)) { siegeId = text.Substring("siege:".Length).Trim(); return !string.IsNullOrWhiteSpace(siegeId); } return false; } } [HarmonyPatch(typeof(TeleportWorld), "Teleport")] internal static class TeleportWorldSiegePortalPatch { private static bool Prefix(TeleportWorld __instance, Player player) { return !SiegePortalBridge.TryHandle(__instance, player); } } internal static class SiegePortalTestCommand { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__1_0; public static ConsoleEvent <>9__1_1; internal void b__1_0(ConsoleEventArgs args) { //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) if (args.Length < 2) { args.Context.AddString("Usage: dt_mark_siege_portal [radius]"); return; } string text = args[1].Trim(); if (string.IsNullOrWhiteSpace(text)) { args.Context.AddString("siegeId is required."); return; } float result = 12f; if (args.Length >= 3) { float.TryParse(args[2], NumberStyles.Float, CultureInfo.InvariantCulture, out result); } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { args.Context.AddString("No local player found."); return; } float distance; TeleportWorld val = FindNearestPortal(((Component)localPlayer).transform.position, Mathf.Clamp(result, 1f, 50f), out distance); if ((Object)(object)val == (Object)null) { args.Context.AddString($"No portal found within {result:0.#}m."); return; } ZNetView component = ((Component)val).GetComponent(); ZDO val2 = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); if (val2 == null) { args.Context.AddString("Nearest portal has no valid ZDO."); return; } val2.Set("valheimCreativeSiegeId", text); val2.Set(ZDOVars.s_tag, "siege:" + text); args.Context.AddString($"Marked nearest portal {val2.m_uid} as siege {text} at {distance:0.#}m."); } internal void b__1_1(ConsoleEventArgs args) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) float result = 12f; if (args.Length >= 2) { float.TryParse(args[1], NumberStyles.Float, CultureInfo.InvariantCulture, out result); } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { args.Context.AddString("No local player found."); return; } float distance; TeleportWorld val = FindNearestPortal(((Component)localPlayer).transform.position, Mathf.Clamp(result, 1f, 50f), out distance); if ((Object)(object)val == (Object)null) { args.Context.AddString($"No portal found within {result:0.#}m."); return; } bool flag = SiegePortalBridge.TryHandle(val, localPlayer); args.Context.AddString($"Nearest siege portal handled={flag} distance={distance:0.#}m."); } } private static bool _registered; internal static void Register() { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected O, but got Unknown if (_registered) { return; } _registered = true; object obj = <>c.<>9__1_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) if (args.Length < 2) { args.Context.AddString("Usage: dt_mark_siege_portal [radius]"); } else { string text = args[1].Trim(); if (string.IsNullOrWhiteSpace(text)) { args.Context.AddString("siegeId is required."); } else { float result = 12f; if (args.Length >= 3) { float.TryParse(args[2], NumberStyles.Float, CultureInfo.InvariantCulture, out result); } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { args.Context.AddString("No local player found."); } else { float distance; TeleportWorld val3 = FindNearestPortal(((Component)localPlayer).transform.position, Mathf.Clamp(result, 1f, 50f), out distance); if ((Object)(object)val3 == (Object)null) { args.Context.AddString($"No portal found within {result:0.#}m."); } else { ZNetView component = ((Component)val3).GetComponent(); ZDO val4 = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); if (val4 == null) { args.Context.AddString("Nearest portal has no valid ZDO."); } else { val4.Set("valheimCreativeSiegeId", text); val4.Set(ZDOVars.s_tag, "siege:" + text); args.Context.AddString($"Marked nearest portal {val4.m_uid} as siege {text} at {distance:0.#}m."); } } } } } }; <>c.<>9__1_0 = val; obj = (object)val; } new ConsoleCommand("dt_mark_siege_portal", "Mark the nearest portal as a valheimCreative siege portal. Usage: dt_mark_siege_portal [radius]", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj2 = <>c.<>9__1_1; if (obj2 == null) { ConsoleEvent val2 = delegate(ConsoleEventArgs args) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) float result = 12f; if (args.Length >= 2) { float.TryParse(args[1], NumberStyles.Float, CultureInfo.InvariantCulture, out result); } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { args.Context.AddString("No local player found."); } else { float distance; TeleportWorld val3 = FindNearestPortal(((Component)localPlayer).transform.position, Mathf.Clamp(result, 1f, 50f), out distance); if ((Object)(object)val3 == (Object)null) { args.Context.AddString($"No portal found within {result:0.#}m."); } else { bool flag = SiegePortalBridge.TryHandle(val3, localPlayer); args.Context.AddString($"Nearest siege portal handled={flag} distance={distance:0.#}m."); } } }; <>c.<>9__1_1 = val2; obj2 = (object)val2; } new ConsoleCommand("dt_enter_nearest_siege_portal", "Enter the nearest marked valheimCreative siege portal. Usage: dt_enter_nearest_siege_portal [radius]", (ConsoleEvent)obj2, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } private static TeleportWorld? FindNearestPortal(Vector3 center, float radius, out float distance) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) distance = float.MaxValue; TeleportWorld result = null; Collider[] array = Physics.OverlapSphere(center, radius); for (int i = 0; i < array.Length; i++) { TeleportWorld componentInParent = ((Component)array[i]).GetComponentInParent(); if (!((Object)(object)componentInParent == (Object)null)) { float num = Vector3.Distance(center, ((Component)componentInParent).transform.position); if (num < distance) { distance = num; result = componentInParent; } } } return result; } } internal static class MetricsMath { internal static float Percentile(List samples, float percentile) { if (samples.Count == 0) { return 0f; } samples.Sort(); int num = (int)Math.Ceiling((float)samples.Count * percentile) - 1; if (num < 0) { num = 0; } if (num >= samples.Count) { num = samples.Count - 1; } return samples[num]; } internal static float Median(List samples) { if (samples.Count == 0) { return 0f; } samples.Sort(); int num = samples.Count / 2; if (samples.Count % 2 == 1) { return samples[num]; } return (samples[num - 1] + samples[num]) * 0.5f; } } internal sealed class TelemetryJson { private readonly StringBuilder _builder = new StringBuilder(); private readonly Stack _needsComma = new Stack(); private TelemetryJson() { } internal static TelemetryJson Object() { TelemetryJson telemetryJson = new TelemetryJson(); telemetryJson._builder.Append('{'); telemetryJson._needsComma.Push(item: false); return telemetryJson; } internal void End() { _builder.Append('}'); if (_needsComma.Count > 0) { _needsComma.Pop(); } } internal void BeginObject(string name) { Name(name); _builder.Append('{'); _needsComma.Push(item: false); } internal void EndObject() { End(); } internal void BeginArray(string name) { Name(name); _builder.Append('['); _needsComma.Push(item: false); } internal void EndArray() { _builder.Append(']'); if (_needsComma.Count > 0) { _needsComma.Pop(); } } internal void BeginArrayObject() { ArrayPrefix(); _builder.Append('{'); _needsComma.Push(item: false); } internal void EndArrayObject() { End(); } internal void ArrayString(string value) { ArrayPrefix(); WriteString(value); } internal void Prop(string name, string value) { Name(name); WriteString(value); } internal void Prop(string name, bool value) { Name(name); _builder.Append(value ? "true" : "false"); } internal void Prop(string name, int value) { Name(name); _builder.Append(value.ToString(CultureInfo.InvariantCulture)); } internal void Prop(string name, uint value) { Name(name); _builder.Append(value.ToString(CultureInfo.InvariantCulture)); } internal void Prop(string name, short value) { Name(name); _builder.Append(value.ToString(CultureInfo.InvariantCulture)); } internal void Prop(string name, ushort value) { Name(name); _builder.Append(value.ToString(CultureInfo.InvariantCulture)); } internal void Prop(string name, long value) { Name(name); _builder.Append(value.ToString(CultureInfo.InvariantCulture)); } internal void Prop(string name, float value) { Name(name); if (float.IsNaN(value) || float.IsInfinity(value)) { _builder.Append("null"); } else { _builder.Append(value.ToString("R", CultureInfo.InvariantCulture)); } } internal void Prop(string name, double value) { Name(name); if (double.IsNaN(value) || double.IsInfinity(value)) { _builder.Append("null"); } else { _builder.Append(value.ToString("R", CultureInfo.InvariantCulture)); } } internal void Prop(string name, Vector3 value) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) BeginObject(name); Prop("x", value.x); Prop("y", value.y); Prop("z", value.z); EndObject(); } internal void Prop(string name, Quaternion value) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) BeginObject(name); Prop("x", value.x); Prop("y", value.y); Prop("z", value.z); Prop("w", value.w); EndObject(); } public override string ToString() { return _builder.ToString(); } private void Name(string name) { if (_needsComma.Count == 0) { throw new InvalidOperationException("JSON object is not open."); } if (_needsComma.Peek()) { _builder.Append(','); } _needsComma.Pop(); _needsComma.Push(item: true); WriteString(name); _builder.Append(':'); } private void ArrayPrefix() { if (_needsComma.Count == 0) { throw new InvalidOperationException("JSON array is not open."); } if (_needsComma.Peek()) { _builder.Append(','); } _needsComma.Pop(); _needsComma.Push(item: true); } private void WriteString(string value) { _builder.Append('"'); string text = value ?? ""; foreach (char c in text) { switch (c) { case '"': _builder.Append("\\\""); continue; case '\\': _builder.Append("\\\\"); continue; case '\b': _builder.Append("\\b"); continue; case '\f': _builder.Append("\\f"); continue; case '\n': _builder.Append("\\n"); continue; case '\r': _builder.Append("\\r"); continue; case '\t': _builder.Append("\\t"); continue; } if (char.IsControl(c)) { StringBuilder stringBuilder = _builder.Append("\\u"); int num = c; stringBuilder.Append(num.ToString("x4", CultureInfo.InvariantCulture)); } else { _builder.Append(c); } } _builder.Append('"'); } } internal readonly struct TraceMethodSource { internal string PluginGuid { get; } internal string PluginName { get; } internal string PluginVersion { get; } internal string AssemblyName { get; } internal bool IsVanilla { get; } internal bool IsModded { get; } internal TraceMethodSource(string pluginGuid, string pluginName, string pluginVersion, string assemblyName, bool isVanilla, bool isModded) { PluginGuid = pluginGuid; PluginName = pluginName; PluginVersion = pluginVersion; AssemblyName = assemblyName; IsVanilla = isVanilla; IsModded = isModded; } } internal static class TraceRuntimeMetadata { private static readonly TraceMethodSource UnknownSource = new TraceMethodSource("", "", "", "", isVanilla: false, isModded: false); internal static string GetGameVersion() { try { return Version.GetVersionString(false); } catch { return Application.version ?? ""; } } internal static string BuildRuntimeId(string role) { return role + ":" + DateTime.UtcNow.ToString("yyyyMMddHHmmssfff") + ":" + Guid.NewGuid().ToString("N"); } internal static void WritePlugins(TelemetryJson json) { List runtimePlugins = GetRuntimePlugins(); json.BeginArray("plugins"); foreach (TraceMethodSource item in runtimePlugins) { json.BeginArrayObject(); json.Prop("pluginGuid", item.PluginGuid); json.Prop("pluginName", item.PluginName); json.Prop("pluginVersion", item.PluginVersion); json.Prop("assemblyName", item.AssemblyName); json.Prop("isVanilla", item.IsVanilla); json.EndArrayObject(); } json.EndArray(); } internal static TraceMethodSource GetMethodSource(Delegate? callback) { Assembly assembly = callback?.Method?.DeclaringType?.Assembly; if (assembly == null) { return UnknownSource; } string assemblyName = assembly.GetName().Name ?? ""; if (IsVanillaAssembly(assemblyName)) { return new TraceMethodSource("vanilla", "Valheim", GetGameVersion(), assemblyName, isVanilla: true, isModded: false); } foreach (PluginInfo value in Chainloader.PluginInfos.Values) { Assembly assembly2 = (((Object)(object)value.Instance != (Object)null) ? ((object)value.Instance).GetType().Assembly : null); if (!(assembly2 == null) && (object)assembly2 == assembly) { return new TraceMethodSource(value.Metadata.GUID ?? "", value.Metadata.Name ?? "", value.Metadata.Version?.ToString() ?? "", assemblyName, isVanilla: false, isModded: true); } } return new TraceMethodSource("", "", "", assemblyName, isVanilla: false, isModded: false); } private static List GetRuntimePlugins() { List list = new List { new TraceMethodSource("vanilla", "Valheim", GetGameVersion(), "assembly_valheim", isVanilla: true, isModded: false) }; foreach (PluginInfo value in Chainloader.PluginInfos.Values) { string assemblyName = (((Object)(object)value.Instance != (Object)null) ? (((object)value.Instance).GetType().Assembly.GetName().Name ?? "") : ""); list.Add(new TraceMethodSource(value.Metadata.GUID ?? "", value.Metadata.Name ?? "", value.Metadata.Version?.ToString() ?? "", assemblyName, isVanilla: false, isModded: true)); } list.Sort((TraceMethodSource left, TraceMethodSource right) => string.Compare(left.PluginGuid, right.PluginGuid, StringComparison.OrdinalIgnoreCase)); return list; } private static bool IsVanillaAssembly(string assemblyName) { if (!string.Equals(assemblyName, "assembly_valheim", StringComparison.OrdinalIgnoreCase)) { return string.Equals(assemblyName, "Valheim", StringComparison.OrdinalIgnoreCase); } return true; } } internal static class FrameTimeMonitor { private readonly struct FrameContext { internal bool Connected { get; } internal string WorldName { get; } internal float PositionX { get; } internal float PositionY { get; } internal float PositionZ { get; } internal FrameContext(bool connected, string worldName, float positionX, float positionY, float positionZ) { Connected = connected; WorldName = worldName ?? ""; PositionX = positionX; PositionY = positionY; PositionZ = positionZ; } } private static readonly object Sync = new object(); private static readonly List FrameSamplesMs = new List(4096); private static string _metricsDirectory = ""; private static string _summaryPath = ""; private static string _longFramePath = ""; private static StreamWriter? _summaryWriter; private static StreamWriter? _longFrameWriter; private static float _nextSummaryTime; private static int _longFrameCount; private static float _maxFrameMs; internal static void Initialize() { lock (Sync) { FrameSamplesMs.Clear(); _longFrameCount = 0; _maxFrameMs = 0f; _nextSummaryTime = 0f; CloseWritersLocked(); _metricsDirectory = Path.Combine(Paths.BepInExRootPath, "logs", "PraetorisClient", "FrameMetrics"); Directory.CreateDirectory(_metricsDirectory); string text = DateTime.UtcNow.ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture); _summaryPath = Path.Combine(_metricsDirectory, "frame_summary_" + text + ".csv"); _longFramePath = Path.Combine(_metricsDirectory, "frame_long_" + text + ".csv"); } } internal static void Shutdown() { lock (Sync) { FlushSummaryLocked(force: true); CloseWritersLocked(); } } internal static void Update() { if (!PraetorisClientPlugin.FrameMetricsEnabled.Value) { return; } float num = Time.unscaledDeltaTime * 1000f; float realtimeSinceStartup = Time.realtimeSinceStartup; float num2 = Math.Max(1f, PraetorisClientPlugin.FrameMetricsLongFrameThresholdMs.Value); lock (Sync) { EnsureWritersLocked(); FrameSamplesMs.Add(num); if (num > _maxFrameMs) { _maxFrameMs = num; } if (num >= num2) { _longFrameCount++; if (PraetorisClientPlugin.FrameMetricsLogLongFrames.Value) { WriteLongFrameLocked(num, num2); } } if (_nextSummaryTime <= 0f) { _nextSummaryTime = realtimeSinceStartup + GetSummaryIntervalSeconds(); } if (realtimeSinceStartup >= _nextSummaryTime) { FlushSummaryLocked(force: false); _nextSummaryTime = realtimeSinceStartup + GetSummaryIntervalSeconds(); } } } private static float GetSummaryIntervalSeconds() { return Math.Max(1f, PraetorisClientPlugin.FrameMetricsSummaryIntervalSeconds.Value); } private static void EnsureWritersLocked() { if (_summaryWriter == null) { Directory.CreateDirectory(_metricsDirectory); _summaryWriter = new StreamWriter(_summaryPath, append: true, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)) { AutoFlush = false }; _summaryWriter.WriteLine("timeUtc,realtimeSeconds,connected,worldName,positionX,positionY,positionZ,samples,fpsAverage,frameAverageMs,frameP50Ms,frameP95Ms,frameP99Ms,frameMaxMs,longFrameThresholdMs,longFrameCount"); } if (_longFrameWriter == null) { _longFrameWriter = new StreamWriter(_longFramePath, append: true, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)) { AutoFlush = false }; _longFrameWriter.WriteLine("timeUtc,realtimeSeconds,connected,worldName,positionX,positionY,positionZ,frameMs,thresholdMs"); } } private static void FlushSummaryLocked(bool force) { if (FrameSamplesMs.Count != 0 && (force || _summaryWriter != null)) { EnsureWritersLocked(); FrameSamplesMs.Sort(); float num = Average(FrameSamplesMs); float num2 = PercentileSorted(FrameSamplesMs, 0.5f); float num3 = PercentileSorted(FrameSamplesMs, 0.95f); float num4 = PercentileSorted(FrameSamplesMs, 0.99f); float num5 = Math.Max(1f, PraetorisClientPlugin.FrameMetricsLongFrameThresholdMs.Value); float num6 = ((num > 0f) ? (1000f / num) : 0f); FrameContext frameContext = CaptureContext(); string value = string.Join(",", DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture), Time.realtimeSinceStartup.ToString("F3", CultureInfo.InvariantCulture), frameContext.Connected ? "1" : "0", Csv(frameContext.WorldName), frameContext.PositionX.ToString("F2", CultureInfo.InvariantCulture), frameContext.PositionY.ToString("F2", CultureInfo.InvariantCulture), frameContext.PositionZ.ToString("F2", CultureInfo.InvariantCulture), FrameSamplesMs.Count.ToString(CultureInfo.InvariantCulture), num6.ToString("F1", CultureInfo.InvariantCulture), num.ToString("F2", CultureInfo.InvariantCulture), num2.ToString("F2", CultureInfo.InvariantCulture), num3.ToString("F2", CultureInfo.InvariantCulture), num4.ToString("F2", CultureInfo.InvariantCulture), _maxFrameMs.ToString("F2", CultureInfo.InvariantCulture), num5.ToString("F1", CultureInfo.InvariantCulture), _longFrameCount.ToString(CultureInfo.InvariantCulture)); _summaryWriter?.WriteLine(value); _summaryWriter?.Flush(); _longFrameWriter?.Flush(); PraetorisClientPlugin.Log.LogInfo((object)("Frame metrics summary: samples=" + FrameSamplesMs.Count + ", fpsAverage=" + num6.ToString("F1", CultureInfo.InvariantCulture) + ", p95Ms=" + num3.ToString("F1", CultureInfo.InvariantCulture) + ", p99Ms=" + num4.ToString("F1", CultureInfo.InvariantCulture) + ", maxMs=" + _maxFrameMs.ToString("F1", CultureInfo.InvariantCulture) + ", longFramesOver" + num5.ToString("F0", CultureInfo.InvariantCulture) + "ms=" + _longFrameCount + ".")); FrameSamplesMs.Clear(); _longFrameCount = 0; _maxFrameMs = 0f; } } private static void WriteLongFrameLocked(float frameMs, float thresholdMs) { FrameContext frameContext = CaptureContext(); string value = string.Join(",", DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture), Time.realtimeSinceStartup.ToString("F3", CultureInfo.InvariantCulture), frameContext.Connected ? "1" : "0", Csv(frameContext.WorldName), frameContext.PositionX.ToString("F2", CultureInfo.InvariantCulture), frameContext.PositionY.ToString("F2", CultureInfo.InvariantCulture), frameContext.PositionZ.ToString("F2", CultureInfo.InvariantCulture), frameMs.ToString("F2", CultureInfo.InvariantCulture), thresholdMs.ToString("F1", CultureInfo.InvariantCulture)); _longFrameWriter?.WriteLine(value); } private static FrameContext CaptureContext() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0060: 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_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) bool connected = (Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer() && (int)ZNet.GetConnectionStatus() == 2; string worldName = ((ZNet.m_world != null) ? ZNet.m_world.m_name : ""); Vector3 val = (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Component)Player.m_localPlayer).transform.position : Vector3.zero); return new FrameContext(connected, worldName, val.x, val.y, val.z); } private static float Average(List samples) { double num = 0.0; for (int i = 0; i < samples.Count; i++) { num += (double)samples[i]; } if (samples.Count <= 0) { return 0f; } return (float)(num / (double)samples.Count); } private static float PercentileSorted(List sortedSamples, float percentile) { if (sortedSamples.Count == 0) { return 0f; } if (sortedSamples.Count == 1) { return sortedSamples[0]; } float num = Mathf.Clamp01(percentile); float num2 = (float)(sortedSamples.Count - 1) * num; int num3 = Mathf.FloorToInt(num2); int num4 = Mathf.CeilToInt(num2); if (num3 == num4) { return sortedSamples[num3]; } float num5 = num2 - (float)num3; return Mathf.Lerp(sortedSamples[num3], sortedSamples[num4], num5); } private static string Csv(string value) { string text = value ?? ""; if (text.IndexOfAny(new char[4] { ',', '"', '\r', '\n' }) < 0) { return text; } return "\"" + text.Replace("\"", "\"\"") + "\""; } private static void CloseWritersLocked() { _summaryWriter?.Flush(); _summaryWriter?.Dispose(); _summaryWriter = null; _longFrameWriter?.Flush(); _longFrameWriter?.Dispose(); _longFrameWriter = null; } } internal static class ClientSocketMetrics { private sealed class PeerWindow { internal long PeerUid { get; } internal string PlayerName { get; set; } internal int AttemptCount { get; set; } internal int SocketSkipCount { get; set; } internal int FlushAttemptCount { get; set; } internal int SentZdoCount { get; set; } internal long ZdoDataBytes { get; set; } internal int ZdoDataPackages { get; set; } internal int LatestSendQueueBytes { get; set; } internal int LatestHeadroomBytes { get; set; } internal int MaxSendQueueBytes { get; set; } internal int MinHeadroomBytes { get; set; } internal int MaxKnownZdos { get; set; } internal int MaxForceSend { get; set; } internal int MaxInvalidSector { get; set; } internal float LastAttemptTime { get; set; } internal List SendDurationSamplesMs { get; } = new List(); internal List CadenceSamplesMs { get; } = new List(); internal PeerWindow(long peerUid, string playerName, int initialHeadroomBytes) { PeerUid = peerUid; PlayerName = playerName; LatestHeadroomBytes = initialHeadroomBytes; MinHeadroomBytes = initialHeadroomBytes; } internal PeerSample TakeSample(float windowSeconds) { List list = SendDurationSamplesMs.ToList(); List list2 = CadenceSamplesMs.ToList(); PeerSample result = new PeerSample(PeerUid, PlayerName, AttemptCount, SocketSkipCount, FlushAttemptCount, (float)SentZdoCount / windowSeconds, (float)ZdoDataBytes / windowSeconds, (float)ZdoDataPackages / windowSeconds, LatestSendQueueBytes, LatestHeadroomBytes, MaxSendQueueBytes, MinHeadroomBytes, MaxKnownZdos, MaxForceSend, MaxInvalidSector, MetricsMath.Median(list), MetricsMath.Percentile(list, 0.95f), (list.Count == 0) ? 0f : list.Max(), MetricsMath.Median(list2), MetricsMath.Percentile(list2, 0.95f), (list2.Count == 0) ? 0f : list2.Max()); AttemptCount = 0; SocketSkipCount = 0; FlushAttemptCount = 0; SentZdoCount = 0; ZdoDataBytes = 0L; ZdoDataPackages = 0; MaxSendQueueBytes = LatestSendQueueBytes; MinHeadroomBytes = LatestHeadroomBytes; MaxKnownZdos = 0; MaxForceSend = 0; MaxInvalidSector = 0; SendDurationSamplesMs.Clear(); CadenceSamplesMs.Clear(); return result; } } private readonly struct PeerSample { internal long PeerUid { get; } internal string PlayerName { get; } internal int AttemptCount { get; } internal int SocketSkipCount { get; } internal int FlushAttemptCount { get; } internal float SentZdosSec { get; } internal float ZdoDataBytesSec { get; } internal float ZdoDataPackagesSec { get; } internal int LatestSendQueueBytes { get; } internal int LatestHeadroomBytes { get; } internal int MaxSendQueueBytes { get; } internal int MinHeadroomBytes { get; } internal int MaxKnownZdos { get; } internal int MaxForceSend { get; } internal int MaxInvalidSector { get; } internal float SendDurationP50Ms { get; } internal float SendDurationP95Ms { get; } internal float SendDurationMaxMs { get; } internal float CadenceP50Ms { get; } internal float CadenceP95Ms { get; } internal float CadenceMaxMs { get; } internal PeerSample(long peerUid, string playerName, int attemptCount, int socketSkipCount, int flushAttemptCount, float sentZdosSec, float zdoDataBytesSec, float zdoDataPackagesSec, int latestSendQueueBytes, int latestHeadroomBytes, int maxSendQueueBytes, int minHeadroomBytes, int maxKnownZdos, int maxForceSend, int maxInvalidSector, float sendDurationP50Ms, float sendDurationP95Ms, float sendDurationMaxMs, float cadenceP50Ms, float cadenceP95Ms, float cadenceMaxMs) { PeerUid = peerUid; PlayerName = playerName; AttemptCount = attemptCount; SocketSkipCount = socketSkipCount; FlushAttemptCount = flushAttemptCount; SentZdosSec = sentZdosSec; ZdoDataBytesSec = zdoDataBytesSec; ZdoDataPackagesSec = zdoDataPackagesSec; LatestSendQueueBytes = latestSendQueueBytes; LatestHeadroomBytes = latestHeadroomBytes; MaxSendQueueBytes = maxSendQueueBytes; MinHeadroomBytes = minHeadroomBytes; MaxKnownZdos = maxKnownZdos; MaxForceSend = maxForceSend; MaxInvalidSector = maxInvalidSector; SendDurationP50Ms = sendDurationP50Ms; SendDurationP95Ms = sendDurationP95Ms; SendDurationMaxMs = sendDurationMaxMs; CadenceP50Ms = cadenceP50Ms; CadenceP95Ms = cadenceP95Ms; CadenceMaxMs = cadenceMaxMs; } } private readonly struct SocketSnapshot { internal bool HasValue { get; } internal int SendQueueBytes { get; } internal int Ping { get; } internal float LocalQuality { get; } internal float RemoteQuality { get; } internal float OutBytesSec { get; } internal float InBytesSec { get; } internal SocketSnapshot(bool hasValue, int sendQueueBytes, int ping, float localQuality, float remoteQuality, float outBytesSec, float inBytesSec) { HasValue = hasValue; SendQueueBytes = sendQueueBytes; Ping = ping; LocalQuality = localQuality; RemoteQuality = remoteQuality; OutBytesSec = outBytesSec; InBytesSec = inBytesSec; } } private const int VanillaSendQueueBudgetBytes = 10240; private const int LowHeadroomBytes = 2048; private const int MaxVbNetTweaksLookupFrames = 1800; private static readonly object MetricsLock = new object(); private static readonly Dictionary PeerWindows = new Dictionary(); private static float _timer; private static float _lastWriteTime; private static int _sampleIndex; private static int _patchWarningCount; private static FieldInfo? _vbNetTweaksZdoQueueLimitField; private static PropertyInfo? _vbNetTweaksConfigEntryValueProperty; private static bool _vbNetTweaksLookupComplete; private static bool _vbNetTweaksConfigEntryValuePropertyLookupComplete; private static int _vbNetTweaksLookupStartFrame = -1; private static int _lastVbNetTweaksLookupFrame = -1; internal static int SendQueueBudgetBytes => ResolveSendQueueBudgetBytes(); internal static void Update() { if (CanCapture()) { if (_lastWriteTime <= 0f) { _lastWriteTime = Time.realtimeSinceStartup; } _timer += Time.unscaledDeltaTime; float num = Math.Max(1f, PraetorisClientPlugin.SocketMetricsSampleIntervalSeconds.Value); if (!(_timer < num)) { _timer = 0f; WriteSample(); } } } internal static void BeginSendZdos(ZDOPeer zdoPeer, bool flush, out SendZdoMetricState state) { state = default(SendZdoMetricState); if (!CanCapture() || zdoPeer?.m_peer == null) { return; } ZNetPeer peer = zdoPeer.m_peer; int sendQueueSize = peer.m_socket.GetSendQueueSize(); int sendQueueBudgetBytes = SendQueueBudgetBytes; int num = sendQueueBudgetBytes - sendQueueSize; bool flag = (!flush && sendQueueSize > sendQueueBudgetBytes) || num < 2048; int sentBefore = ((ZDOMan.instance != null) ? ZDOMan.instance.GetSentZDOs() : 0); long timestamp = Stopwatch.GetTimestamp(); string playerName = (string.IsNullOrEmpty(peer.m_playerName) ? peer.m_uid.ToString(CultureInfo.InvariantCulture) : peer.m_playerName); lock (MetricsLock) { PeerWindow orCreateWindow = GetOrCreateWindow(peer.m_uid, playerName, sendQueueBudgetBytes); orCreateWindow.PlayerName = playerName; orCreateWindow.AttemptCount++; if (flush) { orCreateWindow.FlushAttemptCount++; } if (flag) { orCreateWindow.SocketSkipCount++; } orCreateWindow.LatestSendQueueBytes = sendQueueSize; orCreateWindow.LatestHeadroomBytes = num; orCreateWindow.MaxSendQueueBytes = Math.Max(orCreateWindow.MaxSendQueueBytes, sendQueueSize); orCreateWindow.MinHeadroomBytes = Math.Min(orCreateWindow.MinHeadroomBytes, num); orCreateWindow.MaxKnownZdos = Math.Max(orCreateWindow.MaxKnownZdos, zdoPeer.m_zdos.Count); orCreateWindow.MaxForceSend = Math.Max(orCreateWindow.MaxForceSend, zdoPeer.m_forceSend.Count); orCreateWindow.MaxInvalidSector = Math.Max(orCreateWindow.MaxInvalidSector, zdoPeer.m_invalidSector.Count); float realtimeSinceStartup = Time.realtimeSinceStartup; if (orCreateWindow.LastAttemptTime > 0f) { orCreateWindow.CadenceSamplesMs.Add((realtimeSinceStartup - orCreateWindow.LastAttemptTime) * 1000f); } orCreateWindow.LastAttemptTime = realtimeSinceStartup; } state = new SendZdoMetricState(peer.m_uid, playerName, sentBefore, timestamp); } internal static void EndSendZdos(SendZdoMetricState state) { if (!CanCapture()) { return; } int num = ((ZDOMan.instance != null) ? Math.Max(0, ZDOMan.instance.GetSentZDOs() - state.SentBefore) : 0); float item = (float)((double)(Stopwatch.GetTimestamp() - state.StartTicks) * 1000.0 / (double)Stopwatch.Frequency); int sendQueueBudgetBytes = SendQueueBudgetBytes; lock (MetricsLock) { PeerWindow orCreateWindow = GetOrCreateWindow(state.PeerUid, state.PlayerName, sendQueueBudgetBytes); orCreateWindow.SentZdoCount += num; orCreateWindow.SendDurationSamplesMs.Add(item); } } internal static void RecordZdoDataPackage(ZRpc rpc, int packageBytes) { if (!CanCapture() || (Object)(object)ZNet.instance == (Object)null) { return; } long peerIdForRpc = RpcTraceTelemetry.GetPeerIdForRpc(rpc); if (peerIdForRpc == 0L) { return; } string playerName = peerIdForRpc.ToString(CultureInfo.InvariantCulture); ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer != null && serverPeer.m_uid == peerIdForRpc && !string.IsNullOrEmpty(serverPeer.m_playerName)) { playerName = serverPeer.m_playerName; } int sendQueueBudgetBytes = SendQueueBudgetBytes; lock (MetricsLock) { PeerWindow orCreateWindow = GetOrCreateWindow(peerIdForRpc, playerName, sendQueueBudgetBytes); orCreateWindow.ZdoDataBytes += packageBytes; orCreateWindow.ZdoDataPackages++; } } internal static void LogPatchWarning(string message, Exception exception) { if (_patchWarningCount++ < 5) { PraetorisClientPlugin.Log.LogWarning((object)(message + ": " + exception.GetType().Name + ": " + exception.Message)); } } private static bool CanCapture() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Invalid comparison between Unknown and I4 if (RpcTraceTelemetry.IsTracingEnabled() && PraetorisClientPlugin.SocketMetricsEnabled.Value && (Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer()) { return (int)ZNet.GetConnectionStatus() == 2; } return false; } private static PeerWindow GetOrCreateWindow(long peerUid, string playerName, int initialHeadroomBytes) { if (!PeerWindows.TryGetValue(peerUid, out PeerWindow value)) { value = new PeerWindow(peerUid, playerName, initialHeadroomBytes); PeerWindows[peerUid] = value; } return value; } private static void WriteSample() { float realtimeSinceStartup = Time.realtimeSinceStartup; float windowSeconds = Math.Max(0.001f, realtimeSinceStartup - _lastWriteTime); _lastWriteTime = realtimeSinceStartup; _sampleIndex++; Dictionary dictionary = ReadSocketSnapshots(); List list; lock (MetricsLock) { list = PeerWindows.Values.Select((PeerWindow window) => window.TakeSample(windowSeconds)).ToList(); } int num = 0; int num2 = 0; float num3 = 0f; float num4 = 0f; float num5 = 0f; int num6 = 0; int sendQueueBudgetBytes = SendQueueBudgetBytes; int num7 = sendQueueBudgetBytes; float num8 = 0f; float num9 = 0f; long localPeerId = RpcTraceTelemetry.GetLocalPeerId(); RpcTraceTelemetry.TraceEnvelopeContext context = RpcTraceTelemetry.CaptureEnvelopeContext(localPeerId); foreach (PeerSample item in list) { dictionary.TryGetValue(item.PeerUid, out var value); int num10 = (value.HasValue ? value.SendQueueBytes : item.LatestSendQueueBytes); int num11 = (value.HasValue ? (sendQueueBudgetBytes - value.SendQueueBytes) : item.LatestHeadroomBytes); num += item.AttemptCount; num2 += item.SocketSkipCount; num3 += item.SentZdosSec; num4 += item.ZdoDataBytesSec; num5 += item.ZdoDataPackagesSec; num6 = Math.Max(num6, Math.Max(item.MaxSendQueueBytes, num10)); num7 = Math.Min(num7, Math.Min(item.MinHeadroomBytes, num11)); num8 = Math.Max(num8, item.SendDurationP95Ms); num9 = Math.Max(num9, item.CadenceP95Ms); TelemetryJson telemetryJson = RpcTraceTelemetry.ObjectWithEnvelope("socket_metric_peer_sample", context); telemetryJson.Prop("role", "client"); telemetryJson.Prop("peerId", item.PeerUid); telemetryJson.Prop("playerName", item.PlayerName); telemetryJson.Prop("sampleIndex", _sampleIndex); telemetryJson.Prop("windowSeconds", windowSeconds); telemetryJson.Prop("zdoAttemptCount", item.AttemptCount); telemetryJson.Prop("zdoSocketSkipCount", item.SocketSkipCount); telemetryJson.Prop("flushAttemptCount", item.FlushAttemptCount); telemetryJson.Prop("sentZdosPerSecond", item.SentZdosSec); telemetryJson.Prop("zdoDataBytesPerSecond", item.ZdoDataBytesSec); telemetryJson.Prop("zdoDataPackagesPerSecond", item.ZdoDataPackagesSec); telemetryJson.Prop("sendQueueLatestBytes", num10); telemetryJson.Prop("sendQueueMaxBytes", Math.Max(item.MaxSendQueueBytes, num10)); telemetryJson.Prop("sendHeadroomLatestBytes", num11); telemetryJson.Prop("sendHeadroomMinBytes", Math.Min(item.MinHeadroomBytes, num11)); telemetryJson.Prop("knownZdosMax", item.MaxKnownZdos); telemetryJson.Prop("forceSendMax", item.MaxForceSend); telemetryJson.Prop("invalidSectorMax", item.MaxInvalidSector); telemetryJson.Prop("sendDurationP50Ms", item.SendDurationP50Ms); telemetryJson.Prop("sendDurationP95Ms", item.SendDurationP95Ms); telemetryJson.Prop("sendDurationMaxMs", item.SendDurationMaxMs); telemetryJson.Prop("cadenceP50Ms", item.CadenceP50Ms); telemetryJson.Prop("cadenceP95Ms", item.CadenceP95Ms); telemetryJson.Prop("cadenceMaxMs", item.CadenceMaxMs); telemetryJson.Prop("socketPingMs", value.Ping); telemetryJson.Prop("socketLocalQuality", value.LocalQuality); telemetryJson.Prop("socketRemoteQuality", value.RemoteQuality); telemetryJson.Prop("socketOutBytesPerSecond", value.OutBytesSec); telemetryJson.Prop("socketInBytesPerSecond", value.InBytesSec); RpcTraceTelemetry.AddClockFields(telemetryJson, context); telemetryJson.End(); RpcTraceLocalStore.Append(telemetryJson.ToString(), localPeerId, context.WorldUid); } TelemetryJson telemetryJson2 = RpcTraceTelemetry.ObjectWithEnvelope("socket_metric_summary_sample", context); telemetryJson2.Prop("role", "client"); telemetryJson2.Prop("peerId", localPeerId); telemetryJson2.Prop("sampleIndex", _sampleIndex); telemetryJson2.Prop("windowSeconds", windowSeconds); telemetryJson2.Prop("peerCount", list.Count); telemetryJson2.Prop("totalZdoAttemptCount", num); telemetryJson2.Prop("totalZdoSocketSkipCount", num2); telemetryJson2.Prop("maxSendQueueBytes", num6); telemetryJson2.Prop("minSendHeadroomBytes", (dictionary.Count > 0 || list.Count > 0) ? num7 : sendQueueBudgetBytes); telemetryJson2.Prop("totalSentZdosPerSecond", num3); telemetryJson2.Prop("totalZdoDataBytesPerSecond", num4); telemetryJson2.Prop("totalZdoDataPackagesPerSecond", num5); telemetryJson2.Prop("worstSendDurationP95Ms", num8); telemetryJson2.Prop("worstCadenceP95Ms", num9); RpcTraceTelemetry.AddClockFields(telemetryJson2, context); telemetryJson2.End(); RpcTraceLocalStore.Append(telemetryJson2.ToString(), localPeerId, context.WorldUid); } private static int ResolveSendQueueBudgetBytes() { int num = ((PraetorisClientPlugin.SocketMetricsSendQueueBudgetBytes != null) ? PraetorisClientPlugin.SocketMetricsSendQueueBudgetBytes.Value : 0); if (num > 0) { return num; } int num2 = ReadVbNetTweaksZdoQueueLimit(); if (num2 <= 0) { return 10240; } return num2; } private static int ReadVbNetTweaksZdoQueueLimit() { try { EnsureVbNetTweaksLookup(); object obj = _vbNetTweaksZdoQueueLimitField?.GetValue(null); if (obj == null) { return 0; } if (!_vbNetTweaksConfigEntryValuePropertyLookupComplete) { _vbNetTweaksConfigEntryValueProperty = obj.GetType().GetProperty("Value"); _vbNetTweaksConfigEntryValuePropertyLookupComplete = true; } return (_vbNetTweaksConfigEntryValueProperty?.GetValue(obj) is int num) ? num : 0; } catch (Exception exception) { LogPatchWarning("Failed to read VBNetTweaks ZDOQueueLimit", exception); return 0; } } private static void EnsureVbNetTweaksLookup() { if (_vbNetTweaksLookupComplete || _vbNetTweaksZdoQueueLimitField != null) { return; } int frameCount = Time.frameCount; if (_vbNetTweaksLookupStartFrame < 0) { _vbNetTweaksLookupStartFrame = frameCount; } if (frameCount - _vbNetTweaksLookupStartFrame > 1800) { _vbNetTweaksLookupComplete = true; } else if (_lastVbNetTweaksLookupFrame != frameCount) { _lastVbNetTweaksLookupFrame = frameCount; Type? type = Type.GetType("VBNetTweaks.VBNetTweaks, VBNetTweaks"); _vbNetTweaksZdoQueueLimitField = type?.GetField("ZDOQueueLimit", BindingFlags.Static | BindingFlags.Public); if (type != null) { _vbNetTweaksLookupComplete = true; } } } private static Dictionary ReadSocketSnapshots() { Dictionary dictionary = new Dictionary(); if ((Object)(object)ZNet.instance == (Object)null) { return dictionary; } ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer == null || !serverPeer.IsReady()) { return dictionary; } try { float localQuality = default(float); float remoteQuality = default(float); int ping = default(int); float outBytesSec = default(float); float inBytesSec = default(float); serverPeer.m_socket.GetConnectionQuality(ref localQuality, ref remoteQuality, ref ping, ref outBytesSec, ref inBytesSec); dictionary[serverPeer.m_uid] = new SocketSnapshot(hasValue: true, serverPeer.m_socket.GetSendQueueSize(), ping, localQuality, remoteQuality, outBytesSec, inBytesSec); } catch (Exception ex) { PraetorisClientPlugin.Log.LogWarning((object)("Failed to read client socket snapshot: " + ex.Message)); } return dictionary; } } internal static class RpcProbeClient { private readonly struct PendingClientProbe { internal float ClientSentAt { get; } internal int PayloadBytes { get; } internal int ServerSendQueueBytes { get; } internal int ServerHeadroomBytes { get; } internal PendingClientProbe(float clientSentAt, int payloadBytes, int serverSendQueueBytes, int serverHeadroomBytes) { ClientSentAt = clientSentAt; PayloadBytes = payloadBytes; ServerSendQueueBytes = serverSendQueueBytes; ServerHeadroomBytes = serverHeadroomBytes; } } private static readonly Dictionary Pending = new Dictionary(); private static ZRoutedRpc? _registeredRpc; private static float _sendTimer; private static int _sequence; private static byte[] _payload = Array.Empty(); internal static void Update() { if (CanProbe()) { TryRegisterRpcs(); WriteTimedOutClientProbes(); _sendTimer += Time.unscaledDeltaTime; float num = Math.Max(0.25f, PraetorisClientPlugin.RpcProbeIntervalSeconds.Value); if (!(_sendTimer < num)) { _sendTimer = 0f; SendClientProbe(); } } } private static void TryRegisterRpcs() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null || _registeredRpc == instance) { return; } try { instance.Register("PraetorisClient_RpcProbe_Forward", (Action)OnProbeForward); instance.Register("PraetorisClient_RpcProbe_Ack", (Action)OnProbeAck); _registeredRpc = instance; PraetorisClientPlugin.Log.LogInfo((object)"Registered RPC probe client handlers."); } catch (Exception ex) { _registeredRpc = instance; PraetorisClientPlugin.Log.LogWarning((object)("Failed to register RPC probe client handlers: " + ex.GetType().Name + ": " + ex.Message)); } } private static void SendClientProbe() { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown if (!((Object)(object)ZNet.instance == (Object)null) && ZRoutedRpc.instance != null) { ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer != null) { BuildPayload(); int num = ++_sequence; int sendQueueSize = serverPeer.m_socket.GetSendQueueSize(); int serverHeadroomBytes = ClientSocketMetrics.SendQueueBudgetBytes - sendQueueSize; Pending[num] = new PendingClientProbe(Time.realtimeSinceStartup, _payload.Length, sendQueueSize, serverHeadroomBytes); ZPackage val = new ZPackage(); val.Write(2); val.Write(num); val.Write(GetLocalPlayerName()); val.Write(Time.realtimeSinceStartupAsDouble); val.Write(_payload); ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "PraetorisClient_RpcProbe_Request", new object[1] { val }); } } } private static void OnProbeForward(long senderPeerUid, ZPackage package) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown if (!CanProbe()) { return; } try { int num = package.ReadInt(); long num2 = package.ReadLong(); package.ReadString(); int num3 = package.ReadInt(); byte[] array = package.ReadByteArray(); if (num == 2) { ZPackage val = new ZPackage(); val.Write(2); val.Write(num2); val.Write(num3); val.Write(array); ZRoutedRpc.instance.InvokeRoutedRPC(senderPeerUid, "PraetorisClient_RpcProbe_Reply", new object[1] { val }); } } catch (Exception ex) { PraetorisClientPlugin.Log.LogWarning((object)$"RPC probe forward failed from {senderPeerUid}: {ex.GetType().Name}: {ex.Message}"); } } private static void OnProbeAck(long senderPeerUid, ZPackage package) { if (!CanProbe()) { return; } try { int num = package.ReadInt(); int sequence = package.ReadInt(); long targetPeerUid = package.ReadLong(); string targetPlayerName = package.ReadString(); int payloadBytes = package.ReadInt(); float serverRelayMs = package.ReadSingle(); bool success = package.ReadBool(); string result = package.ReadString(); if (num == 2) { HandleProbeAck(sequence, targetPeerUid, targetPlayerName, payloadBytes, serverRelayMs, success, result); } } catch (Exception ex) { PraetorisClientPlugin.Log.LogWarning((object)$"RPC probe ack failed from {senderPeerUid}: {ex.GetType().Name}: {ex.Message}"); } } private static void HandleProbeAck(int sequence, long targetPeerUid, string targetPlayerName, int payloadBytes, float serverRelayMs, bool success, string result) { if (Pending.TryGetValue(sequence, out var value)) { Pending.Remove(sequence); float fullRoundtripMs = (Time.realtimeSinceStartup - value.ClientSentAt) * 1000f; WriteClientSample(sequence, targetPeerUid, targetPlayerName, payloadBytes, fullRoundtripMs, serverRelayMs, value.ServerSendQueueBytes, value.ServerHeadroomBytes, success ? result : ("failed:" + result)); } } private static void WriteTimedOutClientProbes() { if (Pending.Count == 0) { return; } float num = Math.Max(1f, PraetorisClientPlugin.RpcProbeTimeoutSeconds.Value); float realtimeSinceStartup = Time.realtimeSinceStartup; List list = new List(); foreach (KeyValuePair item in Pending) { if (realtimeSinceStartup - item.Value.ClientSentAt >= num) { list.Add(item.Key); } } foreach (int item2 in list) { PendingClientProbe pendingClientProbe = Pending[item2]; Pending.Remove(item2); WriteClientSample(item2, 0L, "", pendingClientProbe.PayloadBytes, num * 1000f, 0f, pendingClientProbe.ServerSendQueueBytes, pendingClientProbe.ServerHeadroomBytes, "timeout"); } } private static void WriteClientSample(int sequence, long targetPeerUid, string targetPlayerName, int payloadBytes, float fullRoundtripMs, float serverRelayMs, int serverSendQueueBytes, int serverHeadroomBytes, string result) { long localPeerId = RpcTraceTelemetry.GetLocalPeerId(); RpcTraceTelemetry.TraceEnvelopeContext context = RpcTraceTelemetry.CaptureEnvelopeContext(localPeerId); TelemetryJson telemetryJson = RpcTraceTelemetry.ObjectWithEnvelope("rpc_probe_client_sample", context); telemetryJson.Prop("role", "client"); telemetryJson.Prop("probeSequence", sequence); telemetryJson.Prop("originPeerId", localPeerId); telemetryJson.Prop("originPlayerName", context.PlayerName); telemetryJson.Prop("targetPeerId", targetPeerUid); telemetryJson.Prop("targetPlayerName", targetPlayerName ?? ""); telemetryJson.Prop("payloadBytes", payloadBytes); telemetryJson.Prop("fullRoundtripMs", fullRoundtripMs); telemetryJson.Prop("serverRelayMs", serverRelayMs); telemetryJson.Prop("serverSendQueueBeforeBytes", serverSendQueueBytes); telemetryJson.Prop("serverSendHeadroomBeforeBytes", serverHeadroomBytes); telemetryJson.Prop("probeResult", result ?? ""); RpcTraceTelemetry.AddClockFields(telemetryJson, context); telemetryJson.End(); RpcTraceLocalStore.Append(telemetryJson.ToString(), localPeerId, context.WorldUid); } private static void BuildPayload() { int num = Math.Max(0, Math.Min(8192, PraetorisClientPlugin.RpcProbePayloadBytes.Value)); if (_payload.Length != num) { _payload = new byte[num]; for (int i = 0; i < _payload.Length; i++) { _payload[i] = (byte)(i % 251); } } } private static bool CanProbe() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Invalid comparison between Unknown and I4 if (RpcTraceTelemetry.IsTracingEnabled() && PraetorisClientPlugin.RpcProbeEnabled.Value && (Object)(object)ZNet.instance != (Object)null && ZRoutedRpc.instance != null && !ZNet.instance.IsServer()) { return (int)ZNet.GetConnectionStatus() == 2; } return false; } private static string GetLocalPlayerName() { if ((Object)(object)Player.m_localPlayer == (Object)null) { return "unknown"; } string playerName = Player.m_localPlayer.GetPlayerName(); if (!string.IsNullOrEmpty(playerName)) { return playerName; } return "unknown"; } } internal static class RpcTraceHttpUploadContract { internal const string ContentType = "application/gzip"; internal const string UserAgentSuffix = "ValheimTracerHttpUpload"; internal static Dictionary BuildHeaders(string token, string batchId, string runtimeId, string fileId, int batchIndex, bool finalBatch, string flushReason, string modVersion) { return new Dictionary { { "Authorization", "Bearer " + token }, { "Content-Type", "application/gzip" }, { "X-Trace-Batch-Id", batchId }, { "X-Trace-Runtime-Id", runtimeId }, { "X-Trace-File-Id", fileId }, { "X-Trace-Batch-Index", batchIndex.ToString() }, { "X-Trace-Final-Batch", finalBatch ? "true" : "false" }, { "X-Trace-Flush-Reason", flushReason ?? "" }, { "User-Agent", BuildUserAgent(modVersion) } }; } private static string BuildUserAgent(string modVersion) { string text = (string.IsNullOrWhiteSpace(modVersion) ? "unknown" : modVersion); return "PraetorisClient/" + text + " ValheimTracerHttpUpload"; } } internal static class RpcTraceHttpUploadCoordinator { private sealed class PreparedUploadBatch { internal byte[] Body { get; } internal int ConsumedRows { get; } internal bool FinalBatch { get; } internal bool EndOfFile { get; } internal bool SkippedOversizedRow { get; } internal double PreparationMilliseconds { get; } private PreparedUploadBatch(byte[] body, int consumedRows, bool finalBatch, bool endOfFile, bool skippedOversizedRow, double preparationMilliseconds) { Body = body; ConsumedRows = consumedRows; FinalBatch = finalBatch; EndOfFile = endOfFile; SkippedOversizedRow = skippedOversizedRow; PreparationMilliseconds = preparationMilliseconds; } internal static PreparedUploadBatch Ready(byte[] body, int consumedRows, bool finalBatch, double preparationMilliseconds) { return new PreparedUploadBatch(body, consumedRows, finalBatch, endOfFile: false, skippedOversizedRow: false, preparationMilliseconds); } internal static PreparedUploadBatch End() { return new PreparedUploadBatch(Array.Empty(), 0, finalBatch: true, endOfFile: true, skippedOversizedRow: false, 0.0); } internal static PreparedUploadBatch SkipOversizedRow() { return new PreparedUploadBatch(Array.Empty(), 0, finalBatch: false, endOfFile: false, skippedOversizedRow: true, 0.0); } } private sealed class UploadResult { internal bool Success { get; } internal long ResponseCode { get; } internal string Message { get; } internal UploadResult(bool success, long responseCode, string message) { Success = success; ResponseCode = responseCode; Message = message ?? ""; } } private const int MaxHttpRowsPerBatch = 1000; private const int FirstRetryRowsPerBatch = 250; private const int MinHttpRowsPerBatch = 100; private const float InitialFailureRetrySeconds = 15f; private const float MaxFailureRetrySeconds = 300f; private const float FailureLogIntervalSeconds = 60f; private const int HttpUploadTimeoutMilliseconds = 30000; private static readonly object Sync = new object(); private static readonly Queue PendingFiles = new Queue(); private static bool _uploading; private static float _nextUploadTime; private static int _consecutiveFailures; private static int _maxRowsPerUploadBatch = 1000; private static float _nextFailureLogTime; internal static void Initialize() { lock (Sync) { PendingFiles.Clear(); _uploading = false; _nextUploadTime = 0f; _consecutiveFailures = 0; _maxRowsPerUploadBatch = 1000; _nextFailureLogTime = 0f; } } internal static void Shutdown() { lock (Sync) { PendingFiles.Clear(); _uploading = false; _consecutiveFailures = 0; _maxRowsPerUploadBatch = 1000; _nextFailureLogTime = 0f; } } internal static void Update() { if (!CanUpload()) { return; } lock (Sync) { if (_uploading || Time.realtimeSinceStartup < _nextUploadTime) { return; } } PrepareFilesIfNeeded(); StartNextUploadIfReady(); } private static void PrepareFilesIfNeeded() { lock (Sync) { if (PendingFiles.Count > 0 || _uploading) { return; } } List flushableFiles = RpcTraceLocalStore.GetFlushableFiles(); lock (Sync) { if (PendingFiles.Count > 0 || _uploading) { return; } foreach (string item in flushableFiles) { if (File.Exists(item)) { if (new FileInfo(item).Length == 0L) { RpcTraceLocalStore.DeleteFile(item); } else { PendingFiles.Enqueue(item); } } } _nextUploadTime = Time.realtimeSinceStartup + RpcTraceUploadTokenClient.FlushIntervalSeconds; } } private static void StartNextUploadIfReady() { string path; lock (Sync) { if (_uploading || PendingFiles.Count == 0) { return; } path = PendingFiles.Dequeue(); _uploading = true; } if ((Object)(object)PraetorisClientPlugin.Instance != (Object)null) { ((MonoBehaviour)PraetorisClientPlugin.Instance).StartCoroutine(UploadFile(path)); } else { RequeueUpload(path); } } private static IEnumerator UploadFile(string path) { string fileId = RpcTraceLocalStore.BuildFileId(path); int batchIndex = 0; if (!File.Exists(path)) { CompleteUpload(success: true); yield break; } RpcTraceLocalStore.PendingTraceReader reader; try { reader = RpcTraceLocalStore.OpenReader(path); } catch (Exception ex) { PraetorisClientPlugin.Log.LogWarning((object)("Failed to open HTTP network metric file " + fileId + ": " + ex.Message)); RequeueUpload(path); yield break; } using (reader) { while (CanUpload() && File.Exists(path)) { int maxRows = GetMaxRowsPerUploadBatch(); Task prepareTask = Task.Run(() => PrepareUploadBatch(reader, maxRows)); while (!prepareTask.IsCompleted) { yield return null; } if (prepareTask.IsFaulted) { PraetorisClientPlugin.Log.LogWarning((object)("Failed to prepare HTTP network metric batch for " + fileId + ": " + (prepareTask.Exception?.GetBaseException().Message ?? "unknown error"))); RequeueUpload(path); yield break; } PreparedUploadBatch batch = prepareTask.Result; if (batch.EndOfFile) { RpcTraceLocalStore.DeleteFile(path); CompleteUpload(success: true); yield break; } if (batch.SkippedOversizedRow) { PraetorisClientPlugin.Log.LogWarning((object)("Skipping oversized HTTP network metric row in " + fileId + ".")); continue; } string batchId = fileId + "-" + batchIndex.ToString("D6"); PraetorisClientPlugin.Log.LogInfo((object)("Prepared HTTP network metric batch " + batchId + ": rows=" + batch.ConsumedRows + ", gzipBytes=" + batch.Body.Length + ", final=" + batch.FinalBatch + ", prepareMs=" + batch.PreparationMilliseconds.ToString("F1", CultureInfo.InvariantCulture) + " off main thread.")); Dictionary headers = RpcTraceHttpUploadContract.BuildHeaders(RpcTraceUploadTokenClient.Token, batchId, RpcTraceTelemetry.RuntimeId, fileId, batchIndex, batch.FinalBatch, "background", PraetorisClientPlugin.TraceModVersion); Task uploadTask = Task.Run(() => SendHttpUpload(RpcTraceUploadTokenClient.EndpointUrl, headers, batch.Body)); while (!uploadTask.IsCompleted) { yield return null; } if (uploadTask.IsFaulted) { string message = uploadTask.Exception?.GetBaseException().Message ?? "unknown error"; RegisterUploadFailure(batchId, 0L, message); RequeueUpload(path); yield break; } UploadResult result = uploadTask.Result; if (!result.Success) { if (!RpcTraceUploadTokenClient.ShouldRetryUpload(result.ResponseCode, result.Message)) { CompleteUpload(success: false); yield break; } RegisterUploadFailure(batchId, result.ResponseCode, result.Message); RequeueUpload(path); yield break; } RegisterUploadSuccess(); batchIndex++; if (!batch.FinalBatch) { continue; } PraetorisClientPlugin.Log.LogInfo((object)("Uploaded network metric file " + fileId + " over HTTP; deleting local copy.")); RpcTraceLocalStore.DeleteFile(path); CompleteUpload(success: true); yield break; } } RequeueUpload(path); } private static UploadResult SendHttpUpload(string endpointUrl, Dictionary headers, byte[] body) { if (!TryNormalizeEndpointUrl(endpointUrl, out string normalizedEndpointUrl, out string error)) { return new UploadResult(success: false, 0L, error); } try { Uri uri = new Uri(normalizedEndpointUrl); int port = ((uri.Port > 0) ? uri.Port : (string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase) ? 443 : 80)); using TcpClient tcpClient = new TcpClient(); tcpClient.SendTimeout = 30000; tcpClient.ReceiveTimeout = 30000; ConnectWithTimeout(tcpClient, uri.Host, port); using Stream stream = CreateRequestStream(tcpClient, uri); WriteHttpRequest(stream, uri, port, headers, body); return ReadHttpResponse(stream); } catch (IOException ex) { return new UploadResult(success: false, 0L, ex.Message); } catch (SocketException ex2) { return new UploadResult(success: false, 0L, ex2.Message); } catch (AuthenticationException ex3) { return new UploadResult(success: false, 0L, ex3.Message); } catch (Exception ex4) { return new UploadResult(success: false, 0L, ex4.Message); } } private static void ConnectWithTimeout(TcpClient client, string host, int port) { IAsyncResult asyncResult = client.BeginConnect(host, port, null, null); try { if (!asyncResult.AsyncWaitHandle.WaitOne(30000)) { throw new IOException("Request timeout"); } client.EndConnect(asyncResult); } finally { asyncResult.AsyncWaitHandle.Close(); } } private static Stream CreateRequestStream(TcpClient client, Uri uri) { NetworkStream stream = client.GetStream(); if (!string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) { return stream; } SslStream sslStream = new SslStream(stream, leaveInnerStreamOpen: false); sslStream.AuthenticateAsClient(uri.Host); return sslStream; } private static void WriteHttpRequest(Stream stream, Uri uri, int port, Dictionary headers, byte[] body) { StringBuilder stringBuilder = new StringBuilder(); string value = (string.IsNullOrWhiteSpace(uri.PathAndQuery) ? "/" : uri.PathAndQuery); stringBuilder.Append("POST ").Append(value).Append(" HTTP/1.1\r\n"); stringBuilder.Append("Host: ").Append(BuildHostHeader(uri, port)).Append("\r\n"); stringBuilder.Append("Connection: close\r\n"); stringBuilder.Append("Content-Length: ").Append(body.Length.ToString(CultureInfo.InvariantCulture)).Append("\r\n"); foreach (KeyValuePair header in headers) { if (!string.Equals(header.Key, "Host", StringComparison.OrdinalIgnoreCase) && !string.Equals(header.Key, "Connection", StringComparison.OrdinalIgnoreCase) && !string.Equals(header.Key, "Content-Length", StringComparison.OrdinalIgnoreCase)) { stringBuilder.Append(header.Key).Append(": ").Append(header.Value ?? "") .Append("\r\n"); } } stringBuilder.Append("\r\n"); byte[] bytes = Encoding.ASCII.GetBytes(stringBuilder.ToString()); stream.Write(bytes, 0, bytes.Length); stream.Write(body, 0, body.Length); stream.Flush(); } private static string BuildHostHeader(Uri uri, int port) { if ((!string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) || port != 80) && (!string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase) || port != 443)) { return uri.Host + ":" + port.ToString(CultureInfo.InvariantCulture); } return uri.Host; } private static UploadResult ReadHttpResponse(Stream stream) { using MemoryStream memoryStream = new MemoryStream(); byte[] array = new byte[8192]; int num; for (num = -1; num < 0; num = FindHeaderEnd(memoryStream.GetBuffer(), (int)memoryStream.Length)) { int num2 = stream.Read(array, 0, array.Length); if (num2 <= 0) { break; } memoryStream.Write(array, 0, num2); } byte[] array2 = memoryStream.ToArray(); string text = Encoding.UTF8.GetString(array2); string text2 = ((num >= 0) ? text.Substring(0, num) : text); string[] array3 = text2.Split(new string[1] { "\r\n" }, StringSplitOptions.None)[0].Split(new char[1] { ' ' }); if (array3.Length < 2 || !long.TryParse(array3[1], out var result)) { return new UploadResult(success: false, 0L, "Invalid HTTP response"); } int num3 = ((num >= 0) ? (num + 4) : array2.Length); int contentLength = GetContentLength(text2); if (contentLength >= 0) { int num4 = contentLength - Math.Max(0, array2.Length - num3); while (num4 > 0) { int count = Math.Min(array.Length, num4); int num5 = stream.Read(array, 0, count); if (num5 <= 0) { break; } memoryStream.Write(array, 0, num5); num4 -= num5; } array2 = memoryStream.ToArray(); } int num6 = Math.Max(0, Math.Min(array2.Length - num3, (contentLength >= 0) ? contentLength : (array2.Length - num3))); string message = ((num6 > 0) ? Encoding.UTF8.GetString(array2, num3, num6) : ""); return new UploadResult(result >= 200 && result < 300, result, message); } private static int FindHeaderEnd(byte[] buffer, int length) { for (int i = 0; i <= length - 4; i++) { if (buffer[i] == 13 && buffer[i + 1] == 10 && buffer[i + 2] == 13 && buffer[i + 3] == 10) { return i; } } return -1; } private static int GetContentLength(string headerText) { string[] array = headerText.Split(new string[1] { "\r\n" }, StringSplitOptions.None); foreach (string text in array) { int num = text.IndexOf(':'); if (num > 0 && string.Equals(text.Substring(0, num).Trim(), "Content-Length", StringComparison.OrdinalIgnoreCase) && int.TryParse(text.Substring(num + 1).Trim(), out var result)) { return Math.Max(0, result); } } return -1; } private static bool TryNormalizeEndpointUrl(string endpointUrl, out string normalizedEndpointUrl, out string error) { normalizedEndpointUrl = (endpointUrl ?? "").Trim(); error = ""; if (string.IsNullOrWhiteSpace(normalizedEndpointUrl)) { error = "Empty upload endpoint URL"; return false; } if (normalizedEndpointUrl.StartsWith("//", StringComparison.Ordinal)) { normalizedEndpointUrl = "http:" + normalizedEndpointUrl; } else if (!normalizedEndpointUrl.Contains("://")) { normalizedEndpointUrl = "http://" + normalizedEndpointUrl; } if (!Uri.TryCreate(normalizedEndpointUrl, UriKind.Absolute, out Uri result)) { error = "Invalid upload endpoint URL"; return false; } if (!string.Equals(result.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) && !string.Equals(result.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) { error = "Unsupported upload endpoint scheme"; return false; } normalizedEndpointUrl = result.AbsoluteUri; return true; } private static PreparedUploadBatch PrepareUploadBatch(RpcTraceLocalStore.PendingTraceReader reader, int maxRows) { bool reachedEnd; List list = reader.ReadRows(maxRows, out reachedEnd); if (list.Count == 0 && reachedEnd) { return PreparedUploadBatch.End(); } Stopwatch stopwatch = Stopwatch.StartNew(); int num = Math.Max(4096, RpcTraceUploadTokenClient.MaxBatchBytes); while (list.Count > 0) { byte[] array = GzipRows(list); if (array.Length <= num) { stopwatch.Stop(); return PreparedUploadBatch.Ready(array, list.Count, reachedEnd, stopwatch.Elapsed.TotalMilliseconds); } if (list.Count == 1) { return PreparedUploadBatch.SkipOversizedRow(); } int num2 = list.Count - 1; reader.PushBack(list, num2); list.RemoveAt(num2); reachedEnd = false; } return PreparedUploadBatch.SkipOversizedRow(); } private static byte[] GzipRows(IReadOnlyList rows) { using MemoryStream memoryStream = new MemoryStream(); using (GZipStream stream = new GZipStream(memoryStream, CompressionLevel.Fastest, leaveOpen: true)) { using StreamWriter streamWriter = new StreamWriter(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); foreach (string row in rows) { streamWriter.WriteLine(row); } } return memoryStream.ToArray(); } private static void RequeueUpload(string path) { lock (Sync) { if (File.Exists(path)) { PendingFiles.Enqueue(path); } _uploading = false; _nextUploadTime = Time.realtimeSinceStartup + GetFailureRetryDelaySeconds(); } } private static void CompleteUpload(bool success) { lock (Sync) { _uploading = false; if (success) { _consecutiveFailures = 0; } if (success && PendingFiles.Count > 0) { _nextUploadTime = 0f; } else { _nextUploadTime = Time.realtimeSinceStartup + (success ? RpcTraceUploadTokenClient.FlushIntervalSeconds : GetFailureRetryDelaySeconds()); } } } private static void RegisterUploadFailure(string batchId, long responseCode, string message) { int maxRowsPerUploadBatch; float failureRetryDelaySeconds; bool flag; lock (Sync) { _consecutiveFailures++; AdjustBatchSizeAfterFailure(responseCode, message); maxRowsPerUploadBatch = _maxRowsPerUploadBatch; failureRetryDelaySeconds = GetFailureRetryDelaySeconds(); flag = Time.realtimeSinceStartup >= _nextFailureLogTime; if (flag) { _nextFailureLogTime = Time.realtimeSinceStartup + 60f; } } if (flag) { PraetorisClientPlugin.Log.LogWarning((object)("HTTP network metric upload failed for " + batchId + ": HTTP " + responseCode + " " + message + ". Retrying in " + Math.Ceiling(failureRetryDelaySeconds) + "s with maxRows=" + maxRowsPerUploadBatch + ".")); } } private static void RegisterUploadSuccess() { lock (Sync) { _consecutiveFailures = 0; _nextFailureLogTime = 0f; } } private static int GetMaxRowsPerUploadBatch() { lock (Sync) { return Math.Max(100, _maxRowsPerUploadBatch); } } private static void AdjustBatchSizeAfterFailure(long responseCode, string message) { if (IsReceiverTimeoutFailure(responseCode, message)) { if (_maxRowsPerUploadBatch > 250) { _maxRowsPerUploadBatch = 250; } else { _maxRowsPerUploadBatch = 100; } } } private static bool IsReceiverTimeoutFailure(long responseCode, string message) { if (responseCode == 504 || responseCode == 408) { return true; } return (message ?? "").IndexOf("timeout", StringComparison.OrdinalIgnoreCase) >= 0; } private static float GetFailureRetryDelaySeconds() { int num = Math.Max(1, _consecutiveFailures); double num2 = Math.Pow(2.0, Math.Min(5, num - 1)); return Math.Min(300f, 15f * (float)num2); } private static bool CanUpload() { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Invalid comparison between Unknown and I4 if (!PraetorisClientPlugin.MeasurementDisableNetworkMetricHttpUpload.Value && PraetorisClientPlugin.NetworkMetricHttpUploadPreferred.Value && RpcTraceUploadTokenClient.HasUsableToken() && (Object)(object)PraetorisClientPlugin.Instance != (Object)null && (Object)(object)ZNet.instance != (Object)null && ZRoutedRpc.instance != null && !ZNet.instance.IsServer()) { return (int)ZNet.GetConnectionStatus() == 2; } return false; } } internal static class RpcTraceLocalStore { private enum WorkItemKind { Append, Close } private sealed class WorkItem { internal WorkItemKind Kind { get; } internal string Line { get; } internal long LocalPeerId { get; } internal long WorldUid { get; } internal ManualResetEventSlim? Completed { get; } private WorkItem(WorkItemKind kind, string line, long localPeerId, long worldUid, ManualResetEventSlim? completed) { Kind = kind; Line = line; LocalPeerId = localPeerId; WorldUid = worldUid; Completed = completed; } internal static WorkItem Append(string line, long localPeerId, long worldUid) { return new WorkItem(WorkItemKind.Append, line, localPeerId, worldUid, null); } internal static WorkItem Close(ManualResetEventSlim? completed) { return new WorkItem(WorkItemKind.Close, "", 0L, 0L, completed); } } internal sealed class PendingTraceReader : IDisposable { private readonly StreamReader _reader; private readonly Stack _pushbackRows = new Stack(); private bool _disposed; private bool _exhausted; internal PendingTraceReader(string path) { FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete, 65536, FileOptions.SequentialScan); _reader = new StreamReader(path.EndsWith(".jsonl.gz", StringComparison.OrdinalIgnoreCase) ? ((Stream)new GZipStream(fileStream, CompressionMode.Decompress)) : ((Stream)fileStream), Encoding.UTF8); } internal List ReadRows(int maxRows, out bool reachedEnd) { List list = new List(Math.Max(1, maxRows)); reachedEnd = true; while (_pushbackRows.Count > 0 && list.Count < maxRows) { string text = _pushbackRows.Pop(); if (text.Length > 0) { list.Add(text); } } while (!_exhausted && list.Count < maxRows) { string text2; try { text2 = _reader.ReadLine(); } catch (InvalidDataException) { _exhausted = true; break; } catch (IOException) { _exhausted = true; break; } if (text2 == null) { _exhausted = true; break; } if (text2.Length > 0) { list.Add(text2); } } if (_pushbackRows.Count > 0 || list.Count >= maxRows) { reachedEnd = false; } return list; } internal void PushBack(IReadOnlyList rows, int startIndex) { for (int num = rows.Count - 1; num >= startIndex; num--) { _pushbackRows.Push(rows[num]); } } public void Dispose() { if (!_disposed) { _reader.Dispose(); _disposed = true; } } } private const int FileBufferBytes = 65536; private const int WorkerPollMilliseconds = 250; private const int WorkerShutdownTimeoutMilliseconds = 10000; private const string LegacyTraceExtension = ".jsonl"; private const string CompressedTraceExtension = ".jsonl.gz"; private static readonly object LifecycleSync = new object(); private static BlockingCollection? _queue; private static Thread? _worker; private static string _pendingDirectory = ""; private static volatile bool _hasActiveWriter; private static double _nextRotationRealtime; internal static void Initialize() { _pendingDirectory = Path.Combine(Paths.BepInExRootPath, "logs", "PraetorisClient", "NetworkMetrics", "pending"); Directory.CreateDirectory(_pendingDirectory); lock (LifecycleSync) { if (_worker == null || !_worker.IsAlive) { _queue = new BlockingCollection(); _worker = new Thread(WriterLoop) { IsBackground = true, Name = "PraetorisClient network metric writer" }; _worker.Start(_queue); } } } internal static void Append(string line, long localPeerId) { long worldUid = ((ZNet.m_world != null) ? ZNet.m_world.m_uid : 0); Append(line, localPeerId, worldUid); } internal static void Append(string line, long localPeerId, long worldUid) { if (string.IsNullOrWhiteSpace(line)) { return; } BlockingCollection queue = _queue; if (queue == null || queue.IsAddingCompleted) { return; } try { queue.Add(WorkItem.Append(line, localPeerId, worldUid)); } catch (InvalidOperationException) { } } internal static void FlushIfDue(double realtime) { if (_hasActiveWriter) { if (_nextRotationRealtime <= 0.0) { _nextRotationRealtime = realtime + (double)GetBatchIntervalSeconds(); } if (!(realtime < _nextRotationRealtime)) { _nextRotationRealtime = realtime + (double)GetBatchIntervalSeconds(); EnqueueClose(null); } } } internal static void CloseCurrentFile() { using ManualResetEventSlim manualResetEventSlim = new ManualResetEventSlim(initialState: false); EnqueueClose(manualResetEventSlim); if (!manualResetEventSlim.Wait(10000)) { PraetorisClientPlugin.Log.LogWarning((object)"Timed out waiting for network metric writer to close current file."); } } internal static void CloseCurrentFileIfOpen() { BlockingCollection queue = _queue; if (_hasActiveWriter || (queue != null && queue.Count != 0)) { CloseCurrentFile(); } } private static void EnqueueClose(ManualResetEventSlim? completed) { BlockingCollection queue = _queue; if (queue == null || queue.IsAddingCompleted) { return; } try { queue.Add(WorkItem.Close(completed)); } catch (InvalidOperationException) { } } internal static void Shutdown() { BlockingCollection queue; Thread worker; lock (LifecycleSync) { queue = _queue; worker = _worker; _queue = null; _worker = null; } if (queue != null) { try { queue.CompleteAdding(); } catch (InvalidOperationException) { } if (worker != null && worker.IsAlive && !worker.Join(10000)) { PraetorisClientPlugin.Log.LogWarning((object)"Network metric writer did not exit cleanly before timeout."); } queue.Dispose(); _hasActiveWriter = false; } } internal static List GetFlushableFiles() { CloseCurrentFile(); List pendingFiles = GetPendingFiles(); pendingFiles.Sort(StringComparer.Ordinal); return pendingFiles; } internal static PendingTraceReader OpenReader(string path) { return new PendingTraceReader(path); } internal static void DeleteFile(string path) { try { if (File.Exists(path)) { File.Delete(path); } } catch (Exception ex) { PraetorisClientPlugin.Log.LogWarning((object)("Failed to delete uploaded network metric file " + path + ": " + ex.Message)); } } internal static string BuildFileId(string path) { string text = Path.GetFileName(path); if (text.EndsWith(".jsonl.gz", StringComparison.OrdinalIgnoreCase)) { text = text.Substring(0, text.Length - ".jsonl.gz".Length); } else if (text.EndsWith(".jsonl", StringComparison.OrdinalIgnoreCase)) { text = text.Substring(0, text.Length - ".jsonl".Length); } if (!string.IsNullOrEmpty(text)) { return text; } return Guid.NewGuid().ToString("N"); } private static List GetPendingFiles() { List list = new List(); if (!Directory.Exists(_pendingDirectory)) { return list; } list.AddRange(Directory.GetFiles(_pendingDirectory, "*.jsonl")); list.AddRange(Directory.GetFiles(_pendingDirectory, "*.jsonl.gz")); return list; } private static void WriterLoop(object? state) { BlockingCollection blockingCollection = (BlockingCollection)state; StreamWriter writer = null; string currentPath = null; int currentRowCount = 0; bool hasUnflushedRows = false; DateTime nextFlushUtc = DateTime.UtcNow.AddSeconds(1.0); try { while (true) { if (!blockingCollection.TryTake(out var item, 250)) { if (blockingCollection.IsCompleted) { break; } FlushIfNeeded(writer, ref hasUnflushedRows, ref nextFlushUtc); continue; } if (item.Kind == WorkItemKind.Close) { FlushAndClose(ref writer, ref currentPath, ref currentRowCount, ref hasUnflushedRows); SignalCompleted(item.Completed); continue; } try { EnsureWriter(ref writer, ref currentPath, item.LocalPeerId, item.WorldUid); writer?.WriteLine(item.Line); currentRowCount++; hasUnflushedRows = true; _hasActiveWriter = true; FlushIfNeeded(writer, ref hasUnflushedRows, ref nextFlushUtc); if (currentRowCount >= GetMaxBatchRows()) { FlushAndClose(ref writer, ref currentPath, ref currentRowCount, ref hasUnflushedRows); } } catch (Exception ex) { PraetorisClientPlugin.Log.LogWarning((object)("Failed to write network metric row: " + ex.Message)); } } } finally { FlushAndClose(ref writer, ref currentPath, ref currentRowCount, ref hasUnflushedRows); } } private static void SignalCompleted(ManualResetEventSlim? completed) { if (completed == null) { return; } try { completed.Set(); } catch (ObjectDisposedException) { } } private static void EnsureWriter(ref StreamWriter? writer, ref string? currentPath, long localPeerId, long worldUid) { if (writer == null) { Directory.CreateDirectory(_pendingDirectory); string text = DateTime.UtcNow.ToString("yyyyMMdd_HHmmss_fff", CultureInfo.InvariantCulture); string path = "network_metrics_" + text + "_world_" + worldUid.ToString(CultureInfo.InvariantCulture) + "_peer_" + localPeerId.ToString(CultureInfo.InvariantCulture) + "_" + Guid.NewGuid().ToString("N") + ".jsonl.gz"; currentPath = Path.Combine(_pendingDirectory, path); GZipStream stream = new GZipStream(new FileStream(currentPath, FileMode.CreateNew, FileAccess.Write, FileShare.Read, 65536, FileOptions.SequentialScan), CompressionLevel.Fastest); writer = new StreamWriter(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), 65536) { AutoFlush = false }; _hasActiveWriter = true; } } private static void FlushIfNeeded(StreamWriter? writer, ref bool hasUnflushedRows, ref DateTime nextFlushUtc) { if (hasUnflushedRows && writer != null && !(DateTime.UtcNow < nextFlushUtc)) { writer.Flush(); hasUnflushedRows = false; nextFlushUtc = DateTime.UtcNow.AddSeconds(1.0); } } private static void FlushAndClose(ref StreamWriter? writer, ref string? currentPath, ref int currentRowCount, ref bool hasUnflushedRows) { try { writer?.Flush(); writer?.Dispose(); } finally { writer = null; currentPath = null; currentRowCount = 0; hasUnflushedRows = false; _hasActiveWriter = false; _nextRotationRealtime = 0.0; } } private static int GetMaxBatchRows() { return Math.Max(1, PraetorisClientPlugin.MetricMaxBatchRows.Value); } private static float GetBatchIntervalSeconds() { return Math.Max(1f, PraetorisClientPlugin.MetricBatchIntervalSeconds.Value); } } [HarmonyPatch(typeof(ZRpc), "Invoke")] internal static class RpcTraceSendPatch { private static void Prefix(ZRpc __instance, string method, object[] parameters) { if (method == "ZDOData" && parameters != null && parameters.Length != 0) { object obj = parameters[0]; ZPackage val = (ZPackage)((obj is ZPackage) ? obj : null); if (val != null) { ClientSocketMetrics.RecordZdoDataPackage(__instance, val.Size()); } } } } internal readonly struct RpcTracePlayerIdentity { internal string TracePlayerId { get; } internal string SteamId { get; } internal string PlatformUserId { get; } internal string PlayerName { get; } internal RpcTracePlayerIdentity(string tracePlayerId, string steamId, string platformUserId, string playerName) { TracePlayerId = tracePlayerId; SteamId = steamId; PlatformUserId = platformUserId; PlayerName = playerName; } internal unsafe static RpcTracePlayerIdentity Create(long localPeerId) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) string text = ""; string text2 = ""; string text3 = ""; try { PlatformUserID platformUserID = ((IUser)PlatformManager.DistributionPlatform.LocalUser).PlatformUserID; text = ((object)(*(PlatformUserID*)(&platformUserID))/*cast due to .constrained prefix*/).ToString(); Platform platform = platformUserID.m_platform; if (string.Equals(((object)(*(Platform*)(&platform))/*cast due to .constrained prefix*/).ToString(), "Steam", StringComparison.OrdinalIgnoreCase)) { text2 = platformUserID.m_userID ?? ""; } } catch { } if (string.IsNullOrWhiteSpace(text2) && text.StartsWith("Steam_", StringComparison.OrdinalIgnoreCase)) { text2 = text.Substring("Steam_".Length); } try { if ((Object)(object)Player.m_localPlayer != (Object)null) { text3 = Player.m_localPlayer.GetPlayerName(); } } catch { } if (IsMissingPlayerName(text3)) { try { if ((Object)(object)Game.instance != (Object)null && Game.instance.GetPlayerProfile() != null) { text3 = Game.instance.GetPlayerProfile().GetName(); } } catch { } } return new RpcTracePlayerIdentity((!string.IsNullOrWhiteSpace(text2)) ? ("steam:" + text2) : ((!string.IsNullOrWhiteSpace(text)) ? text : ("peer:" + localPeerId)), text2, text, text3 ?? ""); } private static bool IsMissingPlayerName(string playerName) { if (!string.IsNullOrWhiteSpace(playerName)) { return string.Equals(playerName, "...", StringComparison.Ordinal); } return true; } } internal static class RpcTraceTelemetry { internal readonly struct TraceEnvelopeContext { internal long LocalPeerId { get; } internal string TracePlayerId { get; } internal string SteamId { get; } internal string PlatformUserId { get; } internal string PlayerName { get; } internal string TimeUtc { get; } internal long TimeUtcTicks { get; } internal double Realtime { get; } internal string WorldName { get; } internal long WorldUid { get; } internal TraceEnvelopeContext(long localPeerId, string tracePlayerId, string steamId, string platformUserId, string playerName, string timeUtc, long timeUtcTicks, double realtime, string worldName, long worldUid) { LocalPeerId = localPeerId; TracePlayerId = tracePlayerId ?? ""; SteamId = steamId ?? ""; PlatformUserId = platformUserId ?? ""; PlayerName = playerName ?? ""; TimeUtc = timeUtc ?? ""; TimeUtcTicks = timeUtcTicks; Realtime = realtime; WorldName = worldName ?? ""; WorldUid = worldUid; } } internal const int ProtocolVersion = 2; internal const string Schema = "valheim.trace.rpc.v1"; private const double IdentityRefreshSeconds = 1.0; private static long _sequence; private static bool _shutdownCapture; private static bool _runtimeStartSubmitted; private static string _runtimeId = ""; private static RpcTracePlayerIdentity _cachedIdentity = new RpcTracePlayerIdentity("", "", "", ""); private static long _cachedIdentityPeerId; private static double _nextIdentityRefreshRealtime; internal static string RuntimeId => _runtimeId; internal static void Initialize() { _shutdownCapture = false; _runtimeStartSubmitted = false; _runtimeId = TraceRuntimeMetadata.BuildRuntimeId("client"); _cachedIdentity = new RpcTracePlayerIdentity("", "", "", ""); _cachedIdentityPeerId = 0L; _nextIdentityRefreshRealtime = 0.0; RpcTraceLocalStore.Initialize(); RpcTraceUploadTokenClient.Initialize(); RpcTraceHttpUploadCoordinator.Initialize(); } internal static void Shutdown() { _shutdownCapture = true; RpcTraceHttpUploadCoordinator.Shutdown(); RpcTraceLocalStore.Shutdown(); } internal static void Update() { if (!IsTracingEnabled()) { RpcTraceLocalStore.CloseCurrentFileIfOpen(); return; } MaybeWriteRuntimeStart(); ClientSocketMetrics.Update(); RpcProbeClient.Update(); RpcTraceLocalStore.FlushIfDue(Time.realtimeSinceStartupAsDouble); UpdateUploads(); } internal static void BackgroundUpdate() { UpdateUploads(); } private static void UpdateUploads() { RpcTraceUploadTokenClient.Update(); RpcTraceHttpUploadCoordinator.Update(); } internal static bool IsTracingEnabled() { if (!PraetorisClientPlugin.MeasurementDisableNetworkMetrics.Value) { if (!PraetorisClientPlugin.SocketMetricsEnabled.Value) { return PraetorisClientPlugin.RpcProbeEnabled.Value; } return true; } return false; } internal static TelemetryJson ObjectWithEnvelope(string eventType, long localPeerId) { return ObjectWithEnvelope(eventType, CaptureEnvelopeContext(localPeerId)); } internal static TelemetryJson ObjectWithEnvelope(string eventType, TraceEnvelopeContext context) { long value = Interlocked.Increment(ref _sequence); TelemetryJson telemetryJson = TelemetryJson.Object(); telemetryJson.Prop("schema", "valheim.trace.rpc.v1"); telemetryJson.Prop("eventType", eventType); telemetryJson.Prop("traceId", "client:" + context.LocalPeerId.ToString(CultureInfo.InvariantCulture) + ":" + value.ToString(CultureInfo.InvariantCulture)); telemetryJson.Prop("tracePlayerId", context.TracePlayerId); telemetryJson.Prop("steamId", context.SteamId); telemetryJson.Prop("platformUserId", context.PlatformUserId); telemetryJson.Prop("playerName", context.PlayerName); telemetryJson.Prop("sequence", value); telemetryJson.Prop("localPeerId", context.LocalPeerId); telemetryJson.Prop("timeUtc", context.TimeUtc); telemetryJson.Prop("timeUtcTicks", context.TimeUtcTicks); telemetryJson.Prop("realtime", context.Realtime); telemetryJson.Prop("worldName", context.WorldName); telemetryJson.Prop("worldUid", context.WorldUid); return telemetryJson; } internal static TraceEnvelopeContext CaptureEnvelopeContext(long localPeerId) { DateTime utcNow = DateTime.UtcNow; RpcTracePlayerIdentity cachedIdentity = GetCachedIdentity(localPeerId); return new TraceEnvelopeContext(localPeerId, cachedIdentity.TracePlayerId, cachedIdentity.SteamId, cachedIdentity.PlatformUserId, cachedIdentity.PlayerName, utcNow.ToString("o", CultureInfo.InvariantCulture), utcNow.Ticks, Time.realtimeSinceStartupAsDouble, (ZNet.m_world != null) ? ZNet.m_world.m_name : "", (ZNet.m_world != null) ? ZNet.m_world.m_uid : 0); } internal static void AddClockFields(TelemetryJson json) { json.Prop("serverMinusClientOffsetMs", 0.0); json.Prop("clockRoundTripMs", 0.0); json.Prop("clockOffsetQuality", "none"); json.Prop("clockOffsetAgeMs", 0.0); json.Prop("clockSampleCount", 0); } internal static void AddClockFields(TelemetryJson json, TraceEnvelopeContext context) { AddClockFields(json); } internal static long GetPeerIdForRpc(ZRpc rpc) { if ((Object)(object)ZNet.instance == (Object)null) { return 0L; } foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null && peer.m_rpc == rpc) { return peer.m_uid; } } if (!ZNet.instance.IsServer()) { ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer != null && serverPeer.m_rpc == rpc) { return serverPeer.m_uid; } } return 0L; } internal static long GetLocalPeerId() { try { return (ZDOMan.instance != null) ? ZDOMan.GetSessionID() : 0; } catch { return 0L; } } private static void MaybeWriteRuntimeStart() { if (!_runtimeStartSubmitted && CanCaptureMetricRows()) { _runtimeStartSubmitted = true; long localPeerId = GetLocalPeerId(); TelemetryJson telemetryJson = ObjectWithEnvelope("runtime_start", localPeerId); telemetryJson.Prop("role", "client"); telemetryJson.Prop("runtimeId", _runtimeId); telemetryJson.Prop("gameVersion", TraceRuntimeMetadata.GetGameVersion()); telemetryJson.Prop("traceSource", "PraetorisClient"); telemetryJson.Prop("traceModGuid", PraetorisClientPlugin.TraceModGuid); telemetryJson.Prop("traceModName", PraetorisClientPlugin.TraceModName); telemetryJson.Prop("traceModVersion", PraetorisClientPlugin.TraceModVersion); TraceRuntimeMetadata.WritePlugins(telemetryJson); telemetryJson.End(); RpcTraceLocalStore.Append(telemetryJson.ToString(), localPeerId); } } private static bool CanCaptureMetricRows() { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Invalid comparison between Unknown and I4 if (IsTracingEnabled() && !_shutdownCapture && (Object)(object)ZNet.instance != (Object)null && ZRoutedRpc.instance != null && !ZNet.instance.IsServer()) { return (int)ZNet.GetConnectionStatus() == 2; } return false; } private static RpcTracePlayerIdentity GetCachedIdentity(long localPeerId) { double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; if (_cachedIdentityPeerId == localPeerId && realtimeSinceStartupAsDouble < _nextIdentityRefreshRealtime && !string.IsNullOrWhiteSpace(_cachedIdentity.TracePlayerId)) { return _cachedIdentity; } _cachedIdentity = RpcTracePlayerIdentity.Create(localPeerId); _cachedIdentityPeerId = localPeerId; _nextIdentityRefreshRealtime = realtimeSinceStartupAsDouble + 1.0; return _cachedIdentity; } } internal static class RpcTraceUploadRetryPolicy { internal static bool ShouldRetryUpload(long responseCode, string responseText) { return !IsPermanentReceiverConfigurationFailure(responseCode, responseText); } private static bool IsPermanentReceiverConfigurationFailure(long responseCode, string responseText) { string text = responseText ?? ""; if (responseCode != 403 && text.IndexOf("missing relay key", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("missing token signing secret", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("unauthorized relay", StringComparison.OrdinalIgnoreCase) < 0) { return text.IndexOf("invalid token", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } } internal static class RpcTraceUploadTokenClient { private const int TokenCacheVersion = 2; private const string TokenCacheFileName = "http_network_metric_upload_token.cache"; private const float RequestRetrySeconds = 30f; private const float ConfigurationRetrySeconds = 300f; private static string _sessionId = ""; private static float _nextRequestTime; private static float _requestDeadlineTime; private static bool _requestPending; private static float _nextRequestGateLogTime; internal static bool UploadEnabled { get; private set; } internal static string EndpointUrl { get; private set; } = ""; internal static string Token { get; private set; } = ""; internal static int MaxBatchBytes { get; private set; } = 131072; internal static float FlushIntervalSeconds { get; private set; } = 10f; internal static void Initialize() { EnsureSessionId(); UploadEnabled = false; EndpointUrl = ""; Token = ""; _requestPending = false; _nextRequestTime = 0f; _nextRequestGateLogTime = 0f; LoadCachedToken(); } internal static void Update() { if (PraetorisClientPlugin.MeasurementDisableNetworkMetricHttpUpload.Value || !PraetorisClientPlugin.NetworkMetricHttpUploadPreferred.Value || !RpcTraceTelemetry.IsTracingEnabled()) { ClearToken(deleteCachedToken: false); } else if (!CanRequestToken()) { if (_requestPending && Time.realtimeSinceStartup > _requestDeadlineTime) { _requestPending = false; } LogRequestGateIfDue(); } else if (!HasUsableToken() && !(Time.realtimeSinceStartup < _nextRequestTime)) { RequestToken(); } } internal static bool HasUsableToken() { if (UploadEnabled && !string.IsNullOrWhiteSpace(EndpointUrl)) { return !string.IsNullOrWhiteSpace(Token); } return false; } internal static bool ShouldRetryUpload(long responseCode, string responseText) { if (RpcTraceUploadRetryPolicy.ShouldRetryUpload(responseCode, responseText)) { return true; } ClearToken(deleteCachedToken: true); _requestPending = false; _nextRequestTime = Time.realtimeSinceStartup + 300f; PraetorisClientPlugin.Log.LogWarning((object)("HTTP network metric upload rejected by receiver configuration: " + (string.IsNullOrWhiteSpace(responseText) ? ("HTTP " + responseCode) : responseText) + ". Keeping local metric files and pausing token requests.")); return false; } internal static void OnTokenResponse(long sender, ZPackage package) { try { int num = package.ReadInt(); bool flag = package.ReadBool(); string text = package.ReadString(); string endpointUrl = package.ReadString(); string token = package.ReadString(); string text2 = package.ReadString(); int val = package.ReadInt(); float val2 = package.ReadSingle(); if (num != 2) { return; } _requestPending = false; _nextRequestTime = Time.realtimeSinceStartup + 30f; _nextRequestGateLogTime = 0f; if (!flag) { ClearToken(deleteCachedToken: true); if (!string.IsNullOrWhiteSpace(text)) { PraetorisClientPlugin.Log.LogInfo((object)("HTTP network metric upload unavailable: " + text)); } return; } if (!string.IsNullOrWhiteSpace(text2)) { _sessionId = text2; } UploadEnabled = true; EndpointUrl = endpointUrl; Token = token; MaxBatchBytes = Math.Max(4096, val); FlushIntervalSeconds = Math.Max(1f, val2); SaveCachedToken(); PraetorisClientPlugin.Log.LogInfo((object)("Received HTTP network metric upload token for session " + _sessionId + ".")); } catch (Exception ex) { _requestPending = false; PraetorisClientPlugin.Log.LogWarning((object)$"Failed to process network metric upload token response from peer {sender}: {ex.Message}"); } } private static void RequestToken() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown if (ZRoutedRpc.instance == null) { return; } try { EnsureSessionId(); RpcTracePlayerIdentity rpcTracePlayerIdentity = RpcTracePlayerIdentity.Create(RpcTraceTelemetry.GetLocalPeerId()); ZPackage val = new ZPackage(); val.Write(2); val.Write(_sessionId); val.Write(rpcTracePlayerIdentity.TracePlayerId); val.Write(rpcTracePlayerIdentity.SteamId); val.Write(rpcTracePlayerIdentity.PlatformUserId); val.Write(rpcTracePlayerIdentity.PlayerName); PraetorisClientPlugin.Log.LogInfo((object)("Requesting HTTP network metric upload token for session " + _sessionId + ".")); ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "PraetorisClient_RpcTraceUploadTokenRequest", new object[1] { val }); _requestPending = true; _nextRequestTime = Time.realtimeSinceStartup + 30f; _requestDeadlineTime = Time.realtimeSinceStartup + 30f; _nextRequestGateLogTime = 0f; } catch (Exception ex) { _requestPending = false; _nextRequestTime = Time.realtimeSinceStartup + 30f; PraetorisClientPlugin.Log.LogWarning((object)("Failed to request network metric upload token: " + ex.Message)); } } private static bool CanRequestToken() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Invalid comparison between Unknown and I4 if (!_requestPending && (Object)(object)ZNet.instance != (Object)null && ZRoutedRpc.instance != null && !ZNet.instance.IsServer()) { return (int)ZNet.GetConnectionStatus() == 2; } return false; } private static void LogRequestGateIfDue() { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) if (!(Time.realtimeSinceStartup < _nextRequestGateLogTime)) { _nextRequestGateLogTime = Time.realtimeSinceStartup + 30f; string text = (((Object)(object)ZNet.instance == (Object)null) ? "no-znet" : ((object)ZNet.GetConnectionStatus()/*cast due to .constrained prefix*/).ToString()); string text2 = ((ZRoutedRpc.instance == null) ? "no-routed-rpc" : "routed-rpc-ready"); string text3 = (((Object)(object)ZNet.instance == (Object)null) ? "unknown-server-state" : (ZNet.instance.IsServer() ? "server" : "client")); PraetorisClientPlugin.Log.LogDebug((object)("Network metric upload token request deferred: pending=" + _requestPending + ", status=" + text + ", " + text2 + ", " + text3 + ".")); } } private static void ClearToken(bool deleteCachedToken) { UploadEnabled = false; EndpointUrl = ""; Token = ""; if (deleteCachedToken) { DeleteCachedToken(); } } private static void LoadCachedToken() { string tokenCachePath = GetTokenCachePath(); if (!File.Exists(tokenCachePath)) { return; } try { Dictionary values = ReadCacheFile(tokenCachePath); if (!TryReadInt(values, "version", out var value) || value != 2) { DeleteCachedToken(); return; } string text = Decode(values, "sessionId"); string text2 = Decode(values, "endpointUrl"); string text3 = Decode(values, "token"); if (!TryReadInt(values, "maxBatchBytes", out var value2) || !TryReadFloat(values, "flushIntervalSeconds", out var value3) || string.IsNullOrWhiteSpace(text2) || string.IsNullOrWhiteSpace(text3)) { DeleteCachedToken(); return; } if (!string.IsNullOrWhiteSpace(text)) { _sessionId = text; } UploadEnabled = true; EndpointUrl = text2; Token = text3; MaxBatchBytes = Math.Max(4096, value2); FlushIntervalSeconds = Math.Max(1f, value3); PraetorisClientPlugin.Log.LogInfo((object)("Loaded cached HTTP network metric upload token for session " + _sessionId + ".")); } catch (Exception ex) { DeleteCachedToken(); PraetorisClientPlugin.Log.LogWarning((object)("Failed to load cached HTTP network metric upload token: " + ex.Message)); } } private static void SaveCachedToken() { try { string tokenCachePath = GetTokenCachePath(); Directory.CreateDirectory(Path.GetDirectoryName(tokenCachePath) ?? ""); string text = tokenCachePath + ".tmp"; File.WriteAllLines(text, new string[6] { "version=" + 2.ToString(CultureInfo.InvariantCulture), "sessionId=" + Encode(_sessionId), "endpointUrl=" + Encode(EndpointUrl), "token=" + Encode(Token), "maxBatchBytes=" + MaxBatchBytes.ToString(CultureInfo.InvariantCulture), "flushIntervalSeconds=" + FlushIntervalSeconds.ToString("R", CultureInfo.InvariantCulture) }); if (File.Exists(tokenCachePath)) { File.Delete(tokenCachePath); } File.Move(text, tokenCachePath); } catch (Exception ex) { PraetorisClientPlugin.Log.LogWarning((object)("Failed to cache HTTP network metric upload token: " + ex.Message)); } } private static void DeleteCachedToken() { try { string tokenCachePath = GetTokenCachePath(); if (File.Exists(tokenCachePath)) { File.Delete(tokenCachePath); } } catch (Exception ex) { PraetorisClientPlugin.Log.LogWarning((object)("Failed to delete cached HTTP network metric upload token: " + ex.Message)); } } private static Dictionary ReadCacheFile(string path) { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); string[] array = File.ReadAllLines(path); foreach (string text in array) { int num = text.IndexOf('='); if (num > 0) { string key = text.Substring(0, num); string value = text.Substring(num + 1); dictionary[key] = value; } } return dictionary; } private static bool TryReadInt(Dictionary values, string key, out int value) { value = 0; if (values.TryGetValue(key, out string value2)) { return int.TryParse(value2, NumberStyles.Integer, CultureInfo.InvariantCulture, out value); } return false; } private static bool TryReadFloat(Dictionary values, string key, out float value) { value = 0f; if (values.TryGetValue(key, out string value2)) { return float.TryParse(value2, NumberStyles.Float, CultureInfo.InvariantCulture, out value); } return false; } private static string Decode(Dictionary values, string key) { if (!values.TryGetValue(key, out string value) || string.IsNullOrWhiteSpace(value)) { return ""; } return Encoding.UTF8.GetString(Convert.FromBase64String(value)); } private static string Encode(string value) { return Convert.ToBase64String(Encoding.UTF8.GetBytes(value ?? "")); } private static string GetTokenCachePath() { return Path.Combine(Paths.BepInExRootPath, "logs", "PraetorisClient", "NetworkMetrics", "http_network_metric_upload_token.cache"); } private static void EnsureSessionId() { if (string.IsNullOrWhiteSpace(_sessionId)) { _sessionId = Guid.NewGuid().ToString("N"); } } } internal static class SocketMetricPatches { internal static void ApplyManualPatches(Harmony harmony) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown //IL_0064: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(ZDOMan), "SendZDOs", (Type[])null, (Type[])null); if (methodInfo == null) { PraetorisClientPlugin.Log.LogWarning((object)"Could not find ZDOMan.SendZDOs; client socket metric send cadence capture is inactive."); } else { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(SocketMetricPatches), "SendZdosPrefix", (Type[])null), new HarmonyMethod(typeof(SocketMetricPatches), "SendZdosPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static void SendZdosPrefix(object __0, bool __1, out SendZdoMetricState __state) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown __state = default(SendZdoMetricState); try { ClientSocketMetrics.BeginSendZdos((ZDOPeer)__0, __1, out __state); } catch (Exception exception) { ClientSocketMetrics.LogPatchWarning("SendZDOs client socket metric prefix failed", exception); } } private static void SendZdosPostfix(in SendZdoMetricState __state) { if (!__state.IsValid) { return; } try { ClientSocketMetrics.EndSendZdos(__state); } catch (Exception exception) { ClientSocketMetrics.LogPatchWarning("SendZDOs client socket metric postfix failed", exception); } } } } namespace PraetorisClient.ServerChestFeature { internal sealed class ServerChest : MonoBehaviour { private sealed class FieldInfoWrapper { private readonly FieldInfo? _field; internal FieldInfoWrapper(Type type, string fieldName) { _field = AccessTools.Field(type, fieldName); } internal void Set(object instance, T value) { if (_field == null) { PraetorisClientPlugin.Log.LogWarning((object)"Missing reflected field for ServerChest inventory shape."); } else { _field.SetValue(instance, value); } } } internal const string PrefabName = "ServerChest"; internal const string OwnerNameKey = "ServerChestOwnerName"; internal const string OwnerNameLookupKey = "ServerChestOwnerNameKey"; internal const string OwnerPlatformIdKey = "ServerChestOwnerPlatformId"; internal const string OwnerLookupKey = "ServerChestOwnerKey"; internal const int MaxColumns = 8; internal const int MaxRows = 8; internal const int MaxSlots = 64; private static readonly Dictionary InventoryOwners = new Dictionary(); private static readonly FieldInfoWrapper InventoryWidth = new FieldInfoWrapper(typeof(Inventory), "m_width"); private static readonly FieldInfoWrapper InventoryHeight = new FieldInfoWrapper(typeof(Inventory), "m_height"); private static bool _suppressInventoryChanged; private Container? _container; private Inventory? _inventory; private ZNetView? _nview; private void Awake() { _container = ((Component)this).GetComponent(); _nview = ((Component)this).GetComponent(); WearNTear component = ((Component)this).GetComponent(); if ((Object)(object)component != (Object)null) { component.m_onDestroyed = (Action)Delegate.Combine(component.m_onDestroyed, new Action(OnDestroyed)); } ((MonoBehaviour)this).InvokeRepeating("RefreshInventoryRegistration", 0.1f, 0.5f); } private void OnDestroy() { if (_inventory != null) { Inventory? inventory = _inventory; inventory.m_onChanged = (Action)Delegate.Remove(inventory.m_onChanged, new Action(OnInventoryChanged)); InventoryOwners.Remove(_inventory); } } private void RefreshInventoryRegistration() { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_container == (Object)null) { _container = ((Component)this).GetComponent(); } Inventory val = (((Object)(object)_container != (Object)null) ? _container.GetInventory() : null); if (val != null && val != _inventory) { if (_inventory != null) { Inventory? inventory = _inventory; inventory.m_onChanged = (Action)Delegate.Remove(inventory.m_onChanged, new Action(OnInventoryChanged)); InventoryOwners.Remove(_inventory); } _inventory = val; InventoryOwners[val] = this; ApplyMaxInventoryShape(val); ServerChestLog.Debug("registered live inventory zdo=" + ((object)GetZdoId()/*cast due to .constrained prefix*/).ToString() + " items=" + val.NrOfItems().ToString(CultureInfo.InvariantCulture)); val.m_onChanged = (Action)Delegate.Combine(val.m_onChanged, new Action(OnInventoryChanged)); } } private unsafe void OnInventoryChanged() { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) if (!_suppressInventoryChanged && _inventory != null && !((Object)(object)_nview == (Object)null) && _nview.IsValid() && _nview.IsOwner()) { CompactInventory(_inventory); ApplyMaxInventoryShape(_inventory); SaveInventoryToZdo(_nview.GetZDO(), _inventory); string[] obj = new string[6] { "live inventory changed zdo=", null, null, null, null, null }; ZDOID uid = _nview.GetZDO().m_uid; obj[1] = ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString(); obj[2] = " stacks="; obj[3] = _inventory.NrOfItems().ToString(CultureInfo.InvariantCulture); obj[4] = " items="; obj[5] = _inventory.NrOfItemsIncludingStacks().ToString(CultureInfo.InvariantCulture); ServerChestLog.Debug(string.Concat(obj)); } } private void OnDestroyed() { ZDO val = (((Object)(object)_nview != (Object)null && _nview.IsValid()) ? _nview.GetZDO() : null); if (val != null && _nview.IsOwner()) { ClearRegistration(val); } } internal void OpenRegistrationPanel() { ServerChestRegistrationPanel.Open(this); } internal ZDOID GetZdoId() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) return (((Object)(object)_nview != (Object)null && _nview.IsValid()) ? _nview.GetZDO() : null)?.m_uid ?? ZDOID.None; } internal string GetRegistrationPreview() { string obj = (((Object)(object)Game.instance != (Object)null && Game.instance.GetPlayerProfile() != null) ? Game.instance.GetPlayerProfile().GetName() : (((Object)(object)Player.m_localPlayer != (Object)null) ? Player.m_localPlayer.GetPlayerName() : "")); string localPlatformId = ServerChestIdentity.GetLocalPlatformId(); return obj + ":" + localPlatformId; } internal void RequestRegistration() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) ZDOID zdoId = GetZdoId(); if (((ZDOID)(ref zdoId)).IsNone()) { ShowMessage("ServerChest is not ready."); return; } string characterName = (((Object)(object)Game.instance != (Object)null && Game.instance.GetPlayerProfile() != null) ? Game.instance.GetPlayerProfile().GetName() : (((Object)(object)Player.m_localPlayer != (Object)null) ? Player.m_localPlayer.GetPlayerName() : "")); string localPlatformId = ServerChestIdentity.GetLocalPlatformId(); ServerChestRpc.RequestRegistration(zdoId, characterName, localPlatformId); } internal static bool TryGetByInventory(Inventory inventory, out ServerChest serverChest) { return InventoryOwners.TryGetValue(inventory, out serverChest); } internal static void ForgetInventory(Inventory inventory) { InventoryOwners.Remove(inventory); } internal static bool IsServerChestPrefab(ZDO zdo) { if (zdo != null) { return zdo.GetPrefab() == StringExtensionMethods.GetStableHashCode("ServerChest"); } return false; } internal static bool IsServerChest(Container container) { if ((Object)(object)container != (Object)null) { return (Object)(object)((Component)container).GetComponent() != (Object)null; } return false; } internal static string NormalizeLookup(string value) { return (value ?? "").Trim().ToLowerInvariant(); } internal unsafe static void SetRegistration(ZDO zdo, string ownerName, string ownerPlatformId) { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) string text = (ownerName ?? "").Trim(); string text2 = (ownerPlatformId ?? "").Trim(); zdo.Set("ServerChestOwnerName", text); zdo.Set("ServerChestOwnerNameKey", NormalizeLookup(text)); zdo.Set("ServerChestOwnerPlatformId", text2); zdo.Set("ServerChestOwnerKey", NormalizeLookup(text2)); string[] obj = new string[6] { "set registration zdo=", null, null, null, null, null }; ZDOID uid = zdo.m_uid; obj[1] = ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString(); obj[2] = " owner="; obj[3] = text; obj[4] = " platformId="; obj[5] = text2; ServerChestLog.Debug(string.Concat(obj)); } internal unsafe static void ClearRegistration(ZDO zdo) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) zdo.Set("ServerChestOwnerName", ""); zdo.Set("ServerChestOwnerNameKey", ""); zdo.Set("ServerChestOwnerPlatformId", ""); zdo.Set("ServerChestOwnerKey", ""); ZDOID uid = zdo.m_uid; ServerChestLog.Debug("cleared registration zdo=" + ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString()); } internal static string OwnerName(ZDO zdo) { return zdo.GetString("ServerChestOwnerName", ""); } internal static string OwnerNameLookup(ZDO zdo) { return zdo.GetString("ServerChestOwnerNameKey", ""); } internal static string OwnerPlatformId(ZDO zdo) { return zdo.GetString("ServerChestOwnerPlatformId", ""); } internal static string OwnerLookup(ZDO zdo) { return zdo.GetString("ServerChestOwnerKey", ""); } internal static void ApplyMaxInventoryShape(Inventory inventory) { ApplyInventoryShape(inventory, 8, 8); } internal static void ApplyInventoryShape(Inventory inventory, int columns, int rows) { InventoryWidth.Set(inventory, columns); InventoryHeight.Set(inventory, rows); } internal static void CompactInventory(Inventory inventory) { CompactInventory(inventory, 8); } internal static void CompactInventory(Inventory inventory, int columns) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) List allItemsInGridOrder = inventory.GetAllItemsInGridOrder(); for (int i = 0; i < allItemsInGridOrder.Count; i++) { allItemsInGridOrder[i].m_gridPos = new Vector2i(i % columns, i / columns); } } internal unsafe static Inventory LoadInventoryFromZdo(ZDO zdo) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown Inventory val = new Inventory("ServerChest", (Sprite)null, 8, 8); string text = zdo.GetString(ZDOVars.s_items, ""); ZDOID uid; if (!string.IsNullOrEmpty(text)) { try { val.Load(new ZPackage(text)); } catch (Exception ex) { ManualLogSource log = PraetorisClientPlugin.Log; uid = zdo.m_uid; log.LogWarning((object)("Failed to load ServerChest inventory from ZDO " + ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString() + ": " + ex.Message)); } } ApplyMaxInventoryShape(val); CompactInventory(val); string[] obj = new string[8] { "loaded inventory zdo=", null, null, null, null, null, null, null }; uid = zdo.m_uid; obj[1] = ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString(); obj[2] = " stacks="; obj[3] = val.NrOfItems().ToString(CultureInfo.InvariantCulture); obj[4] = " items="; obj[5] = val.NrOfItemsIncludingStacks().ToString(CultureInfo.InvariantCulture); obj[6] = " dataLength="; obj[7] = text.Length.ToString(CultureInfo.InvariantCulture); ServerChestLog.Debug(string.Concat(obj)); return val; } internal unsafe static void SaveInventoryToZdo(ZDO zdo, Inventory inventory) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) CompactInventory(inventory); ZPackage val = new ZPackage(); inventory.Save(val); string @base = val.GetBase64(); zdo.Set(ZDOVars.s_items, @base); string[] obj = new string[8] { "saved inventory zdo=", null, null, null, null, null, null, null }; ZDOID uid = zdo.m_uid; obj[1] = ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString(); obj[2] = " stacks="; obj[3] = inventory.NrOfItems().ToString(CultureInfo.InvariantCulture); obj[4] = " items="; obj[5] = inventory.NrOfItemsIncludingStacks().ToString(CultureInfo.InvariantCulture); obj[6] = " dataLength="; obj[7] = @base.Length.ToString(CultureInfo.InvariantCulture); ServerChestLog.Debug(string.Concat(obj)); } internal static List FindAllZdos() { List list = new List(); if (ZDOMan.instance == null) { return list; } int num = 0; while (!ZDOMan.instance.GetAllZDOsWithPrefabIterative("ServerChest", list, ref num)) { } list.RemoveAll((ZDO zdo) => zdo == null || !zdo.IsValid()); return list; } internal static void WithSuppressedInventoryChanged(Action action) { bool suppressInventoryChanged = _suppressInventoryChanged; _suppressInventoryChanged = true; try { action(); } finally { _suppressInventoryChanged = suppressInventoryChanged; } } internal static void ShowMessage(string message) { if ((Object)(object)Player.m_localPlayer != (Object)null) { ((Character)Player.m_localPlayer).Message((MessageType)2, message, 0, (Sprite)null); } else { PraetorisClientPlugin.Log.LogInfo((object)message); } } } internal static class ServerChestCommand { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__1_0; public static ConsoleEvent <>9__1_1; public static ConsoleEvent <>9__1_2; public static ConsoleEvent <>9__1_3; internal void b__1_0(ConsoleEventArgs args) { if (args.Length < 4) { args.Context.AddString("Usage: serverchest_send [quality]"); return; } if (!TryParsePositiveInt(args[3], out var parsed)) { args.Context.AddString("Amount must be greater than zero."); return; } int parsed2 = 1; if (args.Length >= 5 && !TryParsePositiveInt(args[4], out parsed2)) { args.Context.AddString("Quality must be greater than zero."); return; } List items = new List { new ServerChestService.SendItem { PrefabName = args[2], Amount = parsed, Quality = parsed2 } }; ExecuteOrRoute(args.Context, args[1], items); } internal void b__1_1(ConsoleEventArgs args) { if (args.Length < 3) { args.Context.AddString("Usage: serverchest_send_bulk :[:quality] ..."); return; } List list = new List(); for (int i = 2; i < args.Length; i++) { if (!TryParseBulkItem(args[i], out ServerChestService.SendItem item, out string error)) { args.Context.AddString(error); return; } list.Add(item); } ExecuteOrRoute(args.Context, args[1], list); } internal void b__1_2(ConsoleEventArgs args) { if (args.Length < 2) { args.Context.AddString("Usage: serverchest_status "); return; } if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { args.Context.AddString(ServerChestService.Status(args[1]).Message); return; } ServerChestRpc.RequestStatus(args[1]); args.Context.AddString("ServerChest status request submitted."); } internal void b__1_3(ConsoleEventArgs args) { string characterName = ((args.Length >= 2) ? args[1] : ""); if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { args.Context.AddString(ServerChestService.Find(characterName).Message); return; } ServerChestRpc.RequestFind(characterName); args.Context.AddString("ServerChest find request submitted."); } } private static bool _registered; internal static void Register() { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected O, but got Unknown //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected O, but got Unknown //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Expected O, but got Unknown if (_registered) { return; } _registered = true; object obj = <>c.<>9__1_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { int parsed; if (args.Length < 4) { args.Context.AddString("Usage: serverchest_send [quality]"); } else if (!TryParsePositiveInt(args[3], out parsed)) { args.Context.AddString("Amount must be greater than zero."); } else { int parsed2 = 1; if (args.Length >= 5 && !TryParsePositiveInt(args[4], out parsed2)) { args.Context.AddString("Quality must be greater than zero."); } else { List items = new List { new ServerChestService.SendItem { PrefabName = args[2], Amount = parsed, Quality = parsed2 } }; ExecuteOrRoute(args.Context, args[1], items); } } }; <>c.<>9__1_0 = val; obj = (object)val; } new ConsoleCommand("serverchest_send", "Send item(s) to a registered ServerChest. Usage: serverchest_send [quality]", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, true); object obj2 = <>c.<>9__1_1; if (obj2 == null) { ConsoleEvent val2 = delegate(ConsoleEventArgs args) { if (args.Length < 3) { args.Context.AddString("Usage: serverchest_send_bulk :[:quality] ..."); } else { List list = new List(); for (int i = 2; i < args.Length; i++) { if (!TryParseBulkItem(args[i], out ServerChestService.SendItem item, out string error)) { args.Context.AddString(error); return; } list.Add(item); } ExecuteOrRoute(args.Context, args[1], list); } }; <>c.<>9__1_1 = val2; obj2 = (object)val2; } new ConsoleCommand("serverchest_send_bulk", "Send multiple items to a registered ServerChest. Usage: serverchest_send_bulk :[:quality] ...", (ConsoleEvent)obj2, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, true); object obj3 = <>c.<>9__1_2; if (obj3 == null) { ConsoleEvent val3 = delegate(ConsoleEventArgs args) { if (args.Length < 2) { args.Context.AddString("Usage: serverchest_status "); } else if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { args.Context.AddString(ServerChestService.Status(args[1]).Message); } else { ServerChestRpc.RequestStatus(args[1]); args.Context.AddString("ServerChest status request submitted."); } }; <>c.<>9__1_2 = val3; obj3 = (object)val3; } new ConsoleCommand("serverchest_status", "Print ServerChest status. Usage: serverchest_status ", (ConsoleEvent)obj3, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, true); object obj4 = <>c.<>9__1_3; if (obj4 == null) { ConsoleEvent val4 = delegate(ConsoleEventArgs args) { string characterName = ((args.Length >= 2) ? args[1] : ""); if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { args.Context.AddString(ServerChestService.Find(characterName).Message); } else { ServerChestRpc.RequestFind(characterName); args.Context.AddString("ServerChest find request submitted."); } }; <>c.<>9__1_3 = val4; obj4 = (object)val4; } new ConsoleCommand("serverchest_find", "Find registered ServerChests. Usage: serverchest_find ", (ConsoleEvent)obj4, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, true); } private static void ExecuteOrRoute(Terminal context, string characterName, IReadOnlyList items) { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { ServerChestService.CommandResult commandResult = ServerChestService.SendItems(characterName, items); context.AddString(commandResult.Message); return; } ServerChestService.CommandResult commandResult2 = ServerChestService.ValidateItems(items); if (!commandResult2.Success) { context.AddString(commandResult2.Message); return; } ServerChestRpc.RequestSend(characterName, items); context.AddString("ServerChest send request submitted."); } internal static bool TryParseBulkItem(string token, out ServerChestService.SendItem item, out string error) { item = new ServerChestService.SendItem(); error = ""; string[] array = token.Split(new char[1] { ':' }); if (array.Length < 2 || array.Length > 3) { error = "Bulk item must be :[:quality]."; return false; } if (!TryParsePositiveInt(array[1], out var parsed)) { error = "Amount must be greater than zero for " + array[0] + "."; return false; } int parsed2 = 1; if (array.Length == 3 && !TryParsePositiveInt(array[2], out parsed2)) { error = "Quality must be greater than zero for " + array[0] + "."; return false; } item.PrefabName = array[0]; item.Amount = parsed; item.Quality = parsed2; return true; } internal static bool TryParsePositiveInt(string value, out int parsed) { if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out parsed)) { return parsed > 0; } return false; } } internal static class ServerChestIdentity { internal static string GetLocalPlatformId() { //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) try { return ((object)((IUser)PlatformManager.DistributionPlatform.LocalUser).PlatformUserID/*cast due to .constrained prefix*/).ToString(); } catch { return ""; } } internal static bool TryGetSenderIdentity(long sender, string requestedName, string requestedPlatformId, out string characterName, out string platformId) { characterName = (requestedName ?? "").Trim(); platformId = (requestedPlatformId ?? "").Trim(); ZNetPeer val = PlayerResolver.FindPeerBySender(sender); if (val != null) { if (!string.IsNullOrWhiteSpace(val.m_playerName)) { characterName = val.m_playerName.Trim(); } string text = PlayerResolver.StablePlayerId(val); if (!string.IsNullOrWhiteSpace(text)) { platformId = text.Trim(); } } else if (sender == ZNet.GetUID()) { if ((Object)(object)Game.instance != (Object)null && Game.instance.GetPlayerProfile() != null) { characterName = Game.instance.GetPlayerProfile().GetName(); } string localPlatformId = GetLocalPlatformId(); if (!string.IsNullOrWhiteSpace(localPlatformId)) { platformId = localPlatformId; } } if (!string.IsNullOrWhiteSpace(characterName)) { return !string.IsNullOrWhiteSpace(platformId); } return false; } } internal static class ServerChestLog { internal static void Debug(string message) { if (PraetorisClientPlugin.DebugServerChest != null && PraetorisClientPlugin.DebugServerChest.Value) { PraetorisClientPlugin.Log.LogInfo((object)("[ServerChest] " + message)); } } internal static void Warning(string message) { PraetorisClientPlugin.Log.LogWarning((object)("[ServerChest] " + message)); } } [HarmonyPatch(typeof(Container), "Interact")] internal static class ServerChestContainerInteractPatch { private static bool Prefix(Container __instance, Humanoid character, bool hold, bool alt, ref bool __result) { if (!ServerChest.IsServerChest(__instance)) { return true; } if (hold) { __result = false; return false; } if (!alt) { return true; } ((Component)__instance).GetComponent().OpenRegistrationPanel(); __result = true; return false; } } [HarmonyPatch(typeof(InventoryGrid), "UpdateInventory")] internal static class ServerChestInventoryGridUpdatePatch { private static bool Prefix(InventoryGrid __instance, Inventory inventory) { if (!ServerChest.TryGetByInventory(inventory, out ServerChest _)) { return true; } ServerChest.ApplyMaxInventoryShape(inventory); return true; } } [HarmonyPatch(typeof(InventoryGui), "UpdateContainer")] internal static class ServerChestInventoryGuiUpdateContainerPatch { private static readonly FieldInfo? CurrentContainerField = AccessTools.Field(typeof(InventoryGui), "m_currentContainer"); private static readonly FieldInfo? ElementsField = AccessTools.Field(typeof(InventoryGrid), "m_elements"); private static readonly Dictionary ElementGoFields = new Dictionary(); private static bool GridWasCompacted; private static void Postfix(InventoryGui __instance) { Container val = (Container)((CurrentContainerField != null) ? /*isinst with value type is only supported in some contexts*/: null); if ((Object)(object)val == (Object)null) { return; } InventoryGrid containerGrid = __instance.ContainerGrid; if ((Object)(object)containerGrid == (Object)null) { return; } if (ServerChestPiece.TryRestoreVanillaWoodChest(val, out var restored)) { if (GridWasCompacted || restored) { RestoreGrid(containerGrid); containerGrid.UpdateInventory(val.GetInventory(), (Player)null, (ItemData)null); GridWasCompacted = false; } return; } if (!ServerChest.IsServerChest(val)) { if (GridWasCompacted) { RestoreGrid(containerGrid); containerGrid.UpdateInventory(val.GetInventory(), (Player)null, (ItemData)null); GridWasCompacted = false; } return; } int num = val.GetInventory().NrOfItems(); if ((Object)(object)containerGrid.m_gridRoot != (Object)null) { ((Component)containerGrid.m_gridRoot).gameObject.SetActive(num > 0); float num2 = ((num <= 0) ? 0f : ((float)Math.Ceiling((double)num / 8.0))); containerGrid.m_gridRoot.SetSizeWithCurrentAnchors((Axis)1, num2 * containerGrid.m_elementSpace); } SetGridElementsActive(containerGrid, num); GridWasCompacted = true; } private static void RestoreGrid(InventoryGrid inventoryGrid) { if ((Object)(object)inventoryGrid.m_gridRoot != (Object)null) { ((Component)inventoryGrid.m_gridRoot).gameObject.SetActive(true); } SetGridElementsActive(inventoryGrid, int.MaxValue); } private static void SetGridElementsActive(InventoryGrid inventoryGrid, int visibleSlots) { IList elements = GetElements(inventoryGrid); if (elements == null) { return; } for (int i = 0; i < elements.Count; i++) { GameObject elementGameObject = GetElementGameObject(elements[i]); if ((Object)(object)elementGameObject != (Object)null) { elementGameObject.SetActive(i < visibleSlots); } } } private static IList? GetElements(InventoryGrid inventoryGrid) { if (!(ElementsField != null)) { return null; } return ElementsField.GetValue(inventoryGrid) as IList; } private static GameObject? GetElementGameObject(object element) { Type type = element.GetType(); if (!ElementGoFields.TryGetValue(type, out FieldInfo value)) { value = AccessTools.Field(type, "m_go"); ElementGoFields[type] = value; } if (!(value != null)) { return null; } object? value2 = value.GetValue(element); return (GameObject?)((value2 is GameObject) ? value2 : null); } } [HarmonyPatch(typeof(InventoryGrid), "DropItem")] internal static class ServerChestInventoryGridDropItemPatch { private static bool Prefix(InventoryGrid __instance, Inventory fromInventory, ref bool __result) { Inventory inventory = __instance.GetInventory(); if (inventory != null && fromInventory != null && inventory != fromInventory && ServerChest.TryGetByInventory(inventory, out ServerChest _)) { __result = false; ServerChest.ShowMessage("Items cannot be placed into a ServerChest."); return false; } return true; } } [HarmonyPatch(typeof(Inventory), "MoveItemToThis", new Type[] { typeof(Inventory), typeof(ItemData) })] internal static class ServerChestInventoryMoveItemToThisPatch { private static bool Prefix(Inventory __instance, Inventory fromInventory) { if (ShouldBlockMove(__instance, fromInventory)) { ServerChest.ShowMessage("Items cannot be placed into a ServerChest."); return false; } return true; } internal static bool ShouldBlockMove(Inventory target, Inventory source) { ServerChest serverChest; if (target != null && source != null && target != source) { return ServerChest.TryGetByInventory(target, out serverChest); } return false; } } [HarmonyPatch(typeof(Inventory), "MoveItemToThis", new Type[] { typeof(Inventory), typeof(ItemData), typeof(int), typeof(int), typeof(int) })] internal static class ServerChestInventoryMoveItemToThisAmountPatch { private static bool Prefix(Inventory __instance, Inventory fromInventory, ref bool __result) { if (ServerChestInventoryMoveItemToThisPatch.ShouldBlockMove(__instance, fromInventory)) { __result = false; ServerChest.ShowMessage("Items cannot be placed into a ServerChest."); return false; } return true; } } [HarmonyPatch(typeof(Inventory), "StackAll")] internal static class ServerChestInventoryStackAllPatch { private static bool Prefix(Inventory __instance, ref int __result) { if (ServerChest.TryGetByInventory(__instance, out ServerChest _)) { __result = 0; ServerChest.ShowMessage("Items cannot be placed into a ServerChest."); return false; } return true; } } internal static class ServerChestPiece { private readonly struct ContainerShape { internal string Name { get; } internal int Width { get; } internal int Height { get; } internal ContainerShape(string name, int width, int height) { Name = name; Width = width; Height = height; } } internal const string BasePrefabName = "piece_chest_wood"; private static bool _registered; private static ContainerShape? _vanillaWoodChestShape; internal static void Initialize() { PrefabManager.OnVanillaPrefabsAvailable += Register; } internal static void Shutdown() { PrefabManager.OnVanillaPrefabsAvailable -= Register; _registered = false; } private static void Register() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown if (_registered) { PrefabManager.OnVanillaPrefabsAvailable -= Register; return; } PieceConfig val = new PieceConfig { Name = "Server Chest", Description = "Receives admin-delivered items for one registered player.", PieceTable = PieceTables.Hammer, Category = PieceCategories.Misc }; ContainerShape? vanillaWoodChestShape = _vanillaWoodChestShape; if (!vanillaWoodChestShape.HasValue) { _vanillaWoodChestShape = CaptureVanillaWoodChestShape(); } CustomPiece val2 = new CustomPiece("ServerChest", "piece_chest_wood", val); GameObject piecePrefab = val2.PiecePrefab; if ((Object)(object)piecePrefab == (Object)null) { PraetorisClientPlugin.Log.LogError((object)"Failed to create ServerChest prefab from piece_chest_wood."); return; } ConfigurePrefab(piecePrefab); PieceManager.Instance.AddPiece(val2); RestoreVanillaWoodChestPrefab(); _registered = true; PrefabManager.OnVanillaPrefabsAvailable -= Register; } private static void ConfigurePrefab(GameObject prefab) { Container component = prefab.GetComponent(); if ((Object)(object)component != (Object)null) { component.m_name = "Server Chest"; component.m_width = 8; component.m_height = 8; component.m_defaultItems.m_drops.Clear(); } Piece component2 = prefab.GetComponent(); if ((Object)(object)component2 != (Object)null) { component2.m_name = "Server Chest"; component2.m_description = "Receives admin-delivered items for one registered player."; } if ((Object)(object)prefab.GetComponent() == (Object)null) { prefab.AddComponent(); } } internal static bool TryRestoreVanillaWoodChest(Container container, out bool restored) { restored = false; if (!IsVanillaWoodChest(container)) { return false; } ContainerShape? vanillaWoodChestShape = _vanillaWoodChestShape; if (!vanillaWoodChestShape.HasValue) { return true; } restored |= RestoreContainerComponent(container, vanillaWoodChestShape.Value); Inventory inventory = container.GetInventory(); ServerChest component = ((Component)container).GetComponent(); if ((Object)(object)component != (Object)null) { if (inventory != null) { ServerChest.ForgetInventory(inventory); } Object.Destroy((Object)(object)component); restored = true; } if (inventory == null || (inventory.GetWidth() == vanillaWoodChestShape.Value.Width && inventory.GetHeight() == vanillaWoodChestShape.Value.Height)) { return true; } int num = vanillaWoodChestShape.Value.Width * vanillaWoodChestShape.Value.Height; if (inventory.NrOfItems() > num) { PraetorisClientPlugin.Log.LogWarning((object)"Leaving piece_chest_wood at expanded size because it has more stacks than vanilla capacity."); return true; } ServerChest.CompactInventory(inventory, vanillaWoodChestShape.Value.Width); ServerChest.ApplyInventoryShape(inventory, vanillaWoodChestShape.Value.Width, vanillaWoodChestShape.Value.Height); restored = true; return true; } private static ContainerShape? CaptureVanillaWoodChestShape() { GameObject prefab = PrefabManager.Instance.GetPrefab("piece_chest_wood"); Container val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent() : null); if ((Object)(object)val == (Object)null) { PraetorisClientPlugin.Log.LogWarning((object)"Unable to capture vanilla wood chest container shape."); return null; } return new ContainerShape(val.m_name, val.m_width, val.m_height); } private static void RestoreVanillaWoodChestPrefab() { ContainerShape? vanillaWoodChestShape = _vanillaWoodChestShape; if (!vanillaWoodChestShape.HasValue) { return; } GameObject prefab = PrefabManager.Instance.GetPrefab("piece_chest_wood"); Container val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent() : null); if (!((Object)(object)val == (Object)null)) { RestoreContainerComponent(val, vanillaWoodChestShape.Value); ServerChest component = ((Component)val).GetComponent(); if ((Object)(object)component != (Object)null) { Object.Destroy((Object)(object)component); } } } private static bool RestoreContainerComponent(Container container, ContainerShape shape) { bool result = false; if (container.m_name != shape.Name) { container.m_name = shape.Name; result = true; } if (container.m_width != shape.Width) { container.m_width = shape.Width; result = true; } if (container.m_height != shape.Height) { container.m_height = shape.Height; result = true; } return result; } private static bool IsVanillaWoodChest(Container container) { if ((Object)(object)container == (Object)null) { return false; } string name = ((Object)((Component)container).gameObject).name; if (!(name == "piece_chest_wood")) { return name.StartsWith("piece_chest_wood("); } return true; } } internal static class ServerChestRconCommand { internal const string ValheimRconGuid = "org.tristan.rcon"; private static bool _registered; internal static void Register() { if (!_registered) { _registered = true; RconProxy.Instance.RegisterCommand("serverchest_send", "Send item(s) to a registered ServerChest. Usage: serverchest_send [quality]", (Func)Send); RconProxy.Instance.RegisterCommand("serverchest_send_bulk", "Send multiple items to a registered ServerChest. Usage: serverchest_send_bulk :[:quality] ...", (Func)SendBulk); RconProxy.Instance.RegisterCommand("serverchest_status", "Print ServerChest status. Usage: serverchest_status ", (Func)Status); RconProxy.Instance.RegisterCommand("serverchest_find", "Find registered ServerChests. Usage: serverchest_find ", (Func)Find); PraetorisClientPlugin.Log.LogInfo((object)"Registered ServerChest RCON commands."); } } private static CommandResult Send(CommandArgs args) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) IReadOnlyList arguments = args.Arguments; if (arguments.Count < 3) { return Result("Usage: serverchest_send [quality]"); } if (!ServerChestCommand.TryParsePositiveInt(arguments[2], out var parsed)) { return Result("Amount must be greater than zero."); } int parsed2 = 1; if (arguments.Count >= 4 && !ServerChestCommand.TryParsePositiveInt(arguments[3], out parsed2)) { return Result("Quality must be greater than zero."); } List items = new List { new ServerChestService.SendItem { PrefabName = arguments[1], Amount = parsed, Quality = parsed2 } }; return Result(ServerChestService.SendItems(arguments[0], items).Message); } private static CommandResult SendBulk(CommandArgs args) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) IReadOnlyList arguments = args.Arguments; if (arguments.Count < 2) { return Result("Usage: serverchest_send_bulk :[:quality] ..."); } List list = new List(); for (int i = 1; i < arguments.Count; i++) { if (!ServerChestCommand.TryParseBulkItem(arguments[i], out ServerChestService.SendItem item, out string error)) { return Result(error); } list.Add(item); } return Result(ServerChestService.SendItems(arguments[0], list).Message); } private static CommandResult Status(CommandArgs args) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) IReadOnlyList arguments = args.Arguments; if (arguments.Count < 1) { return Result("Usage: serverchest_status "); } return Result(ServerChestService.Status(arguments[0]).Message); } private static CommandResult Find(CommandArgs args) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) IReadOnlyList arguments = args.Arguments; return Result(ServerChestService.Find((arguments.Count >= 1) ? arguments[0] : "").Message); } private static CommandResult Result(string message) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) return new CommandResult { Text = message }; } } internal sealed class ServerChestRegistrationPanel : MonoBehaviour { private static ServerChestRegistrationPanel? _instance; private GameObject? _panel; private GuiInputField? _input; private TMP_Text? _topic; private ServerChest? _chest; internal static void Open(ServerChest chest) { EnsureInstance().Show(chest); } private static ServerChestRegistrationPanel EnsureInstance() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown if ((Object)(object)_instance != (Object)null) { return _instance; } GameObject val = new GameObject("ServerChestRegistrationPanel"); Object.DontDestroyOnLoad((Object)val); _instance = val.AddComponent(); _instance.Build(); return _instance; } private void Build() { if ((Object)(object)TextInput.instance == (Object)null || (Object)(object)TextInput.instance.m_panel == (Object)null) { PraetorisClientPlugin.Log.LogWarning((object)"TextInput panel is unavailable; ServerChest registration UI cannot be created yet."); return; } Transform parent = TextInput.instance.m_panel.transform.parent; _panel = Object.Instantiate(TextInput.instance.m_panel, parent); ((Object)_panel).name = "ServerChestRegistrationPanel"; _panel.SetActive(false); _input = _panel.GetComponentInChildren(true); _topic = FindTopicText(_panel); ConfigureButtons(_panel); } private void Show(ServerChest chest) { _chest = chest; if ((Object)(object)_panel == (Object)null) { Build(); } if ((Object)(object)_panel == (Object)null || (Object)(object)_input == (Object)null) { ServerChest.ShowMessage("ServerChest registration panel is unavailable."); return; } if ((Object)(object)_topic != (Object)null) { _topic.text = "Register ServerChest"; } ((TMP_InputField)_input).characterLimit = 120; ((TMP_InputField)_input).text = chest.GetRegistrationPreview(); _panel.SetActive(true); _input.ActivateInputField(); } private void Hide() { if ((Object)(object)_panel != (Object)null) { _panel.SetActive(false); } _chest = null; } private void Register() { ServerChest chest = _chest; Hide(); if ((Object)(object)chest != (Object)null) { chest.RequestRegistration(); } } private TMP_Text? FindTopicText(GameObject panel) { TMP_Text[] componentsInChildren = panel.GetComponentsInChildren(true); TMP_Text[] array = componentsInChildren; foreach (TMP_Text val in array) { if ((Object)(object)TextInput.instance != (Object)null && (Object)(object)TextInput.instance.m_topic != (Object)null && ((Object)val).name == ((Object)TextInput.instance.m_topic).name) { return val; } } if (componentsInChildren.Length == 0) { return null; } return componentsInChildren[0]; } private void ConfigureButtons(GameObject panel) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Expected O, but got Unknown //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected O, but got Unknown Button[] componentsInChildren = panel.GetComponentsInChildren