Decompiled source of Death Log v1.1.0

BepInEx/plugins/LastThreeHits/LastThreeHits.dll

Decompiled 10 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using EntityStates.QuestVolatileBattery;
using EntityStates.VoidInfestor;
using HarmonyLib;
using RoR2;
using RoR2.Projectile;
using UnityEngine;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("LastThreeHits")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0+6bf991065379542507209f74b346363b982c1e84")]
[assembly: AssemblyProduct("LastThreeHits")]
[assembly: AssemblyTitle("LastThreeHits")]
[assembly: AssemblyVersion("1.1.0.0")]
namespace LastThreeHits;

public enum ReportMode
{
	Host,
	Local
}
[BepInPlugin("com.rawteam.lastthreehits", "Death Log", "1.1.0")]
public sealed class Plugin : BaseUnityPlugin
{
	private struct PendingReport
	{
		internal DamageHistory History;

		internal string Player;

		internal uint BodyId;

		internal float Due;
	}

	public const string PluginGuid = "com.rawteam.lastthreehits";

	public const string PluginVersion = "1.1.0";

	internal const string Red = "<color=#FF4040>";

	internal const string ReportTag = "rawteam.deathlog.report.v1";

	internal static Plugin Instance;

	private ConfigEntry<ReportMode> reportMode;

	private Harmony hooks;

	private DeathSound sound;

	private bool hostReportsSeen;

	private readonly List<PendingReport> pending = new List<PendingReport>(4);

	internal bool ClientFallback
	{
		get
		{
			if (reportMode.Value == ReportMode.Local)
			{
				return !NetworkServer.active;
			}
			return false;
		}
	}

	private void Awake()
	{
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_0043: Expected O, but got Unknown
		Instance = this;
		reportMode = ((BaseUnityPlugin)this).Config.Bind<ReportMode>("Reports", "Mode", ReportMode.Host, "Host: send lobby reports when hosting. Local: show personal client reports if the host has no Death Log report. Restart after changing this setting.");
		sound = new DeathSound(this);
		hooks = new Harmony("com.rawteam.lastthreehits");
		hooks.PatchAll(typeof(Plugin).Assembly);
		GlobalEventManager.onServerDamageDealt += OnDamage;
		GlobalEventManager.onCharacterDeathServer += OnDeath;
		GlobalEventManager.onClientDamageNotified += OnClientDamage;
		CharacterBody.onBodyStartGlobal += OnBodyStart;
		Run.onRunDestroyGlobal += OnRunEnd;
		Chat.onChatChanged += OnChatChanged;
		((BaseUnityPlugin)this).Logger.LogInfo((object)("Death Log loaded. Mode: " + reportMode.Value));
	}

	internal void LogFailure(string message, Exception error)
	{
		((BaseUnityPlugin)this).Logger.LogWarning((object)(message + ": " + error.Message));
	}

	private void OnDestroy()
	{
		GlobalEventManager.onServerDamageDealt -= OnDamage;
		GlobalEventManager.onCharacterDeathServer -= OnDeath;
		GlobalEventManager.onClientDamageNotified -= OnClientDamage;
		CharacterBody.onBodyStartGlobal -= OnBodyStart;
		Run.onRunDestroyGlobal -= OnRunEnd;
		Chat.onChatChanged -= OnChatChanged;
		foreach (CharacterBody readOnlyInstances in CharacterBody.readOnlyInstancesList)
		{
			readOnlyInstances.OnBodyDeath = (Action<CharacterBody>)Delegate.Remove(readOnlyInstances.OnBodyDeath, new Action<CharacterBody>(OnBodyDeath));
		}
		pending.Clear();
		SourceLabels.Clear();
		if (hooks != null)
		{
			hooks.UnpatchSelf();
		}
		if (sound != null)
		{
			sound.Dispose();
		}
		Instance = null;
	}

	private void OnRunEnd(Run run)
	{
		pending.Clear();
		hostReportsSeen = false;
		SourceLabels.Clear();
	}

	internal static bool IsPlayerBody(CharacterBody body)
	{
		if (!Object.op_Implicit((Object)(object)body))
		{
			return false;
		}
		CharacterMaster master = body.master;
		if (!body.isPlayerControlled)
		{
			if (Object.op_Implicit((Object)(object)master))
			{
				return Object.op_Implicit((Object)(object)((Component)master).GetComponent<PlayerCharacterMasterController>());
			}
			return false;
		}
		return true;
	}

	private static bool IsLocalBody(CharacterBody body)
	{
		if (!Object.op_Implicit((Object)(object)body))
		{
			return false;
		}
		foreach (LocalUser readOnlyLocalUsers in LocalUserManager.readOnlyLocalUsersList)
		{
			if ((Object)(object)readOnlyLocalUsers.cachedBody == (Object)(object)body || (Object.op_Implicit((Object)(object)body.master) && (Object)(object)readOnlyLocalUsers.cachedMaster == (Object)(object)body.master))
			{
				return true;
			}
		}
		return false;
	}

