Decompiled source of DamageSparks v1.0.12

DamageSparks.dll

Decompiled a month ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using PerfectRandom.Sulfur.Core;
using PerfectRandom.Sulfur.Core.Stats;
using PerfectRandom.Sulfur.Core.Units;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: AssemblyTitle("SulfurDamageNumbersMod")]
[assembly: AssemblyDescription("SULFUR damage numbers mod for BepInEx 5")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("ryuka_labs")]
[assembly: AssemblyProduct("SulfurDamageNumbersMod")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("c9ebec31-296a-4d97-9959-9804ea595a74")]
[assembly: AssemblyFileVersion("1.0.12.0")]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("1.0.12.0")]
namespace SulfurDamageNumbersMod;

internal static class DamageLogPatch
{
	private static bool _applied;

	[ThreadStatic]
	private static bool _handlingPostfix;

	public static void Apply(Harmony harmony)
	{
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Expected O, but got Unknown
		if (!_applied)
		{
			MethodBase methodBase = TargetMethod();
			if (methodBase == null)
			{
				Plugin.Log.LogError((object)"SULFUR Damage Numbers failed: Unit.CreateDamageLog(float, float, DamageTypes, Hitmesh.Data, DamageSourceData) was not found.");
				return;
			}
			MethodBase methodBase2 = methodBase;
			HarmonyMethod val = new HarmonyMethod(typeof(DamageLogPatch), "Postfix", (Type[])null);
			harmony.Patch(methodBase2, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			_applied = true;
			Plugin.Log.LogInfo((object)("SULFUR Damage Numbers patched " + methodBase.DeclaringType.FullName + "." + methodBase.Name + "."));
		}
	}

	public static void Reset()
	{
		_applied = false;
		_handlingPostfix = false;
	}

	private static MethodBase TargetMethod()
	{
		return AccessTools.GetDeclaredMethods(typeof(Unit)).FirstOrDefault(delegate(MethodInfo m)
		{
			if (m.Name != "CreateDamageLog")
			{
				return false;
			}
			ParameterInfo[] parameters = m.GetParameters();
			return parameters.Length == 5 && parameters[0].ParameterType == typeof(float) && parameters[1].ParameterType == typeof(float) && parameters[2].ParameterType == typeof(DamageTypes) && parameters[3].ParameterType == typeof(Data) && parameters[4].ParameterType == typeof(DamageSourceData);
		});
	}

	private static void Postfix(Unit __instance, float __0, float __1, DamageTypes __2, Data __3, DamageSourceData __4)
	{
		//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_0199: Unknown result type (might be due to invalid IL or missing references)
		//IL_0119: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)__instance == (Object)null || !Plugin.Enabled.Value || _handlingPostfix)
		{
			return;
		}
		_handlingPostfix = true;
		try
		{
			if (__0 < Plugin.MinDamageToShow.Value)
			{
				if (Plugin.DebugLog.Value)
				{
					Plugin.Log.LogInfo((object)("[DamageNumbers] reject low damage amount=" + __0 + " target=" + SafeName((Object)(object)__instance)));
				}
				return;
			}
			if (!IsAllowedTarget(__instance, out var reason))
			{
				if (Plugin.DebugLog.Value)
				{
					Plugin.Log.LogInfo((object)("[DamageNumbers] reject target: " + reason + " target=" + SafeName((Object)(object)__instance)));
				}
				return;
			}
			if (!IsPlayerExecutedAttack(__4, out reason))
			{
				if (Plugin.DebugLog.Value)
				{
					Plugin.Log.LogInfo((object)("[DamageNumbers] reject source: " + reason + " target=" + SafeName((Object)(object)__instance) + " source=" + DescribeSource(__4)));
				}
				return;
			}
			float num = Mathf.Max(0f, ((Data)(ref __3)).GetShapeMultiplier());
			if (Plugin.DebugLog.Value)
			{
				Plugin.Log.LogInfo((object)("[DamageNumbers] accept amount=" + __0 + " multiplier=" + num + " target=" + SafeName((Object)(object)__instance) + " source=" + DescribeSource(__4)));
			}
			DamageNumberManager.RegisterDamage(__instance, __4, __0, num);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("[DamageNumbers] CreateDamageLog postfix failed: " + ex));
		}
		finally
		{
			_handlingPostfix = false;
		}
	}

	private static bool IsAllowedTarget(Unit target, out string reason)
	{
		if (target.isPlayer)
		{
			reason = "target is player";
			return false;
		}
		if (Plugin.RequireNpcTarget.Value && !(target is Npc))
		{
			reason = "target is not Npc";
			return false;
		}
		reason = null;
		return true;
	}

	private static bool IsPlayerExecutedAttack(DamageSourceData sourceData, out string reason)
	{
		if (!sourceData.isPlayer)
		{
			reason = "sourceData.isPlayer is false";
			return false;
		}
		if (Plugin.RequireSourceUnitPlayer.Value)
		{
			if ((Object)(object)sourceData.sourceUnit == (Object)null)
			{
				reason = "sourceUnit is null";
				return false;
			}
			if (!sourceData.sourceUnit.isPlayer)
			{
				reason = "sourceUnit is not player";
				return false;
			}
		}
		if ((Object)(object)sourceData.sourceWeapon != (Object)null)
		{
			reason = null;
			return true;
		}
		if (sourceData.melee)
		{
			reason = null;
			return true;
		}
		if (Plugin.ShowPlayerOwnedSecondaryEffects.Value && !sourceData.states.notCreatedByPlayer)
		{
			reason = null;
			return true;
		}
		reason = "no weapon/melee/player-owned secondary marker";
		return false;
	}

	private static string DescribeSource(DamageSourceData sourceData)
	{
		string text = ((!((Object)(object)sourceData.sourceUnit == (Object)null)) ? SafeName((Object)(object)sourceData.sourceUnit) : "null");
		string text2 = ((!((Object)(object)sourceData.sourceWeapon == (Object)null)) ? ((Object)sourceData.sourceWeapon).name : "null");
		return "isPlayer=" + sourceData.isPlayer + ", melee=" + sourceData.melee + ", weapon=" + text2 + ", unit=" + text + ", instanceId=" + sourceData.instanceId + ", name=" + sourceData.name;
	}

	private static string SafeName(Object obj)
	{
		return (!(obj == (Object)null)) ? obj.name : "null";
	}
}
internal sealed class DamageNumberManager : MonoBehaviour
{
	private struct AggregateEntry
	{
		public float Amount;

		public Vector3 Position;

		public int Frame;

		public float HighestMultiplier;

		public float LastMultiplier;

		public float WeightedMultiplierSum;
	}

	private struct PerHitSpreadOffset
	{
		public static readonly PerHitSpreadOffset Zero = new PerHitSpreadOffset
		{
			WorldOffset = Vector3.zero,
			ScreenOffset = Vector2.zero
		};

		public Vector3 WorldOffset;

		public Vector2 ScreenOffset;
	}

	private const int HardMaxActiveNumbers = 256;

	private const int HardMaxNumbersPerFrame = 256;

	private static DamageNumberManager _instance;

	private readonly Dictionary<AggregateKey, AggregateEntry> _aggregates = new Dictionary<AggregateKey, AggregateEntry>();

	private readonly List<AggregateKey> _flushBuffer = new List<AggregateKey>();

	private readonly Dictionary<PerHitSpreadKey, int> _perHitSpreadCounters = new Dictionary<PerHitSpreadKey, int>();

	private readonly List<ScreenDamageNumberView> _screenPool = new List<ScreenDamageNumberView>();

	private readonly List<DamageNumberView> _worldPool = new List<DamageNumberView>();

