diff --git a/MxValheim/KillFeed/Chat.cs b/MxValheim/KillFeed/Chat.cs
new file mode 100644
index 0000000..84b2bcd
--- /dev/null
+++ b/MxValheim/KillFeed/Chat.cs
@@ -0,0 +1,47 @@
+using HarmonyLib;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace MxValheim.KillFeed
+{
+ internal class Chat_Patch
+ {
+ [HarmonyPatch(typeof(Chat), nameof(Chat.Awake))]
+ class ChatLimitPatch
+ {
+ static void Postfix(Chat __instance)
+ {
+ // 1. Update the UI component (The most important part for typing)
+ if (__instance.m_input != null)
+ {
+ __instance.m_input.characterLimit = 1000;
+ }
+
+ // 2. Try to find the internal length field (it might be m_maxLength or m_maxMessageLength)
+ try
+ {
+ var field = AccessTools.Field(typeof(Chat), "m_maxMessageLength")
+ ?? AccessTools.Field(typeof(Chat), "m_maxLength");
+
+ if (field != null)
+ {
+ field.SetValue(__instance, 1000);
+ UnityEngine.Debug.Log($"KillFeed: Set {field.Name} to 1000.");
+ }
+ else
+ {
+ UnityEngine.Debug.LogWarning("KillFeed: Could not find a length field in Chat. Logic might use InputField limit only.");
+ }
+ }
+ catch (Exception e)
+ {
+ UnityEngine.Debug.LogWarning($"KillFeed: Non-critical error setting chat limit: {e.Message}");
+ }
+ }
+ }
+ }
+}
diff --git a/MxValheim/KillFeed/Patch.cs b/MxValheim/KillFeed/Patch.cs
new file mode 100644
index 0000000..561b076
--- /dev/null
+++ b/MxValheim/KillFeed/Patch.cs
@@ -0,0 +1,307 @@
+using BepInEx;
+using HarmonyLib;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using UnityEngine;
+using UnityEngine.UI;
+using static MxValheimMod;
+
+namespace MxValheim.KillFeed
+{
+ public class KillFeed_Patch
+ {
+ public static void SendKillToAll(string attacker, string victim, string weaponPrefab, int type)
+ {
+ if (ZRoutedRpc.instance != null)
+ {
+ ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "RPC_MxKillMsg", attacker, victim, weaponPrefab, type);
+ }
+ }
+
+ public void OnReceiveKillMsg(long sender, string attacker, string victim, string weaponPrefab, int type)
+ {
+ ZNet zn = new ZNet();
+ if (zn.IsDedicated()) return;
+
+ string finalMsg = (type == 1) ? $"☠ {victim} a été tué par {attacker}" :
+ (type == 2) ? $"{attacker} a tué {victim}" :
+ $"{attacker} a tué {victim}";
+
+ Sprite weaponIcon = null;
+
+ // Only check ObjectDB if we are actually in a world
+ if (ObjectDB.instance != null && !string.IsNullOrEmpty(weaponPrefab))
+ {
+ GameObject itemObj = ObjectDB.instance.GetItemPrefab(weaponPrefab);
+ if (itemObj != null)
+ {
+ ItemDrop itemDrop = itemObj.GetComponent();
+ if (itemDrop != null && itemDrop.m_itemData.m_shared.m_icons.Length > 0)
+ {
+ weaponIcon = itemDrop.m_itemData.m_shared.m_icons[0];
+ }
+ }
+ }
+ lock (_msgQueue)
+ {
+ _msgQueue.Enqueue(finalMsg);
+ _iconQueue.Enqueue(weaponIcon);
+ }
+ }
+
+ [HarmonyPatch(typeof(Character), nameof(Character.Damage))]
+ public static class DamageTracker
+ {
+ static void Prefix(Character __instance, HitData hit)
+ {
+ if (__instance == null || hit == null) return;
+
+ Character attacker = hit.GetAttacker();
+ if (attacker != null)
+ {
+ string weaponName = "Unknown";
+
+ // If the attacker is a player, get their current weapon
+ if (attacker.IsPlayer())
+ {
+ Player player = attacker as Player;
+ if (player != null && player.GetCurrentWeapon() != null)
+ {
+ weaponName = player.GetCurrentWeapon().m_dropPrefab?.name ?? "Hands";
+ }
+ }
+ else
+ {
+ // If it's a monster, we just use their hover name (e.g., "Troll")
+ weaponName = attacker.GetHoverName();
+ }
+
+ // Store the data for the Postfix to use
+ _activeTrackers[__instance] = new KillData
+ {
+ attackerName = attacker.GetHoverName(),
+ weaponPrefabName = weaponName
+ };
+ }
+ }
+ }
+
+ [HarmonyPatch(typeof(Character), nameof(Character.ApplyDamage))]
+ public static class DeathNotifier
+ {
+ // The parameter names MUST match the game's: hit, showDamageText, triggerEffects, mod
+ static void Postfix(Character __instance, HitData hit, bool showDamageText, bool triggerEffects, HitData.DamageModifier mod)
+ {
+ // Check if the character is dead or just died
+ if (__instance.GetHealth() <= 0f)
+ {
+ ZNetView nview = __instance.GetComponent();
+
+ // This check should now pass because ApplyDamage happens before the object is invalidated
+ if (nview == null || !nview.IsValid() || !nview.IsOwner()) return;
+
+ _activeTrackers.TryGetValue(__instance, out KillData data);
+
+ string attackerName = data?.attackerName ?? hit.GetAttacker()?.GetHoverName() ?? "The World";
+ string weaponName = data?.weaponPrefabName ?? "";
+ string victimName = __instance.GetHoverName();
+
+ int type = 0;
+ if (__instance.IsPlayer()) type = 1;
+ else if (attackerName == Player.m_localPlayer?.GetHoverName()) type = 2;
+
+ if (ZRoutedRpc.instance != null)
+ {
+ ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "RPC_MxKillMsg",
+ attackerName, victimName, weaponName, type);
+ }
+
+ _activeTrackers.Remove(__instance);
+ }
+ }
+ }
+
+ public void ShowNextMessage(string msg, Sprite icon)
+ {
+ if (_hudRoot == null) CreateCustomHud();
+
+ // Re-check for font here just in case it wasn't loaded during Awake
+ if (_killText.font.name != "AveriaSerifLibre-Bold")
+ {
+ foreach (Font f in Resources.FindObjectsOfTypeAll())
+ {
+ if (f.name == "AveriaSerifLibre-Bold")
+ {
+ _killText.font = f;
+ break;
+ }
+ }
+ }
+
+ _killText.text = msg;
+ _weaponIconSlot.sprite = icon;
+ _weaponIconSlot.gameObject.SetActive(icon != null);
+ _hudRoot.SetActive(true);
+ _canvasGroup.alpha = 1f;
+ _displayTimer = 5.0f;
+ }
+
+ private void CreateCustomHud()
+ {
+ GameObject oldRoot = GameObject.Find("MxKillFeed_Root");
+ if (oldRoot != null) UnityEngine.Object.Destroy(oldRoot);
+
+ _hudRoot = new GameObject("MxKillFeed_Root");
+ UnityEngine.Object.DontDestroyOnLoad(_hudRoot);
+
+ Canvas c = _hudRoot.AddComponent