	private void OnBodyStart(CharacterBody body)
	{
		SourceLabels.Remember(body);
		body.OnBodyDeath = (Action<CharacterBody>)Delegate.Combine(body.OnBodyDeath, new Action<CharacterBody>(OnBodyDeath));
	}

	private void OnClientDamage(DamageDealtMessage message)
	{
		if (!ClientFallback || message == null || !Object.op_Implicit((Object)(object)message.victim) || !IsPositiveFinite(message.damage))
		{
			return;
		}
		CharacterBody component = message.victim.GetComponent<CharacterBody>();
		if (!IsLocalBody(component))
		{
			return;
		}
		try
		{
			GetHistory(component).RecordClient(message);
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)("Could not record client damage: " + ex));
		}
	}

	private void OnBodyDeath(CharacterBody body)
	{
		if (!IsLocalBody(body))
		{
			return;
		}
		try
		{
			DamageHistory history = GetHistory(body);
			PlayDeathSound(history);
			if (ClientFallback && !history.Reported && !history.Pending && !hostReportsSeen)
			{
				history.Pending = true;
				pending.Add(new PendingReport
				{
					History = history,
					Player = PlayerName(body),
					BodyId = BodyId(body),
					Due = Time.realtimeSinceStartup + 1.5f
				});
			}
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)("Could not process local death: " + ex));
		}
	}

	private void PlayDeathSound(DamageHistory history)
	{
		if (!history.SoundPlayed)
		{
			history.SoundPlayed = true;
			if (sound.Play())
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Played local death sound.");
			}
		}
	}

	private void Update()
	{
		for (int num = pending.Count - 1; num >= 0; num--)
		{
			PendingReport pendingReport = pending[num];
			if (pendingReport.History.Reported || hostReportsSeen)
			{
				pending.RemoveAt(num);
			}
			else if (!(Time.realtimeSinceStartup < pendingReport.Due))
			{
				pending.RemoveAt(num);
				pendingReport.History.Reported = true;
				string text = BuildReport(pendingReport.Player, pendingReport.History, clientData: true);
				((BaseUnityPlugin)this).Logger.LogInfo((object)StripTags(text));
				LogUnknownSources(pendingReport.History);
				Chat.AddMessage(text);
			}
		}
	}

	private static uint BodyId(CharacterBody body)
	{
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		if (!Object.op_Implicit((Object)(object)body) || !Object.op_Implicit((Object)(object)body.networkIdentity))
		{
			return 0u;
		}
		NetworkInstanceId netId = body.networkIdentity.netId;
		return ((NetworkInstanceId)(ref netId)).Value;
	}

	private static bool IsPlayerReport(DamageReport report)
	{
		if (NetworkServer.active && report != null && report.damageInfo != null)
		{
			return IsPlayerBody(report.victimBody);
		}
		return false;
	}

	private void OnDamage(DamageReport report)
	{
		if (!IsPlayerReport(report) || report.damageInfo.rejected || !IsPositiveFinite(report.damageDealt) || (reportMode.Value == ReportMode.Local && !IsLocalBody(report.victimBody)))
		{
			return;
		}
		try
		{
			GetHistory(report.victimBody).Record(report);
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)("Could not record damage: " + ex));
		}
	}

	private void OnDeath(DamageReport report)
	{
		//IL_0099: Unknown result type (might be due to invalid IL or missing references)
		//IL_009f: Expected O, but got Unknown
		if (!IsPlayerReport(report) || (reportMode.Value == ReportMode.Local && !IsLocalBody(report.victimBody)))
		{
			return;
		}
		try
		{
			DamageHistory history = GetHistory(report.victimBody);
			if (!history.Reported)
			{
				history.RecordDeath(report);
				history.Reported = true;
				if (IsLocalBody(report.victimBody))
				{
					PlayDeathSound(history);
				}
				string text = BuildReport(PlayerName(report.victimBody), history, clientData: false);
				((BaseUnityPlugin)this).Logger.LogInfo((object)StripTags(text));
				LogUnknownSources(history);
				if (reportMode.Value == ReportMode.Host)
				{
					SimpleChatMessage val = new SimpleChatMessage();
					val.baseToken = "{0}";
					val.paramTokens = new string[2]
					{
						text,
						"rawteam.deathlog.report.v1:" + BodyId(report.victimBody).ToString(CultureInfo.InvariantCulture)
					};
					Chat.SendBroadcastChat((ChatMessageBase)(object)val);
				}
				else
				{
					Chat.AddMessage(text);
				}
			}
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)("Could not show the death report: " + ex));
		}
	}

	private void LogUnknownSources(DamageHistory history)
	{
		for (int i = 0; i < history.Count; i++)
		{
			Hit hit = history.Newest(i);
			if (!string.IsNullOrEmpty(hit.Diagnostics))
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Unknown source details, row " + (i + 1) + ": " + hit.Diagnostics));
			}
		}
	}

	internal void ReceiveHostReport(SimpleChatMessage message)
	{
		if (NetworkServer.active || message.baseToken != "{0}" || message.paramTokens == null || message.paramTokens.Length != 2 || message.paramTokens[1] == null || !message.paramTokens[1].StartsWith("rawteam.deathlog.report.v1:", StringComparison.Ordinal) || !uint.TryParse(message.paramTokens[1].Substring("rawteam.deathlog.report.v1".Length + 1), out var result))
		{
			return;
		}
		hostReportsSeen = true;
		for (int num = pending.Count - 1; num >= 0; num--)
		{
			if (pending[num].BodyId == result)
			{
				pending[num].History.Reported = true;
				pending.RemoveAt(num);
			}
		}
		foreach (CharacterBody readOnlyInstances in CharacterBody.readOnlyInstancesList)
		{
			if (BodyId(readOnlyInstances) == result && IsLocalBody(readOnlyInstances))
			{
				GetHistory(readOnlyInstances).Reported = true;
			}
		}
	}

	private void OnChatChanged()
	{
		if (!NetworkServer.active && Chat.readOnlyLog.Count != 0)
		{
			string text = StripTags(Chat.readOnlyLog[Chat.readOnlyLog.Count - 1]);
			if (text.StartsWith("[Death Log] ", StringComparison.Ordinal) || text.StartsWith("[Last Three Hits] ", StringComparison.Ordinal))
			{
				hostReportsSeen = true;
				pending.Clear();
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Received host report:\n" + text));
			}
		}
	}

	internal static bool IsPositiveFinite(float value)
	{
		if (value > 0f && !float.IsNaN(value))
		{
			return !float.IsInfinity(value);
		}
		return false;
	}

	private static DamageHistory GetHistory(CharacterBody body)
	{
		DamageHistory component = ((Component)body).GetComponent<DamageHistory>();
		if (!Object.op_Implicit((Object)(object)component))
		{
			return ((Component)body).gameObject.AddComponent<DamageHistory>();
		}
		return component;
	}

	private static string PlayerName(CharacterBody body)
	{
		string value = body.GetUserName();
		if (string.IsNullOrWhiteSpace(value))
		{
			value = body.GetDisplayName();
		}
		return CleanLabel(value);
	}

	private static string BuildReport(string player, DamageHistory history, bool clientData)
	{
		StringBuilder stringBuilder = new StringBuilder(512);
		stringBuilder.Append("<color=#FF4040>").Append(clientData ? "[Death Log - local] " : "[Death Log] ").Append(player)
			.Append(" - newest first");
		for (int i = 0; i < history.Count; i++)
		{
			Hit hit = history.Newest(i);
			stringBuilder.Append('\n').Append(i + 1).Append(". ")
				.Append(hit.Source)
				.Append(" | ")
				.Append(hit.Effect)
				.Append(" | ")
				.Append(hit.Damage)
				.Append(" damage");
		}
		if (history.Count < 3)
		{
			stringBuilder.Append("\nOnly ").Append(history.Count).Append(" damage event(s) recorded for this body.");
		}
		if (clientData)
		{
			stringBuilder.Append("\nClient data: final hit or effect may be unavailable.");
		}
		return stringBuilder.Append("</color>").ToString();
	}

	internal static Hit Describe(DamageReport report)
	{
		SourceLabels.Origin origin = SourceLabels.ForDamage(report.damageInfo, report.victimBody);
		return new Hit
		{
			Source = CleanLabel(origin.Source),
			Effect = CleanLabel(origin.Effect),
			Diagnostics = (string.IsNullOrWhiteSpace(origin.Source) ? ("server; " + SourceLabels.Details(report.damageInfo) + "; healthBefore=" + report.combinedHealthBeforeDamage.ToString(CultureInfo.InvariantCulture)) : null),
			Damage = (IsPositiveFinite(report.damageDealt) ? report.damageDealt.ToString("0.##", CultureInfo.InvariantCulture) : "unknown")
		};
	}

	internal unsafe static Hit DescribeClient(DamageDealtMessage message)
	{
		//IL_0082: Unknown result type (might be due to invalid IL or missing references)
		//IL_0087: Unknown result type (might be due to invalid IL or missing references)
		SourceLabels.Origin origin = SourceLabels.ForClient(message);
		Hit result = new Hit
		{
			Source = CleanLabel(origin.Source),
			Effect = CleanLabel(origin.Effect)
		};
		object diagnostics;
		if (!string.IsNullOrWhiteSpace(origin.Source))
		{
			diagnostics = null;
		}
		else
		{
			string[] obj = new string[6]
			{
				"client; attacker=",
				SourceLabels.ObjectState(message.attacker),
				"; attackerNetId=",
				origin.AttackerId.ToString(),
				"; flags=",
				null
			};
			DamageTypeCombo damageType = message.damageType;
			obj[5] = ((object)(*(DamageTypeCombo*)(&damageType))/*cast due to .constrained prefix*/).ToString();
			diagnostics = string.Concat(obj);
		}
		result.Diagnostics = (string)diagnostics;
		result.Damage = message.damage.ToString("0.##", CultureInfo.InvariantCulture);
		return result;
	}

	internal static string StripTags(string value)
	{
		StringBuilder stringBuilder = new StringBuilder(value.Length);
		bool flag = false;
		foreach (char c in value)
		{
			switch (c)
			{
			case '<':
				flag = true;
				continue;
			case '>':
				flag = false;
				continue;
			}
			if (!flag)
			{
				stringBuilder.Append(c);
			}
		}
		return stringBuilder.ToString();
	}

	internal static string CleanLabel(string value)
	{
		if (string.IsNullOrWhiteSpace(value))
		{
			return "Unknown source";
		}
		StringBuilder stringBuilder = new StringBuilder(96);
		bool flag = false;
		foreach (char c in value)
		{
			switch (c)
			{
			case '<':
				flag = true;
				continue;
			case '>':
				flag = false;
				continue;
			default:
				if (flag || char.IsControl(c))
				{
					continue;
				}
				stringBuilder.Append(c);
				if (stringBuilder.Length != 96)
				{
					continue;
				}
				break;
			}
			break;
		}
		string text = stringBuilder.ToString().Trim();
		if (text.Length != 0)
		{
			return text;
		}
		return "Unknown source";
	}

	internal static string SplitName(string name)
	{
		StringBuilder stringBuilder = new StringBuilder(name.Length + 16);
		for (int i = 0; i < name.Length; i++)
		{
			char c = name[i];
			if (c == '_')
			{
				stringBuilder.Append(' ');
				continue;
			}
			if (i > 0 && char.IsUpper(c) && (char.IsLower(name[i - 1]) || char.IsDigit(name[i - 1])))
			{
				stringBuilder.Append(' ');
			}
			stringBuilder.Append(c);
		}
		return stringBuilder.ToString().Trim();
	}
}
internal struct Hit
{
	public string Source;