	private readonly List<Vector2> _crowdingPositions = new List<Vector2>(256);

	private int _perHitSpreadFrame = -1;

	private int _crowdingFrame = -1;

	private int _spawnBudgetFrame = -1;

	private int _spawnedThisFrame;

	private int _droppedThisFrame;

	private int _crowdingSkippedThisFrame;

	private float _lastBudgetLogTime = -999f;

	private bool _inputErrorLogged;

	private Camera _cachedCamera;

	private float _nextCameraLookupTime;

	private Material _screenSharedMaterial;

	private Material _worldSharedMaterial;

	private RenderMode _lastAppliedRenderMode;

	private bool _hasAppliedRenderMode;

	private RectTransform _screenRoot;

	private Transform _worldRoot;

	public static void EnsureExists()
	{
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Expected O, but got Unknown
		if (!((Object)(object)_instance != (Object)null))
		{
			GameObject val = new GameObject("SULFUR Damage Number Manager");
			Object.DontDestroyOnLoad((Object)(object)val);
			_instance = val.AddComponent<DamageNumberManager>();
		}
	}

	public static void Shutdown()
	{
		if (!((Object)(object)_instance == (Object)null))
		{
			DamageNumberManager instance = _instance;
			_instance = null;
			Object.Destroy((Object)(object)((Component)instance).gameObject);
		}
	}

	public static void RegisterDamage(Unit target, DamageSourceData sourceData, float amount, float hitMultiplier)
	{
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		if (!((Object)(object)target == (Object)null))
		{
			EnsureExists();
			if ((Object)(object)_instance != (Object)null)
			{
				_instance.RegisterDamageInternal(target, sourceData, amount, hitMultiplier);
			}
		}
	}

	public static void SpawnScreenTestNumber()
	{
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		EnsureExists();
		if ((Object)(object)_instance != (Object)null)
		{
			_instance.SpawnScreenFixed("999", new Vector2((float)Screen.width * 0.5f, (float)Screen.height * 0.45f), DamageNumberColor.ForMultiplier(0f));
		}
	}

	private void Update()
	{
		if (!Plugin.DebugSpawnTestOnF8.Value)
		{
			return;
		}
		try
		{
			if (Input.GetKeyDown((KeyCode)289))
			{
				SpawnScreenTestNumber();
				if (Plugin.DebugLog.Value)
				{
					Plugin.Log.LogInfo((object)"[DamageNumbers] F8 test number spawned.");
				}
			}
		}
		catch (Exception ex)
		{
			if (!_inputErrorLogged)
			{
				_inputErrorLogged = true;
				Plugin.Log.LogWarning((object)("[DamageNumbers] F8 debug input is unavailable: " + ex.Message));
			}
		}
	}

