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 Last Three Hits v1.0.0
BepInEx/plugins/LastThreeHits/LastThreeHits.dll
Decompiled 10 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using RoR2; 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.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("LastThreeHits")] [assembly: AssemblyTitle("LastThreeHits")] [assembly: AssemblyVersion("1.0.0.0")] namespace LastThreeHits; public enum ReportMode { Host, Local } [BepInPlugin("com.rawteam.lastthreehits", "Last Three Hits", "1.0.0")] public sealed class Plugin : BaseUnityPlugin { private struct PendingReport { internal DamageHistory History; internal string Player; internal float Due; } public const string PluginGuid = "com.rawteam.lastthreehits"; public const string PluginVersion = "1.0.0"; private ConfigEntry<ReportMode> reportMode; private readonly List<PendingReport> pending = new List<PendingReport>(4); private void Awake() { reportMode = ((BaseUnityPlugin)this).Config.Bind<ReportMode>("Reports", "Mode", ReportMode.Host, "Host: full reports for the lobby when hosting. Local: reports only in your chat, including when joining an unmodded host. Restart after changing this setting."); GlobalEventManager.onServerDamageDealt += OnDamage; GlobalEventManager.onCharacterDeathServer += OnDeath; GlobalEventManager.onClientDamageNotified += OnClientDamage; CharacterBody.onBodyStartGlobal += OnBodyStart; Run.onRunDestroyGlobal += OnRunEnd; ((BaseUnityPlugin)this).Logger.LogInfo((object)("Last Three Hits loaded. Mode: " + reportMode.Value)); } private void OnDestroy() { GlobalEventManager.onServerDamageDealt -= OnDamage; GlobalEventManager.onCharacterDeathServer -= OnDeath; GlobalEventManager.onClientDamageNotified -= OnClientDamage; CharacterBody.onBodyStartGlobal -= OnBodyStart; Run.onRunDestroyGlobal -= OnRunEnd; foreach (CharacterBody readOnlyInstances in CharacterBody.readOnlyInstancesList) { readOnlyInstances.OnBodyDeath = (Action<CharacterBody>)Delegate.Remove(readOnlyInstances.OnBodyDeath, new Action<CharacterBody>(OnClientDeath)); } pending.Clear(); } private void OnRunEnd(Run run) { pending.Clear(); } 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) { if (reportMode.Value == ReportMode.Local && !NetworkServer.active) { body.OnBodyDeath = (Action<CharacterBody>)Delegate.Combine(body.OnBodyDeath, new Action<CharacterBody>(OnClientDeath)); } } private void OnClientDamage(DamageDealtMessage message) { if (reportMode.Value != ReportMode.Local || NetworkServer.active || 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 OnClientDeath(CharacterBody body) { if (reportMode.Value == ReportMode.Local && !NetworkServer.active && IsLocalBody(body)) { DamageHistory history = GetHistory(body); if (!history.Reported && !history.Pending) { history.Pending = true; pending.Add(new PendingReport { History = history, Player = PlayerName(body), Due = Time.realtimeSinceStartup + 0.75f }); } } } private void Update() { for (int num = pending.Count - 1; num >= 0; num--) { PendingReport pendingReport = pending[num]; 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)text); Chat.AddMessage(text); } } } private static bool IsPlayerReport(DamageReport report) { if (NetworkServer.active && report != null && Object.op_Implicit((Object)(object)report.victimBody) && report.victimBody.isPlayerControlled) { return report.damageInfo != null; } 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_0087: 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_0098: 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) { if (history.LastDamageInfo != report.damageInfo) { history.Record(report); } history.Reported = true; string text = BuildReport(PlayerName(report.victimBody), history, clientData: false); ((BaseUnityPlugin)this).Logger.LogInfo((object)text); if (reportMode.Value == ReportMode.Host) { Chat.SendBroadcastChat((ChatMessageBase)new SimpleChatMessage { baseToken = text }); } else { Chat.AddMessage(text); } } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Could not show the death report: " + ex)); } } private 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(clientData ? "[Last Three Hits - local] " : "[Last Three Hits] ").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.ToString(); } internal static Hit Describe(DamageReport report) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0054: 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_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Invalid comparison between Unknown and I4 //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) DamageInfo damageInfo = report.damageInfo; DamageType damageType = damageInfo.damageType.damageType; string value = (Object.op_Implicit((Object)(object)report.attackerBody) ? Util.GetBestBodyName(((Component)report.attackerBody).gameObject) : (Object.op_Implicit((Object)(object)damageInfo.attacker) ? Util.GetBestBodyName(damageInfo.attacker) : null)); string value2; if ((damageType & 0x200000) != 0) { value = "Environment"; value2 = "Fall damage"; } else if ((damageType & int.MinValue) == 0 && (damageInfo.damageType.damageTypeExtended & int.MinValue) == 0) { value2 = (((damageType & 0x10000) != 0) ? "Void death" : (((int)damageInfo.dotIndex != -1) ? SplitName(((object)Unsafe.As<DotIndex, DotIndex>(ref damageInfo.dotIndex)/*cast due to .constrained prefix*/).ToString()) : (((damageType & 0x4000000) != 0) ? "Damage over time (type unknown)" : ((!Object.op_Implicit((Object)(object)damageInfo.inflictor) || !((Object)(object)damageInfo.inflictor != (Object)(object)damageInfo.attacker)) ? "Hit (attack unknown)" : SplitName(((Object)damageInfo.inflictor).name.Replace("(Clone)", "")))))); } else { value = "Environment"; value2 = "Out of bounds"; } return new Hit { Source = CleanLabel(value), Effect = CleanLabel(value2), Damage = (IsPositiveFinite(report.damageDealt) ? report.damageDealt.ToString("0.##", CultureInfo.InvariantCulture) : "unknown") }; } internal static Hit DescribeClient(DamageDealtMessage message) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_002e: 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_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005e: 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_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) DamageType damageType = message.damageType.damageType; string value = (Object.op_Implicit((Object)(object)message.attacker) ? Util.GetBestBodyName(message.attacker) : null); string effect = "Hit (attack unknown)"; if ((damageType & 0x200000) != 0) { value = "Environment"; effect = "Fall damage"; } else if ((damageType & int.MinValue) != 0 || (message.damageType.damageTypeExtended & int.MinValue) != 0) { value = "Environment"; effect = "Out of bounds"; } else if ((damageType & 0x10000) != 0) { effect = "Void death"; } else if ((damageType & 0x4000000) != 0) { effect = "Damage over time (type unknown)"; } return new Hit { Source = CleanLabel(value), Effect = effect, Damage = message.damage.ToString("0.##", CultureInfo.InvariantCulture) }; } 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"; } private 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; } internal sealed class DamageHistory : MonoBehaviour { private readonly Hit[] hits = new Hit[3]; private int next; internal int Count { get; private set; } internal DamageInfo LastDamageInfo { get; private set; } internal bool Reported { get; set; } internal bool Pending { get; set; } internal void Record(DamageReport report) { if (!Reported) { hits[next] = Plugin.Describe(report); next = (next + 1) % hits.Length; if (Count < hits.Length) { int count = Count + 1; Count = count; } LastDamageInfo = report.damageInfo; } } internal void RecordClient(DamageDealtMessage message) { if (!Reported) { hits[next] = Plugin.DescribeClient(message); 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]; } }