	public string Effect;

	public string Damage;

	public string Diagnostics;
}
internal sealed class DamageHistory : MonoBehaviour
{
	private readonly Hit[] hits = new Hit[3];

	private readonly DamageInfo[] infos = (DamageInfo[])(object)new DamageInfo[3];

	private int next;

	private long lethalToken;

	private int lethalIndex = -1;

	internal int Count { get; private set; }

	internal bool Reported { get; set; }

	internal bool Pending { get; set; }

	internal bool SoundPlayed { get; set; }

	internal void Record(DamageReport report)
	{
		if (!Reported)
		{
			int num = next;
			Push(Plugin.Describe(report), report.damageInfo);
			if (Object.op_Implicit((Object)(object)report.victim) && !report.victim.alive)
			{
				lethalToken = SourceLabels.CurrentToken(report.victim);
				lethalIndex = num;
			}
		}
	}

	internal void RecordDeath(DamageReport report)
	{
		if (Reported)
		{
			return;
		}
		for (int i = 0; i < Count; i++)
		{
			int num = (next - 1 - i + hits.Length) % hits.Length;
			if (infos[num] == report.damageInfo)
			{
				hits[num] = Refine(hits[num], Plugin.Describe(report));
				return;
			}
		}
		long num2 = SourceLabels.CurrentToken(report.victim);
		if (num2 != 0L && num2 == lethalToken && lethalIndex >= 0)
		{
			hits[lethalIndex] = Refine(hits[lethalIndex], Plugin.Describe(report));
		}
		else
		{
			Push(Plugin.Describe(report), report.damageInfo);
		}
	}