	private void RegisterDamageInternal(Unit target, DamageSourceData sourceData, float amount, float hitMultiplier)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_006c: Unknown result type (might be due to invalid IL or missing references)
		//IL_006d: Unknown result type (might be due to invalid IL or missing references)
		Vector3 damagePosition = GetDamagePosition(target);
		if (Plugin.Mode.Value == DisplayMode.PerHit)
		{
			PerHitSpreadOffset nextPerHitSpread = GetNextPerHitSpread(target, sourceData);
			SpawnNumber(damagePosition, amount, hitMultiplier, useJitter: true, nextPerHitSpread.WorldOffset, nextPerHitSpread.ScreenOffset, applyCrowding: true);
			return;
		}
		AggregateKey key = AggregateKey.Create(target, sourceData);
		if (!_aggregates.TryGetValue(key, out var value))
		{
			value = new AggregateEntry
			{
				Amount = 0f,
				Position = damagePosition,
				Frame = Time.frameCount,
				HighestMultiplier = 0f,
				LastMultiplier = 0f,
				WeightedMultiplierSum = 0f
			};
		}
		value.Amount += amount;
		value.Position = damagePosition;
		value.Frame = Time.frameCount;
		value.LastMultiplier = hitMultiplier;
		value.WeightedMultiplierSum += Mathf.Max(0f, hitMultiplier) * Mathf.Max(0f, amount);
		if (hitMultiplier > value.HighestMultiplier)
		{
			value.HighestMultiplier = hitMultiplier;
		}
		_aggregates[key] = value;
	}

	private void LateUpdate()
	{
		FlushAggregates();
		ApplyRendererSelectionIfChanged();
		int maxActiveNumbers = GetMaxActiveNumbers();
		EnforcePoolLimit(_screenPool, maxActiveNumbers);
		EnforcePoolLimit(_worldPool, maxActiveNumbers);
		Camera mainCamera = GetMainCamera();
		float time = Time.time;
		for (int i = 0; i < _screenPool.Count; i++)
		{
			ScreenDamageNumberView screenDamageNumberView = _screenPool[i];
			if (screenDamageNumberView.IsActive)
			{
				screenDamageNumberView.Tick(mainCamera, time, Plugin.ScreenFloatSpeed.Value);
			}
		}
		for (int j = 0; j < _worldPool.Count; j++)
		{
			DamageNumberView damageNumberView = _worldPool[j];
			if (damageNumberView.IsActive)
			{
				damageNumberView.Tick(mainCamera, time);
			}
		}
		MaybeLogDroppedBudget();
	}

	private void FlushAggregates()
	{
		//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
		if (_aggregates.Count == 0)
		{
			return;
		}
		_flushBuffer.Clear();
		foreach (KeyValuePair<AggregateKey, AggregateEntry> aggregate in _aggregates)
		{
			if (aggregate.Value.Frame <= Time.frameCount)
			{
				_flushBuffer.Add(aggregate.Key);
			}
		}
		for (int i = 0; i < _flushBuffer.Count; i++)
		{
			AggregateKey key = _flushBuffer[i];
			if (_aggregates.TryGetValue(key, out var value))
			{
				SpawnNumber(value.Position, value.Amount, ResolveAggregateMultiplier(value), useJitter: false, Vector3.zero, Vector2.zero, applyCrowding: true);
				_aggregates.Remove(key);
			}
		}
	}

	private void SpawnNumber(Vector3 position, float amount, float hitMultiplier, bool useJitter, Vector3 extraWorldJitter, Vector2 extraScreenJitter, bool applyCrowding)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//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_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_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_003b: Unknown result type (might be due to invalid IL or missing references)
		//IL_003c: 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_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_005a: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0070: Unknown result type (might be due to invalid IL or missing references)
		//IL_007f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0084: Unknown result type (might be due to invalid IL or missing references)
		//IL_0089: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b7: 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_0118: Unknown result type (might be due to invalid IL or missing references)
		//IL_0119: Unknown result type (might be due to invalid IL or missing references)
		//IL_0133: Unknown result type (might be due to invalid IL or missing references)
		//IL_0134: 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_0149: Unknown result type (might be due to invalid IL or missing references)
		EnsureFrameCounters();
		Vector3 val = extraWorldJitter;
		Vector2 val2 = extraScreenJitter;
		if (useJitter)
		{
			if (Plugin.RandomJitter.Value > 0f)
			{
				Vector2 val3 = Random.insideUnitCircle * Plugin.RandomJitter.Value;
				val += new Vector3(val3.x, 0f, val3.y);
			}
			if (Plugin.ScreenJitter.Value > 0f)
			{
				val2 += Random.insideUnitCircle * Plugin.ScreenJitter.Value;
			}
		}
		bool flag = false;
		Vector2 screenPosition;
		if (applyCrowding && Plugin.EnableCrowdingProtection.Value)
		{
			flag = TryGetCrowdingScreenPosition(position, val2, out screenPosition);
			if (flag && IsAreaCrowded(screenPosition))
			{
				_crowdingSkippedThisFrame++;
				return;
			}
		}
		else
		{
			screenPosition = Vector2.zero;
		}
		if (TryConsumeSpawnBudget())
		{
			string text = FormatDamage(amount);
			Color color = DamageNumberColor.ForMultiplier(hitMultiplier);
			RenderMode value = Plugin.Renderer.Value;
			if (value == RenderMode.ScreenSpace || value == RenderMode.Both)
			{
				SpawnScreenWorld(text, position, val2, color);
			}
			if (value == RenderMode.WorldTextMeshPro || value == RenderMode.Both)
			{
				SpawnWorld(text, position, val, color);
			}
			if (flag)
			{
				_crowdingPositions.Add(screenPosition);
			}
		}
	}

	private void SpawnScreenWorld(string text, Vector3 worldPosition, Vector2 jitter, Color color)
	{
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		AcquireScreenView()?.PlayWorld(text, worldPosition, jitter, Mathf.Max(0.05f, Plugin.Lifetime.Value), Mathf.Max(8, Plugin.ScreenFontSize.Value), color);
	}

	private void SpawnScreenFixed(string text, Vector2 screenPosition, Color color)
	{
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		AcquireScreenView()?.PlayFixed(text, screenPosition, Mathf.Max(0.05f, Plugin.Lifetime.Value), Mathf.Max(8, Plugin.ScreenFontSize.Value), color);
	}

	private void SpawnWorld(string text, Vector3 worldPosition, Vector3 jitter, Color color)
	{
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		AcquireWorldView()?.Play(text, worldPosition, jitter, Mathf.Max(0.05f, Plugin.Lifetime.Value), Plugin.FloatSpeed.Value, Plugin.WorldScale.Value, Plugin.FontSize.Value, color);
	}

	private ScreenDamageNumberView AcquireScreenView()
	{
		EnsureScreenRoot();
		int maxActiveNumbers = GetMaxActiveNumbers();
		ScreenDamageNumberView screenDamageNumberView = null;
		ScreenDamageNumberView screenDamageNumberView2 = null;
		int num = 0;
		for (int i = 0; i < _screenPool.Count; i++)
		{
			ScreenDamageNumberView screenDamageNumberView3 = _screenPool[i];
			if (!screenDamageNumberView3.IsActive)
			{
				if (screenDamageNumberView == null)
				{
					screenDamageNumberView = screenDamageNumberView3;
				}
				continue;
			}
			num++;
			if (screenDamageNumberView2 == null || screenDamageNumberView3.StartTime < screenDamageNumberView2.StartTime)
			{
				screenDamageNumberView2 = screenDamageNumberView3;
			}
		}
		if (num < maxActiveNumbers)
		{
			if (screenDamageNumberView != null)
			{
				return screenDamageNumberView;
			}
			ScreenDamageNumberView screenDamageNumberView4 = new ScreenDamageNumberView(_screenRoot, _screenSharedMaterial);
			if ((Object)(object)_screenSharedMaterial == (Object)null)
			{
				_screenSharedMaterial = screenDamageNumberView4.FontSharedMaterial;
				screenDamageNumberView4.SetSharedMaterial(_screenSharedMaterial);
			}
			_screenPool.Add(screenDamageNumberView4);
			return screenDamageNumberView4;
		}
		if (screenDamageNumberView2 != null)
		{
			screenDamageNumberView2.Stop();
			return screenDamageNumberView2;
		}
		return screenDamageNumberView;
	}

	private DamageNumberView AcquireWorldView()
	{
		EnsureWorldRoot();
		int maxActiveNumbers = GetMaxActiveNumbers();
		DamageNumberView damageNumberView = null;
		DamageNumberView damageNumberView2 = null;
		int num = 0;
		for (int i = 0; i < _worldPool.Count; i++)
		{
			DamageNumberView damageNumberView3 = _worldPool[i];
			if (!damageNumberView3.IsActive)
			{
				if (damageNumberView == null)
				{
					damageNumberView = damageNumberView3;
				}
				continue;
			}
			num++;
			if (damageNumberView2 == null || damageNumberView3.StartTime < damageNumberView2.StartTime)
			{
				damageNumberView2 = damageNumberView3;
			}
		}
		if (num < maxActiveNumbers)
		{
			if (damageNumberView != null)
			{
				return damageNumberView;
			}
			DamageNumberView damageNumberView4 = new DamageNumberView(_worldRoot, _worldSharedMaterial);
			if ((Object)(object)_worldSharedMaterial == (Object)null)
			{
				_worldSharedMaterial = damageNumberView4.FontSharedMaterial;
				damageNumberView4.SetSharedMaterial(_worldSharedMaterial);
			}
			_worldPool.Add(damageNumberView4);
			return damageNumberView4;
		}
		if (damageNumberView2 != null)
		{
			damageNumberView2.Stop();
			return damageNumberView2;
		}
		return damageNumberView;
	}

	private Camera GetMainCamera()
	{
		if ((Object)(object)_cachedCamera != (Object)null && ((Behaviour)_cachedCamera).isActiveAndEnabled)
		{
			return _cachedCamera;
		}
		float unscaledTime = Time.unscaledTime;
		if (unscaledTime < _nextCameraLookupTime)
		{
			return null;
		}
		_nextCameraLookupTime = unscaledTime + 0.5f;
		_cachedCamera = Camera.main;
		return _cachedCamera;
	}

	private bool TryGetCrowdingScreenPosition(Vector3 worldPosition, Vector2 screenJitter, out Vector2 screenPosition)
	{
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		//IL_005c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0061: Unknown result type (might be due to invalid IL or missing references)
		//IL_0062: Unknown result type (might be due to invalid IL or missing references)
		//IL_0067: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		Camera mainCamera = GetMainCamera();
		PrepareCrowdingPositions(mainCamera);
		if ((Object)(object)mainCamera == (Object)null)
		{
			screenPosition = Vector2.zero;
			return false;
		}
		Vector3 val = mainCamera.WorldToScreenPoint(worldPosition);
		if (val.z <= 0f)
		{
			screenPosition = Vector2.zero;
			return false;
		}
		screenPosition = new Vector2(val.x, val.y) + screenJitter;
		return true;
	}

	private void PrepareCrowdingPositions(Camera camera)
	{
		//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d7: 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_0103: Unknown result type (might be due to invalid IL or missing references)
		//IL_0087: Unknown result type (might be due to invalid IL or missing references)
		int frameCount = Time.frameCount;
		if (_crowdingFrame == frameCount)
		{
			return;
		}
		_crowdingFrame = frameCount;
		_crowdingPositions.Clear();
		if ((Object)(object)camera == (Object)null)
		{
			return;
		}
		RenderMode value = Plugin.Renderer.Value;
		if (value == RenderMode.ScreenSpace || value == RenderMode.Both)
		{
			for (int i = 0; i < _screenPool.Count; i++)
			{
				ScreenDamageNumberView screenDamageNumberView = _screenPool[i];
				if (screenDamageNumberView.IsActive && screenDamageNumberView.IsVisible)
				{
					_crowdingPositions.Add(screenDamageNumberView.CurrentScreenPosition);
				}
			}
			return;
		}
		for (int j = 0; j < _worldPool.Count; j++)
		{
			DamageNumberView damageNumberView = _worldPool[j];
			if (damageNumberView.IsActive)
			{
				Vector3 val = camera.WorldToScreenPoint(damageNumberView.CurrentWorldPosition);
				if (val.z > 0f)
				{
					_crowdingPositions.Add(new Vector2(val.x, val.y));
				}
			}
		}
	}

	private bool IsAreaCrowded(Vector2 screenPosition)
	{
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		//IL_0049: 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)
		float num = Mathf.Clamp(Plugin.CrowdingRadius.Value, 8f, 120f);
		float num2 = num * num;
		int num3 = Mathf.Clamp(Plugin.MaxNearbyNumbers.Value, 1, 32);
		int num4 = 0;
		for (int i = 0; i < _crowdingPositions.Count; i++)
		{
			Vector2 val = _crowdingPositions[i] - screenPosition;
			if (!(((Vector2)(ref val)).sqrMagnitude > num2))
			{
				num4++;
				if (num4 >= num3)
				{
					return true;
				}
			}
		}
		return false;
	}

	private void EnsureScreenRoot()
	{
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		//IL_004a: Expected O, but got Unknown
		if (!((Object)(object)_screenRoot != (Object)null))
		{
			GameObject val = new GameObject("SULFUR Damage Numbers Canvas", new Type[3]
			{
				typeof(RectTransform),
				typeof(Canvas),
				typeof(CanvasScaler)
			});
			val.transform.SetParent(((Component)this).transform, false);
			Canvas component = val.GetComponent<Canvas>();
			component.renderMode = (RenderMode)0;
			component.overrideSorting = true;
			component.sortingOrder = 32760;
			CanvasScaler component2 = val.GetComponent<CanvasScaler>();
			component2.uiScaleMode = (ScaleMode)0;
			component2.scaleFactor = 1f;
			_screenRoot = val.GetComponent<RectTransform>();
		}
	}

	private void EnsureWorldRoot()
	{
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_001d: Expected O, but got Unknown
		if (!((Object)(object)_worldRoot != (Object)null))
		{
			GameObject val = new GameObject("SULFUR Damage Numbers World Pool");
			val.transform.SetParent(((Component)this).transform, false);
			_worldRoot = val.transform;
		}
	}

	private void ApplyRendererSelectionIfChanged()
	{
		RenderMode value = Plugin.Renderer.Value;
		if (_hasAppliedRenderMode && value == _lastAppliedRenderMode)
		{
			return;
		}
		_lastAppliedRenderMode = value;
		_hasAppliedRenderMode = true;
		bool flag = value == RenderMode.ScreenSpace || value == RenderMode.Both;
		bool flag2 = value == RenderMode.WorldTextMeshPro || value == RenderMode.Both;
		if (!flag)
		{
			for (int i = 0; i < _screenPool.Count; i++)
			{
				_screenPool[i].Stop();
			}
		}
		if (!flag2)
		{
			for (int j = 0; j < _worldPool.Count; j++)
			{
				_worldPool[j].Stop();
			}
		}
	}

	private static void EnforcePoolLimit<T>(List<T> pool, int maxActive) where T : class, IDamageNumberView
	{
		int num = 0;
		for (int i = 0; i < pool.Count; i++)
		{
			T val = pool[i];
			if (val.IsActive)
			{
				num++;
			}
		}
		while (num > maxActive)
		{
			T val2 = (T)null;
			for (int j = 0; j < pool.Count; j++)
			{
				T val3 = pool[j];
				if (val3.IsActive && (val2 == null || val3.StartTime < val2.StartTime))
				{
					val2 = val3;
				}
			}
			if (val2 == null)
			{
				break;
			}
			val2.Stop();
			num--;
		}
	}

	private void EnsureFrameCounters()
	{
		int frameCount = Time.frameCount;
		if (_spawnBudgetFrame != frameCount)
		{
			_spawnBudgetFrame = frameCount;
			_spawnedThisFrame = 0;
			_droppedThisFrame = 0;
			_crowdingSkippedThisFrame = 0;
		}
	}

	private bool TryConsumeSpawnBudget()
	{
		EnsureFrameCounters();
		int num = Mathf.Clamp(Plugin.MaxNumbersPerFrame.Value, 1, 256);
		if (_spawnedThisFrame >= num)
		{
			_droppedThisFrame++;
			return false;
		}
		_spawnedThisFrame++;
		return true;
	}

	private void MaybeLogDroppedBudget()
	{
		if (_spawnBudgetFrame == Time.frameCount && Plugin.DebugLog.Value && (_droppedThisFrame > 0 || _crowdingSkippedThisFrame > 0))
		{
			float unscaledTime = Time.unscaledTime;
			if (!(unscaledTime - _lastBudgetLogTime < 1f))
			{
				_lastBudgetLogTime = unscaledTime;
				Plugin.Log.LogWarning((object)("[DamageNumbers] frame " + _spawnBudgetFrame + ": droppedByFrameBudget=" + _droppedThisFrame + ", skippedByCrowding=" + _crowdingSkippedThisFrame + "."));
			}
		}
	}

	private static int GetMaxActiveNumbers()
	{
		return Mathf.Clamp(Plugin.MaxActiveNumbers.Value, 1, 256);
	}

	private static Vector3 GetDamagePosition(Unit target)
	{
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_003b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		Npc val = (Npc)(object)((target is Npc) ? target : null);
		if ((Object)(object)val != (Object)null && (Object)(object)val.overhead != (Object)null)
		{
			return val.overhead.position;
		}
		return ((Component)target).transform.position + Vector3.up * 2f;
	}

	private PerHitSpreadOffset GetNextPerHitSpread(Unit target, DamageSourceData sourceData)
	{
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d1: 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_00fa: 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)
		if (_perHitSpreadFrame != Time.frameCount)
		{
			_perHitSpreadFrame = Time.frameCount;
			_perHitSpreadCounters.Clear();
		}
		PerHitSpreadKey key = PerHitSpreadKey.Create(target, sourceData);
		if (!_perHitSpreadCounters.TryGetValue(key, out var value))
		{
			value = 0;
		}
		_perHitSpreadCounters[key] = value + 1;
		if (value <= 0)
		{
			return PerHitSpreadOffset.Zero;
		}
		float num = (float)value * 2.3999631f;
		float num2 = Mathf.Sqrt((float)value);
		Vector2 val = default(Vector2);
		((Vector2)(ref val))..ctor(Mathf.Cos(num), Mathf.Sin(num));
		float num3 = Mathf.Min(42f, Mathf.Max(0f, Plugin.PerHitStackScreenSpread.Value) * num2);
		float num4 = Mathf.Min(0.36f, Mathf.Max(0f, Plugin.PerHitStackWorldSpread.Value) * num2);
		return new PerHitSpreadOffset
		{
			ScreenOffset = val * num3,
			WorldOffset = new Vector3(val.x * num4, 0f, val.y * num4)
		};
	}

	private static string FormatDamage(float amount)
	{
		if (Plugin.ShowDecimals.Value)
		{
			float num = Mathf.Round(amount * 10f) / 10f;
			float num2 = Mathf.Round(num);
			if (Mathf.Abs(num - num2) <= 0.0001f)
			{
				int num3 = Mathf.RoundToInt(num2);
				if (num3 <= 0 && amount > 0f)
				{
					num3 = 1;
				}
				return num3.ToString(CultureInfo.InvariantCulture);
			}
			return num.ToString("0.0", CultureInfo.InvariantCulture);
		}
		int num4 = Mathf.RoundToInt(amount);
		if (num4 <= 0)
		{
			num4 = 1;
		}
		return num4.ToString(CultureInfo.InvariantCulture);
	}

	private static float ResolveAggregateMultiplier(AggregateEntry entry)
	{
		switch (Plugin.AggregateColorMode.Value)
		{
		case MergedColorMode.LastHit:
			return entry.LastMultiplier;
		case MergedColorMode.DamageWeightedAverage:
			if (entry.Amount > 0.0001f)
			{
				return entry.WeightedMultiplierSum / entry.Amount;
			}
			return entry.HighestMultiplier;
		default:
			return entry.HighestMultiplier;
		}
	}

	private void OnDestroy()
	{
		_aggregates.Clear();
		_flushBuffer.Clear();
		_perHitSpreadCounters.Clear();
		_crowdingPositions.Clear();
		_screenPool.Clear();
		_worldPool.Clear();
		_screenRoot = null;
		_worldRoot = null;
		_cachedCamera = null;
		_screenSharedMaterial = null;
		_worldSharedMaterial = null;
		if ((Object)(object)_instance == (Object)(object)this)
		{
			_instance = null;
		}
	}
}
internal interface IDamageNumberView
{
	bool IsActive { get; }

