Decompiled source of CombatMeter v1.0.0

plugins/CombatMeter.dll

Decompiled 2 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using DiagnosticDamageProbe.Attribution;
using DiagnosticDamageProbe.Configuration;
using DiagnosticDamageProbe.Diagnostics;
using DiagnosticDamageProbe.Encounter;
using DiagnosticDamageProbe.Snapshot;
using DiagnosticDamageProbe.Statistics;
using DiagnosticDamageProbe.Transport;
using DiagnosticDamageProbe.UI;
using HarmonyLib;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("CombatMeter")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("CombatMeter")]
[assembly: AssemblyTitle("CombatMeter")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace DiagnosticDamageProbe
{
	[HarmonyPatch(typeof(Character), "ApplyDamage", new Type[]
	{
		typeof(HitData),
		typeof(bool),
		typeof(bool),
		typeof(DamageModifier)
	})]
	internal static class ApplyDamagePatch
	{
		internal sealed class CallState
		{
			internal readonly ZNetView View;

			internal readonly ZDO Zdo;

			internal readonly long Session;

			internal readonly long Owner;

			internal readonly float HpBefore;

			internal readonly LogFields Fields;

			internal readonly DamageFacts Facts;

			internal CallState(ZNetView view, ZDO zdo, long session, float hpBefore, LogFields fields, DamageFacts facts)
			{
				View = view;
				Zdo = zdo;
				Session = session;
				Owner = zdo.GetOwner();
				HpBefore = hpBefore;
				Fields = fields;
				Facts = facts;
			}
		}

		[HarmonyPrefix]
		internal static void Prefix(Character __instance, HitData hit, out CallState __state)
		{
			//IL_025b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0260: Unknown result type (might be due to invalid IL or missing references)
			//IL_0269: Unknown result type (might be due to invalid IL or missing references)
			//IL_027a: 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_029c: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_02be: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0302: Unknown result type (might be due to invalid IL or missing references)
			//IL_0313: Unknown result type (might be due to invalid IL or missing references)
			//IL_0324: Unknown result type (might be due to invalid IL or missing references)
			//IL_04f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_057a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0580: Invalid comparison between Unknown and I4
			//IL_0583: Unknown result type (might be due to invalid IL or missing references)
			//IL_0589: Invalid comparison between Unknown and I4
			//IL_05b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_05b6: Invalid comparison between Unknown and I4
			__state = null;
			try
			{
				if (!Object.op_Implicit((Object)(object)__instance))
				{
					return;
				}
				ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
				if (!Object.op_Implicit((Object)(object)component) || !component.IsValid() || !component.IsOwner())
				{
					return;
				}
				ZDO zDO = component.GetZDO();
				if (zDO != null && ZDOMan.instance != null)
				{
					float health = __instance.GetHealth();
					long sessionID = ZDOMan.GetSessionID();
					ZNet instance = ZNet.instance;
					bool flag = Object.op_Implicit((Object)(object)instance) && instance.IsServer();
					bool flag2 = Object.op_Implicit((Object)(object)instance) && instance.IsDedicated();
					bool flag3 = Object.op_Implicit((Object)(object)instance) && ZNet.IsSinglePlayer;
					string value = ((!Object.op_Implicit((Object)(object)instance)) ? "Unknown" : (flag2 ? "DedicatedServer" : ((!flag) ? "Client" : (flag3 ? "SinglePlayer" : "ListenHost"))));
					LogFields logFields = new LogFields().Text("TimestampUtc", DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)).Text("CallId", Guid.NewGuid().ToString("N")).Text("ProcessRunId", Plugin.ProcessRunId)
						.Text("PeerSessionId", sessionID.ToString(CultureInfo.InvariantCulture))
						.Text("Role", value)
						.Flag("IsServer", flag)
						.Flag("IsDedicated", flag2)
						.Flag("IsSinglePlayer", flag3)
						.Text("VictimName", __instance.m_name)
						.Text("VictimObjectName", ((Object)__instance).name)
						.Text("VictimType", ((object)__instance).GetType().FullName)
						.Flag("VictimIsPlayer", __instance.IsPlayer())
						.Text("VictimZDOID", ((object)Unsafe.As<ZDOID, ZDOID>(ref zDO.m_uid)/*cast due to .constrained prefix*/).ToString())
						.Text("VictimOwnerBefore", zDO.GetOwner().ToString(CultureInfo.InvariantCulture))
						.Flag("IsOwnerBefore", component.IsOwner())
						.Number("HPBefore", health)
						.Text("HitType", ((object)Unsafe.As<HitType, HitType>(ref hit?.m_hitType)/*cast due to .constrained prefix*/).ToString())
						.Text("HitDataState", (hit == null) ? "Null" : "Present");
					if (hit != null)
					{
						logFields.Number("HitTotalDamageBefore", hit.GetTotalDamage());
						DamageTypes damage = hit.m_damage;
						logFields.Number("GenericBefore", damage.m_damage).Number("BluntBefore", damage.m_blunt).Number("SlashBefore", damage.m_slash)
							.Number("PierceBefore", damage.m_pierce)
							.Number("ChopBefore", damage.m_chop)
							.Number("PickaxeBefore", damage.m_pickaxe)
							.Number("FireBefore", damage.m_fire)
							.Number("FrostBefore", damage.m_frost)
							.Number("LightningBefore", damage.m_lightning)
							.Number("PoisonBefore", damage.m_poison)
							.Number("SpiritBefore", damage.m_spirit)
							.Number("NonPlayerBefore", damage.m_nonPlayer);
					}
					Character val = ((hit != null) ? hit.GetAttacker() : null);
					MagicAttributionProbe.ObserveDotTick(__instance, hit);
					MagicAttributionProbe.ObserveSummonHit(val);
					Player val2 = (Player)(object)((val is Player) ? val : null);
					string value2 = ((hit == null || ((ZDOID)(ref hit.m_attacker)).IsNone()) ? "None" : ((!Object.op_Implicit((Object)(object)val)) ? "Unresolved" : (Object.op_Implicit((Object)(object)val2) ? "Player" : "NPC")));
					logFields.Text("AttackerZDOID", ((object)Unsafe.As<ZDOID, ZDOID>(ref hit?.m_attacker)/*cast due to .constrained prefix*/).ToString()).Text("AttackerClass", value2).Text("GetAttackerResult", Object.op_Implicit((Object)(object)val) ? ((object)val).GetType().FullName : "null")
						.Text("AttackerName", Object.op_Implicit((Object)(object)val) ? val.m_name : null)
						.Text("AttackerObjectName", Object.op_Implicit((Object)(object)val) ? ((Object)val).name : null)
						.Text("AttackerPlayerID", Object.op_Implicit((Object)(object)val2) ? val2.GetPlayerID().ToString(CultureInfo.InvariantCulture) : null)
						.Text("AttackerPlayerName", Object.op_Implicit((Object)(object)val2) ? val2.GetPlayerName() : null);
					Player val3 = (Player)(object)((__instance is Player) ? __instance : null);
					long? victimPlayerId = (Object.op_Implicit((Object)(object)val3) ? Nonzero(val3.GetPlayerID()) : ((long?)null));
					long? attackerPlayerId = (Object.op_Implicit((Object)(object)val2) ? Nonzero(val2.GetPlayerID()) : ((long?)null));
					DamageFacts facts = new DamageFacts(((ZDOID)(ref zDO.m_uid)).UserID, ((ZDOID)(ref zDO.m_uid)).ID, __instance.IsPlayer(), victimPlayerId, (AttackerClass)Enum.Parse(typeof(AttackerClass), value2), attackerPlayerId, (byte)((hit != null) ? ((int)hit.m_hitType) : 0), Object.op_Implicit((Object)(object)val3) ? val3.GetPlayerName() : __instance.m_name, Object.op_Implicit((Object)(object)val2) ? val2.GetPlayerName() : (Object.op_Implicit((Object)(object)val) ? val.m_name : null), (hit == null || ((ZDOID)(ref hit.m_attacker)).IsNone()) ? 0 : ((ZDOID)(ref hit.m_attacker)).UserID, (hit != null && !((ZDOID)(ref hit.m_attacker)).IsNone()) ? ((ZDOID)(ref hit.m_attacker)).ID : 0u, (byte)((hit != null) ? (((int)hit.m_hitType == 7) ? 1 : (((int)hit.m_hitType == 5 && hit.m_damage.m_spirit > 0f && hit.m_damage.m_fire <= 0f) ? 3 : (((int)hit.m_hitType == 5) ? 2 : 0))) : 0));
					__state = new CallState(component, zDO, sessionID, health, logFields, facts);
				}
			}
			catch (Exception ex)
			{
				__state = null;
				Plugin.Warn("DamageProbeCaptureFailed phase=Prefix exception=" + ex.GetType().Name);
			}
		}

		[HarmonyPostfix]
		internal static void Postfix(Character __instance, CallState __state)
		{
			if (__state == null)
			{
				return;
			}
			try
			{
				if (!Object.op_Implicit((Object)(object)__instance) || !Object.op_Implicit((Object)(object)__state.View) || !__state.View.IsValid() || __state.Zdo != __state.View.GetZDO() || ZDOMan.instance == null || ZDOMan.GetSessionID() != __state.Session || !__state.View.IsOwner() || __state.Zdo.GetOwner() != __state.Owner)
				{
					Plugin.Warn("DamageProbeObservationLost reason=VictimInvalidOrOwnershipChanged");
					return;
				}
				float health = __instance.GetHealth();
				if (!IsFinite(__state.HpBefore) || !IsFinite(health))
				{
					Plugin.Warn("DamageProbeObservationLost reason=NonFiniteHP");
					return;
				}
				float num = Math.Max(0f, __state.HpBefore - health);
				if (!(num > 0f) || !IsFinite(num))
				{
					return;
				}
				Plugin.Publish(__state.Session, __state.Facts, num);
				__state.Fields.Text("CompletedUtc", DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)).Text("VictimOwner", __state.Zdo.GetOwner().ToString(CultureInfo.InvariantCulture)).Flag("IsOwner", __state.View.IsOwner())
					.Number("HPAfter", health)
					.Number("EffectiveHpLoss", num);
				if (Plugin.LoggingEnabled)
				{
					ManualLogSource probeLog = Plugin.ProbeLog;
					if (probeLog != null)
					{
						probeLog.LogInfo((object)("DamageProbeEvent " + __state.Fields));
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Warn("DamageProbeCaptureFailed phase=Postfix exception=" + ex.GetType().Name);
			}
		}

		private static bool IsFinite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}

		private static long? Nonzero(long value)
		{
			if (value != 0L)
			{
				return value;
			}
			return null;
		}
	}
	internal sealed class DamageCommitTransport
	{
		internal readonly struct DeathObservation
		{
			internal readonly string Result;

			internal readonly EncounterState StateBefore;

			internal readonly EncounterState StateAfter;

			internal DeathObservation(string result, EncounterState before, EncounterState after)
			{
				Result = result;
				StateBefore = before;
				StateAfter = after;
			}
		}

		internal const string CommitRpc = "CombatMeter.DamageCommit.v1";

		internal const string AckRpc = "CombatMeter.DamageCommitAck.v1";

		internal const string AttributionRpc = "CombatMeter.MagicAttribution.v1";

		internal const string AttributionAckRpc = "CombatMeter.MagicAttributionAck.v1";

		internal const string SnapshotRpc = "CombatMeter.CombatSnapshot.v2";

		internal const double SnapshotIntervalSeconds = 0.5;

		private readonly ConditionalWeakTable<ZRoutedRpc, object> _registrations = new ConditionalWeakTable<ZRoutedRpc, object>();

		private ZNet _net;

		private ZRoutedRpc _rpc;

		private WeakReference<ZNet> _blockedNet;

		private CommitSession _session;

		private EncounterManager _encounter;

		private CombatClusterManager _clusters;

		private CombatMeterPerformance _performance;

		private SessionPlayerIdentity _identities;

		private readonly Func<long> _performanceTimestamp;

		private MagicAttributionResolver _attribution;

		private EventSequence _attributionSequence;

		private double _clockAnchorMonotonic;

		private long _clockAnchorUtcTicks;

		private readonly CombatSnapshotStore _snapshotStore = new CombatSnapshotStore();

		private Guid _snapshotEpoch;

		private long _snapshotSequence;

		private double _nextSnapshotAt;

		private string _lastLocalSnapshotDiagnosticKey;

		private string _lastPublishedSnapshotDiagnosticKey;

		private string _lastAcceptedSnapshotDiagnosticKey;

		private double _nextSnapshotReceivedDiagnosticAt;

		private readonly AttributionAcceptor _attributionAcceptor = new AttributionAcceptor();

		private readonly AttributionOutbox _attributionOutbox = new AttributionOutbox();

		private readonly List<(DamageCommit Commit, double AcceptedAt, double Expires)> _pendingSummon = new List<(DamageCommit, double, double)>();

		private readonly List<(DamageCommit Commit, double AcceptedAt, double Expires)> _pendingDot = new List<(DamageCommit, double, double)>();

		private bool _disposed;

		private readonly Func<double> _now;

		private readonly Func<double> _softTimeout;

		private readonly Func<double> _recoveryTimeout;

		private readonly Func<double> _dpsIdleTimeout;

		private EncounterSettings _encounterSettings;

		internal EncounterManager Encounter => _encounter;

		internal CombatClusterManager Clusters => _clusters;

		internal CombatMeterPerformance Performance => _performance;

		internal MagicAttributionResolver Attribution => _attribution;

		internal int AttributionPendingCount => _attributionOutbox.Count;

		internal CombatSnapshotStore SnapshotStore => _snapshotStore;

		internal bool HasActiveSession => _session != null;

		internal DamageCommitTransport(Func<double> now = null, Func<double> softTimeout = null, Func<double> recoveryTimeout = null, Func<double> dpsIdleTimeout = null, Func<long> performanceTimestamp = null)
		{
			_now = now ?? ((Func<double>)(() => (double)Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency));
			_softTimeout = softTimeout;
			_recoveryTimeout = recoveryTimeout;
			_dpsIdleTimeout = dpsIdleTimeout;
			_performanceTimestamp = performanceTimestamp;
		}

		internal void Bind(ZNet net)
		{
			if (_disposed || !Object.op_Implicit((Object)(object)net) || net.IsDedicated() || IsBlocked(net))
			{
				return;
			}
			ZRoutedRpc rpc = ZRoutedRpc.instance;
			if (rpc == null || ZDOMan.instance == null)
			{
				return;
			}
			if (net != _net || rpc != _rpc)
			{
				Close("NetworkInstanceChanged");
				_net = net;
				_rpc = rpc;
				_blockedNet = null;
			}
			if (!_registrations.TryGetValue(rpc, out var _))
			{
				_registrations.Add(rpc, new object());
				try
				{
					rpc.Register<ZPackage>("CombatMeter.DamageCommit.v1", (Action<long, ZPackage>)delegate(long sender, ZPackage pkg)
					{
						Receive(rpc, sender, pkg);
					});
					rpc.Register<ZPackage>("CombatMeter.DamageCommitAck.v1", (Action<long, ZPackage>)delegate(long sender, ZPackage pkg)
					{
						ReceiveAck(rpc, sender, pkg);
					});
					rpc.Register<ZPackage>("CombatMeter.MagicAttribution.v1", (Action<long, ZPackage>)delegate(long sender, ZPackage pkg)
					{
						ReceiveAttribution(rpc, sender, pkg);
					});
					rpc.Register<ZPackage>("CombatMeter.MagicAttributionAck.v1", (Action<long, ZPackage>)delegate(long sender, ZPackage pkg)
					{
						ReceiveAttributionAck(rpc, sender, pkg);
					});
					rpc.Register<ZPackage>("CombatMeter.CombatSnapshot.v2", (Action<long, ZPackage>)delegate(long sender, ZPackage pkg)
					{
						ReceiveSnapshot(rpc, sender, pkg);
					});
				}
				catch
				{
					_blockedNet = new WeakReference<ZNet>(net);
					Close("RpcRegistrationFailed");
					throw;
				}
			}
			EnsureSession();
		}

		private bool EnsureSession()
		{
			if (_disposed || !Object.op_Implicit((Object)(object)_net) || IsBlocked(_net) || _net != ZNet.instance || _rpc != ZRoutedRpc.instance || ZDOMan.instance == null)
			{
				return false;
			}
			long sessionID = ZDOMan.GetSessionID();
			if (sessionID == 0L)
			{
				return false;
			}
			bool flag = _net.IsServer();
			if (!flag && _net.GetServerPeer() == null)
			{
				return false;
			}
			if (_session != null && (_session.Peer != sessionID || _session.IsHost != flag))
			{
				Close("PeerChanged");
			}
			if (_session == null)
			{
				_encounterSettings = (flag ? new EncounterSettings(ReadSoftTimeout(), ReadRecoveryTimeout(), ReadDpsIdleTimeout()) : null);
				_encounter = (flag ? new EncounterManager(new CombatStatisticsAggregator(), _encounterSettings, Plugin.EncounterLog) : null);
				_clusters = (flag ? new CombatClusterManager(_encounterSettings) : null);
				_identities = (flag ? new SessionPlayerIdentity(Plugin.TransportLog) : null);
				if (flag)
				{
					LogEncounterSettings("CombatEncounterSettings");
				}
				_attribution = (flag ? new MagicAttributionResolver() : null);
				_attributionSequence = new EventSequence(sessionID, Guid.NewGuid());
				_clockAnchorMonotonic = _now();
				_clockAnchorUtcTicks = DateTime.UtcNow.Ticks;
				_snapshotStore.Clear();
				_snapshotEpoch = Guid.NewGuid();
				_snapshotSequence = 0L;
				_nextSnapshotAt = _clockAnchorMonotonic;
				_lastLocalSnapshotDiagnosticKey = null;
				_lastPublishedSnapshotDiagnosticKey = null;
				_lastAcceptedSnapshotDiagnosticKey = null;
				_nextSnapshotReceivedDiagnosticAt = 0.0;
				_session = new CommitSession(sessionID, Guid.NewGuid(), flag, Send, Log, flag ? new Action<DamageCommit>(AcceptForAttribution) : null);
				if (!flag)
				{
					Plugin.EncounterLog("CombatSettingsAuthority role=Client source=ListenHostSnapshot localCombatConfigIgnored=true");
				}
				LogFields logFields = new LogFields().Text("PeerSessionId", sessionID.ToString(CultureInfo.InvariantCulture));
				Guid epoch = _session.Sequence.Epoch;
				Plugin.TransportLog("DamageCommitSessionReady " + logFields.Text("SourceEpoch", epoch.ToString("N")).Flag("IsHost", flag));
			}
			RefreshPerformanceSettings();
			return true;
		}

		internal bool ObserveSummonProvenance(long observedPeer, long summonCreator, uint summonObject, long playerId)
		{
			if (!EnsureSession())
			{
				Plugin.MagicLog("SummonProvenanceRegistrationFailed reason=TransportUnavailable");
				return false;
			}
			if (observedPeer != _session.Peer)
			{
				Plugin.MagicLog("SummonProvenanceRegistrationFailed reason=NotExpectedPeer");
				return false;
			}
			if (summonCreator == 0L || summonObject == 0)
			{
				Plugin.MagicLog("SummonProvenanceRegistrationFailed reason=SummonZdoUnavailable");
				return false;
			}
			if (playerId == 0L)
			{
				Plugin.MagicLog("SummonProvenanceRegistrationFailed reason=PlayerIdZero");
				return false;
			}
			PublishAttribution(new AttributionMessage(_attributionSequence.Next(), AttributionMessageKind.Summon, summonCreator, summonObject, playerId, DotKind.Poison, 0f, 0f, AttackerClass.None, null, 0L));
			return true;
		}

		internal void ObserveDotPool(long observedPeer, long victimCreator, uint victimObject, DotKind kind, float before, float after, AttackerClass sourceClass, long? sourcePlayerId, long sourceCreator, uint sourceObject, bool updateAccepted)
		{
			if (EnsureSession() && observedPeer == _session.Peer && victimCreator != 0L && victimObject != 0 && updateAccepted)
			{
				PublishAttribution(new AttributionMessage(_attributionSequence.Next(), AttributionMessageKind.DotPool, victimCreator, victimObject, null, kind, before, after, sourceClass, sourcePlayerId, sourceCreator, sourceObject, updateAccepted));
			}
		}

		private void PublishAttribution(AttributionMessage message)
		{
			if (_session.IsHost)
			{
				ProcessAttribution(message, _session.Peer);
			}
			else if (!_attributionOutbox.Enqueue(message))
			{
				Plugin.Warn("SummonProvenanceRejected reason=PendingCapacity");
			}
			else if (message.Kind == AttributionMessageKind.Summon)
			{
				Plugin.MagicLog("SummonProvenanceMessageQueued summon=" + message.SubjectZdoId + " event=" + message.Id);
			}
		}

		internal DeathObservation ObservePlayerDeath(long playerId, string incarnation = null)
		{
			if (playerId == 0L)
			{
				return new DeathObservation("MissingPlayerID", EncounterState.NoEncounter, EncounterState.NoEncounter);
			}
			if (_disposed || !Object.op_Implicit((Object)(object)_net) || !_net.IsServer() || _net.IsDedicated())
			{
				return new DeathObservation("NotHost", EncounterState.NoEncounter, EncounterState.NoEncounter);
			}
			if (!EnsureSession() || _session == null || !_session.IsHost || _encounter == null)
			{
				return new DeathObservation("NoActiveSession", EncounterState.NoEncounter, EncounterState.NoEncounter);
			}
			EncounterState state = _encounter.State;
			double now = _now();
			PlayerDeathResult playerDeathResult = _encounter.OnPlayerDied(playerId, now, incarnation);
			_clusters.OnPlayerDied(playerId, now, incarnation);
			return new DeathObservation(playerDeathResult.ToString(), state, _encounter.State);
		}

		internal void Observe(long observedPeer, DamageFacts facts, float loss)
		{
			if (!EnsureSession() || observedPeer != _session.Peer)
			{
				Plugin.Warn("DamageCommitObservationRejected reason=NoActiveSession");
				return;
			}
			CombatMeterPerformance performance = GetPerformance();
			if (performance != null)
			{
				performance.DamageObserved++;
			}
			_session.Observe(facts, loss, DateTime.UtcNow.Ticks);
			Pump(publishSnapshot: false);
		}

		internal void Update()
		{
			if (_disposed)
			{
				return;
			}
			if (!Object.op_Implicit((Object)(object)_net) || _net != ZNet.instance || _rpc != ZRoutedRpc.instance)
			{
				Close("NetworkUnavailable");
				if (Object.op_Implicit((Object)(object)ZNet.instance))
				{
					Bind(ZNet.instance);
				}
				return;
			}
			if (_session != null && !_session.IsHost && _net.GetServerPeer() == null)
			{
				Close("Disconnected");
			}
			if (EnsureSession())
			{
				Pump(publishSnapshot: true);
			}
		}

		private void Pump(bool publishSnapshot)
		{
			double num = _now();
			SyncEncounterSettings();
			_encounter?.Update(num);
			_clusters?.Update(num);
			_session?.Pump(num);
			if (_session != null && !_session.IsHost)
			{
				AttributionMessage expired;
				AttributionMessage attributionMessage = _attributionOutbox.Due(num, out expired);
				if (expired != null && expired.Kind == AttributionMessageKind.Summon)
				{
					Plugin.MagicLog("SummonProvenanceRetryExpired summon=" + expired.SubjectZdoId + " event=" + expired.Id);
				}
				if (attributionMessage != null)
				{
					SendAttribution(attributionMessage);
				}
			}
			if (_session == null || !_session.IsHost)
			{
				return;
			}
			for (int num2 = _pendingSummon.Count - 1; num2 >= 0; num2--)
			{
				if (num >= _pendingSummon[num2].Expires)
				{
					(DamageCommit, double, double) tuple = _pendingSummon[num2];
					_pendingSummon.RemoveAt(num2);
					ProcessAttributed(tuple.Item1, tuple.Item2);
				}
			}
			for (int num3 = _pendingDot.Count - 1; num3 >= 0; num3--)
			{
				if (num >= _pendingDot[num3].Expires)
				{
					(DamageCommit, double, double) tuple2 = _pendingDot[num3];
					_pendingDot.RemoveAt(num3);
					ProcessAttributed(tuple2.Item1, tuple2.Item2);
				}
			}
			if (publishSnapshot && num >= _nextSnapshotAt)
			{
				PublishSnapshot(num);
			}
			if (publishSnapshot)
			{
				ReportPerformance();
			}
		}

		private double ReadSoftTimeout()
		{
			return _softTimeout?.Invoke() ?? 20.0;
		}

		private double ReadRecoveryTimeout()
		{
			return _recoveryTimeout?.Invoke() ?? 180.0;
		}

		private double ReadDpsIdleTimeout()
		{
			return _dpsIdleTimeout?.Invoke() ?? 6.0;
		}

		private void SyncEncounterSettings()
		{
			if (_encounterSettings != null && _session != null && _session.IsHost && _encounterSettings.Update(ReadSoftTimeout(), ReadRecoveryTimeout(), ReadDpsIdleTimeout()))
			{
				LogEncounterSettings("CombatEncounterSettingsChanged");
			}
		}

		private void LogEncounterSettings(string marker)
		{
			Plugin.EncounterLog(marker + " combatTimeout=" + _encounterSettings.SoftTimeoutSeconds.ToString(CultureInfo.InvariantCulture) + " recoveryTimeout=" + _encounterSettings.RecoveryTimeoutSeconds.ToString(CultureInfo.InvariantCulture) + " dpsIdleTimeout=" + _encounterSettings.DpsIdleTimeoutSeconds.ToString(CultureInfo.InvariantCulture) + " authority=" + (ZNet.IsSinglePlayer ? "SinglePlayer" : "ListenHost"));
		}

		private void PublishSnapshot(double now)
		{
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b7: Expected O, but got Unknown
			CombatMeterPerformance performance = GetPerformance();
			long num = ((performance != null) ? PerformanceTimestamp() : 0);
			try
			{
				_nextSnapshotAt = now + 0.5;
				_identities.Sync(_net.GetPeers(), _session.Peer, HostPlayerIdentity.ResolveLocal(_session.Peer));
				if (IsCurrentPerformance(performance))
				{
					performance.ReadyPeers = 0;
				}
				long sequence = checked(++_snapshotSequence);
				CombatSnapshot snapshot = BuildRecipientSnapshot(sequence, ResolveSnapshotPlayer(_session.Peer, local: true), now);
				SnapshotApplyResult snapshotApplyResult = _snapshotStore.Apply(snapshot, _session.Peer, _session.Peer);
				if (snapshotApplyResult != SnapshotApplyResult.Accepted)
				{
					throw new InvalidOperationException("LocalSnapshot" + snapshotApplyResult);
				}
				LogSnapshotChange("CombatSnapshotAppliedLocal", snapshot);
				if (ZNet.IsSinglePlayer)
				{
					return;
				}
				HashSet<long> hashSet = new HashSet<long>();
				foreach (ZNetPeer peer in _net.GetPeers())
				{
					if (peer != null && peer.IsReady() && peer.m_uid != 0L && peer.m_uid != _session.Peer && hashSet.Add(peer.m_uid))
					{
						if (IsCurrentPerformance(performance))
						{
							performance.ReadyPeers++;
						}
						CombatSnapshot snapshot2 = BuildRecipientSnapshot(sequence, ResolveSnapshotPlayer(peer.m_uid, local: false), now);
						long num2 = (IsCurrentPerformance(performance) ? PerformanceTimestamp() : 0);
						ZPackage val = new ZPackage(CombatSnapshotCodec.Encode(snapshot2));
						if (IsCurrentPerformance(performance))
						{
							performance.Serialization.Record(PerformanceTimestamp() - num2);
						}
						_rpc.InvokeRoutedRPC(peer.m_uid, "CombatMeter.CombatSnapshot.v2", new object[1] { val });
						if (IsCurrentPerformance(performance))
						{
							performance.RecordSend(val.Size());
						}
						LogSnapshotChange("CombatSnapshotPublished", snapshot2);
					}
				}
			}
			finally
			{
				if (IsCurrentPerformance(performance))
				{
					performance.SnapshotCycle.Record(PerformanceTimestamp() - num);
				}
			}
		}

		private long? ResolveSnapshotPlayer(long peer, bool local)
		{
			CombatMeterPerformance performance = GetPerformance();
			long num = ((performance != null) ? PerformanceTimestamp() : 0);
			long? result = (local ? HostPlayerIdentity.ResolveLocal(peer) : _identities.Resolve(peer));
			if (IsCurrentPerformance(performance))
			{
				performance.RecordIdentity(PerformanceTimestamp() - num, result.HasValue);
			}
			return result;
		}

		private CombatSnapshot BuildRecipientSnapshot(long sequence, long? playerId, double now)
		{
			CombatMeterPerformance performance = GetPerformance();
			long num = ((performance != null) ? PerformanceTimestamp() : 0);
			CombatSnapshot combatSnapshot = CombatSnapshotBuilder.ForPlayer(_session.Peer, _snapshotEpoch, sequence, _clusters, playerId, now);
			if (IsCurrentPerformance(performance))
			{
				performance.RecordBuild(PerformanceTimestamp() - num, combatSnapshot.EncounterState == EncounterState.NoEncounter);
			}
			return combatSnapshot;
		}

		internal void RefreshPerformanceSettings()
		{
			if (Plugin.PerformanceLoggingEnabled)
			{
				CommitSession session = _session;
				if (session != null && session.IsHost)
				{
					if (_performance == null)
					{
						_performance = new CombatMeterPerformance(PerformanceTimestamp(), 0L);
					}
					return;
				}
			}
			_performance = null;
		}

		private CombatMeterPerformance GetPerformance()
		{
			RefreshPerformanceSettings();
			return _performance;
		}

		private bool IsCurrentPerformance(CombatMeterPerformance performance)
		{
			if (performance != null && Plugin.PerformanceLoggingEnabled)
			{
				return performance == _performance;
			}
			return false;
		}

		private long PerformanceTimestamp()
		{
			return _performanceTimestamp?.Invoke() ?? Stopwatch.GetTimestamp();
		}

		private void ReportPerformance(bool force = false)
		{
			CombatMeterPerformance performance = GetPerformance();
			if (performance != null)
			{
				string text = performance.TryReport(PerformanceTimestamp(), enabled: true, _clusters.ActiveCount, force);
				if (text != null)
				{
					Plugin.PerformanceLog(text);
				}
			}
		}

		private void ReceiveSnapshot(ZRoutedRpc rpc, long sender, ZPackage package)
		{
			if (_disposed || rpc != _rpc || !EnsureSession() || _session.IsHost)
			{
				return;
			}
			ZNetPeer serverPeer = _net.GetServerPeer();
			if (serverPeer == null || sender != serverPeer.m_uid)
			{
				Plugin.SnapshotLog("CombatSnapshotRejected reason=UnexpectedSender sender=" + sender);
				return;
			}
			try
			{
				if (package == null || package.Size() > 16384)
				{
					throw new InvalidDataException("PacketSize");
				}
				CombatSnapshot combatSnapshot = CombatSnapshotCodec.Decode(package.GetArray());
				double num = _now();
				if (num >= _nextSnapshotReceivedDiagnosticAt)
				{
					_nextSnapshotReceivedDiagnosticAt = num + 5.0;
					Guid snapshotEpoch = combatSnapshot.SnapshotEpoch;
					Plugin.SnapshotLog("CombatSnapshotReceived epoch=" + snapshotEpoch.ToString("N") + " sequence=" + combatSnapshot.Sequence);
				}
				SnapshotApplyResult snapshotApplyResult = _snapshotStore.Apply(combatSnapshot, sender, serverPeer.m_uid);
				switch (snapshotApplyResult)
				{
				case SnapshotApplyResult.Accepted:
					LogSnapshotChange("CombatSnapshotAccepted", combatSnapshot);
					break;
				case SnapshotApplyResult.Duplicate:
				case SnapshotApplyResult.Stale:
					Plugin.SnapshotLog("CombatSnapshotIgnored reason=" + snapshotApplyResult.ToString() + " sequence=" + combatSnapshot.Sequence);
					break;
				default:
					Plugin.SnapshotLog("CombatSnapshotRejected reason=" + snapshotApplyResult.ToString() + " sequence=" + combatSnapshot.Sequence);
					break;
				}
			}
			catch (Exception ex) when (ex is InvalidDataException || ex is EndOfStreamException || ex is ArgumentException)
			{
				Plugin.SnapshotLog("CombatSnapshotRejected reason=MalformedPayload");
			}
		}

		private void LogSnapshotChange(string marker, CombatSnapshot snapshot)
		{
			string text = snapshot.EncounterId + "/" + snapshot.EncounterState.ToString() + "/" + snapshot.Players.Count;
			foreach (CombatSnapshotPlayer player in snapshot.Players)
			{
				text = text + "/" + player.PlayerId + "/" + player.DamageDone + "/" + player.DamageTaken;
			}
			string text2 = ((marker == "CombatSnapshotAppliedLocal") ? _lastLocalSnapshotDiagnosticKey : ((!(marker == "CombatSnapshotPublished")) ? _lastAcceptedSnapshotDiagnosticKey : _lastPublishedSnapshotDiagnosticKey));
			if (!(text == text2))
			{
				if (marker == "CombatSnapshotAppliedLocal")
				{
					_lastLocalSnapshotDiagnosticKey = text;
				}
				else if (marker == "CombatSnapshotPublished")
				{
					_lastPublishedSnapshotDiagnosticKey = text;
				}
				else
				{
					_lastAcceptedSnapshotDiagnosticKey = text;
				}
				string[] obj = new string[13]
				{
					marker, " epoch=", null, null, null, null, null, null, null, null,
					null, null, null
				};
				Guid snapshotEpoch = snapshot.SnapshotEpoch;
				obj[2] = snapshotEpoch.ToString("N");
				obj[3] = " sequence=";
				obj[4] = snapshot.Sequence.ToString();
				obj[5] = " encounterId=";
				obj[6] = snapshot.EncounterId.ToString();
				obj[7] = " state=";
				obj[8] = snapshot.EncounterState.ToString();
				obj[9] = " elapsed=";
				obj[10] = snapshot.EncounterElapsedSeconds.ToString(CultureInfo.InvariantCulture);
				obj[11] = " players=";
				obj[12] = snapshot.Players.Count.ToString();
				Plugin.SnapshotLog(string.Concat(obj));
			}
		}

		private void Send(DamageCommit commit)
		{
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Expected O, but got Unknown
			ZNetPeer serverPeer = _net.GetServerPeer();
			if (serverPeer == null || !serverPeer.IsReady() || serverPeer.m_uid == 0L)
			{
				throw new InvalidOperationException("HostNotReady");
			}
			_rpc.InvokeRoutedRPC(serverPeer.m_uid, "CombatMeter.DamageCommit.v1", new object[1] { (object)new ZPackage(CommitCodec.Encode(commit)) });
		}

		private void SendAttribution(AttributionMessage message)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Expected O, but got Unknown
			ZNetPeer serverPeer = _net.GetServerPeer();
			if (serverPeer != null && serverPeer.IsReady() && serverPeer.m_uid != 0L)
			{
				_rpc.InvokeRoutedRPC(serverPeer.m_uid, "CombatMeter.MagicAttribution.v1", new object[1] { (object)new ZPackage(AttributionCodec.Encode(message)) });
				if (message.Kind == AttributionMessageKind.Summon)
				{
					Plugin.MagicLog("SummonProvenanceMessageSent summon=" + message.SubjectZdoId + " event=" + message.Id.ToString() + " host=" + serverPeer.m_uid);
				}
			}
		}

		private void ReceiveAttribution(ZRoutedRpc rpc, long sender, ZPackage package)
		{
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Expected O, but got Unknown
			if (_disposed || rpc != _rpc || !EnsureSession() || !_session.IsHost)
			{
				return;
			}
			try
			{
				AttributionMessage attributionMessage = AttributionCodec.Decode(Bytes(package));
				if (attributionMessage.Kind == AttributionMessageKind.Summon)
				{
					Plugin.MagicLog("SummonProvenanceReceived summon=" + attributionMessage.SubjectZdoId + " event=" + attributionMessage.Id.ToString() + " sender=" + sender);
				}
				string text = ProcessAttribution(attributionMessage, sender);
				if ((text == "Accepted" || text == "Duplicate") && sender != 0L && sender != _session.Peer)
				{
					rpc.InvokeRoutedRPC(sender, "CombatMeter.MagicAttributionAck.v1", new object[1] { (object)new ZPackage(AttributionCodec.EncodeAck(attributionMessage.Id)) });
					if (attributionMessage.Kind == AttributionMessageKind.Summon)
					{
						Plugin.MagicLog("SummonProvenanceAckSent summon=" + attributionMessage.SubjectZdoId + " event=" + attributionMessage.Id.ToString() + " result=" + text);
					}
				}
			}
			catch (InvalidDataException)
			{
				Plugin.MagicLog("SummonProvenanceRejected reason=MalformedPayload");
			}
			catch (Exception ex2)
			{
				Plugin.MagicLog("SummonProvenanceRejected reason=" + ex2.GetType().Name);
			}
		}

		private void ReceiveAttributionAck(ZRoutedRpc rpc, long sender, ZPackage package)
		{
			if (_disposed || rpc != _rpc || !EnsureSession() || _session.IsHost)
			{
				return;
			}
			ZNetPeer serverPeer = _net.GetServerPeer();
			if (serverPeer == null || sender != serverPeer.m_uid)
			{
				Plugin.MagicLog("SummonProvenanceAckUnknown reason=UnexpectedSender sender=" + sender);
				return;
			}
			try
			{
				EventId id = AttributionCodec.DecodeAck(Bytes(package));
				Plugin.MagicLog("SummonProvenanceAckReceived event=" + id.ToString() + " sender=" + sender);
				if (_attributionOutbox.Complete(id, out var completed))
				{
					if (completed.Kind == AttributionMessageKind.Summon)
					{
						Plugin.MagicLog("SummonProvenanceMessageCompleted summon=" + completed.SubjectZdoId + " event=" + id);
					}
				}
				else
				{
					Plugin.MagicLog("SummonProvenanceAckUnknown reason=EventIdMismatch event=" + id);
				}
			}
			catch (InvalidDataException)
			{
				Plugin.MagicLog("SummonProvenanceAckUnknown reason=MalformedPayload");
			}
		}

		private string ProcessAttribution(AttributionMessage message, long sender)
		{
			string text = _attributionAcceptor.Accept(message, sender);
			if (text == "Duplicate")
			{
				if (message.Kind == AttributionMessageKind.Summon)
				{
					Plugin.MagicLog("SummonProvenanceDuplicate summon=" + message.SubjectZdoId + " event=" + message.Id);
				}
				return text;
			}
			if (text != "Accepted")
			{
				string text2 = ((!(text == "InvalidSenderOrEvent")) ? ((text == "InvalidPayload") ? "MalformedPayload" : text) : ((message != null && message.Id.IsValid && message.Id.SourcePeerId != sender) ? "UnexpectedSender" : "InvalidEventId"));
				Plugin.MagicLog("SummonProvenanceRejected reason=" + text2);
				return text;
			}
			if (message.Kind == AttributionMessageKind.Summon)
			{
				if (!_attribution.Summons.Register(message.SubjectZdoId, message.PlayerId.Value))
				{
					Plugin.MagicLog("SummonProvenanceRejected reason=ConflictingMapping");
					return "ConflictingMapping";
				}
				Plugin.MagicLog("SummonProvenanceAccepted summon=" + message.SubjectZdoId + " player=" + message.PlayerId.Value);
				for (int num = _pendingSummon.Count - 1; num >= 0; num--)
				{
					if (_pendingSummon[num].Commit.Facts.AttackerZdoId == message.SubjectZdoId)
					{
						(DamageCommit, double, double) tuple = _pendingSummon[num];
						_pendingSummon.RemoveAt(num);
						ProcessAttributed(tuple.Item1, tuple.Item2);
					}
				}
			}
			else
			{
				_attribution.ApplyPoolUpdate(message.SubjectZdoId, message.DotKind, message.PoolBefore, message.PoolAfter, message.SourceClass, message.SourcePlayerId, message.SourceZdoId, message.PoolUpdateAccepted);
				Plugin.MagicLog(((message.DotKind == DotKind.Poison) ? "PoisonOwnerChanged" : "DotContributionAdded") + " victim=" + message.SubjectZdoId + " kind=" + message.DotKind.ToString() + " delta=" + (message.PoolAfter - message.PoolBefore));
				for (int num2 = _pendingDot.Count - 1; num2 >= 0; num2--)
				{
					if (_pendingDot[num2].Commit.Facts.VictimZdoId == message.SubjectZdoId && (uint)_pendingDot[num2].Commit.Facts.DotKind == (uint)(message.DotKind + 1))
					{
						(DamageCommit, double, double) tuple2 = _pendingDot[num2];
						_pendingDot.RemoveAt(num2);
						ProcessAttributed(tuple2.Item1, tuple2.Item2);
					}
				}
			}
			return "Accepted";
		}

		private void AcceptForAttribution(DamageCommit commit)
		{
			CombatMeterPerformance performance = GetPerformance();
			if (performance != null)
			{
				performance.DamageAccepted++;
			}
			double num = _now();
			DamageFacts facts = commit.Facts;
			if (facts.Attacker == AttackerClass.NPC && !string.IsNullOrEmpty(facts.AttackerZdoId) && !_attribution.Summons.TryResolve(facts.AttackerZdoId, out var _) && _pendingSummon.Count < 256)
			{
				_pendingSummon.Add((commit, num, num + 3.0));
				return;
			}
			if (facts.DotKind > 0)
			{
				DotKind kind = (DotKind)(facts.DotKind - 1);
				if (!_attribution.HasDotState(facts.VictimZdoId, kind) && _pendingDot.Count < 256)
				{
					_pendingDot.Add((commit, num, num + 3.0));
					return;
				}
			}
			ProcessAttributed(commit, num);
		}

		private void ProcessAttributed(DamageCommit commit, double now)
		{
			CombatMeterPerformance performance = GetPerformance();
			long num = ((performance != null) ? PerformanceTimestamp() : 0);
			try
			{
				AttributedDamageEvent attributedDamageEvent = _attribution.Resolve(commit);
				bool flag = false;
				float num2 = 0f;
				foreach (DamagePortion item in attributedDamageEvent.DamageDone)
				{
					if (item.PlayerId.HasValue)
					{
						flag = true;
					}
					num2 += item.Damage;
				}
				Plugin.MagicLog((flag ? "MagicDamageAttributed" : "MagicDamageUnattributed") + " event=" + commit.Id.ToString() + " portions=" + attributedDamageEvent.DamageDone.Count + " sum=" + num2.ToString(CultureInfo.InvariantCulture));
				if (commit.Facts.DotKind > 0)
				{
					Plugin.MagicLog("DotTickDistributed event=" + commit.Id.ToString() + " kind=" + ((DotKind)(commit.Facts.DotKind - 1)/*cast due to .constrained prefix*/).ToString() + " effective=" + commit.EffectiveHpLoss.ToString(CultureInfo.InvariantCulture) + " portions=" + attributedDamageEvent.DamageDone.Count);
				}
				double value = EventTime(commit);
				_encounter.Accept(attributedDamageEvent, now, value);
				if (_clusters.Accept(attributedDamageEvent, now, value) == null && IsCurrentPerformance(performance))
				{
					performance.DamageIgnored++;
				}
			}
			finally
			{
				if (IsCurrentPerformance(performance))
				{
					performance.CommitProcessing.Record(PerformanceTimestamp() - num);
				}
			}
		}

		private double EventTime(DamageCommit commit)
		{
			double val = _clockAnchorMonotonic + (double)(commit.TimestampUtcTicks - _clockAnchorUtcTicks) / 10000000.0;
			return Math.Max(0.0, val);
		}

		private void Receive(ZRoutedRpc rpc, long sender, ZPackage package)
		{
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Expected O, but got Unknown
			if (_disposed || rpc != _rpc || !EnsureSession() || !_session.IsHost)
			{
				return;
			}
			try
			{
				DamageCommit damageCommit = CommitCodec.Decode(Bytes(package));
				CombatMeterPerformance performance = GetPerformance();
				if (performance != null)
				{
					performance.DamageObserved++;
				}
				Acceptance result = _session.ProcessDamageCommit(damageCommit, sender, CommitOrigin.Remote);
				if (sender != 0L && sender != _session.Peer)
				{
					rpc.InvokeRoutedRPC(sender, "CombatMeter.DamageCommitAck.v1", new object[1] { (object)new ZPackage(CommitCodec.EncodeAck(damageCommit.Id, result)) });
				}
			}
			catch (Exception ex)
			{
				Plugin.TransportLog("DamageCommitRejected " + new LogFields().Text("Reason", "MalformedOrHandlerFailure").Text("Exception", ex.GetType().Name));
			}
		}

		private void ReceiveAck(ZRoutedRpc rpc, long sender, ZPackage package)
		{
			if (_disposed || rpc != _rpc || !EnsureSession() || _session.IsHost)
			{
				return;
			}
			ZNetPeer serverPeer = _net.GetServerPeer();
			if (serverPeer == null || sender != serverPeer.m_uid || sender == 0L)
			{
				return;
			}
			try
			{
				(EventId, Acceptance) tuple = CommitCodec.DecodeAck(Bytes(package));
				_session.Acknowledge(tuple.Item1, tuple.Item2);
			}
			catch (Exception ex)
			{
				Plugin.TransportLog("DamageCommitAckRejected " + new LogFields().Text("Exception", ex.GetType().Name));
			}
		}

		private static byte[] Bytes(ZPackage package)
		{
			if (package == null || package.Size() > 1024)
			{
				throw new InvalidDataException("PacketSize");
			}
			return package.GetArray();
		}

		internal void ObserveIdentityPlayer(Player player)
		{
			_identities?.Observe(player);
		}

		internal void ForgetIdentityPlayer(Player player)
		{
			_identities?.Forget(player);
		}

		internal void Disconnected(ZNet net)
		{
			if (net == _net)
			{
				CommitSession session = _session;
				if (session != null && session.IsHost)
				{
					_identities.Sync(net.GetPeers(), _session.Peer, HostPlayerIdentity.ResolveLocal(_session.Peer));
				}
			}
			if (net == _net && !net.IsServer() && net.GetServerPeer() == null)
			{
				Close("Disconnected");
			}
		}

		internal void Stop(ZNet net)
		{
			if (net == _net)
			{
				_blockedNet = new WeakReference<ZNet>(net);
				Close("WorldStopped");
				_net = null;
				_rpc = null;
			}
		}

		internal void Dispose()
		{
			_disposed = true;
			Close("PluginStopped");
			_net = null;
			_rpc = null;
		}

		private void Close(string reason)
		{
			ReportPerformance(force: true);
			_session?.Close(reason);
			_encounter?.Reset();
			_clusters?.Reset();
			_attribution?.Reset();
			_attributionAcceptor.Reset();
			_attributionOutbox.Reset();
			_pendingSummon.Clear();
			_pendingDot.Clear();
			_snapshotStore.Clear();
			_snapshotEpoch = Guid.Empty;
			_snapshotSequence = 0L;
			_nextSnapshotAt = 0.0;
			_lastLocalSnapshotDiagnosticKey = null;
			_lastPublishedSnapshotDiagnosticKey = null;
			_lastAcceptedSnapshotDiagnosticKey = null;
			_nextSnapshotReceivedDiagnosticAt = 0.0;
			_session = null;
			_encounter = null;
			_clusters = null;
			_identities = null;
			_performance = null;
			_encounterSettings = null;
			_attribution = null;
			_attributionSequence = null;
		}

		private bool IsBlocked(ZNet net)
		{
			if (_blockedNet != null && _blockedNet.TryGetTarget(out var target))
			{
				return net == target;
			}
			return false;
		}

		private static void Log(string kind, DamageCommit c, string detail)
		{
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			LogFields logFields = new LogFields().Text("LoggedUtc", DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture));
			if (c != null)
			{
				logFields.Text("EventId", c.Id.ToString()).Text("SourcePeerId", c.SourcePeerId.ToString(CultureInfo.InvariantCulture)).Text("VictimZDOID", c.Facts.VictimZdoId)
					.Text("VictimName", c.Facts.VictimName)
					.Flag("VictimIsPlayer", c.Facts.VictimIsPlayer)
					.Text("VictimPlayerID", c.Facts.VictimPlayerId?.ToString(CultureInfo.InvariantCulture))
					.Text("AttackerClass", c.Facts.Attacker.ToString())
					.Text("AttackerPlayerID", c.Facts.AttackerPlayerId?.ToString(CultureInfo.InvariantCulture))
					.Text("AttackerName", c.Facts.AttackerName)
					.Text("HitType", ((object)(HitType)c.Facts.HitType/*cast due to .constrained prefix*/).ToString())
					.Number("EffectiveHpLoss", c.EffectiveHpLoss)
					.Text("TimestampUtcTicks", c.TimestampUtcTicks.ToString(CultureInfo.InvariantCulture));
			}
			logFields.Text((kind == "DamageCommitCreated") ? "Route" : ((kind == "DamageCommitAccepted") ? "Origin" : "Reason"), detail);
			string message = kind + " " + logFields;
			switch (kind)
			{
			case "DamageCommitAbandoned":
			case "DamageCommitDeliveryFailed":
			case "DamageCommitObservationRejected":
				Plugin.Warn(message);
				break;
			default:
				Plugin.TransportLog(message);
				break;
			}
		}
	}
	internal sealed class LogFields
	{
		private readonly StringBuilder _text = new StringBuilder("{");

		private bool _hasFields;

		internal LogFields Text(string key, string value)
		{
			Key(key);
			if (value == null)
			{
				_text.Append("null");
			}
			else
			{
				Quote(value);
			}
			return this;
		}

		internal LogFields Number(string key, float value)
		{
			Key(key);
			if (float.IsNaN(value) || float.IsInfinity(value))
			{
				Quote(value.ToString(CultureInfo.InvariantCulture));
			}
			else
			{
				_text.Append(value.ToString("R", CultureInfo.InvariantCulture));
			}
			return this;
		}

		internal LogFields Flag(string key, bool value)
		{
			Key(key);
			_text.Append(value ? "true" : "false");
			return this;
		}

		public override string ToString()
		{
			return _text.ToString() + "}";
		}

		private void Key(string key)
		{
			if (_hasFields)
			{
				_text.Append(',');
			}
			_hasFields = true;
			Quote(key);
			_text.Append(':');
		}

		private void Quote(string value)
		{
			_text.Append('"');
			foreach (char c in value)
			{
				if (c == '"' || c == '\\')
				{
					_text.Append('\\').Append(c);
				}
				else if (char.IsControl(c))
				{
					StringBuilder stringBuilder = _text.Append("\\u");
					int num = c;
					stringBuilder.Append(num.ToString("x4", CultureInfo.InvariantCulture));
				}
				else
				{
					_text.Append(c);
				}
			}
			_text.Append('"');
		}
	}
	internal static class MagicAttributionProbe
	{
		internal sealed class RpcContext
		{
			internal readonly Character Attacker;

			internal RpcContext(Character attacker)
			{
				Attacker = attacker;
			}
		}

		internal sealed class DotState
		{
			internal readonly string Type;

			internal readonly float Damage;

			internal readonly StatusEffect Before;

			internal readonly float PoolBefore;

			internal readonly RpcContext Context;

			internal DotState(string type, float damage, StatusEffect before, float poolBefore, RpcContext context)
			{
				Type = type;
				Damage = damage;
				Before = before;
				PoolBefore = poolBefore;
				Context = context;
			}
		}

		private sealed class PendingSummon
		{
			internal readonly GameObject Summon;

			internal readonly SpawnAbility Ability;

			internal readonly Character Caster;

			internal readonly long PlayerId;

			internal readonly float Expires;

			internal PendingSummon(GameObject summon, SpawnAbility ability, Character caster, long playerId)
			{
				Summon = summon;
				Ability = ability;
				Caster = caster;
				PlayerId = playerId;
				Expires = Time.realtimeSinceStartup + 10f;
			}
		}

		[ThreadStatic]
		private static Stack<RpcContext> _rpcStack;

		private static readonly FieldInfo FireLeft = typeof(SE_Burning).GetField("m_fireDamageLeft", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly FieldInfo SpiritLeft = typeof(SE_Burning).GetField("m_spiritDamageLeft", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly FieldInfo PoisonLeft = typeof(SE_Poison).GetField("m_damageLeft", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly FieldInfo SpawnOwner = typeof(SpawnAbility).GetField("m_owner", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly List<PendingSummon> PendingSummons = new List<PendingSummon>();

		internal static RpcContext BeginRpc(HitData hit)
		{
			RpcContext rpcContext = new RpcContext((hit != null) ? hit.GetAttacker() : null);
			(_rpcStack ?? (_rpcStack = new Stack<RpcContext>())).Push(rpcContext);
			return rpcContext;
		}

		internal static void EndRpc(RpcContext context)
		{
			if (_rpcStack != null && _rpcStack.Count != 0)
			{
				if (_rpcStack.Peek() == context)
				{
					_rpcStack.Pop();
				}
				else
				{
					_rpcStack.Clear();
				}
			}
		}

		internal static DotState BeginDot(Character victim, string type, float damage)
		{
			if (damage <= 0f)
			{
				return null;
			}
			StatusEffect effect = GetEffect(victim, type);
			RpcContext context = ((_rpcStack != null && _rpcStack.Count > 0) ? _rpcStack.Peek() : null);
			return new DotState(type, damage, effect, Pool(effect, type), context);
		}

		internal static void EndDot(Character victim, DotState state)
		{
			//IL_00ac: 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_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01de: 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_0129: Unknown result type (might be due to invalid IL or missing references)
			if (state == null)
			{
				return;
			}
			try
			{
				StatusEffect effect = GetEffect(victim, state.Type);
				if (!Object.op_Implicit((Object)(object)effect))
				{
					return;
				}
				float num = Pool(effect, state.Type);
				bool updateAccepted = ((state.Type == "Poisoned") ? (state.Damage >= state.PoolBefore) : (num > state.PoolBefore));
				Character val = state.Context?.Attacker;
				Player val2 = (Player)(object)((val is Player) ? val : null);
				ZNetView component = ((Component)victim).GetComponent<ZNetView>();
				ZDO val3 = ((Object.op_Implicit((Object)(object)component) && component.IsValid()) ? component.GetZDO() : null);
				ZDOID source = (Object.op_Implicit((Object)(object)val) ? val.GetZDOID() : ZDOID.None);
				if (val3 != null)
				{
					Plugin.PublishDotPool(val3.m_uid, (!(state.Type == "Poisoned")) ? ((!(state.Type == "Spirit")) ? DotKind.Burning : DotKind.Spirit) : DotKind.Poison, state.PoolBefore, num, Object.op_Implicit((Object)(object)val2) ? AttackerClass.Player : (Object.op_Implicit((Object)(object)val) ? AttackerClass.NPC : AttackerClass.None), Object.op_Implicit((Object)(object)val2) ? new long?(val2.GetPlayerID()) : ((long?)null), source, updateAccepted);
				}
				if (Plugin.MagicLoggingEnabled)
				{
					string text = (Object.op_Implicit((Object)(object)state.Before) ? "MagicDotUpdated" : "MagicDotApplied");
					LogFields logFields = CharacterFields(victim).Text("StatusEffectType", state.Type).Number("DamageAmount", state.Damage).Text("EffectInstanceIdentity", ((Object)effect).GetInstanceID().ToString(CultureInfo.InvariantCulture))
						.Number("DamagePoolBefore", state.PoolBefore)
						.Number("DamagePoolAfter", num)
						.Flag("DamagePoolChanged", num != state.PoolBefore)
						.Text("SourceAttackerZDOID", Object.op_Implicit((Object)(object)val) ? ((object)val.GetZDOID()/*cast due to .constrained prefix*/).ToString() : null)
						.Text("SourcePlayerID", Object.op_Implicit((Object)(object)val2) ? Nonzero(val2.GetPlayerID()) : null)
						.Text("SourcePlayerName", Object.op_Implicit((Object)(object)val2) ? val2.GetPlayerName() : null)
						.Flag("SameEffectInstance", Object.op_Implicit((Object)(object)state.Before) && state.Before == effect);
					ManualLogSource probeLog = Plugin.ProbeLog;
					if (probeLog != null)
					{
						probeLog.LogInfo((object)(text + " " + logFields));
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Warn("MagicAttributionProbeFailed phase=DotApply exception=" + ex.GetType().Name);
			}
		}

		internal static void ObserveDotTick(Character victim, HitData hit)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Invalid comparison between Unknown and I4
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Invalid comparison between Unknown and I4
			if (!Plugin.MagicLoggingEnabled || !Object.op_Implicit((Object)(object)victim) || hit == null)
			{
				return;
			}
			string text = (((int)hit.m_hitType != 5) ? (((int)hit.m_hitType == 7) ? "Poisoned" : null) : ((hit.m_damage.m_spirit > 0f && hit.m_damage.m_fire <= 0f) ? "Spirit" : "Burning"));
			if (text == null)
			{
				return;
			}
			try
			{
				StatusEffect effect = GetEffect(victim, text);
				ManualLogSource probeLog = Plugin.ProbeLog;
				if (probeLog != null)
				{
					probeLog.LogInfo((object)("MagicDotTickObserved " + CharacterFields(victim).Text("StatusEffectType", text).Number("DamageAmount", hit.GetTotalDamage()).Text("EffectInstanceIdentity", Object.op_Implicit((Object)(object)effect) ? ((Object)effect).GetInstanceID().ToString(CultureInfo.InvariantCulture) : null)
						.Text("SourceAttackerZDOID", ((ZDOID)(ref hit.m_attacker)).IsNone() ? null : ((object)Unsafe.As<ZDOID, ZDOID>(ref hit.m_attacker)/*cast due to .constrained prefix*/).ToString())
						.Text("SourcePlayerID", null)
						.Text("SourcePlayerName", null)));
				}
			}
			catch (Exception ex)
			{
				Plugin.Warn("MagicAttributionProbeFailed phase=DotTick exception=" + ex.GetType().Name);
			}
		}

		internal static void ObserveSpawnAbility(SpawnAbility ability, Character owner)
		{
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.MagicLoggingEnabled)
			{
				return;
			}
			Plugin.MagicLog("MagicSummonRuntimeSetupEntered hook=SpawnAbility.Setup ability=" + (Object.op_Implicit((Object)(object)ability) ? ((Object)ability).name : "null"));
			if (!Object.op_Implicit((Object)(object)ability) || !Object.op_Implicit((Object)(object)owner))
			{
				return;
			}
			try
			{
				Player val = (Player)(object)((owner is Player) ? owner : null);
				string text = "";
				if (ability.m_spawnPrefab != null)
				{
					for (int i = 0; i < ability.m_spawnPrefab.Length; i++)
					{
						text = text + ((i == 0) ? "" : ",") + (Object.op_Implicit((Object)(object)ability.m_spawnPrefab[i]) ? ((Object)ability.m_spawnPrefab[i]).name : "null");
					}
				}
				if (IsRootText(((Object)ability).name + " " + text))
				{
					ManualLogSource probeLog = Plugin.ProbeLog;
					if (probeLog != null)
					{
						probeLog.LogInfo((object)("MagicSummonCastObserved " + new LogFields().Text("Timestamp", Timestamp()).Text("AbilityObjectName", ((Object)ability).name).Text("ConfiguredSpawnPrefabs", text)
							.Text("CasterZDOID", ((object)owner.GetZDOID()/*cast due to .constrained prefix*/).ToString())
							.Text("CasterPlayerID", Object.op_Implicit((Object)(object)val) ? Nonzero(val.GetPlayerID()) : null)
							.Text("CasterPlayerName", Object.op_Implicit((Object)(object)val) ? val.GetPlayerName() : null)));
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Warn("MagicAttributionProbeFailed phase=SummonCast exception=" + ex.GetType().Name);
			}
		}

		internal static void ObserveSpawnCreated(GameObject summon, SpawnAbility ability)
		{
			if (Plugin.MagicLoggingEnabled)
			{
				Plugin.MagicLog("MagicSummonRuntimeSpawnEntered hook=SpawnAbility.Spawn.MoveNext ability=" + (Object.op_Implicit((Object)(object)ability) ? ((Object)ability).name : "null") + " spawned=" + (Object.op_Implicit((Object)(object)summon) ? ((Object)summon).name : "null"));
			}
			try
			{
				object? obj = SpawnOwner?.GetValue(ability);
				Character val = (Character)((obj is Character) ? obj : null);
				if (Plugin.MagicLoggingEnabled)
				{
					Plugin.MagicLog("MagicSummonRuntimeSpawnCreated " + RuntimeFields("SpawnAbility.Spawn.MoveNext", ability, summon, val));
				}
				if (!Object.op_Implicit((Object)(object)summon) || !SupportedSummonPolicy.IsSupportedPrefab(((Object)summon).name))
				{
					Plugin.MagicLog("SummonProvenanceRegistrationFailed reason=SpawnedObjectUnsupported spawned=" + (Object.op_Implicit((Object)(object)summon) ? ((Object)summon).name : "null"));
					return;
				}
				Plugin.MagicLog("SummonProvenanceRegistrationAttempt hook=SpawnAbility.Spawn.MoveNext summon=" + ((Object)summon).name);
				if (!Object.op_Implicit((Object)(object)val))
				{
					Plugin.MagicLog("SummonProvenanceRegistrationFailed reason=CasterUnavailable");
					return;
				}
				Player val2 = (Player)(object)((val is Player) ? val : null);
				if (!Object.op_Implicit((Object)(object)val2))
				{
					Plugin.MagicLog("SummonProvenanceRegistrationFailed reason=CasterNotPlayer");
					return;
				}
				long playerID = val2.GetPlayerID();
				if (playerID == 0L)
				{
					Plugin.MagicLog("SummonProvenanceRegistrationFailed reason=PlayerIdZero");
					return;
				}
				ZNetView component = summon.GetComponent<ZNetView>();
				if (Object.op_Implicit((Object)(object)component) && component.IsValid() && component.GetZDO() != null)
				{
					Plugin.MagicLog("MagicSummonZdoReadyObserved " + RuntimeFields("SpawnAbility.Spawn.MoveNext", ability, summon, val));
					RegisterSummon(component.GetZDO(), playerID, summon);
					return;
				}
				Plugin.MagicLog("SummonProvenanceRegistrationFailed reason=SummonZdoUnavailable delayed=true summonInstance=" + ((Object)summon).GetInstanceID());
				if (PendingSummons.Count >= 64)
				{
					PendingSummons.RemoveAt(0);
				}
				PendingSummons.Add(new PendingSummon(summon, ability, val, playerID));
			}
			catch (Exception ex)
			{
				Plugin.Warn("MagicAttributionProbeFailed phase=RuntimeSpawn exception=" + ex.GetType().Name);
			}
		}

		internal static void UpdatePendingRoots()
		{
			for (int num = PendingSummons.Count - 1; num >= 0; num--)
			{
				PendingSummon pendingSummon = PendingSummons[num];
				if (!Object.op_Implicit((Object)(object)pendingSummon.Summon) || Time.realtimeSinceStartup >= pendingSummon.Expires)
				{
					Plugin.MagicLog("SummonProvenanceRegistrationFailed reason=SummonZdoUnavailable expired=true summonInstance=" + (Object.op_Implicit((Object)(object)pendingSummon.Summon) ? ((Object)pendingSummon.Summon).GetInstanceID().ToString(CultureInfo.InvariantCulture) : "null"));
					PendingSummons.RemoveAt(num);
				}
				else
				{
					ZNetView component = pendingSummon.Summon.GetComponent<ZNetView>();
					if (Object.op_Implicit((Object)(object)component) && component.IsValid() && component.GetZDO() != null)
					{
						Plugin.MagicLog("MagicSummonZdoReadyObserved " + RuntimeFields("Plugin.Update", pendingSummon.Ability, pendingSummon.Summon, pendingSummon.Caster));
						RegisterSummon(component.GetZDO(), pendingSummon.PlayerId, pendingSummon.Summon);
						PendingSummons.RemoveAt(num);
					}
				}
			}
		}

		internal static void ResetRuntimeDiscovery()
		{
			PendingSummons.Clear();
		}

		private unsafe static void RegisterSummon(ZDO zdo, long playerId, GameObject summon)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			if (zdo == null || ((ZDOID)(ref zdo.m_uid)).IsNone())
			{
				Plugin.MagicLog("SummonProvenanceRegistrationFailed reason=SummonZdoUnavailable");
			}
			else if (Plugin.Transport == null)
			{
				Plugin.MagicLog("SummonProvenanceRegistrationFailed reason=TransportUnavailable");
			}
			else if (Plugin.PublishSummonProvenance(zdo.m_uid, playerId))
			{
				string[] obj = new string[6] { "SummonProvenanceRegistered summon=", null, null, null, null, null };
				ZDOID uid = zdo.m_uid;
				obj[1] = ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString();
				obj[2] = " player=";
				obj[3] = playerId.ToString();
				obj[4] = " prefab=";
				obj[5] = (Object.op_Implicit((Object)(object)summon) ? ((Object)summon).name : "null");
				Plugin.MagicLog(string.Concat(obj));
			}
			else
			{
				ZDOID uid = zdo.m_uid;
				Plugin.MagicLog("SummonProvenanceRegistrationFailed reason=TransportUnavailable summon=" + ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString());
			}
		}

		internal unsafe static void ObserveSummonCommand(Tameable tameable, Humanoid user)
		{
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.MagicLoggingEnabled)
			{
				Plugin.MagicLog("MagicSummonCommandEntered hook=Tameable.Command root=" + (Object.op_Implicit((Object)(object)tameable) ? ((Object)tameable).name : "null"));
			}
			if (!Object.op_Implicit((Object)(object)tameable))
			{
				return;
			}
			try
			{
				Character component = ((Component)tameable).GetComponent<Character>();
				if (!IsSummonedRoot(component))
				{
					return;
				}
				Plugin.MagicLog("MagicSummonCommandObserved " + RuntimeFields("Tameable.Command", null, ((Component)tameable).gameObject, (Character)(object)user));
				Player val = (Player)(object)((user is Player) ? user : null);
				ZNetView component2 = ((Component)tameable).GetComponent<ZNetView>();
				ZDO val2 = ((Object.op_Implicit((Object)(object)component2) && component2.IsValid()) ? component2.GetZDO() : null);
				if (val2 == null || ((ZDOID)(ref val2.m_uid)).IsNone() || !Object.op_Implicit((Object)(object)user))
				{
					return;
				}
				ZDOID zDOID = ((Character)user).GetZDOID();
				if (!((ZDOID)(ref zDOID)).IsNone() && Object.op_Implicit((Object)(object)val) && val.GetPlayerID() != 0L && Plugin.MagicLoggingEnabled)
				{
					ManualLogSource probeLog = Plugin.ProbeLog;
					if (probeLog != null)
					{
						probeLog.LogInfo((object)("MagicSummonSpawnObserved " + SummonFields(component).Text("CommandCasterZDOID", ((object)(*(ZDOID*)(&zDOID))/*cast due to .constrained prefix*/).ToString()).Text("ResolvedCasterPlayerID", Object.op_Implicit((Object)(object)val) ? Nonzero(val.GetPlayerID()) : null).Text("ResolvedCasterPlayerName", Object.op_Implicit((Object)(object)val) ? val.GetPlayerName() : null)
							.Text("StoredFollowName", (val2 == null) ? null : val2.GetString(ZDOVars.s_follow, ""))));
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Warn("MagicAttributionProbeFailed phase=SummonSpawn exception=" + ex.GetType().Name);
			}
		}

		internal unsafe static void ObserveSummonRpcCommand(Tameable tameable, long sender, ZDOID casterZdoId, bool message)
		{
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.MagicLoggingEnabled)
			{
				Plugin.MagicLog("MagicSummonRpcCommandEntered hook=Tameable.RPC_Command root=" + (Object.op_Implicit((Object)(object)tameable) ? ((Object)tameable).name : "null"));
				if (Object.op_Implicit((Object)(object)tameable) && IsSummonedRoot(((Component)tameable).GetComponent<Character>()))
				{
					Plugin.MagicLog("MagicSummonRpcCommandObserved " + RuntimeFields("Tameable.RPC_Command", null, ((Component)tameable).gameObject, ResolveCharacter(casterZdoId)).Text("RpcSender", sender.ToString(CultureInfo.InvariantCulture)).Text("CommandCasterZDOID", ((object)(*(ZDOID*)(&casterZdoId))/*cast due to .constrained prefix*/).ToString()).Flag("Message", message));
				}
			}
		}

		internal static void ObserveFollowTarget(MonsterAI ai, GameObject target)
		{
			if (Plugin.MagicLoggingEnabled)
			{
				Plugin.MagicLog("MagicSummonFollowTargetEntered hook=MonsterAI.SetFollowTarget ai=" + (Object.op_Implicit((Object)(object)ai) ? ((Object)ai).name : "null"));
				if (Object.op_Implicit((Object)(object)ai) && IsSummonedRoot(((Component)ai).GetComponent<Character>()))
				{
					Plugin.MagicLog("MagicSummonFollowTargetObserved " + RuntimeFields("MonsterAI.SetFollowTarget", null, ((Component)ai).gameObject, Object.op_Implicit((Object)(object)target) ? target.GetComponent<Character>() : null));
				}
			}
		}

		internal static void ObserveSummonHit(Character attacker)
		{
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.MagicLoggingEnabled || !IsSummonedRoot(attacker))
			{
				return;
			}
			try
			{
				MonsterAI component = ((Component)attacker).GetComponent<MonsterAI>();
				GameObject val = (Object.op_Implicit((Object)(object)component) ? component.GetFollowTarget() : null);
				Player val2 = (Object.op_Implicit((Object)(object)val) ? val.GetComponent<Player>() : null);
				ZNetView component2 = ((Component)attacker).GetComponent<ZNetView>();
				ZDO val3 = ((Object.op_Implicit((Object)(object)component2) && component2.IsValid()) ? component2.GetZDO() : null);
				ManualLogSource probeLog = Plugin.ProbeLog;
				if (probeLog != null)
				{
					probeLog.LogInfo((object)("MagicSummonObserved " + SummonFields(attacker).Text("RuntimeFollowTargetZDOID", Object.op_Implicit((Object)(object)val2) ? ((object)((Character)val2).GetZDOID()/*cast due to .constrained prefix*/).ToString() : null).Text("RuntimeFollowPlayerID", Object.op_Implicit((Object)(object)val2) ? Nonzero(val2.GetPlayerID()) : null).Text("RuntimeFollowPlayerName", Object.op_Implicit((Object)(object)val2) ? val2.GetPlayerName() : null)
						.Text("StoredFollowName", (val3 == null) ? null : val3.GetString(ZDOVars.s_follow, ""))));
				}
			}
			catch (Exception ex)
			{
				Plugin.Warn("MagicAttributionProbeFailed phase=SummonHit exception=" + ex.GetType().Name);
			}
		}

		private static StatusEffect GetEffect(Character victim, string type)
		{
			if (!Object.op_Implicit((Object)(object)victim))
			{
				return null;
			}
			int num = ((type == "Poisoned") ? SEMan.s_statusEffectPoison : ((type == "Spirit") ? SEMan.s_statusEffectSpirit : SEMan.s_statusEffectBurning));
			return victim.GetSEMan().GetStatusEffect(num);
		}

		private static float Pool(StatusEffect effect, string type)
		{
			if (!Object.op_Implicit((Object)(object)effect))
			{
				return 0f;
			}
			FieldInfo fieldInfo = ((type == "Poisoned") ? PoisonLeft : ((type == "Spirit") ? SpiritLeft : FireLeft));
			if (!(fieldInfo == null))
			{
				return (float)fieldInfo.GetValue(effect);
			}
			return 0f;
		}

		private static LogFields CharacterFields(Character character)
		{
			ZNetView val = (Object.op_Implicit((Object)(object)character) ? ((Component)character).GetComponent<ZNetView>() : null);
			ZDO obj = ((Object.op_Implicit((Object)(object)val) && val.IsValid()) ? val.GetZDO() : null);
			return new LogFields().Text("Timestamp", Timestamp()).Text("VictimZDOID", ((object)Unsafe.As<ZDOID, ZDOID>(ref obj?.m_uid)/*cast due to .constrained prefix*/).ToString()).Text("VictimName", Object.op_Implicit((Object)(object)character) ? character.m_name : null);
		}

		private static LogFields SummonFields(Character summon)
		{
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			ZNetView val = (Object.op_Implicit((Object)(object)summon) ? ((Component)summon).GetComponent<ZNetView>() : null);
			ZDO val2 = ((Object.op_Implicit((Object)(object)val) && val.IsValid()) ? val.GetZDO() : null);
			return new LogFields().Text("Timestamp", Timestamp()).Text("SummonZDOID", ((object)Unsafe.As<ZDOID, ZDOID>(ref val2?.m_uid)/*cast due to .constrained prefix*/).ToString()).Text("SummonObjectName", Object.op_Implicit((Object)(object)summon) ? ((Object)summon).name : null)
				.Text("SummonPrefabName", Object.op_Implicit((Object)(object)summon) ? Utils.GetPrefabName(((Component)summon).gameObject) : null)
				.Text("CurrentOwnerPeerId", (val2 == null) ? null : val2.GetOwner().ToString(CultureInfo.InvariantCulture))
				.Flag("IsOwner", Object.op_Implicit((Object)(object)val) && val.IsValid() && val.IsOwner())
				.Text("CharacterName", Object.op_Implicit((Object)(object)summon) ? summon.m_name : null)
				.Text("Faction", Object.op_Implicit((Object)(object)summon) ? ((object)summon.GetFaction()/*cast due to .constrained prefix*/).ToString() : null);
		}

		private static LogFields RuntimeFields(string hook, SpawnAbility ability, GameObject root, Character caster)
		{
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			ZNetView val = (Object.op_Implicit((Object)(object)root) ? root.GetComponent<ZNetView>() : null);
			ZDO val2 = ((Object.op_Implicit((Object)(object)val) && val.IsValid()) ? val.GetZDO() : null);
			Player val3 = (Player)(object)((caster is Player) ? caster : null);
			return new LogFields().Text("HookName", hook).Text("AbilityObjectName", Object.op_Implicit((Object)(object)ability) ? ((Object)ability).name : null).Text("SummonInstance", Object.op_Implicit((Object)(object)root) ? ((Object)root).GetInstanceID().ToString(CultureInfo.InvariantCulture) : null)
				.Text("SummonObjectName", Object.op_Implicit((Object)(object)root) ? ((Object)root).name : null)
				.Text("SummonZDOID", ((object)Unsafe.As<ZDOID, ZDOID>(ref val2?.m_uid)/*cast due to .constrained prefix*/).ToString())
				.Flag("SummonZNetViewPresent", Object.op_Implicit((Object)(object)val))
				.Flag("SummonZNetViewValid", Object.op_Implicit((Object)(object)val) && val.IsValid())
				.Flag("SummonZdoPresent", val2 != null)
				.Text("CasterObjectName", Object.op_Implicit((Object)(object)caster) ? ((Object)caster).name : null)
				.Text("CasterZDOID", Object.op_Implicit((Object)(object)caster) ? ((object)caster.GetZDOID()/*cast due to .constrained prefix*/).ToString() : null)
				.Text("CasterPlayerID", Object.op_Implicit((Object)(object)val3) ? Nonzero(val3.GetPlayerID()) : null)
				.Text("CasterPlayerName", Object.op_Implicit((Object)(object)val3) ? val3.GetPlayerName() : null)
				.Text("LocalPeerId", (ZDOMan.instance == null) ? null : ZDOMan.GetSessionID().ToString(CultureInfo.InvariantCulture))
				.Flag("IsHost", Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsServer())
				.Flag("IsSinglePlayer", Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.IsSinglePlayer)
				.Text("SummonCurrentOwnerPeerId", (val2 == null) ? null : val2.GetOwner().ToString(CultureInfo.InvariantCulture))
				.Flag("SummonIsOwner", Object.op_Implicit((Object)(object)val) && val.IsValid() && val.IsOwner());
		}

		private static Character ResolveCharacter(ZDOID id)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = ((!((ZDOID)(ref id)).IsNone() && Object.op_Implicit((Object)(object)ZNetScene.instance)) ? ZNetScene.instance.FindInstance(id) : null);
			if (!Object.op_Implicit((Object)(object)val))
			{
				return null;
			}
			return val.GetComponent<Character>();
		}

		private static bool IsSummonedRoot(Character character)
		{
			if (!Object.op_Implicit((Object)(object)character))
			{
				return false;
			}
			return IsRootText((character.m_name + " " + ((Object)character).name + " " + Utils.GetPrefabName(((Component)character).gameObject)).ToLowerInvariant());
		}

		private static bool IsRootText(string value)
		{
			value = (value ?? "").ToLowerInvariant();
			if (!value.Contains("summonedroot") && !value.Contains("greenroots"))
			{
				return value.Contains("tentaroot");
			}
			return true;
		}

		private static string Timestamp()
		{
			return DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture);
		}

		private static string Nonzero(long value)
		{
			if (value != 0L)
			{
				return value.ToString(CultureInfo.InvariantCulture);
			}
			return null;
		}
	}
	[HarmonyPatch(typeof(Character), "RPC_Damage")]
	internal static class MagicDamageRpcContextPatch
	{
		[HarmonyPrefix]
		private static void Prefix(HitData hit, out MagicAttributionProbe.RpcContext __state)
		{
			__state = MagicAttributionProbe.BeginRpc(hit);
		}

		[HarmonyFinalizer]
		private static Exception Finalizer(Exception __exception, MagicAttributionProbe.RpcContext __state)
		{
			MagicAttributionProbe.EndRpc(__state);
			return __exception;
		}
	}
	[HarmonyPatch(typeof(Character), "AddFireDamage")]
	internal static class MagicFireAppliedPatch
	{
		[HarmonyPrefix]
		private static void Prefix(Character __instance, float damage, out MagicAttributionProbe.DotState __state)
		{
			__state = MagicAttributionProbe.BeginDot(__instance, "Burning", damage);
		}

		[HarmonyPostfix]
		private static void Postfix(Character __instance, MagicAttributionProbe.DotState __state)
		{
			MagicAttributionProbe.EndDot(__instance, __state);
		}
	}
	[HarmonyPatch(typeof(Character), "AddSpiritDamage")]
	internal static class MagicSpiritAppliedPatch
	{
		[HarmonyPrefix]
		private static void Prefix(Character __instance, float damage, out MagicAttributionProbe.DotState __state)
		{
			__state = MagicAttributionProbe.BeginDot(__instance, "Spirit", damage);
		}

		[HarmonyPostfix]
		private static void Postfix(Character __instance, MagicAttributionProbe.DotState __state)
		{
			MagicAttributionProbe.EndDot(__instance, __state);
		}
	}
	[HarmonyPatch(typeof(Character), "AddPoisonDamage")]
	internal static class MagicPoisonAppliedPatch
	{
		[HarmonyPrefix]
		private static void Prefix(Character __instance, float damage, out MagicAttributionProbe.DotState __state)
		{
			__state = MagicAttributionProbe.BeginDot(__instance, "Poisoned", damage);
		}

		[HarmonyPostfix]
		private static void Postfix(Character __instance, MagicAttributionProbe.DotState __state)
		{
			MagicAttributionProbe.EndDot(__instance, __state);
		}
	}
	[HarmonyPatch(typeof(SpawnAbility), "Setup")]
	internal static class MagicSpawnAbilityPatch
	{
		[HarmonyPostfix]
		private static void Postfix(SpawnAbility __instance, Character owner)
		{
			MagicAttributionProbe.ObserveSpawnAbility(__instance, owner);
		}
	}
	[HarmonyPatch(typeof(Tameable), "Command", new Type[]
	{
		typeof(Humanoid),
		typeof(bool)
	})]
	internal static class MagicSummonCommandPatch
	{
		[HarmonyPrefix]
		private static void Prefix(Tameable __instance, Humanoid user)
		{
			MagicAttributionProbe.ObserveSummonCommand(__instance, user);
		}
	}
	[HarmonyPatch]
	internal static class MagicSummonSpawnCoroutinePatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.EnumeratorMoveNext((MethodBase)AccessTools.Method(typeof(SpawnAbility), "Spawn", (Type[])null, (Type[])null));
		}

		[HarmonyTranspiler]
		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod)
		{
			FieldInfo abilityField = null;
			FieldInfo[] fields = __originalMethod.DeclaringType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (FieldInfo fieldInfo in fields)
			{
				if (fieldInfo.FieldType == typeof(SpawnAbility))
				{
					abilityField = fieldInfo;
					break;
				}
			}
			MethodInfo observer = AccessTools.Method(typeof(MagicAttributionProbe), "ObserveSpawnCreated", (Type[])null, (Type[])null);
			bool injected = false;
			foreach (CodeInstruction instruction in instructions)
			{
				yield return instruction;
				if (!injected && abilityField != null && instruction.operand is MethodInfo { Name: "Instantiate" } methodInfo && methodInfo.DeclaringType == typeof(Object) && methodInfo.ReturnType == typeof(GameObject))
				{
					yield return new CodeInstruction(OpCodes.Dup, (object)null);
					yield return new CodeInstruction(OpCodes.Ldarg_0, (object)null);
					yield return new CodeInstruction(OpCodes.Ldfld, (object)abilityField);
					yield return new CodeInstruction(OpCodes.Call, (object)observer);
					injected = true;
				}
			}
			if (!injected)
			{
				Plugin.Warn("MagicSummonHookInstallFailed hook=SpawnAbility.Spawn.MoveNext reason=InstantiateCallNotFound");
			}
		}
	}
	[HarmonyPatch(typeof(Tameable), "RPC_Command", new Type[]
	{
		typeof(long),
		typeof(ZDOID),
		typeof(bool)
	})]
	internal static class MagicSummonRpcCommandPatch
	{
		[HarmonyPrefix]
		private static void Prefix(Tameable __instance, long sender, ZDOID characterID, bool message)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			MagicAttributionProbe.ObserveSummonRpcCommand(__instance, sender, characterID, message);
		}
	}
	[HarmonyPatch(typeof(MonsterAI), "SetFollowTarget", new Type[] { typeof(GameObject) })]
	internal static class MagicSummonFollowTargetPatch
	{
		[HarmonyPrefix]
		private static void Prefix(MonsterAI __instance, GameObject go)
		{
			MagicAttributionProbe.ObserveFollowTarget(__instance, go);
		}
	}
	[HarmonyPatch(typeof(ZNet), "Awake")]
	internal static class NetworkStartPatch
	{
		[HarmonyPostfix]
		private static void Postfix(ZNet __instance)
		{
			Plugin.SafeTransport(delegate
			{
				Plugin.Transport?.Bind(__instance);
			});
		}
	}
	[HarmonyPatch(typeof(ZNet), "StopAll")]
	internal static class NetworkStopPatch
	{
		[HarmonyPrefix]
		private static void Prefix(ZNet __instance)
		{
			Plugin.DestroyUi();
			MagicAttributionProbe.ResetRuntimeDiscovery();
			Plugin.SafeTransport(delegate
			{
				Plugin.Transport?.Stop(__instance);
			});
		}
	}
	[HarmonyPatch(typeof(ZNet), "OnDestroy")]
	internal static class NetworkDestroyPatch
	{
		[HarmonyPrefix]
		private static void Prefix(ZNet __instance)
		{
			Plugin.DestroyUi();
			MagicAttributionProbe.ResetRuntimeDiscovery();
			Plugin.SafeTransport(delegate
			{
				Plugin.Transport?.Stop(__instance);
			});
		}
	}
	[HarmonyPatch(typeof(ZNet), "Disconnect")]
	internal static class NetworkDisconnectPatch
	{
		[HarmonyPostfix]
		private static void Postfix(ZNet __instance)
		{
			Plugin.DestroyUi();
			MagicAttributionProbe.ResetRuntimeDiscovery();
			Plugin.SafeTransport(delegate
			{
				Plugin.Transport?.Disconnected(__instance);
			});
		}
	}
	internal static class PlayerLifecycleProbe
	{
		internal static void Observe(string marker, string hookName, Player player, long? rpcSender = null, bool? afterDeath = null)
		{
			if (!Plugin.LifecycleLoggingEnabled)
			{
				return;
			}
			try
			{
				ZNet instance = ZNet.instance;
				ZNetView val = (Object.op_Implicit((Object)(object)player) ? ((Component)player).GetComponent<ZNetView>() : null);
				ZDO val2 = ((Object.op_Implicit((Object)(object)val) && val.IsValid()) ? val.GetZDO() : null);
				long num = ((ZDOMan.instance == null) ? 0 : ZDOMan.GetSessionID());
				LogFields logFields = new LogFields().Text("Timestamp", DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)).Text("ProcessRunId", Plugin.ProcessRunId).Text("HookName", hookName)
					.Text("LocalPeerId", num.ToString(CultureInfo.InvariantCulture))
					.Flag("IsHost", Object.op_Implicit((Object)(object)instance) && instance.IsServer())
					.Text("PlayerID", Object.op_Implicit((Object)(object)player) ? Nonzero(player.GetPlayerID()) : null)
					.Text("PlayerName", Object.op_Implicit((Object)(object)player) ? player.GetPlayerName() : null)
					.Text("PlayerZDOID", ((object)Unsafe.As<ZDOID, ZDOID>(ref val2?.m_uid)/*cast due to .constrained prefix*/).ToString())
					.Text("CurrentOwnerPeerId", (val2 == null) ? null : val2.GetOwner().ToString(CultureInfo.InvariantCulture))
					.Flag("IsOwner", Object.op_Implicit((Object)(object)val) && val.IsValid() && val.IsOwner())
					.Flag("IsLocalPlayer", Object.op_Implicit((Object)(object)player) && Player.m_localPlayer == player)
					.Text("CharacterInstanceIdentity", Object.op_Implicit((Object)(object)player) ? ((Object)player).GetInstanceID().ToString(CultureInfo.InvariantCulture) : null)
					.Flag("IsDead", Object.op_Implicit((Object)(object)player) && ((Character)player).IsDead())
					.Number("Health", Object.op_Implicit((Object)(object)player) ? ((Character)player).GetHealth() : 0f)
					.Flag("ZNetViewPresent", Object.op_Implicit((Object)(object)val))
					.Flag("ZNetViewValid", Object.op_Implicit((Object)(object)val) && val.IsValid())
					.Flag("ZdoPresent", val2 != null);
				if (rpcSender.HasValue)
				{
					logFields.Text("RpcSender", rpcSender.Value.ToString(CultureInfo.InvariantCulture));
				}
				if (afterDeath.HasValue)
				{
					logFields.Flag("AfterDeath", afterDeath.Value);
				}
				ManualLogSource probeLog = Plugin.ProbeLog;
				if (probeLog != null)
				{
					probeLog.LogInfo((object)(marker + " " + logFields));
				}
			}
			catch (Exception ex)
			{
				Plugin.Warn("PlayerLifecycleProbeFailed hook=" + hookName + " exception=" + ex.GetType().Name);
			}
		}

		private static string Nonzero(long value)
		{
			if (value != 0L)
			{
				return value.ToString(CultureInfo.InvariantCulture);
			}
			return null;
		}

		internal static void CommitDeath(Player player)
		{
			try
			{
				long num = (Object.op_Implicit((Object)(object)player) ? player.GetPlayerID() : 0);
				ZNetView val = (Object.op_Implicit((Object)(object)player) ? ((Component)player).GetComponent<ZNetView>() : null);
				ZDO val2 = ((Object.op_Implicit((Object)(object)val) && val.IsValid()) ? val.GetZDO() : null);
				string incarnation = ((object)Unsafe.As<ZDOID, ZDOID>(ref val2?.m_uid)/*cast due to .constrained prefix*/).ToString();
				DamageCommitTransport.DeathObservation deathObservation = ((Plugin.Transport == null) ? new DamageCommitTransport.DeathObservation("NoActiveSession", EncounterState.NoEncounter, EncounterState.NoEncounter) : Plugin.Transport.ObservePlayerDeath(num, incarnation));
				if (Plugin.LifecycleLoggingEnabled)
				{
					ZNet instance = ZNet.instance;
					string text = ((deathObservation.Result == PlayerDeathResult.Committed.ToString()) ? "PlayerLifecycleDeathCommitted" : "PlayerLifecycleDeathIgnored");
					LogFields logFields = new LogFields().Text("Timestamp", DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)).Text("PlayerID", (num == 0L) ? null : num.ToString(CultureInfo.InvariantCulture)).Text("PlayerName", Object.op_Implicit((Object)(object)player) ? player.GetPlayerName() : null)
						.Text("PlayerZDOID", ((object)Unsafe.As<ZDOID, ZDOID>(ref val2?.m_uid)/*cast due to .constrained prefix*/).ToString())
						.Flag("IsLocalPlayer", Object.op_Implicit((Object)(object)player) && Player.m_localPlayer == player)
						.Flag("IsOwner", Object.op_Implicit((Object)(object)val) && val.IsValid() && val.IsOwner())
						.Flag("IsHost", Object.op_Implicit((Object)(object)instance) && instance.IsServer() && !instance.IsDedicated())
						.Text("EncounterStateBefore", deathObservation.StateBefore.ToString())
						.Text("EncounterStateAfter", deathObservation.StateAfter.ToString());
					if (text == "PlayerLifecycleDeathIgnored")
					{
						logFields.Text("Reason", deathObservation.Result);
					}
					ManualLogSource probeLog = Plugin.ProbeLog;
					if (probeLog != null)
					{
						probeLog.LogInfo((object)(text + " " + logFields));
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Warn("PlayerLifecycleProbeFailed hook=Player.RPC_OnDeath.Commit exception=" + ex.GetType().Name);
			}
		}
	}
	[HarmonyPatch(typeof(Player), "OnDeath")]
	internal static class PlayerOnDeathLifecyclePatch
	{
		[HarmonyPrefix]
		private static void Prefix(Player __instance)
		{
			PlayerLifecycleProbe.Observe("PlayerLifecycleDeathObserved", "Player.OnDeath.Prefix", __instance);
		}
	}
	[HarmonyPatch(typeof(Player), "RPC_OnDeath")]
	internal static class PlayerRpcOnDeathLifecyclePatch
	{
		[HarmonyPrefix]
		private static void Prefix(Player __instance, long sender)
		{
			PlayerLifecycleProbe.Observe("PlayerLifecycleDeathObserved", "Player.RPC_OnDeath.Prefix", __instance, sender);
			PlayerLifecycleProbe.CommitDeath(__instance);
		}
	}
	[HarmonyPatch(typeof(Game), "SpawnPlayer")]
	internal static class GameSpawnPlayerLifecyclePatch
	{
		[HarmonyPostfix]
		private static void Postfix(Player __result, bool ___m_respawnAfterDeath)
		{
			if (___m_respawnAfterDeath)
			{
				bool? afterDeath = true;
				PlayerLifecycleProbe.Observe("PlayerLifecycleRespawnObserved", "Game.SpawnPlayer.Postfix", __result, null, afterDeath);
			}
		}
	}
	[HarmonyPatch(typeof(Player), "Start")]
	internal static class PlayerStartLifecyclePatch
	{
		[HarmonyPostfix]
		private static void Postfix(Player __instance)
		{
			Plugin.Transport?.ObserveIdentityPlayer(__instance);
			PlayerLifecycleProbe.Observe("PlayerLifecycleInstanceObserved", "Player.Start.Postfix", __instance);
		}
	}
	[HarmonyPatch(typeof(Player), "OnDestroy")]
	internal static class PlayerDestroyLifecyclePatch
	{
		[HarmonyPrefix]
		private static void Prefix(Player __instance)
		{
			Plugin.Transport?.ForgetIdentityPlayer(__instance);
			PlayerLifecycleProbe.Observe("PlayerLifecycleInstanceObserved", "Player.OnDestroy.Prefix", __instance);
		}
	}
	[BepInPlugin("Likhtenvald.CombatMeter", "CombatMeter", "1.0.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginId = "Likhtenvald.CombatMeter";

		public const string LegacyPluginId = "local.valheim.diagnosticdamageprobe";

		public const string PluginVersion = "1.0.0";

		internal static ConfigEntry<bool> EnableDiagnosticLogging;

		internal static ConfigEntry<bool> EnableTransportDiagnosticLogging;

		internal static ConfigEntry<bool> EnableLifecycleDiagnosticLogging;

		internal static ConfigEntry<bool> EnableMagicAttributionDiagnosticLogging;

		internal static ConfigEntry<bool> EnablePerformanceDiagnosticLogging;

		internal static ConfigEntry<bool> UiEnabled;

		internal static ConfigEntry<KeyboardShortcut> UiToggleKey;

		internal static ConfigEntry<KeyboardShortcut> UiEditModeKey;

		internal static ConfigEntry<float> UiPositionX;

		internal static ConfigEntry<float> UiPositionY;

		internal static ConfigEntry<float> UiScale;

		internal static ConfigEntry<float> UiWindowWidth;

		internal static ConfigEntry<float> UiBackgroundOpacity;

		internal static ConfigEntry<bool> UiShowDamageBars;

		internal static ConfigEntry<bool> UiShowDamagePercent;

		internal static ConfigEntry<bool> UiShowLargestHit;

		internal static ConfigEntry<bool> UiShowDeaths;

		internal static ConfigEntry<float> UiDamageBarOpacity;

		internal static ConfigEntry<float> CombatTimeout;

		internal static ConfigEntry<float> RecoveryTimeout;

		internal static ConfigEntry<float> DpsIdleTimeout;

		internal static DamageCommitTransport Transport;

		private static Plugin Instance;

		internal static ManualLogSource ProbeLog;

		internal static readonly string ProcessRunId = Guid.NewGuid().ToString("N");

		private Harmony _harmony;

		private CombatMeterUiController _ui;

		private static bool _uiFailureLogged;

		private static int _uiEscapeConsumedFrame = -1;

		internal static bool LoggingEnabled => EnableDiagnosticLogging?.Value ?? false;

		internal static bool LifecycleLoggingEnabled => EnableLifecycleDiagnosticLogging?.Value ?? false;

		internal static bool MagicLoggingEnabled => EnableMagicAttributionDiagnosticLogging?.Value ?? false;

		internal static bool UiEditing
		{
			get
			{
				Plugin instance = Instance;
				if (instance == null)
				{
					return false;
				}
				return instance._ui?.IsEditing == true;
			}
		}

		internal static bool PerformanceLoggingEnabled => EnablePerformanceDiagnosticLogging?.Value ?? false;

		private void Awake()
		{
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0181: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Expected O, but got Unknown
			//IL_01be: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Expected O, but got Unknown
			//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0205: Expected O, but got Unknown
			//IL_02b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c2: Expected O, but got Unknown
			//IL_033d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0347: Expected O, but got Unknown
			//IL_037a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0384: Expected O, but got Unknown
			//IL_03b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c1: Expected O, but got Unknown
			//IL_044c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0456: Expected O, but got Unknown
			//IL_0775: Unknown result type (might be due to invalid IL or missing references)
			Instance = this;
			ProbeLog = ((BaseUnityPlugin)this).Logger;
			Dictionary<string, string> values = LoadLegacyConfig();
			EnableDiagnosticLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "EnableDiagnosticLogging", false, "Logs detailed owner-side HP observations on this process only. Turning this off does not disable DamageCommit transport.");
			EnableTransportDiagnosticLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "EnableTransportDiagnosticLogging", false, "Logs DamageCommit creation, acceptance, rejection and acknowledgements on this process only. Does not disable transport.");
			EnableLifecycleDiagnosticLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "EnableLifecycleDiagnosticLogging", false, "Logs Player death, respawn and instance lifecycle candidates on this process only. Does not update encounters.");
			EnableMagicAttributionDiagnosticLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "EnableMagicAttributionDiagnosticLogging", false, "Logs summoned-root and elemental DoT provenance candidates on this process only. Does not change attribution.");
			EnablePerformanceDiagnosticLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "EnablePerformanceDiagnosticLogging", false, "Collects and logs aggregated CombatMeter performance metrics on the host. Disabled by default. Does not change combat, routing, snapshots or gameplay.");
			EnablePerformanceDiagnosticLogging.SettingChanged += OnPerformanceLoggingChanged;
			UiEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("UI", "UI Enabled", true, "Shows the Combat Meter on this client only.");
			UiToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("UI", "Toggle Key", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), "Shows or hides the Combat Meter on this client only. New snapshots do not override a manual hide.");
			UiEditModeKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("UI", "Edit Mode Key", new KeyboardShortcut((KeyCode)289, (KeyCode[])(object)new KeyCode[1] { (KeyCode)306 }), "Enters or exits edit mode on this client only. Escape also exits; F8 visibility is ignored while editing.");
			UiScale = ((BaseUnityPlugin)this).Config.Bind<float>("UI", "UI Scale", 1f, new ConfigDescription("Changes the size of the Combat Meter on this client only; applied live.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 2f), Array.Empty<object>()));
			UiWindowWidth = ((BaseUnityPlugin)this).Config.Bind<float>("UI", "Window Width", 480f, new ConfigDescription("Changes the Combat Meter width on this client only; applied live.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(350f, 800f), Array.Empty<object>()));
			UiBackgroundOpacity = ((BaseUnityPlugin)this).Config.Bind<float>("UI", "Background Opacity", 0.65f, new ConfigDescription("Changes panel background opacity on this client only; applied live.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			UiShowDamageBars = ((BaseUnityPlugin)this).Config.Bind<bool>("UI", "Show Damage Bars", true, "Shows damage contribution bars on this client only.");
			UiShowLargestHit = ((BaseUnityPlugin)this).Config.Bind<bool>("UI", "Show Largest Hit", false, "Shows the largest accepted Damage Done contribution after DPS on this client only.");
			UiShowDeaths = ((BaseUnityPlugin)this).Config.Bind<bool>("UI", "Show Deaths", false, "Shows accepted encounter deaths as the final column on this client only.");
			UiShowDamagePercent = ((BaseUnityPlugin)this).Config.Bind<bool>("UI", "Show Damage Percent", true, "Shows the damage contribution percentage on this client only.");
			UiDamageBarOpacity = ((BaseUnityPlugin)this).Config.Bind<float>("UI", "Damage Bar Opacity", 0.25f, new ConfigDescription("Changes contribution bar opacity on this client only; applied live.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			UiPositionX = ((BaseUnityPlugin)this).Config.Bind<float>("UI Position", "X", 24f, "Saved horizontal HUD position on this client only.");
			UiPositionY = ((BaseUnityPlugin)this).Config.Bind<float>("UI Position", "Y", -150f, "Saved vertical HUD position on this client only.");
			CombatTimeout = ((BaseUnityPlugin)this).Config.Bind<float>("Combat", "Combat Timeout", 20f, new ConfigDescription("How long combat can remain inactive before the current encounter ends. The host's value is used for the whole session.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 60f), Array.Empty<object>()));
			RecoveryTimeout = ((BaseUnityPlugin)this).Config.Bind<float>("Combat", "Recovery Timeout", 180f, new ConfigDescription("How long a dead player may return to the same encounter before their recovery period expires. The host's value is used for the whole session.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(30f, 600f), Array.Empty<object>()));
			DpsIdleTimeout = ((BaseUnityPlugin)this).Config.Bind<float>("Combat", "DPS Idle Timeout", 6f, new ConfigDescription("How long after your last damaging PvE event time continues to count toward your DPS. Longer idle gaps do not reduce DPS. The host's value is used for the whole session.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 20f), Array.Empty<object>()));
			ApplyLegacyConfig(values);
			WarnIfLegacyPluginLoaded();
			Transport = new DamageCommitTransport(null, () => CombatTimeout?.Value ?? 20f, () => RecoveryTimeout?.Value ?? 180f, () => DpsIdleTimeout?.Value ?? 6f);
			_ui = new CombatMeterUiController();
			_harmony = new Harmony("Likhtenvald.CombatMeter");
			_harmony.PatchAll(typeof(ApplyDamagePatch));
			_harmony.PatchAll(typeof(NetworkStartPatch));
			_harmony.PatchAll(typeof(NetworkStopPatch));
			_harmony.PatchAll(typeof(NetworkDestroyPatch));
			_harmony.PatchAll(typeof(NetworkDisconnectPatch));
			_harmony.PatchAll(typeof(PlayerOnDeathLifecyclePatch));
			_harmony.PatchAll(typeof(PlayerRpcOnDeathLifecyclePatch));
			_harmony.PatchAll(typeof(GameSpawnPlayerLifecyclePatch));
			_harmony.PatchAll(typeof(PlayerStartLifecyclePatch));
			_harmony.PatchAll(typeof(PlayerDestroyLifecyclePatch));
			_harmony.PatchAll(typeof(MagicDamageRpcContextPatch));
			_harmony.PatchAll(typeof(MagicFireAppliedPatch));
			_harmony.PatchAll(typeof(MagicSpiritAppliedPatch));
			_harmony.PatchAll(typeof(MagicPoisonAppliedPatch));
			_harmony.PatchAll(typeof(MagicSpawnAbilityPatch));
			_harmony.PatchAll(typeof(MagicSummonCommandPatch));
			_harmony.PatchAll(typeof(MagicSummonSpawnCoroutinePatch));
			_harmony.PatchAll(typeof(MagicSummonRpcCommandPatch));
			_harmony.PatchAll(typeof(MagicSummonFollowTargetPatch));
			_harmony.PatchAll(typeof(CombatMeterCursorPatch));
			_harmony.PatchAll(typeof(CombatMeterPlayerInputPatch));
			_harmony.PatchAll(typeof(CombatMeterEscapePatch));
			LogMagicHook("SpawnAbility.Setup", AccessTools.Method(typeof(SpawnAbility), "Setup", (Type[])null, (Type[])null));
			LogMagicHook("Tameable.Command", AccessTools.Method(typeof(Tameable), "Command", new Type[2]
			{
				typeof(Humanoid),
				typeof(bool)
			}, (Type[])null));
			LogMagicHook("Tameable.RPC_Command", AccessTools.Method(typeof(Tameable), "RPC_Command", new Type[3]
			{
				typeof(long),
				typeof(ZDOID),
				typeof(bool)
			}, (Type[])null));
			LogMagicHook("MonsterAI.SetFollowTarget", AccessTools.Method(typeof(MonsterAI), "SetFollowTarget", new Type[1] { typeof(GameObject) }, (Type[])null));
			LogMagicHook("SpawnAbility.Spawn.MoveNext", AccessTools.EnumeratorMoveNext((MethodBase)AccessTools.Method(typeof(SpawnAbility), "Spawn", (Type[])null, (Type[])null)));
			SafeTransport(delegate
			{
				Transport.Bind(ZNet.instance);
			});
			((BaseUnityPlugin)this).Logger.LogInfo((object)("CombatMeterReady version=1.0.0 run=" + ProcessRunId + " " + $"valheim={Version.CurrentVersion} enabled={LoggingEnabled} " + "target=Character.ApplyDamage(HitData,bool,bool,HitData.DamageModifier)"));
		}

		private Dictionary<string, string> LoadLegacyConfig()
		{
			try
			{
				string text = Path.Combine(Paths.ConfigPath, "local.valheim.diagnosticdamageprobe.cfg");
				if (!LegacyConfigMigration.ShouldMigrate(text, ((BaseUnityPlugin)this).Config.ConfigFilePath))
				{
					return null;
				}
				return LegacyConfigMigration.ParseKnown(File.ReadAllText(text));
			}
			catch (Exception ex)
			{
				Warn("CombatMeterConfigMigrationSkipped reason=" + ex.GetType().Name);
				return null;
			}
		}

		private void ApplyLegacyConfig(Dictionary<string, string> values)
		{
			if (values != null && values.Count != 0)
			{
				int num = 0;
				num += Apply<bool>(values, "Diagnostics", "EnableDiagnosticLogging", EnableDiagnosticLogging);
				num += Apply<bool>(values, "Diagnostics", "EnableTransportDiagnosticLogging", EnableTransportDiagnosticLogging);
				num += Apply<bool>(values, "Diagnostics", "EnableLifecycleDiagnosticLogging", EnableLifecycleDiagnosticLogging);
				num += Apply<bool>(values, "Diagnostics", "EnableMagicAttributionDiagnosticLogging", EnableMagicAttributionDiagnosticLogging);
				num += Apply<bool>(values, "Diagnostics", "EnablePerformanceDiagnosticLogging", EnablePerformanceDiagnosticLogging);
				num += Apply<bool>(values, "UI", "UI Enabled", UiEnabled);
				num += Apply<KeyboardShortcut>(values, "UI", "Toggle Key", UiToggleKey);
				num += Apply<KeyboardShortcut>(values, "UI", "Edit Mode Key", UiEditModeKey);
				num += Apply<float>(values, "UI", "UI Scale", UiScale);
				num += Apply<float>(values, "UI", "Window Width", UiWindowWidth);
				num += Apply<float>(values, "UI", "Background Opacity", UiBackgroundOpacity);
				num += Apply<bool>(values, "UI", "Show Largest Hit", UiShowLargestHit);
				num += Apply<bool>(values, "UI", "Show Deaths", UiShowDeaths);
				num += Apply<bool>(values, "UI", "Show Damage Bars", UiShowDamageBars);
				num += Apply<bool>(values, "UI", "Show Damage Percent", UiShowDamagePercent);
				num += Apply<float>(values, "UI", "Damage Bar Opacity", UiDamageBarOpacity);
				num += Apply<float>(values, "UI Position", "X", UiPositionX);
				num += Apply<float>(values, "UI Position", "Y", UiPositionY);
				num += Apply<float>(values, "Combat", "Combat Timeout", CombatTimeout);
				num += Apply<float>(values, "Combat", "Recovery Timeout", RecoveryTimeout);
				num += Apply<float>(values, "Combat", "DPS Idle Timeout", DpsIdleTimeout);
				((BaseUnityPlugin)this).Config.Save();
				((BaseUnityPlugin)this).Logger.LogInfo((object)("CombatMeterConfigMigrated source=local.valheim.diagnosticdamageprobe.cfg values=" + num));
			}
		}

		private static int Apply<T>(Dictionary<string, string> values, string section, string key, ConfigEntry<T> entry)
		{
			if (!LegacyConfigMigration.TryGet(values, section, key, out var value))
			{
				return 0;
			}
			try
			{
				entry.Value = (T)TomlTypeConverter.ConvertToValue(value, typeof(T));
				return 1;
			}
			catch
			{
				return 0;
			}
		}

		private static void WarnIfLegacyPluginLoaded()
		{
			try
			{
				if (Chainloader.PluginInfos.ContainsKey("local.valheim.diagnosticdamageprobe"))
				{
					Warn("CombatMeterLegacyPluginDetected remove DiagnosticDamageProbe.dll to avoid duplicate patches and statistics");
				}
			}
			catch
			{
			}
		}

		private void OnDestroy()
		{
			SafeUi(delegate
			{
				_ui?.ExitEditMode("Lifecycle", PersistUiPosition);
				_ui?.Destroy();
			});
			SafeTransport(delegate
			{
				Transport?.Dispose();
			});
			MagicAttributionProbe.ResetRuntimeDiscovery();
			Transport = null;
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			EnableDiagnosticLogging = null;
			EnableTransportDiagnosticLogging = null;
			EnableLifecycleDiagnosticLogging = null;
			EnableMagicAttributionDia