Decompiled source of CombatStats v1.0.8

plugins/Tenemo.CombatStats.dll

Decompiled 16 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using CombatStats.Collect;
using CombatStats.Model;
using CombatStats.Net;
using CombatStats.Stats;
using CombatStats.Ui;
using HarmonyLib;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyVersion("0.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 CombatStats
{
	internal sealed class ModConfig
	{
		public readonly ConfigEntry<KeyboardShortcut> CompactKey;

		public readonly ConfigEntry<KeyboardShortcut> DetailKey;

		public readonly ConfigEntry<MeterMode> CompactMode;

		public readonly ConfigEntry<int> CompactRows;

		public readonly ConfigEntry<ScreenAnchor> CompactAnchor;

		public readonly ConfigEntry<Vector2> CompactOffset;

		public readonly ConfigEntry<Vector2> DetailSize;

		public readonly ConfigEntry<float> CompactWidth;

		public readonly ConfigEntry<float> CompactScale;

		public readonly ConfigEntry<float> CompactOpacity;

		public readonly ConfigEntry<bool> CompactCaption;

		public readonly ConfigEntry<bool> CompactShare;

		public readonly ConfigEntry<bool> CompactBars;

		public readonly ConfigEntry<bool> IsDebug;

		public readonly ConfigEntry<string> Colors;

		private readonly ConfigFile config;

		private readonly SortedDictionary<string, SortedDictionary<string, string>> personal = new SortedDictionary<string, SortedDictionary<string, string>>();

		private readonly string path;

		private bool writable = true;

		public ModConfig(ConfigFile config)
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Expected O, but got Unknown
			//IL_00f6: 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_013e: Expected O, but got Unknown
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Expected O, but got Unknown
			//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: Expected O, but got Unknown
			//IL_021f: Unknown result type (might be due to invalid IL or missing references)
			this.config = config;
			config.SaveOnConfigSet = false;
			CompactKey = config.Bind<KeyboardShortcut>("Compact", "CompactKey", new KeyboardShortcut((KeyCode)291, Array.Empty<KeyCode>()), "Cycle compact meter visibility.");
			DetailKey = config.Bind<KeyboardShortcut>("Detail", "DetailKey", new KeyboardShortcut((KeyCode)292, Array.Empty<KeyCode>()), "Open fight details.");
			CompactMode = config.Bind<MeterMode>("Compact", "CompactMode", MeterMode.Off, "Auto shows the running or latest fight; Always also shows an empty meter; Off hides it.");
			CompactRows = config.Bind<int>("Compact", "CompactRows", 5, new ConfigDescription("Visible rows, including your own.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 12), Array.Empty<object>()));
			CompactAnchor = config.Bind<ScreenAnchor>("Compact", "CompactAnchor", ScreenAnchor.TopRight, "Screen corner or edge.");
			CompactOffset = config.Bind<Vector2>("Compact", "CompactOffset", new Vector2(28f, 164f), "Distance in pixels from the chosen corner towards the middle of the screen; middle edges centre the meter vertically.");
			CompactWidth = config.Bind<float>("Compact", "CompactWidth", 360f, new ConfigDescription("Width in pixels.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(260f, 700f), Array.Empty<object>()));
			CompactScale = config.Bind<float>("Compact", "CompactScale", 1f, new ConfigDescription("", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.75f, 2f), Array.Empty<object>()));
			CompactOpacity = config.Bind<float>("Compact", "CompactOpacity", 0.9f, new ConfigDescription("", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 1f), Array.Empty<object>()));
			CompactCaption = config.Bind<bool>("Compact", "CompactCaption", true, "Show the fight's name, duration and result.");
			CompactShare = config.Bind<bool>("Compact", "CompactShare", true, "Show contribution percentages.");
			CompactBars = config.Bind<bool>("Compact", "CompactBars", true, "Show damage-type bars.");
			DetailSize = config.Bind<Vector2>("Detail", "DetailSize", new Vector2(700f, 480f), "Width and height in pixels.");
			Colors = config.Bind<string>("Colours", "Colors", "", "Damage-type overrides, such as Fire=#ff7733.");
			IsDebug = config.Bind<bool>("General", "IsDebug", false, "Enable diagnostic logging.");
			path = Path.Combine(Paths.BepInExRootPath, "combat-stats-state", "settings.dat");
			LoadPersonal();
			config.SettingChanged += delegate(object _, SettingChangedEventArgs change)
			{
				Remember(change.ChangedSetting.Definition, change.ChangedSetting.GetSerializedValue());
				Save();
			};
		}

		private void LoadPersonal()
		{
			//IL_0198: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: Unknown result type (might be due to invalid IL or missing references)
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Expected O, but got Unknown
			string problem;
			string text = Storage.Read(path, out problem);
			if (problem != null)
			{
				writable = false;
				Plugin.Log.LogError((object)(problem + ". Display changes are not saved this session."));
			}
			if (text == null)
			{
				return;
			}
			string text2 = null;
			bool flag = false;
			string[] array = text.Split('\n');
			for (int i = 0; i < array.Length; i++)
			{
				string text3 = array[i].Trim();
				if (text3.Length == 0 || text3.StartsWith("#"))
				{
					continue;
				}
				if (text3.StartsWith("[") && text3.EndsWith("]"))
				{
					text2 = text3.Substring(1, text3.Length - 2);
					continue;
				}
				string[] array2 = text3.Split(new char[1] { '=' }, 2);
				if (text2 != null && array2.Length == 2)
				{
					ConfigDefinition val = new ConfigDefinition(text2, array2[0].Trim());
					if (text2 == "General" && val.Key == "Enabled" && bool.TryParse(array2[1].Trim(), out var result))
					{
						flag = !result;
					}
					if (config.ContainsKey(val))
					{
						config[val].SetSerializedValue(array2[1].Trim());
						Remember(val, config[val].GetSerializedValue());
					}
				}
			}
			if (flag)
			{
				CompactMode.Value = MeterMode.Off;
				Remember(((ConfigEntryBase)CompactMode).Definition, ((ConfigEntryBase)CompactMode).GetSerializedValue());
			}
			Vector2 value = CompactOffset.Value;
			if (value.x < 0f || value.y < 0f)
			{
				CompactOffset.Value = new Vector2(Mathf.Abs(value.x), Mathf.Abs(value.y));
				Remember(((ConfigEntryBase)CompactOffset).Definition, ((ConfigEntryBase)CompactOffset).GetSerializedValue());
			}
		}

		private void Remember(ConfigDefinition definition, string value)
		{
			if (!personal.TryGetValue(definition.Section, out var value2))
			{
				value2 = (personal[definition.Section] = new SortedDictionary<string, string>());
			}
			value2[definition.Key] = value;
		}

		private void Save()
		{
			if (!writable)
			{
				return;
			}
			StringBuilder stringBuilder = new StringBuilder("## Personal combat meter display settings. Gale does not replace this file.\n");
			foreach (KeyValuePair<string, SortedDictionary<string, string>> item in personal)
			{
				stringBuilder.Append("\n[").Append(item.Key).Append("]\n");
				foreach (KeyValuePair<string, string> item2 in item.Value)
				{
					stringBuilder.Append(item2.Key).Append(" = ").Append(item2.Value)
						.Append('\n');
				}
			}
			string text = Storage.Write(path, stringBuilder.ToString());
			if (text != null)
			{
				Plugin.Log.LogWarning((object)text);
			}
		}

		public Color[] ParseColors()
		{
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			Color[] array = (Color[])(object)new Color[11];
			for (int i = 0; i < array.Length; i++)
			{
				ColorUtility.TryParseHtmlString(DamageKinds.ColorHex((DamageKind)i), ref array[i]);
			}
			string[] array2 = Colors.Value.Split(',');
			Color val = default(Color);
			for (int j = 0; j < array2.Length; j++)
			{
				string[] array3 = array2[j].Split('=');
				if (array3.Length == 2 && Enum.TryParse<DamageKind>(array3[0].Trim(), ignoreCase: true, out var result) && result >= DamageKind.Blunt && (int)result < array.Length && ColorUtility.TryParseHtmlString(array3[1].Trim(), ref val))
				{
					array[(int)result] = val;
				}
			}
			return array;
		}
	}
	[BepInPlugin("tenemo.CombatStats", "Combat stats", "1.0.8")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInIncompatibility("muindor.CombatStats")]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Id = "tenemo.CombatStats";

		public const string Version = "1.0.8";

		internal static ManualLogSource Log;

		internal static ModConfig Settings;

		internal static DamageCollector Collector;

		internal static CombatHistory History = new CombatHistory();

		internal static FightSync Sync;

		private Harmony harmony;

		private void Awake()
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Invalid comparison between Unknown and I4
			Log = ((BaseUnityPlugin)this).Logger;
			Settings = new ModConfig(((BaseUnityPlugin)this).Config);
			Collector = new DamageCollector();
			harmony = new Harmony("tenemo.CombatStats");
			try
			{
				harmony.PatchAll(typeof(Plugin).Assembly);
			}
			catch
			{
				harmony.UnpatchSelf();
				throw;
			}
			Sync = ((Component)this).gameObject.AddComponent<FightSync>();
			if ((int)SystemInfo.graphicsDeviceType != 4)
			{
				((Component)this).gameObject.AddComponent<CompactMeter>();
				((Component)this).gameObject.AddComponent<DetailWindow>();
			}
			Log.LogInfo((object)"Combat stats 1.0.8 loaded: shared fights, whole-fight DPS and effect attribution.");
		}

		private void OnDestroy()
		{
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
		}

		internal static void Debug(string text)
		{
			if (Settings.IsDebug.Value)
			{
				Log.LogInfo((object)text);
			}
		}
	}
	internal static class Shortcut
	{
		public static bool IsPressed(KeyboardShortcut shortcut)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			if (!IsTyping())
			{
				return WentDown(shortcut);
			}
			return false;
		}

		public static bool IsTyping()
		{
			if (!Console.IsVisible() && !TextInput.IsVisible())
			{
				if ((Object)(object)Chat.instance != (Object)null)
				{
					return Chat.instance.HasFocus();
				}
				return false;
			}
			return true;
		}

		private static bool WentDown(KeyboardShortcut shortcut)
		{
			//IL_0002: 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_002a: Unknown result type (might be due to invalid IL or missing references)
			if ((int)((KeyboardShortcut)(ref shortcut)).MainKey == 0 || !ZInput.GetKeyDown(((KeyboardShortcut)(ref shortcut)).MainKey, false))
			{
				return false;
			}
			foreach (KeyCode modifier in ((KeyboardShortcut)(ref shortcut)).Modifiers)
			{
				if (!ZInput.GetKey(modifier, false))
				{
					return false;
				}
			}
			return true;
		}
	}
	internal static class Storage
	{
		internal static string Read(string path, out string problem)
		{
			problem = null;
			try
			{
				return File.Exists(path) ? File.ReadAllText(path) : null;
			}
			catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
			{
				problem = "Could not read " + path + ": " + ex.Message;
				return null;
			}
		}

		internal static string Write(string path, string text)
		{
			try
			{
				Directory.CreateDirectory(Path.GetDirectoryName(path));
				string text2 = path + ".tmp";
				File.WriteAllText(text2, text, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
				if (File.Exists(path))
				{
					File.Replace(text2, path, null);
				}
				else
				{
					File.Move(text2, path);
				}
				return null;
			}
			catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
			{
				return "Could not write " + path + ": " + ex.Message;
			}
		}
	}
}
namespace CombatStats.Patches
{
	internal static class Windows
	{
		public static bool AnyOpen
		{
			get
			{
				if (!DetailWindow.IsOpen)
				{
					return SettingsPanel.IsOpen;
				}
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(Player), "TakeInput")]
	internal static class BlockPlayerInput
	{
		private static void Postfix(ref bool __result)
		{
			if (Windows.AnyOpen)
			{
				__result = false;
			}
		}
	}
	[HarmonyPatch(typeof(PlayerController), "TakeInput")]
	internal static class BlockControllerInput
	{
		private static void Postfix(ref bool __result)
		{
			if (Windows.AnyOpen)
			{
				__result = false;
			}
		}
	}
	[HarmonyPatch(typeof(ZInput), "GetMouseScrollWheel")]
	internal static class CaptureScrollWheel
	{
		private static void Postfix(ref float __result)
		{
			if (__result != 0f && DetailWindow.IsOpen)
			{
				DetailWindow.FeedScrollWheel(__result);
				__result = 0f;
			}
		}
	}
	[HarmonyPatch(typeof(GameCamera), "UpdateMouseCapture")]
	internal static class KeepCursorFree
	{
		private static bool Prefix()
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			if (!Windows.AnyOpen)
			{
				return true;
			}
			if ((int)Cursor.lockState != 0)
			{
				ZCursor.LockState = (CursorLockMode)0;
			}
			ZCursor.Show();
			return false;
		}
	}
	[HarmonyPatch(typeof(Menu), "Update")]
	internal static class CloseWindowsOnEscape
	{
		private static bool Prefix()
		{
			if (!Windows.AnyOpen || Menu.IsVisible())
			{
				return true;
			}
			if (!ZInput.GetKeyDown((KeyCode)27, false))
			{
				return true;
			}
			if (SettingsPanel.IsOpen)
			{
				SettingsPanel.Instance?.Close();
				return false;
			}
			DetailWindow.Instance?.Close();
			return false;
		}
	}
}
namespace CombatStats.Net
{
	internal sealed class PlayerIdentity
	{
		public long Id;