	private static Hit Refine(Hit recorded, Hit death)
	{
		bool flag = UnknownEffect(recorded.Effect) && !UnknownEffect(death.Effect);
		if ((recorded.Source == "Unknown source" || flag) && death.Source != "Unknown source")
		{
			recorded.Source = death.Source;
			recorded.Diagnostics = death.Diagnostics;
		}
		if (flag)
		{
			recorded.Effect = death.Effect;
		}
		if (recorded.Damage == "unknown")
		{
			recorded.Damage = death.Damage;
		}
		return recorded;
	}

	private static bool UnknownEffect(string effect)
	{
		if (!(effect == "Hit (attack unknown)"))
		{
			return effect == "Damage over time (type unknown)";
		}
		return true;
	}

	internal void RecordClient(DamageDealtMessage message)
	{
		if (!Reported)
		{
			Push(Plugin.DescribeClient(message), null);
		}
	}

	private void Push(Hit hit, DamageInfo info)
	{
		hits[next] = hit;
		infos[next] = info;
		next = (next + 1) % hits.Length;
		if (Count < hits.Length)
		{
			int count = Count + 1;
			Count = count;
		}
	}

	internal Hit Newest(int offset)
	{
		return hits[(next - 1 - offset + hits.Length) % hits.Length];
	}
}
[HarmonyPatch(typeof(SimpleChatMessage), "ConstructChatString")]
internal static class HostReportPatch
{
	private static void Prefix(SimpleChatMessage __instance)
	{
		if ((Object)(object)Plugin.Instance == (Object)null)
		{
			return;
		}
		try
		{
			Plugin.Instance.ReceiveHostReport(__instance);
		}
		catch (Exception error)
		{
			Plugin.Instance.LogFailure("Could not read host report metadata", error);
		}
	}
}
internal static class SourceLabels
{
	internal sealed class Origin
	{
		internal string Source;

		internal string Effect;

		internal long Token;

		internal uint AttackerId;
	}

