Decompiled source of StateKeeper v0.1.0

StateKeeper.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Peak.Afflictions;
using Photon.Pun;
using Photon.Realtime;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.UI;
using Zorro.Core;
using Zorro.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("StateKeeper")]
[assembly: AssemblyDescription("PEAK expedition run telemetry collector")]
[assembly: AssemblyCompany("Local MOD")]
[assembly: AssemblyProduct("StateKeeper")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: ComVisible(false)]
[assembly: InternalsVisibleTo("StateKeeper.Tests")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("0.1.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace StateKeeper
{
	[Serializable]
	internal sealed class AnalysisResult
	{
		public const int CurrentVersion = 5;

		public List<LifecycleObservation> lifecycle = new List<LifecycleObservation>();

		public List<LifecycleInterval> lifecycleIntervals = new List<LifecycleInterval>();

		public List<ReportEpisode> episodes = new List<ReportEpisode>();

		public List<QualityObservation> diagnostics = new List<QualityObservation>();

		public List<ReportEvent> timeline = new List<ReportEvent>();

		public int processedInventoryCount;

		public int analysisVersion = 5;

		public string[] collectionCapabilities = new string[0];

		public List<AnalysisAssistanceEvent> assistance = new List<AnalysisAssistanceEvent>();

		public List<AnalysisEffectChange> effects = new List<AnalysisEffectChange>();

		public string sourceFingerprint;

		public string[] statusTypeOrder = new string[0];

		public string[] afflictionTypeOrder = new string[0];

		public List<AnalysisDistancePoint> teamSpan = new List<AnalysisDistancePoint>();

		public int schemaVersion;

		public string runId;

		public string sourceLastSavedUtc;

		public int sourceChunkCount;

		public int sourceSampleCount;

		public int sourceInventorySnapshotCount;

		public int sourceEventCount;

		public bool hasAscentLevel;

		public int ascentLevel;

		public bool hasCustomRun;

		public bool isCustomRun;

		public string gameVersion;

		public string recordingUserId;

		public double analysisMilliseconds;

		public long observedManagedBytes;

		public AnalysisOverview overview = new AnalysisOverview();

		public AnalysisQuality quality = new AnalysisQuality();

		public List<AnalysisPlayer> players = new List<AnalysisPlayer>();

		public List<AnalysisDistancePair> distancePairs = new List<AnalysisDistancePair>();

		public List<AnalysisItemObservation> items = new List<AnalysisItemObservation>();

		public List<ItemDefinition> definitions = new List<ItemDefinition>();

		public List<MountainSegmentDefinition> mountainSegments = new List<MountainSegmentDefinition>();

		public List<AnalysisSegment> segments = new List<AnalysisSegment>();
	}
	[Serializable]
	internal sealed class AnalysisOverview
	{
		public string outcome;

		public float durationSeconds;

		public int playerCount;

		public int jumpCount;

		public int deathCount;

		public int passedOutCount;

		public int lifeSavedCount;

		public int rescuePullCount;

		public float friendHealingAmount;

		public int itemObservationCount;

		public int validDistanceCount;

		public int mountainSegmentCount;
	}
	[Serializable]
	internal sealed class AnalysisAssistanceEvent
	{
		public float time;

		public int epoch;

		public string kind;

		public int actorPlayerIndex = -1;

		public int targetPlayerIndex = -1;

		public string itemName;

		public string itemGuid;

		public float amount;

		public string resourceKey;
	}
	[Serializable]
	internal sealed class AnalysisQuality
	{
		public int missingChunkCount;

		public int corruptChunkCount;

		public int timeBackwardsCount;

		public int missingPlayerSampleCount;

		public int invalidPositionCount;

		public int nonFiniteValueCount;

		public int excludedDistanceCount;

		public int gapCount;

		public int countMismatchCount;

		public int outOfRangeStaminaCount;

		public List<string> warnings = new List<string>();
	}
	[Serializable]
	internal sealed class AnalysisPlayer
	{
		public int playerIndex;

		public string displayName;

		public string stableUserId;

		public int rawDeathCount;

		public int duplicateDeathCount;

		public int unconfirmedDeathCount;

		public int rawPassedOutCount;

		public int consumedCount;

		public int possibleHelpCount;

		public float lowStaminaSeconds;

		public float lowCapacitySeconds;

		public float isolatedSeconds;

		public Dictionary<string, float> movementSeconds = new Dictionary<string, float>();

		public Dictionary<string, float> statusIntegrals = new Dictionary<string, float>();

		public int jumpCount;

		public List<float> jumpTimes = new List<float>();

		public List<EvidenceReference> jumpEvidence = new List<EvidenceReference>();

		public int deathCount;

		public int passedOutCount;

		public float staminaMin = 1f;

		public float staminaMax;

		public float staminaAverage;

		public float observedSeconds;

		public float aliveSeconds;

		public List<float> deathTimes = new List<float>();

		public List<float> passedOutTimes = new List<float>();

		public List<AnalysisDistancePoint> nearestSeries = new List<AnalysisDistancePoint>();

		public List<AnalysisSeriesPoint> staminaSeries = new List<AnalysisSeriesPoint>();

		public List<AnalysisStaminaEpisode> staminaEpisodes = new List<AnalysisStaminaEpisode>();

		public List<AnalysisIsolationEpisode> isolationEpisodes = new List<AnalysisIsolationEpisode>();

		public int lifeSavedCount;

		public int rescuePullCount;

		public float friendHealingAmount;
	}
	[Serializable]
	internal sealed class AnalysisSeriesPoint
	{
		public EvidenceReference evidence;

		public float time;

		public float endTime;

		public int epoch;

		public bool breakBefore;

		public bool dead;

		public float maxStamina;

		public int? petrifyAmount;

		public float[] statuses;

		public List<int> afflictions;

		public float minimum;

		public float maximum;

		public float validSeconds;

		public double totalIntegral;

		public float regularStamina;

		public float extraStamina;

		public float regularMinimum;

		public float regularMaximum;

		public float extraMinimum;

		public float extraMaximum;

		public float totalStamina;

		public float normalizedStamina;

		public int sampleCount;

		public float observedSeconds;

		public float[] movementSeconds;

		public float[] statusIntegrals;
	}
	[Serializable]
	internal sealed class AnalysisDistancePair
	{
		public int playerA;

		public int playerB;

		public int sampleCount;

		public float median;

		public float p90;

		public float p99;

		public float max;

		public float nearestObserved;

		public float over25Ratio;

		public float over50Ratio;

		public float over100Ratio;

		public float validSeconds;

		public List<AnalysisDistancePoint> series = new List<AnalysisDistancePoint>();

		public List<float> intervals = new List<float>();

		public List<int> intervalEpochs = new List<int>();
	}
	[Serializable]
	internal sealed class AnalysisItemObservation
	{
		public EvidenceReference evidence;

		public int observationId;

		public int attributionGroupId;

		public int? cookedAmount;

		public float endTime;

		public List<string> attributionReasons = new List<string>();

		public List<AnalysisEffectRule> rules = new List<AnalysisEffectRule>();

		public bool possibleHelp;

		public bool possibleRescue;

		public float time;

		public int playerIndex = -1;

		public int actorPlayerIndex = -1;

		public bool actorInferred;

		public int targetPlayerIndex = -1;

		public ushort itemId;

		public int epoch;

		public int evidenceCount = 1;

		public string attribution = "Ambiguous";

		public string attributionReason;

		public List<AnalysisEffectChange> observedEffects = new List<AnalysisEffectChange>();

		public string itemGuid;

		public string itemName;

		public string prefabName;

		public string slot;

		public string kind;

		public string resourceKey;

		public float previousValue;

		public float value;

		public string confidence;

		public string detail;

		public List<string> effectHints = new List<string>();
	}
	[Serializable]
	internal sealed class AnalysisSegment
	{
		public int index;

		public string titleKey;

		public string displayName;

		public bool hasStartTime;

		public bool hasEndTime;

		public float startTime;

		public float endTime;

		public bool derivedFromBoundary;
	}
	[Serializable]
	internal sealed class AnalysisStaminaEpisode
	{
		public float startTime;

		public float endTime;

		public float startValue;

		public float endValue;

		public string direction;

		public bool burst;
	}
	[Serializable]
	internal sealed class AnalysisIsolationEpisode
	{
		public EvidenceReference evidence;

		public float startTime;

		public float endTime;

		public float peakNearestDistance;

		public string endReason;
	}
	[Serializable]
	internal sealed class AnalysisDistancePoint
	{
		public EvidenceReference evidence;

		public float time;

		public float endTime;

		public float value;

		public float maximum;

		public float minimum;

		public int count;

		public bool breakBefore;

		public int epoch;
	}
	[Serializable]
	internal sealed class AnalysisEffectChange
	{
		public int effectId;

		public int epoch;

		public string attribution = "Ambiguous";

		public List<string> reasons = new List<string>();

		public List<int> candidateGroupIds = new List<int>();

		public float cumulativeDelta;

		public string channel;

		public int playerIndex;

		public float previousValue;

		public float value;

		public float beforeTime;

		public float afterTime;

		public bool matchesTheory;
	}
	[Serializable]
	internal sealed class AnalysisEffectRule
	{
		public string channel;

		public float? amount;

		public int direction;

		public float delay;

		public float duration;

		public bool continuous;

		public string scope = "Recipient";

		public float? radius;

		public bool ignoreCaster;

		public string trigger;

		public string source;

		public bool uncertain;

		public string requiredAffliction;

		public string afterAfflictionEnds;

		public string budgetGroup;

		public float? budget;

		public int priority;
	}
	internal static class AssistanceEvidence
	{
		internal static float HealedAmount(float before, float after)
		{
			if (float.IsNaN(before) || float.IsInfinity(before) || float.IsNaN(after) || float.IsInfinity(after))
			{
				return 0f;
			}
			return Math.Max(0f, before - after);
		}

		internal static bool RestoredRecoveryEligibility(bool wasPassedOut, bool dead, float beforeSum, float afterSum, int petrify)
		{
			if (wasPassedOut && !dead && petrify < 100 && beforeSum > 0.99f && afterSum >= 0f && afterSum <= 0.99f)
			{
				return !float.IsInfinity(beforeSum);
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(CharacterSyncer), "OnDataReceived")]
	internal static class CharacterObservationSyncPatch
	{
		private static void Postfix(CharacterSyncer __instance)
		{
			if ((Object)(object)RunCollector.Instance != (Object)null && (Object)(object)__instance != (Object)null)
			{
				RunCollector.Instance.ObserveSync(((Component)__instance).GetComponent<Character>());
			}
		}
	}
	[HarmonyPatch(typeof(Character), "OnJump")]
	internal static class CharacterJumpPatch
	{
		private static void Postfix(Character __instance)
		{
			if ((Object)(object)RunCollector.Instance != (Object)null)
			{
				RunCollector.Instance.RecordJump(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(GlobalEvents), "TriggerSomeoneWonRun")]
	internal static class GlobalEventsVictoryPatch
	{
		private static void Postfix()
		{
			if ((Object)(object)RunCollector.Instance != (Object)null)
			{
				RunCollector.Instance.MarkVictory();
			}
		}
	}
	[HarmonyPatch(typeof(GlobalEvents), "TriggerRunEnded")]
	internal static class GlobalEventsRunEndedPatch
	{
		private static void Prefix()
		{
			if ((Object)(object)RunCollector.Instance != (Object)null)
			{
				RunCollector.Instance.EndRun();
			}
		}
	}
	[HarmonyPatch(typeof(GlobalEvents), "TriggerCharacterDied")]
	internal static class CharacterDiedPatch
	{
		private static void Postfix(Character character)
		{
			if ((Object)(object)RunCollector.Instance != (Object)null)
			{
				RunCollector.Instance.RecordSimpleEvent("PlayerDied", character, (!((Object)(object)character != (Object)null) || !character.IsLocal) ? EventSource.RemoteObserved : EventSource.LocalAuthoritative, "GlobalEvents");
			}
		}
	}
	[HarmonyPatch(typeof(GlobalEvents), "TriggerCharacterPassedOut")]
	internal static class CharacterPassedOutPatch
	{
		private static void Postfix(Character character)
		{
			if ((Object)(object)RunCollector.Instance != (Object)null)
			{
				RunCollector.Instance.RecordSimpleEvent("PlayerPassedOut", character, (!((Object)(object)character != (Object)null) || !character.IsLocal) ? EventSource.RemoteObserved : EventSource.LocalAuthoritative, "GlobalEvents");
			}
		}
	}
	[HarmonyPatch(typeof(Item), "Interact")]
	internal static class ItemInteractPatch
	{
		private static void Postfix(Item __instance, Character interactor)
		{
			if ((Object)(object)RunCollector.Instance != (Object)null)
			{
				RunCollector.Instance.RecordItemEvent("ItemPickupRequested", __instance, interactor, null, EventSource.LocalAuthoritative, "Item.Interact");
			}
		}
	}
	[HarmonyPatch(typeof(CharacterItems), "OnPickupAccepted")]
	internal static class PickupAcceptedPatch
	{
		private static void Postfix(CharacterItems __instance, byte slotID)
		{
			if (!((Object)(object)RunCollector.Instance == (Object)null))
			{
				Character val = (((Object)(object)__instance == (Object)null) ? null : ((Component)__instance).GetComponent<Character>());
				ItemSlot itemSlot = null;
				if ((Object)(object)val != (Object)null && (Object)(object)val.player != (Object)null)
				{
					itemSlot = val.player.GetItemSlot(slotID);
				}
				RunCollector.Instance.RecordItemSlotEvent("ItemPickedUp", itemSlot, val, EventSource.LocalAuthoritative, "OnPickupAccepted:" + slotID);
			}
		}
	}
	[HarmonyPatch(typeof(Item), "StartUsePrimary")]
	internal static class ItemPrimaryStartedPatch
	{
		private static void Postfix(Item __instance)
		{
			RecordItem("ItemUseStarted", __instance, "StartUsePrimary");
		}

		private static void RecordItem(string type, Item item, string detail)
		{
			if (!((Object)(object)RunCollector.Instance == (Object)null) && !((Object)(object)item == (Object)null))
			{
				Character holderCharacter = item.holderCharacter;
				RunCollector.Instance.RecordItemEvent(type, item, holderCharacter, null, (!((Object)(object)holderCharacter != (Object)null) || !holderCharacter.IsLocal) ? EventSource.RemoteObserved : EventSource.LocalAuthoritative, detail);
			}
		}
	}
	[HarmonyPatch(typeof(Item), "FinishCastPrimary")]
	internal static class ItemPrimaryFinishedPatch
	{
		private static void Postfix(Item __instance)
		{
			if (!((Object)(object)RunCollector.Instance == (Object)null) && !((Object)(object)__instance == (Object)null))
			{
				Character holderCharacter = __instance.holderCharacter;
				RunCollector.Instance.RecordItemEvent("ItemPrimaryCastFinished", __instance, holderCharacter, null, (!((Object)(object)holderCharacter != (Object)null) || !holderCharacter.IsLocal) ? EventSource.RemoteObserved : EventSource.LocalAuthoritative, "FinishCastPrimary");
			}
		}
	}
	[HarmonyPatch(typeof(Item), "FinishCastSecondary")]
	internal static class ItemSecondaryFinishedPatch
	{
		private static void Postfix(Item __instance)
		{
			if (!((Object)(object)RunCollector.Instance == (Object)null) && !((Object)(object)__instance == (Object)null))
			{
				Character holderCharacter = __instance.holderCharacter;
				RunCollector.Instance.RecordItemEvent("ItemSecondaryCastFinished", __instance, holderCharacter, null, (!((Object)(object)holderCharacter != (Object)null) || !holderCharacter.IsLocal) ? EventSource.RemoteObserved : EventSource.LocalAuthoritative, "FinishCastSecondary");
			}
		}
	}
	[HarmonyPatch(typeof(Character), "GetFedItemRPC")]
	internal static class FedItemObservedPatch
	{
		private static void Prefix(Character __instance, int itemPhotonID, out RunCollector.ItemEventCapture __state)
		{
			__state = null;
			if (!((Object)(object)RunCollector.Instance == (Object)null) && !((Object)(object)__instance == (Object)null) && ((MonoBehaviourPun)__instance).photonView.IsMine)
			{
				PhotonView val = PhotonView.Find(itemPhotonID);
				Item val2 = (((Object)(object)val == (Object)null) ? null : ((Component)val).GetComponent<Item>());
				if ((Object)(object)val2 != (Object)null && val2.OnPrimaryFinishedCast != null)
				{
					__state = RunCollector.Instance.CaptureItemEvent(val2, __instance, __instance);
				}
			}
		}

		private static void Postfix(RunCollector.ItemEventCapture __state)
		{
			if ((Object)(object)RunCollector.Instance != (Object)null)
			{
				RunCollector.Instance.RecordCapturedItemEvent("ItemFedToPlayer", __state);
			}
		}
	}
	internal sealed class ModifyStatusObservation
	{
		internal Item item;

		internal Character target;

		internal Character actor;

		internal float before;

		internal float statusSumBefore;

		internal bool wasPassedOut;

		internal STATUSTYPE status;
	}
	[HarmonyPatch(typeof(Action_ModifyStatus), "RunAction")]
	internal static class ModifyStatusOutcomePatch
	{
		private static void Prefix(Action_ModifyStatus __instance, out ModifyStatusObservation __state)
		{
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			__state = null;
			if (!((Object)(object)RunCollector.Instance == (Object)null) && RunCollector.Instance.CollectionEnabled && !((Object)(object)__instance == (Object)null) && !(__instance.changeAmount >= 0f))
			{
				Item component = ((Component)__instance).GetComponent<Item>();
				Character val = (((Object)(object)component == (Object)null) ? null : component.holderCharacter);
				Character val2 = default(Character);
				if (!((Object)(object)component == (Object)null) && !((Object)(object)val == (Object)null) && val.IsLocal && !((Object)(object)val.refs.afflictions == (Object)null) && component.TryGetFeeder(ref val2) && !((Object)(object)val2 == (Object)null) && !((Object)(object)val2 == (Object)(object)val))
				{
					__state = new ModifyStatusObservation
					{
						item = component,
						target = val,
						before = ReadStatus(val, __instance.statusType),
						statusSumBefore = val.refs.afflictions.statusSum,
						wasPassedOut = (val.data.passedOut || val.data.fullyPassedOut),
						status = __instance.statusType,
						actor = val2
					};
				}
			}
		}

		private static void Postfix(ModifyStatusObservation __state)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			if (__state == null || (Object)(object)RunCollector.Instance == (Object)null || (Object)(object)__state.target.refs.afflictions == (Object)null)
			{
				return;
			}
			float after = ReadStatus(__state.target, __state.status);
			float num = AssistanceEvidence.HealedAmount(__state.before, after);
			if (!(num <= 0f))
			{
				RunCollector.Instance.RecordItemOutcome("PlayerFriendHealed", __state.item, __state.actor, __state.target, __state.actor, num, "Observed status reduction", ((object)Unsafe.As<STATUSTYPE, STATUSTYPE>(ref __state.status)/*cast due to .constrained prefix*/).ToString(), __state.before);
				if (AssistanceEvidence.RestoredRecoveryEligibility(__state.wasPassedOut, __state.target.data.dead, __state.statusSumBefore, __state.target.refs.afflictions.statusSum, __state.target.data.petrifyAmount))
				{
					RunCollector.Instance.RecordItemOutcome("PlayerLifeSaved", __state.item, __state.actor, __state.target, __state.actor, num, "Treatment crossed unconsciousness threshold; not a resurrection");
				}
			}
		}

		private static float ReadStatus(Character target, STATUSTYPE status)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Invalid comparison between Unknown and I4
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			if ((int)status != 13)
			{
				return target.refs.afflictions.GetCurrentStatus(status);
			}
			return (float)target.data.petrifyAmount / 100f;
		}
	}
	[HarmonyPatch(typeof(RescueHook), "RPCA_RescueCharacter")]
	internal static class RescueHookOutcomePatch
	{
		private static void Postfix(RescueHook __instance, PhotonView characterView)
		{
			if ((Object)(object)RunCollector.Instance == (Object)null || (Object)(object)__instance == (Object)null || (Object)(object)characterView == (Object)null)
			{
				return;
			}
			Character component = ((Component)characterView).GetComponent<Character>();
			Item component2 = ((Component)__instance).GetComponent<Item>();
			if (!((Object)(object)component2 == (Object)null))
			{
				Character trueHolderCharacter = component2.trueHolderCharacter;
				if ((Object)(object)component != (Object)null && (Object)(object)trueHolderCharacter != (Object)null && (Object)(object)trueHolderCharacter != (Object)(object)component)
				{
					RunCollector.Instance.RecordItemOutcome("PlayerRescuePulled", component2, trueHolderCharacter, component, trueHolderCharacter, 1f, "RescueHook target acquired");
				}
			}
		}
	}
	[HarmonyPatch(typeof(Character), "RPCA_ReviveAtPosition")]
	internal static class ReviveObservedPatch
	{
		private static void Prefix(Character __instance, out bool __state)
		{
			__state = (Object)(object)__instance != (Object)null && (__instance.data.dead || __instance.data.passedOut || __instance.data.fullyPassedOut);
		}

		private static void Postfix(Character __instance, bool __state)
		{
			if (__state && (Object)(object)__instance != (Object)null && !__instance.data.dead && (Object)(object)RunCollector.Instance != (Object)null)
			{
				RunCollector.Instance.RecordSimpleEvent("PlayerRevivedObserved", __instance, EventSource.RemoteObserved, "RPCA_ReviveAtPosition; rescuer unknown");
			}
		}
	}
	[HarmonyPatch(typeof(Item), "Consume")]
	internal static class ItemConsumedPatch
	{
		private static void Prefix(Item __instance, int consumerID, out RunCollector.ItemEventCapture __state)
		{
			__state = null;
			if (!((Object)(object)RunCollector.Instance == (Object)null) && !((Object)(object)__instance == (Object)null))
			{
				Character val = null;
				PhotonView val2 = PhotonView.Find(consumerID);
				if ((Object)(object)val2 != (Object)null)
				{
					val = ((Component)val2).GetComponent<Character>();
				}
				__state = RunCollector.Instance.CaptureItemEvent(__instance, val ?? __instance.holderCharacter, val);
			}
		}

		private static void Postfix(RunCollector.ItemEventCapture __state)
		{
			if ((Object)(object)RunCollector.Instance != (Object)null)
			{
				RunCollector.Instance.RecordCapturedItemEvent("ItemConsumed", __state);
			}
		}
	}
	[HarmonyPatch(typeof(Action_ReduceUses), "ReduceUsesRPC")]
	internal static class ItemUsesReducedPatch
	{
		private static void Postfix(Action_ReduceUses __instance)
		{
			if (!((Object)(object)RunCollector.Instance == (Object)null) && !((Object)(object)__instance == (Object)null))
			{
				Item component = ((Component)__instance).GetComponent<Item>();
				Character val = (((Object)(object)component == (Object)null) ? null : component.holderCharacter);
				RunCollector.Instance.RecordItemEvent("ItemUsesReduced", component, val, null, (!((Object)(object)val != (Object)null) || !val.IsLocal) ? EventSource.RemoteObserved : EventSource.LocalAuthoritative, "ReduceUsesRPC");
			}
		}
	}
	[HarmonyPatch(typeof(Character), "UseStamina")]
	internal static class StaminaUsePatch
	{
		private static void Prefix(Character __instance, out float __state)
		{
			__state = (((Object)(object)__instance == (Object)null || (Object)(object)__instance.data == (Object)null) ? 0f : __instance.GetTotalStamina());
		}

		private static void Postfix(Character __instance, float __state)
		{
			if (!((Object)(object)RunCollector.Instance == (Object)null) && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.data == (Object)null))
			{
				float totalStamina = __instance.GetTotalStamina();
				float num = Mathf.Abs(totalStamina - __state);
				if (num > 0f && num + 1E-06f >= StateKeeperPlugin.StaminaEventThreshold)
				{
					RunCollector.Instance.RecordSimpleEvent("StaminaChanged", __instance, (!__instance.IsLocal) ? EventSource.RemoteObserved : EventSource.LocalAuthoritative, "UseStamina:" + (__state - totalStamina).ToString("R"));
				}
			}
		}
	}
	internal sealed class ItemAttributionEngine
	{
		private sealed class Frame
		{
			internal float time;

			internal PlayerTelemetry value;
		}

		private sealed class Change
		{
			internal string channel;

			internal float before;

			internal float after;

			internal float start;

			internal float time;

			internal int player;

			internal int epoch;

			internal PlayerTelemetry beforePlayer;

			internal PlayerTelemetry afterPlayer;

			internal Dictionary<int, PlayerTelemetry> players;
		}

		private sealed class Candidate
		{
			internal AnalysisItemObservation row;

			internal List<AnalysisItemObservation> rows = new List<AnalysisItemObservation>();

			internal List<AnalysisEffectRule> rules;

			internal List<string> reasons = new List<string>();

			internal List<AnalysisEffectChange> effects = new List<AnalysisEffectChange>();

			internal HashSet<int> recipients = new HashSet<int>();

			internal Dictionary<string, float> spent = new Dictionary<string, float>();

			internal List<StatsEvent> proofs = new List<StatsEvent>();

			internal Dictionary<string, float> endedAt = new Dictionary<string, float>();

			internal float lateEnd;

			internal int actor;

			internal int target;

			internal float time;

			internal float lastEvidence;

			internal float end = float.PositiveInfinity;

			internal bool weak;

			internal bool explicitTarget;

			internal bool passive;
		}

		private sealed class Match
		{
			internal Candidate candidate;

			internal AnalysisEffectRule rule;

			internal List<string> reasons = new List<string>();
		}

		internal const float Lead = 0.75f;

		internal const float Lag = 1.5f;

		private const float Settle = 2f;

		private readonly AnalysisResult _result;

		private readonly RunRecord _record;

		private readonly RecordedItemRules _rules;

		private readonly List<Candidate> _candidates = new List<Candidate>();

		private readonly List<Change> _changes = new List<Change>();

		private readonly Dictionary<int, Frame> _previous = new Dictionary<int, Frame>();

		private readonly Dictionary<int, float> _barriers = new Dictionary<int, float>();

		private readonly HashSet<int> _transformed = new HashSet<int>();

		private readonly Dictionary<string, Candidate> _passive = new Dictionary<string, Candidate>();

		private readonly Dictionary<string, AnalysisEffectChange> _lastEffects = new Dictionary<string, AnalysisEffectChange>();

		private float _lastTime = float.NaN;

		private float _environmentTime = float.NegativeInfinity;

		private int _epoch;

		private bool _trackingLimited;

		internal int PeakPendingChanges { get; private set; }

		internal ItemAttributionEngine(RunRecord record, AnalysisResult result)
		{
			_record = record;
			_result = result;
			_rules = new RecordedItemRules(record);
		}

		internal void Observe(AnalysisItemObservation row)
		{
			bool flag = row.kind == "ItemRemovedObserved";
			bool flag2 = row.kind == "ItemConsumed" || row.kind == "ItemFedToPlayer" || row.kind == "ItemPrimaryCastFinished" || row.kind == "ItemSecondaryCastFinished";
			bool flag3 = row.kind == "ResourceChanged" && ((new string[4] { "uses", "fuel", "useRemaining", "petterItemUses" }.Contains(row.resourceKey) && row.value < row.previousValue) || (row.resourceKey == "used" && row.previousValue == 0f && row.value == 1f));
			if (row.kind == "ItemMoved" || row.kind == "ItemTransferObserved" || row.kind == "ItemAppeared")
			{
				foreach (Candidate item in _candidates.Where((Candidate c) => c.weak && c.row.itemGuid == row.itemGuid && row.time >= c.time && row.time - c.time <= 2f))
				{
					item.reasons.Add("MovedOrTransferred");
				}
			}
			if (!flag2 && !flag3 && !flag)
			{
				return;
			}
			if ((flag3 || flag) && row.actorPlayerIndex < 0)
			{
				row.actorPlayerIndex = row.playerIndex;
				row.actorInferred = true;
			}
			Candidate candidate = (string.IsNullOrEmpty(row.itemGuid) ? null : _candidates.LastOrDefault((Candidate c) => !c.passive && c.row.itemGuid == row.itemGuid && c.row.epoch == row.epoch && Math.Abs(c.time - row.time) <= 0.75f && !c.rows.Any((AnalysisItemObservation r) => r.kind == row.kind && r.resourceKey == row.resourceKey) && (c.target < 0 || row.targetPlayerIndex < 0 || c.target == row.targetPlayerIndex) && (c.actor < 0 || row.actorPlayerIndex < 0 || c.actor == row.actorPlayerIndex)));
			if (candidate != null)
			{
				candidate.rows.Add(row);
				row.attributionGroupId = candidate.row.attributionGroupId;
				if (row.targetPlayerIndex >= 0)
				{
					candidate.target = row.targetPlayerIndex;
				}
				if (row.actorPlayerIndex >= 0)
				{
					candidate.actor = row.actorPlayerIndex;
				}
				candidate.weak &= flag;
				candidate.lastEvidence = Math.Max(candidate.lastEvidence, row.time);
				if (row.detail == "ConflictingGuid")
				{
					candidate.reasons.Add("ConflictingGuid");
				}
				if (row.cookedAmount.HasValue && !candidate.row.cookedAmount.HasValue)
				{
					candidate.row.cookedAmount = row.cookedAmount;
					List<AnalysisEffectRule> collection = candidate.rules.Where((AnalysisEffectRule r) => r.source == "DirectStatusObservation").ToList();
					candidate.rules = _rules.Compile(candidate.row, candidate.reasons);
					candidate.rules.AddRange(collection);
				}
				if (row.kind == "ItemConsumed" || row.kind == "ItemFedToPlayer")
				{
					candidate.explicitTarget = true;
				}
				return;
			}
			Candidate candidate2 = new Candidate
			{
				row = row,
				actor = row.actorPlayerIndex,
				target = row.targetPlayerIndex,
				time = row.time,
				lastEvidence = row.time,
				weak = flag,
				explicitTarget = (row.kind == "ItemConsumed" || row.kind == "ItemFedToPlayer")
			};
			candidate2.rows.Add(row);
			row.attributionGroupId = row.observationId;
			candidate2.rules = _rules.Compile(row, candidate2.reasons);
			if (string.IsNullOrEmpty(row.itemGuid))
			{
				candidate2.reasons.Add("InstanceUnknown");
			}
			if (row.detail == "ConflictingGuid")
			{
				candidate2.reasons.Add("ConflictingGuid");
			}
			_candidates.Add(candidate2);
			if (_candidates.Count > 1024)
			{
				_trackingLimited = true;
				FinishCandidate(_candidates[0], "TrackingLimitReached");
				if (!_result.quality.warnings.Contains("Attribution candidate limit reached"))
				{
					_result.quality.warnings.Add("Attribution candidate limit reached");
				}
			}
		}

		internal void Inventory(InventorySnapshot snapshot, int epoch, EvidenceReference evidence = null)
		{
			_epoch = epoch;
			List<ItemSnapshot> source = (snapshot.slots ?? new List<ItemSnapshot>()).Where((ItemSnapshot i) => i != null && !string.IsNullOrEmpty(i.guid)).ToList();
			KeyValuePair<string, Candidate>[] array = _passive.Where((KeyValuePair<string, Candidate> p) => p.Value.actor == snapshot.playerIndex).ToArray();
			for (int num = 0; num < array.Length; num++)
			{
				KeyValuePair<string, Candidate> entry = array[num];
				if (!source.Any((ItemSnapshot i) => i.guid == entry.Value.row.itemGuid && PocketEnabled(i)))
				{
					entry.Value.end = snapshot.time;
					_passive.Remove(entry.Key);
				}
			}
			foreach (ItemSnapshot item in source.Where(PocketEnabled))
			{
				string key = snapshot.playerIndex + ":" + item.guid;
				if (!_passive.ContainsKey(key))
				{
					ItemDefinition definition = RunAnalysisEngine.Definition(_record.definitions ?? new List<ItemDefinition>(), item.itemId, item.prefabName);
					List<AnalysisEffectRule> list = _rules.Passive(definition);
					if (list.Count != 0)
					{
						AnalysisItemObservation analysisItemObservation = new AnalysisItemObservation
						{
							evidence = evidence,
							observationId = _result.items.Count + 1,
							time = snapshot.time,
							epoch = epoch,
							playerIndex = snapshot.playerIndex,
							actorPlayerIndex = snapshot.playerIndex,
							actorInferred = true,
							targetPlayerIndex = snapshot.playerIndex,
							itemId = item.itemId,
							itemGuid = item.guid,
							itemName = item.itemName,
							prefabName = item.prefabName,
							kind = "PassiveEffectObserved",
							confidence = "Likely"
						};
						analysisItemObservation.attributionGroupId = analysisItemObservation.observationId;
						_result.items.Add(analysisItemObservation);
						Candidate candidate = new Candidate
						{
							row = analysisItemObservation,
							time = snapshot.time,
							actor = snapshot.playerIndex,
							target = snapshot.playerIndex,
							passive = true,
							rules = list
						};
						candidate.rows.Add(analysisItemObservation);
						_passive[key] = candidate;
						_candidates.Add(candidate);
					}
				}
			}
		}

		private static bool PocketEnabled(ItemSnapshot item)
		{
			if (item.hasPowerEnabled && item.powerEnabled && item.slot != null)
			{
				if (!item.slot.StartsWith("main:", StringComparison.Ordinal))
				{
					return item.slot == "temporary";
				}
				return true;
			}
			return false;
		}

		internal void Event(StatsEvent e, int epoch)
		{
			if (e == null || !Finite(e.time))
			{
				return;
			}
			_epoch = epoch;
			if (e.type == "MountainSegmentReached" || e.type == "CampfireLit" || e.type == "CampfireRested")
			{
				_environmentTime = e.time;
			}
			if (e.type == "ObservationContextChanged")
			{
				if (((int)e.value & 6) != 0)
				{
					_transformed.Add(e.subjectPlayerIndex);
				}
				else
				{
					_transformed.Remove(e.subjectPlayerIndex);
				}
				Interrupt(e.subjectPlayerIndex, e.time);
			}
			if (e.type == "PlayerDied" || e.type == "PlayerRevivedObserved" || e.type == "PlayerTeleported")
			{
				Interrupt(e.subjectPlayerIndex, e.time);
			}
		}

		internal void DirectTreatment(StatsEvent e, EvidenceReference evidence = null)
		{
			if (e.actorPlayerIndex.HasValue && e.actorPlayerIndex.Value != e.targetPlayerIndex && e.targetPlayerIndex >= 0 && !string.IsNullOrEmpty(e.resourceKey) && Finite(e.value) && Finite(e.previousValue) && !(e.value <= 0f) && !(e.previousValue < e.value))
			{
				AnalysisItemObservation analysisItemObservation = new AnalysisItemObservation
				{
					evidence = evidence,
					observationId = _result.items.Count + 1,
					time = e.time,
					epoch = _epoch,
					itemId = e.itemId,
					itemName = e.itemName,
					itemGuid = e.itemGuid,
					prefabName = e.definitionKey,
					kind = "PlayerFriendHealed",
					confidence = "Certain",
					playerIndex = e.actorPlayerIndex.Value,
					actorPlayerIndex = e.actorPlayerIndex.Value,
					targetPlayerIndex = e.targetPlayerIndex
				};
				Candidate candidate = _candidates.LastOrDefault((Candidate p) => !string.IsNullOrEmpty(e.itemGuid) && p.row.itemGuid == e.itemGuid && Math.Abs(p.time - e.time) <= 0.75f && (p.actor < 0 || p.actor == e.actorPlayerIndex) && (p.target < 0 || p.target == e.targetPlayerIndex));
				if (candidate == null)
				{
					candidate = new Candidate
					{
						row = analysisItemObservation,
						actor = analysisItemObservation.actorPlayerIndex,
						target = analysisItemObservation.targetPlayerIndex,
						time = analysisItemObservation.time,
						lastEvidence = analysisItemObservation.time,
						rules = new List<AnalysisEffectRule>(),
						explicitTarget = true
					};
					analysisItemObservation.attributionGroupId = analysisItemObservation.observationId;
					_candidates.Add(candidate);
				}
				analysisItemObservation.attributionGroupId = candidate.row.attributionGroupId;
				_result.items.Add(analysisItemObservation);
				candidate.rows.Add(analysisItemObservation);
				candidate.actor = analysisItemObservation.actorPlayerIndex;
				candidate.target = analysisItemObservation.targetPlayerIndex;
				candidate.explicitTarget = true;
				candidate.weak = false;
				candidate.proofs.Add(e);
				candidate.rules.Add(new AnalysisEffectRule
				{
					channel = e.resourceKey,
					amount = 0f - e.value,
					direction = -1,
					source = "DirectStatusObservation"
				});
				AnalysisEffectChange effect = new AnalysisEffectChange
				{
					epoch = _epoch,
					channel = e.resourceKey,
					playerIndex = e.targetPlayerIndex,
					previousValue = e.previousValue,
					value = e.previousValue - e.value,
					cumulativeDelta = 0f - e.value,
					beforeTime = e.time,
					afterTime = e.time,
					matchesTheory = true,
					attribution = "Certain",
					reasons = new List<string> { "DirectStatusObservation" },
					candidateGroupIds = new List<int> { analysisItemObservation.attributionGroupId }
				};
				candidate.effects.Add(StoreEffect(effect, candidate));
			}
		}

		internal void Sample(PlayerSample sample, int epoch)
		{
			_epoch = epoch;
			if (!Finite(sample.time))
			{
				return;
			}
			if (Finite(_lastTime) && (sample.time <= _lastTime || sample.time - _lastTime > 1f))
			{
				Reset();
			}
			Dictionary<int, PlayerTelemetry> current = (from playerTelemetry in sample.players ?? new List<PlayerTelemetry>()
				where playerTelemetry != null
				group playerTelemetry by playerTelemetry.playerIndex).ToDictionary((IGrouping<int, PlayerTelemetry> g) => g.Key, (IGrouping<int, PlayerTelemetry> g) => g.Last());
			int[] array = _previous.Keys.Where((int id) => !current.ContainsKey(id)).ToArray();
			foreach (int player in array)
			{
				Interrupt(player, sample.time);
			}
			foreach (KeyValuePair<int, PlayerTelemetry> item in current)
			{
				PlayerTelemetry p = item.Value;
				if (!_previous.TryGetValue(item.Key, out var value))
				{
					continue;
				}
				float num2 = sample.time - value.time;
				if (num2 <= 0f || num2 > 1f || p.dead || value.value.dead)
				{
					Interrupt(item.Key, sample.time);
					continue;
				}
				if (!RunAnalysisEngine.ValidStamina(p) || !RunAnalysisEngine.ValidStamina(value.value))
				{
					Interrupt(item.Key, sample.time);
					continue;
				}
				if (RunAnalysisEngine.ValidPosition(p) && RunAnalysisEngine.ValidPosition(value.value) && WorldDistance(p, value.value) > Math.Max(20f, 60f * num2))
				{
					Interrupt(item.Key, sample.time);
					continue;
				}
				PlayerTelemetry value2 = value.value;
				foreach (Candidate item2 in _candidates.Where((Candidate candidate) => candidate.target < 0 || candidate.target == p.playerIndex))
				{
					foreach (string name in (from r in item2.rules
						select r.afterAfflictionEnds into n
						where n != null
						select n).Distinct())
					{
						int num3 = Array.IndexOf(_result.afflictionTypeOrder, name);
						bool num4 = num3 >= 0 && (value2.activeAfflictionTypes?.Contains(num3) ?? false);
						bool flag = num3 >= 0 && (p.activeAfflictionTypes?.Contains(num3) ?? false);
						if (flag)
						{
							item2.lateEnd = Math.Max(item2.lateEnd, sample.time + 1.5f + 2f);
						}
						if (num4 && !flag)
						{
							item2.endedAt[p.playerIndex + ":" + name] = sample.time;
							item2.lateEnd = Math.Max(item2.lateEnd, sample.time + item2.rules.Where((AnalysisEffectRule r) => r.afterAfflictionEnds == name).Max((AnalysisEffectRule r) => r.delay + r.duration) + 1.5f);
						}
					}
				}
				ChangeValue("RegularStamina", value2.regularStamina, p.regularStamina, value, p, sample.time, current);
				ChangeValue("ExtraStamina", value2.extraStamina, p.extraStamina, value, p, sample.time, current);
				if (value2.petrifyAmount.HasValue && p.petrifyAmount.HasValue)
				{
					ChangeValue("Petrify", (float)value2.petrifyAmount.Value / 100f, (float)p.petrifyAmount.Value / 100f, value, p, sample.time, current);
				}
				if (value2.statuses != null && p.statuses != null)
				{
					for (int num5 = 0; num5 < Math.Min(_result.statusTypeOrder.Length, Math.Min(value2.statuses.Length, p.statuses.Length)); num5++)
					{
						if (_result.statusTypeOrder[num5] != "Petrify")
						{
							ChangeValue(_result.statusTypeOrder[num5], value2.statuses[num5], p.statuses[num5], value, p, sample.time, current);
						}
					}
				}
				if (value2.activeAfflictionTypes != null && p.activeAfflictionTypes != null)
				{
					foreach (int item3 in value2.activeAfflictionTypes.Union(p.activeAfflictionTypes).Distinct())
					{
						if (item3 >= 0 && item3 < _result.afflictionTypeOrder.Length)
						{
							ChangeValue("Affliction:" + _result.afflictionTypeOrder[item3], value2.activeAfflictionTypes.Contains(item3) ? 1 : 0, p.activeAfflictionTypes.Contains(item3) ? 1 : 0, value, p, sample.time, current);
						}
					}
				}
				ChangeValue("Unconscious", (value2.passedOut || value2.fullyPassedOut) ? 1 : 0, (p.passedOut || p.fullyPassedOut) ? 1 : 0, value, p, sample.time, current);
			}
			_previous.Clear();
			foreach (KeyValuePair<int, PlayerTelemetry> item4 in current)
			{
				_previous[item4.Key] = new Frame
				{
					time = sample.time,
					value = item4.Value
				};
			}
			_lastTime = sample.time;
			PeakPendingChanges = Math.Max(PeakPendingChanges, _changes.Count);
			SettleChanges(sample.time - 2f, interrupted: false);
			Candidate[] array2 = _candidates.Where((Candidate c2) => sample.time > End(c2) + 1.5f + 2f).ToArray();
			foreach (Candidate c in array2)
			{
				FinishCandidate(c, null);
			}
		}

		private void ChangeValue(string channel, float before, float after, Frame frame, PlayerTelemetry player, float time, Dictionary<int, PlayerTelemetry> players)
		{
			if (Finite(before) && Finite(after) && !(Math.Abs(after - before) < 0.002f))
			{
				_changes.Add(new Change
				{
					channel = channel,
					before = before,
					after = after,
					start = frame.time,
					time = time,
					player = player.playerIndex,
					beforePlayer = frame.value,
					afterPlayer = player,
					players = players,
					epoch = _epoch
				});
			}
		}

		private void SettleChanges(float deadline, bool interrupted)
		{
			Change[] array = _changes.Where((Change c) => c.time <= deadline).ToArray();
			foreach (Change change in array)
			{
				List<Match> list = new List<Match>();
				List<Candidate> list2 = new List<Candidate>();
				foreach (Candidate candidate in _candidates)
				{
					if (change.epoch != candidate.row.epoch || change.time < candidate.time - 0.75f || change.start > End(candidate) + 1.5f)
					{
						continue;
					}
					if (candidate.explicitTarget && candidate.target == change.player && change.time <= candidate.time + 1.5f)
					{
						list2.Add(candidate);
					}
					if (change.channel == "Unconscious" && change.after < change.before && (candidate.target == change.player || candidate.recipients.Contains(change.player)))
					{
						list2.Add(candidate);
					}
					foreach (AnalysisEffectRule rule in candidate.rules)
					{
						if (rule.channel != change.channel || rule.direction == 0 || Math.Sign(change.after - change.before) != rule.direction || !TriggerMatches(candidate, rule))
						{
							continue;
						}
						float num2 = candidate.time + rule.delay;
						float num3 = ((!rule.continuous) ? num2 : (candidate.passive ? candidate.end : (num2 + rule.duration)));
						if (rule.afterAfflictionEnds != null)
						{
							if (!candidate.endedAt.TryGetValue(change.player + ":" + rule.afterAfflictionEnds, out var value))
							{
								continue;
							}
							num2 = value + rule.delay;
							num3 = num2 + rule.duration;
						}
						if (change.time < num2 - 0.75f || change.time > num3 + 1.5f)
						{
							continue;
						}
						Match match = new Match
						{
							candidate = candidate,
							rule = rule
						};
						if (!RecipientMatches(match, change))
						{
							continue;
						}
						if (rule.uncertain)
						{
							match.reasons.Add("IncompleteRuleParameters");
						}
						if (!string.IsNullOrEmpty(rule.requiredAffliction))
						{
							bool num4 = rule.requiredAffliction.StartsWith("Ended:", StringComparison.Ordinal);
							int num5 = Array.IndexOf(value: num4 ? rule.requiredAffliction.Substring(6) : rule.requiredAffliction, array: _result.afflictionTypeOrder);
							bool flag = num5 >= 0 && (change.beforePlayer.activeAfflictionTypes?.Contains(num5) ?? false);
							bool flag2 = num5 >= 0 && (change.afterPlayer.activeAfflictionTypes?.Contains(num5) ?? false);
							bool num6;
							if (!num4)
							{
								if (flag)
								{
									goto IL_03df;
								}
								num6 = !flag2;
							}
							else
							{
								num6 = !flag || flag2;
							}
							if (num6)
							{
								match.reasons.Add("RequiredAfflictionNotObserved");
							}
						}
						goto IL_03df;
						IL_03df:
						CheckAmount(match, change);
						if (_transformed.Contains(change.player) || _transformed.Contains(candidate.actor))
						{
							match.reasons.Add("TransformedCharacter");
						}
						if (_barriers.TryGetValue(change.player, out var value2) && value2 >= change.start - 1.5f && value2 <= change.time + 2f)
						{
							match.reasons.Add("DeathRevivalOrDiscontinuity");
						}
						if (candidate.weak)
						{
							match.reasons.Add("DisappearanceOnly");
						}
						match.reasons.AddRange(candidate.reasons.Where(delegate(string r)
						{
							switch (r)
							{
							default:
								return r == "InstanceUnknown";
							case "MovedOrTransferred":
							case "ConflictingGuid":
							case "ObservationInterrupted":
								return true;
							}
						}));
						if (Natural(change))
						{
							match.reasons.Add("NaturalRecoveryPossible");
						}
						if (change.time >= _environmentTime && change.time - _environmentTime <= 5f && change.after < change.before)
						{
							match.reasons.Add("CheckpointRecoveryPossible");
						}
						if (change.after < change.before && _changes.Any((Change other) => other.player != change.player && other.channel == change.channel && other.after < other.before && Math.Abs(other.time - change.time) <= 0.5f))
						{
							match.reasons.Add("SharedEnvironmentRecoveryPossible");
						}
						if (interrupted)
						{
							match.reasons.Add("IncompleteWindow");
						}
						if (_trackingLimited)
						{
							match.reasons.Add("TrackingLimitReached");
						}
						list.Add(match);
					}
				}
				List<Match> list3 = list.Where((Match m) => m.candidate.proofs.Any((StatsEvent p) => p.resourceKey == change.channel && p.targetPlayerIndex == change.player && Math.Abs(p.time - change.time) <= 1.5f && Math.Abs(p.previousValue - change.before) <= 0.002f && Math.Abs(p.previousValue - p.value - change.after) <= 0.002f)).ToList();
				if (list3.Count > 0)
				{
					list = list3;
					foreach (Match item2 in list)
					{
						item2.reasons.Clear();
					}
				}
				List<Candidate> candidates = list.Select((Match m) => m.candidate).Distinct().ToList();
				List<string> list4 = list.SelectMany((Match m) => m.reasons).Distinct().ToList();
				if (list3.Count == 0)
				{
					List<Candidate> list5 = list2.Where((Candidate c) => !candidates.Contains(c) && !c.weak && (c.rules.Count == 0 || c.reasons.Any((string r) => r.StartsWith("UnsupportedAction:", StringComparison.Ordinal)))).Distinct().ToList();
					if (list5.Count > 0 && candidates.Count > 0)
					{
						list4.Add("UnknownItemCompetition");
						candidates.AddRange(list5);
					}
				}
				if (candidates.Count > 1)
				{
					list4.Add("CompetingItems");
				}
				bool flag3 = candidates.Count == 1 && list4.Count == 0;
				AnalysisEffectChange analysisEffectChange = new AnalysisEffectChange
				{
					channel = change.channel,
					playerIndex = change.player,
					previousValue = change.before,
					value = change.after,
					beforeTime = change.start,
					afterTime = change.time,
					epoch = change.epoch,
					cumulativeDelta = change.after - change.before,
					matchesTheory = (list.Count > 0),
					attribution = ((!flag3) ? "Ambiguous" : ((list3.Count > 0) ? "Certain" : "Likely")),
					reasons = list4,
					candidateGroupIds = candidates.Select((Candidate c) => c.row.attributionGroupId).ToList()
				};
				if (analysisEffectChange.reasons.Count == 0 && !flag3)
				{
					analysisEffectChange.reasons.Add((list2.Count > 0) ? "ObservedWithoutMatchingRule" : "NoItemCandidate");
				}
				foreach (Candidate item3 in candidates.Union(list2))
				{
					if (candidates.Contains(item3))
					{
						item3.recipients.Add(change.player);
					}
					AnalysisEffectChange item = StoreEffect(analysisEffectChange, item3);
					if (!item3.effects.Contains(item))
					{
						item3.effects.Add(item);
					}
				}
				foreach (Match item4 in from m in list
					where !m.rule.continuous
					group m by m.candidate.row.attributionGroupId + ":" + m.rule.source + ":" + m.rule.channel into g
					select g.First())
				{
					string key = change.player + ":" + item4.rule.source + ":" + item4.rule.channel;
					item4.candidate.spent.TryGetValue(key, out var value3);
					item4.candidate.spent[key] = value3 + Math.Abs(change.after - change.before);
				}
				if (candidates.Count == 0 && list2.Count == 0 && change.channel != "RegularStamina")
				{
					StoreEffect(analysisEffectChange, null);
				}
			}
			_changes.RemoveAll((Change c) => c.time <= deadline);
		}

		private AnalysisEffectChange StoreEffect(AnalysisEffectChange effect, Candidate candidate)
		{
			string key = (candidate?.row.attributionGroupId ?? 0) + ":" + effect.playerIndex + ":" + effect.channel + ":" + effect.attribution + ":" + string.Join(",", effect.reasons) + ":" + string.Join(",", effect.candidateGroupIds);
			if (_lastEffects.TryGetValue(key, out var value) && value.epoch == effect.epoch && effect.beforeTime >= value.afterTime && effect.beforeTime - value.afterTime <= 0.6f && Math.Sign(value.cumulativeDelta) == Math.Sign(effect.cumulativeDelta) && effect.afterTime - value.beforeTime <= 10f && !effect.channel.StartsWith("Affliction:", StringComparison.Ordinal))
			{
				value.afterTime = effect.afterTime;
				value.value = effect.value;
				value.cumulativeDelta += effect.cumulativeDelta;
				return value;
			}
			effect.effectId = _result.effects.Count + 1;
			AnalysisEffectChange analysisEffectChange = new AnalysisEffectChange
			{
				effectId = effect.effectId,
				epoch = effect.epoch,
				channel = effect.channel,
				playerIndex = effect.playerIndex,
				previousValue = effect.previousValue,
				value = effect.value,
				beforeTime = effect.beforeTime,
				afterTime = effect.afterTime,
				cumulativeDelta = effect.cumulativeDelta,
				matchesTheory = effect.matchesTheory,
				attribution = effect.attribution,
				reasons = new List<string>(effect.reasons),
				candidateGroupIds = new List<int>(effect.candidateGroupIds)
			};
			_result.effects.Add(analysisEffectChange);
			_lastEffects[key] = analysisEffectChange;
			if (_lastEffects.Count > 4096)
			{
				_lastEffects.Clear();
			}
			return analysisEffectChange;
		}

		private bool RecipientMatches(Match match, Change change)
		{
			Candidate candidate = match.candidate;
			AnalysisEffectRule rule = match.rule;
			bool flag = rule.scope == "Area" || rule.scope == "PlayerArea";
			if (rule.scope == "Actor")
			{
				if (candidate.actor >= 0)
				{
					return candidate.actor == change.player;
				}
				return false;
			}
			if (!flag && candidate.target >= 0)
			{
				return candidate.target == change.player;
			}
			if (!flag && candidate.actor == change.player)
			{
				return true;
			}
			if (candidate.actor < 0 || !change.players.TryGetValue(candidate.actor, out var value) || !RunAnalysisEngine.ValidPosition(value) || !RunAnalysisEngine.ValidPosition(change.afterPlayer))
			{
				match.reasons.Add("SpatialEvidenceUnavailable");
				return true;
			}
			float num = WorldDistance(value, change.afterPlayer);
			if (flag)
			{
				if (rule.ignoreCaster && candidate.actor == change.player)
				{
					return false;
				}
				if (rule.radius.HasValue && num > rule.radius.Value + 1f)
				{
					return false;
				}
				if (rule.scope == "Area")
				{
					match.reasons.Add("ItemOriginNotRecorded");
				}
				if (rule.radius.HasValue && num > rule.radius.Value)
				{
					match.reasons.Add("SpatialBoundaryUncertain");
				}
				return true;
			}
			return num <= 3f;
		}

		private void CheckAmount(Match match, Change change)
		{
			AnalysisEffectRule rule = match.rule;
			if (!rule.amount.HasValue)
			{
				match.reasons.Add("AmountNotRecorded");
				return;
			}
			float num = Math.Abs(change.after - change.before);
			float num2 = Math.Abs(rule.amount.Value);
			if (rule.continuous)
			{
				num2 *= Math.Max(0.5f, change.time - change.start);
			}
			else
			{
				match.candidate.spent.TryGetValue(change.player + ":" + rule.source + ":" + rule.channel, out var value);
				num2 = Math.Max(0f, num2 - value);
			}
			if (rule.direction < 0)
			{
				num2 = Math.Min(num2, Math.Max(0f, change.before));
			}
			else if (rule.channel == "ExtraStamina")
			{
				float num3 = 1f - (float)change.beforePlayer.petrifyAmount.GetValueOrDefault() / 100f;
				num2 = Math.Min(num2, Math.Max(0f, num3 - change.before));
			}
			if (rule.budget.HasValue && !rule.continuous)
			{
				float num4 = match.candidate.rules.Where((AnalysisEffectRule r) => r.budgetGroup == rule.budgetGroup && r.priority < rule.priority).Sum((AnalysisEffectRule r) => Status(change.beforePlayer, r.channel));
				num2 = Math.Min(num2, Math.Max(0f, rule.budget.Value - num4));
			}
			if (rule.budget.HasValue && rule.continuous && match.candidate.rules.Any((AnalysisEffectRule r) => r.budgetGroup == rule.budgetGroup && r.priority < rule.priority && Status(change.beforePlayer, r.channel) >= 0.025f))
			{
				match.reasons.Add("HealingPriorityMismatch");
			}
			if (rule.channel.StartsWith("Affliction:", StringComparison.Ordinal))
			{
				num2 = 1f;
			}
			if (num > num2 + 0.026f)
			{
				match.reasons.Add("AmountOrBudgetMismatch");
			}
		}

		private float Status(PlayerTelemetry player, string channel)
		{
			int num = Array.IndexOf(_result.statusTypeOrder, channel);
			if (num < 0 || player.statuses == null || num >= player.statuses.Length || !Finite(player.statuses[num]))
			{
				return 0f;
			}
			return Math.Max(0f, player.statuses[num]);
		}

		private static bool TriggerMatches(Candidate candidate, AnalysisEffectRule rule)
		{
			if (candidate.passive || string.IsNullOrEmpty(rule.trigger))
			{
				return true;
			}
			string[] triggers = rule.trigger.Split(new char[1] { ',' });
			return candidate.rows.Any((AnalysisItemObservation row) => (row.kind == "ItemConsumed" && (triggers.Contains("OnConsumed") || triggers.Contains("OnCastFinished"))) || (row.kind == "ItemFedToPlayer" && (triggers.Contains("OnConsumed") || triggers.Contains("OnCastFinished"))) || (row.kind == "ItemPrimaryCastFinished" && triggers.Contains("OnCastFinished")) || (row.kind == "ItemSecondaryCastFinished" && triggers.Contains("OnSecondaryCastFinished")) || ((row.kind == "ResourceChanged" || row.kind == "ItemRemovedObserved") && triggers.Any((string t) => t == "OnConsumed" || t == "OnCastFinished" || t == "OnSecondaryCastFinished")));
		}

		private static bool Natural(Change change)
		{
			if (!(change.channel == "RegularStamina"))
			{
				if (change.after < change.before)
				{
					return new string[6] { "Poison", "Drowsy", "Hot", "Cold", "Spores", "Thorns" }.Contains(change.channel);
				}
				return false;
			}
			return true;
		}

		private static float End(Candidate c)
		{
			if (!c.passive)
			{
				return Math.Max(c.lateEnd, c.time + Math.Max(4f, c.rules.Select((AnalysisEffectRule r) => r.delay + r.duration).DefaultIfEmpty(0f).Max()));
			}
			return c.end;
		}

		internal void Interrupt(int player, float time)
		{
			_barriers[player] = time;
			_previous.Remove(player);
			foreach (Candidate item in _candidates.Where((Candidate c) => c.actor == player || c.target == player))
			{
				item.reasons.Add("ObservationInterrupted");
				if (item.passive)
				{
					item.end = time;
				}
			}
			string[] array = (from p in _passive
				where p.Value.actor == player
				select p.Key).ToArray();
			foreach (string key in array)
			{
				_passive.Remove(key);
			}
		}

		private void FinishCandidate(Candidate c, string reason)
		{
			if (reason != null)
			{
				c.reasons.Add(reason);
			}
			bool flag = c.target < 0 && c.recipients.Count > 1 && !c.rules.Any((AnalysisEffectRule r) => r.scope == "Area" || r.scope == "PlayerArea");
			bool flag2 = c.reasons.Contains("ObservationInterrupted") || c.reasons.Contains("MovedOrTransferred") || c.reasons.Contains("IncompleteWindow") || c.reasons.Contains("ConflictingGuid") || c.reasons.Contains("TrackingLimitReached");
			if (flag || flag2)
			{
				foreach (AnalysisEffectChange item in c.effects.Where((AnalysisEffectChange e) => e.attribution != "Certain"))
				{
					item.attribution = "Ambiguous";
					item.reasons.Add(flag ? "CompetingRecipients" : "ObservationInterrupted");
				}
			}
			int num = ((c.target >= 0) ? c.target : ((c.recipients.Count == 1) ? c.recipients.First() : (-1)));
			foreach (AnalysisItemObservation row in c.rows)
			{
				row.rules = c.rules;
				row.endTime = ((!c.passive) ? c.lastEvidence : (Finite(c.end) ? c.end : _lastTime));
				row.observedEffects = c.effects;
				row.attributionReasons = c.reasons.Concat(c.effects.SelectMany((AnalysisEffectChange e) => e.reasons)).Distinct().ToList();
				row.attribution = (c.effects.Any((AnalysisEffectChange e) => e.attribution == "Certain" && e.candidateGroupIds.Contains(c.row.attributionGroupId)) ? "Certain" : (c.effects.Any((AnalysisEffectChange e) => e.attribution == "Likely" && e.candidateGroupIds.Contains(c.row.attributionGroupId)) ? "Likely" : "Ambiguous"));
				if (row.attributionReasons.Count == 0)
				{
					row.attributionReasons.Add((c.rules.Count == 0) ? "NoObservableEffectRule" : ((c.effects.Count == 0) ? "NoObservedChange" : "CompatibleObservedChange"));
				}
				row.attributionReason = row.attributionReasons[0];
				if (row.targetPlayerIndex < 0 && num >= 0)
				{
					row.targetPlayerIndex = num;
				}
				row.possibleHelp = c.actor >= 0 && c.effects.Any((AnalysisEffectChange e) => e.playerIndex != c.actor && e.attribution != "Ambiguous" && Helpful(e));
				row.possibleRescue = row.possibleHelp && c.effects.Any((AnalysisEffectChange recovery) => recovery.playerIndex != c.actor && recovery.channel == "Unconscious" && recovery.cumulativeDelta < 0f && !recovery.reasons.Contains("DeathRevivalOrDiscontinuity") && c.effects.Any((AnalysisEffectChange healing) => healing.playerIndex == recovery.playerIndex && healing.attribution != "Ambiguous" && healing.cumulativeDelta < 0f && Helpful(healing) && healing.afterTime <= recovery.afterTime && recovery.afterTime - healing.afterTime <= 2f));
			}
			_candidates.Remove(c);
			string[] array = (from p in _passive
				where p.Value == c
				select p.Key).ToArray();
			foreach (string key in array)
			{
				_passive.Remove(key);
			}
		}

		internal void Reset()
		{
			SettleChanges(float.MaxValue, interrupted: true);
			Candidate[] array = _candidates.ToArray();
			foreach (Candidate c in array)
			{
				FinishCandidate(c, "ObservationInterrupted");
			}
			_changes.Clear();
			_previous.Clear();
			_passive.Clear();
			_lastEffects.Clear();
			_barriers.Clear();
			_trackingLimited = false;
			_environmentTime = float.NegativeInfinity;
			_lastTime = float.NaN;
		}

		internal void Finish()
		{
			SettleChanges(float.MaxValue, interrupted: true);
			Candidate[] array = _candidates.ToArray();
			foreach (Candidate candidate in array)
			{
				FinishCandidate(candidate, (!candidate.passive && _lastTime < End(candidate) + 1.5f) ? "IncompleteWindow" : null);
			}
		}

		private static bool Finite(float value)
		{
			return RunAnalysisEngine.Finite(value);
		}

		private static bool Helpful(AnalysisEffectChange effect)
		{
			if (effect.channel == "ExtraStamina" && effect.cumulativeDelta > 0f)
			{
				return true;
			}
			if (effect.cumulativeDelta < 0f)
			{
				return new string[12]
				{
					"Injury", "Hunger", "Poison", "Cold", "Hot", "Drowsy", "Spores", "Petrify", "Curse", "Web",
					"Thorns", "Arrow"
				}.Contains(effect.channel);
			}
			if (effect.channel.StartsWith("Affliction:", StringComparison.Ordinal))
			{
				return new string[10] { "InfiniteStamina", "FasterBoi", "Invincibility", "LowGravity", "NoHunger", "Sunscreen", "ClimbingChalk", "HealAll", "DoubleJumpAmulet", "BingBongShield" }.Contains(effect.channel.Substring(11));
			}
			return false;
		}

		private static float WorldDistance(PlayerTelemetry a, PlayerTelemetry b)
		{
			double num = a.positionX - b.positionX;
			double num2 = a.positionY - b.positionY;
			double num3 = a.positionZ - b.positionZ;
			return (float)Math.Sqrt(num * num + num2 * num2 + num3 * num3);
		}
	}
	internal static class ItemDefinitionCatalog
	{
		internal static List<ItemDefinition> Capture()
		{
			List<ItemDefinition> list = new List<ItemDefinition>();
			try
			{
				if ((Object)(object)SingletonAsset<ItemDatabase>.Instance == (Object)null || SingletonAsset<ItemDatabase>.Instance.itemLookup == null)
				{
					return list;
				}
				foreach (KeyValuePair<ushort, Item> item in SingletonAsset<ItemDatabase>.Instance.itemLookup)
				{
					if ((Object)(object)item.Value == (Object)null)
					{
						continue;
					}
					try
					{
						list.Add(Capture(item.Key, item.Value));
					}
					catch (Exception ex)
					{
						if (StateKeeperPlugin.IsDebugLogging)
						{
							StateKeeperPlugin.LogException("capture item definition " + item.Key, ex);
						}
					}
				}
			}
			catch (Exception ex2)
			{
				if (StateKeeperPlugin.IsDebugLogging)
				{
					StateKeeperPlugin.LogException("capture item definitions", ex2);
				}
			}
			return list.OrderBy((ItemDefinition definition) => definition.itemId).ToList();
		}

		private unsafe static ItemDefinition Capture(ushort id, Item item)
		{
			ItemDefinition itemDefinition = new ItemDefinition
			{
				itemId = id,
				itemName = ((item.UIData == null) ? ((Object)item).name : item.UIData.itemName),
				prefabName = (((Object)(object)((Component)item).gameObject == (Object)null) ? ((Object)item).name : ((Object)((Component)item).gameObject).name),
				totalUses = item.totalUses,
				usingTimePrimary = item.usingTimePrimary,
				itemTags = (from ItemTags tag in Enum.GetValues(typeof(ItemTags))
					where (int)tag != 0 && ((Enum)item.itemTags).HasFlag((Enum)(object)tag)
					select ((object)(*(ItemTags*)(&tag))/*cast due to .constrained prefix*/).ToString()).ToList()
			};
			ItemActionBase[] componentsInChildren = ((Component)item).GetComponentsInChildren<ItemActionBase>(false);
			foreach (ItemActionBase val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null))
				{
					List<ItemDefinitionParameter> parameters = DescribePublicParameters(val);
					itemDefinition.actions.Add(new ItemDefinitionAction
					{
						typeName = ((object)val).GetType().FullName,
						trigger = DescribeTriggers(val),
						parameterSummary = DescribeParameters(parameters),
						parameters = parameters
					});
					AddEffectHints(itemDefinition.effectHints, val);
				}
			}
			ItemComponent[] componentsInChildren2 = ((Component)item).GetComponentsInChildren<ItemComponent>(false);
			foreach (ItemComponent val2 in componentsInChildren2)
			{
				if (!((Object)(object)val2 == (Object)null))
				{
					List<ItemDefinitionParameter> parameters2 = DescribePublicParameters(val2);
					ItemCooking val3 = (ItemCooking)(object)((val2 is ItemCooking) ? val2 : null);
					if (val3 != null)
					{
						itemDefinition.cookingRules.Add(new ItemDefinitionCooking
						{
							typeName = ((object)val3).GetType().FullName,
							parameterSummary = DescribeParameters(parameters2),
							parameters = parameters2
						});
					}
					else
					{
						itemDefinition.components.Add(new ItemDefinitionComponent
						{
							typeName = ((object)val2).GetType().FullName,
							parameterSummary = DescribeParameters(parameters2),
							parameters = parameters2
						});
					}
				}
			}
			return itemDefinition;
		}

		private static string DescribeTriggers(ItemActionBase action)
		{
			if (!(action is ItemAction))
			{
				return string.Empty;
			}
			List<string> list = new List<string>();
			FieldInfo[] fields = typeof(ItemAction).GetFields(BindingFlags.Instance | BindingFlags.Public);
			foreach (FieldInfo fieldInfo in fields)
			{
				if (fieldInfo.FieldType == typeof(bool) && (bool)fieldInfo.GetValue(action))
				{
					list.Add(fieldInfo.Name);
				}
			}
			return string.Join(",", list);
		}

		private static List<ItemDefinitionParameter> DescribePublicParameters(object value, int depth = 0)
		{
			List<ItemDefinitionParameter> list = new List<ItemDefinitionParameter>();
			FieldInfo[] fields = value.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public);
			foreach (FieldInfo fieldInfo in fields)
			{
				try
				{
					object value2 = fieldInfo.GetValue(value);
					if (TryDescribeValue(value2, fieldInfo.FieldType, out var text))
					{
						list.Add(new ItemDefinitionParameter
						{
							name = fieldInfo.Name,
							type = fieldInfo.FieldType.FullName,
							value = text,
							children = DescribeChildren(value2, depth + 1),
							truncated = (depth >= 4)
						});
					}
				}
				catch
				{
				}
			}
			return list;
		}

		private static List<ItemDefinitionParameter> DescribeChildren(object value, int depth)
		{
			if (value == null || depth > 4 || value is Object || value is string)
			{
				return null;
			}
			if (value is IEnumerable enumerable)
			{
				List<ItemDefinitionParameter> list = new List<ItemDefinitionParameter>();
				foreach (object item in enumerable)
				{
					if (list.Count >= 64)
					{
						list.Add(new ItemDefinitionParameter
						{
							name = "truncated",
							truncated = true
						});
						break;
					}
					list.Add(new ItemDefinitionParameter
					{
						name = list.Count.ToString(),
						type = item?.GetType().FullName,
						value = ((item == null) ? null : (TryDescribeValue(item, item.GetType(), out var text) ? text : item.GetType().Name)),
						children = DescribeChildren(item, depth + 1)
					});
				}
				return list;
			}
			if (value is Affliction || value is AdditionalCookingBehavior)
			{
				return DescribePublicParameters(value, depth);
			}
			return null;
		}

		private static string DescribeParameters(IEnumerable<ItemDefinitionParameter> parameters)
		{
			return string.Join(",", parameters.Select((ItemDefinitionParameter parameter) => parameter.name + "=" + parameter.value));
		}

		private static bool TryDescribeValue(object value, Type declaredType, out string text)
		{
			text = null;
			if (value == null)
			{
				return false;
			}
			Type type = value.GetType();
			if (type.IsEnum || type == typeof(bool) || type == typeof(byte) || type == typeof(short) || type == typeof(ushort) || type == typeof(int) || type == typeof(uint) || type == typeof(long) || type == typeof(float) || type == typeof(double) || type == typeof(decimal) || type == typeof(string))
			{
				text = Convert.ToString(value, CultureInfo.InvariantCulture);
				return true;
			}
			Object val = (Object)((value is Object) ? value : null);
			if (val != null)
			{
				text = ((val == (Object)null) ? declaredType.Name : (((object)val).GetType().Name + ":" + val.name));
				return true;
			}
			if (value is IEnumerable enumerable && !(value is string))
			{
				List<string> list = new List<string>();
				foreach (object item in enumerable)
				{
					if (list.Count >= 16)
					{
						list.Add("...");
						break;
					}
					string text2;
					if (item == null)
					{
						list.Add("null");
					}
					else if (TryDescribeValue(item, item.GetType(), out text2))
					{
						list.Add(text2);
					}
					else
					{
						list.Add(item.GetType().Name);
					}
				}
				text = "[" + string.Join("|", list) + "]";
				return true;
			}
			if (type.Namespace != null && type.Namespace.StartsWith("Peak.Afflictions", StringComparison.Ordinal))
			{
				text = type.Name;
				return true;
			}
			if (value is AdditionalCookingBehavior)
			{
				text = type.Name;
				return true;
			}
			return false;
		}

		private static void AddEffectHints(List<ItemEffectHint> result, ItemActionBase action)
		{
			string trigger = DescribeTriggers(action);
			Action_RestoreHunger val = (Action_RestoreHunger)(object)((action is Action_RestoreHunger) ? action : null);
			if (val != null)
			{
				result.Add(new ItemEffectHint
				{
					type = "RestoreStatus",
					target = "Hunger",
					hasAmount = true,
					amount = 0f - Mathf.Abs(val.restorationAmount),
					trigger = trigger
				});
				return;
			}
			Action_GiveExtraStamina val2 = (Action_GiveExtraStamina)(object)((action is Action_GiveExtraStamina) ? action : null);
			if (val2 != null)
			{
				result.Add(new ItemEffectHint
				{
					type = "GiveExtraStamina",
					target = "ExtraStamina",
					hasAmount = true,
					amount = val2.amount,
					trigger = trigger
				});
				return;
			}
			Action_ModifyStatus val3 = (Action_ModifyStatus)(object)((action is Action_ModifyStatus) ? action : null);
			if (val3 != null)
			{
				result.Add(new ItemEffectHint
				{
					type = "ModifyStatus",
					target = ((object)Unsafe.As<STATUSTYPE, STATUSTYPE>(ref val3.statusType)/*cast due to .constrained prefix*/).ToString(),
					hasAmount = true,
					amount = val3.changeAmount,
					trigger = trigger
				});
				return;
			}
			Action_ApplyAffliction val4 = (Action_ApplyAffliction)(object)((action is Action_ApplyAffliction) ? action : null);
			if (val4 != null)
			{
				result.Add(new ItemEffectHint
				{
					type = "ApplyAffliction",
					target = ((val4.affliction == null) ? null : ((object)val4.affliction).GetType().Name),
					trigger = trigger
				});
			}
			else if (action is Action_ClearAllStatus)
			{
				result.Add(new ItemEffectHint
				{
					type = "ClearStatuses",
					target = "Multiple",
					trigger = trigger
				});
			}
			else if (action is Action_ReduceUses)
			{
				result.Add(new ItemEffectHint
				{
					type = "ReduceUses",
					target = "ItemUses",
					trigger = trigger
				});
			}
			else if (((object)action).GetType().Name == "Action_Consume")
			{
				result.Add(new ItemEffectHint
				{
					type = "ConsumeItem",
					target = "Item",
					trigger = trigger
				});
			}
		}
	}
	internal sealed class LifecycleAnalysis
	{
		private sealed class State
		{
			internal EvidenceReference evidence;

			internal int player;

			internal int epoch;

			internal int count;

			internal float start;

			internal float end;

			internal float second = float.NaN;

			internal string kind;

			internal bool entered;
		}

		private readonly AnalysisResult _result;

		private readonly Dictionary<int, State> _current = new Dictionary<int, State>();

		private readonly List<State> _states = new List<State>();

		private readonly Dictionary<string, LifecycleObservation> _events = new Dictionary<string, LifecycleObservation>();

		private readonly List<Tuple<int, int, float>> _revivals = new List<Tuple<int, int, float>>();

		internal LifecycleAnalysis(AnalysisResult result)
		{
			_result = result;
		}

		internal void Interrupt(int player = -1)
		{
			if (player < 0)
			{
				_current.Clear();
			}
			else
			{
				_current.Remove(player);
			}
		}

		internal void Sample(float time, PlayerTelemetry p, EvidenceReference evidence)
		{
			string text = (p.dead ? "Death" : ((p.passedOut || p.fullyPassedOut) ? "PassedOut" : "Alive"));
			State value;
			bool flag = _current.TryGetValue(p.playerIndex, out value) && value.epoch == evidence.epoch && time > value.end && time - value.end <= 1f;
			if (!flag || value.kind != text)
			{
				State state = new State
				{
					evidence = evidence,
					player = p.playerIndex,
					epoch = evidence.epoch,
					start = time,
					end = time,
					count = 1,
					kind = text,
					entered = (flag && (value.kind == "Alive" || (text == "Death" && value.kind == "PassedOut")))
				};
				_current[p.playerIndex] = state;
				_states.Add(state);
				if (flag && value.kind != "Alive" && text == "Alive")
				{
					_result.timeline.Add(new ReportEvent
					{
						evidence = evidence,
						playerIndex = p.playerIndex,
						time = time,
						endTime = time,
						kind = "RecoveryObserved",
						confidence = "Likely"
					});
				}
			}
			else
			{
				value.end = time;
				value.count++;
				if (value.count == 2)
				{
					value.second = time;
				}
			}
		}

		internal void Event(StatsEvent e, EvidenceReference evidence)
		{
			if (e.type == "PlayerRevivedObserved" && evidence.epoch >= 0)
			{
				_revivals.Add(Tuple.Create(e.subjectPlayerIndex, evidence.epoch, e.time));
				Interrupt(e.subjectPlayerIndex);
			}
			if (!(e.type != "PlayerDied") || !(e.type != "PlayerPassedOut"))
			{
				string key = evidence.epoch + ":" + e.subjectPlayerIndex + ":" + e.type + ":" + e.time.ToString("R", CultureInfo.InvariantCulture);
				if (evidence.epoch >= 0 && _events.TryGetValue(key, out var value))
				{
					value.duplicateCount++;
					return;
				}
				LifecycleObservation lifecycleObservation = new LifecycleObservation
				{
					evidence = evidence,
					playerIndex = e.subjectPlayerIndex,
					time = e.time,
					kind = ((e.type == "PlayerDied") ? "Death" : "PassedOut")
				};
				_result.lifecycle.Add(lifecycleObservation);
				_events[key] = lifecycleObservation;
			}
		}

		internal void Finish()
		{
			foreach (AnalysisPlayer player in _result.players)
			{
				State state = null;
				State state2 = null;
				IOrderedEnumerable<LifecycleObservation> orderedEnumerable = from lifecycleObservation in _result.lifecycle
					where lifecycleObservation.playerIndex == player.playerIndex
					orderby lifecycleObservation.evidence.epoch, lifecycleObservation.time
					select lifecycleObservation;
				List<State> source = _states.Where((State s) => s.player == player.playerIndex).ToList();
				foreach (LifecycleObservation e in orderedEnumerable)
				{
					State state3 = ((e.evidence.epoch < 0) ? null : (from s in source
						where s.epoch == e.evidence.epoch && s.kind == e.kind && ((s.entered && Math.Abs(s.start - e.time) <= 2f) || (s.count >= 2 && s.start >= e.time && s.second <= e.time + 2f))
						orderby (!(e.time >= s.start) || !(e.time <= s.end)) ? Math.Min(Math.Abs(s.start - e.time), Math.Abs(s.end - e.time)) : 0f, Math.Abs(s.start - e.time)
						select s).FirstOrDefault());
					if (e.kind == "Death")
					{
						player.rawDeathCount += 1 + e.duplicateCount;
					}
					else
					{
						player.rawPassedOutCount += 1 + e.duplicateCount;
					}
					State credited = ((e.kind == "Death") ? state : state2);
					bool flag = credited == null || credited.epoch != e.evidence.epoch || source.Any((State s) => s.epoch == e.evidence.epoch && s.kind == "Alive" && s.count >= 2 && s.start > credited.end && s.second <= e.time) || _revivals.Any((Tuple<int, int, float> r) => r.Item1 == e.playerIndex && r.Item2 == e.evidence.epoch && r.Item3 > credited.end && r.Item3 <= e.time);
					if (state3 != null && state3 != credited && flag)
					{
						e.verdict = "Confirmed";
						e.reason = "StateCorroborated";
						if (e.kind == "Death")
						{
							player.deathCount++;
							player.deathTimes.Add(e.time);
							state = state3;
						}
						else
						{
							player.passedOutCount++;
							player.passedOutTimes.Add(e.time);
							state2 = state3;
						}
						_result.timeline.Add(new ReportEvent
						{
							evidence = e.evidence,
							playerIndex = e.playerIndex,
							time = e.time,
							endTime = e.time,
							kind = e.kind,
							confidence = "Certain"
						});
					}
					else if (credited != null && credited.epoch == e.evidence.epoch && (state3 == credited || !flag))
					{
						e.verdict = "Duplicate";
						e.reason = "AlreadyInState";
					}
					else
					{
						e.reason = ((e.evidence.epoch < 0) ? "ClockAmbiguous" : "StateNotCorroborated");
					}
					if (e.kind == "Death")
					{
						player.duplicateDeathCount += e.duplicateCount + ((e.verdict == "Duplicate") ? 1 : 0);
						if (e.verdict == "Unconfirmed")
						{
							player.unconfirmedDeathCount++;
						}
					}
				}
			}
			_result.overview.deathCount = _result.players.Sum((AnalysisPlayer p) => p.deathCount);
			_result.overview.passedOutCount = _result.players.Sum((AnalysisPlayer p) => p.passedOutCount);
			foreach (State state4 in _states)
			{
				_result.lifecycleIntervals.Add(new LifecycleInterval
				{
					evidence = state4.evidence,
					playerIndex = state4.player,
					start = state4.start,
					end = state4.end,
					state = state4.kind,
					entered = state4.entered,
					sampleCount = state4.count
				});
				if (state4.entered && state4.kind != "Alive" && !_result.lifecycle.Any((LifecycleObservation lifecycleObservation) => lifecycleObservation.playerIndex == state4.player && lifecycleObservation.kind == state4.kind && lifecycleObservation.evidence.epoch == state4.epoch && lifecycleObservation.verdict == "Confirmed" && Math.Abs(lifecycleObservation.time - state4.start) <= 2f))
				{
					_result.timeline.Add(new ReportEvent
					{
						evidence = state4.evidence,
						playerIndex = state4.player,
						time = state4.start,
						endTime = state4.start,
						kind = state4.kind + "StateObserved",
						confidence = "Likely"
					});
				}
			}
		}
	}
	[HarmonyPatch(typeof(PauseMenuMainPage), "Start")]
	internal static class PauseMenuMainPagePatches
	{
		private static void Postfix(PauseMenuMainPage __instance)
		{
			StateKeeperUi.BuildPauseMenu(__instance);
		}
	}
	[HarmonyPatch(typeof(PauseMenuMainPage), "OnEnable")]
	internal static class PauseMenuMainPageEnablePatches
	{
		private static void Postfix(PauseMenuMainPage __instance)
		{
			StateKeeperUi.BuildPauseMenu(__instance);
		}
	}
	internal sealed class PerformanceWindow
	{
		private long _count;

		private double _total;

		private double _maximum;

		internal void Record(long start)
		{
			double num = (double)(Stopwatch.GetTimestamp() - start) * 1000.0 / (double)Stopwatch.Frequency;
			_count++;
			_total += num;
			if (num > _maximum)
			{
				_maximum = num;
			}
		}

		internal string Take(string name)
		{
			string result = string.Format(CultureInfo.InvariantCulture, "{0}: calls={1}, mean={2:0.000}ms, max={3:0.000}ms", name, _count, (_count > 0) ? (_total / (double)_count) : 0.0, _maximum);
			_count = 0L;
			_total = (_maximum = 0.0);
			return result;
		}
	}
	internal sealed class RecordedItemRules
	{
		internal sealed class Parameters
		{
			private readonly List<ItemDefinitionParameter> _entries;

			private readonly string _legacy;

			internal Parameters(List<ItemDefinitionParameter> entries, string legacy)
			{
				_entries = entries ?? new List<ItemDefinitionParameter>();
				_legacy = legacy;
			}

			internal ItemDefinitionParameter Entry(string name)
			{
				return _entries.FirstOrDefault((ItemDefinitionParameter e) => e != null && e.name == name);
			}

			internal string String(string name)
			{
				ItemDefinitionParameter itemDefinitionParameter = Entry(name);
				if (itemDefinitionParameter != null)
				{
					if (!itemDefinitionParameter.truncated)
					{
						return itemDefinitionParameter.value;
					}
					return null;
				}
				if (string.IsNullOrEmpty(_legacy))
				{
					return null;
				}
				MatchCollection matchCollection = Regex.Matches(_legacy, "(?:^|,)" + Regex.Escape(name) + "=([^,\\[\\]]+)(?=,|$)");
				if (matchCollection.Count != 1)
				{
					return null;
				}
				return matchCollection[0].Groups[1].Value;
			}

			internal float? Number(string name)
			{
				if (!float.TryParse(String(name), NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || !RunAnalysisEngine.Finite(result))
				{
					return null;
				}
				return result;
			}

			internal bool? Boolean(string name)
			{
				if (!bool.TryParse(String(name), out var result))
				{
					return null;
				}
				return result;
			}
		}

		private readonly RunRecord _record;

		internal static readonly string[] HealingOrder = new string[6] { "Injury", "Spores", "Poison", "Cold", "Hot", "Drowsy" };

		internal RecordedItemRules(RunRecord record)
		{
			_record = record;
		}

		internal List<AnalysisEffectRule> Compile(AnalysisItemObservation row, List<string> reasons)
		{
			ItemDefinition itemDefinition = RunAnalysisEngine.Definition(_record.definitions ?? new List<ItemDefinition>(), row.itemId, row.prefabName);
			List<AnalysisEffectRule> list = new List<AnalysisEffectRule>();
			if (itemDefinition == null)
			{
				reasons.Add("DefinitionNotRecorded");
				return list;
			}
			int num = 0;
			foreach (ItemDefinitionAction item in itemDefinition.actions ?? new List<ItemDefinitionAction>())
			{
				if (item == null)
				{
					continue;
				}
				Parameters parameters = new Parameters(item.parameters, item.parameterSummary);
				string source = item.typeName + "#" + num++;
				int count = list.Count;
				switch (item.typeName)
				{
				case "Action_RestoreHunger":
					Add(list, "Hunger", Negative(parameters.Number("restorationAmount")), -1, source);
					break;
				case "Action_GiveExtraStamina":
					Add(list, "ExtraStamina", parameters.Number("amount"), 1, source);
					break;
				case "Action_ApplyInfiniteStamina":
					Add(list, "Affliction:InfiniteStamina", 1f, 1, source);
					break;
				case "Action_ModifyStatus":
				{
					string text = parameters.String("statusType");
					float? amount = parameters.Number("changeAmount");
					if (!string.IsNullOrEmpty(text) && amount.HasValue)
					{
						Add(list, text, amount, Math.Sign(amount.Value), source);
					}
					else
					{
						reasons.Add("StatusTypeNotRecorded");
					}
					if (parameters.Boolean("ifSkeleton") != true && parameters.Boolean("ifSkeleton").HasValue)
					{
						break;
					}
					foreach (AnalysisEffectRule item2 in list.Skip(count))
					{
						item2.uncertain = true;
					}
					break;
				}
				case "Action_ClearAllStatus":
				{
					string[] healingOrder = _record.statusTypeOrder ?? new string[0];
					foreach (string channel2 in healingOrder)
					{
						if (!new string[4] { "Weight", "Petrify", "Arrow", "Thorns" }.Contains(channel2) && (channel2 != "Curse" || parameters.Boolean("excludeCurse") == false) && !(parameters.Entry("otherExclusions")?.children ?? new List<ItemDefinitionParameter>()).Any((ItemDefinitionParameter v) => v.value == channel2))
						{
							Add(list, channel2, -5f, -1, source).uncertain = parameters.Entry("otherExclusions") == null;
						}
					}
					break;
				}
				case "Action_InflictPoison":
				{
					Add(list, "Affliction:PoisonOverTime", 1f, 1, source);
					AnalysisEffectRule analysisEffectRule2 = Add(list, "Poison", parameters.Number("poisonPerSecond"), 1, source);
					analysisEffectRule2.delay = parameters.Number("delay").GetValueOrDefault();
					analysisEffectRule2.duration = parameters.Number("inflictionTime").GetValueOrDefault();
					analysisEffectRule2.continuous = true;
					analysisEffectRule2.requiredAffliction = "PoisonOverTime";
					analysisEffectRule2.uncertain = !parameters.Number("delay").HasValue || analysisEffectRule2.duration <= 0f;
					break;
				}
				case "Action_ApplyAffliction":
				case "Action_ApplyMassAffliction":
					CompileAffliction(parameters.Entry("affliction"), list, source, 0f, reasons);
					foreach (ItemDefinitionParameter item3 in parameters.Entry("extraAfflictions")?.children ?? new List<ItemDefinitionParameter>())
					{
						CompileAffliction(item3, list, source, 0f, reasons);
					}
					if (!(item.typeName == "Action_ApplyMassAffliction"))
					{
						break;
					}
					foreach (AnalysisEffectRule item4 in list.Skip(count))
					{
						item4.scope = "Area";
						item4.radius = parameters.Number("radius");
						item4.ignoreCaster = parameters.Boolean("ignoreCaster") == true;
						item4.uncertain |= !item4.radius.HasValue;
					}
					break;
				case "Peak.Action_HealingGem":
				{
					CompileAffliction(parameters.Entry("healingAffliction"), list, source, 0f, reasons);
					CompileAffliction(parameters.Entry("invincibilityAffliction"), list, source, 0f, reasons);
					if (list.Count == count)
					{
						string[] healingOrder = HealingOrder;
						foreach (string channel in healingOrder)
						{
							Add(list, channel, null, -1, source).uncertain = true;
						}
					}
					AnalysisEffectRule analysisEffectRule = Add(list, "Petrify", null, 1, source);
					analysisEffectRule.scope = "Actor";
					analysisEffectRule.uncertain = true;
					break;
				}
				case "Action_RandomMushroomEffect":
					Mushroom(parameters, list, row.time, source, reasons);
					break;
				default:
					if (!NonEffectAction(item.typeName))
					{
						reasons.Add("UnsupportedAction:" + item.typeName);
					}
					break;
				}
				foreach (AnalysisEffectRule item5 in list.Skip(count))
				{
					item5.trigger = item.trigger;
					if (!RunAnalysisEngine.Finite(item5.delay) || !RunAnalysisEngine.Finite(item5.duration) || item5.delay < 0f || item5.duration < 0f)
					{
						item5.uncertain = true;
					}
				}
			}
			Cook(itemDefinition, row.cookedAmount, list, reasons);
			return list.GroupBy(delegate(AnalysisEffectRule r)
			{
				string[] obj = new string[25]
				{
					r.channel,
					"|",
					r.delay.ToString(),
					"|",
					r.duration.ToString(),
					"|",
					r.scope,
					"|",
					r.trigger,
					"|",
					r.budgetGroup,
					"|",
					r.continuous.ToString(),
					"|",
					r.requiredAffliction,
					"|",
					r.afterAfflictionEnds,
					"|",
					r.direction.ToString(),
					"|",
					null,
					null,
					null,
					null,
					null
				};
				float? radius = r.radius;
				obj[20] = radius.ToString();
				obj[21] = "|";
				obj[22] = r.ignoreCaster.ToString();
				obj[23] = "|";
				obj[24] = r.priority.ToString();
				return string.Concat(obj);
			}).Select(delegate(IGrouping<string, AnalysisEffectRule> group)
			{
				AnalysisEffectRule analysisEffectRule3 = group.First();
				if (group.Count() > 1 && group.All((AnalysisEffectRule r) => r.amount.HasValue))
				{
					analysisEffectRule3.amount = group.Sum((AnalysisEffectRule r) => r.amount.Value);
				}
				analysisEffectRule3.uncertain |= group.Any((AnalysisEffectRule r) => r.uncertain);
				return analysisEffectRule3;
			}).ToList();
		}

		internal List<AnalysisEffectRule> Passive(ItemDefinition definition)
		{
			List<AnalysisEffectRule> list = new List<AnalysisEffectRule>();
			foreach (ItemDefinitionComponent item in definition?.components ?? new List<ItemDefinitionComponent>())
			{
				Parameters parameters = new Parameters(item.parameters, item.parameterSummary);
				if (item.typeName == "Peak.InfiniteStamAmulet")
				{
					AnalysisEffectRule analysisEffectRule = Add(list, "Affliction:InfiniteStamina", 1f, 1, "PEAK2.4.b:InfiniteStamAmulet");
					analysisEffectRule.scope = "PlayerArea";
					analysisEffectRule.radius = parameters.Number("radius");
					analysisEffectRule.continuous = true;
					analysisEffectRule.uncertain = !analysisEffectRule.radius.HasValue;
					AnalysisEffectRule analysisEffectRule2 = Add(list, "Petrify", parameters.Number("petrifyPerSecond") / 100f, 1, "PEAK2.4.b:InfiniteStamAmulet");
					analysisEffectRule2.continuous = true;
					analysisEffectRule2.scope = "Actor";
				}
				if (item.typeName == "Peak.DoubleJumpAmulet")
				{
					Add(list, "Affliction:DoubleJumpAmulet", 1f, 1, "PEAK2.4.b:DoubleJumpAmulet").continuous = true;
					AnalysisEffectRule analysisEffectRule3 = Add(list, "Petrify", parameters.Number("petrifyPerJump") / 100f, 1, "PEAK2.4.b:DoubleJumpAmulet");
					analysisEffectRule3.continuous = true;
					analysisEffectRule3.uncertain = true;
				}
			}
			if (!(definition?.components ?? new List<ItemDefinitionComponent>()).Any((ItemDefinitionComponent c) => c.typeName == "Peak.HealingAmulet"))
			{
				return list;
			}
			string[] array = new string[6] { "Injury", "Poison", "Spores", "Cold", "Drowsy", "Hot" };
			for (int num = 0; num < array.Length; num++)
			{
				AnalysisEffectRule analysisEffectRule4 = Add(list, array[num], -0.05f, -1, "PEAK2.4.b:HealingAmulet");
				analysisEffectRule4.continuous = true;
				analysisEffectRule4.budgetGroup = "HealingAmuletTick";
				analysisEffectRule4.priority = num;
				analysisEffectRule4.budget = 0.05f;
			}
			Add(list, "Petrify", 0.02f, 1, "PEAK2.4.b:HealingAmulet").continuous = true;
			return list;
		}

		private static AnalysisEffectRule Add(List<AnalysisEffectRule> rules, string channel, float? amount, int direction, string source)
		{
			AnalysisEffectRule analysisEffectRule = new AnalysisEffectRule
			{
				channel = channel,
				amount = amount,
				direction = (amount.HasValue ? Math.Sign(amount.Value) : direction),
				source = source
			};
			rules.Add(analysisEffectRule);
			return analysisEffectRule;
		}

		private static float? Negative(float? number)
		{
			if (!number.HasValue)
			{
				return null;
			}
			return 0f - Math.Abs(number.Value);
		}

		private static void CompileAffliction(ItemDefinitionParameter entry, List<AnalysisEffectRule> rules, string source, float delay, List<string> reasons, int depth = 0)
		{
			if (entry == null)
			{
				reasons.Add("NestedParametersNotRecorded");
				return;
			}
			if (depth > 4 || entry.truncated)
			{
				reasons.Add("TruncatedParameters");
				return;
			}
			string type = entry.type;
			string text = ((type != null && type.StartsWith("Peak.Afflictions.Affliction_", StringComparison.Ordinal)) ? entry.type.Substring("Peak.Afflictions.Affliction_".Length) : null);
			if (text == null)
			{
				string value = entry.value;
				if (value != null && value.StartsWith("Affliction_", StringComparison.Ordinal))
				{
					text = entry.value.Substring("Affliction_".Length);
				}
			}
			if (text == null)
			{
				reasons.Add("UnknownAfflictionType");
				return;
			}
			Parameters parameters = new Parameters(entry.children, null);
			AnalysisEffectRule analysisEffectRule = Add(rules, "Affliction:" + text switch
			{
				"Exhaustion" => "Exhausted", 
				"AdjustColdOverTime" => "ColdOverTime", 
				"AdjustDrowsyOverTime" => "DrowsyOverTime", 
				_ => text, 
			}, 1f, 1, source);
			analysisEffectRule.delay = delay;
			int count = rules.Count;
			switch (text)
			{
			case "HealAll":
			{
				for (int i = 0; i < HealingOrder.Length; i++)
				{
					AnalysisEffectRule analysisEffectRule2 = Add(rules, HealingOrder[i], Negative(parameters.Number("maxHealing")), -1, source);
					analysisEffectRule2.budgetGroup = source + ":HealAll";
					analysisEffectRule2.budget = parameters.Number("maxHealing");
					analysisEffectRule2.priority = i;
					analysisEffectRule2.uncertain = !analysisEffectRule2.budget.HasValue;
				}
				break;
			}
			case "ClearAllStatus":
				foreach (string item in HealingOrder.Concat(new string[1] { "Hunger" }))
				{
					Add(rules, item, -2f, -1, source);
				}
				break;
			case "AdjustStatus":
				if (parameters.String("statusType") != null && parameters.Number("statusAmount").HasValue)
				{
					Add(rules, parameters.String("statusType"), parameters.Number("statusAmount"), 0, source);
				}
				else
				{
					reasons.Add("NestedParametersNotRecorded");
				}
				break;
			case "AddBonusStamina":
				Add(rules, "ExtraStamina", parameters.Number("staminaAmount"), 1, source);
				break;
			case "PoisonOverTime":
			case "AdjustDrowsyOverTime":
			case "AdjustColdOverTime":
			{
				AnalysisEffectRule analysisEffectRule4 = Add(rules, (text == "AdjustDrowsyOverTime") ? "Drowsy" : ((text == "AdjustColdOverTime") ? "Cold" : "Poison"), parameters.Number("statusPerSecond"), 0, source);
				analysisEffectRule4.delay = parameters.Number("delayBeforeEffect").GetValueOrDefault();
				analysisEffectRule4.duration = parameters.Number("totalTime").GetValueOrDefault();
				analysisEffectRule4.continuous = true;
				analysisEffectRule4.requiredAffliction = analysisEffectRule.channel.Substring(11);
				analysisEffectRule4.uncertain = !analysisEffectRule4.amount.HasValue || analysisEffectRule4.duration <= 0f;
				break;
			}
			case "FasterBoi":
			{
				Add(rules, "Drowsy", -0.5f, -1, source);
				AnalysisEffectRule analysisEffectRule3 = Add(rules, "Drowsy", parameters.Number("drowsyOnEnd"), 1, source);
				analysisEffectRule3.afterAfflictionEnds = "FasterBoi";
				analysisEffectRule3.uncertain = !analysisEffectRule3.amount.HasValue;
				break;
			}
			case "InfiniteStamina":
			{
				if (parameters.Entry("drowsyAffliction") == null)
				{
					break;
				}
				int count2 = rules.Count;
				CompileAffliction(parameters.Entry("drowsyAffliction"), rules, source, 0f, reasons, depth + 1);
				foreach (AnalysisEffectRule item2 in rules.Skip(count2))
				{
					item2.afterAfflictionEnds = "InfiniteStamina";
				}
				break;
			}
			}
			foreach (AnalysisEffectRule item3 in rules.Skip(count))
			{
				item3.delay += delay;
			}
		}

		private void Mushroom(Parameters p, List<AnalysisEffectRule> rules, float time, string source, List<string> reasons)
		{
			int num = -1;
			if (p.Boolean("useDebugEffect") == true && p.Number("debugEffect").HasValue)
			{
				num = (int)p.Number("debugEffect").Value;
			}
			else
			{
				RunEffectContext runEffectContext = (from c in _record.effectContexts ?? new List<RunEffectContext>()
					where c != null && c.observedTime <= time
					orderby c.observedTime
					select c).LastOrDefault();
				int num2 = (int)(p.Number("mushroomTypeIndex") ?? (-1f));
				if (runEffectContext?.mushroomEffects == null || runEffectContext.mushroomEffects.Length == 0 || num2 < 0)
				{
					reasons.Add("RandomContextNotRecorded");
					return;
				}
				num2 %= runEffectContext.mushroomEffects.Length;
				num = runEffectContext.mushroomEffects[num2];
				if (runEffectContext.mushroomStaminaAmounts != null && num2 < runEffectContext.mushroomStaminaAmounts.Length)
				{
					Add(rules, "ExtraStamina", (float)runEffectContext.mushroomStaminaAmounts[num2] * 0.05f, 1, source);
				}
			}
			string[] array = new string[4] { "InfiniteStamina", "FasterBoi", "LowGravity", "Invincibility" };
			if (num >= 0 && num <= 3)
			{
				Add(rules, "Affliction:" + array[num], 1f, 1, source).delay = ((num != 3) ? 3 : 0);
				return;
			}
			switch (num)
			{
			case 4:
			{
				string[] array2 = new string[3] { "Hunger", "Injury", "Poison" };
				foreach (string channel in array2)
				{
					Add(rules, channel, -0.15f, -1, source);
				}
				break;
			}
			case 6:
			case 9:
				Add(rules, "Affliction:" + ((num == 6) ? "Blind" : "Numb"), 1f, 1, source).delay = 3f;
				break;
			case 8:
				Add(rules, "Spores", 0.25f, 1, source);
				break;
			default:
				reasons.Add("EffectNotObservableInTelemetry");
				break;
			}
		}

		private static void Cook(ItemDefinition definition, int? cooked, List<AnalysisEffectRule> rules, List<string> reasons)
		{
			if (definition.cookingRules == null || definition.cookingRules.Count == 0)
			{
				return;
			}
			if (!cooked.HasValue)
			{
				reasons.Add("CookingStateUnknown");
				{
					foreach (AnalysisEffectRule rule in rules)
					{
						rule.uncertain = true;
					}
					return;
				}
			}
			if (cooked <= 0)
			{
				return;
			}
			foreach (ItemDefinitionCooking cookingRule in definition.cookingRules)
			{
				Parameters parameters = new Parameters(cookingRule.parameters, cookingRule.parameterSummary);
				if (!(cookingRule.typeName != "ItemCooking"))
				{
					ItemDefinitionParameter itemDefinitionParameter = parameters.Entry("additionalCookingBehaviors");
					if (itemDefinitionParameter == null || !(itemDefinitionParameter.children?.Count > 0))
					{
						if (parameters.Boolean("wreckWhenCooked") == true)
						{
							rules.Clear();
							reasons.Add("WreckedItem");
							break;
						}
						if (parameters.Boolean("ignoreDefaultCookBehavior") == true)
						{
							continue;
						}
						if (!parameters.Boolean("ignoreDefaultCookBehavior").HasValue)
						{
							foreach (AnalysisEffectRule rule2 in rules)
							{
								rule2.uncertain = true;
							}
							reasons.Add("CookingRulesNotRecorded");
							continue;
						}
						AnalysisEffectRule analysisEffectRule = rules.FirstOrDefault((AnalysisEffectRule r) => r.source.StartsWith("Action_RestoreHunger#", StringComparison.Ordinal));
						if (analysisEffectRule != null && analysisEffectRule.amount.HasValue)
						{
							analysisEffectRule.amount = 0f - Math.Max(0f, Math.Abs(analysisEffectRule.amount.Value) * 2f - (float)Math.Max(0, cooked.Value - 2) * 0.05f);
						}
						AnalysisEffectRule analysisEffectRule2 = rules.FirstOrDefault((AnalysisEffectRule r) => r.source.StartsWith("Action_GiveExtraStamina#", StringComparison.Ordinal));
						if (analysisEffectRule2 == null)
						{
							analysisEffectRule2 = Add(rules, "ExtraStamina", 0f, 1, "ItemCooking");
							analysisEffectRule2.trigger = "OnConsumed";
						}
						analysisEffectRule2.amount = ((cooked <= 2) ? Math.Max(0.1f, analysisEffectRule2.amount.GetValueOrDefault() * 1.5f) : 0f);
						analysisEffectRule2.direction = ((analysisEffectRule2.amount > 0f) ? 1 : 0);
						if (cooked >= 4 && parameters.Boolean("ignoreDefaultPoisonBehavior") == false)
						{
							Add(rules, "Poison", 0.1f * (float)(cooked.Value - 3), 1, "ItemCooking").trigger = "OnConsumed";
						}
						continue;
					}
				}
				reasons.Add("CustomCookingRules");
				foreach (AnalysisEffectRule rule3 in rules)
				{
					rule3.uncertain = true;
				}
			}
		}

		private static bool NonEffectAction(string type)
		{
			return new string[10] { "Action_Consume", "Action_ReduceUses", "Action_PlayAnimation", "Action_PlaySound", "Action_PlaySFX", "Action_PlayParticles", "Action_StrangeGem", "Action_RopeShooter", "Action_Shoot", "Action_ApplySuperJump" }.Contains(type);
		}
	}
	internal sealed class RecordingClock
	{
		private readonly Func<double> _seconds;

		private double _origin;

		private double _offset;

		internal float Time => (float)(_offset + Math.Max(0.0, _seconds() - _origin));

		internal RecordingClock(Func<double> seconds = null)
		{
			_seconds = seconds ?? ((Func<double>)(() => (double)Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency));
			Reset(0.0);
		}

		internal void Reset(double offset)
		{
			_origin = _seconds();
			_offset = Math.Max(0.0, offset);
		}
	}
	internal sealed class ReportAnalysis
	{
		private sealed class Frame
		{
			internal float time;

			internal PlayerTelemetry value;
		}

		private sealed class Pending
		{
			internal ReportEpisode episode;

			internal float last;

			internal bool wasActive;

			internal float minimum;
		}

		internal static readonly string[] MovementTypes = new string[8] { "Unconscious", "Rope", "Vine", "Climbing", "Gliding", "Sprinting", "Grounded", "Airborne" };

		private readonly AnalysisResult _result;

		private readonly Dictionary<int, AnalysisPlayer> _players;

		private readonly Dictionary<int, Frame> _previous = new Dictionary<int, Frame>();

		private readonly Dictionary<int, Queue<Frame>> _statusWindow = new Dictionary<int, Queue<Frame>>();

		private readonly Dictionary<string, Pending> _pending = new Dictionary<string, Pending>();

		private readonly Dictionary<string, bool> _together = new Dictionary<string, bool>();

		internal static string Movement(PlayerTelemetry p)
		{
			if (!p.passedOut && !p.fullyPassedOut)
			{
				if (!p.ropeClimbing)
				{
					if (!p.vineClimbing)
					{
						if (!p.climbing)
						{
							if (!p.gliding)
							{
								if (!p.sprinting)
								{
									if (!p.grounded)
									{
										return "Airborne";
									}
									return "Grounded";
								}
								return "Sprinting";
							}
							return "Gliding";
						}
						return "Climbing";
					}
					return "Vine";
				}
				return "Rope";
			}
			return "Unconscious";
		}

		internal ReportAnalysis(AnalysisResult result)
		{
			_result = result;
			_players = result.players.ToDictionary((AnalysisPlayer p) => p.playerIndex);
		}

		internal void Interrupt(int player = -1)
		{
			string[] array = (from p in _pending
				where player < 0 || p.Value.episode.playerIndex == player || p.Value.episode.otherPlayerIndex == player
				select p.Key).ToArray();
			foreach (string key in array)
			{
				Close(key);
			}
			if (player < 0)
			{
				_previous.Clear();
				_statusWindow.Clear();
				_together.Clear();
				return;
			}
			_previous.Remove(player);
			_statusWindow.Remove(player);
			array = _together.Keys.Where((string k) => k.StartsWith(player + ":", StringComparison.Ordinal) || k.EndsWith(":" + player, StringComparison.Ordinal)).ToArray();
			foreach (string key2 in array)
			{
				_together.Remove(key2);
			}
		}

		internal void Sample(float time, Dictionary<int, PlayerTelemetry> current, EvidenceReference evidence)
		{
			int[] array = _previous.Keys.Where((int p) => !current.ContainsKey(p)).ToArray();
			foreach (int player in array)
			{
				Interrupt(player);
			}
			foreach (PlayerTele