	float StartTime { get; }

	void Stop();
}
internal sealed class ScreenDamageNumberView : IDamageNumberView
{
	private static readonly Vector2[] OutlineOffsets = (Vector2[])(object)new Vector2[4]
	{
		new Vector2(-1.15f, -1.15f),
		new Vector2(-1.15f, 1.15f),
		new Vector2(1.15f, -1.15f),
		new Vector2(1.15f, 1.15f)
	};

	private readonly GameObject _gameObject;

	private readonly RectTransform _rectTransform;

	private readonly TextMeshProUGUI[] _outlineTexts;

	private readonly TextMeshProUGUI _text;

	private Vector3 _worldPosition;

	private Vector2 _screenPosition;

	private Vector2 _screenJitter;

	private float _lifetime;

	private bool _fixedScreen;

	public bool IsActive { get; private set; }

	public float StartTime { get; private set; }

	public bool IsVisible { get; private set; }

	public Vector2 CurrentScreenPosition { get; private set; }

	public Material FontSharedMaterial => ((TMP_Text)_text).fontSharedMaterial;

	public ScreenDamageNumberView(RectTransform parent, Material sharedMaterial)
	{
		//IL_001f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: Expected O, but got Unknown
		//IL_0052: Unknown result type (might be due to invalid IL or missing references)
		//IL_0062: Unknown result type (might be due to invalid IL or missing references)
		//IL_007c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0096: 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_0100: Unknown result type (might be due to invalid IL or missing references)
		_gameObject = new GameObject("SULFUR Damage Number Screen", new Type[1] { typeof(RectTransform) });
		_gameObject.transform.SetParent((Transform)(object)parent, false);
		_rectTransform = _gameObject.GetComponent<RectTransform>();
		_rectTransform.anchorMin = Vector2.zero;
		_rectTransform.anchorMax = Vector2.zero;
		_rectTransform.pivot = new Vector2(0.5f, 0.5f);
		_rectTransform.sizeDelta = new Vector2(240f, 80f);
		_outlineTexts = (TextMeshProUGUI[])(object)new TextMeshProUGUI[OutlineOffsets.Length];
		for (int i = 0; i < _outlineTexts.Length; i++)
		{
			_outlineTexts[i] = CreateTextLayer("Outline" + i, OutlineOffsets[i]);
		}
		_text = CreateTextLayer("Face", Vector2.zero);
		if ((Object)(object)sharedMaterial != (Object)null)
		{
			SetSharedMaterial(sharedMaterial);
		}
		else if ((Object)(object)((TMP_Text)_text).fontSharedMaterial != (Object)null)
		{
			SetOutlineSharedMaterial(((TMP_Text)_text).fontSharedMaterial);
		}
		Stop();
	}

