Decompiled source of ValheimKillFeed v1.0.0

plugins/ValheimKillFeed.dll

Decompiled 5 days ago
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<ZPackage>("ValheimKillFeed_KillEvent", (Action<long, ZPackage>)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<string, List<string>> _sections = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);

		private readonly Random _rng = new Random();

		public int SectionCount => _sections.Count;

		public int TotalLines
		{
			get
			{
				int num = 0;
				foreach (KeyValuePair<string, List<string>> 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<string>();
					}
				}
				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<string> list = new List<string>();
			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<KillFeedHud>();
			_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<HudAnchor> Position;

		public ConfigEntry<float> EntryDurationSeconds;

		public ConfigEntry<int> MaxEntries;

		public ConfigEntry<int> FontSize;

		public KillFeedConfig(ConfigFile cfg)
		{
			Position = cfg.Bind<HudAnchor>("HUD", "Position", HudAnchor.TopRight, "Where on the screen the kill feed appears.");
			EntryDurationSeconds = cfg.Bind<float>("HUD", "EntryDurationSeconds", 6f, "How long each entry stays on screen before fading out.");
			MaxEntries = cfg.Bind<int>("HUD", "MaxEntries", 6, "Maximum number of entries displayed at once.");
			FontSize = cfg.Bind<int>("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<Entry> _entries = new LinkedList<Entry>();

		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<float> list = new List<float>(_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<Player, AttackerSnapshot> LastHitByVictim = new ConditionalWeakTable<Player, AttackerSnapshot>();

		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<HitData>("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
			});
		}
	}
}