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".