	private TextMeshProUGUI CreateTextLayer(string name, Vector2 offset)
	{
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Expected O, but got Unknown
		//IL_004e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_0064: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0084: Unknown result type (might be due to invalid IL or missing references)
		//IL_008f: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = new GameObject(name, new Type[3]
		{
			typeof(RectTransform),
			typeof(CanvasRenderer),
			typeof(TextMeshProUGUI)
		});
		val.transform.SetParent((Transform)(object)_rectTransform, false);
		RectTransform component = val.GetComponent<RectTransform>();
		component.anchorMin = Vector2.zero;
		component.anchorMax = Vector2.one;
		component.offsetMin = Vector2.zero;
		component.offsetMax = Vector2.zero;
		component.pivot = new Vector2(0.5f, 0.5f);
		component.anchoredPosition = offset;
		TextMeshProUGUI component2 = val.GetComponent<TextMeshProUGUI>();
		((TMP_Text)component2).alignment = (TextAlignmentOptions)514;
		((TMP_Text)component2).fontStyle = (FontStyles)1;
		((TMP_Text)component2).textWrappingMode = (TextWrappingModes)0;
		((TMP_Text)component2).overflowMode = (TextOverflowModes)0;
		((Graphic)component2).raycastTarget = false;
		return component2;
	}

	public void SetSharedMaterial(Material sharedMaterial)
	{
		if (!((Object)(object)sharedMaterial == (Object)null))
		{
			((TMP_Text)_text).fontSharedMaterial = sharedMaterial;
			SetOutlineSharedMaterial(sharedMaterial);
		}
	}

	private void SetOutlineSharedMaterial(Material sharedMaterial)
	{
		for (int i = 0; i < _outlineTexts.Length; i++)
		{
			((TMP_Text)_outlineTexts[i]).fontSharedMaterial = sharedMaterial;
		}
	}

	public void PlayWorld(string value, Vector3 worldPosition, Vector2 jitter, float lifetime, int fontSize, Color color)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//IL_0009: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		_worldPosition = worldPosition;
		_screenJitter = jitter;
		_fixedScreen = false;
		PlayCommon(value, lifetime, fontSize, color);
	}

	public void PlayFixed(string value, Vector2 screenPosition, float lifetime, int fontSize, Color color)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		_screenPosition = screenPosition;
		_screenJitter = Vector2.zero;
		_fixedScreen = true;
		PlayCommon(value, lifetime, fontSize, color);
	}

	private void PlayCommon(string value, float lifetime, int fontSize, Color color)
	{
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		//IL_0083: Unknown result type (might be due to invalid IL or missing references)
		StartTime = Time.time;
		_lifetime = Mathf.Max(0.05f, lifetime);
		color.a = 1f;
		((TMP_Text)_text).text = value;
		((TMP_Text)_text).fontSize = fontSize;
		((Graphic)_text).color = color;
		_text.canvasRenderer.SetAlpha(1f);
		for (int i = 0; i < _outlineTexts.Length; i++)
		{
			TextMeshProUGUI val = _outlineTexts[i];
			((TMP_Text)val).text = value;
			((TMP_Text)val).fontSize = fontSize;
			((Graphic)val).color = Color.black;
			val.canvasRenderer.SetAlpha(1f);
			((Behaviour)val).enabled = true;
		}
		((Behaviour)_text).enabled = true;
		_gameObject.SetActive(true);
		IsVisible = true;
		IsActive = true;
	}

	public void Tick(Camera camera, float now, float floatSpeed)
	{
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_005b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0060: Unknown result type (might be due to invalid IL or missing references)
		//IL_006c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0071: Unknown result type (might be due to invalid IL or missing references)
		//IL_0076: Unknown result type (might be due to invalid IL or missing references)
		//IL_0078: Unknown result type (might be due to invalid IL or missing references)
		//IL_0079: Unknown result type (might be due to invalid IL or missing references)
		//IL_007e: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ce: 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_00ed: Unknown result type (might be due to invalid IL or missing references)
		if (!IsActive)
		{
			return;
		}
		float num = now - StartTime;
		if (num >= _lifetime)
		{
			Stop();
			return;
		}
		Vector2 val = default(Vector2);
		if (_fixedScreen)
		{
			val = _screenPosition;
		}
		else
		{
			if ((Object)(object)camera == (Object)null)
			{
				SetLayersEnabled(enabled: false);
				IsVisible = false;
				return;
			}
			Vector3 val2 = _worldPosition + Vector3.up * (num * 0.25f);
			Vector3 val3 = camera.WorldToScreenPoint(val2);
			if (val3.z <= 0f)
			{
				SetLayersEnabled(enabled: false);
				IsVisible = false;
				return;
			}
			((Vector2)(ref val))..ctor(val3.x, val3.y);
		}
		SetLayersEnabled(enabled: true);
		IsVisible = true;
		val += _screenJitter;
		val.y += num * floatSpeed;
		CurrentScreenPosition = val;
		_rectTransform.anchoredPosition = val;
		float alpha = Mathf.Clamp01(1f - num / _lifetime);
		_text.canvasRenderer.SetAlpha(alpha);
		for (int i = 0; i < _outlineTexts.Length; i++)
		{
			_outlineTexts[i].canvasRenderer.SetAlpha(alpha);
		}
	}

	private void SetLayersEnabled(bool enabled)
	{
		((Behaviour)_text).enabled = enabled;
		for (int i = 0; i < _outlineTexts.Length; i++)
		{
			((Behaviour)_outlineTexts[i]).enabled = enabled;
		}
	}

	public void Stop()
	{
		IsActive = false;
		IsVisible = false;
		if ((Object)(object)_text != (Object)null)
		{
			_text.canvasRenderer.SetAlpha(0f);
			((Behaviour)_text).enabled = false;
		}
		if (_outlineTexts != null)
		{
			for (int i = 0; i < _outlineTexts.Length; i++)
			{
				TextMeshProUGUI val = _outlineTexts[i];
				if (!((Object)(object)val == (Object)null))
				{
					val.canvasRenderer.SetAlpha(0f);
					((Behaviour)val).enabled = false;
				}
			}
		}
		if ((Object)(object)_gameObject != (Object)null)
		{
			_gameObject.SetActive(false);
		}
	}
}
internal struct PerHitSpreadKey : IEquatable<PerHitSpreadKey>
{
	private int _targetId;