		public string Name = "";

		public string Character = "";

		public float X;

		public float Y;

		public float Z;

		public double Sent;
	}
	internal sealed class KnownPlayer
	{
		public long Id;

		public string Name = "";

		public string Character = "";
	}
	internal sealed class ClockReply
	{
		public string Session = "";

		public double Echo;

		public double Time;
	}
	internal sealed class CombatBatch
	{
		public string Session = "";

		public List<Observation> Events = new List<Observation>();
	}
	internal sealed class CombatFeed
	{
		public string Session = "";

		public double Time;

		public List<Encounter> Fights = new List<Encounter>();

		public Dictionary<long, long> Redirects = new Dictionary<long, long>();

		public List<KnownPlayer> Players = new List<KnownPlayer>();
	}
	internal sealed class FightSync : MonoBehaviour
	{
		private const string HelloRpc = "Tenemo.CombatStats.Hello.1";

		private const string ClockRpc = "Tenemo.CombatStats.Clock.1";

		private const string EventsRpc = "Tenemo.CombatStats.Events.1";

		private const string FeedRpc = "Tenemo.CombatStats.Feed.1";

		private static readonly Stopwatch Clock = Stopwatch.StartNew();

		private static readonly Func<ZRoutedRpc, long> ServerPeerOf = AccessTools.MethodDelegate<Func<ZRoutedRpc, long>>(AccessTools.Method(typeof(ZRoutedRpc), "GetServerPeerID", (Type[])null, (Type[])null), (object)null, true);

		private static readonly JsonSerializerSettings Json = new JsonSerializerSettings
		{
			MaxDepth = 20
		};

		private static double offset;

		private ZRoutedRpc bound;

		private readonly List<Observation> pending = new List<Observation>();

		private readonly Dictionary<long, PlayerIdentity> players = new Dictionary<long, PlayerIdentity>();

		private List<KnownPlayer> known = new List<KnownPlayer>();

		private readonly Dictionary<long, Dictionary<long, long>> published = new Dictionary<long, Dictionary<long, long>>();

		private EncounterRecorder recorder = new EncounterRecorder();

		private string session = "";

		private string historyPath = "";

		private long sequence;

		private double nextSend;

		private double nextHello;

		private bool synchronized;

		internal static double Now => Clock.Elapsed.TotalSeconds + offset;

		internal void Queue(Observation report)
		{
			if (bound != null)
			{
				report.Sequence = ++sequence;
				pending.Add(report);
			}
		}

		internal DamageCollector.Source FindPlayer(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return null;
			}
			List<KnownPlayer> list = known.Where((KnownPlayer x) => x.Name == name).ToList();
			if (list.Count != 1)
			{
				return null;
			}
			return Source(list[0]);
		}

		internal DamageCollector.Source FindCharacter(string character)
		{
			KnownPlayer knownPlayer = known.FirstOrDefault((KnownPlayer x) => x.Character == character);
			if (knownPlayer != null)
			{
				return Source(knownPlayer);
			}
			return null;
		}

		private static DamageCollector.Source Source(KnownPlayer player)
		{
			return new DamageCollector.Source
			{
				Id = player.Id,
				Name = player.Name
			};
		}

		private void Update()
		{
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_015e: 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)
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			ZNet instance = ZNet.instance;
			ZRoutedRpc instance2 = ZRoutedRpc.instance;
			if (!Object.op_Implicit((Object)(object)instance) || instance2 == null)
			{
				if (bound != null)
				{
					EndSession();
				}
				return;
			}
			if (bound != instance2)
			{
				Bind(instance2, instance);
			}
			if (Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				string text = Path.Combine(Paths.BepInExRootPath, "combat-stats-state", "world-" + instance.GetWorldUID() + "-player-" + DamageCollector.LocalId() + ".dat");
				if (text != historyPath)
				{
					OpenHistory(text);
				}
				if (Clock.Elapsed.TotalSeconds >= nextHello)
				{
					nextHello = Clock.Elapsed.TotalSeconds + (synchronized ? 2.0 : 0.25);
					Player localPlayer = Player.m_localPlayer;
					Vector3 position = ((Component)localPlayer).transform.position;
					PlayerIdentity value = new PlayerIdentity
					{
						Id = localPlayer.GetPlayerID(),
						Name = localPlayer.GetPlayerName(),
						Character = DamageCollector.Key((Character)(object)localPlayer),
						Sent = Clock.Elapsed.TotalSeconds,
						X = position.x,
						Y = position.y,
						Z = position.z
					};
					if (instance.IsServer())
					{
						players[ZNet.GetUID()] = value;
					}
					else
					{
						instance2.InvokeRoutedRPC(ServerPeerOf(instance2), "Tenemo.CombatStats.Hello.1", new object[1] { Encode(value) });
					}
				}
			}
			if (!(Clock.Elapsed.TotalSeconds < nextSend))
			{
				nextSend = Clock.Elapsed.TotalSeconds + 0.25;
				Plugin.Collector.Engagement();
				Flush(instance, instance2);
				if (instance.IsServer())
				{
					recorder.Tick(Now);
					Publish(instance, instance2);
				}
				else if (synchronized)
				{
					Plugin.History.Now = Now;
				}
			}
		}

		private void Bind(ZRoutedRpc rpc, ZNet net)
		{
			EndSession();
			bound = rpc;
			session = (net.IsServer() ? Guid.NewGuid().ToString("N") : "");
			synchronized = net.IsServer();
			offset = 0.0;
			rpc.Register<string>("Tenemo.CombatStats.Hello.1", (Action<long, string>)Hello);
			rpc.Register<string>("Tenemo.CombatStats.Clock.1", (Action<long, string>)SetClock);
			rpc.Register<string>("Tenemo.CombatStats.Events.1", (Action<long, string>)Events);
			rpc.Register<string>("Tenemo.CombatStats.Feed.1", (Action<long, string>)Feed);
			Plugin.Debug("Fight sync registered for a new world.");
		}

		private void EndSession()
		{
			CloseHistory();
			recorder = new EncounterRecorder();
			pending.Clear();
			players.Clear();
			known.Clear();
			published.Clear();
			sequence = 0L;
			bound = null;
			synchronized = false;
			nextHello = 0.0;
			nextSend = 0.0;
			Plugin.Collector?.Clear();
			DetailWindow.Instance?.Close();
		}

		private void Hello(long sender, string json)
		{
			if (ZNet.instance.IsServer())
			{
				PlayerIdentity playerIdentity = Decode<PlayerIdentity>(json);
				ZNetPeer peer = ZNet.instance.GetPeer(sender);
				if (playerIdentity != null && peer != null && playerIdentity.Id != 0L && playerIdentity.Name != null && playerIdentity.Name.Length <= 160 && !(playerIdentity.Character != ((object)Unsafe.As<ZDOID, ZDOID>(ref peer.m_characterID)/*cast due to .constrained prefix*/).ToString()))
				{
					players[sender] = playerIdentity;
					bound.InvokeRoutedRPC(sender, "Tenemo.CombatStats.Clock.1", new object[1] { Encode(new ClockReply
					{
						Session = session,
						Echo = playerIdentity.Sent,
						Time = Now
					}) });
				}
			}
		}

		private void SetClock(long sender, string json)
		{
			if (ZNet.instance.IsServer() || sender != ServerPeerOf(bound))
			{
				return;
			}
			ClockReply clockReply = Decode<ClockReply>(json);
			if (clockReply == null || string.IsNullOrEmpty(clockReply.Session))
			{
				return;
			}
			double totalSeconds = Clock.Elapsed.TotalSeconds;
			double num = totalSeconds - clockReply.Echo;
			if (num < 0.0 || num > 5.0 || double.IsNaN(clockReply.Time) || double.IsInfinity(clockReply.Time))
			{
				return;
			}
			double num2 = clockReply.Time + num * 0.5 - totalSeconds;
			foreach (Observation item in pending)
			{
				item.Time += num2 - offset;
			}
			offset = num2;
			session = clockReply.Session;
			synchronized = true;
		}

		private void Flush(ZNet net, ZRoutedRpc rpc)
		{
			if (pending.Count == 0 || (!net.IsServer() && !synchronized))
			{
				return;
			}
			while (pending.Count > 0)
			{
				CombatBatch combatBatch = new CombatBatch
				{
					Session = session,
					Events = pending.Take(128).ToList()
				};
				pending.RemoveRange(0, combatBatch.Events.Count);
				if (net.IsServer())
				{
					recorder.Receive(ZNet.GetUID(), combatBatch.Events, Now);
					continue;
				}
				rpc.InvokeRoutedRPC(ServerPeerOf(rpc), "Tenemo.CombatStats.Events.1", new object[1] { Encode(combatBatch) });
			}
		}

		private void Events(long sender, string json)
		{
			if (ZNet.instance.IsServer() && players.ContainsKey(sender) && ZNet.instance.GetPeer(sender) != null)
			{
				CombatBatch combatBatch = Decode<CombatBatch>(json);
				if (combatBatch != null && !(combatBatch.Session != session) && combatBatch.Events != null && combatBatch.Events.Count <= 128 && !combatBatch.Events.Any((Observation x) => x == null))
				{
					recorder.Receive(sender, combatBatch.Events, Now);
				}
			}
		}

		private void Publish(ZNet net, ZRoutedRpc rpc)
		{
			HashSet<long> connected = new HashSet<long>(from x in net.GetPeers()
				select x.m_uid) { ZNet.GetUID() };
			foreach (long item in players.Keys.Where((long x) => !connected.Contains(x)).ToList())
			{
				players.Remove(item);
				published.Remove(item);
				recorder.ForgetPeer(item);
			}
			known = players.Values.Select((PlayerIdentity x) => new KnownPlayer
			{
				Id = x.Id,
				Name = x.Name,
				Character = x.Character
			}).ToList();
			foreach (KeyValuePair<long, PlayerIdentity> item2 in players.ToList())
			{
				PlayerIdentity player = item2.Value;
				if (!published.TryGetValue(item2.Key, out var revisions))
				{
					published[item2.Key] = (revisions = new Dictionary<long, long>());
				}
				long value;
				CombatFeed combatFeed = new CombatFeed
				{
					Session = session,
					Time = Now,
					Redirects = recorder.Redirects,
					Players = known,
					Fights = recorder.Fights.Where((Encounter x) => (x.Participants.ContainsKey(player.Id) || revisions.ContainsKey(x.Id) || (x.Active && EncounterRecorder.Distance(x.X, x.Y, x.Z, player.X, player.Y, player.Z) <= 9216f)) && (x.Active || !revisions.TryGetValue(x.Id, out value) || value != x.Revision)).ToList()
				};
				string text = Encode(combatFeed);
				if (item2.Key == ZNet.GetUID())
				{
					AcceptFeed(Decode<CombatFeed>(text));
				}
				else
				{
					rpc.InvokeRoutedRPC(item2.Key, "Tenemo.CombatStats.Feed.1", new object[1] { text });
				}
				foreach (Encounter fight in combatFeed.Fights)
				{
					revisions[fight.Id] = fight.Revision;
				}
				foreach (long item3 in revisions.Keys.Where((long x) => recorder.Fights.All((Encounter f) => f.Id != x)).ToList())
				{
					revisions.Remove(item3);
				}
			}
		}

		private void Feed(long sender, string json)
		{
			if (!ZNet.instance.IsServer() && sender == ServerPeerOf(bound) && synchronized)
			{
				CombatFeed combatFeed = Decode<CombatFeed>(json);
				if (combatFeed != null && !(combatFeed.Session != session))
				{
					AcceptFeed(combatFeed);
				}
			}
		}

		private void AcceptFeed(CombatFeed feed)
		{
			if (feed != null && historyPath.Length != 0)
			{
				if (!ZNet.instance.IsServer())
				{
					known = feed.Players ?? new List<KnownPlayer>();
				}
				Plugin.History.Receive(feed.Session, feed.Time, feed.Fights, feed.Redirects);
			}
		}

		private void OpenHistory(string path)
		{
			CloseHistory();
			historyPath = path;
			Plugin.History = CombatHistory.Load(path, out var problem);
			if (problem != null)
			{
				Plugin.Log.LogError((object)problem);
			}
			Plugin.History.Changed += SaveHistory;
		}

		private void CloseHistory()
		{
			SaveHistory();
			Plugin.History.Changed -= SaveHistory;
			Plugin.History = new CombatHistory();
			historyPath = "";
		}

		private static void SaveHistory()
		{
			string text = Plugin.History.Save();
			if (text != null)
			{
				Plugin.Log.LogWarning((object)text);
			}
		}

		internal static string Encode(object value)
		{
			return JsonConvert.SerializeObject(value, Json);
		}

		private static T Decode<T>(string value) where T : class
		{
			//IL_002a: Expected O, but got Unknown
			if (value == null || value.Length > 1048576)
			{
				return null;
			}
			try
			{
				return JsonConvert.DeserializeObject<T>(value, Json);
			}
			catch (JsonException ex)
			{
				JsonException ex2 = ex;
				Plugin.Log.LogWarning((object)("Invalid combat packet: " + ((Exception)(object)ex2).Message));
				return null;
			}
		}
	}
}
namespace CombatStats.Collect
{
	internal sealed class DamageCollector
	{
		internal sealed class Source
		{
			internal long Id;