	private static ConditionalWeakTable<DamageInfo, Origin> damageOrigins = new ConditionalWeakTable<DamageInfo, Origin>();

	private static ConditionalWeakTable<DamageDealtMessage, Origin> clientOrigins = new ConditionalWeakTable<DamageDealtMessage, Origin>();

	private static ConditionalWeakTable<GameObject, Origin> objectNames = new ConditionalWeakTable<GameObject, Origin>();

	private static readonly Dictionary<HealthComponent, Origin> active = new Dictionary<HealthComponent, Origin>();

	private static readonly Dictionary<uint, string> names = new Dictionary<uint, string>();

	private static long nextToken;

	[ThreadStatic]
	internal static int FogDepth;

	[ThreadStatic]
	internal static CharacterBody LavaBody;

	internal static void Clear()
	{
		active.Clear();
		names.Clear();
		objectNames = new ConditionalWeakTable<GameObject, Origin>();
		damageOrigins = new ConditionalWeakTable<DamageInfo, Origin>();
		clientOrigins = new ConditionalWeakTable<DamageDealtMessage, Origin>();
	}

	internal static void Remember(CharacterBody body)
	{
		//IL_0056: Unknown result type (might be due to invalid IL or missing references)
		//IL_005b: Unknown result type (might be due to invalid IL or missing references)
		//IL_008c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0091: Unknown result type (might be due to invalid IL or missing references)
		if (!Object.op_Implicit((Object)(object)body))
		{
			return;
		}
		string text = Name(((Component)body).gameObject);
		objectNames.Remove(((Component)body).gameObject);
		objectNames.Add(((Component)body).gameObject, new Origin
		{
			Source = text
		});
		if (!Object.op_Implicit((Object)(object)body.networkIdentity))
		{
			return;
		}
		NetworkInstanceId netId = body.networkIdentity.netId;
		if (!((NetworkInstanceId)(ref netId)).IsEmpty())
		{
			if (names.Count >= 8192)
			{
				names.Clear();
			}
			Dictionary<uint, string> dictionary = names;
			netId = body.networkIdentity.netId;
			dictionary[((NetworkInstanceId)(ref netId)).Value] = text;
		}
	}

	private static string Name(GameObject source, int depth = 0)
	{
		if (source == null)
		{
			return null;
		}
		if (!Object.op_Implicit((Object)(object)source))
		{
			if (!objectNames.TryGetValue(source, out var value))
			{
				return null;
			}
			return value.Source;
		}
		CharacterBody val = source.GetComponent<CharacterBody>();
		if (!Object.op_Implicit((Object)(object)val))
		{
			CharacterMaster component = source.GetComponent<CharacterMaster>();
			if (Object.op_Implicit((Object)(object)component))
			{
				val = component.GetBody();
			}
		}
		if (Object.op_Implicit((Object)(object)val))
		{
			return Plugin.CleanLabel(Util.GetBestBodyName(((Component)val).gameObject));
		}
		PurchaseInteraction component2 = source.GetComponent<PurchaseInteraction>();
		if (Object.op_Implicit((Object)(object)component2) && !string.IsNullOrEmpty(component2.displayNameToken))
		{
			return Plugin.CleanLabel(Language.GetString(component2.displayNameToken));
		}
		ProjectileController component3 = source.GetComponent<ProjectileController>();
		if (depth < 4 && Object.op_Implicit((Object)(object)component3) && component3.owner != null && component3.owner != source)
		{
			return Name(component3.owner, depth + 1);
		}
		NetworkedBodyAttachment component4 = source.GetComponent<NetworkedBodyAttachment>();
		if (Object.op_Implicit((Object)(object)component4) && Object.op_Implicit((Object)(object)component4.attachedBody))
		{
			return Name(((Component)component4.attachedBody).gameObject);
		}
		return Plugin.CleanLabel(Plugin.SplitName(((Object)source).name.Replace("(Clone)", "")));
	}

	private static bool IsFuelArray(GameObject inflictor)
	{
		if (!Object.op_Implicit((Object)(object)inflictor))
		{
			return false;
		}
		EntityStateMachine[] components = inflictor.GetComponents<EntityStateMachine>();
		for (int i = 0; i < components.Length; i++)
		{
			if (components[i].state is QuestVolatileBatteryBaseState)
			{
				return true;
			}
		}
		return ((Object)inflictor).name.StartsWith("QuestVolatileBatteryAttachment", StringComparison.Ordinal);
	}

