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 UsefulTankards v1.0.2
UsefulTankards.dll
Decompiled a month ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using ServerSync; using TMPro; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("UsefulTankards")] [assembly: AssemblyCompany("sighsorry")] [assembly: AssemblyProduct("UsefulTankards")] [assembly: ComVisible(false)] [assembly: AssemblyFileVersion("1.0.2")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.2.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace UsefulTankards { [BepInPlugin("sighsorry.UsefulTankards", "UsefulTankards", "1.0.2")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class UsefulTankardsPlugin : BaseUnityPlugin { internal enum Toggle { On = 1, Off = 0 } private sealed class ConfigurationManagerAttributes { public int? Order; } public const string ModName = "UsefulTankards"; public const string ModVersion = "1.0.2"; public const string Author = "sighsorry"; public const string ModGuid = "sighsorry.UsefulTankards"; private const string ValheimCuisineGuid = "XutzBR.ValheimCuisine"; internal static ManualLogSource Log = null; private static readonly ConfigSync ConfigSync = new ConfigSync("sighsorry.UsefulTankards") { DisplayName = "UsefulTankards", CurrentVersion = "1.0.2", MinimumRequiredVersion = "1.0.2" }; private static ConfigEntry<Toggle> ServerConfigLocked = null; private static ConfigEntry<float> MovementWhileDrinking = null; private static ConfigEntry<float> TankardAnimationSpeed = null; private readonly Harmony _harmony = new Harmony("sighsorry.UsefulTankards"); private static bool _roundingMovementWhileDrinking; private static bool _roundingTankardAnimationSpeed; internal static float MovementWhileDrinkingMultiplier => Math.Min(1f, Math.Max(0f, MovementWhileDrinking.Value)); internal static float TankardAnimationSpeedMultiplier => Math.Min(3f, Math.Max(1f, TankardAnimationSpeed.Value)); private void Awake() { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; ValheimAccess.Validate(); ServerConfigLocked = ConfigEntry("1 - General", "Lock Configuration", Toggle.On, "If on, the configuration is locked and can be changed by server admins only.", synchronizedSetting: true, 1000); ConfigSync.AddLockingConfigEntry<Toggle>(ServerConfigLocked); MovementWhileDrinking = ConfigEntry("1 - General", "Movement While Drinking", 0.5f, new ConfigDescription("Movement and rotation speed multiplier while drinking through a tankard. 0 keeps vanilla movement lock; 1 allows normal movement.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()), synchronizedSetting: true, 900); TankardAnimationSpeed = ConfigEntry("1 - General", "Tankard Animation Speed", 2f, new ConfigDescription("Drinking animation speed multiplier for tankards. 1 keeps vanilla speed; 2 is twice as fast; 3 is three times as fast.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 3f), Array.Empty<object>()), synchronizedSetting: true, 850); NormalizeFloatConfig(MovementWhileDrinking, 0f, 1f, ref _roundingMovementWhileDrinking); NormalizeFloatConfig(TankardAnimationSpeed, 1f, 3f, ref _roundingTankardAnimationSpeed); MovementWhileDrinking.SettingChanged += OnMovementWhileDrinkingChanged; TankardAnimationSpeed.SettingChanged += OnTankardAnimationSpeedChanged; TankardLocalization.Register(); TankardTweaks.RegisterProfile(this, "02 - Tankard", "Tankard", 10, 0.1f, 0.1f, 3); TankardRecipes.RegisterRecipe(this, "02 - Tankard", "Tankard", "piece_workbench, 1", "FineWood:5, Resin:2"); TankardTweaks.RegisterProfile(this, "03 - Anniversary Tankard", "TankardAnniversary", 15, 0.2f, 0.2f, 4); TankardRecipes.RegisterRecipe(this, "03 - Anniversary Tankard", "TankardAnniversary", "piece_workbench, 1", "Bronze:2, TrollHide:2, Iron:2"); TankardTweaks.RegisterProfile(this, "04 - Dvergr Tankard", "Tankard_dvergr", 20, 0.3f, 0.3f, 5); if (Chainloader.PluginInfos.ContainsKey("XutzBR.ValheimCuisine")) { TankardTweaks.RegisterProfile(this, "05 - Goblet of Kings", "VC_GoK", 20, 0.3f, 0.3f, 5); } _harmony.PatchAll(); } private static void OnMovementWhileDrinkingChanged(object sender, EventArgs args) { NormalizeFloatConfig(MovementWhileDrinking, 0f, 1f, ref _roundingMovementWhileDrinking); } private static void OnTankardAnimationSpeedChanged(object sender, EventArgs args) { NormalizeFloatConfig(TankardAnimationSpeed, 1f, 3f, ref _roundingTankardAnimationSpeed); } private static void NormalizeFloatConfig(ConfigEntry<float> configEntry, float minimum, float maximum, ref bool normalizing) { if (normalizing) { return; } float num = (float)Math.Round(Math.Min(maximum, Math.Max(minimum, configEntry.Value)), 2, MidpointRounding.AwayFromZero); if (Math.Abs(configEntry.Value - num) <= 0.0001f) { return; } try { normalizing = true; configEntry.Value = num; } finally { normalizing = false; } } private void OnDestroy() { try { TankardStorageSystem.CloseOpenTankardStorage(); } finally { try { UsefulTankardsTankardAnimationSpeed.RestoreAll(); } finally { _harmony.UnpatchSelf(); } } } internal ConfigEntry<T> ConfigEntry<T>(string group, string name, T value, ConfigDescription description, bool synchronizedSetting = true, int? order = null) { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Expected O, but got Unknown object[] array = description.Tags ?? Array.Empty<object>(); if (order.HasValue) { object[] array2 = new object[array.Length + 1]; Array.Copy(array, array2, array.Length); array2[array.Length] = new ConfigurationManagerAttributes { Order = order.Value }; array = array2; } ConfigDescription val = new ConfigDescription(description.Description + (synchronizedSetting ? " [Synced with Server]" : " [Not Synced with Server]"), description.AcceptableValues, array); ConfigEntry<T> val2 = ((BaseUnityPlugin)this).Config.Bind<T>(group, name, value, val); ConfigSync.AddConfigEntry<T>(val2).SynchronizedConfig = synchronizedSetting; return val2; } internal ConfigEntry<T> ConfigEntry<T>(string group, string name, T value, string description, bool synchronizedSetting = true, int? order = null) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown return ConfigEntry(group, name, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>()), synchronizedSetting, order); } } internal static class TankardLocalization { internal const string OpenHintKey = "$ut_tankard_open_hint"; internal const string CooldownReductionKey = "$ut_tankard_cooldown_reduction"; internal const string BuffDurationBonusKey = "$ut_tankard_buff_duration_bonus"; internal const string StoredMeadsKey = "$ut_tankard_stored_meads"; private const string EnglishLanguage = "english"; private const string KoreanLanguage = "korean"; private static readonly MethodInfo? AddWordMethod = AccessTools.Method(typeof(Localization), "AddWord", new Type[2] { typeof(string), typeof(string) }, (Type[])null); private static bool _addWordUnavailable; private static bool _addWordFailureLogged; private static readonly (string Key, string English, string Korean)[] Translations = new(string, string, string)[4] { ("$ut_tankard_open_hint", "Press <b>$KEY_Use</b> to open tankard storage.", "<b>$KEY_Use</b>를 눌러 탱커드 저장소를 엽니다."), ("$ut_tankard_cooldown_reduction", "Potion cooldown reduction: {0}%", "포션 재사용 대기시간 감소: {0}%"), ("$ut_tankard_buff_duration_bonus", "Buff duration bonus: {0}%", "버프 지속시간 증가: {0}%"), ("$ut_tankard_stored_meads", "Stored meads:", "저장된 벌꿀주:") }; internal static void Register() { if (Localization.instance != null) { Register(Localization.instance); } } internal static void Register(Localization? localization) { if (localization == null) { return; } bool flag = NormalizeLanguageName(localization.GetSelectedLanguage()) == "korean"; (string, string, string)[] translations = Translations; for (int i = 0; i < translations.Length; i++) { var (text, text2, text3) = translations[i]; if (!AddWord(localization, text.Substring(1), flag ? text3 : text2)) { break; } } } internal static string Localize(string key) { if (Localization.instance == null) { return key; } string text = Localization.instance.Localize(key); if (!text.Contains("$")) { return text; } return Localization.instance.Localize(text); } private static string NormalizeLanguageName(string? languageName) { if (!string.IsNullOrWhiteSpace(languageName)) { return languageName.Trim().Replace(" ", string.Empty).ToLowerInvariant(); } return "english"; } private static bool AddWord(Localization localization, string key, string value) { if (_addWordUnavailable) { return false; } if (AddWordMethod == null) { DisableAddWordRegistration("Localization.AddWord(string, string) was not found."); return false; } try { AddWordMethod.Invoke(localization, new object[2] { key, value }); return true; } catch (Exception ex) { Exception baseException = ex.GetBaseException(); LogAddWordFailureOnce("Localization.AddWord failed: " + baseException.GetType().Name + ": " + baseException.Message); return false; } } private static void DisableAddWordRegistration(string reason) { _addWordUnavailable = true; LogAddWordFailureOnce(reason); } private static void LogAddWordFailureOnce(string reason) { if (!_addWordFailureLogged) { _addWordFailureLogged = true; UsefulTankardsPlugin.Log.LogError((object)("Could not register UsefulTankards localization. " + reason)); } } } [HarmonyPatch(typeof(Localization), "SetupLanguage")] internal static class UsefulTankardsLocalizationSetupLanguagePatch { private static void Postfix(Localization __instance) { TankardLocalization.Register(__instance); } } internal static class TankardRecipes { private sealed class TankardRecipeProfile { internal string PrefabName { get; } internal ConfigEntry<UsefulTankardsPlugin.Toggle> Enabled { get; } internal ConfigEntry<string> Station { get; } internal ConfigEntry<string> Resources { get; } internal TankardRecipeProfile(string prefabName, ConfigEntry<UsefulTankardsPlugin.Toggle> enabled, ConfigEntry<string> station, ConfigEntry<string> resources) { PrefabName = prefabName; Enabled = enabled; Station = station; Resources = resources; } } private static readonly Dictionary<string, TankardRecipeProfile> Profiles = new Dictionary<string, TankardRecipeProfile>(StringComparer.OrdinalIgnoreCase); private static readonly HashSet<string> WarnedMessages = new HashSet<string>(StringComparer.OrdinalIgnoreCase); internal static void RegisterRecipe(UsefulTankardsPlugin plugin, string section, string prefabName, string defaultStation, string defaultResources) { TankardRecipeProfile tankardRecipeProfile = new TankardRecipeProfile(prefabName, plugin.ConfigEntry(section, "Recipe Enabled", UsefulTankardsPlugin.Toggle.On, "If on, UsefulTankards updates this tankard recipe. If off, the recipe is disabled.", synchronizedSetting: true, 950), plugin.ConfigEntry(section, "Recipe Station", defaultStation, "Crafting station and minimum level for this tankard recipe. Format: StationPrefab, Level. Use None, 1 for hand crafting.", synchronizedSetting: true, 940), plugin.ConfigEntry(section, "Recipe Resources", defaultResources, "Resources required to craft this tankard. Format: Item:Amount, OtherItem:Amount. Amounts may be zero; leave the value empty for no resource requirements.", synchronizedSetting: true, 930)); tankardRecipeProfile.Enabled.SettingChanged += delegate { ApplyRecipeDefinitions(); }; tankardRecipeProfile.Station.SettingChanged += delegate { ApplyRecipeDefinitions(); }; tankardRecipeProfile.Resources.SettingChanged += delegate { ApplyRecipeDefinitions(); }; Profiles[prefabName] = tankardRecipeProfile; } internal static void ApplyRecipeDefinitions() { if ((Object)(object)ObjectDB.instance == (Object)null || (Object)(object)ZNetScene.instance == (Object)null) { return; } foreach (TankardRecipeProfile value in Profiles.Values) { ApplyRecipeDefinition(value); } RefreshLocalCraftingUi(); } private static void ApplyRecipeDefinition(TankardRecipeProfile profile) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Expected O, but got Unknown //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Expected O, but got Unknown if (profile.Enabled.Value != UsefulTankardsPlugin.Toggle.On) { Recipe val = FindRecipe(profile.PrefabName); if (val == null || (Object)val == (Object)null) { ItemDrop val2 = ResolveItemDrop(profile.PrefabName); if (val2 == null || (Object)val2 == (Object)null) { return; } val = CreateRecipe(profile.PrefabName, val2); } val.m_enabled = false; return; } ItemDrop val3 = ResolveItemDrop(profile.PrefabName); string station; int level; CraftingStation station2; Requirement[] requirements; if (val3 == null || (Object)val3 == (Object)null) { DisableExistingRecipe(profile.PrefabName); WarnOnce("Could not apply recipe for '" + profile.PrefabName + "': item prefab was not found."); } else if (!TryParseCraftingStationWithLevel(profile.Station.Value, out station, out level) || !TryResolveCraftingStation(station, out station2)) { DisableExistingRecipe(profile.PrefabName); WarnOnce("Could not apply recipe for '" + profile.PrefabName + "': crafting station '" + profile.Station.Value + "' was invalid or not found."); } else if (!TryParseRequirements(profile.Resources.Value, out requirements)) { DisableExistingRecipe(profile.PrefabName); WarnOnce("Could not apply recipe for '" + profile.PrefabName + "': resources '" + profile.Resources.Value + "' were invalid."); } else { Recipe? obj = FindRecipe(profile.PrefabName) ?? CreateRecipe(profile.PrefabName, val3); obj.m_item = val3; obj.m_amount = 1; obj.m_enabled = true; obj.m_craftingStation = station2; obj.m_minStationLevel = level; obj.m_requireOnlyOneIngredient = false; obj.m_qualityResultAmountMultiplier = 1f; obj.m_resources = requirements; } } private static Recipe? FindRecipe(string prefabName) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown string recipeName = GetRecipeName(prefabName); foreach (Recipe recipe in ObjectDB.instance.m_recipes) { if (!((Object)recipe == (Object)null) && string.Equals(((Object)recipe).name, recipeName, StringComparison.OrdinalIgnoreCase)) { return recipe; } } return null; } private static void DisableExistingRecipe(string prefabName) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown Recipe val = FindRecipe(prefabName); if (val != null && (Object)val != (Object)null) { val.m_enabled = false; } } private static Recipe CreateRecipe(string prefabName, ItemDrop item) { Recipe val = ScriptableObject.CreateInstance<Recipe>(); ((Object)val).name = GetRecipeName(prefabName); val.m_item = item; ObjectDB.instance.m_recipes.Add(val); return val; } private static bool TryParseRequirements(string? definition, out Requirement[] requirements) { //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Expected O, but got Unknown //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Expected O, but got Unknown List<Requirement> list = new List<Requirement>(); requirements = Array.Empty<Requirement>(); string[] array = (definition ?? "").Split(new char[4] { ',', ';', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0) { string[] array2 = text.Split(new char[1] { ':' }); if (array2.Length != 2) { return false; } string text2 = NormalizePrefabName(array2[0]); if (!int.TryParse(array2[1].Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result < 0) { return false; } ItemDrop val = ResolveItemDrop(text2); if (val == null || (Object)val == (Object)null) { WarnOnce("Could not resolve recipe resource item '" + text2 + "'."); return false; } list.Add(new Requirement { m_resItem = val, m_amount = result, m_amountPerLevel = 1, m_recover = true }); } } requirements = list.ToArray(); return true; } private static bool TryParseCraftingStationWithLevel(string? value, out string station, out int level) { station = "None"; level = 1; string[] array = (value ?? "").Split(new char[1] { ',' }); if (array.Length > 2) { return false; } station = NormalizePrefabName(array[0]); if (station.Length == 0) { station = "None"; } if (array.Length == 2) { if (int.TryParse(array[1].Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out level)) { return level >= 1; } return false; } return true; } private static bool TryResolveCraftingStation(string value, out CraftingStation? station) { station = null; string text = NormalizePrefabName(value); if (IsNone(text)) { return true; } GameObject val = ResolvePrefab(text); CraftingStation val2 = default(CraftingStation); if ((Object)(object)val == (Object)null || !val.TryGetComponent<CraftingStation>(ref val2)) { return false; } station = val2; return true; } private static ItemDrop? ResolveItemDrop(string itemName) { string text = NormalizePrefabName(itemName); ObjectDB instance = ObjectDB.instance; GameObject val = ((instance != null) ? instance.GetItemPrefab(text) : null); if ((Object)(object)val == (Object)null) { val = ResolvePrefab(text); } ItemDrop result = default(ItemDrop); if (!((Object)(object)val != (Object)null) || !val.TryGetComponent<ItemDrop>(ref result)) { return null; } return result; } private static GameObject? ResolvePrefab(string prefabName) { string text = NormalizePrefabName(prefabName); if ((Object)(object)ZNetScene.instance != (Object)null) { GameObject prefab = ZNetScene.instance.GetPrefab(text); if ((Object)(object)prefab != (Object)null) { return prefab; } } ObjectDB instance = ObjectDB.instance; if (instance == null) { return null; } return instance.GetItemPrefab(text); } private static void RefreshLocalCraftingUi() { if (!((Object)(object)Player.m_localPlayer == (Object)null)) { Player.m_localPlayer.UpdateKnownRecipesList(); if ((Object)(object)InventoryGui.instance != (Object)null) { InventoryGui.instance.UpdateCraftingPanel(false); InventoryGui.instance.UpdateRecipe(Player.m_localPlayer, 0f); } } } private static void WarnOnce(string message) { if (WarnedMessages.Add(message)) { UsefulTankardsPlugin.Log.LogWarning((object)message); } } private static string GetRecipeName(string itemName) { return "Recipe_" + NormalizePrefabName(itemName); } private static bool IsNone(string value) { if (value.Length != 0 && !string.Equals(value, "None", StringComparison.OrdinalIgnoreCase)) { return string.Equals(value, "null", StringComparison.OrdinalIgnoreCase); } return true; } private static string NormalizePrefabName(string? value) { value = (value ?? "").Trim(); if (!value.EndsWith("(Clone)", StringComparison.Ordinal)) { return value; } return value.Substring(0, value.Length - "(Clone)".Length).Trim(); } } internal sealed class TankardProfile { internal ConfigEntry<int> Durability { get; } internal ConfigEntry<UsefulTankardsPlugin.Toggle> CanBeRepaired { get; } internal ConfigEntry<float> CooldownReduction { get; } internal ConfigEntry<float> DurationBonus { get; } internal ConfigEntry<int> StorageSlots { get; } internal int DurabilityUses => Math.Max(0, Durability.Value); internal bool Repairable => CanBeRepaired.Value == UsefulTankardsPlugin.Toggle.On; internal int TankardStorageSlots => Math.Max(0, StorageSlots.Value); internal float CooldownReductionMultiplier => Math.Max(0f, 1f - Clamp(CooldownReduction.Value, 0f, 0.95f)); internal float DurationBonusMultiplier => 1f + Math.Max(0f, DurationBonus.Value); internal float CooldownReductionPercent => Clamp(CooldownReduction.Value, 0f, 0.95f) * 100f; internal float DurationBonusPercent => Math.Max(0f, DurationBonus.Value) * 100f; internal TankardProfile(ConfigEntry<int> durability, ConfigEntry<UsefulTankardsPlugin.Toggle> canBeRepaired, ConfigEntry<float> cooldownReduction, ConfigEntry<float> durationBonus, ConfigEntry<int> storageSlots) { Durability = durability; CanBeRepaired = canBeRepaired; CooldownReduction = cooldownReduction; DurationBonus = durationBonus; StorageSlots = storageSlots; } private static float Clamp(float value, float min, float max) { if (value < min) { return min; } if (!(value > max)) { return value; } return max; } } internal static class TankardTweaks { private readonly struct DurabilityBaseline { private readonly bool _useDurability; private readonly bool _destroyBroken; private readonly bool _canBeRepaired; private readonly float _maxDurability; private readonly float _durabilityPerLevel; private readonly float _useDurabilityDrain; private readonly float _durabilityDrain; internal DurabilityBaseline(SharedData shared) { _useDurability = shared.m_useDurability; _destroyBroken = shared.m_destroyBroken; _canBeRepaired = shared.m_canBeReparied; _maxDurability = shared.m_maxDurability; _durabilityPerLevel = shared.m_durabilityPerLevel; _useDurabilityDrain = shared.m_useDurabilityDrain; _durabilityDrain = shared.m_durabilityDrain; } internal void Restore(SharedData shared) { shared.m_useDurability = _useDurability; shared.m_destroyBroken = _destroyBroken; shared.m_canBeReparied = _canBeRepaired; shared.m_maxDurability = _maxDurability; shared.m_durabilityPerLevel = _durabilityPerLevel; shared.m_useDurabilityDrain = _useDurabilityDrain; shared.m_durabilityDrain = _durabilityDrain; } } private static readonly Dictionary<string, TankardProfile> Profiles = new Dictionary<string, TankardProfile>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, DurabilityBaseline> DurabilityBaselines = new Dictionary<string, DurabilityBaseline>(StringComparer.OrdinalIgnoreCase); [ThreadStatic] private static TankardProfile? _currentUseContext; internal static TankardProfile? CurrentUseContext { get { return _currentUseContext; } set { _currentUseContext = value; } } internal static void RegisterProfile(UsefulTankardsPlugin plugin, string section, string prefabName, int durability, float cooldownReduction, float durationBonus, int storageSlots) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Expected O, but got Unknown //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Expected O, but got Unknown //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Expected O, but got Unknown TankardProfile tankardProfile = new TankardProfile(plugin.ConfigEntry(section, "Durability Uses", durability, new ConfigDescription("Tankard uses before it can no longer be used. 0 disables durability changes for this tankard.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), Array.Empty<object>()), synchronizedSetting: true, 400), plugin.ConfigEntry(section, "Can Be Repaired", UsefulTankardsPlugin.Toggle.Off, "If on, this tankard can be repaired at a valid repair station. If off, durability is limited-use only.", synchronizedSetting: true, 350), plugin.ConfigEntry(section, "Potion Cooldown Reduction", cooldownReduction, new ConfigDescription("Multiplier-style reduction for pure over-time potions drunk through this tankard. 0.10 means -10%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.95f), Array.Empty<object>()), synchronizedSetting: true, 300), plugin.ConfigEntry(section, "Buff Duration Bonus", durationBonus, new ConfigDescription("Duration bonus for timed effects that are not pure recovery-over-time effects. 0.10 means +10%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()), synchronizedSetting: true, 200), plugin.ConfigEntry(section, "Storage Slots", storageSlots, new ConfigDescription("Minimum number of mead storage slots in this tankard. Above five, the rectangular grid rounds capacity up to the next multiple of five. 0 disables storage for this tankard.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 20), Array.Empty<object>()), synchronizedSetting: true, 100)); tankardProfile.Durability.SettingChanged += delegate { ApplyItemDefinitions(); }; tankardProfile.CanBeRepaired.SettingChanged += delegate { ApplyItemDefinitions(); }; Profiles[prefabName] = tankardProfile; } internal static bool TryGetProfile(ItemData? item, out TankardProfile profile) { profile = null; if (item == null) { return false; } string prefabName = GetPrefabName(item); if (prefabName.Length > 0) { return Profiles.TryGetValue(prefabName, out profile); } return false; } internal static void ApplyItemDefinitions() { if ((Object)(object)ObjectDB.instance != (Object)null) { foreach (GameObject item in ObjectDB.instance.m_items) { ApplyItemDefinition(item); } } if ((Object)(object)ZNetScene.instance == (Object)null) { return; } foreach (string key in Profiles.Keys) { ApplyItemDefinition(ZNetScene.instance.GetPrefab(key)); } } private static void ApplyItemDefinition(GameObject? prefab) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown if ((Object)(object)prefab == (Object)null || (Object)prefab == (Object)null) { return; } string key = CleanPrefabName(((Object)prefab).name); if (!Profiles.TryGetValue(key, out TankardProfile value)) { return; } ItemDrop component = prefab.GetComponent<ItemDrop>(); if (!((Object)component == (Object)null)) { SharedData shared = component.m_itemData.m_shared; if (!DurabilityBaselines.ContainsKey(key)) { DurabilityBaselines[key] = new DurabilityBaseline(shared); } if (value.DurabilityUses <= 0) { DurabilityBaselines[key].Restore(shared); return; } shared.m_useDurability = true; shared.m_maxDurability = value.DurabilityUses; shared.m_durabilityPerLevel = 0f; shared.m_useDurabilityDrain = 1f; shared.m_durabilityDrain = 0f; shared.m_canBeReparied = value.Repairable; shared.m_destroyBroken = false; } } internal static void ModifyEffectForCurrentTankard(StatusEffect effect) { TankardProfile currentUseContext = CurrentUseContext; if (currentUseContext != null && !(effect.m_ttl <= 0f)) { float num = (IsPureOverTimeEffect(effect) ? currentUseContext.CooldownReductionMultiplier : currentUseContext.DurationBonusMultiplier); effect.m_ttl *= num; } } private static bool IsPureOverTimeEffect(StatusEffect effect) { //IL_0296: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Unknown result type (might be due to invalid IL or missing references) //IL_02e1: Unknown result type (might be due to invalid IL or missing references) //IL_02ec: Expected O, but got Unknown SE_Stats val = (SE_Stats)(object)((effect is SE_Stats) ? effect : null); if (val == null) { return false; } if (!(val.m_healthOverTime > 0f) && val.m_staminaOverTime == 0f && val.m_eitrOverTime == 0f) { return false; } if (val.m_tickInterval == 0f && val.m_healthPerTick == 0f && val.m_healthPerTickMinHealthPercentage == 0f && val.m_healthUpFront == 0f && val.m_staminaUpFront == 0f && val.m_staminaDrainPerSec == 0f && val.m_runStaminaDrainModifier == 0f && val.m_jumpStaminaUseModifier == 0f && val.m_attackStaminaUseModifier == 0f && val.m_blockStaminaUseModifier == 0f && val.m_blockStaminaUseFlatValue == 0f && val.m_dodgeStaminaUseModifier == 0f && val.m_swimStaminaUseModifier == 0f && val.m_homeItemStaminaUseModifier == 0f && val.m_sneakStaminaUseModifier == 0f && val.m_runStaminaUseModifier == 0f && val.m_adrenalineUpFront == 0f && val.m_adrenalineModifier == 0f && val.m_staggerModifier == 0f && val.m_timedBlockBonus == 0f && val.m_eitrUpFront == 0f && val.m_healthRegenMultiplier == 1f && val.m_staminaRegenMultiplier == 1f && val.m_eitrRegenMultiplier == 1f && val.m_addArmor == 0f && val.m_armorMultiplier == 0f && val.m_raiseSkillModifier == 0f && val.m_skillLevelModifier == 0f && val.m_skillLevelModifier2 == 0f && (val.m_mods == null || val.m_mods.Count == 0) && val.m_damageModifier == 1f && !((DamageTypes)(ref val.m_percentigeDamageModifiers)).HaveDamage() && val.m_noiseModifier == 0f && val.m_stealthModifier == 0f && val.m_addMaxCarryWeight == 0f && val.m_speedModifier == 0f && val.m_swimSpeedModifier == 0f && val.m_jumpModifier == Vector3.zero && val.m_maxMaxFallSpeed == 0f && val.m_fallDamageModifier == 0f && val.m_windMovementModifier == 0f && val.m_windRunStaminaModifier == 0f && (Object)val.m_pheromoneTarget == (Object)null && val.m_pheromoneSpawnChanceOverride == 0f && val.m_pheromoneSpawnMinLevel == 0 && val.m_pheromoneLevelUpMultiplier == 1f && val.m_pheromoneMaxInstanceOverride == 0) { return !val.m_pheromoneFlee; } return false; } internal static void AppendTankardTooltip(ItemData item, ref string tooltip) { if (!TryGetProfile(item, out TankardProfile profile)) { return; } List<string> list = new List<string>(); if (profile.TankardStorageSlots > 0) { list.Add(TankardLocalization.Localize("$ut_tankard_open_hint")); } if (profile.CooldownReductionPercent > 0f) { list.Add(FormatTooltip("$ut_tankard_cooldown_reduction", profile.CooldownReductionPercent)); } if (profile.DurationBonusPercent > 0f) { list.Add(FormatTooltip("$ut_tankard_buff_duration_bonus", profile.DurationBonusPercent)); } List<string> storedDrinkTooltipLines = TankardStorageSystem.GetStoredDrinkTooltipLines(item); if (storedDrinkTooltipLines.Count > 0) { list.Add(TankardLocalization.Localize("$ut_tankard_stored_meads")); list.AddRange(storedDrinkTooltipLines); } if (list.Count > 0) { tooltip = tooltip + "\n\n<color=orange>" + string.Join("\n", list.Where((string line) => !string.IsNullOrWhiteSpace(line))) + "</color>"; } } private static string FormatTooltip(string key, float percentage) { string format = TankardLocalization.Localize(key); string arg = percentage.ToString("0.#", CultureInfo.InvariantCulture); return string.Format(CultureInfo.InvariantCulture, format, arg); } private static string GetPrefabName(ItemData item) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown if (!((Object)item.m_dropPrefab != (Object)null)) { return ""; } return CleanPrefabName(((Object)item.m_dropPrefab).name); } internal static string GetCleanPrefabName(ItemData item) { return GetPrefabName(item); } private static string CleanPrefabName(string name) { if (!name.EndsWith("(Clone)", StringComparison.Ordinal)) { return name; } return name.Substring(0, name.Length - "(Clone)".Length); } } internal static class TankardStorageSystem { private sealed class StoredDrinkSummary { internal string Name { get; } internal int Stack { get; set; } internal int Capacity { get; set; } internal StoredDrinkSummary(string name, int stack, int capacity) { Name = name; Stack = stack; Capacity = capacity; } } internal sealed class TankardStorageContainer : MonoBehaviour { private Player? _player; private ItemData? _tankard; private Inventory? _inventory; private Container? _container; private Action? _saveHandler; private bool _loadComplete = true; private bool _closed; private bool _cleanupComplete; private bool _saveInProgress; internal bool LoadComplete => _loadComplete; internal void Initialize(Player player, ItemData tankard, TankardProfile profile, Container container) { _player = player; _tankard = tankard; _container = container; _inventory = LoadTankardStorageInventory(tankard, profile, out var width, out var height, out _loadComplete); ValheimAccess.SetContainerFields(container, tankard.m_shared.m_name, width, height, _inventory, inUse: true); if (_loadComplete) { RegisterTankardStorageInventory(_inventory, tankard); } AttachImmediateSave(); ValheimAccess.Changed(((Humanoid)player).GetInventory()); } private void Update() { //IL_007f: Unknown result type (might be due to invalid IL or missing references) if (!_closed) { InventoryGui instance = InventoryGui.instance; if ((Object)(object)_container == (Object)null || (Object)(object)_player == (Object)null || _tankard == null || (Object)(object)instance == (Object)null || (Object)(object)ValheimAccess.GetCurrentContainer(instance) != (Object)(object)_container || !((Humanoid)_player).GetInventory().ContainsItem(_tankard)) { CloseAndDestroy(); } else { ((Component)this).transform.position = ((Component)_player).transform.position; } } } internal void Save() { if (!_cleanupComplete) { SaveInventory(); } } private void SaveInventory() { if (_saveInProgress || _tankard == null || _inventory == null || !_loadComplete) { return; } try { _saveInProgress = true; SaveTankardStorageInventory(_tankard, _inventory); ValheimAccess.Changed(((Object)(object)_player != (Object)null) ? ((Humanoid)_player).GetInventory() : null); } catch (Exception ex) { UsefulTankardsPlugin.Log.LogWarning((object)("Could not save tankard storage: " + ex.GetBaseException().Message)); } finally { _saveInProgress = false; } } private void AttachImmediateSave() { if (_loadComplete && _inventory != null && _saveHandler == null) { _saveHandler = Save; ValheimAccess.AddInventoryChangedHandler(_inventory, _saveHandler); } } private void DetachImmediateSave() { if (_saveHandler != null) { ValheimAccess.RemoveInventoryChangedHandler(_inventory, _saveHandler); _saveHandler = null; } } internal void CloseAndDestroy() { if (_closed) { return; } _closed = true; try { Cleanup(); } finally { Object.Destroy((Object)(object)((Component)this).gameObject); } } private void OnDestroy() { _closed = true; Cleanup(); } private void Cleanup() { if (_cleanupComplete) { return; } _cleanupComplete = true; try { SaveInventory(); } finally { try { DetachImmediateSave(); } finally { if (_inventory != null) { UnregisterTankardStorageInventory(_inventory); } } } } } private const string StorageDataKey = "UsefulTankards.Storage.Data"; private const string StorageWeightKey = "UsefulTankards.Storage.Weight"; private static readonly HashSet<Inventory> StorageInventories = new HashSet<Inventory>(); private static readonly Dictionary<Inventory, ItemData> InventoryOwners = new Dictionary<Inventory, ItemData>(); private static readonly HashSet<string> WarnedIncompleteLoads = new HashSet<string>(StringComparer.Ordinal); private static Player? _cachedStoredDrinkPlayer; private static ItemData? _cachedStoredDrinkTankard; private static int _cachedStoredDrinkFrame = -1; private static bool _cachedStoredDrinkResult; internal static bool IsTankardStorageInventory(Inventory inventory) { if (inventory != null) { return StorageInventories.Contains(inventory); } return false; } internal static bool IsTankardStorageContainer(Container? container) { if ((Object)(object)container != (Object)null) { return (Object)(object)((Component)container).GetComponent<TankardStorageContainer>() != (Object)null; } return false; } internal static bool CanAddToTankardStorage(Inventory inventory, ItemData item) { if (!IsTankardStorageInventory(inventory)) { return true; } return IsAllowedStoredDrink(inventory, item); } internal static bool CanAddToTankardStorage(Inventory inventory, GameObject prefab) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown if (!IsTankardStorageInventory(inventory)) { return true; } if ((Object)prefab == (Object)null) { return false; } ItemDrop component = prefab.GetComponent<ItemDrop>(); if ((Object)(object)component != (Object)null) { return IsAllowedStoredDrink(inventory, component.m_itemData); } return false; } internal static bool TrySaveTankardStorageContainer(Container container) { TankardStorageContainer tankardStorageContainer = (((Object)(object)container != (Object)null) ? ((Component)container).GetComponent<TankardStorageContainer>() : null); if ((Object)(object)tankardStorageContainer == (Object)null) { return false; } tankardStorageContainer.Save(); return true; } internal static float GetStoredDrinkWeight(ItemData tankard) { if (tankard == null || !TankardTweaks.TryGetProfile(tankard, out TankardProfile _) || tankard.m_customData == null) { return 0f; } if (!tankard.m_customData.TryGetValue("UsefulTankards.Storage.Data", out var value) || string.IsNullOrWhiteSpace(value)) { tankard.m_customData.Remove("UsefulTankards.Storage.Weight"); return 0f; } if (TryGetStoredWeightCache(tankard, out var weight)) { return weight; } if (!TryReadStorageSummary(value, out var _, out var _, out var _, out var storedWeight, out var hasCompleteWeight) || !hasCompleteWeight) { return 0f; } SetStoredWeightCache(tankard, storedWeight); return storedWeight; } internal static List<string> GetStoredDrinkTooltipLines(ItemData tankard) { List<string> list = new List<string>(); if (!TryLoadStoredInventorySnapshot(tankard, out Inventory inventory)) { return list; } Dictionary<string, StoredDrinkSummary> dictionary = new Dictionary<string, StoredDrinkSummary>(StringComparer.Ordinal); foreach (ItemData allItem in inventory.GetAllItems()) { if (allItem != null) { string text = LocalizeItemName(allItem); int num = Math.Max(1, allItem.m_stack); int num2 = Math.Max(num, allItem.m_shared?.m_maxStackSize ?? num); if (!dictionary.TryGetValue(text, out var value)) { dictionary[text] = new StoredDrinkSummary(text, num, num2); continue; } value.Stack += num; value.Capacity += num2; } } foreach (StoredDrinkSummary value2 in dictionary.Values) { string text2 = ((value2.Capacity > 1) ? $" {value2.Stack}/{value2.Capacity}" : ""); list.Add("- " + value2.Name + text2); } return list; } internal static bool TryHandleInventoryGuiUseInput(InventoryGui inventoryGui) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)inventoryGui == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null) { return false; } Container currentContainer = ValheimAccess.GetCurrentContainer(inventoryGui); if (IsTankardStorageContainer(currentContainer)) { if (!ZInput.GetButtonDown("Use")) { return false; } ZInput.ResetButtonStatus("Use"); ValheimAccess.CloseContainer(inventoryGui); return true; } if ((Object)(object)currentContainer != (Object)null || !ZInput.GetButtonDown("Use")) { return false; } InventoryGrid playerGrid = ValheimAccess.GetPlayerGrid(inventoryGui); ItemData val = (((Object)(object)playerGrid != (Object)null) ? playerGrid.GetItem(new Vector2i(Mathf.RoundToInt(Input.mousePosition.x), Mathf.RoundToInt(Input.mousePosition.y))) : null); if (!TankardTweaks.TryGetProfile(val, out TankardProfile profile) || profile.TankardStorageSlots <= 0) { return false; } OpenTankardStorage(Player.m_localPlayer, val, profile); ZInput.ResetButtonStatus("Use"); return true; } internal static void CloseTankardStorage(Container? container) { if ((Object)(object)container != (Object)null) { ((Component)container).GetComponent<TankardStorageContainer>()?.CloseAndDestroy(); } } internal static void CloseOpenTankardStorage() { InventoryGui instance = InventoryGui.instance; if ((Object)(object)instance == (Object)null) { return; } Container currentContainer = ValheimAccess.GetCurrentContainer(instance); if (!IsTankardStorageContainer(currentContainer)) { return; } try { ValheimAccess.CloseContainer(instance); } finally { CloseTankardStorage(currentContainer); } } internal static bool TryConsumeStoredDrinks(Player player, ItemData tankard, TankardProfile profile, out ItemData consumedAmmo) { consumedAmmo = null; if ((Object)(object)player == (Object)null || tankard == null || profile.TankardStorageSlots <= 0) { return false; } if (!tankard.m_customData.TryGetValue("UsefulTankards.Storage.Data", out var value) || string.IsNullOrWhiteSpace(value)) { return false; } int width; int height; bool loadComplete; Inventory val = LoadTankardStorageInventory(tankard, profile, out width, out height, out loadComplete); if (!loadComplete) { return false; } bool flag = false; List<ItemData> allItems = val.GetAllItems(); for (int num = allItems.Count - 1; num >= 0; num--) { ItemData val2 = allItems[num]; if (CanConsumeStoredDrinkQuietly(player, tankard, val2)) { ItemData val3 = val2.Clone(); if (((Humanoid)player).ConsumeItem(val, val2, false)) { if (consumedAmmo == null) { consumedAmmo = val3; } flag = true; } } } if (!flag) { return false; } SaveTankardStorageInventory(tankard, val); ClearStoredDrinkCheckCache(); ValheimAccess.Changed(((Humanoid)player).GetInventory()); return true; } internal static bool HasConsumableStoredDrink(Player player, ItemData tankard, TankardProfile profile) { if ((Object)(object)player == (Object)null || tankard == null || profile.TankardStorageSlots <= 0) { return false; } if (_cachedStoredDrinkPlayer == player && _cachedStoredDrinkTankard == tankard && _cachedStoredDrinkFrame == Time.frameCount) { return _cachedStoredDrinkResult; } bool num = HasConsumableStoredDrinkUncached(player, tankard, profile); _cachedStoredDrinkPlayer = player; _cachedStoredDrinkTankard = tankard; _cachedStoredDrinkFrame = Time.frameCount; _cachedStoredDrinkResult = num; return num; } private static bool HasConsumableStoredDrinkUncached(Player player, ItemData tankard, TankardProfile profile) { if (!tankard.m_customData.TryGetValue("UsefulTankards.Storage.Data", out var value) || string.IsNullOrWhiteSpace(value)) { return false; } int width; int height; bool loadComplete; Inventory val = LoadTankardStorageInventory(tankard, profile, out width, out height, out loadComplete); if (!loadComplete) { return false; } foreach (ItemData allItem in val.GetAllItems()) { if (CanConsumeStoredDrinkQuietly(player, tankard, allItem)) { return true; } } return false; } private static void OpenTankardStorage(Player player, ItemData tankard, TankardProfile profile) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0016: 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) GameObject val = new GameObject("UsefulTankards_TankardStorage"); val.transform.position = ((Component)player).transform.position; TankardStorageContainer tankardStorageContainer = val.AddComponent<TankardStorageContainer>(); Container val2 = val.AddComponent<Container>(); tankardStorageContainer.Initialize(player, tankard, profile, val2); if (!tankardStorageContainer.LoadComplete) { tankardStorageContainer.CloseAndDestroy(); } else { InventoryGui.instance.Show(val2, 1); } } private static Inventory LoadTankardStorageInventory(ItemData tankard, TankardProfile profile, out int width, out int height, out bool loadComplete) { ResolveGridSize(ResolveStorageSlots(profile), out width, out height); Inventory val = CreateTankardStorageInventory(tankard, width, height); loadComplete = true; if (tankard.m_customData.TryGetValue("UsefulTankards.Storage.Data", out var value) && !string.IsNullOrWhiteSpace(value)) { loadComplete = TryDeserializeTankardStorage(tankard, val, value); } return val; } private static bool TryLoadStoredInventorySnapshot(ItemData tankard, out Inventory inventory) { inventory = null; if (tankard == null || !TankardTweaks.TryGetProfile(tankard, out TankardProfile profile) || !tankard.m_customData.TryGetValue("UsefulTankards.Storage.Data", out var value) || string.IsNullOrWhiteSpace(value)) { return false; } ResolveGridSize(ResolveStorageSlots(profile), out var width, out var height); inventory = CreateTankardStorageInventory(tankard, width, height); if (!TryDeserializeTankardStorage(tankard, inventory, value)) { inventory = null; return false; } return inventory.GetAllItems().Count > 0; } private static bool TryDeserializeTankardStorage(ItemData tankard, Inventory inventory, string rawData) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown if (!TryReadStorageSummary(rawData, out var version, out var expectedEntries, out var expectedStack, out var _, out var _)) { WarnUnverifiableLoad(tankard, version); return false; } try { inventory.Load(new ZPackage(rawData)); return IsStorageLoadComplete(tankard, inventory, expectedEntries, expectedStack); } catch (Exception ex) { UsefulTankardsPlugin.Log.LogWarning((object)("Could not read tankard storage for " + TankardTweaks.GetCleanPrefabName(tankard) + ": " + ex.GetBaseException().Message)); return false; } } private static void SaveTankardStorageInventory(ItemData tankard, Inventory inventory) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown if (tankard != null && inventory != null) { List<ItemData> allItems = inventory.GetAllItems(); if (allItems.Count == 0) { tankard.m_customData.Remove("UsefulTankards.Storage.Data"); tankard.m_customData.Remove("UsefulTankards.Storage.Weight"); } else { ZPackage val = new ZPackage(); inventory.Save(val); tankard.m_customData["UsefulTankards.Storage.Data"] = val.GetBase64(); SetStoredWeightCache(tankard, CalculateStoredInventoryWeight(allItems)); } ClearStoredDrinkCheckCache(); } } private static Inventory CreateTankardStorageInventory(ItemData tankard, int width, int height) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown return new Inventory(tankard?.m_shared?.m_name ?? "$item_tankard", (tankard != null) ? tankard.GetIcon() : null, width, height); } private static bool IsStorageLoadComplete(ItemData tankard, Inventory inventory, int expectedEntries, int expectedStack) { if (expectedEntries <= 0) { return true; } List<ItemData> allItems = inventory.GetAllItems(); int num = 0; foreach (ItemData item in allItems) { num += Math.Max(0, item.m_stack); } if (allItems.Count >= expectedEntries && num >= expectedStack) { return true; } WarnIncompleteLoad(tankard, expectedEntries, allItems.Count, expectedStack, num); return false; } private static bool TryReadStorageSummary(string rawData, out int version, out int expectedEntries, out int expectedStack, out float storedWeight, out bool hasCompleteWeight) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown //IL_0061: Unknown result type (might be due to invalid IL or missing references) version = -1; expectedEntries = 0; expectedStack = 0; storedWeight = 0f; hasCompleteWeight = true; try { ZPackage val = new ZPackage(rawData); version = val.ReadInt(); int num = val.ReadInt(); if (version < 100 || version > 106 || num < 0) { return false; } for (int i = 0; i < num; i++) { string prefabName = val.ReadString(); int num2 = val.ReadInt(); val.ReadSingle(); val.ReadVector2i(); val.ReadBool(); int quality = 1; if (version >= 101) { quality = val.ReadInt(); } if (version >= 102) { val.ReadInt(); } if (version >= 103) { val.ReadLong(); val.ReadString(); } if (version >= 104) { int num3 = val.ReadInt(); if (num3 < 0) { return false; } for (int j = 0; j < num3; j++) { val.ReadString(); val.ReadString(); } } if (version >= 105) { val.ReadInt(); } if (version >= 106) { val.ReadBool(); } expectedEntries++; expectedStack += Math.Max(0, num2); if (TryCalculateSerializedItemWeight(prefabName, num2, quality, out var weight)) { storedWeight += weight; } else { hasCompleteWeight = false; } } if (!IsValidStoredWeight(storedWeight)) { storedWeight = 0f; hasCompleteWeight = false; } return true; } catch (Exception) { storedWeight = 0f; hasCompleteWeight = false; return false; } } private static float CalculateStoredInventoryWeight(IEnumerable<ItemData> items) { float num = 0f; foreach (ItemData item in items) { if (item != null) { float weight = item.GetWeight(-1); if (!IsValidStoredWeight(weight)) { return float.NaN; } num += weight; if (!IsValidStoredWeight(num)) { return float.NaN; } } } return num; } private static bool TryCalculateSerializedItemWeight(string prefabName, int stack, int quality, out float weight) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected O, but got Unknown weight = 0f; if (string.IsNullOrWhiteSpace(prefabName) || stack < 0 || (Object)(object)ObjectDB.instance == (Object)null) { return false; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(prefabName); ItemDrop val = default(ItemDrop); if ((Object)itemPrefab == (Object)null || !itemPrefab.TryGetComponent<ItemDrop>(ref val) || val.m_itemData?.m_shared == null) { return false; } SharedData shared = val.m_itemData.m_shared; weight = shared.m_weight * (float)stack; if (shared.m_scaleWeightByQuality != 0f && quality != 1) { weight += weight * (float)(quality - 1) * shared.m_scaleWeightByQuality; } return IsValidStoredWeight(weight); } private static bool TryGetStoredWeightCache(ItemData tankard, out float weight) { weight = 0f; if (tankard.m_customData.TryGetValue("UsefulTankards.Storage.Weight", out var value) && float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out weight)) { return IsValidStoredWeight(weight); } return false; } private static void SetStoredWeightCache(ItemData tankard, float weight) { if (!IsValidStoredWeight(weight)) { tankard.m_customData.Remove("UsefulTankards.Storage.Weight"); } else { tankard.m_customData["UsefulTankards.Storage.Weight"] = weight.ToString("R", CultureInfo.InvariantCulture); } } private static bool IsValidStoredWeight(float weight) { if (weight >= 0f && !float.IsNaN(weight)) { return !float.IsInfinity(weight); } return false; } private static void WarnUnverifiableLoad(ItemData tankard, int version) { string cleanPrefabName = TankardTweaks.GetCleanPrefabName(tankard); string item = $"{cleanPrefabName}:unverifiable:{version}"; if (WarnedIncompleteLoads.Add(item)) { UsefulTankardsPlugin.Log.LogWarning((object)$"Tankard storage for {cleanPrefabName} uses an unsupported or unreadable inventory format ({version}). The stored data was preserved and the tankard storage was not opened."); } } private static void WarnIncompleteLoad(ItemData tankard, int expectedEntries, int actualEntries, int expectedStack, int actualStack) { string cleanPrefabName = TankardTweaks.GetCleanPrefabName(tankard); string item = $"{cleanPrefabName}:{expectedEntries}:{actualEntries}:{expectedStack}:{actualStack}"; if (WarnedIncompleteLoads.Add(item)) { UsefulTankardsPlugin.Log.LogWarning((object)$"Tankard storage for {cleanPrefabName} loaded only {actualEntries}/{expectedEntries} entries and {actualStack}/{expectedStack} stored item stack. The stored data was preserved and the tankard storage was not opened."); } } private static int ResolveStorageSlots(TankardProfile profile) { return Math.Max(0, profile.TankardStorageSlots); } private static void ResolveGridSize(int slots, out int width, out int height) { int num = Math.Max(1, slots); width = Mathf.Clamp(num, 1, 5); height = Mathf.CeilToInt((float)num / 5f); } private static void RegisterTankardStorageInventory(Inventory inventory, ItemData tankard) { if (inventory != null) { StorageInventories.Add(inventory); if (tankard != null) { InventoryOwners[inventory] = tankard; } } } private static void UnregisterTankardStorageInventory(Inventory inventory) { if (inventory != null) { StorageInventories.Remove(inventory); InventoryOwners.Remove(inventory); } } private static void ClearStoredDrinkCheckCache() { _cachedStoredDrinkPlayer = null; _cachedStoredDrinkTankard = null; _cachedStoredDrinkFrame = -1; _cachedStoredDrinkResult = false; } private static string LocalizeItemName(ItemData item) { string text = item.m_shared?.m_name ?? ""; if (Localization.instance == null || string.IsNullOrWhiteSpace(text)) { return text; } return Localization.instance.Localize(text); } private static bool IsAllowedStoredDrink(Inventory inventory, ItemData item) { InventoryOwners.TryGetValue(inventory, out ItemData value); return IsAllowedStoredDrink(value, item); } private static bool IsAllowedStoredDrink(ItemData? tankard, ItemData item) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Invalid comparison between Unknown and I4 //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown if (item == null || (int)item.m_shared.m_itemType != 2 || (Object)item.m_shared.m_consumeStatusEffect == (Object)null || TankardTweaks.TryGetProfile(item, out TankardProfile _)) { return false; } string text = tankard?.m_shared?.m_ammoType ?? ""; if (!string.IsNullOrWhiteSpace(text) && !string.Equals(item.m_shared.m_ammoType, text, StringComparison.OrdinalIgnoreCase)) { if ((Object)item.m_dropPrefab != (Object)null) { return string.Equals(((Object)item.m_dropPrefab).name, text, StringComparison.OrdinalIgnoreCase); } return false; } return true; } private static bool CanConsumeStoredDrinkQuietly(Player player, ItemData tankard, ItemData item) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown if ((Object)(object)player == (Object)null || !IsAllowedStoredDrink(tankard, item)) { return false; } if (item.m_shared.m_food > 0f && !player.CanEat(item, false)) { return false; } StatusEffect consumeStatusEffect = item.m_shared.m_consumeStatusEffect; if (!((Object)consumeStatusEffect == (Object)null)) { if (!((Character)player).GetSEMan().HaveStatusEffect(consumeStatusEffect.NameHash())) { return !((Character)player).GetSEMan().HaveStatusEffectCategory(consumeStatusEffect.m_category); } return false; } return true; } } internal static class ValheimAccess { private static readonly FieldInfo InventoryGuiCurrentContainerField = RequireField(typeof(InventoryGui), "m_currentContainer"); private static readonly FieldInfo InventoryGuiPlayerGridField = RequireField(typeof(InventoryGui), "m_playerGrid"); private static readonly FieldInfo ContainerNameField = RequireField(typeof(Container), "m_name"); private static readonly FieldInfo ContainerWidthField = RequireField(typeof(Container), "m_width"); private static readonly FieldInfo ContainerHeightField = RequireField(typeof(Container), "m_height"); private static readonly FieldInfo ContainerInventoryField = RequireField(typeof(Container), "m_inventory"); private static readonly FieldInfo ContainerInUseField = RequireField(typeof(Container), "m_inUse"); private static readonly FieldInfo InventoryOnChangedField = RequireField(typeof(Inventory), "m_onChanged"); private static readonly MethodInfo InventoryGuiCloseContainerMethod = RequireMethod(typeof(InventoryGui), "CloseContainer"); private static readonly MethodInfo InventoryChangedMethod = RequireMethod(typeof(Inventory), "Changed"); internal static void Validate() { _ = InventoryChangedMethod.Name; } internal static Container? GetCurrentContainer(InventoryGui? gui) { if (!((Object)(object)gui == (Object)null)) { object? value = InventoryGuiCurrentContainerField.GetValue(gui); return (Container?)((value is Container) ? value : null); } return null; } internal static InventoryGrid? GetPlayerGrid(InventoryGui? gui) { if (!((Object)(object)gui == (Object)null)) { object? value = InventoryGuiPlayerGridField.GetValue(gui); return (InventoryGrid?)((value is InventoryGrid) ? value : null); } return null; } internal static void SetContainerFields(Container container, string name, int width, int height, Inventory inventory, bool inUse) { ContainerNameField.SetValue(container, name); ContainerWidthField.SetValue(container, width); ContainerHeightField.SetValue(container, height); ContainerInventoryField.SetValue(container, inventory); SetContainerInUse(container, inUse); } internal static void SetContainerInUse(Container container, bool inUse) { ContainerInUseField.SetValue(container, inUse); } internal static void CloseContainer(InventoryGui gui) { InventoryGuiCloseContainerMethod.Invoke(gui, null); } internal static void Changed(Inventory? inventory) { if (inventory != null) { InventoryChangedMethod.Invoke(inventory, null); } } internal static void AddInventoryChangedHandler(Inventory? inventory, Action handler) { if (inventory != null) { Action a = InventoryOnChangedField.GetValue(inventory) as Action; InventoryOnChangedField.SetValue(inventory, (Action)Delegate.Combine(a, handler)); } } internal static void RemoveInventoryChangedHandler(Inventory? inventory, Action handler) { if (inventory != null) { Action source = InventoryOnChangedField.GetValue(inventory) as Action; InventoryOnChangedField.SetValue(inventory, (Action)Delegate.Remove(source, handler)); } } private static FieldInfo RequireField(Type type, string name) { return AccessTools.Field(type, name) ?? throw new MissingFieldException(type.FullName, name); } private static MethodInfo RequireMethod(Type type, string name) { return AccessTools.Method(type, name, (Type[])null, (Type[])null) ?? throw new MissingMethodException(type.FullName, name); } } [HarmonyPatch(typeof(ObjectDB), "Awake")] internal static class UsefulTankardsObjectDBAwakePatch { [HarmonyPriority(0)] private static void Postfix() { TankardTweaks.ApplyItemDefinitions(); TankardRecipes.ApplyRecipeDefinitions(); } } [HarmonyPatch(typeof(ZNetScene), "Awake")] internal static class UsefulTankardsZNetSceneAwakePatch { [HarmonyPriority(0)] private static void Postfix() { TankardTweaks.ApplyItemDefinitions(); TankardRecipes.ApplyRecipeDefinitions(); } } [HarmonyPatch(typeof(Humanoid), "GetAttackSpeedFactorMovement")] internal static class UsefulTankardsAttackMovementSpeedPatch { private static void Postfix(Humanoid __instance, ref float __result) { float movementWhileDrinkingMultiplier = UsefulTankardsPlugin.MovementWhileDrinkingMultiplier; if (movementWhileDrinkingMultiplier > 0f && ((Character)__instance).InAttack() && TankardTweaks.TryGetProfile(__instance.GetCurrentWeapon(), out TankardProfile _)) { __result = Mathf.Max(__result, movementWhileDrinkingMultiplier); } } } [HarmonyPatch(typeof(Humanoid), "GetAttackSpeedFactorRotation")] internal static class UsefulTankardsAttackRotationSpeedPatch { private static void Postfix(Humanoid __instance, ref float __result) { float movementWhileDrinkingMultiplier = UsefulTankardsPlugin.MovementWhileDrinkingMultiplier; if (movementWhileDrinkingMultiplier > 0f && ((Character)__instance).InAttack() && TankardTweaks.TryGetProfile(__instance.GetCurrentWeapon(), out TankardProfile _)) { __result = Mathf.Max(__result, movementWhileDrinkingMultiplier); } } } [HarmonyPatch(typeof(CharacterAnimEvent), "CustomFixedUpdate")] internal static class UsefulTankardsCharacterAnimEventAnimationSpeedPatch { [HarmonyPriority(0)] private static void Prefix(Animator ___m_animator, Character ___m_character) { UsefulTankardsTankardAnimationSpeed.Apply(___m_animator, ___m_character); } [HarmonyPriority(0)] private static void Postfix(Animator ___m_animator, Character ___m_character) { UsefulTankardsTankardAnimationSpeed.Apply(___m_animator, ___m_character); } } internal static class UsefulTankardsTankardAnimationSpeed { private sealed class AnimationSpeedState { private readonly ZSyncAnimation _zAnim; private readonly float _originalSpeed; internal Animator Animator { get; } internal AnimationSpeedState(Animator animator, ZSyncAnimation zAnim) { Animator = animator; _zAnim = zAnim; _originalSpeed = animator.speed; } internal void Apply(float speed) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown if (!((Object)Animator == (Object)null)) { if ((Object)_zAnim != (Object)null) { _zAnim.SetSpeed(speed); } Animator.speed = speed; } } internal void Restore() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown if (!((Object)Animator == (Object)null)) { if ((Object)_zAnim != (Object)null) { _zAnim.SetSpeed(_originalSpeed); } Animator.speed = _originalSpeed; } } } private static readonly Dictionary<Humanoid, AnimationSpeedState> ActiveStates = new Dictionary<Humanoid, AnimationSpeedState>(); internal static void Apply(Animator animator, Character character) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown if (!((Object)animator == (Object)null)) { Humanoid val = (Humanoid)(object)((character is Humanoid) ? character : null); if (val != null) { Apply(val, animator); } } } private static void Apply(Humanoid humanoid, Animator animator) { float tankardAnimationSpeedMultiplier = UsefulTankardsPlugin.TankardAnimationSpeedMultiplier; if (tankardAnimationSpeedMultiplier <= 1.0001f || !((Character)humanoid).InAttack() || !TankardTweaks.TryGetProfile(humanoid.GetCurrentWeapon(), out TankardProfile _)) { Restore(humanoid); return; } ZSyncAnimation zAnim = ((Character)humanoid).GetZAnim(); if (!ActiveStates.TryGetValue(humanoid, out AnimationSpeedState value)) { value = new AnimationSpeedState(animator, zAnim); ActiveStates[humanoid] = value; } else if (value.Animator != animator) { value.Restore(); value = new AnimationSpeedState(animator, zAnim); ActiveStates[humanoid] = value; } value.Apply(tankardAnimationSpeedMultiplier); } internal static void Restore(Humanoid humanoid) { if (ActiveStates.TryGetValue(humanoid, out AnimationSpeedState value)) { TryRestore(value); ActiveStates.Remove(humanoid); } } internal static void RestoreAll() { foreach (AnimationSpeedState value in ActiveStates.Values) { TryRestore(value); } ActiveStates.Clear(); } private static void TryRestore(AnimationSpeedState state) { try { state.Restore(); } catch (Exception ex) { UsefulTankardsPlugin.Log.LogWarning((object)("Could not restore tankard animation speed: " + ex.GetBaseException().Message)); } } } [HarmonyPatch(typeof(Humanoid), "OnDestroy")] internal static class UsefulTankardsHumanoidDestroyPatch { private static void Prefix(Humanoid __instance) { UsefulTankardsTankardAnimationSpeed.Restore(__instance); } } internal static class UsefulTankardsAttackAmmo { internal static bool HasStoredDrink(Humanoid character, ItemData weapon) { if (TankardTweaks.TryGetProfile(weapon, out TankardProfile profile)) { Player val = (Player)(object)((character is Player) ? character : null); if (val != null) { return TankardStorageSystem.HasConsumableStoredDrink(val, weapon, profile); } } return false; } } [HarmonyPatch(typeof(Attack), "UseAmmo")] internal static class UsefulTankardsUseAmmoPatch { private readonly struct UseContextState { internal TankardProfile? PreviousContext { get; } internal bool Captured { get; } internal UseContextState(TankardProfile? previousContext) { PreviousContext = previousContext; Captured = true; } } private static bool Prefix(Attack __instance, ref ItemData ammoItem, ref bool __result, ref UseContextState __state) { __state = new UseContextState(TankardTweaks.CurrentUseContext); if (TankardTweaks.TryGetProfile(__instance.GetWeapon(), out TankardProfile profile)) { TankardTweaks.CurrentUseContext = profile; if (TankardStorageSystem.TryConsumeStoredDrinks(Player.m_localPlayer, __instance.GetWeapon(), profile, out ItemData consumedAmmo)) { ammoItem = consumedAmmo; __result = true; return false; } } return true; } private static void Finalizer(UseContextState __state) { if (__state.Captured) { TankardTweaks.CurrentUseContext = __state.PreviousContext; } } } [HarmonyPatch(typeof(Attack), "EquipAmmoItem")] internal static class UsefulTankardsEquipAmmoItemPatch { private static bool Prefix(Humanoid character, ItemData weapon, ref bool __result) { if (UsefulTankardsAttackAmmo.HasStoredDrink(character, weapon)) { __result = true; return false; } return true; } } [HarmonyPatch(typeof(Attack), "HaveAmmo")] internal static class UsefulTankardsHaveAmmoPatch { private static bool Prefix(Humanoid character, ItemData weapon, ref bool __result) { if (!UsefulTankardsAttackAmmo.HasStoredDrink(character, weapon)) { return true; } __result = true; return false; } } [HarmonyPatch(typeof(StatusEffect), "Setup")] internal static class UsefulTankardsStatusEffectSetupPatch { private static void Prefix(StatusEffect __instance) { TankardTweaks.ModifyEffectForCurrentTankard(__instance); } } [HarmonyPatch(typeof(ItemData), "GetTooltip", new Type[] { typeof(ItemData), typeof(int), typeof(bool), typeof(float), typeof(int) })] internal static class UsefulTankardsItemTooltipPatch { private static void Postfix(ItemData item, ref string __result) { TankardTweaks.AppendTankardTooltip(item, ref __result); } } [HarmonyPatch(typeof(ItemData), "GetWeight", new Type[] { typeof(int) })] internal static class UsefulTankardsItemWeightPatch { private static void Postfix(ItemData __instance, int stackOverride, ref float __result) { if (stackOverride != 0) { __result += TankardStorageSystem.GetStoredDrinkWeight(__instance); } } } [HarmonyPatch(typeof(Container), "Awake")] internal static class UsefulTankardsContainerAwakePatch { private static bool Prefix(Container __instance) { return !TankardStorageSystem.IsTankardStorageContainer(__instance); } } [HarmonyPatch(typeof(Container), "IsOwner")] internal static class UsefulTankardsContainerIsOwnerPatch { private static bool Prefix(Container __instance, ref bool __result) { if (!TankardStorageSystem.IsTankardStorageContainer(__instance)) { return true; } __result = true; return false; } } [HarmonyPatch(typeof(Container), "SetInUse")] internal static class UsefulTankardsContainerSetInUsePatch { private static bool Prefix(Container __instance, bool inUse) { if (!TankardStorageSystem.IsTankardStorageContainer(__instance)) { return true; } ValheimAccess.SetContainerInUse(__instance, inUse); return false; } } [HarmonyPatch(typeof(Container), "Save")] internal static class UsefulTankardsContainerSavePatch { private static bool Prefix(Container __instance) { return !TankardStorageSystem.TrySaveTankardStorageContainer(__instance); } } [HarmonyPatch(typeof(Container), "Load")] internal static class UsefulTankardsContainerLoadPatch { private static bool Prefix(Container __instance, ref bool __result) { if (!TankardStorageSystem.IsTankardStorageContainer(__instance)) { return true; } __result = true; return false; } } [HarmonyPatch(typeof(InventoryGui), "Update")] internal static class UsefulTankardsInventoryGuiUpdatePatch { private static void Prefix(InventoryGui __instance) { TankardStorageSystem.TryHandleInventoryGuiUseInput(__instance); } } [HarmonyPatch(typeof(InventoryGui), "CloseContainer")] internal static class UsefulTankardsInventoryGuiCloseContainerPatch { private static void Prefix(InventoryGui __instance, out Container __state) { __state = ValheimAccess.GetCurrentContainer(__instance); } private static void Postfix(Container __state) { TankardStorageSystem.CloseTankardStorage(__state); } } [HarmonyPatch(typeof(InventoryGui), "Hide")] internal static class UsefulTankardsInventoryGuiHidePatch { private static void Prefix(InventoryGui __instance, out Container __state) { __state = ValheimAccess.GetCurrentContainer(__instance); } private static void Postfix(Container __state) { TankardStorageSystem.CloseTankardStorage(__state); } } [HarmonyPatch(typeof(Inventory), "CanAddItem", new Type[] { typeof(ItemData), typeof(int) })] internal static class UsefulTankardsInventoryCanAddItemPatch { private static bool Prefix(Inventory __instance, ItemData item, ref bool __result) { if (TankardStorageSystem.CanAddToTankardStorage(__instance, item)) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(Inventory), "CanAddItem", new Type[] { typeof(GameObject), typeof(int) })] internal static class UsefulTankardsInventoryCanAddPrefabPatch { private static bool Prefix(Inventory __instance, GameObject prefab, ref bool __result) { if (TankardStorageSystem.CanAddToTankardStorage(__instance, prefab)) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(ItemData) })] internal static class UsefulTankardsInventoryAddItemPatch { private static bool Prefix(Inventory __instance, ItemData item, ref bool __result) { if (TankardStorageSystem.CanAddToTankardStorage(__instance, item)) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(GameObject), typeof(int) })] internal static class UsefulTankardsInventoryAddPrefabPatch { private static bool Prefix(Inventory __instance, GameObject prefab, ref bool __result) { if (TankardStorageSystem.CanAddToTankardStorage(__instance, prefab)) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(ItemData), typeof(Vector2i) })] internal static class UsefulTankardsInventoryAddItemAtPatch { private static bool Prefix(Inventory __instance, ItemData item, ref bool __result) { if (TankardStorageSystem.CanAddToTankardStorage(__instance, item)) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(ItemData), typeof(int), typeof(int), typeof(int) })] internal static class UsefulTankardsInventoryAddItemAmountPatch { private static bool Prefix(Inventory __instance, ItemData item, ref bool __result) { if (TankardStorageSystem.CanAddToTankardStorage(__instance, item)) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(Inventory), "MoveItemToThis", new Type[] { typeof(Inventory), typeof(ItemData) })] internal static class UsefulTankardsInventoryMoveItemPatch { private static bool Prefix(Inventory __instance, ItemData item) { return TankardStorageSystem.CanAddToTankardStorage(__instance, item); } } [HarmonyPatch(typeof(Inventory), "MoveItemToThis", new Type[] { typeof(Inventory), typeof(ItemData), typeof(int), typeof(int), typeof(int) })] internal static class UsefulTankardsInventoryMoveItemAmountPatch { private static bool Prefix(Inventory __instance, ItemData item, ref bool __result) { if (TankardStorageSystem.CanAddToTankardStorage(__instance, item)) { return true; } __result = false; return false; } } } 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 ServerSync { [PublicAPI] internal abstract class OwnConfigEntryBase { public object? LocalBaseValue; public bool SynchronizedConfig = true; public abstract ConfigEntryBase BaseConfig { get; } } [PublicAPI] internal class SyncedConfigEntry<T>(ConfigEntry<T> sourceConfig) : OwnConfigEntryBase() { public readonly ConfigEntry<T> SourceConfig = sourceConfig; public override ConfigEntryBase BaseConfig => (ConfigEntryBase)(object)SourceConfig; public T Value { get { return SourceConfig.Value; } set { SourceConfig.Value = value; } } public void AssignLocalValue(T value) { if (LocalBaseValue == null) { Value = value; } else { LocalBaseValue = value; } } } internal abstract class CustomSyncedValueBase { public object? LocalBaseValue; public readonly string Identifier; public readonly Type Type; private object? boxedValue; protected bool localIsOwner; public readonly int Priority; public object? BoxedValue { get { return boxedValue; } set { boxedValue = value; this.ValueChanged?.Invoke(); } } public event Action? ValueChanged; protected CustomSyncedValueBase(ConfigSync configSync, string identifier, Type type, int priority) { Priority = priority; Identifier = identifier; Type = type; configSync.AddCustomValue(this); localIsOwner = configSync.IsSourceOfTruth; configSync.SourceOfTruthChanged += delegate(bool truth) { localIsOwner = truth; }; } } [PublicAPI] internal sealed class CustomSyncedValue<T> : CustomSyncedValueBase { public T Value { get { return (T)base.BoxedValue; } set { base.BoxedValue = value; } } public CustomSyncedValue(ConfigSync configSync, string identifier, T value = default(T), int priority = 0) : base(configSync, identifier, typeof(T), priority) { Value = value; } public void AssignLocalValue(T value) { if (localIsOwner) { Value = value; } else { LocalBaseValue = value; } } } internal class ConfigurationManagerAttributes { [UsedImplicitly] public bool? ReadOnly = false; } [PublicAPI] internal class ConfigSync { [HarmonyPatch(typeof(ZRpc), "HandlePackage")] private static class SnatchCurrentlyHandlingRPC { public static ZRpc? currentRpc; [HarmonyPrefix] private static void Prefix(ZRpc __instance) { currentRpc = __instance; } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class RegisterRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance) { isServer = __instance.IsServer(); foreach (ConfigSync configSync2 in configSyncs) { ZRoutedRpc.instance.Register<ZPackage>(configSync2.Name + " ConfigSync", (Action<long, ZPackage>)configSync2.RPC_FromOtherClientConfigSync); if (isServer) { configSync2.InitialSyncDone = true; Debug.Log((object)("Registered '" + configSync2.Name + " ConfigSync' RPC - waiting for incoming connections")); } } if (isServer) { ((MonoBehaviour)__instance).StartCoroutine(WatchAdminListChanges()); } static void SendAdmin(List<ZNetPeer> peers, bool isAdmin) { ZPackage package = ConfigsToPackage(null, null, new PackageEntry[1] { new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = isAdmin } }); ConfigSync configSync = configSyncs.First(); if (configSync != null) { ((MonoBehaviour)ZNet.instance).StartCoroutine(configSync.sendZPackage(peers, package)); } } static IEnumerator WatchAdminListChanges() { MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); List<string> CurrentList = new List<string>(adminList.GetList()); while (true) { yield return (object)new WaitForSeconds(30f); if (!adminList.GetList().SequenceEqual(CurrentList)) { CurrentList = new List<string>(adminList.GetList()); List<ZNetPeer> adminPeer = ZNet.instance.GetPeers().Where(delegate(ZNetPeer p) { string hostName = p.m_rpc.GetSocket().GetHostName(); return ((object)listContainsId == null) ? adminList.Contains(hostName) : ((bool)listContainsId.Invoke(ZNet.instance, new object[2] { adminList, hostName })); }).ToList(); List<ZNetPeer> nonAdminPeer = ZNet.instance.GetPeers().Except(adminPeer).ToList(); SendAdmin(nonAdminPeer, isAdmin: false); SendAdmin(adminPeer, isAdmin: true); } } } } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class RegisterClientRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance, ZNetPeer peer) { if (__instance.IsServer()) { return; } foreach (ConfigSync configSync in configSyncs) { peer.m_rpc.Register<ZPackage>(configSync.Name + " ConfigSync", (Action<ZRpc, ZPackage>)configSync.RPC_FromServerConfigSync); } } } private class ParsedConfigs { public readonly Dictionary<OwnConfigEntryBase, object?> configValues = new Dictionary<OwnConfigEntryBase, object>(); public readonly Dictionary<CustomSyncedValueBase, object?> customValues = new Dictionary<CustomSyncedValueBase, object>(); } [HarmonyPatch(typeof(ZNet), "Shutdown")] private class ResetConfigsOnShutdown { [HarmonyPostfix] private static void Postfix() { ProcessingServerUpdate = true; foreach (ConfigSync configSync in configSyncs) { configSync.resetConfigsFromServer(); configSync.IsSourceOfTruth = true; configSync.InitialSyncDone = false; } ProcessingServerUpdate = false; } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private class SendConfigsAfterLogin { private class BufferingSocket : ZPlayFabSocket, ISocket { public volatile bool finished = false; public volatile int versionMatchQueued = -1; public readonly List<ZPackage> Package = new List<ZPackage>(); public readonly ISocket Original; public BufferingSocket(ISocket original) { Original = original; ((ZPlayFabSocket)this)..ctor(); } public bool IsConnected() { return Original.IsConnected(); } public ZPackage Recv() { return Original.Recv(); } public int GetSendQueueSize() { return Original.GetSendQueueSize(); } public int GetCurrentSendRate() { return Original.GetCurrentSendRate(); } public bool IsHost() { return Original.IsHost(); } public void Dispose() { Original.Dispose(); } public bool GotNewData() { return Original.GotNewData(); } public void Close() { Original.Close(); } public string GetEndPointString() { return Original.GetEndPointString(); } public void GetAndResetStats(out int totalSent, out int totalRecv) { Original.GetAndResetStats(ref totalSent, ref totalRecv); } public void GetConnectionQuality(out float localQuality, out float remoteQuality, out int ping, out float outByteSec, out float inByteSec) { Original.GetConnectionQuality(ref localQuality, ref remoteQuality, ref ping, ref outByteSec, ref inByteSec); } public ISocket Accept() { return Original.Accept(); } public int GetHostPort() { return Original.GetHostPort(); } public bool Flush() { return Original.Flush(); } public string GetHostName() { return Original.GetHostName(); } public void VersionMatch() { if (finished) { Original.VersionMatch(); } else { versionMatchQueued = Package.Count; } } public void Send(ZPackage pkg) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown int pos = pkg.GetPos(); pkg.SetPos(0); int num = pkg.ReadInt(); if ((num == StringExtensionMethods.GetStableHashCode("PeerInfo") || num == StringExtensionMethods.GetStableHashCode("RoutedRPC") || num == StringExtensionMethods.GetStableHashCode("ZDOData")) && !finished) { ZPackage val = new ZPackage(pkg.GetArray()); val.SetPos(pos); Package.Add(val); } else { pkg.SetPos(pos); Original.Send(pkg); } } } [HarmonyPriority(800)] [HarmonyPrefix] private static void Prefix(ref Dictionary<Assembly, BufferingSocket>? __state, ZNet __instance, ZRpc rpc) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Invalid comparison between Unknown and I4 if (!__instance.IsServer()) { return; } BufferingSocket bufferingSocket = new BufferingSocket(rpc.GetSocket()); AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket); object? obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (val != null && (int)ZNet.m_onlineBackend > 0) { FieldInfo fieldInfo = AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket"); object? value = fieldInfo.GetValue(val); ZPlayFabSocket val2 = (ZPlayFabSocket)((value is ZPlayFabSocket) ? value : null); if (val2 != null) { typeof(ZPlayFabSocket).GetField("m_remotePlayerId").SetValue(bufferingSocket, val2.m_remotePlayerId); } fieldInfo.SetValue(val, bufferingSocket); } if (__state == null) { __state = new Dictionary<Assembly, BufferingSocket>(); } __state[Assembly.GetExecutingAssembly()] = bufferingSocket; } [HarmonyPostfix] private static void Postfix(Dictionary<Assembly, BufferingSocket> __state, ZNet __instance, ZRpc rpc) { ZNetPeer peer; if (__instance.IsServer()) { object obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); peer = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (peer == null) { SendBufferedData(); } else { ((MonoBehaviour)__instance).StartCoroutine(sendAsync()); } } void SendBufferedData() { if (rpc.GetSocket() is BufferingSocket bufferingSocket) { AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket.Original); object? obj2 = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj2 is ZNetPeer) ? obj2 : null); if (val != null) { AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket").SetValue(val, bufferingSocket.Original); } } BufferingSocket bufferingSocket2 = __state[Assembly.GetExecutingAssembly()]; bufferingSocket2.finished = true; for (int i = 0; i < bufferingSocket2.Package.Count; i++) { if (i == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } bufferingSocket2.Original.Send(bufferingSocket2.Package[i]); } if (bufferingSocket2.Package.Count == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } } IEnumerator sendAsync() { foreach (ConfigSync configSync in configSyncs) { List<PackageEntry> entries = new List<PackageEntry>(); if (configSync.CurrentVersion != null) { entries.Add(new PackageEntry { section = "Internal", key = "serverversion", type = typeof(string), value = configSync.CurrentVersion }); } MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); entries.Add(new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = (((object)listContainsId == null) ? ((object)adminList.Contains(rpc.GetSocket().GetHostName())) : listContainsId.Invoke(ZNet.instance, new object[2] { adminList, rpc.GetSocket().GetHostName() })) }); ZPackage package = ConfigsToPackage(configSync.allConfigs.Select((OwnConfigEntryBase c) => c.BaseConfig), configSync.allCustomValues, entries, partial: false); yield return ((MonoBehaviour)__instance).StartCoroutine(configSync.sendZPackage(new List<ZNetPeer> { peer }, package)); } SendBufferedData(); } } } private class PackageEntry { public string section = null; public string key = null; public Type type = null; public object? value; } [HarmonyPatch(typeof(ConfigEntryBase), "GetSerializedValue")] private static class PreventSavingServerInfo { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, ref string __result) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || isWritableConfig(ownConfigEntryBase)) { return true; } __result = TomlTypeConverter.ConvertToString(ownConfigEntryBase.LocalBaseValue, __instance.SettingType); return false; } } [HarmonyPatch(typeof(ConfigEntryBase), "SetSerializedValue")] private static class PreventConfigRereadChangingValues { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, string value) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || ownConfigEntryBase.LocalBaseValue == null) { return true; } try { ownConfigEntryBase.LocalBaseValue = TomlTypeConverter.ConvertToValue(value, __instance.SettingType); } catch (Exception ex) { Debug.LogWarning((object)$"Config value of setting \"{__instance.Definition}\" could not be parsed and will be ignored. Reason: {ex.Message}; Value: {value}"); } return false; } } private class InvalidDeserializationTypeException : Exception { public string expected = null; public string received = null; public string field = ""; } public static bool ProcessingServerUpdate; public readonly string Name; public string? DisplayName; public string? CurrentVersion; public string? MinimumRequiredVersion; public bool ModRequired = false; private bool? forceConfigLocking; private bool isSourceOfTruth = true; private static readonly HashSet<ConfigSync> configSyncs; private readonly HashSet<OwnConfigEntryBase> allConfigs = new HashSet<OwnConfigEntryBase>(); private HashSet<CustomSyncedValueBase> allCustomValues = new HashSet<CustomSyncedValueBase>(); private static bool isServer; private static bool lockExempt; private OwnConfigEntryBase? lockedConfig = null; private const byte PARTIAL_CONFIGS = 1; private const byte FRAGMENTED_CONFIG = 2; private const byte COMPRESSED_CONFIG = 4; private readonly Dictionary<string, SortedDictionary<int, byte[]>> configValueCache = new Dictionary<string, SortedDictionary<int, byte[]>>(); private readonly List<KeyValuePair<long, string>> cacheExpirations = new List<KeyValuePair<long, string>>(); private static long packageCounter; public bool IsLocked { get { bool? flag = forceConfigLocking; bool num; if (!flag.HasValue) { if (lockedConfig == null) { goto IL_0052; } num = ((IConvertible)lockedConfig.BaseConfig.BoxedValue).ToInt32(CultureInfo.InvariantCulture) != 0; } else { num = flag == true; } if (!num) { goto IL_0052; } int result = ((!lockExempt) ? 1 : 0); goto IL_0053; IL_0052: result = 0; goto IL_0053; IL_0053: return (byte)result != 0; } set { forceConfigLocking = value; } } public bool IsAdmin => lockExempt || isSourceOfTruth; public bool IsSourceOfTruth { get { return isSourceOfTruth; } private set { if (value != isSourceOfTruth) { isSourceOfTruth = value; this.SourceOfTruthChanged?.Invoke(value); } } } public bool InitialSyncDone { get; private set; } = false; public event Action<bool>? SourceOfTruthChanged; private event Action? lockedConfigChanged; static ConfigSync() { ProcessingServerUpdate = false; configSyncs = new HashSet<ConfigSync>(); lockExempt = false; packageCounter = 0L; RuntimeHelpers.RunClassConstructor(typeof(VersionCheck).TypeHandle); } public ConfigSync(string name) { Name = name; configSyncs.Add(this); new VersionCheck(this); } public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigEntry<T> configEntry) { OwnConfigEntryBase ownConfigEntryBase = configData((ConfigEntryBase)(object)configEntry); SyncedConfigEntry<T> syncedEntry = ownConfigEntryBase as SyncedConfigEntry<T>; if (syncedEntry == null) { syncedEntry = new SyncedConfigEntry<T>(configEntry); AccessTools.DeclaredField(typeof(ConfigDescription), "<Tags>k__BackingField").SetValue(((ConfigEntryBase)configEntry).Description, new object[1] { new ConfigurationManagerAttributes() }.Concat(((ConfigEntryBase)configEntry).Description.Tags ?? Array.Empty<object>()).Concat(new SyncedConfigEntry<T>[1] { syncedEntry }).ToArray()); configEntry.SettingChanged += delegate { if (!ProcessingServerUpdate && syncedEntry.SynchronizedConfig) { Broadcast(ZRoutedRpc.Everybody, (ConfigEntryBase)configEntry); } }; allConfigs.Add(syncedEntry); } return syncedEntry; } public SyncedConfigEntry<T> AddLockingConfigEntry<T>(ConfigEntry<T> lockingConfig) where T : IConvertible { if (lockedConfig != null) { throw new Exception("Cannot initialize locking ConfigEntry twice"); } lockedConfig = AddConfigEntry<T>(lockingConfig); lockingConfig.SettingChanged += delegate { this.lockedConfigChanged?.Invoke(); }; return (SyncedConfigEntry<T>)lockedConfig; } internal void AddCustomValue(CustomSyncedValueBase customValue) { if (allCustomValues.Select((CustomSyncedValueBase v) => v.Identifier).Concat(new string[1] { "serverversion" }).Contains(customValue.Identifier)) { throw new Exception("Cannot have multiple settings with the same name or with a reserved name (serverversion)"); } allCustomValues.Add(customValue); allCustomValues = new HashSet<CustomSyncedValueBase>(allCustomValues.OrderByDescending((CustomSyncedValueBase v) => v.Priority)); customValue.ValueChanged += delegate { if (!ProcessingServerUpdate) { Broadcast(ZRoutedRpc.Everybody, customValue); } }; } private void RPC_FromServerConfigSync(ZRpc rpc, ZPackage package) { lockedConfigChanged += serverLockedSettingChanged; IsSourceOfTruth = false; if (HandleConfigSyncRPC(0L, package, clientUpdate: false)) { InitialSyncDone = true; } } private void RPC_FromOtherClientConfigSync(long sender, ZPackage package) { HandleConfigSyncRPC(sender, package, clientUpdate: true); } private bool HandleConfigSyncRPC(long sender, ZPackage package, bool clientUpdate) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Expected O, but got Unknown //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Expected O, but got Unknown try { if (isServer && IsLocked) { ZRpc? currentRpc = SnatchCurrentlyHandlingRPC.currentRpc; object obj; if (currentRpc == null) { obj = null; } else { ISocket socket = currentRpc.GetSocket(); obj = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj; if (text != null) { MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList val = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); if (!(((object)methodInfo == null) ? val.Contains(text) : ((bool)methodInfo.Invoke(ZNet.instance, new object[2] { val, text })))) { return false; } } } cacheExpirations.RemoveAll(delegate(KeyValuePair<long, string> kv) { if (kv.Key < DateTimeOffset.Now.Ticks) { configValueCache.Remove(kv.Value); return true; } return false; }); byte b = package.ReadByte(); if ((b & 2) != 0) { long num = package.ReadLong(); string text2 = sender.ToString() + num; if (!configValueCache.TryGetValue(text2, out SortedDictionary<int, byte[]> value)) { value = new SortedDictionary<int, byte[]>(); configValueCache[text2] = value; cacheExpirations.Add(new KeyValuePair<long, string>(DateTimeOffset.Now.AddSeconds(60.0).Ticks, text2)); } int key = package.ReadInt(); int num2 = package.ReadInt(); value.Add(key, package.ReadByteArray()); if (value.Count < num2) { return false; } configValueCache.Remove(text2); package = new ZPackage(value.Values.SelectMany((byte[] a) => a).ToArray()); b = package.ReadByte(); } ProcessingServerUpdate = true; if ((b & 4) != 0) { byte[] buffer = package.ReadByteArray(); MemoryStream stream = new MemoryStream(buffer); MemoryStream memoryStream = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress)) { deflateStream.CopyTo(memoryStream); } package = new ZPackage(memoryStream.ToArray()); b = package.ReadByte(); } if ((b & 1) == 0)