			internal string Name = "";

			internal string Pet = "";
		}

		internal sealed class DamageState
		{
			internal float Health;

			internal bool Valid;

			internal float[] Raw;
		}

		internal sealed class DotState
		{
			internal float Before;

			internal float Amount;

			internal int Kind;
		}

		internal sealed class RpcContext
		{
			internal Character Target;

			internal HitData Hit;
		}

		private sealed class EffectKinds
		{
			internal readonly HashSet<int> Values = new HashSet<int>();
		}

		private sealed class Tracked
		{
			internal Character Character;

			internal bool Reported;

			internal long Actor;

			internal double Sent;
		}

		private readonly Dictionary<string, Tracked> tracked = new Dictionary<string, Tracked>();

		private readonly ConditionalWeakTable<Character, Source> petOwners = new ConditionalWeakTable<Character, Source>();

		private readonly ConditionalWeakTable<StatusEffect, EffectKinds> effectKinds = new ConditionalWeakTable<StatusEffect, EffectKinds>();

		internal static RpcContext CurrentHit;

		internal static StatusEffect CurrentEffect;

		private static readonly FieldRef<StatusEffect, Character> EffectCharacter = AccessTools.FieldRefAccess<StatusEffect, Character>("m_character");

		private static readonly FieldRef<SE_Burning, float> FireLeft = AccessTools.FieldRefAccess<SE_Burning, float>("m_fireDamageLeft");

		private static readonly FieldRef<SE_Burning, float> SpiritLeft = AccessTools.FieldRefAccess<SE_Burning, float>("m_spiritDamageLeft");

		private static readonly FieldRef<SE_Poison, float> PoisonLeft = AccessTools.FieldRefAccess<SE_Poison, float>("m_damageLeft");

		private static readonly FieldRef<AnimalAI, Character> AnimalTarget = AccessTools.FieldRefAccess<AnimalAI, Character>("m_target");

		internal static long LocalId()
		{
			if (!Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				return 0L;
			}
			return Player.m_localPlayer.GetPlayerID();
		}

		internal static string Key(Character character)
		{
			//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)
			return ((object)character.GetZDOID()/*cast due to .constrained prefix*/).ToString();
		}

		internal static string Name(Character character)
		{
			Player val = (Player)(object)((character is Player) ? character : null);
			if (val == null)
			{
				return character.m_name;
			}
			return val.GetPlayerName();
		}

		internal static bool Owns(Character character)
		{
			if (Object.op_Implicit((Object)(object)character))
			{
				return character.IsOwner();
			}
			return false;
		}

		private Source Actor(Character actor)
		{
			if (!Object.op_Implicit((Object)(object)actor))
			{
				return new Source();
			}
			Player val = (Player)(object)((actor is Player) ? actor : null);
			if (val != null)
			{
				return new Source
				{
					Id = val.GetPlayerID(),
					Name = val.GetPlayerName()
				};
			}
			if (!actor.IsTamed())
			{
				return new Source();
			}
			BaseAI baseAI = actor.GetBaseAI();
			MonsterAI val2 = (MonsterAI)(object)((baseAI is MonsterAI) ? baseAI : null);
			GameObject val3 = (Object.op_Implicit((Object)(object)val2) ? val2.GetFollowTarget() : null);
			Player val4 = (Object.op_Implicit((Object)(object)val3) ? val3.GetComponent<Player>() : null);
			if (Object.op_Implicit((Object)(object)val4))
			{
				Source source = new Source
				{
					Id = val4.GetPlayerID(),
					Name = val4.GetPlayerName(),
					Pet = Name(actor)
				};
				petOwners.Remove(actor);
				petOwners.Add(actor, source);
				return source;
			}
			if (petOwners.TryGetValue(actor, out var value))
			{
				return value;
			}
			ZNetView component = ((Component)actor).GetComponent<ZNetView>();
			string name = ((Object.op_Implicit((Object)(object)component) && component.IsValid()) ? component.GetZDO().GetString("follow", "") : "");
			Source source2 = Plugin.Sync.FindPlayer(name);
			if (source2 != null)
			{
				source2.Pet = Name(actor);
				petOwners.Add(actor, source2);
				return source2;
			}
			return new Source
			{
				Id = Combatant.UnownedPetId((uint)StringExtensionMethods.GetStableHashCode(Key(actor))),
				Name = Name(actor),
				Pet = Name(actor)
			};
		}

		private Observation Create(Character target, HitData hit)
		{
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: 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_0120: Unknown result type (might be due to invalid IL or missing references)
			if (!Owns(target) || (!target.IsPlayer() && target.IsTamed()))
			{
				return null;
			}
			Character val = ((hit != null) ? hit.GetAttacker() : null);
			if (Object.op_Implicit((Object)(object)val) && val.IsPlayer() && target.IsPlayer())
			{
				return null;
			}
			Source source = Actor(val);
			if (!Object.op_Implicit((Object)(object)val) && hit != null && !((ZDOID)(ref hit.m_attacker)).IsNone())
			{
				Source source2 = Plugin.Sync.FindCharacter(((object)Unsafe.As<ZDOID, ZDOID>(ref hit.m_attacker)/*cast due to .constrained prefix*/).ToString());
				if (source2 != null)
				{
					source = source2;
				}
			}
			Vector3 position = ((Component)target).transform.position;
			Observation obj = new Observation
			{
				Time = FightSync.Now,
				Target = Key(target),
				TargetName = Name(target),
				Actor = source.Id,
				ActorName = source.Name,
				Pet = source.Pet,
				Skill = SkillOf(hit, val)
			};
			Player val2 = (Player)(object)((target is Player) ? target : null);
			obj.Victim = ((val2 != null) ? val2.GetPlayerID() : 0);
			obj.X = position.x;
			obj.Y = position.y;
			obj.Z = position.z;
			obj.Boss = target.IsBoss();
			Observation observation = obj;
			if (!target.IsPlayer())
			{
				Track(observation.Target, target);
			}
			else if (Object.op_Implicit((Object)(object)val) && !val.IsPlayer() && !val.IsTamed())
			{
				observation.Enemy = Key(val);
				observation.EnemyName = Name(val);
				observation.Boss = val.IsBoss();
				if (val.IsOwner())
				{
					Track(observation.Enemy, val);
				}
			}
			return observation;
		}

		private static string SkillOf(HitData hit, Character attacker)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			if (hit == null)
			{
				return "Other";
			}
			if ((int)hit.m_skill != 0)
			{
				return ((object)Unsafe.As<SkillType, SkillType>(ref hit.m_skill)/*cast due to .constrained prefix*/).ToString();
			}
			if (!Object.op_Implicit((Object)(object)attacker) || attacker.IsPlayer())
			{
				return "Other";
			}
			return "Creature attack";
		}

		private void Track(string key, Character character)
		{
			if (!tracked.TryGetValue(key, out var value) || (Object)(object)value.Character != (Object)(object)character)
			{
				tracked[key] = new Tracked
				{
					Character = character
				};
			}
		}

		internal void Activity(Character target, HitData hit)
		{
			if (Object.op_Implicit((Object)(object)target) && !target.IsDead() && !target.IsTeleporting() && hit != null)
			{
				Observation observation = Create(target, hit);
				if (observation != null && (observation.Actor != 0L || observation.Enemy.Length != 0))
				{
					observation.Kind = ObservationKind.Activity;
					Plugin.Sync.Queue(observation);
				}
			}
		}

		internal void TrackAttacker(Character target, HitData hit)
		{
			if (Object.op_Implicit((Object)(object)target) && hit != null && (target.IsPlayer() || target.IsTamed()))
			{
				Character attacker = hit.GetAttacker();
				if (Object.op_Implicit((Object)(object)attacker) && !attacker.IsPlayer() && !attacker.IsTamed() && attacker.IsOwner())
				{
					Track(Key(attacker), attacker);
				}
			}
		}

		internal void Applied(Character target, HitData hit, DamageState state)
		{
			if (!state.Valid || hit == null)
			{
				return;
			}
			Observation observation = Create(target, hit);
			if (observation == null)
			{
				return;
			}
			float[] array = Values(hit);
			float num = array.Sum();
			if (num <= 0.1f)
			{
				return;
			}
			float num2 = Math.Min(num, Math.Max(0f, state.Health));
			float num3 = num2 / num;
			for (int i = 0; i < array.Length; i++)
			{
				array[i] *= num3;
			}
			observation.Kind = ObservationKind.Damage;
			observation.Amounts = array;
			observation.Overkill = num - num2;
			observation.Periodic = CurrentEffect is SE_Burning || CurrentEffect is SE_Poison;
			if (observation.Actor != 0L || observation.Victim != 0L || observation.Periodic)
			{
				if (observation.Periodic)
				{
					observation.Consumed = state.Raw;
				}
				Plugin.Sync.Queue(observation);
			}
		}

