using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; using ValheimKillFeed.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("ValheimKillFeed")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("ValheimKillFeed")] [assembly: AssemblyTitle("ValheimKillFeed")] [assembly: AssemblyVersion("1.0.0.0")] namespace ValheimKillFeed { public static class CauseResolver { public struct Resolved { public string Section; public string Weapon; public string Distance; public bool HasAttacker; } public static Resolved Resolve(Player victim, HitData hit) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Expected I4, but got Unknown //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Invalid comparison between Unknown and I4 //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Invalid comparison between Unknown and I4 //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) Resolved resolved = default(Resolved); resolved.Section = ""; resolved.Weapon = ""; resolved.Distance = ""; resolved.HasAttacker = false; Resolved result = resolved; if (hit == null) { result.Section = "fall"; return result; } HitType hitType = hit.m_hitType; switch (hitType - 3) { default: if ((int)hitType != 14) { if ((int)hitType != 21) { break; } goto case 1; } result.Section = "fireplace"; return result; case 0: result.Section = "fall"; return result; case 1: case 5: result.Section = "drown"; return result; case 6: result.Section = "smoke"; return result; case 7: result.Section = "oob"; return result; case 2: case 3: case 4: break; } Character attacker = hit.GetAttacker(); if ((Object)(object)attacker != (Object)null && (Object)(object)victim != (Object)null && (Object)(object)attacker == (Object)(object)victim) { result.Section = "fireplace"; return result; } result.HasAttacker = (Object)(object)attacker != (Object)null || hit.HaveAttacker(); result.Section = DamageTypeToSection(((DamageTypes)(ref hit.m_damage)).GetMajorityDamageType()); if ((int)hit.m_skill != 0) { result.Weapon = ((object)(SkillType)(ref hit.m_skill)).ToString(); } if ((Object)(object)attacker != (Object)null && (Object)(object)victim != (Object)null) { float num = Vector3.Distance(((Component)attacker).transform.position, ((Component)victim).transform.position); result.Distance = Mathf.RoundToInt(num).ToString(); } return result; } private static string DamageTypeToSection(DamageType t) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Invalid comparison between Unknown and I4 //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Invalid comparison between Unknown and I4 //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Invalid comparison between Unknown and I4 //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Invalid comparison between Unknown and I4 //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected I4, but got Unknown //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Invalid comparison between Unknown and I4 //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Invalid comparison between Unknown and I4 //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Invalid comparison between Unknown and I4 //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Invalid comparison between Unknown and I4 if ((int)t <= 32) { if ((int)t <= 8) { switch (t - 1) { default: if ((int)t != 8) { break; } return "chop"; case 0: return "blunt"; case 1: return "slash"; case 3: return "pierce"; case 2: break; } } else { if ((int)t == 16) { return "pickaxe"; } if ((int)t == 32) { return "fire"; } } } else if ((int)t <= 128) { if ((int)t == 64) { return "frost"; } if ((int)t == 128) { return "lightning"; } } else { if ((int)t == 256) { return "poison"; } if ((int)t == 512) { return "spirit"; } } return "blunt"; } } public class KillEvent { public string Victim; public string Killer; public string Section; public string Weapon; public string Distance; public void Write(ZPackage pkg) { pkg.Write(Victim ?? ""); pkg.Write(Killer ?? ""); pkg.Write(Section ?? ""); pkg.Write(Weapon ?? ""); pkg.Write(Distance ?? ""); } public static KillEvent Read(ZPackage pkg) { return new KillEvent { Victim = pkg.ReadString(), Killer = pkg.ReadString(), Section = pkg.ReadString(), Weapon = pkg.ReadString(), Distance = pkg.ReadString() }; } } public static class NameResolver { public static string Of(Character c) { if ((Object)(object)c == (Object)null) { return ""; } Player val = (Player)(object)((c is Player) ? c : null); if (val != null) { string playerName = val.GetPlayerName(); if (!string.IsNullOrEmpty(playerName) && playerName != "...") { return playerName; } } string hoverName = c.GetHoverName(); if (!string.IsNullOrEmpty(hoverName)) { return hoverName; } if (!string.IsNullOrEmpty(c.m_name)) { return c.m_name; } return "something"; } } public static class Network { [HarmonyPatch(/*Could not decode attribute arguments.*/)] private static class ZRoutedRpc_Ctor_Patch { private static void Postfix() { ZRoutedRpc.instance.Register("ValheimKillFeed_KillEvent", (Action)OnRpc); } } public const string RpcName = "ValheimKillFeed_KillEvent"; public static void Broadcast(KillEvent ev) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown if (ZRoutedRpc.instance != null) { ZPackage val = new ZPackage(); ev.Write(val); ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "ValheimKillFeed_KillEvent", new object[1] { val }); } } private static void OnRpc(long sender, ZPackage pkg) { if (pkg != null && !((Object)(object)Player.m_localPlayer == (Object)null)) { KillEvent ev; try { ev = KillEvent.Read(pkg); } catch { return; } Plugin.Instance?.OnKillEventReceived(ev); } } } public class PhraseTable { private readonly Dictionary> _sections = new Dictionary>(StringComparer.OrdinalIgnoreCase); private readonly Random _rng = new Random(); public int SectionCount => _sections.Count; public int TotalLines { get { int num = 0; foreach (KeyValuePair> section in _sections) { num += section.Value.Count; } return num; } } public void Load(string path) { _sections.Clear(); if (!File.Exists(path)) { return; } string text = null; string[] array = File.ReadAllLines(path); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length == 0 || text2.StartsWith("#")) { continue; } if (text2.StartsWith("[") && text2.EndsWith("]")) { text = text2.Substring(1, text2.Length - 2).Trim().ToLowerInvariant(); if (!_sections.TryGetValue(text, out var _)) { _sections[text] = new List(); } } else if (text != null) { _sections[text].Add(text2); } } } public string Pick(string section, string victim, string killer, string weapon, string distance) { if (!_sections.TryGetValue(section, out var value) || value.Count == 0) { return victim + " died."; } bool HasKiller = !string.IsNullOrEmpty(killer); bool HasWeapon = !string.IsNullOrEmpty(weapon); bool HasDistance = !string.IsNullOrEmpty(distance); List list = new List(); foreach (string item in value) { if (Compatible(item)) { list.Add(item); } } if (list.Count == 0) { list = value; } return list[_rng.Next(list.Count)].Replace("{victim}", victim ?? "").Replace("{killer}", killer ?? "").Replace("{weapon}", weapon ?? "") .Replace("{distance}", distance ?? ""); bool Compatible(string t) { if ((HasKiller || !t.Contains("{killer}")) && (HasWeapon || !t.Contains("{weapon}"))) { if (!HasDistance) { return !t.Contains("{distance}"); } return true; } return false; } } } [BepInPlugin("tarbaby.valheim-kill-feed", "ValheimKillFeed", "1.0.0")] public class Plugin : BaseUnityPlugin { public const string PluginGUID = "tarbaby.valheim-kill-feed"; public const string PluginName = "ValheimKillFeed"; public const string PluginVersion = "1.0.0"; private Harmony _harmony; private KillFeedConfig _config; private KillFeedHud _hud; private PhraseTable _phrases; public static Plugin Instance { get; private set; } public static ManualLogSource Log { get; private set; } private void Awake() { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Expected O, but got Unknown //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; _config = new KillFeedConfig(((BaseUnityPlugin)this).Config); _phrases = new PhraseTable(); string text = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? "", "phrasings.txt"); EnsurePhrasingsFile(text); _phrases.Load(text); Log.LogInfo((object)$"Loaded phrasings: {_phrases.SectionCount} sections, {_phrases.TotalLines} lines from {text}"); GameObject val = new GameObject("ValheimKillFeed_Hud"); Object.DontDestroyOnLoad((Object)(object)val); _hud = val.AddComponent(); _hud.Configure(_config); _harmony = new Harmony("tarbaby.valheim-kill-feed"); _harmony.PatchAll(); Log.LogInfo((object)"ValheimKillFeed v1.0.0 loaded."); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } public void OnKillEventReceived(KillEvent ev) { if (ev != null && !((Object)(object)_hud == (Object)null) && _phrases != null) { string text = _phrases.Pick(ev.Section, ev.Victim, ev.Killer, ev.Weapon, ev.Distance); _hud.Push(text); } } private static void EnsurePhrasingsFile(string targetPath) { if (File.Exists(targetPath)) { return; } Assembly executingAssembly = Assembly.GetExecutingAssembly(); string text = executingAssembly.GetName().Name + ".phrasings.txt"; using Stream stream = executingAssembly.GetManifestResourceStream(text); if (stream == null) { Log.LogWarning((object)("Embedded phrasings resource '" + text + "' not found.")); return; } using FileStream destination = File.Create(targetPath); stream.CopyTo(destination); Log.LogInfo((object)("Wrote default phrasings to " + targetPath)); } } } namespace ValheimKillFeed.UI { public enum HudAnchor { TopLeft, TopRight, BottomLeft, BottomRight } public class KillFeedConfig { public ConfigEntry Position; public ConfigEntry EntryDurationSeconds; public ConfigEntry MaxEntries; public ConfigEntry FontSize; public KillFeedConfig(ConfigFile cfg) { Position = cfg.Bind("HUD", "Position", HudAnchor.TopRight, "Where on the screen the kill feed appears."); EntryDurationSeconds = cfg.Bind("HUD", "EntryDurationSeconds", 6f, "How long each entry stays on screen before fading out."); MaxEntries = cfg.Bind("HUD", "MaxEntries", 6, "Maximum number of entries displayed at once."); FontSize = cfg.Bind("HUD", "FontSize", 18, "Pixel size of kill feed text."); } } public class KillFeedHud : MonoBehaviour { private class Entry { public string Text; public float SpawnedAt; } private readonly LinkedList _entries = new LinkedList(); private KillFeedConfig _cfg; private GUIStyle _style; private Texture2D _bgTex; private const string Prefix = "☠ "; public void Configure(KillFeedConfig cfg) { _cfg = cfg; } public void Push(string text) { if (!string.IsNullOrEmpty(text)) { _entries.AddLast(new Entry { Text = "☠ " + text, SpawnedAt = Time.unscaledTime }); TrimToMax(); } } private void TrimToMax() { int num = _cfg?.MaxEntries.Value ?? 6; while (_entries.Count > num) { _entries.RemoveFirst(); } } private void Update() { if (_cfg != null) { float value = _cfg.EntryDurationSeconds.Value; float unscaledTime = Time.unscaledTime; while (_entries.First != null && unscaledTime - _entries.First.Value.SpawnedAt > value + 1f) { _entries.RemoveFirst(); } } } private void EnsureStyle() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: 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_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Expected O, but got Unknown //IL_008e: Unknown result type (might be due to invalid IL or missing references) if (_style == null) { _style = new GUIStyle(GUI.skin.label) { richText = true, wordWrap = false, alignment = (TextAnchor)3 }; } _style.fontSize = _cfg?.FontSize.Value ?? 18; if ((Object)(object)_bgTex == (Object)null) { _bgTex = new Texture2D(1, 1, (TextureFormat)4, false); _bgTex.SetPixel(0, 0, new Color(0f, 0f, 0f, 0.55f)); _bgTex.Apply(); } } private void OnGUI() { //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Expected O, but got Unknown //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Expected O, but got Unknown //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_0240: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Unknown result type (might be due to invalid IL or missing references) //IL_0258: Unknown result type (might be due to invalid IL or missing references) //IL_0269: Unknown result type (might be due to invalid IL or missing references) //IL_028a: Unknown result type (might be due to invalid IL or missing references) //IL_02b2: Unknown result type (might be due to invalid IL or missing references) //IL_02c4: Unknown result type (might be due to invalid IL or missing references) if (_cfg == null || _entries.Count == 0 || (Object)(object)Player.m_localPlayer == (Object)null) { return; } EnsureStyle(); float value = _cfg.EntryDurationSeconds.Value; float unscaledTime = Time.unscaledTime; float num = (float)Screen.height * 0.25f; List list = new List(_entries.Count); float num2 = 0f; foreach (Entry entry in _entries) { float num3 = _style.CalcSize(new GUIContent(entry.Text)).y + 12f; list.Add(num3); num2 += num3 + 4f; } if (num2 > 0f) { num2 -= 4f; } float num4 = Screen.width; float num5 = Screen.height; float num6 = Mathf.Min(560f, num4 * 0.45f); bool flag = _cfg.Position.Value == HudAnchor.TopRight || _cfg.Position.Value == HudAnchor.BottomRight; float num7 = ((_cfg.Position.Value == HudAnchor.TopLeft || _cfg.Position.Value == HudAnchor.TopRight) ? num : (num5 - 120f - num2)); int num8 = 0; foreach (Entry entry2 in _entries) { float num9 = unscaledTime - entry2.SpawnedAt; float num10 = ((num9 < value) ? 1f : ((!(num9 < value + 1f)) ? 0f : (1f - (num9 - value)))); if (num10 <= 0f) { num8++; num7 += list[num8 - 1] + 4f; continue; } GUIContent val = new GUIContent(entry2.Text); Vector2 val2 = _style.CalcSize(val); float num11 = Mathf.Min(num6, val2.x + 20f); float num12 = list[num8]; float num13 = (flag ? (num4 - 16f - num11) : 16f); Color color = GUI.color; GUI.color = new Color(1f, 1f, 1f, num10); GUI.DrawTexture(new Rect(num13, num7, num11, num12), (Texture)(object)_bgTex); GUI.color = new Color(1f, 1f, 1f, num10); GUI.Label(new Rect(num13 + 10f, num7 + 6f, num11 - 20f, num12 - 12f), val, _style); GUI.color = color; num7 += num12 + 4f; num8++; } } } } namespace ValheimKillFeed.Patches { public class AttackerSnapshot { public string KillerName; public CauseResolver.Resolved Resolved; } [HarmonyPatch(typeof(Character), "Damage")] public static class CharacterDamagePatch { public static readonly ConditionalWeakTable LastHitByVictim = new ConditionalWeakTable(); private static void Postfix(Character __instance, HitData hit) { if ((Object)(object)__instance == (Object)null || hit == null) { return; } Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val != null) { CauseResolver.Resolved resolved = CauseResolver.Resolve(val, hit); string killerName = ""; Character attacker = hit.GetAttacker(); if ((Object)(object)attacker != (Object)null) { killerName = NameResolver.Of(attacker); } AttackerSnapshot value = new AttackerSnapshot { KillerName = killerName, Resolved = resolved }; LastHitByVictim.Remove(val); LastHitByVictim.Add(val, value); } } } [HarmonyPatch(typeof(Player), "OnDeath")] public static class PlayerDeathPatch { private static void Prefix(Player __instance) { if ((Object)(object)__instance == (Object)null) { return; } string victim = NameResolver.Of((Character)(object)__instance); string killer = ""; CharacterDamagePatch.LastHitByVictim.TryGetValue(__instance, out var value); CauseResolver.Resolved resolved; if (value != null) { killer = value.KillerName ?? ""; resolved = value.Resolved; } else { HitData value2 = Traverse.Create((object)__instance).Field("m_lastHit").Value; resolved = CauseResolver.Resolve(__instance, value2); Character val = ((value2 != null) ? value2.GetAttacker() : null); if ((Object)(object)val != (Object)null) { killer = NameResolver.Of(val); } } Network.Broadcast(new KillEvent { Victim = victim, Killer = killer, Section = resolved.Section, Weapon = resolved.Weapon, Distance = resolved.Distance }); } } }