	private static Origin FromInfo(DamageInfo info, CharacterBody victim)
	{
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_0053: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_0062: Unknown result type (might be due to invalid IL or missing references)
		//IL_006c: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
		//IL_0107: Unknown result type (might be due to invalid IL or missing references)
		//IL_010e: Invalid comparison between Unknown and I4
		//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
		//IL_01fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d9: Unknown result type (might be due to invalid IL or missing references)
		//IL_01df: Invalid comparison between Unknown and I4
		//IL_0210: Unknown result type (might be due to invalid IL or missing references)
		//IL_0216: Invalid comparison between Unknown and I4
		//IL_01e1: 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_0239: Unknown result type (might be due to invalid IL or missing references)
		//IL_023f: Unknown result type (might be due to invalid IL or missing references)
		Origin origin = new Origin
		{
			Source = Name(info.attacker),
			Effect = "Hit (attack unknown)"
		};
		DamageType damageType = info.damageType.damageType;
		if ((damageType & 0x200000) != 0)
		{
			origin.Source = "Environment";
			origin.Effect = "Fall damage";
		}
		else if ((damageType & int.MinValue) != 0 || (info.damageType.damageTypeExtended & int.MinValue) != 0)
		{
			origin.Source = "Environment";
			origin.Effect = "Out of bounds";
		}
		else if (!Object.op_Implicit((Object)(object)info.attacker) && !Object.op_Implicit((Object)(object)info.inflictor) && Object.op_Implicit((Object)(object)victim) && (Object)(object)victim == (Object)(object)LavaBody)
		{
			origin.Source = "Environment";
			origin.Effect = "Lava";
		}
		else if (!Object.op_Implicit((Object)(object)info.attacker) && !Object.op_Implicit((Object)(object)info.inflictor) && (info.damageType.damageTypeExtended & 0x4000) != 0 && (int)info.damageType.damageSource == 16)
		{
			origin.Source = "Environment";
			origin.Effect = ((FogDepth > 0) ? "Fog damage" : "Damage field");
		}
		else if (IsFuelArray(info.inflictor))
		{
			origin.Source = Language.GetString(Equipment.QuestVolatileBattery.nameToken);
			origin.Effect = ((Object.op_Implicit((Object)(object)victim) && (Object)(object)info.attacker == (Object)(object)((Component)victim).gameObject) ? "Explosion (self-damage)" : "Explosion");
		}
		else
		{
			if (Object.op_Implicit((Object)(object)victim) && (Object)(object)info.attacker == (Object)(object)((Component)victim).gameObject)
			{
				origin.Source = "Self";
			}
			PurchaseInteraction val = (Object.op_Implicit((Object)(object)info.attacker) ? info.attacker.GetComponent<PurchaseInteraction>() : null);
			if (Object.op_Implicit((Object)(object)val) && (int)val.costType == 2 && (damageType & 1) != 0)
			{
				origin.Effect = "Health cost";
			}
			else if ((damageType & 0x10000) != 0)
			{
				origin.Effect = "Void death";
			}
			else if ((int)info.dotIndex != -1)
			{
				origin.Effect = Plugin.SplitName(((object)Unsafe.As<DotIndex, DotIndex>(ref info.dotIndex)/*cast due to .constrained prefix*/).ToString());
			}
			else if ((damageType & 0x4000000) != 0)
			{
				origin.Effect = "Damage over time (type unknown)";
			}
			else if (Object.op_Implicit((Object)(object)info.attacker) && Object.op_Implicit((Object)(object)info.attacker.GetComponent<EntityStateMachine>()) && Array.Exists(info.attacker.GetComponents<EntityStateMachine>(), (EntityStateMachine machine) => machine.state is Infest))
			{
				origin.Effect = "Leap / infestation";
			}
			else if (Object.op_Implicit((Object)(object)info.inflictor) && (Object)(object)info.inflictor != (Object)(object)info.attacker)
			{
				origin.Effect = Plugin.SplitName(((Object)info.inflictor).name.Replace("(Clone)", ""));
			}
			if (string.IsNullOrWhiteSpace(origin.Source) && Object.op_Implicit((Object)(object)info.inflictor))
			{
				origin.Source = Name(info.inflictor);
			}
		}
		return origin;
	}

	internal static string ObjectState(GameObject value)
	{
		if (value == null)
		{
			return "not supplied";
		}
		if (!Object.op_Implicit((Object)(object)value))
		{
			return "destroyed";
		}
		return Plugin.CleanLabel(((Object)value).name);
	}

	internal unsafe static string Details(DamageInfo info)
	{
		//IL_003d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		string[] obj = new string[8]
		{
			"attacker=",
			ObjectState(info.attacker),
			"; inflictor=",
			ObjectState(info.inflictor),
			"; flags=",
			null,
			null,
			null
		};
		DamageTypeCombo damageType = info.damageType;
		obj[5] = ((object)(*(DamageTypeCombo*)(&damageType))/*cast due to .constrained prefix*/).ToString();
		obj[6] = "; dot=";
		obj[7] = ((object)Unsafe.As<DotIndex, DotIndex>(ref info.dotIndex)/*cast due to .constrained prefix*/).ToString();
		return string.Concat(obj);
	}

	internal static Origin ForDamage(DamageInfo info, CharacterBody victim)
	{
		if (damageOrigins.TryGetValue(info, out var value))
		{
			if (string.IsNullOrWhiteSpace(value.Source) || value.Source == "Unknown source")
			{
				Origin origin = FromInfo(info, victim);
				if (!string.IsNullOrWhiteSpace(origin.Source) && origin.Source != "Unknown source")
				{
					return origin;
				}
			}
			return value;
		}
		value = FromInfo(info, victim);
		if (Object.op_Implicit((Object)(object)victim) && Object.op_Implicit((Object)(object)victim.healthComponent) && active.TryGetValue(victim.healthComponent, out var value2) && string.IsNullOrWhiteSpace(value.Source))
		{
			value.Source = value2.Source;
			if (value.Effect == "Hit (attack unknown)")
			{
				value.Effect = value2.Effect;
			}
		}
		return value;
	}