		internal void Death(Character target)
		{
			Observation observation = Create(target, null);
			if (observation != null)
			{
				observation.Kind = ObservationKind.Death;
				Plugin.Sync.Queue(observation);
				tracked.Remove(observation.Target);
			}
		}

		internal void Healed(Character target, float before)
		{
			if (!Owns(target))
			{
				return;
			}
			Player val = (Player)(object)((target is Player) ? target : null);
			if (val != null)
			{
				float num = target.GetHealth() - before;
				if (!(num <= 0f))
				{
					Observation observation = Create(target, null);
					observation.Kind = ObservationKind.Healing;
					observation.ActorName = val.GetPlayerName();
					observation.Amounts[10] = num;
					Plugin.Sync.Queue(observation);
				}
			}
		}

		private Source EngagedActor(Character target)
		{
			BaseAI baseAI = target.GetBaseAI();
			if (!Object.op_Implicit((Object)(object)baseAI) || !baseAI.IsAlerted())
			{
				return null;
			}
			AnimalAI val = (AnimalAI)(object)((baseAI is AnimalAI) ? baseAI : null);
			Character val2 = ((val != null) ? AnimalTarget.Invoke(val) : baseAI.GetTargetCreature());
			if (!Object.op_Implicit((Object)(object)val2) || val2.IsDead())
			{
				return null;
			}
			Source source = Actor(val2);
			if (source.Id == 0L)
			{
				return null;
			}
			return source;
		}

		internal void Engagement()
		{
			foreach (Character allCharacter in Character.GetAllCharacters())
			{
				if (Owns(allCharacter) && !allCharacter.IsPlayer() && !allCharacter.IsTamed() && !allCharacter.IsDead())
				{
					string key = Key(allCharacter);
					if (!tracked.ContainsKey(key) && EngagedActor(allCharacter) != null)
					{
						Track(key, allCharacter);
					}
				}
			}
			double now = FightSync.Now;
			foreach (KeyValuePair<string, Tracked> item in tracked.ToList())
			{
				Character character = item.Value.Character;
				if (!Owns(character) || character.IsTamed())
				{
					tracked.Remove(item.Key);
					continue;
				}
				if (character.IsDead())
				{
					Death(character);
					continue;
				}
				Source source = EngagedActor(character);
				long num = source?.Id ?? 0;
				if (!item.Value.Reported || num != item.Value.Actor || !(now - item.Value.Sent < 1.0))
				{
					item.Value.Reported = true;
					item.Value.Actor = num;
					item.Value.Sent = now;
					Observation observation = Create(character, null);
					observation.Kind = ObservationKind.Engagement;
					observation.Actor = num;
					observation.ActorName = source?.Name ?? "";
					observation.Engaged = source != null;
					Plugin.Sync.Queue(observation);
					if (!observation.Engaged)
					{
						tracked.Remove(item.Key);
					}
				}
			}
		}

		internal DotState BeforeDot(StatusEffect effect, float amount, int kind)
		{
			return new DotState
			{
				Before = Remaining(effect, kind),
				Amount = amount,
				Kind = kind
			};
		}

		private static float Remaining(StatusEffect effect, int kind)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Expected O, but got Unknown
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			SE_Poison val = (SE_Poison)(object)((effect is SE_Poison) ? effect : null);
			if (val == null)
			{
				if (kind != 5)
				{
					return SpiritLeft.Invoke((SE_Burning)effect);
				}
				return FireLeft.Invoke((SE_Burning)effect);
			}
			return PoisonLeft.Invoke(val);
		}

		internal void AddedDot(StatusEffect effect, DotState state, bool accepted)
		{
			Character val = EffectCharacter.Invoke(effect);
			if (accepted && Owns(val))
			{
				HitData hit = ((CurrentHit != null && (Object)(object)CurrentHit.Target == (Object)(object)val) ? CurrentHit.Hit : null);
				Observation observation = Create(val, hit);
				if (observation != null)
				{
					observation.Kind = ObservationKind.DotAdded;
					observation.DotKind = state.Kind;
					observation.DotBefore = state.Before;
					observation.ReplaceDot = effect is SE_Poison;
					observation.DotAmount = (observation.ReplaceDot ? Remaining(effect, state.Kind) : Math.Max(0f, Remaining(effect, state.Kind) - state.Before));
					effectKinds.GetOrCreateValue(effect).Values.Add(state.Kind);
					Plugin.Sync.Queue(observation);
				}
			}
		}

		internal void RemovedDot(StatusEffect effect)
		{
			if (!Object.op_Implicit((Object)(object)effect) || !effectKinds.TryGetValue(effect, out var value))
			{
				return;
			}
			Character target = EffectCharacter.Invoke(effect);
			foreach (int value2 in value.Values)
			{
				Observation observation = Create(target, null);
				if (observation != null)
				{
					observation.Kind = ObservationKind.DotCleared;
					observation.DotKind = value2;
					Plugin.Sync.Queue(observation);
				}
			}
			effectKinds.Remove(effect);
		}

		internal void Clear()
		{
			tracked.Clear();
		}

		internal static float[] Values(HitData hit)
		{
			//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_0010: 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_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: 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_003d: 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)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: 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_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			DamageTypes damage = hit.m_damage;
			return new float[11]
			{
				damage.m_blunt,
				damage.m_slash,
				damage.m_pierce,
				damage.m_chop,
				damage.m_pickaxe,
				damage.m_fire,
				damage.m_frost,
				damage.m_lightning,
				damage.m_poison,
				damage.m_spirit,
				Math.Max(0f, damage.m_damage) + Math.Max(0f, damage.m_nonPlayer)
			}.Select((float x) => Math.Max(0f, x)).ToArray();
		}
	}
	[HarmonyPatch(typeof(Character), "Damage")]
	internal static class ObserveOutgoing
	{
		private static void Prefix(Character __instance, HitData hit)
		{
			Plugin.Collector.TrackAttacker(__instance, hit);
		}
	}
	[HarmonyPatch(typeof(Character), "RPC_Damage")]
	internal static class ObserveContact
	{
		private static void Prefix(Character __instance, HitData hit, out DamageCollector.RpcContext __state)
		{
			__state = DamageCollector.CurrentHit;
			if (DamageCollector.Owns(__instance))
			{
				DamageCollector.CurrentHit = new DamageCollector.RpcContext
				{
					Target = __instance,
					Hit = hit
				};
				Plugin.Collector.Activity(__instance, hit);
			}
		}

		private static void Finalizer(DamageCollector.RpcContext __state)
		{
			DamageCollector.CurrentHit = __state;
		}
	}
	[HarmonyPatch(typeof(Character), "ApplyDamage")]
	internal static class ObserveDamage
	{
		[HarmonyPriority(0)]
		private static void Prefix(Character __instance, HitData hit, out DamageCollector.DamageState __state)
		{
			__state = new DamageCollector.DamageState
			{
				Valid = (DamageCollector.Owns(__instance) && !__instance.IsDead() && !__instance.IsTeleporting() && !__instance.InCutscene() && !__instance.IsDebugFlying() && !CinematicsManager.IsPlaying()),
				Health = __instance.GetHealth(),
				Raw = ((hit == null) ? new float[11] : DamageCollector.Values(hit))
			};
		}

		private static void Postfix(Character __instance, HitData hit, DamageCollector.DamageState __state)
		{
			Plugin.Collector.Applied(__instance, hit, __state);
		}
	}
	[HarmonyPatch(typeof(Character), "OnDeath")]
	internal static class ObserveDeath
	{
		private static void Prefix(Character __instance)
		{
			Plugin.Collector.Death(__instance);
		}
	}
	[HarmonyPatch(typeof(Player), "OnDeath")]
	internal static class ObservePlayerDeath
	{
		private static void Prefix(Player __instance)
		{
			Plugin.Collector.Death((Character)(object)__instance);
		}
	}
	[HarmonyPatch(typeof(Character), "RPC_Heal")]
	internal static class ObserveHealing
	{
		private static void Prefix(Character __instance, out float __state)
		{
			__state = __instance.GetHealth();
		}

		private static void Postfix(Character __instance, float __state)
		{
			Plugin.Collector.Healed(__instance, __state);
		}
	}
	[HarmonyPatch]
	internal static class ObserveTick
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			yield return AccessTools.Method(typeof(SE_Burning), "UpdateStatusEffect", (Type[])null, (Type[])null);
			yield return AccessTools.Method(typeof(SE_Poison), "UpdateStatusEffect", (Type[])null, (Type[])null);
		}

		private static void Prefix(StatusEffect __instance, out StatusEffect __state)
		{
			__state = DamageCollector.CurrentEffect;
			DamageCollector.CurrentEffect = __instance;
		}

		private static void Finalizer(StatusEffect __state)
		{
			DamageCollector.CurrentEffect = __state;
		}
	}
	[HarmonyPatch(typeof(SE_Burning), "AddFireDamage")]
	internal static class ObserveFire
	{
		private static void Prefix(SE_Burning __instance, float __0, out DamageCollector.DotState __state)
		{
			__state = Plugin.Collector.BeforeDot((StatusEffect)(object)__instance, __0, 5);
		}

		private static void Postfix(SE_Burning __instance, bool __result, DamageCollector.DotState __state)
		{
			Plugin.Collector.AddedDot((StatusEffect)(object)__instance, __state, __result);
		}
	}
	[HarmonyPatch(typeof(SE_Burning), "AddSpiritDamage")]
	internal static class ObserveSpirit
	{
		private static void Prefix(SE_Burning __instance, float __0, out DamageCollector.DotState __state)
		{
			__state = Plugin.Collector.BeforeDot((StatusEffect)(object)__instance, __0, 9);
		}

		private static void Postfix(SE_Burning __instance, bool __result, DamageCollector.DotState __state)
		{
			Plugin.Collector.AddedDot((StatusEffect)(object)__instance, __state, __result);
		}
	}
	[HarmonyPatch(typeof(SE_Poison), "AddDamage")]
	internal static class ObservePoison
	{
		private static void Prefix(SE_Poison __instance, float __0, out DamageCollector.DotState __state)
		{
			__state = Plugin.Collector.BeforeDot((StatusEffect)(object)__instance, __0, 8);
		}

		private static void Postfix(SE_Poison __instance, DamageCollector.DotState __state)
		{
			Plugin.Collector.AddedDot((StatusEffect)(object)__instance, __state, __state.Amount >= __state.Before);
		}
	}
	[HarmonyPatch(typeof(SEMan), "RemoveStatusEffect", new Type[]
	{
		typeof(StatusEffect),
		typeof(bool)
	})]
	internal static class ObserveEffectRemoval
	{
		private static void Postfix(StatusEffect __0, bool __result)
		{
			if (__result)
			{
				Plugin.Collector.RemovedDot(__0);
			}
		}
	}
}
namespace CombatStats.Ui
{
	internal sealed class CompactMeter : MonoBehaviour
	{
		private const float RefreshInterval = 0.25f;

		private const float FadeSeconds = 0.4f;

		private const float PadLeft = 11f;

		private const float PadTop = 7f;

		private const float PadBottom = 9f;

		private const float CaptionHeight = 14f;

		private readonly List<MeterRow> _rows = new List<MeterRow>();

		private RectTransform _root;

		private RectTransform _list;

		private CanvasGroup _group;

		private Image _background;

		private TMP_Text _caption;

		private float _nextRefresh;

		private float _alpha;

		private bool _failed;