	private int _frame;

	private int _sourceId;

	public static PerHitSpreadKey Create(Unit target, DamageSourceData sourceData)
	{
		int sourceId = 0;
		if ((Object)(object)sourceData.sourceWeapon != (Object)null)
		{
			sourceId = ((Object)sourceData.sourceWeapon).GetInstanceID();
		}
		else if (sourceData.instanceId != 0)
		{
			sourceId = sourceData.instanceId;
		}
		else if ((Object)(object)sourceData.sourceUnit != (Object)null)
		{
			sourceId = ((Object)sourceData.sourceUnit).GetInstanceID();
		}
		return new PerHitSpreadKey
		{
			_targetId = ((Object)target).GetInstanceID(),
			_frame = Time.frameCount,
			_sourceId = sourceId
		};
	}

	public bool Equals(PerHitSpreadKey other)
	{
		return _targetId == other._targetId && _frame == other._frame && _sourceId == other._sourceId;
	}

	public override bool Equals(object obj)
	{
		return obj is PerHitSpreadKey && Equals((PerHitSpreadKey)obj);
	}

	public override int GetHashCode()
	{
		int targetId = _targetId;
		targetId = (targetId * 397) ^ _frame;
		return (targetId * 397) ^ _sourceId;
	}
}
internal struct AggregateKey : IEquatable<AggregateKey>
{
	private int _targetId;

	private int _frame;

	private int _sourceId;

	private int _damageType;

	private bool _melee;

	public static AggregateKey Create(Unit target, DamageSourceData sourceData)
	{
		//IL_008b: Unknown result type (might be due to invalid IL or missing references)
		int sourceId = 0;
		if ((Object)(object)sourceData.sourceWeapon != (Object)null)
		{
			sourceId = ((Object)sourceData.sourceWeapon).GetInstanceID();
		}
		else if (sourceData.instanceId != 0)
		{
			sourceId = sourceData.instanceId;
		}
		else if ((Object)(object)sourceData.sourceUnit != (Object)null)
		{
			sourceId = ((Object)sourceData.sourceUnit).GetInstanceID();
		}
		return new AggregateKey
		{
			_targetId = ((Object)target).GetInstanceID(),
			_frame = Time.frameCount,
			_sourceId = sourceId,
			_damageType = Convert.ToInt32(sourceData.damageType),
			_melee = sourceData.melee
		};
	}

	public bool Equals(AggregateKey other)
	{
		return _targetId == other._targetId && _frame == other._frame && _sourceId == other._sourceId && _damageType == other._damageType && _melee == other._melee;
	}

	public override bool Equals(object obj)
	{
		return obj is AggregateKey && Equals((AggregateKey)obj);
	}

	public override int GetHashCode()
	{
		int targetId = _targetId;
		targetId = (targetId * 397) ^ _frame;
		targetId = (targetId * 397) ^ _sourceId;
		targetId = (targetId * 397) ^ _damageType;
		return (targetId * 397) ^ (_melee ? 1 : 0);
	}
}
internal static class DamageNumberColor
{
	private static readonly Color NormalDamageColor = Color.white;

	private static readonly Color OrangeDamageColor = new Color(1f, 0.5f, 0f, 1f);

	public static Color ForMultiplier(float multiplier)
	{
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		//IL_005c: Unknown result type (might be due to invalid IL or missing references)
		//IL_004b: Unknown result type (might be due to invalid IL or missing references)
		//IL_006d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0084: Unknown result type (might be due to invalid IL or missing references)
		//IL_007e: Unknown result type (might be due to invalid IL or missing references)
		if (!Plugin.UseWeakspotColors.Value)
		{
			return NormalDamageColor;
		}
		if (multiplier <= 0f)
		{
			return NormalDamageColor;
		}
		if (Plugin.TreatOneMultiplierAsNormal.Value && Mathf.Abs(multiplier - 1f) <= 0.0001f)
		{
			return NormalDamageColor;
		}
		if (multiplier >= 1.5f)
		{
			return Color.red;
		}
		if (multiplier >= 1f)
		{
			return OrangeDamageColor;
		}
		if (multiplier >= 0.75f)
		{
			return Color.yellow;
		}
		return NormalDamageColor;
	}
}
internal sealed class DamageNumberView : IDamageNumberView
{
	private const int WorldFadeSteps = 10;

	private const float WorldOutlineOffsetFactor = 0.02f;

	private static readonly Vector2[] OutlineDirections = (Vector2[])(object)new Vector2[4]
	{
		new Vector2(-1f, -1f),
		new Vector2(-1f, 1f),
		new Vector2(1f, -1f),
		new Vector2(1f, 1f)
	};

	private readonly GameObject _gameObject;

	private readonly Transform _transform;

	private readonly TextMeshPro[] _outlineTexts;

	private readonly TextMeshPro _text;

	private Vector3 _basePosition;

	private Vector3 _jitter;

	private float _lifetime;

	private float _floatSpeed;

	private Color _baseColor;

	private int _lastFadeStep = -1;

	public bool IsActive { get; private set; }

	public float StartTime { get; private set; }

	public Vector3 CurrentWorldPosition => _transform.position;

	public Material FontSharedMaterial => ((TMP_Text)_text).fontSharedMaterial;

