Decompiled source of Carturs Combat Text v1.0.0

plugins/CarturCombatText/CarturCombatText.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("CarturCombatText")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+625cefd5c240f899ff7c9c622c1d0dafcb666d59")]
[assembly: AssemblyProduct("CarturCombatText")]
[assembly: AssemblyTitle("CarturCombatText")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace CarturCombatText
{
	[BepInPlugin("com.jekkle.valheim.carturcombattext", "Cartur's Combat Text", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		private sealed class Tracked
		{
			public float BaseSize;

			public bool Crit;

			public Character Victim;

			public Vector3 Jitter;
		}

		private struct EnemyHit
		{
			public float Time;

			public Vector3 Point;

			public Character Victim;

			public bool Backstab;
		}

		[HarmonyPatch(typeof(DamageText), "Awake")]
		private static class LogVanillaValues
		{
			private static void Postfix(DamageText __instance)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogInfo((object)($"vanilla DamageText: maxTextDistance={__instance.m_maxTextDistance} " + $"smallFontDistance={__instance.m_smallFontDistance} " + $"textDuration={__instance.m_textDuration} " + $"fontSize large={__instance.m_largeFontSize} small={__instance.m_smallFontSize} " + $"- enemy hits will use maxTextDistance={80f}"));
				}
			}
		}

		[HarmonyPatch(typeof(DamageText), "Awake")]
		private static class LogFonts
		{
			private static void Postfix(DamageText __instance)
			{
				TMP_Text val = (((Object)(object)__instance.m_worldTextBase == (Object)null) ? null : __instance.m_worldTextBase.GetComponent<TMP_Text>());
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogInfo((object)("combat text font in use: " + (((Object)(object)((val != null) ? val.font : null) == (Object)null) ? "unknown" : ((Object)val.font).name)));
				}
				List<string> list = new List<string>();
				TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll<TMP_FontAsset>();
				foreach (TMP_FontAsset val2 in array)
				{
					if ((Object)(object)val2 != (Object)null && !list.Contains(((Object)val2).name))
					{
						list.Add(((Object)val2).name);
					}
				}
				ManualLogSource log2 = Log;
				if (log2 != null)
				{
					log2.LogInfo((object)string.Format("TMP font assets loaded ({0}): {1}", list.Count, string.Join(", ", list)));
				}
			}
		}

		[HarmonyPatch(typeof(Character), "ApplyDamage")]
		private static class MarkEnemyHit
		{
			private static float _saved = -1f;

			private static void Prefix(Character __instance, HitData hit, bool showDamageText)
			{
				//IL_0064: Unknown result type (might be due to invalid IL or missing references)
				//IL_0069: Unknown result type (might be due to invalid IL or missing references)
				if (showDamageText && hit != null && !((Object)(object)__instance == (Object)null) && !__instance.IsPlayer() && !__instance.IsTamed())
				{
					bool backstab = BackstabTimeField != null && (float)BackstabTimeField.GetValue(__instance) == Time.time;
					EnemyHits.Add(new EnemyHit
					{
						Time = Time.time,
						Point = hit.m_point,
						Victim = __instance,
						Backstab = backstab
					});
					DamageText instance = DamageText.instance;
					if ((Object)(object)instance != (Object)null && instance.m_maxTextDistance < 80f)
					{
						_saved = instance.m_maxTextDistance;
						instance.m_maxTextDistance = 80f;
					}
				}
			}

			private static void Postfix()
			{
				if (!(_saved < 0f))
				{
					DamageText instance = DamageText.instance;
					if ((Object)(object)instance != (Object)null)
					{
						instance.m_maxTextDistance = _saved;
					}
					_saved = -1f;
				}
			}
		}

		[HarmonyPatch(typeof(DamageText), "AddInworldText")]
		private static class TagNewText
		{
			private static void Postfix(DamageText __instance, TextType type, Vector3 pos)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				//IL_0073: Unknown result type (might be due to invalid IL or missing references)
				//IL_0075: Invalid comparison between Unknown and I4
				//IL_0077: Unknown result type (might be due to invalid IL or missing references)
				//IL_0079: Invalid comparison between Unknown and I4
				//IL_0082: Unknown result type (might be due to invalid IL or missing references)
				//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
				//IL_00af: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e5: Invalid comparison between Unknown and I4
				//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
				//IL_00db: Unknown result type (might be due to invalid IL or missing references)
				//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
				//IL_0106: Unknown result type (might be due to invalid IL or missing references)
				//IL_0117: Unknown result type (might be due to invalid IL or missing references)
				//IL_011c: Unknown result type (might be due to invalid IL or missing references)
				//IL_012e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0135: Unknown result type (might be due to invalid IL or missing references)
				//IL_0145: Unknown result type (might be due to invalid IL or missing references)
				//IL_014b: Unknown result type (might be due to invalid IL or missing references)
				//IL_01c9: Unknown result type (might be due to invalid IL or missing references)
				//IL_01cb: Unknown result type (might be due to invalid IL or missing references)
				if (!WasEnemyHit(pos, out var victim, out var backstab) || !(TextsField?.GetValue(__instance) is IList { Count: not 0 } list) || TextFieldField == null)
				{
					return;
				}
				object? value = TextFieldField.GetValue(list[list.Count - 1]);
				TMP_Text val = (TMP_Text)((value is TMP_Text) ? value : null);
				if (!((Object)(object)val == (Object)null) && !Live.ContainsKey(val))
				{
					bool flag = (int)type == 7 || (int)type == 2 || backstab;
					if ((int)type == 0 && float.TryParse(val.text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && IsBigHit(result))
					{
						flag = true;
					}
					Vector3 jitter = Vector3.zero;
					if (WorldPosField != null)
					{
						jitter = (Vector3)WorldPosField.GetValue(list[list.Count - 1]) - pos;
					}
					if ((int)type == 2)
					{
						Color color = ((Graphic)val).color;
						((Graphic)val).color = new Color(1f, 0f, 0f, color.a);
					}
					else if (flag)
					{
						Color color2 = ((Graphic)val).color;
						((Graphic)val).color = new Color(1f, 1f, 0f, color2.a);
					}
					if (flag)
					{
						val.fontStyle = (FontStyles)(val.fontStyle | 1);
					}
					if (flag && DurationField != null)
					{
						DurationField.SetValue(list[list.Count - 1], (float)DurationField.GetValue(list[list.Count - 1]) * 0.6f);
					}
					Live[val] = new Tracked
					{
						BaseSize = val.fontSize,
						Crit = flag,
						Victim = victim,
						Jitter = jitter
					};
				}
			}
		}

		[HarmonyPatch(typeof(DamageText), "UpdateWorldTexts")]
		private static class AnchorToVictim
		{
			private static void Prefix(DamageText __instance)
			{
				//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
				//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
				//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
				//IL_00da: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
				if (!(TextsField?.GetValue(__instance) is IList list) || TextFieldField == null || TimerField == null || WorldPosField == null)
				{
					return;
				}
				for (int i = 0; i < list.Count; i++)
				{
					object obj = list[i];
					object? value = TextFieldField.GetValue(obj);
					TMP_Text val = (TMP_Text)((value is TMP_Text) ? value : null);
					if (val != null && !((Object)(object)val == (Object)null) && Live.TryGetValue(val, out var value2) && !((Object)(object)value2.Victim == (Object)null))
					{
						float num = (float)TimerField.GetValue(obj);
						float num2 = (value2.Crit ? 2.5f : 1f);
						WorldPosField.SetValue(obj, value2.Victim.GetTopPoint() + value2.Jitter + Vector3.up * (num * num2));
					}
				}
			}
		}

		[HarmonyPatch(typeof(DamageText), "UpdateWorldTexts")]
		private static class ScaleTexts
		{
			private static void Postfix(DamageText __instance)
			{
				if (!(TextsField?.GetValue(__instance) is IList list) || TextFieldField == null || TimerField == null)
				{
					return;
				}
				Dead.Clear();
				foreach (KeyValuePair<TMP_Text, Tracked> item in Live)
				{
					if ((Object)(object)item.Key == (Object)null)
					{
						Dead.Add(item.Key);
					}
				}
				for (int i = 0; i < Dead.Count; i++)
				{
					Live.Remove(Dead[i]);
				}
				for (int j = 0; j < list.Count; j++)
				{
					object obj = list[j];
					object? value = TextFieldField.GetValue(obj);
					TMP_Text val = (TMP_Text)((value is TMP_Text) ? value : null);
					if (val != null && !((Object)(object)val == (Object)null) && Live.TryGetValue(val, out var value2))
					{
						float num = (value2.Crit ? (3f * CritCurve((float)TimerField.GetValue(obj))) : 2f);
						val.fontSize = value2.BaseSize * num;
					}
				}
			}
		}

		public const string PluginGuid = "com.jekkle.valheim.carturcombattext";

		public const string PluginName = "Cartur's Combat Text";

		public const string PluginVersion = "1.0.0";

		public const float SizeScale = 2f;

		public const float CritSizeScale = 3f;

		private const float CritPeakScale = 3.5f;

		private const float CritScaleTime = 0.04f;

		private const float CritShrinkTime = 0.16f;

		private const float CritSettleScale = 0.9f;

		private const float CritSettleTime = 0.24f;

		private const float CritRiseSpeed = 2.5f;

		private const float CritDurationFactor = 0.6f;

		private const float EnemyTextDistance = 80f;

		private const float BigHitFactor = 2f;

		private const int BigHitSamples = 20;

		private static ManualLogSource Log;

		private static readonly Type WorldTextType = AccessTools.Inner(typeof(DamageText), "WorldTextInstance");

		private static readonly FieldInfo TextsField = AccessTools.Field(typeof(DamageText), "m_worldTexts");

		private static readonly FieldInfo TextFieldField = ((WorldTextType == null) ? null : AccessTools.Field(WorldTextType, "m_textField"));

		private static readonly FieldInfo TimerField = ((WorldTextType == null) ? null : AccessTools.Field(WorldTextType, "m_timer"));

		private static readonly FieldInfo WorldPosField = ((WorldTextType == null) ? null : AccessTools.Field(WorldTextType, "m_worldPos"));

		private static readonly FieldInfo DurationField = ((WorldTextType == null) ? null : AccessTools.Field(WorldTextType, "m_duration"));

		private static readonly FieldInfo BackstabTimeField = AccessTools.Field(typeof(Character), "m_backstabTime");

		private static readonly Dictionary<TMP_Text, Tracked> Live = new Dictionary<TMP_Text, Tracked>();

		private static readonly List<TMP_Text> Dead = new List<TMP_Text>();

		private static readonly Queue<float> RecentHits = new Queue<float>();

		private static float _recentSum;

		private const float PointLifetime = 2f;

		private static readonly List<EnemyHit> EnemyHits = new List<EnemyHit>();

		private const float RiseSpeed = 1f;

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			try
			{
				Harmony.CreateAndPatchAll(typeof(Plugin).Assembly, "com.jekkle.valheim.carturcombattext");
				((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1} loaded - scaling DamageText world text {2}x with a WoW-style crit pop.", "Cartur's Combat Text", "1.0.0", 2f));
			}
			catch (Exception arg)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)string.Format("{0} failed to patch, leaving combat text vanilla: {1}", "Cartur's Combat Text", arg));
			}
		}

		private static bool IsBigHit(float value)
		{
			bool result = RecentHits.Count >= 5 && _recentSum > 0f && value >= _recentSum / (float)RecentHits.Count * 2f;
			RecentHits.Enqueue(value);
			_recentSum += value;
			if (RecentHits.Count > 20)
			{
				_recentSum -= RecentHits.Dequeue();
			}
			return result;
		}

		private static bool WasEnemyHit(Vector3 pos, out Character victim, out bool backstab)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			victim = null;
			backstab = false;
			float time = Time.time;
			for (int num = EnemyHits.Count - 1; num >= 0; num--)
			{
				if (time - EnemyHits[num].Time > 2f)
				{
					EnemyHits.RemoveAt(num);
				}
				else if (EnemyHits[num].Point == pos)
				{
					victim = EnemyHits[num].Victim;
					backstab = EnemyHits[num].Backstab;
					EnemyHits.RemoveAt(num);
					return true;
				}
			}
			return false;
		}

		private static float CritCurve(float age)
		{
			if (age < 0.04f)
			{
				return Mathf.Lerp(1f, 3.5f, age / 0.04f);
			}
			if (age < 0.16f)
			{
				return Mathf.Lerp(3.5f, 0.9f, (age - 0.04f) / 0.12f);
			}
			if (age < 0.24f)
			{
				return Mathf.Lerp(0.9f, 1f, (age - 0.16f) / 0.08f);
			}
			return 1f;
		}
	}
}