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 CombatMeter v1.1.2
plugins/CombatMeter.dll
Decompiled a month ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using DiagnosticDamageProbe.Analytics; using DiagnosticDamageProbe.Attribution; using DiagnosticDamageProbe.Configuration; using DiagnosticDamageProbe.Diagnostics; using DiagnosticDamageProbe.Encounter; using DiagnosticDamageProbe.History; using DiagnosticDamageProbe.Snapshot; using DiagnosticDamageProbe.Statistics; using DiagnosticDamageProbe.Transport; using DiagnosticDamageProbe.UI; using HarmonyLib; using Jotunn.Utils; using Newtonsoft.Json; using Newtonsoft.Json.Linq; 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.1.2.0")] [assembly: AssemblyInformationalVersion("1.1.2")] [assembly: AssemblyProduct("CombatMeter")] [assembly: AssemblyTitle("CombatMeter")] [assembly: AssemblyVersion("1.1.2.0")] namespace DiagnosticDamageProbe { internal static class AnalyticsRuntime { private sealed class DamageLink { internal HitData Hit; internal AnalyticsDamageEvidence Evidence; } private sealed class EffectState { internal readonly Guid Id = Guid.NewGuid(); internal bool BreakReported; internal ActorId Attacker; internal ActorId Victim; internal ActorEvidence Actor; } private sealed class RejectedHit { internal readonly Guid Token = Guid.NewGuid(); } internal sealed class AttackScope { internal readonly object Source; internal readonly ActorId Attacker; internal readonly Guid Token = Guid.NewGuid(); internal readonly Guid Epoch; internal readonly AttackScope Parent; internal Character Character; internal AttackScope(object source, ActorId attacker) { Source = source; Attacker = attacker; Epoch = _epoch; Parent = Current; } } private static ConditionalWeakTable<Character, DamageLink> _damage = new ConditionalWeakTable<Character, DamageLink>(); private static ConditionalWeakTable<SE_Shield, EffectState> _effects = new ConditionalWeakTable<SE_Shield, EffectState>(); private static Guid _epoch = Guid.NewGuid(); private static ConditionalWeakTable<HitData, RejectedHit> _rejectedHits = new ConditionalWeakTable<HitData, RejectedHit>(); [ThreadStatic] internal static AttackScope Current; internal static ActorId Id(Character c) { if (!Object.op_Implicit((Object)(object)c)) { return default(ActorId); } ZNetView component = ((Component)c).GetComponent<ZNetView>(); ZDO val = ((Object.op_Implicit((Object)(object)component) && component.IsValid()) ? component.GetZDO() : null); if (val != null) { return new ActorId(((ZDOID)(ref val.m_uid)).UserID, ((ZDOID)(ref val.m_uid)).ID); } return default(ActorId); } internal static ActorEvidence CaptureActor(Character actor, Character counterpart) { //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Invalid comparison between Unknown and I4 if (!Object.op_Implicit((Object)(object)actor)) { return null; } try { ZNetView component = ((Component)actor).GetComponent<ZNetView>(); ZDO val = ((Object.op_Implicit((Object)(object)component) && component.IsValid()) ? component.GetZDO() : null); if (val == null) { return null; } object obj; if (!Object.op_Implicit((Object)(object)ZNetScene.instance)) { obj = null; } else { GameObject prefab = ZNetScene.instance.GetPrefab(val.GetPrefab()); obj = ((prefab != null) ? ((Object)prefab).name : null); } string text = (string)obj; Character val2 = (Character)((counterpart is Player) ? ((object)counterpart) : ((object)Player.m_localPlayer)); bool hostile = Object.op_Implicit((Object)(object)val2) && !actor.IsPlayer() && BaseAI.IsEnemy(actor, val2); bool summon = (int)actor.GetFaction() == 11; return new ActorEvidence(new ActorId(((ZDOID)(ref val.m_uid)).UserID, ((ZDOID)(ref val.m_uid)).ID), text, actor.IsPlayer(), actor.IsTamed(), hostile, summon, text == "staff_greenroots_tentaroot"); } catch (Exception ex) { CombatAnalyticsProbe.Failed(ex); return null; } } internal static void TrackDamage(Character victim, HitData hit, AnalyticsDamageEvidence evidence) { try { if (CombatAnalyticsProbe.LastHit.GetValue(victim) == hit) { _damage.Remove(victim); _damage.Add(victim, new DamageLink { Hit = hit, Evidence = evidence }); } } catch (Exception ex) { CombatAnalyticsProbe.Failed(ex); } } internal static void ConfirmDeath(Character victim) { try { if (CombatAnalyticsProbe.Owner(victim) && !(victim.GetHealth() > 0f) && _damage.TryGetValue(victim, out var value) && value.Evidence.HpAfter == 0.0 && CombatAnalyticsProbe.LastHit.GetValue(victim) == value.Hit) { value.Evidence.ConfirmDeath(); } } catch (Exception ex) { CombatAnalyticsProbe.Failed(ex); } } internal static AttackScope BeginAttack(Attack attack) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown try { Character val = (Character)CombatAnalyticsProbe.AttackCharacter.GetValue(attack); return Current = new AttackScope(attack, CombatAnalyticsProbe.Owner(val) ? Id(val) : default(ActorId)) { Character = val }; } catch (Exception ex) { CombatAnalyticsProbe.Failed(ex); return null; } } internal static void EndAttack(AttackScope state) { if (state != null && state.Epoch == _epoch) { Current = state.Parent; } } internal static void RejectedDodge(Character victim, object source) { Player val = (Player)(object)((victim is Player) ? victim : null); if (val == null || victim.GetHealth() <= 0f || victim.IsDead() || victim.IsTeleporting() || victim.InCutscene()) { return; } HitData val2 = (HitData)((source is HitData) ? source : null); if (val2 != null) { if (!CombatAnalyticsProbe.Owner(victim)) { return; } ActorEvidence actorEvidence = CaptureActor(val2.GetAttacker(), victim); if (actorEvidence != null && actorEvidence.Eligible()) { ActorId victim2 = Id(victim); if (victim2.IsValid) { Plugin.PublishDefense(val.GetPlayerID(), new DefenseObservation(Guid.NewGuid(), DefenseKind.Dodge, _rejectedHits.GetOrCreateValue(val2).Token, actorEvidence.Id, victim2, applied: false, parry: false, rejected: true, 0.0, 0.0, 0.0, 0.0, 0.0, actorEvidence, ActivityTime())); } } } else if (Current != null && Current.Source == source) { ActorId victim3 = Id(victim); if (victim3.IsValid && Current.Attacker.IsValid) { Plugin.PublishDefense(val.GetPlayerID(), new DefenseObservation(Guid.NewGuid(), DefenseKind.Dodge, Current.Token, Current.Attacker, victim3, applied: false, parry: false, rejected: true, 0.0, 0.0, 0.0, 0.0, 0.0, CaptureActor(Current.Character, victim), ActivityTime())); } } } private static double ActivityTime() { return (double)Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency; } internal static void Block(Character victim, bool applied, bool parry, double removed, Guid eventId, ActorId attacker, Character source) { Player val = (Player)(object)((victim is Player) ? victim : null); if (val != null && applied && removed > 0.0) { long playerID = val.GetPlayerID(); ActorId victim2 = Id(victim); bool parry2 = parry; ActorEvidence activityAttacker = CaptureActor(source, victim); double? activityTime = ActivityTime(); Plugin.PublishDefense(playerID, new DefenseObservation(eventId, DefenseKind.Block, default(Guid), attacker, victim2, applied: true, parry2, rejected: false, removed, 0.0, 0.0, 0.0, 0.0, activityAttacker, activityTime)); } } internal static void ShieldHit(SE_Shield shield, Character victim, Guid eventId, double incoming, double outgoing, ActorId attacker, Character source) { Player val = (Player)(object)((victim is Player) ? victim : null); if (val != null) { EffectState orCreateValue = _effects.GetOrCreateValue(shield); orCreateValue.Attacker = attacker; orCreateValue.Victim = Id(victim); orCreateValue.Actor = CaptureActor(source, victim); Plugin.PublishDefense(val.GetPlayerID(), new DefenseObservation(eventId, DefenseKind.BarrierHit, orCreateValue.Id, attacker, orCreateValue.Victim, applied: false, parry: false, rejected: false, 0.0, incoming, outgoing, 0.0, 0.0, orCreateValue.Actor, ActivityTime())); } } internal static void ShieldDone(SE_Shield shield, Character victim, double consumed, double capacity) { Player val = (Player)(object)((victim is Player) ? victim : null); if (val != null && consumed > capacity) { EffectState orCreateValue = _effects.GetOrCreateValue(shield); if (!orCreateValue.BreakReported) { orCreateValue.BreakReported = true; Plugin.PublishDefense(val.GetPlayerID(), new DefenseObservation(Guid.NewGuid(), DefenseKind.BarrierDone, orCreateValue.Id, orCreateValue.Attacker, orCreateValue.Victim, applied: false, parry: false, rejected: false, 0.0, 0.0, 0.0, consumed, capacity, orCreateValue.Actor)); } } } internal static void Reset() { _damage = new ConditionalWeakTable<Character, DamageLink>(); _effects = new ConditionalWeakTable<SE_Shield, EffectState>(); _rejectedHits = new ConditionalWeakTable<HitData, RejectedHit>(); Current = null; _epoch = Guid.NewGuid(); } } [HarmonyPatch] internal static class AnalyticsAttackScopePatch { internal static IEnumerable<MethodBase> TargetMethods() { yield return AccessTools.Method(typeof(Attack), "DoMeleeAttack", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(Attack), "DoAreaAttack", (Type[])null, (Type[])null); } [HarmonyPrefix] internal static void Prefix(Attack __instance, out AnalyticsRuntime.AttackScope __state) { __state = AnalyticsRuntime.BeginAttack(__instance); } [HarmonyFinalizer] internal static Exception Finalizer(Exception __exception, AnalyticsRuntime.AttackScope __state) { AnalyticsRuntime.EndAttack(__state); return __exception; } } [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 HitData Hit; internal Guid AnalyticsInvocation; internal ActorEvidence VictimActor; internal ActorEvidence AttackerActor; 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_026a: Unknown result type (might be due to invalid IL or missing references) //IL_026f: Unknown result type (might be due to invalid IL or missing references) //IL_0278: Unknown result type (might be due to invalid IL or missing references) //IL_0289: Unknown result type (might be due to invalid IL or missing references) //IL_029a: Unknown result type (might be due to invalid IL or missing references) //IL_02ab: Unknown result type (might be due to invalid IL or missing references) //IL_02bc: Unknown result type (might be due to invalid IL or missing references) //IL_02cd: Unknown result type (might be due to invalid IL or missing references) //IL_02de: Unknown result type (might be due to invalid IL or missing references) //IL_02ef: Unknown result type (might be due to invalid IL or missing references) //IL_0300: Unknown result type (might be due to invalid IL or missing references) //IL_0311: Unknown result type (might be due to invalid IL or missing references) //IL_0322: Unknown result type (might be due to invalid IL or missing references) //IL_0333: Unknown result type (might be due to invalid IL or missing references) //IL_0503: Unknown result type (might be due to invalid IL or missing references) //IL_0589: Unknown result type (might be due to invalid IL or missing references) //IL_058f: Invalid comparison between Unknown and I4 //IL_0592: Unknown result type (might be due to invalid IL or missing references) //IL_0598: Invalid comparison between Unknown and I4 //IL_05bf: Unknown result type (might be due to invalid IL or missing references) //IL_05c5: 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("AnalyticsCallId", AnalyticsCorrelation.Current) .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); __state.Hit = hit; __state.AnalyticsInvocation = Guid.NewGuid(); __state.VictimActor = AnalyticsRuntime.CaptureActor(__instance, val); __state.AttackerActor = AnalyticsRuntime.CaptureActor(val, __instance); } } catch (Exception ex) { __state = null; Plugin.Warn("DamageProbeCaptureFailed phase=Prefix exception=" + ex.GetType().Name); } } [HarmonyPostfix] internal static void Postfix(Character __instance, CallState __state) { //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0122: 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 { 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; } AnalyticsDamageEvidence analyticsDamageEvidence = null; if (__state.Hit != null) { DamageTypes damage = __state.Hit.m_damage; analyticsDamageEvidence = new AnalyticsDamageEvidence(__state.AnalyticsInvocation, __state.VictimActor, __state.AttackerActor, new DamageComponents(damage.m_blunt, damage.m_slash, damage.m_pierce, damage.m_fire, damage.m_frost, damage.m_lightning, damage.m_poison, damage.m_spirit), __state.Hit.GetTotalDamage(), health); AnalyticsRuntime.TrackDamage(__instance, __state.Hit, analyticsDamageEvidence); } Plugin.Publish(__state.Session, __state.Facts, num, analyticsDamageEvidence); __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.LogDebug((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 static class CombatAnalyticsProbe { internal sealed class ShieldState { internal bool BreakLogged; } internal sealed class HitIdentity { internal string CallId; internal HitData Hit; internal string DeathId; } internal static readonly FieldInfo ShieldCharacter = AccessTools.Field(typeof(StatusEffect), "m_character"); internal static readonly FieldInfo ShieldDamage = AccessTools.Field(typeof(SE_Shield), "m_damage"); internal static readonly FieldInfo ShieldCapacity = AccessTools.Field(typeof(SE_Shield), "m_totalAbsorbDamage"); internal static readonly FieldInfo LastHit = AccessTools.Field(typeof(Character), "m_lastHit"); internal static readonly FieldInfo BlockTimer = AccessTools.Field(typeof(Humanoid), "m_blockTimer"); internal static readonly MethodInfo Blocker = AccessTools.Method(typeof(Humanoid), "GetCurrentBlocker", (Type[])null, (Type[])null); internal static readonly FieldInfo AttackCharacter = AccessTools.Field(typeof(Attack), "m_character"); internal static readonly FieldInfo ProjectileOwner = AccessTools.Field(typeof(Projectile), "m_owner"); internal static readonly FieldInfo AoeOwner = AccessTools.Field(typeof(Aoe), "m_owner"); internal static readonly ConditionalWeakTable<SE_Shield, ShieldState> Shields = new ConditionalWeakTable<SE_Shield, ShieldState>(); internal static readonly ConditionalWeakTable<Character, HitIdentity> Hits = new ConditionalWeakTable<Character, HitIdentity>(); internal static bool Enabled => Plugin.LoggingEnabled; internal static bool Owner(Character c) { if (!Object.op_Implicit((Object)(object)c)) { return false; } ZNetView component = ((Component)c).GetComponent<ZNetView>(); if (Object.op_Implicit((Object)(object)component) && component.IsValid()) { return component.IsOwner(); } return false; } internal static LogFields Fields(string hook, Character victim, HitData hit = null) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) LogFields logFields = new LogFields().Text("TimestampUtc", DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)).Text("ProcessRunId", Plugin.ProcessRunId).Text("PeerSessionId", ZDOMan.GetSessionID().ToString(CultureInfo.InvariantCulture)) .Text("Hook", hook) .Text("VictimZDOID", Object.op_Implicit((Object)(object)victim) ? ((object)victim.GetZDOID()/*cast due to .constrained prefix*/).ToString() : null) .Text("VictimToken", Object.op_Implicit((Object)(object)victim) ? victim.m_name : null) .Text("VictimObject", Object.op_Implicit((Object)(object)victim) ? ((Object)victim).name : null) .Flag("Owner", Owner(victim)); if (Object.op_Implicit((Object)(object)victim)) { ZNetView component = ((Component)victim).GetComponent<ZNetView>(); ZDO val = ((component != null) ? component.GetZDO() : null); logFields.Text("VictimPrefabHash", (val != null) ? val.GetPrefab().ToString(CultureInfo.InvariantCulture) : null); if (val != null && Object.op_Implicit((Object)(object)ZNetScene.instance)) { GameObject prefab = ZNetScene.instance.GetPrefab(val.GetPrefab()); logFields.Text("VictimPrefab", (prefab != null) ? ((Object)prefab).name : null); } logFields.Flag("VictimIsPlayer", victim.IsPlayer()).Flag("VictimIsTamed", victim.IsTamed()); Player val2 = (Player)(object)((victim is Player) ? victim : null); if (val2 != null) { logFields.Text("PlayerID", val2.GetPlayerID().ToString(CultureInfo.InvariantCulture)).Text("PlayerName", val2.GetPlayerName()); } } if (hit != null) { logFields.Text("AttackerZDOID", ((object)Unsafe.As<ZDOID, ZDOID>(ref hit.m_attacker)/*cast due to .constrained prefix*/).ToString()).Text("HitType", ((object)Unsafe.As<HitType, HitType>(ref hit.m_hitType)/*cast due to .constrained prefix*/).ToString()); Character attacker = hit.GetAttacker(); if (Object.op_Implicit((Object)(object)attacker)) { if (Object.op_Implicit((Object)(object)victim)) { logFields.Flag("VictimEnemyOfAttacker", BaseAI.IsEnemy(attacker, victim)); } LogFields logFields2 = logFields.Text("AttackerToken", attacker.m_name).Text("AttackerObject", ((Object)attacker).name); ZNetView component2 = ((Component)attacker).GetComponent<ZNetView>(); object value; if (component2 == null) { value = null; } else { ZDO zDO = component2.GetZDO(); value = ((zDO != null) ? zDO.GetPrefab().ToString(CultureInfo.InvariantCulture) : null); } logFields2.Text("AttackerPrefabHash", (string)value).Flag("AttackerIsPlayer", attacker.IsPlayer()).Flag("AttackerIsTamed", attacker.IsTamed()); Player val3 = (Player)(object)((attacker is Player) ? attacker : null); if (val3 != null) { logFields.Text("AttackerPlayerID", val3.GetPlayerID().ToString(CultureInfo.InvariantCulture)); } ZNetView component3 = ((Component)attacker).GetComponent<ZNetView>(); ZDO val4 = ((component3 != null) ? component3.GetZDO() : null); if (val4 != null && Object.op_Implicit((Object)(object)ZNetScene.instance)) { GameObject prefab2 = ZNetScene.instance.GetPrefab(val4.GetPrefab()); logFields.Text("AttackerPrefab", (prefab2 != null) ? ((Object)prefab2).name : null); } } } return logFields; } internal static void Emit(string family, LogFields fields) { try { ManualLogSource probeLog = Plugin.ProbeLog; if (probeLog != null) { probeLog.LogDebug((object)("CombatAnalytics" + family + "Probe " + fields)); } } catch { } } internal static void Components(LogFields f, string suffix, DamageTypes d) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0038: 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_0064: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0090: 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_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: 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) f.Number("Generic" + suffix, d.m_damage).Number("Blunt" + suffix, d.m_blunt).Number("Slash" + suffix, d.m_slash) .Number("Pierce" + suffix, d.m_pierce) .Number("Chop" + suffix, d.m_chop) .Number("Pickaxe" + suffix, d.m_pickaxe) .Number("Fire" + suffix, d.m_fire) .Number("Frost" + suffix, d.m_frost) .Number("Lightning" + suffix, d.m_lightning) .Number("Poison" + suffix, d.m_poison) .Number("Spirit" + suffix, d.m_spirit) .Number("NonPlayer" + suffix, d.m_nonPlayer); } internal static void Failed(Exception ex) { Plugin.Warn("CombatAnalyticsProbeFailed exception=" + ex.GetType().Name); } } [HarmonyPatch(typeof(Humanoid), "BlockAttack")] internal static class AnalyticsBlockPatch { internal sealed class State { internal State Parent; internal HitData Hit; internal LogFields Fields; internal bool TimedCandidate; internal bool Applied; internal float Prevented; internal Character Victim; internal readonly Guid Event = Guid.NewGuid(); } [ThreadStatic] internal static State Current; [HarmonyPrefix] internal static void Prefix(Humanoid __instance, HitData hit, out State __state) { __state = null; if (!CombatAnalyticsProbe.Owner((Character)(object)__instance)) { return; } try { object? obj = CombatAnalyticsProbe.Blocker.Invoke(__instance, null); ItemData val = (ItemData)((obj is ItemData) ? obj : null); float num = (float)CombatAnalyticsProbe.BlockTimer.GetValue(__instance); __state = new State { Parent = Current, Hit = hit, Victim = (Character)(object)__instance, TimedCandidate = (val != null && val.m_shared.m_timedBlockBonus > 1f && num != -1f && num < 0.25f), Fields = (CombatAnalyticsProbe.Enabled ? CombatAnalyticsProbe.Fields("Humanoid.BlockAttack", (Character)(object)__instance, hit).Number("Incoming", hit.GetTotalDamage()).Number("BlockTimer", num) : null) }; Current = __state; } catch (Exception ex) { CombatAnalyticsProbe.Failed(ex); } } [HarmonyPostfix] internal static void Postfix(HitData hit, bool __result, State __state) { if (__state == null) { return; } try { AnalyticsRuntime.Block(__state.Victim, __state.Applied, __state.TimedCandidate, __state.Prevented, __state.Event, new ActorId(((ZDOID)(ref hit.m_attacker)).UserID, ((ZDOID)(ref hit.m_attacker)).ID), hit.GetAttacker()); if (__state.Fields != null) { CombatAnalyticsProbe.Emit("Block", __state.Fields.Flag("MethodResult", __result).Flag("MitigationApplied", __state.Applied).Number("BlockDamageRemoved", __state.Prevented) .Flag("TimedBlockCandidate", __state.TimedCandidate) .Flag("SuccessfulTimedBlock", __state.Applied && __state.TimedCandidate) .Number("Outgoing", hit.GetTotalDamage())); } } catch (Exception ex) { CombatAnalyticsProbe.Failed(ex); } } [HarmonyFinalizer] internal static Exception Finalizer(Exception __exception, State __state) { if (__state != null) { Current = __state.Parent; } return __exception; } } [HarmonyPatch(typeof(HitData), "BlockDamage")] internal static class AnalyticsBlockDamagePatch { [HarmonyPrefix] internal static void Prefix(HitData __instance, out float __state) { __state = ((AnalyticsBlockPatch.Current?.Hit == __instance) ? __instance.GetTotalBlockableDamage() : (-1f)); } [HarmonyPostfix] internal static void Postfix(HitData __instance, float __state) { AnalyticsBlockPatch.State current = AnalyticsBlockPatch.Current; if (!(__state < 0f) && current != null && current.Hit == __instance) { current.Applied = true; current.Prevented += Math.Max(0f, __state - __instance.GetTotalBlockableDamage()); } } } [HarmonyPatch] internal static class AnalyticsDodgePatch { internal static IEnumerable<MethodBase> TargetMethods() { yield return AccessTools.Method(typeof(Character), "RPC_Damage", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(Attack), "DoMeleeAttack", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(Attack), "<DoAreaAttack>g__checkHits|27_0", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(Projectile), "IsValidTarget", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(Aoe), "ShouldHit", (Type[])null, (Type[])null); } internal static bool Observe(Character victim, string hook, object source) { //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Expected O, but got Unknown //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) bool flag = victim.IsDodgeInvincible(); if (flag) { try { AnalyticsRuntime.RejectedDodge(victim, source); } catch (Exception ex) { CombatAnalyticsProbe.Failed(ex); } } if (flag && CombatAnalyticsProbe.Enabled) { try { LogFields logFields = CombatAnalyticsProbe.Fields(hook, victim, (HitData)((source is HitData) ? source : null)).Flag("RejectedByIFrames", value: true).Text("Semantics", "rejected candidate; not deduplicated attack count") .Text("SourceInstance", RuntimeHelpers.GetHashCode(source).ToString(CultureInfo.InvariantCulture)) .Text("SourceType", source.GetType().FullName); Character val = ((!(source is Attack)) ? ((!(source is Projectile)) ? ((!(source is Aoe)) ? ((Character)null) : ((Character)CombatAnalyticsProbe.AoeOwner.GetValue(source))) : ((Character)CombatAnalyticsProbe.ProjectileOwner.GetValue(source))) : ((Character)CombatAnalyticsProbe.AttackCharacter.GetValue(source))); if (Object.op_Implicit((Object)(object)val)) { logFields.Text("AttackerZDOID", ((object)val.GetZDOID()/*cast due to .constrained prefix*/).ToString()).Text("AttackerToken", val.m_name); } CombatAnalyticsProbe.Emit("Dodge", logFields); } catch (Exception ex2) { CombatAnalyticsProbe.Failed(ex2); } } return flag; } [HarmonyTranspiler] internal static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod) { //IL_0046: 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_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Expected O, but got Unknown //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Expected O, but got Unknown List<CodeInstruction> list = new List<CodeInstruction>(); MethodInfo methodInfo = AccessTools.Method(typeof(Character), "IsDodgeInvincible", (Type[])null, (Type[])null); int num = 0; foreach (CodeInstruction instruction in instructions) { if (CodeInstructionExtensions.Calls(instruction, methodInfo)) { num++; CodeInstruction item = new CodeInstruction(instruction) { opcode = OpCodes.Ldstr, operand = __originalMethod.DeclaringType.Name + "." + __originalMethod.Name }; list.Add(item); list.Add(new CodeInstruction((__originalMethod.DeclaringType == typeof(Character)) ? OpCodes.Ldarg_2 : OpCodes.Ldarg_0, (object)null)); list.Add(new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(AnalyticsDodgePatch), "Observe", (Type[])null, (Type[])null))); } else { list.Add(instruction); } } if (num != 1) { throw new InvalidOperationException("Analytics dodge hook requires exactly one verified predicate: " + __originalMethod); } return list; } } [HarmonyPatch(typeof(SE_Shield), "OnDamaged")] internal static class AnalyticsBarrierDamagePatch { internal sealed class State { internal Character Victim; internal float Incoming; internal readonly Guid Event = Guid.NewGuid(); internal LogFields Fields; } [HarmonyPrefix] internal static void Prefix(SE_Shield __instance, HitData hit, out State __state) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown __state = null; try { Character val = (Character)CombatAnalyticsProbe.ShieldCharacter.GetValue(__instance); if (CombatAnalyticsProbe.Owner(val)) { __state = new State { Victim = val, Incoming = hit.GetTotalDamage() }; if (CombatAnalyticsProbe.Enabled) { __state.Fields = CombatAnalyticsProbe.Fields("SE_Shield.OnDamaged", val, hit).Text("ShieldName", ((Object)__instance).name).Text("ShieldToken", ((StatusEffect)__instance).m_name) .Text("ShieldInstance", RuntimeHelpers.GetHashCode(__instance).ToString(CultureInfo.InvariantCulture)) .Number("IncomingAtShield", hit.GetTotalDamage()) .Number("ConsumedBefore", (float)CombatAnalyticsProbe.ShieldDamage.GetValue(__instance)) .Number("Capacity", (float)CombatAnalyticsProbe.ShieldCapacity.GetValue(__instance)); } } } catch (Exception ex) { CombatAnalyticsProbe.Failed(ex); } } [HarmonyPostfix] internal static void Postfix(SE_Shield __instance, HitData hit, State __state) { if (__state == null) { return; } try { AnalyticsRuntime.ShieldHit(__instance, __state.Victim, __state.Event, __state.Incoming, hit.GetTotalDamage(), new ActorId(((ZDOID)(ref hit.m_attacker)).UserID, ((ZDOID)(ref hit.m_attacker)).ID), hit.GetAttacker()); if (__state.Fields != null) { CombatAnalyticsProbe.Emit("Barrier", __state.Fields.Number("ConsumedAfter", (float)CombatAnalyticsProbe.ShieldDamage.GetValue(__instance)).Number("OutgoingFromShield", hit.GetTotalDamage())); } } catch (Exception ex) { CombatAnalyticsProbe.Failed(ex); } } } [HarmonyPatch(typeof(SE_Shield), "IsDone")] internal static class AnalyticsBarrierBreakPatch { [HarmonyPostfix] internal static void Postfix(SE_Shield __instance, bool __result) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown if (!__result) { return; } try { Character val = (Character)CombatAnalyticsProbe.ShieldCharacter.GetValue(__instance); if (!CombatAnalyticsProbe.Owner(val)) { return; } float num = (float)CombatAnalyticsProbe.ShieldDamage.GetValue(__instance); float num2 = (float)CombatAnalyticsProbe.ShieldCapacity.GetValue(__instance); AnalyticsRuntime.ShieldDone(__instance, val, num, num2); if (CombatAnalyticsProbe.Enabled) { CombatAnalyticsProbe.ShieldState orCreateValue = CombatAnalyticsProbe.Shields.GetOrCreateValue(__instance); if (!orCreateValue.BreakLogged) { orCreateValue.BreakLogged = true; CombatAnalyticsProbe.Emit("Barrier", CombatAnalyticsProbe.Fields("SE_Shield.IsDone", val).Text("ShieldName", ((Object)__instance).name).Text("ShieldToken", ((StatusEffect)__instance).m_name) .Text("ShieldInstance", RuntimeHelpers.GetHashCode(__instance).ToString(CultureInfo.InvariantCulture)) .Number("Consumed", num) .Number("Capacity", num2) .Flag("CapacityBreak", num > num2)); } } } catch (Exception ex) { CombatAnalyticsProbe.Failed(ex); } } } [HarmonyPatch(typeof(SE_Shield), "SetLevel")] internal static class AnalyticsBarrierLevelPatch { [HarmonyPostfix] internal static void Postfix(SE_Shield __instance, int itemLevel, float skillLevel) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown if (!CombatAnalyticsProbe.Enabled) { return; } try { Character val = (Character)CombatAnalyticsProbe.ShieldCharacter.GetValue(__instance); if (CombatAnalyticsProbe.Owner(val)) { CombatAnalyticsProbe.Emit("Barrier", CombatAnalyticsProbe.Fields("SE_Shield.SetLevel", val).Text("ShieldName", ((Object)__instance).name).Text("ShieldToken", ((StatusEffect)__instance).m_name) .Text("ShieldInstance", RuntimeHelpers.GetHashCode(__instance).ToString(CultureInfo.InvariantCulture)) .Number("ItemLevel", itemLevel) .Number("SkillLevel", skillLevel) .Number("Consumed", (float)CombatAnalyticsProbe.ShieldDamage.GetValue(__instance)) .Number("Capacity", (float)CombatAnalyticsProbe.ShieldCapacity.GetValue(__instance))); } } catch (Exception ex) { CombatAnalyticsProbe.Failed(ex); } } } [HarmonyPatch(typeof(Character), "OnDeath")] internal static class AnalyticsDeathPatch { [HarmonyPrefix] internal static void Prefix(Character __instance) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown if (!CombatAnalyticsProbe.Owner(__instance)) { return; } AnalyticsRuntime.ConfirmDeath(__instance); if (!CombatAnalyticsProbe.Enabled) { return; } try { CombatAnalyticsProbe.Emit("Death", CombatAnalyticsProbe.Fields("Character.OnDeath", __instance, (HitData)CombatAnalyticsProbe.LastHit.GetValue(__instance)).Number("HP", __instance.GetHealth()).Text("FinalDamageCallId", FinalCall(__instance)) .Text("DeathObservationId", DeathId(__instance))); } catch (Exception ex) { CombatAnalyticsProbe.Failed(ex); } } private static string FinalCall(Character c) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown HitData val = (HitData)CombatAnalyticsProbe.LastHit.GetValue(c); if (val == null || !CombatAnalyticsProbe.Hits.TryGetValue(c, out var value) || value.Hit != val) { return null; } return value.CallId; } private static string DeathId(Character c) { CombatAnalyticsProbe.HitIdentity orCreateValue = CombatAnalyticsProbe.Hits.GetOrCreateValue(c); return orCreateValue.DeathId ?? (orCreateValue.DeathId = Guid.NewGuid().ToString("N")); } } [HarmonyPatch(typeof(Character), "ApplyDamage", new Type[] { typeof(HitData), typeof(bool), typeof(bool), typeof(DamageModifier) })] internal static class AnalyticsDamagePatch { internal sealed class State { internal LogFields Fields; internal float Hp; internal AnalyticsCorrelation Scope; internal string Id; } [HarmonyPrefix] [HarmonyPriority(800)] internal static void Prefix(Character __instance, HitData hit, out State __state) { //IL_007e: Unknown result type (might be due to invalid IL or missing references) __state = null; if (!CombatAnalyticsProbe.Enabled || !CombatAnalyticsProbe.Owner(__instance) || hit == null) { return; } try { string text = Guid.NewGuid().ToString("N"); __state = new State { Id = text, Hp = __instance.GetHealth(), Fields = CombatAnalyticsProbe.Fields("Character.ApplyDamage", __instance, hit).Text("CallId", text).Number("InputTotal", hit.GetTotalDamage()) }; CombatAnalyticsProbe.Components(__state.Fields, "Before", hit.m_damage); __state.Scope = new AnalyticsCorrelation(text); } catch (Exception ex) { CombatAnalyticsProbe.Failed(ex); } } [HarmonyPostfix] internal static void Postfix(Character __instance, HitData hit, State __state) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) if (__state == null || !Object.op_Implicit((Object)(object)__instance)) { return; } try { CombatAnalyticsProbe.Components(__state.Fields, "After", hit.m_damage); if (__instance.GetHealth() < __state.Hp && CombatAnalyticsProbe.LastHit.GetValue(__instance) == hit) { CombatAnalyticsProbe.HitIdentity orCreateValue = CombatAnalyticsProbe.Hits.GetOrCreateValue(__instance); orCreateValue.CallId = __state.Id; orCreateValue.Hit = hit; } CombatAnalyticsProbe.Emit("DamageType", __state.Fields.Number("HPBefore", __state.Hp).Number("HPAfter", __instance.GetHealth()).Number("EffectiveHpLossObserved", Math.Max(0f, __state.Hp - __instance.GetHealth())) .Number("PostProcessedTotal", hit.GetTotalDamage()) .Flag("StillOwner", CombatAnalyticsProbe.Owner(__instance))); } catch (Exception ex) { CombatAnalyticsProbe.Failed(ex); } } [HarmonyFinalizer] internal static Exception Finalizer(Exception __exception, State __state) { __state?.Scope?.Dispose(); return __exception; } } internal sealed class CompatibilityRuntime { internal const string Rpc = "CombatMeter.Compatibility.v1"; private static CompatibilityRuntime _instance; private readonly Plugin _owner; private readonly HashSet<ZNetPeer> _rejections = new HashSet<ZNetPeer>(); private ZNet _net; private readonly CompatibilityRegistry<ZNetPeer> _states = new CompatibilityRegistry<ZNetPeer>(); private readonly Dictionary<ZNetPeer, (ZRpc Rpc, Coroutine Timeout)> _bindings = new Dictionary<ZNetPeer, (ZRpc, Coroutine)>(); private CompatibilityRuntime(Plugin owner) { _owner = owner; } internal static void Initialize(Plugin owner) { Shutdown(); _instance = new CompatibilityRuntime(owner); Bind(ZNet.instance); } internal static void Shutdown() { _instance?.Clear(); _instance = null; } internal static void Bind(ZNet net) { if (_instance == null || !Object.op_Implicit((Object)(object)net) || _instance._net == net) { return; } _instance.Clear(); _instance._net = net; foreach (ZNetPeer item in new List<ZNetPeer>(net.GetPeers())) { Connected(net, item); } } internal static bool IsReady(ZNetPeer peer) { CompatibilityRuntime instance = _instance; if (instance != null && instance._net == ZNet.instance && peer != null) { CompatibilityHandshake compatibilityHandshake = instance._states.Get(peer); if (compatibilityHandshake != null && compatibilityHandshake.Ready && instance._bindings.TryGetValue(peer, out (ZRpc, Coroutine) value)) { return value.Item1 == peer.m_rpc; } } return false; } internal static Guid EvidenceScope(ZNetPeer peer, bool remote) { if (!IsReady(peer)) { return Guid.Empty; } CompatibilityHandshake compatibilityHandshake = _instance._states.Get(peer); if (!remote) { return compatibilityHandshake.Scope; } return compatibilityHandshake.RemoteScope; } internal static void Connected(ZNet net, ZNetPeer peer) { //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Expected O, but got Unknown Bind(net); CompatibilityRuntime i = _instance; if (i == null || !Object.op_Implicit((Object)(object)net) || peer == null || net != i._net || i._states.Get(peer) != null) { return; } if (!i._states.Start(peer, "1.1.2", 2, out var state)) { Plugin.Warn("CombatMeterCompatibilityRejected peer=" + peer.m_uid + " reason=HandshakeCapacity"); net.Disconnect(peer); return; } ZRpc rpc = peer.m_rpc; i._bindings[peer] = (rpc, null); try { rpc.Register<ZPackage>("CombatMeter.Compatibility.v1", (Action<ZRpc, ZPackage>)delegate(ZRpc sender, ZPackage pkg) { try { i.Receive(peer, state, sender, pkg); } catch { if (i.Current(peer, state, sender)) { state.Reject("HandshakeUnavailable"); i.Reject(peer, state); } } }); i._bindings[peer] = (rpc, ((MonoBehaviour)i._owner).StartCoroutine(i.Timeout(peer, state))); rpc.Invoke("CombatMeter.Compatibility.v1", new object[1] { (object)new ZPackage(state.Offer()) }); } catch { state.Reject("HandshakeUnavailable"); i.Reject(peer, state); } } private bool Current(ZNetPeer peer, CompatibilityHandshake state, ZRpc rpc) { if (_net == ZNet.instance && _states.Get(peer) == state && _bindings.TryGetValue(peer, out (ZRpc, Coroutine) value) && value.Item1 == rpc) { return peer.m_rpc == rpc; } return false; } private void Receive(ZNetPeer peer, CompatibilityHandshake state, ZRpc rpc, ZPackage package) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown if (!Current(peer, state, rpc)) { return; } bool ready = state.Ready; byte[] array = state.Receive((package != null && package.Size() <= 96) ? package.GetArray() : null); if (state.Rejection != null) { Reject(peer, state); return; } if (array != null) { rpc.Invoke("CombatMeter.Compatibility.v1", new object[1] { (object)new ZPackage(array) }); } if (!ready && state.Ready) { CancelTimer(peer); Plugin.TransportLog("CombatMeterCompatibilityAccepted peer=" + peer.m_uid + " localVersion=" + state.LocalVersion + " remoteVersion=" + state.RemoteVersion + " localCompat=" + state.LocalCompatibility + " remoteCompat=" + state.RemoteCompatibility); } } private IEnumerator Timeout(ZNetPeer peer, CompatibilityHandshake state) { yield return (object)new WaitForSecondsRealtime(10f); if (Current(peer, state, peer.m_rpc) && !state.Ready) { state.Timeout(); Reject(peer, state); } } private void Reject(ZNetPeer peer, CompatibilityHandshake state) { if (_states.Get(peer) != state || !_rejections.Add(peer)) { return; } CancelTimer(peer); Plugin.Warn("CombatMeterCompatibilityRejected peer=" + peer.m_uid + " reason=" + state.Rejection + " localVersion=" + state.LocalVersion + " remoteVersion=" + (state.RemoteVersion ?? "unknown") + " localCompat=" + state.LocalCompatibility + " remoteCompat=" + state.RemoteCompatibility + ". Install the same CombatMeter build on host and clients."); try { if (_net.IsServer()) { peer.m_rpc.Invoke("Error", new object[1] { 3 }); } else { ZNet.SetExternalError((ConnectionStatus)3); peer.m_rpc.Invoke("Disconnect", Array.Empty<object>()); } } finally { _net.Disconnect(peer); } } private void CancelTimer(ZNetPeer peer) { if (_bindings.TryGetValue(peer, out (ZRpc, Coroutine) value) && value.Item2 != null) { ((MonoBehaviour)_owner).StopCoroutine(value.Item2); _bindings[peer] = (value.Item1, null); } } internal static void Disconnected(ZNet net, ZNetPeer peer) { CompatibilityRuntime instance = _instance; if (instance != null && net == instance._net && peer != null) { instance.CancelTimer(peer); instance._rejections.Remove(peer); instance._states.Remove(peer); instance._bindings.Remove(peer); } } internal static void Stop(ZNet net) { if (_instance != null && _instance._net == net) { _instance.Clear(); } } private void Clear() { foreach (var value in _bindings.Values) { if (value.Timeout != null) { ((MonoBehaviour)_owner).StopCoroutine(value.Timeout); } } _bindings.Clear(); _rejections.Clear(); _states.Clear(); _net = 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 string DetailsRpc = "CombatMeter.CombatDetails.v1"; internal const string HistoryRequestRpc = "CombatMeter.CombatHistory.v1.Request"; internal const string HistoryResponseRpc = "CombatMeter.CombatHistory.v1.Response"; internal const string StatisticsRequestRpc = "CombatMeter.CombatHistory.Statistics.v1.Request"; internal const string StatisticsResponseRpc = "CombatMeter.CombatHistory.Statistics.v1.Response"; internal const string StatisticsSnapshotRequestRpc = "CombatMeter.CombatStatistics.v1.Request"; internal const string StatisticsSnapshotResponseRpc = "CombatMeter.CombatStatistics.v1.Response"; internal const string HistoryRevisionRpc = "CombatMeter.CombatStatistics.v1.Changed"; private StatisticsRevision _statisticsObserved; private StatisticsRevision _statisticsDeferred; private StatisticsRevision _statisticsApplied; private readonly HashSet<long> _statisticsPendingSnapshots = new HashSet<long>(); private long _statisticsPublishedHistory = -1L; private long _statisticsPublishedSession = -1L; private HistoryReadService _historyRead; private readonly HistoryReadCache _historyCache = new HistoryReadCache(Plugin.HistoryLog); private readonly Dictionary<long, (ZNetPeer Peer, Guid Connection, long Request, double Window, int Count)> _historyReaders = new Dictionary<long, (ZNetPeer, Guid, long, double, int)>(); private readonly Dictionary<long, (double Expires, Action<HistoryReadResult, string> Callback)> _historyProbeRequests = new Dictionary<long, (double, Action<HistoryReadResult, string>)>(); internal const double SnapshotIntervalSeconds = 0.5; private readonly ConditionalWeakTable<ZRoutedRpc, object> _registrations = new ConditionalWeakTable<ZRoutedRpc, object>(); private ZNet _net; private ZNetPeer _historyServerConnection; private ZRoutedRpc _rpc; private WeakReference<ZNet> _blockedNet; private CommitSession _session; private EncounterManager _encounter; private CombatClusterManager _clusters; private CombatMeterPerformance _performance; private SessionPlayerIdentity _identities; private Func<long, string> _displayName; private EncounterAnalyticsSnapshot _lastAnalyticsSummary; private readonly Func<string, bool> _knownSummon; private readonly Func<long> _performanceTimestamp; private MagicAttributionResolver _attribution; private EventSequence _attributionSequence; private double _clockAnchorMonotonic; private long _clockAnchorUtcTicks; private readonly CombatSnapshotStore _snapshotStore = new CombatSnapshotStore(); private readonly CombatDetailsStore _detailsStore = new CombatDetailsStore(); private readonly CombatDetailsRetention _detailsRetention = new CombatDetailsRetention(); private Guid _detailsExpectedEpoch; private EncounterHistoryStore _history; private readonly Func<EncounterHistoryStore> _historyFactory; 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 readonly Dictionary<EventId, List<(long Player, DefenseObservation Observation)>> _pendingDefense = new Dictionary<EventId, List<(long, DefenseObservation)>>(); private int _pendingDefenseCount; 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 const string AnalyticsRpc = "CombatMeter.AnalyticsEvidence.v1"; internal const string AnalyticsAckRpc = "CombatMeter.AnalyticsEvidence.v1.Ack"; private readonly Queue<(AnalyticsWireEvent Event, double Expires)> _analyticsOutbox = new Queue<(AnalyticsWireEvent, double)>(); private CommitAcceptor _analyticsReplay = new CommitAcceptor(); private long _analyticsSequence; private double _analyticsNextSend; private readonly Dictionary<(long, Guid, Guid), (AnalyticsDamageEvidence Evidence, EventId DamageId, double Expires)> _analyticsDeaths = new Dictionary<(long, Guid, Guid), (AnalyticsDamageEvidence, EventId, double)>(); private readonly Queue<((long, Guid, Guid) Key, double Expires)> _analyticsDeathExpiry = new Queue<((long, Guid, Guid), double)>(); internal HistoryReadService HistoryRead => _historyRead; internal HistoryReadCache HistoryCache => _historyCache; internal EncounterManager Encounter => _encounter; internal CombatClusterManager Clusters => _clusters; internal EncounterAnalyticsSnapshot LastFinishedAnalytics => _clusters?.LastFinished?.Encounter.LastFinishedAnalytics; internal CombatMeterPerformance Performance => _performance; internal MagicAttributionResolver Attribution => _attribution; internal int AttributionPendingCount => _attributionOutbox.Count; internal CombatSnapshotStore SnapshotStore => _snapshotStore; internal CombatDetailsStore DetailsStore => _detailsStore; internal EncounterHistoryStore HistoryStore => _history; internal bool HasActiveSession => _session != null; internal event Action<StatisticsRevision> HistoryChanged; internal DamageCommitTransport(Func<double> now = null, Func<double> softTimeout = null, Func<double> recoveryTimeout = null, Func<double> dpsIdleTimeout = null, Func<long> performanceTimestamp = null, Func<EncounterHistoryStore> historyFactory = null) { _now = now ?? ((Func<double>)(() => (double)Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency)); _softTimeout = softTimeout; _recoveryTimeout = recoveryTimeout; _dpsIdleTimeout = dpsIdleTimeout; _performanceTimestamp = performanceTimestamp; _historyFactory = historyFactory; _detailsRetention.CompletedSnapshot += CaptureHistory; _knownSummon = (string id) => _attribution != null && _attribution.Summons.TryResolve(id, out var _); } 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 { RegisterAnalytics(rpc); 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); }); rpc.Register<ZPackage>("CombatMeter.CombatDetails.v1", (Action<long, ZPackage>)delegate(long sender, ZPackage pkg) { ReceiveDetails(rpc, sender, pkg); }); rpc.Register<ZPackage>("CombatMeter.CombatHistory.v1.Request", (Action<long, ZPackage>)delegate(long sender, ZPackage pkg) { ReceiveHistoryRequest(rpc, sender, pkg); }); rpc.Register<ZPackage>("CombatMeter.CombatHistory.v1.Response", (Action<long, ZPackage>)delegate(long sender, ZPackage pkg) { ReceiveHistoryResponse(rpc, sender, pkg); }); rpc.Register<ZPackage>("CombatMeter.CombatHistory.Statistics.v1.Request", (Action<long, ZPackage>)delegate(long sender, ZPackage pkg) { ReceiveHistoryRequest(rpc, sender, pkg, statistics: true); }); rpc.Register<ZPackage>("CombatMeter.CombatHistory.Statistics.v1.Response", (Action<long, ZPackage>)delegate(long sender, ZPackage pkg) { ReceiveHistoryResponse(rpc, sender, pkg, statistics: true); }); rpc.Register<ZPackage>("CombatMeter.CombatStatistics.v1.Changed", (Action<long, ZPackage>)delegate(long sender, ZPackage pkg) { ReceiveHistoryRevision(rpc, sender, pkg); }); rpc.Register<ZPackage>("CombatMeter.CombatStatistics.v1.Request", (Action<long, ZPackage>)delegate(long sender, ZPackage pkg) { ReceiveHistoryRequest(rpc, sender, pkg, statistics: false, snapshot: true); }); rpc.Register<ZPackage>("CombatMeter.CombatStatistics.v1.Response", (Action<long, ZPackage>)delegate(long sender, ZPackage pkg) { ReceiveHistoryResponse(rpc, sender, pkg, statistics: false, snapshot: true); }); } 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 && !CompatibilityRuntime.IsReady(_net.GetServerPeer())) { return false; } if (!flag && _session != null && _historyServerConnection != _net.GetServerPeer()) { Close("ServerConnectionChanged"); } 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, analyticsEnabled: false) : null); _clusters = (flag ? new CombatClusterManager(_encounterSettings) : null); if (flag) { _clusters.Finished += RetainDetails; } _identities = (flag ? new SessionPlayerIdentity(Plugin.TransportLog, ObserveDisplayName) : null); _displayName = (flag ? new Func<long, string>(_identities.ResolveDisplayName) : null); if (flag) { _identities.Sync(_net.GetPeers(), sessionID, HostPlayerIdentity.ResolveLocal(sessionID)); } if (flag) { LogEncounterSettings("CombatEncounterSettings"); } _attribution = (flag ? new MagicAttributionResolver() : null); _attributionSequence = new EventSequence(sessionID, Guid.NewGuid()); _clockAnchorMonotonic = _now(); _clockAnchorUtcTicks = DateTime.UtcNow.Ticks; _snapshotStore.Clear(); _detailsStore.Clear(); _detailsRetention.Clear(); _detailsExpectedEpoch = Guid.Empty; _snapshotEpoch = Guid.NewGuid(); if (flag) { try { _history = (_historyFactory ?? new Func<EncounterHistoryStore>(HistoryRuntime.Open))(); } catch (Exception ex) { Plugin.HistoryLog("CombatHistoryLoadFailed reason=" + ex.GetType().Name + " historyDisabled=true"); } } _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); _historyServerConnection = (flag ? null : _net.GetServerPeer()); if (flag && _history != null) { _historyRead = new HistoryReadService(_history, _snapshotEpoch, new DateTimeOffset(_clockAnchorUtcTicks, TimeSpan.Zero), Plugin.HistoryLog); _history.Accepted += NotifyHistoryChanged; } _historyCache.Bind(flag ? sessionID : _net.GetServerPeer().m_uid); 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, AnalyticsDamageEvidence analytics = null) { if (!EnsureSession() || observedPeer != _session.Peer) { Plugin.Warn("DamageCommitObservationRejected reason=NoActiveSession"); return; } CombatMeterPerformance performance = GetPerformance(); if (performance != null) { performance.DamageObserved++; } if (!_session.IsHost && analytics != null) { ZNetPeer connection = _historyServerConnection; Guid epoch = _detailsExpectedEpoch; analytics.WhenDeath(delegate { QueueRemoteDeath(analytics.Invocation, connection, epoch); }); } _session.Observe(facts, loss, DateTime.UtcNow.Ticks, analytics); Pump(publishSnapshot: false); } internal void ObserveDefense(long observedPeer, long playerId, DefenseObservation observation) { if (!EnsureSession() || observedPeer != _session.Peer || playerId == 0L) { return; } if (!_session.IsHost) { QueueRemoteDefense(playerId, observation); return; } _clusters.Update(_now()); if (_clusters.TryGetClusterForPlayer(playerId, out var cluster)) { ApplyDefense(cluster, playerId, observation, _now()); } else { if (observation == null || !observation.Attacker.IsValid || !observation.Victim.IsValid || _pendingDefenseCount >= 256) { return; } foreach (var item in _pendingSummon) { DamageFacts facts = item.Commit.Facts; if (facts.VictimPlayerId == playerId && facts.VictimCreator == observation.Victim.Creator && facts.VictimObject == observation.Victim.Object && facts.AttackerCreator == observation.Attacker.Creator && facts.AttackerObject == observation.Attacker.Object) { if (!_pendingDefense.TryGetValue(item.Commit.Id, out List<(long, DefenseObservation)> value)) { value = (_pendingDefense[item.Commit.Id] = new List<(long, DefenseObservation)>()); } value.Add((playerId, observation)); _pendingDefenseCount++; break; } } } } private void ApplyDefense(CombatCluster cluster, long playerId, DefenseObservation observation, double now) { if (cluster.Encounter.Analytics.AcceptDefense(playerId, observation)) { ActorEvidence activityAttacker = observation.ActivityAttacker; if (observation.ActivityTime.HasValue && observation.Victim.IsValid && activityAttacker != null && activityAttacker.Id.Equals(observation.Attacker) && activityAttacker.Eligible(_knownSummon) && cluster.Encounter.Analytics.ContainsEnemy(activityAttacker.Id) && _clusters.TryGetMembership(CombatNode.Combatant(activityAttacker.Id.Creator, activityAttacker.Id.Object), out var cluster2) && cluster == cluster2 && cluster.Encounter.TryRefreshExistingCombatActivity(playerId, now, observation.ActivityTime.Value) && Plugin.LoggingEnabled) { Plugin.TransportTrace("CombatAnalyticsDefenseActivity " + new LogFields().Text("EncounterId", cluster.Id.ToString(CultureInfo.InvariantCulture)).Text("PlayerID", playerId.ToString(CultureInfo.InvariantCulture)).Text("Kind", (observation.Kind != DefenseKind.Block) ? ((observation.Kind == DefenseKind.Dodge) ? "Dodge" : "Barrier") : (observation.Parry ? "Parry" : "Block")) .Number("ActivityTime", observation.ActivityTime.Value) .Flag("ActivityRefreshed", value: true)); } } } internal void Update() { if (_disposed) { return; } foreach (long item in new List<long>(_historyProbeRequests.Keys)) { if (_now() >= _historyProbeRequests[item].Expires) { CompleteHistoryProbe(item, null, "Timeout"); } } 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); PumpAnalytics(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(); } if (publishSnapshot && Plugin.LoggingEnabled) { EncounterAnalyticsSnapshot lastFinishedAnalytics = LastFinishedAnalytics; if (lastFinishedAnalytics != null && lastFinishedAnalytics != _lastAnalyticsSummary) { _lastAnalyticsSummary = lastFinishedAnalytics; Plugin.TransportLog("CombatAnalyticsCoreSummary " + AnalyticsDiagnostics.Format(lastFinishedAnalytics).Text("HostEpoch", _snapshotEpoch.ToString("N"))); } } } 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_0214: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Expected O, but got Unknown CombatMeterPerformance performance = GetPerformance(); long num = ((performance != null) ? PerformanceTimestamp() : 0); try { _nextSnapshotAt = now + 0.5; if (_history != null) { try { _detailsRetention.RefreshCompletedRevisions(_snapshotEpoch); } catch (Exception ex) { Plugin.HistoryLog("CombatHistoryEncounterRejected reason=RevisionRefresh:" + ex.GetType().Name); } } _identities.Sync(_net.GetPeers(), _session.Peer, HostPlayerIdentity.ResolveLocal(_session.Peer)); if (IsCurrentPerformance(performance)) { performance.ReadyPeers = 0; } long sequence = checked(++_snapshotSequence); long? num2 = ResolveSnapshotPlayer(_session.Peer, local: true); CombatSnapshot snapshot = BuildRecipientSnapshot(sequence, num2, now); SnapshotApplyResult snapshotApplyResult = _snapshotStore.Apply(snapshot, _session.Peer, _session.Peer); if (snapshotApplyResult != SnapshotApplyResult.Accepted) { throw new InvalidOperationException("LocalSnapshot" + snapshotApplyResult); } LogSnapshotChange("CombatSnapshotAppliedLocal", snapshot); PublishDetails(sequence, _session.Peer, num2, now, local: true); if (ZNet.IsSinglePlayer) { return; } HashSet<long> hashSet = new HashSet<long>(); foreach (ZNetPeer peer in _net.GetPeers()) { if (peer != null && peer.IsReady() && CompatibilityRuntime.IsReady(peer) && peer.m_uid != 0L && peer.m_uid != _session.Peer && hashSet.Add(peer.m_uid)) { if (IsCurrentPerformance(performance)) { performance.ReadyPeers++; } long? num3 = ResolveSnapshotPlayer(peer.m_uid, local: false); CombatSnapshot snapshot2 = BuildRecipientSnapshot(sequence, num3, now); long num4 = (IsCurrentPerformance(performance) ? PerformanceTimestamp() : 0); ZPackage val = new ZPackage(CombatSnapshotCodec.Encode(snapshot2)); if (IsCurrentPerformance(performance)) { performance.Serialization.Record(PerformanceTimestamp() - num4); } _rpc.InvokeRoutedRPC(peer.m_uid, "CombatMeter.CombatSnapshot.v2", new object[1] { val }); if (IsCurrentPerformance(performance)) { performance.RecordSend(val.Size()); } LogSnapshotChange("CombatSnapshotPublished", snapshot2); PublishDetails(sequence, peer.m_uid, num3, now, local: false); } } } finally { if (IsCurrentPerformance(performance)) { performance.SnapshotCycle.Record(PerformanceTimestamp() - num); } } } private DateTimeOffset HistoryTime(double monotonic) { return new DateTimeOffset(checked(_clockAnchorUtcTicks + (long)Math.Round((monotonic - _clockAnchorMonotonic) * 10000000.0)), TimeSpan.Zero); } private void CaptureHistory(EncounterManager source, CombatDetailsEncounter previous, CombatDetailsEncounter completed) { if (_history == null) { return; } CommitSession session = _session; if (session == null || !session.IsHost) { return; } try { if (previous != null) { _history.ApplyTerminalRevision(previous, completed); return; } DateTimeOffset finalized = HistoryTime(Math.Max(_now(), source.EndTime)); _history.AddCompletedEncounter(new HistoryEncounter(completed, HistoryTime(source.StartTime), HistoryTime(source.EndTime), finalized, HistoryRuntime.DayKey(finalized))); } catch (Exception ex) { Plugin.HistoryLog("CombatHistoryEncounterRejected reason=" + ex.GetType().Name); } } private void RetainDetails(CombatCluster cluster) { try { _detailsRetention.Completed(_snapshotEpoch, cluster); } catch (Exception ex) when (ex is InvalidDataException || ex is InvalidOperationException || ex is OverflowException || ex is ArgumentException) { _detailsRetention.Unavailable(cluster); Plugin.SnapshotLog("CombatDetailsSnapshotRejected reason=CompletedBuild " + ex.GetType().Name); } } private void PublishDetails(long sequence, long target, long? player, double now, bool local) { //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Expected O, but got Unknown //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Expected O, but got Unknown try { CombatDetailsSnapshot combatDetailsSnapshot = _detailsRetention.ForPlayer(_session.Peer, _snapshotEpoch, sequence, _clusters, player, now); byte[] array = CombatDetailsCodec.Encode(combatDetailsSnapshot); string hash = DetailsHash(array); DetailsLog("CombatDetailsSnapshotBuilt", combatDetailsSnapshot, target, array.Length, hash); if (local) { CombatDetailsEncounter lastCompletedEncounter = _detailsStore.LastCompletedEncounter; SnapshotApplyResult snapshotApplyResult = _detailsStore.Apply(combatDetailsSnapshot, _session.Peer, _session.Peer); if (snapshotApplyResult != SnapshotApplyResult.Accepted) { throw new InvalidOperationException("DetailsLocal" + snapshotApplyResult); } DetailsLog("CombatDetailsSnapshotAppliedLocal", combatDetailsSnapshot, target, array.Length, hash); if (lastCompletedEncounter != _detailsStore.LastCompletedEncounter && _detailsStore.LastCompletedEncounter != null) { DetailsLog("CombatDetailsSnapshotStoredCompleted", combatDetailsSnapshot, target, array.Length, hash); } } else { _rpc.InvokeRoutedRPC(target, "CombatMeter.CombatDetails.v1", new object[1] { (object)new ZPackage(array) }); DetailsLog("CombatDetailsSnapshotSent", combatDetailsSnapshot, target, array.Length, hash); } if (!player.HasValue) { DetailsLog("CombatDetailsSnapshotCleared", combatDetailsSnapshot, target, array.Length); } } catch (Exception ex) when (ex is InvalidDataException || ex is InvalidOperationException || ex is OverflowException || ex is ArgumentException) { Plugin.SnapshotLog("CombatDetailsSnapshotRejected reason=BuildBounds target=" + target + " type=" + ex.GetType().Name); CombatDetailsSnapshot snapshot = new CombatDetailsSnapshot(_session.Peer, _snapshotEpoch, sequence, 0L, null, null); if (local) { _detailsStore.Apply(snapshot, _session.Peer, _session.Peer); return; } _rpc.InvokeRoutedRPC(target, "CombatMeter.CombatDetails.v1", new object[1] { (object)new ZPackage(CombatDetailsCodec.Encode(snapshot)) }); } } internal long RequestHistory(HistoryQuery query, int offset = 0, int limit = 20, string key = "", Action<HistoryReadResult, string> completed = null) { return RequestHistoryCore(query, offset, limit, key, completed, statistics: false); } internal long RequestStatistics(HistoryQuery query, int offset = 0, int limit = 20, string key = "", Action<HistoryReadResult, string> completed = null) { return RequestHistoryCore(query, offset, limit, key, completed, statistics: true); } internal long RequestStatisticsSnapshot(Action<StatisticsSnapshot, string> completed) { return RequestHistoryCore(HistoryQuery.Session, 0, 20, "", delegate(HistoryReadResult a, string failure) { completed((a == null) ? null : new StatisticsSnapshot(a), failure); }, statistics: false, snapshot: true); } private long RequestHistoryCore(HistoryQuery query, int offset, int limit, string key, Action<HistoryReadResult, string> completed, bool statistics, bool snapshot = false) { //IL_02a7: Unknown result type (might be due to invalid IL or missing references) //IL_02ad: Expected O, but got Unknown if (!HistoryTransportReady(_rpc)) { throw new InvalidOperationException("HistoryTransportUnavailable"); } if (_session.IsHost && _historyRead == null) { throw new InvalidOperationException("HistoryUnavailable"); } if (!_session.IsHost && (_net.GetServerPeer() == null || !_net.GetServerPeer().IsReady())) { throw new InvalidOperationException("HostNotReady"); } long num = (_session.IsHost ? _session.Peer : _net.GetServerPeer().m_uid); _historyCache.Bind(num); if (!_session.IsHost && _snapshotStore.TryGetLatest(out var snapshot2)) { _historyCache.ObserveEpoch(snapshot2.SnapshotEpoch); } _historyCache.Bind(num); if (completed != null && _historyProbeRequests.Count >= 8) { throw new InvalidOperationException("ProbeCapacity"); } HistoryReadRequest historyReadRequest = _historyCache.Begin(query, _now(), offset, limit, key, (byte)(snapshot ? 2 : (statistics ? 1 : 0))); if (completed != null) { _historyProbeRequests.Add(historyReadRequest.RequestId, (_now() + 30.0, completed)); } if (snapshot) { _statisticsPendingSnapshots.Add(historyReadRequest.RequestId); } if (snapshot) { Plugin.HistoryLog("CombatStatisticsSnapshotRequested request=" + historyReadRequest.RequestId + " connection=" + historyReadRequest.ConnectionId.ToString("N")); } try { if (_session.IsHost) { byte[] bytes = (snapshot ? StatisticsSnapshotCodec.Encode(_historyRead.ReadStatisticsSnapshot(historyReadRequest)) : (statistics ? StatisticsReadCodec.Encode(_historyRead.Read(historyReadRequest)) : HistoryReadCodec.EncodeBoundedResponse(_historyRead.Read(historyReadRequest)))); if (_historyCache.Accept(num, bytes, _now(), out var accepted, statistics, snapshot)) { if (snapshot) { LogStatisticsSnapshot(accepted, bytes); } CompleteHistoryProbe(historyReadRequest.RequestId, accepted, null); } else { CompleteHistoryProbe(historyReadRequest.RequestId, null, "ResponseRejected"); } } else { ZNetPeer serverPeer = _net.GetServerPeer(); if (serverPeer == null || !serverPeer.IsReady() || num == 0L) { throw new InvalidOperationException("HostNotReady"); } _rpc.InvokeRoutedRPC(num, snapshot ? "CombatMeter.CombatStatistics.v1.Request" : (statistics ? "CombatMeter.CombatHistory.Statistics.v1.Request" : "CombatMeter.CombatHistory.v1.Request"), new object[1] { (object)new ZPackage(HistoryReadCodec.EncodeRequest(historyReadRequest)) }); } return historyReadRequest.RequestId; } catch { CancelHistoryRequest(historyReadRequest.RequestId); throw; } } private bool CompatibleSender(long sender) { if (!Object.op_Implicit((Object)(object)_net) || sender == 0L) { return false; } if (!_net.IsServer()) { ZNetPeer serverPeer = _net.GetServerPeer(); if (serverPeer != null && serverPeer.m_uid == sender) { return CompatibilityRuntime.IsReady(serverPeer); } return false; } foreach (ZNetPeer peer in _net.GetPeers()) { if (peer != null && peer.m_uid == sender && CompatibilityRuntime.IsReady(peer)) { return true; } } return false; } private bool HistoryTransportReady(ZRoutedRpc rpc) { if (!_disposed && _session != null && Object.op_Implicit((Object)(object)_net) && !IsBlocked(_net) && _net == ZNet.instance && rpc == _rpc && rpc == ZRoutedRpc.instance && ZDOMan.instance != null && ZDOMan.GetSessionID() == _session.Peer && _net.IsServer() == _session.IsHost) { if (!_session.IsHost) { if (CompatibilityRuntime.IsReady(_net.GetServerPeer())) { return _historyServerConnection == _net.GetServerPeer(); } return false; } return true; } return false; } private void ReceiveHistoryRequest(ZRoutedRpc rpc, long sender, ZPackage package, bool statistics = false, bool snapshot = false) { //IL_032b: Unknown result type (might be due to invalid IL or missing references) //IL_0331: Expected O, but got Unknown if (!HistoryTransportReady(rpc) || !_session.IsHost || _historyRead == null) { return; } try { ZNetPeer val = null; foreach (ZNetPeer peer in _net.GetPeers()) { if (peer != null && peer.m_uid == sender && peer.IsReady() && CompatibilityRuntime.IsReady(peer)) { val = peer; break; } } if (sender == 0L || sender == _session.Peer || val == null || !_identities.ResolveConnection(sender, val).HasValue) { Plugin.HistoryLog("CombatHistoryReadResponseRejected reason=UnauthorizedPeer"); return; } if (package == null || package.Size() > 256) { throw new InvalidDataException("RequestBytes"); } HistoryReadRequest historyReadRequest = HistoryReadCodec.DecodeRequest(package.GetArray()); double num = _now(); if (snapshot && historyReadRequest.Query != HistoryQuery.Session) { throw new InvalidDataException("StatisticsSnapshotRequestShape"); } foreach (long item in new List<long>(_historyReaders.Keys)) { if (!_net.GetPeers().Contains(_historyReaders[item].Peer)) { _historyReaders.Remove(item); } } _historyReaders.TryGetValue(sender, out (ZNetPeer, Guid, long, double, int) value); if (value.Item1 != val) { value = (val, historyReadRequest.ConnectionId, 0L, num, 0); } if (num - value.Item4 >= 1.0) { value = (value.Item1, value.Item2, value.Item3, num, 0); } if (value.Item5 >= 8 || (_historyReaders.Count >= 256 && !_historyReaders.ContainsKey(sender))) { Plugin.HistoryLog("CombatHistoryReadResponseRejected reason=RequestCapacity"); return; } if (value.Item2 == historyReadRequest.ConnectionId && historyReadRequest.RequestId <= value.Item3) { Plugin.HistoryLog("CombatHistoryReadResponseRejected reason=StaleRequest"); return; } _historyReaders[sender] = (val, historyReadRequest.ConnectionId, historyReadRequest.RequestId, value.Item4, value.Item5 + 1); Plugin.HistoryLog("CombatHistoryReadRequestReceived query=" + historyReadRequest.Query.ToString() + " request=" + historyReadRequest.RequestId + " peer=" + sender); HistoryReadResult historyReadResult = (snapshot ? _historyRead.ReadStatisticsSnapshot(historyReadRequest).Data : _historyRead.Read(historyReadRequest)); byte[] array = (snapshot ? StatisticsSnapshotCodec.Encode(new StatisticsSnapshot(historyReadResult)) : (statistics ? StatisticsReadCodec.Encode(historyReadResult) : HistoryReadCodec.EncodeBoundedResponse(historyReadResult))); rpc.InvokeRoutedRPC(sender, snapshot ? "CombatMeter.CombatStatistics.v1.Response" : (statistics ? "CombatMeter.CombatHistory.Statistics.v1.Response" : "CombatMeter.CombatHistory.v1.Response"), new object[1] { (object)new ZPackage(array) }); if (Plugin.HistoryDiagnosticsEnabled) { Plugin.HistoryLog("CombatHistoryReadResponseSent query=" + historyReadRequest.Query.ToString() + " request=" + historyReadRequest.RequestId + " peer=" + sender + " bytes=" + array.Length + " hash=" + DetailsHash(array)); } } catch (Exception ex) { Plugin.HistoryLog("CombatHistoryReadResponseRejected reason=MalformedOrHandlerFailure type=" + ex.GetType().Name); } } private void ReceiveHistoryResponse(ZRoutedRpc rpc, long sender, ZPackage package, bool statistics = false, bool snapshot = false) { if (!HistoryTransportReady(rpc) || _session.IsHost) { return; } ZNetPeer serverPeer = _net.GetServerPeer(); if (serverPeer == null || serverPeer.m_uid == 0L || sender != serverPeer.m_uid) { return; } try { _historyCache.Bind(serverPeer.m_uid); if (_snapshotStore.TryGetLatest(out var snapshot2)) { _historyCache.ObserveEpoch(snapshot2.SnapshotEpoch); } _historyCache.Bind(serverPeer.m_uid); if (package == null || package.Size() > 1048576) { throw new InvalidDataException("ResponseBytes"); } byte[] array = package.GetArray(); if (_historyCache.Accept(sender, array, _now(), out var accepted, statistics, snapshot)) { if (snapshot) { LogStatisticsSnapshot(accepted, array); } CompleteHistoryProbe(accepted.Request.RequestId, accepted, null); if (snapshot && _statisticsDeferred != null) { StatisticsRevision statisticsDeferred = _statisticsDeferred; _statisticsDeferred = null; AcceptStatisticsRevision(statisticsDeferred); } } } catch (Exception ex) { Plugin.HistoryLog("CombatHistoryReadResponseRejected reason=MalformedOrHandlerFailure type=" + ex.GetType().Name); } } private void CompleteHistoryProbe(long request, HistoryReadResult result, string reason) { if (!_historyProbeRequests.TryGetValue(request, out (double, Action<HistoryReadResult, string>) value)) { return; } _historyProbeRequests.Remove(request); _statisticsPendingSnapshots.Remove(request); try { value.Item2(result, reason); } catch { } } internal void CancelHistoryRequest(long id) { _historyProbeRequests.Remove(id); _statisticsPendingSnapshots.Remove(id); _historyCache.Cancel(id); } private void LogStatisticsSnapshot(HistoryReadResult a, byte[] bytes) { if (a.Status == HistoryReadStatus.Ok) { _statisticsApplied = new StatisticsRevision(a.WorldIdentity, a.HostEpoch, a.SessionId, a.Request.ConnectionId, a.HistoryRevision, a.SessionRevision); } if (Plugin.HistoryDiagnosticsEnabled) { Plugin.HistoryLog("CombatStatisticsSnapshotAccepted request=" + a.Request.RequestId + " history=" + a.HistoryRevision + " session=" + a.SessionRevision + " bytes=" + bytes.Length + " status=" + a.Status.ToString() + " hash=" + StatisticsSnapshotCodec.SemanticHash(new StatisticsSnapshot(a))); } } private void NotifyHistoryChanged(HistoryEncounter record, CombatDetailsEncounter before, CombatDetailsEncounter after) { //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Expected O, but got Unknown if (_historyRead == null) { return; } CommitSession session = _session; if (session == null || !session.IsHost) { return; } long revision = _history.Revision; long revision2 = _historyRead.CurrentSession.Revision; if (revision == _statisticsPublishedHistory && revision2 == _statisticsPublishedSession) { return; } _statisticsPublishedHistory = revision; _statisticsPublishedSession = revision2; foreach (ZNetPeer peer in _net.GetPeers()) { if (peer != null && peer.IsReady() && CompatibilityRuntime.IsReady(peer) && peer.m_uid != 0L && _identities.ResolveConnection(peer.m_uid, peer).HasValue && _historyReaders.TryGetValue(peer.m_uid, out (ZNetPeer, Guid, long, double, int) value) && value.Item1 == peer) { try { byte[] array = StatisticsSnapshotCodec.Revision(new StatisticsRevision(_history.WorldIdentity, _snapshotEpoch, _historyRead.CurrentSession.SessionId, value.Item2, revision, revision2)); _rpc.InvokeRoutedRPC(peer.m_uid, "CombatMeter.CombatStatistics.v1.Changed", new object[1] { (object)new ZPackage(array) }); Plugin.HistoryLog("CombatStatisticsRevisionNotified history=" + revision + " session=" + revision2 + " peer=" + peer.m_uid + " bytes=" + array.Length); } catch { } } } RaiseHistoryChanged(new StatisticsRevision(_history.WorldIdentity, _snapshotEpoch, _historyRead.CurrentSession.SessionId, _historyCache.ConnectionId, revision, revision2)); } private void RaiseHistoryChanged(StatisticsRevision revision) { if (this.HistoryChanged == null) { return; } Delegate[] invocationList = this.HistoryChanged.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action<StatisticsRevision> action = (Action<StatisticsRevision>)invocationList[i]; try { action(revision); } catch { } } } private void ReceiveHistoryRevision(ZRoutedRpc rpc, long sender, ZPackage package) { if (!HistoryTransportReady(rpc) || _session.IsHost || sender != _net.GetServerPeer()?.m_uid) { return; } try { if (package == null || package.Size() > 512) { return; } StatisticsRevision statisticsRevision = StatisticsSnapshotCodec.Revision(package.GetArray()); if (statisticsRevision.Connection != _historyCache.ConnectionId || (_historyCache.WorldIdentity != null && (statisticsRevision.World != _historyCache.WorldIdentity || statisticsRevision.Epoch != _historyCache.HostEpoch || statisticsRevision.Session != _historyCache.SessionId))) { return; } StatisticsRevision statisticsRevision2 = ((_statisticsApplied?.Connection == statisticsRevision.Connection) ? _statisticsApplied : null); if (statisticsRevision2 != null && (statisticsRevision.History < statisticsRevision2.History || statisticsRevision.Summary < statisticsRevision2.Summary || (statisticsRevision.History == statisticsRevision2.History && statisticsRevision.Summary == statisticsRevision2.Summary))) { return; } if (_statisticsPendingSnapshots.Count > 0 || (_historyCache.WorldIdentity == null && _historyProbeRequests.Count > 0)) { if (_statisticsDeferred == null || (statisticsRevision.History >= _statisticsDeferred.History && statisticsRevision.Summary >= _statisticsDeferred.Summary && (statisticsRevision.History > _statisticsDeferred.History || statisticsRevision.Summary > _statisticsDeferred.Summary))) { _statisticsDeferred = statisticsRevision; Plugin.HistoryLog("CombatStatisticsRefreshCoalesced history=" + statisticsRevision.History + " session=" + statisticsRevision.Summary); } } else { AcceptStatisticsRevision(statisticsRevision); } } catch (Exception ex) { Plugin.HistoryLog("CombatHistoryReadResponseRejected reason=RevisionMalformed type=" + ex.GetType().Name); } } private void AcceptStatisticsRevision(StatisticsRevision revision) { if (!(revision.Connection != _historyCache.ConnectionId) && !(revision.World != _historyCache.WorldIdentity) && !(revision.Epoch != _historyCache.HostEpoch) && !(revision.Session != _historyCache.SessionId)) { StatisticsRevision obj = ((_statisticsApplied?.Connection == revision.Connection) ? _statisticsApplied : null); long num = obj?.History ?? 0; long num2 = obj?.Summary ?? 0; if (_statisticsObserved?.Connection == revision.Connection) { num = Math.Max(num, _statisticsObserved.History); num2 = Math.Max(num2, _statisticsObserved.Summary); } if (revision.History >= num && revision.Summary >= num2 && (revision.History != num || revision.Summary != num2)) { _statisticsObserved = revision; RaiseHistoryChanged(revision); } } } private void ReceiveDetails(ZRoutedRpc rpc, long sender, ZPackage package) { if (!CompatibleSender(sender) || _disposed || rpc != _rpc || !EnsureSession() || _session.IsHost) { return; } ZNetPeer serverPeer = _net.GetServerPeer(); if (serverPeer == null || sender != serverPeer.m_uid) { Plugin.SnapshotLog("CombatDetailsSnapshotRejected reason=UnexpectedSender"); return; } try { if (package == null || package.Size() > 524288) { throw new InvalidDataException("PacketSize"); } CombatDetailsSnapshot combatDetailsSnapshot = CombatDetailsCodec.Decode(package.GetArray()); if (_snapshotStore.TryGetLatest(out var snapshot)) { _detailsExpectedEpoch = snapshot.SnapshotEpoch; } if (_detailsExpectedEpoch != Guid.Empty && combatDetailsSnapshot.HostEpoch != _detailsExpectedEpoch) { _detailsStore.Invalidate(); Plugin.SnapshotLog("CombatDetailsSnapshotCleared reason=HostEpochMismatch"); Plugin.SnapshotLog("CombatDetailsSnapshotRejected reason=HostEpochMismatch"); return; } string hash = DetailsHash(package.GetArray()); DetailsLog("CombatDetailsSnapshotReceived", combatDetailsSnapshot, _session.Peer, package.Size(), hash); CombatDetailsEncounter lastCompletedEncounter = _detailsStore.LastCompletedEncounter; SnapshotApplyResult snapshotApplyResult = _detailsStore.Apply(combatDetailsSnapshot, sender, serverPeer.m_uid); switch (snapshotApplyResult) { case SnapshotApplyResult.Accepted: _detailsExpectedEpoch = combatDetailsSnapshot.HostEpoch; DetailsLog("CombatDetailsSnapshotAppliedRemote", combatDetailsSnapshot, _session.Peer, package.Size(), hash); if (lastCompletedEncounter != _detailsStore.LastCompletedEncounter && _detailsStore.LastCompletedEncounter != null) { DetailsLog("CombatDetailsSnapshotStoredCompleted", combatDetailsSnapshot, _session.Peer, package.Size(), hash); } if (combatDetailsSnapshot.TargetPlayerId == 0L) { DetailsLog("CombatDetailsSnapshotCleared", combatDetailsSnapshot, _session.Peer, package.Size()); _historyCache.Clear("IdentityRevoked"); } return; case SnapshotApplyResult.SessionMismatch: _detailsStore.Invalidate(); break; } Plugin.SnapshotLog("CombatDetailsSnapshotRejected reason=" + snapshotApplyResult); } catch (Exception ex) when (ex is InvalidDataException || ex is EndOfStreamException || ex is ArgumentException || ex is OverflowException) { if (ex is InvalidDataException && ex.Message == "DetailsSchemaVersion"