	public DamageNumberView(Transform parent, Material sharedMaterial)
	{
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		//IL_001d: Expected O, but got Unknown
		_gameObject = new GameObject("SULFUR Damage Number World");
		_gameObject.transform.SetParent(parent, false);
		_transform = _gameObject.transform;
		_outlineTexts = (TextMeshPro[])(object)new TextMeshPro[OutlineDirections.Length];
		for (int i = 0; i < _outlineTexts.Length; i++)
		{
			_outlineTexts[i] = CreateTextLayer("Outline" + i, 0);
		}
		_text = CreateTextLayer("Face", 1);
		if ((Object)(object)sharedMaterial != (Object)null)
		{
			SetSharedMaterial(sharedMaterial);
		}
		else if ((Object)(object)((TMP_Text)_text).fontSharedMaterial != (Object)null)
		{
			SetOutlineSharedMaterial(((TMP_Text)_text).fontSharedMaterial);
		}
		Stop();
	}

	private TextMeshPro CreateTextLayer(string name, int sortingOrder)
	{
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Expected O, but got Unknown
		GameObject val = new GameObject(name, new Type[1] { typeof(TextMeshPro) });
		val.transform.SetParent(_transform, false);
		TextMeshPro component = val.GetComponent<TextMeshPro>();
		((TMP_Text)component).alignment = (TextAlignmentOptions)514;
		((TMP_Text)component).fontStyle = (FontStyles)1;
		((TMP_Text)component).textWrappingMode = (TextWrappingModes)0;
		((TMP_Text)component).overflowMode = (TextOverflowModes)0;
		component.sortingOrder = sortingOrder;
		return component;
	}

	public void SetSharedMaterial(Material sharedMaterial)
	{
		if (!((Object)(object)sharedMaterial == (Object)null))
		{
			((TMP_Text)_text).fontSharedMaterial = sharedMaterial;
			SetOutlineSharedMaterial(sharedMaterial);
		}
	}

	private void SetOutlineSharedMaterial(Material sharedMaterial)
	{
		for (int i = 0; i < _outlineTexts.Length; i++)
		{
			((TMP_Text)_outlineTexts[i]).fontSharedMaterial = sharedMaterial;
		}
	}

	public void Play(string value, Vector3 position, Vector3 jitter, float lifetime, float floatSpeed, float worldScale, float fontSize, Color color)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//IL_0009: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_0049: Unknown result type (might be due to invalid IL or missing references)
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_004b: Unknown result type (might be due to invalid IL or missing references)
		//IL_005b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0062: Unknown result type (might be due to invalid IL or missing references)
		//IL_0097: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
		//IL_0109: Unknown result type (might be due to invalid IL or missing references)
		_basePosition = position;
		_jitter = jitter;
		StartTime = Time.time;
		_lifetime = Mathf.Max(0.05f, lifetime);
		_floatSpeed = floatSpeed;
		_baseColor = color;
		_lastFadeStep = 10;
		_transform.position = position + jitter;
		_transform.localScale = Vector3.one * worldScale;
		color.a = 1f;
		((TMP_Text)_text).text = value;
		((TMP_Text)_text).fontSize = fontSize;
		((Graphic)_text).color = color;
		float num = Mathf.Max(0.05f, fontSize * 0.02f);
		for (int i = 0; i < _outlineTexts.Length; i++)
		{
			TextMeshPro val = _outlineTexts[i];
			Vector2 val2 = OutlineDirections[i];
			val.transform.localPosition = new Vector3(val2.x * num, val2.y * num, 0f);
			((TMP_Text)val).text = value;
			((TMP_Text)val).fontSize = fontSize;
			((Graphic)val).color = Color.black;
			((Behaviour)val).enabled = true;
		}
		((Behaviour)_text).enabled = true;
		_gameObject.SetActive(true);
		IsActive = true;
	}

	public void Tick(Camera camera, float now)
	{
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//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)
		//IL_006d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0078: Unknown result type (might be due to invalid IL or missing references)
		//IL_007d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0082: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_010d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0114: Unknown result type (might be due to invalid IL or missing references)
		//IL_0119: Unknown result type (might be due to invalid IL or missing references)
		//IL_009a: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a6: 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)
		if (!IsActive)
		{
			return;
		}
		float num = now - StartTime;
		if (num >= _lifetime)
		{
			Stop();
			return;
		}
		_transform.position = _basePosition + _jitter + Vector3.up * (num * _floatSpeed);
		if ((Object)(object)camera != (Object)null)
		{
			Vector3 val = _transform.position - ((Component)camera).transform.position;
			if (((Vector3)(ref val)).sqrMagnitude > 0.0001f)
			{
				_transform.rotation = Quaternion.LookRotation(val, ((Component)camera).transform.up);
			}
		}
		float num2 = Mathf.Clamp01(1f - num / _lifetime);
		int num3 = Mathf.Clamp(Mathf.CeilToInt(num2 * 10f), 0, 10);
		if (num3 != _lastFadeStep)
		{
			_lastFadeStep = num3;
			float a = (float)num3 / 10f;
			Color baseColor = _baseColor;
			baseColor.a = a;
			((Graphic)_text).color = baseColor;
			Color black = Color.black;
			black.a = a;
			for (int i = 0; i < _outlineTexts.Length; i++)
			{
				((Graphic)_outlineTexts[i]).color = black;
			}
		}
	}

	public void Stop()
	{
		IsActive = false;
		if ((Object)(object)_text != (Object)null)
		{
			((Behaviour)_text).enabled = false;
		}
		if (_outlineTexts != null)
		{
			for (int i = 0; i < _outlineTexts.Length; i++)
			{
				if ((Object)(object)_outlineTexts[i] != (Object)null)
				{
					((Behaviour)_outlineTexts[i]).enabled = false;
				}
			}
		}
		if ((Object)(object)_gameObject != (Object)null)
		{
			_gameObject.SetActive(false);
		}
	}
}
internal static class DamageReceivePatch
{
	public static void Apply(Harmony harmony)
	{
		DamageLogPatch.Apply(harmony);
	}
}
[BepInPlugin("ryuka.sulfur.damagenumbers", "SULFUR Damage Numbers", "1.0.12")]
public sealed class Plugin : BaseUnityPlugin
{
	public const string PluginGuid = "ryuka.sulfur.damagenumbers";

	public const string PluginName = "SULFUR Damage Numbers";

	public const string PluginVersion = "1.0.12";

	internal static ConfigEntry<bool> Enabled;

	internal static ConfigEntry<DisplayMode> Mode;

	internal static ConfigEntry<RenderMode> Renderer;

	internal static ConfigEntry<MergedColorMode> AggregateColorMode;

	internal static ConfigEntry<bool> UseWeakspotColors;

	internal static ConfigEntry<bool> TreatOneMultiplierAsNormal;

	internal static ConfigEntry<bool> RequireNpcTarget;

	internal static ConfigEntry<bool> RequireSourceUnitPlayer;

	internal static ConfigEntry<bool> ShowPlayerOwnedSecondaryEffects;

	internal static ConfigEntry<float> MinDamageToShow;

	internal static ConfigEntry<bool> ShowDecimals;

	internal static ConfigEntry<float> Lifetime;

	internal static ConfigEntry<float> FloatSpeed;

	internal static ConfigEntry<float> WorldScale;

	internal static ConfigEntry<float> RandomJitter;

	internal static ConfigEntry<float> FontSize;

	internal static ConfigEntry<int> ScreenFontSize;

	internal static ConfigEntry<float> ScreenFloatSpeed;

	internal static ConfigEntry<float> ScreenJitter;

	internal static ConfigEntry<float> PerHitStackScreenSpread;

	internal static ConfigEntry<float> PerHitStackWorldSpread;

	internal static ConfigEntry<int> MaxActiveNumbers;

	internal static ConfigEntry<int> MaxNumbersPerFrame;

	internal static ConfigEntry<bool> EnableCrowdingProtection;

	internal static ConfigEntry<float> CrowdingRadius;

