Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of VikingQoL v1.3.2
plugins/VikingQoL.dll
Decompiled 2 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Splatform; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.Rendering; using UnityEngine.UI; using Valheim.SettingsGui; using Valheim.UI; using VikingQoL.Core; using VikingQoL.Modules.Building; using VikingQoL.Modules.Map; using VikingQoL.Modules.SettingsUI; using VikingQoL.Modules.Stations; using VikingQoL.Modules.Storage; using VikingQoL.Modules.Storage.Craft; using VikingQoL.Modules.Storage.Extras; using VikingQoL.Modules.Storage.Shared; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_guiutils")] [assembly: IgnoresAccessChecksTo("assembly_utils")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("VikingQoL")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.3.2.0")] [assembly: AssemblyInformationalVersion("1.3.2")] [assembly: AssemblyProduct("VikingQoL")] [assembly: AssemblyTitle("VikingQoL")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.3.2.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace VikingQoL { [BepInPlugin("ff15vikings.vikingqol", "VikingQoL", "1.3.2")] public class Plugin : BaseUnityPlugin { public const string Guid = "ff15vikings.vikingqol"; public const string Name = "VikingQoL"; public const string Version = "1.3.2"; private Harmony _harmony; internal static Plugin Instance { get; private set; } internal static bool IsHeadless => (int)SystemInfo.graphicsDeviceType == 4; private void Awake() { //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Expected O, but got Unknown Instance = this; Log.Init(((BaseUnityPlugin)this).Logger); MigrateOldConfig(); ModConfig.Init(((BaseUnityPlugin)this).Config); StorageConfig.Bind(((BaseUnityPlugin)this).Config); StationsConfig.Bind(((BaseUnityPlugin)this).Config); BuildingConfig.Bind(((BaseUnityPlugin)this).Config); MapConfig.Bind(((BaseUnityPlugin)this).Config); SettingsUIConfig.Bind(((BaseUnityPlugin)this).Config); ModConfig.FinishBinding(); ConfigWatcher.Start(((BaseUnityPlugin)this).Config); StackSizes.Init(); PortalPins.Init(); PlayerColors.Init(); Beacons.Init(); _harmony = new Harmony("ff15vikings.vikingqol"); Patch(typeof(VersionCheck), "Core.VersionCheck", required: true); Patch(typeof(ConsoleCommands), "Core.ConsoleCommands", required: false); if (!IsHeadless) { Patch(typeof(VersionCheck.ConnectErrorPatch), "Core.ConnectErrorMessage", required: false); } Patch(typeof(KeepOversizedStacks), "Storage.KeepOversizedStacks", required: true); Patch(typeof(StackSizes), "Storage.StackSizes", required: false); Patch(typeof(ChestRules), "Storage.ChestRules", required: false); Patch(typeof(ContainerService), "Storage.ContainerService", required: false); Patch(typeof(ChestViewers), "Storage.ChestViewers", required: false); Patch(typeof(ChestLabels), "Storage.ChestLabels", required: false); Patch(typeof(ChestCrafting), "Storage.ChestCrafting", required: false); Patch(typeof(ChestLocks), "Storage.ChestLocks", required: false); Patch(typeof(PortalPins), "Map.PortalPins", required: false); Patch(typeof(PlayerColors), "Map.PlayerColors", required: false); Patch(typeof(Beacons), "Map.Beacons", required: false); if (!IsHeadless) { Patch(typeof(SharedChestUI), "Storage.SharedChestUI", required: false); Patch(typeof(NearbyChests), "Storage.NearbyChests", required: false); Patch(typeof(CraftFromChests), "Storage.CraftFromChests", required: false); Patch(typeof(StationsFromChests), "Storage.StationsFromChests", required: false); Patch(typeof(StationItemChoice), "Storage.StationItemChoice", required: false); Patch(typeof(HiddenItemsMessage), "Storage.HiddenItemsMessage", required: false); Patch(typeof(RecipeListUI), "Storage.RecipeListUI", required: false); Patch(typeof(SearchBoxes), "Storage.SearchBoxes", required: false); Patch(typeof(SearchKeyBlock), "Storage.SearchKeyBlock", required: false); Patch(typeof(StationRadialMenu), "Storage.StationRadialMenu", required: false); Patch(typeof(WeightAndTooltip), "Storage.WeightAndTooltip", required: false); Patch(typeof(InventoryButtons), "Storage.InventoryButtons", required: false); Patch(typeof(LockedSlots), "Storage.LockedSlots", required: false); Patch(typeof(LockedSlotsUI), "Storage.LockedSlotsUI", required: false); Patch(typeof(ChestHover), "Storage.ChestHover", required: false); Patch(typeof(ChestLabelsUI), "Storage.ChestLabelsUI", required: false); Patch(typeof(ChestPeekIcons), "Storage.ChestPeekIcons", required: false); Patch(typeof(BulkAdd), "Stations.BulkAdd", required: false); Patch(typeof(KilnWoodOnly), "Stations.KilnWoodOnly", required: false); Patch(typeof(StationTimers), "Stations.StationTimers", required: false); Patch(typeof(CreatureTimers), "Stations.CreatureTimers", required: false); Patch(typeof(ProducerTimers), "Stations.ProducerTimers", required: false); Patch(typeof(BuildCamera), "Building.BuildCamera", required: false); Patch(typeof(MapPinColors), "Map.MapPinColors", required: false); Patch(typeof(PlayerColorsMap), "Map.PlayerColorsMap", required: false); Patch(typeof(BeaconsClient), "Map.BeaconsClient", required: false); Patch(typeof(LookPing), "Map.LookPing", required: false); Patch(typeof(SettingsMenuButton), "SettingsUI.MenuButton", required: false); } Log.Info("VikingQoL 1.3.2 loaded (" + (IsHeadless ? "dedicated server" : "client") + ")."); } private void Start() { ConflictCheck.Run(); } private void MigrateOldConfig() { string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath; if (File.Exists(configFilePath)) { return; } try { string[] files = Directory.GetFiles(Paths.ConfigPath, "*.vikingqol.cfg"); if (files.Length == 1) { File.Move(files[0], configFilePath); ((BaseUnityPlugin)this).Config.Reload(); Log.Info("Settings moved from " + Path.GetFileName(files[0]) + " to " + Path.GetFileName(configFilePath) + "."); } } catch (Exception ex) { Log.Warn("Couldn't move the old settings file, using default settings: " + ex.Message); } } private void Update() { ConfigWatcher.Tick(); StackSizes.Tick(); PortalPins.ServerTick(); PlayerColors.Tick(); Beacons.Tick(); if (!IsHeadless) { ContainerService.Tick(); ChestViewers.Tick(); StorageExtras.TickHotkeys(); BuildCamera.Tick(); BuildReach.Tick(); EnemyMarkers.Tick(); Clock.Tick(); PortalPinsClient.Tick(); BeaconsClient.Tick(); LookPing.Tick(); ChestLabels.Tick(); ChestCrafting.Tick(); ChestLocks.Tick(); ChestPeekIcons.Tick(); SettingsWindow.Tick(); } } private void OnDestroy() { ConfigWatcher.Stop(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } private void Patch(Type patchClass, string feature, bool required) { try { _harmony.CreateClassProcessor(patchClass).Patch(); Log.Debug("Patched " + feature); } catch (Exception arg) { Log.Error(string.Format("Failed to patch {0}{1}: {2}", feature, required ? " (required!)" : ", feature disabled", arg)); } } } } namespace VikingQoL.Modules.Storage { [HarmonyPatch] internal static class KeepOversizedStacks { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(Inventory), "AddItem", new Type[14] { typeof(int), typeof(int), typeof(float), typeof(Vector2i), typeof(bool), typeof(int), typeof(int), typeof(long), typeof(string), typeof(Dictionary<string, string>), typeof(int), typeof(bool), typeof(bool), typeof(bool) }, (Type[])null); } private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { MethodInfo methodInfo = AccessTools.Method(typeof(Mathf), "Min", new Type[2] { typeof(int), typeof(int) }, (Type[])null); MethodInfo operand = AccessTools.Method(typeof(KeepOversizedStacks), "KeepStack", (Type[])null, (Type[])null); List<CodeInstruction> list = instructions.ToList(); int num = 0; foreach (CodeInstruction item in list) { if (CodeInstructionExtensions.Calls(item, methodInfo)) { item.opcode = OpCodes.Call; item.operand = operand; num++; } } if (num != 1) { throw new InvalidOperationException($"expected 1 stack clamp in Inventory.AddItem, found {num}"); } return list; } private static int KeepStack(int stack, int maxStackSize) { if (stack > maxStackSize) { Log.Debug($"Kept oversized stack: {stack} (max {maxStackSize})."); } return stack; } } [HarmonyPatch] internal static class StackSizes { internal const int SaveFormatLimit = 65535; private static readonly Dictionary<string, int> VanillaByPrefab = new Dictionary<string, int>(StringComparer.Ordinal); private static readonly Dictionary<string, int> MaxBySharedName = new Dictionary<string, int>(StringComparer.Ordinal); private static bool _dirty; internal static void Init() { Action value = delegate { _dirty = true; }; StorageConfig.StacksEnabled.Changed += value; StorageConfig.StackMultiplier.Changed += value; StorageConfig.MaterialMultiplier.Changed += value; StorageConfig.ConsumableMultiplier.Changed += value; StorageConfig.AmmoMultiplier.Changed += value; StorageConfig.TrophyMultiplier.Changed += value; StorageConfig.ItemOverrides.Changed += value; StorageConfig.ExcludedItems.Changed += value; } internal static void Tick() { if (_dirty && !((Object)(object)ObjectDB.instance == (Object)null)) { _dirty = false; Apply(ObjectDB.instance, refreshLiveItems: true); } } [HarmonyPatch(typeof(ObjectDB), "Awake")] [HarmonyPostfix] [HarmonyPriority(0)] private static void ObjectDBAwake(ObjectDB __instance) { Apply(__instance, refreshLiveItems: false); } [HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")] [HarmonyPostfix] [HarmonyPriority(0)] private static void ObjectDBCopy(ObjectDB __instance) { Apply(__instance, refreshLiveItems: false); } internal static void Apply(ObjectDB db, bool refreshLiveItems) { //IL_00b1: Unknown result type (might be due to invalid IL or missing references) if (db.m_items.Count == 0) { return; } Dictionary<string, int> overrides = ParseOverrides(StorageConfig.ItemOverrides.Value); HashSet<string> excluded = ParseNames(StorageConfig.ExcludedItems.Value); int num = 0; foreach (GameObject item in db.m_items) { ItemDrop val = (Object.op_Implicit((Object)(object)item) ? item.GetComponent<ItemDrop>() : null); if (!((Object)(object)val == (Object)null)) { SharedData shared = val.m_itemData.m_shared; if (!VanillaByPrefab.TryGetValue(((Object)item).name, out var value)) { value = shared.m_maxStackSize; VanillaByPrefab[((Object)item).name] = value; } int num2 = (shared.m_maxStackSize = Compute(((Object)item).name, shared.m_itemType, value, overrides, excluded)); MaxBySharedName[shared.m_name] = num2; if (num2 > value) { num++; } } } if (refreshLiveItems) { RefreshLiveItems(); } Log.Info(StorageConfig.StacksEnabled.Value ? $"Stack sizes applied: {num} items enlarged (multiplier {StorageConfig.StackMultiplier.Value}x)." : "Stack sizes: feature disabled, vanilla sizes in use."); } internal static int Compute(string prefabName, ItemType type, int vanilla, Dictionary<string, int> overrides, HashSet<string> excluded) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) if (vanilla <= 1) { return vanilla; } if (!StorageConfig.StacksEnabled.Value || excluded.Contains(prefabName)) { return vanilla; } if (overrides.TryGetValue(prefabName, out var value)) { return Mathf.Clamp(value, 1, 65535); } int num = CategoryMultiplier(type); if (num <= 0) { num = StorageConfig.StackMultiplier.Value; } return (int)Math.Min((long)vanilla * (long)num, 65535L); } private static int CategoryMultiplier(ItemType type) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Invalid comparison between Unknown and I4 if ((int)type <= 2) { if ((int)type != 1) { if ((int)type != 2) { goto IL_004b; } return StorageConfig.ConsumableMultiplier.Value; } return StorageConfig.MaterialMultiplier.Value; } if ((int)type != 9) { if ((int)type == 13) { return StorageConfig.TrophyMultiplier.Value; } if ((int)type != 23) { goto IL_004b; } } return StorageConfig.AmmoMultiplier.Value; IL_004b: return 0; } private static void RefreshLiveItems() { foreach (ItemDrop s_instance in ItemDrop.s_instances) { if (Object.op_Implicit((Object)(object)s_instance)) { Refresh(s_instance.m_itemData); } } if (Object.op_Implicit((Object)(object)Player.m_localPlayer)) { RefreshInventory(((Humanoid)Player.m_localPlayer).GetInventory()); } Container[] array = Object.FindObjectsByType<Container>((FindObjectsSortMode)0); for (int i = 0; i < array.Length; i++) { RefreshInventory(array[i].GetInventory()); } HotkeyBar[] array2 = Object.FindObjectsByType<HotkeyBar>((FindObjectsSortMode)0); for (int i = 0; i < array2.Length; i++) { foreach (ElementData element in array2[i].m_elements) { element.m_stackText = -1; } } } private static void RefreshInventory(Inventory inventory) { if (inventory == null) { return; } foreach (ItemData allItem in inventory.GetAllItems()) { Refresh(allItem); } } private static void Refresh(ItemData item) { if (item?.m_shared != null && MaxBySharedName.TryGetValue(item.m_shared.m_name, out var value)) { item.m_shared.m_maxStackSize = value; } } internal static bool TryDescribe(string prefabName, out string description) { //IL_009a: Unknown result type (might be due to invalid IL or missing references) description = null; GameObject val = (Object.op_Implicit((Object)(object)ObjectDB.instance) ? ObjectDB.instance.GetItemPrefab(prefabName) : null); ItemDrop val2 = (Object.op_Implicit((Object)(object)val) ? val.GetComponent<ItemDrop>() : null); if ((Object)(object)val2 == (Object)null) { return false; } int maxStackSize = val2.m_itemData.m_shared.m_maxStackSize; int value; int num = (VanillaByPrefab.TryGetValue(((Object)val).name, out value) ? value : maxStackSize); description = $"{((Object)val).name}: max stack {maxStackSize} (vanilla {num}, type {val2.m_itemData.m_shared.m_itemType})"; return true; } private static Dictionary<string, int> ParseOverrides(string text) { Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); foreach (string item in Split(text)) { string[] array = item.Split(':'); if (array.Length == 2 && int.TryParse(array[1].Trim(), out var result) && result > 0) { dictionary[array[0].Trim()] = result; } else { Log.Warn("Stacks.ItemOverrides: ignoring '" + item + "' (expected Name:Amount, e.g. Wood:1000)."); } } return dictionary; } private static HashSet<string> ParseNames(string text) { return new HashSet<string>(Split(text), StringComparer.OrdinalIgnoreCase); } private static IEnumerable<string> Split(string text) { if (string.IsNullOrWhiteSpace(text)) { yield break; } string[] array = text.Split(','); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length > 0) { yield return text2; } } } } internal enum SortMode { Name, Type, Weight } internal enum RecipeSortMode { Original, Name, Type, Weight } internal enum ChestPeekStyle { Icons, Text } internal static class StorageConfig { internal static SyncedEntry<bool> StacksEnabled; internal static SyncedEntry<int> StackMultiplier; internal static SyncedEntry<int> MaterialMultiplier; internal static SyncedEntry<int> ConsumableMultiplier; internal static SyncedEntry<int> AmmoMultiplier; internal static SyncedEntry<int> TrophyMultiplier; internal static SyncedEntry<string> ItemOverrides; internal static SyncedEntry<string> ExcludedItems; internal static SyncedEntry<bool> SharedChestsEnabled; internal static SyncedEntry<bool> ShowViewersInHoverText; internal static SyncedEntry<bool> IgnorePersonalChestLock; internal static SyncedEntry<bool> IgnoreWardOnChests; internal static SyncedEntry<bool> CraftFromChestsEnabled; internal static SyncedEntry<float> CraftingRadius; internal static SyncedEntry<float> BuildingRadius; internal static SyncedEntry<float> StationRadius; internal static SyncedEntry<bool> InventoryFirst; internal static SyncedEntry<bool> QuickStackEnabled; internal static SyncedEntry<float> QuickStackRadius; internal static SyncedEntry<bool> LockedSlotsEnabled; internal static SyncedEntry<bool> LockedChestSlotsEnabled; internal static SyncedEntry<bool> SortEnabled; internal static SyncedEntry<bool> RestockEnabled; internal static SyncedEntry<float> RestockRadius; internal static SyncedEntry<float> WeightMultiplier; internal static SyncedEntry<bool> ChestPeekEnabled; internal static SyncedEntry<bool> ChestLabelsEnabled; internal static ConfigEntry<bool> ShowSortButton; internal static ConfigEntry<bool> ShowQuickStackButton; internal static ConfigEntry<bool> ShowRestockButton; internal static ConfigEntry<bool> ShowCraftToggleButton; internal static ConfigEntry<KeyboardShortcut> QuickStackKey; internal static ConfigEntry<KeyboardShortcut> RestockKey; internal static ConfigEntry<KeyboardShortcut> ToggleCraftFromChestsKey; internal static ConfigEntry<KeyboardShortcut> SortKey; internal static ConfigEntry<bool> LockSlotMiddleClick; internal static ConfigEntry<KeyboardShortcut> LockSlotHoldKey; internal static ConfigEntry<SortMode> CurrentSortMode; internal static ConfigEntry<bool> ShowChestCountTooltip; internal static ConfigEntry<bool> RestockEquippedAmmo; internal static ConfigEntry<int> RestockAmmoTarget; internal static ConfigEntry<string> RestockItems; internal static ConfigEntry<bool> ShowChestPeek; internal static ConfigEntry<ChestPeekStyle> ChestPeekStyle; internal static ConfigEntry<int> ChestPeekItems; internal static ConfigEntry<int> ChestLabelSize; internal static ConfigEntry<bool> ShowHaveCount; internal static ConfigEntry<bool> ShowHaveCountBuilding; internal static ConfigEntry<RecipeSortMode> RecipeSortMode; internal static ConfigEntry<bool> ShowRecipeSortButton; internal static ConfigEntry<bool> ShowChestSearch; internal static ConfigEntry<bool> ShowRecipeSearch; internal static void Bind(ConfigFile file) { //IL_03f0: Unknown result type (might be due to invalid IL or missing references) //IL_040f: Unknown result type (might be due to invalid IL or missing references) //IL_042e: Unknown result type (might be due to invalid IL or missing references) //IL_044d: Unknown result type (might be due to invalid IL or missing references) //IL_0491: Unknown result type (might be due to invalid IL or missing references) AcceptableValueRange<int> acceptable = new AcceptableValueRange<int>(1, 100); AcceptableValueRange<int> acceptable2 = new AcceptableValueRange<int>(0, 100); AcceptableValueRange<float> acceptable3 = new AcceptableValueRange<float>(1f, 100f); StacksEnabled = ModConfig.BindServer("Stacks", "Enabled", defaultValue: true, "Bigger stack sizes."); StackMultiplier = ModConfig.BindServer("Stacks", "StackMultiplier", 5, "Multiplies the max stack of every item that stacks.\nItems that don't stack (weapons, armor, tools, Megingjord, Wisplight, ...) never change.\nItemOverrides and the category multipliers below win over this one.", (AcceptableValueBase)(object)acceptable); MaterialMultiplier = ModConfig.BindServer("Stacks", "MaterialMultiplier", 0, "Stack multiplier for materials (wood, ore, hides, ...). 0 = same as StackMultiplier.", (AcceptableValueBase)(object)acceptable2); ConsumableMultiplier = ModConfig.BindServer("Stacks", "ConsumableMultiplier", 0, "Stack multiplier for food, potions and meads. 0 = same as StackMultiplier.", (AcceptableValueBase)(object)acceptable2); AmmoMultiplier = ModConfig.BindServer("Stacks", "AmmoMultiplier", 0, "Stack multiplier for arrows, bolts and missiles. 0 = same as StackMultiplier.", (AcceptableValueBase)(object)acceptable2); TrophyMultiplier = ModConfig.BindServer("Stacks", "TrophyMultiplier", 0, "Stack multiplier for trophies. 0 = same as StackMultiplier.", (AcceptableValueBase)(object)acceptable2); ItemOverrides = ModConfig.BindServer("Stacks", "ItemOverrides", "", "Exact max stack for single items, by prefab name, comma separated. Example: Coins:5000, Wood:1000\nItems that don't stack are skipped."); ExcludedItems = ModConfig.BindServer("Stacks", "ExcludedItems", "", "Items that keep their vanilla stack size, by prefab name, comma separated. Example: Coins, Amber"); SharedChestsEnabled = ModConfig.BindServer("Shared Chests", "Enabled", defaultValue: true, "Several players can use the same chest at the same time. Carts, ships and graves stay vanilla."); ShowViewersInHoverText = ModConfig.BindServer("Shared Chests", "ShowViewersInHoverText", defaultValue: true, "Hovering a chest shows who has it open."); IgnorePersonalChestLock = ModConfig.BindServer("Shared Chests", "IgnorePersonalChestLock", defaultValue: true, "Everyone can open personal (private) chests, not only the player who built them."); IgnoreWardOnChests = ModConfig.BindServer("Shared Chests", "IgnoreWardOnChests", defaultValue: true, "Wards don't stop anyone from opening chests."); CraftFromChestsEnabled = ModConfig.BindServer("Craft From Chests", "Enabled", defaultValue: true, "Crafting, building, cooking, smelting and refueling use items from nearby chests."); CraftingRadius = ModConfig.BindServer("Craft From Chests", "CraftingRadius", 20f, "Chests within this many meters of a crafting station (workbench, forge, ...) are used.", (AcceptableValueBase)(object)acceptable3); BuildingRadius = ModConfig.BindServer("Craft From Chests", "BuildingRadius", 20f, "Chests within this many meters of you are used when building with the hammer, hoe or cultivator.", (AcceptableValueBase)(object)acceptable3); StationRadius = ModConfig.BindServer("Craft From Chests", "StationRadius", 20f, "Chests within this many meters of a smelter, kiln, cooking station, fire, fermenter, turret and so on are used.", (AcceptableValueBase)(object)acceptable3); InventoryFirst = ModConfig.BindServer("Craft From Chests", "InventoryFirst", defaultValue: true, "Use items in your inventory before taking from chests."); QuickStackEnabled = ModConfig.BindServer("Quick Stack", "Enabled", defaultValue: true, "Quick-stack button: puts your items into nearby chests that already have the same item."); QuickStackRadius = ModConfig.BindServer("Quick Stack", "Radius", 20f, "Chests within this many meters of you are used.", (AcceptableValueBase)(object)acceptable3); LockedSlotsEnabled = ModConfig.BindServer("Quick Stack", "LockedSlotsEnabled", defaultValue: true, "Players can lock inventory slots (middle-click a slot). Quick stack, Place stacks and Sort leave locked slots alone."); LockedChestSlotsEnabled = ModConfig.BindServer("Quick Stack", "LockedChestSlotsEnabled", defaultValue: true, "Players can lock slots in player-built chests. Crafting, building, stations, Restock, Take all and Sort leave them alone. Adding items and moving them by hand still work. Off = locks are ignored but kept in the world."); SortEnabled = ModConfig.BindServer("Sort", "Enabled", defaultValue: true, "Sort button for your inventory and chests."); RestockEnabled = ModConfig.BindServer("Restock", "Enabled", defaultValue: true, "Restock button: tops up your ammo and your restock list from nearby chests."); RestockRadius = ModConfig.BindServer("Restock", "Radius", 20f, "Chests within this many meters of you are used.", (AcceptableValueBase)(object)acceptable3); WeightMultiplier = ModConfig.BindServer("Difficulty", "WeightMultiplier", 1f, "How much carried items weigh. 1.0 = vanilla, 0.5 = half.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f)); ChestPeekEnabled = ModConfig.BindServer("Chests", "ChestPeekEnabled", defaultValue: true, "Hovering a chest shows what's inside. Carts, ships and graves stay vanilla."); ChestLabelsEnabled = ModConfig.BindServer("Chests", "ChestLabelsEnabled", defaultValue: true, "Chests can be renamed and given a name color (Rename and Color buttons in the chest window). Off = names are hidden and the buttons are gone; saved names stay in the world."); ShowSortButton = ModConfig.BindPersonal("Buttons", "ShowSortButton", defaultValue: true, "Show the Sort button."); ShowQuickStackButton = ModConfig.BindPersonal("Buttons", "ShowQuickStackButton", defaultValue: true, "Show the Quick-stack button."); ShowRestockButton = ModConfig.BindPersonal("Buttons", "ShowRestockButton", defaultValue: true, "Show the Restock button."); ShowCraftToggleButton = ModConfig.BindPersonal("Buttons", "ShowCraftToggleButton", defaultValue: true, "Show the button that switches crafting from chests on and off for you."); ShowRecipeSortButton = ModConfig.BindPersonal("Buttons", "ShowRecipeSortButton", defaultValue: true, "Show the Sort button in the crafting window (workbench, forge, ...)."); ShowChestSearch = ModConfig.BindPersonal("Buttons", "ShowChestSearch", defaultValue: true, "Show a search box beside the chest window. Slots whose item doesn't match go dim."); ShowRecipeSearch = ModConfig.BindPersonal("Buttons", "ShowRecipeSearch", defaultValue: true, "Show a search box in the crafting window. The list shows recipes whose name matches, then recipes that use a matching ingredient."); QuickStackKey = ModConfig.BindPersonal<KeyboardShortcut>("Hotkeys", "QuickStackKey", KeyboardShortcut.Empty, "Puts your items into nearby chests, like the Quick-stack button. Empty = no key."); RestockKey = ModConfig.BindPersonal<KeyboardShortcut>("Hotkeys", "RestockKey", KeyboardShortcut.Empty, "Tops up from nearby chests, like the Restock button. Empty = no key."); ToggleCraftFromChestsKey = ModConfig.BindPersonal<KeyboardShortcut>("Hotkeys", "ToggleCraftFromChestsKey", KeyboardShortcut.Empty, "Switches crafting from chests on and off. Empty = no key."); SortKey = ModConfig.BindPersonal<KeyboardShortcut>("Hotkeys", "SortKey", KeyboardShortcut.Empty, "Sorts your inventory, like the Sort button. Empty = no key."); LockSlotMiddleClick = ModConfig.BindPersonal("Hotkeys", "LockSlotMiddleClick", defaultValue: true, "Middle-click (press the mouse wheel) on an inventory slot to lock or unlock it."); LockSlotHoldKey = ModConfig.BindPersonal<KeyboardShortcut>("Hotkeys", "LockSlotHoldKey", new KeyboardShortcut((KeyCode)285, Array.Empty<KeyCode>()), "Hold this key and left-click an inventory slot to lock or unlock it (for mice without a wheel button). Empty = off."); CurrentSortMode = ModConfig.BindPersonal("Options", "SortMode", SortMode.Name, "How the Sort button sorts your inventory: by name, type or weight. Right-clicking the Sort button changes it too."); RecipeSortMode = ModConfig.BindPersonal("Options", "RecipeSortMode", VikingQoL.Modules.Storage.RecipeSortMode.Name, "How the recipe list in crafting windows is sorted: Original (the game's order), Name, Type or Weight. Recipes you can make always come first. The Sort button in the crafting window changes it too."); ShowHaveCount = ModConfig.BindPersonal("Options", "ShowHaveCount", defaultValue: true, "Crafting window: under each ingredient, show how many you have (inventory and nearby chests) and how many you need, like 100/6."); ShowHaveCountBuilding = ModConfig.BindPersonal("Options", "ShowHaveCountBuilding", defaultValue: true, "The same counts in the building menu (hammer, hoe, cultivator)."); ShowChestCountTooltip = ModConfig.BindPersonal("Options", "ShowChestCountTooltip", defaultValue: true, "Item tooltips show how many are in nearby chests."); RestockEquippedAmmo = ModConfig.BindPersonal("Options", "RestockEquippedAmmo", defaultValue: true, "Restock tops up the arrows or bolts you have equipped."); RestockAmmoTarget = ModConfig.BindPersonal("Options", "RestockAmmoTarget", 100, "Restock fills your equipped ammo up to this many.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10000)); RestockItems = ModConfig.BindPersonal("Options", "RestockItems", "", "Items Restock keeps topped up, as Name:Amount, comma separated. Example: MeadHealthMinor:10, CookedMeat:20"); ShowChestPeek = ModConfig.BindPersonal("Chests", "ShowChestPeek", defaultValue: true, "Show what's inside when you hover a chest."); ChestPeekStyle = ModConfig.BindPersonal("Chests", "ChestPeekStyle", VikingQoL.Modules.Storage.ChestPeekStyle.Icons, "How the chest hover shows what's inside: Icons (item pictures with totals) or Text (a grey list)."); ChestPeekItems = ModConfig.BindPersonal("Chests", "ChestPeekItems", 6, "How many different items the chest hover shows, biggest stacks first.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 15)); ChestLabelSize = ModConfig.BindPersonal("Chests", "ChestLabelSize", 120, "Size of a chest's name in the hover text, in percent. 100 = same size as the rest of the text.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(100, 150)); } } } namespace VikingQoL.Modules.Storage.Shared { internal static class ChestActions { private static Player LocalPlayer => Player.m_localPlayer; internal static void MoveWithin(Container chest, ItemData item, int amount, Vector2i to) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) ContainerService.Move(chest, item, amount, to, delegate(OpStatus status, Inventory _) { Report(status); }); } internal static void TakeToPlayer(Container chest, ItemData item, int amount, Vector2i? target) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) Vector2i source = item.m_gridPos; bool wholeStack = amount >= item.m_stack; ContainerService.Take(chest, new TakeItem[1] { TakeItem.From(item, amount) }, delegate(OpStatus status, Inventory taken) { //IL_0021: 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) Report(status); if (target.HasValue) { PlaceAtTarget(chest, taken, target.Value, source, wholeStack); } GiveToPlayer(chest, taken); }, respectLocks: false); } internal static void PutFromPlayer(Container chest, ItemData item, int amount, Vector2i? target) { //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) Player localPlayer = LocalPlayer; Inventory inventory = ((Humanoid)localPlayer).GetInventory(); ((Humanoid)localPlayer).RemoveEquipAction(item); ((Humanoid)localPlayer).UnequipItem(item, false); Vector2i source = item.m_gridPos; amount = Mathf.Min(amount, item.m_stack); bool allowSwap = amount == item.m_stack; Inventory val = ContainerService.NewTemp(); val.MoveItemToThis(inventory, item, amount, source.x, source.y); ContainerService.Put(chest, val, target, allowSwap, delegate(OpStatus status, Inventory leftovers) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) Report(status); ReturnToPlayer(chest, leftovers, source); }); } internal static void TakeAndDrop(Container chest, ItemData item, int amount) { ContainerService.Take(chest, new TakeItem[1] { TakeItem.From(item, amount) }, delegate(OpStatus status, Inventory taken) { Report(status); DropAll(taken); }, respectLocks: false); } internal static void TakeAll(Container chest) { Inventory val = CopyOf(((Humanoid)LocalPlayer).GetInventory()); List<TakeItem> list = new List<TakeItem>(); foreach (ItemData allItem in chest.GetInventory().GetAllItems()) { if (!ChestLocks.IsLocked(chest, allItem)) { ItemData val2 = allItem.Clone(); int stack = val2.m_stack; int num = (val.AddItem(val2) ? stack : (stack - val2.m_stack)); if (num > 0) { list.Add(TakeItem.From(allItem, num)); } } } if (list.Count != 0) { ContainerService.Take(chest, list, delegate(OpStatus status, Inventory taken) { Report(status); GiveToPlayer(chest, taken); }); } } internal static void PlaceStacks(Container chest) { Player localPlayer = LocalPlayer; Inventory inventory = ((Humanoid)localPlayer).GetInventory(); Inventory inventory2 = chest.GetInventory(); Inventory val = ContainerService.NewTemp(); foreach (ItemData item in inventory.GetAllItems().ToList()) { if (inventory2.ContainsItemByName(item.m_shared.m_name) && !((Humanoid)localPlayer).IsItemEquiped(item) && !LockedSlots.IsLocked(item)) { val.MoveItemToThis(inventory, item, item.m_stack, item.m_gridPos.x, item.m_gridPos.y); } } int sent = val.CountItems((string)null, -1, false); if (sent == 0) { ((Character)localPlayer).Message((MessageType)2, "$msg_stackall_none", 0, (Sprite)null, false); return; } ContainerService.StackInto(chest, val, delegate(OpStatus status, Inventory leftovers) { int num = sent - leftovers.CountItems((string)null, -1, false); ReturnToPlayer(chest, leftovers, null); if (Report(status)) { Player localPlayer2 = LocalPlayer; if (localPlayer2 != null) { ((Character)localPlayer2).Message((MessageType)2, (num > 0) ? ("$msg_stackall " + num) : "$msg_stackall_none", 0, (Sprite)null, false); } } }); } private static void PlaceAtTarget(Container chest, Inventory taken, Vector2i target, Vector2i chestSource, bool wholeStack) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = LocalPlayer; Inventory val = ((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null); ItemData val2 = taken.GetAllItems().FirstOrDefault(); if (val == null || val2 == null) { return; } ItemData itemAt = val.GetItemAt(target.x, target.y); if (itemAt == null || (!(itemAt.m_shared.m_name != val2.m_shared.m_name) && (val2.m_shared.m_maxQuality <= 1 || itemAt.m_quality == val2.m_quality) && itemAt.m_shared.m_maxStackSize != 1)) { val.MoveItemToThis(taken, val2, val2.m_stack, target.x, target.y); } else if (wholeStack && !itemAt.m_shared.m_questItem) { ((Humanoid)localPlayer).RemoveEquipAction(itemAt); ((Humanoid)localPlayer).UnequipItem(itemAt, false); Inventory val3 = ContainerService.NewTemp(); val3.MoveItemToThis(val, itemAt, itemAt.m_stack, 0, 0); val.MoveItemToThis(taken, val2, val2.m_stack, target.x, target.y); ContainerService.Put(chest, val3, chestSource, allowSwap: false, delegate(OpStatus status, Inventory leftovers) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) Report(status); ReturnToPlayer(chest, leftovers, target); }); } } internal static void GiveToPlayer(Container chest, Inventory items) { Player localPlayer = LocalPlayer; Inventory val = ((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null); if (val != null) { foreach (ItemData item in items.GetAllItems().ToList()) { val.MoveItemToThis(items, item); } } PutBackOrDrop(chest, items); } internal static void ReturnToPlayer(Container chest, Inventory items, Vector2i? preferred) { //IL_004e: 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) Player localPlayer = LocalPlayer; Inventory val = ((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null); if (val != null) { foreach (ItemData item in items.GetAllItems().ToList()) { if ((!preferred.HasValue || !TryMoveTo(val, items, item, preferred.Value)) && !TryMoveTo(val, items, item, item.m_gridPos)) { val.MoveItemToThis(items, item); } } } if (items.NrOfItems() > 0) { DropAll(items); } } private static bool TryMoveTo(Inventory inventory, Inventory from, ItemData item, Vector2i pos) { //IL_0000: 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_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) if (pos.x < 0 || pos.y < 0 || pos.x >= inventory.GetWidth() || pos.y >= inventory.GetHeight()) { return false; } inventory.MoveItemToThis(from, item, item.m_stack, pos.x, pos.y); return !from.ContainsItem(item); } private static void PutBackOrDrop(Container chest, Inventory items) { if (items.NrOfItems() == 0) { return; } if ((Object)(object)chest == (Object)null || !chest.m_nview.IsValid()) { DropAll(items); return; } ContainerService.Put(chest, items, null, allowSwap: false, delegate(OpStatus status, Inventory leftovers) { DropAll(leftovers); }); } private static void DropAll(Inventory items) { Player localPlayer = LocalPlayer; if ((Object)(object)localPlayer == (Object)null || items.NrOfItems() == 0) { return; } foreach (ItemData item in items.GetAllItems().ToList()) { ((Humanoid)localPlayer).DropItem(items, item, item.m_stack); } ((Character)localPlayer).Message((MessageType)2, "$inventory_full", 0, (Sprite)null, false); } private static Inventory CopyOf(Inventory source) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown //IL_0029: Expected O, but got Unknown Inventory val = new Inventory("VikingQoL", (Sprite)null, source.GetWidth(), source.GetHeight()); val.Load(new ZPackage(ContainerService.Pack(source))); return val; } private static bool Report(OpStatus status) { switch (status) { case OpStatus.Ok: return true; case OpStatus.Invalid: { Player localPlayer2 = LocalPlayer; if (localPlayer2 != null) { ((Character)localPlayer2).Message((MessageType)2, "Something went wrong with the chest; see the log.", 0, (Sprite)null, false); } return true; } default: { Player localPlayer = LocalPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, "The chest didn't respond, try again.", 0, (Sprite)null, false); } return false; } } } } [HarmonyPatch] internal static class ChestCrafting { private sealed class Pending { public Container Chest; public bool Disabled; public long PlayerId; public float SentAt; public int Retries; } private const string Key = "vqol_nocraft"; private const string RpcSet = "VikingQoL_SetChestCrafting"; private const float ConfirmAfter = 1.5f; private const int MaxRetries = 2; private static readonly int KeyHash = StringExtensionMethods.GetStableHashCode("vqol_nocraft"); private static readonly List<Pending> PendingChanges = new List<Pending>(); [HarmonyPatch(typeof(Container), "Awake")] [HarmonyPostfix] private static void ContainerAwake(Container __instance) { if (!((Object)(object)__instance.m_nview == (Object)null) && __instance.m_nview.IsValid()) { Container chest = __instance; chest.m_nview.Register<bool, long>("VikingQoL_SetChestCrafting", (Action<long, bool, long>)delegate(long sender, bool disabled, long playerId) { Apply(chest, disabled, playerId); }); } } internal static bool IsDisabled(Container chest) { if (ChestRules.IsPlayerChest(chest)) { return chest.m_nview.GetZDO().GetString(KeyHash, "") == "1"; } return false; } internal static bool EffectiveDisabled(Container chest) { return PendingChanges.FirstOrDefault((Pending p) => (Object)(object)p.Chest == (Object)(object)chest)?.Disabled ?? IsDisabled(chest); } internal static bool CanSet(Container chest) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) if (StorageConfig.CraftFromChestsEnabled.Value && ChestRules.IsPlayerChest(chest) && (Object)(object)Game.instance != (Object)null && chest.CheckAccess(Game.instance.GetPlayerProfile().GetPlayerID())) { if (chest.m_checkGuardStone && !StorageConfig.IgnoreWardOnChests.Value) { return PrivateArea.CheckAccess(((Component)chest).transform.position, 0f, false, false); } return true; } return false; } internal static void Toggle(Container chest) { if (CanSet(chest)) { Pending pending = new Pending { Chest = chest, Disabled = !EffectiveDisabled(chest), PlayerId = Game.instance.GetPlayerProfile().GetPlayerID(), SentAt = Time.unscaledTime }; PendingChanges.RemoveAll((Pending p) => (Object)(object)p.Chest == (Object)(object)chest); PendingChanges.Add(pending); Send(pending); } } private static void Apply(Container chest, bool disabled, long playerId) { if (ChestRules.IsPlayerChest(chest) && chest.m_nview.IsOwner() && StorageConfig.CraftFromChestsEnabled.Value && chest.CheckAccess(playerId)) { string text = (disabled ? "1" : ""); ZDO zDO = chest.m_nview.GetZDO(); if (zDO.GetString(KeyHash, "") != text) { zDO.Set("vqol_nocraft", text); } } } private static void Send(Pending pending) { ZNetView nview = pending.Chest.m_nview; if (!nview.HasOwner()) { nview.ClaimOwnership(); } if (nview.IsOwner()) { Apply(pending.Chest, pending.Disabled, pending.PlayerId); return; } nview.InvokeRPC("VikingQoL_SetChestCrafting", new object[2] { pending.Disabled, pending.PlayerId }); } internal static void Tick() { if (PendingChanges.Count == 0) { return; } float unscaledTime = Time.unscaledTime; foreach (Pending item in PendingChanges.ToList()) { if (unscaledTime - item.SentAt < 1.5f) { continue; } if (!ChestRules.IsPlayerChest(item.Chest) || IsDisabled(item.Chest) == item.Disabled || !StorageConfig.CraftFromChestsEnabled.Value) { PendingChanges.Remove(item); } else if (item.Retries >= 2) { PendingChanges.Remove(item); Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, "The chest didn't respond, try again.", 0, (Sprite)null, false); } } else { item.Retries++; item.SentAt = unscaledTime; Log.Debug($"Chest crafting switch not confirmed, asking again (try {item.Retries})."); Send(item); } } } } [HarmonyPatch] internal static class ChestHover { private static bool _failed; [HarmonyPatch(typeof(Container), "GetHoverText")] [HarmonyPostfix] private static void Hover(Container __instance, ref string __result) { try { string text = ChestLabels.Label(__instance); string text2 = ChestViewers.OpenByLine(__instance); string text3 = PeekBlock(__instance); string text4 = (ChestCrafting.IsDisabled(__instance) ? "<color=#AAAAAA>Not used for crafting</color>" : null); if (text != null || text2 != null || text3 != null || text4 != null) { int num = __result.IndexOf('\n'); string text5 = ((num < 0) ? __result : __result.Substring(0, num)); string text6 = ((num < 0) ? "" : __result.Substring(num)); if (text != null) { text5 = WithLabel(text, ChestLabels.ColorHex(__instance), text5, Localization.instance.Localize(__instance.m_name)); } __result = text5 + ((text2 != null) ? ("\n" + text2) : "") + text6 + ((text4 != null) ? ("\n" + text4) : "") + ((text3 != null) ? ("\n" + text3) : ""); } } catch (Exception ex) { if (!_failed) { Log.Warn("Chest hover text failed: " + ex.Message); } _failed = true; } } private static string WithLabel(string label, string colorHex, string nameLine, string chestName) { string text = ((colorHex != null) ? ("<color=" + colorHex + ">" + label + "</color>") : label); int value = StorageConfig.ChestLabelSize.Value; if (value != 100) { text = $"<size={value}%>{text}</size>"; } if (!nameLine.StartsWith(chestName, StringComparison.Ordinal)) { return text + " (" + nameLine + ")"; } return text + " (" + chestName + ")" + nameLine.Substring(chestName.Length); } private static string PeekBlock(Container chest) { if (StorageConfig.ChestPeekStyle.Value == ChestPeekStyle.Icons && ChestPeekIcons.Working) { return null; } ChestPeekData.Result result = ChestPeekData.Get(chest); if (result == null) { return null; } return ChestPeekData.TextOf(result); } } [HarmonyPatch] internal static class ChestLabels { private sealed class Pending { public Container Chest; public bool IsColor; public string Value; public float SentAt; public int Retries; } private const string Key = "vqol_label"; private const string ColorKey = "vqol_label_color"; private const string RpcSetLabel = "VikingQoL_SetLabel"; private const string RpcSetColor = "VikingQoL_SetLabelColor"; private const int MaxLength = 32; private const float ConfirmAfter = 1.5f; private const int MaxRetries = 2; private static readonly int KeyHash = StringExtensionMethods.GetStableHashCode("vqol_label"); private static readonly int ColorKeyHash = StringExtensionMethods.GetStableHashCode("vqol_label_color"); private static readonly List<Pending> PendingChanges = new List<Pending>(); [HarmonyPatch(typeof(Container), "Awake")] [HarmonyPostfix] private static void ContainerAwake(Container __instance) { ZNetView nview = __instance.m_nview; if (!((Object)(object)nview == (Object)null) && nview.GetZDO() != null) { Container chest = __instance; nview.Register<string>("VikingQoL_SetLabel", (Action<long, string>)delegate(long sender, string text) { Apply(chest, text); }); nview.Register<string>("VikingQoL_SetLabelColor", (Action<long, string>)delegate(long sender, string name) { ApplyColor(chest, name); }); } } internal static string Label(Container chest) { if (!StorageConfig.ChestLabelsEnabled.Value || (Object)(object)chest == (Object)null || (Object)(object)chest.m_nview == (Object)null || !chest.m_nview.IsValid()) { return null; } string text = chest.m_nview.GetZDO().GetString(KeyHash, ""); if (!string.IsNullOrEmpty(text)) { return text; } return null; } internal static string ColorName(Container chest) { if (Label(chest) == null) { return null; } if (!Palette.TryGet(chest.m_nview.GetZDO().GetString(ColorKeyHash, ""), out var name, out var _)) { return null; } return name; } internal static string ColorHex(Container chest) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (!Palette.TryGet(ColorName(chest), out var _, out var color)) { return null; } return Palette.ToHex(color); } internal static string EffectiveColorName(Container chest) { foreach (Pending pendingChange in PendingChanges) { if (pendingChange.IsColor && (Object)(object)pendingChange.Chest == (Object)(object)chest) { return (pendingChange.Value.Length == 0) ? null : pendingChange.Value; } } return ColorName(chest); } internal static bool CanRename(Container chest) { if (StorageConfig.ChestLabelsEnabled.Value && ChestRules.IsPlayerChest(chest)) { return chest.CheckAccess(Game.instance.GetPlayerProfile().GetPlayerID()); } return false; } internal static string Sanitize(string text) { text = (text ?? "").Replace("<", "").Replace(">", "").Replace("\n", " ") .Trim(); if (text.Length <= 32) { return text; } return text.Substring(0, 32).TrimEnd(); } private static void Apply(Container chest, string text) { if (chest.m_nview.IsValid() && chest.m_nview.IsOwner() && StorageConfig.ChestLabelsEnabled.Value && ChestRules.IsPlayerChest(chest)) { string text2 = Sanitize(text); ZDO zDO = chest.m_nview.GetZDO(); if (zDO.GetString(KeyHash, "") != text2) { zDO.Set("vqol_label", text2); } if (text2.Length == 0 && zDO.GetString(ColorKeyHash, "").Length > 0) { zDO.Set("vqol_label_color", ""); } } } private static void ApplyColor(Container chest, string name) { if (chest.m_nview.IsValid() && chest.m_nview.IsOwner() && StorageConfig.ChestLabelsEnabled.Value && ChestRules.IsPlayerChest(chest)) { ZDO zDO = chest.m_nview.GetZDO(); string name2 = ""; if ((string.IsNullOrWhiteSpace(name) || (Palette.TryGet(name, out name2, out var _) && zDO.GetString(KeyHash, "").Length != 0)) && zDO.GetString(ColorKeyHash, "") != name2) { zDO.Set("vqol_label_color", name2); } } } internal static void Set(Container chest, string text) { if (!((Object)(object)chest == (Object)null) && chest.m_nview.IsValid()) { string value = Sanitize(text); Track(new Pending { Chest = chest, Value = value, SentAt = Time.unscaledTime }); Send(chest, value, isColor: false); } } internal static void SetColor(Container chest, string colorName) { if (!((Object)(object)chest == (Object)null) && chest.m_nview.IsValid()) { string value = colorName ?? ""; Track(new Pending { Chest = chest, IsColor = true, Value = value, SentAt = Time.unscaledTime }); Send(chest, value, isColor: true); } } internal static void CycleColor(Container chest, int direction) { if (Label(chest) == null) { return; } string current = EffectiveColorName(chest); int num = Palette.Entries.Length + 1; int num2 = 0; if (current != null) { num2 = Array.FindIndex(Palette.Entries, ((string name, Color color) e) => e.name == current) + 1; } num2 = ((num2 + direction) % num + num) % num; SetColor(chest, (num2 == 0) ? "" : Palette.Entries[num2 - 1].name); } private static void Track(Pending change) { PendingChanges.RemoveAll((Pending p) => (Object)(object)p.Chest == (Object)(object)change.Chest && p.IsColor == change.IsColor); PendingChanges.Add(change); } private static void Send(Container chest, string value, bool isColor) { if (!chest.m_nview.HasOwner()) { chest.m_nview.ClaimOwnership(); } if (chest.m_nview.IsOwner()) { if (isColor) { ApplyColor(chest, value); } else { Apply(chest, value); } } else { chest.m_nview.InvokeRPC(isColor ? "VikingQoL_SetLabelColor" : "VikingQoL_SetLabel", new object[1] { value }); } } internal static void Tick() { if (PendingChanges.Count == 0) { return; } float unscaledTime = Time.unscaledTime; foreach (Pending item in PendingChanges.ToList()) { if (unscaledTime - item.SentAt < 1.5f) { continue; } if ((Object)(object)item.Chest == (Object)null || !item.Chest.m_nview.IsValid() || Confirmed(item)) { PendingChanges.Remove(item); } else if (item.Retries >= 2) { PendingChanges.Remove(item); Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, "The chest didn't respond, try again.", 0, (Sprite)null, false); } } else { item.Retries++; item.SentAt = unscaledTime; Log.Debug(string.Format("Chest {0} not confirmed, asking again (try {1}).", item.IsColor ? "label color" : "label", item.Retries)); Send(item.Chest, item.Value, item.IsColor); } } } private static bool Confirmed(Pending pending) { ZDO zDO = pending.Chest.m_nview.GetZDO(); if (!pending.IsColor) { return (zDO.GetString(KeyHash, "") ?? "") == pending.Value; } if (!((zDO.GetString(ColorKeyHash, "") ?? "") == pending.Value)) { return (zDO.GetString(KeyHash, "") ?? "").Length == 0; } return true; } } internal sealed class ChestLabelReceiver : TextReceiver { private readonly Container _chest; private ChestLabelReceiver(Container chest) { _chest = chest; } public string GetText() { return ChestLabels.Label(_chest) ?? ""; } public void SetText(string text) { ChestLabels.Set(_chest, text); } internal static void Begin(Container chest) { if (!((Object)(object)chest == (Object)null) && !((Object)(object)TextInput.instance == (Object)null) && ChestLabels.CanRename(chest)) { InventoryGui.instance.Hide(); TextInput.instance.RequestText((TextReceiver)(object)new ChestLabelReceiver(chest), "Chest name", 32); } } } [HarmonyPatch] internal static class ChestLabelsUI { private static Color? _defaultColor; [HarmonyPatch(typeof(InventoryGui), "UpdateContainer")] [HarmonyPostfix] private static void UpdateContainer(InventoryGui __instance) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) Container currentContainer = __instance.m_currentContainer; if (!((Object)(object)currentContainer == (Object)null) && ((Component)__instance.m_container).gameObject.activeSelf) { TMP_Text containerName = __instance.m_containerName; if (!_defaultColor.HasValue) { _defaultColor = ((Graphic)containerName).color; } string text = ChestLabels.Label(currentContainer); if (text != null && containerName.text != text) { containerName.text = text; } Color value = _defaultColor.Value; string text2 = ((text != null) ? ChestLabels.ColorHex(currentContainer) : null); if (text2 != null) { ColorUtility.TryParseHtmlString(text2, ref value); } if (((Graphic)containerName).color != value) { ((Graphic)containerName).color = value; } } } } [HarmonyPatch] internal static class ChestLocks { private sealed class Snapshot { public string Text; public readonly HashSet<Vector2i> Slots = new HashSet<Vector2i>(); } private sealed class Pending { public Container Chest; public Vector2i Position; public bool Locked; public long PlayerId; public float SentAt; public int Retries; } private const string Key = "vqol_locks"; private const string RpcSet = "VikingQoL_SetChestLock"; private const float ConfirmAfter = 1.5f; private const int MaxRetries = 2; private static readonly int KeyHash = StringExtensionMethods.GetStableHashCode("vqol_locks"); private static readonly ConditionalWeakTable<Container, Snapshot> Snapshots = new ConditionalWeakTable<Container, Snapshot>(); private static readonly List<Pending> PendingChanges = new List<Pending>(); internal static bool Enabled => StorageConfig.LockedChestSlotsEnabled.Value; internal static int Revision { get; private set; } [HarmonyPatch(typeof(Container), "Awake")] [HarmonyPostfix] private static void ContainerAwake(Container __instance) { if (!((Object)(object)__instance.m_nview == (Object)null) && __instance.m_nview.IsValid()) { Container chest = __instance; chest.m_nview.Register<int, int, bool, long>("VikingQoL_SetChestLock", (Method<int, int, bool, long>)delegate(long sender, int x, int y, bool locked, long playerId) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) Apply(chest, new Vector2i(x, y), locked, playerId); }); } } internal static string State(Container chest) { if (!Enabled || !ChestRules.IsPlayerChest(chest)) { return ""; } return Read(chest).Text; } private static Snapshot Read(Container chest) { //IL_00c4: Unknown result type (might be due to invalid IL or missing references) string text = chest.m_nview.GetZDO().GetString(KeyHash, ""); Snapshot value = Snapshots.GetValue(chest, (Container _) => new Snapshot()); if (value.Text == text) { return value; } value.Text = text; Revision++; value.Slots.Clear(); string[] array = text.Split(';'); for (int num = 0; num < array.Length; num++) { string[] array2 = array[num].Split(','); if (array2.Length == 2 && int.TryParse(array2[0], out var result) && int.TryParse(array2[1], out var result2) && result >= 0 && result2 >= 0) { value.Slots.Add(new Vector2i(result, result2)); } } return value; } internal static bool IsLocked(Container chest, Vector2i position) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (Enabled && ChestRules.IsPlayerChest(chest)) { return Read(chest).Slots.Contains(position); } return false; } internal static bool IsLocked(Container chest, ItemData item) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (item != null) { return IsLocked(chest, item.m_gridPos); } return false; } internal static bool EffectiveLocked(Container chest, Vector2i position) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) if (Enabled) { return PendingChanges.FirstOrDefault((Pending p) => (Object)(object)p.Chest == (Object)(object)chest && p.Position == position)?.Locked ?? IsLocked(chest, position); } return false; } internal static bool CanSet(Container chest) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) if (Enabled && ChestRules.IsPlayerChest(chest) && (Object)(object)Game.instance != (Object)null && chest.CheckAccess(Game.instance.GetPlayerProfile().GetPlayerID())) { if (chest.m_checkGuardStone && !StorageConfig.IgnoreWardOnChests.Value) { return PrivateArea.CheckAccess(((Component)chest).transform.position, 0f, false, false); } return true; } return false; } private static bool InBounds(Container chest, Vector2i position) { //IL_0008: 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_001a: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) if (chest.GetInventory() != null && position.x >= 0 && position.y >= 0 && position.x < chest.GetInventory().GetWidth()) { return position.y < chest.GetInventory().GetHeight(); } return false; } internal static void Toggle(Container chest, Vector2i position) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0028: 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_005a: Unknown result type (might be due to invalid IL or missing references) if (CanSet(chest) && InBounds(chest, position)) { Pending pending = new Pending { Chest = chest, Position = position, Locked = !EffectiveLocked(chest, position), PlayerId = Game.instance.GetPlayerProfile().GetPlayerID(), SentAt = Time.unscaledTime }; PendingChanges.RemoveAll((Pending p) => (Object)(object)p.Chest == (Object)(object)chest && p.Position == position); PendingChanges.Add(pending); Send(pending); } } private static void Apply(Container chest, Vector2i position, bool locked, long playerId) { //IL_001d: 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_0044: Unknown result type (might be due to invalid IL or missing references) if (!Enabled || !ChestRules.IsPlayerChest(chest) || !chest.m_nview.IsOwner() || !InBounds(chest, position) || !chest.CheckAccess(playerId)) { return; } HashSet<Vector2i> hashSet = new HashSet<Vector2i>(Read(chest).Slots); if (locked ? hashSet.Add(position) : hashSet.Remove(position)) { chest.m_nview.GetZDO().Set("vqol_locks", string.Join(";", from p in hashSet orderby p.y, p.x select $"{p.x},{p.y}")); } } private static void Send(Pending pending) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) ZNetView nview = pending.Chest.m_nview; if (!nview.HasOwner()) { nview.ClaimOwnership(); } if (nview.IsOwner()) { Apply(pending.Chest, pending.Position, pending.Locked, pending.PlayerId); return; } nview.InvokeRPC("VikingQoL_SetChestLock", new object[4] { pending.Position.x, pending.Position.y, pending.Locked, pending.PlayerId }); } internal static void Tick() { //IL_005d: 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) if (PendingChanges.Count == 0) { return; } float unscaledTime = Time.unscaledTime; foreach (Pending item in PendingChanges.ToList()) { if (unscaledTime - item.SentAt < 1.5f) { continue; } if (!Enabled || !ChestRules.IsPlayerChest(item.Chest) || !InBounds(item.Chest, item.Position) || IsLocked(item.Chest, item.Position) == item.Locked) { PendingChanges.Remove(item); } else if (item.Retries >= 2) { PendingChanges.Remove(item); Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, "The chest didn't respond, try again.", 0, (Sprite)null, false); } } else { item.Retries++; item.SentAt = unscaledTime; Log.Debug($"Chest slot lock not confirmed, asking again (try {item.Retries})."); Send(item); } } } } internal static class ChestPeekData { internal sealed class Entry { public string Name; public int Total; public Sprite Icon; } internal sealed class Result { public uint Revision; public int Items; public int Limit; public readonly List<Entry> Shown = new List<Entry>(); public int More; public string Text; } private const string TextColor = "#C0C0C0"; private const int TextPerLine = 3; private static readonly Dictionary<Container, Result> Cache = new Dictionary<Container, Result>(); internal static Result Get(Container chest) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)chest == (Object)null || !StorageConfig.ChestPeekEnabled.Value || !StorageConfig.ShowChestPeek.Value || !ChestRules.IsPlayerChest(chest)) { return null; } Inventory inventory = chest.GetInventory(); if (inventory == null || inventory.NrOfItems() == 0) { return null; } if (!chest.CheckAccess(Game.instance.GetPlayerProfile().GetPlayerID()) || (chest.m_checkGuardStone && !PrivateArea.CheckAccess(((Component)chest).transform.position, 0f, false, false))) { return null; } uint dataRevision = chest.m_nview.GetZDO().DataRevision; int value = StorageConfig.ChestPeekItems.Value; int num = inventory.NrOfItems(); if (Cache.TryGetValue(chest, out var value2) && value2.Revision == dataRevision && value2.Items == num && value2.Limit == value) { return value2; } if (Cache.Count > 64) { foreach (Container item in Cache.Keys.Where((Container c) => (Object)(object)c == (Object)null).ToList()) { Cache.Remove(item); } } Result result = Build(inventory, dataRevision, num, value); Cache[chest] = result; return result; } private static Result Build(Inventory inventory, uint revision, int count, int limit) { var list = (from i in inventory.GetAllItems() group i by i.m_shared.m_name into g select new { Key = g.Key, Total = g.Sum((ItemData i) => i.m_stack), First = g.First() } into g orderby g.Total descending, g.Key select g).ToList(); Result result = new Result { Revision = revision, Items = count, Limit = limit, More = Math.Max(0, list.Count - limit) }; foreach (var item in list.Take(limit)) { result.Shown.Add(new Entry { Name = Localization.instance.Localize(item.Key), Total = item.Total, Icon = IconOf(item.First) }); } return result; } private static Sprite IconOf(ItemData item) { Sprite[] icons = item.m_shared.m_icons; if (icons == null || item.m_variant < 0 || item.m_variant >= icons.Length) { if (icons == null || icons.Length == 0) { return null; } return icons[0]; } return icons[item.m_variant]; } internal static string TextOf(Result result) { if (result.Text != null) { return result.Text; } List<string> list = result.Shown.Select((Entry e) => $"{e.Name} {e.Total}").ToList(); if (result.More > 0) { list.Add($"+{result.More} more"); } StringBuilder stringBuilder = new StringBuilder(); for (int num = 0; num < list.Count; num += 3) { if (num > 0) { stringBuilder.Append('\n'); } stringBuilder.Append("<color=#C0C0C0>").Append(string.Join(" · ", list.Skip(num).Take(3))).Append("</color>"); } return result.Text = stringBuilder.ToString(); } } [HarmonyPatch] internal static class ChestPeekIcons { private sealed class CellUI { public GameObject Root; public RectTransform Rect; public Image Icon; public TextMeshProUGUI Count; } private const float CellSize = 34f; private const float Spacing = 4f; private const float Gap = 6f; private const int PerLine = 6; private static readonly List<CellUI> Cells = new List<CellUI>(); private static GameObject _panel; private static RectTransform _panelRect; private static bool _failed; private static bool _ran; private static ChestPeekData.Result _shown; private static float _shownPivot = -1f; private static string _measuredText; private static GameObject _lastHover; private static Container _lastChest; internal static bool Working { get { if (_ran) { return !_failed; } return false; } } [HarmonyPatch(typeof(Hud), "UpdateCrosshair")] [HarmonyPostfix] private static void UpdateCrosshair(Hud __instance, Player player) { if (_failed) { return; } _ran = true; try { ChestPeekData.Result result = Wanted(__instance, player); if (result == null) { Hide(); return; } if ((Object)(object)_panel == (Object)null) { Setup(__instance); } Show(__instance, result); } catch (Exception arg) { _failed = true; Hide(); Log.Warn($"Chest peek icons unavailable, using the text list instead: {arg}"); } } internal static void Tick() { if ((Object)(object)_panel != (Object)null && _panel.activeSelf && (Object)(object)Player.m_localPlayer == (Object)null) { Hide(); } } private static ChestPeekData.Result Wanted(Hud hud, Player player) { if (StorageConfig.ChestPeekStyle.Value != ChestPeekStyle.Icons || ((TMP_Text)hud.m_hoverName).text.Length == 0) { return null; } GameObject hoverObject = ((Humanoid)player).GetHoverObject(); if ((Object)(object)hoverObject != (Object)(object)_lastHover) { _lastHover = hoverObject; _lastChest = (Object.op_Implicit((Object)(object)hoverObject) ? hoverObject.GetComponentInParent<Container>() : null); } if (!((Object)(object)_lastChest != (Object)null)) { return null; } return ChestPeekData.Get(_lastChest); } private static void Setup(Hud hud) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Expected O, but got Unknown //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) TextMeshProUGUI hoverName = hud.m_hoverName; Cells.Clear(); _shown = null; _measuredText = null; _lastHover = null; _lastChest = null; _panel = new GameObject("VikingQoL_ChestPeek", new Type[1] { typeof(RectTransform) }); _panel.transform.SetParent(((TMP_Text)hoverName).transform.parent, false); _panelRect = (RectTransform)_panel.transform; RectTransform panelRect = _panelRect; RectTransform panelRect2 = _panelRect; Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(0.5f, 0.5f); panelRect2.anchorMax = val; panelRect.anchorMin = val; _panel.AddComponent<LayoutElement>().ignoreLayout = true; _panel.SetActive(false); string name = ((Object)((TMP_Text)hoverName).transform.parent).name; object arg = ((TMP_Text)hoverName).horizontalAlignment; Rect rect = ((TMP_Text)hoverName).rectTransform.rect; Log.Debug("Chest peek panel created next to the hover text (parent '" + name + "', " + $"alignment {arg}, rect {((Rect)(ref rect)).size})."); } private static CellUI NewCell(TextMeshProUGUI hoverText) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown //IL_0061: 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_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0072: 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_00a9: 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) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: 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_0101: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_010c: 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_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0195: 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_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("Cell", new Type[2] { typeof(RectTransform), typeof(Image) }); val.transform.SetParent(_panel.transform, false); RectTransform val2 = (RectTransform)val.transform; Vector2 val3 = default(Vector2); ((Vector2)(ref val3))..ctor(0f, 1f); val2.pivot = val3; Vector2 anchorMin = (val2.anchorMax = val3); val2.anchorMin = anchorMin; val2.sizeDelta = new Vector2(34f, 34f); Image component = val.GetComponent<Image>(); ((Graphic)component).color = new Color(0f, 0f, 0f, 0.45f); ((Graphic)component).raycastTarget = false; GameObject val5 = new GameObject("Icon", new Type[2] { typeof(RectTransform), typeof(Image) }); val5.transform.SetParent(val.transform, false); RectTransform val6 = (RectTransform)val5.transform; val6.anchorMin = Vector2.zero; val6.anchorMax = Vector2.one; val6.offsetMin = new Vector2(2f, 2f); val6.offsetMax = new Vector2(-2f, -2f); Image component2 = val5.GetComponent<Image>(); component2.preserveAspect = true; ((Graphic)component2).raycastTarget = false; GameObject val7 = new GameObject("Count", new Type[2] { typeof(RectTransform), typeof(TextMeshProUGUI) }); val7.transform.SetParent(val.transform, false); RectTransform val8 = (RectTransform)val7.transform; val8.anchorMin = Vector2.zero; val8.anchorMax = Vector2.one; val8.offsetMin = new Vector2(1f, 0f); val8.offsetMax = new Vector2(-2f, 0f); TextMeshProUGUI component3 = val7.GetComponent<TextMeshProUGUI>(); ((TMP_Text)component3).font = ((TMP_Text)hoverText).font; ((TMP_Text)component3).fontSharedMaterial = ((TMP_Text)hoverText).fontSharedMaterial; ((TMP_Text)component3).fontSize = 13f; ((TMP_Text)component3).alignment = (TextAlignmentOptions)1028; ((Graphic)component3).color = Color.white; ((TMP_Text)component3).textWrappingMode = (TextWrappingModes)0; ((TMP_Text)component3).overflowMode = (TextOverflowModes)0; ((Graphic)component3).raycastTarget = false; return new CellUI { Root = val, Rect = val2, Icon = component2, Count = component3 }; } private static void Show(Hud hud, ChestPeekData.Result data) { //IL_004f: 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_0083: 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_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: 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_00d4: 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_0067: Unknown result type (might be due to invalid IL or missing references) TextMeshProUGUI hoverName = hud.m_hoverName; float num = PivotX(hoverName); string text = ((TMP_Text)hoverName).text; if (!string.Equals(text, _measuredText, StringComparison.Ordinal)) { ((TMP_Text)hoverName).ForceMeshUpdate(false, false); _measuredText = text; } if (data != _shown || !Mathf.Approximately(num, _shownPivot)) { Rebuild(hoverName, data, num); } Bounds textBounds = ((TMP_Text)hoverName).textBounds; float num2 = ((num < 0.25f) ? ((Bounds)(ref textBounds)).min.x : ((num > 0.75f) ? ((Bounds)(ref textBounds)).max.x : ((Bounds)(ref textBounds)).center.x)); Vector3 position = ((TMP_Text)hoverName).transform.TransformPoint(new Vector3(num2, ((Bounds)(ref textBounds)).min.y - 6f, 0f)); _panelRect.pivot = new Vector2(num, 1f); ((Transform)_panelRect).position = position; if (!_panel.activeSelf) { _panel.SetActive(true); } } private static float PivotX(TextMeshProUGUI hoverText) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Invalid comparison between Unknown and I4 //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 HorizontalAlignmentOptions horizontalAlignment = ((TMP_Text)hoverText).horizontalAlignment; if ((int)horizontalAlignment != 2) { if ((int)horizontalAlignment == 4) { return 1f; } if ((int)horizontalAlignment != 32) { return 0f; } } return 0.5f; } private static void Rebuild(TextMeshProUGUI hoverText, ChestPeekData.Result data, float pivotX) { //IL_005d: 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) _shown = data; _shownPivot = pivotX; int num = data.Shown.Count + ((data.More > 0) ? 1 : 0); int num2 = Mathf.Min(num, 6); int num3 = (num + 6 - 1) / 6; _panelRect.sizeDelta = new Vector2((float)num2 * 34f + (float)(num2 - 1) * 4f, (float)num3 * 34f + (float)(num3 - 1) * 4f); while (Cells.Count < num) { Cells.Add(NewCell(hoverText)); } for (int i = 0; i < Cells.Count; i++) { CellUI cellUI = Cells[i]; if (i >= num) { cellUI.Root.SetActive(false); continue; } int num4 = i / 6; int num5 = Mathf.Min(6, num - num4 * 6); float num6 = (float)(num2 - num5) * 38f * pivotX; cellUI.Rect.anchoredPosition = new Vector2(num6 + (float)(i % 6) * 38f, (float)(-num4) * 38f); cellUI.Root.SetActive(true); if (i < data.Shown.Count) { ChestPeekData.Entry entry = data.Shown[i]; cellUI.Icon.sprite = entry.Icon; ((Behaviour)cellUI.Icon).enabled = (Object)(object)entry.Icon != (Object)null; ((TMP_Text)cellUI.Count).alignment = (TextAlignmentOptions)1028; ((TMP_Text)cellUI.Count).fontSize = 13f; ((TMP_Text)cellUI.Count).text = Numbers.Short(entry.Total); } else { ((Behaviour)cellUI.Icon).enabled = false; ((TMP_Text)cellUI.Count).alignment = (TextAlignmentOptions)514; ((TMP_Text)cellUI.Count).fontSize = 14f; ((TMP_Text)cellUI.Count).text = "+" + data.More; } } } private static void Hide() { if ((Object)(object)_panel != (Object)null && _panel.activeSelf) { _panel.SetActive(false); } _shown = null; } } [HarmonyPatch] internal static class ChestRules { internal static bool IsPlayerChest(Container container) { if ((Object)(object)container == (Object)null || (Object)(object)container.m_nview == (Object)null || !container.m_nview.IsValid()) { return false; } if ((Object)(object)container.m_wagon != (Object)null || (Object)(object)container.m_rootObjectOverride != (Object)null) { return false; } if ((Object)(object)((Component)container).GetComponentInParent<Ship>() != (Object)null || (Object)(object)((Component)container).GetComponent<TombStone>() != (Object)null) { return false; } if ((Object)(object)container.m_piece != (Object)null) { return container.m_piece.IsPlacedByPlayer(); } return false; } internal static bool IsShared(Container container) { if (StorageConfig.SharedChestsEnabled.Value) { return IsPlayerChest(container); } return false; } [HarmonyPatch(typeof(Container), "CheckAccess")] [HarmonyPostfix] private static void CheckAccess(Container __instance, ref bool __result) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (!__result && (int)__instance.m_privacy == 0 && StorageConfig.IgnorePersonalChestLock.Value && IsPlayerChest(__instance)) { __result = true; } } [HarmonyPatch(typeof(Container), "Interact")] [HarmonyPrefix] private static void InteractPrefix(Container __instance, out bool __state) { __state = SuspendWard(__instance); } [HarmonyPatch(typeof(Container), "Interact")] [HarmonyFinalizer] private static void InteractFinalizer(Container __instance, bool __state) { RestoreWard(__instance, __state); } [HarmonyPatch(typeof(Container), "GetHoverText")] [HarmonyPrefix] private static void HoverPrefix(Container __instance, out bool __state) { __state = SuspendWard(__instance); } [HarmonyPatch(typeof(Container), "GetHoverText")] [HarmonyFinalizer] private static void HoverFinalizer(Container __instance, bool __state) { RestoreWard(__instance, __state); } private static bool SuspendWard(Container container) { if (!container.m_checkGuardStone || !StorageConfig.IgnoreWardOnChests.Value || !IsPlayerChest(container)) { return false; } container.m_checkGuardStone = false; return true; } private static void RestoreWard(Container container, bool suspended) { if (suspended) { container.m_checkGuardStone = true; } } } [HarmonyPatch] internal static class ChestViewers { private sealed class Viewer { public string Name; public float LastSeen; } private const string RpcViewer = "VikingQoL_ChestViewer"; private const string ZdoViewersKey = "vqol_viewers"; private const string ZdoViewersTimeKey = "vqol_viewers_t"; private const float PingInterval = 2f; private const float ViewerExpiry = 6f; private const float HeartbeatInterval = 5f; private const float NamesExpiry = 15f; private const float OpenRetryAfter = 1.2f; private const int OpenMaxRetries = 2; private static readonly Dictionary<Container, Dictionary<long, Viewer>> Remote = new Dictionary<Container, Dictionary<long, Viewer>>(); private static float _nextPrune; private static float _nextHeartbeat; private static readonly HashSet<Container> Occupied = new HashSet<Container>(); private static Container _viewing; private static float _lastPing; private static Container _pendingOpen; private static float _pendingOpenAt; private static int _pendingOpenTries; [HarmonyPatch(typeof(Container), "Awake")] [HarmonyPostfix] private static void ContainerAwake(Container __instance) { ZNetView nview = __instance.m_nview; if (!((Object)(object)nview == (Object)null) && nview.GetZDO() != null) { Container container = __instance; nview.Register<bool, string>("VikingQoL_ChestViewer", (Action<long, bool, string>)delegate(long sender, bool open, string name) { OnViewer(container, sender, open, name); }); } } [HarmonyPatch(typeof(Container), "RPC_RequestOpen")] [HarmonyPrefix] private static bool RequestOpen(Container __instance, long uid, long playerID) { //IL_008c: Unknown result type (might be due to invalid IL or missing references) if (!ChestRules.IsShared(__instance) || !__instance.m_nview.IsOwner()) { return true; } if ((!__instance.IsInUse() || uid == ZNet.GetUID()) && !HasOtherViewers(__instance, uid)) { return true; } if (!__instance.CheckAccess(playerID)) { __instance.m_nview.InvokeRPC(uid, "RPC_OpenResponse", new object[1] { false }); return false; } Log.Debug($"Shared open: {uid} joins {((Object)__instance).name} (owner keeps it)."); ZDOMan.instance.ForceSendZDO(uid, __instance.m_nview.GetZDO().m_uid); __instance.m_nview.InvokeRPC(uid, "RPC_OpenResponse", new object[1] { true }); return false; } [HarmonyPatch(typeof(Container), "Interact")] [HarmonyPostfix] private static void Interact(Container __instance, bool hold, bool __result) { if (!hold && __result && ChestRules.IsPlayerChest(__instance) && __instance.CheckAccess(Game.instance.GetPlayerProfile().GetPlayerID())) { _pendingOpen = __instance; _pendingOpenAt = Time.unscaledTime; _pendingOpenTries = 0; } } [HarmonyPatch(typeof(Container), "RPC_OpenResponse")] [HarmonyPrefix] private static void OpenResponse(Container __instance) { if ((Object)(object)_pendingOpen == (Object)(object)__instance) { _pendingOpen = null; } } private static void OnViewer(Container chest, long sender, bool open, string name) { if (chest.IsOwner()) { if (!Remote.TryGetValue(chest, out var value)) { value = (Remote[chest] = new Dictionary<long, Viewer>()); } if (open) { value[sender] = new Viewer { Name = name, LastSeen = Time.unscaledTime }; } else { value.Remove(sender); } Publish(chest); } } private static bool HasOtherViewers(Container chest, long uid) { Prune(chest); if (Remote.TryGetValue(chest, out var value)) { return value.Keys.Any((long k) => k != uid); } return false; } private static bool HasRemoteViewers(Container chest) { if (Remote.TryGetValue(chest, out var value)) { return value.Count > 0; } return false; } private static void Prune(Container chest) { if (!Remote.TryGetValue(chest, out var value)) { return; } float now = Time.unscaledTime; foreach (long item in (from v in value where now - v.Value.LastSeen > 6f select v.Key).ToList()) { value.Remove(item); } } private static void Publish(Container chest) { if ((Object)(object)chest == (Object)null || !chest.m_nview.IsValid() || !chest.IsOwner()) { return; } List<string> list = new List<string>(); if (chest.IsInUse() && Object.op_Implicit((Object)(object)Player.m_localPlayer)) { list.Add(Player.m_localPlayer.GetPlayerName()); } if (Remote.TryGetValue(chest, out var value)) { list.AddRange(value.Values.Select((Viewer v) => v.Name)); } ZDO zDO = chest.m_nview.GetZDO(); string text = string.Join(", ", list.Distinct()); if (zDO.GetString("vqol_viewers", "") != text) { zDO.Set("vqol_viewers", text); } if (text.Length > 0) { zDO.Set("vqol_viewers_t", (float)ZNet.instance.GetTimeSeconds()); } if (text.Length > 0) { Occupied.Add(chest); } else { Occupied.Remove(chest); } chest.UpdateUseVisual(); } [HarmonyPatch(typeof(Container), "SetInUse")] [HarmonyPrefix] private static void SetInUsePrefix(Container __instance, out bool __state) { __state = __instance.m_inUse; } [HarmonyPatch(typeof(Container), "SetInUse")] [HarmonyPostfix] private static void SetInUse(Container __instance, bool __state) { if (__instance.m_inUse != __state && ChestRules.IsShared(__instance)) { Publish(__instance); } } [HarmonyPatch(typeof(Container), "UpdateUseVisual")] [HarmonyPostfix] private static void UpdateUseVisual(Container __instance) { if (__instance.m_nview.IsValid() && __instance.IsOwner() && HasRemoteViewers(__instance)) { __instance.m_nview.GetZDO().Set(ZDOVars.s_inUse, 1, false); if (Object.op_Implicit((Object)(object)__instance.m_open)) { __instance.m_open.SetActive(true); } if (Object.op_Implicit((Object)(object)__instance.m_closed)) { __instance.m_closed.SetActive(false); } } } internal static string OpenByLine(Container chest) { if (!StorageConfig.ShowViewersInHoverText.Value || !ChestRules.IsShared(chest)) { return null; } ZDO zDO = chest.m_nview.GetZDO(); string text = zDO.GetString("vqol_viewers", ""); float num = (float)ZNet.instance.GetTimeSeconds() - zDO.GetFloat("vqol_viewers_t", 0f); if (string.IsNullOrEmpty(text) || !(num < 15f)) { return null; } return "<color=#B0B0B0>Open by: " + text + "</color>"; } internal static void Tick() { float unscaledTime = Time.unscaledTime; TickViewing(unscaledTime); TickPendingOpen(unscaledTime); if (unscaledTime >= _nextPrune) { _nextPrune = unscaledTime + 1f; foreach (Container item in Remote.Keys.ToList()) { if ((Object)(object)item == (Object)null || !item.m_nview.IsValid() || !item.IsOwner()) { Remote.Remove(item); continue; } int count = Remote[item].Count; Prune(item); if (Remote[item].Count != count) { Publish(item); } } } if (!(unscaledTime >= _nextHeartbeat)) { return; } _nextHeartbeat = unscaledTime + 5f; foreach (Container item2 in Occupied.ToList()) { if ((Object)(object)item2 == (Object)null || !item2.m_nview.IsValid() || !item2.IsOwner()) { Occupied.Remove(item2); } else { Publish(item2); } } } private static void TickViewing(float now) { Container val = (Object.op_Implicit((Object)(object)InventoryGui.instance) ? InventoryGui.instance.m_currentContainer : null); bool num = (Object)(object)val != (Object)null && val.m_nview.IsValid() && !val.IsOwner() && ChestRules.IsShared(val); if ((Object)(object)_viewing != (Object)null && (Object)(object)_viewing != (Object)(object)val) { SendViewer(_viewing, open: false); _viewing = null; } if (num) { val.m_inUse = false; val.CheckForChanges(); if ((Object)(object)_viewing != (Object)(object)val || now - _lastPing >= 2f) { SendViewer(val, open: true); _viewing = val; _lastPing = now; } } } private static void SendViewer(Container chest, bool open) { if (!((Object)(object)chest == (Object)null) && chest.m_nview.IsValid()) { string text = (Object.op_Implicit((Object)(object)Player.m_localPlayer) ? Player.m_localPlayer.GetPlayerName() : "?"); chest.m_nview.InvokeRPC("VikingQoL_ChestViewer", new object[2] { open, text }); } } private static void TickPendingOpen(float now) { if ((Object)(object)_pendingOpen == (Object)null) { return; } if (!_pendingOpen.m_nview.IsValid() || (Object)(object)InventoryGui.instance?.m_currentContainer == (Object)(object)_pendingOpen) { _pendingOpen = null; } else { if (now - _pendingOpenAt < 1.2f) { return; } if (_pendingOpenTries >= 2) { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, "The chest didn't respond, try again.", 0, (Sprite)null, false); } _pendingOpen = null; } else { _pendingOpenTries++; _pendingOpenAt = now; Log.Debug($"Chest open not answered, asking again (owner {_pendingOpen.m_nview.GetZDO().GetOwner()})."); _pendingOpen.m_nview.InvokeRPC("RPC_RequestOpen", new object[1] { Game.instance.GetPlayerProfile().GetPlayerID() }); } } } } internal enum OpStatus : byte { Ok, NotOwner, Invalid, Gone, Timeout } internal struct TakeItem { public Vector2i Pos; public string Name; public int Quality; public int Amount; public static TakeItem From(ItemData item, int amount) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) return new TakeItem { Pos = item.m_gridPos, Name = item.m_shared.m_name, Quality = item.m_quality, Amount = amount }; } } [HarmonyPatch] internal static class ContainerService { private enum OpType : byte { Take = 1, Put, Move, StackInto, Sort, TakeUnlocked } private sealed class Pending { public int Id; public Container Container; public byte[] Request; public Inventory SentItems; public Action<OpStatus, Inventory> Done; public float SentAt; public float RetryAt = -1f; public int Retries; } private const string RpcRequest = "VikingQoL_ChestOp"; private const string RpcResult = "VikingQoL_ChestOpResult"; private const float TimeoutSeconds = 3f; private const int MaxNotOwnerRetries = 3; private const float RetryDelaySeconds = 0.3f; private const int TempWidth = 16; private const int TempHeight = 64; private static readonly Dictionary<int, Pending> PendingOps = new Dictionary<int, Pending>(); private static int _nextId = 1; internal static bool Busy => PendingOps.Count > 0; [HarmonyPatch(typeof(Container), "Awake")] [HarmonyPostfix] private static void ContainerAwake(Container __instance) { ZNetView nview = __instance.m_nview; if (!((Object)(object)nview == (Object)null) && nview.GetZDO() != null) { Container container = __instance; nview.Register<ZPackage>("VikingQoL_ChestOp", (Action<long, ZPackage>)delegate(long sender, ZPackage pkg) { OnRequest(container, sender, pkg); }); nview.Register<ZPackage>("VikingQoL_ChestOpResult", (Action<long, ZPackage>)delegate(long sender, ZPackage pkg) { OnResult(container, pkg); }); } } internal static void Take(Container chest, IList<TakeItem> items, Action<OpStatus, Inventory> done, bool respectLocks = true) { //IL_002b: 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) ZPackage val = Begin((!respectLocks) ? OpType.Take : OpType.TakeUnlocked); val.Write(items.Count); foreach (TakeItem item in items) { val.Write(item.Pos.x); val.Write(item.Pos.y); val.Write(item.Name); val.Write(item.Quality); val.Write(item.Amount); } Send(chest, val, null, done); } internal static void Put(Container chest, Inventory items, Vector2i? target, bool allowSwap, Action<OpStatus, Inventory> done) { //IL_0023: 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) ZPackage val = Begin(OpType.Put); val.Write(target.HasValue); val.Write(target?.x ?? (-1)); val.Write(target?.y ?? (-1)); val.Write(allowSwap); val.Write(Pack(items)); Send(chest, val, items, done); } internal static void Move(Container chest, ItemData item, int amount, Vector2i to, Action<OpStatus, Inventory> done) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) ZPackage val = Begin(OpType.Move); val.Write(item.m_gridPos.x); val.Write(item.m_gridPos.y); val.Write(to.x); val.Write(to.y); val.Write(amount); val.Write(item.m_shared.m_name); val.Write(item.m_quality); Send(chest, val, null, done); } internal static void StackInto(Container chest, Inventory items, Action<OpStatus, Inventory> done) { ZPackage val = Begin(OpType.StackInto); val.Write(Pack(items)); Send(chest, val, items, done); } internal static void Sort(Container chest, SortMode mode, Action<OpStatus, Inventory> done) { ZPackage val = Begin(OpType.Sort); val.Write((int)mode); Send(chest, val, null, done); } private static ZPackage Begin(OpType type) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write((byte)type); return val; } private static void Send(Container chest, ZPackage body, Inventory sentItems, Action<OpStatus, Inventory> done) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown byte[] array = body.GetArray(); if (chest.IsOwner()) { OpStatus status; Inventory items = Execute(chest, new ZPackage(array), out status); Finish(done, status, items); return; } Pending pending = new Pending { Id = _nextId++, Container = chest, Request = array, SentItems = sentItems, Done = done }; PendingOps[pending.Id] = pending; Transmit(pending); } private static void Transmit(Pending pending) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown pending.SentAt = Time.unscaledTime; pending.RetryAt = -1f; ZPackage val = new ZPackage(); val.Write(pending.Id); val.Write(pending.Request); pending.Container.m_nview.InvokeRPC("VikingQoL_ChestOp", new object[1] { val }); Log.Debug($"Chest op {pending.Id} sent to owner {pending.Container.m_nview.GetZDO().GetOwner()}."); } private static void OnResult(Container chest, ZPackage pkg) { int key = pkg.ReadInt(); OpStatus opStatus = (OpStatus)pkg.ReadByte(); byte[] data = pkg.ReadByteArray(); byte[] array = pkg.ReadByteArray(); if (!PendingOps.TryGetValue(key, out var value)) { return; } if (opStatus == OpStatus.NotOwner && value.Retries < 3) { value.Retries++; value.RetryAt = Time.unscaledTime + 0.3f; return; } PendingOps.Remove(key); if (array.Length != 0 && !chest.IsOwner()) { ApplySnapshot(chest, array); } Inventory items = ((opStatus == OpStatus.NotOwner) ? value.SentItems : Unpack(data)); Finish(value.Done, opStat