From e1a15adad64051cd5a0c21dfd71202d3e122facd Mon Sep 17 00:00:00 2001 From: mikx Date: Wed, 11 Feb 2026 22:44:05 -0500 Subject: [PATCH 1/5] (1.5.6) Day/Time UI + New AutoDoor Logic --- MxValheim/KillFeed/Patch.cs | 2 +- MxValheim/MxValheim.cs | 65 ++++++++++++++++++++++-- MxValheim/MxValheim.csproj | 2 + MxValheim/Patch/Doors.cs | 41 ++++++++------- MxValheim/Patch/HUD/Clock.cs | 96 +++++++++++++++++++++++++++++++++++ MxValheim/Patch/HUD/Splash.cs | 75 +++++++++++++++++++++++++++ README.md | 1 + 7 files changed, 259 insertions(+), 23 deletions(-) create mode 100644 MxValheim/Patch/HUD/Clock.cs create mode 100644 MxValheim/Patch/HUD/Splash.cs diff --git a/MxValheim/KillFeed/Patch.cs b/MxValheim/KillFeed/Patch.cs index 76e4cc0..ccefeff 100644 --- a/MxValheim/KillFeed/Patch.cs +++ b/MxValheim/KillFeed/Patch.cs @@ -290,7 +290,7 @@ namespace MxValheim.KillFeed _activeTrackers.Remove(__instance); } } - } + } private void CreateCustomHud() { diff --git a/MxValheim/MxValheim.cs b/MxValheim/MxValheim.cs index 9cc55a6..e78bc02 100644 --- a/MxValheim/MxValheim.cs +++ b/MxValheim/MxValheim.cs @@ -2,6 +2,8 @@ using BepInEx.Configuration; using HarmonyLib; using MxValheim.KillFeed; +using MxValheim.Patch; +using MxValheim.Patch.HUD; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -19,10 +21,11 @@ public class MxValheimMod : BaseUnityPlugin { public static MxValheimMod Instance; // Singleton reference public static KillFeed_Patch kfp = new KillFeed_Patch(); + public static Doors_Patch dpatch = new Doors_Patch(); private const string ModGUID = "ovh.mxdev.mxvalheim"; private const string ModName = "MxValheim"; - private const string ModVersion = "1.5.5"; + public const string ModVersion = "1.5.6"; public static ConfigEntry Config_Locked; public static ConfigEntry Config_OreMultiplier; @@ -64,6 +67,11 @@ public class MxValheimMod : BaseUnityPlugin public static readonly Queue _victimIconQueue = new Queue(); public static readonly Dictionary _activeTrackers = new Dictionary(); + public static readonly Queue _doorQueueNview = new Queue(); + public static readonly Queue _doorQueueDoor = new Queue(); + public static readonly Queue _doorQueueName = new Queue(); + public static float _doorTimer; + void Awake() { Instance = this; @@ -79,6 +87,8 @@ public class MxValheimMod : BaseUnityPlugin LoadLocalization(); LoadJsonConfig(); + _doorTimer = MxValheimMod.Config_autoDoorClose.Value; + Harmony harmony = new Harmony(ModGUID); harmony.PatchAll(); } @@ -134,7 +144,54 @@ public class MxValheimMod : BaseUnityPlugin void Update() { - // Use the game's native check for dedicated servers + // Only run if we are actually in the game world + if (Player.m_localPlayer != null) + { + if (Splash.m_canvasObject == null) + { + Splash spl = new Splash(); + spl.CreateOverlay(); + } + + if (Splash.m_textComponent != null) + { + Splash.m_textComponent.text = $"MxValheim {MxValheimMod.ModVersion}"; + } + + if (Clock.m_canvasObject == null) + { + Clock clk = new Clock(); + clk.CreateOverlay(); + } + + if (Clock.m_textComponent != null) + { + Clock clk = new Clock(); + string timeString = clk.GetFormattedTime(); + int day = EnvMan.instance.GetDay(); + Clock.m_textComponent.text = $"{Localization.instance.Localize("$clock_string_day")} {day} | {timeString}"; + } + } + + // Door Logic + if (_doorQueueNview.Count > 0) + { + if (_doorTimer > 0) + { + _doorTimer -= Time.deltaTime; + } + + if (_doorTimer <= 0) + { + dpatch.CloseNextDoor(_doorQueueDoor.Dequeue(), _doorQueueNview.Dequeue(), _doorQueueName.Dequeue()); + _doorTimer = MxValheimMod.Config_autoDoorClose.Value; + } + } + // Door Logic + ///////////// + + + // KillFeed Logic ZNet zn = new ZNet(); if (zn.IsDedicated() || Player.m_localPlayer == null) return; @@ -173,8 +230,10 @@ public class MxValheimMod : BaseUnityPlugin if (_displayTimer <= 0 && _hudRoot != null) _hudRoot.SetActive(false); } + // KillFeed Logic + ///////////////// - } + } public static void LoadLocalization() { diff --git a/MxValheim/MxValheim.csproj b/MxValheim/MxValheim.csproj index 9418bca..00b0d14 100644 --- a/MxValheim/MxValheim.csproj +++ b/MxValheim/MxValheim.csproj @@ -91,6 +91,8 @@ + + diff --git a/MxValheim/Patch/Doors.cs b/MxValheim/Patch/Doors.cs index 0de84af..a9141cf 100644 --- a/MxValheim/Patch/Doors.cs +++ b/MxValheim/Patch/Doors.cs @@ -3,18 +3,18 @@ using HarmonyLib; using System.Collections; using UnityEngine; using UnityEngine.Diagnostics; +using static MxValheimMod; namespace MxValheim.Patch { - internal class Doors + public class Doors_Patch { [HarmonyPatch(typeof(Door), nameof(Door.Interact))] - static class Door_Interact_Patch + public static class DoorTracker { static void Postfix(Door __instance, Humanoid character, bool hold, bool alt, ZNetView ___m_nview) { ZNetView nview = __instance.GetComponent(); - if (nview != null && nview.IsValid()) { // Get the prefab hash and look up the name in the master list @@ -28,28 +28,31 @@ namespace MxValheim.Patch // Get state: 0 is closed int state = ___m_nview.GetZDO().GetInt("state"); - if (state != 0) + if (state == 0) return; + + lock (_doorQueueNview) { - // Start coroutine on the door object itself - __instance.StartCoroutine(CloseDoorAfterDelay(__instance, ___m_nview)); + Debug.Log($"AutoDoor:Door.Interact Adding \"{prefabName}\" to queue."); + _doorQueueNview.Enqueue(nview); + _doorQueueDoor.Enqueue(__instance); + _doorQueueName.Enqueue(prefabName); } - } + } } + } - static IEnumerator CloseDoorAfterDelay(Door door, ZNetView nview) + public void CloseNextDoor(Door door, ZNetView nview, string doorName) + { + if (door != null && nview != null && nview.IsValid()) { - yield return new WaitForSeconds(MxValheimMod.Config_autoDoorClose.Value); - - // Verify the door still exists and is still open before closing - if (door != null && nview != null && nview.IsValid()) + if (nview.GetZDO().GetInt("state") != 0) { - if (nview.GetZDO().GetInt("state") != 0) - { - // Directly invoke the RPC with '0' (closed). - // This avoids the 'Null' player error in Door.Interact - ZDO zd0 = nview.GetZDO(); - zd0.Set("state", 0); - } + Debug.Log($"AutoDoor:CloseNextDoor Closing door \"{doorName}\"."); + ZDO zd0 = nview.GetZDO(); + zd0.Set("state", 0); + } else + { + Debug.Log($"AutoDoor:CloseNextDoor Door \"{doorName}\" was already closed."); } } } diff --git a/MxValheim/Patch/HUD/Clock.cs b/MxValheim/Patch/HUD/Clock.cs new file mode 100644 index 0000000..81ead0f --- /dev/null +++ b/MxValheim/Patch/HUD/Clock.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.UI; + +namespace MxValheim.Patch.HUD +{ + internal class Clock + { + public static GameObject m_canvasObject; + public static Text m_textComponent; + + // This replaces the missing GetTimeString() method + public string GetFormattedTime() + { + // Get fractional day (0.0 to 1.0) + float fraction = EnvMan.instance.GetDayFraction(); + + // Convert to hours and minutes + float totalHours = fraction * 24f; + int hours = Mathf.FloorToInt(totalHours); + int minutes = Mathf.FloorToInt((totalHours - hours) * 60f); + + return $"{hours:00}:{minutes:00}"; + } + + public void CreateOverlay() + { + // 1. Root Canvas + m_canvasObject = new GameObject("ModdedCanvas"); + Canvas canvas = m_canvasObject.AddComponent(); + canvas.renderMode = RenderMode.ScreenSpaceOverlay; + canvas.sortingOrder = 999; + + // 2. Background Panel + GameObject panelObj = new GameObject("TextBackground"); + panelObj.transform.SetParent(m_canvasObject.transform, false); + + Image panelImage = panelObj.AddComponent(); + panelImage.color = new Color(0, 0, 0, 0.5f); // 50% transparent black + + RectTransform panelRect = panelObj.GetComponent(); + panelRect.anchorMin = new Vector2(0.5f, 1.0f); // Top Center + panelRect.anchorMax = new Vector2(0.5f, 1.0f); + panelRect.pivot = new Vector2(0.5f, 1.0f); + panelRect.anchoredPosition = new Vector2(0, -5); // 5px gap from top + panelRect.sizeDelta = new Vector2(180, 35); // Width and Height of the bar + + // 3. Text (Attached to Panel) + GameObject textObj = new GameObject("ModdedText"); + textObj.transform.SetParent(panelObj.transform, false); + + m_textComponent = textObj.AddComponent(); + // Attempt to find Valheim's specific font, fallback to Arial if not found + Font valheimFont = null; + foreach (Font f in Resources.FindObjectsOfTypeAll()) + { + if (f.name == "AveriaSerifLibre-Bold") + { + valheimFont = f; + break; + } + } + + // Corrected fallback for older Unity versions used in Valheim + if (valheimFont == null) + { + valheimFont = Resources.GetBuiltinResource("Arial.ttf"); + } + + m_textComponent.font = valheimFont; + m_textComponent.fontSize = 18; + m_textComponent.color = Color.white; + m_textComponent.alignment = TextAnchor.MiddleCenter; + m_textComponent.supportRichText = true; + + // Add Shadow so it's visible in snow + var shadow = textObj.AddComponent(); + shadow.effectColor = Color.black; + shadow.effectDistance = new Vector2(2, 2); + + // FIX: Correct way to make text fill the parent panel + RectTransform textRect = textObj.GetComponent(); + textRect.anchorMin = Vector2.zero; // (0, 0) + textRect.anchorMax = Vector2.one; // (1, 1) + textRect.pivot = new Vector2(0.5f, 0.5f); + + // Resetting these to zero ensures the text box matches the panel exactly + textRect.offsetMin = Vector2.zero; + textRect.offsetMax = Vector2.zero; + } + } +} diff --git a/MxValheim/Patch/HUD/Splash.cs b/MxValheim/Patch/HUD/Splash.cs new file mode 100644 index 0000000..9d72a1b --- /dev/null +++ b/MxValheim/Patch/HUD/Splash.cs @@ -0,0 +1,75 @@ +using HarmonyLib; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.UI; + +namespace MxValheim.Patch.HUD +{ + internal class Splash + { + public static GameObject m_canvasObject; + public static Text m_textComponent; + + public void CreateOverlay() + { + // 1. Create a dedicated Canvas for our mod + m_canvasObject = new GameObject("ModdedCanvas"); + Canvas canvas = m_canvasObject.AddComponent(); + canvas.renderMode = RenderMode.ScreenSpaceOverlay; // Always stays on top + canvas.sortingOrder = 100; // Higher than standard HUD + + m_canvasObject.AddComponent(); + m_canvasObject.AddComponent(); + + // 2. Create the Text Object + GameObject textObj = new GameObject("ModdedText"); + textObj.transform.SetParent(m_canvasObject.transform, false); + + m_textComponent = textObj.AddComponent(); + + // Attempt to find Valheim's specific font, fallback to Arial if not found + Font valheimFont = null; + foreach (Font f in Resources.FindObjectsOfTypeAll()) + { + if (f.name == "AveriaSerifLibre-Bold") + { + valheimFont = f; + break; + } + } + + // Corrected fallback for older Unity versions used in Valheim + if (valheimFont == null) + { + valheimFont = Resources.GetBuiltinResource("Arial.ttf"); + } + + m_textComponent.font = valheimFont; + m_textComponent.fontSize = 24; + m_textComponent.color = Color.yellow; // Bright yellow to stand out + m_textComponent.alignment = TextAnchor.UpperRight; + m_textComponent.supportRichText = true; + + // This prevents the text from vanishing if the box is too small + m_textComponent.horizontalOverflow = HorizontalWrapMode.Overflow; + m_textComponent.verticalOverflow = VerticalWrapMode.Overflow; + + // Add Shadow so it's visible in snow + var shadow = textObj.AddComponent(); + shadow.effectColor = Color.black; + shadow.effectDistance = new Vector2(2, 2); + + // 3. Position it + RectTransform rect = textObj.GetComponent(); + rect.anchorMin = new Vector2(1, 1); + rect.anchorMax = new Vector2(1, 1); + rect.pivot = new Vector2(1, 1); + rect.anchoredPosition = new Vector2(-10, -5); // Top Right + rect.sizeDelta = new Vector2(300, 100); + } + } +} diff --git a/README.md b/README.md index 8e1124c..292c6a3 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ Official **MxValheim Server** Mod. **This mod is created to be used Client/Server side. A lot of features are not working in solo mode.** ## Features +- Display the day and the formated time in-game. - Use your client language with built-in localization. - Kill Feed with custom UI showing player kill and death. - Tweak individual item(s) weight in "BepInEx\config\mxvalheim.custom_weights.json". From fb68bb61e20f545235b7ee7a1a05df72a9ba26c4 Mon Sep 17 00:00:00 2001 From: mikx Date: Wed, 11 Feb 2026 22:45:39 -0500 Subject: [PATCH 2/5] (1.5.6) readme update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 292c6a3..708101b 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Official **MxValheim Server** Mod. ## How-To Install 1. Download the latest dll. -2. Copy it to your (Client/Server) "BepInEx­\plugins". +2. Copy it to your (Client/Server) "BepInEx­\plugins\MxValheim\". 3. Run your (Client/Server) at least one time. 4. Tweaks the config in "BepInEx­\config\ovh.mxdev.mxvalheim.cfg" to your liking. 5. Enjoy! \ No newline at end of file From 2291d1c162e2e4c9b7065282e02f6ee4dd334dd7 Mon Sep 17 00:00:00 2001 From: mikx Date: Wed, 11 Feb 2026 22:50:43 -0500 Subject: [PATCH 3/5] (1.5.6) readme update again --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 708101b..6fbd24b 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ Official **MxValheim Server** Mod. - Auto close doors after a specified amount of time. (Enable/Disable and configure desired time in the generated config.) ## How-To Install -1. Download the latest dll. -2. Copy it to your (Client/Server) "BepInEx­\plugins\MxValheim\". +1. Download the latest zip. +2. Copy the zip content to your (Client/Server) "BepInEx­\plugins\MxValheim\". 3. Run your (Client/Server) at least one time. 4. Tweaks the config in "BepInEx­\config\ovh.mxdev.mxvalheim.cfg" to your liking. 5. Enjoy! \ No newline at end of file From cb93f20b1859c012c1cf5c00a528db0eb09030b7 Mon Sep 17 00:00:00 2001 From: mikx Date: Thu, 12 Feb 2026 03:15:36 -0500 Subject: [PATCH 4/5] (1.5.7) AutoDoor Ignore List --- MxValheim/MxValheim.cs | 5 +++-- MxValheim/Patch/Doors.cs | 13 +++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/MxValheim/MxValheim.cs b/MxValheim/MxValheim.cs index e78bc02..58f5fec 100644 --- a/MxValheim/MxValheim.cs +++ b/MxValheim/MxValheim.cs @@ -25,7 +25,7 @@ public class MxValheimMod : BaseUnityPlugin private const string ModGUID = "ovh.mxdev.mxvalheim"; private const string ModName = "MxValheim"; - public const string ModVersion = "1.5.6"; + public const string ModVersion = "1.5.7"; public static ConfigEntry Config_Locked; public static ConfigEntry Config_OreMultiplier; @@ -38,7 +38,8 @@ public class MxValheimMod : BaseUnityPlugin public static string modPath = Path.Combine(Paths.PluginPath, "MxValheim"); public static string internalConfigsPath = Path.Combine(modPath, "Configs"); - private static string WeightConfigPath => Path.Combine(internalConfigsPath, "items_weight.json"); + public static string WeightConfigPath => Path.Combine(internalConfigsPath, "items_weight.json"); + public static string AutoDoorConfigPath => Path.Combine(internalConfigsPath, "auto_doors.json"); public static Dictionary WeightSettings = new Dictionary(); // Data structures diff --git a/MxValheim/Patch/Doors.cs b/MxValheim/Patch/Doors.cs index a9141cf..77dbcca 100644 --- a/MxValheim/Patch/Doors.cs +++ b/MxValheim/Patch/Doors.cs @@ -1,6 +1,9 @@ using BepInEx; using HarmonyLib; +using Newtonsoft.Json; using System.Collections; +using System.Collections.Generic; +using System.IO; using UnityEngine; using UnityEngine.Diagnostics; using static MxValheimMod; @@ -9,6 +12,11 @@ namespace MxValheim.Patch { public class Doors_Patch { + public class DoorRoot + { + public List ignoreList { get; set; } + } + [HarmonyPatch(typeof(Door), nameof(Door.Interact))] public static class DoorTracker { @@ -21,14 +29,15 @@ namespace MxValheim.Patch int prefabHash = nview.GetZDO().GetPrefab(); string prefabName = ZNetScene.instance.GetPrefab(prefabHash).name; - if (prefabName == "piece_crypt_door") return; + DoorRoot iL = JsonConvert.DeserializeObject(File.ReadAllText(MxValheimMod.AutoDoorConfigPath)); + if (iL.ignoreList.Contains(prefabName)) return; if (hold || alt || ___m_nview == null || !___m_nview.IsValid()) return; // Get state: 0 is closed int state = ___m_nview.GetZDO().GetInt("state"); - if (state == 0) return; + if (state == 0) return; lock (_doorQueueNview) { From d0f7a156c7f5a53e52ac9c4f0394a09fd8bcff38 Mon Sep 17 00:00:00 2001 From: mikx Date: Thu, 12 Feb 2026 09:05:33 -0500 Subject: [PATCH 5/5] (1.5.8) Perf. Optimization --- MxValheim/MxValheim.cs | 14 +++++++++++++- MxValheim/Patch/Doors.cs | 4 ++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/MxValheim/MxValheim.cs b/MxValheim/MxValheim.cs index 58f5fec..cc763ba 100644 --- a/MxValheim/MxValheim.cs +++ b/MxValheim/MxValheim.cs @@ -15,6 +15,7 @@ using System.Xml; using TMPro; using UnityEngine; using UnityEngine.UI; +using static MxValheim.Patch.Doors_Patch; [BepInPlugin(ModGUID, ModName, ModVersion)] public class MxValheimMod : BaseUnityPlugin @@ -25,7 +26,7 @@ public class MxValheimMod : BaseUnityPlugin private const string ModGUID = "ovh.mxdev.mxvalheim"; private const string ModName = "MxValheim"; - public const string ModVersion = "1.5.7"; + public const string ModVersion = "1.5.8"; public static ConfigEntry Config_Locked; public static ConfigEntry Config_OreMultiplier; @@ -40,6 +41,7 @@ public class MxValheimMod : BaseUnityPlugin public static string internalConfigsPath = Path.Combine(modPath, "Configs"); public static string WeightConfigPath => Path.Combine(internalConfigsPath, "items_weight.json"); public static string AutoDoorConfigPath => Path.Combine(internalConfigsPath, "auto_doors.json"); + public static DoorRoot CachedConfig; public static Dictionary WeightSettings = new Dictionary(); // Data structures @@ -87,6 +89,7 @@ public class MxValheimMod : BaseUnityPlugin LoadLocalization(); LoadJsonConfig(); + LoadDoorConfig(); _doorTimer = MxValheimMod.Config_autoDoorClose.Value; @@ -305,6 +308,15 @@ public class MxValheimMod : BaseUnityPlugin } } + public void LoadDoorConfig() + { + if (System.IO.File.Exists(AutoDoorConfigPath)) + { + string json = System.IO.File.ReadAllText(AutoDoorConfigPath); + CachedConfig = JsonConvert.DeserializeObject(json); + } + } + internal static void ShowKillMessage(string v) { throw new NotImplementedException(); diff --git a/MxValheim/Patch/Doors.cs b/MxValheim/Patch/Doors.cs index 77dbcca..11d2396 100644 --- a/MxValheim/Patch/Doors.cs +++ b/MxValheim/Patch/Doors.cs @@ -29,8 +29,8 @@ namespace MxValheim.Patch int prefabHash = nview.GetZDO().GetPrefab(); string prefabName = ZNetScene.instance.GetPrefab(prefabHash).name; - DoorRoot iL = JsonConvert.DeserializeObject(File.ReadAllText(MxValheimMod.AutoDoorConfigPath)); - if (iL.ignoreList.Contains(prefabName)) return; + if (MxValheimMod.CachedConfig?.ignoreList != null) + if (MxValheimMod.CachedConfig.ignoreList.Contains(prefabName)) return; if (hold || alt || ___m_nview == null || !___m_nview.IsValid()) return;