	internal static Origin Enter(HealthComponent victim, DamageInfo info)
	{
		active.TryGetValue(victim, out var value);
		if (info == null || !Plugin.IsPlayerBody(victim.body))
		{
			return value;
		}
		Origin origin = FromInfo(info, victim.body);
		origin.Token = ++nextToken;
		damageOrigins.Remove(info);
		damageOrigins.Add(info, origin);
		active[victim] = origin;
		return value;
	}

	internal static void Leave(HealthComponent victim, Origin previous)
	{
		if (previous == null)
		{
			active.Remove(victim);
		}
		else
		{
			active[victim] = previous;
		}
	}

	internal static long CurrentToken(HealthComponent victim)
	{
		if (!Object.op_Implicit((Object)(object)victim) || !active.TryGetValue(victim, out var value))
		{
			return 0L;
		}
		return value.Token;
	}

	internal static Origin ForClient(DamageDealtMessage message)
	{
		//IL_0063: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: Unknown result type (might be due to invalid IL or missing references)
		//IL_0069: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: Unknown result type (might be due to invalid IL or missing references)
		//IL_008d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0093: Unknown result type (might be due to invalid IL or missing references)
		//IL_009c: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
		//IL_0108: Unknown result type (might be due to invalid IL or missing references)
		//IL_010e: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00de: Unknown result type (might be due to invalid IL or missing references)
		//IL_011e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0124: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ee: Invalid comparison between Unknown and I4
		if (!clientOrigins.TryGetValue(message, out var value))
		{
			value = new Origin
			{
				Source = Name(message.attacker)
			};
		}
		value.Effect = "Hit (attack unknown)";
		if (Object.op_Implicit((Object)(object)message.attacker) && (Object)(object)message.attacker == (Object)(object)message.victim)
		{
			value.Source = "Self";
		}
		DamageType damageType = message.damageType.damageType;
		if ((damageType & 0x200000) != 0)
		{
			value.Source = "Environment";
			value.Effect = "Fall damage";
		}
		else if ((damageType & int.MinValue) != 0 || (message.damageType.damageTypeExtended & int.MinValue) != 0)
		{
			value.Source = "Environment";
			value.Effect = "Out of bounds";
		}
		else if (!Object.op_Implicit((Object)(object)message.attacker) && (message.damageType.damageTypeExtended & 0x4000) != 0 && (int)message.damageType.damageSource == 16)
		{
			value.Source = "Environment";
			value.Effect = "Damage field";
		}
		else if ((damageType & 0x10000) != 0)
		{
			value.Effect = "Void death";
		}
		else if ((damageType & 0x4000000) != 0)
		{
			value.Effect = "Damage over time (type unknown)";
		}
		return value;
	}

	internal static void RestoreClientName(DamageDealtMessage message, uint attackerId)
	{
		if (!Object.op_Implicit((Object)(object)message.attacker))
		{
			names.TryGetValue(attackerId, out var value);
			clientOrigins.Remove(message);
			clientOrigins.Add(message, new Origin
			{
				Source = value,
				AttackerId = attackerId
			});
		}
	}
}
[HarmonyPatch(typeof(HealthComponent), "TakeDamage")]
internal static class DamageCapturePatch
{
	private static void Prefix(HealthComponent __instance, DamageInfo damageInfo, out SourceLabels.Origin __state)
	{
		__state = null;
		if (!NetworkServer.active)
		{
			return;
		}
		try
		{
			__state = SourceLabels.Enter(__instance, damageInfo);
		}
		catch (Exception error)
		{
			Plugin.Instance.LogFailure("Could not capture damage source", error);
		}
	}

	private static Exception Finalizer(HealthComponent __instance, SourceLabels.Origin __state, Exception __exception)
	{
		if (NetworkServer.active)
		{
			SourceLabels.Leave(__instance, __state);
		}
		return __exception;
	}
}
[HarmonyPatch(typeof(FogDamageController), "MyFixedUpdate")]
internal static class FogSourcePatch
{
	private static void Prefix()
	{
		SourceLabels.FogDepth++;
	}

	private static Exception Finalizer(Exception __exception)
	{
		SourceLabels.FogDepth--;
		return __exception;
	}
}
[HarmonyPatch(typeof(CharacterBody), "InflictLavaDamage")]
internal static class LavaSourcePatch
{
	private static void Prefix(CharacterBody __instance, out CharacterBody __state)
	{
		__state = SourceLabels.LavaBody;
		SourceLabels.LavaBody = __instance;
	}