	internal static ConfigEntry<int> MaxNearbyNumbers;

	internal static ConfigEntry<bool> DebugLog;

	internal static ConfigEntry<bool> DebugSpawnTestOnF8;

	internal static ManualLogSource Log;

	private Harmony _harmony;

	private void Awake()
	{
		//IL_03c8: Unknown result type (might be due to invalid IL or missing references)
		//IL_03d2: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable damage number display.");
		Mode = ((BaseUnityPlugin)this).Config.Bind<DisplayMode>("General", "DamageDisplayMode", DisplayMode.PerTargetTotal, "PerTargetTotal = same frame, same player attack source, same target damage is merged. PerHit = each real damage log is shown separately. Crowding and frame-budget settings are independent visual performance limits applied after either mode.");
		Renderer = ((BaseUnityPlugin)this).Config.Bind<RenderMode>("Visual", "Renderer", RenderMode.ScreenSpace, "ScreenSpace uses pooled TextMeshProUGUI objects. WorldTextMeshPro uses pooled 3D TextMeshPro objects. Both renders both at the same time.");
		UseWeakspotColors = ((BaseUnityPlugin)this).Config.Bind<bool>("Visual", "UseWeakspotColors", true, "Use weakspot/body-part multiplier colors. Red >= 1.5, orange >= 1.0, yellow >= 0.75, white when no multiplier is available.");
		TreatOneMultiplierAsNormal = ((BaseUnityPlugin)this).Config.Bind<bool>("Visual", "TreatOneMultiplierAsNormal", false, "If true, multiplier 1.0 is shown as white instead of orange.");
		AggregateColorMode = ((BaseUnityPlugin)this).Config.Bind<MergedColorMode>("Visual", "AggregateColorMode", MergedColorMode.HighestMultiplier, "Color rule for PerTargetTotal. HighestMultiplier = strongest hit color. DamageWeightedAverage = damage-weighted multiplier. LastHit = last hit color.");
		RequireNpcTarget = ((BaseUnityPlugin)this).Config.Bind<bool>("Filter", "RequireNpcTarget", true, "Only show damage numbers on Npc targets. Keep true to avoid barrels/objects if they also use Unit damage code.");
		RequireSourceUnitPlayer = ((BaseUnityPlugin)this).Config.Bind<bool>("Filter", "RequireSourceUnitPlayer", false, "If true, sourceData.sourceUnit must exist and be the player. Default false because some projectiles/explosions keep only the game's isPlayer flag.");
		ShowPlayerOwnedSecondaryEffects = ((BaseUnityPlugin)this).Config.Bind<bool>("Filter", "ShowPlayerOwnedSecondaryEffects", true, "Show player-owned secondary damage such as projectile explosions if the game reports them as created by the player.");
		MinDamageToShow = ((BaseUnityPlugin)this).Config.Bind<float>("Filter", "MinDamageToShow", 0.01f, "Final damage below this value will not be shown.");
		ShowDecimals = ((BaseUnityPlugin)this).Config.Bind<bool>("Visual", "ShowDecimals", false, "Show one decimal place for non-integer damage. Integer values such as 5.0 are still shown as 5.");
		Lifetime = ((BaseUnityPlugin)this).Config.Bind<float>("Visual", "Lifetime", 0.85f, "Damage number lifetime in seconds.");
		FloatSpeed = ((BaseUnityPlugin)this).Config.Bind<float>("Visual", "FloatSpeed", 1.25f, "World-space upward speed for WorldTextMeshPro renderer.");
		WorldScale = ((BaseUnityPlugin)this).Config.Bind<float>("Visual", "WorldScale", 0.045f, "World-space scale of each TextMeshPro damage number.");
		RandomJitter = ((BaseUnityPlugin)this).Config.Bind<float>("Visual", "RandomJitter", 0.18f, "Random horizontal world-space offset. Useful for PerHit shotgun pellets.");
		FontSize = ((BaseUnityPlugin)this).Config.Bind<float>("Visual", "FontSize", 8f, "TextMeshPro font size for WorldTextMeshPro renderer.");
		ScreenFontSize = ((BaseUnityPlugin)this).Config.Bind<int>("Visual", "ScreenFontSize", 30, "Screen-space TextMeshPro font size.");
		ScreenFloatSpeed = ((BaseUnityPlugin)this).Config.Bind<float>("Visual", "ScreenFloatSpeed", 70f, "Screen-space upward speed in pixels per second.");
		ScreenJitter = ((BaseUnityPlugin)this).Config.Bind<float>("Visual", "ScreenJitter", 26f, "Random screen-space offset in pixels. Useful for PerHit shotgun pellets.");
		PerHitStackScreenSpread = ((BaseUnityPlugin)this).Config.Bind<float>("Visual", "PerHitStackScreenSpread", 12.8f, "Extra deterministic screen-space spread in pixels for multiple PerHit numbers created on the same target in the same frame.");
		PerHitStackWorldSpread = ((BaseUnityPlugin)this).Config.Bind<float>("Visual", "PerHitStackWorldSpread", 0.06f, "Extra deterministic world-space spread for multiple PerHit numbers created on the same target in the same frame.");
		MaxActiveNumbers = ((BaseUnityPlugin)this).Config.Bind<int>("Performance", "MaxActiveNumbers", 160, "Maximum active damage numbers in each enabled renderer. Values are hard-clamped to 1-256 and the oldest active number is reused when full.");
		MaxNumbersPerFrame = ((BaseUnityPlugin)this).Config.Bind<int>("Performance", "MaxNumbersPerFrame", 32, "Maximum new logical damage numbers spawned in one frame. Values are hard-clamped to 1-256. Extra events are dropped to protect Unity's GC during extreme hit spam.");
		EnableCrowdingProtection = ((BaseUnityPlugin)this).Config.Bind<bool>("Performance", "EnableCrowdingProtection", true, "Skip a new number when several active numbers already occupy the same small screen area. This is a visual limiter applied after the selected display mode; it does not merge PerHit damage or change PerTargetTotal aggregation.");
		CrowdingRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Performance", "CrowdingRadius", 36f, "Screen-space radius in pixels used by crowding protection. Values are hard-clamped to 8-120.");
		MaxNearbyNumbers = ((BaseUnityPlugin)this).Config.Bind<int>("Performance", "MaxNearbyNumbers", 5, "Maximum existing numbers allowed inside the crowding radius before a new visually covered number is skipped. Values are hard-clamped to 1-32.");
		DebugLog = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DebugLog", false, "Print accepted/rejected damage events and rate-limit warnings to LogOutput.log.");
		DebugSpawnTestOnF8 = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DebugSpawnTestOnF8", false, "Press F8 in-game to spawn a test number. Disabled by default in release builds.");
		DamageNumberManager.EnsureExists();
		_harmony = new Harmony("ryuka.sulfur.damagenumbers");
		DamageLogPatch.Apply(_harmony);
		((BaseUnityPlugin)this).Logger.LogInfo((object)string.Concat("SULFUR Damage Numbers 1.0.12 loaded. Renderer=", Renderer.Value, ", Mode=", Mode.Value, "."));
	}

	private void OnDestroy()
	{
		if (_harmony != null)
		{
			_harmony.UnpatchSelf();
			_harmony = null;
		}
		DamageLogPatch.Reset();
		DamageNumberManager.Shutdown();
	}
}
public enum DisplayMode
{
	PerTargetTotal = 1,
	PerHit
}
public enum RenderMode
{
	ScreenSpace = 1,
	WorldTextMeshPro,
	Both
}
public enum MergedColorMode
{
	HighestMultiplier = 1,
	DamageWeightedAverage,
	LastHit
}