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.0
UsefulTankards.dll
Decompiled 8 hours 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: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("sighsorry")] [assembly: AssemblyProduct("UsefulTankards")] [assembly: AssemblyCopyright("")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("C4125E26-AB66-46F5-B6B3-4A4C3D11E57E")] [assembly: AssemblyFileVersion("1.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.0.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.0")] [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.0"; 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.0", MinimumRequiredVersion = "1.0.0" }; private static ConfigEntry<Toggle> ServerConfigLocked = null; internal static ConfigEntry<float> MovementWhileDrinking = null; internal 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_006a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Expected O, but got Unknown //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; 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); RoundMovementWhileDrinking(); RoundTankardAnimationSpeed(); MovementWhileDrinking.SettingChanged += OnMovementWhileDrinkingChanged; TankardAnimationSpeed.SettingChanged += OnTankardAnimationSpeedChanged; TankardLocalization.Register(); TankardTweaks.RegisterProfile(this, "02 - Tankard", "Tankard", 10, 0.1f, 0.1f, 3); TankardTweaks.RegisterProfile(this, "03 - Anniversary Tankard", "TankardAnniversary", 15, 0.2f, 0.2f, 4); 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); } TankardRecipes.RegisterRecipe(this, "02 - Tankard", "Tankard", "piece_workbench, 1", "FineWood:5, Resin:2"); TankardRecipes.RegisterRecipe(this, "03 - Anniversary Tankard", "TankardAnniversary", "piece_workbench, 1", "Bronze:2, TrollHide:2, Iron:2"); _harmony.PatchAll(); } private static void OnMovementWhileDrinkingChanged(object sender, EventArgs args) { RoundMovementWhileDrinking(); } private static void OnTankardAnimationSpeedChanged(object sender, EventArgs args) { RoundTankardAnimationSpeed(); } private static void RoundMovementWhileDrinking() { if (MovementWhileDrinking == null || _roundingMovementWhileDrinking) { return; } float num = (float)Math.Round(Math.Min(1f, Math.Max(0f, MovementWhileDrinking.Value)), 2, MidpointRounding.AwayFromZero); if (Math.Abs(MovementWhileDrinking.Value - num) <= 0.0001f) { return; } try { _roundingMovementWhileDrinking = true; MovementWhileDrinking.Value = num; } finally { _roundingMovementWhileDrinking = false; } } private static void RoundTankardAnimationSpeed() { if (TankardAnimationSpeed == null || _roundingTankardAnimationSpeed) { return; } float num = (float)Math.Round(Math.Min(3f, Math.Max(1f, TankardAnimationSpeed.Value)), 2, MidpointRounding.AwayFromZero); if (Math.Abs(TankardAnimationSpeed.Value - num) <= 0.0001f) { return; } try { _roundingTankardAnimationSpeed = true; TankardAnimationSpeed.Value = num; } finally { _roundingTankardAnimationSpeed = false; } } private void OnDestroy() { _harmony.UnpatchSelf(); } internal ConfigEntry<T> ConfigEntry<T>(string group, string name, T value, ConfigDescription description, bool synchronizedSetting = true, int? order = null) { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Expected O, but got Unknown object[] array = description.Tags ?? Array.Empty<object>(); if (order.HasValue) { object[] array2 = new object[array.Length + 1]; Array.Copy(array, array2, array.Length); array2[array.Length] = new ConfigurationManagerAttributes { Order = order.Value }; array = array2; } ConfigDescription val = new ConfigDescription(description.Description + (synchronizedSetting ? " [Synced with Server]" : " [Not Synced with Server]"), description.AcceptableValues, array); ConfigEntry<T> val2 = ((BaseUnityPlugin)this).Config.Bind<T>(group, name, value, val); ConfigSync.AddConfigEntry<T>(val2).SynchronizedConfig = synchronizedSetting; return val2; } internal ConfigEntry<T> ConfigEntry<T>(string group, string name, T value, string description, bool synchronizedSetting = true, int? order = null) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown return ConfigEntry(group, name, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>()), synchronizedSetting, order); } } internal static class TankardLocalization { internal const string OpenHintKey = "$ut_tankard_open_hint"; internal const string CooldownReductionKey = "$ut_tankard_cooldown_reduction"; internal const string BuffDurationBonusKey = "$ut_tankard_buff_duration_bonus"; internal const string StoredMeadsKey = "$ut_tankard_stored_meads"; private const string OpenHintWord = "ut_tankard_open_hint"; private const string CooldownReductionWord = "ut_tankard_cooldown_reduction"; private const string BuffDurationBonusWord = "ut_tankard_buff_duration_bonus"; private const string StoredMeadsWord = "ut_tankard_stored_meads"; private const string EnglishLanguage = "english"; private static readonly MethodInfo? AddWordMethod = AccessTools.Method(typeof(Localization), "AddWord", new Type[2] { typeof(string), typeof(string) }, (Type[])null); private static readonly Dictionary<string, string> OpenHintByLanguage = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { ["english"] = "Press <b>$KEY_Use</b> to open tankard storage.", ["korean"] = "<b>$KEY_Use</b>를 눌러 탱커드 저장소를 엽니다." }; private static readonly Dictionary<string, string> CooldownReductionByLanguage = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { ["english"] = "Potion cooldown reduction: {0}%", ["korean"] = "포션 재사용 대기시간 감소: {0}%" }; private static readonly Dictionary<string, string> BuffDurationBonusByLanguage = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { ["english"] = "Buff duration bonus: {0}%", ["korean"] = "버프 지속시간 증가: {0}%" }; private static readonly Dictionary<string, string> StoredMeadsByLanguage = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { ["english"] = "Stored meads:", ["korean"] = "저장된 벌꿀주:" }; internal static void Register() { if (Localization.instance != null) { Register(Localization.instance); } } internal static void Register(Localization localization) { if (localization != null) { string languageName = NormalizeLanguageName(localization.GetSelectedLanguage()); AddWord(localization, "ut_tankard_open_hint", Get(OpenHintByLanguage, languageName)); AddWord(localization, "ut_tankard_cooldown_reduction", Get(CooldownReductionByLanguage, languageName)); AddWord(localization, "ut_tankard_buff_duration_bonus", Get(BuffDurationBonusByLanguage, languageName)); AddWord(localization, "ut_tankard_stored_meads", Get(StoredMeadsByLanguage, languageName)); } } 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 string Get(Dictionary<string, string> translations, string languageName) { if (!translations.TryGetValue(languageName, out string value)) { return translations["english"]; } return value; } private static void AddWord(Localization localization, string key, string value) { AddWordMethod?.Invoke(localization, new object[2] { key, value }); } } [HarmonyPatch(typeof(Localization), "SetupLanguage")] internal static class UsefulTankardsLocalizationSetupLanguagePatch { private static void Postfix(Localization __instance) { TankardLocalization.Register(__instance); } } internal 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; } } internal static class TankardRecipes { 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.", synchronizedSetting: true, 930)); tankardRecipeProfile.Enabled.SettingChanged += delegate { ApplyRecipeDefinitions(); }; tankardRecipeProfile.Station.SettingChanged += delegate { ApplyRecipeDefinitions(); }; tankardRecipeProfile.Resources.SettingChanged += delegate { ApplyRecipeDefinitions(); }; Profiles[prefabName] = tankardRecipeProfile; } internal static void ApplyRecipeDefinitions() { if ((Object)(object)ObjectDB.instance == (Object)null || (Object)(object)ZNetScene.instance == (Object)null) { return; } foreach (TankardRecipeProfile value in Profiles.Values) { ApplyRecipeDefinition(value); } RefreshLocalCraftingUi(); } private static void ApplyRecipeDefinition(TankardRecipeProfile profile) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown Recipe val = FindRecipe(profile.PrefabName) ?? CreateRecipe(profile.PrefabName); if ((Object)val == (Object)null) { return; } if (profile.Enabled.Value != UsefulTankardsPlugin.Toggle.On) { val.m_enabled = false; return; } ItemDrop val2 = ResolveItemDrop(profile.PrefabName); string station; int level; CraftingStation station2; Requirement[] requirements; if ((Object)val2 == (Object)null) { 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)) { 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)) { WarnOnce("Could not apply recipe for '" + profile.PrefabName + "': resources '" + profile.Resources.Value + "' were invalid."); } else { val.m_item = val2; val.m_amount = 1; val.m_enabled = true; val.m_craftingStation = station2; val.m_minStationLevel = level; val.m_requireOnlyOneIngredient = false; val.m_qualityResultAmountMultiplier = 1f; val.m_resources = requirements; } } private static Recipe FindRecipe(string prefabName) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown foreach (Recipe recipe in ObjectDB.instance.m_recipes) { if (!((Object)recipe == (Object)null) && (string.Equals(((Object)recipe).name, GetRecipeName(prefabName), StringComparison.OrdinalIgnoreCase) || IsRecipeForPrefab(recipe, prefabName))) { return recipe; } } return null; } private static Recipe CreateRecipe(string prefabName) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Expected O, but got Unknown ItemDrop val = ResolveItemDrop(prefabName); if ((Object)val == (Object)null) { return null; } Recipe val2 = ScriptableObject.CreateInstance<Recipe>(); ((Object)val2).name = GetRecipeName(prefabName); val2.m_item = val; ObjectDB.instance.m_recipes.Add(val2); return val2; } private static bool IsRecipeForPrefab(Recipe recipe, string prefabName) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown if ((Object)recipe.m_item == (Object)null) { return false; } return string.Equals(NormalizePrefabName(((Object)((Component)recipe.m_item).gameObject).name), NormalizePrefabName(prefabName), StringComparison.OrdinalIgnoreCase); } private static bool TryParseRequirements(string definition, out Requirement[] requirements) { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Expected O, but got Unknown //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: 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 ((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 == 0 || 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); if ((Object)(object)val != (Object)null) { return val.TryGetComponent<CraftingStation>(ref station); } return false; } private static ItemDrop ResolveItemDrop(string itemName) { string text = NormalizePrefabName(itemName); ObjectDB instance = ObjectDB.instance; GameObject val = ((instance != null) ? instance.GetItemPrefab(text) : null); if ((Object)(object)val == (Object)null) { val = ResolvePrefab(text); } ItemDrop result = default(ItemDrop); if (!((Object)(object)val != (Object)null) || !val.TryGetComponent<ItemDrop>(ref result)) { return null; } return result; } private static GameObject ResolvePrefab(string prefabName) { string text = NormalizePrefabName(prefabName); if ((Object)(object)ZNetScene.instance != (Object)null) { GameObject prefab = ZNetScene.instance.GetPrefab(text); if ((Object)(object)prefab != (Object)null) { return prefab; } } ObjectDB instance = ObjectDB.instance; if (instance == null) { return null; } return instance.GetItemPrefab(text); } private static void RefreshLocalCraftingUi() { if (!((Object)(object)Player.m_localPlayer == (Object)null)) { Player.m_localPlayer.UpdateKnownRecipesList(); if ((Object)(object)InventoryGui.instance != (Object)null) { InventoryGui.instance.UpdateCraftingPanel(false); InventoryGui.instance.UpdateRecipe(Player.m_localPlayer, 0f); } } } private static void WarnOnce(string message) { if (WarnedMessages.Add(message)) { UsefulTankardsPlugin.Log.LogWarning((object)message); } } private static string GetRecipeName(string itemName) { return "Recipe_" + NormalizePrefabName(itemName); } private static bool IsNone(string value) { if (value.Length != 0 && !string.Equals(value, "None", StringComparison.OrdinalIgnoreCase)) { return string.Equals(value, "null", StringComparison.OrdinalIgnoreCase); } return true; } private static string NormalizePrefabName(string value) { value = (value ?? "").Trim(); if (!value.EndsWith("(Clone)", StringComparison.Ordinal)) { return value; } return value.Substring(0, value.Length - "(Clone)".Length).Trim(); } } internal sealed class TankardProfile { internal string PrefabName { get; } 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 { get { ConfigEntry<UsefulTankardsPlugin.Toggle>? canBeRepaired = CanBeRepaired; if (canBeRepaired == null) { return false; } return 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(string prefabName, ConfigEntry<int> durability, ConfigEntry<UsefulTankardsPlugin.Toggle>? canBeRepaired, ConfigEntry<float> cooldownReduction, ConfigEntry<float> durationBonus, ConfigEntry<int> storageSlots) { PrefabName = prefabName; 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; private 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 static DurabilityBaseline From(SharedData shared) { return new DurabilityBaseline(shared); } 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, bool canBeRepairedConfig = true) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Expected O, but got Unknown //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Expected O, but got Unknown //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Expected O, but got Unknown ConfigEntry<UsefulTankardsPlugin.Toggle> canBeRepaired = (canBeRepairedConfig ? 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) : null); TankardProfile tankardProfile = new TankardProfile(prefabName, 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), canBeRepaired, 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 non-over-time buffs drunk through this tankard. 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("Number of mead storage slots in this tankard. 0 disables storage for this tankard.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 20), Array.Empty<object>()), synchronizedSetting: true, 100)); tankardProfile.Durability.SettingChanged += delegate { ApplyItemDefinitions(); }; if (tankardProfile.CanBeRepaired != null) { tankardProfile.CanBeRepaired.SettingChanged += delegate { ApplyItemDefinitions(); }; } Profiles[prefabName] = tankardProfile; } internal static bool TryGetProfile(ItemData? item, out TankardProfile profile) { profile = null; if (item == null) { return false; } string prefabName = GetPrefabName(item); if (prefabName.Length > 0) { return Profiles.TryGetValue(prefabName, out profile); } return false; } internal static bool TryGetProfile(GameObject? prefab, out TankardProfile profile) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown profile = null; if ((Object)(object)prefab == (Object)null || (Object)prefab == (Object)null) { return false; } string text = CleanPrefabName(((Object)prefab).name); if (text.Length > 0) { return Profiles.TryGetValue(text, 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 (TankardProfile value in Profiles.Values) { ApplyItemDefinition(ZNetScene.instance.GetPrefab(value.PrefabName)); } } 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; } } internal static void AppendTankardTooltip(ItemData item, ref string tooltip) { if (!TryGetProfile(item, out TankardProfile profile)) { return; } List<string> list = new List<string>(); if (profile.TankardStorageSlots > 0) { list.Add(TankardLocalization.Localize("$ut_tankard_open_hint")); } if (profile.CooldownReductionPercent > 0f) { list.Add(FormatTooltip("$ut_tankard_cooldown_reduction", profile.CooldownReductionPercent)); } if (profile.DurationBonusPercent > 0f) { list.Add(FormatTooltip("$ut_tankard_buff_duration_bonus", profile.DurationBonusPercent)); } List<string> storedDrinkTooltipLines = TankardStorageSystem.GetStoredDrinkTooltipLines(item); if (storedDrinkTooltipLines.Count > 0) { list.Add(TankardLocalization.Localize("$ut_tankard_stored_meads")); list.AddRange(storedDrinkTooltipLines); } if (list.Count > 0) { tooltip = tooltip + "\n\n<color=orange>" + string.Join("\n", list.Where((string line) => !string.IsNullOrWhiteSpace(line))) + "</color>"; } } private static string FormatTooltip(string key, float percentage) { string format = TankardLocalization.Localize(key); string arg = percentage.ToString("0.#", CultureInfo.InvariantCulture); return string.Format(CultureInfo.InvariantCulture, format, arg); } private static 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] = DurabilityBaseline.From(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; } } 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; } private static string GetPrefabName(ItemData item) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown if (!((Object)item.m_dropPrefab != (Object)null)) { return ""; } return CleanPrefabName(((Object)item.m_dropPrefab).name); } internal static string GetCleanPrefabName(ItemData item) { return GetPrefabName(item); } private static string CleanPrefabName(string name) { if (!name.EndsWith("(Clone)", StringComparison.Ordinal)) { return name; } return name.Substring(0, name.Length - "(Clone)".Length); } } internal static class TankardStorageSystem { private sealed class StoredDrinkSummary { internal string Name { get; } internal int Stack { get; set; } internal int Capacity { get; set; } internal StoredDrinkSummary(string name, int stack, int capacity) { Name = name; Stack = stack; Capacity = capacity; } } internal sealed class TankardStorageContainer : MonoBehaviour { private Player? _player; private ItemData? _tankard; private Inventory? _inventory; private Container? _container; private Action? _saveHandler; private bool _loadComplete = true; private bool _closed; internal bool LoadComplete => _loadComplete; internal void Initialize(Player player, ItemData tankard, TankardProfile profile, Container container, int slots, int width, int height) { _player = player; _tankard = tankard; _container = container; _inventory = LoadTankardStorageInventory(player, tankard, profile, out width, out height, out _loadComplete); ValheimAccess.SetContainerFields(container, tankard.m_shared.m_name, width, height, _inventory, inUse: true); tankard.m_customData["UsefulTankards.Storage.Slots"] = slots.ToString(); AttachImmediateSave(); ValheimAccess.Changed(((Humanoid)player).GetInventory()); } private void Update() { //IL_0060: Unknown result type (might be due to invalid IL or missing references) if (!_closed && (Object)(object)_container != (Object)null && (Object)(object)InventoryGui.instance != (Object)null && (Object)(object)ValheimAccess.GetCurrentContainer(InventoryGui.instance) != (Object)(object)_container) { CloseAndDestroy(); } else if ((Object)(object)_player != (Object)null) { ((Component)this).transform.position = ((Component)_player).transform.position; } } internal void Save() { if (_tankard != null && _inventory != null && _loadComplete) { SaveTankardStorageInventory(_tankard, _inventory); Player? player = _player; ValheimAccess.Changed((player != null) ? ((Humanoid)player).GetInventory() : null); } } 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) { _closed = true; Save(); DetachImmediateSave(); if (_inventory != null) { UnregisterTankardStorageInventory(_inventory); } Object.Destroy((Object)(object)((Component)this).gameObject); } } } private const string StorageDataKey = "UsefulTankards.Storage.Data"; private const string StorageSlotsKey = "UsefulTankards.Storage.Slots"; private static readonly HashSet<Inventory> StorageInventories = new HashSet<Inventory>(); private static readonly Dictionary<Inventory, ItemData> InventoryOwners = new Dictionary<Inventory, ItemData>(); private static readonly HashSet<string> WarnedIncompleteLoads = new HashSet<string>(StringComparer.Ordinal); private static Player? _cachedStoredDrinkPlayer; private static ItemData? _cachedStoredDrinkTankard; private static int _cachedStoredDrinkFrame = -1; private static bool _cachedStoredDrinkResult; internal static bool IsTankardStorageInventory(Inventory inventory) { if (inventory != null) { return StorageInventories.Contains(inventory); } return false; } internal static bool IsTankardStorageContainer(Container? container) { if ((Object)(object)container != (Object)null) { return (Object)(object)((Component)container).GetComponent<TankardStorageContainer>() != (Object)null; } return false; } internal static bool CanAddToTankardStorage(Inventory inventory, ItemData item) { if (!IsTankardStorageInventory(inventory)) { return true; } return IsAllowedStoredDrink(inventory, item); } internal static bool CanAddToTankardStorage(Inventory inventory, GameObject prefab) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown if (!IsTankardStorageInventory(inventory)) { return true; } if ((Object)prefab == (Object)null) { return false; } ItemDrop component = prefab.GetComponent<ItemDrop>(); if ((Object)(object)component != (Object)null) { return IsAllowedStoredDrink(inventory, component.m_itemData); } return false; } internal static bool TrySaveTankardStorageContainer(Container container) { TankardStorageContainer tankardStorageContainer = (((Object)(object)container != (Object)null) ? ((Component)container).GetComponent<TankardStorageContainer>() : null); if ((Object)(object)tankardStorageContainer == (Object)null) { return false; } tankardStorageContainer.Save(); return true; } internal static float GetStoredDrinkWeight(ItemData tankard) { if (!TryLoadStoredInventorySnapshot(tankard, out Inventory inventory)) { return 0f; } float num = 0f; foreach (ItemData allItem in inventory.GetAllItems()) { num += allItem.GetWeight(-1); } return num; } internal static List<string> GetStoredDrinkTooltipLines(ItemData tankard) { List<string> list = new List<string>(); if (!TryLoadStoredInventorySnapshot(tankard, out Inventory inventory)) { return list; } Dictionary<string, StoredDrinkSummary> dictionary = new Dictionary<string, StoredDrinkSummary>(StringComparer.Ordinal); foreach (ItemData allItem in inventory.GetAllItems()) { if (allItem != null) { string text = LocalizeItemName(allItem); int num = Math.Max(1, allItem.m_stack); int num2 = Math.Max(num, allItem.m_shared?.m_maxStackSize ?? num); if (!dictionary.TryGetValue(text, out var value)) { dictionary[text] = new StoredDrinkSummary(text, num, num2); continue; } value.Stack += num; value.Capacity += num2; } } foreach (StoredDrinkSummary value2 in dictionary.Values) { string text2 = ((value2.Capacity > 1) ? $" {value2.Stack}/{value2.Capacity}" : ""); list.Add("- " + value2.Name + text2); } return list; } internal static bool TryHandleInventoryGuiUseInput(InventoryGui inventoryGui) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)inventoryGui == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null) { return false; } Container currentContainer = ValheimAccess.GetCurrentContainer(inventoryGui); if (IsTankardStorageContainer(currentContainer)) { if (!ZInput.GetButtonDown("Use")) { return false; } ZInput.ResetButtonStatus("Use"); ValheimAccess.CloseContainer(inventoryGui); return true; } if ((Object)(object)currentContainer != (Object)null || !ZInput.GetButtonDown("Use")) { return false; } InventoryGrid playerGrid = ValheimAccess.GetPlayerGrid(inventoryGui); ItemData val = (((Object)(object)playerGrid != (Object)null) ? playerGrid.GetItem(new Vector2i(Mathf.RoundToInt(Input.mousePosition.x), Mathf.RoundToInt(Input.mousePosition.y))) : null); if (!TankardTweaks.TryGetProfile(val, out TankardProfile profile) || profile.TankardStorageSlots <= 0) { return false; } OpenTankardStorage(Player.m_localPlayer, val, profile); ZInput.ResetButtonStatus("Use"); return true; } internal static void CloseTankardStorage(Container container) { if (container != null) { ((Component)container).GetComponent<TankardStorageContainer>()?.CloseAndDestroy(); } } internal static bool TryConsumeStoredDrinks(Player player, ItemData tankard, TankardProfile profile, out ItemData consumedAmmo) { consumedAmmo = null; if ((Object)(object)player == (Object)null || tankard == null || profile.TankardStorageSlots <= 0) { return false; } if (!tankard.m_customData.TryGetValue("UsefulTankards.Storage.Data", out var value) || string.IsNullOrWhiteSpace(value)) { return false; } int width; int height; bool loadComplete; Inventory val = LoadTankardStorageInventory(player, tankard, profile, out width, out height, out loadComplete); if (!loadComplete) { return false; } try { bool flag = false; List<ItemData> allItems = val.GetAllItems(); for (int num = allItems.Count - 1; num >= 0; num--) { ItemData val2 = allItems[num]; if (CanConsumeStoredDrinkQuietly(player, tankard, val2)) { ItemData val3 = val2.Clone(); if (((Humanoid)player).ConsumeItem(val, val2, false)) { if (consumedAmmo == null) { consumedAmmo = val3; } flag = true; } } } if (!flag) { return false; } SaveTankardStorageInventory(tankard, val); ClearStoredDrinkCheckCache(); ValheimAccess.Changed(((Humanoid)player).GetInventory()); return true; } finally { UnregisterTankardStorageInventory(val); } } internal static bool HasConsumableStoredDrink(Player player, ItemData tankard, TankardProfile profile) { if ((Object)(object)player == (Object)null || tankard == null || profile.TankardStorageSlots <= 0) { return false; } if (_cachedStoredDrinkPlayer == player && _cachedStoredDrinkTankard == tankard && _cachedStoredDrinkFrame == Time.frameCount) { return _cachedStoredDrinkResult; } bool num = HasConsumableStoredDrinkUncached(player, tankard, profile); _cachedStoredDrinkPlayer = player; _cachedStoredDrinkTankard = tankard; _cachedStoredDrinkFrame = Time.frameCount; _cachedStoredDrinkResult = num; return num; } private static bool HasConsumableStoredDrinkUncached(Player player, ItemData tankard, TankardProfile profile) { if (!tankard.m_customData.TryGetValue("UsefulTankards.Storage.Data", out var value) || string.IsNullOrWhiteSpace(value)) { return false; } int width; int height; bool loadComplete; Inventory val = LoadTankardStorageInventory(player, tankard, profile, out width, out height, out loadComplete); if (!loadComplete) { return false; } try { foreach (ItemData allItem in val.GetAllItems()) { if (CanConsumeStoredDrinkQuietly(player, tankard, allItem)) { return true; } } return false; } finally { UnregisterTankardStorageInventory(val); } } private static void OpenTankardStorage(Player player, ItemData tankard, TankardProfile profile) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) int slots = ResolveStorageSlots(tankard, profile); ResolveGridSize(slots, out var width, out var height); 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, slots, width, height); if (!tankardStorageContainer.LoadComplete) { tankardStorageContainer.CloseAndDestroy(); } else { InventoryGui.instance.Show(val2, 1); } } private static Inventory LoadTankardStorageInventory(Player player, ItemData tankard, TankardProfile profile, out int width, out int height, out bool loadComplete) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown ResolveGridSize(ResolveStorageSlots(tankard, 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)) { try { val.Load(new ZPackage(value)); loadComplete = IsStorageLoadComplete(tankard, value, val); } catch (Exception ex) { UsefulTankardsPlugin.Log.LogWarning((object)("Could not read tankard storage for " + TankardTweaks.GetCleanPrefabName(tankard) + ": " + ex.GetBaseException().Message)); loadComplete = false; } } RegisterTankardStorageInventory(val, tankard); return val; } private static bool TryLoadStoredInventorySnapshot(ItemData tankard, out Inventory inventory) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Expected O, but got Unknown inventory = null; if (tankard == null || !TankardTweaks.TryGetProfile(tankard, out TankardProfile profile) || !tankard.m_customData.TryGetValue("UsefulTankards.Storage.Data", out var value) || string.IsNullOrWhiteSpace(value)) { return false; } ResolveGridSize(ResolveStorageSlots(tankard, profile), out var width, out var height); inventory = CreateTankardStorageInventory(tankard, width, height); try { inventory.Load(new ZPackage(value)); if (!IsStorageLoadComplete(tankard, value, inventory)) { inventory = null; return false; } return inventory.GetAllItems().Count > 0; } catch (Exception ex) { UsefulTankardsPlugin.Log.LogWarning((object)("Could not read tankard storage for " + TankardTweaks.GetCleanPrefabName(tankard) + ": " + ex.GetBaseException().Message)); inventory = null; return false; } } private static void SaveTankardStorageInventory(ItemData tankard, Inventory inventory) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown if (tankard != null && inventory != null) { ZPackage val = new ZPackage(); inventory.Save(val); string @base = val.GetBase64(); if (inventory.GetAllItems().Count == 0) { tankard.m_customData.Remove("UsefulTankards.Storage.Data"); } else { tankard.m_customData["UsefulTankards.Storage.Data"] = @base; } 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, string rawData, Inventory inventory) { if (!TryReadExpectedStorageStack(rawData, out var expectedEntries, out var expectedStack) || expectedEntries <= 0) { return true; } int num = 0; foreach (ItemData allItem in inventory.GetAllItems()) { num += Math.Max(0, allItem.m_stack); } if (num >= expectedStack) { return true; } WarnIncompleteLoad(tankard, expectedStack, num); return false; } private static bool TryReadExpectedStorageStack(string rawData, out int expectedEntries, out int expectedStack) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown //IL_0047: Unknown result type (might be due to invalid IL or missing references) expectedEntries = 0; expectedStack = 0; try { ZPackage val = new ZPackage(rawData); int num = val.ReadInt(); int num2 = val.ReadInt(); if (num != 106) { return false; } for (int i = 0; i < num2; i++) { string value = val.ReadString(); int val2 = val.ReadInt(); val.ReadSingle(); val.ReadVector2i(); val.ReadBool(); val.ReadInt(); val.ReadInt(); val.ReadLong(); val.ReadString(); int num3 = val.ReadInt(); for (int j = 0; j < num3; j++) { val.ReadString(); val.ReadString(); } val.ReadInt(); val.ReadBool(); if (!string.IsNullOrWhiteSpace(value)) { expectedEntries++; expectedStack += Math.Max(0, val2); } } return true; } catch (Exception ex) { UsefulTankardsPlugin.Log.LogWarning((object)("Could not inspect tankard storage payload: " + ex.GetBaseException().Message)); return false; } } private static void WarnIncompleteLoad(ItemData tankard, int expectedStack, int actualStack) { string cleanPrefabName = TankardTweaks.GetCleanPrefabName(tankard); string item = $"{cleanPrefabName}:{expectedStack}:{actualStack}"; if (WarnedIncompleteLoads.Add(item)) { UsefulTankardsPlugin.Log.LogWarning((object)$"Tankard storage for {cleanPrefabName} loaded only {actualStack}/{expectedStack} stored item stack. The stored data was preserved and the tankard storage was not opened."); } } private static int ResolveStorageSlots(ItemData tankard, TankardProfile profile) { int result = Math.Max(0, profile.TankardStorageSlots); if (tankard != null) { tankard.m_customData["UsefulTankards.Storage.Slots"] = result.ToString(); } return result; } private static void ResolveGridSize(int slots, out int width, out int height) { int num = Math.Max(1, slots); width = Mathf.Clamp(num, 1, 5); height = Mathf.CeilToInt((float)num / 5f); } private static void RegisterTankardStorageInventory(Inventory inventory, ItemData tankard) { if (inventory != null) { StorageInventories.Add(inventory); if (tankard != null) { InventoryOwners[inventory] = tankard; } } } private static void UnregisterTankardStorageInventory(Inventory inventory) { if (inventory != null) { StorageInventories.Remove(inventory); InventoryOwners.Remove(inventory); } } private static void ClearStoredDrinkCheckCache() { _cachedStoredDrinkPlayer = null; _cachedStoredDrinkTankard = null; _cachedStoredDrinkFrame = -1; _cachedStoredDrinkResult = false; } private static string LocalizeItemName(ItemData item) { string text = item.m_shared?.m_name ?? ""; if (Localization.instance == null || string.IsNullOrWhiteSpace(text)) { return text; } return Localization.instance.Localize(text); } private static bool IsAllowedStoredDrink(Inventory inventory, ItemData item) { //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_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0082: 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; } if (!InventoryOwners.TryGetValue(inventory, out ItemData value) || value == null) { return true; } string ammoType = value.m_shared.m_ammoType; if (!string.IsNullOrWhiteSpace(ammoType) && !string.Equals(item.m_shared.m_ammoType, ammoType, StringComparison.OrdinalIgnoreCase)) { if ((Object)item.m_dropPrefab != (Object)null) { return string.Equals(((Object)item.m_dropPrefab).name, ammoType, 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 || !IsAllowedStoredDrinkForTankard(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; } private static bool IsAllowedStoredDrinkForTankard(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; } } internal static class ValheimAccess { private static readonly FieldInfo? InventoryGuiCurrentContainerField = AccessTools.Field(typeof(InventoryGui), "m_currentContainer"); private static readonly FieldInfo? InventoryGuiPlayerGridField = AccessTools.Field(typeof(InventoryGui), "m_playerGrid"); private static readonly FieldInfo? ContainerNameField = AccessTools.Field(typeof(Container), "m_name"); private static readonly FieldInfo? ContainerWidthField = AccessTools.Field(typeof(Container), "m_width"); private static readonly FieldInfo? ContainerHeightField = AccessTools.Field(typeof(Container), "m_height"); private static readonly FieldInfo? ContainerInventoryField = AccessTools.Field(typeof(Container), "m_inventory"); private static readonly FieldInfo? ContainerInUseField = AccessTools.Field(typeof(Container), "m_inUse"); private static readonly FieldInfo? InventoryOnChangedField = AccessTools.Field(typeof(Inventory), "m_onChanged"); private static readonly MethodInfo? InventoryGuiCloseContainerMethod = AccessTools.Method(typeof(InventoryGui), "CloseContainer", (Type[])null, (Type[])null); private static readonly MethodInfo? InventoryChangedMethod = AccessTools.Method(typeof(Inventory), "Changed", (Type[])null, (Type[])null); internal static Container? GetCurrentContainer(InventoryGui? gui) { if (!((Object)(object)gui == (Object)null)) { object? obj = InventoryGuiCurrentContainerField?.GetValue(gui); return (Container?)((obj is Container) ? obj : null); } return null; } internal static InventoryGrid? GetPlayerGrid(InventoryGui? gui) { if (!((Object)(object)gui == (Object)null)) { object? obj = InventoryGuiPlayerGridField?.GetValue(gui); return (InventoryGrid?)((obj is InventoryGrid) ? obj : 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 bool GetContainerInUse(Container container) { return ContainerInUseField?.GetValue(container) as bool? == true; } internal static void CloseContainer(InventoryGui gui) { InventoryGuiCloseContainerMethod?.Invoke(gui, null); } internal static void Changed(Inventory? inventory) { if (inventory != null) { InventoryChangedMethod?.Invoke(inventory, null); } } internal static void AddInventoryChangedHandler(Inventory? inventory, Action handler) { if (inventory != null && !(InventoryOnChangedField == 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 && !(InventoryOnChangedField == null)) { Action source = InventoryOnChangedField.GetValue(inventory) as Action; InventoryOnChangedField.SetValue(inventory, (Action)Delegate.Remove(source, handler)); } } } [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); } private static void Restore(Humanoid humanoid) { if (ActiveStates.TryGetValue(humanoid, out AnimationSpeedState value)) { value.Restore(); ActiveStates.Remove(humanoid); } } } internal static class UsefulTankardsAttackAmmo { internal static bool HasStoredDrink(Humanoid character, ItemData weapon, out TankardProfile profile) { profile = null; if (TankardTweaks.TryGetProfile(weapon, out 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 static bool Prefix(Attack __instance, ref ItemData ammoItem, ref bool __result, ref TankardProfile? __state) { __state = TankardTweaks.CurrentUseContext; if (TankardTweaks.TryGetProfile(__instance.GetWeapon(), out TankardProfile profile)) { TankardTweaks.CurrentUseContext = profile; if (TankardStorageSystem.TryConsumeStoredDrinks(Player.m_localPlayer, __instance.GetWeapon(), profile, out ItemData consumedAmmo)) { ammoItem = consumedAmmo; __result = true; return false; } } return true; } private static void Postfix(TankardProfile? __state) { TankardTweaks.CurrentUseContext = __state; } } [HarmonyPatch(typeof(Attack), "EquipAmmoItem")] internal static class UsefulTankardsEquipAmmoItemPatch { private static bool Prefix(Humanoid character, ItemData weapon, ref bool __result) { if (UsefulTankardsAttackAmmo.HasStoredDrink(character, weapon, out TankardProfile _)) { __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, out TankardProfile _)) { return true; } __result = true; return false; } } [HarmonyPatch(typeof(StatusEffect), "Setup")] internal static class UsefulTankardsStatusEffectSetupPatch { private static void Prefix(StatusEffect __instance) { TankardTweaks.ModifyEffectForCurrentTankard(__instance); } } [HarmonyPatch(typeof(ItemData), "GetTooltip", new Type[] { typeof(ItemData), typeof(int), typeof(bool), typeof(float), typeof(int) })] internal static class UsefulTankardsItemTooltipPatch { private static void Postfix(ItemData item, ref string __result) { TankardTweaks.AppendTankardTooltip(item, ref __result); } } [HarmonyPatch(typeof(ItemData), "GetWeight", new Type[] { typeof(int) })] internal static class UsefulTankardsItemWeightPatch { private static void Postfix(ItemData __instance, int stackOverride, ref float __result) { if (stackOverride != 0) { __result += TankardStorageSystem.GetStoredDrinkWeight(__instance); } } } internal static class UsefulTankardsInventoryGuards { internal static bool CanAdd(Inventory inventory, ItemData item) { return TankardStorageSystem.CanAddToTankardStorage(inventory, item); } internal static bool CanAdd(Inventory inventory, GameObject prefab) { return TankardStorageSystem.CanAddToTankardStorage(inventory, prefab); } } [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), "IsInUse")] internal static class UsefulTankardsContainerIsInUsePatch { private static bool Prefix(Container __instance, ref bool __result) { if (!TankardStorageSystem.IsTankardStorageContainer(__instance)) { return true; } __result = ValheimAccess.GetContainerInUse(__instance); return false; } } [HarmonyPatch(typeof(Container), "Save")] internal static class UsefulTankardsContainerSavePatch { private static bool Prefix(Container __instance) { return !TankardStorageSystem.TrySaveTankardStorageContainer(__instance); } } [HarmonyPatch(typeof(Container), "Load")] internal static class UsefulTankardsContainerLoadPatch { private static bool Prefix(Container __instance, ref bool __result) { if (!TankardStorageSystem.IsTankardStorageContainer(__instance)) { return true; } __result = true; return false; } } [HarmonyPatch(typeof(InventoryGui), "Update")] internal static class UsefulTankardsInventoryGuiUpdatePatch { private static void Prefix(InventoryGui __instance) { TankardStorageSystem.TryHandleInventoryGuiUseInput(__instance); } } [HarmonyPatch(typeof(InventoryGui), "CloseContainer")] internal static class UsefulTankardsInventoryGuiCloseContainerPatch { private static void Prefix(InventoryGui __instance, out Container __state) { __state = ValheimAccess.GetCurrentContainer(__instance); } private static void Postfix(Container __state) { TankardStorageSystem.CloseTankardStorage(__state); } } [HarmonyPatch(typeof(InventoryGui), "Hide")] internal static class UsefulTankardsInventoryGuiHidePatch { private static void Prefix(InventoryGui __instance, out Container __state) { __state = ValheimAccess.GetCurrentContainer(__instance); } private static void Postfix(Container __state) { TankardStorageSystem.CloseTankardStorage(__state); } } [HarmonyPatch(typeof(Inventory), "CanAddItem", new Type[] { typeof(ItemData), typeof(int) })] internal static class UsefulTankardsInventoryCanAddItemPatch { private static bool Prefix(Inventory __instance, ItemData item, ref bool __result) { if (UsefulTankardsInventoryGuards.CanAdd(__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 (UsefulTankardsInventoryGuards.CanAdd(__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 (UsefulTankardsInventoryGuards.CanAdd(__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 (UsefulTankardsInventoryGuards.CanAdd(__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 (UsefulTankardsInventoryGuards.CanAdd(__instance, item)) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(ItemData), typeof(int), typeof(int), typeof(int) })] internal static class UsefulTankardsInventoryAddItemAmountPatch { private static bool Prefix(Inventory __instance, ItemData item, ref bool __result) { if (UsefulTankardsInventoryGuards.CanAdd(__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 UsefulTankardsInventoryGuards.CanAdd(__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 (UsefulTankardsInventoryGuards.CanAdd(__instance, item)) { return true; } __result = false; return false; } } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ServerSync { [PublicAPI] internal abstract class OwnConfigEntryBase { public object? LocalBaseValue; public bool SynchronizedConfig = true; public abstract ConfigEntryBase BaseConfig { get; } } [PublicAPI] internal class SyncedConfigEntry<T>(ConfigEntry<T> sourceConfig) : OwnConfigEntryBase() { public readonly ConfigEntry<T> SourceConfig = sourceConfig; public override ConfigEntryBase BaseConfig => (ConfigEntryBase)(object)SourceConfig; public T Value { get { return SourceConfig.Value; } set { SourceConfig.Value = value; } } public void AssignLocalValue(T value) { if (LocalBaseValue == null) { Value = value; } else { LocalBaseValue = value; } } } internal abstract class CustomSyncedValueBase { public object? LocalBaseValue; public readonly string Identifier; public readonly Type Type; private object? boxedValue; protected bool localIsOwner; public readonly int Priority; public object? BoxedValue { get { return boxedValue; } set { boxedValue = value; this.ValueChanged?.Invoke(); } } public event Action? ValueChanged; protected CustomSyncedValueBase(ConfigSync configSync, string identifier, Type type, int priority) { Priority = priority; Identifier = identifier; Type = type; configSync.AddCustomValue(this); localIsOwner = configSync.IsSourceOfTruth; configSync.SourceOfTruthChanged += delegate(bool truth) { localIsOwner = truth; }; } } [PublicAPI] internal sealed class CustomSyncedValue<T> : CustomSyncedValueBase { public T Value { get { return (T)base.BoxedValue; } set { base.BoxedValue = value; } } public CustomSyncedValue(ConfigSync configSync, string identifier, T value = default(T), int priority = 0) : base(configSync, identifier, typeof(T), priority) { Value = value; } public void AssignLocalValue(T value) { if (localIsOwner) { Value = value; } else { LocalBaseValue = value; } } } internal class ConfigurationManagerAttributes { [UsedImplicitly] public bool? ReadOnly = false; } [PublicAPI] internal class ConfigSync { [HarmonyPatch(typeof(ZRpc), "HandlePackage")] private static class SnatchCurrentlyHandlingRPC { public static ZRpc? currentRpc; [HarmonyPrefix] private static void Prefix(ZRpc __instance) { currentRpc = __instance; } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class RegisterRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance) { isServer = __instance.IsServer(); foreach (ConfigSync configSync2 in configSyncs) { ZRoutedRpc.instance.Register<ZPackage>(configSync2.Name + " ConfigSync", (Action<long, ZPackage>)configSync2.RPC_FromOtherClientConfigSync); if (isServer) { configSync2.InitialSyncDone = true; Debug.Log((object)("Registered '" + configSync2.Name + " ConfigSync' RPC - waiting for incoming connections")); } } if (isServer) { ((MonoBehaviour)__instance).StartCoroutine(WatchAdminListChanges()); } static void SendAdmin(List<ZNetPeer> peers, bool isAdmin) { ZPackage package = ConfigsToPackage(null, null, new PackageEntry[1] { new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = isAdmin } }); ConfigSync configSync = configSyncs.First(); if (configSync != null) { ((MonoBehaviour)ZNet.instance).StartCoroutine(configSync.sendZPackage(peers, package)); } } static IEnumerator WatchAdminListChanges() { MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); List<string> CurrentList = new List<string>(adminList.GetList()); while (true) { yield return (object)new WaitForSeconds(30f); if (!adminList.GetList().SequenceEqual(CurrentList)) { CurrentList = new List<string>(adminList.GetList()); List<ZNetPeer> adminPeer = ZNet.instance.GetPeers().Where(delegate(ZNetPeer p) { string hostName = p.m_rpc.GetSocket().GetHostName(); return ((object)listContainsId == null) ? adminList.Contains(hostName) : ((bool)listContainsId.Invoke(ZNet.instance, new object[2] { adminList, hostName })); }).ToList(); List<ZNetPeer> nonAdminPeer = ZNet.instance.GetPeers().Except(adminPeer).ToList(); SendAdmin(nonAdminPeer, isAdmin: false); SendAdmin(adminPeer, isAdmin: true); } } } } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class RegisterClientRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance, ZNetPeer peer) { if (__instance.IsServer()) { return; } foreach (ConfigSync configSync in configSyncs) { peer.m_rpc.Register<ZPackage>(configSync.Name + " ConfigSync", (Action<ZRpc, ZPackage>)configSync.RPC_FromServerConfigSync); } } } private class ParsedConfigs { public readonly Dictionary<OwnConfigEntryBase, object?> configValues = new Dictionary<OwnConfigEntryBase, object>(); public readonly Dictionary<CustomSyncedValueBase, object?> customValues = new Dictionary<CustomSyncedValueBase, object>(); } [HarmonyPatch(typeof(ZNet), "Shutdown")] private class ResetConfigsOnShutdown { [HarmonyPostfix] private static void Postfix() { ProcessingServerUpdate = true; foreach (ConfigSync configSync in configSyncs) { configSync.resetConfigsFromServer(); configSync.IsSourceOfTruth = true; configSync.InitialSyncDone = false; } ProcessingServerUpdate = false; } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private class SendConfigsAfterLogin { private class BufferingSocket : ZPlayFabSocket, ISocket { public volatile bool finished = false; public volatile int versionMatchQueued = -1; public readonly List<ZPackage> Package = new List<ZPackage>(); public readonly ISocket Original; public BufferingSocket(ISocket original) { Original = original; ((ZPlayFabSocket)this)..ctor(); } public bool IsConnected() { return Original.IsConnected(); } public ZPackage Recv() { return Original.Recv(); } public int GetSendQueueSize() { return Original.GetSendQueueSize(); } public int GetCurrentSendRate() { return Original.GetCurrentSendRate(); } public bool IsHost() { return Original.IsHost(); } public void Dispose() { Original.Dispose(); } public bool GotNewData() { return Original.GotNewData(); } public void Close() { Original.Close(); } public string GetEndPointString() { return Original.GetEndPointString(); } public void GetAndResetStats(out int totalSent, out int totalRecv) { Original.GetAndResetStats(ref totalSent, ref totalRecv); } public void GetConnectionQuality(out float localQuality, out float remoteQuality, out int ping, out float outByteSec, out float inByteSec) { Original.GetConnectionQuality(ref localQuality, ref remoteQuality, ref ping, ref outByteSec, ref inByteSec); } public ISocket Accept() { return Original.Accept(); } public int GetHostPort() { return Original.GetHostPort(); } public bool Flush() { return Original.Flush(); } public string GetHostName() { return Original.GetHostName(); } public void VersionMatch() { if (finished) { Original.VersionMatch(); } else { versionMatchQueued = Package.Count; } } public void Send(ZPackage pkg) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown int pos = pkg.GetPos(); pkg.SetPos(0); int num = pkg.ReadInt(); if ((num == StringExtensionMethods.GetStableHashCode("PeerInfo") || num == StringExtensionMethods.GetStableHashCode("RoutedRPC") || num == StringExtensionMethods.GetStableHashCode("ZDOData")) && !finished) { ZPackage val = new ZPackage(pkg.GetArray()); val.SetPos(pos); Package.Add(val); } else { pkg.SetPos(pos); Original.Send(pkg); } } } [HarmonyPriority(800)] [HarmonyPrefix] private static void Prefix(ref Dictionary<Assembly, BufferingSocket>? __state, ZNet __instance, ZRpc rpc) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Invalid comparison between Unknown and I4 if (!__instance.IsServer()) { return; } BufferingSocket bufferingSocket = new BufferingSocket(rpc.GetSocket()); AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket); object? obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (val != null && (int)ZNet.m_onlineBackend > 0) { FieldInfo fieldInfo = AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket"); object? value = fieldInfo.GetValue(val); ZPlayFabSocket val2 = (ZPlayFabSocket)((value is ZPlayFabSocket) ? value : null); if (val2 != null) { typeof(ZPlayFabSocket).GetField("m_remotePlayerId").SetValue(bufferingSocket, val2.m_remotePlayerId); } fieldInfo.SetValue(val, bufferingSocket); } if (__state == null) { __state = new Dictionary<Assembly, BufferingSocket>(); } __state[Assembly.GetExecutingAssembly()] = bufferingSocket; } [HarmonyPostfix] private static void Postfix(Dictionary<Assembly, BufferingSocket> __state, ZNet __instance, ZRpc rpc) { ZNetPeer peer; if (__instance.IsServer()) { object obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); peer = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (peer == null) { SendBufferedData(); } else { ((MonoBehaviour)__instance).StartCoroutine(sendAsync()); } } void SendBufferedData() { if (rpc.GetSocket() is BufferingSocket bufferingSocket) { AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket.Original); object? obj2 = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj2 is ZNetPeer) ? obj2 : null); if (val != null) { AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket").SetValue(val, bufferingSocket.Original); } } BufferingSocket bufferingSocket2 = __state[Assembly.GetExecutingAssembly()]; bufferingSocket2.finished = true; for (int i = 0; i < bufferingSocket2.Package.Count; i++) { if (i == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } bufferingSocket2.Original.Send(bufferingSocket2.Package[i]); } if (bufferingSocket2.Package.Count == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } } IEnumerator sendAsync() { foreach (ConfigSync configSync in configSyncs) { List<PackageEntry> entries = new List<PackageEntry>(); if (configSync.CurrentVersion != null) { entries.Add(new PackageEntry { section = "Internal", key = "serverversion", type = typeof(string), value = configSync.CurrentVersion }); } MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); entries.Add(new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = (((object)listContainsId == null) ? ((object)adminList.Contains(rpc.GetSocket().GetHostName())) : listContainsId.Invoke(ZNet.instance, new object[2] { adminList, rpc.GetSocket().GetHostName() })) }); ZPackage package = ConfigsToPackage(configSync.allConfigs.Select((OwnConfigEntryBase c) => c.BaseConfig), configSync.allCustomValues, entries, partial: false); yield return ((MonoBehaviour)__instance).StartCoroutine(configSync.sendZPackage(new List<ZNetPeer> { peer }, package)); } SendBufferedData(); } } } private class PackageEntry { public string section = null; public string key = null; public Type type = null; public object? value; } [HarmonyPatch(typeof(ConfigEntryBase), "GetSerializedValue")] private static class PreventSavingServerInfo { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, ref string __result) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || isWritableConfig(ownConfigEntryBase)) { return true; } __result = TomlTypeConverter.ConvertToString(ownConfigEntryBase.LocalBaseValue, __instance.SettingType); return false; } } [HarmonyPatch(typeof(ConfigEntryBase), "SetSerializedValue")] private static class PreventConfigRereadChangingValues { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, string value) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || ownConfigEntryBase.LocalBaseValue == null) { return true; } try { ownConfigEntryBase.LocalBaseValue = TomlTypeConverter.ConvertToValue(value, __instance.SettingType); } catch (Exception ex) { Debug.LogWarning((object)$"Config value of setting \"{__instance.Definition}\" could not be parsed and will be ignored. Reason: {ex.Message}; Value: {value}"); } return false; } } private class InvalidDeserializationTypeException : Exception { public string expected = null; public string received = null; public string field = ""; } public static bool ProcessingServerUpdate; public readonly string Name; public string? DisplayName; public string? CurrentVersion; public string? MinimumRequiredVersion; public bool ModRequired = false; private bool? forceConfigLocking; private bool isSourceOfTruth = true; private static readonly HashSet<ConfigSync> configSyncs; private readonly HashSet<OwnConfigEntryBase> allConfigs = new HashSet<OwnConfigEntryBase>(); private HashSet<CustomSyncedValueBase> allCustomValues = new HashSet<CustomSyncedValueBase>(); private static bool isServer; private static bool lockExempt; private OwnConfigEntryBase? lockedConfig = null; private const byte PARTIAL_CONFIGS = 1; private const byte FRAGMENTED_CONFIG = 2; private const byte COMPRESSED_CONFIG = 4; private readonly Dictionary<string, SortedDictionary<int, byte[]>> configValueCache = new Dictionary<string, SortedDictionary<int, byte[]>>(); private readonly List<KeyValuePair<long, string>> cacheExpirations = new List<KeyValuePair<long, string>>(); private static long packageCounter; public bool IsLocked { get { bool? flag = forceConfigLocking; bool num; if (!flag.HasValue) { if (lockedConfig == null) { goto IL_0052; } num = ((IConvertible)lockedConfig.BaseConfig.BoxedValue).ToInt32(CultureInfo.InvariantCulture) != 0; } else { num = flag == true; } if (!num) { goto IL_0052; } int result = ((!lockExempt) ? 1 : 0); goto IL_0053; IL_0052: result = 0; goto IL_0053; IL_0053: return (byte)result != 0; } set { forceConfigLocking = value; } } public bool IsAdmin => lockExempt || isSourceOfTruth; public bool IsSourceOfTruth { get { return isSourceOfTruth; } private set { if (value != isSourceOfTruth) { isSourceOfTruth = value; this.SourceOfTruthChanged?.Invoke(value); } } } public bool InitialSyncDone { get; private set; } = false; public event Action<bool>? SourceOfTruthChanged; private event Action? lockedConfigChanged; static ConfigSync() { ProcessingServerUpdate = false; configSyncs = new HashSet<ConfigSync>(); lockExempt = false; packageCounter = 0L; RuntimeHelpers.RunClassConstructor(typeof(VersionCheck).TypeHandle); } public ConfigSync(string name) { Name = name; configSyncs.Add(this); new VersionCheck(this); } public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigEntry<T> configEntry) { OwnConfigEntryBase ownConfigEntryBase = configData((ConfigEntryBase)(object)configEntry); SyncedConfigEntry<T> syncedEntry = ownConfigEntryBase as SyncedConfigEntry<T>; if (syncedEntry == null) { syncedEntry = new SyncedConfigEntry<T>(configEntry); AccessTools.DeclaredField(typeof(ConfigDescription), "<Tags>k__BackingField").SetValue(((ConfigEntryBase)configEntry).Description, new object[1] { new ConfigurationManagerAttributes() }.Concat(((ConfigEntryBase)configEntry).Description.Tags ?? Array.Empty<object>()).Concat(new SyncedConfigEntry<T>[1] { syncedEntry }).ToArray()); configEntry.SettingChanged += delegate { if (!ProcessingServerUpdate && syncedEntry.SynchronizedConfig) { Broadcast(ZRoutedRpc.Everybody, (ConfigEntryBase)configEntry); } }; allConfigs.Add(syncedEntry); } return syncedEntry; } public SyncedConfigEntry<T> AddLockingConfigEntry<T>(ConfigEntry<T> lockingConfig) where T : IConvertible { if (lockedConfig != null) { throw new Exception("Cannot initialize locking ConfigEntry twice"); } lockedConfig = AddConfigEntry<T>(lockingConfig); lockingConfig.SettingChanged += delegate { this.lockedConfigChanged?.Invoke(); }; return (SyncedConfigEntry<T>)lockedConfig; } internal void AddCustomValue(CustomSyncedValueBase customValue) { if (allCustomValues.Select((CustomSyncedValueBase v) => v.Identifier).Concat(new string[1] { "serverversion" }).Contains(customValue.Identifier)) { throw new Exception("Cannot have multiple settings with the same name or with a reserved name (serverversion)"); } allCustomValues.Add(customValue); allCustomValues = new HashSet<CustomSyncedValueBase>(allCustomValues.OrderByDescending((CustomSyncedValueBase v) => v.Priority)); customValue.ValueChanged += delegate { if (!ProcessingServerUpdate) { Broadcast(ZRoutedRpc.Everybody, customValue); } }; } private void RPC_FromServerConfigSync(ZRpc rpc, ZPackage package) { lockedConfigChanged += serverLockedSettingChanged; IsSourceOfTruth = false; if (HandleConfigSyncRPC(0L, package, clientUpdate: false)) { InitialSyncDone = true; } } private void RPC_FromOtherClientConfigSync(long sender, ZPackage package) { HandleConfigSyncRPC(sender, package, clientUpdate: true); } private bool HandleConfigSyncRPC(long sender, ZPackage package, bool clientUpdate) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Expected O, but got Unknown //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Expected O, but got Unknown try { if (isServer && IsLocked) { ZRpc? currentRpc = SnatchCurrentlyHandlingRPC.currentRpc; object obj; if (currentRpc == null) { obj = null; } else { ISocket socket = currentRpc.GetSocket(); obj = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj; if (text != null) { MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList val = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); if (!(((object)methodInfo == null) ? val.Contains(text) : ((bool)methodInfo.Invoke(ZNet.instance, new object[2] { val, text })))) { return false; } } } cacheExpirations.RemoveAll(delegate(KeyValuePair<long, string> kv) { if (kv.Key < DateTimeOffset.Now.Ticks) { configValueCache.Remove(kv.Value); return true; } return false; }); byte b = package.ReadByte(); if ((b & 2) != 0) { long num = package.ReadLong(); string text2 = sender.ToString() + num; if (!configValueCache.TryGetValue(text2, out SortedDictionary<int, byte[]> value)) { value = new SortedDictionary<int, byte[]>(); configValueCache[text2] = value; cacheExpirations.Add(new KeyValuePair<long, string>(DateTimeOffset.Now.AddSeconds(60.0).Ticks, text2)); } int key = package.ReadInt(); int num2 = package.ReadInt(); value.Add(key, package.ReadByteArray()); if (value.Count < num2) { return false; } configValueCache.Remove(text2); package = new ZPackage(value.Values.SelectMany((byte[] a) => a).ToArray()); b = package.ReadByte(); } ProcessingServerUpdate = true; if ((b & 4) != 0) { byte[] buffer = package.ReadByteArray(); MemoryStream stream = new MemoryStream(buffer); MemoryStream memoryStream = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress)) { deflateStream.CopyTo(memoryStream); } package = new ZPackage(memoryStream.ToArray()); b = package.ReadByte(); } if ((b & 1) == 0) { resetConfigsFromServer(); } ParsedConfigs parsedConfigs = ReadConfigsFromPackage(package); ConfigFile val2 = null; bool saveOnConfigSet = false; foreach (KeyValuePair<OwnConfigEntryBase, object> configValue in parsedConfigs.configValues) { if (!isServer && configValue.Key.LocalBaseValue == null) { configValue.Key.LocalBaseValue = configValue.Key.BaseConfig.BoxedValue; } if (val2 == null) { val2 = configValue.Key.BaseConfig.ConfigFile; saveOnConfigSet = val2.SaveOnConfigSet; val2.SaveOnConfigSet = false; } configValue.Key.BaseConfig.BoxedValue = configValue.Value; } if (val2 != null) { val2.SaveOnConfigSet = saveOnConfigSet; val2.Save(); } foreach (KeyValuePair<CustomSyncedValueBase, object> customValue in parsedConfigs.customValues) { if (!isServer) { CustomSyncedValueBase key2 = customValue.Key; if (key2.LocalBaseValue == null) { key2.LocalBaseValue = customValue.Key.BoxedValue; } } customValue.Key.BoxedValue = customValue.Value; } Debug.Log((object)string.Format("Received {0} configs and {1} custom values from {2} for mod {3}", parsedConfigs.configValues.Count, parsedConfigs.customValues.Count, (isServer || clientUpdate) ? $"client {sender}" : "the server", DisplayName ?? Name)); if (!isServer) { serverLockedSettingChanged(); } return true; } finally { ProcessingServerUpdate = false; } } private ParsedConfigs ReadConfigsFromPackage(ZPackage package) { ParsedConfigs parsedConfigs = new ParsedConfigs(); Dictionary<string, OwnConfigEntryBase> dictionary = allConfigs.Where((OwnConfigEntryBase c) => c.SynchronizedConfig).ToDictionary((OwnConfigEntryBase c) => c.BaseConfig.Definition.Section + "_" + c.BaseConfig.Definition.Key, (OwnConfigEntryBase c) => c); Dictionary<string, CustomSyncedValueBase> dictionary2 = allCustomValues.ToDictionary((CustomSyncedValueBase c) => c.Identifier, (CustomSyncedValueBase c) => c); int num = package.ReadInt(); for (int num2 = 0; num2 < num; num2++) { string text = package.ReadString(); string text2 = package.ReadString(); string text3 = package.ReadString(); Type type = Type.GetType(text3); if (text3 == "" || type != null) { object obj; try { obj = ((text3 == "") ? null : ReadValueWithTypeFromZPackage(package, type)); } catch (InvalidDeserializationTypeException ex) { Debug.LogWarning((object)("Got unexpected struct internal type " + ex.received + " for field " + ex.field + " struct " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + ex.expected)); continue; } OwnConfigEntryBase value2; if (text == "Internal") { CustomSyncedValueBase value; if (text2 == "serverversion") { if (obj?.ToString() != CurrentVersion) { Debug.LogWarning((object)("Received server version is not equal: server version = " + (obj?.ToString() ?? "null") + "; local version = " + (CurrentVersion ?? "unknown"))); } }