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 OdinsMantle v0.4.6
BepInEx/plugins/OdinsMantle/OdinsMantle.dll
Decompiled 4 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Managers; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("OdinsMantle")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.4.6.0")] [assembly: AssemblyInformationalVersion("0.4.6")] [assembly: AssemblyProduct("OdinsMantle")] [assembly: AssemblyTitle("OdinsMantle")] [assembly: AssemblyVersion("0.4.6.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace OdinsMantle { internal static class FusionData { private const string SourcesKey = "OdinsMantle_FusedCloaks"; private const char Separator = '|'; internal static List<string> GetSources(ItemData item) { if (item == null || item.m_customData == null) { return new List<string>(); } if (!item.m_customData.TryGetValue("OdinsMantle_FusedCloaks", out var value)) { return new List<string>(); } if (string.IsNullOrWhiteSpace(value)) { return new List<string>(); } return (from source in value.Split('|') where !string.IsNullOrWhiteSpace(source) select source).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToList(); } internal static void AddSources(ItemData item, IEnumerable<string> sourceNames) { if (item == null || sourceNames == null) { return; } if (item.m_customData == null) { item.m_customData = new Dictionary<string, string>(); } List<string> sources = GetSources(item); foreach (string sourceName in sourceNames) { if (!string.IsNullOrWhiteSpace(sourceName)) { string text = sourceName.Trim(); if (!text.Contains('|') && !sources.Contains<string>(text, StringComparer.OrdinalIgnoreCase)) { sources.Add(text); } } } item.m_customData["OdinsMantle_FusedCloaks"] = string.Join('|'.ToString(), sources); } internal static bool HasSource(ItemData item, string sourceName) { if (string.IsNullOrWhiteSpace(sourceName)) { return false; } return GetSources(item).Contains<string>(sourceName, StringComparer.OrdinalIgnoreCase); } } [Flags] internal enum MantleEffect { None = 0, FeatherFall = 1, FrostResistance = 2, WildRun = 4, ArmorBonus = 8, AttackStaminaBenefit = 0x10, BlockStaminaBenefit = 0x20, FireWeakness = 0x40, RunStaminaBenefit = 0x80, EitrRegenBenefit = 0x100, DodgeStaminaBenefit = 0x200 } internal sealed class FusionDefinition { public string Name { get; } public string SourceCloakPrefab { get; } public int RequiredStationLevel { get; } public MantleEffect Effects { get; } public bool EnabledForFusion { get; } public string Notes { get; } public FusionDefinition(string name, string sourceCloakPrefab, int requiredStationLevel, MantleEffect effects, bool enabledForFusion, string notes) { Name = name; SourceCloakPrefab = sourceCloakPrefab; RequiredStationLevel = requiredStationLevel; Effects = effects; EnabledForFusion = enabledForFusion; Notes = notes; } } internal static class FusionDefinitions { internal const int FirstFusionStationLevel = 2; internal const int MaximumFusedCloaks = 5; internal static readonly IReadOnlyList<FusionDefinition> Paths = new List<FusionDefinition> { new FusionDefinition("Feather Cape", "CapeFeather", 2, MantleEffect.FeatherFall | MantleEffect.FireWeakness, enabledForFusion: true, "Grants Feather Fall; adds fire weakness unless Asksvin is fused."), new FusionDefinition("Asksvin Cape", "CapeAsksvin", 2, MantleEffect.WildRun, enabledForFusion: true, "Grants Wild Run and cancels Feather Cape fire weakness."), new FusionDefinition("Lox Cape", "CapeLox", 2, MantleEffect.None, enabledForFusion: false, "Disabled: frost resistance is built into Odin's Mantle."), new FusionDefinition("Wolf Fur Cape", "CapeWolf", 2, MantleEffect.None, enabledForFusion: false, "Disabled: frost resistance is built into Odin's Mantle."), new FusionDefinition("Ashen Cape", "CapeAsh", 2, MantleEffect.ArmorBonus | MantleEffect.AttackStaminaBenefit | MantleEffect.BlockStaminaBenefit, enabledForFusion: true, "Grants the shared, non-stacking +11 armor bonus, -10% attack stamina usage, and -20% block stamina usage."), new FusionDefinition("Deer Hide Cape", "CapeDeerHide", 2, MantleEffect.None, enabledForFusion: false, "Catalogued; no fusion effect currently implemented."), new FusionDefinition("Troll Hide Cape", "CapeTrollHide", 2, MantleEffect.None, enabledForFusion: false, "Catalogued; no fusion effect currently implemented."), new FusionDefinition("Linen Cape", "CapeLinen", 2, MantleEffect.None, enabledForFusion: false, "Catalogued; no fusion effect currently implemented."), new FusionDefinition("Moose Cape", "CapeDeepNorth", 2, MantleEffect.ArmorBonus | MantleEffect.AttackStaminaBenefit | MantleEffect.RunStaminaBenefit, enabledForFusion: true, "Grants the shared, non-stacking +11 armor bonus, -20% attack stamina usage, and -20% run stamina usage. Attack stamina reduction uses the strongest fused-cape benefit, not additive stacking."), new FusionDefinition("Cape of the Caller", "CapeDeepNorthMage", 2, MantleEffect.None, enabledForFusion: false, "Disabled in 0.4.6. Intended effects (+11 shared armor, +20% Eitr regeneration, and -10% dodge stamina usage) are future implementation pending further testing.") }; internal static FusionDefinition FindByPrefab(string sourceCloakPrefab) { if (string.IsNullOrWhiteSpace(sourceCloakPrefab)) { return null; } foreach (FusionDefinition path in Paths) { if (string.Equals(path.SourceCloakPrefab, sourceCloakPrefab, StringComparison.OrdinalIgnoreCase)) { return path; } } return null; } } internal static class FusionEffectResolver { internal static MantleEffect Resolve(ItemData mantle) { if (mantle == null) { return MantleEffect.None; } return Resolve((IEnumerable<string>)FusionData.GetSources(mantle)); } internal static MantleEffect Resolve(IEnumerable<string> sourcePrefabs) { MantleEffect mantleEffect = MantleEffect.FrostResistance; bool flag = false; bool flag2 = false; if (sourcePrefabs != null) { foreach (string sourcePrefab in sourcePrefabs) { if (!string.IsNullOrWhiteSpace(sourcePrefab)) { FusionDefinition fusionDefinition = FusionDefinitions.FindByPrefab(sourcePrefab); if (fusionDefinition != null) { mantleEffect |= fusionDefinition.Effects; } if (string.Equals(sourcePrefab, "CapeAsksvin", StringComparison.OrdinalIgnoreCase)) { flag = true; } if (IsSharedArmorBonusSource(sourcePrefab)) { flag2 = true; } } } } mantleEffect = ((!flag2) ? (mantleEffect & ~MantleEffect.ArmorBonus) : (mantleEffect | MantleEffect.ArmorBonus)); if (flag) { mantleEffect &= ~MantleEffect.FireWeakness; } return mantleEffect; } internal static MantleEffect ResolvePreview(ItemData mantle, string incomingCloakPrefab) { List<string> list = ((mantle == null) ? new List<string>() : FusionData.GetSources(mantle)); if (!string.IsNullOrWhiteSpace(incomingCloakPrefab) && !list.Contains<string>(incomingCloakPrefab, StringComparer.OrdinalIgnoreCase)) { list.Add(incomingCloakPrefab); } return Resolve((IEnumerable<string>)list); } internal static bool IsSharedArmorBonusSource(string sourcePrefab) { if (!string.Equals(sourcePrefab, "CapeAsh", StringComparison.OrdinalIgnoreCase)) { return string.Equals(sourcePrefab, "CapeDeepNorth", StringComparison.OrdinalIgnoreCase); } return true; } internal static float GetAttackStaminaMultiplier(ItemData mantle) { List<string> source = ((mantle == null) ? new List<string>() : FusionData.GetSources(mantle)); if (source.Any((string a) => string.Equals(a, "CapeDeepNorth", StringComparison.OrdinalIgnoreCase))) { return 0.8f; } if (source.Any((string a) => string.Equals(a, "CapeAsh", StringComparison.OrdinalIgnoreCase))) { return 0.9f; } return 1f; } internal static bool HasMooseRunStaminaBenefit(ItemData mantle) { if (mantle != null) { return Resolve(mantle).HasFlag(MantleEffect.RunStaminaBenefit); } return false; } internal static bool HasEffect(ItemData mantle, MantleEffect effect) { return (Resolve(mantle) & effect) == effect; } internal static List<string> DescribeEffects(MantleEffect effects) { List<string> list = new List<string>(); if ((effects & MantleEffect.FrostResistance) != MantleEffect.None) { list.Add("Frost resistance: Built in"); } if ((effects & MantleEffect.FeatherFall) != MantleEffect.None) { list.Add("Feather fall: Enabled"); } if ((effects & MantleEffect.WildRun) != MantleEffect.None) { list.Add("Wild Run: Enabled"); } if ((effects & MantleEffect.ArmorBonus) != MantleEffect.None) { list.Add("Shared armor bonus: +11 armor (Ashen or Moose; non-stacking)"); } bool num = (effects & MantleEffect.RunStaminaBenefit) != 0; bool flag = (effects & MantleEffect.BlockStaminaBenefit) != 0; if (num) { list.Add("Attack stamina usage: -20% (strongest cape benefit; does not stack)"); } else if (flag) { list.Add("Attack stamina usage: -10%"); } if (flag) { list.Add("Block stamina usage: -20%"); } if (num) { list.Add("Run stamina usage: -20%"); } if ((effects & MantleEffect.EitrRegenBenefit) != MantleEffect.None) { list.Add("Eitr regeneration: +20%"); } if ((effects & MantleEffect.DodgeStaminaBenefit) != MantleEffect.None) { list.Add("Dodge stamina usage: -10%"); } list.Add(((effects & MantleEffect.FireWeakness) != MantleEffect.None) ? "Fire weakness: Present" : "Fire weakness: Absent"); return list; } } internal static class FusionInterface { private const string WindowName = "OdinsMantleFusionWindow"; private static GameObject window; private static bool initialized; private static GameObject stationText; private static GameObject mantleText; private static GameObject sourceText; private static GameObject fusedText; private static GameObject validationText; private static GameObject effectsTextLeft; private static GameObject effectsTextRight; private static Image mantleIcon; private static Image sourceIcon; private static readonly List<Image> fusedIcons = new List<Image>(); private static readonly List<ItemData> mantleItems = new List<ItemData>(); private static readonly List<ItemData> sourceItems = new List<ItemData>(); private static int mantleIndex; private static int sourceIndex; private static bool previewRequested; private static string lastMessage = "Refresh inventory to begin."; internal static void Initialize() { if (!initialized) { initialized = true; GUIManager.OnCustomGUIAvailable += CreateWindow; if ((Object)(object)GUIManager.CustomGUIFront != (Object)null) { CreateWindow(); } } } internal static void Update() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) if (Plugin.FusionMenuKey != null) { KeyboardShortcut value = Plugin.FusionMenuKey.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { ToggleWindow(); } } } private static void CreateWindow() { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_0380: Unknown result type (might be due to invalid IL or missing references) //IL_03aa: Unknown result type (might be due to invalid IL or missing references) //IL_03d4: Unknown result type (might be due to invalid IL or missing references) //IL_03fe: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)GUIManager.CustomGUIFront == (Object)null) { return; } Transform transform = GUIManager.CustomGUIFront.transform; Transform val = transform.Find("OdinsMantleFusionWindow"); if ((Object)(object)val != (Object)null) { window = ((Component)val).gameObject; window.SetActive(false); return; } window = GUIManager.Instance.CreateWoodpanel(transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, 720f, 610f, false); ((Object)window).name = "OdinsMantleFusionWindow"; window.SetActive(false); CreateLabel("ODIN'S MANTLE", 26, GUIManager.Instance.ValheimOrange, 0f, 265f, 560f, 38f); CreateLabel("CLOAK FUSION", 18, Color.white, 0f, 231f, 560f, 30f); stationText = CreateLabel("", 14, Color.white, 0f, 197f, 620f, 28f); mantleIcon = CreateItemIcon("MantleIcon", -245f, 145f, 44f); mantleText = CreateLabel("", 15, Color.white, 5f, 145f, 430f, 30f); CreateButton("Previous Mantle", -115f, 110f, 160f, 32f, PreviousMantle); CreateButton("Next Mantle", 115f, 110f, 160f, 32f, NextMantle); sourceIcon = CreateItemIcon("SourceCapeIcon", -245f, 60f, 44f); sourceText = CreateLabel("", 15, Color.white, 5f, 60f, 430f, 30f); CreateButton("Previous Source Cape", -115f, 25f, 180f, 32f, PreviousSource); CreateButton("Next Source Cape", 115f, 25f, 180f, 32f, NextSource); CreateButton("Refresh Inventory", -190f, -20f, 170f, 34f, RefreshInventory); CreateButton("Preview Fusion", 0f, -20f, 170f, 34f, PreviewFusion); CreateButton("CONFIRM FUSION", 190f, -20f, 190f, 34f, ConfirmFusion); for (int i = 0; i < 5; i++) { float x = (float)(i - 2) * 78f; fusedIcons.Add(CreateItemIcon("FusedCapeIcon" + i, x, -83f, 36f)); } fusedText = CreateLabel("", 13, Color.white, 0f, -115f, 630f, 28f); validationText = CreateLabel("", 14, Color.white, 0f, -151f, 640f, 42f); effectsTextLeft = CreateLabel("", 12, Color.white, -157f, -207f, 300f, 76f); effectsTextRight = CreateLabel("", 12, Color.white, 157f, -207f, 300f, 76f); CreateButton("Close", 0f, -265f, 150f, 32f, CloseWindow); RefreshInventory(); } private static GameObject CreateLabel(string text, int fontSize, Color color, float x, float y, float width, float height) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) return GUIManager.Instance.CreateText(text, window.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(x, y), GUIManager.Instance.AveriaSerif, fontSize, color, true, Color.black, width, height, false); } private static void CreateButton(string text, float x, float y, float width, float height, Action action) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown Button component = GUIManager.Instance.CreateButton(text, window.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(x, y), width, height).GetComponent<Button>(); if ((Object)(object)component != (Object)null) { ((UnityEvent)component.onClick).AddListener((UnityAction)delegate { action(); }); } } private static Image CreateItemIcon(string objectName, float x, float y, float size) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(objectName, new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(Image) }); val.transform.SetParent(window.transform, false); RectTransform component = val.GetComponent<RectTransform>(); component.anchorMin = new Vector2(0.5f, 0.5f); component.anchorMax = new Vector2(0.5f, 0.5f); component.pivot = new Vector2(0.5f, 0.5f); component.anchoredPosition = new Vector2(x, y); component.sizeDelta = new Vector2(size, size); Image component2 = val.GetComponent<Image>(); component2.preserveAspect = true; ((Graphic)component2).raycastTarget = false; return component2; } private static Sprite GetItemSprite(ItemData item) { if (item == null || item.m_shared == null || item.m_shared.m_icons == null || item.m_shared.m_icons.Length == 0) { return null; } return item.m_shared.m_icons[0]; } private static void SetIcon(Image image, ItemData item) { if (!((Object)(object)image == (Object)null)) { image.sprite = GetItemSprite(item); ((Behaviour)image).enabled = (Object)(object)image.sprite != (Object)null; } } private static void RefreshInventory() { Player localPlayer = Player.m_localPlayer; mantleItems.Clear(); sourceItems.Clear(); previewRequested = false; if ((Object)(object)localPlayer == (Object)null || ((Humanoid)localPlayer).GetInventory() == null) { lastMessage = "Player inventory is not available yet."; RefreshView(); return; } foreach (ItemData allItem in ((Humanoid)localPlayer).GetInventory().GetAllItems()) { string prefabName = FusionTransaction.GetPrefabName(allItem); if (string.Equals(prefabName, "OdinsMantle", StringComparison.OrdinalIgnoreCase)) { mantleItems.Add(allItem); continue; } FusionDefinition fusionDefinition = FusionDefinitions.FindByPrefab(prefabName); if (fusionDefinition != null && fusionDefinition.EnabledForFusion) { sourceItems.Add(allItem); } } mantleIndex = Mathf.Clamp(mantleIndex, 0, Math.Max(0, mantleItems.Count - 1)); sourceIndex = Mathf.Clamp(sourceIndex, 0, Math.Max(0, sourceItems.Count - 1)); lastMessage = "Inventory refreshed."; RefreshView(); } private static CraftingStation GetNearbyGaldrStation() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return null; } CraftingStation currentCraftingStation = localPlayer.GetCurrentCraftingStation(); if ((Object)(object)currentCraftingStation == (Object)null) { return null; } if (!string.Equals(((Object)((Component)currentCraftingStation).gameObject).name.Replace("(Clone)", "").Trim(), "piece_magetable", StringComparison.OrdinalIgnoreCase)) { return null; } return currentCraftingStation; } private static int GetStationLevel() { CraftingStation nearbyGaldrStation = GetNearbyGaldrStation(); if (!((Object)(object)nearbyGaldrStation == (Object)null)) { return nearbyGaldrStation.GetLevel(true); } return 0; } private static ItemData SelectedMantle() { if (mantleIndex < 0 || mantleIndex >= mantleItems.Count) { return null; } return mantleItems[mantleIndex]; } private static ItemData SelectedSource() { if (sourceIndex < 0 || sourceIndex >= sourceItems.Count) { return null; } return sourceItems[sourceIndex]; } private static void PreviousMantle() { if (mantleItems.Count != 0) { mantleIndex = (mantleIndex - 1 + mantleItems.Count) % mantleItems.Count; previewRequested = false; RefreshView(); } } private static void NextMantle() { if (mantleItems.Count != 0) { mantleIndex = (mantleIndex + 1) % mantleItems.Count; previewRequested = false; RefreshView(); } } private static void PreviousSource() { if (sourceItems.Count != 0) { sourceIndex = (sourceIndex - 1 + sourceItems.Count) % sourceItems.Count; previewRequested = false; RefreshView(); } } private static void NextSource() { if (sourceItems.Count != 0) { sourceIndex = (sourceIndex + 1) % sourceItems.Count; previewRequested = false; RefreshView(); } } private static bool ValidateSelection(out string reason) { reason = string.Empty; if ((Object)(object)GetNearbyGaldrStation() == (Object)null) { reason = "Stand near an active Galdr Table to fuse cloaks."; return false; } if (SelectedMantle() == null) { reason = "No Odin's Mantle found in your inventory."; return false; } if (SelectedSource() == null) { reason = "No eligible source cape found in your inventory."; return false; } return FusionRules.CanFuse(SelectedMantle(), FusionTransaction.GetPrefabName(SelectedSource()), GetStationLevel(), out reason); } private static void PreviewFusion() { RefreshInventory(); lastMessage = ((previewRequested = ValidateSelection(out var reason)) ? "Preview ready. Confirm Fusion to consume the selected source cape." : reason); RefreshView(); } private static void ConfirmFusion() { Debug.Log((object)"[OdinsMantle] Confirm Fusion button clicked."); bool flag = previewRequested; try { RefreshInventory(); previewRequested = flag; if (!ValidateSelection(out var reason)) { lastMessage = "Fusion blocked: " + reason; previewRequested = false; RefreshView(); Debug.LogWarning((object)("[OdinsMantle] Fusion blocked: " + reason)); return; } if (!flag) { lastMessage = "Preview the fusion first, then confirm it."; RefreshView(); Debug.LogWarning((object)"[OdinsMantle] Confirmation rejected: no valid preview was recorded."); return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || ((Humanoid)localPlayer).GetInventory() == null) { lastMessage = "Fusion failed: player inventory is unavailable."; previewRequested = false; RefreshView(); Debug.LogError((object)"[OdinsMantle] Confirm Fusion found no local player inventory."); return; } bool num = FusionTransaction.TryFuse(((Humanoid)localPlayer).GetInventory(), SelectedMantle(), SelectedSource(), GetStationLevel(), out reason); previewRequested = false; lastMessage = (num ? "Fusion successful. Odin's Mantle has been upgraded." : ("Fusion failed: " + reason)); Debug.Log((object)((num ? "[OdinsMantle] " : "[OdinsMantle] ERROR: ") + lastMessage)); string text = lastMessage; RefreshInventory(); lastMessage = text; RefreshView(); } catch (Exception ex) { previewRequested = false; lastMessage = "Fusion error: " + ex.GetType().Name + ": " + ex.Message; Debug.LogError((object)("[OdinsMantle] Confirm Fusion threw an exception: " + ex)); RefreshView(); } } private static void RefreshView() { if ((Object)(object)window == (Object)null) { return; } CraftingStation nearbyGaldrStation = GetNearbyGaldrStation(); SetLabel(stationText, ((Object)(object)nearbyGaldrStation == (Object)null) ? "Galdr Table: not detected nearby" : ("Galdr Table Level: " + nearbyGaldrStation.GetLevel(true))); ItemData val = SelectedMantle(); ItemData val2 = SelectedSource(); SetIcon(mantleIcon, val); SetIcon(sourceIcon, val2); SetLabel(mantleText, (val == null) ? "Odin's Mantle: not found in inventory" : ("Odin's Mantle (" + FusionData.GetSources(val).Count + "/" + 5 + " fused)")); SetLabel(sourceText, (val2 == null) ? "Source Cape: not found in inventory" : ("Source Cape: " + GetDisplayName(val2))); List<string> list = ((val == null) ? new List<string>() : FusionData.GetSources(val)); for (int i = 0; i < fusedIcons.Count; i++) { ItemData val3 = null; if (i < list.Count) { string prefab = list[i]; val3 = ((IEnumerable<ItemData>)sourceItems).FirstOrDefault((Func<ItemData, bool>)((ItemData item) => string.Equals(FusionTransaction.GetPrefabName(item), prefab, StringComparison.OrdinalIgnoreCase))); if (val3 == null) { val3 = GetPrefabItemData(prefab); } } SetIcon(fusedIcons[i], val3); } SetLabel(fusedText, (list.Count == 0) ? "Fused Capes: none" : ("Fused Capes: " + string.Join(", ", list.Select(GetCapeName)))); if (previewRequested && val != null && val2 != null) { List<string> list2 = FusionEffectResolver.DescribeEffects(FusionEffectResolver.ResolvePreview(val, FusionTransaction.GetPrefabName(val2))).ToList(); int count = (list2.Count + 1) / 2; SetLabel(effectsTextLeft, "Projected Effects:\n" + string.Join("\n", list2.Take(count))); SetLabel(effectsTextRight, string.Join("\n", list2.Skip(count))); } else { SetLabel(effectsTextLeft, "Projected Effects:\nPreview a valid fusion to see the result."); SetLabel(effectsTextRight, string.Empty); } SetLabel(validationText, lastMessage); } private static ItemData GetPrefabItemData(string prefabName) { if ((Object)(object)ObjectDB.instance == (Object)null) { return null; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(prefabName); if ((Object)(object)itemPrefab == (Object)null) { return null; } ItemDrop component = itemPrefab.GetComponent<ItemDrop>(); if (!((Object)(object)component == (Object)null)) { return component.m_itemData; } return null; } private static string GetDisplayName(ItemData item) { if (item == null || item.m_shared == null) { return "Unknown Cape"; } string name = item.m_shared.m_name; if (string.IsNullOrEmpty(name)) { return "Unknown Cape"; } try { Type type = (from assembly in AppDomain.CurrentDomain.GetAssemblies() select assembly.GetType("Localization", throwOnError: false)).FirstOrDefault((Type type2) => type2 != null); if (type != null) { FieldInfo field = type.GetField("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); object obj = ((field == null) ? null : field.GetValue(null)); if (obj != null) { MethodInfo method = type.GetMethod("Localize", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(string) }, null); if (method != null && method.Invoke(obj, new object[1] { name }) is string text && !string.IsNullOrEmpty(text)) { return text; } } } } catch (Exception ex) { Debug.LogWarning((object)("[OdinsMantle] Could not localize source cape name: " + ex.Message)); } return name; } private static string GetCapeName(string prefab) { FusionDefinition fusionDefinition = FusionDefinitions.FindByPrefab(prefab); if (fusionDefinition != null) { return fusionDefinition.Name; } return prefab; } private static void SetLabel(GameObject labelObject, string value) { if (!((Object)(object)labelObject == (Object)null)) { Text component = labelObject.GetComponent<Text>(); if ((Object)(object)component != (Object)null) { component.text = value; } } } internal static void ToggleWindow() { if ((Object)(object)window == (Object)null) { CreateWindow(); } if (!((Object)(object)window == (Object)null)) { if (window.activeSelf) { CloseWindow(); return; } RefreshInventory(); window.SetActive(true); GUIManager.BlockInput(true); } } private static void CloseWindow() { if (!((Object)(object)window == (Object)null) && window.activeSelf) { window.SetActive(false); GUIManager.BlockInput(false); } } } internal static class FusionRules { internal static int GetMaxSourceCloaksForStationLevel(int stationLevel) { if (stationLevel < 2) { return 0; } return Math.Min(stationLevel, 5); } internal static bool CanFuse(ItemData mantle, string incomingCloakPrefab, int stationLevel, out string reason) { reason = string.Empty; if (mantle == null) { reason = "Odin's Mantle is missing."; return false; } if (string.IsNullOrWhiteSpace(incomingCloakPrefab)) { reason = "The incoming cloak could not be identified."; return false; } FusionDefinition fusionDefinition = FusionDefinitions.FindByPrefab(incomingCloakPrefab); if (fusionDefinition == null) { reason = "This cloak has no fusion definition."; return false; } if (!fusionDefinition.EnabledForFusion) { reason = "Fusion for this cloak is not enabled yet."; return false; } if (stationLevel < 2) { reason = "Upgrade the Galdr Table to level 2 before fusing cloaks."; return false; } List<string> sources = FusionData.GetSources(mantle); if (sources.Contains<string>(incomingCloakPrefab, StringComparer.OrdinalIgnoreCase)) { reason = "This cloak has already been fused into Odin's Mantle."; return false; } int maxSourceCloaksForStationLevel = GetMaxSourceCloaksForStationLevel(stationLevel); if (maxSourceCloaksForStationLevel < 1) { reason = "Cloak fusion is unavailable at this Galdr Table level."; return false; } if (sources.Count >= maxSourceCloaksForStationLevel) { reason = "Odin's Mantle has reached its fusion limit for this Galdr Table level."; return false; } return true; } internal static bool CanFuse(ItemData mantle, string incomingCloakPrefab, int stationLevel, int requiredStationLevel, int maxSourceCloaks, IEnumerable<string> eligibleCloakPrefabs, out string reason) { reason = string.Empty; if (mantle == null) { reason = "Odin's Mantle is missing."; return false; } if (stationLevel < 2) { reason = "Upgrade the Galdr Table to level 2 before fusing cloaks."; return false; } if (string.IsNullOrWhiteSpace(incomingCloakPrefab) || eligibleCloakPrefabs == null || !eligibleCloakPrefabs.Any((string p) => string.Equals(p, incomingCloakPrefab, StringComparison.OrdinalIgnoreCase))) { reason = "This cloak cannot be fused into Odin's Mantle."; return false; } List<string> sources = FusionData.GetSources(mantle); if (sources.Contains<string>(incomingCloakPrefab, StringComparer.OrdinalIgnoreCase)) { reason = "This cloak has already been fused into Odin's Mantle."; return false; } int num = Math.Min(GetMaxSourceCloaksForStationLevel(stationLevel), maxSourceCloaks); if (num < 1 || sources.Count >= num) { reason = "Odin's Mantle has reached its fusion limit for this Galdr Table level."; return false; } return true; } } internal static class FusionTransaction { internal static bool TryFuse(Inventory inventory, ItemData mantle, ItemData sourceCape, int stationLevel, out string reason) { reason = string.Empty; if (inventory == null) { reason = "Player inventory is unavailable."; return false; } if (mantle == null || sourceCape == null) { reason = "Select both Odin's Mantle and a source cape."; return false; } if (mantle == sourceCape) { reason = "Odin's Mantle cannot be used as its own source cape."; return false; } if (!IsPrefab(mantle, "OdinsMantle")) { reason = "The selected mantle item could not be verified."; return false; } string prefabName = GetPrefabName(sourceCape); if (string.IsNullOrWhiteSpace(prefabName)) { reason = "The selected source cape could not be identified."; return false; } if (!inventory.GetAllItems().Contains(mantle) || !inventory.GetAllItems().Contains(sourceCape)) { reason = "The selected items must both be in your inventory."; return false; } if (!FusionRules.CanFuse(mantle, prefabName, stationLevel, out reason)) { return false; } Dictionary<string, string> dictionary = ((mantle.m_customData == null) ? null : new Dictionary<string, string>(mantle.m_customData)); try { FusionData.AddSources(mantle, new string[1] { prefabName }); inventory.RemoveItem(sourceCape); return true; } catch (Exception ex) { if (mantle.m_customData == null) { mantle.m_customData = new Dictionary<string, string>(); } else { mantle.m_customData.Clear(); } if (dictionary != null) { foreach (KeyValuePair<string, string> item in dictionary) { mantle.m_customData[item.Key] = item.Value; } } reason = "Fusion failed and the mantle data was restored: " + ex.Message; return false; } } internal static string GetPrefabName(ItemData item) { if (item == null || (Object)(object)item.m_dropPrefab == (Object)null) { return string.Empty; } return ((Object)item.m_dropPrefab).name.Replace("(Clone)", "").Trim(); } internal static bool IsPrefab(ItemData item, string expectedPrefab) { return string.Equals(GetPrefabName(item), expectedPrefab, StringComparison.OrdinalIgnoreCase); } } internal static class MantleDescription { private const string BaseDescription = "A cloak worthy of one who would walk among the gods."; private const string TooltipMarker = "\n\nFused cloaks:"; internal static string Build(ItemData mantle) { List<string> sources = FusionData.GetSources(mantle); if (sources.Count == 0) { return "A cloak worthy of one who would walk among the gods."; } MantleEffect effects = FusionEffectResolver.Resolve(mantle); IEnumerable<string> values = sources.Select(delegate(string source) { FusionDefinition fusionDefinition = FusionDefinitions.FindByPrefab(source); return (fusionDefinition != null) ? fusionDefinition.Name : source; }); List<string> list = new List<string> { "Fused cloaks: " + string.Join(", ", values), "Fused capabilities:" }; list.AddRange(FusionEffectResolver.DescribeEffects(effects)); return string.Join("\n", list); } internal static void Initialize() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Expected O, but got Unknown Harmony val = new Harmony("com.odinsmantle.valheim.itemtooltip"); MethodInfo method = typeof(MantleDescription).GetMethod("TooltipPostfix", BindingFlags.Static | BindingFlags.NonPublic); int num = 0; MethodInfo[] methods = typeof(ItemData).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "GetTooltip") && !(methodInfo.ReturnType != typeof(string))) { val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; Debug.Log((object)("[OdinsMantle][TooltipDiag] Patched ItemData method: " + methodInfo)); } } Debug.Log((object)("[OdinsMantle][TooltipDiag] Tooltip patch initialized on " + num + " ItemData.GetTooltip method(s).")); if (num == 0) { Debug.LogWarning((object)"[OdinsMantle] Could not find ItemData.GetTooltip(); fused capabilities will not appear in item tooltips."); } } private static void TooltipPostfix(ItemData __instance, ref string __result) { if (!FusionTransaction.IsPrefab(__instance, "OdinsMantle") || FusionData.GetSources(__instance).Count == 0) { return; } string text = __result ?? string.Empty; bool flag = text.Contains("\n\nFused cloaks:"); if (FusionEffectResolver.HasEffect(__instance, MantleEffect.ArmorBonus)) { float displayedArmor = __instance.m_shared.m_armor + (float)Math.Max(0, __instance.m_quality - 1) * __instance.m_shared.m_armorPerLevel + 11f; int replacements = 0; text = Regex.Replace(text, "(?i)(\\$item_armor\\s*:\\s*<color=[^>]+>)[0-9]+(?:\\.[0-9]+)?(</color>)", delegate(Match match) { replacements++; return match.Groups[1].Value + displayedArmor.ToString("0.##") + match.Groups[2].Value; }, RegexOptions.None, TimeSpan.FromMilliseconds(100.0)); Debug.Log((object)("[OdinsMantle][TooltipDiag] Armor token replacement: quality=" + __instance.m_quality + "; baseArmor=" + __instance.m_shared.m_armor + "; armorPerLevel=" + __instance.m_shared.m_armorPerLevel + "; targetDisplayArmor=" + displayedArmor.ToString("0.##") + "; replacements=" + replacements + "; tokenFound=" + text.Contains("$item_armor:"))); if (replacements == 0) { Debug.LogWarning((object)("[OdinsMantle][TooltipDiag] No $item_armor tooltip token matched. Tooltip preview: " + text.Substring(0, Math.Min(500, text.Length)).Replace("\n", "\\n"))); } } if (!flag) { string text2 = Build(__instance); if (!string.IsNullOrWhiteSpace(text2)) { text = text + "\n\n" + text2; } } __result = text; } } internal static class MantleEffectController { private static class SharedArmorBonusPatch { private static string lastDiagnosticState = string.Empty; private static void Postfix(Character __instance, ref float __result) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if ((Object)(object)val == (Object)null) { return; } if (!IsMantleEquipped(val, out var mantle) || mantle == null) { LogDiagnosticState("No Odin's Mantle detected as equipped."); return; } List<string> sources = FusionData.GetSources(mantle); bool flag = sources.Any(FusionEffectResolver.IsSharedArmorBonusSource); bool flag2 = FusionEffectResolver.HasEffect(mantle, MantleEffect.ArmorBonus); float num = __result; if (flag2) { __result += 11f; } string text = ((sources == null || sources.Count == 0) ? "(none)" : string.Join("|", sources.ToArray())); LogDiagnosticState($"Mantle detected; quality={mantle.m_quality}; sources={text}; hasSharedArmorSource={flag}; armorEffect={flag2}; armorBefore={num:0.##}; armorAfter={__result:0.##}"); } private static void LogDiagnosticState(string state) { if (!string.Equals(lastDiagnosticState, state, StringComparison.Ordinal)) { lastDiagnosticState = state; Debug.Log((object)("[OdinsMantle][AshenDiagnostic] " + state)); } } } private static class WildRunPatch { private static void Postfix(Character __instance, ref float __result) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (!((Object)(object)val == (Object)null) && IsMantleEquipped(val, out var mantle) && FusionEffectResolver.HasEffect(mantle, MantleEffect.WildRun)) { float num = ((Plugin.WildRunSpeedBonusPercent != null) ? Plugin.WildRunSpeedBonusPercent.Value : 10f); if (num > 0f) { __result *= 1f + num / 100f; } } } } private static class UpgradeDataPatch { private sealed class UpgradeState { public ItemData OriginalItem; public string Sources; public int OriginalQuality; public Player Player; } private const string SourcesKey = "OdinsMantle_FusedCloaks"; private static readonly FieldInfo UpgradeItemField = typeof(InventoryGui).GetField("m_craftUpgradeItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static void Prefix(InventoryGui __instance, Player player, out UpgradeState __state) { __state = null; if (!((Object)(object)__instance == (Object)null) && !(UpgradeItemField == null)) { object? value = UpgradeItemField.GetValue(__instance); ItemData val = (ItemData)((value is ItemData) ? value : null); if (val != null && FusionTransaction.IsPrefab(val, "OdinsMantle") && val.m_customData != null && val.m_customData.TryGetValue("OdinsMantle_FusedCloaks", out var value2) && !string.IsNullOrWhiteSpace(value2)) { __state = new UpgradeState { OriginalItem = val, Sources = value2, OriginalQuality = val.m_quality, Player = (((Object)(object)player != (Object)null) ? player : Player.m_localPlayer) }; Debug.Log((object)("[OdinsMantle] Captured fusion data before DoCrafting: " + value2 + "; quality=" + val.m_quality)); } } } private static void Postfix(UpgradeState __state) { if (__state == null) { return; } try { Player val = (((Object)(object)__state.Player != (Object)null) ? __state.Player : Player.m_localPlayer); Inventory val2 = (((Object)(object)val != (Object)null) ? ((Humanoid)val).GetInventory() : null); if (val2 == null) { Debug.LogWarning((object)"[OdinsMantle] DoCrafting finished, but player inventory was unavailable for fusion-data restoration."); return; } List<ItemData> allItems = val2.GetAllItems(); ItemData val3 = ((IEnumerable<ItemData>)allItems).FirstOrDefault((Func<ItemData, bool>)((ItemData item) => item == __state.OriginalItem)); if (val3 == null || val3.m_quality <= __state.OriginalQuality) { List<ItemData> list = allItems.Where((ItemData item) => FusionTransaction.IsPrefab(item, "OdinsMantle") && item.m_quality == __state.OriginalQuality + 1).ToList(); if (list.Count == 1) { val3 = list[0]; } } if (val3 == null || val3.m_quality <= __state.OriginalQuality) { Debug.Log((object)"[OdinsMantle] DoCrafting did not produce an upgraded mantle in this call; fusion metadata was left unchanged."); return; } if (val3.m_customData == null) { val3.m_customData = new Dictionary<string, string>(); } val3.m_customData["OdinsMantle_FusedCloaks"] = __state.Sources; Debug.Log((object)("[OdinsMantle] Restored fusion data after DoCrafting: " + __state.Sources + "; quality=" + val3.m_quality)); } catch (Exception ex) { Debug.LogError((object)("[OdinsMantle] Fusion-data restoration after DoCrafting failed: " + ex)); } } } private static class EitrReadDiagnosticPatch { private static string lastState = string.Empty; private static void Postfix(Character __instance, ref float __result) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (!((Object)(object)val == (Object)null) && !((Object)(object)val != (Object)(object)Player.m_localPlayer)) { ItemData mantle; bool flag = IsMantleEquipped(val, out mantle) && FusionEffectResolver.HasEffect(mantle, MantleEffect.EitrRegenBenefit); float f = ((CallerEitrRegenPatch.EitrRegenField != null) ? ((float)CallerEitrRegenPatch.EitrRegenField.GetValue(val)) : float.NaN); string text = "getter=" + ((object)__instance).GetType().Name + "; result=" + __result.ToString("0.####") + "; m_eiterRegen=" + f.ToString("0.####") + "; callerFusionActive=" + flag; if (float.IsNaN(__result) || float.IsInfinity(__result) || float.IsNaN(f) || float.IsInfinity(f)) { text += "; INVALID_VALUE"; } if (!(text == lastState)) { lastState = text; Debug.Log((object)("[OdinsMantle][EitrDiag] " + text)); } } } } private static class CallerEitrRegenPatch { internal static FieldInfo EitrRegenField; private static Player lastPlayer; private static float lastBaseValue; private static float lastAdjustedValue; private static bool hasAppliedBonus; private static void Prefix() { if (!(EitrRegenField == null) && hasAppliedBonus && !((Object)(object)lastPlayer == (Object)null)) { float num = (float)EitrRegenField.GetValue(lastPlayer); float num2 = Math.Max(0.001f, Math.Abs(lastAdjustedValue) * 0.0001f); if (Math.Abs(num - lastAdjustedValue) <= num2) { EitrRegenField.SetValue(lastPlayer, lastBaseValue); DebugCallerState("Eitr field restored before UpdateStats; restored=" + lastBaseValue.ToString("0.####")); } else { DebugCallerState("Eitr field changed externally before restore; current=" + num.ToString("0.####") + "; expectedAdjusted=" + lastAdjustedValue.ToString("0.####") + "; expectedBase=" + lastBaseValue.ToString("0.####")); } hasAppliedBonus = false; lastPlayer = null; } } private static void Postfix(Player __instance) { if (!((Object)(object)__instance == (Object)null) && !(EitrRegenField == null) && IsMantleEquipped(__instance, out var mantle) && FusionEffectResolver.HasEffect(mantle, MantleEffect.EitrRegenBenefit)) { float num = (float)EitrRegenField.GetValue(__instance); if (!(num <= 0f) && !float.IsNaN(num) && !float.IsInfinity(num)) { float num2 = num * 1.2f; EitrRegenField.SetValue(__instance, num2); DebugCallerState("Eitr UpdateStats field observed; base=" + num.ToString("0.####") + "; adjusted=" + num2.ToString("0.####") + "; callerFusionActive=true"); lastPlayer = __instance; lastBaseValue = num; lastAdjustedValue = num2; hasAppliedBonus = true; DebugCallerState("Eitr regeneration +20% active; base=" + num.ToString("0.##") + "; adjusted=" + num2.ToString("0.##")); } } } } private static class CallerDodgeStaminaPatch { private static void Postfix(Humanoid __instance, ref float __result) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (!((Object)(object)val == (Object)null) && !((Object)(object)val != (Object)(object)Player.m_localPlayer) && !(__result <= 0f) && IsMantleEquipped(val, out var mantle) && FusionEffectResolver.HasEffect(mantle, MantleEffect.DodgeStaminaBenefit)) { float num = __result; __result *= 0.9f; DebugCallerState("Dodge stamina -10% active; base=" + num.ToString("0.##") + "; adjusted=" + __result.ToString("0.##")); } } } [HarmonyPatch(typeof(Attack), "GetAttackStamina", new Type[] { })] private static class AshenAttackStaminaPatch { private static void Postfix(Attack __instance, Humanoid ___m_character, ref float __result) { if (__result <= 0f || (Object)(object)___m_character == (Object)null || (Object)(object)___m_character != (Object)(object)Player.m_localPlayer) { return; } Player val = (Player)(object)((___m_character is Player) ? ___m_character : null); if (!((Object)(object)val == (Object)null) && IsMantleEquipped(val, out var mantle) && FusionEffectResolver.HasEffect(mantle, MantleEffect.AttackStaminaBenefit)) { float attackStaminaMultiplier = FusionEffectResolver.GetAttackStaminaMultiplier(mantle); if (attackStaminaMultiplier < 1f) { __result *= attackStaminaMultiplier; } } } } [HarmonyPatch(typeof(Player), "UpdateStats", new Type[] { typeof(float) })] private static class MooseRunStaminaPatch { private static string lastDiagnosticState = string.Empty; private static void Postfix(Player __instance, ref float ___m_runStaminaDrain) { if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && IsMantleEquipped(__instance, out var mantle) && FusionEffectResolver.HasMooseRunStaminaBenefit(mantle)) { float num = ___m_runStaminaDrain; ___m_runStaminaDrain *= 0.8f; string text = $"Moose run stamina reduction active; baseDrain={num:0.##}; adjustedDrain={___m_runStaminaDrain:0.##}"; if (!string.Equals(lastDiagnosticState, text, StringComparison.Ordinal)) { lastDiagnosticState = text; Debug.Log((object)("[OdinsMantle][MooseDiag] " + text)); } } } } [HarmonyPatch(typeof(Humanoid), "BlockAttack")] private static class AshenBlockStaminaPatch { private static void Prefix(Humanoid __instance, ref float __state) { __state = 0f; if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (!((Object)(object)val == (Object)null) && IsMantleEquipped(val, out var mantle) && FusionEffectResolver.HasEffect(mantle, MantleEffect.BlockStaminaBenefit)) { __state = __instance.m_blockStaminaDrain; __instance.m_blockStaminaDrain *= 0.8f; } } } private static void Postfix(Humanoid __instance, float __state) { if ((Object)(object)__instance != (Object)null && __state > 0f) { __instance.m_blockStaminaDrain = __state; } } } [HarmonyPatch(typeof(Character), "Damage", new Type[] { typeof(HitData) })] private static class FireWeaknessPatch { private static void Prefix(Character __instance, HitData hit) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (!((Object)(object)val == (Object)null) && hit != null && IsMantleEquipped(val, out var mantle) && HasFireWeakness(mantle)) { hit.m_damage.m_fire *= 1.5f; } } } private const string SlowFallEffect = "SlowFall"; private const string MantleFrostEffect = "SetEffect_WolfArmor"; private static readonly int SlowFallHash = StableHashCode("SlowFall"); private static readonly int MantleFrostHash = StableHashCode("SetEffect_WolfArmor"); private static bool mantleWasEquipped; private static bool warnedSlowFall; private static bool warnedMantleFrost; private static string lastEffectDiagnosticState = string.Empty; private static string lastCallerDiagnosticState = string.Empty; private static int StableHashCode(string value) { int num = 5381; int num2 = num; for (int i = 0; i < value.Length && value[i] != 0; i += 2) { num = ((num << 5) + num) ^ value[i]; if (i == value.Length - 1 || value[i + 1] == '\0') { break; } num2 = ((num2 << 5) + num2) ^ value[i + 1]; } return num + num2 * 1566083941; } internal static void Initialize() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_0042: Expected O, but got Unknown Harmony val = new Harmony("com.odinsmantle.valheim.effects"); val.PatchAll(typeof(MantleEffectController).Assembly); TryInstallWildRunPatch(val); TryInstallArmorPatch(val); TryInstallUpgradeDataPatch(val); TryInstallCallerEitrPatch(val); TryInstallEitrReadDiagnostics(val); TryInstallCallerDodgePatch(val); } private static void TryInstallArmorPatch(Harmony harmony) { //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Expected O, but got Unknown Type[] array = new Type[2] { typeof(Player), typeof(Character) }; for (int i = 0; i < array.Length; i++) { MethodInfo method = array[i].GetMethod("GetBodyArmor", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (!(method == null) && !(method.ReturnType != typeof(float))) { MethodInfo method2 = typeof(SharedArmorBonusPatch).GetMethod("Postfix", BindingFlags.Static | BindingFlags.NonPublic); if (!(method2 == null)) { harmony.Patch((MethodBase)method, (HarmonyMethod)null, new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Debug.Log((object)("[OdinsMantle] Shared armor bonus patched " + method.DeclaringType.FullName + ".GetBodyArmor().")); return; } } } Debug.LogWarning((object)"[OdinsMantle] Shared armor bonus unavailable: no compatible GetBodyArmor() method was found. Other mantle effects remain enabled."); } private static void TryInstallWildRunPatch(Harmony harmony) { //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Expected O, but got Unknown Type[] array = new Type[2] { typeof(Player), typeof(Character) }; for (int i = 0; i < array.Length; i++) { MethodInfo method = array[i].GetMethod("GetRunSpeedFactor", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (!(method == null) && !(method.ReturnType != typeof(float))) { MethodInfo method2 = typeof(WildRunPatch).GetMethod("Postfix", BindingFlags.Static | BindingFlags.NonPublic); if (!(method2 == null)) { harmony.Patch((MethodBase)method, (HarmonyMethod)null, new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Debug.Log((object)("[OdinsMantle] Wild Run patched " + method.DeclaringType.FullName + ".GetRunSpeedFactor().")); return; } } } Debug.LogWarning((object)"[OdinsMantle] Wild Run is unavailable on this Valheim build: no compatible GetRunSpeedFactor() method was found. Other mantle effects remain enabled."); } internal static void Update() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || ((Humanoid)localPlayer).GetInventory() == null || ((Character)localPlayer).GetSEMan() == null) { return; } ItemData val = ((IEnumerable<ItemData>)((Humanoid)localPlayer).GetInventory().GetEquippedItems()).FirstOrDefault((Func<ItemData, bool>)((ItemData item) => FusionTransaction.IsPrefab(item, "OdinsMantle"))); if (val == null) { if (mantleWasEquipped) { ((Character)localPlayer).GetSEMan().RemoveStatusEffect(SlowFallHash, false); ((Character)localPlayer).GetSEMan().RemoveStatusEffect(MantleFrostHash, false); } mantleWasEquipped = false; return; } mantleWasEquipped = true; List<string> sources = FusionData.GetSources(val); MantleEffect mantleEffect = FusionEffectResolver.Resolve(val); bool flag = (mantleEffect & MantleEffect.FeatherFall) != 0; string text = string.Format("quality={0}; sources={1}; effects={2}; wantsFeatherFall={3}; slowFallStatusExists={4}; slowFallActive={5}", val.m_quality, (sources.Count == 0) ? "(none)" : string.Join("|", sources.ToArray()), mantleEffect, flag, (Object)(object)ObjectDB.instance != (Object)null && (Object)(object)ObjectDB.instance.GetStatusEffect(SlowFallHash) != (Object)null, ((Character)localPlayer).GetSEMan().HaveStatusEffect(SlowFallHash)); if (!string.Equals(text, lastEffectDiagnosticState, StringComparison.Ordinal)) { Debug.Log((object)("[OdinsMantle][EffectDiag] " + text)); lastEffectDiagnosticState = text; } if (flag) { EnsureEffect(localPlayer, SlowFallHash, "SlowFall", ref warnedSlowFall); } else { ((Character)localPlayer).GetSEMan().RemoveStatusEffect(SlowFallHash, false); } if ((mantleEffect & MantleEffect.FrostResistance) != MantleEffect.None) { EnsureEffect(localPlayer, MantleFrostHash, "SetEffect_WolfArmor", ref warnedMantleFrost); } else { ((Character)localPlayer).GetSEMan().RemoveStatusEffect(MantleFrostHash, false); } } private static void EnsureEffect(Player player, int hash, string effectName, ref bool warned) { if (((Character)player).GetSEMan().HaveStatusEffect(hash)) { return; } if ((Object)(object)ObjectDB.instance == (Object)null || (Object)(object)ObjectDB.instance.GetStatusEffect(hash) == (Object)null) { if (!warned) { Debug.LogWarning((object)("[OdinsMantle] Could not find vanilla status effect '" + effectName + "'.")); warned = true; } } else { ((Character)player).GetSEMan().AddStatusEffect(hash, false, 0, 0f, (short)(-1)); } } internal static bool IsMantleEquipped(Player player, out ItemData mantle) { mantle = null; if ((Object)(object)player == (Object)null || ((Humanoid)player).GetInventory() == null) { return false; } mantle = ((IEnumerable<ItemData>)((Humanoid)player).GetInventory().GetEquippedItems()).FirstOrDefault((Func<ItemData, bool>)((ItemData item) => FusionTransaction.IsPrefab(item, "OdinsMantle"))); return mantle != null; } internal static bool HasFireWeakness(ItemData mantle) { return (FusionEffectResolver.Resolve(mantle) & MantleEffect.FireWeakness) != 0; } private static void TryInstallUpgradeDataPatch(Harmony harmony) { //IL_008a: 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) //IL_009d: Expected O, but got Unknown //IL_009d: Expected O, but got Unknown BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; MethodInfo method = typeof(InventoryGui).GetMethod("DoCrafting", bindingAttr, null, new Type[1] { typeof(Player) }, null); if (method == null || method.ReturnType != typeof(void)) { Debug.LogWarning((object)"[OdinsMantle] Could not find InventoryGui.DoCrafting(Player); fusion-data preservation patch was not installed."); return; } MethodInfo method2 = typeof(UpgradeDataPatch).GetMethod("Prefix", BindingFlags.Static | BindingFlags.NonPublic); MethodInfo method3 = typeof(UpgradeDataPatch).GetMethod("Postfix", BindingFlags.Static | BindingFlags.NonPublic); harmony.Patch((MethodBase)method, new HarmonyMethod(method2), new HarmonyMethod(method3), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Debug.Log((object)"[OdinsMantle] Fusion-data preservation patched InventoryGui.DoCrafting(Player)."); } private static void TryInstallCallerEitrPatch(Harmony harmony) { //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Expected O, but got Unknown //IL_00d4: Expected O, but got Unknown BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; MethodInfo method = typeof(Player).GetMethod("UpdateStats", bindingAttr, null, new Type[1] { typeof(float) }, null); FieldInfo fieldInfo = FindInstanceField(typeof(Player), "m_eiterRegen"); if (method == null || method.ReturnType != typeof(void) || fieldInfo == null || fieldInfo.FieldType != typeof(float)) { Debug.LogWarning((object)"[OdinsMantle] Caller Eitr regeneration patch not installed: Player.UpdateStats(float) or Player.m_eiterRegen was not found. Other effects remain enabled."); return; } MethodInfo method2 = typeof(CallerEitrRegenPatch).GetMethod("Prefix", BindingFlags.Static | BindingFlags.NonPublic); MethodInfo method3 = typeof(CallerEitrRegenPatch).GetMethod("Postfix", BindingFlags.Static | BindingFlags.NonPublic); harmony.Patch((MethodBase)method, new HarmonyMethod(method2), new HarmonyMethod(method3), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); CallerEitrRegenPatch.EitrRegenField = fieldInfo; Debug.Log((object)("[OdinsMantle] Caller Eitr regeneration patched Player.UpdateStats(float) using " + fieldInfo.DeclaringType.FullName + "." + fieldInfo.Name + " (prevents repeated multiplier application).")); } private static void TryInstallEitrReadDiagnostics(Harmony harmony) { //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Expected O, but got Unknown BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; Type[] array = new Type[3] { typeof(Player), typeof(Character), typeof(Humanoid) }; string[] array2 = new string[5] { "GetEitrRegen", "GetEitrRegenRate", "GetEitrRegenPerSecond", "GetEitrRegenMultiplier", "GetEitrRegenModifier" }; HashSet<MethodBase> hashSet = new HashSet<MethodBase>(); MethodInfo method = typeof(EitrReadDiagnosticPatch).GetMethod("Postfix", BindingFlags.Static | BindingFlags.NonPublic); int num = 0; Type[] array3 = array; foreach (Type type in array3) { string[] array4 = array2; foreach (string name in array4) { MethodInfo method2 = type.GetMethod(name, bindingAttr, null, Type.EmptyTypes, null); if (!(method2 == null) && !(method2.ReturnType != typeof(float)) && !method2.IsAbstract && !method2.ContainsGenericParameters && hashSet.Add(method2)) { harmony.Patch((MethodBase)method2, (HarmonyMethod)null, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Debug.Log((object)("[OdinsMantle][EitrDiag] Observing " + method2.DeclaringType.FullName + "." + method2.Name + "().")); num++; } } } if (num == 0) { Debug.LogWarning((object)"[OdinsMantle][EitrDiag] No known Eitr-regeneration getter found. The field diagnostics still run; inspect the game assembly for the consumer method."); } } private static void TryInstallCallerDodgePatch(Harmony harmony) { //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected O, but got Unknown BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; Type[] array = new Type[3] { typeof(Humanoid), typeof(Player), typeof(Character) }; for (int i = 0; i < array.Length; i++) { MethodInfo method = array[i].GetMethod("GetDodgeStaminaUse", bindingAttr, null, Type.EmptyTypes, null); if (!(method == null) && !(method.ReturnType != typeof(float))) { MethodInfo method2 = typeof(CallerDodgeStaminaPatch).GetMethod("Postfix", BindingFlags.Static | BindingFlags.NonPublic); harmony.Patch((MethodBase)method, (HarmonyMethod)null, new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Debug.Log((object)("[OdinsMantle] Caller dodge stamina patched " + method.DeclaringType.FullName + ".GetDodgeStaminaUse().")); return; } } Debug.LogWarning((object)"[OdinsMantle] Caller dodge stamina patch not installed: no compatible GetDodgeStaminaUse() method was found. Other effects remain enabled."); } private static FieldInfo FindInstanceField(Type type, string fieldName) { BindingFlags bindingAttr = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; Type type2 = type; while (type2 != null) { FieldInfo field = type2.GetField(fieldName, bindingAttr); if (field != null) { return field; } type2 = type2.BaseType; } return null; } private static void DebugCallerState(string state) { if (!string.Equals(lastCallerDiagnosticState, state, StringComparison.Ordinal)) { lastCallerDiagnosticState = state; Debug.Log((object)("[OdinsMantle][CallerDiag] " + state)); } } } [BepInPlugin("com.odinsmantle.valheim", "Odin's Mantle", "0.4.6")] public class Plugin : BaseUnityPlugin { internal static ConfigEntry<KeyboardShortcut> FusionMenuKey { get; private set; } internal static ConfigEntry<float> WildRunSpeedBonusPercent { get; private set; } private void Awake() { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Expected O, but got Unknown //IL_008b: 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) ((BaseUnityPlugin)this).Logger.LogInfo((object)"Odin's Mantle is starting."); FusionMenuKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Controls", "FusionMenuKey", new KeyboardShortcut((KeyCode)287, Array.Empty<KeyCode>()), "Key to open or close the Odin's Mantle fusion menu."); WildRunSpeedBonusPercent = ((BaseUnityPlugin)this).Config.Bind<float>("Wild Run", "SpeedBonusPercent", 10f, new ConfigDescription("Run-speed bonus percentage while Odin's Mantle is equipped and Asksvin is fused. Set to 0 to disable Wild Run.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 50f), Array.Empty<object>())); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Fusion menu key: " + ((object)FusionMenuKey.Value/*cast due to .constrained prefix*/).ToString())); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Wild Run speed bonus: " + WildRunSpeedBonusPercent.Value + "%")); PrefabManager.OnVanillaPrefabsAvailable += AddMantle; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Waiting for vanilla prefabs."); FusionInterface.Initialize(); MantleDescription.Initialize(); MantleEffectController.Initialize(); } private void Update() { FusionInterface.Update(); MantleEffectController.Update(); } private void AddMantle() { //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Expected O, but got Unknown //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Expected O, but got Unknown try { GameObject prefab = PrefabManager.Instance.GetPrefab("CapeOdin"); ((BaseUnityPlugin)this).Logger.LogInfo((object)("CapeOdin prefab available: " + ((Object)(object)prefab != (Object)null))); GameObject val = PrefabManager.Instance.CreateClonedPrefab("OdinsMantle", "CapeOdin"); if ((Object)(object)val == (Object)null) { ((BaseUnityPlugin)this).Logger.LogError((object)"Failed to clone the CapeOdin prefab."); return; } ItemDrop component = val.GetComponent<ItemDrop>(); if ((Object)(object)component != (Object)null && component.m_itemData != null && component.m_itemData.m_shared != null) { component.m_itemData.m_shared.m_dlc = ""; component.m_itemData.m_shared.m_armorPerLevel = 2f; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Cleared inherited DLC requirement on Odin's Mantle."); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find item data on the mantle prefab."); } ItemConfig val2 = new ItemConfig { Name = "Odin's Mantle", Description = "A cloak worthy of one who would walk among the gods.", CraftingStation = "piece_magetable", MinStationLevel = 2 }; val2.AddRequirement("Silver", 5, 1); val2.AddRequirement("Eitr", 5, 1); CustomItem val3 = new CustomItem(val, true, val2); if (ItemManager.Instance.AddItem(val3)) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Odin's Mantle item registered."); LogCapePrefabs(); } else { ((BaseUnityPlugin)this).Logger.LogError((object)"Jotunn did not register Odin's Mantle."); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Failed to register Odin's Mantle: " + ex)); } finally { PrefabManager.OnVanillaPrefabsAvailable -= AddMantle; } } private void LogCapePrefabs() { string[] obj = new string[11] { "CapeOdin", "CapeDeerHide", "CapeTrollHide", "CapeWolf", "CapeLox", "CapeLinen", "CapeFeather", "CapeAsksvin", "CapeAsh", "CapeDeepNorth", "CapeDeepNorthMage" }; ((BaseUnityPlugin)this).Logger.LogInfo((object)"=== Odin's Mantle: Jotunn Cloak Prefab Scan ==="); string[] array = obj; foreach (string text in array) { GameObject prefab = PrefabManager.Instance.GetPrefab(text); if ((Object)(object)prefab == (Object)null) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("NOT FOUND: " + text)); continue; } string text2 = "(unknown)"; ItemDrop component = prefab.GetComponent<ItemDrop>(); if ((Object)(object)component != (Object)null && component.m_itemData != null && component.m_itemData.m_shared != null) { text2 = component.m_itemData.m_shared.m_name; } ((BaseUnityPlugin)this).Logger.LogInfo((object)("FOUND: " + text + " | Display name: " + text2)); } ((BaseUnityPlugin)this).Logger.LogInfo((object)"=== End Jotunn Cloak Prefab Scan ==="); } private void OnDestroy() { PrefabManager.OnVanillaPrefabsAvailable -= AddMantle; } } }