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 0486fe7..da6fa9e 100644 --- a/MxValheim/MxValheim.cs +++ b/MxValheim/MxValheim.cs @@ -3,6 +3,8 @@ using BepInEx.Configuration; using HarmonyLib; using MxValheim.EventSystem; using MxValheim.KillFeed; +using MxValheim.Patch; +using MxValheim.Patch.HUD; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -14,12 +16,14 @@ using System.Xml; using TMPro; using UnityEngine; using UnityEngine.UI; +using static MxValheim.Patch.Doors_Patch; [BepInPlugin(ModGUID, ModName, ModVersion)] 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"; @@ -36,7 +40,9 @@ 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 DoorRoot CachedConfig; public static Dictionary WeightSettings = new Dictionary(); // Data structures @@ -65,6 +71,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 +90,9 @@ public class MxValheimMod : BaseUnityPlugin LoadLocalization(); LoadJsonConfig(); + LoadDoorConfig(); + + _doorTimer = MxValheimMod.Config_autoDoorClose.Value; Harmony harmony = new Harmony(ModGUID); harmony.PatchAll(); @@ -147,7 +161,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; @@ -186,6 +247,8 @@ public class MxValheimMod : BaseUnityPlugin if (_displayTimer <= 0 && _hudRoot != null) _hudRoot.SetActive(false); } + // KillFeed Logic + ///////////////// if (RandEventSystem.instance.GetActiveEvent() == null) { @@ -264,6 +327,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/MxValheim.csproj b/MxValheim/MxValheim.csproj index 4cd05b8..1c84a64 100644 --- a/MxValheim/MxValheim.csproj +++ b/MxValheim/MxValheim.csproj @@ -101,6 +101,8 @@ + + diff --git a/MxValheim/Patch/Doors.cs b/MxValheim/Patch/Doors.cs index 0de84af..11d2396 100644 --- a/MxValheim/Patch/Doors.cs +++ b/MxValheim/Patch/Doors.cs @@ -1,55 +1,67 @@ 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; namespace MxValheim.Patch { - internal class Doors + public class Doors_Patch { + public class DoorRoot + { + public List ignoreList { get; set; } + } + [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 int prefabHash = nview.GetZDO().GetPrefab(); string prefabName = ZNetScene.instance.GetPrefab(prefabHash).name; - if (prefabName == "piece_crypt_door") return; + if (MxValheimMod.CachedConfig?.ignoreList != null) + if (MxValheimMod.CachedConfig.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) + 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..6fbd24b 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". @@ -15,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". +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