using System; using System.Collections; 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.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using BountyHuntercontract; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using PieceManager; using ServerSync; using TMPro; using UnityEngine; using UnityEngine.Networking; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("BountyHunterContract")] [assembly: AssemblyDescription("Multiplayer player bounty contracts for Valheim")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Azathoth")] [assembly: AssemblyProduct("BountyHunterContract")] [assembly: AssemblyCopyright("Copyright © Azathoth 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("4358610B-F3F4-4843-B7AF-98B7BC60DCDE")] [assembly: AssemblyFileVersion("1.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] 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; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace PieceManager { [PublicAPI] public static class MaterialReplacer { public enum ShaderType { PieceShader, VegetationShader, RockShader, RugShader, GrassShader, CustomCreature, UseUnityShader, ToonDeferred } private static readonly Dictionary ObjectToSwap; private static readonly Dictionary OriginalMaterials; private static readonly Dictionary ObjectsForShaderReplace; private static readonly HashSet ArmoredHumanoidShaderObjects; private static readonly HashSet CachedShaders; private static bool hasRun; static MaterialReplacer() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown ArmoredHumanoidShaderObjects = new HashSet(); CachedShaders = new HashSet(); hasRun = false; OriginalMaterials = new Dictionary(); ObjectToSwap = new Dictionary(); ObjectsForShaderReplace = new Dictionary(); Harmony val = new Harmony("org.bepinex.helpers.PieceManager"); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(ZoneSystem), "Start", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(MaterialReplacer), "ReplaceAllMaterialsWithOriginal", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } public static void RegisterGameObjectForShaderSwap(GameObject go, ShaderType type) { if (!((Object)(object)go == (Object)null)) { if (!ObjectsForShaderReplace.ContainsKey(go)) { ObjectsForShaderReplace.Add(go, type); } else { ObjectsForShaderReplace[go] = type; } BountyDiagnostics.Detailed("SHADER", "Registered object='" + ((Object)go).name + "' for shader profile=" + type.ToString() + "."); } } public static void RegisterArmoredHumanoidForShaderSwap(GameObject go) { if (!((Object)(object)go == (Object)null)) { if (!ArmoredHumanoidShaderObjects.Contains(go)) { ArmoredHumanoidShaderObjects.Add(go); } if (!ObjectsForShaderReplace.ContainsKey(go)) { ObjectsForShaderReplace.Add(go, ShaderType.ToonDeferred); } else { ObjectsForShaderReplace[go] = ShaderType.ToonDeferred; } } } public static void RegisterGameObjectForMatSwap(GameObject go, bool isJotunnMock = false) { if (!((Object)(object)go == (Object)null) && !ObjectToSwap.ContainsKey(go)) { ObjectToSwap.Add(go, isJotunnMock); } } private static void GetAllMaterials() { Material[] array = Resources.FindObjectsOfTypeAll(); foreach (Material val in array) { OriginalMaterials[((Object)val).name] = val; } } [HarmonyPriority(700)] private static void ReplaceAllMaterialsWithOriginal() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 if ((int)SystemInfo.graphicsDeviceType == 4 || hasRun) { return; } BountyDiagnostics.Info("SHADER", "Starting material/shader replacement. materialObjects=" + ObjectToSwap.Count + " shaderObjects=" + ObjectsForShaderReplace.Count + "."); if (OriginalMaterials.Count == 0) { GetAllMaterials(); } foreach (KeyValuePair item in ObjectToSwap) { GameObject key = item.Key; bool value = item.Value; ProcessGameObjectMaterials(key, value); } AssetBundle[] array = Resources.FindObjectsOfTypeAll(); AssetBundle[] array2 = array; foreach (AssetBundle val in array2) { IEnumerable enumerable3; try { IEnumerable enumerable2; if (!val.isStreamedSceneAssetBundle || !Object.op_Implicit((Object)(object)val)) { IEnumerable enumerable = val.LoadAllAssets(); enumerable2 = enumerable; } else { enumerable2 = from shader in ((IEnumerable)val.GetAllAssetNames()).Select((Func)val.LoadAsset) where (Object)(object)shader != (Object)null select shader; } enumerable3 = enumerable2; } catch (Exception) { continue; } if (enumerable3 == null) { continue; } foreach (Shader item2 in enumerable3) { CachedShaders.Add(item2); } } foreach (KeyValuePair item3 in ObjectsForShaderReplace) { GameObject key2 = item3.Key; ShaderType value2 = item3.Value; ProcessGameObjectShaders(key2, value2); } hasRun = true; BountyDiagnostics.Info("SHADER", "Material/shader replacement completed. cachedShaders=" + CachedShaders.Count + " originalMaterials=" + OriginalMaterials.Count + "."); } private static void ProcessGameObjectMaterials(GameObject go, bool isJotunnMock) { Renderer[] componentsInChildren = go.GetComponentsInChildren(true); Renderer[] array = componentsInChildren; foreach (Renderer val in array) { Material[] sharedMaterials = val.sharedMaterials.Select((Material material) => ((Object)(object)material == (Object)null) ? null : ReplaceMaterial(material, isJotunnMock)).ToArray(); val.sharedMaterials = sharedMaterials; } } private static Material ReplaceMaterial(Material originalMaterial, bool isJotunnMock) { if ((Object)(object)originalMaterial == (Object)null) { return null; } string text = (isJotunnMock ? "JVLmock_" : "_REPLACE_"); if (!((Object)originalMaterial).name.StartsWith(text, StringComparison.Ordinal)) { return originalMaterial; } string text2 = ((Object)originalMaterial).name.Replace(" (Instance)", "").Replace(text, ""); if (OriginalMaterials.TryGetValue(text2, out Material value)) { return value; } Debug.LogWarning((object)("No suitable material found to replace: " + text2)); return originalMaterial; } private static void ProcessGameObjectShaders(GameObject go, ShaderType shaderType) { if ((Object)(object)go == (Object)null) { return; } if (ArmoredHumanoidShaderObjects.Contains(go)) { ProcessArmoredHumanoidShaders(go); return; } Renderer[] componentsInChildren = go.GetComponentsInChildren(true); Renderer[] array = componentsInChildren; foreach (Renderer val in array) { Material[] sharedMaterials = val.sharedMaterials; foreach (Material val2 in sharedMaterials) { if ((Object)(object)val2 != (Object)null) { string text = (((Object)(object)val2.shader != (Object)null) ? ((Object)val2.shader).name : ""); Shader val3 = (val2.shader = GetShaderForType(val2.shader, shaderType, text)); BountyDiagnostics.Detailed("SHADER", "Object='" + ((Object)go).name + "' renderer='" + ((Object)val).name + "' material='" + ((Object)val2).name + "' shader=" + text + " -> " + (((Object)(object)val3 != (Object)null) ? ((Object)val3).name : "") + "."); } } } } private static void ProcessArmoredHumanoidShaders(GameObject go) { Renderer[] componentsInChildren = go.GetComponentsInChildren(true); Renderer[] array = componentsInChildren; foreach (Renderer val in array) { if ((Object)(object)val == (Object)null) { continue; } string text = ((Object)val).name.ToLowerInvariant(); if (text.Contains("eyepos") || text.Contains("collider") || text.Contains("hitbox")) { continue; } Material[] sharedMaterials = val.sharedMaterials; foreach (Material val2 in sharedMaterials) { if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2.shader == (Object)null)) { val2.shader = FindShaderWithName(val2.shader, "ToonDeferredShading2017"); } } } } private static Shader GetShaderForType(Shader orig, ShaderType shaderType, string originalShaderName) { return (Shader)(shaderType switch { ShaderType.PieceShader => FindShaderWithName(orig, "Custom/Piece"), ShaderType.VegetationShader => FindShaderWithName(orig, "Custom/Vegetation"), ShaderType.RockShader => FindShaderWithName(orig, "Custom/StaticRock"), ShaderType.RugShader => FindShaderWithName(orig, "Custom/Rug"), ShaderType.GrassShader => FindShaderWithName(orig, "Custom/Grass"), ShaderType.CustomCreature => FindShaderWithName(orig, "Custom/Creature"), ShaderType.ToonDeferred => FindShaderWithName(orig, "ToonDeferredShading2017"), ShaderType.UseUnityShader => FindShaderWithName(orig, ((Object)(object)FindShaderWithName(orig, originalShaderName) != (Object)null) ? originalShaderName : "ToonDeferredShading2017"), _ => FindShaderWithName(orig, "Standard"), }); } public static Shader FindShaderWithName(Shader origShader, string name) { foreach (Shader cachedShader in CachedShaders) { if ((Object)(object)cachedShader != (Object)null && ((Object)cachedShader).name == name) { return cachedShader; } } Shader val = Shader.Find(name); if ((Object)(object)val != (Object)null) { CachedShaders.Add(val); return val; } BountyDiagnostics.Warning("SHADER", "Shader '" + name + "' was not found; keeping original shader '" + (((Object)(object)origShader != (Object)null) ? ((Object)origShader).name : "") + "'."); return origShader; } } [PublicAPI] public enum CraftingTable { None, [InternalName("piece_workbench")] Workbench, [InternalName("piece_cauldron")] Cauldron, [InternalName("forge")] Forge, [InternalName("piece_artisanstation")] ArtisanTable, [InternalName("piece_stonecutter")] StoneCutter, [InternalName("piece_magetable")] MageTable, [InternalName("blackforge")] BlackForge, [InternalName("piece_preptable")] FoodPreparationTable, [InternalName("piece_MeadCauldron")] MeadKetill, Custom } public class InternalName : Attribute { public readonly string internalName; public InternalName(string internalName) { this.internalName = internalName; } } [PublicAPI] public class ExtensionList { public readonly List ExtensionStations = new List(); public void Set(CraftingTable table, int maxStationDistance = 5) { ExtensionStations.Add(new ExtensionConfig { Table = table, maxStationDistance = maxStationDistance }); } public void Set(string customTable, int maxStationDistance = 5) { ExtensionStations.Add(new ExtensionConfig { Table = CraftingTable.Custom, custom = customTable, maxStationDistance = maxStationDistance }); } } public struct ExtensionConfig { public CraftingTable Table; public float maxStationDistance; public string? custom; } [PublicAPI] public class CraftingStationList { public readonly List Stations = new List(); public void Set(CraftingTable table) { Stations.Add(new CraftingStationConfig { Table = table }); } public void Set(string customTable) { Stations.Add(new CraftingStationConfig { Table = CraftingTable.Custom, custom = customTable }); } } public struct CraftingStationConfig { public CraftingTable Table; public int level; public string? custom; } [PublicAPI] public enum BuildPieceCategory { Misc = 0, Crafting = 1, BuildingWorkbench = 2, BuildingStonecutter = 3, Furniture = 4, All = 100, Custom = 99 } [PublicAPI] public class RequiredResourcesList { public readonly List Requirements = new List(); public void Add(string item, int amount, bool recover) { Requirements.Add(new Requirement { itemName = item, amount = amount, recover = recover }); } } public struct Requirement { public string itemName; public int amount; public bool recover; } public struct SpecialProperties { [Description("Admins should be the only ones that can build this piece.")] public bool AdminOnly; [Description("Turns off generating a config for this build piece.")] public bool NoConfig; } [PublicAPI] public class BuildingPieceCategory { public BuildPieceCategory Category; public string custom = ""; public void Set(BuildPieceCategory category) { Category = category; } public void Set(string customCategory) { Category = BuildPieceCategory.Custom; custom = customCategory; } } [PublicAPI] public class PieceTool { public readonly HashSet Tools = new HashSet(); public void Add(string tool) { Tools.Add(tool); } } [PublicAPI] public class BuildPiece { internal class PieceConfig { public ConfigEntry craft = null; public ConfigEntry category = null; public ConfigEntry customCategory = null; public ConfigEntry tools = null; public ConfigEntry extensionTable = null; public ConfigEntry customExtentionTable = null; public ConfigEntry maxStationDistance = null; public ConfigEntry table = null; public ConfigEntry customTable = null; } private class ConfigurationManagerAttributes { [UsedImplicitly] public int? Order; [UsedImplicitly] public bool? Browsable; [UsedImplicitly] public string? Category; [UsedImplicitly] public Action? CustomDrawer; } private class SerializedRequirements { public readonly List Reqs; public SerializedRequirements(List reqs) { Reqs = reqs; } public SerializedRequirements(string reqs) { Reqs = reqs.Split(new char[1] { ',' }).Select(delegate(string r) { string[] array = r.Split(new char[1] { ':' }); int result; bool result2 = default(bool); return new Requirement { itemName = array[0], amount = ((array.Length <= 1 || !int.TryParse(array[1], out result)) ? 1 : result), recover = (array.Length <= 2 || !bool.TryParse(array[2], out result2) || result2) }; }).ToList(); } public override string ToString() { return string.Join(",", Reqs.Select((Requirement r) => $"{r.itemName}:{r.amount}:{r.recover}")); } public static ItemDrop? fetchByName(ObjectDB objectDB, string name) { GameObject itemPrefab = objectDB.GetItemPrefab(name); ItemDrop val = ((itemPrefab != null) ? itemPrefab.GetComponent() : null); if ((Object)(object)val == (Object)null) { Debug.LogWarning((object)(((!string.IsNullOrWhiteSpace(((Object)plugin).name)) ? ("[" + ((Object)plugin).name + "]") : "") + " The required item '" + name + "' does not exist.")); } return val; } public static Requirement[] toPieceReqs(SerializedRequirements craft) { Dictionary dictionary = craft.Reqs.Where((Requirement r) => r.itemName != "").ToDictionary((Func)((Requirement r) => r.itemName), (Func)delegate(Requirement r) { //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_001e: 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) ItemDrop val = ResItem(r); return (val == null) ? ((Requirement)null) : new Requirement { m_amount = r.amount, m_resItem = val, m_recover = r.recover }; }); return dictionary.Values.Where((Requirement v) => v != null).ToArray(); static ItemDrop? ResItem(Requirement r) { return fetchByName(ObjectDB.instance, r.itemName); } } } internal static readonly List registeredPieces = new List(); private static readonly Queue<(GameObject prefab, float lightIntensity, Quaternion? camRot)> _snapshotQueue = new Queue<(GameObject, float, Quaternion?)>(); private static readonly Dictionary pieceMap = new Dictionary(); internal static Dictionary pieceConfigs = new Dictionary(); internal List Conversions = new List(); internal List conversions = new List(); [Description("Disables generation of the configs for your pieces. This is global, this turns it off for all pieces in your mod.")] public static bool ConfigurationEnabled = true; public readonly GameObject Prefab; [Description("Specifies the resources needed to craft the piece.\nUse .Add to add resources with their internal ID and an amount.\nUse one .Add for each resource type the building piece should need.")] public readonly RequiredResourcesList RequiredItems = new RequiredResourcesList(); [Description("Sets the category for the building piece.")] public readonly BuildingPieceCategory Category = new BuildingPieceCategory(); [Description("Specifies the tool needed to build your piece.\nUse .Add to add a tool.")] public readonly PieceTool Tool = new PieceTool(); [Description("Specifies the crafting station needed to build your piece.\nUse .Add to add a crafting station, using the CraftingTable enum and a minimum level for the crafting station.")] public CraftingStationList Crafting = new CraftingStationList(); [Description("Makes this piece a station extension")] public ExtensionList Extension = new ExtensionList(); [Description("Change the extended/special properties of your build piece.")] public SpecialProperties SpecialProperties; [Description("Specifies a config entry which toggles whether a recipe is active.")] public ConfigEntryBase? RecipeIsActive = null; private LocalizeKey? _name; private LocalizeKey? _description; internal string[] activeTools = null; private static object? configManager; private static Localization? _english; internal static BaseUnityPlugin? _plugin = null; private static bool hasConfigSync = true; private static object? _configSync; public LocalizeKey Name { get { LocalizeKey name = _name; if (name != null) { return name; } Piece component = Prefab.GetComponent(); if (component.m_name.StartsWith("$")) { _name = new LocalizeKey(component.m_name); } else { string text = "$piece_" + ((Object)Prefab).name.Replace(" ", "_"); _name = new LocalizeKey(text).English(component.m_name); component.m_name = text; } return _name; } } public LocalizeKey Description { get { LocalizeKey description = _description; if (description != null) { return description; } Piece component = Prefab.GetComponent(); if (component.m_description.StartsWith("$")) { _description = new LocalizeKey(component.m_description); } else { string text = "$piece_" + ((Object)Prefab).name.Replace(" ", "_") + "_description"; _description = new LocalizeKey(text).English(component.m_description); component.m_description = text; } return _description; } } private static Localization english => _english ?? (_english = LocalizationCache.ForLanguage("English")); internal static BaseUnityPlugin plugin { get { //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Expected O, but got Unknown if (_plugin != null) { return _plugin; } IEnumerable source; try { source = Assembly.GetExecutingAssembly().DefinedTypes.ToList(); } catch (ReflectionTypeLoadException ex) { source = from t in ex.Types where t != null select t.GetTypeInfo(); } _plugin = (BaseUnityPlugin)Chainloader.ManagerObject.GetComponent((Type)source.First((TypeInfo t) => t.IsClass && typeof(BaseUnityPlugin).IsAssignableFrom(t))); return _plugin; } } private static object? configSync { get { if (_configSync != null || !hasConfigSync) { return _configSync; } Type type = Assembly.GetExecutingAssembly().GetType("ServerSync.ConfigSync"); if ((object)type != null) { _configSync = Activator.CreateInstance(type, plugin.Info.Metadata.GUID + " PieceManager"); type.GetField("CurrentVersion").SetValue(_configSync, plugin.Info.Metadata.Version.ToString()); type.GetProperty("IsLocked").SetValue(_configSync, true); } else { hasConfigSync = false; } return _configSync; } } public BuildPiece(string assetBundleFileName, string prefabName, string folderName = "assets") : this(PiecePrefabManager.RegisterAssetBundle(assetBundleFileName, folderName), prefabName) { } public BuildPiece(AssetBundle bundle, string prefabName) { Prefab = PiecePrefabManager.RegisterPrefab(bundle, prefabName); registeredPieces.Add(this); } internal static void Patch_FejdStartup(FejdStartup __instance) { //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_02e1: Unknown result type (might be due to invalid IL or missing references) //IL_02eb: Expected O, but got Unknown //IL_0389: Unknown result type (might be due to invalid IL or missing references) //IL_0393: Expected O, but got Unknown //IL_0431: Unknown result type (might be due to invalid IL or missing references) //IL_040b: Unknown result type (might be due to invalid IL or missing references) //IL_0410: Unknown result type (might be due to invalid IL or missing references) //IL_048c: Unknown result type (might be due to invalid IL or missing references) //IL_0496: Expected O, but got Unknown //IL_0606: Unknown result type (might be due to invalid IL or missing references) //IL_0610: Expected O, but got Unknown //IL_08ea: Unknown result type (might be due to invalid IL or missing references) //IL_08f4: Expected O, but got Unknown //IL_067b: Unknown result type (might be due to invalid IL or missing references) //IL_0685: Expected O, but got Unknown //IL_0721: Unknown result type (might be due to invalid IL or missing references) //IL_072b: Expected O, but got Unknown //IL_095f: Unknown result type (might be due to invalid IL or missing references) //IL_0969: Expected O, but got Unknown //IL_0b7e: Unknown result type (might be due to invalid IL or missing references) //IL_0b88: Expected O, but got Unknown //IL_0c18: Unknown result type (might be due to invalid IL or missing references) //IL_0c22: Expected O, but got Unknown Type configManagerType = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => a.GetName().Name == "ConfigurationManager")?.GetType("ConfigurationManager.ConfigurationManager"); configManager = ((configManagerType == null) ? null : Chainloader.ManagerObject.GetComponent(configManagerType)); foreach (BuildPiece registeredPiece in registeredPieces) { registeredPiece.activeTools = registeredPiece.Tool.Tools.DefaultIfEmpty("Hammer").ToArray(); if (registeredPiece.Category.Category != BuildPieceCategory.Custom) { registeredPiece.Prefab.GetComponent().m_category = (PieceCategory)registeredPiece.Category.Category; } else { registeredPiece.Prefab.GetComponent().m_category = PiecePrefabManager.GetCategory(registeredPiece.Category.custom); } } if (!ConfigurationEnabled) { return; } bool saveOnConfigSet = plugin.Config.SaveOnConfigSet; plugin.Config.SaveOnConfigSet = false; foreach (BuildPiece registeredPiece2 in registeredPieces) { BuildPiece piece = registeredPiece2; if (piece.SpecialProperties.NoConfig) { continue; } PieceConfig pieceConfig = (pieceConfigs[piece] = new PieceConfig()); PieceConfig cfg = pieceConfig; Piece piecePrefab = piece.Prefab.GetComponent(); string pieceName = piecePrefab.m_name; string englishName = new Regex("[=\\n\\t\\\\\"\\'\\[\\]]*").Replace(english.Localize(pieceName), "").Trim(); string localizedName = Localization.instance.Localize(pieceName).Trim(); int order = 0; cfg.category = config(englishName, "Build Table Category", piece.Category.Category, new ConfigDescription("Build Category where " + localizedName + " is available.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = (order -= 1), Category = localizedName } })); ConfigurationManagerAttributes customTableAttributes = new ConfigurationManagerAttributes { Order = (order -= 1), Browsable = (cfg.category.Value == BuildPieceCategory.Custom), Category = localizedName }; cfg.customCategory = config(englishName, "Custom Build Category", piece.Category.custom, new ConfigDescription("", (AcceptableValueBase)null, new object[1] { customTableAttributes })); cfg.category.SettingChanged += BuildTableConfigChanged; cfg.customCategory.SettingChanged += BuildTableConfigChanged; if (cfg.category.Value == BuildPieceCategory.Custom) { piecePrefab.m_category = PiecePrefabManager.GetCategory(cfg.customCategory.Value); } else { piecePrefab.m_category = (PieceCategory)cfg.category.Value; } cfg.tools = config(englishName, "Tools", string.Join(", ", piece.activeTools), new ConfigDescription("Comma separated list of tools where " + localizedName + " is available.", (AcceptableValueBase)null, new object[1] { customTableAttributes })); piece.activeTools = (from s in cfg.tools.Value.Split(new char[1] { ',' }) select s.Trim()).ToArray(); cfg.tools.SettingChanged += delegate { Inventory[] source = (from c in Player.s_players.Select((Player p) => ((Humanoid)p).GetInventory()).Concat(from c in Object.FindObjectsByType((FindObjectsSortMode)0) select c.GetInventory()) where c != null select c).ToArray(); Dictionary> dictionary = (from kv in (from i in (from p in ObjectDB.instance.m_items select p.GetComponent() into c where Object.op_Implicit((Object)(object)c) && Object.op_Implicit((Object)(object)((Component)c).GetComponent()) select c).Concat(ItemDrop.s_instances) select new KeyValuePair(Utils.GetPrefabName(((Component)i).gameObject), i.m_itemData)).Concat(from i in source.SelectMany((Inventory i) => i.GetAllItems()) select new KeyValuePair(((Object)i.m_dropPrefab).name, i)) where Object.op_Implicit((Object)(object)kv.Value.m_shared.m_buildPieces) group kv by kv.Key).ToDictionary((IGrouping> g) => g.Key, (IGrouping> g) => g.Select((KeyValuePair kv) => kv.Value.m_shared.m_buildPieces).Distinct().ToList()); string[] array4 = piece.activeTools; foreach (string key in array4) { if (dictionary.TryGetValue(key, out var value)) { foreach (PieceTable item3 in value) { item3.m_pieces.Remove(piece.Prefab); } } } piece.activeTools = (from s in cfg.tools.Value.Split(new char[1] { ',' }) select s.Trim()).ToArray(); if (Object.op_Implicit((Object)(object)ObjectDB.instance)) { string[] array5 = piece.activeTools; foreach (string key2 in array5) { if (dictionary.TryGetValue(key2, out var value2)) { foreach (PieceTable item4 in value2) { if (!item4.m_pieces.Contains(piece.Prefab)) { item4.m_pieces.Add(piece.Prefab); } } } } if (Object.op_Implicit((Object)(object)Player.m_localPlayer) && Object.op_Implicit((Object)(object)Player.m_localPlayer.m_buildPieces)) { PiecePrefabManager.CategoryRefreshNeeded = true; ((Humanoid)Player.m_localPlayer).SetPlaceMode(Player.m_localPlayer.m_buildPieces); } } }; StationExtension pieceExtensionComp; List hideWhenNoneAttributes; if (piece.Extension.ExtensionStations.Count > 0) { pieceExtensionComp = piece.Prefab.GetOrAddComponent(); PieceConfig pieceConfig3 = cfg; string text = englishName; CraftingTable table = piece.Extension.ExtensionStations.First().Table; string text2 = "Crafting station that " + localizedName + " extends."; object[] array = new object[1]; ConfigurationManagerAttributes configurationManagerAttributes = new ConfigurationManagerAttributes(); int num = order - 1; order = num; configurationManagerAttributes.Order = num; array[0] = configurationManagerAttributes; pieceConfig3.extensionTable = config(text, "Extends Station", table, new ConfigDescription(text2, (AcceptableValueBase)null, array)); cfg.customExtentionTable = config(englishName, "Custom Extend Station", piece.Extension.ExtensionStations.First().custom ?? "", new ConfigDescription("", (AcceptableValueBase)null, new object[1] { customTableAttributes })); PieceConfig pieceConfig4 = cfg; string text3 = englishName; float maxStationDistance = piece.Extension.ExtensionStations.First().maxStationDistance; string text4 = "Distance from the station that " + localizedName + " can be placed."; object[] array2 = new object[1]; ConfigurationManagerAttributes configurationManagerAttributes2 = new ConfigurationManagerAttributes(); num = order - 1; order = num; configurationManagerAttributes2.Order = num; array2[0] = configurationManagerAttributes2; pieceConfig4.maxStationDistance = config(text3, "Max Station Distance", maxStationDistance, new ConfigDescription(text4, (AcceptableValueBase)null, array2)); hideWhenNoneAttributes = new List(); cfg.extensionTable.SettingChanged += ExtensionTableConfigChanged; cfg.customExtentionTable.SettingChanged += ExtensionTableConfigChanged; cfg.maxStationDistance.SettingChanged += ExtensionTableConfigChanged; ConfigurationManagerAttributes configurationManagerAttributes3 = new ConfigurationManagerAttributes(); num = order - 1; order = num; configurationManagerAttributes3.Order = num; configurationManagerAttributes3.Browsable = cfg.extensionTable.Value != CraftingTable.None; ConfigurationManagerAttributes item = configurationManagerAttributes3; hideWhenNoneAttributes.Add(item); } List hideWhenNoneAttributes2; if (piece.Crafting.Stations.Count > 0) { hideWhenNoneAttributes2 = new List(); PieceConfig pieceConfig5 = cfg; string text5 = englishName; CraftingTable table2 = piece.Crafting.Stations.First().Table; string text6 = "Crafting station where " + localizedName + " is available."; object[] array3 = new object[1]; ConfigurationManagerAttributes configurationManagerAttributes4 = new ConfigurationManagerAttributes(); int num = order - 1; order = num; configurationManagerAttributes4.Order = num; array3[0] = configurationManagerAttributes4; pieceConfig5.table = config(text5, "Crafting Station", table2, new ConfigDescription(text6, (AcceptableValueBase)null, array3)); cfg.customTable = config(englishName, "Custom Crafting Station", piece.Crafting.Stations.First().custom ?? "", new ConfigDescription("", (AcceptableValueBase)null, new object[1] { customTableAttributes })); cfg.table.SettingChanged += TableConfigChanged; cfg.customTable.SettingChanged += TableConfigChanged; ConfigurationManagerAttributes configurationManagerAttributes5 = new ConfigurationManagerAttributes(); num = order - 1; order = num; configurationManagerAttributes5.Order = num; configurationManagerAttributes5.Browsable = cfg.table.Value != CraftingTable.None; ConfigurationManagerAttributes item2 = configurationManagerAttributes5; hideWhenNoneAttributes2.Add(item2); } cfg.craft = itemConfig("Crafting Costs", new SerializedRequirements(piece.RequiredItems.Requirements).ToString(), "Item costs to craft " + localizedName); cfg.craft.SettingChanged += delegate { if (Object.op_Implicit((Object)(object)ObjectDB.instance) && (Object)(object)ObjectDB.instance.GetItemPrefab("YmirRemains") != (Object)null) { Requirement[] resources = SerializedRequirements.toPieceReqs(new SerializedRequirements(cfg.craft.Value)); piecePrefab.m_resources = resources; Piece[] array4 = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (Piece val in array4) { if (val.m_name == pieceName) { val.m_resources = resources; } } } }; for (int num2 = 0; num2 < piece.Conversions.Count; num2++) { string text7 = ((piece.Conversions.Count > 1) ? $"{num2 + 1}. " : ""); Conversion conversion = piece.Conversions[num2]; conversion.config = new Conversion.ConversionConfig(); int index = num2; conversion.config.input = config(englishName, text7 + "Conversion Input Item", conversion.Input, new ConfigDescription("Conversion input item within " + englishName, (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Category = localizedName } })); conversion.config.input.SettingChanged += delegate { if (index < piece.conversions.Count) { ObjectDB instance = ObjectDB.instance; if (instance != null) { ItemDrop val = SerializedRequirements.fetchByName(instance, conversion.config.input.Value); piece.conversions[index].m_from = val; } } }; conversion.config.output = config(englishName, text7 + "Conversion Output Item", conversion.Output, new ConfigDescription("Conversion output item within " + englishName, (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Category = localizedName } })); conversion.config.output.SettingChanged += delegate { if (index < piece.conversions.Count) { ObjectDB instance = ObjectDB.instance; if (instance != null) { ItemDrop to = SerializedRequirements.fetchByName(instance, conversion.config.output.Value); piece.conversions[index].m_to = to; } } }; } void BuildTableConfigChanged(object o, EventArgs e) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) if (registeredPieces.Count > 0) { if (cfg.category.Value == BuildPieceCategory.Custom) { piecePrefab.m_category = PiecePrefabManager.GetCategory(cfg.customCategory.Value); } else { piecePrefab.m_category = (PieceCategory)cfg.category.Value; } if (Object.op_Implicit((Object)(object)Hud.instance)) { PiecePrefabManager.CategoryRefreshNeeded = true; PiecePrefabManager.CreateCategoryTabs(); } } customTableAttributes.Browsable = cfg.category.Value == BuildPieceCategory.Custom; ReloadConfigDisplay(); } void ExtensionTableConfigChanged(object o, EventArgs e) { if (piece.RequiredItems.Requirements.Count > 0) { CraftingTable value = cfg.extensionTable.Value; CraftingTable craftingTable = value; if (craftingTable == CraftingTable.Custom) { StationExtension obj = pieceExtensionComp; GameObject prefab = ZNetScene.instance.GetPrefab(cfg.customExtentionTable.Value); obj.m_craftingStation = ((prefab != null) ? prefab.GetComponent() : null); } else { pieceExtensionComp.m_craftingStation = ZNetScene.instance.GetPrefab(((InternalName)typeof(CraftingTable).GetMember(cfg.extensionTable.Value.ToString())[0].GetCustomAttributes(typeof(InternalName)).First()).internalName).GetComponent(); } pieceExtensionComp.m_maxStationDistance = cfg.maxStationDistance.Value; } customTableAttributes.Browsable = cfg.extensionTable.Value == CraftingTable.Custom; foreach (ConfigurationManagerAttributes item5 in hideWhenNoneAttributes) { item5.Browsable = cfg.extensionTable.Value != CraftingTable.None; } ReloadConfigDisplay(); plugin.Config.Save(); } void TableConfigChanged(object o, EventArgs e) { if (piece.RequiredItems.Requirements.Count > 0) { switch (cfg.table.Value) { case CraftingTable.None: piecePrefab.m_craftingStation = null; break; case CraftingTable.Custom: { Piece obj = piecePrefab; GameObject prefab = ZNetScene.instance.GetPrefab(cfg.customTable.Value); obj.m_craftingStation = ((prefab != null) ? prefab.GetComponent() : null); break; } default: piecePrefab.m_craftingStation = ZNetScene.instance.GetPrefab(((InternalName)typeof(CraftingTable).GetMember(cfg.table.Value.ToString())[0].GetCustomAttributes(typeof(InternalName)).First()).internalName).GetComponent(); break; } } customTableAttributes.Browsable = cfg.table.Value == CraftingTable.Custom; foreach (ConfigurationManagerAttributes item6 in hideWhenNoneAttributes2) { item6.Browsable = cfg.table.Value != CraftingTable.None; } ReloadConfigDisplay(); plugin.Config.Save(); } ConfigEntry itemConfig(string name, string value, string desc) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown ConfigurationManagerAttributes configurationManagerAttributes6 = new ConfigurationManagerAttributes { CustomDrawer = DrawConfigTable, Order = (order -= 1), Category = localizedName }; return config(englishName, name, value, new ConfigDescription(desc, (AcceptableValueBase)null, new object[1] { configurationManagerAttributes6 })); } } foreach (BuildPiece registeredPiece3 in registeredPieces) { ConfigEntryBase enabledCfg = registeredPiece3.RecipeIsActive; Piece piecePrefab2; if (enabledCfg != null) { piecePrefab2 = registeredPiece3.Prefab.GetComponent(); ConfigChanged(null, null); ((object)enabledCfg).GetType().GetEvent("SettingChanged").AddEventHandler(enabledCfg, new EventHandler(ConfigChanged)); } registeredPiece3.InitializeNewRegisteredPiece(registeredPiece3); void ConfigChanged(object? o, EventArgs? e) { piecePrefab2.m_enabled = (int)enabledCfg.BoxedValue != 0; } } if (saveOnConfigSet) { plugin.Config.SaveOnConfigSet = true; plugin.Config.Save(); } void ReloadConfigDisplay() { if (configManagerType?.GetProperty("DisplayingWindow").GetValue(configManager) is int num3 && num3 != 0) { configManagerType.GetMethod("BuildSettingList").Invoke(configManager, Array.Empty()); } } } private void InitializeNewRegisteredPiece(BuildPiece piece) { ConfigEntryBase recipeIsActive = piece.RecipeIsActive; PieceConfig cfg; Piece piecePrefab; string pieceName; if (recipeIsActive != null) { pieceConfigs.TryGetValue(piece, out cfg); piecePrefab = piece.Prefab.GetComponent(); pieceName = piecePrefab.m_name; ((object)recipeIsActive).GetType().GetEvent("SettingChanged").AddEventHandler(recipeIsActive, new EventHandler(ConfigChanged)); } void ConfigChanged(object o, EventArgs e) { if (Object.op_Implicit((Object)(object)ObjectDB.instance) && (Object)(object)ObjectDB.instance.GetItemPrefab("YmirRemains") != (Object)null && cfg != null) { Requirement[] resources = SerializedRequirements.toPieceReqs(new SerializedRequirements(cfg.craft.Value)); piecePrefab.m_resources = resources; Piece[] array = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (Piece val in array) { if (val.m_name == pieceName) { val.m_resources = resources; } } } } } [HarmonyPriority(700)] internal static void Patch_ObjectDBInit(ObjectDB __instance) { //IL_0489: Unknown result type (might be due to invalid IL or missing references) //IL_048e: Unknown result type (might be due to invalid IL or missing references) //IL_04c1: Unknown result type (might be due to invalid IL or missing references) //IL_04f9: Expected O, but got Unknown if ((Object)(object)__instance.GetItemPrefab("YmirRemains") == (Object)null) { return; } foreach (BuildPiece registeredPiece in registeredPieces) { pieceConfigs.TryGetValue(registeredPiece, out PieceConfig value); registeredPiece.Prefab.GetComponent().m_resources = SerializedRequirements.toPieceReqs((value == null) ? new SerializedRequirements(registeredPiece.RequiredItems.Requirements) : new SerializedRequirements(value.craft.Value)); foreach (ExtensionConfig extensionStation in registeredPiece.Extension.ExtensionStations) { switch ((value == null || registeredPiece.Extension.ExtensionStations.Count > 1) ? extensionStation.Table : value.extensionTable.Value) { case CraftingTable.None: registeredPiece.Prefab.GetComponent().m_craftingStation = null; break; case CraftingTable.Custom: { GameObject prefab = ZNetScene.instance.GetPrefab((value == null || registeredPiece.Extension.ExtensionStations.Count > 0) ? extensionStation.custom : value.customExtentionTable.Value); if (prefab != null) { registeredPiece.Prefab.GetComponent().m_craftingStation = prefab.GetComponent(); } else { Debug.LogWarning((object)("Custom crafting station '" + ((value == null || registeredPiece.Extension.ExtensionStations.Count > 0) ? extensionStation.custom : value.customExtentionTable.Value) + "' does not exist")); } break; } default: if (value != null && value.table.Value == CraftingTable.None) { registeredPiece.Prefab.GetComponent().m_craftingStation = null; } else { registeredPiece.Prefab.GetComponent().m_craftingStation = ZNetScene.instance.GetPrefab(((InternalName)typeof(CraftingTable).GetMember(((value == null || registeredPiece.Extension.ExtensionStations.Count > 1) ? extensionStation.Table : value.extensionTable.Value).ToString())[0].GetCustomAttributes(typeof(InternalName)).First()).internalName).GetComponent(); } break; } } foreach (CraftingStationConfig station in registeredPiece.Crafting.Stations) { switch ((value == null || registeredPiece.Crafting.Stations.Count > 1) ? station.Table : value.table.Value) { case CraftingTable.None: registeredPiece.Prefab.GetComponent().m_craftingStation = null; break; case CraftingTable.Custom: { GameObject prefab2 = ZNetScene.instance.GetPrefab((value == null || registeredPiece.Crafting.Stations.Count > 0) ? station.custom : value.customTable.Value); if (prefab2 != null) { registeredPiece.Prefab.GetComponent().m_craftingStation = prefab2.GetComponent(); } else { Debug.LogWarning((object)("Custom crafting station '" + ((value == null || registeredPiece.Crafting.Stations.Count > 0) ? station.custom : value.customTable.Value) + "' does not exist")); } break; } default: if (value != null && value.table.Value == CraftingTable.None) { registeredPiece.Prefab.GetComponent().m_craftingStation = null; } else { registeredPiece.Prefab.GetComponent().m_craftingStation = ZNetScene.instance.GetPrefab(((InternalName)typeof(CraftingTable).GetMember(((value == null || registeredPiece.Crafting.Stations.Count > 1) ? station.Table : value.table.Value).ToString())[0].GetCustomAttributes(typeof(InternalName)).First()).internalName).GetComponent(); } break; } } registeredPiece.conversions = new List(); for (int i = 0; i < registeredPiece.Conversions.Count; i++) { Conversion conversion = registeredPiece.Conversions[i]; registeredPiece.conversions.Add(new ItemConversion { m_from = SerializedRequirements.fetchByName(ObjectDB.instance, conversion.config?.input.Value ?? conversion.Input), m_to = SerializedRequirements.fetchByName(ObjectDB.instance, conversion.config?.output.Value ?? conversion.Output) }); if (registeredPiece.conversions[i].m_from != null && registeredPiece.conversions[i].m_to != null) { registeredPiece.Prefab.GetComponent().m_conversion.Add(registeredPiece.conversions[i]); } } } } public void Snapshot(float lightIntensity = 1.3f, Quaternion? cameraRotation = null) { QueueSnapshot(Prefab, lightIntensity, cameraRotation); } internal void QueueSnapshot(GameObject prefab, float lightIntensity, Quaternion? cameraRotation = null) { _snapshotQueue.Enqueue((prefab, lightIntensity, cameraRotation)); } internal static void KickoffQueuedSnapshots() { if (_snapshotQueue.Count != 0) { BaseUnityPlugin? obj = _plugin; if (obj != null) { ((MonoBehaviour)obj).StartCoroutine(RunQueuedSnapshots()); } } } private static IEnumerator RunQueuedSnapshots() { yield return null; WaitForEndOfFrame eof = new WaitForEndOfFrame(); yield return eof; while (_snapshotQueue.Count > 0) { var (prefab, intensity, rot) = _snapshotQueue.Dequeue(); yield return eof; if (!Application.isBatchMode && Object.op_Implicit((Object)(object)prefab)) { try { SnapshotPiece(prefab, intensity, rot); } catch (Exception ex) { Exception ex2 = ex; Debug.LogError((object)$"[PieceManager] Snapshot failed for '{((Object)prefab).name}': {ex2}"); } } } } internal static void SnapshotPiece(GameObject prefab, float lightIntensity = 1.3f, Quaternion? cameraRotation = null) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0061: 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_00be: 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_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0255: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Unknown result type (might be due to invalid IL or missing references) //IL_0259: Unknown result type (might be due to invalid IL or missing references) //IL_0263: Unknown result type (might be due to invalid IL or missing references) //IL_0268: Unknown result type (might be due to invalid IL or missing references) //IL_0273: Unknown result type (might be due to invalid IL or missing references) //IL_0275: Unknown result type (might be due to invalid IL or missing references) //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_027c: Unknown result type (might be due to invalid IL or missing references) //IL_02d6: Unknown result type (might be due to invalid IL or missing references) //IL_02dd: Unknown result type (might be due to invalid IL or missing references) //IL_0322: Unknown result type (might be due to invalid IL or missing references) //IL_0333: Unknown result type (might be due to invalid IL or missing references) //IL_0338: Unknown result type (might be due to invalid IL or missing references) //IL_036f: Unknown result type (might be due to invalid IL or missing references) //IL_0376: Expected O, but got Unknown //IL_0394: Unknown result type (might be due to invalid IL or missing references) //IL_03d5: Unknown result type (might be due to invalid IL or missing references) //IL_03da: Unknown result type (might be due to invalid IL or missing references) //IL_03e4: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)prefab == (Object)null) && (prefab.GetComponentsInChildren().Any() || prefab.GetComponentsInChildren().Any())) { Camera component = new GameObject("CameraIcon", new Type[1] { typeof(Camera) }).GetComponent(); component.backgroundColor = Color.clear; component.clearFlags = (CameraClearFlags)2; ((Component)component).transform.position = new Vector3(10000f, 10000f, 10000f); ((Component)component).transform.rotation = (Quaternion)(((??)cameraRotation) ?? Quaternion.Euler(0f, 180f, 0f)); component.fieldOfView = 0.5f; component.farClipPlane = 100000f; component.cullingMask = 8; Light component2 = new GameObject("LightIcon", new Type[1] { typeof(Light) }).GetComponent(); ((Component)component2).transform.position = new Vector3(10000f, 10000f, 10000f); ((Component)component2).transform.rotation = Quaternion.Euler(5f, 180f, 5f); component2.type = (LightType)1; component2.cullingMask = 8; component2.intensity = lightIntensity; GameObject val = Object.Instantiate(prefab); Transform[] componentsInChildren = val.GetComponentsInChildren(); foreach (Transform val2 in componentsInChildren) { ((Component)val2).gameObject.layer = 3; } val.transform.position = Vector3.zero; val.transform.rotation = Quaternion.Euler(23f, 51f, 25.8f); ((Object)val).name = ((Object)prefab).name; MeshRenderer[] componentsInChildren2 = val.GetComponentsInChildren(); Vector3 val3 = componentsInChildren2.Aggregate(Vector3.positiveInfinity, delegate(Vector3 cur, MeshRenderer renderer) { //IL_0000: 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) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //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) Bounds bounds = ((Renderer)renderer).bounds; return Vector3.Min(cur, ((Bounds)(ref bounds)).min); }); Vector3 val4 = componentsInChildren2.Aggregate(Vector3.negativeInfinity, delegate(Vector3 cur, MeshRenderer renderer) { //IL_0000: 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) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //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) Bounds bounds = ((Renderer)renderer).bounds; return Vector3.Max(cur, ((Bounds)(ref bounds)).max); }); val.transform.position = new Vector3(10000f, 10000f, 10000f) - (val3 + val4) / 2f; Vector3 val5 = val4 - val3; TimedDestruction val6 = val.AddComponent(); val6.Trigger(1f); Rect val7 = default(Rect); ((Rect)(ref val7))..ctor(0f, 0f, 128f, 128f); component.targetTexture = RenderTexture.GetTemporary((int)((Rect)(ref val7)).width, (int)((Rect)(ref val7)).height); component.fieldOfView = 20f; float num = Mathf.Max(val5.x, val5.y) + 0.1f; float num2 = num / Mathf.Tan(component.fieldOfView * ((float)Math.PI / 180f)) * 1.1f; ((Component)component).transform.position = new Vector3(10000f, 10000f, 10000f) + new Vector3(0f, 0f, num2); component.Render(); RenderTexture active = RenderTexture.active; RenderTexture.active = component.targetTexture; Texture2D val8 = new Texture2D((int)((Rect)(ref val7)).width, (int)((Rect)(ref val7)).height, (TextureFormat)4, false); val8.ReadPixels(new Rect(0f, 0f, (float)(int)((Rect)(ref val7)).width, (float)(int)((Rect)(ref val7)).height), 0, 0); val8.Apply(); RenderTexture.active = active; prefab.GetComponent().m_icon = Sprite.Create(val8, new Rect(0f, 0f, (float)(int)((Rect)(ref val7)).width, (float)(int)((Rect)(ref val7)).height), Vector2.one / 2f); ((Component)component2).gameObject.SetActive(false); component.targetTexture.Release(); ((Component)component).gameObject.SetActive(false); val.SetActive(false); Object.DestroyImmediate((Object)(object)val); Object.Destroy((Object)(object)component); Object.Destroy((Object)(object)component2); Object.Destroy((Object)(object)((Component)component).gameObject); Object.Destroy((Object)(object)((Component)component2).gameObject); } } private static void DrawConfigTable(ConfigEntryBase cfg) { //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Expected O, but got Unknown //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Expected O, but got Unknown //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: Expected O, but got Unknown //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Expected O, but got Unknown //IL_0279: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Unknown result type (might be due to invalid IL or missing references) //IL_0294: Expected O, but got Unknown bool valueOrDefault = cfg.Description.Tags.Select((object a) => (a.GetType().Name == "ConfigurationManagerAttributes") ? ((bool?)a.GetType().GetField("ReadOnly")?.GetValue(a)) : ((bool?)null)).FirstOrDefault((bool? v) => v.HasValue) == true; List list = new List(); bool flag = false; int num = (int)(configManager?.GetType().GetProperty("RightColumnWidth", BindingFlags.Instance | BindingFlags.NonPublic).GetGetMethod(nonPublic: true) .Invoke(configManager, Array.Empty()) ?? ((object)130)); GUILayout.BeginVertical(Array.Empty()); foreach (Requirement req in new SerializedRequirements((string)cfg.BoxedValue).Reqs) { GUILayout.BeginHorizontal(Array.Empty()); int num2 = req.amount; if (int.TryParse(GUILayout.TextField(num2.ToString(), new GUIStyle(GUI.skin.textField) { fixedWidth = 40f }, Array.Empty()), out var result) && result != num2 && !valueOrDefault) { num2 = result; flag = true; } string text = GUILayout.TextField(req.itemName, new GUIStyle(GUI.skin.textField) { fixedWidth = num - 40 - 67 - 21 - 21 - 12 }, Array.Empty()); string text2 = (valueOrDefault ? req.itemName : text); flag = flag || text2 != req.itemName; bool flag2 = req.recover; if (GUILayout.Toggle(req.recover, "Recover", new GUIStyle(GUI.skin.toggle) { fixedWidth = 67f }, Array.Empty()) != req.recover) { flag2 = !flag2; flag = true; } if (GUILayout.Button("x", new GUIStyle(GUI.skin.button) { fixedWidth = 21f }, Array.Empty()) && !valueOrDefault) { flag = true; } else { list.Add(new Requirement { amount = num2, itemName = text2, recover = flag2 }); } if (GUILayout.Button("+", new GUIStyle(GUI.skin.button) { fixedWidth = 21f }, Array.Empty()) && !valueOrDefault) { flag = true; list.Add(new Requirement { amount = 1, itemName = "", recover = false }); } GUILayout.EndHorizontal(); } GUILayout.EndVertical(); if (flag) { cfg.BoxedValue = new SerializedRequirements(list).ToString(); } } private static ConfigEntry config(string group, string name, T value, ConfigDescription description) { ConfigEntry val = plugin.Config.Bind(group, name, value, description); configSync?.GetType().GetMethod("AddConfigEntry").MakeGenericMethod(typeof(T)) .Invoke(configSync, new object[1] { val }); return val; } private static ConfigEntry config(string group, string name, T value, string description) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown return config(group, name, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty())); } } public static class GoExtensions { public static T GetOrAddComponent(this GameObject gameObject) where T : Component { return gameObject.GetComponent() ?? gameObject.AddComponent(); } } [PublicAPI] public class LocalizeKey { private static readonly List keys = new List(); public readonly string Key; public readonly Dictionary Localizations = new Dictionary(); public LocalizeKey(string key) { Key = key.Replace("$", ""); keys.Add(this); } public void Alias(string alias) { Localizations.Clear(); if (!alias.Contains("$")) { alias = "$" + alias; } Localizations["alias"] = alias; if (Localization.m_instance != null) { Localization.instance.AddWord(Key, Localization.instance.Localize(alias)); } } public LocalizeKey English(string key) { return addForLang("English", key); } public LocalizeKey Swedish(string key) { return addForLang("Swedish", key); } public LocalizeKey French(string key) { return addForLang("French", key); } public LocalizeKey Italian(string key) { return addForLang("Italian", key); } public LocalizeKey German(string key) { return addForLang("German", key); } public LocalizeKey Spanish(string key) { return addForLang("Spanish", key); } public LocalizeKey Russian(string key) { return addForLang("Russian", key); } public LocalizeKey Romanian(string key) { return addForLang("Romanian", key); } public LocalizeKey Bulgarian(string key) { return addForLang("Bulgarian", key); } public LocalizeKey Macedonian(string key) { return addForLang("Macedonian", key); } public LocalizeKey Finnish(string key) { return addForLang("Finnish", key); } public LocalizeKey Danish(string key) { return addForLang("Danish", key); } public LocalizeKey Norwegian(string key) { return addForLang("Norwegian", key); } public LocalizeKey Icelandic(string key) { return addForLang("Icelandic", key); } public LocalizeKey Turkish(string key) { return addForLang("Turkish", key); } public LocalizeKey Lithuanian(string key) { return addForLang("Lithuanian", key); } public LocalizeKey Czech(string key) { return addForLang("Czech", key); } public LocalizeKey Hungarian(string key) { return addForLang("Hungarian", key); } public LocalizeKey Slovak(string key) { return addForLang("Slovak", key); } public LocalizeKey Polish(string key) { return addForLang("Polish", key); } public LocalizeKey Dutch(string key) { return addForLang("Dutch", key); } public LocalizeKey Portuguese_European(string key) { return addForLang("Portuguese_European", key); } public LocalizeKey Portuguese_Brazilian(string key) { return addForLang("Portuguese_Brazilian", key); } public LocalizeKey Chinese(string key) { return addForLang("Chinese", key); } public LocalizeKey Japanese(string key) { return addForLang("Japanese", key); } public LocalizeKey Korean(string key) { return addForLang("Korean", key); } public LocalizeKey Hindi(string key) { return addForLang("Hindi", key); } public LocalizeKey Thai(string key) { return addForLang("Thai", key); } public LocalizeKey Abenaki(string key) { return addForLang("Abenaki", key); } public LocalizeKey Croatian(string key) { return addForLang("Croatian", key); } public LocalizeKey Georgian(string key) { return addForLang("Georgian", key); } public LocalizeKey Greek(string key) { return addForLang("Greek", key); } public LocalizeKey Serbian(string key) { return addForLang("Serbian", key); } public LocalizeKey Ukrainian(string key) { return addForLang("Ukrainian", key); } private LocalizeKey addForLang(string lang, string value) { Localizations[lang] = value; if (Localization.m_instance != null) { if (Localization.instance.GetSelectedLanguage() == lang) { Localization.instance.AddWord(Key, value); } else if (lang == "English" && !Localization.instance.m_translations.ContainsKey(Key)) { Localization.instance.AddWord(Key, value); } } return this; } [HarmonyPriority(300)] internal static void AddLocalizedKeys(Localization __instance, string language) { foreach (LocalizeKey key in keys) { string value2; if (key.Localizations.TryGetValue(language, out string value) || key.Localizations.TryGetValue("English", out value)) { __instance.AddWord(key.Key, value); } else if (key.Localizations.TryGetValue("alias", out value2)) { __instance.AddWord(key.Key, Localization.instance.Localize(value2)); } } } } public static class LocalizationCache { private static readonly Dictionary localizations = new Dictionary(); internal static void LocalizationPostfix(Localization __instance, string language) { string key = localizations.FirstOrDefault>((KeyValuePair l) => l.Value == __instance).Key; if (key != null) { localizations.Remove(key); } if (!localizations.ContainsKey(language)) { localizations.Add(language, __instance); } } public static Localization ForLanguage(string? language = null) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown if (localizations.TryGetValue(language ?? PlayerPrefs.GetString("language", "English"), out Localization value)) { return value; } value = new Localization(); if (language != null) { value.SetupLanguage(language); } return value; } } public class AdminSyncing { private static bool isServer; internal static bool registeredOnClient; [HarmonyPriority(700)] internal static void AdminStatusSync(ZNet __instance) { isServer = __instance.IsServer(); if (BuildPiece._plugin != null) { if (isServer) { ZRoutedRpc.instance.Register(BuildPiece._plugin.Info.Metadata.Name + " PMAdminStatusSync", (Action)RPC_AdminPieceAddRemove); } else if (!registeredOnClient) { ZRoutedRpc.instance.Register(BuildPiece._plugin.Info.Metadata.Name + " PMAdminStatusSync", (Action)RPC_AdminPieceAddRemove); registeredOnClient = true; } } if (isServer) { ((MonoBehaviour)ZNet.instance).StartCoroutine(WatchAdminListChanges()); } static void SendAdmin(List peers, bool isAdmin) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(isAdmin); ((MonoBehaviour)ZNet.instance).StartCoroutine(sendZPackage(peers, val)); } static IEnumerator WatchAdminListChanges() { List currentList = new List(ZNet.instance.m_adminList.GetList()); while (true) { yield return (object)new WaitForSeconds(30f); if (!ZNet.instance.m_adminList.GetList().SequenceEqual(currentList)) { currentList = new List(ZNet.instance.m_adminList.GetList()); List adminPeer = (from p in ZNet.instance.GetPeers() where ZNet.instance.ListContainsId(ZNet.instance.m_adminList, p.m_rpc.GetSocket().GetHostName()) select p).ToList(); List nonAdminPeer = ZNet.instance.GetPeers().Except(adminPeer).ToList(); SendAdmin(nonAdminPeer, isAdmin: false); SendAdmin(adminPeer, isAdmin: true); } } } } private static IEnumerator sendZPackage(List peers, ZPackage package) { if (!Object.op_Implicit((Object)(object)ZNet.instance)) { yield break; } byte[] rawData = package.GetArray(); if (rawData != null && rawData.LongLength > 10000) { ZPackage compressedPackage = new ZPackage(); compressedPackage.Write(4); MemoryStream output = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(output, CompressionLevel.Optimal)) { deflateStream.Write(rawData, 0, rawData.Length); } compressedPackage.Write(output.ToArray()); package = compressedPackage; } List> writers = (from p in peers where p.IsReady() select TellPeerAdminStatus(p, package)).ToList(); writers.RemoveAll((IEnumerator writer) => !writer.MoveNext()); while (writers.Count > 0) { yield return null; writers.RemoveAll((IEnumerator writer) => !writer.MoveNext()); } } private static IEnumerator TellPeerAdminStatus(ZNetPeer peer, ZPackage package) { ZRoutedRpc rpc = ZRoutedRpc.instance; if (rpc != null) { SendPackage(package); } void SendPackage(ZPackage pkg) { BaseUnityPlugin? plugin = BuildPiece._plugin; string text = ((plugin != null) ? plugin.Info.Metadata.Name : null) + " PMAdminStatusSync"; if (isServer) { peer.m_rpc.Invoke(text, new object[1] { pkg }); } else { rpc.InvokeRoutedRPC(peer.m_server ? 0 : peer.m_uid, text, new object[1] { pkg }); } } yield break; } internal static void RPC_AdminPieceAddRemove(long sender, ZPackage package) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Expected O, but got Unknown ZNetPeer peer = ZNet.instance.GetPeer(sender); bool flag = false; try { flag = package.ReadBool(); } catch { } if (isServer) { ZRoutedRpc instance = ZRoutedRpc.instance; long everybody = ZRoutedRpc.Everybody; BaseUnityPlugin? plugin = BuildPiece._plugin; instance.InvokeRoutedRPC(everybody, ((plugin != null) ? plugin.Info.Metadata.Name : null) + " PMAdminStatusSync", new object[1] { (object)new ZPackage() }); if (ZNet.instance.ListContainsId(ZNet.instance.m_adminList, peer.m_rpc.GetSocket().GetHostName())) { ZPackage val = new ZPackage(); val.Write(true); ZRpc rpc = peer.m_rpc; BaseUnityPlugin? plugin2 = BuildPiece._plugin; rpc.Invoke(((plugin2 != null) ? plugin2.Info.Metadata.Name : null) + " PMAdminStatusSync", new object[1] { val }); } return; } foreach (BuildPiece registeredPiece in BuildPiece.registeredPieces) { if (!registeredPiece.SpecialProperties.AdminOnly) { continue; } Piece component = registeredPiece.Prefab.GetComponent(); string name = component.m_name; string text = Localization.instance.Localize(name).Trim(); if (!Object.op_Implicit((Object)(object)ObjectDB.instance) || (Object)(object)ObjectDB.instance.GetItemPrefab("YmirRemains") == (Object)null) { continue; } Piece[] array = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (Piece val2 in array) { if (flag) { if (val2.m_name == name) { val2.m_enabled = true; } } else if (val2.m_name == name) { val2.m_enabled = false; } } List pieces = ObjectDB.instance.GetItemPrefab("Hammer").GetComponent().m_itemData.m_shared.m_buildPieces.m_pieces; if (flag) { if (!pieces.Contains(ZNetScene.instance.GetPrefab(((Object)component).name))) { pieces.Add(ZNetScene.instance.GetPrefab(((Object)component).name)); } } else if (pieces.Contains(ZNetScene.instance.GetPrefab(((Object)component).name))) { pieces.Remove(ZNetScene.instance.GetPrefab(((Object)component).name)); } } } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] internal class RegisterClientRPCPatch { private static void Postfix(ZNet __instance, ZNetPeer peer) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown if (!__instance.IsServer()) { ZRpc rpc = peer.m_rpc; BaseUnityPlugin? plugin = BuildPiece._plugin; rpc.Register(((plugin != null) ? plugin.Info.Metadata.Name : null) + " PMAdminStatusSync", (Action)RPC_InitialAdminSync); return; } ZPackage val = new ZPackage(); val.Write(__instance.ListContainsId(__instance.m_adminList, peer.m_rpc.GetSocket().GetHostName())); ZRpc rpc2 = peer.m_rpc; BaseUnityPlugin? plugin2 = BuildPiece._plugin; rpc2.Invoke(((plugin2 != null) ? plugin2.Info.Metadata.Name : null) + " PMAdminStatusSync", new object[1] { val }); } private static void RPC_InitialAdminSync(ZRpc rpc, ZPackage package) { AdminSyncing.RPC_AdminPieceAddRemove(0L, package); } } public static class PiecePrefabManager { private struct BundleId { [UsedImplicitly] public string assetBundleFileName; [UsedImplicitly] public string folderName; } private static readonly Dictionary bundleCache; private static readonly List piecePrefabs; private static readonly Dictionary PieceCategories; private static readonly Dictionary OtherPieceCategories; private static readonly Dictionary VanillaLabels; internal static bool CategoryRefreshNeeded; static PiecePrefabManager() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Expected O, but got Unknown //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Expected O, but got Unknown //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Expected O, but got Unknown //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Expected O, but got Unknown //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Expected O, but got Unknown //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: Expected O, but got Unknown //IL_0210: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Expected O, but got Unknown //IL_024b: Unknown result type (might be due to invalid IL or missing references) //IL_0259: Expected O, but got Unknown //IL_0288: Unknown result type (might be due to invalid IL or missing references) //IL_0295: Expected O, but got Unknown //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_02d1: Expected O, but got Unknown //IL_02ff: Unknown result type (might be due to invalid IL or missing references) //IL_031a: Unknown result type (might be due to invalid IL or missing references) //IL_0327: Expected O, but got Unknown //IL_0327: Expected O, but got Unknown //IL_0356: Unknown result type (might be due to invalid IL or missing references) //IL_0363: Expected O, but got Unknown //IL_0392: Unknown result type (might be due to invalid IL or missing references) //IL_039f: Expected O, but got Unknown //IL_03cd: Unknown result type (might be due to invalid IL or missing references) //IL_03db: Expected O, but got Unknown //IL_040a: Unknown result type (might be due to invalid IL or missing references) //IL_0417: Expected O, but got Unknown //IL_0446: Unknown result type (might be due to invalid IL or missing references) //IL_0453: Expected O, but got Unknown //IL_0482: Unknown result type (might be due to invalid IL or missing references) //IL_048f: Expected O, but got Unknown bundleCache = new Dictionary(); piecePrefabs = new List(); PieceCategories = new Dictionary(); OtherPieceCategories = new Dictionary(); VanillaLabels = new Dictionary(); Harmony val = new Harmony("org.bepinex.helpers.PieceManager"); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(FejdStartup), "Awake", (Type[])null, (Type[])null), new HarmonyMethod(AccessTools.DeclaredMethod(typeof(BuildPiece), "Patch_FejdStartup", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(FejdStartup), "Awake", (Type[])null, (Type[])null), new HarmonyMethod(AccessTools.DeclaredMethod(typeof(BuildPiece), "KickoffQueuedSnapshots", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(Localization), "LoadCSV", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(LocalizeKey), "AddLocalizedKeys", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(Localization), "SetupLanguage", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(LocalizationCache), "LocalizationPostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(ObjectDB), "Awake", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(PiecePrefabManager), "Patch_ObjectDBInit", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(ObjectDB), "Awake", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(BuildPiece), "Patch_ObjectDBInit", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(ObjectDB), "CopyOtherDB", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(PiecePrefabManager), "Patch_ObjectDBInit", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(ZNet), "Awake", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(AdminSyncing), "AdminStatusSync", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(ZNetScene), "Awake", (Type[])null, (Type[])null), new HarmonyMethod(AccessTools.DeclaredMethod(typeof(PiecePrefabManager), "Patch_ZNetSceneAwake", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(ZNetScene), "Awake", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(PiecePrefabManager), "RefFixPatch_ZNetSceneAwake", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(PieceTable), "UpdateAvailable", (Type[])null, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(PiecePrefabManager), "UpdateAvailable_Transpiler", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(PieceTable), "UpdateAvailable", (Type[])null, (Type[])null), new HarmonyMethod(AccessTools.DeclaredMethod(typeof(PiecePrefabManager), "UpdateAvailable_Prefix", (Type[])null, (Type[])null)), new HarmonyMethod(AccessTools.DeclaredMethod(typeof(PiecePrefabManager), "UpdateAvailable_Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(Player), "SetPlaceMode", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(PiecePrefabManager), "Patch_SetPlaceMode", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(Hud), "Awake", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(PiecePrefabManager), "Hud_AwakeCreateTabs", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(Hud), "UpdateBuild", (Type[])null, (Type[])null), new HarmonyMethod(AccessTools.DeclaredMethod(typeof(PiecePrefabManager), "RepositionCatsIfNeeded", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(Hud), "LateUpdate", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(PiecePrefabManager), "RepositionCatsIfNeeded", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(Enum), "GetValues", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(PiecePrefabManager), "EnumGetValuesPatch", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(Enum), "GetNames", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(PiecePrefabManager), "EnumGetNamesPatch", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } public static AssetBundle RegisterAssetBundle(string assetBundleFileName, string folderName = "assets") { BundleId key = new BundleId { assetBundleFileName = assetBundleFileName, folderName = folderName }; if (!bundleCache.TryGetValue(key, out AssetBundle value)) { Dictionary dictionary = bundleCache; AssetBundle? obj = ((IEnumerable)Resources.FindObjectsOfTypeAll()).FirstOrDefault((Func)((AssetBundle a) => ((Object)a).name == assetBundleFileName)) ?? AssetBundle.LoadFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(Assembly.GetExecutingAssembly().GetName().Name + "." + folderName + "." + assetBundleFileName)); AssetBundle result = obj; dictionary[key] = obj; return result; } return value; } public static IEnumerable FixRefs(AssetBundle assetBundle) { return assetBundle.LoadAllAssets(); } public static GameObject RegisterPrefab(string assetBundleFileName, string prefabName, string folderName = "assets") { return RegisterPrefab(RegisterAssetBundle(assetBundleFileName, folderName), prefabName); } public static GameObject RegisterPrefab(AssetBundle assets, string prefabName) { if ((Object)(object)assets == (Object)null) { Debug.LogError((object)"Failed to load asset bundle. Please make sure to mark all asset bundles as embedded resources."); return null; } GameObject val = assets.LoadAsset(prefabName); if ((Object)(object)val == (Object)null) { Debug.LogError((object)("Failed to load prefab " + prefabName + " from asset bundle " + ((Object)assets).name)); return null; } piecePrefabs.Add(val); return val; } public static Sprite RegisterSprite(string assetBundleFileName, string prefabName, string folderName = "assets") { return RegisterSprite(RegisterAssetBundle(assetBundleFileName, folderName), prefabName); } public static Sprite RegisterSprite(AssetBundle assets, string prefabName) { return assets.LoadAsset(prefabName); } private static void EnumGetValuesPatch(Type enumType, ref Array __result) { if (!(enumType != typeof(PieceCategory)) && PieceCategories.Count != 0) { PieceCategory[] array = (PieceCategory[])(object)new PieceCategory[__result.Length + PieceCategories.Count]; __result.CopyTo(array, 0); PieceCategories.Values.CopyTo(array, __result.Length); __result = array; } } private static void EnumGetNamesPatch(Type enumType, ref string[] __result) { if (!(enumType != typeof(PieceCategory)) && PieceCategories.Count != 0) { __result = CollectionExtensions.AddRangeToArray(__result, PieceCategories.Keys.ToArray()); } } public static Dictionary GetPieceCategoriesMap() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) Array values = Enum.GetValues(typeof(PieceCategory)); string[] names = Enum.GetNames(typeof(PieceCategory)); Dictionary dictionary = new Dictionary(); for (int i = 0; i < values.Length; i++) { dictionary[(PieceCategory)values.GetValue(i)] = names[i]; } return dictionary; } public static PieceCategory GetCategory(string name) { //IL_000f: 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) //IL_002b: 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_00e2: 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_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0080: 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_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) if (Enum.TryParse(name, ignoreCase: true, out PieceCategory result)) { return result; } if (PieceCategories.TryGetValue(name, out result)) { return result; } if (OtherPieceCategories.TryGetValue(name, out result)) { return result; } Dictionary pieceCategoriesMap = GetPieceCategoriesMap(); foreach (KeyValuePair item in pieceCategoriesMap) { if (item.Value == name) { result = item.Key; OtherPieceCategories[name] = result; return result; } } result = (PieceCategory)(pieceCategoriesMap.Count - 1); PieceCategories[name] = result; string categoryToken = GetCategoryToken(name); Localization.instance.AddWord(categoryToken, name); return result; } internal static void CreateCategoryTabs() { if (Object.op_Implicit((Object)(object)Hud.instance)) { int num = ModifiedMaxCategory(); for (int i = Hud.instance.m_pieceCategoryTabs.Length; i < num; i++) { GameObject val = CreateCategoryTab(); Hud.instance.m_pieceCategoryTabs = CollectionExtensions.AddItem((IEnumerable)Hud.instance.m_pieceCategoryTabs, val).ToArray(); } if (Object.op_Implicit((Object)(object)Player.m_localPlayer) && Object.op_Implicit((Object)(object)Player.m_localPlayer.m_buildPieces)) { RepositionCategories(Player.m_localPlayer.m_buildPieces); Player.m_localPlayer.UpdateAvailablePiecesList(); } } } private static GameObject CreateCategoryTab() { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) GameObject val = Hud.instance.m_pieceCategoryTabs[0]; GameObject val2 = Object.Instantiate(Hud.instance.m_pieceCategoryTabs[0], val.transform.parent); val2.SetActive(false); UIInputHandler orAddComponent = val2.GetOrAddComponent(); orAddComponent.m_onLeftDown = (Action)Delegate.Combine(orAddComponent.m_onLeftDown, new Action(Hud.instance.OnLeftClickCategory)); TMP_Text[] componentsInChildren = val2.GetComponentsInChildren(); foreach (TMP_Text val3 in componentsInChildren) { val3.rectTransform.offsetMin = new Vector2(3f, 1f); val3.rectTransform.offsetMax = new Vector2(-3f, -1f); val3.enableAutoSizing = true; val3.fontSizeMin = 12f; val3.fontSizeMax = 20f; val3.lineSpacing = 0.8f; val3.textWrappingMode = (TextWrappingModes)1; val3.overflowMode = (TextOverflowModes)3; } return val2; } private static int ModifiedMaxCategory() { return Enum.GetValues(typeof(PieceCategory)).Length - 1; } private static int GetMaxCategoryOrDefault() { try { return (int)Enum.Parse(typeof(PieceCategory), "Max"); } catch (ArgumentException) { Debug.LogWarning((object)"Could not find Piece.PieceCategory.Max, using fallback value 4"); return 4; } } private static List TranspileMaxCategory(IEnumerable instructions, int maxOffset) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Expected O, but got Unknown //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Expected O, but got Unknown int num = GetMaxCategoryOrDefault() + maxOffset; List list = new List(); foreach (CodeInstruction instruction in instructions) { if (CodeInstructionExtensions.LoadsConstant(instruction, (long)num)) { list.Add(new CodeInstruction(OpCodes.Call, (object)AccessTools.DeclaredMethod(typeof(PiecePrefabManager), "ModifiedMaxCategory", (Type[])null, (Type[])null))); if (maxOffset != 0) { list.Add(new CodeInstruction(OpCodes.Ldc_I4, (object)maxOffset)); list.Add(new CodeInstruction(OpCodes.Add, (object)null)); } } else { list.Add(instruction); } } return list; } private static IEnumerable UpdateAvailable_Transpiler(IEnumerable instructions) { return TranspileMaxCategory(instructions, 0); } private static HashSet CategoriesInPieceTable(PieceTable pieceTable) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) HashSet hashSet = new HashSet(); Piece val = default(Piece); foreach (GameObject item in pieceTable.m_pieces.Where((GameObject pieceFab) => (Object)(object)pieceFab != (Object)null)) { if (item.TryGetComponent(ref val)) { hashSet.Add(val.m_category); } } return hashSet; } private static void RepositionCatsIfNeeded() { if (CategoryRefreshNeeded) { CategoryRefreshNeeded = false; CreateCategoryTabs(); RepositionCats(); } } private static void RepositionCats() { if (Object.op_Implicit((Object)(object)Player.m_localPlayer) && Object.op_Implicit((Object)(object)Player.m_localPlayer.m_buildPieces)) { RepositionCategories(Player.m_localPlayer.m_buildPieces); } } private static void RepositionCategories(PieceTable pieceTable) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown //IL_005f: 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_0068: 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_0096: 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_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_010e: 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_0130: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Unknown result type (might be due to invalid IL or missing references) //IL_028d: Expected O, but got Unknown //IL_029b: Unknown result type (might be due to invalid IL or missing references) //IL_02c4: Unknown result type (might be due to invalid IL or missing references) //IL_02d0: Unknown result type (might be due to invalid IL or missing references) RectTransform val = (RectTransform)Hud.instance.m_pieceCategoryTabs[0].transform; RectTransform val2 = (RectTransform)Hud.instance.m_pieceCategoryRoot.transform; RectTransform val3 = (RectTransform)Hud.instance.m_pieceSelectionWindow.transform; HorizontalLayoutGroup val4 = default(HorizontalLayoutGroup); if (((Component)((Transform)val).parent).TryGetComponent(ref val4)) { Object.DestroyImmediate((Object)(object)val4); } Rect rect = val.rect; Vector2 size = ((Rect)(ref rect)).size; GridLayoutGroup val6 = default(GridLayoutGroup); GridLayoutGroup val5 = (((Component)((Transform)val).parent).TryGetComponent(ref val6) ? val6 : ((Component)((Transform)val).parent).gameObject.AddComponent()); val5.cellSize = size; val5.spacing = new Vector2(0f, 1f); val5.constraint = (Constraint)1; val5.constraintCount = 5; ((LayoutGroup)val5).childAlignment = (TextAnchor)4; HashSet hashSet = CategoriesInPieceTable(pieceTable); UpdatePieceTableCategories(pieceTable, hashSet); rect = val2.rect; int num = Mathf.Max((int)(((Rect)(ref rect)).width / size.x), 1); int count = pieceTable.m_categories.Count; float num2 = (0f - size.x) * (float)num / 2f + size.x / 2f; float num3 = (size.y + 1f) * Mathf.Floor((float)(count - 1) / (float)num) + 5f; Vector2 val7 = default(Vector2); ((Vector2)(ref val7))..ctor(num2, num3); int num4 = Mathf.CeilToInt((float)count / (float)num); float num5 = (size.y + 1f) * (float)num4; RectTransform component = ((Component)((Transform)val).parent).GetComponent(); component.anchoredPosition = new Vector2(component.anchoredPosition.x, num5 / 2f); int num6 = 0; for (int i = 0; i < Hud.instance.m_pieceCategoryTabs.Length; i++) { GameObject val8 = Hud.instance.m_pieceCategoryTabs[i]; if ((Object)(object)val8 == (Object)null) { continue; } if (i >= pieceTable.m_categories.Count) { val8.SetActive(false); continue; } PieceCategory item = pieceTable.m_categories[i]; string text = pieceTable.m_categoryLabels[i]; if (hashSet.Contains(item)) { num6++; } val8.GetComponentInChildren().text = Localization.instance.Localize(text); } Transform obj = ((Transform)val3).Find("Bkg2"); RectTransform val9 = (RectTransform)((obj != null) ? ((Component)obj).transform : null); if (Object.op_Implicit((Object)(object)val9)) { float num7 = (size.y + 1f) * (float)Mathf.Max(0, Mathf.FloorToInt((float)(num6 - 1) / (float)num)); val9.offsetMax = new Vector2(val9.offsetMax.x, num7); } else { Debug.LogWarning((object)"RefreshCategories: Could not find background image"); } ((Component)Hud.instance).GetComponentInParent().RefreshLocalization(); } private static void UpdatePieceTableCategories(PieceTable pieceTable, HashSet visibleCategories) { //IL_000a: 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_001a: 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_0031: 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_00d0: 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) //IL_00d8: 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_00e7: 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) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < GetMaxCategoryOrDefault(); i++) { PieceCategory val = (PieceCategory)i; if (visibleCategories.Contains(val) && !pieceTable.m_categories.Contains(val)) { pieceTable.m_categories.Add(val); pieceTable.m_categoryLabels.Add(GetVanillaLabel(val)); } if (!visibleCategories.Contains(val) && pieceTable.m_categories.Contains(val)) { int index = pieceTable.m_categories.IndexOf(val); pieceTable.m_categories.RemoveAt(index); pieceTable.m_categoryLabels.RemoveAt(index); } } foreach (KeyValuePair pieceCategory in PieceCategories) { string key = pieceCategory.Key; PieceCategory value = pieceCategory.Value; if (visibleCategories.Contains(value) && !pieceTable.m_categories.Contains(value)) { pieceTable.m_categories.Add(value); pieceTable.m_categoryLabels.Add("$" + GetCategoryToken(key)); } if (visibleCategories.Contains(value) && !pieceTable.m_categoryLabels.Contains("$" + GetCategoryToken(key))) { pieceTable.m_categoryLabels.Add("$" + GetCategoryToken(key)); } if (!visibleCategories.Contains(value) && pieceTable.m_categories.Contains(value)) { int index2 = pieceTable.m_categories.IndexOf(value); pieceTable.m_categories.RemoveAt(index2); pieceTable.m_categoryLabels.RemoveAt(index2); } } } private static string GetVanillaLabel(PieceCategory category) { //IL_0006: 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 (!VanillaLabels.ContainsKey(category)) { SearchVanillaLabels(); } string value; return VanillaLabels.TryGetValue(category, out value) ? value : string.Empty; } private static void SearchVanillaLabels() { //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_003a: 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) PieceTable[] array = Resources.FindObjectsOfTypeAll(); foreach (PieceTable val in array) { for (int j = 0; j < val.m_categories.Count; j++) { PieceCategory key = val.m_categories[j]; if (j < val.m_categoryLabels.Count && !VanillaLabels.ContainsKey(key) && !string.IsNullOrEmpty(val.m_categoryLabels[j])) { VanillaLabels[key] = val.m_categoryLabels[j]; } } } } private static string GetCategoryToken(string name) { char[] endChars = Localization.instance.m_endChars; string text = string.Concat(name.ToLower().Split(endChars)); return "piecemanager_cat_" + text; } private static void Patch_SetPlaceMode(Player __instance) { if (Object.op_Implicit((Object)(object)__instance.m_buildPieces)) { RepositionCategories(__instance.m_buildPieces); } } private static void UpdateAvailable_Prefix(PieceTable __instance) { if (__instance.m_availablePieces.Count > 0) { int num = ModifiedMaxCategory() - __instance.m_availablePieces.Count; for (int i = 0; i < num; i++) { __instance.m_availablePieces.Add(new List()); } } } private static void UpdateAvailable_Postfix(PieceTable __instance) { Array.Resize(ref __instance.m_selectedPiece, __instance.m_availablePieces.Count); Array.Resize(ref __instance.m_lastSelectedPiece, __instance.m_availablePieces.Count); } [HarmonyPriority(200)] private static void Hud_AwakeCreateTabs() { CreateCategoryTabs(); } [HarmonyPriority(700)] private static void Patch_ZNetSceneAwake(ZNetScene __instance) { foreach (GameObject piecePrefab in piecePrefabs) { if (!__instance.m_prefabs.Contains(piecePrefab)) { __instance.m_prefabs.Add(piecePrefab); } } } [HarmonyPriority(700)] private static void RefFixPatch_ZNetSceneAwake(ZNetScene __instance) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) foreach (GameObject piecePrefab in piecePrefabs) { if (__instance.m_prefabs.Contains(piecePrefab) && Object.op_Implicit((Object)(object)piecePrefab.GetComponent())) { piecePrefab.GetComponent().m_isUpgrade = true; piecePrefab.GetComponent().m_connectionPrefab = __instance.GetPrefab("piece_workbench_ext3").GetComponent().m_connectionPrefab; piecePrefab.GetComponent().m_connectionOffset = __instance.GetPrefab("piece_workbench_ext3").GetComponent().m_connectionOffset; } } } [HarmonyPriority(300)] private static void Patch_ObjectDBInit(ObjectDB __instance) { foreach (BuildPiece registeredPiece in BuildPiece.registeredPieces) { string[] activeTools = registeredPiece.activeTools; foreach (string text in activeTools) { GameObject itemPrefab = __instance.GetItemPrefab(text); PieceTable val = ((itemPrefab != null) ? itemPrefab.GetComponent().m_itemData.m_shared.m_buildPieces : null); if (val != null && !val.m_pieces.Contains(registeredPiece.Prefab)) { val.m_pieces.Add(registeredPiece.Prefab); } } } } } public static class PieceManagerVersion { public const string Version = "1.2.10"; } [PublicAPI] public class Conversion { internal class ConversionConfig { public ConfigEntry input = null; public ConfigEntry output = null; } public string Input = null; public string Output = null; internal ConversionConfig? config; public Conversion(BuildPiece conversionPiece) { conversionPiece.Conversions.Add(this); } } [PublicAPI] public static class SnapPointMaker { private static readonly List ObjectsToApplySnaps = new List(); public static void AddObjectForSnapPoints(GameObject obj) { if ((Object)(object)obj != (Object)null && !ObjectsToApplySnaps.Contains(obj)) { ObjectsToApplySnaps.Add(obj); } } public static void ApplySnapPoints() { GameObject[] array = ObjectsToApplySnaps.ToArray(); foreach (GameObject obj in array) { EnsureSnapPoints(obj); } ObjectsToApplySnaps.Clear(); } public static int EnsureSnapPoints(GameObject obj) { //IL_007b: 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_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008c: 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_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00af: 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) //IL_00ca: 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_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0100: 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_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Expected O, but got Unknown //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)obj == (Object)null) { return 0; } int num = obj.GetComponentsInChildren(true).Count((Transform transform) => (Object)(object)transform != (Object)(object)obj.transform && (((Object)transform).name.StartsWith("_snappoint", StringComparison.OrdinalIgnoreCase) || ((Component)transform).gameObject.tag == "snappoint")); if (num > 0) { return num; } BoxCollider componentInChildren = obj.GetComponentInChildren(true); if ((Object)(object)componentInChildren == (Object)null) { return 0; } Vector3 val = componentInChildren.size * 0.5f; Vector3 center = componentInChildren.center; Vector3[] array = (Vector3[])(object)new Vector3[8] { new Vector3(-1f, -1f, -1f), new Vector3(-1f, -1f, 1f), new Vector3(-1f, 1f, -1f), new Vector3(-1f, 1f, 1f), new Vector3(1f, -1f, -1f), new Vector3(1f, -1f, 1f), new Vector3(1f, 1f, -1f), new Vector3(1f, 1f, 1f) }; for (int num2 = 0; num2 < array.Length; num2++) { Vector3 val2 = center + Vector3.Scale(val, array[num2]); Vector3 val3 = ((Component)componentInChildren).transform.TransformPoint(val2); GameObject val4 = new GameObject("_snappoint_" + (num2 + 1)); val4.transform.SetParent(obj.transform, false); val4.transform.localPosition = obj.transform.InverseTransformPoint(val3); val4.transform.localRotation = Quaternion.identity; val4.layer = 10; try { val4.tag = "snappoint"; } catch { } val4.SetActive(true); } return array.Length; } } } namespace BountyHuntercontract { internal sealed class BountyBoardInteract : MonoBehaviour, Hoverable, Interactable { private const int PosterSlots = 7; private static readonly HashSet Instances = new HashSet(); private readonly GameObject?[] _contractPosters = (GameObject?[])(object)new GameObject[7]; private int _shownPosterCount = -1; private bool _missingPosterSlotsLogged; private void Awake() { Instances.Add(this); RefreshContractPosters(); } private void OnDestroy() { Instances.Remove(this); } internal static void RefreshAllContractPosters() { BountyBoardInteract[] array = Instances.ToArray(); foreach (BountyBoardInteract bountyBoardInteract in array) { if ((Object)(object)bountyBoardInteract != (Object)null) { bountyBoardInteract.RefreshContractPosters(); } } } private void RefreshContractPosters() { if (ResolveContractPosters() == 0) { _shownPosterCount = -1; if (!_missingPosterSlotsLogged) { _missingPosterSlotsLogged = true; BountyDiagnostics.Warning("BOARD", "Could not find poster children contra1..contra7 on '" + ((Object)((Component)this).gameObject).name + "'."); } return; } int val = BountyClient.Contracts.Count((BountyContract contract) => contract.Status == BountyStatus.Active); int num = Math.Min(7, val); if (_shownPosterCount == num) { return; } for (int num2 = 0; num2 < _contractPosters.Length; num2++) { GameObject val2 = _contractPosters[num2]; if ((Object)(object)val2 != (Object)null) { val2.SetActive(num2 < num); } } _shownPosterCount = num; BountyDiagnostics.Detailed("BOARD", "Updated contract posters visible=" + num + " activeContracts=" + val + "."); } private int ResolveContractPosters() { Transform[] componentsInChildren = ((Component)this).GetComponentsInChildren(true); int num = 0; for (int i = 0; i < 7; i++) { if ((Object)(object)_contractPosters[i] == (Object)null) { string name = "contra" + (i + 1); Transform val = ((IEnumerable)componentsInChildren).FirstOrDefault((Func)((Transform item) => ((Object)item).name == name)); _contractPosters[i] = (((Object)(object)val != (Object)null) ? ((Component)val).gameObject : null); } if ((Object)(object)_contractPosters[i] != (Object)null) { num++; } } return num; } public string GetHoverName() { return BountyLocalization.Text("Tablón de contratos", "Bounty Board"); } public string GetHoverText() { string text = "E"; if (Localization.instance != null) { string text2 = Localization.instance.Localize("$KEY_Use"); if (!string.IsNullOrWhiteSpace(text2) && !text2.Contains("$KEY_Use")) { text = text2; } } string text3 = BountyLocalization.Text("Abrir contratos de asesinato", "Open assassination contracts"); return "[" + text + "] " + text3; } public bool Interact(Humanoid user, bool hold, bool alt) { //IL_00aa: Unknown result type (might be due to invalid IL or missing references) if (hold || (Object)(object)user != (Object)(object)Player.m_localPlayer) { BountyDiagnostics.Detailed("BOARD", "Ignored interaction. hold=" + hold + ", localPlayer=" + ((Object)(object)user == (Object)(object)Player.m_localPlayer) + "."); return false; } BountyDiagnostics.Info("BOARD", "Board interaction accepted by " + BountyDiagnostics.LocalIdentity() + " board=" + ((Object)((Component)this).gameObject).name + " pos=" + BountyDiagnostics.Format(((Component)this).transform.position) + "."); BountyUI.Open(); return true; } public bool UseItem(Humanoid user, ItemData item) { BountyDiagnostics.Detailed("BOARD", "UseItem ignored for " + (item?.m_shared?.m_name ?? "") + "."); return false; } } internal static class BountyBoardRegistrar { internal const string AssetBundleName = "bountyhunterv2"; internal const string AssetFolderName = "assets"; internal const string PrefabName = "BH_BOARDBOUNTY"; internal const string LegacyPrefabName = "BHC_BountyBoard"; private static BuildPiece? _piece; private static GameObject? _prefab; private static GameObject? _legacyPrefab; private static GameObject? _legacyContainer; private static bool _registrationAttempted; private static bool _buildMenuLogged; private static int _buildMenuChecks; private static int _lastSceneGeneration; internal static GameObject? Prefab => _prefab; internal static void RegisterPiece() { if (_registrationAttempted) { BountyDiagnostics.Detailed("ASSET", "RegisterPiece called again. Current prefab=" + (((Object)(object)_prefab != (Object)null) ? ((Object)_prefab).name : "") + "."); return; } _registrationAttempted = true; BuildPiece.ConfigurationEnabled = true; try { BountyDiagnostics.Info("ASSET", "Registering PieceManager build piece bundle='bountyhunterv2' prefab='BH_BOARDBOUNTY'."); _piece = new BuildPiece("bountyhunterv2", "BH_BOARDBOUNTY"); _prefab = _piece.Prefab; if ((Object)(object)_prefab == (Object)null) { throw new InvalidDataException("PieceManager returned a null prefab for BH_BOARDBOUNTY."); } ConfigurePrefab(_prefab); _piece.Name.English("Bounty Board"); _piece.Name.Spanish("Tablón de contratos"); _piece.Name.Portuguese_Brazilian("Quadro de recompensas"); _piece.Description.English("Publish and accept assassination contracts against other players."); _piece.Description.Spanish("Publica y acepta contratos de asesinato contra otros jugadores."); _piece.Description.Portuguese_Brazilian("Publique e aceite contratos de assassinato contra outros jogadores."); _piece.RequiredItems.Add("FineWood", 10, recover: true); _piece.RequiredItems.Add("Bronze", 2, recover: true); _piece.Category.Set(BuildPieceCategory.Furniture); _piece.Tool.Add("Hammer"); _piece.Crafting.Set(CraftingTable.Workbench); _piece.SpecialProperties = new SpecialProperties { AdminOnly = true, NoConfig = false }; int num = SnapPointMaker.EnsureSnapPoints(_prefab); CreateLegacyNetworkAlias(); LogPrefabReport(_prefab, "ASSET PREFAB"); BountyDiagnostics.Info("BOARD", "PieceManager registration configured successfully: category=Furniture, tool=Hammer, station=Workbench, recipe=FineWood:10/Bronze:2, AdminOnly=true, NoConfig=false, snapPoints=" + num + "."); } catch (Exception exception) { _piece = null; _prefab = null; BountyDiagnostics.Error("ASSET", exception, "Failed to register build piece 'BH_BOARDBOUNTY'."); } } private static void ConfigurePrefab(GameObject prefab) { ((Object)prefab).name = "BH_BOARDBOUNTY"; if (!prefab.activeSelf) { prefab.SetActive(true); BountyDiagnostics.Warning("ASSET", "AssetBundle prefab was inactive. Set activeSelf=true so placed copies are visible."); } Sign component = prefab.GetComponent(); if ((Object)(object)component != (Object)null) { Object.DestroyImmediate((Object)(object)component); BountyDiagnostics.Detailed("BOARD", "Removed inherited Sign component."); } if ((Object)(object)prefab.GetComponent() == (Object)null) { prefab.AddComponent(); BountyDiagnostics.Info("BOARD", "Attached BountyBoardInteract to BH_BOARDBOUNTY."); } Piece component2 = prefab.GetComponent(); if ((Object)(object)component2 == (Object)null) { throw new InvalidDataException("BH_BOARDBOUNTY has no Piece component."); } if ((Object)(object)prefab.GetComponent() == (Object)null) { throw new InvalidDataException("BH_BOARDBOUNTY has no ZNetView component."); } component2.m_name = "Bounty Board"; component2.m_description = "Publish and accept assassination contracts against other players."; component2.m_enabled = true; } private static void CreateLegacyNetworkAlias() { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown if ((Object)(object)_prefab == (Object)null || (Object)(object)_legacyPrefab != (Object)null) { return; } _legacyContainer = new GameObject("BHC_LegacyPrefabContainer"); Object.DontDestroyOnLoad((Object)(object)_legacyContainer); _legacyContainer.SetActive(false); bool activeSelf = _prefab.activeSelf; try { if (activeSelf) { _prefab.SetActive(false); } _legacyPrefab = Object.Instantiate(_prefab); ((Object)_legacyPrefab).name = "BHC_BountyBoard"; _legacyPrefab.transform.SetParent(_legacyContainer.transform, false); _legacyPrefab.SetActive(true); Piece component = _legacyPrefab.GetComponent(); if ((Object)(object)component != (Object)null) { component.m_enabled = false; } BountyDiagnostics.Info("ASSET", "Created hidden legacy network alias 'BHC_BountyBoard'."); } catch (Exception exception) { BountyDiagnostics.Error("ASSET", exception, "Could not create legacy prefab alias."); if ((Object)(object)_legacyPrefab != (Object)null) { Object.Destroy((Object)(object)_legacyPrefab); } _legacyPrefab = null; } finally { if (activeSelf && (Object)(object)_prefab != (Object)null) { _prefab.SetActive(true); } } } internal static void OnZNetSceneReady(ZNetScene scene) { if ((Object)(object)scene == (Object)null) { return; } _lastSceneGeneration++; if ((Object)(object)_legacyPrefab != (Object)null) { int stableHashCode = StringExtensionMethods.GetStableHashCode("BHC_BountyBoard"); if (!scene.m_prefabs.Contains(_legacyPrefab)) { scene.m_prefabs.Add(_legacyPrefab); } if (scene.m_namedPrefabs.TryGetValue(stableHashCode, out var value)) { if ((Object)(object)value != (Object)(object)_legacyPrefab) { BountyDiagnostics.Warning("BOARD", "Legacy hash collision with prefab '" + ((Object)value).name + "'."); } } else { scene.m_namedPrefabs.Add(stableHashCode, _legacyPrefab); } } GameObject prefab = scene.GetPrefab("BH_BOARDBOUNTY"); if ((Object)(object)prefab == (Object)null) { BountyDiagnostics.Warning("BOARD", "ZNetScene Awake completed before PieceManager exposed 'BH_BOARDBOUNTY'. It will be checked again from the Hammer table."); return; } BountyDiagnostics.Info("BOARD", "ZNetScene registration OK generation=" + _lastSceneGeneration + " prefab='" + ((Object)prefab).name + "' hash=" + StringExtensionMethods.GetStableHashCode("BH_BOARDBOUNTY") + "."); LogPrefabReport(prefab, "NETWORK PREFAB"); } internal static void OnObjectDbReady() { if (!BountyDiagnostics.DiagnosticModeEnabled || (Object)(object)_prefab == (Object)null || (Object)(object)ObjectDB.instance == (Object)null || _buildMenuLogged) { return; } _buildMenuChecks++; GameObject itemPrefab = ObjectDB.instance.GetItemPrefab("Hammer"); PieceTable val = ((itemPrefab == null) ? null : itemPrefab.GetComponent()?.m_itemData.m_shared.m_buildPieces); if ((Object)(object)val == (Object)null) { BountyDiagnostics.Detailed("BOARD", "Hammer PieceTable not ready yet. check=" + _buildMenuChecks + "."); return; } GameObject val2 = ((IEnumerable)val.m_pieces).FirstOrDefault((Func)((GameObject piece) => (Object)(object)piece != (Object)null && (piece == _prefab || ((Object)piece).name == "BH_BOARDBOUNTY"))); if ((Object)(object)val2 != (Object)null) { _buildMenuLogged = true; BountyDiagnostics.Info("BOARD", "Hammer registration OK: 'BH_BOARDBOUNTY' is present in the build menu. totalPieces=" + val.m_pieces.Count + " checks=" + _buildMenuChecks + "."); } else if (_buildMenuChecks == 3 || _buildMenuChecks == 10) { BountyDiagnostics.Warning("BOARD", "PieceManager has not added 'BH_BOARDBOUNTY' to Hammer yet. check=" + _buildMenuChecks + " totalPieces=" + val.m_pieces.Count + "."); } else { BountyDiagnostics.Detailed("BOARD", "Waiting for PieceManager Hammer registration. check=" + _buildMenuChecks + "."); } } private static void LogPrefabReport(GameObject prefab, string label) { Piece component = prefab.GetComponent(); ZNetView component2 = prefab.GetComponent(); WearNTear component3 = prefab.GetComponent(); Renderer[] componentsInChildren = prefab.GetComponentsInChildren(true); Collider[] componentsInChildren2 = prefab.GetComponentsInChildren(true); int num = prefab.GetComponentsInChildren(true).Count((Transform transform) => (Object)(object)transform != (Object)(object)prefab.transform && ((Object)transform).name.StartsWith("_snappoint", StringComparison.OrdinalIgnoreCase)); BountyDiagnostics.Info("BOARD", label + " report: name=" + ((Object)prefab).name + ", activeSelf=" + prefab.activeSelf + ", Piece=" + ((Object)(object)component != (Object)null) + ", ZNetView=" + ((Object)(object)component2 != (Object)null) + ", WearNTear=" + ((Object)(object)component3 != (Object)null) + ", Renderers=" + componentsInChildren.Length + ", Colliders=" + componentsInChildren2.Length + ", SnapPoints=" + num + "."); Renderer[] array = componentsInChildren; foreach (Renderer val in array) { string text = string.Join(", ", (from material in val.sharedMaterials where (Object)(object)material != (Object)null select ((Object)material).name + "[" + (((Object)(object)material.shader != (Object)null) ? ((Object)material.shader).name : "no shader") + "]").ToArray()); BountyDiagnostics.Detailed("BOARD", "Renderer '" + ((Object)val).name + "' enabled=" + val.enabled + " materials=" + text + "."); } } } internal static class BountyClient { private sealed class PendingPersonalAnnouncement { internal long PlayerId; internal string ContractId = string.Empty; internal string Title = string.Empty; internal string Message = string.Empty; internal DateTime ReceivedUtc; internal float DelaySeconds; } internal static readonly List OnlinePlayers = new List(); internal static readonly List Contracts = new List(); internal static readonly List PlayerStatistics = new List(); internal static readonly List History = new List(); internal static BountyGlobalStats GlobalStatistics = new BountyGlobalStats(); private static readonly Dictionary TrackingReceived = new Dictionary(); private static readonly Dictionary> PinsByContract = new Dictionary>(); private static readonly HashSet GrantedRewardIds = new HashSet(); private static readonly Dictionary LocalLastHits = new Dictionary(); private static readonly Queue PersonalAnnouncementQueue = new Queue(); private static readonly HashSet CompletionAnnouncementsShown = new HashSet(); private static float _personalAnnouncementCooldown; private static long _lastPlayerId; private static int _lastNetworkGeneration; private static float _helloTimer; private static float _positionTimer; private static bool _historyRequestPending; private static bool _wasForcedPvp; private static bool _previousPvp; private static bool _wasLocalDead; private static DateTime _lastBlockedPvpLogUtc = DateTime.MinValue; private static DateTime _lastPveOverrideLogUtc = DateTime.MinValue; private static bool _pvpUiForced; private static DateTime _lastDeathReportUtc = DateTime.MinValue; private static string _lastDeathReportedContractId = string.Empty; private static readonly MethodInfo? IsPvpMethod = AccessTools.Method(typeof(Player), "IsPVPEnabled", (Type[])null, (Type[])null); private static readonly MethodInfo? SetPvpMethod = AccessTools.Method(typeof(Player), "SetPVP", (Type[])null, (Type[])null); internal static bool HistoryLoaded { get; private set; } internal static long LocalPlayerId { get { Player localPlayer = Player.m_localPlayer; return ((Object)(object)localPlayer == (Object)null) ? 0 : localPlayer.GetPlayerID(); } } internal static void Update(float deltaTime) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null) { RestorePvpIfNeeded(); ClearAllPins(); _lastPlayerId = 0L; return; } long playerID = localPlayer.GetPlayerID(); int registrationGeneration = BountyNetwork.RegistrationGeneration; if (_lastPlayerId != playerID || _lastNetworkGeneration != registrationGeneration) { _lastPlayerId = playerID; _lastNetworkGeneration = registrationGeneration; _helloTimer = 999f; _positionTimer = 999f; OnlinePlayers.Clear(); Contracts.Clear(); PlayerStatistics.Clear(); History.Clear(); HistoryLoaded = false; _historyRequestPending = false; GlobalStatistics = new BountyGlobalStats(); LocalLastHits.Clear(); CompletionAnnouncementsShown.Clear(); PersonalAnnouncementQueue.Clear(); _personalAnnouncementCooldown = 0f; _pvpUiForced = false; ClearAllPins(); _wasLocalDead = ((Character)localPlayer).IsDead(); _lastDeathReportedContractId = string.Empty; _lastDeathReportUtc = DateTime.MinValue; BountyDiagnostics.Info("CLIENT", "Local bounty session initialized/reset. " + BountyDiagnostics.LocalIdentity() + " rpcGeneration=" + registrationGeneration + "."); } _helloTimer += deltaTime; _positionTimer += deltaTime; if (_helloTimer >= 15f) { _helloTimer = 0f; SendHello(); } float num = Mathf.Max(1f, BountyHuntercontractPlugin.TrackingIntervalSeconds.Value); if (IsLocalParticipantInActiveCombat()) { if (_positionTimer >= num) { _positionTimer = 0f; SendPosition(); } } else { _positionTimer = 0f; } AdvanceLocalContractTimers(deltaTime); bool flag = ((Character)localPlayer).IsDead(); if (flag && !_wasLocalDead) { BountyDiagnostics.Info("DEATH", "Detected local alive->dead transition from client update fallback for " + BountyDiagnostics.LocalIdentity() + "."); ReportLocalDeath(localPlayer); } _wasLocalDead = flag; UpdateForcedPvp(); RemoveStalePins(num * 3.5f); UpdatePersonalAnnouncements(deltaTime); } private static ZPackage CreateIdentifiedPackage() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown ZPackage val = new ZPackage(); Player localPlayer = Player.m_localPlayer; val.Write(((Object)(object)localPlayer == (Object)null) ? 0 : localPlayer.GetPlayerID()); val.Write(((Object)(object)localPlayer == (Object)null) ? string.Empty : localPlayer.GetPlayerName()); return val; } internal static void SendHello() { if (!((Object)(object)Player.m_localPlayer == (Object)null)) { BountyDiagnostics.Detailed("CLIENT", "Sending Hello. " + BountyDiagnostics.LocalIdentity() + "."); BountyNetwork.SendToServer("BHC_Hello", CreateIdentifiedPackage()); } } internal static void RequestState() { if (!((Object)(object)Player.m_localPlayer == (Object)null)) { BountyDiagnostics.Detailed("CLIENT", "Requesting complete bounty state. " + BountyDiagnostics.LocalIdentity() + "."); BountyNetwork.SendToServer("BHC_RequestState", CreateIdentifiedPackage()); } } internal static void RequestHistory(bool force = false) { if (!((Object)(object)Player.m_localPlayer == (Object)null) && !_historyRequestPending && (!HistoryLoaded || force)) { _historyRequestPending = true; BountyDiagnostics.Detailed("CLIENT", "Requesting bounty history on demand. " + BountyDiagnostics.LocalIdentity() + "."); BountyNetwork.SendToServer("BHC_RequestHistory", CreateIdentifiedPackage()); } } internal static void RequestCreate(long targetPlayerId, BountyRewardItem reward, int maximumHunters, int durationMinutes) { if (!((Object)(object)Player.m_localPlayer == (Object)null) && reward != null && reward.IsValid()) { ZPackage val = CreateIdentifiedPackage(); val.Write(targetPlayerId); BountySerialization.WriteRewardItem(val, reward); val.Write(maximumHunters); val.Write(durationMinutes); BountyDiagnostics.Info("CLIENT", "Create requested target=" + targetPlayerId + " reward='" + BountyItemCodec.Describe(reward, localized: false) + "' maxHunters=" + maximumHunters + " durationMinutes=" + durationMinutes + " by " + BountyDiagnostics.LocalIdentity() + "."); BountyNetwork.SendToServer("BHC_CreateIntent", val); } } internal static void RequestAccept(string contractId) { if (!((Object)(object)Player.m_localPlayer == (Object)null)) { ZPackage val = CreateIdentifiedPackage(); val.Write(contractId); BountyDiagnostics.Info("CLIENT", "Accept requested contract=" + contractId + " by " + BountyDiagnostics.LocalIdentity() + "."); BountyNetwork.SendToServer("BHC_AcceptContract", val); } } internal static void RequestLeave(string contractId) { if (!((Object)(object)Player.m_localPlayer == (Object)null)) { ZPackage val = CreateIdentifiedPackage(); val.Write(contractId); BountyDiagnostics.Info("CLIENT", "Leave requested contract=" + contractId + " by " + BountyDiagnostics.LocalIdentity() + "."); BountyNetwork.SendToServer("BHC_LeaveContract", val); } } internal static void RequestCancel(string contractId) { if (!((Object)(object)Player.m_localPlayer == (Object)null)) { ZPackage val = CreateIdentifiedPackage(); val.Write(contractId); BountyDiagnostics.Info("CLIENT", "Cancel requested contract=" + contractId + " by " + BountyDiagnostics.LocalIdentity() + "."); BountyNetwork.SendToServer("BHC_CancelContract", val); } } internal static void RequestAdminCommand(string command) { if ((Object)(object)Player.m_localPlayer == (Object)null) { BountyCommands.PrintLocal("Debes estar dentro de un mundo para usar los comandos BHC."); return; } ZPackage val = CreateIdentifiedPackage(); val.Write(command ?? string.Empty); BountyDiagnostics.Info("ADMIN", "Admin command requested: " + command + " by " + BountyDiagnostics.LocalIdentity() + "."); BountyNetwork.SendToServer("BHC_AdminCommand", val); } internal static void RequestClaim(string contractId) { if (!((Object)(object)Player.m_localPlayer == (Object)null)) { ZPackage val = CreateIdentifiedPackage(); val.Write(contractId); BountyDiagnostics.Info("CLAIM", "Reward claim requested contract=" + contractId + " by " + BountyDiagnostics.LocalIdentity() + "."); BountyNetwork.SendToServer("BHC_ClaimReward", val); } } internal static void RequestClaimBond(string contractId) { if (!((Object)(object)Player.m_localPlayer == (Object)null)) { ZPackage val = CreateIdentifiedPackage(); val.Write(contractId); BountyDiagnostics.Info("BOND", "Bond claim requested contract=" + contractId + " by " + BountyDiagnostics.LocalIdentity() + "."); BountyNetwork.SendToServer("BHC_ClaimBond", val); } } internal static void RpcState(long sender, ZPackage package) { if (!BountyNetwork.IsFromServer(sender)) { return; } try { int num = package.ReadInt(); if (num > 6) { return; } OnlinePlayers.Clear(); int num2 = package.ReadInt(); for (int i = 0; i < num2; i++) { OnlinePlayers.Add(new BountyPlayerInfo { PlayerId = package.ReadLong(), Name = package.ReadString(), Connected = package.ReadBool() }); } Contracts.Clear(); int num3 = package.ReadInt(); for (int j = 0; j < num3; j++) { Contracts.Add(BountySerialization.ReadContract(package, num)); } int num4 = GlobalStatistics.TotalContractsCompleted + GlobalStatistics.TotalContractsCancelled + GlobalStatistics.TotalContractsExpired; PlayerStatistics.Clear(); BountyGlobalStats globalStatistics = new BountyGlobalStats(); bool flag = false; if (num >= 4) { int num5 = package.ReadInt(); for (int k = 0; k < num5; k++) { PlayerStatistics.Add(BountySerialization.ReadPlayerStats(package)); } globalStatistics = BountySerialization.ReadGlobalStats(package); int num6 = package.ReadInt(); if (num6 > 0) { History.Clear(); for (int l = 0; l < num6; l++) { History.Add(BountySerialization.ReadHistory(package, num)); } SortHistoryNewestFirst(); HistoryLoaded = true; flag = true; _historyRequestPending = false; } } GlobalStatistics = globalStatistics; BountyBoardInteract.RefreshAllContractPosters(); int num7 = GlobalStatistics.TotalContractsCompleted + GlobalStatistics.TotalContractsCancelled + GlobalStatistics.TotalContractsExpired; if (!flag && num7 != num4 && History.Count > 0) { HistoryLoaded = false; } HashSet activeContractIds = new HashSet(from contract in Contracts where contract.Status == BountyStatus.Active select contract.ContractId); foreach (string item in PinsByContract.Keys.Where((string contractId) => !activeContractIds.Contains(contractId)).ToList()) { RemovePins(item); TrackingReceived.Remove(item); } foreach (string item2 in LocalLastHits.Keys.Where((string contractId) => !activeContractIds.Contains(contractId)).ToList()) { LocalLastHits.Remove(item2); } QueueCompletionAnnouncementFallbacks(); BountyDiagnostics.Info("STATE", "State received from sender=" + sender + " version=" + num + " players=" + num2 + " contracts=" + num3 + " stats=" + PlayerStatistics.Count + " history=" + History.Count + ". Players=[" + string.Join(", ", OnlinePlayers.Select((BountyPlayerInfo player) => player.Name + "#" + player.PlayerId).ToArray()) + "] Contracts=[" + string.Join(", ", Contracts.Select((BountyContract contract) => contract.ContractId + ":" + contract.TargetName + " hunters=" + contract.Hunters.Count + "/" + contract.MaximumHunters).ToArray()) + "]."); } catch (Exception exception) { BountyDiagnostics.Error("STATE", exception, "Failed to read bounty state."); } } internal static void RpcHistory(long sender, ZPackage package) { if (!BountyNetwork.IsFromServer(sender)) { return; } try { int num = package.ReadInt(); if (num > 6) { _historyRequestPending = false; return; } int num2 = package.ReadInt(); History.Clear(); for (int i = 0; i < num2; i++) { History.Add(BountySerialization.ReadHistory(package, num)); } SortHistoryNewestFirst(); HistoryLoaded = true; _historyRequestPending = false; BountyDiagnostics.Info("STATE", "History received from sender=" + sender + " entries=" + History.Count + "."); } catch (Exception exception) { _historyRequestPending = false; BountyDiagnostics.Error("STATE", exception, "Failed to read bounty history."); } } private static void SortHistoryNewestFirst() { History.Sort((BountyHistoryEntry left, BountyHistoryEntry right) => right.FinishedUtcTicks.CompareTo(left.FinishedUtcTicks)); } internal static void RpcReserveReward(long sender, ZPackage package) { if (!BountyNetwork.IsFromServer(sender) || (Object)(object)Player.m_localPlayer == (Object)null) { return; } string text = package.ReadString(); BountyRewardItem reward = BountySerialization.ReadRewardItem(package); int bondCoins = package.ReadInt(); BountyDiagnostics.Info("REWARD", "Server requested item reservation token=" + text + " reward='" + BountyItemCodec.Describe(reward, localized: false) + "' bondCoins=" + bondCoins + "."); bool flag = false; string error = string.Empty; BountyCompatibilityDiagnostics.BeginInventoryTransaction("deposit", text, reward); try { flag = BountyItemCodec.TryRemoveRewardAndBond(Player.m_localPlayer, reward, bondCoins, out error); } catch (Exception ex) { error = ex.Message; throw; } finally { BountyCompatibilityDiagnostics.EndInventoryTransaction(flag, error); } BountyDiagnostics.Info("REWARD", "Item and bond reservation result token=" + text + " removed=" + flag + " bondCoins=" + bondCoins + " error='" + error + "'."); if (flag) { BountyUI.NotifyDepositedReward(reward, bondCoins); } ZPackage val = CreateIdentifiedPackage(); val.Write(text); val.Write(flag); val.Write(error ?? string.Empty); BountyNetwork.SendToServer("BHC_ReserveConfirmed", val); } internal static void RpcTracking(long sender, ZPackage package) { //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) if (BountyNetwork.IsFromServer(sender) && !((Object)(object)Player.m_localPlayer == (Object)null) && !((Object)(object)Minimap.instance == (Object)null)) { string text = package.ReadString(); int num = package.ReadInt(); List list = new List(); for (int i = 0; i < num; i++) { list.Add(new TrackingEntry { PlayerId = package.ReadLong(), Name = package.ReadString(), Position = package.ReadVector3(), Role = package.ReadString() }); } ReplacePins(text, list); TrackingReceived[text] = DateTime.UtcNow; BountyDiagnostics.Tracking("TRACK", "Tracking packet received contract=" + text + " entries=" + num + " [" + string.Join(", ", list.Select((TrackingEntry entry) => entry.Role + ":" + entry.Name + "#" + entry.PlayerId + "@" + BountyDiagnostics.Format(entry.Position)).ToArray()) + "]."); } } internal static void RpcGrantReward(long sender, ZPackage package) { if (!BountyNetwork.IsFromServer(sender) || (Object)(object)Player.m_localPlayer == (Object)null) { return; } PendingReward pendingReward = BountySerialization.ReadReward(package); if (pendingReward.PlayerId != Player.m_localPlayer.GetPlayerID()) { return; } if (!GrantedRewardIds.Add(pendingReward.RewardId)) { BountyDiagnostics.Warning("REWARD", "Duplicate reward packet ignored id=" + pendingReward.RewardId + "."); return; } BountyDiagnostics.Info("REWARD", "Reward received id=" + pendingReward.RewardId + " item='" + BountyItemCodec.Describe(pendingReward.Reward, localized: false) + "' reason='" + pendingReward.Reason + "'."); bool success = false; string error = string.Empty; BountyCompatibilityDiagnostics.BeginInventoryTransaction("grant", pendingReward.RewardId, pendingReward.Reward); try { BountyItemCodec.GiveReward(Player.m_localPlayer, pendingReward.Reward); success = true; } catch (Exception ex) { error = ex.Message; throw; } finally { BountyCompatibilityDiagnostics.EndInventoryTransaction(success, error); } ShowMessage(BountyLocalization.Text("Recibiste ", "You received ") + BountyItemCodec.Describe(pendingReward.Reward) + ". " + BountyLocalization.TranslateIncoming(pendingReward.Reason)); } internal static void RpcStatus(long sender, ZPackage package) { if (BountyNetwork.IsFromServer(sender)) { bool success = package.ReadBool(); string text = BountyLocalization.TranslateIncoming(package.ReadString()); BountyDiagnostics.Info("STATUS", "Server status success=" + success + " message='" + text + "'."); BountyUI.SetStatus(text, success); ShowMessage(text); } } internal static void RpcAdminResult(long sender, ZPackage package) { if (BountyNetwork.IsFromServer(sender)) { int num = package.ReadInt(); for (int i = 0; i < num; i++) { string line = package.ReadString(); BountyCommands.PrintLocal(line); } } } internal static void RpcAnnouncement(long sender, ZPackage package) { if (BountyNetwork.IsFromServer(sender)) { string text = BountyLocalization.TranslateIncoming(package.ReadString()); string text2 = BountyLocalization.TranslateIncoming(package.ReadString()); BountyDiagnostics.Info("ANNOUNCEMENT", "Global contract announcement received title='" + text + "' message='" + text2.Replace("\n", " | ") + "'."); ShowAnnouncement(text, text2); } } internal static void RpcPersonalAnnouncement(long sender, ZPackage package) { if (BountyNetwork.IsFromServer(sender)) { long num = package.ReadLong(); string text = package.ReadString(); string text2 = BountyLocalization.TranslateIncoming(package.ReadString()); string text3 = BountyLocalization.TranslateIncoming(package.ReadString()); long localPlayerId = LocalPlayerId; if (localPlayerId != 0L && num != localPlayerId) { BountyDiagnostics.Warning("ANNOUNCEMENT", "Personal announcement ignored because intendedPlayer=" + num + " localPlayer=" + localPlayerId + "."); return; } if (!string.IsNullOrEmpty(text) && !CompletionAnnouncementsShown.Add(text)) { BountyDiagnostics.Detailed("ANNOUNCEMENT", "Duplicate personal completion announcement ignored contract=" + text + "."); return; } PersonalAnnouncementQueue.Enqueue(new PendingPersonalAnnouncement { PlayerId = num, ContractId = (text ?? string.Empty), Title = (text2 ?? string.Empty), Message = (text3 ?? string.Empty), ReceivedUtc = DateTime.UtcNow, DelaySeconds = 1.5f }); BountyDiagnostics.Info("ANNOUNCEMENT", "Personal announcement queued player=" + num + " contract=" + text + " title='" + text2 + "'."); } } internal static bool IsLocalParticipantInActiveCombat() { long localId = LocalPlayerId; if (localId == 0) { return false; } return Contracts.Any((BountyContract contract) => contract.Status == BountyStatus.Active && contract.ActiveHunterCount > 0 && contract.IsActiveParticipant(localId)); } internal static bool ShouldBlockPvpDisable(Player player, object[] arguments) { if ((Object)(object)player != (Object)(object)Player.m_localPlayer || arguments.Length == 0 || !(arguments[0] is bool)) { return false; } return !(bool)arguments[0] && IsLocalParticipantInActiveCombat(); } internal static void LogBlockedPvpDisable() { DateTime utcNow = DateTime.UtcNow; if (!((utcNow - _lastBlockedPvpLogUtc).TotalSeconds < 5.0)) { _lastBlockedPvpLogUtc = utcNow; BountyDiagnostics.Info("PVP", "Blocked SetPVP(false) while a bounty contract is active for " + BountyDiagnostics.LocalIdentity() + ". Further identical logs are throttled for 5 seconds."); } } internal static void ReassertForcedPvp(string source) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || !IsLocalParticipantInActiveCombat()) { ReleaseForcedPvpUi(); return; } bool flag = !ReadPvp(localPlayer); if (flag) { WritePvp(localPlayer, enabled: true); } ForcePvpUi(); DateTime utcNow = DateTime.UtcNow; if (flag || (utcNow - _lastPveOverrideLogUtc).TotalSeconds >= 30.0) { _lastPveOverrideLogUtc = utcNow; BountyDiagnostics.Info("PVE-OVERRIDE", "Forced bounty PvP over biome/ward/city/territory PvE rule source=" + (string.IsNullOrWhiteSpace(source) ? "unknown" : source) + " for " + BountyDiagnostics.LocalIdentity() + "."); } } internal static void LogPveRuleConvertedToPvp(string source) { DateTime utcNow = DateTime.UtcNow; if (!((utcNow - _lastPveOverrideLogUtc).TotalSeconds < 5.0)) { _lastPveOverrideLogUtc = utcNow; BountyDiagnostics.Info("PVE-OVERRIDE", "Converted an external forced-PvE request into PvP because the local player is in an active bounty. source=" + (string.IsNullOrWhiteSpace(source) ? "unknown" : source) + "."); } } private static void ForcePvpUi() { try { if (!((Object)(object)InventoryGui.instance == (Object)null) && !((Object)(object)InventoryGui.instance.m_pvp == (Object)null)) { InventoryGui.instance.m_pvp.isOn = true; ((Selectable)InventoryGui.instance.m_pvp).interactable = false; _pvpUiForced = true; } } catch (Exception ex) { BountyDiagnostics.Detailed("PVE-OVERRIDE", "Could not synchronize the PvP toggle UI: " + ex.Message); } } private static void ReleaseForcedPvpUi() { if (!_pvpUiForced) { return; } _pvpUiForced = false; try { if (!((Object)(object)InventoryGui.instance == (Object)null) && !((Object)(object)InventoryGui.instance.m_pvp == (Object)null)) { ((Selectable)InventoryGui.instance.m_pvp).interactable = true; Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { InventoryGui.instance.m_pvp.isOn = ReadPvp(localPlayer); } } } catch { } } internal static bool? EvaluatePlayerDamage(long attackerPlayerId, long victimPlayerId, out string contractId) { contractId = string.Empty; bool flag = false; foreach (BountyContract contract in Contracts) { if (contract.Status == BountyStatus.Active && contract.Hunters.Count != 0) { if (contract.IsParticipant(attackerPlayerId) || contract.IsParticipant(victimPlayerId)) { flag = true; } if (contract.IsOpposingPair(attackerPlayerId, victimPlayerId)) { contractId = contract.ContractId; return true; } } } return flag ? new bool?(false) : ((bool?)null); } internal static void ReportValidHit(string contractId, long attackerPlayerId, long victimPlayerId) { if (!((Object)(object)Player.m_localPlayer == (Object)null) && Player.m_localPlayer.GetPlayerID() == victimPlayerId) { LocalLastHits[contractId] = new LastValidHit { ContractId = contractId, AttackerPlayerId = attackerPlayerId, VictimPlayerId = victimPlayerId, ServerReceivedUtc = DateTime.UtcNow }; ZPackage val = CreateIdentifiedPackage(); val.Write(contractId); val.Write(attackerPlayerId); val.Write(victimPlayerId); BountyDiagnostics.Info("COMBAT", "Reporting victim-owned bounty hit contract=" + contractId + " attacker=" + attackerPlayerId + " victim=" + victimPlayerId + "."); BountyNetwork.SendToServer("BHC_CombatHit", val); } } internal static void ReportLocalDeath(Player player) { if ((Object)(object)player != (Object)(object)Player.m_localPlayer) { return; } long playerId = player.GetPlayerID(); DateTime now = DateTime.UtcNow; BountyContract bountyContract = null; long num = 0L; LastValidHit newestHit = (from hit in LocalLastHits.Values where hit.VictimPlayerId == playerId && (now - hit.ServerReceivedUtc).TotalSeconds >= 0.0 && (now - hit.ServerReceivedUtc).TotalSeconds <= (double)Math.Max(1f, BountyHuntercontractPlugin.KillCreditSeconds.Value) orderby hit.ServerReceivedUtc descending select hit).FirstOrDefault(); if (newestHit != null) { bountyContract = Contracts.FirstOrDefault((BountyContract item) => item.Status == BountyStatus.Active && item.ContractId == newestHit.ContractId && item.IsActiveParticipant(playerId)); if (bountyContract != null) { num = newestHit.AttackerPlayerId; } } if (bountyContract == null) { bountyContract = Contracts.FirstOrDefault((BountyContract item) => item.Status == BountyStatus.Active && item.TargetPlayerId == playerId); } if (bountyContract == null) { BountyDiagnostics.Detailed("DEATH", "Local death is not an active bounty participant player=" + playerId + "."); return; } if (_lastDeathReportedContractId == bountyContract.ContractId && (now - _lastDeathReportUtc).TotalSeconds < 5.0) { BountyDiagnostics.Detailed("DEATH", "Duplicate local death report suppressed contract=" + bountyContract.ContractId + "."); return; } _lastDeathReportedContractId = bountyContract.ContractId; _lastDeathReportUtc = now; ZPackage val = CreateIdentifiedPackage(); val.Write(playerId); val.Write(bountyContract.ContractId); val.Write(num); BountyDiagnostics.Info("DEATH", "Reporting bounty participant death player=" + playerId + " contract=" + bountyContract.ContractId + " localLastAttacker=" + num + "."); BountyNetwork.SendToServer("BHC_Death", val); } private static void AdvanceLocalContractTimers(float deltaTime) { if (deltaTime <= 0f) { return; } long ticks = TimeSpan.FromSeconds(deltaTime).Ticks; if (ticks <= 0) { return; } foreach (BountyContract contract in Contracts) { if (contract.Status == BountyStatus.Active && contract.DurationMinutes > 0 && contract.DurationTimerRunning && contract.RemainingDurationTicks > 0) { contract.RemainingDurationTicks = Math.Max(0L, contract.RemainingDurationTicks - ticks); } } } private static void SendPosition() { //IL_0022: 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_0029: 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) if (!((Object)(object)Player.m_localPlayer == (Object)null)) { ZPackage val = CreateIdentifiedPackage(); Vector3 position = ((Component)Player.m_localPlayer).transform.position; val.Write(position); BountyDiagnostics.Tracking("TRACK", "Sending local position " + BountyDiagnostics.Format(position) + " for " + BountyDiagnostics.LocalIdentity() + "."); BountyNetwork.SendToServer("BHC_Position", val); } } private static void UpdateForcedPvp() { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { bool flag = IsLocalParticipantInActiveCombat(); if (flag && !_wasForcedPvp) { _previousPvp = ReadPvp(localPlayer); _wasForcedPvp = true; BountyDiagnostics.Info("PVP", "Beginning absolute bounty PvP override. previousState=" + _previousPvp + " for " + BountyDiagnostics.LocalIdentity() + "."); } if (flag) { ReassertForcedPvp("BountyClient.Update"); } else { RestorePvpIfNeeded(); } } } private static void RestorePvpIfNeeded() { if (_wasForcedPvp) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { BountyDiagnostics.Info("PVP", "Contract combat ended; restoring PvP=" + _previousPvp + "."); WritePvp(localPlayer, _previousPvp); } _wasForcedPvp = false; ReleaseForcedPvpUi(); } } private static bool ReadPvp(Player player) { try { return IsPvpMethod != null && (bool)IsPvpMethod.Invoke(player, null); } catch { return false; } } private static void WritePvp(Player player, bool enabled) { try { SetPvpMethod?.Invoke(player, new object[1] { enabled }); BountyDiagnostics.Detailed("PVP", "SetPVP invoked enabled=" + enabled + "."); } catch (Exception ex) { BountyDiagnostics.Warning("PVP", "Could not change PvP state: " + ex.Message); } } private static void ReplacePins(string contractId, List entries) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) RemovePins(contractId); List list = new List(); foreach (TrackingEntry entry in entries) { object obj = MinimapPinAdapter.AddPin(entry.Position, "[Contrato] " + entry.Role + ": " + entry.Name); if (obj != null) { list.Add(obj); } } PinsByContract[contractId] = list; BountyDiagnostics.Tracking("TRACK", "Map pins replaced contract=" + contractId + " requested=" + entries.Count + " created=" + list.Count + "."); } private static void RemoveStalePins(float maximumAgeSeconds) { DateTime cutoff = DateTime.UtcNow.AddSeconds(0f - maximumAgeSeconds); List list = (from pair in TrackingReceived where pair.Value < cutoff select pair.Key).ToList(); foreach (string item in list) { RemovePins(item); TrackingReceived.Remove(item); } } private static void RemovePins(string contractId) { if (!PinsByContract.TryGetValue(contractId, out List value)) { return; } foreach (object item in value) { MinimapPinAdapter.RemovePin(item); } PinsByContract.Remove(contractId); BountyDiagnostics.Tracking("TRACK", "Removed " + value.Count + " map pin(s) for contract=" + contractId + "."); } private static void ClearAllPins() { foreach (string item in PinsByContract.Keys.ToList()) { RemovePins(item); } TrackingReceived.Clear(); } private static void QueueCompletionAnnouncementFallbacks() { long localId = LocalPlayerId; if (localId == 0) { return; } foreach (BountyContract item in Contracts.Where((BountyContract item) => item.Status == BountyStatus.Completed && item.WinnerPlayerId == localId && !item.RewardClaimed)) { if (CompletionAnnouncementsShown.Add(item.ContractId)) { bool flag = item.Outcome == BountyOutcome.TargetVictory && item.TargetPlayerId == localId; PersonalAnnouncementQueue.Enqueue(new PendingPersonalAnnouncement { PlayerId = localId, ContractId = item.ContractId, Title = (flag ? BountyLocalization.Text("¡SOBREVIVISTE AL CONTRATO!", "YOU SURVIVED THE CONTRACT!") : BountyLocalization.Text("¡CONTRATO CUMPLIDO!", "CONTRACT COMPLETED!")), Message = (flag ? (BountyLocalization.Text("La recompensa ahora es tuya. Vuelve al tablón para cobrar ", "The reward is now yours. Return to the board to claim ") + BountyItemCodec.Describe(item.Reward) + ".") : (BountyLocalization.Text("Eliminaste a ", "You eliminated ") + item.TargetName + BountyLocalization.Text(". Vuelve al tablón para cobrar ", ". Return to the board to claim ") + BountyItemCodec.Describe(item.Reward) + ".")), ReceivedUtc = DateTime.UtcNow, DelaySeconds = 0.75f }); BountyDiagnostics.Info("ANNOUNCEMENT", "Queued completion announcement from synchronized state contract=" + item.ContractId + " outcome=" + item.Outcome.ToString() + "."); } } } private static void UpdatePersonalAnnouncements(float deltaTime) { if (_personalAnnouncementCooldown > 0f) { _personalAnnouncementCooldown -= deltaTime; } else { if (PersonalAnnouncementQueue.Count == 0 || (Object)(object)Player.m_localPlayer == (Object)null) { return; } PendingPersonalAnnouncement pendingPersonalAnnouncement = PersonalAnnouncementQueue.Peek(); if ((DateTime.UtcNow - pendingPersonalAnnouncement.ReceivedUtc).TotalSeconds > 30.0) { PersonalAnnouncementQueue.Dequeue(); BountyDiagnostics.Warning("ANNOUNCEMENT", "Expired queued personal announcement contract=" + pendingPersonalAnnouncement.ContractId + "."); } else if (!((DateTime.UtcNow - pendingPersonalAnnouncement.ReceivedUtc).TotalSeconds < (double)pendingPersonalAnnouncement.DelaySeconds)) { if (pendingPersonalAnnouncement.PlayerId != 0L && pendingPersonalAnnouncement.PlayerId != LocalPlayerId) { PersonalAnnouncementQueue.Dequeue(); return; } PersonalAnnouncementQueue.Dequeue(); ShowAnnouncement(pendingPersonalAnnouncement.Title, pendingPersonalAnnouncement.Message); _personalAnnouncementCooldown = 3f; BountyDiagnostics.Info("ANNOUNCEMENT", "Displayed queued personal announcement contract=" + pendingPersonalAnnouncement.ContractId + "."); } } } private static void ShowMessage(string message) { try { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, message, 0, (Sprite)null); } } catch { BountyDiagnostics.Info("MESSAGE", message); } } private static void ShowAnnouncement(string title, string message) { string text = title + "\n" + message; try { if ((Object)(object)Player.m_localPlayer == (Object)null) { BountyDiagnostics.Warning("ANNOUNCEMENT", "Announcement arrived before the local player was ready: " + text.Replace("\n", " | ") + "."); return; } ((Character)Player.m_localPlayer).Message((MessageType)2, text, 0, (Sprite)null); BountyDiagnostics.Info("ANNOUNCEMENT", "Displayed center-screen global contract announcement."); } catch (Exception exception) { BountyDiagnostics.Error("ANNOUNCEMENT", exception, "Could not display global contract announcement."); } } } internal static class MinimapPinAdapter { private static readonly MethodInfo? AddPinMethod = (from method in typeof(Minimap).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) where method.Name == "AddPin" orderby method.GetParameters().Length descending select method).FirstOrDefault(); internal static object? AddPin(Vector3 position, string name) { //IL_006e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Minimap.instance == (Object)null || AddPinMethod == null) { return null; } try { ParameterInfo[] parameters = AddPinMethod.GetParameters(); object[] array = new object[parameters.Length]; int num = 0; for (int i = 0; i < parameters.Length; i++) { Type parameterType = parameters[i].ParameterType; if (parameterType == typeof(Vector3)) { array[i] = position; } else if (parameterType == typeof(string)) { array[i] = name; } else if (parameterType == typeof(bool)) { array[i] = num++ > 1; } else if (parameterType == typeof(long)) { array[i] = 0L; } else if (parameterType == typeof(int)) { array[i] = 0; } else if (parameterType.IsEnum) { try { array[i] = Enum.Parse(parameterType, "Player", ignoreCase: true); } catch { array[i] = Enum.GetValues(parameterType).GetValue(0); } } else if (parameters[i].HasDefaultValue) { array[i] = parameters[i].DefaultValue; } else { array[i] = (parameterType.IsValueType ? Activator.CreateInstance(parameterType) : null); } } return AddPinMethod.Invoke(Minimap.instance, array); } catch (Exception ex) { BountyDiagnostics.Warning("TRACK", "Could not add bounty map pin: " + ex.Message); return null; } } internal static void RemovePin(object pin) { if ((Object)(object)Minimap.instance == (Object)null || pin == null) { return; } try { typeof(Minimap).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo method) => method.Name == "RemovePin" && method.GetParameters().Length == 1 && method.GetParameters()[0].ParameterType.IsInstanceOfType(pin))?.Invoke(Minimap.instance, new object[1] { pin }); } catch { } } } internal static class BountyCommands { internal static List Tokenize(string command) { List list = new List(); StringBuilder stringBuilder = new StringBuilder(); bool flag = false; string text = command ?? string.Empty; foreach (char c in text) { if (c == '"') { flag = !flag; } else if (char.IsWhiteSpace(c) && !flag) { if (stringBuilder.Length > 0) { list.Add(stringBuilder.ToString()); stringBuilder.Length = 0; } } else { stringBuilder.Append(c); } } if (stringBuilder.Length > 0) { list.Add(stringBuilder.ToString()); } return list; } internal static void PrintLocal(string line) { BountyDiagnostics.Info("ADMIN", line); bool flag = false; try { MethodInfo methodInfo = AccessTools.Method(typeof(Terminal), "AddString", new Type[1] { typeof(string) }, (Type[])null); Terminal[] array = Resources.FindObjectsOfTypeAll(); foreach (Terminal val in array) { if (!((Object)(object)val == (Object)null) && ((Component)val).gameObject.activeInHierarchy) { methodInfo?.Invoke(val, new object[1] { line }); flag = true; } } } catch { } if (!flag && (Object)(object)Player.m_localPlayer != (Object)null) { ((Character)Player.m_localPlayer).Message((MessageType)2, line, 0, (Sprite)null); } } } [HarmonyPatch] internal static class BountyTerminalCommandPatch { private static MethodBase? TargetMethod() { return AccessTools.Method(typeof(Terminal), "InputText", (Type[])null, (Type[])null); } private static bool Prefix(Terminal __instance) { try { object obj = AccessTools.Field(typeof(Terminal), "m_input")?.GetValue(__instance); PropertyInfo propertyInfo = obj?.GetType().GetProperty("text"); string text = (propertyInfo?.GetValue(obj, null) as string) ?? string.Empty; if (!text.TrimStart(Array.Empty()).StartsWith("bhc", StringComparison.OrdinalIgnoreCase)) { return true; } AccessTools.Method(typeof(Terminal), "AddString", new Type[1] { typeof(string) }, (Type[])null)?.Invoke(__instance, new object[1] { "> " + text }); propertyInfo?.SetValue(obj, string.Empty, null); BountyClient.RequestAdminCommand(text.Trim()); return false; } catch (Exception ex) { BountyDiagnostics.Warning("ADMIN", "Could not process terminal command: " + ex.Message); return true; } } } internal static class BountyDiagnostics { private const string Prefix = "[BHC]"; private static bool _runtimeDebugEnabled; internal static bool DetailedEnabled => BountyHuntercontractPlugin.DetailedLogging != null && BountyHuntercontractPlugin.DetailedLogging.Value == BountyHuntercontractPlugin.Toggle.On; internal static bool RuntimeDebugEnabled => _runtimeDebugEnabled; internal static bool DiagnosticModeEnabled => DetailedEnabled || RuntimeDebugEnabled || BountyCompatibilityDiagnostics.Enabled; internal static bool TrackingEnabled => DiagnosticModeEnabled && BountyHuntercontractPlugin.TrackingLogging != null && BountyHuntercontractPlugin.TrackingLogging.Value == BountyHuntercontractPlugin.Toggle.On; internal static void SetRuntimeDebug(bool enabled) { if (_runtimeDebugEnabled != enabled) { _runtimeDebugEnabled = enabled; BountyHuntercontractPlugin.Log.LogInfo((object)("[BHC][ADMIN] Temporary runtime debug logging=" + (enabled ? "On" : "Off") + ".")); } } internal static void Info(string area, string message) { if (DiagnosticModeEnabled) { BountyHuntercontractPlugin.Log.LogInfo((object)("[BHC][" + area + "] " + message)); } } internal static void Warning(string area, string message) { BountyHuntercontractPlugin.Log.LogWarning((object)("[BHC][" + area + "] " + message)); } internal static void Error(string area, string message) { BountyHuntercontractPlugin.Log.LogError((object)("[BHC][" + area + "] " + message)); } internal static void Error(string area, Exception exception, string message) { BountyHuntercontractPlugin.Log.LogError((object)string.Format("{0}[{1}] {2}\n{3}", "[BHC]", area, message, exception)); } internal static void Detailed(string area, string message) { if (DiagnosticModeEnabled) { BountyHuntercontractPlugin.Log.LogInfo((object)("[BHC][" + area + "] " + message)); } } internal static void Tracking(string area, string message) { if (TrackingEnabled) { BountyHuntercontractPlugin.Log.LogInfo((object)("[BHC][" + area + "] " + message)); } } internal static string LocalIdentity() { //IL_0036: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return "player="; } return $"player={SafeName(localPlayer)} id={localPlayer.GetPlayerID()} pos={Format(((Component)localPlayer).transform.position)}"; } internal static string SafeName(Player player) { try { return string.IsNullOrWhiteSpace(player.GetPlayerName()) ? "" : player.GetPlayerName(); } catch { return ""; } } internal static string Format(Vector3 position) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) return $"({position.x:0.0}, {position.y:0.0}, {position.z:0.0})"; } internal static string Role() { if ((Object)(object)ZNet.instance == (Object)null) { return "no-znet"; } return ZNet.instance.IsServer() ? "server" : "client"; } } internal static class BountyCompatibilityDiagnostics { private sealed class DetectedMod { internal string Label = string.Empty; internal string Guid = string.Empty; internal string Name = string.Empty; internal string Version = string.Empty; internal bool Installed; } private sealed class RpcCounter { internal long Count; internal long Bytes; } private sealed class InventoryTransaction { internal string Kind = string.Empty; internal string Reference = string.Empty; internal string Item = string.Empty; internal int RequestedAmount; internal int BeforeSlots; internal int BeforeUnits; internal int BeforeCoins; internal int ChangedEvents; internal DateTime StartedUtc; } private sealed class DamageTraceState { internal bool Relevant; internal Player? Victim; internal Player? Attacker; internal long VictimId; internal long AttackerId; internal string ContractId = string.Empty; internal bool? BountyPair; internal float HealthBefore; internal float HitDamageBefore; internal Biome Biome; internal bool VictimProtectedBefore; internal bool AttackerProtectedBefore; } private sealed class PvpTraceState { internal bool Relevant; internal bool Requested; internal bool Before; } [HarmonyPatch] private static class CompatibilitySetPvpPatch { private static MethodBase? TargetMethod() { return AccessTools.Method(typeof(Player), "SetPVP", (Type[])null, (Type[])null); } [HarmonyPriority(800)] private static void Prefix(Player __instance, object[] __args, out PvpTraceState __state) { __state = new PvpTraceState(); if (Enabled && !((Object)(object)__instance == (Object)null) && __args != null && __args.Length != 0) { object obj = __args[0]; bool requested = default(bool); int num; if (obj is bool) { requested = (bool)obj; num = 1; } else { num = 0; } if (num != 0) { __state.Relevant = true; __state.Requested = requested; __state.Before = SafePvp(__instance); } } } [HarmonyPriority(0)] private static void Postfix(Player __instance, PvpTraceState __state) { if (__state != null && __state.Relevant) { TracePvp(__instance, __state.Requested, __state.Before); } } } [HarmonyPatch(typeof(Character), "ApplyDamage")] private static class CompatibilityApplyDamagePatch { [HarmonyPriority(800)] private static void Prefix(Character __instance, HitData hit, out DamageTraceState __state) { __state = CaptureDamageState(__instance, hit); } [HarmonyPriority(0)] private static void Postfix(HitData hit, DamageTraceState __state) { TraceDamagePostfix(__state, hit); } } [HarmonyPatch(typeof(Inventory), "Changed")] private static class CompatibilityInventoryChangedPatch { [HarmonyPriority(0)] private static void Postfix(Inventory __instance) { RecordInventoryChanged(__instance); } } [HarmonyPatch(typeof(Player), "OnDeath")] private static class CompatibilityDeathPatch { [HarmonyPriority(0)] private static void Postfix(Player __instance) { TraceDeath(__instance); } } [HarmonyPatch(typeof(Player), "OnSpawned")] private static class CompatibilitySpawnPatch { [HarmonyPriority(0)] private static void Postfix(Player __instance) { TraceSpawn(__instance); } } private static readonly object Sync = new object(); private static readonly Dictionary SentRpc = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary ReceivedRpc = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary LastPvpState = new Dictionary(); private static readonly Dictionary LastPvpLogUtc = new Dictionary(); private static readonly Dictionary Mods = new Dictionary(StringComparer.OrdinalIgnoreCase); private static InventoryTransaction? _inventoryTransaction; private static float _summaryTimer; private static float _patchScanTimer; private static bool _initialized; private static bool _patchOwnersScanned; private static long _pvpCalls; private static long _pvpMismatches; private static long _pvpLogsSuppressed; private static long _playerDamageCalls; private static long _playerDamageNoHealthLoss; private static long _inventoryTransactions; private static long _inventoryFailures; internal static bool Enabled => BountyHuntercontractPlugin.CompatibilityDiagnostics != null && BountyHuntercontractPlugin.CompatibilityDiagnostics.Value == BountyHuntercontractPlugin.Toggle.On; internal static bool CombatEnabled => Enabled && BountyHuntercontractPlugin.CompatibilityCombatLogs != null && BountyHuntercontractPlugin.CompatibilityCombatLogs.Value == BountyHuntercontractPlugin.Toggle.On; internal static bool RpcEnabled => Enabled && BountyHuntercontractPlugin.CompatibilityRpcSummary != null && BountyHuntercontractPlugin.CompatibilityRpcSummary.Value == BountyHuntercontractPlugin.Toggle.On; internal static bool InventoryEnabled => Enabled && BountyHuntercontractPlugin.CompatibilityInventoryLogs != null && BountyHuntercontractPlugin.CompatibilityInventoryLogs.Value == BountyHuntercontractPlugin.Toggle.On; internal static bool CallerEnabled => Enabled && BountyHuntercontractPlugin.CompatibilityCaptureExternalCaller != null && BountyHuntercontractPlugin.CompatibilityCaptureExternalCaller.Value == BountyHuntercontractPlugin.Toggle.On; internal static void Initialize() { if (_initialized) { return; } _initialized = true; DetectKnownMods(); if (!Enabled) { BountyDiagnostics.Info("COMPAT", "Compatibility diagnostics are disabled."); return; } BountyDiagnostics.Info("COMPAT", "Compatibility diagnostics enabled. This mode only observes and logs; it does not change mod behavior."); foreach (DetectedMod item in Mods.Values.OrderBy((DetectedMod mod) => mod.Label, StringComparer.OrdinalIgnoreCase)) { BountyDiagnostics.Info("COMPAT", item.Label + "=" + (item.Installed ? ("DETECTED guid='" + item.Guid + "' name='" + item.Name + "' version='" + item.Version + "'") : "not detected") + "."); } if (IsInstalled("VBNetTweaks") && IsInstalled("VAGhettoNetworking")) { BountyDiagnostics.Error("COMPAT", "NETWORK CONFLICT WARNING: VBNetTweaks and VAGhettoNetworking are both installed. They modify overlapping core network paths; test with only one enabled."); } if (IsInstalled("AzuAntiCheat")) { BountyDiagnostics.Warning("COMPAT", "AzuAntiCheat detected. Ensure every client uses the exact same BountyHunterContract DLL/version/hash and watch reward-delivery diagnostics."); } if (IsInstalled("PvPBiomeDominions")) { BountyDiagnostics.Warning("COMPAT", "PvP Biome Dominions detected. Watch COMPAT-PVP and COMPAT-DAMAGE lines for SetPVP overrides, spawn protection and zero-health-loss hits."); } } internal static void Update(float deltaTime) { if (!Enabled) { return; } if (!_patchOwnersScanned) { _patchScanTimer += deltaTime; if (_patchScanTimer >= 8f) { _patchOwnersScanned = true; ScanPatchOwners(); } } _summaryTimer += deltaTime; float num = 60f; if (BountyHuntercontractPlugin.CompatibilitySummaryIntervalSeconds != null) { num = Mathf.Clamp(BountyHuntercontractPlugin.CompatibilitySummaryIntervalSeconds.Value, 10f, 600f); } if (!(_summaryTimer < num)) { _summaryTimer = 0f; WriteSummary(num); } } internal static void RecordRpcSend(string direction, string method, long peer, ZPackage package) { if (RpcEnabled) { AddRpc(SentRpc, direction + ":" + method, PackageBytes(package)); } } internal static void RecordRpcReceive(string method, long sender, ZPackage package) { if (RpcEnabled) { AddRpc(ReceivedRpc, method, PackageBytes(package)); } } internal static void BeginInventoryTransaction(string kind, string reference, BountyRewardItem reward) { if (InventoryEnabled) { Player localPlayer = Player.m_localPlayer; Inventory inventory = ((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null); _inventoryTransaction = new InventoryTransaction { Kind = (kind ?? string.Empty), Reference = (reference ?? string.Empty), Item = ((reward == null) ? "" : BountyItemCodec.Describe(reward, localized: false)), RequestedAmount = (reward?.Stack ?? 0), BeforeSlots = CountSlots(inventory), BeforeUnits = CountUnits(inventory), BeforeCoins = CountCoins(inventory), StartedUtc = DateTime.UtcNow }; BountyDiagnostics.Info("COMPAT-INVENTORY", "BEGIN kind=" + _inventoryTransaction.Kind + " ref=" + Short(reference) + " item='" + _inventoryTransaction.Item + "' slots=" + _inventoryTransaction.BeforeSlots + " units=" + _inventoryTransaction.BeforeUnits + " coins=" + _inventoryTransaction.BeforeCoins + " ServerCharacters=" + IsInstalled("ServerCharacters") + " AzuAntiCheat=" + IsInstalled("AzuAntiCheat") + "."); } } internal static void EndInventoryTransaction(bool success, string error) { if (InventoryEnabled && _inventoryTransaction != null) { InventoryTransaction inventoryTransaction = _inventoryTransaction; _inventoryTransaction = null; _inventoryTransactions++; if (!success) { _inventoryFailures++; } Player localPlayer = Player.m_localPlayer; Inventory inventory = ((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null); int num = CountSlots(inventory); int num2 = CountUnits(inventory); int num3 = CountCoins(inventory); double totalMilliseconds = (DateTime.UtcNow - inventoryTransaction.StartedUtc).TotalMilliseconds; BountyDiagnostics.Info("COMPAT-INVENTORY", "END kind=" + inventoryTransaction.Kind + " ref=" + Short(inventoryTransaction.Reference) + " success=" + success + " changedEvents=" + inventoryTransaction.ChangedEvents + " slots=" + inventoryTransaction.BeforeSlots + "->" + num + " units=" + inventoryTransaction.BeforeUnits + "->" + num2 + " coins=" + inventoryTransaction.BeforeCoins + "->" + num3 + " elapsedMs=" + totalMilliseconds.ToString("0") + " error='" + (error ?? string.Empty) + "'."); } } internal static void RecordInventoryChanged(Inventory inventory) { if (InventoryEnabled && _inventoryTransaction != null && inventory != null) { Player localPlayer = Player.m_localPlayer; Inventory val = ((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null); if (val == inventory) { _inventoryTransaction.ChangedEvents++; } } } internal static void TraceDeath(Player player) { if (Enabled && !((Object)(object)player == (Object)null)) { bool flag = (Object)(object)player == (Object)(object)Player.m_localPlayer; bool flag2 = flag && BountyClient.IsLocalParticipantInActiveCombat(); BountyDiagnostics.Info("COMPAT-DEATH", "Player.OnDeath player=" + SafePlayer(player) + " local=" + flag + " bountyParticipant=" + flag2 + " ServerCharacters=" + IsInstalled("ServerCharacters") + " PvPBiomeDominions=" + IsInstalled("PvPBiomeDominions") + " EpicMMOSystem=" + IsInstalled("EpicMMOSystem") + CallerSuffix() + "."); } } internal static void TraceSpawn(Player player) { if (Enabled && !((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer)) { BountyDiagnostics.Info("COMPAT-SPAWN", "Player.OnSpawned player=" + SafePlayer(player) + " pvp=" + SafePvp(player) + " pvpBiomeProtection=" + HasPvpBiomeProtection(player) + " bountyParticipant=" + BountyClient.IsLocalParticipantInActiveCombat() + "."); } } internal static void TracePvp(Player player, bool requested, bool before) { if (Enabled && !((Object)(object)player == (Object)null)) { _pvpCalls++; bool flag = SafePvp(player); if (requested != flag) { _pvpMismatches++; } long key = SafeId(player); DateTime utcNow = DateTime.UtcNow; bool value; bool flag2 = !LastPvpState.TryGetValue(key, out value) || value != flag; bool flag3 = requested != flag; bool flag4 = (Object)(object)player == (Object)(object)Player.m_localPlayer && BountyClient.IsLocalParticipantInActiveCombat(); DateTime value2; bool flag5 = !LastPvpLogUtc.TryGetValue(key, out value2) || (utcNow - value2).TotalSeconds >= 5.0; LastPvpState[key] = flag; if (!flag2 && !flag3 && !flag4) { _pvpLogsSuppressed++; return; } if (!flag2 && !flag5) { _pvpLogsSuppressed++; return; } LastPvpLogUtc[key] = utcNow; BountyDiagnostics.Info("COMPAT-PVP", "SetPVP player=" + SafePlayer(player) + " requested=" + requested + " before=" + before + " after=" + flag + " mismatch=" + flag3 + " bountyParticipant=" + flag4 + " biome=" + SafeBiome(player) + " pvpBiomeProtection=" + HasPvpBiomeProtection(player) + CallerSuffix() + "."); } } private static void TraceDamagePostfix(DamageTraceState state, HitData hit) { if (CombatEnabled && state != null && state.Relevant && !((Object)(object)state.Victim == (Object)null) && !((Object)(object)state.Attacker == (Object)null)) { _playerDamageCalls++; float num = SafeHealth((Character?)(object)state.Victim); float num2 = Mathf.Max(0f, state.HealthBefore - num); float num3 = SafeDamage(hit); bool flag = num2 <= 0.001f; if (flag) { _playerDamageNoHealthLoss++; } bool flag2 = HasPvpBiomeProtection(state.Victim); bool flag3 = HasPvpBiomeProtection(state.Attacker); string text = (flag ? "NO_HEALTH_LOSS" : "HEALTH_REDUCED"); BountyDiagnostics.Info("COMPAT-DAMAGE", "PvP ApplyDamage outcome=" + text + " bountyPair=" + (state.BountyPair.HasValue ? state.BountyPair.Value.ToString() : "unknown") + " contract=" + Short(state.ContractId) + " attacker=" + SafePlayer(state.Attacker) + " victim=" + SafePlayer(state.Victim) + " hp=" + state.HealthBefore.ToString("0.0") + "->" + num.ToString("0.0") + " delta=" + num2.ToString("0.0") + " hitDamage=" + state.HitDamageBefore.ToString("0.0") + "->" + num3.ToString("0.0") + " biome=" + ((object)Unsafe.As(ref state.Biome)/*cast due to .constrained prefix*/).ToString() + " protection(attacker/victim)=" + state.AttackerProtectedBefore + "/" + state.VictimProtectedBefore + "->" + flag3 + "/" + flag2 + " mods=[ServerCharacters:" + IsInstalled("ServerCharacters") + ",EpicMMOSystem:" + IsInstalled("EpicMMOSystem") + ",PvPBiomeDominions:" + IsInstalled("PvPBiomeDominions") + ",AzuAntiCheat:" + IsInstalled("AzuAntiCheat") + "]" + CallerSuffix() + "."); } } private static DamageTraceState CaptureDamageState(Character character, HitData hit) { //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) DamageTraceState damageTraceState = new DamageTraceState(); if (CombatEnabled) { Player val = (Player)(object)((character is Player) ? character : null); if (val != null && hit != null) { Character val2 = null; try { val2 = hit.GetAttacker(); } catch { } Player val3 = (Player)(object)((val2 is Player) ? val2 : null); if (val3 == null || (Object)(object)val3 == (Object)(object)val) { return damageTraceState; } long num = SafeId(val3); long num2 = SafeId(val); string contractId; bool? bountyPair = BountyClient.EvaluatePlayerDamage(num, num2, out contractId); damageTraceState.Relevant = true; damageTraceState.Victim = val; damageTraceState.Attacker = val3; damageTraceState.VictimId = num2; damageTraceState.AttackerId = num; damageTraceState.ContractId = contractId ?? string.Empty; damageTraceState.BountyPair = bountyPair; damageTraceState.HealthBefore = SafeHealth((Character?)(object)val); damageTraceState.HitDamageBefore = SafeDamage(hit); damageTraceState.Biome = SafeBiomeValue(val); damageTraceState.VictimProtectedBefore = HasPvpBiomeProtection(val); damageTraceState.AttackerProtectedBefore = HasPvpBiomeProtection(val3); return damageTraceState; } } return damageTraceState; } private static void WriteSummary(float interval) { if (!Enabled) { return; } long num; long num2; long num3; long num4; string text; string text2; lock (Sync) { num = SentRpc.Values.Sum((RpcCounter counter) => counter.Count); num2 = SentRpc.Values.Sum((RpcCounter counter) => counter.Bytes); num3 = ReceivedRpc.Values.Sum((RpcCounter counter) => counter.Count); num4 = ReceivedRpc.Values.Sum((RpcCounter counter) => counter.Bytes); text = TopRpc(SentRpc); text2 = TopRpc(ReceivedRpc); SentRpc.Clear(); ReceivedRpc.Clear(); } BountyDiagnostics.Info("COMPAT-SUMMARY", "interval=" + interval.ToString("0") + "s rpcSent=" + num + " bytes=" + num2 + " top=[" + text + "] rpcReceived=" + num3 + " bytes=" + num4 + " top=[" + text2 + "] pvpCalls=" + _pvpCalls + " pvpMismatches=" + _pvpMismatches + " pvpLogsSuppressed=" + _pvpLogsSuppressed + " playerDamageCalls=" + _playerDamageCalls + " zeroHealthLoss=" + _playerDamageNoHealthLoss + " inventoryTransactions=" + _inventoryTransactions + " inventoryFailures=" + _inventoryFailures + "."); _pvpCalls = 0L; _pvpMismatches = 0L; _pvpLogsSuppressed = 0L; _playerDamageCalls = 0L; _playerDamageNoHealthLoss = 0L; _inventoryTransactions = 0L; _inventoryFailures = 0L; } private static void DetectKnownMods() { Mods.Clear(); Detect("ServerCharacters", "org.bepinex.plugins.servercharacters", "Server Characters", "ServerCharacters"); Detect("EpicMMOSystem", "WackyMole.EpicMMOSystem", "EpicMMOSystem", "WackyEpicMMOSystem"); Detect("VBNetTweaks", "VitByr.VBNetTweaks", "VBNetTweaks"); Detect("PvPBiomeDominions", "Turbero.PvPBiomeDominions", "PvP Biome Dominions", "PvPBiomeDominions"); Detect("VAGhettoNetworking", "VAGhettoNetworking", "GhettoNetworking", "VA Ghetto Networking"); Detect("AzuAntiCheat", "AzuAntiCheat", "Azu Anti Cheat", "AntiCheat"); } private static void Detect(string label, params string[] terms) { DetectedMod detectedMod = new DetectedMod { Label = label }; foreach (KeyValuePair pluginInfo in Chainloader.PluginInfos) { PluginInfo value = pluginInfo.Value; string text = value.Metadata.GUID ?? string.Empty; string text2 = value.Metadata.Name ?? string.Empty; string text3 = ((object)value.Instance)?.GetType().Assembly.GetName().Name ?? string.Empty; string haystack = text + "|" + text2 + "|" + text3; if (!terms.Any((string term) => haystack.IndexOf(term, StringComparison.OrdinalIgnoreCase) >= 0)) { continue; } detectedMod.Installed = true; detectedMod.Guid = text; detectedMod.Name = text2; detectedMod.Version = value.Metadata.Version?.ToString() ?? "unknown"; break; } Mods[label] = detectedMod; } private static bool IsInstalled(string label) { DetectedMod value; return Mods.TryGetValue(label, out value) && value.Installed; } private static void ScanPatchOwners() { if (!Enabled) { return; } BountyDiagnostics.Info("COMPAT-PATCHES", "Scanning Harmony owners on methods shared by the tested mods."); ReportPatches("Player.SetPVP", AccessTools.Method(typeof(Player), "SetPVP", (Type[])null, (Type[])null)); ReportPatches("Character.Damage", AccessTools.Method(typeof(Character), "Damage", new Type[1] { typeof(HitData) }, (Type[])null)); ReportPatches("Character.RPC_Damage", typeof(Character).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo method) => method.Name == "RPC_Damage" && method.GetParameters().Any((ParameterInfo parameter) => parameter.ParameterType == typeof(HitData)))); ReportPatches("Character.ApplyDamage", AccessTools.Method(typeof(Character), "ApplyDamage", (Type[])null, (Type[])null)); ReportPatches("Player.OnDeath", AccessTools.Method(typeof(Player), "OnDeath", (Type[])null, (Type[])null)); ReportPatches("Player.OnSpawned", AccessTools.Method(typeof(Player), "OnSpawned", (Type[])null, (Type[])null)); ReportPatches("Inventory.Changed", AccessTools.Method(typeof(Inventory), "Changed", (Type[])null, (Type[])null)); ReportPatches("ZRoutedRpc.InvokeRoutedRPC", AccessTools.Method(typeof(ZRoutedRpc), "InvokeRoutedRPC", new Type[3] { typeof(long), typeof(string), typeof(object[]) }, (Type[])null)); ReportPatches("ZDOMan.SendZDOs", AccessTools.Method(typeof(ZDOMan), "SendZDOs", (Type[])null, (Type[])null)); } private static void ReportPatches(string label, MethodBase? method) { if (method == null) { BountyDiagnostics.Warning("COMPAT-PATCHES", label + " could not be resolved on this Valheim version."); return; } Patches patchInfo = Harmony.GetPatchInfo(method); if (patchInfo == null) { BountyDiagnostics.Info("COMPAT-PATCHES", label + " has no Harmony patches."); return; } string text = Owners(patchInfo.Prefixes); string text2 = Owners(patchInfo.Postfixes); string text3 = Owners(patchInfo.Transpilers); string text4 = Owners(patchInfo.Finalizers); BountyDiagnostics.Info("COMPAT-PATCHES", label + " prefixes=[" + text + "] postfixes=[" + text2 + "] transpilers=[" + text3 + "] finalizers=[" + text4 + "]."); } private static string Owners(IEnumerable patches) { string[] array = (from patch in patches select patch.owner into owner where !string.IsNullOrWhiteSpace(owner) select owner).Distinct(StringComparer.OrdinalIgnoreCase).OrderBy((string owner) => owner, StringComparer.OrdinalIgnoreCase).ToArray(); return (array.Length == 0) ? "none" : string.Join(",", array); } private static string CallerSuffix() { if (!CallerEnabled) { return string.Empty; } string text = FindExternalCaller(); return string.IsNullOrEmpty(text) ? string.Empty : (" externalCaller=" + text); } private static string FindExternalCaller() { try { StackFrame[] frames = new StackTrace(2, fNeedFileInfo: false).GetFrames(); if (frames == null) { return string.Empty; } Assembly assembly = typeof(BountyCompatibilityDiagnostics).Assembly; StackFrame[] array = frames; foreach (StackFrame stackFrame in array) { MethodBase method = stackFrame.GetMethod(); Type type = method?.DeclaringType; Assembly assembly2 = type?.Assembly; if (!(assembly2 == null) && !(assembly2 == assembly)) { string text = assembly2.GetName().Name ?? string.Empty; if (!(text == "Assembly-CSharp") && !text.StartsWith("Unity", StringComparison.OrdinalIgnoreCase) && !text.StartsWith("System", StringComparison.OrdinalIgnoreCase) && !(text == "mscorlib") && !(text == "0Harmony") && !(text == "BepInEx.Core")) { return text + ":" + type?.FullName + "." + method?.Name; } } } } catch { } return string.Empty; } private static bool HasPvpBiomeProtection(Player player) { if (!IsInstalled("PvPBiomeDominions") || (Object)(object)player == (Object)null) { return false; } try { Type type = (from assembly in AppDomain.CurrentDomain.GetAssemblies() select assembly.GetType("PvPBiomeDominions.PvPManagement.PvPSpawnProtectionTracker", throwOnError: false)).FirstOrDefault((Type type2) => type2 != null); MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "HasProtection", new Type[1] { typeof(Player) }, (Type[])null)); bool flag = default(bool); int num; if (methodInfo != null) { object obj = methodInfo.Invoke(null, new object[1] { player }); if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch { return false; } } private static void AddRpc(Dictionary target, string key, int bytes) { lock (Sync) { if (!target.TryGetValue(key, out RpcCounter value)) { value = (target[key] = new RpcCounter()); } value.Count++; if (bytes > 0) { value.Bytes += bytes; } } } private static string TopRpc(Dictionary source) { return string.Join(", ", (from pair in (from pair in source orderby pair.Value.Bytes descending, pair.Value.Count descending select pair).Take(5) select pair.Key + "=" + pair.Value.Count + "/" + pair.Value.Bytes + "B").ToArray()); } private static int PackageBytes(ZPackage package) { try { return ((package == null) ? ((int?)null) : package.GetArray()?.Length).GetValueOrDefault(); } catch { return 0; } } private static int CountSlots(Inventory? inventory) { try { return (inventory != null) ? inventory.GetAllItems().Count : 0; } catch { return -1; } } private static int CountUnits(Inventory? inventory) { try { return (inventory != null) ? inventory.GetAllItems().Sum((ItemData item) => item?.m_stack ?? 0) : 0; } catch { return -1; } } private static int CountCoins(Inventory? inventory) { try { return (inventory != null) ? (from item in inventory.GetAllItems() where item != null && (Object)(object)item.m_dropPrefab != (Object)null && ((Object)item.m_dropPrefab).name == "Coins" select item).Sum((ItemData item) => item.m_stack) : 0; } catch { return -1; } } private static float SafeDamage(HitData? hit) { try { return (hit != null) ? hit.GetTotalDamage() : 0f; } catch { return 0f; } } private static float SafeHealth(Character? character) { try { return (character != null) ? character.GetHealth() : 0f; } catch { return 0f; } } private static bool SafePvp(Player? player) { try { return (Object)(object)player != (Object)null && ((Character)player).IsPVPEnabled(); } catch { return false; } } private static long SafeId(Player? player) { try { return (player != null) ? player.GetPlayerID() : 0; } catch { return 0L; } } private static string SafePlayer(Player? player) { if ((Object)(object)player == (Object)null) { return ""; } try { return player.GetPlayerName() + "#" + player.GetPlayerID(); } catch { return ""; } } private static string SafeBiome(Player? player) { //IL_0002: 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) return ((object)SafeBiomeValue(player)/*cast due to .constrained prefix*/).ToString(); } private static Biome SafeBiomeValue(Player? player) { //IL_0014: 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_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) try { return (Biome)((player != null) ? ((int)player.GetCurrentBiome()) : 0); } catch { return (Biome)0; } } private static string Short(string? value) { if (string.IsNullOrEmpty(value)) { return ""; } return (value.Length <= 8) ? value : value.Substring(0, 8); } } internal sealed class BountyCompatibilityMonitor : MonoBehaviour { private void Update() { BountyCompatibilityDiagnostics.Update(Time.unscaledDeltaTime); } } internal static class BountyItemCodec { private static readonly FieldInfo? WorldLevelField = AccessTools.Field(typeof(ItemData), "m_worldLevel"); private static readonly FieldInfo? EquippedField = AccessTools.Field(typeof(ItemData), "m_equipped"); private static readonly MethodInfo? IsItemEquipedMethod = AccessTools.Method(typeof(Humanoid), "IsItemEquiped", new Type[1] { typeof(ItemData) }, (Type[])null); private static readonly MethodInfo? RemoveItemAmountMethod = AccessTools.Method(typeof(Inventory), "RemoveItem", new Type[2] { typeof(ItemData), typeof(int) }, (Type[])null); private static readonly MethodInfo? RemoveItemSingleMethod = AccessTools.Method(typeof(Inventory), "RemoveItem", new Type[1] { typeof(ItemData) }, (Type[])null); private static readonly MethodInfo? ItemDropSaveMethod = AccessTools.Method(typeof(ItemDrop), "Save", (Type[])null, (Type[])null); internal static bool IsProhibitedRewardPrefab(string prefabName) { if (string.IsNullOrWhiteSpace(prefabName)) { return true; } string text = BountyHuntercontractPlugin.ProhibitedRewardPrefabs?.Value ?? string.Empty; if (string.IsNullOrWhiteSpace(text)) { return false; } return (from value in text.Split(new char[4] { ',', ';', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries) select value.Trim() into value where value.Length > 0 select value).Any((string value) => string.Equals(value, prefabName, StringComparison.OrdinalIgnoreCase)); } internal static bool IsEquipped(Player player, ItemData item) { if (item == null) { return false; } try { object obj = EquippedField?.GetValue(item); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { return true; } } catch { } try { if ((Object)(object)player != (Object)null && IsItemEquipedMethod != null && IsItemEquipedMethod.Invoke(player, new object[1] { item }) is bool result) { return result; } } catch { } return false; } internal static bool CanUseAsReward(Player player, ItemData item, out string reason) { reason = string.Empty; if ((Object)(object)player == (Object)null || item == null || (Object)(object)item.m_dropPrefab == (Object)null || item.m_stack <= 0) { reason = BountyLocalization.Text("Este objeto no es una recompensa válida.", "This item is not a valid reward."); return false; } string prefabName = Utils.GetPrefabName(item.m_dropPrefab); if (IsProhibitedRewardPrefab(prefabName)) { reason = BountyLocalization.Text("PROHIBIDO: el prefab '" + prefabName + "' está bloqueado por la configuración del servidor.", "PROHIBITED: prefab '" + prefabName + "' is blocked by the server configuration."); return false; } if (IsEquipped(player, item)) { reason = BountyLocalization.Text("EQUIPADO: desequipa este objeto antes de usarlo como recompensa.", "EQUIPPED: unequip this item before using it as a reward."); return false; } return true; } internal static int CountEligibleMatching(Player player, ItemData exemplar) { if ((Object)(object)player == (Object)null || exemplar == null || (Object)(object)exemplar.m_dropPrefab == (Object)null) { return 0; } string prefabName = Utils.GetPrefabName(exemplar.m_dropPrefab); if (IsProhibitedRewardPrefab(prefabName)) { return 0; } BountyRewardItem signature = FromItemData(exemplar, 1); return (from item in ((Humanoid)player).GetInventory().GetAllItems() where item != null && !IsEquipped(player, item) && Matches(item, signature, requireEnoughStack: false) select item).Sum((ItemData item) => Math.Max(0, item.m_stack)); } internal static int CountEligibleMatching(Player player, BountyRewardItem reward) { if ((Object)(object)player == (Object)null || reward == null || !reward.IsValid() || IsProhibitedRewardPrefab(reward.PrefabName)) { return 0; } BountyRewardItem signature = reward.Clone(); signature.Stack = 1; return (from item in ((Humanoid)player).GetInventory().GetAllItems() where item != null && !IsEquipped(player, item) && Matches(item, signature, requireEnoughStack: false) select item).Sum((ItemData item) => Math.Max(0, item.m_stack)); } internal static BountyRewardItem FromItemData(ItemData item, int requestedStack) { if (item == null) { throw new ArgumentNullException("item"); } string prefabName = (((Object)(object)item.m_dropPrefab != (Object)null) ? Utils.GetPrefabName(item.m_dropPrefab) : string.Empty); int stack = Math.Max(1, requestedStack); BountyRewardItem bountyRewardItem = new BountyRewardItem { PrefabName = prefabName, DisplayName = GetLocalizedItemName(item), Stack = stack, Quality = Math.Max(1, item.m_quality), Variant = item.m_variant, Durability = item.m_durability, CrafterId = item.m_crafterID, CrafterName = (item.m_crafterName ?? string.Empty), WorldLevel = GetWorldLevel(item) }; if (item.m_customData != null) { foreach (KeyValuePair customDatum in item.m_customData) { bountyRewardItem.CustomData[customDatum.Key ?? string.Empty] = customDatum.Value ?? string.Empty; } } return bountyRewardItem; } internal static bool ValidateForServer(BountyRewardItem reward, out string error) { error = string.Empty; if (reward == null || !reward.IsValid()) { error = "La recompensa seleccionada no es válida."; return false; } if (reward.Stack > Math.Max(1, BountyHuntercontractPlugin.MaximumRewardItemAmount.Value)) { error = "La cantidad de la recompensa supera el máximo configurado."; return false; } if (IsProhibitedRewardPrefab(reward.PrefabName)) { error = BountyLocalization.Text("El prefab de recompensa '" + reward.PrefabName + "' está prohibido por el servidor.", "Reward prefab '" + reward.PrefabName + "' is prohibited by the server."); return false; } if (reward.CustomData.Any>((KeyValuePair pair) => pair.Key.Length > 1024 || pair.Value.Length > 65535)) { error = "Los datos personalizados de ese objeto son demasiado grandes."; return false; } if ((Object)(object)ObjectDB.instance == (Object)null) { error = "La base de objetos todavía no está preparada."; return false; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(reward.PrefabName); ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent() : null); if ((Object)(object)val == (Object)null) { error = "El servidor no reconoce el objeto de recompensa '" + reward.PrefabName + "'."; return false; } int num = Math.Max(1, val.m_itemData.m_shared.m_maxStackSize); if (num == 1 && reward.Stack != 1) { error = "Ese objeto no es apilable y solo puede depositarse una unidad."; return false; } if (reward.Quality > Math.Max(1, val.m_itemData.m_shared.m_maxQuality)) { error = "La calidad informada para la recompensa no es válida."; return false; } return true; } internal static string Describe(BountyRewardItem reward, bool localized = true) { if (reward == null) { return BountyLocalization.Text("Recompensa desconocida", "Unknown reward"); } string text = (localized ? GetLocalizedRewardName(reward) : ((!string.IsNullOrWhiteSpace(reward.DisplayName)) ? reward.DisplayName : reward.PrefabName)); string text2 = reward.Stack + " x " + text; if (reward.Quality > 1) { text2 = text2 + BountyLocalization.Text(" (calidad ", " (quality ") + reward.Quality + ")"; } return text2; } internal static string GetLocalizedRewardName(BountyRewardItem reward) { if (reward == null) { return BountyLocalization.Text("Recompensa desconocida", "Unknown reward"); } try { ObjectDB instance = ObjectDB.instance; GameObject val = ((instance != null) ? instance.GetItemPrefab(reward.PrefabName) : null); ItemDrop val2 = (((Object)(object)val != (Object)null) ? val.GetComponent() : null); if ((Object)(object)val2 != (Object)null && Localization.instance != null) { return Localization.instance.Localize(val2.m_itemData.m_shared.m_name); } } catch { } return (!string.IsNullOrWhiteSpace(reward.DisplayName)) ? reward.DisplayName : reward.PrefabName; } internal static string GetLocalizedItemName(ItemData item) { if (item == null) { return BountyLocalization.Text("Objeto desconocido", "Unknown item"); } try { return (Localization.instance != null) ? Localization.instance.Localize(item.m_shared.m_name) : item.m_shared.m_name; } catch { return item.m_shared.m_name; } } internal static Sprite? GetItemIcon(ItemData item) { if (item == null) { return null; } try { Sprite icon = item.GetIcon(); if ((Object)(object)icon != (Object)null) { return icon; } } catch { } try { Sprite[] icons = item.m_shared.m_icons; if (icons == null || icons.Length == 0) { return null; } int num = Mathf.Clamp(item.m_variant, 0, icons.Length - 1); return icons[num] ?? icons[0]; } catch { return null; } } internal static Sprite? GetRewardIcon(BountyRewardItem reward) { if (reward == null || string.IsNullOrWhiteSpace(reward.PrefabName)) { return null; } try { ObjectDB instance = ObjectDB.instance; GameObject val = ((instance != null) ? instance.GetItemPrefab(reward.PrefabName) : null); ItemDrop val2 = (((Object)(object)val != (Object)null) ? val.GetComponent() : null); if ((Object)(object)val2 == (Object)null) { return null; } ItemData val3 = val2.m_itemData.Clone(); val3.m_dropPrefab = val; val3.m_variant = reward.Variant; return GetItemIcon(val3); } catch { return null; } } internal static Sprite? GetCoinsIcon() { return GetRewardIcon(BountyRewardItem.Coins(1)); } internal static string BuildItemTooltip(ItemData item, int displayedStack = -1) { if (item == null) { return BountyLocalization.Text("Objeto desconocido", "Unknown item"); } string localizedItemName = GetLocalizedItemName(item); string text = LocalizeSafe(item.m_shared.m_description); int num = ((displayedStack > 0) ? displayedStack : Math.Max(1, item.m_stack)); List list = new List { localizedItemName, BountyLocalization.Text("Cantidad: ", "Amount: ") + num }; if (item.m_quality > 1 || item.m_shared.m_maxQuality > 1) { list.Add(BountyLocalization.Text("Calidad: ", "Quality: ") + Math.Max(1, item.m_quality)); } if (item.m_shared.m_useDurability) { float num2 = Math.Max(0.01f, item.m_shared.m_maxDurability); float num3 = Mathf.Clamp01(item.m_durability / num2) * 100f; list.Add(BountyLocalization.Text("Durabilidad: ", "Durability: ") + Math.Max(0f, item.m_durability).ToString("0") + "/" + num2.ToString("0") + " (" + num3.ToString("0") + "%)"); } list.Add(BountyLocalization.Text("Peso: ", "Weight: ") + (item.m_shared.m_weight * (float)num).ToString("0.0")); if (!string.IsNullOrWhiteSpace(item.m_crafterName)) { list.Add(BountyLocalization.Text("Creado por: ", "Crafted by: ") + item.m_crafterName); } if (!string.IsNullOrWhiteSpace(text) && text != item.m_shared.m_description) { list.Add(string.Empty); list.Add(text); } else if (!string.IsNullOrWhiteSpace(item.m_shared.m_description) && !item.m_shared.m_description.StartsWith("$", StringComparison.Ordinal)) { list.Add(string.Empty); list.Add(item.m_shared.m_description); } return string.Join("\n", list.ToArray()); } internal static string BuildRewardTooltip(BountyRewardItem reward) { if (reward == null) { return BountyLocalization.Text("Recompensa desconocida", "Unknown reward"); } try { ObjectDB instance = ObjectDB.instance; GameObject val = ((instance != null) ? instance.GetItemPrefab(reward.PrefabName) : null); ItemDrop val2 = (((Object)(object)val != (Object)null) ? val.GetComponent() : null); if ((Object)(object)val2 != (Object)null) { ItemData val3 = val2.m_itemData.Clone(); val3.m_dropPrefab = val; val3.m_stack = Math.Max(1, reward.Stack); val3.m_quality = Math.Max(1, reward.Quality); val3.m_variant = reward.Variant; val3.m_durability = reward.Durability; val3.m_crafterID = reward.CrafterId; val3.m_crafterName = reward.CrafterName ?? string.Empty; val3.m_customData = new Dictionary(reward.CustomData); return BuildItemTooltip(val3, reward.Stack); } } catch { } List list = new List { GetLocalizedRewardName(reward), BountyLocalization.Text("Cantidad: ", "Amount: ") + Math.Max(1, reward.Stack) }; if (reward.Quality > 1) { list.Add(BountyLocalization.Text("Calidad: ", "Quality: ") + reward.Quality); } return string.Join("\n", list.ToArray()); } private static string LocalizeSafe(string value) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } try { return (Localization.instance != null) ? Localization.instance.Localize(value) : value; } catch { return value; } } internal static bool Matches(ItemData item, BountyRewardItem reward, bool requireEnoughStack) { if (item == null || reward == null || (Object)(object)item.m_dropPrefab == (Object)null) { return false; } if (!string.Equals(Utils.GetPrefabName(item.m_dropPrefab), reward.PrefabName, StringComparison.OrdinalIgnoreCase)) { return false; } if (requireEnoughStack && item.m_stack < reward.Stack) { return false; } if (item.m_quality != reward.Quality || item.m_variant != reward.Variant) { return false; } if (Math.Abs(item.m_durability - reward.Durability) > 0.1f) { return false; } if (item.m_crafterID != reward.CrafterId) { return false; } if (!string.Equals(item.m_crafterName ?? string.Empty, reward.CrafterName ?? string.Empty, StringComparison.Ordinal)) { return false; } if (GetWorldLevel(item) != reward.WorldLevel) { return false; } Dictionary dictionary = item.m_customData ?? new Dictionary(); if (dictionary.Count != reward.CustomData.Count) { return false; } foreach (KeyValuePair customDatum in reward.CustomData) { if (!dictionary.TryGetValue(customDatum.Key, out var value) || value != customDatum.Value) { return false; } } return true; } internal static int CountMatching(Inventory inventory, ItemData exemplar) { if (inventory == null || exemplar == null) { return 0; } BountyRewardItem signature = FromItemData(exemplar, 1); return (from item in inventory.GetAllItems() where Matches(item, signature, requireEnoughStack: false) select item).Sum((ItemData item) => Math.Max(0, item.m_stack)); } internal static int CountMatching(Inventory inventory, BountyRewardItem reward) { if (inventory == null || reward == null || !reward.IsValid()) { return 0; } BountyRewardItem signature = reward.Clone(); signature.Stack = 1; return (from item in inventory.GetAllItems() where Matches(item, signature, requireEnoughStack: false) select item).Sum((ItemData item) => Math.Max(0, item.m_stack)); } internal static int CountCoins(Inventory inventory) { if (inventory == null) { return 0; } return (from item in inventory.GetAllItems() where item != null && (Object)(object)item.m_dropPrefab != (Object)null && string.Equals(Utils.GetPrefabName(item.m_dropPrefab), "Coins", StringComparison.OrdinalIgnoreCase) select item).Sum((ItemData item) => Math.Max(0, item.m_stack)); } internal static bool TryRemoveCoins(Player player, int amount, out string error) { error = string.Empty; if ((Object)(object)player == (Object)null || amount <= 0) { error = "La cantidad de Coins para retirar no es válida."; return false; } Inventory inventory = ((Humanoid)player).GetInventory(); List list = (from item in inventory.GetAllItems() where item != null && (Object)(object)item.m_dropPrefab != (Object)null && string.Equals(Utils.GetPrefabName(item.m_dropPrefab), "Coins", StringComparison.OrdinalIgnoreCase) select item).ToList(); int num = list.Sum((ItemData item) => Math.Max(0, item.m_stack)); if (num < amount) { error = "Necesitas " + amount + " Coins. Disponibles: " + num + "."; return false; } if (!TryRemoveCandidateAmount(inventory, list, amount, out var removedTotal, out error)) { return false; } BountyDiagnostics.Info("BOND", "Removed Coins amount=" + removedTotal + " availableBefore=" + num + "."); return true; } internal static bool TryRemoveRewardAndBond(Player player, BountyRewardItem reward, int bondCoins, out string error) { error = string.Empty; bondCoins = Math.Max(0, bondCoins); if (bondCoins == 0) { return TryRemoveExactReward(player, reward, out error); } if ((Object)(object)player == (Object)null || reward == null || !reward.IsValid()) { error = BountyLocalization.Text("No se pudo validar la recompensa y la fianza.", "The reward and bond could not be validated."); return false; } if (IsProhibitedRewardPrefab(reward.PrefabName)) { error = BountyLocalization.Text("Ese objeto está prohibido como recompensa por la configuración del servidor.", "That item is prohibited as a reward by the server configuration."); return false; } Inventory inventory = ((Humanoid)player).GetInventory(); if (string.Equals(reward.PrefabName, "Coins", StringComparison.OrdinalIgnoreCase)) { long num = (long)reward.Stack + (long)bondCoins; if (num > int.MaxValue) { error = "La recompensa y la fianza juntas superan el límite permitido."; return false; } if (!TryRemoveCoins(player, (int)num, out error)) { error = "Necesitas " + num + " Coins en total: " + reward.Stack + " de recompensa y " + bondCoins + " de fianza. " + error; return false; } BountyDiagnostics.Info("BOND", "Removed combined coin reward and bond total=" + num + " rewardCoins=" + reward.Stack + " bondCoins=" + bondCoins + "."); return true; } int num2 = CountEligibleMatching(player, reward); int num3 = CountCoins(inventory); if (num2 < reward.Stack) { error = "Ya no tienes suficientes unidades de la recompensa seleccionada. Disponibles: " + num2 + "."; return false; } if (num3 < bondCoins) { error = "Necesitas " + bondCoins + " Coins adicionales como fianza. Disponibles: " + num3 + "."; return false; } if (!TryRemoveExactReward(player, reward, out error)) { return false; } if (TryRemoveCoins(player, bondCoins, out string error2)) { BountyDiagnostics.Info("BOND", "Removed cancellation bond Coins=" + bondCoins + " together with reward='" + Describe(reward, localized: false) + "'."); return true; } GiveReward(player, reward); error = "No se pudo retirar la fianza de " + bondCoins + " Coins. La recompensa fue devuelta. " + error2; BountyDiagnostics.Warning("BOND", error); return false; } internal static bool TryRemoveExactReward(Player player, BountyRewardItem reward, out string error) { error = string.Empty; if ((Object)(object)player == (Object)null || reward == null || !reward.IsValid()) { error = BountyLocalization.Text("No se pudo validar el objeto seleccionado.", "The selected item could not be validated."); return false; } if (IsProhibitedRewardPrefab(reward.PrefabName)) { error = BountyLocalization.Text("Ese objeto está prohibido como recompensa por la configuración del servidor.", "That item is prohibited as a reward by the server configuration."); return false; } Inventory inventory = ((Humanoid)player).GetInventory(); List list = (from candidate in inventory.GetAllItems() where !IsEquipped(player, candidate) && Matches(candidate, reward, requireEnoughStack: false) select candidate).ToList(); int num = list.Sum((ItemData candidate) => Math.Max(0, candidate.m_stack)); if (num < reward.Stack) { error = BountyLocalization.Text("No tienes suficientes unidades no equipadas de ese objeto. Disponibles: ", "You do not have enough unequipped matching units. Available: ") + num + "."; return false; } if (!TryRemoveCandidateAmount(inventory, list, reward.Stack, out var removedTotal, out error)) { if (string.IsNullOrWhiteSpace(error)) { error = "No fue posible retirar exactamente " + Describe(reward) + " del inventario."; } return false; } BountyDiagnostics.Info("REWARD", "Removed deposited item prefab=" + reward.PrefabName + " stack=" + reward.Stack + " quality=" + reward.Quality + " availableBefore=" + num + " removed=" + removedTotal + "."); return true; } private static bool TryRemoveCandidateAmount(Inventory inventory, List candidates, int requestedAmount, out int removedTotal, out string error) { error = string.Empty; removedTotal = 0; int num = requestedAmount; bool flag3 = default(bool); bool flag4 = default(bool); foreach (ItemData candidate in candidates) { if (num <= 0) { break; } int num2 = Math.Min(num, candidate.m_stack); int stack = candidate.m_stack; bool flag = false; bool flag2 = false; try { if (RemoveItemAmountMethod != null) { object obj = RemoveItemAmountMethod.Invoke(inventory, new object[2] { candidate, num2 }); int num3; if (obj is bool) { flag3 = (bool)obj; num3 = ((1 == 0) ? 1 : 0); } else { num3 = 1; } flag2 = (byte)((uint)num3 | (flag3 ? 1u : 0u)) != 0; flag = true; } else if (num2 == candidate.m_stack && RemoveItemSingleMethod != null) { object obj2 = RemoveItemSingleMethod.Invoke(inventory, new object[1] { candidate }); int num4; if (obj2 is bool) { flag4 = (bool)obj2; num4 = ((1 == 0) ? 1 : 0); } else { num4 = 1; } flag2 = (byte)((uint)num4 | (flag4 ? 1u : 0u)) != 0; flag = true; } } catch (Exception exception) { BountyDiagnostics.Error("REWARD", exception, "Inventory removal failed through reflection."); } if (!flag || !flag2) { error = "Esta versión de Valheim no pudo retirar el objeto de forma segura."; return false; } int num5 = (inventory.GetAllItems().Contains(candidate) ? candidate.m_stack : 0); int num6 = stack - num5; removedTotal += num6; num -= num6; if (num6 != num2) { error = "El inventario retiró una cantidad distinta de la solicitada."; return false; } } if (removedTotal != requestedAmount) { error = "No fue posible retirar exactamente la cantidad solicitada."; return false; } return true; } internal static void GiveReward(Player player, BountyRewardItem reward) { //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_011a: 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) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || reward == null || !reward.IsValid() || (Object)(object)ObjectDB.instance == (Object)null) { return; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(reward.PrefabName); ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent() : null); if ((Object)(object)itemPrefab == (Object)null || (Object)(object)val == (Object)null) { BountyDiagnostics.Error("REWARD", "Cannot deliver unknown reward prefab '" + reward.PrefabName + "'."); return; } Inventory inventory = ((Humanoid)player).GetInventory(); int val2 = Math.Max(1, val.m_itemData.m_shared.m_maxStackSize); int num = reward.Stack; int num2 = 0; int num3 = 0; while (num > 0) { int num4 = Math.Min(val2, num); BountyRewardItem bountyRewardItem = reward.Clone(); bountyRewardItem.Stack = num4; ItemData val3 = CreateItemData(bountyRewardItem, itemPrefab, val); if (inventory.AddItem(val3)) { num2 += num4; } else { Vector3 val4 = ((Component)player).transform.position + Vector3.up + ((Component)player).transform.forward * 0.7f; GameObject val5 = Object.Instantiate(itemPrefab, val4, Quaternion.identity); ItemDrop component = val5.GetComponent(); if ((Object)(object)component != (Object)null) { Apply(bountyRewardItem, component.m_itemData, itemPrefab); try { ItemDropSaveMethod?.Invoke(component, null); } catch (Exception ex) { BountyDiagnostics.Detailed("REWARD", "ItemDrop.Save invocation failed: " + ex.Message); } } num3 += num4; } num -= num4; } BountyDiagnostics.Info("REWARD", "Item reward delivery complete reward='" + Describe(reward, localized: false) + "' inventory=" + num2 + " ground=" + num3 + "."); } private static ItemData CreateItemData(BountyRewardItem reward, GameObject prefab, ItemDrop itemDrop) { ItemData val = itemDrop.m_itemData.Clone(); Apply(reward, val, prefab); return val; } private static void Apply(BountyRewardItem reward, ItemData data, GameObject prefab) { data.m_dropPrefab = prefab; data.m_stack = reward.Stack; data.m_quality = reward.Quality; data.m_variant = reward.Variant; data.m_durability = reward.Durability; data.m_crafterID = reward.CrafterId; data.m_crafterName = reward.CrafterName ?? string.Empty; data.m_customData.Clear(); foreach (KeyValuePair customDatum in reward.CustomData) { data.m_customData[customDatum.Key] = customDatum.Value; } SetWorldLevel(data, reward.WorldLevel); } private static int GetWorldLevel(ItemData item) { try { return (WorldLevelField?.GetValue(item) is int num) ? num : 0; } catch { return 0; } } private static void SetWorldLevel(ItemData item, int value) { try { WorldLevelField?.SetValue(item, value); } catch { } } } internal enum BountyStatus { Active, Completed, Cancelled, Expired } internal enum BountyOutcome { None, HunterVictory, TargetVictory, CancelledByCreator, ExpiredWithoutHunters, AdminRemoved } internal sealed class BountyPlayerInfo { internal long PeerId; internal long PlayerId; internal string Name = string.Empty; internal bool Connected; internal Vector3 Position; internal DateTime LastSeenUtc; } internal sealed class BountyHunterInfo { internal long PlayerId; internal string Name = string.Empty; internal bool Eliminated; internal long EliminatedUtcTicks; internal BountyHunterInfo Clone() { return new BountyHunterInfo { PlayerId = PlayerId, Name = Name, Eliminated = Eliminated, EliminatedUtcTicks = EliminatedUtcTicks }; } } internal sealed class BountyRewardItem { internal string PrefabName = string.Empty; internal string DisplayName = string.Empty; internal int Stack = 1; internal int Quality = 1; internal int Variant; internal float Durability; internal long CrafterId; internal string CrafterName = string.Empty; internal int WorldLevel; internal readonly Dictionary CustomData = new Dictionary(); internal bool IsValid() { return !string.IsNullOrWhiteSpace(PrefabName) && Stack > 0 && Quality > 0 && PrefabName.Length <= 256 && DisplayName.Length <= 512 && CrafterName.Length <= 512 && CustomData.Count <= 256; } internal BountyRewardItem Clone() { BountyRewardItem bountyRewardItem = new BountyRewardItem { PrefabName = PrefabName, DisplayName = DisplayName, Stack = Stack, Quality = Quality, Variant = Variant, Durability = Durability, CrafterId = CrafterId, CrafterName = CrafterName, WorldLevel = WorldLevel }; foreach (KeyValuePair customDatum in CustomData) { bountyRewardItem.CustomData[customDatum.Key] = customDatum.Value; } return bountyRewardItem; } internal static BountyRewardItem Coins(int amount) { return new BountyRewardItem { PrefabName = "Coins", DisplayName = "Coins", Stack = Math.Max(1, amount), Quality = 1 }; } } internal sealed class BountyContract { internal string ContractId = Guid.NewGuid().ToString("N"); internal long CreatorPlayerId; internal string CreatorName = string.Empty; internal long TargetPlayerId; internal string TargetName = string.Empty; internal BountyRewardItem Reward = BountyRewardItem.Coins(1); internal int BondCoins; internal bool RewardClaimed; internal bool BondClaimed; internal int MaximumHunters; internal int DurationMinutes; internal readonly List Hunters = new List(); internal BountyStatus Status = BountyStatus.Active; internal BountyOutcome Outcome = BountyOutcome.None; internal long CreatedUtcTicks; internal long ExpiresUtcTicks; internal long RemainingDurationTicks; internal long LastDurationUpdateUtcTicks; internal bool DurationTimerRunning; internal long FinishedUtcTicks; internal long WinnerPlayerId; internal string WinnerName = string.Empty; internal TimeSpan RemainingDuration { get { long value = Math.Max(0L, RemainingDurationTicks); return TimeSpan.FromTicks(value); } } internal int ActiveHunterCount => Hunters.Count((BountyHunterInfo hunter) => !hunter.Eliminated); internal int EliminatedHunterCount => Hunters.Count((BountyHunterInfo hunter) => hunter.Eliminated); internal bool IsExpired(DateTime utcNow) { return DurationMinutes > 0 && RemainingDurationTicks <= 0; } internal bool HasHunter(long playerId) { return Hunters.Exists((BountyHunterInfo hunter) => hunter.PlayerId == playerId); } internal bool HasActiveHunter(long playerId) { return Hunters.Exists((BountyHunterInfo hunter) => hunter.PlayerId == playerId && !hunter.Eliminated); } internal BountyHunterInfo? GetHunter(long playerId) { return Hunters.FirstOrDefault((BountyHunterInfo hunter) => hunter.PlayerId == playerId); } internal bool IsParticipant(long playerId) { return TargetPlayerId == playerId || HasHunter(playerId); } internal bool IsActiveParticipant(long playerId) { return TargetPlayerId == playerId || HasActiveHunter(playerId); } internal bool IsOpposingPair(long firstPlayerId, long secondPlayerId) { if (TargetPlayerId == firstPlayerId && HasActiveHunter(secondPlayerId)) { return true; } if (TargetPlayerId == secondPlayerId && HasActiveHunter(firstPlayerId)) { return true; } return false; } } internal sealed class PendingCreation { internal string Token = Guid.NewGuid().ToString("N"); internal long RequestPeerId; internal long CreatorPlayerId; internal string CreatorName = string.Empty; internal long TargetPlayerId; internal string TargetName = string.Empty; internal BountyRewardItem Reward = BountyRewardItem.Coins(1); internal int BondCoins; internal int MaximumHunters; internal int DurationMinutes; internal DateTime CreatedUtc; } internal sealed class PendingReward { internal string RewardId = Guid.NewGuid().ToString("N"); internal long PlayerId; internal string PlayerName = string.Empty; internal BountyRewardItem Reward = BountyRewardItem.Coins(1); internal string Reason = string.Empty; } internal sealed class LastValidHit { internal string ContractId = string.Empty; internal long AttackerPlayerId; internal long VictimPlayerId; internal DateTime ServerReceivedUtc; } internal sealed class TrackingEntry { internal long PlayerId; internal string Name = string.Empty; internal Vector3 Position; internal string Role = string.Empty; } internal sealed class BountyPlayerStats { internal long PlayerId; internal string LastKnownName = string.Empty; internal int ContractsCreated; internal int ContractsAccepted; internal int HunterVictories; internal int HunterDeaths; internal int TimesTargeted; internal int TargetVictories; internal int TargetDeaths; internal int HuntersEliminated; internal int RewardsClaimed; internal int TotalVictories => HunterVictories + TargetVictories; internal BountyPlayerStats Clone() { return (BountyPlayerStats)MemberwiseClone(); } } internal sealed class BountyGlobalStats { internal int TotalContractsCreated; internal int TotalContractsCompleted; internal int TotalContractsCancelled; internal int TotalContractsExpired; internal int HunterVictories; internal int TargetVictories; internal int HuntersEliminated; internal int RewardsClaimed; internal BountyGlobalStats Clone() { return (BountyGlobalStats)MemberwiseClone(); } } internal sealed class BountyHistoryEntry { internal string HistoryId = Guid.NewGuid().ToString("N"); internal string ContractId = string.Empty; internal long CreatorPlayerId; internal string CreatorName = string.Empty; internal long TargetPlayerId; internal string TargetName = string.Empty; internal long WinnerPlayerId; internal string WinnerName = string.Empty; internal BountyOutcome Outcome; internal BountyRewardItem Reward = BountyRewardItem.Coins(1); internal int BondCoins; internal int MaximumHunters; internal int AcceptedHunters; internal int EliminatedHunters; internal int DurationMinutes; internal long CreatedUtcTicks; internal long FinishedUtcTicks; } internal static class BountyNetwork { internal const string RpcHello = "BHC_Hello"; internal const string RpcRequestState = "BHC_RequestState"; internal const string RpcRequestHistory = "BHC_RequestHistory"; internal const string RpcState = "BHC_State"; internal const string RpcHistory = "BHC_History"; internal const string RpcCreateIntent = "BHC_CreateIntent"; internal const string RpcReserveReward = "BHC_ReserveReward"; internal const string RpcReserveConfirmed = "BHC_ReserveConfirmed"; internal const string RpcAcceptContract = "BHC_AcceptContract"; internal const string RpcLeaveContract = "BHC_LeaveContract"; internal const string RpcCancelContract = "BHC_CancelContract"; internal const string RpcPosition = "BHC_Position"; internal const string RpcTracking = "BHC_Tracking"; internal const string RpcCombatHit = "BHC_CombatHit"; internal const string RpcDeath = "BHC_Death"; internal const string RpcClaimReward = "BHC_ClaimReward"; internal const string RpcClaimBond = "BHC_ClaimBond"; internal const string RpcGrantReward = "BHC_GrantReward"; internal const string RpcStatus = "BHC_Status"; internal const string RpcAnnouncement = "BHC_Announcement"; internal const string RpcPersonalAnnouncement = "BHC_PersonalAnnouncement"; internal const string RpcAdminCommand = "BHC_AdminCommand"; internal const string RpcAdminResult = "BHC_AdminResult"; private static ZRoutedRpc? _registeredInstance; private static int _registrationGeneration; internal static int RegistrationGeneration => _registrationGeneration; internal static bool EnsureRegistered() { if (ZRoutedRpc.instance == null) { return false; } if (_registeredInstance == ZRoutedRpc.instance) { return true; } _registeredInstance = ZRoutedRpc.instance; _registrationGeneration++; Register("BHC_Hello", BountyServer.RpcHello); Register("BHC_RequestState", BountyServer.RpcRequestState); Register("BHC_RequestHistory", BountyServer.RpcRequestHistory); Register("BHC_CreateIntent", BountyServer.RpcCreateIntent); Register("BHC_ReserveConfirmed", BountyServer.RpcReserveConfirmed); Register("BHC_AcceptContract", BountyServer.RpcAcceptContract); Register("BHC_LeaveContract", BountyServer.RpcLeaveContract); Register("BHC_CancelContract", BountyServer.RpcCancelContract); Register("BHC_Position", BountyServer.RpcPosition); Register("BHC_CombatHit", BountyServer.RpcCombatHit); Register("BHC_Death", BountyServer.RpcDeath); Register("BHC_ClaimReward", BountyServer.RpcClaimReward); Register("BHC_ClaimBond", BountyServer.RpcClaimBond); Register("BHC_AdminCommand", BountyServer.RpcAdminCommand); Register("BHC_State", BountyClient.RpcState); Register("BHC_History", BountyClient.RpcHistory); Register("BHC_ReserveReward", BountyClient.RpcReserveReward); Register("BHC_Tracking", BountyClient.RpcTracking); Register("BHC_GrantReward", BountyClient.RpcGrantReward); Register("BHC_Status", BountyClient.RpcStatus); Register("BHC_Announcement", BountyClient.RpcAnnouncement); Register("BHC_PersonalAnnouncement", BountyClient.RpcPersonalAnnouncement); Register("BHC_AdminResult", BountyClient.RpcAdminResult); BountyDiagnostics.Info("RPC", "Registered 23 routed RPC handlers. generation=" + _registrationGeneration + " role=" + BountyDiagnostics.Role() + " serverPeer=" + ZRoutedRpc.instance.GetServerPeerID() + "."); return true; } private static void Register(string name, Action action) { Action action2 = delegate(long sender, ZPackage package) { BountyCompatibilityDiagnostics.RecordRpcReceive(name, sender, package); action(sender, package); }; ZRoutedRpc.instance.Register(name, action2); BountyDiagnostics.Detailed("RPC", "Handler registered: " + name + " -> " + action.Method.DeclaringType?.Name + "." + action.Method.Name + "."); } internal static void SendToServer(string method, ZPackage package) { if (ZRoutedRpc.instance == null) { BountyDiagnostics.Warning("RPC", "Cannot send " + method + " to server: ZRoutedRpc.instance is null."); return; } LogSend("client->server", method, ZRoutedRpc.instance.GetServerPeerID(), package); ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), method, new object[1] { package }); } internal static void SendToEverybody(string method, ZPackage package) { if (ZRoutedRpc.instance == null) { BountyDiagnostics.Warning("RPC", "Cannot broadcast " + method + ": ZRoutedRpc.instance is null."); return; } LogSend("server->everybody", method, ZRoutedRpc.Everybody, package); ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, method, new object[1] { package }); } internal static bool IsFromServer(long sender) { if ((Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null) { return false; } bool flag = ZNet.instance.IsServer() || sender == ZRoutedRpc.instance.GetServerPeerID(); if (!flag) { BountyDiagnostics.Warning("RPC", "Rejected packet from non-server sender=" + sender + " expected=" + ZRoutedRpc.instance.GetServerPeerID() + "."); } return flag; } internal static void SendToPeer(long peerId, string method, ZPackage package) { if (ZRoutedRpc.instance == null) { BountyDiagnostics.Warning("RPC", "Cannot send " + method + " to peer " + peerId + ": ZRoutedRpc.instance is null."); } else { LogSend("server->peer", method, peerId, package); ZRoutedRpc.instance.InvokeRoutedRPC(peerId, method, new object[1] { package }); } } private static void LogSend(string direction, string method, long peer, ZPackage package) { BountyCompatibilityDiagnostics.RecordRpcSend(direction, method, peer, package); string message = direction + " method=" + method + " peer=" + peer + "."; if (method == "BHC_Position" || method == "BHC_Tracking") { BountyDiagnostics.Tracking("RPC", message); } else { BountyDiagnostics.Detailed("RPC", message); } } } [HarmonyPatch] internal static class PlayerSetPvpPatch { private static MethodBase? TargetMethod() { return AccessTools.Method(typeof(Player), "SetPVP", (Type[])null, (Type[])null); } [HarmonyPriority(800)] [HarmonyBefore(new string[] { "Turbero.PvPBiomeDominions" })] private static bool Prefix(Player __instance, object[] __args) { bool flag = BountyClient.ShouldBlockPvpDisable(__instance, __args); if (flag) { BountyClient.LogBlockedPvpDisable(); } return !flag; } } [HarmonyPatch] internal static class CharacterDamagePatch { private static MethodBase? TargetMethod() { return AccessTools.Method(typeof(Character), "Damage", new Type[1] { typeof(HitData) }, (Type[])null); } [HarmonyPriority(800)] private static bool Prefix(Character __instance, HitData hit) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if ((Object)(object)val == (Object)null || hit == null) { return true; } Character val2 = null; try { object? obj = AccessTools.Method(typeof(HitData), "GetAttacker", (Type[])null, (Type[])null)?.Invoke(hit, null); val2 = (Character)((obj is Character) ? obj : null); } catch { return true; } Player val3 = (Player)(object)((val2 is Player) ? val2 : null); if ((Object)(object)val3 == (Object)null || (Object)(object)val3 == (Object)(object)val) { return true; } long playerID = val3.GetPlayerID(); long playerID2 = val.GetPlayerID(); string contractId; bool? flag = BountyClient.EvaluatePlayerDamage(playerID, playerID2, out contractId); if (flag == false) { BountyDiagnostics.Detailed("COMBAT", "Blocked player damage attacker=" + playerID + " victim=" + playerID2 + " because they are not an opposing bounty pair."); return false; } if (flag == true) { BountyDiagnostics.Detailed("COMBAT", "Allowed outgoing bounty damage contract=" + contractId + " attacker=" + playerID + " victim=" + playerID2 + "."); } return true; } } [HarmonyPatch] internal static class CharacterRpcDamagePatch { private static MethodBase? _targetMethod; private static bool Prepare() { _targetMethod = typeof(Character).GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo candidate) => candidate.Name == "RPC_Damage" && candidate.GetParameters().Any((ParameterInfo parameter) => parameter.ParameterType == typeof(HitData))); if (_targetMethod == null) { BountyDiagnostics.Error("COMBAT", "Could not resolve Character.RPC_Damage(HitData). The update-loop death fallback remains active, but hunter kill credit may be unavailable on this Valheim version."); return false; } BountyDiagnostics.Info("COMBAT", "Resolved victim damage hook: " + _targetMethod?.ToString() + "."); return true; } private static MethodBase? TargetMethod() { return _targetMethod; } private static void Prefix(Character __instance, object[] __args) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if ((Object)(object)val == (Object)null || (Object)(object)val != (Object)(object)Player.m_localPlayer) { return; } HitData val2 = __args.OfType().FirstOrDefault(); if (val2 == null) { return; } Character val3 = null; try { object? obj = AccessTools.Method(typeof(HitData), "GetAttacker", (Type[])null, (Type[])null)?.Invoke(val2, null); val3 = (Character)((obj is Character) ? obj : null); } catch (Exception ex) { BountyDiagnostics.Warning("COMBAT", "Could not resolve attacker from RPC_Damage: " + ex.Message); return; } Player val4 = (Player)(object)((val3 is Player) ? val3 : null); if (!((Object)(object)val4 == (Object)null) && !((Object)(object)val4 == (Object)(object)val)) { long playerID = val4.GetPlayerID(); long playerID2 = val.GetPlayerID(); if (BountyClient.EvaluatePlayerDamage(playerID, playerID2, out string contractId) == true) { BountyDiagnostics.Info("COMBAT", "Victim received bounty hit through RPC_Damage contract=" + contractId + " attacker=" + val4.GetPlayerName() + "#" + playerID + " victim=" + val.GetPlayerName() + "#" + playerID2 + "."); BountyClient.ReportValidHit(contractId, playerID, playerID2); } } } } [HarmonyPatch] internal static class PlayerDeathPatch { private static MethodBase? TargetMethod() { return AccessTools.Method(typeof(Player), "OnDeath", (Type[])null, (Type[])null); } private static void Postfix(Player __instance) { if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer) { BountyDiagnostics.Info("DEATH", "Local Player.OnDeath fired for " + BountyDiagnostics.LocalIdentity() + "."); } BountyClient.ReportLocalDeath(__instance); } } [HarmonyPatch] internal static class PlayerTakeInputPatch { private static MethodBase? TargetMethod() { return AccessTools.Method(typeof(Player), "TakeInput", (Type[])null, (Type[])null); } private static bool Prefix(ref bool __result) { if (!BountyUI.IsVisible) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(Player), "PlayerAttackInput")] internal static class BountyBoardAttackInputPatch { private static bool Prefix() { return !BountyUI.IsVisible; } } [HarmonyPatch(typeof(GameCamera), "UpdateCamera")] internal static class BountyBoardCameraInputPatch { private static bool Prefix() { return !BountyUI.IsVisible; } } [HarmonyPatch(typeof(Player), "Update")] [HarmonyAfter(new string[] { "Turbero.PvPBiomeDominions" })] internal static class BountyAbsolutePvpPlayerUpdatePatch { [HarmonyPriority(0)] private static void Postfix(Player __instance) { if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer) { BountyClient.ReassertForcedPvp("Player.Update postfix"); } } } [HarmonyPatch(typeof(InventoryGui), "UpdateCharacterStats")] [HarmonyAfter(new string[] { "Turbero.PvPBiomeDominions" })] internal static class BountyAbsolutePvpInventoryGuiPatch { [HarmonyPriority(0)] private static void Postfix() { BountyClient.ReassertForcedPvp("InventoryGui.UpdateCharacterStats postfix"); } } [HarmonyPatch] internal static class PvPBiomeDominionsBountyOverridePatch { private static Type? _targetType; private static MethodBase? _targetMethod; private static bool Prepare() { _targetType = (from assembly in AppDomain.CurrentDomain.GetAssemblies() select assembly.GetType("PvPBiomeDominions.PvPManagement.PlayerUpdatePatch", throwOnError: false)).FirstOrDefault((Type type) => type != null); _targetMethod = ((_targetType == null) ? null : AccessTools.Method(_targetType, "SetupPvP", (Type[])null, (Type[])null)); if (_targetMethod == null) { BountyDiagnostics.Detailed("PVE-OVERRIDE", "PvP Biome Dominions SetupPvP was not found; generic absolute PvP enforcement remains active."); return false; } BountyDiagnostics.Info("PVE-OVERRIDE", "PvP Biome Dominions detected; biome/ward PvE requests will be converted to PvP for active bounty participants."); return true; } private static MethodBase? TargetMethod() { return _targetMethod; } [HarmonyPriority(800)] private static void Prefix(ref bool isPvPOn) { if (!isPvPOn && BountyClient.IsLocalParticipantInActiveCombat()) { isPvPOn = true; BountyClient.LogPveRuleConvertedToPvp("PvPBiomeDominions.SetupPvP"); } } } [HarmonyPatch(typeof(ZNetScene), "Awake")] internal static class ZNetSceneBountyBoardPatch { private static void Postfix(ZNetScene __instance) { BountyBoardRegistrar.OnZNetSceneReady(__instance); } } [HarmonyPatch(typeof(ObjectDB), "Awake")] internal static class ObjectDbBountyBoardPatch { private static void Postfix() { BountyBoardRegistrar.OnObjectDbReady(); } } [HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")] internal static class ObjectDbCopyBountyBoardPatch { private static void Postfix() { BountyBoardRegistrar.OnObjectDbReady(); } } internal static class BountyPersistence { private const int FileMagic = 1112032049; internal static void Save(long worldId, IEnumerable contracts, IEnumerable rewards, IEnumerable playerStats, BountyGlobalStats globalStats, IEnumerable history) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown if (worldId == 0) { return; } try { string text = Path.Combine(Paths.ConfigPath, "BountyHunterContract"); Directory.CreateDirectory(text); string text2 = Path.Combine(text, "world_" + worldId + ".dat"); string text3 = text2 + ".tmp"; ZPackage val = new ZPackage(); val.Write(1112032049); val.Write(6); List list = new List(contracts); val.Write(list.Count); foreach (BountyContract item in list) { BountySerialization.WriteContract(val, item); } List list2 = new List(rewards); val.Write(list2.Count); foreach (PendingReward item2 in list2) { BountySerialization.WriteReward(val, item2); } List list3 = new List(playerStats); val.Write(list3.Count); foreach (BountyPlayerStats item3 in list3) { BountySerialization.WritePlayerStats(val, item3); } BountySerialization.WriteGlobalStats(val, globalStats); List list4 = history.OrderByDescending((BountyHistoryEntry entry) => entry.FinishedUtcTicks).Take(Math.Max(10, BountyHuntercontractPlugin.MaximumHistoryEntries.Value)).ToList(); val.Write(list4.Count); foreach (BountyHistoryEntry item4 in list4) { BountySerialization.WriteHistory(val, item4); } File.WriteAllBytes(text3, val.GetArray()); if (File.Exists(text2)) { File.Delete(text2); } File.Move(text3, text2); BountyDiagnostics.Detailed("SAVE", "Persistence file written path='" + text2 + "' contracts=" + list.Count + " rewards=" + list2.Count + " stats=" + list3.Count + " history=" + list4.Count + " bytes=" + new FileInfo(text2).Length + "."); } catch (Exception exception) { BountyDiagnostics.Error("SAVE", exception, "Could not save bounty data."); } } internal static void Load(long worldId, List contracts, List rewards, Dictionary playerStats, BountyGlobalStats globalStats, List history) { //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Expected O, but got Unknown contracts.Clear(); rewards.Clear(); playerStats.Clear(); history.Clear(); ResetGlobalStats(globalStats); if (worldId == 0) { return; } try { string text = Path.Combine(Paths.ConfigPath, "BountyHunterContract", "world_" + worldId + ".dat"); if (!File.Exists(text)) { BountyDiagnostics.Info("SAVE", "No persistence file exists for world=" + worldId + " path='" + text + "'. Starting empty."); return; } ZPackage val = new ZPackage(File.ReadAllBytes(text)); if (val.ReadInt() != 1112032049) { BountyDiagnostics.Warning("SAVE", "Ignored bounty data with an invalid header."); return; } int num = val.ReadInt(); if (num > 6) { BountyDiagnostics.Warning("SAVE", "Bounty data was created by a newer mod version=" + num + " supported=" + 6 + "."); return; } int num2 = val.ReadInt(); for (int i = 0; i < num2; i++) { BountyContract bountyContract = BountySerialization.ReadContract(val, num); if (bountyContract.Status == BountyStatus.Active || bountyContract.Status == BountyStatus.Completed) { contracts.Add(bountyContract); } } int num3 = val.ReadInt(); for (int j = 0; j < num3; j++) { rewards.Add(BountySerialization.ReadReward(val, num)); } if (num >= 4) { int num4 = val.ReadInt(); for (int k = 0; k < num4; k++) { BountyPlayerStats bountyPlayerStats = BountySerialization.ReadPlayerStats(val); if (bountyPlayerStats.PlayerId != 0) { playerStats[bountyPlayerStats.PlayerId] = bountyPlayerStats; } } CopyGlobalStats(BountySerialization.ReadGlobalStats(val), globalStats); int num5 = val.ReadInt(); for (int l = 0; l < num5; l++) { history.Add(BountySerialization.ReadHistory(val, num)); } } else { globalStats.TotalContractsCreated = contracts.Count; foreach (BountyContract contract in contracts) { GetOrCreateStats(playerStats, contract.CreatorPlayerId, contract.CreatorName).ContractsCreated++; GetOrCreateStats(playerStats, contract.TargetPlayerId, contract.TargetName).TimesTargeted++; foreach (BountyHunterInfo hunter in contract.Hunters) { GetOrCreateStats(playerStats, hunter.PlayerId, hunter.Name).ContractsAccepted++; } } } BountyDiagnostics.Info("SAVE", "Persistence loaded path='" + text + "' version=" + num + " activeOrClaimableContracts=" + contracts.Count + " pendingRewards=" + rewards.Count + " playerStats=" + playerStats.Count + " history=" + history.Count + "."); } catch (Exception exception) { BountyDiagnostics.Error("SAVE", exception, "Could not load bounty data."); } } private static BountyPlayerStats GetOrCreateStats(Dictionary stats, long playerId, string name) { if (!stats.TryGetValue(playerId, out BountyPlayerStats value)) { value = (stats[playerId] = new BountyPlayerStats { PlayerId = playerId, LastKnownName = (name ?? string.Empty) }); } else if (!string.IsNullOrWhiteSpace(name)) { value.LastKnownName = name; } return value; } private static void ResetGlobalStats(BountyGlobalStats stats) { CopyGlobalStats(new BountyGlobalStats(), stats); } private static void CopyGlobalStats(BountyGlobalStats source, BountyGlobalStats destination) { destination.TotalContractsCreated = source.TotalContractsCreated; destination.TotalContractsCompleted = source.TotalContractsCompleted; destination.TotalContractsCancelled = source.TotalContractsCancelled; destination.TotalContractsExpired = source.TotalContractsExpired; destination.HunterVictories = source.HunterVictories; destination.TargetVictories = source.TargetVictories; destination.HuntersEliminated = source.HuntersEliminated; destination.RewardsClaimed = source.RewardsClaimed; } } internal sealed class BountyRuntime : MonoBehaviour { private float _boardDiagnosticTimer; private void Update() { if (!BountyNetwork.EnsureRegistered()) { return; } BountyServer.Update(Time.unscaledDeltaTime); BountyClient.Update(Time.unscaledDeltaTime); if (BountyDiagnostics.DiagnosticModeEnabled) { _boardDiagnosticTimer += Time.unscaledDeltaTime; if (_boardDiagnosticTimer >= 5f) { _boardDiagnosticTimer = 0f; BountyBoardRegistrar.OnObjectDbReady(); } } else { _boardDiagnosticTimer = 0f; } } } internal static class BountySerialization { internal const int DataVersion = 6; internal static void WriteRewardItem(ZPackage package, BountyRewardItem reward) { package.Write(reward.PrefabName ?? string.Empty); package.Write(reward.DisplayName ?? string.Empty); package.Write(reward.Stack); package.Write(reward.Quality); package.Write(reward.Variant); package.Write(reward.Durability); package.Write(reward.CrafterId); package.Write(reward.CrafterName ?? string.Empty); package.Write(reward.WorldLevel); package.Write(reward.CustomData.Count); foreach (KeyValuePair customDatum in reward.CustomData) { package.Write(customDatum.Key ?? string.Empty); package.Write(customDatum.Value ?? string.Empty); } } internal static BountyRewardItem ReadRewardItem(ZPackage package) { BountyRewardItem bountyRewardItem = new BountyRewardItem { PrefabName = package.ReadString(), DisplayName = package.ReadString(), Stack = package.ReadInt(), Quality = package.ReadInt(), Variant = package.ReadInt(), Durability = package.ReadSingle(), CrafterId = package.ReadLong(), CrafterName = package.ReadString(), WorldLevel = package.ReadInt() }; int num = package.ReadInt(); for (int i = 0; i < num; i++) { bountyRewardItem.CustomData[package.ReadString()] = package.ReadString(); } return bountyRewardItem; } internal static void WriteContract(ZPackage package, BountyContract contract) { package.Write(contract.ContractId); package.Write(contract.CreatorPlayerId); package.Write(contract.CreatorName ?? string.Empty); package.Write(contract.TargetPlayerId); package.Write(contract.TargetName ?? string.Empty); WriteRewardItem(package, contract.Reward); package.Write(contract.BondCoins); package.Write(contract.RewardClaimed); package.Write(contract.BondClaimed); package.Write(contract.MaximumHunters); package.Write(contract.DurationMinutes); package.Write((int)contract.Status); package.Write((int)contract.Outcome); package.Write(contract.CreatedUtcTicks); package.Write(contract.ExpiresUtcTicks); package.Write(contract.FinishedUtcTicks); package.Write(contract.RemainingDurationTicks); package.Write(contract.LastDurationUpdateUtcTicks); package.Write(contract.DurationTimerRunning); package.Write(contract.WinnerPlayerId); package.Write(contract.WinnerName ?? string.Empty); package.Write(contract.Hunters.Count); foreach (BountyHunterInfo hunter in contract.Hunters) { package.Write(hunter.PlayerId); package.Write(hunter.Name ?? string.Empty); package.Write(hunter.Eliminated); package.Write(hunter.EliminatedUtcTicks); } } internal static BountyContract ReadContract(ZPackage package, int dataVersion = 6) { BountyContract bountyContract = new BountyContract { ContractId = package.ReadString(), CreatorPlayerId = package.ReadLong(), CreatorName = package.ReadString(), TargetPlayerId = package.ReadLong(), TargetName = package.ReadString() }; bountyContract.Reward = ((dataVersion <= 1) ? BountyRewardItem.Coins(package.ReadInt()) : ReadRewardItem(package)); if (dataVersion >= 6) { bountyContract.BondCoins = package.ReadInt(); bountyContract.RewardClaimed = package.ReadBool(); bountyContract.BondClaimed = package.ReadBool(); } else { bountyContract.BondCoins = 0; bountyContract.RewardClaimed = false; bountyContract.BondClaimed = true; } bountyContract.MaximumHunters = package.ReadInt(); if (dataVersion >= 4) { bountyContract.DurationMinutes = package.ReadInt(); } bountyContract.Status = (BountyStatus)package.ReadInt(); if (dataVersion >= 4) { bountyContract.Outcome = (BountyOutcome)package.ReadInt(); } bountyContract.CreatedUtcTicks = package.ReadLong(); bountyContract.ExpiresUtcTicks = package.ReadLong(); if (dataVersion >= 4) { bountyContract.FinishedUtcTicks = package.ReadLong(); } if (dataVersion >= 5) { bountyContract.RemainingDurationTicks = package.ReadLong(); bountyContract.LastDurationUpdateUtcTicks = package.ReadLong(); bountyContract.DurationTimerRunning = package.ReadBool(); } bountyContract.WinnerPlayerId = package.ReadLong(); bountyContract.WinnerName = package.ReadString(); if (bountyContract.DurationMinutes <= 0 && bountyContract.CreatedUtcTicks > 0 && bountyContract.ExpiresUtcTicks > bountyContract.CreatedUtcTicks) { bountyContract.DurationMinutes = (int)((bountyContract.ExpiresUtcTicks - bountyContract.CreatedUtcTicks) / 600000000); } if (dataVersion < 5 && bountyContract.DurationMinutes > 0) { long val = ((bountyContract.ExpiresUtcTicks > 0) ? (bountyContract.ExpiresUtcTicks - DateTime.UtcNow.Ticks) : TimeSpan.FromMinutes(bountyContract.DurationMinutes).Ticks); bountyContract.RemainingDurationTicks = Math.Max(0L, val); bountyContract.LastDurationUpdateUtcTicks = DateTime.UtcNow.Ticks; bountyContract.DurationTimerRunning = false; } if (dataVersion < 4 && bountyContract.Status == BountyStatus.Completed) { bountyContract.Outcome = BountyOutcome.HunterVictory; } int num = package.ReadInt(); for (int i = 0; i < num; i++) { BountyHunterInfo bountyHunterInfo = new BountyHunterInfo { PlayerId = package.ReadLong(), Name = package.ReadString() }; if (dataVersion >= 4) { bountyHunterInfo.Eliminated = package.ReadBool(); bountyHunterInfo.EliminatedUtcTicks = package.ReadLong(); } bountyContract.Hunters.Add(bountyHunterInfo); } return bountyContract; } internal static void WriteReward(ZPackage package, PendingReward reward) { package.Write(reward.RewardId); package.Write(reward.PlayerId); package.Write(reward.PlayerName ?? string.Empty); WriteRewardItem(package, reward.Reward); package.Write(reward.Reason ?? string.Empty); } internal static PendingReward ReadReward(ZPackage package, int dataVersion = 6) { PendingReward pendingReward = new PendingReward { RewardId = package.ReadString(), PlayerId = package.ReadLong(), PlayerName = package.ReadString() }; pendingReward.Reward = ((dataVersion <= 1) ? BountyRewardItem.Coins(package.ReadInt()) : ReadRewardItem(package)); pendingReward.Reason = package.ReadString(); return pendingReward; } internal static void WritePlayerStats(ZPackage package, BountyPlayerStats stats) { package.Write(stats.PlayerId); package.Write(stats.LastKnownName ?? string.Empty); package.Write(stats.ContractsCreated); package.Write(stats.ContractsAccepted); package.Write(stats.HunterVictories); package.Write(stats.HunterDeaths); package.Write(stats.TimesTargeted); package.Write(stats.TargetVictories); package.Write(stats.TargetDeaths); package.Write(stats.HuntersEliminated); package.Write(stats.RewardsClaimed); } internal static BountyPlayerStats ReadPlayerStats(ZPackage package) { return new BountyPlayerStats { PlayerId = package.ReadLong(), LastKnownName = package.ReadString(), ContractsCreated = package.ReadInt(), ContractsAccepted = package.ReadInt(), HunterVictories = package.ReadInt(), HunterDeaths = package.ReadInt(), TimesTargeted = package.ReadInt(), TargetVictories = package.ReadInt(), TargetDeaths = package.ReadInt(), HuntersEliminated = package.ReadInt(), RewardsClaimed = package.ReadInt() }; } internal static void WriteGlobalStats(ZPackage package, BountyGlobalStats stats) { package.Write(stats.TotalContractsCreated); package.Write(stats.TotalContractsCompleted); package.Write(stats.TotalContractsCancelled); package.Write(stats.TotalContractsExpired); package.Write(stats.HunterVictories); package.Write(stats.TargetVictories); package.Write(stats.HuntersEliminated); package.Write(stats.RewardsClaimed); } internal static BountyGlobalStats ReadGlobalStats(ZPackage package) { return new BountyGlobalStats { TotalContractsCreated = package.ReadInt(), TotalContractsCompleted = package.ReadInt(), TotalContractsCancelled = package.ReadInt(), TotalContractsExpired = package.ReadInt(), HunterVictories = package.ReadInt(), TargetVictories = package.ReadInt(), HuntersEliminated = package.ReadInt(), RewardsClaimed = package.ReadInt() }; } internal static void WriteHistory(ZPackage package, BountyHistoryEntry entry) { package.Write(entry.HistoryId ?? string.Empty); package.Write(entry.ContractId ?? string.Empty); package.Write(entry.CreatorPlayerId); package.Write(entry.CreatorName ?? string.Empty); package.Write(entry.TargetPlayerId); package.Write(entry.TargetName ?? string.Empty); package.Write(entry.WinnerPlayerId); package.Write(entry.WinnerName ?? string.Empty); package.Write((int)entry.Outcome); WriteRewardItem(package, entry.Reward); package.Write(entry.BondCoins); package.Write(entry.MaximumHunters); package.Write(entry.AcceptedHunters); package.Write(entry.EliminatedHunters); package.Write(entry.DurationMinutes); package.Write(entry.CreatedUtcTicks); package.Write(entry.FinishedUtcTicks); } internal static BountyHistoryEntry ReadHistory(ZPackage package, int dataVersion = 6) { BountyHistoryEntry bountyHistoryEntry = new BountyHistoryEntry { HistoryId = package.ReadString(), ContractId = package.ReadString(), CreatorPlayerId = package.ReadLong(), CreatorName = package.ReadString(), TargetPlayerId = package.ReadLong(), TargetName = package.ReadString(), WinnerPlayerId = package.ReadLong(), WinnerName = package.ReadString(), Outcome = (BountyOutcome)package.ReadInt(), Reward = ReadRewardItem(package) }; if (dataVersion >= 6) { bountyHistoryEntry.BondCoins = package.ReadInt(); } bountyHistoryEntry.MaximumHunters = package.ReadInt(); bountyHistoryEntry.AcceptedHunters = package.ReadInt(); bountyHistoryEntry.EliminatedHunters = package.ReadInt(); bountyHistoryEntry.DurationMinutes = package.ReadInt(); bountyHistoryEntry.CreatedUtcTicks = package.ReadLong(); bountyHistoryEntry.FinishedUtcTicks = package.ReadLong(); return bountyHistoryEntry; } internal static ZPackage BuildStatePackage(IEnumerable players, IEnumerable contracts, IEnumerable stats, BountyGlobalStats globalStats, IEnumerable history) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown ZPackage val = new ZPackage(); List list = new List(players); List list2 = new List(contracts); List list3 = new List(stats); List list4 = new List(history); val.Write(6); val.Write(list.Count); foreach (BountyPlayerInfo item in list) { val.Write(item.PlayerId); val.Write(item.Name ?? string.Empty); val.Write(item.Connected); } val.Write(list2.Count); foreach (BountyContract item2 in list2) { WriteContract(val, item2); } val.Write(list3.Count); foreach (BountyPlayerStats item3 in list3) { WritePlayerStats(val, item3); } WriteGlobalStats(val, globalStats); val.Write(list4.Count); foreach (BountyHistoryEntry item4 in list4) { WriteHistory(val, item4); } return val; } } internal static class BountyServer { private static readonly Dictionary PlayersByPeer = new Dictionary(); private static readonly Dictionary PlayersById = new Dictionary(); private static readonly List Contracts = new List(); private static readonly Dictionary PendingCreations = new Dictionary(); private static readonly List PendingRewards = new List(); private static readonly Dictionary LastHits = new Dictionary(); private static readonly Dictionary PlayerStatsById = new Dictionary(); private static readonly BountyGlobalStats GlobalStats = new BountyGlobalStats(); private static readonly List History = new List(); private const int HistoryTransferLimit = 75; private static bool _loaded; private static long _worldId; private static float _stateTimer; private static float _trackingTimer; private static float _maintenanceTimer; private static float _timerSaveTimer; private static bool _contractTimerDirty; internal static void Update(float deltaTime) { if (!IsServer()) { return; } EnsureLoaded(); if (!_loaded) { return; } _stateTimer += deltaTime; _trackingTimer += deltaTime; _maintenanceTimer += deltaTime; _timerSaveTimer += deltaTime; if (_maintenanceTimer >= 2f) { _maintenanceTimer = 0f; MaintainPlayers(); UpdateContractTimers(); ExpirePendingCreations(); ExpireContracts(); DeliverPendingRewards(); } if (_timerSaveTimer >= 30f) { _timerSaveTimer = 0f; if (_contractTimerDirty) { _contractTimerDirty = false; Save(); } } if (_stateTimer >= 30f) { _stateTimer = 0f; BroadcastState(); } float num = Mathf.Max(1f, BountyHuntercontractPlugin.TrackingIntervalSeconds.Value); if (_trackingTimer >= num) { _trackingTimer = 0f; SendTrackingUpdates(); } } internal static void Shutdown() { BountyDiagnostics.Info("SERVER", "Shutdown requested. loaded=" + _loaded + " world=" + _worldId + " players=" + PlayersById.Count + " contracts=" + Contracts.Count + " pendingRewards=" + PendingRewards.Count + " stats=" + PlayerStatsById.Count + " history=" + History.Count + "."); if (_loaded && _worldId != 0) { Save(); } _loaded = false; _worldId = 0L; PlayersByPeer.Clear(); PlayersById.Clear(); Contracts.Clear(); PendingCreations.Clear(); PendingRewards.Clear(); LastHits.Clear(); PlayerStatsById.Clear(); History.Clear(); ResetGlobalStats(); } private static bool IsServer() { return (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer(); } private static void EnsureLoaded() { if ((Object)(object)ZNet.instance == (Object)null) { return; } try { long worldUID = ZNet.instance.GetWorldUID(); if (worldUID != 0 && (!_loaded || worldUID != _worldId)) { if (_loaded && _worldId != 0) { Save(); } _loaded = false; _worldId = worldUID; PlayersByPeer.Clear(); PlayersById.Clear(); Contracts.Clear(); PendingCreations.Clear(); PendingRewards.Clear(); LastHits.Clear(); PlayerStatsById.Clear(); History.Clear(); ResetGlobalStats(); BountyPersistence.Load(_worldId, Contracts, PendingRewards, PlayerStatsById, GlobalStats, History); PrepareLoadedContractTimers(); _loaded = true; BountyDiagnostics.Info("SERVER", "World bounty state ready world=" + _worldId + " contracts=" + Contracts.Count + " pendingRewards=" + PendingRewards.Count + " stats=" + PlayerStatsById.Count + " history=" + History.Count + "."); } } catch (Exception ex) { BountyDiagnostics.Detailed("SERVER", "World data not ready: " + ex.Message); } } internal static void RpcHello(long sender, ZPackage package) { if (!IsServer()) { return; } EnsureLoaded(); if (!_loaded) { return; } long playerId = package.ReadLong(); string text = package.ReadString(); BountyDiagnostics.Detailed("SERVER", "RPC Hello received peer=" + sender + " player=" + text + "#" + playerId + "."); if (!RegisterOrRefreshPlayer(sender, playerId, text, out BountyPlayerInfo player, out bool stateChanged)) { BountyDiagnostics.Warning("PLAYER", "Ignored invalid bounty hello from peer " + sender + "."); return; } if (stateChanged) { BountyDiagnostics.Info("PLAYER", "Player online " + player.Name + "#" + player.PlayerId + " peer=" + sender + "."); BroadcastState(); } DeliverPendingRewards(player.PlayerId); } internal static void RpcRequestState(long sender, ZPackage package) { if (!IsServer()) { return; } EnsureLoaded(); if (_loaded) { long playerId = package.ReadLong(); string text = package.ReadString(); BountyDiagnostics.Detailed("SERVER", "RPC RequestState received peer=" + sender + " player=" + text + "#" + playerId + "."); RegisterOrRefreshPlayer(sender, playerId, text, out BountyPlayerInfo _, out bool stateChanged); if (stateChanged) { BroadcastState(); } else { SendState(sender); } } } internal static void RpcRequestHistory(long sender, ZPackage package) { if (!IsServer()) { return; } EnsureLoaded(); if (!_loaded) { return; } long playerId = package.ReadLong(); string text = package.ReadString(); BountyDiagnostics.Detailed("SERVER", "RPC RequestHistory received peer=" + sender + " player=" + text + "#" + playerId + "."); if (RegisterOrRefreshPlayer(sender, playerId, text, out BountyPlayerInfo _, out bool stateChanged)) { if (stateChanged) { BroadcastState(); } SendHistory(sender); } } internal static void RpcCreateIntent(long sender, ZPackage package) { //IL_0456: Unknown result type (might be due to invalid IL or missing references) //IL_045d: Expected O, but got Unknown if (!IsServer()) { return; } EnsureLoaded(); if (!_loaded) { SendStatus(sender, success: false, "El mundo todavía no está listo. Inténtalo nuevamente."); return; } long playerId = package.ReadLong(); string text = package.ReadString(); long targetPlayerId = package.ReadLong(); BountyRewardItem bountyRewardItem = BountySerialization.ReadRewardItem(package); int num = package.ReadInt(); int durationMinutes = package.ReadInt(); int bondCoins = Math.Max(0, BountyHuntercontractPlugin.CancellationBondCoins.Value); BountyDiagnostics.Info("SERVER", "RPC CreateIntent peer=" + sender + " creator=" + text + "#" + playerId + " targetId=" + targetPlayerId + " reward='" + BountyItemCodec.Describe(bountyRewardItem, localized: false) + "' maxHunters=" + num + " durationMinutes=" + durationMinutes + " bondCoins=" + bondCoins + "."); if (!RegisterOrRefreshPlayer(sender, playerId, text, out BountyPlayerInfo player, out bool _)) { SendStatus(sender, success: false, "No fue posible identificar al creador del contrato."); return; } if (!PlayersById.TryGetValue(targetPlayerId, out BountyPlayerInfo value) || !value.Connected) { SendStatus(sender, success: false, "El jugador objetivo ya no está conectado."); return; } if (player.PlayerId == targetPlayerId) { SendStatus(sender, success: false, "No puedes publicar un contrato contra ti mismo."); return; } if (!BountyItemCodec.ValidateForServer(bountyRewardItem, out string error)) { SendStatus(sender, success: false, error); BountyDiagnostics.Warning("REWARD", "Rejected creation reward from " + player.Name + ": " + error + " reward='" + BountyItemCodec.Describe(bountyRewardItem, localized: false) + "'."); return; } if (num < 1 || num > 5) { SendStatus(sender, success: false, "La cantidad de cazadores debe estar entre 1 y 5."); return; } if (!ValidateDuration(durationMinutes, out string error2)) { SendStatus(sender, success: false, error2); return; } int num2 = Math.Max(1, BountyHuntercontractPlugin.MaximumOpenContractsPerCreator.Value); if (CountOpenContractsForCreator(player.PlayerId, includePending: true) >= num2) { SendStatus(sender, success: false, "Ya tienes " + num2 + " contratos abiertos. Debes esperar, cobrar o pedir a un administrador que elimine alguno."); return; } if (Contracts.Any((BountyContract contract) => contract.Status == BountyStatus.Active && contract.TargetPlayerId == targetPlayerId)) { SendStatus(sender, success: false, "Ese jugador ya tiene un contrato activo."); return; } if (PendingCreations.Values.Any((PendingCreation pending) => pending.TargetPlayerId == targetPlayerId)) { SendStatus(sender, success: false, "Ya se está publicando un contrato contra ese jugador."); return; } PendingCreation pendingCreation = new PendingCreation { RequestPeerId = sender, CreatorPlayerId = player.PlayerId, CreatorName = player.Name, TargetPlayerId = value.PlayerId, TargetName = value.Name, Reward = bountyRewardItem.Clone(), BondCoins = bondCoins, MaximumHunters = num, DurationMinutes = durationMinutes, CreatedUtc = DateTime.UtcNow }; PendingCreations[pendingCreation.Token] = pendingCreation; BountyDiagnostics.Info("CONTRACT", "Creation reserved token=" + pendingCreation.Token + " creator=" + player.Name + " target=" + value.Name + " reward='" + BountyItemCodec.Describe(bountyRewardItem, localized: false) + "' maxHunters=" + num + " durationMinutes=" + durationMinutes + " bondCoins=" + bondCoins + "."); ZPackage val = new ZPackage(); val.Write(pendingCreation.Token); BountySerialization.WriteRewardItem(val, pendingCreation.Reward); val.Write(pendingCreation.BondCoins); BountyNetwork.SendToPeer(sender, "BHC_ReserveReward", val); } internal static void RpcReserveConfirmed(long sender, ZPackage package) { if (!IsServer()) { return; } EnsureLoaded(); if (!_loaded) { return; } long playerId = package.ReadLong(); string text = package.ReadString(); string text2 = package.ReadString(); bool flag = package.ReadBool(); string text3 = package.ReadString(); BountyDiagnostics.Info("SERVER", "RPC ReserveConfirmed peer=" + sender + " player=" + text + "#" + playerId + " token=" + text2 + " removed=" + flag + " error='" + text3 + "'."); if (!RegisterOrRefreshPlayer(sender, playerId, text, out BountyPlayerInfo player, out bool _)) { SendStatus(sender, success: false, "No fue posible confirmar el depósito de la recompensa."); return; } if (!PendingCreations.TryGetValue(text2, out PendingCreation creation) || creation.RequestPeerId != sender || creation.CreatorPlayerId != player.PlayerId) { SendStatus(sender, success: false, "La solicitud de publicación expiró."); return; } PendingCreations.Remove(text2); if (!flag) { SendStatus(sender, success: false, string.IsNullOrWhiteSpace(text3) ? "No fue posible retirar del inventario el objeto seleccionado como recompensa." : text3); return; } int num = Math.Max(1, BountyHuntercontractPlugin.MaximumOpenContractsPerCreator.Value); if (!PlayersById.TryGetValue(creation.TargetPlayerId, out BountyPlayerInfo value) || !value.Connected || CountOpenContractsForCreator(creation.CreatorPlayerId, includePending: false) >= num || Contracts.Any((BountyContract contract) => contract.Status == BountyStatus.Active && contract.TargetPlayerId == creation.TargetPlayerId)) { AddPendingReward(creation.CreatorPlayerId, creation.CreatorName, creation.Reward, "Devolución de contrato no publicado"); AddPendingCoins(creation.CreatorPlayerId, creation.CreatorName, creation.BondCoins, "Devolución de fianza de contrato no publicado"); SendStatus(sender, success: false, "El contrato ya no pudo publicarse; la recompensa y la fianza fueron devueltas."); Save(); DeliverPendingRewards(creation.CreatorPlayerId); return; } DateTime utcNow = DateTime.UtcNow; BountyContract bountyContract = new BountyContract { CreatorPlayerId = creation.CreatorPlayerId, CreatorName = creation.CreatorName, TargetPlayerId = creation.TargetPlayerId, TargetName = creation.TargetName, Reward = creation.Reward.Clone(), BondCoins = creation.BondCoins, RewardClaimed = false, BondClaimed = (creation.BondCoins <= 0), MaximumHunters = creation.MaximumHunters, DurationMinutes = creation.DurationMinutes, CreatedUtcTicks = utcNow.Ticks, ExpiresUtcTicks = 0L, RemainingDurationTicks = GetInitialDurationTicks(creation.DurationMinutes), LastDurationUpdateUtcTicks = utcNow.Ticks, DurationTimerRunning = (creation.DurationMinutes > 0 && !IsFiveSecondTestMode()) }; Contracts.Add(bountyContract); GetOrCreateStats(bountyContract.CreatorPlayerId, bountyContract.CreatorName).ContractsCreated++; GetOrCreateStats(bountyContract.TargetPlayerId, bountyContract.TargetName).TimesTargeted++; GlobalStats.TotalContractsCreated++; Save(); BountyDiagnostics.Info("CONTRACT", "Published contract=" + bountyContract.ContractId + " target=" + bountyContract.TargetName + "#" + bountyContract.TargetPlayerId + " creator=" + bountyContract.CreatorName + "#" + bountyContract.CreatorPlayerId + " reward='" + BountyItemCodec.Describe(bountyContract.Reward, localized: false) + "' maxHunters=" + bountyContract.MaximumHunters + " durationMinutes=" + bountyContract.DurationMinutes + " effectiveRemaining=" + FormatRemainingTicks(bountyContract.RemainingDurationTicks) + " bondCoins=" + bountyContract.BondCoins + "."); BroadcastState(); BroadcastContractAnnouncement(bountyContract); BountyDiscordWebhook.NotifyPublished(bountyContract, FormatEffectiveDuration(bountyContract)); SendStatus(sender, success: true, "Contrato publicado correctamente. Duración efectiva: " + FormatEffectiveDuration(bountyContract) + " de tiempo real acumulado mientras el objetivo esté conectado. Fianza: " + bountyContract.BondCoins + " Coins."); } internal static void RpcAcceptContract(long sender, ZPackage package) { if (!IsServer()) { return; } EnsureLoaded(); if (!_loaded) { SendStatus(sender, success: false, "El mundo todavía no está listo. Inténtalo nuevamente."); return; } long playerId = package.ReadLong(); string text = package.ReadString(); string contractId = package.ReadString(); BountyDiagnostics.Info("SERVER", "RPC AcceptContract peer=" + sender + " hunter=" + text + "#" + playerId + " contract=" + contractId + "."); if (!RegisterOrRefreshPlayer(sender, playerId, text, out BountyPlayerInfo player, out bool _)) { SendStatus(sender, success: false, "No fue posible identificar al cazador."); return; } BountyContract bountyContract = Contracts.FirstOrDefault((BountyContract item) => item.ContractId == contractId && item.Status == BountyStatus.Active); if (bountyContract == null) { SendStatus(sender, success: false, "Ese contrato ya no está disponible."); return; } if (player.PlayerId == bountyContract.TargetPlayerId || player.PlayerId == bountyContract.CreatorPlayerId) { SendStatus(sender, success: false, "No puedes aceptar este contrato."); return; } if (bountyContract.HasHunter(player.PlayerId)) { SendStatus(sender, success: false, bountyContract.HasActiveHunter(player.PlayerId) ? "Ya aceptaste este contrato." : "El objetivo ya te eliminó de este contrato y no puedes volver a aceptarlo."); return; } if (OccupiedHunterSlots(bountyContract) >= bountyContract.MaximumHunters) { SendStatus(sender, success: false, "El contrato ya alcanzó su máximo de cazadores."); return; } bool flag = bountyContract.Hunters.Count == 0; bountyContract.Hunters.Add(new BountyHunterInfo { PlayerId = player.PlayerId, Name = player.Name }); if (flag && IsFiveSecondTestMode() && bountyContract.DurationMinutes > 0) { bountyContract.RemainingDurationTicks = GetInitialDurationTicks(bountyContract.DurationMinutes); bountyContract.LastDurationUpdateUtcTicks = DateTime.UtcNow.Ticks; bountyContract.DurationTimerRunning = PlayersById.TryGetValue(bountyContract.TargetPlayerId, out BountyPlayerInfo value) && value.Connected; BountyDiagnostics.Info("TIMER", "5-second test timer armed after first hunter accepted contract=" + bountyContract.ContractId + " remaining=" + FormatRemainingTicks(bountyContract.RemainingDurationTicks) + "."); } GetOrCreateStats(player.PlayerId, player.Name).ContractsAccepted++; Save(); BountyDiagnostics.Info("CONTRACT", "Accepted contract=" + bountyContract.ContractId + " hunter=" + player.Name + "#" + player.PlayerId + " huntersNow=" + bountyContract.ActiveHunterCount + "/" + bountyContract.MaximumHunters + "."); BroadcastState(); SendStatus(sender, success: true, "Contrato aceptado. El objetivo ya puede rastrearte y atacarte."); SendPersonalAnnouncement(bountyContract.TargetPlayerId, "NUEVO CAZADOR", player.Name + " aceptó el contrato contra ti. Cazadores activos: " + bountyContract.ActiveHunterCount + "/" + bountyContract.MaximumHunters + "."); } internal static void RpcLeaveContract(long sender, ZPackage package) { if (!IsServer()) { return; } EnsureLoaded(); if (!_loaded) { return; } long playerId = package.ReadLong(); string playerName = package.ReadString(); string contractId = package.ReadString(); if (!RegisterOrRefreshPlayer(sender, playerId, playerName, out BountyPlayerInfo player, out bool _)) { SendStatus(sender, success: false, "No fue posible identificar al cazador."); return; } BountyContract bountyContract = Contracts.FirstOrDefault((BountyContract item) => item.ContractId == contractId && item.Status == BountyStatus.Active); if (bountyContract == null) { SendStatus(sender, success: false, "Ese contrato ya no está activo."); return; } BountyHunterInfo hunter = bountyContract.GetHunter(player.PlayerId); if (hunter == null || hunter.Eliminated) { SendStatus(sender, success: false, (hunter == null) ? "No formas parte de ese contrato." : "Ya fuiste eliminado del contrato."); return; } bountyContract.Hunters.Remove(hunter); LastHits.Remove(bountyContract.ContractId); Save(); BountyDiagnostics.Info("SERVER", "Hunter " + player.Name + " left contract=" + bountyContract.ContractId + ". huntersNow=" + bountyContract.ActiveHunterCount + "."); BroadcastState(); SendStatus(sender, success: true, "Abandonaste el contrato."); } internal static void RpcCancelContract(long sender, ZPackage package) { if (!IsServer()) { return; } EnsureLoaded(); if (!_loaded) { return; } long playerId = package.ReadLong(); string playerName = package.ReadString(); string contractId = package.ReadString(); if (!RegisterOrRefreshPlayer(sender, playerId, playerName, out BountyPlayerInfo player, out bool _)) { SendStatus(sender, success: false, "No fue posible identificar al creador."); return; } BountyContract bountyContract = Contracts.FirstOrDefault((BountyContract item) => item.ContractId == contractId && item.Status == BountyStatus.Active); if (bountyContract == null || bountyContract.CreatorPlayerId != player.PlayerId) { SendStatus(sender, success: false, "No puedes cancelar ese contrato."); return; } bool flag = bountyContract.Hunters.Count > 0; bountyContract.Status = BountyStatus.Cancelled; bountyContract.Outcome = BountyOutcome.CancelledByCreator; bountyContract.FinishedUtcTicks = DateTime.UtcNow.Ticks; bountyContract.RewardClaimed = true; bountyContract.BondClaimed = true; Contracts.Remove(bountyContract); LastHits.Remove(bountyContract.ContractId); GlobalStats.TotalContractsCancelled++; RecordHistory(bountyContract); AddPendingReward(bountyContract.CreatorPlayerId, bountyContract.CreatorName, bountyContract.Reward, "Devolución de recompensa por contrato cancelado"); if (flag) { DistributeCancellationBond(bountyContract); } else { AddPendingCoins(bountyContract.CreatorPlayerId, bountyContract.CreatorName, bountyContract.BondCoins, "Devolución de fianza por contrato cancelado sin cazadores"); } Save(); BountyDiagnostics.Info("SERVER", "Contract cancelled id=" + bountyContract.ContractId + " rewardRefund='" + BountyItemCodec.Describe(bountyContract.Reward, localized: false) + "' creator=" + bountyContract.CreatorName + " acceptedHunters=" + bountyContract.Hunters.Count + " bondCoins=" + bountyContract.BondCoins + " bondResult=" + (flag ? "distributed" : "refunded") + "."); BountyDiscordWebhook.NotifyCancelled(bountyContract, flag); BroadcastState(); DeliverPendingRewards(); SendStatus(sender, success: true, flag ? ("Contrato cancelado. Recuperaste la recompensa; la fianza de " + bountyContract.BondCoins + " Coins fue repartida entre los cazadores aceptados.") : "Contrato cancelado. Recuperaste la recompensa y la fianza completas."); } internal static void RpcAdminCommand(long sender, ZPackage package) { if (!IsServer()) { return; } EnsureLoaded(); if (_loaded) { long playerId = package.ReadLong(); string playerName = package.ReadString(); string text = package.ReadString(); if (!RegisterOrRefreshPlayer(sender, playerId, playerName, out BountyPlayerInfo player, out bool _)) { SendAdminResult(sender, "No fue posible identificar al administrador."); } else if (!IsAdministrator(sender)) { SendAdminResult(sender, "No tienes permisos de administrador para usar comandos BHC."); BountyDiagnostics.Warning("ADMIN", "Rejected admin command from " + player.Name + "#" + player.PlayerId + ": " + text); } else { List list = ExecuteAdminCommand(text); SendAdminResult(sender, list.ToArray()); } } } private static List ExecuteAdminCommand(string command) { List list = BountyCommands.Tokenize(command); if (list.Count > 0 && list[0].Equals("bhc", StringComparison.OrdinalIgnoreCase)) { list.RemoveAt(0); } string text = ((list.Count == 0) ? "help" : list[0].ToLowerInvariant()); if (list.Count > 0) { list.RemoveAt(0); } switch (text) { case "help": return new List { "BHC: bhc list [active|completed|all]", "BHC: bhc debug [on|off|status]", "BHC: bhc remove [auto|creator|winner|target|destroy]", "BHC: bhc remove-target \"Nombre\" [modo]", "BHC: bhc remove-creator \"Nombre\" [modo]", "BHC: bhc clear-active|clear-completed|clear-all [modo]", "Modo auto: resuelve la recompensa pendiente y devuelve cualquier fianza pendiente al creador." }; case "list": { string text3 = ((list.Count > 0) ? list[0].ToLowerInvariant() : "all"); IEnumerable source = Contracts; if (text3 == "active") { source = source.Where((BountyContract item) => item.Status == BountyStatus.Active); } else if (text3 == "completed") { source = source.Where((BountyContract item) => item.Status == BountyStatus.Completed); } List list4 = (from item in source orderby item.Status, item.TargetName select item).ToList(); List list5 = new List { "BHC contratos: " + list4.Count }; foreach (BountyContract item in list4.Take(30)) { list5.Add(item.ContractId.Substring(0, Math.Min(8, item.ContractId.Length)) + " | " + item.Status.ToString() + " | creador=" + item.CreatorName + " | objetivo=" + item.TargetName + " | premio=" + BountyItemCodec.Describe(item.Reward, localized: false) + " | fianza=" + item.BondCoins + (item.BondClaimed ? " cobrada" : " pendiente")); } if (list4.Count > 30) { list5.Add("... y " + (list4.Count - 30) + " contratos más."); } return list5; } case "debug": switch ((list.Count > 0) ? list[0].ToLowerInvariant() : "status") { case "on": BountyDiagnostics.SetRuntimeDebug(enabled: true); return new List { "BHC: debug temporal activado en el servidor. Los logs Info/Detailed volverán a mostrarse hasta reiniciar o usar 'bhc debug off'." }; case "off": BountyDiagnostics.SetRuntimeDebug(enabled: false); return new List { "BHC: debug temporal desactivado. Solo permanecerán visibles advertencias y errores." }; case "status": { List list3 = new List(); list3.Add("BHC: debug temporal=" + (BountyDiagnostics.RuntimeDebugEnabled ? "On" : "Off") + ", Detailed Logging cfg=" + BountyHuntercontractPlugin.DetailedLogging.Value.ToString() + ", Compatibility Diagnostics cfg=" + BountyHuntercontractPlugin.CompatibilityDiagnostics.Value.ToString() + "."); return list3; } default: return new List { "Uso: bhc debug [on|off|status]" }; } default: { string text2 = ((list.Count > 1) ? list[list.Count - 1].ToLowerInvariant() : ((list.Count == 1 && IsRemovalMode(list[0])) ? list[0].ToLowerInvariant() : "auto")); if (!IsRemovalMode(text2)) { return new List { "Modo de recompensa inválido. Usa auto, creator, winner, target o destroy." }; } List list2 = new List(); if (text == "remove") { if (list.Count == 0) { return new List { "Uso: bhc remove [modo]" }; } string id = list[0]; list2 = Contracts.Where((BountyContract item) => item.ContractId.StartsWith(id, StringComparison.OrdinalIgnoreCase)).ToList(); if (list2.Count > 1) { return new List { "Ese prefijo coincide con varios contratos. Usa más caracteres del ID." }; } } else if (text == "remove-target" || text == "remove-creator") { if (list.Count == 0) { return new List { "Debes indicar el nombre del jugador entre comillas si contiene espacios." }; } string name = list[0]; list2 = ((text == "remove-target") ? Contracts.Where((BountyContract item) => item.TargetName.Equals(name, StringComparison.OrdinalIgnoreCase)).ToList() : Contracts.Where((BountyContract item) => item.CreatorName.Equals(name, StringComparison.OrdinalIgnoreCase)).ToList()); } else { switch (text) { case "clear-active": list2 = Contracts.Where((BountyContract item) => item.Status == BountyStatus.Active).ToList(); break; case "clear-completed": list2 = Contracts.Where((BountyContract item) => item.Status == BountyStatus.Completed).ToList(); break; case "clear-all": list2 = Contracts.ToList(); break; default: return new List { "Comando desconocido. Usa: bhc help" }; } } if (list2.Count == 0) { return new List { "No se encontraron contratos coincidentes." }; } int num = 0; foreach (BountyContract item2 in list2.ToList()) { if (AdminRemoveContract(item2, text2)) { num++; } } Save(); BroadcastState(); DeliverPendingRewards(); List list3 = new List(); list3.Add("BHC: eliminados " + num + " contrato(s). Modo de recompensa: " + text2 + "."); return list3; } } } private static bool IsRemovalMode(string value) { string text = (value ?? string.Empty).ToLowerInvariant(); int result; switch (text) { default: result = ((text == "destroy") ? 1 : 0); break; case "auto": case "creator": case "winner": case "target": result = 1; break; } return (byte)result != 0; } private static bool AdminRemoveContract(BountyContract contract, string mode) { if (!Contracts.Contains(contract)) { return false; } string text = (string.IsNullOrWhiteSpace(mode) ? "auto" : mode.ToLowerInvariant()); long num = 0L; string playerName = string.Empty; if (text == "auto") { text = ((contract.Status == BountyStatus.Completed && contract.WinnerPlayerId != 0L) ? "winner" : "creator"); } switch (text) { case "creator": num = contract.CreatorPlayerId; playerName = contract.CreatorName; break; case "winner": if (contract.WinnerPlayerId != 0) { num = contract.WinnerPlayerId; playerName = contract.WinnerName; } else { num = contract.CreatorPlayerId; playerName = contract.CreatorName; text = "creator"; } break; case "target": num = contract.TargetPlayerId; playerName = contract.TargetName; break; } if (num != 0L && text != "destroy" && !contract.RewardClaimed) { AddPendingReward(num, playerName, contract.Reward, "Recompensa resuelta por eliminación administrativa del contrato " + contract.ContractId); contract.RewardClaimed = true; } if (text != "destroy" && contract.BondCoins > 0 && !contract.BondClaimed) { AddPendingCoins(contract.CreatorPlayerId, contract.CreatorName, contract.BondCoins, "Devolución de fianza por eliminación administrativa del contrato " + contract.ContractId); contract.BondClaimed = true; } if (contract.Status == BountyStatus.Active) { contract.Status = BountyStatus.Cancelled; contract.Outcome = BountyOutcome.AdminRemoved; contract.FinishedUtcTicks = DateTime.UtcNow.Ticks; GlobalStats.TotalContractsCancelled++; RecordHistory(contract); } Contracts.Remove(contract); LastHits.Remove(contract.ContractId); BountyDiagnostics.Info("ADMIN", "Removed contract=" + contract.ContractId + " status=" + contract.Status.ToString() + " rewardMode=" + text + " bondCoins=" + contract.BondCoins + "."); BountyDiscordWebhook.NotifyAdminRemoved(contract, text); return true; } private static bool IsAdministrator(long sender) { if ((Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null) { return false; } if (sender == 0L || sender == ZRoutedRpc.instance.GetServerPeerID()) { return true; } ZNetPeer peer = ZNet.instance.GetPeer(sender); if (peer == null) { return false; } try { return ZNet.instance.ListContainsId(ZNet.instance.m_adminList, peer.m_rpc.GetSocket().GetHostName()); } catch { return false; } } private static void SendAdminResult(long peerId, params string[] lines) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(lines.Length); foreach (string text in lines) { val.Write(text ?? string.Empty); } BountyNetwork.SendToPeer(peerId, "BHC_AdminResult", val); } internal static void RpcPosition(long sender, ZPackage package) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) if (!IsServer()) { return; } EnsureLoaded(); if (!_loaded) { return; } long playerId = package.ReadLong(); string playerName = package.ReadString(); if (RegisterOrRefreshPlayer(sender, playerId, playerName, out BountyPlayerInfo player, out bool stateChanged)) { player.Position = package.ReadVector3(); BountyDiagnostics.Tracking("SERVER", "RPC Position peer=" + sender + " player=" + player.Name + "#" + player.PlayerId + " pos=" + BountyDiagnostics.Format(player.Position) + "."); player.LastSeenUtc = DateTime.UtcNow; player.Connected = true; if (stateChanged) { BroadcastState(); } } } internal static void RpcCombatHit(long sender, ZPackage package) { if (!IsServer()) { return; } EnsureLoaded(); if (!_loaded) { return; } long playerId = package.ReadLong(); string playerName = package.ReadString(); string contractId = package.ReadString(); long num = package.ReadLong(); long num2 = package.ReadLong(); BountyDiagnostics.Detailed("SERVER", "RPC CombatHit peer=" + sender + " contract=" + contractId + " attacker=" + num + " victim=" + num2 + "."); if (!RegisterOrRefreshPlayer(sender, playerId, playerName, out BountyPlayerInfo player, out bool _) || player.PlayerId != num2) { BountyDiagnostics.Warning("COMBAT", "Rejected hit report: reporting peer is not the victim. peer=" + sender + " senderPlayer=" + playerId + " victim=" + num2 + "."); return; } BountyContract bountyContract = Contracts.FirstOrDefault((BountyContract item) => item.ContractId == contractId && item.Status == BountyStatus.Active); if (bountyContract == null || !bountyContract.IsOpposingPair(num, num2)) { BountyDiagnostics.Warning("COMBAT", "Rejected hit report: contract/pair validation failed contract=" + contractId + " attacker=" + num + " victim=" + num2 + "."); } else { LastHits[contractId] = new LastValidHit { ContractId = contractId, AttackerPlayerId = num, VictimPlayerId = num2, ServerReceivedUtc = DateTime.UtcNow }; BountyDiagnostics.Info("COMBAT", "Valid bounty hit recorded contract=" + contractId + " attacker=" + num + " victim=" + num2 + "."); } } internal static void RpcDeath(long sender, ZPackage package) { if (!IsServer()) { return; } EnsureLoaded(); if (!_loaded) { return; } long playerId = package.ReadLong(); string text = package.ReadString(); long victimPlayerId = package.ReadLong(); string reportedContractId = package.ReadString(); long reportedAttackerId = package.ReadLong(); BountyDiagnostics.Info("SERVER", "RPC Death peer=" + sender + " victim=" + text + "#" + victimPlayerId + " contract=" + reportedContractId + " reportedAttacker=" + reportedAttackerId + "."); if (!RegisterOrRefreshPlayer(sender, playerId, text, out BountyPlayerInfo player, out bool _) || player.PlayerId != victimPlayerId) { BountyDiagnostics.Warning("DEATH", "Rejected death report: peer identity does not match victim. peer=" + sender + " senderPlayer=" + playerId + " victim=" + victimPlayerId + "."); return; } BountyContract bountyContract = Contracts.FirstOrDefault((BountyContract item) => item.Status == BountyStatus.Active && item.ContractId == reportedContractId && item.IsActiveParticipant(victimPlayerId)); if (bountyContract == null) { BountyDiagnostics.Detailed("DEATH", "Death does not match an active contract participant victim=" + victimPlayerId + " contract=" + reportedContractId + "."); return; } string evidenceSource; long num = ResolveDeathAttacker(bountyContract, victimPlayerId, reportedAttackerId, out evidenceSource); if (num == 0) { BountyDiagnostics.Info("DEATH", "Participant died without valid opposing-player evidence. Contract remains active id=" + bountyContract.ContractId + " victim=" + victimPlayerId + "."); } else if (victimPlayerId == bountyContract.TargetPlayerId && bountyContract.HasActiveHunter(num)) { string knownName = GetKnownName(num, bountyContract.GetHunter(num)?.Name ?? string.Empty); CompleteContract(bountyContract, BountyOutcome.HunterVictory, num, knownName, "evidence=" + evidenceSource); } else if (bountyContract.HasActiveHunter(victimPlayerId) && num == bountyContract.TargetPlayerId) { EliminateHunter(bountyContract, victimPlayerId, evidenceSource); } else { BountyDiagnostics.Warning("DEATH", "Death evidence did not match a valid victory direction contract=" + bountyContract.ContractId + " attacker=" + num + " victim=" + victimPlayerId + "."); } } internal static void RpcClaimReward(long sender, ZPackage package) { if (!IsServer()) { return; } EnsureLoaded(); if (!_loaded) { return; } long playerId = package.ReadLong(); string text = package.ReadString(); string contractId = package.ReadString(); BountyDiagnostics.Info("CLAIM", "RPC claim received peer=" + sender + " player=" + text + "#" + playerId + " contract=" + contractId + "."); if (!RegisterOrRefreshPlayer(sender, playerId, text, out BountyPlayerInfo player, out bool _)) { SendStatus(sender, success: false, "No se pudo validar tu identidad para cobrar la recompensa."); return; } BountyContract bountyContract = Contracts.FirstOrDefault((BountyContract item) => item.ContractId == contractId && item.Status == BountyStatus.Completed); if (bountyContract == null) { SendStatus(sender, success: false, "Ese contrato no tiene una recompensa pendiente."); return; } if (bountyContract.WinnerPlayerId != player.PlayerId) { SendStatus(sender, success: false, "La recompensa pertenece a " + bountyContract.WinnerName + "."); return; } if (bountyContract.RewardClaimed) { SendStatus(sender, success: false, "La recompensa principal de ese contrato ya fue cobrada."); return; } bountyContract.RewardClaimed = true; AddPendingReward(player.PlayerId, player.Name, bountyContract.Reward, (bountyContract.Outcome == BountyOutcome.TargetVictory) ? "Recompensa por sobrevivir al contrato contra ti" : ("Recompensa por eliminar a " + bountyContract.TargetName)); GetOrCreateStats(player.PlayerId, player.Name).RewardsClaimed++; GlobalStats.RewardsClaimed++; RemoveContractIfFullySettled(bountyContract); Save(); BroadcastState(); DeliverPendingRewards(player.PlayerId); SendStatus(sender, success: true, "Recompensa cobrada: " + BountyItemCodec.Describe(bountyContract.Reward) + "."); BountyDiagnostics.Info("CLAIM", "Reward claimed contract=" + bountyContract.ContractId + " winner=" + player.Name + "#" + player.PlayerId + " reward='" + BountyItemCodec.Describe(bountyContract.Reward, localized: false) + "' bondClaimed=" + bountyContract.BondClaimed + "."); } internal static void RpcClaimBond(long sender, ZPackage package) { if (!IsServer()) { return; } EnsureLoaded(); if (!_loaded) { return; } long playerId = package.ReadLong(); string text = package.ReadString(); string contractId = package.ReadString(); BountyDiagnostics.Info("BOND", "RPC bond claim received peer=" + sender + " player=" + text + "#" + playerId + " contract=" + contractId + "."); if (!RegisterOrRefreshPlayer(sender, playerId, text, out BountyPlayerInfo player, out bool _)) { SendStatus(sender, success: false, "No se pudo validar tu identidad para recuperar la fianza."); return; } BountyContract bountyContract = Contracts.FirstOrDefault((BountyContract item) => item.ContractId == contractId && item.Status == BountyStatus.Completed); if (bountyContract == null) { SendStatus(sender, success: false, "Ese contrato no tiene una fianza pendiente."); return; } if (bountyContract.CreatorPlayerId != player.PlayerId) { SendStatus(sender, success: false, "La fianza pertenece al creador " + bountyContract.CreatorName + "."); return; } if (bountyContract.BondCoins <= 0 || bountyContract.BondClaimed) { SendStatus(sender, success: false, "La fianza de ese contrato ya fue recuperada o no existía."); return; } bountyContract.BondClaimed = true; AddPendingCoins(player.PlayerId, player.Name, bountyContract.BondCoins, "Devolución de fianza del contrato resuelto contra " + bountyContract.TargetName); RemoveContractIfFullySettled(bountyContract); Save(); BroadcastState(); DeliverPendingRewards(player.PlayerId); SendStatus(sender, success: true, "Fianza recuperada: " + bountyContract.BondCoins + " Coins."); BountyDiagnostics.Info("BOND", "Bond claimed contract=" + bountyContract.ContractId + " creator=" + player.Name + "#" + player.PlayerId + " bondCoins=" + bountyContract.BondCoins + " rewardClaimed=" + bountyContract.RewardClaimed + "."); } private static long ResolveDeathAttacker(BountyContract contract, long victimPlayerId, long reportedAttackerId, out string evidenceSource) { evidenceSource = string.Empty; if (LastHits.TryGetValue(contract.ContractId, out LastValidHit value)) { double totalSeconds = (DateTime.UtcNow - value.ServerReceivedUtc).TotalSeconds; if (totalSeconds >= 0.0 && totalSeconds <= (double)Math.Max(1f, BountyHuntercontractPlugin.KillCreditSeconds.Value) && value.VictimPlayerId == victimPlayerId && contract.IsOpposingPair(value.AttackerPlayerId, victimPlayerId)) { evidenceSource = "server-hit"; return value.AttackerPlayerId; } BountyDiagnostics.Warning("DEATH", "Server hit evidence rejected contract=" + contract.ContractId + " attacker=" + value.AttackerPlayerId + " victim=" + value.VictimPlayerId + " ageSeconds=" + totalSeconds.ToString("0.00") + "."); } if (reportedAttackerId != 0L && contract.IsOpposingPair(reportedAttackerId, victimPlayerId)) { evidenceSource = "victim-death-packet"; BountyDiagnostics.Warning("DEATH", "Using victim death-packet fallback contract=" + contract.ContractId + " attacker=" + reportedAttackerId + "."); return reportedAttackerId; } return 0L; } private static void EliminateHunter(BountyContract contract, long hunterPlayerId, string evidenceSource) { BountyHunterInfo hunter = contract.GetHunter(hunterPlayerId); if (hunter != null && !hunter.Eliminated) { hunter.Eliminated = true; hunter.EliminatedUtcTicks = DateTime.UtcNow.Ticks; LastHits.Remove(contract.ContractId); GetOrCreateStats(contract.TargetPlayerId, contract.TargetName).HuntersEliminated++; GetOrCreateStats(hunter.PlayerId, hunter.Name).HunterDeaths++; GlobalStats.HuntersEliminated++; BountyDiagnostics.Info("CONTRACT", "Hunter eliminated contract=" + contract.ContractId + " hunter=" + hunter.Name + "#" + hunter.PlayerId + " target=" + contract.TargetName + " activeHunters=" + contract.ActiveHunterCount + " accepted=" + contract.Hunters.Count + "/" + contract.MaximumHunters + " evidence=" + evidenceSource + "."); SendPersonalAnnouncement(hunter.PlayerId, "HAS SIDO ELIMINADO", contract.TargetName + " te derrotó. Quedaste fuera de este contrato."); SendPersonalAnnouncement(contract.TargetPlayerId, "CAZADOR ELIMINADO", "Eliminaste a " + hunter.Name + ". Cazadores activos restantes: " + contract.ActiveHunterCount + "."); if (BountyHuntercontractPlugin.TargetCanWinReward.Value == BountyHuntercontractPlugin.Toggle.On && contract.Hunters.Count >= contract.MaximumHunters && contract.ActiveHunterCount == 0) { CompleteContract(contract, BountyOutcome.TargetVictory, contract.TargetPlayerId, contract.TargetName, "all hunters eliminated"); return; } Save(); BroadcastState(); } } private static void CompleteContract(BountyContract contract, BountyOutcome outcome, long winnerPlayerId, string winnerName, string evidence) { if (contract.Status != BountyStatus.Active) { return; } DateTime utcNow = DateTime.UtcNow; contract.Status = BountyStatus.Completed; contract.Outcome = outcome; contract.FinishedUtcTicks = utcNow.Ticks; contract.WinnerPlayerId = winnerPlayerId; contract.WinnerName = winnerName; contract.RewardClaimed = false; contract.BondClaimed = contract.BondCoins <= 0; LastHits.Remove(contract.ContractId); GlobalStats.TotalContractsCompleted++; switch (outcome) { case BountyOutcome.HunterVictory: GlobalStats.HunterVictories++; GetOrCreateStats(winnerPlayerId, winnerName).HunterVictories++; GetOrCreateStats(contract.TargetPlayerId, contract.TargetName).TargetDeaths++; break; case BountyOutcome.TargetVictory: GlobalStats.TargetVictories++; GetOrCreateStats(contract.TargetPlayerId, contract.TargetName).TargetVictories++; break; } RecordHistory(contract); Save(); BroadcastState(); BountyDiagnostics.Info("CONTRACT", "Completed contract pending board claim id=" + contract.ContractId + " outcome=" + outcome.ToString() + " winner=" + winnerName + "#" + winnerPlayerId + " target=" + contract.TargetName + " reward='" + BountyItemCodec.Describe(contract.Reward, localized: false) + "' " + evidence + "."); BountyDiscordWebhook.NotifyCompleted(contract, evidence); if (outcome == BountyOutcome.HunterVictory) { SendPersonalAnnouncement(winnerPlayerId, "¡CONTRATO CUMPLIDO!", "Eliminaste a " + contract.TargetName + ". Vuelve al tablón para cobrar " + BountyItemCodec.Describe(contract.Reward) + ".", contract.ContractId); SendPersonalAnnouncement(contract.TargetPlayerId, "OBJETIVO ELIMINADO", winnerName + " cumplió el contrato contra ti.", contract.ContractId); BroadcastCompletionAnnouncement("¡CONTRATO CUMPLIDO!", winnerName + " eliminó a " + contract.TargetName + ".\nRecompensa: " + BountyItemCodec.Describe(contract.Reward)); } else { SendPersonalAnnouncement(contract.TargetPlayerId, "¡SOBREVIVISTE AL CONTRATO!", "La recompensa ahora es tuya. Vuelve al tablón para cobrar " + BountyItemCodec.Describe(contract.Reward) + ".", contract.ContractId); foreach (BountyHunterInfo hunter in contract.Hunters) { SendPersonalAnnouncement(hunter.PlayerId, "EL OBJETIVO SOBREVIVIÓ", contract.TargetName + " ganó el contrato y podrá cobrar la recompensa.", contract.ContractId); } BroadcastCompletionAnnouncement("¡EL OBJETIVO SOBREVIVIÓ!", contract.TargetName + " derrotó a sus cazadores o resistió hasta el final.\nRecompensa: " + BountyItemCodec.Describe(contract.Reward)); } if (contract.BondCoins > 0) { SendPersonalAnnouncement(contract.CreatorPlayerId, "FIANZA DISPONIBLE", "El contrato contra " + contract.TargetName + " terminó normalmente. Vuelve al tablón para recuperar " + contract.BondCoins + " Coins de fianza.", contract.ContractId); } } private static bool RegisterOrRefreshPlayer(long sender, long playerId, string playerName, out BountyPlayerInfo player, out bool stateChanged) { player = null; stateChanged = false; if (playerId == 0L || string.IsNullOrWhiteSpace(playerName)) { return false; } BountyPlayerInfo value2; if (PlayersByPeer.TryGetValue(sender, out BountyPlayerInfo value)) { player = value; if (player.PlayerId != playerId) { PlayersById.Remove(player.PlayerId); stateChanged = true; } } else if (PlayersById.TryGetValue(playerId, out value2)) { player = value2; if (player.PeerId != sender) { PlayersByPeer.Remove(player.PeerId); stateChanged = true; } } else { player = new BountyPlayerInfo(); stateChanged = true; } if (player.Name != playerName || !player.Connected || player.PeerId != sender) { stateChanged = true; } player.PeerId = sender; player.Name = playerName.Trim(); player.PlayerId = playerId; player.Connected = true; player.LastSeenUtc = DateTime.UtcNow; PlayersByPeer[sender] = player; PlayersById[playerId] = player; GetOrCreateStats(playerId, player.Name); if (stateChanged) { BountyDiagnostics.Info("PLAYER", "Registered/refreshed player " + player.Name + "#" + player.PlayerId + " peer=" + sender + " connected=" + player.Connected + "."); } return true; } private static void MaintainPlayers() { DateTime utcNow = DateTime.UtcNow; bool flag = false; foreach (BountyPlayerInfo value in PlayersByPeer.Values) { bool flag2 = (utcNow - value.LastSeenUtc).TotalSeconds < 45.0; if (value.Connected != flag2) { value.Connected = flag2; flag = true; BountyDiagnostics.Info("PLAYER", "Player connection state changed " + value.Name + "#" + value.PlayerId + " connected=" + flag2 + "."); } } if (flag) { BroadcastState(); } } private static void PrepareLoadedContractTimers() { long ticks = DateTime.UtcNow.Ticks; foreach (BountyContract item in Contracts.Where((BountyContract item) => item.Status == BountyStatus.Active)) { if (item.DurationMinutes <= 0) { item.RemainingDurationTicks = 0L; item.DurationTimerRunning = false; } else if (item.RemainingDurationTicks <= 0 && item.ExpiresUtcTicks > 0) { item.RemainingDurationTicks = Math.Max(0L, item.ExpiresUtcTicks - ticks); } else if (item.RemainingDurationTicks <= 0 && item.LastDurationUpdateUtcTicks <= 0) { item.RemainingDurationTicks = TimeSpan.FromMinutes(item.DurationMinutes).Ticks; } item.LastDurationUpdateUtcTicks = ticks; item.DurationTimerRunning = false; item.ExpiresUtcTicks = 0L; BountyDiagnostics.Info("TIMER", "Prepared loaded contract=" + item.ContractId + " target=" + item.TargetName + " remaining=" + FormatRemainingTicks(item.RemainingDurationTicks) + " state=PAUSED until target connects."); } } private static void UpdateContractTimers() { long ticks = DateTime.UtcNow.Ticks; bool flag = false; foreach (BountyContract item in Contracts.Where((BountyContract item) => item.Status == BountyStatus.Active && item.DurationMinutes > 0)) { if (item.LastDurationUpdateUtcTicks <= 0 || item.LastDurationUpdateUtcTicks > ticks) { item.LastDurationUpdateUtcTicks = ticks; } long num = Math.Max(0L, ticks - item.LastDurationUpdateUtcTicks); BountyPlayerInfo value; bool flag2 = PlayersById.TryGetValue(item.TargetPlayerId, out value) && value.Connected; bool flag3 = IsFiveSecondTestMode() && item.Hunters.Count == 0; bool flag4 = flag2 && !flag3; bool flag5 = item.DurationTimerRunning != flag4; item.DurationTimerRunning = flag4; item.LastDurationUpdateUtcTicks = ticks; if (flag4 && num > 0 && item.RemainingDurationTicks > 0) { item.RemainingDurationTicks = Math.Max(0L, item.RemainingDurationTicks - num); _contractTimerDirty = true; } if (flag5) { flag = true; _contractTimerDirty = true; BountyDiagnostics.Info("TIMER", "Contract=" + item.ContractId + " target=" + item.TargetName + " timer=" + (flag4 ? "RUNNING" : "PAUSED") + " remaining=" + FormatRemainingTicks(item.RemainingDurationTicks) + "."); } } if (flag) { BroadcastState(); } } private static int CountOpenContractsForCreator(long creatorPlayerId, bool includePending) { int num = Contracts.Count((BountyContract contract) => contract.CreatorPlayerId == creatorPlayerId && (contract.Status == BountyStatus.Active || contract.Status == BountyStatus.Completed)); if (includePending) { num += PendingCreations.Values.Count((PendingCreation creation) => creation.CreatorPlayerId == creatorPlayerId); } return num; } private static void ExpirePendingCreations() { DateTime cutoff = DateTime.UtcNow.AddSeconds(-30.0); List list = (from creation in PendingCreations.Values where creation.CreatedUtc < cutoff select creation.Token).ToList(); foreach (string item in list) { PendingCreations.Remove(item); BountyDiagnostics.Warning("CONTRACT", "Expired pending creation token=" + item + "."); } } private static void ExpireContracts() { DateTime now = DateTime.UtcNow; List list = Contracts.Where((BountyContract contract) => contract.Status == BountyStatus.Active && contract.IsExpired(now)).ToList(); if (list.Count == 0) { return; } foreach (BountyContract item in list) { if (item.Hunters.Count > 0 && BountyHuntercontractPlugin.TargetWinsOnExpiration.Value == BountyHuntercontractPlugin.Toggle.On) { GlobalStats.TotalContractsExpired++; BountyDiagnostics.Info("TIMER", "Contract duration reached zero. Awarding survival victory contract=" + item.ContractId + " target=" + item.TargetName + " acceptedHunters=" + item.Hunters.Count + " bondCoins=" + item.BondCoins + "."); CompleteContract(item, BountyOutcome.TargetVictory, item.TargetPlayerId, item.TargetName, "contract duration expired"); } else { item.Status = BountyStatus.Expired; item.Outcome = BountyOutcome.ExpiredWithoutHunters; item.FinishedUtcTicks = now.Ticks; item.RewardClaimed = true; item.BondClaimed = true; Contracts.Remove(item); GlobalStats.TotalContractsExpired++; RecordHistory(item); AddPendingReward(item.CreatorPlayerId, item.CreatorName, item.Reward, "Devolución por contrato expirado sin cazadores"); AddPendingCoins(item.CreatorPlayerId, item.CreatorName, item.BondCoins, "Devolución de fianza por contrato expirado sin cazadores"); SendPersonalAnnouncement(item.CreatorPlayerId, "CONTRATO EXPIRADO", "Nadie aceptó el contrato contra " + item.TargetName + ". Recuperarás la recompensa y la fianza.", item.ContractId); BountyDiagnostics.Info("CONTRACT", "Expired contract=" + item.ContractId + " target=" + item.TargetName + " rewardRefund='" + BountyItemCodec.Describe(item.Reward, localized: false) + "' bondRefund=" + item.BondCoins + " Coins."); BountyDiscordWebhook.NotifyExpiredWithoutHunters(item); } } Save(); BroadcastState(); DeliverPendingRewards(); } private static void AddPendingReward(long playerId, string playerName, BountyRewardItem item, string reason) { if (playerId != 0L && item != null && item.IsValid()) { PendingReward pendingReward = new PendingReward { PlayerId = playerId, PlayerName = (playerName ?? string.Empty), Reward = item.Clone(), Reason = (reason ?? string.Empty) }; PendingRewards.Add(pendingReward); BountyDiagnostics.Info("REWARD", "Queued reward id=" + pendingReward.RewardId + " player=" + pendingReward.PlayerName + "#" + pendingReward.PlayerId + " item='" + BountyItemCodec.Describe(pendingReward.Reward, localized: false) + "' reason='" + pendingReward.Reason + "'."); } } private static void AddPendingCoins(long playerId, string playerName, int amount, string reason) { if (amount > 0) { AddPendingReward(playerId, playerName, BountyRewardItem.Coins(amount), reason); } } private static void DistributeCancellationBond(BountyContract contract) { int num = Math.Max(0, contract.BondCoins); if (num <= 0 || contract.Hunters.Count == 0) { return; } int count = contract.Hunters.Count; int num2 = num / count; int num3 = num % count; for (int i = 0; i < count; i++) { BountyHunterInfo bountyHunterInfo = contract.Hunters[i]; int num4 = num2 + ((i < num3) ? 1 : 0); if (num4 > 0) { AddPendingCoins(bountyHunterInfo.PlayerId, bountyHunterInfo.Name, num4, "Compensación por cancelación del contrato contra " + contract.TargetName); SendPersonalAnnouncement(bountyHunterInfo.PlayerId, "CONTRATO CANCELADO", contract.CreatorName + " canceló el contrato. Recibirás " + num4 + " Coins de la fianza como compensación.", contract.ContractId); BountyDiagnostics.Info("BOND", "Cancellation compensation queued contract=" + contract.ContractId + " hunter=" + bountyHunterInfo.Name + "#" + bountyHunterInfo.PlayerId + " share=" + num4 + "/" + num + "."); } } } private static void RemoveContractIfFullySettled(BountyContract contract) { if (contract != null && contract.Status == BountyStatus.Completed) { bool rewardClaimed = contract.RewardClaimed; bool flag = contract.BondCoins <= 0 || contract.BondClaimed; if (rewardClaimed && flag) { Contracts.Remove(contract); LastHits.Remove(contract.ContractId); BountyDiagnostics.Info("CONTRACT", "Removed fully settled contract=" + contract.ContractId + " rewardClaimed=" + contract.RewardClaimed + " bondClaimed=" + contract.BondClaimed + "."); } } } private static void DeliverPendingRewards() { foreach (BountyPlayerInfo item in PlayersById.Values.Where((BountyPlayerInfo item) => item.Connected).ToList()) { DeliverPendingRewards(item.PlayerId); } } private static void DeliverPendingRewards(long playerId) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Expected O, but got Unknown if (!PlayersById.TryGetValue(playerId, out BountyPlayerInfo value) || !value.Connected) { return; } List list = PendingRewards.Where((PendingReward reward) => reward.PlayerId == playerId).ToList(); if (list.Count == 0) { return; } foreach (PendingReward item in list) { ZPackage package = new ZPackage(); BountySerialization.WriteReward(package, item); BountyDiagnostics.Info("REWARD", "Sending reward id=" + item.RewardId + " to peer=" + value.PeerId + " player=" + value.Name + " item='" + BountyItemCodec.Describe(item.Reward, localized: false) + "'."); BountyNetwork.SendToPeer(value.PeerId, "BHC_GrantReward", package); PendingRewards.Remove(item); } Save(); } private static void SendTrackingUpdates() { //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Expected O, but got Unknown //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: Expected O, but got Unknown //IL_024f: Unknown result type (might be due to invalid IL or missing references) foreach (BountyContract item in Contracts.Where((BountyContract item) => item.Status == BountyStatus.Active && item.ActiveHunterCount > 0)) { if (!PlayersById.TryGetValue(item.TargetPlayerId, out BountyPlayerInfo value) || !value.Connected) { continue; } BountyPlayerInfo value2; List list = (from hunter in item.Hunters where !hunter.Eliminated select PlayersById.TryGetValue(hunter.PlayerId, out value2) ? value2 : null into info where info?.Connected ?? false select info).Cast().ToList(); BountyDiagnostics.Tracking("TRACK", "Preparing tracking contract=" + item.ContractId + " target=" + value.Name + " huntersConnected=" + list.Count + "/" + item.ActiveHunterCount + "."); foreach (BountyPlayerInfo item2 in list) { ZPackage val = new ZPackage(); val.Write(item.ContractId); val.Write(1); val.Write(value.PlayerId); val.Write(value.Name); val.Write(value.Position); val.Write("Objetivo"); BountyNetwork.SendToPeer(item2.PeerId, "BHC_Tracking", val); } ZPackage val2 = new ZPackage(); val2.Write(item.ContractId); val2.Write(list.Count); foreach (BountyPlayerInfo item3 in list) { val2.Write(item3.PlayerId); val2.Write(item3.Name); val2.Write(item3.Position); val2.Write("Cazador"); } BountyNetwork.SendToPeer(value.PeerId, "BHC_Tracking", val2); } } private static void SendState(long peerId) { if (_loaded) { ZPackage package = BuildStatePackage(); BountyDiagnostics.Detailed("STATE", "Sending lightweight state to peer=" + peerId + " players=" + PlayersById.Values.Count((BountyPlayerInfo player) => player.Connected) + " visibleContracts=" + VisibleContracts().Count() + " stats=" + PlayerStatsById.Count + " history=on-demand."); BountyNetwork.SendToPeer(peerId, "BHC_State", package); } } private static void BroadcastState() { if (_loaded) { ZPackage package = BuildStatePackage(); BountyDiagnostics.Detailed("STATE", "Broadcasting lightweight state players=" + PlayersById.Values.Count((BountyPlayerInfo player) => player.Connected) + " visibleContracts=" + VisibleContracts().Count() + " stats=" + PlayerStatsById.Count + " history=on-demand."); BountyNetwork.SendToEverybody("BHC_State", package); } } private static ZPackage BuildStatePackage() { return BountySerialization.BuildStatePackage(PlayersById.Values.Where((BountyPlayerInfo player) => player.Connected), VisibleContracts(), from stats in PlayerStatsById.Values orderby stats.TotalVictories descending, stats.LastKnownName select stats, GlobalStats, Enumerable.Empty()); } private static void SendHistory(long peerId) { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown if (!_loaded) { return; } int val = Math.Max(10, BountyHuntercontractPlugin.MaximumHistoryEntries.Value); List list = History.OrderByDescending((BountyHistoryEntry entry) => entry.FinishedUtcTicks).Take(Math.Min(val, 75)).ToList(); ZPackage val2 = new ZPackage(); val2.Write(6); val2.Write(list.Count); foreach (BountyHistoryEntry item in list) { BountySerialization.WriteHistory(val2, item); } BountyDiagnostics.Detailed("STATE", "Sending history on demand peer=" + peerId + " entries=" + list.Count + "."); BountyNetwork.SendToPeer(peerId, "BHC_History", val2); } private static IEnumerable VisibleContracts() { return Contracts.Where((BountyContract contract) => contract.Status == BountyStatus.Active || contract.Status == BountyStatus.Completed); } private static void SendStatus(long peerId, bool success, string message) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(success); val.Write(message ?? string.Empty); BountyDiagnostics.Info("STATUS", "Sending status peer=" + peerId + " success=" + success + " message='" + message + "'."); BountyNetwork.SendToPeer(peerId, "BHC_Status", val); } private static void BroadcastStatus(string message) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(true); val.Write(message ?? string.Empty); BountyDiagnostics.Info("STATUS", "Broadcasting status message='" + message + "'."); BountyNetwork.SendToEverybody("BHC_Status", val); } private static void BroadcastContractAnnouncement(BountyContract contract) { if (BountyHuntercontractPlugin.GlobalContractAnnouncements.Value == BountyHuntercontractPlugin.Toggle.On) { string title = "¡NUEVO CONTRATO DE ASESINATO!"; string message = "Objetivo: " + contract.TargetName + "\nRecompensa: " + BountyItemCodec.Describe(contract.Reward) + "\nCazadores: " + contract.MaximumHunters + " | Tiempo activo: " + FormatEffectiveDuration(contract) + " (solo con el objetivo conectado)"; BroadcastAnnouncement(title, message); BountyDiagnostics.Info("ANNOUNCEMENT", "Broadcasting published-contract announcement contract=" + contract.ContractId + " target=" + contract.TargetName + " reward='" + BountyItemCodec.Describe(contract.Reward, localized: false) + "' maxHunters=" + contract.MaximumHunters + " durationMinutes=" + contract.DurationMinutes + "."); } } private static void BroadcastCompletionAnnouncement(string title, string message) { if (BountyHuntercontractPlugin.GlobalCompletionAnnouncements.Value == BountyHuntercontractPlugin.Toggle.On) { BroadcastAnnouncement("" + title + "", message); } } private static void BroadcastAnnouncement(string title, string message) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(title ?? string.Empty); val.Write(message ?? string.Empty); BountyNetwork.SendToEverybody("BHC_Announcement", val); } private static void SendPersonalAnnouncement(long playerId, string title, string message, string contractId = "") { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown if (!PlayersById.TryGetValue(playerId, out BountyPlayerInfo value) || !value.Connected) { BountyDiagnostics.Warning("ANNOUNCEMENT", "Personal announcement could not be sent because player=" + playerId + " is not connected. contract=" + contractId + "."); return; } ZPackage val = new ZPackage(); val.Write(playerId); val.Write(contractId ?? string.Empty); val.Write(title ?? string.Empty); val.Write(message ?? string.Empty); BountyNetwork.SendToPeer(value.PeerId, "BHC_PersonalAnnouncement", val); BountyDiagnostics.Info("ANNOUNCEMENT", "Personal announcement sent player=" + value.Name + "#" + value.PlayerId + " contract=" + contractId + " title='" + title + "'."); } private static BountyPlayerStats GetOrCreateStats(long playerId, string playerName) { if (!PlayerStatsById.TryGetValue(playerId, out BountyPlayerStats value)) { value = new BountyPlayerStats { PlayerId = playerId, LastKnownName = (playerName ?? string.Empty) }; PlayerStatsById[playerId] = value; } else if (!string.IsNullOrWhiteSpace(playerName)) { value.LastKnownName = playerName; } return value; } private static void RecordHistory(BountyContract contract) { if (History.Any((BountyHistoryEntry entry) => entry.ContractId == contract.ContractId)) { return; } History.Add(new BountyHistoryEntry { ContractId = contract.ContractId, CreatorPlayerId = contract.CreatorPlayerId, CreatorName = contract.CreatorName, TargetPlayerId = contract.TargetPlayerId, TargetName = contract.TargetName, WinnerPlayerId = contract.WinnerPlayerId, WinnerName = contract.WinnerName, Outcome = contract.Outcome, Reward = contract.Reward.Clone(), BondCoins = contract.BondCoins, MaximumHunters = contract.MaximumHunters, AcceptedHunters = contract.Hunters.Count, EliminatedHunters = contract.EliminatedHunterCount, DurationMinutes = contract.DurationMinutes, CreatedUtcTicks = contract.CreatedUtcTicks, FinishedUtcTicks = contract.FinishedUtcTicks }); int num = Math.Max(10, BountyHuntercontractPlugin.MaximumHistoryEntries.Value); if (History.Count <= num) { return; } List list = History.OrderByDescending((BountyHistoryEntry entry) => entry.FinishedUtcTicks).Skip(num).ToList(); foreach (BountyHistoryEntry item in list) { History.Remove(item); } } private static bool IsFiveSecondTestMode() { return BountyHuntercontractPlugin.EnableFiveSecondTestContracts.Value == BountyHuntercontractPlugin.Toggle.On; } private static long GetInitialDurationTicks(int durationMinutes) { if (durationMinutes <= 0) { return 0L; } if (IsFiveSecondTestMode()) { int num = Math.Max(1, BountyHuntercontractPlugin.TestContractDurationSeconds.Value); return TimeSpan.FromSeconds(num).Ticks; } return TimeSpan.FromMinutes(durationMinutes).Ticks; } private static string FormatEffectiveDuration(BountyContract contract) { if (contract.DurationMinutes <= 0) { return "sin expiración"; } if (IsFiveSecondTestMode()) { return Math.Max(1, BountyHuntercontractPlugin.TestContractDurationSeconds.Value) + "s (modo de prueba)"; } return FormatDuration(contract.DurationMinutes); } private static bool ValidateDuration(int durationMinutes, out string error) { error = string.Empty; if (durationMinutes == 0 && BountyHuntercontractPlugin.AllowPermanentContracts.Value == BountyHuntercontractPlugin.Toggle.On) { return true; } int num = Math.Max(1, BountyHuntercontractPlugin.MinimumContractDurationMinutes.Value); int num2 = Math.Max(num, BountyHuntercontractPlugin.MaximumContractDurationMinutes.Value); if (durationMinutes < num || durationMinutes > num2) { error = "La duración debe estar entre " + num + " y " + num2 + " minutos."; return false; } return true; } private static int OccupiedHunterSlots(BountyContract contract) { return (BountyHuntercontractPlugin.TargetCanWinReward.Value == BountyHuntercontractPlugin.Toggle.On) ? contract.Hunters.Count : contract.ActiveHunterCount; } private static string GetKnownName(long playerId, string fallback) { if (PlayersById.TryGetValue(playerId, out BountyPlayerInfo value) && !string.IsNullOrWhiteSpace(value.Name)) { return value.Name; } if (PlayerStatsById.TryGetValue(playerId, out BountyPlayerStats value2) && !string.IsNullOrWhiteSpace(value2.LastKnownName)) { return value2.LastKnownName; } return fallback; } private static string FormatRemainingTicks(long ticks) { TimeSpan timeSpan = TimeSpan.FromTicks(Math.Max(0L, ticks)); if (timeSpan.TotalDays >= 1.0) { return (int)timeSpan.TotalDays + "d " + timeSpan.Hours + "h " + timeSpan.Minutes + "m"; } if (timeSpan.TotalHours >= 1.0) { return (int)timeSpan.TotalHours + "h " + timeSpan.Minutes + "m"; } if (timeSpan.TotalMinutes >= 1.0) { return (int)timeSpan.TotalMinutes + "m " + timeSpan.Seconds + "s"; } return Math.Max(0, (int)Math.Ceiling(timeSpan.TotalSeconds)) + "s"; } private static string FormatDuration(int minutes) { if (minutes <= 0) { return "sin expiración"; } TimeSpan timeSpan = TimeSpan.FromMinutes(minutes); if (timeSpan.TotalDays >= 1.0) { return (int)timeSpan.TotalDays + "d " + timeSpan.Hours + "h"; } if (timeSpan.TotalHours >= 1.0) { return (int)timeSpan.TotalHours + "h " + timeSpan.Minutes + "m"; } return minutes + "m"; } private static void ResetGlobalStats() { GlobalStats.TotalContractsCreated = 0; GlobalStats.TotalContractsCompleted = 0; GlobalStats.TotalContractsCancelled = 0; GlobalStats.TotalContractsExpired = 0; GlobalStats.HunterVictories = 0; GlobalStats.TargetVictories = 0; GlobalStats.HuntersEliminated = 0; GlobalStats.RewardsClaimed = 0; } private static void Save() { BountyDiagnostics.Detailed("SAVE", "Saving world=" + _worldId + " contracts=" + Contracts.Count + " pendingRewards=" + PendingRewards.Count + " stats=" + PlayerStatsById.Count + " history=" + History.Count + "."); BountyPersistence.Save(_worldId, Contracts, PendingRewards, PlayerStatsById.Values, GlobalStats, History); } } internal sealed class BountyUI : MonoBehaviour { private static BountyUI? _instance; private static bool _visible; private static string _status = string.Empty; private static bool _statusSuccess; private static string _tooltipForFrame = string.Empty; private const float MinimumWindowWidth = 700f; private const float MinimumWindowHeight = 600f; private const float WindowScreenMargin = 10f; private const string WindowWidthPreference = "BHC_UI_WindowWidth"; private const string WindowHeightPreference = "BHC_UI_WindowHeight"; private const string WindowXPreference = "BHC_UI_WindowX"; private const string WindowYPreference = "BHC_UI_WindowY"; private Rect _windowRect = new Rect(80f, 45f, 920f, 760f); private Vector2 _requestedWindowSize = Vector2.zero; private Vector2 _resizeStartMouse; private Vector2 _resizeStartSize; private bool _resizingWindow; private Vector2 _creationPageScroll; private Vector2 _playerScroll; private Vector2 _rewardScroll; private Vector2 _contractScroll; private Vector2 _statsScroll; private Vector2 _historyScroll; private int _tab; private long _selectedTargetId; private ItemData? _selectedRewardItem; private string _rewardAmountText = "1"; private int _maximumHunters = 1; private string _durationMinutesText = "1440"; private string _statsSearch = string.Empty; private string _confirmCancelContractId = string.Empty; private float _refreshTimer; private GUISkin? _bountySkin; private GUISkin? _baseSkin; private readonly List _themeTextures = new List(); private readonly Texture2D?[] _tabBackgrounds = (Texture2D?[])(object)new Texture2D[4]; private GUIStyle? _inventoryTitleStyle; private GUIStyle? _inventorySelectedTitleStyle; private GUIStyle? _inventoryDetailStyle; private GUIStyle? _rewardHeaderStyle; private GUIStyle? _rewardDetailStyle; private GUIStyle? _tooltipStyle; private static readonly string[] TabBackgroundResourceNames = new string[4] { "BountyHuntercontract.assets.ui.bounty_publish_background.jpg", "BountyHuntercontract.assets.ui.bounty_contracts_background.jpg", "BountyHuntercontract.assets.ui.bounty_players_background.jpg", "BountyHuntercontract.assets.ui.bounty_registry_background.jpg" }; internal static bool IsVisible => _visible; private static string L(string spanish, string english) { return BountyLocalization.Text(spanish, english); } private static string LF(string spanish, string english, params object[] values) { return string.Format(L(spanish, english), values); } private void Awake() { _instance = this; LoadTabBackgrounds(); _durationMinutesText = Math.Max(0, BountyHuntercontractPlugin.DefaultContractDurationMinutes.Value).ToString(); LoadWindowLayout(); } private void OnDestroy() { ReleaseThemeResources(); ReleaseTabBackgrounds(); if ((Object)(object)_instance == (Object)(object)this) { _instance = null; } } internal static void Open() { if (!((Object)(object)_instance == (Object)null)) { _instance.Show(); } } internal static void SetStatus(string message, bool success) { _status = message ?? string.Empty; _statusSuccess = success; BountyDiagnostics.Detailed("UI", "Status updated success=" + success + " message='" + _status + "'."); } internal static void NotifyDepositedReward(BountyRewardItem reward, int bondCoins) { SetStatus(L("Recompensa depositada: ", "Reward deposited: ") + BountyItemCodec.Describe(reward) + L(". Fianza depositada: ", ". Bond deposited: ") + bondCoins + L(" Coins. Publicando contrato...", " Coins. Publishing contract..."), success: true); } private void Show() { if (!_visible) { _visible = true; Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; if ((Object)(object)GameCamera.instance != (Object)null) { GameCamera.instance.m_mouseCapture = false; } _status = string.Empty; _tooltipForFrame = string.Empty; _refreshTimer = 999f; BountyDiagnostics.Info("UI", "Bounty board UI opened by " + BountyDiagnostics.LocalIdentity() + "."); BountyClient.SendHello(); BountyClient.RequestState(); if (_tab == 3) { BountyClient.RequestHistory(); } } } private void Hide() { if (_visible) { _visible = false; _confirmCancelContractId = string.Empty; _resizingWindow = false; _tooltipForFrame = string.Empty; SaveWindowLayout(); Cursor.lockState = (CursorLockMode)1; Cursor.visible = false; if ((Object)(object)GameCamera.instance != (Object)null) { GameCamera.instance.m_mouseCapture = true; } BountyDiagnostics.Info("UI", "Bounty board UI closed."); } } private void Update() { if (_visible && Input.GetKeyDown((KeyCode)27)) { Hide(); } if (_visible) { Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; if ((Object)(object)GameCamera.instance != (Object)null) { GameCamera.instance.m_mouseCapture = false; } _refreshTimer += Time.unscaledDeltaTime; if (_tab == 3 && !BountyClient.HistoryLoaded && _refreshTimer >= 0.75f) { _refreshTimer = 0f; BountyClient.RequestHistory(); } } } private void OnGUI() { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Expected O, but got Unknown //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) if (!_visible) { return; } GUISkin skin = GUI.skin; EnsureBountySkin(skin); if ((Object)(object)_bountySkin != (Object)null) { GUI.skin = _bountySkin; } try { _tooltipForFrame = string.Empty; ClampWindowToScreen(); Rect windowRect = GUI.Window(831472, _windowRect, new WindowFunction(DrawWindow), L("Tablón de contratos de asesinato", "Bounty Contract Board")); if (_requestedWindowSize.x > 0f && _requestedWindowSize.y > 0f) { ((Rect)(ref windowRect)).width = _requestedWindowSize.x; ((Rect)(ref windowRect)).height = _requestedWindowSize.y; } _windowRect = windowRect; ClampWindowToScreen(); DrawTooltipOverlay(); } finally { GUI.skin = skin; } } private void EnsureBountySkin(GUISkin sourceSkin) { //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Expected O, but got Unknown //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_0262: Unknown result type (might be due to invalid IL or missing references) //IL_0267: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Unknown result type (might be due to invalid IL or missing references) //IL_02aa: Unknown result type (might be due to invalid IL or missing references) //IL_02c3: Unknown result type (might be due to invalid IL or missing references) //IL_02dc: Unknown result type (might be due to invalid IL or missing references) //IL_02f5: Unknown result type (might be due to invalid IL or missing references) //IL_02fa: Unknown result type (might be due to invalid IL or missing references) //IL_0311: Unknown result type (might be due to invalid IL or missing references) //IL_033d: Unknown result type (might be due to invalid IL or missing references) //IL_0356: Unknown result type (might be due to invalid IL or missing references) //IL_036f: Unknown result type (might be due to invalid IL or missing references) //IL_0388: Unknown result type (might be due to invalid IL or missing references) //IL_038d: Unknown result type (might be due to invalid IL or missing references) //IL_03aa: Unknown result type (might be due to invalid IL or missing references) //IL_03b4: Expected O, but got Unknown //IL_03c2: Unknown result type (might be due to invalid IL or missing references) //IL_03c7: Unknown result type (might be due to invalid IL or missing references) //IL_03cc: Unknown result type (might be due to invalid IL or missing references) //IL_03d1: Unknown result type (might be due to invalid IL or missing references) //IL_03d6: Unknown result type (might be due to invalid IL or missing references) //IL_040b: Unknown result type (might be due to invalid IL or missing references) //IL_0415: Expected O, but got Unknown //IL_0437: Unknown result type (might be due to invalid IL or missing references) //IL_0450: Unknown result type (might be due to invalid IL or missing references) //IL_0469: Unknown result type (might be due to invalid IL or missing references) //IL_0482: Unknown result type (might be due to invalid IL or missing references) //IL_0487: Unknown result type (might be due to invalid IL or missing references) //IL_04bc: Unknown result type (might be due to invalid IL or missing references) //IL_04c6: Expected O, but got Unknown //IL_04d3: Unknown result type (might be due to invalid IL or missing references) //IL_04dd: Expected O, but got Unknown //IL_04e9: Unknown result type (might be due to invalid IL or missing references) //IL_0500: Unknown result type (might be due to invalid IL or missing references) //IL_053d: Unknown result type (might be due to invalid IL or missing references) //IL_0542: Unknown result type (might be due to invalid IL or missing references) //IL_054a: Unknown result type (might be due to invalid IL or missing references) //IL_0557: Expected O, but got Unknown //IL_055e: Unknown result type (might be due to invalid IL or missing references) //IL_0563: Unknown result type (might be due to invalid IL or missing references) //IL_0570: Expected O, but got Unknown //IL_057c: Unknown result type (might be due to invalid IL or missing references) //IL_0581: Unknown result type (might be due to invalid IL or missing references) //IL_0589: Unknown result type (might be due to invalid IL or missing references) //IL_05a9: Unknown result type (might be due to invalid IL or missing references) //IL_05b6: Expected O, but got Unknown //IL_05c2: Unknown result type (might be due to invalid IL or missing references) //IL_05c7: Unknown result type (might be due to invalid IL or missing references) //IL_05cf: Unknown result type (might be due to invalid IL or missing references) //IL_05d7: Unknown result type (might be due to invalid IL or missing references) //IL_05e4: Expected O, but got Unknown //IL_05f0: Unknown result type (might be due to invalid IL or missing references) //IL_05f5: Unknown result type (might be due to invalid IL or missing references) //IL_05fd: Unknown result type (might be due to invalid IL or missing references) //IL_060a: Expected O, but got Unknown //IL_0616: Unknown result type (might be due to invalid IL or missing references) //IL_061b: Unknown result type (might be due to invalid IL or missing references) //IL_0623: Unknown result type (might be due to invalid IL or missing references) //IL_062b: Unknown result type (might be due to invalid IL or missing references) //IL_0632: Unknown result type (might be due to invalid IL or missing references) //IL_063c: Expected O, but got Unknown //IL_0642: Expected O, but got Unknown //IL_052a: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_bountySkin != (Object)null) || !((Object)(object)_baseSkin == (Object)(object)sourceSkin)) { ReleaseThemeResources(); _baseSkin = sourceSkin; _bountySkin = Object.Instantiate(sourceSkin); ((Object)_bountySkin).name = "BHC_WarmWoodSkin"; Color val = default(Color); ((Color)(ref val))..ctor(0.97f, 0.91f, 0.8f, 1f); Color textColor = default(Color); ((Color)(ref textColor))..ctor(1f, 0.72f, 0.28f, 1f); Color textColor2 = default(Color); ((Color)(ref textColor2))..ctor(0.86f, 0.78f, 0.66f, 1f); _bountySkin.window = CreateFilledStyle(sourceSkin.window, new Color(0.18f, 0.1f, 0.05f, 0.48f), new Color(0.2f, 0.12f, 0.06f, 0.52f), new Color(0.16f, 0.08f, 0.04f, 0.58f), new Color(0.18f, 0.1f, 0.05f, 0.48f), textColor); _bountySkin.window.fontStyle = (FontStyle)1; _bountySkin.window.border = new RectOffset(0, 0, 0, 0); _bountySkin.box = CreateFilledStyle(sourceSkin.box, new Color(0.1f, 0.055f, 0.025f, 0.08f), new Color(0.15f, 0.08f, 0.035f, 0.16f), new Color(0.1f, 0.05f, 0.02f, 0.22f), new Color(0.18f, 0.1f, 0.04f, 0.16f), val); _bountySkin.scrollView = CreateFilledStyle(sourceSkin.scrollView, Color.clear, Color.clear, Color.clear, Color.clear, val); _bountySkin.button = CreateFilledStyle(sourceSkin.button, new Color(0.13f, 0.07f, 0.03f, 0.28f), new Color(0.25f, 0.13f, 0.04f, 0.46f), new Color(0.38f, 0.19f, 0.04f, 0.58f), new Color(0.5f, 0.26f, 0.035f, 0.6f), val); SetOnStateTextColors(_bountySkin.button, Color.white); _bountySkin.toggle = CreateFilledStyle(sourceSkin.toggle, new Color(0.2f, 0.115f, 0.06f, 0.98f), new Color(0.29f, 0.16f, 0.075f, 1f), new Color(0.4f, 0.21f, 0.06f, 1f), new Color(0.58f, 0.31f, 0.055f, 1f), val); SetOnStateTextColors(_bountySkin.toggle, Color.white); _bountySkin.textField = CreateFilledStyle(sourceSkin.textField, new Color(0.06f, 0.03f, 0.012f, 0.34f), new Color(0.11f, 0.055f, 0.02f, 0.48f), new Color(0.16f, 0.075f, 0.02f, 0.58f), new Color(0.16f, 0.075f, 0.02f, 0.58f), val); _bountySkin.textArea = new GUIStyle(_bountySkin.textField); _bountySkin.verticalScrollbar = CreateFilledStyle(sourceSkin.verticalScrollbar, Color.clear, Color.clear, Color.clear, Color.clear, Color.clear); _bountySkin.verticalScrollbar.fixedWidth = 7f; _bountySkin.verticalScrollbar.margin = new RectOffset(3, 2, 0, 0); _bountySkin.verticalScrollbarThumb = CreateFilledStyle(sourceSkin.verticalScrollbarThumb, new Color(0.82f, 0.61f, 0.28f, 0.58f), new Color(1f, 0.76f, 0.32f, 0.78f), new Color(1f, 0.82f, 0.4f, 0.92f), new Color(0.82f, 0.61f, 0.28f, 0.68f), Color.clear); _bountySkin.verticalScrollbarThumb.fixedWidth = 7f; _bountySkin.verticalScrollbarThumb.margin = new RectOffset(0, 0, 0, 0); _bountySkin.label = new GUIStyle(sourceSkin.label); SetAllTextColors(_bountySkin.label, val); if (_bountySkin.label.normal.textColor.a <= 0f) { _bountySkin.label.normal.textColor = textColor2; } _inventoryTitleStyle = new GUIStyle(_bountySkin.label) { alignment = (TextAnchor)0, clipping = (TextClipping)1 }; _inventorySelectedTitleStyle = new GUIStyle(_inventoryTitleStyle) { fontStyle = (FontStyle)1 }; _inventoryDetailStyle = new GUIStyle(_bountySkin.label) { alignment = (TextAnchor)6, fontSize = Math.Max(10, _bountySkin.label.fontSize - 1), clipping = (TextClipping)1 }; _rewardHeaderStyle = new GUIStyle(_bountySkin.label) { fontStyle = (FontStyle)1, alignment = (TextAnchor)0, clipping = (TextClipping)1 }; _rewardDetailStyle = new GUIStyle(_bountySkin.label) { alignment = (TextAnchor)6, clipping = (TextClipping)1 }; _tooltipStyle = new GUIStyle(_bountySkin.box) { alignment = (TextAnchor)0, wordWrap = true, padding = new RectOffset(10, 10, 8, 8) }; } } private GUIStyle CreateFilledStyle(GUIStyle source, Color normal, Color hover, Color active, Color selected, Color textColor) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected O, but got Unknown //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0022: 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_0060: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Expected O, but got Unknown //IL_00c3: Unknown result type (might be due to invalid IL or missing references) GUIStyle val = new GUIStyle(source); val.normal.background = CreateThemeTexture(normal); val.hover.background = CreateThemeTexture(hover); val.active.background = CreateThemeTexture(active); val.focused.background = val.hover.background; val.onNormal.background = CreateThemeTexture(selected); val.onHover.background = val.onNormal.background; val.onActive.background = val.active.background; val.onFocused.background = val.onNormal.background; val.border = new RectOffset(0, 0, 0, 0); SetAllTextColors(val, textColor); return val; } private Texture2D CreateThemeTexture(Color color) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_0056: 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) Texture2D val = new Texture2D(4, 4, (TextureFormat)4, false); ((Object)val).name = "BHC_UI_" + _themeTextures.Count; ((Object)val).hideFlags = (HideFlags)61; ((Texture)val).filterMode = (FilterMode)1; ((Texture)val).wrapMode = (TextureWrapMode)1; Color[] array = (Color[])(object)new Color[16]; for (int i = 0; i < array.Length; i++) { array[i] = color; } val.SetPixels(array); val.Apply(false, true); _themeTextures.Add(val); return val; } private static void SetAllTextColors(GUIStyle style, Color color) { //IL_0007: 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_0021: 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_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0048: 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_0062: Unknown result type (might be due to invalid IL or missing references) style.normal.textColor = color; style.hover.textColor = color; style.active.textColor = color; style.focused.textColor = color; style.onNormal.textColor = color; style.onHover.textColor = color; style.onActive.textColor = color; style.onFocused.textColor = color; } private static void SetOnStateTextColors(GUIStyle style, Color color) { //IL_0007: 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_0021: 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) style.onNormal.textColor = color; style.onHover.textColor = color; style.onActive.textColor = color; style.onFocused.textColor = color; } private void ReleaseThemeResources() { if ((Object)(object)_bountySkin != (Object)null) { Object.Destroy((Object)(object)_bountySkin); _bountySkin = null; } foreach (Texture2D themeTexture in _themeTextures) { if ((Object)(object)themeTexture != (Object)null) { Object.Destroy((Object)(object)themeTexture); } } _themeTextures.Clear(); _baseSkin = null; _inventoryTitleStyle = null; _inventorySelectedTitleStyle = null; _inventoryDetailStyle = null; _rewardHeaderStyle = null; _rewardDetailStyle = null; _tooltipStyle = null; } private void ReleaseTabBackgrounds() { for (int i = 0; i < _tabBackgrounds.Length; i++) { if ((Object)(object)_tabBackgrounds[i] != (Object)null) { Object.Destroy((Object)(object)_tabBackgrounds[i]); } _tabBackgrounds[i] = null; } } private void LoadTabBackgrounds() { //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Expected O, but got Unknown Assembly executingAssembly = Assembly.GetExecutingAssembly(); for (int i = 0; i < TabBackgroundResourceNames.Length; i++) { using Stream stream = executingAssembly.GetManifestResourceStream(TabBackgroundResourceNames[i]); if (stream == null) { BountyDiagnostics.Warning("UI", "Missing tab background resource '" + TabBackgroundResourceNames[i] + "'."); continue; } byte[] array = new byte[stream.Length]; int num; for (int j = 0; j < array.Length; j += num) { num = stream.Read(array, j, array.Length - j); if (num == 0) { break; } } Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false) { name = "BHC_TabBackground_" + i, hideFlags = (HideFlags)61, filterMode = (FilterMode)1, wrapMode = (TextureWrapMode)1 }; if (!LoadPng(val, array)) { Object.Destroy((Object)(object)val); BountyDiagnostics.Warning("UI", "Could not decode tab background '" + TabBackgroundResourceNames[i] + "'."); } else { _tabBackgrounds[i] = val; } } } private static bool LoadPng(Texture2D texture, byte[] bytes) { return (Type.GetType("UnityEngine.ImageConversion, UnityEngine.ImageConversionModule")?.GetMethod("LoadImage", new Type[3] { typeof(Texture2D), typeof(byte[]), typeof(bool) }))?.Invoke(null, new object[3] { texture, bytes, true }) as bool? == true; } private void DrawWindow(int windowId) { //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_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: 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) //IL_0051: 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_0096: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Expected O, but got Unknown //IL_0300: Unknown result type (might be due to invalid IL or missing references) Texture2D val = ((_tab >= 0 && _tab < _tabBackgrounds.Length) ? _tabBackgrounds[_tab] : null); if ((Object)(object)val != (Object)null) { Color color = GUI.color; GUI.color = new Color(1.12f, 1.08f, 1f, 1f); GUI.DrawTexture(new Rect(4f, 28f, ((Rect)(ref _windowRect)).width - 8f, ((Rect)(ref _windowRect)).height - 32f), (Texture)(object)val, (ScaleMode)0, true); GUI.color = color; } GUILayout.BeginVertical(Array.Empty()); GUILayout.Space(4f); GUIStyle val2 = new GUIStyle(GUI.skin.label) { wordWrap = true, alignment = (TextAnchor)0 }; GUILayout.Label(L("La recompensa queda depositada. El tiempo del contrato solo avanza mientras el objetivo está conectado.", "The reward is held in escrow. Contract time only advances while the target is online."), val2, Array.Empty()); GUILayout.Space(4f); GUILayout.BeginHorizontal(Array.Empty()); DrawTabButton(0, L("Publicar", "Publish")); DrawTabButton(1, L("Contratos y cobros", "Contracts and claims")); DrawTabButton(2, L("Jugadores", "Players")); DrawTabButton(3, L("Registro global", "Global registry")); GUILayout.EndHorizontal(); GUILayout.Space(8f); switch (_tab) { case 0: DrawCreationTab(); break; case 1: DrawContractsTab(); break; case 2: DrawPlayerStatsTab(); break; default: DrawGlobalRegistryTab(); break; } GUILayout.FlexibleSpace(); if (!string.IsNullOrEmpty(_status)) { GUIStyle val3 = new GUIStyle(GUI.skin.box) { alignment = (TextAnchor)4, wordWrap = true, fontStyle = (FontStyle)1 }; GUILayout.Box((_statusSuccess ? "✓ " : "⚠ ") + _status, val3, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(44f) }); } GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button(L("Actualizar", "Refresh"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(34f) })) { BountyClient.RequestState(); if (_tab == 3) { BountyClient.RequestHistory(force: true); } } if (GUILayout.Button(L("Cerrar", "Close"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(34f) })) { Hide(); } GUILayout.EndHorizontal(); GUILayout.EndVertical(); DrawResizeHandle(); if (!_resizingWindow) { GUI.DragWindow(new Rect(0f, 0f, ((Rect)(ref _windowRect)).width - 34f, 28f)); } } private void DrawTabButton(int tab, string label) { if (GUILayout.Toggle(_tab == tab, label, GUI.skin.button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(32f) }) && _tab != tab) { _tab = tab; _tooltipForFrame = string.Empty; _refreshTimer = 999f; if (tab == 3) { BountyClient.RequestHistory(); } BountyDiagnostics.Detailed("UI", "Switched bounty board tab=" + tab + "."); } } private void DrawCreationTab() { //IL_0010: 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_002e: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0404: Unknown result type (might be due to invalid IL or missing references) //IL_0409: Unknown result type (might be due to invalid IL or missing references) //IL_0411: Unknown result type (might be due to invalid IL or missing references) //IL_0419: Unknown result type (might be due to invalid IL or missing references) //IL_0420: Unknown result type (might be due to invalid IL or missing references) //IL_042a: Expected O, but got Unknown //IL_042d: Expected O, but got Unknown //IL_0447: Unknown result type (might be due to invalid IL or missing references) //IL_046c: Expected O, but got Unknown _creationPageScroll = GUILayout.BeginScrollView(_creationPageScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(GetMainContentHeight()) }); long localId = BountyClient.LocalPlayerId; BountyPlayerInfo[] array = (from player in BountyClient.OnlinePlayers where player.Connected && player.PlayerId != localId orderby player.Name select player).ToArray(); if (_selectedTargetId != 0L && !array.Any((BountyPlayerInfo target) => target.PlayerId == _selectedTargetId)) { _selectedTargetId = 0L; } float num = Mathf.Clamp(((Rect)(ref _windowRect)).height * 0.27f, 150f, 245f); float num2 = Mathf.Clamp(((Rect)(ref _windowRect)).width * 0.36f, 245f, 350f); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(num2) }); GUILayout.Label(L("1. Jugador objetivo:", "1. Target player:"), Array.Empty()); _playerScroll = GUILayout.BeginScrollView(_playerScroll, GUI.skin.box, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(num) }); if (array.Length == 0) { GUILayout.Label(L("No hay otros jugadores disponibles.", "No other players are available."), Array.Empty()); } BountyPlayerInfo[] array2 = array; foreach (BountyPlayerInfo bountyPlayerInfo in array2) { if (GUILayout.Button(((_selectedTargetId == bountyPlayerInfo.PlayerId) ? "▶ " : string.Empty) + bountyPlayerInfo.Name, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(29f) })) { _selectedTargetId = bountyPlayerInfo.PlayerId; BountyDiagnostics.Info("UI", "Selected bounty target " + bountyPlayerInfo.Name + "#" + bountyPlayerInfo.PlayerId + "."); } } GUILayout.EndScrollView(); GUILayout.EndVertical(); GUILayout.Space(10f); GUILayout.BeginVertical(Array.Empty()); GUILayout.Label(L("2. Objeto de recompensa de tu inventario:", "2. Reward item from your inventory:"), Array.Empty()); DrawInventoryRewardSelector(num); GUILayout.EndVertical(); GUILayout.EndHorizontal(); GUILayout.Space(8f); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(L("Cantidad a depositar:", "Amount to deposit:"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(170f) }); _rewardAmountText = GUILayout.TextField(_rewardAmountText, 9, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) }); if (_selectedRewardItem != null) { int num4 = BountyItemCodec.CountEligibleMatching(Player.m_localPlayer, _selectedRewardItem); GUILayout.Label(L("Disponibles iguales: ", "Matching available: ") + num4, Array.Empty()); } GUILayout.EndHorizontal(); if (_selectedRewardItem != null) { int result; int requestedStack = ((!int.TryParse(_rewardAmountText, out result)) ? 1 : Math.Max(1, result)); DrawRewardCard(L("Recompensa seleccionada", "Selected reward"), BountyItemCodec.FromItemData(_selectedRewardItem, requestedStack)); } int amount = Math.Max(0, BountyHuntercontractPlugin.CancellationBondCoins.Value); int num5 = ((!((Object)(object)Player.m_localPlayer == (Object)null)) ? BountyItemCodec.CountCoins(((Humanoid)Player.m_localPlayer).GetInventory()) : 0); DrawBondCard(L("Fianza obligatoria", "Required bond"), amount, L("Disponibles: ", "Available: ") + num5); GUIStyle val = new GUIStyle(GUI.skin.box) { wordWrap = true, alignment = (TextAnchor)0, padding = new RectOffset(9, 9, 7, 7) }; string text = L("La fianza se devuelve cuando el contrato termina normalmente. Solo se pierde si cancelas después de que un cazador lo haya aceptado.", "The bond is returned when the contract ends normally. It is only lost if you cancel after a hunter has accepted."); float num6 = Mathf.Max(42f, val.CalcHeight(new GUIContent(text), Mathf.Max(240f, ((Rect)(ref _windowRect)).width - 46f))); GUILayout.Box(text, val, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Height(num6), GUILayout.ExpandWidth(true) }); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(L("3. Máximo de cazadores:", "3. Maximum hunters:"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(170f) }); for (int num7 = 1; num7 <= 5; num7++) { if (GUILayout.Toggle(_maximumHunters == num7, num7.ToString(), GUI.skin.button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(58f) })) { _maximumHunters = num7; } } GUILayout.EndHorizontal(); GUILayout.Space(6f); GUILayout.Label(L("4. Duración del contrato:", "4. Contract duration:"), Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); DrawDurationPreset(30, "30m"); DrawDurationPreset(60, "1h"); DrawDurationPreset(180, "3h"); DrawDurationPreset(360, "6h"); DrawDurationPreset(720, "12h"); DrawDurationPreset(1440, L("1 día", "1 day")); DrawDurationPreset(4320, L("3 días", "3 days")); DrawDurationPreset(10080, L("7 días", "7 days")); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(L("Duración personalizada (minutos):", "Custom duration (minutes):"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(225f) }); _durationMinutesText = GUILayout.TextField(_durationMinutesText, 7, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) }); if (BountyHuntercontractPlugin.AllowPermanentContracts.Value == BountyHuntercontractPlugin.Toggle.On) { GUILayout.Label(L("0 = permanente", "0 = permanent"), Array.Empty()); } GUILayout.EndHorizontal(); if (BountyHuntercontractPlugin.EnableFiveSecondTestContracts.Value == BountyHuntercontractPlugin.Toggle.On) { int num8 = Math.Max(1, BountyHuntercontractPlugin.TestContractDurationSeconds.Value); GUILayout.Box(L("MODO DE PRUEBA ACTIVO: los contratos no permanentes durarán ", "TEST MODE ACTIVE: non-permanent contracts will last ") + num8 + L(" segundos reales mientras el objetivo esté conectado.", " real seconds while the target is online."), Array.Empty()); } GUILayout.Space(8f); int num9 = BountyClient.Contracts.Count((BountyContract contract) => contract.CreatorPlayerId == localId && (contract.Status == BountyStatus.Active || contract.Status == BountyStatus.Completed)); int num10 = Math.Max(1, BountyHuntercontractPlugin.MaximumOpenContractsPerCreator.Value); GUILayout.Label(L("Tus contratos abiertos: ", "Your open contracts: ") + num9 + "/" + num10 + L(" (los finalizados pendientes de cobro también ocupan un cupo)", " (completed contracts awaiting claims also use a slot)"), Array.Empty()); GUILayout.Space(10f); string reason; bool flag = _selectedRewardItem != null && (Object)(object)Player.m_localPlayer != (Object)null && BountyItemCodec.CanUseAsReward(Player.m_localPlayer, _selectedRewardItem, out reason); GUI.enabled = _selectedTargetId != 0 && flag && num9 < num10; if (GUILayout.Button(L("Depositar recompensa + fianza y publicar", "Deposit reward + bond and publish"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(43f) })) { TryPublishSelectedContract(); } GUI.enabled = true; GUILayout.EndScrollView(); } private void DrawInventoryRewardSelector(float selectorHeight) { //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_013e: 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) Player player = Player.m_localPlayer; if ((Object)(object)player == (Object)null) { GUILayout.Box(L("El jugador local todavía no está disponible.", "The local player is not available yet."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(selectorHeight) }); return; } string reason3; List list = (from item in ((Humanoid)player).GetInventory().GetAllItems() where item != null && (Object)(object)item.m_dropPrefab != (Object)null && item.m_stack > 0 orderby (!BountyItemCodec.CanUseAsReward(player, item, out reason3)) ? 1 : 0, BountyItemCodec.GetLocalizedItemName(item), item.m_quality descending select item).ToList(); if (_selectedRewardItem != null && (!list.Contains(_selectedRewardItem) || !BountyItemCodec.CanUseAsReward(player, _selectedRewardItem, out string _))) { _selectedRewardItem = null; } _rewardScroll = GUILayout.BeginScrollView(_rewardScroll, GUI.skin.box, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(selectorHeight) }); if (list.Count == 0) { GUILayout.Label(L("No tienes objetos para depositar.", "You have no items to deposit."), Array.Empty()); } foreach (ItemData item in list) { string reason2; bool allowed = BountyItemCodec.CanUseAsReward(player, item, out reason2); DrawInventoryItemButton(item, _selectedRewardItem == item, allowed, reason2); GUILayout.Space(2f); } GUILayout.EndScrollView(); } private void DrawDurationPreset(int minutes, string label) { if (GUILayout.Button(label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(28f) })) { _durationMinutesText = minutes.ToString(); } } private void TryPublishSelectedContract() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || _selectedRewardItem == null) { SetStatus(L("Selecciona un objeto de recompensa.", "Select a reward item."), success: false); return; } if (!BountyItemCodec.CanUseAsReward(localPlayer, _selectedRewardItem, out string reason)) { _selectedRewardItem = null; SetStatus(reason, success: false); return; } if (!int.TryParse(_rewardAmountText, out var result) || result <= 0) { SetStatus(L("Debes escribir una cantidad válida para la recompensa.", "Enter a valid reward amount."), success: false); return; } int num = BountyItemCodec.CountEligibleMatching(localPlayer, _selectedRewardItem); if (result > num) { SetStatus(L("Solo tienes ", "You only have ") + num + L(" unidades exactamente iguales a la seleccionada.", " matching units of the selected item."), success: false); return; } if (result > Math.Max(1, BountyHuntercontractPlugin.MaximumRewardItemAmount.Value)) { SetStatus(L("La cantidad supera el máximo configurado por el servidor.", "The amount exceeds the server-configured maximum."), success: false); return; } if (!int.TryParse(_durationMinutesText, out var result2)) { SetStatus(L("Debes escribir una duración válida.", "Enter a valid duration."), success: false); return; } if (!ValidateDuration(result2, out string error)) { SetStatus(error, success: false); return; } BountyRewardItem reward = BountyItemCodec.FromItemData(_selectedRewardItem, result); int num2 = Math.Max(0, BountyHuntercontractPlugin.CancellationBondCoins.Value); SetStatus(L("Comprobando y depositando ", "Checking and depositing ") + BountyItemCodec.Describe(reward) + L(" + fianza de ", " + bond of ") + num2 + " Coins...", success: true); BountyDiagnostics.Info("UI", "Publish button pressed target=" + _selectedTargetId + " reward='" + BountyItemCodec.Describe(reward, localized: false) + "' maxHunters=" + _maximumHunters + " durationMinutes=" + result2 + " bondCoins=" + num2 + "."); BountyClient.RequestCreate(_selectedTargetId, reward, _maximumHunters, result2); } private static bool ValidateDuration(int durationMinutes, out string error) { error = string.Empty; if (durationMinutes == 0 && BountyHuntercontractPlugin.AllowPermanentContracts.Value == BountyHuntercontractPlugin.Toggle.On) { return true; } int num = Math.Max(1, BountyHuntercontractPlugin.MinimumContractDurationMinutes.Value); int num2 = Math.Max(num, BountyHuntercontractPlugin.MaximumContractDurationMinutes.Value); if (durationMinutes < num || durationMinutes > num2) { error = L("La duración debe estar entre ", "Duration must be between ") + num + L(" y ", " and ") + num2 + L(" minutos.", " minutes."); return false; } return true; } private void DrawContractsTab() { //IL_0080: 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_009e: Unknown result type (might be due to invalid IL or missing references) long localPlayerId = BountyClient.LocalPlayerId; BountyContract[] array = (from contract in BountyClient.Contracts where contract.Status == BountyStatus.Active || contract.Status == BountyStatus.Completed orderby (contract.Status != BountyStatus.Completed) ? 1 : 0, contract.CreatedUtcTicks descending select contract).ToArray(); _contractScroll = GUILayout.BeginScrollView(_contractScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(GetMainContentHeight()) }); if (array.Length == 0) { GUILayout.Box(L("No existen contratos activos ni recompensas pendientes de cobro.", "There are no active contracts or pending rewards."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(55f) }); } BountyContract[] array2 = array; foreach (BountyContract bountyContract in array2) { GUILayout.BeginVertical(GUI.skin.box, Array.Empty()); GUILayout.Label(((bountyContract.Status == BountyStatus.Completed) ? L("CONTRATO FINALIZADO: ", "COMPLETED CONTRACT: ") : L("OBJETIVO: ", "TARGET: ")) + bountyContract.TargetName, Array.Empty()); DrawRewardCard((bountyContract.Status == BountyStatus.Completed) ? L("Recompensa pendiente / cobrada", "Pending / claimed reward") : L("Recompensa del contrato", "Contract reward"), bountyContract.Reward, bountyContract.RewardClaimed ? L("Cobrada", "Claimed") : L("Depositada", "Deposited")); GUILayout.Label(L("Creador: ", "Creator: ") + bountyContract.CreatorName, Array.Empty()); DrawBondCard(L("Fianza", "Bond"), bountyContract.BondCoins, bountyContract.BondClaimed ? L("Recuperada", "Returned") : L("Depositada", "Deposited")); GUILayout.Label(L("Cazadores: ", "Hunters: ") + bountyContract.ActiveHunterCount + L(" activos, ", " active, ") + bountyContract.EliminatedHunterCount + L(" eliminados, ", " eliminated, ") + bountyContract.Hunters.Count + "/" + bountyContract.MaximumHunters + L(" cupos usados", " slots used"), Array.Empty()); if (bountyContract.Status == BountyStatus.Active && bountyContract.DurationMinutes > 0) { TimeSpan displayedRemaining = GetDisplayedRemaining(bountyContract); GUILayout.Label(L("Tiempo activo restante: ", "Active time remaining: ") + FormatRemaining(displayedRemaining), Array.Empty()); DrawDurationProgress(bountyContract, displayedRemaining); if (IsFiveSecondTestContract(bountyContract) && bountyContract.Hunters.Count == 0) { GUILayout.Label(L("MODO PRUEBA: el contador comenzará cuando el primer cazador acepte.", "TEST MODE: the timer will start when the first hunter accepts."), Array.Empty()); } else { GUILayout.Label(bountyContract.DurationTimerRunning ? L("El contador está avanzando porque el objetivo está conectado.", "The timer is running because the target is online.") : L("PAUSADO: el objetivo está desconectado.", "PAUSED: the target is offline."), Array.Empty()); } } else if (bountyContract.Status == BountyStatus.Active) { GUILayout.Label(L("Duración: permanente", "Duration: permanent"), Array.Empty()); } BountyHunterInfo[] array3 = bountyContract.Hunters.Where((BountyHunterInfo item) => !item.Eliminated).ToArray(); BountyHunterInfo[] array4 = bountyContract.Hunters.Where((BountyHunterInfo item) => item.Eliminated).ToArray(); if (array3.Length != 0) { GUILayout.Label(L("Cazadores activos: ", "Active hunters: ") + string.Join(", ", array3.Select((BountyHunterInfo item) => item.Name).ToArray()), Array.Empty()); } if (array4.Length != 0) { GUILayout.Label(L("Eliminados por el objetivo: ", "Eliminated by the target: ") + string.Join(", ", array4.Select((BountyHunterInfo item) => item.Name).ToArray()), Array.Empty()); } bool flag = bountyContract.CreatorPlayerId == localPlayerId; bool flag2 = bountyContract.TargetPlayerId == localPlayerId; bool flag3 = bountyContract.HasHunter(localPlayerId); bool flag4 = bountyContract.HasActiveHunter(localPlayerId); if (bountyContract.Status == BountyStatus.Completed) { GUILayout.Box(OutcomeText(bountyContract.Outcome) + L(" | Ganador: ", " | Winner: ") + bountyContract.WinnerName, Array.Empty()); if (!bountyContract.RewardClaimed) { if (bountyContract.WinnerPlayerId == localPlayerId) { GUILayout.Box(L("La recompensa está reservada para ti. Cóbrala aquí en el tablón.", "The reward is reserved for you. Claim it here at the board."), Array.Empty()); if (GUILayout.Button(L("COBRAR RECOMPENSA", "CLAIM REWARD"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(42f) })) { SetStatus(L("Solicitando el cobro de la recompensa...", "Requesting reward claim..."), success: true); BountyDiagnostics.Info("UI", "Claim button pressed contract=" + bountyContract.ContractId + "."); BountyClient.RequestClaim(bountyContract.ContractId); } } else { GUILayout.Box(L("La recompensa está reservada para ", "The reward is reserved for ") + bountyContract.WinnerName + ".", Array.Empty()); } } else { GUILayout.Box(L("La recompensa principal ya fue cobrada.", "The main reward has already been claimed."), Array.Empty()); } if (bountyContract.BondCoins > 0) { if (!bountyContract.BondClaimed && flag) { GUILayout.Box(L("El contrato terminó normalmente. Puedes recuperar tu fianza.", "The contract ended normally. You can recover your bond."), Array.Empty()); if (GUILayout.Button(L("RECUPERAR FIANZA", "RECOVER BOND") + ": " + bountyContract.BondCoins + " COINS", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(38f) })) { SetStatus(L("Solicitando la devolución de la fianza...", "Requesting bond return..."), success: true); BountyDiagnostics.Info("UI", "Bond claim button pressed contract=" + bountyContract.ContractId + "."); BountyClient.RequestClaimBond(bountyContract.ContractId); } } else if (!bountyContract.BondClaimed) { GUILayout.Box(L("La fianza de ", "The ") + bountyContract.BondCoins + L(" Coins está reservada para el creador ", " Coins bond is reserved for the creator ") + bountyContract.CreatorName + ".", Array.Empty()); } else { GUILayout.Box(L("La fianza ya fue recuperada por el creador.", "The creator has already recovered the bond."), Array.Empty()); } } } else if (flag2) { if (bountyContract.ActiveHunterCount == 0) { GUILayout.Box((bountyContract.Hunters.Count >= bountyContract.MaximumHunters) ? L("Has eliminado a todos los cazadores; el servidor está finalizando el contrato.", "You eliminated every hunter; the server is completing the contract.") : (L("Eres el objetivo. Aún quedan ", "You are the target. There are still ") + (bountyContract.MaximumHunters - bountyContract.Hunters.Count) + L(" cupos disponibles.", " available slots.")), Array.Empty()); } else { GUILayout.Box(L("¡ERES EL OBJETIVO! El PvP está forzado. Si eliminas a todos los cazadores disponibles, podrás cobrar la recompensa.", "YOU ARE THE TARGET! PvP is forced. If you eliminate every available hunter, you can claim the reward."), Array.Empty()); } } else if (flag4) { GUILayout.Box(L("Contrato activo: puedes rastrear y atacar al objetivo.", "Active contract: you can track and attack the target."), Array.Empty()); if (GUILayout.Button(L("Abandonar contrato", "Leave contract"), Array.Empty())) { SetStatus(L("Enviando solicitud para abandonar el contrato...", "Sending request to leave the contract..."), success: true); BountyClient.RequestLeave(bountyContract.ContractId); } } else if (flag3) { GUILayout.Box(L("El objetivo te eliminó. Quedaste fuera de este contrato y no puedes volver a aceptarlo.", "The target eliminated you. You are out of this contract and cannot accept it again."), Array.Empty()); } else if (flag) { GUILayout.Box(L("Publicaste este contrato.", "You published this contract."), Array.Empty()); bool flag5 = bountyContract.Hunters.Count > 0; string text = (flag5 ? L("Cancelar: recuperar recompensa y perder fianza", "Cancel: recover reward and lose bond") : L("Cancelar y recuperar recompensa + fianza", "Cancel and recover reward + bond")); if (_confirmCancelContractId == bountyContract.ContractId) { GUILayout.Box(flag5 ? (L("CONFIRMACIÓN: la recompensa volverá intacta, pero la fianza de ", "CONFIRMATION: the reward will be returned intact, but the ") + bountyContract.BondCoins + L(" Coins se repartirá entre ", " Coins bond will be shared among ") + bountyContract.Hunters.Count + L(" cazador(es) aceptados.", " accepted hunter(s).")) : L("CONFIRMACIÓN: recuperarás la recompensa y la fianza completas.", "CONFIRMATION: you will recover the full reward and bond."), Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button(L("CONFIRMAR CANCELACIÓN", "CONFIRM CANCELLATION"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(34f) })) { _confirmCancelContractId = string.Empty; SetStatus(L("Enviando solicitud de cancelación...", "Sending cancellation request..."), success: true); BountyClient.RequestCancel(bountyContract.ContractId); } if (GUILayout.Button(L("Volver", "Back"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(34f) })) { _confirmCancelContractId = string.Empty; } GUILayout.EndHorizontal(); } else if (GUILayout.Button(text, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(34f) })) { _confirmCancelContractId = bountyContract.ContractId; } } else if (((BountyHuntercontractPlugin.TargetCanWinReward.Value == BountyHuntercontractPlugin.Toggle.On) ? bountyContract.Hunters.Count : bountyContract.ActiveHunterCount) < bountyContract.MaximumHunters) { if (GUILayout.Button(L("Aceptar contrato", "Accept contract"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(34f) })) { SetStatus(L("Enviando aceptación al servidor...", "Sending acceptance to the server..."), success: true); BountyClient.RequestAccept(bountyContract.ContractId); } } else { GUILayout.Box(L("Cupos completos.", "All slots are filled."), Array.Empty()); } GUILayout.EndVertical(); GUILayout.Space(6f); } GUILayout.EndScrollView(); } private void DrawPlayerStatsTab() { //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_013e: 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) GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(L("Buscar jugador:", "Search player:"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) }); _statsSearch = GUILayout.TextField(_statsSearch, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(260f) }); GUILayout.Label(L("El registro se guarda por mundo y por ID de personaje.", "The registry is saved per world and character ID."), Array.Empty()); GUILayout.EndHorizontal(); GUILayout.Space(6f); IEnumerable source = BountyClient.PlayerStatistics; if (!string.IsNullOrWhiteSpace(_statsSearch)) { source = source.Where((BountyPlayerStats stats) => stats.LastKnownName.IndexOf(_statsSearch, StringComparison.OrdinalIgnoreCase) >= 0); } BountyPlayerStats[] array = (from stats in source orderby stats.TotalVictories descending, stats.HuntersEliminated descending, stats.LastKnownName select stats).ToArray(); _statsScroll = GUILayout.BeginScrollView(_statsScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(GetMainContentHeight()) }); if (array.Length == 0) { GUILayout.Box(L("Todavía no hay estadísticas registradas.", "No statistics have been recorded yet."), Array.Empty()); } BountyPlayerStats[] array2 = array; foreach (BountyPlayerStats bountyPlayerStats in array2) { bool flag = bountyPlayerStats.PlayerId == BountyClient.LocalPlayerId; GUILayout.BeginVertical(GUI.skin.box, Array.Empty()); GUILayout.Label((flag ? "▶ " : string.Empty) + bountyPlayerStats.LastKnownName, Array.Empty()); GUILayout.Label(L("Contratos creados: ", "Contracts created: ") + bountyPlayerStats.ContractsCreated + L(" | Contratos aceptados: ", " | Contracts accepted: ") + bountyPlayerStats.ContractsAccepted + L(" | Veces que fue objetivo: ", " | Times targeted: ") + bountyPlayerStats.TimesTargeted, Array.Empty()); GUILayout.Label(L("Victorias como cazador: ", "Hunter victories: ") + bountyPlayerStats.HunterVictories + L(" | Muertes como cazador: ", " | Hunter deaths: ") + bountyPlayerStats.HunterDeaths, Array.Empty()); GUILayout.Label(L("Supervivencias como objetivo: ", "Target survivals: ") + bountyPlayerStats.TargetVictories + L(" | Muertes como objetivo: ", " | Target deaths: ") + bountyPlayerStats.TargetDeaths + L(" | Cazadores eliminados: ", " | Hunters eliminated: ") + bountyPlayerStats.HuntersEliminated, Array.Empty()); GUILayout.Label(L("Recompensas cobradas: ", "Rewards claimed: ") + bountyPlayerStats.RewardsClaimed + L(" | Victorias totales: ", " | Total victories: ") + bountyPlayerStats.TotalVictories, Array.Empty()); GUILayout.EndVertical(); GUILayout.Space(5f); } GUILayout.EndScrollView(); } private void DrawGlobalRegistryTab() { //IL_0241: Unknown result type (might be due to invalid IL or missing references) //IL_026a: Unknown result type (might be due to invalid IL or missing references) //IL_026f: Unknown result type (might be due to invalid IL or missing references) BountyGlobalStats globalStatistics = BountyClient.GlobalStatistics; int num = BountyClient.Contracts.Count((BountyContract contract) => contract.Status == BountyStatus.Active); int num2 = BountyClient.Contracts.Count((BountyContract contract) => contract.Status == BountyStatus.Completed); GUILayout.BeginVertical(GUI.skin.box, Array.Empty()); GUILayout.Label(L("RESUMEN GLOBAL DEL MUNDO", "GLOBAL WORLD SUMMARY"), Array.Empty()); GUILayout.Label(L("Contratos totales creados: ", "Total contracts created: ") + globalStatistics.TotalContractsCreated + L(" | Activos: ", " | Active: ") + num + L(" | Pendientes de cobro: ", " | Pending claims: ") + num2, Array.Empty()); GUILayout.Label(L("Finalizados con ganador: ", "Completed with a winner: ") + globalStatistics.TotalContractsCompleted + L(" | Cancelados: ", " | Cancelled: ") + globalStatistics.TotalContractsCancelled + L(" | Expiraciones totales: ", " | Total expirations: ") + globalStatistics.TotalContractsExpired, Array.Empty()); GUILayout.Label(L("Victorias de cazadores: ", "Hunter victories: ") + globalStatistics.HunterVictories + L(" | Victorias de objetivos: ", " | Target victories: ") + globalStatistics.TargetVictories + L(" | Cazadores eliminados: ", " | Hunters eliminated: ") + globalStatistics.HuntersEliminated, Array.Empty()); GUILayout.Label(L("Recompensas cobradas: ", "Rewards claimed: ") + globalStatistics.RewardsClaimed, Array.Empty()); GUILayout.EndVertical(); GUILayout.Space(8f); GUILayout.Label(L("HISTORIAL RECIENTE", "RECENT HISTORY"), Array.Empty()); List history = BountyClient.History; _historyScroll = GUILayout.BeginScrollView(_historyScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(Mathf.Max(220f, GetMainContentHeight() - 145f)) }); if (!BountyClient.HistoryLoaded) { GUILayout.Box(L("Cargando el historial desde el servidor...", "Loading history from the server..."), Array.Empty()); } else if (history.Count == 0) { GUILayout.Box(L("Todavía no hay contratos finalizados en el registro.", "There are no completed contracts in the registry yet."), Array.Empty()); } foreach (BountyHistoryEntry item in history) { GUILayout.BeginVertical(GUI.skin.box, Array.Empty()); GUILayout.Label(FormatDate(item.FinishedUtcTicks) + " | " + OutcomeText(item.Outcome), Array.Empty()); GUILayout.Label(L("Publicado por ", "Published by ") + item.CreatorName + L(" contra ", " against ") + item.TargetName, Array.Empty()); GUILayout.Label(L("Ganador: ", "Winner: ") + ((item.WinnerPlayerId == 0L) ? L("Ninguno", "None") : item.WinnerName), Array.Empty()); DrawRewardCard(L("Recompensa", "Reward"), item.Reward); DrawBondCard(L("Fianza", "Bond"), item.BondCoins); GUILayout.Label(L("Cazadores aceptados: ", "Hunters accepted: ") + item.AcceptedHunters + "/" + item.MaximumHunters + L(" | Eliminados: ", " | Eliminated: ") + item.EliminatedHunters + L(" | Duración elegida: ", " | Selected duration: ") + FormatDuration(item.DurationMinutes), Array.Empty()); GUILayout.EndVertical(); GUILayout.Space(5f); } GUILayout.EndScrollView(); } private static void DrawInventoryItemButton(ItemData item, bool selected, bool allowed, string restrictionReason) { //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_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_02e9: Unknown result type (might be due to invalid IL or missing references) Rect rect = GUILayoutUtility.GetRect(GUIContent.none, GUI.skin.button, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Height(52f), GUILayout.ExpandWidth(true) }); string text = BountyItemCodec.BuildItemTooltip(item); if (!allowed && !string.IsNullOrWhiteSpace(restrictionReason)) { text = restrictionReason + "\n\n" + text; } RegisterTooltip(rect, text); bool enabled = GUI.enabled; GUI.enabled = enabled && allowed; bool flag = GUI.Button(rect, GUIContent.none, GUI.skin.button); GUI.enabled = enabled; if (flag && allowed) { _instance._selectedRewardItem = item; _instance._rewardAmountText = Math.Max(1, item.m_stack).ToString(); BountyDiagnostics.Info("UI", "Selected reward item prefab=" + Utils.GetPrefabName(item.m_dropPrefab) + " stack=" + item.m_stack + " quality=" + item.m_quality + "."); } Rect rect2 = default(Rect); ((Rect)(ref rect2))..ctor(((Rect)(ref rect)).x + 7f, ((Rect)(ref rect)).y + 6f, 40f, 40f); DrawSprite(BountyItemCodec.GetItemIcon(item), rect2); GUIStyle val = ((!selected) ? (_instance?._inventoryTitleStyle ?? GUI.skin.label) : (_instance?._inventorySelectedTitleStyle ?? GUI.skin.label)); GUIStyle val2 = _instance?._inventoryDetailStyle ?? GUI.skin.label; string localizedItemName = BountyItemCodec.GetLocalizedItemName(item); string text2 = (selected ? "▶ " : string.Empty); string text3 = string.Empty; if (!allowed) { text3 = (BountyItemCodec.IsEquipped(Player.m_localPlayer, item) ? L(" [EQUIPADO]", " [EQUIPPED]") : L(" [PROHIBIDO]", " [PROHIBITED]")); } GUI.Label(new Rect(((Rect)(ref rect)).x + 53f, ((Rect)(ref rect)).y + 4f, ((Rect)(ref rect)).width - 60f, 23f), text2 + localizedItemName + text3, val); string text4 = L("Cantidad: ", "Amount: ") + item.m_stack; if (item.m_quality > 1) { text4 = text4 + L(" | Calidad: ", " | Quality: ") + item.m_quality; } GUI.Label(new Rect(((Rect)(ref rect)).x + 53f, ((Rect)(ref rect)).y + 25f, ((Rect)(ref rect)).width - 60f, 21f), text4, val2); } private static void DrawRewardCard(string label, BountyRewardItem reward, string? extra = null) { //IL_003b: 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) //IL_0041: 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_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: 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) if (reward != null) { Rect rect = GUILayoutUtility.GetRect(GUIContent.none, GUI.skin.box, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Height(60f), GUILayout.ExpandWidth(true) }); GUI.Box(rect, GUIContent.none); RegisterTooltip(rect, BountyItemCodec.BuildRewardTooltip(reward)); Rect rect2 = default(Rect); ((Rect)(ref rect2))..ctor(((Rect)(ref rect)).x + 8f, ((Rect)(ref rect)).y + 8f, 44f, 44f); DrawSprite(BountyItemCodec.GetRewardIcon(reward), rect2); GUIStyle val = _instance?._rewardHeaderStyle ?? GUI.skin.label; GUIStyle val2 = _instance?._rewardDetailStyle ?? GUI.skin.label; GUI.Label(new Rect(((Rect)(ref rect)).x + 60f, ((Rect)(ref rect)).y + 5f, ((Rect)(ref rect)).width - 68f, 24f), label, val); string text = BountyItemCodec.GetLocalizedRewardName(reward) + " x" + reward.Stack; if (reward.Quality > 1) { text = text + L(" | Calidad ", " | Quality ") + reward.Quality; } if (!string.IsNullOrWhiteSpace(extra)) { text = text + " | " + extra; } GUI.Label(new Rect(((Rect)(ref rect)).x + 60f, ((Rect)(ref rect)).y + 28f, ((Rect)(ref rect)).width - 68f, 24f), text, val2); } } private static void DrawBondCard(string label, int amount, string? extra = null) { BountyRewardItem reward = BountyRewardItem.Coins(Math.Max(1, amount)); DrawRewardCard(label, reward, extra); } private static void DrawSprite(Sprite? sprite, Rect rect) { //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_008d: Unknown result type (might be due to invalid IL or missing references) //IL_008f: 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_0035: 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)sprite == (Object)null || (Object)(object)sprite.texture == (Object)null) { GUI.Box(rect, "?"); return; } Texture texture = (Texture)(object)sprite.texture; Rect val; try { val = sprite.textureRect; } catch { val = sprite.rect; } Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(((Rect)(ref val)).x / (float)texture.width, ((Rect)(ref val)).y / (float)texture.height, ((Rect)(ref val)).width / (float)texture.width, ((Rect)(ref val)).height / (float)texture.height); GUI.DrawTextureWithTexCoords(rect, texture, val2, true); } private static void RegisterTooltip(Rect controlRect, string tooltip) { //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) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: 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_004e: 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_005f: 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_0070: 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_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: 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_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: 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_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) if (_visible && !string.IsNullOrWhiteSpace(tooltip)) { Vector2 val = GUIUtility.GUIToScreenPoint(new Vector2(((Rect)(ref controlRect)).xMin, ((Rect)(ref controlRect)).yMin)); Vector2 val2 = GUIUtility.GUIToScreenPoint(new Vector2(((Rect)(ref controlRect)).xMax, ((Rect)(ref controlRect)).yMax)); Rect val3 = Rect.MinMaxRect(Mathf.Min(val.x, val2.x), Mathf.Min(val.y, val2.y), Mathf.Max(val.x, val2.x), Mathf.Max(val.y, val2.y)); Vector2 val4 = default(Vector2); ((Vector2)(ref val4))..ctor(Input.mousePosition.x, (float)Screen.height - Input.mousePosition.y); Rect val5 = (Rect)(((Object)(object)_instance != (Object)null) ? _instance._windowRect : default(Rect)); if ((Object)(object)_instance != (Object)null && ((Rect)(ref val5)).Contains(val4) && ((Rect)(ref val3)).Contains(val4)) { _tooltipForFrame = tooltip; } } } private static void DrawTooltipOverlay() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0059: 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_00b0: Expected O, but got Unknown //IL_00b1: 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) //IL_0125: Unknown result type (might be due to invalid IL or missing references) if (_visible && !string.IsNullOrWhiteSpace(_tooltipForFrame)) { Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(Input.mousePosition.x, (float)Screen.height - Input.mousePosition.y); if ((Object)(object)_instance == (Object)null || !((Rect)(ref _instance._windowRect)).Contains(val)) { _tooltipForFrame = string.Empty; return; } GUIStyle val2 = _instance?._tooltipStyle ?? GUI.skin.box; float num = val2.CalcHeight(new GUIContent(_tooltipForFrame), 360f); float num2 = Mathf.Min(val.x + 18f, (float)Screen.width - 360f - 8f); float num3 = Mathf.Min(val.y + 18f, (float)Screen.height - num - 8f); int depth = GUI.depth; GUI.depth = -1000; GUI.Box(new Rect(Mathf.Max(8f, num2), Mathf.Max(8f, num3), 360f, num), _tooltipForFrame, val2); GUI.depth = depth; } } private float GetMainContentHeight() { float num = (string.IsNullOrEmpty(_status) ? 176f : 226f); return Mathf.Max(300f, ((Rect)(ref _windowRect)).height - num); } private void DrawResizeHandle() { //IL_0050: 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_006a: Unknown result type (might be due to invalid IL or missing references) //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_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0082: 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_009b: Expected I4, but got Unknown //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: 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_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) Rect val = default(Rect); ((Rect)(ref val))..ctor(((Rect)(ref _windowRect)).width - 24f - 5f, ((Rect)(ref _windowRect)).height - 24f - 5f, 24f, 24f); string tooltip = L("Arrastra para cambiar el tamaño del tablón", "Drag to resize the bounty board"); RegisterTooltip(val, tooltip); GUI.Box(val, "↘"); int controlID = GUIUtility.GetControlID(831473, (FocusType)2, val); Event current = Event.current; EventType typeForControl = current.GetTypeForControl(controlID); EventType val2 = typeForControl; switch ((int)val2) { case 0: if (current.button == 0 && ((Rect)(ref val)).Contains(current.mousePosition)) { GUIUtility.hotControl = controlID; _resizingWindow = true; _resizeStartMouse = GUIUtility.GUIToScreenPoint(current.mousePosition); _resizeStartSize = ((Rect)(ref _windowRect)).size; current.Use(); } break; case 3: if (GUIUtility.hotControl == controlID && _resizingWindow) { Vector2 val3 = GUIUtility.GUIToScreenPoint(current.mousePosition); Vector2 val4 = val3 - _resizeStartMouse; float num = Mathf.Max(320f, (float)Screen.width - 20f); float num2 = Mathf.Max(320f, (float)Screen.height - 20f); float num3 = Mathf.Min(700f, num); float num4 = Mathf.Min(600f, num2); _requestedWindowSize = new Vector2(Mathf.Clamp(_resizeStartSize.x + val4.x, num3, num), Mathf.Clamp(_resizeStartSize.y + val4.y, num4, num2)); current.Use(); } break; case 1: if (GUIUtility.hotControl == controlID) { GUIUtility.hotControl = 0; _resizingWindow = false; SaveWindowLayout(); current.Use(); } break; case 2: break; } } private void ClampWindowToScreen() { //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Max(320f, (float)Screen.width - 20f); float num2 = Mathf.Max(320f, (float)Screen.height - 20f); float num3 = Mathf.Min(700f, num); float num4 = Mathf.Min(600f, num2); ((Rect)(ref _windowRect)).width = Mathf.Clamp(((Rect)(ref _windowRect)).width, num3, num); ((Rect)(ref _windowRect)).height = Mathf.Clamp(((Rect)(ref _windowRect)).height, num4, num2); ((Rect)(ref _windowRect)).x = Mathf.Clamp(((Rect)(ref _windowRect)).x, 10f, Mathf.Max(10f, (float)Screen.width - ((Rect)(ref _windowRect)).width - 10f)); ((Rect)(ref _windowRect)).y = Mathf.Clamp(((Rect)(ref _windowRect)).y, 10f, Mathf.Max(10f, (float)Screen.height - ((Rect)(ref _windowRect)).height - 10f)); if (_requestedWindowSize.x <= 0f || _requestedWindowSize.y <= 0f) { _requestedWindowSize = ((Rect)(ref _windowRect)).size; } else { _requestedWindowSize = new Vector2(Mathf.Clamp(_requestedWindowSize.x, num3, num), Mathf.Clamp(_requestedWindowSize.y, num4, num2)); } } private void LoadWindowLayout() { //IL_008c: 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) ((Rect)(ref _windowRect)).width = PlayerPrefs.GetFloat("BHC_UI_WindowWidth", ((Rect)(ref _windowRect)).width); ((Rect)(ref _windowRect)).height = PlayerPrefs.GetFloat("BHC_UI_WindowHeight", ((Rect)(ref _windowRect)).height); ((Rect)(ref _windowRect)).x = PlayerPrefs.GetFloat("BHC_UI_WindowX", ((Rect)(ref _windowRect)).x); ((Rect)(ref _windowRect)).y = PlayerPrefs.GetFloat("BHC_UI_WindowY", ((Rect)(ref _windowRect)).y); _requestedWindowSize = ((Rect)(ref _windowRect)).size; ClampWindowToScreen(); } private void SaveWindowLayout() { //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) float num = ((_requestedWindowSize.x > 0f) ? _requestedWindowSize.x : ((Rect)(ref _windowRect)).width); float num2 = ((_requestedWindowSize.y > 0f) ? _requestedWindowSize.y : ((Rect)(ref _windowRect)).height); PlayerPrefs.SetFloat("BHC_UI_WindowWidth", num); PlayerPrefs.SetFloat("BHC_UI_WindowHeight", num2); PlayerPrefs.SetFloat("BHC_UI_WindowX", ((Rect)(ref _windowRect)).x); PlayerPrefs.SetFloat("BHC_UI_WindowY", ((Rect)(ref _windowRect)).y); PlayerPrefs.Save(); BountyDiagnostics.Detailed("UI", "Saved bounty board layout size=" + num + "x" + num2 + " position=" + ((object)((Rect)(ref _windowRect)).position/*cast due to .constrained prefix*/).ToString() + "."); } private static string OutcomeText(BountyOutcome outcome) { return outcome switch { BountyOutcome.HunterVictory => L("Victoria del cazador", "Hunter victory"), BountyOutcome.TargetVictory => L("El objetivo sobrevivió", "The target survived"), BountyOutcome.CancelledByCreator => L("Cancelado por el creador", "Cancelled by the creator"), BountyOutcome.ExpiredWithoutHunters => L("Expiró sin cazadores", "Expired without hunters"), BountyOutcome.AdminRemoved => L("Eliminado por administrador", "Removed by an administrator"), _ => L("Sin resultado", "No outcome"), }; } private static TimeSpan GetDisplayedRemaining(BountyContract contract) { long num = Math.Max(0L, contract.RemainingDurationTicks); if (contract.DurationTimerRunning && contract.LastDurationUpdateUtcTicks > 0) { num = Math.Max(0L, num - Math.Max(0L, DateTime.UtcNow.Ticks - contract.LastDurationUpdateUtcTicks)); } return TimeSpan.FromTicks(num); } private static void DrawDurationProgress(BountyContract contract, TimeSpan remaining) { //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Unknown result type (might be due to invalid IL or missing references) //IL_023d: Expected O, but got Unknown //IL_023d: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) long displayedTotalDurationTicks = GetDisplayedTotalDurationTicks(contract); float num = ((displayedTotalDurationTicks <= 0) ? 0f : Mathf.Clamp01((float)((double)remaining.Ticks / (double)displayedTotalDurationTicks))); bool flag = IsFiveSecondTestContract(contract); string text = ((flag && contract.Hunters.Count == 0) ? L("PRUEBA · ESPERANDO CAZADOR", "TEST · WAITING FOR HUNTER") : ((!contract.DurationTimerRunning) ? (flag ? L("PRUEBA · PAUSADO", "TEST · PAUSED") : L("PAUSADO", "PAUSED")) : (flag ? L("PRUEBA · ACTIVO", "TEST · ACTIVE") : L("ACTIVO", "ACTIVE")))); GUILayout.Label(L("Estado del tiempo: ", "Timer status: ") + text, Array.Empty()); Rect rect = GUILayoutUtility.GetRect(GUIContent.none, GUI.skin.box, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Height(24f), GUILayout.ExpandWidth(true) }); GUI.Box(rect, GUIContent.none); Rect val = default(Rect); ((Rect)(ref val))..ctor(((Rect)(ref rect)).x + 3f, ((Rect)(ref rect)).y + 3f, Mathf.Max(0f, ((Rect)(ref rect)).width - 6f), Mathf.Max(0f, ((Rect)(ref rect)).height - 6f)); Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(((Rect)(ref val)).x, ((Rect)(ref val)).y, ((Rect)(ref val)).width * num, ((Rect)(ref val)).height); Color color = GUI.color; GUI.color = new Color(0.13f, 0.035f, 0.025f, 0.95f); GUI.DrawTexture(val, (Texture)(object)Texture2D.whiteTexture); if (((Rect)(ref val2)).width > 0f) { GUI.color = (contract.DurationTimerRunning ? new Color(0.78f, 0.08f, 0.045f, 1f) : new Color(0.44f, 0.12f, 0.08f, 0.88f)); GUI.DrawTexture(val2, (Texture)(object)Texture2D.whiteTexture); } GUI.color = color; GUIStyle val3 = new GUIStyle(GUI.skin.label) { alignment = (TextAnchor)4, fontStyle = (FontStyle)1 }; GUI.Label(rect, Mathf.RoundToInt(num * 100f) + L("% restante", "% remaining"), val3); } private static bool IsFiveSecondTestContract(BountyContract contract) { return contract.DurationMinutes > 0 && BountyHuntercontractPlugin.EnableFiveSecondTestContracts.Value == BountyHuntercontractPlugin.Toggle.On; } private static long GetDisplayedTotalDurationTicks(BountyContract contract) { if (contract.DurationMinutes <= 0) { return 0L; } long val = (IsFiveSecondTestContract(contract) ? TimeSpan.FromSeconds(Math.Max(1, BountyHuntercontractPlugin.TestContractDurationSeconds.Value)).Ticks : TimeSpan.FromMinutes(contract.DurationMinutes).Ticks); return Math.Max(val, Math.Max(0L, contract.RemainingDurationTicks)); } private static string FormatRemaining(TimeSpan time) { long num = Math.Max(0L, (long)Math.Ceiling(time.TotalSeconds)); long num2 = num / 86400; long num3 = num % 86400 / 3600; long num4 = num % 3600 / 60; long num5 = num % 60; if (num2 > 0) { return num2 + "d " + num3.ToString("00") + "h " + num4.ToString("00") + "m " + num5.ToString("00") + "s"; } if (num3 > 0) { return num3.ToString("00") + "h " + num4.ToString("00") + "m " + num5.ToString("00") + "s"; } return num4.ToString("00") + "m " + num5.ToString("00") + "s"; } private static string FormatDuration(int minutes) { if (minutes <= 0) { return L("permanente", "permanent"); } TimeSpan time = TimeSpan.FromMinutes(minutes); return FormatRemaining(time); } private static string FormatDate(long ticks) { if (ticks <= 0) { return L("Fecha desconocida", "Unknown date"); } try { return new DateTime(ticks, DateTimeKind.Utc).ToLocalTime().ToString("yyyy-MM-dd HH:mm"); } catch { return L("Fecha desconocida", "Unknown date"); } } } internal static class BountyLocalization { private static readonly Dictionary BrazilianPortuguese = new Dictionary(StringComparer.Ordinal) { { "Bounty Contract Board", "Quadro de contratos de recompensa" }, { "The reward is held in escrow. Contract time only advances while the target is online.", "A recompensa fica em depósito. O tempo do contrato só avança enquanto o alvo estiver conectado." }, { "Publish", "Publicar" }, { "Contracts and claims", "Contratos e resgates" }, { "Players", "Jogadores" }, { "Global registry", "Registro global" }, { "Refresh", "Atualizar" }, { "Close", "Fechar" }, { "1. Target player:", "1. Jogador alvo:" }, { "No other players are available.", "Não há outros jogadores disponíveis." }, { "2. Reward item from your inventory:", "2. Item de recompensa do seu inventário:" }, { "Amount to deposit:", "Quantidade a depositar:" }, { "Matching available: ", "Disponível igual: " }, { "Selected reward", "Recompensa selecionada" }, { "Required bond", "Caução obrigatória" }, { "Available: ", "Disponível: " }, { "3. Maximum hunters:", "3. Máximo de caçadores:" }, { "4. Contract duration:", "4. Duração do contrato:" }, { "1 day", "1 dia" }, { "3 days", "3 dias" }, { "7 days", "7 dias" }, { "Custom duration (minutes):", "Duração personalizada (minutos):" }, { "0 = permanent", "0 = permanente" }, { "Your open contracts: ", "Seus contratos abertos: " }, { "Deposit reward + bond and publish", "Depositar recompensa + caução e publicar" }, { "No items are available to deposit.", "Você não possui itens para depositar." }, { "Select a reward item.", "Selecione um item de recompensa." }, { "Enter a valid reward amount.", "Informe uma quantidade válida para a recompensa." }, { "Enter a valid duration.", "Informe uma duração válida." }, { "Checking and depositing ", "Verificando e depositando " }, { " + bond of ", " + caução de " }, { "No active contracts or pending rewards exist.", "Não há contratos ativos nem recompensas pendentes." }, { "COMPLETED CONTRACT: ", "CONTRATO CONCLUÍDO: " }, { "TARGET: ", "ALVO: " }, { "Pending / claimed reward", "Recompensa pendente / resgatada" }, { "Contract reward", "Recompensa do contrato" }, { "Claimed", "Resgatada" }, { "Deposited", "Depositada" }, { "Creator: ", "Criador: " }, { "Bond", "Caução" }, { "Hunters: ", "Caçadores: " }, { " active, ", " ativos, " }, { " eliminated, ", " eliminados, " }, { " slots used", " vagas usadas" }, { "Active time remaining: ", "Tempo ativo restante: " }, { "Duration: permanent", "Duração: permanente" }, { "Active hunters: ", "Caçadores ativos: " }, { "Eliminated by target: ", "Eliminados pelo alvo: " }, { " | Winner: ", " | Vencedor: " }, { "None", "Nenhum" }, { "CLAIM REWARD", "RESGATAR RECOMPENSA" }, { "RECOVER BOND", "RECUPERAR CAUÇÃO" }, { "Leave contract", "Abandonar contrato" }, { "Accept contract", "Aceitar contrato" }, { "All slots are filled.", "Todas as vagas estão preenchidas." }, { "Search player:", "Buscar jogador:" }, { "The registry is saved per world and character ID.", "O registro é salvo por mundo e ID de personagem." }, { "No statistics have been recorded yet.", "Ainda não há estatísticas registradas." }, { "Contracts created: ", "Contratos criados: " }, { " | Contracts accepted: ", " | Contratos aceitos: " }, { " | Times targeted: ", " | Vezes como alvo: " }, { "Hunter victories: ", "Vitórias como caçador: " }, { " | Hunter deaths: ", " | Mortes como caçador: " }, { "Target survivals: ", "Sobrevivências como alvo: " }, { " | Target deaths: ", " | Mortes como alvo: " }, { " | Hunters eliminated: ", " | Caçadores eliminados: " }, { "Rewards claimed: ", "Recompensas resgatadas: " }, { " | Total victories: ", " | Vitórias totais: " }, { "GLOBAL WORLD SUMMARY", "RESUMO GLOBAL DO MUNDO" }, { "RECENT HISTORY", "HISTÓRICO RECENTE" }, { "Loading history from the server...", "Carregando histórico do servidor..." }, { "There are no completed contracts in the registry yet.", "Ainda não há contratos concluídos no registro." }, { "Published by ", "Publicado por " }, { " against ", " contra " }, { "Winner: ", "Vencedor: " }, { "Reward", "Recompensa" }, { "Hunters accepted: ", "Caçadores aceitos: " }, { " | Eliminated: ", " | Eliminados: " }, { " | Selected duration: ", " | Duração escolhida: " }, { "Amount: ", "Quantidade: " }, { " [EQUIPPED]", " [EQUIPADO]" }, { " [PROHIBITED]", " [PROIBIDO]" }, { "Hunter victory", "Vitória do caçador" }, { "The target survived", "O alvo sobreviveu" }, { "Cancelled by creator", "Cancelado pelo criador" }, { "Expired without hunters", "Expirou sem caçadores" }, { "Removed by an administrator", "Removido por administrador" }, { "No outcome", "Sem resultado" }, { "ACTIVE", "ATIVO" }, { "PAUSED", "PAUSADO" }, { "Timer status: ", "Estado do tempo: " }, { "% remaining", "% restante" }, { "permanent", "permanente" }, { "Unknown date", "Data desconhecida" }, { "Reward deposited: ", "Recompensa depositada: " }, { ". Bond deposited: ", ". Caução depositada: " }, { " Coins. Publishing contract...", " Coins. Publicando contrato..." } }; internal static bool IsSpanish { get { try { string text = ((Localization.instance != null) ? Localization.instance.GetSelectedLanguage() : PlayerPrefs.GetString("language", "English")); return text.StartsWith("Spanish", StringComparison.OrdinalIgnoreCase); } catch { return false; } } } internal static bool IsBrazilianPortuguese { get { try { string text = ((Localization.instance != null) ? Localization.instance.GetSelectedLanguage() : PlayerPrefs.GetString("language", "English")); return text.IndexOf("portuguese", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("pt-br", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("brazil", StringComparison.OrdinalIgnoreCase) >= 0; } catch { return false; } } } internal static string Text(string spanish, string english) { if (IsSpanish) { return spanish; } if (IsBrazilianPortuguese && BrazilianPortuguese.TryGetValue(english, out string value)) { return value; } return english; } internal static string TranslateIncoming(string value) { if (IsSpanish || string.IsNullOrEmpty(value)) { return value ?? string.Empty; } KeyValuePair[] array = new KeyValuePair[40] { new KeyValuePair("¡NUEVO CONTRATO DE ASESINATO!", "NEW BOUNTY CONTRACT!"), new KeyValuePair("¡CONTRATO CUMPLIDO!", "CONTRACT COMPLETED!"), new KeyValuePair("¡SOBREVIVISTE AL CONTRATO!", "YOU SURVIVED THE CONTRACT!"), new KeyValuePair("¡EL OBJETIVO SOBREVIVIÓ!", "THE TARGET SURVIVED!"), new KeyValuePair("EL OBJETIVO SOBREVIVIÓ", "THE TARGET SURVIVED"), new KeyValuePair("El mundo todavía no está listo. Inténtalo nuevamente.", "The world is not ready yet. Try again."), new KeyValuePair("No puedes publicar un contrato contra ti mismo.", "You cannot publish a contract against yourself."), new KeyValuePair("La cantidad de cazadores debe estar entre 1 y 5.", "The number of hunters must be between 1 and 5."), new KeyValuePair("El jugador objetivo ya no está conectado.", "The target player is no longer online."), new KeyValuePair("Contrato publicado correctamente.", "Contract published successfully."), new KeyValuePair("Contrato aceptado.", "Contract accepted."), new KeyValuePair("Contrato cancelado.", "Contract cancelled."), new KeyValuePair("No puedes aceptar este contrato.", "You cannot accept this contract."), new KeyValuePair("El contrato ya alcanzó su máximo de cazadores.", "The contract has reached its maximum number of hunters."), new KeyValuePair("No puedes cancelar ese contrato.", "You cannot cancel that contract."), new KeyValuePair("No formas parte de ese contrato.", "You are not part of that contract."), new KeyValuePair("La recompensa principal de ese contrato ya fue cobrada.", "The main reward for that contract has already been claimed."), new KeyValuePair("La fianza de ese contrato ya fue recuperada o no existía.", "That contract bond has already been recovered or did not exist."), new KeyValuePair("Recompensa cobrada: ", "Reward claimed: "), new KeyValuePair("Fianza recuperada: ", "Bond recovered: "), new KeyValuePair("Recompensa por sobrevivir al contrato contra ti", "Reward for surviving the contract against you"), new KeyValuePair("Recompensa por eliminar a ", "Reward for eliminating "), new KeyValuePair("La recompensa ahora es tuya. ", "The reward is now yours. "), new KeyValuePair("Vuelve al tablón para cobrar ", "Return to the board to claim "), new KeyValuePair("Vuelve al tablón para recuperar ", "Return to the board to recover "), new KeyValuePair("Eliminaste a ", "You eliminated "), new KeyValuePair("El objetivo ya puede rastrearte y atacarte.", "The target can now track and attack you."), new KeyValuePair("El objetivo te eliminó de este contrato y no puedes volver a aceptarlo.", "The target eliminated you from this contract and you cannot accept it again."), new KeyValuePair("Recuperaste la recompensa y la fianza completas.", "You recovered the full reward and bond."), new KeyValuePair("Recuperaste la recompensa; la fianza de ", "You recovered the reward; the bond of "), new KeyValuePair(" se repartió entre los cazadores aceptados.", " was shared among the accepted hunters."), new KeyValuePair("Objetivo: ", "Target: "), new KeyValuePair("Recompensa: ", "Reward: "), new KeyValuePair("Cazadores: ", "Hunters: "), new KeyValuePair("Tiempo activo: ", "Active time: "), new KeyValuePair("Duración efectiva: ", "Effective duration: "), new KeyValuePair("Objetivo", "Target"), new KeyValuePair("Cazador", "Hunter"), new KeyValuePair("fianza", "bond"), new KeyValuePair("recompensa", "reward") }; string text = value; KeyValuePair[] array2 = array; for (int i = 0; i < array2.Length; i++) { KeyValuePair keyValuePair = array2[i]; text = text.Replace(keyValuePair.Key, keyValuePair.Value); } return IsBrazilianPortuguese ? TranslateIncomingToBrazilianPortuguese(text) : text; } private static string TranslateIncomingToBrazilianPortuguese(string value) { KeyValuePair[] array = new KeyValuePair[35] { new KeyValuePair("NEW BOUNTY CONTRACT!", "NOVO CONTRATO DE RECOMPENSA!"), new KeyValuePair("CONTRACT COMPLETED!", "CONTRATO CONCLUÍDO!"), new KeyValuePair("YOU SURVIVED THE CONTRACT!", "VOCÊ SOBREVIVEU AO CONTRATO!"), new KeyValuePair("THE TARGET SURVIVED!", "O ALVO SOBREVIVEU!"), new KeyValuePair("The world is not ready yet. Try again.", "O mundo ainda não está pronto. Tente novamente."), new KeyValuePair("You cannot publish a contract against yourself.", "Você não pode publicar um contrato contra si mesmo."), new KeyValuePair("The number of hunters must be between 1 and 5.", "O número de caçadores deve estar entre 1 e 5."), new KeyValuePair("The target player is no longer online.", "O jogador alvo não está mais online."), new KeyValuePair("Contract published successfully.", "Contrato publicado com sucesso."), new KeyValuePair("Contract accepted.", "Contrato aceito."), new KeyValuePair("Contract cancelled.", "Contrato cancelado."), new KeyValuePair("You cannot accept this contract.", "Você não pode aceitar este contrato."), new KeyValuePair("The contract has reached its maximum number of hunters.", "O contrato atingiu o número máximo de caçadores."), new KeyValuePair("You cannot cancel that contract.", "Você não pode cancelar esse contrato."), new KeyValuePair("You are not part of that contract.", "Você não faz parte desse contrato."), new KeyValuePair("The main reward for that contract has already been claimed.", "A recompensa principal desse contrato já foi resgatada."), new KeyValuePair("That contract bond has already been recovered or did not exist.", "A garantia desse contrato já foi recuperada ou não existia."), new KeyValuePair("Reward claimed: ", "Recompensa resgatada: "), new KeyValuePair("Bond recovered: ", "Garantia recuperada: "), new KeyValuePair("Reward for surviving the contract against you", "Recompensa por sobreviver ao contrato contra você"), new KeyValuePair("Reward for eliminating ", "Recompensa por eliminar "), new KeyValuePair("The reward is now yours. ", "A recompensa agora é sua. "), new KeyValuePair("Return to the board to claim ", "Volte ao quadro para resgatar "), new KeyValuePair("Return to the board to recover ", "Volte ao quadro para recuperar "), new KeyValuePair("You eliminated ", "Você eliminou "), new KeyValuePair("The target can now track and attack you.", "O alvo agora pode rastrear e atacar você."), new KeyValuePair("The target eliminated you from this contract and you cannot accept it again.", "O alvo eliminou você deste contrato e você não pode aceitá-lo novamente."), new KeyValuePair("You recovered the full reward and bond.", "Você recuperou a recompensa e a garantia completas."), new KeyValuePair("You recovered the reward; the bond of ", "Você recuperou a recompensa; a garantia de "), new KeyValuePair(" was shared among the accepted hunters.", " foi dividida entre os caçadores aceitos."), new KeyValuePair("Target: ", "Alvo: "), new KeyValuePair("Reward: ", "Recompensa: "), new KeyValuePair("Hunters: ", "Caçadores: "), new KeyValuePair("Active time: ", "Tempo ativo: "), new KeyValuePair("Effective duration: ", "Duração efetiva: ") }; string text = value; KeyValuePair[] array2 = array; for (int i = 0; i < array2.Length; i++) { KeyValuePair keyValuePair = array2[i]; text = text.Replace(keyValuePair.Key, keyValuePair.Value); } return text; } } [BepInPlugin("Azathoth.BountyHunterContract", "BountyHunterContract", "1.0.0")] public sealed class BountyHuntercontractPlugin : BaseUnityPlugin { public enum Toggle { Off, On } internal const string ModName = "BountyHunterContract"; internal const string ModVersion = "1.0.0"; internal const string Author = "Azathoth"; internal const string ModGUID = "Azathoth.BountyHunterContract"; internal static BountyHuntercontractPlugin Instance = null; internal static readonly ManualLogSource Log = Logger.CreateLogSource("BountyHunterContract"); internal static readonly ConfigSync ConfigSync = new ConfigSync("Azathoth.BountyHunterContract") { DisplayName = "BountyHunterContract", CurrentVersion = "1.0.0", MinimumRequiredVersion = "1.0.0" }; internal static ConfigEntry LockConfiguration = null; internal static ConfigEntry MaximumReward = null; internal static ConfigEntry MaximumRewardItemAmount = null; internal static ConfigEntry ProhibitedRewardPrefabs = null; internal static ConfigEntry MaximumOpenContractsPerCreator = null; internal static ConfigEntry CancellationBondCoins = null; internal static ConfigEntry DefaultContractDurationMinutes = null; internal static ConfigEntry MinimumContractDurationMinutes = null; internal static ConfigEntry MaximumContractDurationMinutes = null; internal static ConfigEntry AllowPermanentContracts = null; internal static ConfigEntry TargetCanWinReward = null; internal static ConfigEntry TargetWinsOnExpiration = null; internal static ConfigEntry GlobalCompletionAnnouncements = null; internal static ConfigEntry MaximumHistoryEntries = null; internal static ConfigEntry TrackingIntervalSeconds = null; internal static ConfigEntry KillCreditSeconds = null; internal static ConfigEntry DetailedLogging = null; internal static ConfigEntry TrackingLogging = null; internal static ConfigEntry GlobalContractAnnouncements = null; internal static ConfigEntry EnableFiveSecondTestContracts = null; internal static ConfigEntry TestContractDurationSeconds = null; internal static ConfigEntry CompatibilityDiagnostics = null; internal static ConfigEntry CompatibilityCombatLogs = null; internal static ConfigEntry CompatibilityRpcSummary = null; internal static ConfigEntry CompatibilityInventoryLogs = null; internal static ConfigEntry CompatibilityCaptureExternalCaller = null; internal static ConfigEntry CompatibilitySummaryIntervalSeconds = null; internal static ConfigEntry DiscordWebhookEnabled = null; internal static ConfigEntry DiscordWebhookUrl = null; internal static ConfigEntry DiscordWebhookLanguage = null; internal static ConfigEntry DiscordWebhookUsername = null; internal static ConfigEntry DiscordWebhookAvatarUrl = null; internal static ConfigEntry DiscordNotifyPublished = null; internal static ConfigEntry DiscordNotifyResolved = null; internal static ConfigEntry DiscordNotifyCancelled = null; internal static ConfigEntry DiscordNotifyAdminRemoved = null; private Harmony _harmony = null; private FileSystemWatcher? _watcher; private void Awake() { //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Expected O, but got Unknown Instance = this; BindConfiguration(); BountyDiagnostics.Info("BOOT", "Starting BountyHunterContract 1.0.0. Detailed logs=" + DetailedLogging.Value.ToString() + ", tracking logs=" + TrackingLogging.Value.ToString() + ", global announcements=" + GlobalContractAnnouncements.Value.ToString() + ", cancellation bond=" + CancellationBondCoins.Value + " Coins, 5-second test mode=" + EnableFiveSecondTestContracts.Value.ToString() + ", Discord webhook=" + DiscordWebhookEnabled.Value.ToString() + ", webhook configured=" + !string.IsNullOrWhiteSpace(DiscordWebhookUrl.Value) + "."); BountyBoardRegistrar.RegisterPiece(); _harmony = new Harmony("Azathoth.BountyHunterContract"); _harmony.PatchAll(typeof(BountyHuntercontractPlugin).Assembly); BountyCompatibilityDiagnostics.Initialize(); ((Component)this).gameObject.AddComponent(); ((Component)this).gameObject.AddComponent(); ((Component)this).gameObject.AddComponent(); BountyDiscordWebhook.Initialize(); SetupWatcher(); BountyDiagnostics.Info("BOOT", "BountyHunterContract 1.0.0 loaded. AssetBundle board prefab=BH_BOARDBOUNTY."); } private void OnDestroy() { _watcher?.Dispose(); BountyDiscordWebhook.Shutdown(); BountyServer.Shutdown(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } private void BindConfiguration() { LockConfiguration = ConfigEntry("1 - General", "Lock Configuration", Toggle.On, "Locks synchronized configuration entries to server administrators."); ConfigSync.AddLockingConfigEntry(LockConfiguration); MaximumReward = ConfigEntry("2 - Contracts", "Maximum Coin Reward", 1000000, "Legacy compatibility limit for coin rewards."); MaximumRewardItemAmount = ConfigEntry("2 - Contracts", "Maximum Reward Item Amount", 1000000, "Maximum number of units from one exact item/stack signature that may be deposited as a reward."); ProhibitedRewardPrefabs = ConfigEntry("2 - Contracts", "Prohibited Reward Prefabs", string.Empty, "Comma, semicolon or line-break separated list of exact item prefab names that cannot be deposited as contract rewards. Example: DragonEgg, Wishbone, BeltStrength."); MaximumOpenContractsPerCreator = ConfigEntry("2 - Contracts", "Maximum Open Contracts Per Creator", 5, "Maximum number of active or completed-but-unclaimed contracts one player may have published at once."); CancellationBondCoins = ConfigEntry("2 - Contracts", "Cancellation Bond Coins", 100, "Coin deposit required in addition to the selected reward. It is returned after a normal resolution, but distributed among accepted hunters when the creator cancels."); DefaultContractDurationMinutes = ConfigEntry("2 - Contracts", "Default Contract Duration Minutes", 1440, "Default duration shown when publishing a contract."); MinimumContractDurationMinutes = ConfigEntry("2 - Contracts", "Minimum Contract Duration Minutes", 10, "Shortest duration a publisher may select."); MaximumContractDurationMinutes = ConfigEntry("2 - Contracts", "Maximum Contract Duration Minutes", 10080, "Longest duration a publisher may select. One day means 24 real hours accumulated while the target is connected."); AllowPermanentContracts = ConfigEntry("2 - Contracts", "Allow Permanent Contracts", Toggle.Off, "Allows publishers to select 0 minutes for a contract with no expiration."); TargetCanWinReward = ConfigEntry("2 - Contracts", "Target Can Win Reward", Toggle.On, "Allows the target to claim the deposited reward after defeating every available hunter."); TargetWinsOnExpiration = ConfigEntry("2 - Contracts", "Target Wins On Expiration", Toggle.On, "Awards the target when a contract with at least one accepted hunter expires while the target survives."); GlobalCompletionAnnouncements = ConfigEntry("2 - Contracts", "Global Completion Announcements", Toggle.On, "Shows a global center-screen announcement when either the hunter or the target wins a contract."); MaximumHistoryEntries = ConfigEntry("3 - Registry", "Maximum History Entries", 100, "Maximum number of completed/cancelled contract records kept per world."); TrackingIntervalSeconds = ConfigEntry("2 - Contracts", "Tracking Interval Seconds", 5f, "How often participant positions are refreshed."); KillCreditSeconds = ConfigEntry("2 - Contracts", "Kill Credit Seconds", 20f, "Maximum time between the last valid hunter hit and the target's death."); GlobalContractAnnouncements = ConfigEntry("2 - Contracts", "Global Contract Announcements", Toggle.On, "Shows a boss-style center-screen announcement to every connected player when a contract is published."); DetailedLogging = ConfigEntry("4 - Diagnostics", "Detailed Logs", Toggle.Off, "Writes detailed contract, player, RPC, PvP and reward diagnostics to the BepInEx log.", synchronized: false); TrackingLogging = ConfigEntry("4 - Diagnostics", "Position And Tracking Logs", Toggle.Off, "Writes recurring player-position and map-tracking diagnostics. Disable this after testing to reduce log volume.", synchronized: false); EnableFiveSecondTestContracts = ConfigEntry("4 - Diagnostics", "Enable 5 Second Test Contracts", Toggle.Off, "Testing only. While enabled, every non-permanent contract expires after the configured number of real seconds accumulated while the target is connected."); TestContractDurationSeconds = ConfigEntry("4 - Diagnostics", "Test Contract Duration Seconds", 5, "Duration used while 5-second test mode is enabled. Keep this at 5 for rapid expiration testing."); CompatibilityDiagnostics = ConfigEntry("4 - Diagnostics", "Compatibility Diagnostics", Toggle.Off, "Logs installed compatibility mods, Harmony patch owners and periodic compatibility summaries. Observation only; it does not alter gameplay.", synchronized: false); CompatibilityCombatLogs = ConfigEntry("4 - Diagnostics", "Compatibility Player PvP Logs", Toggle.Off, "Logs health before/after each player-versus-player ApplyDamage call, including zero-health-loss hits and PvP Biome Dominions spawn protection.", synchronized: false); CompatibilityRpcSummary = ConfigEntry("4 - Diagnostics", "Compatibility RPC Summary", Toggle.Off, "Counts BHC RPC packets and approximate bytes, then writes one compact periodic summary instead of logging every packet.", synchronized: false); CompatibilityInventoryLogs = ConfigEntry("4 - Diagnostics", "Compatibility Inventory Transactions", Toggle.Off, "Logs reward deposit and delivery inventory snapshots to help test ServerCharacters and anti-cheat interactions.", synchronized: false); CompatibilityCaptureExternalCaller = ConfigEntry("4 - Diagnostics", "Compatibility Capture External Caller", Toggle.Off, "Adds stack-trace caller information to selected PvP and damage logs. Enable only during focused tests because stack traces are more expensive.", synchronized: false); CompatibilitySummaryIntervalSeconds = ConfigEntry("4 - Diagnostics", "Compatibility Summary Interval Seconds", 60f, "Seconds between compact compatibility summaries. Values are clamped between 10 and 600 seconds.", synchronized: false); DiscordWebhookEnabled = ConfigEntry("5 - Discord Webhook", "Enabled", Toggle.Off, "Enables server-only Discord notifications for bounty contract events.", synchronized: false); DiscordWebhookUrl = ConfigEntry("5 - Discord Webhook", "Webhook URL", string.Empty, "Discord webhook URL. This value stays local to the server and is never synchronized to clients.", synchronized: false); DiscordWebhookLanguage = ConfigEntry("5 - Discord Webhook", "Language", "Spanish", "Language used by webhook messages. Supported values: Spanish or English.", synchronized: false); DiscordWebhookUsername = ConfigEntry("5 - Discord Webhook", "Username", "Bounty Hunter", "Display name used by the Discord webhook.", synchronized: false); DiscordWebhookAvatarUrl = ConfigEntry("5 - Discord Webhook", "Avatar URL", string.Empty, "Optional public image URL used as the webhook avatar.", synchronized: false); DiscordNotifyPublished = ConfigEntry("5 - Discord Webhook", "Notify Contract Published", Toggle.On, "Posts one Discord message when a contract is successfully published.", synchronized: false); DiscordNotifyResolved = ConfigEntry("5 - Discord Webhook", "Notify Contract Resolved", Toggle.On, "Posts when a hunter wins, the target defeats all hunters, the target survives the timer, or a contract expires without hunters.", synchronized: false); DiscordNotifyCancelled = ConfigEntry("5 - Discord Webhook", "Notify Contract Cancelled", Toggle.On, "Posts when the creator cancels a contract and reports what happened to the reward and bond.", synchronized: false); DiscordNotifyAdminRemoved = ConfigEntry("5 - Discord Webhook", "Notify Admin Removed", Toggle.Off, "Posts when an administrator removes a contract.", synchronized: false); } private ConfigEntry ConfigEntry(string section, string key, T value, string description, bool synchronized = true) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown ConfigDescription val = new ConfigDescription(description + (synchronized ? " [Synced with Server]" : " [Not Synced with Server]"), (AcceptableValueBase)null, Array.Empty()); ConfigEntry val2 = ((BaseUnityPlugin)this).Config.Bind(section, key, value, val); SyncedConfigEntry syncedConfigEntry = ConfigSync.AddConfigEntry(val2); syncedConfigEntry.SynchronizedConfig = synchronized; return val2; } private void SetupWatcher() { string text = "Azathoth.BountyHunterContract.cfg"; string fullPath = Path.Combine(Paths.ConfigPath, text); _watcher = new FileSystemWatcher(Paths.ConfigPath, text); _watcher.Changed += delegate { if (!File.Exists(fullPath)) { return; } try { ((BaseUnityPlugin)this).Config.Reload(); } catch (Exception ex) { BountyDiagnostics.Error("CONFIG", "Failed to reload configuration: " + ex.Message); } }; _watcher.Created += delegate { ((BaseUnityPlugin)this).Config.Reload(); }; _watcher.Renamed += delegate { ((BaseUnityPlugin)this).Config.Reload(); }; _watcher.SynchronizingObject = ThreadingHelper.SynchronizingObject; _watcher.EnableRaisingEvents = true; } } internal static class BountyDiscordWebhook { private sealed class WebhookJob { internal string Url = string.Empty; internal string Json = string.Empty; internal string EventName = string.Empty; } private sealed class WebhookField { internal string Name = string.Empty; internal string Value = string.Empty; internal bool Inline; } private static readonly object QueueLock = new object(); private static readonly Queue Queue = new Queue(); private static Coroutine? _workerCoroutine; private static bool _workerRunning; private static bool _shutdown; private const int MaximumQueuedMessages = 64; private const int MaximumAttempts = 3; private const int RequestTimeoutSeconds = 15; internal static void Initialize() { lock (QueueLock) { Queue.Clear(); _workerCoroutine = null; _workerRunning = false; _shutdown = false; } BountyDiagnostics.Info("DISCORD", "Webhook transport initialized with UnityWebRequest coroutine mode."); } internal static void Shutdown() { Coroutine workerCoroutine; lock (QueueLock) { _shutdown = true; Queue.Clear(); _workerRunning = false; workerCoroutine = _workerCoroutine; _workerCoroutine = null; } if (workerCoroutine != null && (Object)(object)BountyHuntercontractPlugin.Instance != (Object)null) { try { ((MonoBehaviour)BountyHuntercontractPlugin.Instance).StopCoroutine(workerCoroutine); } catch { } } } internal static void NotifyPublished(BountyContract contract, string durationText) { if (CanSend(BountyHuntercontractPlugin.DiscordNotifyPublished)) { bool english = IsEnglish(); List list = CommonFields(contract, english); list.Add(Field(T(english, "Cazadores", "Hunters"), "0/" + contract.MaximumHunters, inline: true)); list.Add(Field(T(english, "Duración", "Duration"), Clean(durationText, 256) + T(english, " de conexión del objetivo", " of target online time"), inline: true)); list.Add(Field(T(english, "Fianza", "Bond"), contract.BondCoins + " Coins", inline: true)); EnqueueEmbed(T(english, "\ud83d\udde1\ufe0f NUEVO CONTRATO DE ASESINATO", "\ud83d\udde1\ufe0f NEW BOUNTY CONTRACT"), T(english, "Se ha publicado un nuevo contrato en el servidor.", "A new bounty contract has been posted on the server."), 11673389, contract, list, "published"); } } internal static void NotifyCompleted(BountyContract contract, string evidence) { if (CanSend(BountyHuntercontractPlugin.DiscordNotifyResolved)) { bool english = IsEnglish(); string title; string description; int color; if (contract.Outcome == BountyOutcome.HunterVictory) { title = T(english, "✅ CONTRATO CUMPLIDO", "✅ CONTRACT COMPLETED"); description = T(english, Clean(contract.WinnerName, 128) + " eliminó al objetivo " + Clean(contract.TargetName, 128) + ".", Clean(contract.WinnerName, 128) + " eliminated the target " + Clean(contract.TargetName, 128) + "."); color = 3908957; } else if (EvidenceContains(evidence, "all hunters eliminated")) { title = T(english, "\ud83d\udee1\ufe0f EL OBJETIVO DERROTÓ A SUS CAZADORES", "\ud83d\udee1\ufe0f TARGET DEFEATED THE HUNTERS"); description = T(english, Clean(contract.TargetName, 128) + " eliminó a todos los cazadores disponibles y ganó la recompensa.", Clean(contract.TargetName, 128) + " eliminated every available hunter and won the reward."); color = 5793266; } else if (EvidenceContains(evidence, "duration expired")) { title = T(english, "⏳ EL OBJETIVO SOBREVIVIÓ AL TIEMPO", "⏳ TARGET SURVIVED THE TIMER"); description = T(english, Clean(contract.TargetName, 128) + " sobrevivió hasta que se agotó el tiempo activo del contrato.", Clean(contract.TargetName, 128) + " survived until the contract's active timer expired."); color = 15844367; } else { title = T(english, "\ud83d\udee1\ufe0f EL OBJETIVO SOBREVIVIÓ", "\ud83d\udee1\ufe0f TARGET SURVIVED"); description = T(english, Clean(contract.TargetName, 128) + " ganó el contrato y podrá cobrar la recompensa.", Clean(contract.TargetName, 128) + " won the contract and may claim the reward."); color = 5793266; } List list = CommonFields(contract, english); list.Add(Field(T(english, "Ganador", "Winner"), Clean(contract.WinnerName, 256), inline: true)); list.Add(Field(T(english, "Cazadores aceptados", "Accepted hunters"), contract.Hunters.Count + "/" + contract.MaximumHunters, inline: true)); list.Add(Field(T(english, "Cazadores eliminados", "Eliminated hunters"), contract.EliminatedHunterCount.ToString(), inline: true)); EnqueueEmbed(title, description, color, contract, list, "resolved"); } } internal static void NotifyExpiredWithoutHunters(BountyContract contract) { if (CanSend(BountyHuntercontractPlugin.DiscordNotifyResolved)) { bool english = IsEnglish(); List list = CommonFields(contract, english); list.Add(Field(T(english, "Resultado", "Result"), T(english, "La recompensa y la fianza regresaron al creador.", "The reward and bond were returned to the creator."), inline: false)); EnqueueEmbed(T(english, "⌛ CONTRATO CADUCADO SIN CAZADORES", "⌛ CONTRACT EXPIRED WITHOUT HUNTERS"), T(english, "Nadie aceptó el contrato antes de que terminara su tiempo.", "Nobody accepted the contract before its timer ended."), 9807270, contract, list, "expired-no-hunters"); } } internal static void NotifyCancelled(BountyContract contract, bool hadAcceptedHunters) { if (CanSend(BountyHuntercontractPlugin.DiscordNotifyCancelled)) { bool english = IsEnglish(); List list = new List { Field(T(english, "Publicado por", "Posted by"), Clean(contract.CreatorName, 256), inline: true), Field(T(english, "Objetivo", "Target"), Clean(contract.TargetName, 256), inline: true) }; list.Add(Field(T(english, "Recompensa", "Reward"), RewardLine(contract.Reward, english) + T(english, " · devuelta al creador", " · returned to creator"), inline: false)); list.Add(Field(T(english, "Fianza", "Bond"), hadAcceptedHunters ? T(english, contract.BondCoins + " Coins repartidos entre " + contract.Hunters.Count + " cazador(es).", contract.BondCoins + " Coins distributed among " + contract.Hunters.Count + " hunter(s).") : T(english, contract.BondCoins + " Coins devueltos al creador.", contract.BondCoins + " Coins returned to the creator."), inline: false)); EnqueueEmbed(T(english, "❌ CONTRATO CANCELADO", "❌ CONTRACT CANCELLED"), T(english, Clean(contract.CreatorName, 128) + " canceló el contrato contra " + Clean(contract.TargetName, 128) + ".", Clean(contract.CreatorName, 128) + " cancelled the contract against " + Clean(contract.TargetName, 128) + "."), 15548997, contract, list, "cancelled"); } } internal static void NotifyAdminRemoved(BountyContract contract, string rewardMode) { if (CanSend(BountyHuntercontractPlugin.DiscordNotifyAdminRemoved)) { bool english = IsEnglish(); List list = CommonFields(contract, english); list.Add(Field(T(english, "Resolución de recompensa", "Reward resolution"), Clean(rewardMode, 128), inline: false)); EnqueueEmbed(T(english, "\ud83d\udee0\ufe0f CONTRATO ELIMINADO POR ADMIN", "\ud83d\udee0\ufe0f CONTRACT REMOVED BY ADMIN"), T(english, "Un administrador eliminó este contrato.", "An administrator removed this contract."), 8359053, contract, list, "admin-removed"); } } private static List CommonFields(BountyContract contract, bool english) { return new List { Field(T(english, "Publicado por", "Posted by"), Clean(contract.CreatorName, 256), inline: true), Field(T(english, "Objetivo", "Target"), Clean(contract.TargetName, 256), inline: true), Field(T(english, "Recompensa", "Reward"), RewardLine(contract.Reward, english), inline: false) }; } private static WebhookField Field(string name, string value, bool inline) { return new WebhookField { Name = Clean(name, 256), Value = (string.IsNullOrWhiteSpace(value) ? "-" : Clean(value, 1024)), Inline = inline }; } private static string RewardLine(BountyRewardItem reward, bool english) { if (reward == null) { return T(english, "Recompensa desconocida", "Unknown reward"); } string value = ((!string.IsNullOrWhiteSpace(reward.DisplayName)) ? reward.DisplayName : reward.PrefabName); string text = "x" + Math.Max(1, reward.Stack) + " " + Clean(value, 384); if (reward.Quality > 1) { text = text + T(english, " · Calidad ", " · Quality ") + reward.Quality; } return text; } private static void EnqueueEmbed(string title, string description, int color, BountyContract contract, List fields, string eventName) { if (!TryGetWebhookUrl(out string url)) { return; } string json = BuildJson(title, description, color, contract, fields); bool flag = false; lock (QueueLock) { if (_shutdown) { _shutdown = false; } while (Queue.Count >= 64) { WebhookJob webhookJob = Queue.Dequeue(); BountyDiagnostics.Warning("DISCORD", "Webhook queue was full; discarded oldest event=" + webhookJob.EventName + "."); } Queue.Enqueue(new WebhookJob { Url = url, Json = json, EventName = eventName }); if (!_workerRunning) { _workerRunning = true; flag = true; } } if (flag) { StartWorker(); } } private static void StartWorker() { BountyHuntercontractPlugin instance = BountyHuntercontractPlugin.Instance; if ((Object)(object)instance == (Object)null) { lock (QueueLock) { _workerRunning = false; } BountyDiagnostics.Warning("DISCORD", "Could not start webhook coroutine because the plugin instance is unavailable."); return; } try { _workerCoroutine = ((MonoBehaviour)instance).StartCoroutine(ProcessQueue()); } catch (Exception ex) { lock (QueueLock) { _workerCoroutine = null; _workerRunning = false; } BountyDiagnostics.Warning("DISCORD", "Could not start webhook coroutine: " + Clean(ex.Message, 300) + "."); } } private static IEnumerator ProcessQueue() { while (true) { WebhookJob job = null; bool stop; lock (QueueLock) { stop = _shutdown || Queue.Count == 0; if (stop) { _workerRunning = false; _workerCoroutine = null; } else { job = Queue.Dequeue(); } } if (stop || job == null) { break; } yield return SendWithRetry(job); } } private static IEnumerator SendWithRetry(WebhookJob job) { int attempt = 1; while (attempt <= 3) { long statusCode = 0L; string error = string.Empty; string responseBody = string.Empty; float retryDelaySeconds = 1.5f * (float)attempt; UnityWebRequest request = new UnityWebRequest(job.Url, "POST"); bool retryable; try { byte[] body = Encoding.UTF8.GetBytes(job.Json); request.uploadHandler = (UploadHandler)new UploadHandlerRaw(body); request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer(); request.SetRequestHeader("Content-Type", "application/json"); request.SetRequestHeader("Accept", "application/json"); request.timeout = 15; yield return request.SendWebRequest(); statusCode = request.responseCode; if ((int)request.result == 1 && statusCode >= 200 && statusCode < 300) { BountyDiagnostics.Info("DISCORD", "Webhook event sent event=" + job.EventName + " status=" + statusCode + " attempt=" + attempt + "."); break; } error = (string.IsNullOrWhiteSpace(request.error) ? ((object)request.result/*cast due to .constrained prefix*/).ToString() : request.error); DownloadHandler downloadHandler = request.downloadHandler; responseBody = ((downloadHandler != null) ? downloadHandler.text : null) ?? string.Empty; retryDelaySeconds = ResolveRetryDelaySeconds(request, attempt); retryable = (int)request.result == 2 || statusCode == 0L || statusCode == 408 || statusCode == 429 || statusCode >= 500; } finally { ((IDisposable)request)?.Dispose(); } if (!retryable || attempt >= 3) { BountyDiagnostics.Warning("DISCORD", "Webhook event failed event=" + job.EventName + " status=" + statusCode + " attempt=" + attempt + "/" + 3 + " error='" + Clean(error, 300) + "'" + (string.IsNullOrWhiteSpace(responseBody) ? string.Empty : (" response='" + Clean(responseBody, 500) + "'")) + "."); break; } BountyDiagnostics.Warning("DISCORD", "Webhook event retry event=" + job.EventName + " status=" + statusCode + " attempt=" + attempt + "/" + 3 + " wait=" + retryDelaySeconds.ToString("0.0", CultureInfo.InvariantCulture) + "s error='" + Clean(error, 200) + "'."); yield return (object)new WaitForSecondsRealtime(retryDelaySeconds); int num = attempt + 1; attempt = num; } } private static float ResolveRetryDelaySeconds(UnityWebRequest request, int attempt) { string s = request.GetResponseHeader("Retry-After") ?? string.Empty; if (double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && result > 0.0) { double num = ((result > 1000.0) ? (result / 1000.0) : result); return Mathf.Clamp((float)num, 0.5f, 30f); } DownloadHandler downloadHandler = request.downloadHandler; string text = ((downloadHandler != null) ? downloadHandler.text : null) ?? string.Empty; int num2 = text.IndexOf("\"retry_after\":", StringComparison.OrdinalIgnoreCase); if (num2 >= 0) { int num3 = num2 + "\"retry_after\":".Length; int i; for (i = num3; i < text.Length && (char.IsDigit(text[i]) || text[i] == '.' || text[i] == ','); i++) { } string s2 = text.Substring(num3, i - num3).Replace(',', '.'); if (double.TryParse(s2, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2) && result2 > 0.0) { double num4 = ((result2 > 1000.0) ? (result2 / 1000.0) : result2); return Mathf.Clamp((float)num4, 0.5f, 30f); } } return Mathf.Clamp(1.5f * (float)attempt, 0.5f, 10f); } private static bool CanSend(ConfigEntry? eventToggle) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return false; } if (BountyHuntercontractPlugin.DiscordWebhookEnabled == null || BountyHuntercontractPlugin.DiscordWebhookEnabled.Value != BountyHuntercontractPlugin.Toggle.On) { return false; } if (eventToggle == null || eventToggle.Value != BountyHuntercontractPlugin.Toggle.On) { return false; } string url; return TryGetWebhookUrl(out url); } private static bool TryGetWebhookUrl(out string url) { url = (BountyHuntercontractPlugin.DiscordWebhookUrl?.Value ?? string.Empty).Trim().Trim(new char[1] { '"' }); if (string.IsNullOrWhiteSpace(url)) { return false; } if (!Uri.TryCreate(url, UriKind.Absolute, out Uri result) || !string.Equals(result.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase) || result.AbsolutePath.IndexOf("/api/webhooks/", StringComparison.OrdinalIgnoreCase) < 0) { BountyDiagnostics.Warning("DISCORD", "Webhook URL is invalid. Expected an HTTPS Discord /api/webhooks/ URL."); url = string.Empty; return false; } return true; } private static bool IsEnglish() { string text = BountyHuntercontractPlugin.DiscordWebhookLanguage?.Value ?? "Spanish"; return text.StartsWith("en", StringComparison.OrdinalIgnoreCase) || text.IndexOf("english", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("ingl", StringComparison.OrdinalIgnoreCase) >= 0; } private static string BuildJson(string title, string description, int color, BountyContract contract, List fields) { string value = Clean(BountyHuntercontractPlugin.DiscordWebhookUsername?.Value ?? "Bounty Hunter", 80); string text = (BountyHuntercontractPlugin.DiscordWebhookAvatarUrl?.Value ?? string.Empty).Trim(); if (!Uri.TryCreate(text, UriKind.Absolute, out Uri result) || (result.Scheme != Uri.UriSchemeHttps && result.Scheme != Uri.UriSchemeHttp)) { text = string.Empty; } StringBuilder stringBuilder = new StringBuilder(2048); stringBuilder.Append('{'); stringBuilder.Append("\"username\":\"").Append(Json(value)).Append("\","); if (!string.IsNullOrWhiteSpace(text)) { stringBuilder.Append("\"avatar_url\":\"").Append(Json(text)).Append("\","); } stringBuilder.Append("\"allowed_mentions\":{\"parse\":[]},"); stringBuilder.Append("\"embeds\":[{"); stringBuilder.Append("\"title\":\"").Append(Json(Clean(title, 256))).Append("\","); stringBuilder.Append("\"description\":\"").Append(Json(Clean(description, 4096))).Append("\","); stringBuilder.Append("\"color\":").Append(color).Append(','); stringBuilder.Append("\"fields\":["); for (int i = 0; i < fields.Count && i < 25; i++) { if (i > 0) { stringBuilder.Append(','); } WebhookField webhookField = fields[i]; stringBuilder.Append('{').Append("\"name\":\"").Append(Json(webhookField.Name)) .Append("\",") .Append("\"value\":\"") .Append(Json(webhookField.Value)) .Append("\",") .Append("\"inline\":") .Append(webhookField.Inline ? "true" : "false") .Append('}'); } stringBuilder.Append("],"); stringBuilder.Append("\"footer\":{\"text\":\"").Append(Json("BHC · " + ShortId(contract.ContractId))).Append("\"},"); long num = ((contract.FinishedUtcTicks > 0) ? contract.FinishedUtcTicks : contract.CreatedUtcTicks); DateTime minValue = DateTime.MinValue; DateTime dateTime; if (num > minValue.Ticks) { minValue = DateTime.MaxValue; if (num < minValue.Ticks) { dateTime = new DateTime(num, DateTimeKind.Utc); goto IL_02d6; } } dateTime = DateTime.UtcNow; goto IL_02d6; IL_02d6: DateTime dateTime2 = dateTime; stringBuilder.Append("\"timestamp\":\"").Append(Json(dateTime2.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"))).Append("\""); stringBuilder.Append("}]}"); return stringBuilder.ToString(); } private static string Json(string value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(value.Length + 16); foreach (char c in value) { switch (c) { case '\\': stringBuilder.Append("\\\\"); continue; case '"': stringBuilder.Append("\\\""); continue; case '\n': stringBuilder.Append("\\n"); continue; case '\r': stringBuilder.Append("\\r"); continue; case '\t': stringBuilder.Append("\\t"); continue; } if (c < ' ') { StringBuilder stringBuilder2 = stringBuilder.Append("\\u"); int num = c; stringBuilder2.Append(num.ToString("x4")); } else { stringBuilder.Append(c); } } return stringBuilder.ToString(); } private static string Clean(string value, int maximum) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(Math.Min(value.Length, maximum)); foreach (char c in value) { if (c != '\r' && (c >= ' ' || c == '\n' || c == '\t')) { stringBuilder.Append(c); if (stringBuilder.Length >= maximum) { break; } } } return stringBuilder.ToString().Trim(); } private static string ShortId(string contractId) { if (string.IsNullOrWhiteSpace(contractId)) { return "UNKNOWN"; } return (contractId.Length <= 8) ? contractId.ToUpperInvariant() : contractId.Substring(0, 8).ToUpperInvariant(); } private static bool EvidenceContains(string evidence, string value) { return !string.IsNullOrWhiteSpace(evidence) && evidence.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0; } private static string T(bool english, string spanish, string englishText) { return english ? englishText : spanish; } } } namespace ServerSync { [PublicAPI] internal abstract class OwnConfigEntryBase { public object? LocalBaseValue; public bool SynchronizedConfig = true; public abstract ConfigEntryBase BaseConfig { get; } } [PublicAPI] internal class SyncedConfigEntry(ConfigEntry sourceConfig) : OwnConfigEntryBase() { public readonly ConfigEntry SourceConfig = sourceConfig; public override ConfigEntryBase BaseConfig => (ConfigEntryBase)(object)SourceConfig; public T Value { get { return SourceConfig.Value; } set { SourceConfig.Value = value; } } public void AssignLocalValue(T value) { if (LocalBaseValue == null) { Value = value; } else { LocalBaseValue = value; } } } internal abstract class CustomSyncedValueBase { public object? LocalBaseValue; public readonly string Identifier; public readonly Type Type; private object? boxedValue; protected bool localIsOwner; public readonly int Priority; public object? BoxedValue { get { return boxedValue; } set { boxedValue = value; this.ValueChanged?.Invoke(); } } public event Action? ValueChanged; protected CustomSyncedValueBase(ConfigSync configSync, string identifier, Type type, int priority) { Priority = priority; Identifier = identifier; Type = type; configSync.AddCustomValue(this); localIsOwner = configSync.IsSourceOfTruth; configSync.SourceOfTruthChanged += delegate(bool truth) { localIsOwner = truth; }; } } [PublicAPI] internal sealed class CustomSyncedValue : CustomSyncedValueBase { public T Value { get { return (T)base.BoxedValue; } set { base.BoxedValue = value; } } public CustomSyncedValue(ConfigSync configSync, string identifier, T value = default(T), int priority = 0) : base(configSync, identifier, typeof(T), priority) { Value = value; } public void AssignLocalValue(T value) { if (localIsOwner) { Value = value; } else { LocalBaseValue = value; } } } internal class ConfigurationManagerAttributes { [UsedImplicitly] public bool? ReadOnly = false; } [PublicAPI] internal class ConfigSync { [HarmonyPatch(typeof(ZRpc), "HandlePackage")] private static class SnatchCurrentlyHandlingRPC { public static ZRpc? currentRpc; [HarmonyPrefix] private static void Prefix(ZRpc __instance) { currentRpc = __instance; } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class RegisterRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance) { isServer = __instance.IsServer(); foreach (ConfigSync configSync2 in configSyncs) { ZRoutedRpc.instance.Register(configSync2.Name + " ConfigSync", (Action)configSync2.RPC_FromOtherClientConfigSync); if (isServer) { configSync2.InitialSyncDone = true; Debug.Log((object)("Registered '" + configSync2.Name + " ConfigSync' RPC - waiting for incoming connections")); } } if (isServer) { ((MonoBehaviour)__instance).StartCoroutine(WatchAdminListChanges()); } static void SendAdmin(List peers, bool isAdmin) { ZPackage package = ConfigsToPackage(null, null, new PackageEntry[1] { new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = isAdmin } }); ConfigSync configSync = configSyncs.First(); if (configSync != null) { ((MonoBehaviour)ZNet.instance).StartCoroutine(configSync.sendZPackage(peers, package)); } } static IEnumerator WatchAdminListChanges() { MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); List CurrentList = new List(adminList.GetList()); while (true) { yield return (object)new WaitForSeconds(30f); if (!adminList.GetList().SequenceEqual(CurrentList)) { CurrentList = new List(adminList.GetList()); List adminPeer = ZNet.instance.GetPeers().Where(delegate(ZNetPeer p) { string hostName = p.m_rpc.GetSocket().GetHostName(); return ((object)listContainsId == null) ? adminList.Contains(hostName) : ((bool)listContainsId.Invoke(ZNet.instance, new object[2] { adminList, hostName })); }).ToList(); List nonAdminPeer = ZNet.instance.GetPeers().Except(adminPeer).ToList(); SendAdmin(nonAdminPeer, isAdmin: false); SendAdmin(adminPeer, isAdmin: true); } } } } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class RegisterClientRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance, ZNetPeer peer) { if (__instance.IsServer()) { return; } foreach (ConfigSync configSync in configSyncs) { peer.m_rpc.Register(configSync.Name + " ConfigSync", (Action)configSync.RPC_FromServerConfigSync); } } } private class ParsedConfigs { public readonly Dictionary configValues = new Dictionary(); public readonly Dictionary customValues = new Dictionary(); } [HarmonyPatch(typeof(ZNet), "Shutdown")] private class ResetConfigsOnShutdown { [HarmonyPostfix] private static void Postfix() { ProcessingServerUpdate = true; foreach (ConfigSync configSync in configSyncs) { configSync.resetConfigsFromServer(); configSync.IsSourceOfTruth = true; configSync.InitialSyncDone = false; } ProcessingServerUpdate = false; } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private class SendConfigsAfterLogin { private class BufferingSocket : ZPlayFabSocket, ISocket { public volatile bool finished = false; public volatile int versionMatchQueued = -1; public readonly List Package = new List(); public readonly ISocket Original; public BufferingSocket(ISocket original) { Original = original; ((ZPlayFabSocket)this)..ctor(); } public bool IsConnected() { return Original.IsConnected(); } public ZPackage Recv() { return Original.Recv(); } public int GetSendQueueSize() { return Original.GetSendQueueSize(); } public int GetCurrentSendRate() { return Original.GetCurrentSendRate(); } public bool IsHost() { return Original.IsHost(); } public void Dispose() { Original.Dispose(); } public bool GotNewData() { return Original.GotNewData(); } public void Close() { Original.Close(); } public string GetEndPointString() { return Original.GetEndPointString(); } public void GetAndResetStats(out int totalSent, out int totalRecv) { Original.GetAndResetStats(ref totalSent, ref totalRecv); } public void GetConnectionQuality(out float localQuality, out float remoteQuality, out int ping, out float outByteSec, out float inByteSec) { Original.GetConnectionQuality(ref localQuality, ref remoteQuality, ref ping, ref outByteSec, ref inByteSec); } public ISocket Accept() { return Original.Accept(); } public int GetHostPort() { return Original.GetHostPort(); } public bool Flush() { return Original.Flush(); } public string GetHostName() { return Original.GetHostName(); } public void VersionMatch() { if (finished) { Original.VersionMatch(); } else { versionMatchQueued = Package.Count; } } public void Send(ZPackage pkg) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown int pos = pkg.GetPos(); pkg.SetPos(0); int num = pkg.ReadInt(); if ((num == StringExtensionMethods.GetStableHashCode("PeerInfo") || num == StringExtensionMethods.GetStableHashCode("RoutedRPC") || num == StringExtensionMethods.GetStableHashCode("ZDOData")) && !finished) { ZPackage val = new ZPackage(pkg.GetArray()); val.SetPos(pos); Package.Add(val); } else { pkg.SetPos(pos); Original.Send(pkg); } } } [HarmonyPriority(800)] [HarmonyPrefix] private static void Prefix(ref Dictionary? __state, ZNet __instance, ZRpc rpc) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Invalid comparison between Unknown and I4 if (!__instance.IsServer()) { return; } BufferingSocket bufferingSocket = new BufferingSocket(rpc.GetSocket()); AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket); object? obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (val != null && (int)ZNet.m_onlineBackend > 0) { FieldInfo fieldInfo = AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket"); object? value = fieldInfo.GetValue(val); ZPlayFabSocket val2 = (ZPlayFabSocket)((value is ZPlayFabSocket) ? value : null); if (val2 != null) { typeof(ZPlayFabSocket).GetField("m_remotePlayerId").SetValue(bufferingSocket, val2.m_remotePlayerId); } fieldInfo.SetValue(val, bufferingSocket); } if (__state == null) { __state = new Dictionary(); } __state[Assembly.GetExecutingAssembly()] = bufferingSocket; } [HarmonyPostfix] private static void Postfix(Dictionary __state, ZNet __instance, ZRpc rpc) { ZNetPeer peer; if (__instance.IsServer()) { object obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); peer = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (peer == null) { SendBufferedData(); } else { ((MonoBehaviour)__instance).StartCoroutine(sendAsync()); } } void SendBufferedData() { if (rpc.GetSocket() is BufferingSocket bufferingSocket) { AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket.Original); object? obj2 = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj2 is ZNetPeer) ? obj2 : null); if (val != null) { AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket").SetValue(val, bufferingSocket.Original); } } BufferingSocket bufferingSocket2 = __state[Assembly.GetExecutingAssembly()]; bufferingSocket2.finished = true; for (int i = 0; i < bufferingSocket2.Package.Count; i++) { if (i == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } bufferingSocket2.Original.Send(bufferingSocket2.Package[i]); } if (bufferingSocket2.Package.Count == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } } IEnumerator sendAsync() { foreach (ConfigSync configSync in configSyncs) { List entries = new List(); if (configSync.CurrentVersion != null) { entries.Add(new PackageEntry { section = "Internal", key = "serverversion", type = typeof(string), value = configSync.CurrentVersion }); } MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); entries.Add(new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = (((object)listContainsId == null) ? ((object)adminList.Contains(rpc.GetSocket().GetHostName())) : listContainsId.Invoke(ZNet.instance, new object[2] { adminList, rpc.GetSocket().GetHostName() })) }); ZPackage package = ConfigsToPackage(configSync.allConfigs.Select((OwnConfigEntryBase c) => c.BaseConfig), configSync.allCustomValues, entries, partial: false); yield return ((MonoBehaviour)__instance).StartCoroutine(configSync.sendZPackage(new List { peer }, package)); } SendBufferedData(); } } } private class PackageEntry { public string section = null; public string key = null; public Type type = null; public object? value; } [HarmonyPatch(typeof(ConfigEntryBase), "GetSerializedValue")] private static class PreventSavingServerInfo { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, ref string __result) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || isWritableConfig(ownConfigEntryBase)) { return true; } __result = TomlTypeConverter.ConvertToString(ownConfigEntryBase.LocalBaseValue, __instance.SettingType); return false; } } [HarmonyPatch(typeof(ConfigEntryBase), "SetSerializedValue")] private static class PreventConfigRereadChangingValues { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, string value) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || ownConfigEntryBase.LocalBaseValue == null) { return true; } try { ownConfigEntryBase.LocalBaseValue = TomlTypeConverter.ConvertToValue(value, __instance.SettingType); } catch (Exception ex) { Debug.LogWarning((object)$"Config value of setting \"{__instance.Definition}\" could not be parsed and will be ignored. Reason: {ex.Message}; Value: {value}"); } return false; } } private class InvalidDeserializationTypeException : Exception { public string expected = null; public string received = null; public string field = ""; } public static bool ProcessingServerUpdate; public readonly string Name; public string? DisplayName; public string? CurrentVersion; public string? MinimumRequiredVersion; public bool ModRequired = false; private bool? forceConfigLocking; private bool isSourceOfTruth = true; private static readonly HashSet configSyncs; private readonly HashSet allConfigs = new HashSet(); private HashSet allCustomValues = new HashSet(); private static bool isServer; private static bool lockExempt; private OwnConfigEntryBase? lockedConfig = null; private const byte PARTIAL_CONFIGS = 1; private const byte FRAGMENTED_CONFIG = 2; private const byte COMPRESSED_CONFIG = 4; private readonly Dictionary> configValueCache = new Dictionary>(); private readonly List> cacheExpirations = new List>(); private static long packageCounter; public bool IsLocked { get { bool? flag = forceConfigLocking; bool num; if (!flag.HasValue) { if (lockedConfig == null) { goto IL_0052; } num = ((IConvertible)lockedConfig.BaseConfig.BoxedValue).ToInt32(CultureInfo.InvariantCulture) != 0; } else { num = flag == true; } if (!num) { goto IL_0052; } int result = ((!lockExempt) ? 1 : 0); goto IL_0053; IL_0052: result = 0; goto IL_0053; IL_0053: return (byte)result != 0; } set { forceConfigLocking = value; } } public bool IsAdmin => lockExempt || isSourceOfTruth; public bool IsSourceOfTruth { get { return isSourceOfTruth; } private set { if (value != isSourceOfTruth) { isSourceOfTruth = value; this.SourceOfTruthChanged?.Invoke(value); } } } public bool InitialSyncDone { get; private set; } = false; public event Action? SourceOfTruthChanged; private event Action? lockedConfigChanged; static ConfigSync() { ProcessingServerUpdate = false; configSyncs = new HashSet(); lockExempt = false; packageCounter = 0L; RuntimeHelpers.RunClassConstructor(typeof(VersionCheck).TypeHandle); } public ConfigSync(string name) { Name = name; configSyncs.Add(this); new VersionCheck(this); } public SyncedConfigEntry AddConfigEntry(ConfigEntry configEntry) { OwnConfigEntryBase ownConfigEntryBase = configData((ConfigEntryBase)(object)configEntry); SyncedConfigEntry syncedEntry = ownConfigEntryBase as SyncedConfigEntry; if (syncedEntry == null) { syncedEntry = new SyncedConfigEntry(configEntry); AccessTools.DeclaredField(typeof(ConfigDescription), "k__BackingField").SetValue(((ConfigEntryBase)configEntry).Description, new object[1] { new ConfigurationManagerAttributes() }.Concat(((ConfigEntryBase)configEntry).Description.Tags ?? Array.Empty()).Concat(new SyncedConfigEntry[1] { syncedEntry }).ToArray()); configEntry.SettingChanged += delegate { if (!ProcessingServerUpdate && syncedEntry.SynchronizedConfig) { Broadcast(ZRoutedRpc.Everybody, (ConfigEntryBase)configEntry); } }; allConfigs.Add(syncedEntry); } return syncedEntry; } public SyncedConfigEntry AddLockingConfigEntry(ConfigEntry lockingConfig) where T : IConvertible { if (lockedConfig != null) { throw new Exception("Cannot initialize locking ConfigEntry twice"); } lockedConfig = AddConfigEntry(lockingConfig); lockingConfig.SettingChanged += delegate { this.lockedConfigChanged?.Invoke(); }; return (SyncedConfigEntry)lockedConfig; } internal void AddCustomValue(CustomSyncedValueBase customValue) { if (allCustomValues.Select((CustomSyncedValueBase v) => v.Identifier).Concat(new string[1] { "serverversion" }).Contains(customValue.Identifier)) { throw new Exception("Cannot have multiple settings with the same name or with a reserved name (serverversion)"); } allCustomValues.Add(customValue); allCustomValues = new HashSet(allCustomValues.OrderByDescending((CustomSyncedValueBase v) => v.Priority)); customValue.ValueChanged += delegate { if (!ProcessingServerUpdate) { Broadcast(ZRoutedRpc.Everybody, customValue); } }; } private void RPC_FromServerConfigSync(ZRpc rpc, ZPackage package) { lockedConfigChanged += serverLockedSettingChanged; IsSourceOfTruth = false; if (HandleConfigSyncRPC(0L, package, clientUpdate: false)) { InitialSyncDone = true; } } private void RPC_FromOtherClientConfigSync(long sender, ZPackage package) { HandleConfigSyncRPC(sender, package, clientUpdate: true); } private bool HandleConfigSyncRPC(long sender, ZPackage package, bool clientUpdate) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Expected O, but got Unknown //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Expected O, but got Unknown try { if (isServer && IsLocked) { ZRpc? currentRpc = SnatchCurrentlyHandlingRPC.currentRpc; object obj; if (currentRpc == null) { obj = null; } else { ISocket socket = currentRpc.GetSocket(); obj = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj; if (text != null) { MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList val = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); if (!(((object)methodInfo == null) ? val.Contains(text) : ((bool)methodInfo.Invoke(ZNet.instance, new object[2] { val, text })))) { return false; } } } cacheExpirations.RemoveAll(delegate(KeyValuePair kv) { if (kv.Key < DateTimeOffset.Now.Ticks) { configValueCache.Remove(kv.Value); return true; } return false; }); byte b = package.ReadByte(); if ((b & 2) != 0) { long num = package.ReadLong(); string text2 = sender.ToString() + num; if (!configValueCache.TryGetValue(text2, out SortedDictionary value)) { value = new SortedDictionary(); configValueCache[text2] = value; cacheExpirations.Add(new KeyValuePair(DateTimeOffset.Now.AddSeconds(60.0).Ticks, text2)); } int key = package.ReadInt(); int num2 = package.ReadInt(); value.Add(key, package.ReadByteArray()); if (value.Count < num2) { return false; } configValueCache.Remove(text2); package = new ZPackage(value.Values.SelectMany((byte[] a) => a).ToArray()); b = package.ReadByte(); } ProcessingServerUpdate = true; if ((b & 4) != 0) { byte[] buffer = package.ReadByteArray(); MemoryStream stream = new MemoryStream(buffer); MemoryStream memoryStream = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress)) { deflateStream.CopyTo(memoryStream); } package = new ZPackage(memoryStream.ToArray()); b = package.ReadByte(); } if ((b & 1) == 0) { resetConfigsFromServer(); } ParsedConfigs parsedConfigs = ReadConfigsFromPackage(package); ConfigFile val2 = null; bool saveOnConfigSet = false; foreach (KeyValuePair configValue in parsedConfigs.configValues) { if (!isServer && configValue.Key.LocalBaseValue == null) { configValue.Key.LocalBaseValue = configValue.Key.BaseConfig.BoxedValue; } if (val2 == null) { val2 = configValue.Key.BaseConfig.ConfigFile; saveOnConfigSet = val2.SaveOnConfigSet; val2.SaveOnConfigSet = false; } configValue.Key.BaseConfig.BoxedValue = configValue.Value; } if (val2 != null) { val2.SaveOnConfigSet = saveOnConfigSet; val2.Save(); } foreach (KeyValuePair customValue in parsedConfigs.customValues) { if (!isServer) { CustomSyncedValueBase key2 = customValue.Key; if (key2.LocalBaseValue == null) { key2.LocalBaseValue = customValue.Key.BoxedValue; } } customValue.Key.BoxedValue = customValue.Value; } Debug.Log((object)string.Format("Received {0} configs and {1} custom values from {2} for mod {3}", parsedConfigs.configValues.Count, parsedConfigs.customValues.Count, (isServer || clientUpdate) ? $"client {sender}" : "the server", DisplayName ?? Name)); if (!isServer) { serverLockedSettingChanged(); } return true; } finally { ProcessingServerUpdate = false; } } private ParsedConfigs ReadConfigsFromPackage(ZPackage package) { ParsedConfigs parsedConfigs = new ParsedConfigs(); Dictionary dictionary = allConfigs.Where((OwnConfigEntryBase c) => c.SynchronizedConfig).ToDictionary((OwnConfigEntryBase c) => c.BaseConfig.Definition.Section + "_" + c.BaseConfig.Definition.Key, (OwnConfigEntryBase c) => c); Dictionary dictionary2 = allCustomValues.ToDictionary((CustomSyncedValueBase c) => c.Identifier, (CustomSyncedValueBase c) => c); int num = package.ReadInt(); for (int num2 = 0; num2 < num; num2++) { string text = package.ReadString(); string text2 = package.ReadString(); string text3 = package.ReadString(); Type type = Type.GetType(text3); if (text3 == "" || type != null) { object obj; try { obj = ((text3 == "") ? null : ReadValueWithTypeFromZPackage(package, type)); } catch (InvalidDeserializationTypeException ex) { Debug.LogWarning((object)("Got unexpected struct internal type " + ex.received + " for field " + ex.field + " struct " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + ex.expected)); continue; } OwnConfigEntryBase value2; if (text == "Internal") { CustomSyncedValueBase value; if (text2 == "serverversion") { if (obj?.ToString() != CurrentVersion) { Debug.LogWarning((object)("Received server version is not equal: server version = " + (obj?.ToString() ?? "null") + "; local version = " + (CurrentVersion ?? "unknown"))); } } else if (text2 == "lockexempt") { if (obj is bool flag) { lockExempt = flag; } } else if (dictionary2.TryGetValue(text2, out value)) { if ((text3 == "" && (!value.Type.IsValueType || Nullable.GetUnderlyingType(value.Type) != null)) || GetZPackageTypeString(value.Type) == text3) { parsedConfigs.customValues[value] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for internal value " + text2 + " for mod " + (DisplayName ?? Name) + ", expecting " + value.Type.AssemblyQualifiedName)); } } else if (dictionary.TryGetValue(text + "_" + text2, out value2)) { Type type2 = configType(value2.BaseConfig); if ((text3 == "" && (!type2.IsValueType || Nullable.GetUnderlyingType(type2) != null)) || GetZPackageTypeString(type2) == text3) { parsedConfigs.configValues[value2] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + type2.AssemblyQualifiedName)); } else { Debug.LogWarning((object)("Received unknown config entry " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ". This may happen if client and server versions of the mod do not match.")); } continue; } Debug.LogWarning((object)("Got invalid type " + text3 + ", abort reading of received configs")); return new ParsedConfigs(); } return parsedConfigs; } private static bool isWritableConfig(OwnConfigEntryBase config) { ConfigSync configSync = configSyncs.FirstOrDefault((ConfigSync cs) => cs.allConfigs.Contains(config)); if (configSync == null) { return true; } return configSync.IsSourceOfTruth || !config.SynchronizedConfig || config.LocalBaseValue == null || (!configSync.IsLocked && (config != configSync.lockedConfig || lockExempt)); } private void serverLockedSettingChanged() { foreach (OwnConfigEntryBase allConfig in allConfigs) { configAttribute(allConfig.BaseConfig).ReadOnly = !isWritableConfig(allConfig); } } private void resetConfigsFromServer() { ConfigFile val = null; bool saveOnConfigSet = false; foreach (OwnConfigEntryBase item in allConfigs.Where((OwnConfigEntryBase config) => config.LocalBaseValue != null)) { if (val == null) { val = item.BaseConfig.ConfigFile; saveOnConfigSet = val.SaveOnConfigSet; val.SaveOnConfigSet = false; } item.BaseConfig.BoxedValue = item.LocalBaseValue; item.LocalBaseValue = null; } if (val != null) { val.SaveOnConfigSet = saveOnConfigSet; } foreach (CustomSyncedValueBase item2 in allCustomValues.Where((CustomSyncedValueBase config) => config.LocalBaseValue != null)) { item2.BoxedValue = item2.LocalBaseValue; item2.LocalBaseValue = null; } lockedConfigChanged -= serverLockedSettingChanged; serverLockedSettingChanged(); } private IEnumerator distributeConfigToPeers(ZNetPeer peer, ZPackage package) { ZRoutedRpc rpc = ZRoutedRpc.instance; if (rpc == null) { yield break; } byte[] data = package.GetArray(); if (data != null && data.LongLength > 250000) { int fragments = (int)(1 + (data.LongLength - 1) / 250000); long packageIdentifier = ++packageCounter; int fragment = 0; while (fragment < fragments) { foreach (bool item in waitForQueue()) { yield return item; } if (peer.m_socket.IsConnected()) { ZPackage fragmentedPackage = new ZPackage(); fragmentedPackage.Write((byte)2); fragmentedPackage.Write(packageIdentifier); fragmentedPackage.Write(fragment); fragmentedPackage.Write(fragments); fragmentedPackage.Write(data.Skip(250000 * fragment).Take(250000).ToArray()); SendPackage(fragmentedPackage); if (fragment != fragments - 1) { yield return true; } int num = fragment + 1; fragment = num; continue; } break; } yield break; } foreach (bool item2 in waitForQueue()) { yield return item2; } SendPackage(package); void SendPackage(ZPackage pkg) { string text = Name + " ConfigSync"; if (isServer) { peer.m_rpc.Invoke(text, new object[1] { pkg }); } else { rpc.InvokeRoutedRPC(peer.m_server ? 0 : peer.m_uid, text, new object[1] { pkg }); } } IEnumerable waitForQueue() { float timeout = Time.time + 30f; while (peer.m_socket.GetSendQueueSize() > 20000) { if (Time.time > timeout) { Debug.Log((object)$"Disconnecting {peer.m_uid} after 30 seconds config sending timeout"); peer.m_rpc.Invoke("Error", new object[1] { (object)(ConnectionStatus)5 }); ZNet.instance.Disconnect(peer); break; } yield return false; } } } private IEnumerator sendZPackage(long target, ZPackage package) { if (!Object.op_Implicit((Object)(object)ZNet.instance)) { return Enumerable.Empty().GetEnumerator(); } List list = (List)AccessTools.DeclaredField(typeof(ZRoutedRpc), "m_peers").GetValue(ZRoutedRpc.instance); if (target != ZRoutedRpc.Everybody) { list = list.Where((ZNetPeer p) => p.m_uid == target).ToList(); } return sendZPackage(list, package); } private IEnumerator sendZPackage(List peers, ZPackage package) { if (!Object.op_Implicit((Object)(object)ZNet.instance)) { yield break; } byte[] rawData = package.GetArray(); if (rawData != null && rawData.LongLength > 10000) { ZPackage compressedPackage = new ZPackage(); compressedPackage.Write((byte)4); MemoryStream output = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(output, CompressionLevel.Optimal)) { deflateStream.Write(rawData, 0, rawData.Length); } compressedPackage.Write(output.ToArray()); package = compressedPackage; } List> writers = (from p in peers where p.IsReady() select distributeConfigToPeers(p, package)).ToList(); writers.RemoveAll((IEnumerator writer) => !writer.MoveNext()); while (writers.Count > 0) { yield return null; writers.RemoveAll((IEnumerator writer) => !writer.MoveNext()); } } private void Broadcast(long target, params ConfigEntryBase[] configs) { if (!IsLocked || isServer) { ZPackage package = ConfigsToPackage(configs); ZNet instance = ZNet.instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(sendZPackage(target, package)); } } } private void Broadcast(long target, params CustomSyncedValueBase[] customValues) { if (!IsLocked || isServer) { ZPackage package = ConfigsToPackage(null, customValues); ZNet instance = ZNet.instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(sendZPackage(target, package)); } } } private static OwnConfigEntryBase? configData(ConfigEntryBase config) { return config.Description.Tags?.OfType().SingleOrDefault(); } public static SyncedConfigEntry? ConfigData(ConfigEntry config) { return ((ConfigEntryBase)config).Description.Tags?.OfType>().SingleOrDefault(); } private static T configAttribute(ConfigEntryBase config) { return config.Description.Tags.OfType().First(); } private static Type configType(ConfigEntryBase config) { return configType(config.SettingType); } private static Type configType(Type type) { return type.IsEnum ? Enum.GetUnderlyingType(type) : type; } private static ZPackage ConfigsToPackage(IEnumerable? configs = null, IEnumerable? customValues = null, IEnumerable? packageEntries = null, bool partial = true) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown List list = configs?.Where((ConfigEntryBase config) => configData(config).SynchronizedConfig).ToList() ?? new List(); List list2 = customValues?.ToList() ?? new List(); ZPackage val = new ZPackage(); val.Write((byte)(partial ? 1 : 0)); val.Write(list.Count + list2.Count + (packageEntries?.Count() ?? 0)); foreach (PackageEntry item in packageEntries ?? Array.Empty()) { AddEntryToPackage(val, item); } foreach (CustomSyncedValueBase item2 in list2) { AddEntryToPackage(val, new PackageEntry { section = "Internal", key = item2.Identifier, type = item2.Type, value = item2.BoxedValue }); } foreach (ConfigEntryBase item3 in list) { AddEntryToPackage(val, new PackageEntry { section = item3.Definition.Section, key = item3.Definition.Key, type = configType(item3), value = item3.BoxedValue }); } return val; } private static void AddEntryToPackage(ZPackage package, PackageEntry entry) { package.Write(entry.section); package.Write(entry.key); package.Write((entry.value == null) ? "" : GetZPackageTypeString(entry.type)); AddValueToZPackage(package, entry.value); } private static string GetZPackageTypeString(Type type) { return type.AssemblyQualifiedName; } private static void AddValueToZPackage(ZPackage package, object? value) { Type type = value?.GetType(); if (value is Enum) { value = ((IConvertible)value).ToType(Enum.GetUnderlyingType(value.GetType()), CultureInfo.InvariantCulture); } else { if (value is ICollection collection) { package.Write(collection.Count); { foreach (object item in collection) { AddValueToZPackage(package, item); } return; } } if ((object)type != null && type.IsValueType && !type.IsPrimitive) { FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); package.Write(fields.Length); FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { package.Write(GetZPackageTypeString(fieldInfo.FieldType)); AddValueToZPackage(package, fieldInfo.GetValue(value)); } return; } } ZRpc.Serialize(new object[1] { value }, ref package); } private static object ReadValueWithTypeFromZPackage(ZPackage package, Type type) { if ((object)type != null && type.IsValueType && !type.IsPrimitive && !type.IsEnum) { FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); int num = package.ReadInt(); if (num != fields.Length) { throw new InvalidDeserializationTypeException { received = $"(field count: {num})", expected = $"(field count: {fields.Length})" }; } object uninitializedObject = FormatterServices.GetUninitializedObject(type); FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { string text = package.ReadString(); if (text != GetZPackageTypeString(fieldInfo.FieldType)) { throw new InvalidDeserializationTypeException { received = text, expected = GetZPackageTypeString(fieldInfo.FieldType), field = fieldInfo.Name }; } fieldInfo.SetValue(uninitializedObject, ReadValueWithTypeFromZPackage(package, fieldInfo.FieldType)); } return uninitializedObject; } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<, >)) { int num2 = package.ReadInt(); IDictionary dictionary = (IDictionary)Activator.CreateInstance(type); Type type2 = typeof(KeyValuePair<, >).MakeGenericType(type.GenericTypeArguments); FieldInfo field = type2.GetField("key", BindingFlags.Instance | BindingFlags.NonPublic); FieldInfo field2 = type2.GetField("value", BindingFlags.Instance | BindingFlags.NonPublic); for (int j = 0; j < num2; j++) { object obj = ReadValueWithTypeFromZPackage(package, type2); dictionary.Add(field.GetValue(obj), field2.GetValue(obj)); } return dictionary; } if (type != typeof(List) && type.IsGenericType) { Type type3 = typeof(ICollection<>).MakeGenericType(type.GenericTypeArguments[0]); if ((object)type3 != null && type3.IsAssignableFrom(type)) { int num3 = package.ReadInt(); object obj2 = Activator.CreateInstance(type); MethodInfo method = type3.GetMethod("Add"); for (int k = 0; k < num3; k++) { method.Invoke(obj2, new object[1] { ReadValueWithTypeFromZPackage(package, type.GenericTypeArguments[0]) }); } return obj2; } } ParameterInfo parameterInfo = (ParameterInfo)FormatterServices.GetUninitializedObject(typeof(ParameterInfo)); AccessTools.DeclaredField(typeof(ParameterInfo), "ClassImpl").SetValue(parameterInfo, type); List source = new List(); ZRpc.Deserialize(new ParameterInfo[2] { null, parameterInfo }, package, ref source); return source.First(); } } [PublicAPI] [HarmonyPatch] internal class VersionCheck { private static readonly HashSet versionChecks; private static readonly Dictionary notProcessedNames; public string Name; private string? displayName; private string? currentVersion; private string? minimumRequiredVersion; public bool ModRequired = true; private string? ReceivedCurrentVersion; private string? ReceivedMinimumRequiredVersion; private readonly List ValidatedClients = new List(); private ConfigSync? ConfigSync; public string DisplayName { get { return displayName ?? Name; } set { displayName = value; } } public string CurrentVersion { get { return currentVersion ?? "0.0.0"; } set { currentVersion = value; } } public string MinimumRequiredVersion { get { return minimumRequiredVersion ?? (ModRequired ? CurrentVersion : "0.0.0"); } set { minimumRequiredVersion = value; } } private static void PatchServerSync() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown Patches patchInfo = PatchProcessor.GetPatchInfo((MethodBase)AccessTools.DeclaredMethod(typeof(ZNet), "Awake", (Type[])null, (Type[])null)); if (patchInfo != null && patchInfo.Postfixes.Count((Patch p) => p.PatchMethod.DeclaringType == typeof(ConfigSync.RegisterRPCPatch)) > 0) { return; } Harmony val = new Harmony("org.bepinex.helpers.ServerSync"); foreach (Type item in from t in typeof(ConfigSync).GetNestedTypes(BindingFlags.NonPublic).Concat(new Type[1] { typeof(VersionCheck) }) where t.IsClass select t) { val.PatchAll(item); } } static VersionCheck() { versionChecks = new HashSet(); notProcessedNames = new Dictionary(); typeof(ThreadingHelper).GetMethod("StartSyncInvoke").Invoke(ThreadingHelper.Instance, new object[1] { new Action(PatchServerSync) }); } public VersionCheck(string name) { Name = name; ModRequired = true; versionChecks.Add(this); } public VersionCheck(ConfigSync configSync) { ConfigSync = configSync; Name = ConfigSync.Name; versionChecks.Add(this); } public void Initialize() { ReceivedCurrentVersion = null; ReceivedMinimumRequiredVersion = null; if (ConfigSync != null) { Name = ConfigSync.Name; DisplayName = ConfigSync.DisplayName; CurrentVersion = ConfigSync.CurrentVersion; MinimumRequiredVersion = ConfigSync.MinimumRequiredVersion; ModRequired = ConfigSync.ModRequired; } } private bool IsVersionOk() { if (ReceivedMinimumRequiredVersion == null || ReceivedCurrentVersion == null) { return !ModRequired; } bool flag = new Version(CurrentVersion) >= new Version(ReceivedMinimumRequiredVersion); bool flag2 = new Version(ReceivedCurrentVersion) >= new Version(MinimumRequiredVersion); return flag && flag2; } private string ErrorClient() { if (ReceivedMinimumRequiredVersion == null) { return DisplayName + " is not installed on the server."; } return (new Version(CurrentVersion) >= new Version(ReceivedMinimumRequiredVersion)) ? (DisplayName + " may not be higher than version " + ReceivedCurrentVersion + ". You have version " + CurrentVersion + ".") : (DisplayName + " needs to be at least version " + ReceivedMinimumRequiredVersion + ". You have version " + CurrentVersion + "."); } private string ErrorServer(ZRpc rpc) { return "Disconnect: The client (" + rpc.GetSocket().GetHostName() + ") doesn't have the correct " + DisplayName + " version " + MinimumRequiredVersion; } private string Error(ZRpc? rpc = null) { return (rpc == null) ? ErrorClient() : ErrorServer(rpc); } private static VersionCheck[] GetFailedClient() { return versionChecks.Where((VersionCheck check) => !check.IsVersionOk()).ToArray(); } private static VersionCheck[] GetFailedServer(ZRpc rpc) { return versionChecks.Where((VersionCheck check) => check.ModRequired && !check.ValidatedClients.Contains(rpc)).ToArray(); } private static void Logout() { Game.instance.Logout(true, true); AccessTools.DeclaredField(typeof(ZNet), "m_connectionStatus").SetValue(null, (object)(ConnectionStatus)3); } private static void DisconnectClient(ZRpc rpc) { rpc.Invoke("Error", new object[1] { 3 }); } private static void CheckVersion(ZRpc rpc, ZPackage pkg) { CheckVersion(rpc, pkg, null); } private static void CheckVersion(ZRpc rpc, ZPackage pkg, Action? original) { string text = pkg.ReadString(); string text2 = pkg.ReadString(); string text3 = pkg.ReadString(); bool flag = false; foreach (VersionCheck versionCheck in versionChecks) { if (!(text != versionCheck.Name)) { Debug.Log((object)("Received " + versionCheck.DisplayName + " version " + text3 + " and minimum version " + text2 + " from the " + (ZNet.instance.IsServer() ? "client" : "server") + ".")); versionCheck.ReceivedMinimumRequiredVersion = text2; versionCheck.ReceivedCurrentVersion = text3; if (ZNet.instance.IsServer() && versionCheck.IsVersionOk()) { versionCheck.ValidatedClients.Add(rpc); } flag = true; } } if (flag) { return; } pkg.SetPos(0); if (original != null) { original(rpc, pkg); if (pkg.GetPos() == 0) { notProcessedNames.Add(text, text3); } } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] [HarmonyPrefix] private static bool RPC_PeerInfo(ZRpc rpc, ZNet __instance) { VersionCheck[] array = (__instance.IsServer() ? GetFailedServer(rpc) : GetFailedClient()); if (array.Length == 0) { return true; } VersionCheck[] array2 = array; foreach (VersionCheck versionCheck in array2) { Debug.LogWarning((object)versionCheck.Error(rpc)); } if (__instance.IsServer()) { DisconnectClient(rpc); } else { Logout(); } return false; } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] [HarmonyPrefix] private static void RegisterAndCheckVersion(ZNetPeer peer, ZNet __instance) { //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Expected O, but got Unknown notProcessedNames.Clear(); IDictionary dictionary = (IDictionary)typeof(ZRpc).GetField("m_functions", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(peer.m_rpc); if (dictionary.Contains(StringExtensionMethods.GetStableHashCode("ServerSync VersionCheck"))) { object obj = dictionary[StringExtensionMethods.GetStableHashCode("ServerSync VersionCheck")]; Action action = (Action)obj.GetType().GetField("m_action", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj); peer.m_rpc.Register("ServerSync VersionCheck", (Action)delegate(ZRpc rpc, ZPackage pkg) { CheckVersion(rpc, pkg, action); }); } else { peer.m_rpc.Register("ServerSync VersionCheck", (Action)CheckVersion); } foreach (VersionCheck versionCheck in versionChecks) { versionCheck.Initialize(); if (versionCheck.ModRequired || __instance.IsServer()) { Debug.Log((object)("Sending " + versionCheck.DisplayName + " version " + versionCheck.CurrentVersion + " and minimum version " + versionCheck.MinimumRequiredVersion + " to the " + (__instance.IsServer() ? "client" : "server") + ".")); ZPackage val = new ZPackage(); val.Write(versionCheck.Name); val.Write(versionCheck.MinimumRequiredVersion); val.Write(versionCheck.CurrentVersion); peer.m_rpc.Invoke("ServerSync VersionCheck", new object[1] { val }); } } } [HarmonyPatch(typeof(ZNet), "Disconnect")] [HarmonyPrefix] private static void RemoveDisconnected(ZNetPeer peer, ZNet __instance) { if (!__instance.IsServer()) { return; } foreach (VersionCheck versionCheck in versionChecks) { versionCheck.ValidatedClients.Remove(peer.m_rpc); } } [HarmonyPatch(typeof(FejdStartup), "ShowConnectError")] [HarmonyPostfix] private static void ShowConnectionError(FejdStartup __instance) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) if (!__instance.m_connectionFailedPanel.activeSelf || (int)ZNet.GetConnectionStatus() != 3) { return; } bool flag = false; VersionCheck[] failedClient = GetFailedClient(); if (failedClient.Length != 0) { string text = string.Join("\n", failedClient.Select((VersionCheck check) => check.Error())); TMP_Text connectionFailedError = __instance.m_connectionFailedError; connectionFailedError.text = connectionFailedError.text + "\n" + text; flag = true; } foreach (KeyValuePair item in notProcessedNames.OrderBy, string>((KeyValuePair kv) => kv.Key)) { if (!__instance.m_connectionFailedError.text.Contains(item.Key)) { TMP_Text connectionFailedError2 = __instance.m_connectionFailedError; connectionFailedError2.text = connectionFailedError2.text + "\nServer expects you to have " + item.Key + " (Version: " + item.Value + ") installed."; flag = true; } } if (flag) { RectTransform component = ((Component)__instance.m_connectionFailedPanel.transform.Find("Image")).GetComponent(); Vector2 sizeDelta = component.sizeDelta; sizeDelta.x = 675f; component.sizeDelta = sizeDelta; __instance.m_connectionFailedError.ForceMeshUpdate(false, false); float num = __instance.m_connectionFailedError.renderedHeight + 105f; RectTransform component2 = ((Component)((Component)component).transform.Find("ButtonOk")).GetComponent(); component2.anchoredPosition = new Vector2(component2.anchoredPosition.x, component2.anchoredPosition.y - (num - component.sizeDelta.y) / 2f); sizeDelta = component.sizeDelta; sizeDelta.y = num; component.sizeDelta = sizeDelta; } } } }