		private void Update()
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.Settings != null)
			{
				if (Shortcut.IsPressed(Plugin.Settings.CompactKey.Value))
				{
					Cycle();
				}
				bool flag = Wanted();
				if (flag && Time.time >= _nextRefresh)
				{
					_nextRefresh = Time.time + 0.25f;
					Refresh();
				}
				Fade(flag);
			}
		}

		private void Cycle()
		{
			ConfigEntryCycle();
			string text = Plugin.Settings.CompactMode.Value switch
			{
				MeterMode.Always => "Combat meter: always shown", 
				MeterMode.Auto => "Combat meter: shows the latest fight", 
				_ => "Combat meter: off", 
			};
			MessageHud instance = MessageHud.instance;
			if ((Object)(object)instance != (Object)null)
			{
				instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false, true);
			}
			Plugin.Debug(text);
		}

		private void ConfigEntryCycle()
		{
			switch (Plugin.Settings.CompactMode.Value)
			{
			case MeterMode.Auto:
				Plugin.Settings.CompactMode.Value = MeterMode.Always;
				break;
			case MeterMode.Always:
				Plugin.Settings.CompactMode.Value = MeterMode.Off;
				break;
			default:
				Plugin.Settings.CompactMode.Value = MeterMode.Auto;
				break;
			}
		}

		private bool Wanted()
		{
			if (Plugin.Settings.CompactMode.Value == MeterMode.Off)
			{
				return false;
			}
			if ((Object)(object)Player.m_localPlayer == (Object)null || Hud.IsUserHidden() || Menu.IsVisible() || Minimap.IsOpen())
			{
				return false;
			}
			if (Plugin.Settings.CompactMode.Value == MeterMode.Always)
			{
				return true;
			}
			return Plugin.History.Current(DamageCollector.LocalId()) != null;
		}

		private void Fade(bool wanted)
		{
			float num = (wanted ? 1f : 0f);
			if (Mathf.Approximately(_alpha, num))
			{
				if ((Object)(object)_group != (Object)null && ((Component)_group).gameObject.activeSelf != _alpha > 0.01f)
				{
					((Component)_group).gameObject.SetActive(_alpha > 0.01f);
				}
				return;
			}
			_alpha = Mathf.MoveTowards(_alpha, num, Time.unscaledDeltaTime / 0.4f);
			if (!((Object)(object)_group == (Object)null))
			{
				_group.alpha = _alpha;
				bool flag = _alpha > 0.01f;
				if (((Component)_group).gameObject.activeSelf != flag)
				{
					((Component)_group).gameObject.SetActive(flag);
				}
			}
		}

		private void Refresh()
		{
			//IL_0200: 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_022b: Unknown result type (might be due to invalid IL or missing references)
			//IL_023b: Unknown result type (might be due to invalid IL or missing references)
			//IL_024b: Unknown result type (might be due to invalid IL or missing references)
			//IL_026c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0276: Unknown result type (might be due to invalid IL or missing references)
			//IL_027b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0295: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_root == (Object)null)
			{
				_root = UiStyle.Build("CombatStats Compact", ref _failed, Build);
				if ((Object)(object)_root == (Object)null)
				{
					return;
				}
			}
			ModConfig settings = Plugin.Settings;
			float value = settings.CompactWidth.Value;
			bool value2 = settings.CompactBars.Value;
			float width = value - 22f;
			long num = DamageCollector.LocalId();
			EncounterSnapshot encounterSnapshot = Plugin.History.Snapshot(FightView.Current, CombatChannel.DamageDealt, num);
			List<CombatantRow> list = encounterSnapshot.Visible(settings.CompactRows.Value, num);
			float leader = ((list.Count > 0) ? list[0].Total : 0f);
			float num2 = 0f;
			bool flag = settings.CompactCaption.Value || list.Count == 0;
			((Component)_caption).gameObject.SetActive(flag);
			if (flag)
			{
				_caption.text = ((list.Count == 0) ? MeterFormat.EmptyText(encounterSnapshot, CombatChannel.DamageDealt) : MeterFormat.Caption(encounterSnapshot, MeterFormat.Plain(Localization.instance.Localize(encounterSnapshot.Name))));
				UiStyle.PlaceTopStretch(_caption.rectTransform, 0f, 0f, 0f, 14f);
				num2 += 14f;
			}
			for (int i = 0; i < list.Count; i++)
			{
				MeterRow meterRow = RowAt(i);
				meterRow.SetActive(active: true);
				meterRow.Apply(list[i], leader, width, value2, settings.CompactShare.Value, list[i].Id == num, string.Empty);
				meterRow.Place(num2);
				num2 += MeterRow.Height(value2);
			}
			for (int j = list.Count; j < _rows.Count; j++)
			{
				_rows[j].SetActive(active: false);
			}
			_list.sizeDelta = new Vector2(_list.sizeDelta.x, num2);
			_root.sizeDelta = new Vector2(value, 7f + num2 + 9f);
			((Transform)_root).localScale = Vector3.one * settings.CompactScale.Value;
			UiStyle.Anchor(_root, settings.CompactAnchor.Value, settings.CompactOffset.Value);
			Color meterBackground = UiStyle.MeterBackground;
			meterBackground.a = settings.CompactOpacity.Value;
			((Graphic)_background).color = meterBackground;
		}

		private MeterRow RowAt(int index)
		{
			while (_rows.Count <= index)
			{
				_rows.Add(MeterRow.Create(_list, detailed: false, null));
			}
			return _rows[index];
		}

		private void Build(RectTransform root)
		{
			//IL_0020: 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_00bf: 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_00fe: 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_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_0169: Expected O, but got Unknown
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_017a: Expected O, but got Unknown
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			_rows.Clear();
			root.sizeDelta = new Vector2(Plugin.Settings.CompactWidth.Value, 80f);
			_group = ((Component)root).gameObject.AddComponent<CanvasGroup>();
			_group.alpha = 0f;
			_group.interactable = false;
			_group.blocksRaycasts = false;
			((Component)root).gameObject.SetActive(false);
			_background = UiStyle.Box("Background", root, UiStyle.MeterBackground);
			_background.sprite = UiStyle.Fade();
			UiStyle.Stretch(((Graphic)_background).rectTransform, 0f, 0f, 0f, 0f);
			Image obj = UiStyle.Box("Hairline", root, UiStyle.Hairline);
			obj.sprite = UiStyle.Flat();
			((Graphic)obj).rectTransform.anchorMin = new Vector2(0f, 1f);
			((Graphic)obj).rectTransform.anchorMax = new Vector2(1f, 1f);
			((Graphic)obj).rectTransform.pivot = new Vector2(0.5f, 1f);
			((Graphic)obj).rectTransform.sizeDelta = new Vector2(0f, 1f);
			((Graphic)obj).rectTransform.anchoredPosition = Vector2.zero;
			GameObject val = new GameObject("List", new Type[1] { typeof(RectTransform) });
			_list = (RectTransform)val.transform;
			((Transform)_list).SetParent((Transform)(object)root, false);
			UiStyle.PlaceTopStretch(_list, 11f, 11f, 7f, 40f);
			_caption = UiStyle.Text("Caption", _list, 12f, (TextAlignmentOptions)4097, UiStyle.Dim);
			_caption.richText = false;
			Plugin.Debug("Compact meter built");
		}
	}
	internal sealed class DetailWindow : MonoBehaviour
	{
		private enum Breakdown
		{
			Types,
			Targets,
			Skills,
			Pets
		}

		private sealed class BreakdownLine
		{
			public const float Height = 16f;

			private readonly RectTransform _root;

			private readonly TMP_Text _name;

			private readonly TMP_Text _amount;

			private readonly TMP_Text _share;

			private BreakdownLine(RectTransform root, TMP_Text name, TMP_Text amount, TMP_Text share)
			{
				_root = root;
				_name = name;
				_amount = amount;
				_share = share;
			}

			public static BreakdownLine Create(RectTransform parent)
			{
				//IL_0018: Unknown result type (might be due to invalid IL or missing references)
				//IL_0022: Unknown result type (might be due to invalid IL or missing references)
				//IL_0028: Expected O, but got Unknown
				//IL_003b: 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_0065: Unknown result type (might be due to invalid IL or missing references)
				//IL_007a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0094: 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_00d0: Unknown result type (might be due to invalid IL or missing references)
				//IL_010a: Unknown result type (might be due to invalid IL or missing references)
				RectTransform val = (RectTransform)new GameObject("Breakdown", new Type[1] { typeof(RectTransform) }).transform;
				((Transform)val).SetParent((Transform)(object)parent, false);
				val.anchorMin = new Vector2(0f, 1f);
				val.anchorMax = new Vector2(1f, 1f);
				val.pivot = new Vector2(0f, 1f);
				val.sizeDelta = new Vector2(0f, 16f);
				TMP_Text val2 = UiStyle.Text("Name", val, 13f, (TextAlignmentOptions)4097, UiStyle.Dim);
				val2.richText = false;
				val2.rectTransform.offsetMin = new Vector2(22f, 0f);
				TMP_Text val3 = UiStyle.Text("Amount", val, 13f, (TextAlignmentOptions)4100, UiStyle.Dim);
				UiStyle.PinRight(val3.rectTransform, 250f, 152f, 0f, 16f);
				TMP_Text val4 = UiStyle.Text("Share", val, 13f, (TextAlignmentOptions)4100, UiStyle.Dim);
				UiStyle.PinRight(val4.rectTransform, 68f, 0f, 0f, 16f);
				return new BreakdownLine(val, val2, val3, val4);
			}

			public void ApplySummary(string text)
			{
				//IL_0002: Unknown result type (might be due to invalid IL or missing references)
				SetName(text, UiStyle.Dim, 0f);
				_amount.text = "";
				_share.text = "";
			}

			public void ApplyText(string name, string amount, string share)
			{
				//IL_0011: Unknown result type (might be due to invalid IL or missing references)
				SetName(MeterFormat.Plain(Localization.instance.Localize(name)), UiStyle.Dim, 250f);
				_amount.text = amount;
				_share.text = share;
			}

			public void Apply(DamageKind kind, float amount, float share, bool healing)
			{
				//IL_0023: Unknown result type (might be due to invalid IL or missing references)
				//IL_001c: Unknown result type (might be due to invalid IL or missing references)
				SetName(healing ? "Healing" : KindName(kind), healing ? UiStyle.Dim : UiStyle.Colors[(int)kind], 250f);
				_amount.text = MeterFormat.Number(amount);
				_share.text = MeterFormat.Percent(share);
			}

			public void SetActive(bool active)
			{
				if (((Component)_root).gameObject.activeSelf != active)
				{
					((Component)_root).gameObject.SetActive(active);
				}
			}

			public void Place(float y)
			{
				//IL_000d: Unknown result type (might be due to invalid IL or missing references)
				_root.anchoredPosition = new Vector2(0f, 0f - y);
			}

			private void SetName(string text, Color color, float right)
			{
				//IL_0012: 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)
				_name.text = text;
				((Graphic)_name).color = color;
				_name.rectTransform.offsetMax = new Vector2(0f - right, 0f);
			}

			private static string KindName(DamageKind kind)
			{
				string text = DamageKinds.LocalizationKey(kind);
				Localization instance = Localization.instance;
				if (instance == null)
				{
					return DamageKinds.EnglishName(kind);
				}
				string text2 = instance.Localize(text);
				if (!string.IsNullOrEmpty(text2) && !text2.StartsWith("[", StringComparison.Ordinal) && !text2.Contains("$"))
				{
					return text2;
				}
				return DamageKinds.EnglishName(kind);
			}
		}

		private const float RefreshInterval = 0.25f;

		private const float Padding = 14f;

		private const float ScrollStep = 48f;

		private const float ScreenMargin = 20f;

		private static readonly Vector2 MinimumSize = new Vector2(600f, 280f);

		private static readonly FightView[] Views = new FightView[4]
		{
			FightView.Current,
			FightView.Last,
			FightView.Overall,
			FightView.History
		};

		private readonly List<MeterRow> _rows = new List<MeterRow>();

		private readonly List<BreakdownLine> _lines = new List<BreakdownLine>();

		private readonly List<Button> _viewTabs = new List<Button>();

		private readonly List<Button> _channelTabs = new List<Button>();

		private readonly List<GameObject> _columnHeaders = new List<GameObject>();

		private readonly HashSet<long> _expanded = new HashSet<long>();

		private RectTransform _panel;

		private RectTransform _content;

		private RectTransform _viewport;

		private TMP_Text _summary;

		private TMP_Text _empty;

		private TMP_Text _rateHeading;

		private Button _breakdownButton;

		private FightView _view;

		private int _historyIndex;

		private Breakdown _breakdown;

		private CombatChannel _channel;

		private float _nextRefresh;

		private float _scrolled;

		private int _scrollFrame = -1;

		private bool _failed;

		private bool _open;

		private bool _seededExpanded;

		public static bool IsOpen
		{
			get
			{
				if ((Object)(object)Instance != (Object)null)
				{
					return Instance._open;
				}
				return false;
			}
		}

		public static DetailWindow Instance { get; private set; }

		public static void FeedScrollWheel(float amount)
		{
			DetailWindow instance = Instance;
			if (IsOpen && !((Object)(object)instance._panel == (Object)null) && instance._scrollFrame != Time.frameCount)
			{
				instance._scrollFrame = Time.frameCount;
				instance._scrolled = Mathf.Max(0f, instance._scrolled - Mathf.Sign(amount) * 48f);
				instance.Layout();
			}
		}

		public void Toggle()
		{
			if (_open)
			{
				Close();
				return;
			}
			if ((Object)(object)_panel == (Object)null)
			{
				_panel = UiStyle.Build("CombatStats Detail", ref _failed, Build);
				if ((Object)(object)_panel == (Object)null)
				{
					return;
				}
			}
			_open = true;
			_scrolled = 0f;
			((Component)_panel).gameObject.SetActive(true);
			Refresh();
		}

		public void Close()
		{
			_open = false;
			SettingsPanel.Instance?.Close();
			if ((Object)(object)_panel != (Object)null)
			{
				((Component)_panel).gameObject.SetActive(false);
			}
		}

		private void Awake()
		{
			Instance = this;
		}

		private void OnDestroy()
		{
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}

		private void Update()
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.Settings != null)
			{
				if (Shortcut.IsPressed(Plugin.Settings.DetailKey.Value))
				{
					Toggle();
				}
				if (_open && (Object)(object)_panel == (Object)null)
				{
					Close();
				}
				if (_open && Time.time >= _nextRefresh)
				{
					_nextRefresh = Time.time + 0.25f;
					Refresh();
				}
			}
		}

		private void Refresh()
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: 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_0025: 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_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: 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_0041: 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_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: 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_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			Vector2 value = Plugin.Settings.DetailSize.Value;
			Rect rect = ((RectTransform)((Transform)_panel).parent).rect;
			Vector2 val = ((Rect)(ref rect)).size - 40f * Vector2.one;
			Vector2 val2 = default(Vector2);
			((Vector2)(ref val2))..ctor(Mathf.Min(Mathf.Max(MinimumSize.x, value.x), val.x), Mathf.Min(Mathf.Max(MinimumSize.y, value.y), val.y));
			if (_panel.sizeDelta != val2)
			{
				_panel.sizeDelta = val2;
			}
			for (int i = 0; i < _viewTabs.Count; i++)
			{
				UiStyle.MarkSelected(_viewTabs[i], Views[i] == _view);
			}
			for (int j = 0; j < _channelTabs.Count; j++)
			{
				UiStyle.MarkSelected(_channelTabs[j], CombatChannels.All[j] == _channel);
			}
			_rateHeading.text = ((_channel == CombatChannel.Healing) ? "HPS" : ((_channel == CombatChannel.DamageTaken) ? "Taken/s" : "DPS"));
			((Component)_breakdownButton).gameObject.SetActive(_channel != CombatChannel.Healing);
			UiStyle.LabelOf(_breakdownButton).text = Label(_breakdown, _channel);
			Layout();
		}

		private void Layout()
		{
			//IL_01e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0672: Unknown result type (might be due to invalid IL or missing references)
			//IL_0677: Unknown result type (might be due to invalid IL or missing references)
			//IL_0694: Unknown result type (might be due to invalid IL or missing references)
			//IL_06b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_06bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_06e6: Unknown result type (might be due to invalid IL or missing references)
			long num = DamageCollector.LocalId();
			EncounterSnapshot encounterSnapshot = Plugin.History.Snapshot(_view, _channel, num, _historyIndex);
			int historyCount = Plugin.History.HistoryCount;
			string text = ((_view == FightView.History && historyCount > 0) ? ("Fight " + (Math.Min(_historyIndex, historyCount - 1) + 1) + " of " + historyCount + " | ") : "");
			_summary.richText = false;
			_summary.text = (encounterSnapshot.HasEncounter ? (text + MeterFormat.Caption(encounterSnapshot, MeterFormat.Plain(Localization.instance.Localize(encounterSnapshot.Name)))) : "");
			if (encounterSnapshot.Rows.Count > 0)
			{
				TMP_Text summary = _summary;
				summary.text = summary.text + "\nTotal " + MeterFormat.Number(encounterSnapshot.Total) + " | " + ((_channel == CombatChannel.Healing) ? "HPS received " : ((_channel == CombatChannel.DamageTaken) ? "Taken/s " : "DPS ")) + MeterFormat.Number(encounterSnapshot.Dps);
			}
			foreach (GameObject columnHeader in _columnHeaders)
			{
				columnHeader.SetActive(encounterSnapshot.Rows.Count > 0);
			}
			((Component)_breakdownButton).gameObject.SetActive(encounterSnapshot.Rows.Count > 0 && _channel != CombatChannel.Healing);
			_empty.text = MeterFormat.EmptyText(encounterSnapshot, _channel);
			Rect rect = _viewport.rect;
			float width = ((Rect)(ref rect)).width;
			float leader = ((encounterSnapshot.Rows.Count > 0) ? encounterSnapshot.Rows[0].Total : 0f);
			Breakdown breakdown = ((_channel != CombatChannel.Healing) ? _breakdown : Breakdown.Types);
			if (!_seededExpanded && num != 0L)
			{
				_seededExpanded = true;
				_expanded.Add(num);
			}
			int num2 = 0;
			int num3 = 0;
			float num4 = 0f;
			foreach (CombatantRow row in encounterSnapshot.Rows)
			{
				MeterRow meterRow = RowAt(num2++);
				bool flag = _expanded.Contains(row.Id);
				meterRow.SetActive(active: true);
				meterRow.Apply(row, leader, width, bars: true, showShare: false, row.Id == num, flag ? "- " : "+ ");
				meterRow.Place(num4);
				num4 += MeterRow.Height(bars: true);
				if (!flag)
				{
					continue;
				}
				BreakdownLine breakdownLine = LineAt(num3++);
				breakdownLine.SetActive(active: true);
				breakdownLine.ApplySummary((_channel == CombatChannel.Healing) ? ("Heals " + row.Hits + " | Largest " + MeterFormat.Number(row.Max) + " | Avg/heal " + MeterFormat.Number(row.Average)) : ("Hits " + row.Hits + ((row.Ticks > 0) ? (" | Ticks " + row.Ticks) : "") + " | Largest " + MeterFormat.Number(row.Max) + ((row.Ticks > 0) ? " | Avg/hit or tick " : " | Avg/hit ") + MeterFormat.Number(row.Average) + ((row.Overkill > 0f) ? (" | Overkill " + MeterFormat.Number(row.Overkill)) : "")));
				breakdownLine.Place(num4);
				num4 += 16f;
				if (_channel == CombatChannel.Healing)
				{
					continue;
				}
				if (breakdown == Breakdown.Types)
				{
					for (int i = 0; i < 11; i++)
					{
						float num5 = row.ByKind[i];
						if (!(num5 <= 0f))
						{
							BreakdownLine breakdownLine2 = LineAt(num3++);
							breakdownLine2.SetActive(active: true);
							breakdownLine2.Apply((DamageKind)i, num5, (row.Total > 0f) ? (num5 / row.Total) : 0f, _channel == CombatChannel.Healing);
							breakdownLine2.Place(num4);
							num4 += 16f;
						}
					}
				}
				else
				{
					foreach (KeyValuePair<string, float> item in from x in breakdown switch
						{
							Breakdown.Skills => row.Skills, 
							Breakdown.Targets => row.Targets, 
							_ => row.Pets, 
						}
						where x.Value > 0f
						orderby x.Value descending
						select x)
					{
						BreakdownLine breakdownLine3 = LineAt(num3++);
						breakdownLine3.SetActive(active: true);
						breakdownLine3.ApplyText((breakdown == Breakdown.Skills) ? MeterFormat.Skill(item.Key) : item.Key, MeterFormat.Number(item.Value), (row.Total > 0f) ? MeterFormat.Percent(item.Value / row.Total) : "");
						breakdownLine3.Place(num4);
						num4 += 16f;
					}
				}
				num4 += 4f;
			}
			for (int num6 = num2; num6 < _rows.Count; num6++)
			{
				_rows[num6].SetActive(active: false);
			}
			for (int num7 = num3; num7 < _lines.Count; num7++)
			{
				_lines[num7].SetActive(active: false);
			}
			((Component)_empty).gameObject.SetActive(num2 == 0);
			float num8 = num4;
			rect = _viewport.rect;
			float num9 = Mathf.Max(num8, ((Rect)(ref rect)).height);
			_content.sizeDelta = new Vector2(0f, num9);
			float scrolled = _scrolled;
			rect = _viewport.rect;
			_scrolled = Mathf.Clamp(scrolled, 0f, Mathf.Max(0f, num9 - ((Rect)(ref rect)).height));
			_content.anchoredPosition = new Vector2(0f, _scrolled);
		}

		private void OnRowClicked(long id)
		{
			if (!_expanded.Remove(id))
			{
				_expanded.Add(id);
			}
			Layout();
		}

		private MeterRow RowAt(int index)
		{
			while (_rows.Count <= index)
			{
				_rows.Add(MeterRow.Create(_content, detailed: true, OnRowClicked));
			}
			return _rows[index];
		}

		private BreakdownLine LineAt(int index)
		{
			while (_lines.Count <= index)
			{
				_lines.Add(BreakdownLine.Create(_content));
			}
			return _lines[index];
		}

		private void Build(RectTransform panel)
		{
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0225: Unknown result type (might be due to invalid IL or missing references)
			//IL_0235: Unknown result type (might be due to invalid IL or missing references)
			_panel = panel;
			_rows.Clear();
			_lines.Clear();
			_viewTabs.Clear();
			_channelTabs.Clear();
			_columnHeaders.Clear();
			UiStyle.Panel(panel, Plugin.Settings.DetailSize.Value, 100, "Combat stats");
			UiStyle.CornerButton(panel, "Close", 14f, 70f, Close);
			UiStyle.CornerButton(panel, "Settings", 88f, 84f, OpenSettings);
			BuildViewTabs();
			BuildChannelTabs();
			_summary = UiStyle.Text("Summary", panel, 13f, (TextAlignmentOptions)4097, UiStyle.Dim);
			UiStyle.PlaceTopStretch(_summary.rectTransform, 14f, 14f, 100f, 36f);
			TMP_Text val = UiStyle.Text("Header", panel, 12f, (TextAlignmentOptions)4097, UiStyle.Dim);
			UiStyle.PlaceTopStretch(val.rectTransform, 14f, 14f, 148f, 14f);
			val.text = "Player";
			_columnHeaders.Add(((Component)val).gameObject);
			Column("Total", 250f, 152f);
			_rateHeading = Column("DPS", 145f, 75f);
			Column("Share", 68f, 0f);
			Image val2 = UiStyle.Line("Separator", panel);
			UiStyle.PlaceTopStretch(((Graphic)val2).rectTransform, 14f, 14f, 164f, 1f);
			_columnHeaders.Add(((Component)val2).gameObject);
			BuildScroll();
			_empty = UiStyle.Text("Empty", _viewport, 14f, (TextAlignmentOptions)514, UiStyle.Dim);
			_empty.text = "No fights recorded for this view.";
			((Component)_empty).gameObject.SetActive(false);
			((Component)panel).gameObject.SetActive(false);
			Plugin.Debug($"Detail window built ({panel.sizeDelta.x}x{panel.sizeDelta.y})");
		}

		private void BuildScroll()
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Expected O, but got Unknown
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Expected O, but got Unknown
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Expected O, but got Unknown
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Expected O, but got Unknown
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("Viewport", new Type[3]
			{
				typeof(RectTransform),
				typeof(RectMask2D),
				typeof(Image)
			});
			_viewport = (RectTransform)val.transform;
			((Transform)_viewport).SetParent((Transform)(object)_panel, false);
			UiStyle.Stretch(_viewport, 14f, 14f, 172f, 14f);
			Image component = val.GetComponent<Image>();
			((Graphic)component).color = new Color(0f, 0f, 0f, 0f);
			((Graphic)component).raycastTarget = true;
			GameObject val2 = new GameObject("Content", new Type[1] { typeof(RectTransform) });
			_content = (RectTransform)val2.transform;
			((Transform)_content).SetParent((Transform)(object)_viewport, false);
			_content.anchorMin = new Vector2(0f, 1f);
			_content.anchorMax = new Vector2(1f, 1f);
			_content.pivot = new Vector2(0f, 1f);
			_content.anchoredPosition = Vector2.zero;
			_content.sizeDelta = new Vector2(0f, 0f);
		}

		private TMP_Text Column(string text, float from, float to)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			TMP_Text val = UiStyle.Text("Column" + text, _panel, 12f, (TextAlignmentOptions)4100, UiStyle.Dim);
			val.text = text;
			_columnHeaders.Add(((Component)val).gameObject);
			UiStyle.PinRight(val.rectTransform, from + 14f, to + 14f, 148f, 14f);
			return val;
		}

		private void BuildViewTabs()
		{
			//IL_005d: 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_0097: Expected O, but got Unknown
			//IL_00ce: 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)
			//IL_0109: Expected O, but got Unknown
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: Expected O, but got Unknown
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Expected O, but got Unknown
			float num = 14f;
			FightView[] views = Views;
			for (int i = 0; i < views.Length; i++)
			{
				FightView fightView = views[i];
				FightView captured = fightView;
				Button val = UiStyle.TextButton("View" + fightView, _panel, fightView.ToString(), 14f, UiStyle.Dim, delegate
				{
					_view = captured;
					_scrolled = 0f;
					Refresh();
				});
				UiStyle.PlaceTopLeft((RectTransform)((Component)val).transform, num, 42f, 72f, 22f);
				num += 74f;
				_viewTabs.Add(val);
			}
			UiStyle.PlaceTopLeft((RectTransform)((Component)UiStyle.TextButton("Earlier", _panel, "Earlier", 13f, UiStyle.Dim, delegate
			{
				_view = FightView.History;
				_historyIndex = Math.Min(Plugin.History.HistoryCount - 1, _historyIndex + 1);
				_scrolled = 0f;
				Refresh();
			})).transform, num + 8f, 42f, 65f, 22f);
			UiStyle.PlaceTopLeft((RectTransform)((Component)UiStyle.TextButton("Later", _panel, "Later", 13f, UiStyle.Dim, delegate
			{
				_view = FightView.History;
				_historyIndex = Math.Max(0, _historyIndex - 1);
				_scrolled = 0f;
				Refresh();
			})).transform, num + 76f, 42f, 65f, 22f);
			UiStyle.PlaceTopLeft((RectTransform)((Component)UiStyle.TextButton("Reset", _panel, "Reset", 13f, UiStyle.Dim, delegate
			{
				if (!Plugin.History.Reset() && Object.op_Implicit((Object)(object)MessageHud.instance))
				{
					MessageHud.instance.ShowMessage((MessageType)1, "Finish the fight before resetting history.", 0, (Sprite)null, false, true);
				}
				_historyIndex = 0;
				Refresh();
			})).transform, num + 150f, 42f, 65f, 22f);
		}

		private void BuildChannelTabs()
		{
			//IL_0056: 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_0090: Expected O, but got Unknown
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Expected O, but got Unknown
			float num = 14f;
			CombatChannel[] all = CombatChannels.All;
			for (int i = 0; i < all.Length; i++)
			{
				CombatChannel combatChannel = all[i];
				CombatChannel captured = combatChannel;
				Button val = UiStyle.TextButton("Channel" + combatChannel, _panel, Label(combatChannel), 13f, UiStyle.Dim, delegate
				{
					_channel = captured;
					_scrolled = 0f;
					Refresh();
				});
				UiStyle.PlaceTopLeft((RectTransform)((Component)val).transform, num, 70f, 130f, 22f);
				num += 132f;
				_channelTabs.Add(val);
			}
			_breakdownButton = UiStyle.TextButton("Breakdown", _panel, "Types", 13f, UiStyle.Dim, delegate
			{
				_breakdown = (Breakdown)((int)(_breakdown + 1) % 4);
				_scrolled = 0f;
				Refresh();
			});
			UiStyle.PlaceTopLeft((RectTransform)((Component)_breakdownButton).transform, num + 10f, 70f, 100f, 22f);
		}

		private void OpenSettings()
		{
			(SettingsPanel.Instance ?? ((Component)this).gameObject.AddComponent<SettingsPanel>()).Toggle();
		}

		private static string Label(CombatChannel channel)
		{
			return channel switch
			{
				CombatChannel.Healing => "Healing received", 
				CombatChannel.DamageTaken => "Damage taken", 
				_ => "Damage done", 
			};
		}

		private static string Label(Breakdown breakdown, CombatChannel channel)
		{
			switch (breakdown)
			{
			case Breakdown.Targets:
				if (channel != CombatChannel.DamageTaken)
				{
					return "Targets";
				}
				return "Sources";
			case Breakdown.Skills:
				return "Skills";
			case Breakdown.Pets:
				return "Pets";
			default:
				return "Types";
			}
		}
	}
	internal sealed class MeterRow
	{
		public const float LineHeight = 17f;

		public const float BarHeight = 6f;

		public const float CompactValueWidth = 185f;

		private const float BarGap = 2f;

		private const float RowGap = 4f;

		private readonly TMP_Text _name;

		private readonly TMP_Text _value;

		private readonly TMP_Text _share;

		private readonly TMP_Text _rate;

		private readonly RectTransform _bar;

		private readonly Image[] _segments = (Image[])(object)new Image[11];

		private readonly bool _detailed;

		public RectTransform Root { get; }

		public long Id { get; private set; }

		private MeterRow(RectTransform root, bool detailed, TMP_Text name, TMP_Text value, TMP_Text share, TMP_Text rate, RectTransform bar)
		{
			Root = root;
			_detailed = detailed;
			_name = name;
			_value = value;
			_share = share;
			_rate = rate;
			_bar = bar;
		}

		public static float Height(bool bars)
		{
			if (!bars)
			{
				return 21f;
			}
			return 29f;
		}

		public static MeterRow Create(RectTransform parent, bool detailed, Action<long> onClick)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("Row", new Type[1] { typeof(RectTransform) });
			RectTransform val2 = (RectTransform)val.transform;
			((Transform)val2).SetParent((Transform)(object)parent, false);
			val2.anchorMin = new Vector2(0f, 1f);
			val2.anchorMax = new Vector2(1f, 1f);
			val2.pivot = new Vector2(0f, 1f);
			MeterRow row = null;
			if (onClick != null)
			{
				UiStyle.Clickable(val, delegate
				{
					onClick(row.Id);
				});
			}
			row = Build(val2, detailed);
			return row;
		}

		public void Apply(CombatantRow row, float leader, float width, bool bars, bool showShare, bool local, string prefix)
		{
			//IL_0070: 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)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0180: Unknown result type (might be due to invalid IL or missing references)
			Id = row.Id;
			string text = (string.IsNullOrEmpty(row.Name) ? "..." : row.Name);
			if (Combatant.IsUnownedPet(row.Id) && Localization.instance != null)
			{
				text = MeterFormat.Plain(Localization.instance.Localize(text));
			}
			_name.text = prefix + text;
			((Graphic)_name).color = (local ? UiStyle.Mine : UiStyle.FontColor);
			string text2 = MeterFormat.Number(row.Total);
			if (_detailed)
			{
				_value.text = text2;
				_share.text = MeterFormat.Percent(row.Share);
				_rate.text = MeterFormat.Number(row.Dps);
			}
			else
			{
				_value.text = (showShare ? (text2 + "  " + MeterFormat.Number(row.Dps) + "/s  <color=#" + ColorUtility.ToHtmlStringRGB(UiStyle.Dim) + ">" + MeterFormat.Percent(row.Share) + "</color>") : (text2 + "  " + MeterFormat.Number(row.Dps) + "/s"));
			}
			((Component)_bar).gameObject.SetActive(bars);
			if (bars)
			{
				Layout(row, leader, width);
			}
			Root.sizeDelta = new Vector2(0f, Height(bars));
		}

		public void SetActive(bool active)
		{
			if (((Component)Root).gameObject.activeSelf != active)
			{
				((Component)Root).gameObject.SetActive(active);
			}
		}

		public void Place(float y)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			Root.anchoredPosition = new Vector2(0f, 0f - y);
		}

		private static MeterRow Build(RectTransform root, bool detailed)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: 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_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: 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_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: Expected O, but got Unknown
			//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01db: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: Unknown result type (might be due to invalid IL or missing references)
			//IL_021d: Unknown result type (might be due to invalid IL or missing references)
			//IL_022e: Unknown result type (might be due to invalid IL or missing references)
			//IL_028b: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_02df: Unknown result type (might be due to invalid IL or missing references)
			//IL_02eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0305: Unknown result type (might be due to invalid IL or missing references)
			//IL_0314: Unknown result type (might be due to invalid IL or missing references)
			TMP_Text val = UiStyle.Text("Name", root, 15f, (TextAlignmentOptions)4097, UiStyle.FontColor);
			val.richText = false;
			val.rectTransform.anchorMin = new Vector2(0f, 1f);
			val.rectTransform.anchorMax = new Vector2(1f, 1f);
			val.rectTransform.pivot = new Vector2(0.5f, 1f);
			val.rectTransform.offsetMin = new Vector2(0f, -17f);
			val.rectTransform.offsetMax = new Vector2(detailed ? (-250f) : (-185f), 0f);
			TMP_Text val2 = UiStyle.Text("Value", root, 15f, (TextAlignmentOptions)4100, UiStyle.FontColor);
			TMP_Text val3 = null;
			TMP_Text val4 = null;
			if (detailed)
			{
				UiStyle.PinRight(val2.rectTransform, 250f, 152f, 0f, 17f);
				val3 = UiStyle.Text("Share", root, 13f, (TextAlignmentOptions)4100, UiStyle.Dim);
				UiStyle.PinRight(val3.rectTransform, 68f, 0f, 0f, 17f);
				val4 = UiStyle.Text("Rate", root, 13f, (TextAlignmentOptions)4100, UiStyle.Dim);
				UiStyle.PinRight(val4.rectTransform, 145f, 75f, 0f, 17f);
			}
			else
			{
				UiStyle.PinRight(val2.rectTransform, 185f, 0f, 0f, 17f);
			}
			RectTransform val5 = (RectTransform)new GameObject("Bar", new Type[1] { typeof(RectTransform) }).transform;
			((Transform)val5).SetParent((Transform)(object)root, false);
			val5.anchorMin = new Vector2(0f, 1f);
			val5.anchorMax = new Vector2(1f, 1f);
			val5.pivot = new Vector2(0f, 1f);
			val5.anchoredPosition = new Vector2(0f, -19f);
			val5.sizeDelta = new Vector2(0f, 6f);
			Image obj = UiStyle.Box("Back", val5, UiStyle.BarTrack);
			obj.sprite = UiStyle.Flat();
			UiStyle.Stretch(((Graphic)obj).rectTransform, 0f, 0f, 0f, 0f);
			MeterRow meterRow = new MeterRow(root, detailed, val, val2, val3, val4, val5);
			for (int i = 0; i < 11; i++)
			{
				Image val6 = UiStyle.Box("Kind" + i, val5, Color.white);
				val6.sprite = UiStyle.Flat();
				RectTransform rectTransform = ((Graphic)val6).rectTransform;
				rectTransform.anchorMin = new Vector2(0f, 0f);
				rectTransform.anchorMax = new Vector2(0f, 1f);
				rectTransform.pivot = new Vector2(0f, 0.5f);
				rectTransform.offsetMin = new Vector2(rectTransform.offsetMin.x, 0f);
				rectTransform.offsetMax = new Vector2(rectTransform.offsetMax.x, 0f);
				((Component)val6).gameObject.SetActive(false);
				meterRow._segments[i] = val6;
			}
			return meterRow;
		}

		private void Layout(CombatantRow row, float leader, float width)
		{
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			float num = ((leader > 0f) ? (Mathf.Clamp01(row.Total / leader) * width) : 0f);
			Color[] colors = UiStyle.Colors;
			float num2 = 0f;
			for (int i = 0; i < 11; i++)
			{
				Image val = _segments[i];
				float num3 = row.ByKind[i];
				if (num3 <= 0f || row.Total <= 0f || num <= 0f)
				{
					if (((Component)val).gameObject.activeSelf)
					{
						((Component)val).gameObject.SetActive(false);
					}
					continue;
				}
				float num4 = num * (num3 / row.Total);
				RectTransform rectTransform = ((Graphic)val).rectTransform;
				rectTransform.anchoredPosition = new Vector2(num2, 0f);
				rectTransform.sizeDelta = new Vector2(num4, 0f);
				((Graphic)val).color = colors[i];
				if (!((Component)val).gameObject.activeSelf)
				{
					((Component)val).gameObject.SetActive(true);
				}
				num2 += num4;
			}
		}
	}
	internal sealed class SettingsPanel : MonoBehaviour
	{
		private sealed class Control
		{
			private readonly TMP_Text _value;

			private readonly Func<string> _read;

			public Control(TMP_Text value, Func<string> read)
			{
				_value = value;
				_read = read;
			}

			public void Refresh()
			{
				_value.text = _read();
			}
		}

		private const float Padding = 16f;

		private const float RowHeight = 24f;

		private const float ColumnWidth = 320f;

		private const float ValueWidth = 110f;

		private readonly List<Control> _rows = new List<Control>();

		private RectTransform _panel;

		private TMP_Text _hint;

		private bool _failed;

		private bool _open;

		public static SettingsPanel Instance { get; private set; }

		public static bool IsOpen
		{
			get
			{
				if ((Object)(object)Instance != (Object)null)
				{
					return Instance._open;
				}
				return false;
			}
		}

		public void Toggle()
		{
			if (_open)
			{
				Close();
				return;
			}
			if ((Object)(object)_panel == (Object)null)
			{
				_panel = UiStyle.Build("CombatStats Settings", ref _failed, Build);
				if ((Object)(object)_panel == (Object)null)
				{
					return;
				}
			}
			_open = true;
			((Component)_panel).gameObject.SetActive(true);
			RefreshValues();
		}

		public void Close()
		{
			_open = false;
			if ((Object)(object)_panel != (Object)null)
			{
				((Component)_panel).gameObject.SetActive(false);
			}
		}

		private void Awake()
		{
			Instance = this;
		}

		private void Update()
		{
			if (_open && (Object)(object)_panel == (Object)null)
			{
				Close();
			}
		}

		private void OnDestroy()
		{
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}

		private void RefreshValues()
		{
			//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_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			foreach (Control row in _rows)
			{
				row.Refresh();
			}
			ModConfig settings = Plugin.Settings;
			_hint.text = ((object)settings.CompactKey.Value/*cast due to .constrained prefix*/).ToString() + "  Meter visibility    " + ((object)settings.DetailKey.Value/*cast due to .constrained prefix*/).ToString() + "  Fight details";
		}

		private void Build(RectTransform panel)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_029b: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0308: Unknown result type (might be due to invalid IL or missing references)
			_panel = panel;
			_rows.Clear();
			UiStyle.Panel(panel, new Vector2(720f, 470f), 110, "Combat stats settings");
			UiStyle.CornerButton(panel, "Close", 16f, 70f, Close);
			ModConfig settings = Plugin.Settings;
			float x = 16f;
			float x2 = 384f;
			float y = 46f;
			y = Heading("Compact meter", x, y);
			y = Row(x, y, "When to show", () => Word(settings.CompactMode.Value), delegate(int step)
			{
				settings.CompactMode.Value = (MeterMode)Wrap((int)(settings.CompactMode.Value + step), 3);
			});
			y = Row(x, y, "Rows", () => settings.CompactRows.Value.ToString(CultureInfo.InvariantCulture), delegate(int step)
			{
				ConfigEntry<int> compactRows = settings.CompactRows;
				compactRows.Value += step;
			});
			y = Row(x, y, "Position", () => Word(settings.CompactAnchor.Value), delegate(int step)
			{
				settings.CompactAnchor.Value = (ScreenAnchor)Wrap((int)(settings.CompactAnchor.Value + step), 6);
			});
			y = Row(x, y, "Horizontal inset", () => settings.CompactOffset.Value.x.ToString("0", CultureInfo.InvariantCulture), delegate(int step)
			{
				//IL_001b: Unknown result type (might be due to invalid IL or missing references)
				//IL_003e: 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)
				settings.CompactOffset.Value = new Vector2(Mathf.Max(0f, settings.CompactOffset.Value.x + (float)step * 10f), settings.CompactOffset.Value.y);
			});
			y = Row(x, y, "Vertical inset", () => settings.CompactOffset.Value.y.ToString("0", CultureInfo.InvariantCulture), delegate(int step)
			{
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				//IL_0030: 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)
				settings.CompactOffset.Value = new Vector2(settings.CompactOffset.Value.x, Mathf.Max(0f, settings.CompactOffset.Value.y + (float)step * 10f));
			});
			y = Row(x, y, "Width", () => settings.CompactWidth.Value.ToString("0", CultureInfo.InvariantCulture), delegate(int step)
			{
				ConfigEntry<float> compactWidth = settings.CompactWidth;
				compactWidth.Value += (float)step * 20f;
			});
			y = Row(x, y, "Scale", () => settings.CompactScale.Value.ToString("0.00", CultureInfo.InvariantCulture), delegate(int step)
			{
				ConfigEntry<float> compactScale = settings.CompactScale;
				compactScale.Value += (float)step * 0.05f;
			});
			y = Row(x, y, "Background opacity", () => settings.CompactOpacity.Value.ToString("0.00", CultureInfo.InvariantCulture), delegate(int step)
			{
				ConfigEntry<float> compactOpacity = settings.CompactOpacity;
				compactOpacity.Value += (float)step * 0.05f;
			});
			y = Toggle(x, y, "Caption line", () => settings.CompactCaption.Value, delegate(bool value)
			{
				settings.CompactCaption.Value = value;
			});
			y = Toggle(x, y, "Percentages", () => settings.CompactShare.Value, delegate(bool value)
			{
				settings.CompactShare.Value = value;
			});
			Toggle(x, y, "Bars", () => settings.CompactBars.Value, delegate(bool value)
			{
				settings.CompactBars.Value = value;
			});
			y = 46f;
			y = Heading("Detail window", x2, y);
			y = Row(x2, y, "Width", () => settings.DetailSize.Value.x.ToString("0", CultureInfo.InvariantCulture), delegate(int step)
			{
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				//IL_0043: 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)
				settings.DetailSize.Value = new Vector2(Mathf.Clamp(settings.DetailSize.Value.x + (float)step * 20f, 600f, 1400f), settings.DetailSize.Value.y);
			});
			y = Row(x2, y, "Height", () => settings.DetailSize.Value.y.ToString("0", CultureInfo.InvariantCulture), delegate(int step)
			{
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				//IL_002b: 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)
				settings.DetailSize.Value = new Vector2(settings.DetailSize.Value.x, Mathf.Clamp(settings.DetailSize.Value.y + (float)step * 20f, 280f, 1000f));
			});
			y += 16f;
			TMP_Text obj = UiStyle.Text("RecordingRules", panel, 13f, (TextAlignmentOptions)257, UiStyle.Dim);
			UiStyle.PlaceTopLeft(obj.rectTransform, x2, y, 320f, 44f);
			obj.text = "Pets and summons count for their owner.\nDPS uses the whole fight.";
			_hint = UiStyle.Text("Hint", panel, 12f, (TextAlignmentOptions)4097, UiStyle.Dim);
			UiStyle.PlaceTopLeft(_hint.rectTransform, 16f, panel.sizeDelta.y - 30f, panel.sizeDelta.x - 32f, 18f);
			((Component)panel).gameObject.SetActive(false);
			Plugin.Debug("Settings panel built");
		}

		private float Heading(string text, float x, float y)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			TMP_Text obj = UiStyle.Text("Heading", _panel, 13f, (TextAlignmentOptions)4097, UiStyle.Mine);
			UiStyle.PlaceTopLeft(obj.rectTransform, x, y, 320f, 20f);
			obj.text = text;
			UiStyle.PlaceTopLeft(((Graphic)UiStyle.Line("HeadingLine", _panel)).rectTransform, x, y + 19f, 320f, 1f);
			return y + 26f;
		}

		private float Toggle(float x, float y, string label, Func<bool> read, Action<bool> write)
		{
			return Row(x, y, label, () => (!read()) ? "off" : "on", delegate(int step)
			{
				if (step != 0)
				{
					write(!read());
				}
			});
		}

		private float Row(float x, float y, string label, Func<string> read, Action<int> step)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: 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_00c0: 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_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Unknown result type (might be due to invalid IL or missing references)
			RectTransform val = (RectTransform)new GameObject("Setting", new Type[1] { typeof(RectTransform) }).transform;
			((Transform)val).SetParent((Transform)(object)_panel, false);
			UiStyle.PlaceTopLeft(val, x, y, 320f, 24f);
			TMP_Text obj = UiStyle.Text("Label", val, 14f, (TextAlignmentOptions)4097, UiStyle.FontColor);
			obj.rectTransform.offsetMax = new Vector2(-162f, 0f);
			obj.text = label;
			TMP_Text obj2 = UiStyle.Text("Value", val, 14f, (TextAlignmentOptions)514, UiStyle.Dim);
			obj2.rectTransform.anchorMin = new Vector2(1f, 0f);
			obj2.rectTransform.anchorMax = new Vector2(1f, 1f);
			obj2.rectTransform.offsetMin = new Vector2(-134f, 0f);
			obj2.rectTransform.offsetMax = new Vector2(-24f, 0f);
			Control control = new Control(obj2, read);
			Place(UiStyle.TextButton("Less", val, "<", 15f, UiStyle.Dim, delegate
			{
				step(-1);
				RefreshValues();
			}), -158f, -136f);
			Place(UiStyle.TextButton("More", val, ">", 15f, UiStyle.Dim, delegate
			{
				step(1);
				RefreshValues();
			}), -22f, 0f);
			control.Refresh();
			_rows.Add(control);
			return y + 24f;
		}

		private static void Place(Button button, float from, float to)
		{
			//IL_0006: 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_0016: 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_002b: 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_0040: 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_005