Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Death Log v1.1.0
BepInEx/plugins/LastThreeHits/LastThreeHits.dll
Decompiled 10 hours agousing 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); } } }