	private static Exception Finalizer(CharacterBody __state, Exception __exception)
	{
		SourceLabels.LavaBody = __state;
		return __exception;
	}
}
[HarmonyPatch(typeof(DamageDealtMessage), "Deserialize")]
internal static class ClientNamePatch
{
	private static void Prefix(NetworkReader reader, out uint __state)
	{
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		__state = 0u;
		if ((Object)(object)Plugin.Instance == (Object)null || !Plugin.Instance.ClientFallback)
		{
			return;
		}
		uint position = reader.Position;
		try
		{
			reader.ReadNetworkId();
			reader.ReadSingle();
			NetworkInstanceId val = reader.ReadNetworkId();
			__state = ((NetworkInstanceId)(ref val)).Value;
		}
		catch (Exception)
		{
			__state = 0u;
		}
		finally
		{
			reader.SeekZero();
			for (uint num = 0u; num < position; num++)
			{
				reader.ReadByte();
			}
		}
	}

	private static void Postfix(DamageDealtMessage __instance, uint __state)
	{
		if (__state != 0)
		{
			SourceLabels.RestoreClientName(__instance, __state);
		}
	}
}
internal sealed class DeathSound
{
	private readonly Plugin plugin;

	private readonly AudioSource source;

	private readonly ConfigEntry<bool> enabled;

	private readonly ConfigEntry<float> volume;

	private readonly ConfigEntry<string> customFile;

	private AudioClip clip;

	internal DeathSound(Plugin owner)
	{
		//IL_005d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0067: Expected O, but got Unknown
		plugin = owner;
		enabled = ((BaseUnityPlugin)owner).Config.Bind<bool>("Sound", "Enabled", true, "Play the death sound once when your own player dies.");
		volume = ((BaseUnityPlugin)owner).Config.Bind<float>("Sound", "Volume", 0.65f, new ConfigDescription("Death sound volume.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
		customFile = ((BaseUnityPlugin)owner).Config.Bind<string>("Sound", "CustomFile", "", "Optional WAV or OGG file. Use an absolute path, or a path relative to the plugin folder. Empty uses the built-in tone. Restart after changing.");
		source = ((Component)owner).gameObject.AddComponent<AudioSource>();
		source.playOnAwake = false;
		source.spatialBlend = 0f;
		source.ignoreListenerPause = true;
		clip = CreateDefault();
		if (!string.IsNullOrWhiteSpace(customFile.Value))
		{
			((MonoBehaviour)owner).StartCoroutine(LoadCustom());
		}
	}

	private static AudioClip CreateDefault()
	{
		float[] array = new float[28664];
		for (int i = 0; i < array.Length; i++)
		{
			double num = (double)i / 44100.0;
			double num2 = Math.Min(num / 0.015, 1.0) * Math.Pow(1.0 - num / 0.6499999761581421, 2.0);
			double num3 = ((num < 0.28) ? 330.0 : 220.0);
			double num4 = ((num < 0.28) ? Math.Min((0.28 - num) / 0.02, 1.0) : Math.Min((num - 0.28) / 0.015, 1.0));
			array[i] = (float)(Math.Sin(Math.PI * 2.0 * num3 * num) * num2 * num4 * 0.35);
		}
		AudioClip obj = AudioClip.Create("Death Log", array.Length, 1, 44100, false);
		obj.SetData(array, 0);
		return obj;
	}

	private IEnumerator LoadCustom()
	{
		string fullPath;
		AudioType val;
		try
		{
			string value = customFile.Value;
			fullPath = Path.GetFullPath(Path.IsPathRooted(value) ? value : Path.Combine(Path.GetDirectoryName(typeof(Plugin).Assembly.Location), value));
			string text = Path.GetExtension(fullPath).ToLowerInvariant();
			if (text != ".wav" && text != ".ogg")
			{
				throw new InvalidDataException("Use a WAV or OGG file.");
			}
			if (!File.Exists(fullPath))
			{
				throw new FileNotFoundException("The configured sound file does not exist.", fullPath);
			}
			val = (AudioType)((text == ".ogg") ? 14 : 20);
		}
		catch (Exception error)
		{
			plugin.LogFailure("Using the default death sound", error);
			yield break;
		}
		UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(new Uri(fullPath).AbsoluteUri, val);
		try
		{
			yield return request.SendWebRequest();
			if ((int)request.result != 1)
			{
				plugin.LogFailure("Using the default death sound", new IOException(request.error));
				yield break;
			}
			AudioClip content = DownloadHandlerAudioClip.GetContent(request);
			if (!Object.op_Implicit((Object)(object)content))
			{
				plugin.LogFailure("Using the default death sound", new InvalidDataException("Audio could not be decoded."));
				yield break;
			}
			Object.Destroy((Object)(object)clip);
			clip = content;
		}
		finally
		{
			((IDisposable)request)?.Dispose();
		}
	}

	internal bool Play()
	{
		if (!enabled.Value || !Object.op_Implicit((Object)(object)clip) || !Object.op_Implicit((Object)(object)source))
		{
			return false;
		}
		source.PlayOneShot(clip, volume.Value);
		return true;
	}

	internal void Dispose()
	{
		if (Object.op_Implicit((Object)(object)source))
		{
			Object.Destroy((Object)(object)source);
		}
		if (Object.op_Implicit((Object)(object)clip))
		{
			Object.Destroy((Object)(object)clip);
		}
	}
}