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.8
UsefulTankards.dll
Decompiled 2 weeks 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.8")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.8.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.8")] [BepInDependency(/*Could not decode attribute arguments.*/)] [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.8"; 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.8", MinimumRequiredVersion = "1.0.8" }; 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_0079: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Expected O, but got Unknown //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Expected O, but got Unknown UnsubscribeConfigurationEvents(); Log = ((BaseUnityPlugin)this).Logger; ValheimAccess.Validate(); EpicLootStorageCompatibility.Initialize(); 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 UnsubscribeConfigurationEvents() { if (MovementWhileDrinking != null) { MovementWhileDrinking.SettingChanged -= OnMovementWhileDrinkingChanged; } if (TankardAnimationSpeed != null) { TankardAnimationSpeed.SettingChanged -= OnTankardAnimationSpeedChanged; } TankardTweaks.UnsubscribeConfigurationEvents(); TankardRecipes.UnsubscribeConfigurationEvents(); } 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 { try { _harmony.UnpatchSelf(); } finally { EpicLootStorageCompatibility.Reset(); UnsubscribeConfigurationEvents(); } } } } 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"; internal const string StorageLoadFailedKey = "$ut_tankard_storage_load_failed"; 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)[5] { ("$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:", "저장된 벌꿀주:"), ("$ut_tankard_storage_load_failed", "Tankard storage could not be restored safely. Saved data was preserved. Check the log.", "맥주잔 저장소를 안전하게 복원하지 못했습니다. 저장 데이터는 보존했습니다. 로그를 확인해 주세요.") }; 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)); if (Profiles.TryGetValue(prefabName, out TankardRecipeProfile value)) { UnsubscribeConfigurationEvents(value); } Profiles[prefabName] = tankardRecipeProfile; tankardRecipeProfile.Enabled.SettingChanged += OnRecipeConfigurationChanged; tankardRecipeProfile.Station.SettingChanged += OnRecipeConfigurationChanged; tankardRecipeProfile.Resources.SettingChanged += OnRecipeConfigurationChanged; } internal static void UnsubscribeConfigurationEvents() { foreach (TankardRecipeProfile value in Profiles.Values) { UnsubscribeConfigurationEvents(value); } } private static void UnsubscribeConfigurationEvents(TankardRecipeProfile profile) { profile.Enabled.SettingChanged -= OnRecipeConfigurationChanged; profile.Station.SettingChanged -= OnRecipeConfigurationChanged; profile.Resources.SettingChanged -= OnRecipeConfigurationChanged; } private static void OnRecipeConfigurationChanged(object sender, EventArgs args) { ApplyRecipeDefinitions(); } 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)) { ValheimAccess.RefreshCrafting(Player.m_localPlayer, InventoryGui.instance); } } 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)); if (Profiles.TryGetValue(prefabName, out TankardProfile value)) { UnsubscribeConfigurationEvents(value); } Profiles[prefabName] = tankardProfile; tankardProfile.Durability.SettingChanged += OnItemConfigurationChanged; tankardProfile.CanBeRepaired.SettingChanged += OnItemConfigurationChanged; } internal static void UnsubscribeConfigurationEvents() { foreach (TankardProfile value in Profiles.Values) { UnsubscribeConfigurationEvents(value); } } private static void UnsubscribeConfigurationEvents(TankardProfile profile) { profile.Durability.SettingChanged -= OnItemConfigurationChanged; profile.CanBeRepaired.SettingChanged -= OnItemConfigurationChanged; } private static void OnItemConfigurationChanged(object sender, EventArgs args) { ApplyItemDefinitions(); } 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=#FF8000>" + 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 StoredItemSnapshot { internal int PrefabHash; internal int Stack; internal int Quality; internal int Variant; internal int Durability; internal int WorldLevel; internal long CrafterId; internal string CrafterName = ""; internal bool PickedUp; internal bool Equipped; internal bool Cheated; internal Dictionary<string, string> CustomData = new Dictionary<string, string>(StringComparer.Ordinal); } 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(); } } internal void StackAll() { InventoryGui instance = InventoryGui.instance; if (!_closed && _loadComplete && !((Object)(object)_player == (Object)null) && !((Object)(object)_player != (Object)(object)Player.m_localPlayer) && ((Character)_player).IsOwner() && _tankard != null && ((Humanoid)_player).GetInventory().ContainsItem(_tankard) && !((Object)(object)_container == (Object)null) && !((Object)(object)instance == (Object)null) && !((Object)(object)ValheimAccess.GetCurrentContainer(instance) != (Object)(object)_container)) { ValheimAccess.StackAll(instance); } } 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 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 InventoryOwners.ContainsKey(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 bool TryStackAllTankardStorageContainer(Container container) { TankardStorageContainer tankardStorageContainer = (((Object)(object)container != (Object)null) ? ((Component)container).GetComponent<TankardStorageContainer>() : null); if ((Object)(object)tankardStorageContainer == (Object)null) { return false; } tankardStorageContainer.StackAll(); 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 (!TryGetStoredInventoryForTooltip(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 || !InventoryGui.IsVisible() || (Object)(object)Player.m_localPlayer == (Object)null || !ZInput.GetButtonDown("Use") || EpicLootStorageCompatibility.ShouldYieldUse) { return false; } Container currentContainer = ValheimAccess.GetCurrentContainer(inventoryGui); if (IsTankardStorageContainer(currentContainer)) { ZInput.ResetButtonStatus("Use"); ValheimAccess.CloseContainer(inventoryGui); return true; } if ((Object)(object)currentContainer != (Object)null) { 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; } Player localPlayer = Player.m_localPlayer; if (!((Character)localPlayer).IsOwner() || !((Humanoid)localPlayer).GetInventory().ContainsItem(val)) { return false; } if (EpicLootStorageCompatibility.IsSocketGridOpen) { ValheimAccess.CloseContainer(inventoryGui); if (EpicLootStorageCompatibility.IsSocketGridOpen || (Object)(object)Player.m_localPlayer != (Object)(object)localPlayer || !((Character)localPlayer).IsOwner() || !((Humanoid)localPlayer).GetInventory().ContainsItem(val)) { return false; } } OpenTankardStorage(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 || !((Character)player).IsOwner() || !((Humanoid)player).GetInventory().ContainsItem(tankard)) { return false; } if (!TryGetOpenTankardInventory(tankard, out Inventory inventory)) { if (!tankard.m_customData.TryGetValue("UsefulTankards.Storage.Data", out var value) || string.IsNullOrWhiteSpace(value)) { return false; } inventory = LoadTankardStorageInventory(tankard, profile, out var _, out var _, out var loadComplete); if (!loadComplete) { return false; } } bool flag = false; List<ItemData> list = new List<ItemData>(inventory.GetAllItems()); int num = list.Count - 1; while (num >= 0 && ((Character)player).IsOwner() && ((Humanoid)player).GetInventory().ContainsItem(tankard)) { ItemData val = list[num]; if (inventory.ContainsItem(val) && CanConsumeStoredDrinkQuietly(player, tankard, val)) { ItemData val2 = val.Clone(); if (((Humanoid)player).ConsumeItem(inventory, val, false)) { if (consumedAmmo == null) { consumedAmmo = val2; } flag = true; } } num--; } if (!flag) { return false; } SaveTankardStorageInventory(tankard, inventory); 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 || !((Humanoid)player).GetInventory().ContainsItem(tankard)) { 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 (!TryGetOpenTankardInventory(tankard, out Inventory inventory)) { if (!tankard.m_customData.TryGetValue("UsefulTankards.Storage.Data", out var value) || string.IsNullOrWhiteSpace(value)) { return false; } inventory = LoadTankardStorageInventory(tankard, profile, out var _, out var _, out var loadComplete); if (!loadComplete) { return false; } } foreach (ItemData allItem in inventory.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(); ((Character)player).Message((MessageType)2, TankardLocalization.Localize("$ut_tankard_storage_load_failed"), 0, (Sprite)null, false); } 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 TryGetStoredInventoryForTooltip(ItemData tankard, out Inventory inventory) { inventory = null; if (tankard == null || !TankardTweaks.TryGetProfile(tankard, out TankardProfile profile)) { return false; } if (TryGetOpenTankardInventory(tankard, out inventory)) { return inventory.GetAllItems().Count > 0; } if (!tankard.m_customData.TryGetValue("UsefulTankards.Storage.Data", out var value) || string.IsNullOrWhiteSpace(value)) { return false; } inventory = LoadTankardStorageInventory(tankard, profile, out var _, out var _, out var loadComplete); if (!loadComplete) { inventory = null; return false; } return inventory.GetAllItems().Count > 0; } private static bool TryGetOpenTankardInventory(ItemData tankard, out Inventory inventory) { foreach (KeyValuePair<Inventory, ItemData> inventoryOwner in InventoryOwners) { if (inventoryOwner.Value == tankard) { inventory = inventoryOwner.Key; return true; } } inventory = null; return false; } private static bool TryDeserializeTankardStorage(ItemData tankard, Inventory inventory, string rawData) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown Dictionary<int, StoredItemSnapshot> expectedItems = new Dictionary<int, StoredItemSnapshot>(); if (!TryReadStorageSummary(rawData, out var version, out var expectedEntries, out var expectedStack, out var _, out var _, calculateWeight: false, expectedItems)) { WarnUnverifiableLoad(tankard, version); return false; } try { inventory.Load(new ZPackage(rawData)); return IsStorageLoadComplete(tankard, inventory, expectedEntries, expectedStack, expectedItems); } 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, Dictionary<int, StoredItemSnapshot> expectedItems) { 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) { WarnIncompleteLoad(tankard, expectedEntries, allItems.Count, expectedStack, num); return false; } foreach (ItemData item2 in allItems) { int x = item2.m_gridPos.x; int y = item2.m_gridPos.y; int key = y * 256 + x; if (x < 0 || x > 255 || y < 0 || y > 255 || !expectedItems.TryGetValue(key, out StoredItemSnapshot value)) { WarnChangedStorageItem(tankard, x, y, "grid position"); return false; } expectedItems.Remove(key); string text = FindChangedStorageField(value, item2); if (text != null) { WarnChangedStorageItem(tankard, x, y, text); return false; } } return true; } private static string? FindChangedStorageField(StoredItemSnapshot expected, ItemData actual) { if ((Object)(object)actual.m_dropPrefab == (Object)null || StringExtensionMethods.GetStableHashCode(((Object)actual.m_dropPrefab).name) != expected.PrefabHash) { return "prefab"; } if (actual.m_stack != expected.Stack) { return "stack"; } if (actual.m_quality != expected.Quality) { return "quality"; } if (actual.m_variant != expected.Variant) { return "variant"; } if (actual.m_crafterID != expected.CrafterId) { return "crafter ID"; } if (expected.CrafterId != 0L && !string.Equals(actual.m_crafterName, expected.CrafterName, StringComparison.Ordinal)) { return "crafter name"; } if (!IsStoredDurabilityUnchanged(expected.Durability, actual.m_durability)) { return "durability"; } if (actual.m_worldLevel != expected.WorldLevel) { return "world level"; } if (actual.m_pickedUp != expected.PickedUp) { return "picked-up flag"; } if (actual.m_equipped != expected.Equipped) { return "equipped flag"; } if (expected.Cheated && !actual.m_cheated) { return "cheated flag"; } Dictionary<string, string> customData = actual.m_customData; if (customData == null || customData.Count != expected.CustomData.Count) { return "customData"; } foreach (KeyValuePair<string, string> item in customData) { if (!expected.CustomData.TryGetValue(item.Key, out string value) || !string.Equals(item.Value, value, StringComparison.Ordinal)) { return "customData"; } } return null; } private static bool IsStoredDurabilityUnchanged(int serializedDurability, float restoredDurability) { return ((float)serializedDurability * 0.01f).Equals(restoredDurability); } private static bool TryReadStorageSummary(string rawData, out int version, out int expectedEntries, out int expectedStack, out float storedWeight, out bool hasCompleteWeight, bool calculateWeight = true, Dictionary<int, StoredItemSnapshot>? expectedItems = null) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown version = -1; expectedEntries = 0; expectedStack = 0; storedWeight = 0f; hasCompleteWeight = true; try { ZPackage val = new ZPackage(rawData); version = val.ReadInt(); if (version != 109) { return false; } int num = val.ReadUShort(); for (int i = 0; i < num; i++) { int durability = val.ReadInt(); int num2 = val.ReadByte(); int num3 = val.ReadByte(); int worldLevel = val.ReadByte(); byte b = val.ReadByte(); int num4 = (((b & 4) == 0) ? 1 : val.ReadUShort()); int num5 = (((b & 8) == 0) ? 1 : val.ReadUShort()); int variant = (((b & 0x10) != 0) ? val.ReadInt() : 0); long crafterId = (((b & 0x20) != 0) ? val.ReadLong() : 0); string crafterName = (((b & 0x20) != 0) ? val.ReadString() : ""); int num6 = (((b & 0x40) != 0) ? val.ReadInt() : 0); if (num6 == 0 || num5 <= 0 || num4 <= 0) { return false; } StoredItemSnapshot storedItemSnapshot = ((expectedItems == null) ? null : new StoredItemSnapshot { PrefabHash = num6, Stack = num5, Quality = num4, Variant = variant, Durability = durability, WorldLevel = worldLevel, CrafterId = crafterId, CrafterName = crafterName, PickedUp = ((b & 1) != 0), Equipped = ((b & 2) != 0) }); if ((b & 0x80) != 0) { int num7 = val.ReadNumItems(); for (int j = 0; j < num7; j++) { string key = val.ReadString(); string value = val.ReadString(); storedItemSnapshot?.CustomData.Add(key, value); } } byte b2 = val.ReadByte(); if (storedItemSnapshot != null) { storedItemSnapshot.Cheated = (b2 & 1) != 0; expectedItems.Add(num3 * 256 + num2, storedItemSnapshot); } expectedEntries++; expectedStack += Math.Max(0, num5); if (calculateWeight) { if (TryCalculateSerializedItemWeight(num6, num5, num4, out var weight)) { storedWeight += weight; } else { hasCompleteWeight = false; } } } if (!IsValidStoredWeight(storedWeight)) { storedWeight = 0f; hasCompleteWeight = false; } return val.GetPos() == val.Size(); } 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(int prefabHash, int stack, int quality, out float weight) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown weight = 0f; if (prefabHash == 0 || stack < 0 || (Object)(object)ObjectDB.instance == (Object)null) { return false; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(prefabHash); 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} restored {actualEntries}/{expectedEntries} expected entries and {actualStack}/{expectedStack} expected total stack. The stored data was preserved and the tankard storage was not opened."); } } private static void WarnChangedStorageItem(ItemData tankard, int x, int y, string field) { string cleanPrefabName = TankardTweaks.GetCleanPrefabName(tankard); string item = $"{cleanPrefabName}:metadata:{x}:{y}:{field}"; if (WarnedIncompleteLoads.Add(item)) { UsefulTankardsPlugin.Log.LogWarning((object)$"Tankard storage for {cleanPrefabName} changed {field} while restoring slot ({x}, {y}). 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 && (tankard != null || !InventoryOwners.ContainsKey(inventory))) { InventoryOwners[inventory] = tankard; } } private static void UnregisterTankardStorageInventory(Inventory inventory) { if (inventory != null) { 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 InventoryGuiStackAllMethod = RequireMethod(typeof(InventoryGui), "OnStackAll"); private static readonly MethodInfo InventoryChangedMethod = RequireMethod(typeof(Inventory), "Changed", typeof(bool), typeof(bool)); private static readonly MethodInfo PlayerUpdateKnownRecipesMethod = RequireMethod(typeof(Player), "UpdateKnownRecipesList"); private static readonly MethodInfo InventoryGuiUpdateCraftingPanelMethod = RequireMethod(typeof(InventoryGui), "UpdateCraftingPanel", typeof(bool)); private static readonly MethodInfo InventoryGuiUpdateRecipeMethod = RequireMethod(typeof(InventoryGui), "UpdateRecipe", typeof(Player), typeof(float)); private static readonly object[] InventoryChangedArguments = new object[2] { false, false }; 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 StackAll(InventoryGui gui) { InventoryGuiStackAllMethod.Invoke(gui, null); } internal static void Changed(Inventory? inventory) { if (inventory != null) { InventoryChangedMethod.Invoke(inventory, InventoryChangedArguments); } } internal static void RefreshCrafting(Player player, InventoryGui? gui) { PlayerUpdateKnownRecipesMethod.Invoke(player, null); if ((Object)(object)gui != (Object)null) { InventoryGuiUpdateCraftingPanelMethod.Invoke(gui, new object[1] { false }); InventoryGuiUpdateRecipeMethod.Invoke(gui, new object[2] { player, 0f }); } } 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, params Type[] parameters) { return AccessTools.Method(type, name, parameters, (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, Humanoid ___m_character, 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; Player val = (Player)(object)((___m_character is Player) ? ___m_character : null); if (val != null && TankardStorageSystem.TryConsumeStoredDrinks(val, __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), typeof(bool) })] 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), "StackAll")] internal static class UsefulTankardsContainerStackAllPatch { private static bool Prefix(Container __instance) { return !TankardStorageSystem.TryStackAllTankardStorageContainer(__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 { [HarmonyPriority(801)] [HarmonyBefore(new string[] { "randyknapp.mods.epicloot" })] private static void Prefix(InventoryGui __instance) { TankardStorageSystem.TryHandleInventoryGuiUseInput(__instance); } } internal static class EpicLootStorageCompatibility { internal const string PluginGuid = "randyknapp.mods.epicloot"; private static Func<bool>? _promptOpen; private static Func<bool>? _socketGridOpen; private static ConfigEntry<KeyCode>? _socketModifier; private static bool _unavailable; internal static bool ShouldYieldUse { get { //IL_0026: Unknown result type (might be due to invalid IL or missing references) if (!_unavailable) { Func<bool>? promptOpen = _promptOpen; if (promptOpen == null || !promptOpen()) { if (_socketModifier != null) { return Input.GetKey(_socketModifier.Value); } return false; } } return true; } } internal static bool IsSocketGridOpen => _socketGridOpen?.Invoke() ?? false; internal static void Initialize() { Reset(); if (!Chainloader.PluginInfos.TryGetValue("randyknapp.mods.epicloot", out var value)) { return; } try { Assembly assembly = ((object)value.Instance).GetType().Assembly; _promptOpen = ResolveFlag(assembly, "EpicLoot.InventoryPromptHost", "IsOpen"); _socketGridOpen = ResolveFlag(assembly, "EpicLoot.ShardStones.SocketsUI", "IsSocketGridOpen"); Type type = assembly.GetType("EpicLoot.Config.ELConfig", throwOnError: true); _socketModifier = (AccessTools.Field(type, "SocketOverlayModifier")?.GetValue(null) as ConfigEntry<KeyCode>) ?? throw new MissingFieldException(type.FullName, "SocketOverlayModifier"); UsefulTankardsPlugin.Log.LogInfo((object)"EpicLoot tankard input compatibility enabled (method priority 801)."); } catch (Exception ex) { Reset(); _unavailable = true; UsefulTankardsPlugin.Log.LogWarning((object)("EpicLoot storage input compatibility is unavailable; leaving Use input to EpicLoot. " + ex.Message)); } } private static Func<bool> ResolveFlag(Assembly assembly, string typeName, string propertyName) { MethodInfo method = AccessTools.PropertyGetter(assembly.GetType(typeName, throwOnError: true), propertyName) ?? throw new MissingMethodException(typeName, "get_" + propertyName); return (Func<bool>)Delegate.CreateDelegate(typeof(Func<bool>), method); } internal static void Reset() { _promptOpen = null; _socketGridOpen = null; _socketModifier = null; _unavailable = false; } } [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), typeof(bool) })] 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() { 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 ZNet.instance.IsAdmin(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) {