Decompiled source of Sanity v0.0.2

plugins/Lowlight.Sanity.dll

Decompiled 19 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using GameNetcodeStuff;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Sanity.Compat;
using Sanity.Symptoms;
using TMPro;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.Rendering;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Lowlight.Sanity")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.0.2.0")]
[assembly: AssemblyInformationalVersion("0.0.2+440439d4ded3a4a22616832f815030fa2ae54529")]
[assembly: AssemblyProduct("Lowlight.Sanity")]
[assembly: AssemblyTitle("Sanity")]
[assembly: AssemblyMetadata("AI_Assisted_Creation", "This assembly was partially or fully created with the assistance of Generative AI (Claude Opus 5.5).")]
[assembly: AssemblyMetadata("AI_Model_Vendor", "Anthropic")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.2.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 BepInEx
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	[Embedded]
	internal sealed class BepInAutoPluginAttribute : Attribute
	{
		public BepInAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
		{
		}
	}
}
namespace BepInEx.Preloader.Core.Patching
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	[Embedded]
	internal sealed class PatcherAutoPluginAttribute : Attribute
	{
		public PatcherAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
		{
		}
	}
}
namespace Microsoft.CodeAnalysis
{
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace Sanity
{
	internal static class DayLength
	{
		private const float MeasureAfterSeconds = 20f;

		private const float LogAfterSeconds = 60f;

		private const float RestartJump = 0.01f;

		private static bool started;

		private static bool logged;

		private static float startTime;

		private static float startFraction;

		private static float lastFraction;

		internal static float Factor { get; private set; } = 1f;

		internal static bool Measured { get; private set; }

		internal static void Tick(TimeOfDay timeOfDay)
		{
			if ((Object)(object)timeOfDay == (Object)null || (Object)(object)timeOfDay.currentLevel == (Object)null || !timeOfDay.currentDayTimeStarted || timeOfDay.totalTime <= 0f)
			{
				return;
			}
			float normalizedTimeOfDay = timeOfDay.normalizedTimeOfDay;
			if (!started || Mathf.Abs(normalizedTimeOfDay - lastFraction) > 0.01f)
			{
				started = true;
				startTime = Time.time;
				startFraction = normalizedTimeOfDay;
			}
			lastFraction = normalizedTimeOfDay;
			float num = Time.time - startTime;
			float num2 = normalizedTimeOfDay - startFraction;
			float num3 = 1f / timeOfDay.totalTime;
			if (!(num < 20f) && !(num2 <= 0.0005f) && !(num3 <= 0f) && !(normalizedTimeOfDay >= 0.995f))
			{
				Factor = Mathf.Clamp(num3 * num / num2, 0.25f, 8f);
				Measured = true;
				if (!logged && num >= 60f)
				{
					logged = true;
					Telemetry.Event("DAY_LENGTH", Factor, FormattableString.Invariant($"day lasts x{Factor:F2} a normal day ({1f / Mathf.Max(1E-06f, num2 / num):F0} real s per day; total time {timeOfDay.totalTime:F0}, moon day speed {timeOfDay.currentLevel.DaySpeedMultiplier:0.###}, global time speed {timeOfDay.globalTimeSpeedMultiplier:0.###})"));
				}
			}
		}

		internal static void Reset()
		{
			Factor = 1f;
			Measured = false;
			started = false;
			logged = false;
		}
	}
	internal static class DebugLog
	{
		internal struct Entry
		{
			internal float At;

			internal string Kind;

			internal string Mark;

			internal string Text;
		}

		private const int Max = 80;

		private static readonly List<Entry> Entries = new List<Entry>(80);

		internal static IReadOnlyList<Entry> All => Entries;

		internal static void Clear()
		{
			Entries.Clear();
		}

		internal static void Note(string name, string detail)
		{
			string mark = ".";
			if (name == null)
			{
				return;
			}
			string text;
			switch (name.Length)
			{
			default:
				return;
			case 7:
			{
				char c = name[0];
				if (c != 'F')
				{
					if (c != 'R')
					{
						if (c != 'S' || !(name == "SYMPTOM"))
						{
							return;
						}
						text = (detail.Contains("(chain from") ? "chain" : "symptom");
						mark = ((text == "chain") ? "->" : ">");
						break;
					}
					if (!(name == "REVIVED"))
					{
						return;
					}
					goto IL_0522;
				}
				if (!(name == "FLICKER"))
				{
					return;
				}
				goto IL_0538;
			}
			case 13:
				switch (name[0])
				{
				case 'S':
					break;
				case 'T':
					goto IL_0220;
				case 'F':
					goto IL_0231;
				case 'P':
					goto IL_0242;
				case 'L':
					goto IL_0263;
				default:
					return;
				}
				if (!(name == "SYMPTOM_ARMED"))
				{
					return;
				}
				text = "symptom";
				mark = "+";
				break;
			case 15:
			{
				char c = name[0];
				if (c != 'F')
				{
					if (c != 'P')
					{
						if (c != 'S' || !(name == "SYMPTOM_DROPPED"))
						{
							return;
						}
						goto IL_04bc;
					}
					if (!(name == "PHANTOM_HIT_END"))
					{
						return;
					}
				}
				else if (!(name == "FAKE_DOOR_AFTER"))
				{
					return;
				}
				goto IL_0538;
			}
			case 14:
			{
				char c = name[2];
				if ((uint)c <= 69u)
				{
					if (c != 'A')
					{
						if (c != 'E' || !(name == "PRESENCE_EVENT"))
						{
							return;
						}
						goto IL_0500;
					}
					if (!(name == "PHANTOM_WALKIE"))
					{
						return;
					}
					goto IL_0538;
				}
				if (c != 'M')
				{
					if (c != 'O' || !(name == "PROMPT_MISSING"))
					{
						return;
					}
					goto IL_052a;
				}
				if (!(name == "SYMPTOM_FAILED"))
				{
					return;
				}
				goto IL_04bc;
			}
			case 5:
			{
				char c = name[0];
				if (c != 'C')
				{
					if (c != 'R' || !(name == "RELAX"))
					{
						return;
					}
					goto IL_0522;
				}
				if (!(name == "CHAIN"))
				{
					return;
				}
				text = "chain";
				mark = (detail.EndsWith("played") ? "->" : (detail.Contains("didn't play") ? "x" : "."));
				break;
			}
			case 12:
			{
				char c = name[0];
				if (c != 'P')
				{
					if (c != 'S' || !(name == "SANITY_ERROR"))
					{
						return;
					}
					goto IL_052a;
				}
				if (!(name == "PRESENCE_END"))
				{
					return;
				}
				goto IL_0500;
			}
			case 6:
			{
				char c = name[0];
				if ((uint)c <= 68u)
				{
					switch (c)
					{
					default:
						return;
					case 'B':
						if (!(name == "BOUNCE"))
						{
							return;
						}
						break;
					case 'D':
						if (!(name == "DAMAGE"))
						{
							return;
						}
						break;
					}
				}
				else
				{
					if (c != 'R')
					{
						if (c != 'V' || !(name == "VANISH"))
						{
							return;
						}
						goto IL_0538;
					}
					if (!(name == "RELIEF"))
					{
						return;
					}
				}
				goto IL_0522;
			}
			case 4:
				switch (name[0])
				{
				default:
					return;
				case 'M':
					if (!(name == "MOOD"))
					{
						return;
					}
					break;
				case 'E':
					if (!(name == "ECHO"))
					{
						return;
					}
					break;
				}
				goto IL_0522;
			case 9:
			{
				char c = name[1];
				if (c != 'A')
				{
					if (c == 'E')
					{
						if (!(name == "FEAR_JUMP"))
						{
							return;
						}
						goto IL_0522;
					}
					if (c != 'O' || !(name == "LOST_TIME"))
					{
						return;
					}
				}
				else if (!(name == "FAKE_DOOR"))
				{
					return;
				}
				goto IL_0538;
			}
			case 20:
			{
				char c = name[0];
				if (c != 'P')
				{
					if (c != 'T' || !(name == "TEAMMATE_DIED_NEARBY"))
					{
						return;
					}
					goto IL_0522;
				}
				if (!(name == "PHANTOM_WALKIE_REPLY"))
				{
					return;
				}
				goto IL_0538;
			}
			case 18:
			{
				char c = name[8];
				if (c != 'H')
				{
					if (c != 'W')
					{
						if (c != '_' || !(name == "TEAMMATE_DIED_SHIP"))
						{
							return;
						}
						goto IL_0522;
					}
					if (!(name == "PHANTOM_WALKIE_END"))
					{
						return;
					}
				}
				else if (!(name == "PHANTOM_HIT_HEALED"))
				{
					return;
				}
				goto IL_0538;
			}
			case 16:
				if (!(name == "SYMPTOM_REJECTED"))
				{
					return;
				}
				goto IL_04bc;
			case 8:
				if (!(name == "PRESENCE"))
				{
					return;
				}
				goto IL_0500;
			case 10:
				if (!(name == "VANISH_END"))
				{
					return;
				}
				goto IL_0538;
			case 11:
				if (!(name == "PHANTOM_HIT"))
				{
					return;
				}
				goto IL_0538;
			case 17:
				if (!(name == "PHANTOM_STEPS_END"))
				{
					return;
				}
				goto IL_0538;
			case 19:
				return;
				IL_04bc:
				text = "symptom";
				mark = "x";
				break;
				IL_0220:
				if (!(name == "TEAMMATE_DIED"))
				{
					return;
				}
				goto IL_0522;
				IL_0538:
				text = "symptom";
				break;
				IL_0522:
				text = "meter";
				break;
				IL_0500:
				text = "presence";
				mark = ((name == "PRESENCE") ? ">" : ".");
				break;
				IL_0263:
				if (!(name == "LOST_TIME_END"))
				{
					return;
				}
				goto IL_0538;
				IL_0242:
				if (!(name == "PHANTOM_SOUND") && !(name == "PHANTOM_STEPS"))
				{
					return;
				}
				goto IL_0538;
				IL_0231:
				if (!(name == "FAKE_DOOR_END"))
				{
					return;
				}
				goto IL_0538;
				IL_052a:
				text = "problem";
				mark = "!";
				break;
			}
			if (Entries.Count >= 80)
			{
				Entries.RemoveAt(0);
			}
			Entries.Add(new Entry
			{
				At = Time.time,
				Kind = text,
				Mark = mark,
				Text = Label(name) + detail
			});
		}

		private static string Label(string name)
		{
			switch (name)
			{
			case "PRESENCE_EVENT":
			case "SYMPTOM":
			case "SYMPTOM_ARMED":
			case "CHAIN":
				return (name == "SYMPTOM_ARMED") ? "armed " : "";
			case "SYMPTOM_DROPPED":
				return "dropped ";
			case "SYMPTOM_FAILED":
				return "failed ";
			case "SYMPTOM_REJECTED":
				return "";
			default:
				return name.ToLowerInvariant().Replace('_', ' ') + ": ";
			}
		}
	}
	internal sealed class DebugMenu : MonoBehaviour
	{
		private const float Width = 460f;

		private const float Height = 540f;

		private static readonly float[] SanityPresets = new float[6] { 100f, 70f, 60f, 40f, 15f, 0f };

		private static readonly string[] StrengthNames = new string[4] { "Current", "Uneasy", "Disturbed", "Breaking" };

		private static readonly string[] Tabs = new string[4] { "Now", "Symptoms", "Log", "Tools" };

		private static readonly string[] LogFilters = new string[6] { "All", "Symptoms", "Chains", "Presence", "Meter", "Problems" };

		private static readonly string[] LogKinds = new string[6] { "", "symptom", "chain", "presence", "meter", "problem" };

		private bool open;

		private bool weDisabledLook;

		private int strength;

		private int tab;

		private int logFilter;

		private string message = "";

		private Rect window;

		private Vector2 scroll;

		private string[] statuses = Array.Empty<string>();

		private string[] statusesShort = Array.Empty<string>();

		private readonly List<string> nowLines = new List<string>();

		private float statusTimer;

		private GUIStyle? small;

		private GUIStyle? heading;

		internal static bool IsOpen { get; private set; }

		internal static void Create()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			GameObject val = new GameObject("Sanity.DebugMenu");
			((Object)val).hideFlags = (HideFlags)61;
			Object.DontDestroyOnLoad((Object)(object)val);
			val.AddComponent<DebugMenu>();
		}

		private void Awake()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			window = new Rect((float)Screen.width - 460f - 20f, 40f, 460f, 540f);
		}

		private void Update()
		{
			bool value = Plugin.DebugKeys.Value;
			if (value && Keyboard.current != null && ((ButtonControl)Keyboard.current.f7Key).wasPressedThisFrame)
			{
				SetOpen(!open);
			}
			else if (open && !value)
			{
				SetOpen(value: false);
			}
			if (open)
			{
				Reactions.NoteConfound("debug menu");
				Cursor.lockState = (CursorLockMode)0;
				Cursor.visible = true;
				statusTimer -= Time.deltaTime;
				if (statusTimer <= 0f)
				{
					statusTimer = 0.5f;
					Refresh();
				}
			}
		}

		private void Refresh()
		{
			PlayerControllerB val = LocalPlayer();
			int count = SanityDirector.Symptoms.Count;
			if (statuses.Length != count)
			{
				statuses = new string[count];
				statusesShort = new string[count];
			}
			for (int i = 0; i < count; i++)
			{
				Symptom symptom = SanityDirector.Symptoms[i];
				string text;
				if (!symptom.Enabled)
				{
					text = "off in config";
				}
				else if ((Object)(object)val == (Object)null || !SanityMeter.OnMoon)
				{
					text = "not on a moon";
				}
				else if (symptom.RunningState(val).Length > 0)
				{
					text = "RUNNING: " + symptom.RunningState(val);
				}
				else if (symptom is ArmedSymptom { IsArmed: not false } armedSymptom)
				{
					string heldByNow = armedSymptom.HeldByNow;
					text = FormattableString.Invariant(FormattableStringFactory.Create("ARMED {0:F0}/{1:F0}s{2}; {3}", armedSymptom.ArmedFor, armedSymptom.WaitLimit, (heldByNow.Length > 0) ? (", held: " + heldByNow) : "", armedSymptom.MomentStatus(val)));
				}
				else
				{
					string text2 = SanityDirector.NaturalCantPlayReason(symptom, val);
					text = ((text2.Length > 0) ? ("can't: " + text2) : ((SanityMeter.Tier < symptom.MinTier) ? $"ready, needs {symptom.MinTier}" : "ready"));
				}
				statuses[i] = FormattableString.Invariant($"{text} (recovery {symptom.Recovery * 100f:F0}%)");
				statusesShort[i] = Short(text, 44);
			}
			nowLines.Clear();
			if ((Object)(object)val == (Object)null)
			{
				nowLines.Add("Not in a game.");
			}
			else
			{
				BuildNow(val);
			}
		}

		private void BuildNow(PlayerControllerB player)
		{
			string text = (SanityDirector.Relaxing ? FormattableString.Invariant($", RELAX {SanityDirector.RelaxLeft:F0}s") : "");
			nowLines.Add("#Director");
			nowLines.Add(FormattableString.Invariant(FormattableStringFactory.Create("chance {0:F3}/s, wave {1}, mood {2}{3}", SanityDirector.ChancePerSecond, Waves.High ? "high x1.6" : "low x0.4", DayMood.Name, text)));
			nowLines.Add(FormattableString.Invariant($"last: {SanityDirector.LastSymptom}, {Ago(SanityDirector.SecondsSinceVisible)} since something showed, quiet {SanityDirector.QuietSeconds:F0}s"));
			string pendingSummary = Chains.PendingSummary;
			if (pendingSummary.Length > 0)
			{
				nowLines.Add("chain coming: " + pendingSummary);
			}
			nowLines.Add("#Armed and showing");
			int num = 0;
			foreach (Symptom symptom in SanityDirector.Symptoms)
			{
				if (symptom is ArmedSymptom armedSymptom && (armedSymptom.IsArmed || armedSymptom.IsShowing))
				{
					string heldByNow = armedSymptom.HeldByNow;
					nowLines.Add(armedSymptom.IsArmed ? FormattableString.Invariant(FormattableStringFactory.Create("{0}: armed {1:F0}/{2:F0}s{3}. {4}", armedSymptom.Name, armedSymptom.ArmedFor, armedSymptom.WaitLimit, (heldByNow.Length > 0) ? (", HELD by " + heldByNow) : "", armedSymptom.MomentStatus(player))) : (armedSymptom.Name + ": showing. " + armedSymptom.MomentStatus(player)));
					num++;
				}
			}
			if (SprintHold.Waiting)
			{
				nowLines.Add(SprintHold.Status);
				num++;
			}
			if (PresenceSymptom.Active)
			{
				nowLines.Add("Presence: " + PresenceSymptom.DebugState(player));
				nowLines.Add("  did: " + PresenceSymptom.LastNotes);
				num++;
			}
			if (PhantomHitSymptom.DebugState.Length > 0)
			{
				nowLines.Add("Phantom hit: " + PhantomHitSymptom.DebugState);
				num++;
			}
			if (PhantomAudio.IsPlaying && !PresenceSymptom.Active)
			{
				nowLines.Add("a phantom sound or steps is playing");
				num++;
			}
			if (num == 0)
			{
				nowLines.Add("nothing armed or showing. Presence: " + PresenceSymptom.DebugState(player));
			}
			nowLines.Add("#Meter");
			nowLines.Add(FormattableString.Invariant($"inside {SanityMeter.PartInside:F2} outside {SanityMeter.PartOutside:F2} night {SanityMeter.PartNight:F2} fear {SanityMeter.PartFear:F2} ship +{SanityMeter.PartShip:F2} /s"));
			nowLines.Add(SanityReliefs.DebugSummary);
			string text2 = ((SanityMeter.NearestTeammateDistance < 0f) ? "nobody" : FormattableString.Invariant(FormattableStringFactory.Create("{0:F0} m{1}", SanityMeter.NearestTeammateDistance, SanityMeter.NearTeammate ? " (company)" : "")));
			nowLines.Add(FormattableString.Invariant($"nearest teammate {text2}, lobby {SanityMeter.PlayersAtStart} (x{SanityMeter.LobbyMultiplier:0.##}), loot today +{LootRecovery.GainedToday:F1}"));
			nowLines.Add("#World and loaded");
			nowLines.Add(FormattableString.Invariant(FormattableStringFactory.Create("{0} (out x{1:0.##} in x{2:0.##}), day x{3:0.##} long{4}, phantom loudness {5:0.##}x closer", Weather.Name, Weather.Outside, Weather.Inside, DayLength.Factor, DayLength.Measured ? "" : " (measuring)", PhantomAudio.Closer)));
			nowLines.Add("mods seen: " + Telemetry.ModsSummary());
			string text3 = ((!Plugin.TelemetryEnabled.Value) ? "off" : (Telemetry.Failed ? "FAILED (see BepInEx log)" : ((Telemetry.FilePath.Length == 0) ? "on (file starts at first landing)" : Path.GetFileName(Telemetry.FilePath))));
			nowLines.Add("telemetry: " + text3);
			if (Diagnostics.LastProblem.Length > 0)
			{
				nowLines.Add("problem seen: " + Diagnostics.LastProblem);
			}
		}

		private void SetOpen(bool value)
		{
			open = value;
			IsOpen = value;
			PlayerControllerB val = LocalPlayer();
			if (open)
			{
				message = "";
				statusTimer = 0f;
				if ((Object)(object)val != (Object)null && !val.disableLookInput)
				{
					val.disableLookInput = true;
					weDisabledLook = true;
				}
				return;
			}
			if ((Object)(object)val != (Object)null)
			{
				if (weDisabledLook)
				{
					val.disableLookInput = false;
				}
				if (!val.quickMenuManager.isMenuOpen && !val.inTerminalMenu && !val.inSpecialMenu)
				{
					Cursor.lockState = (CursorLockMode)1;
					Cursor.visible = false;
				}
			}
			weDisabledLook = false;
		}

		private void OnGUI()
		{
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Expected O, but got Unknown
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Expected O, but got Unknown
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Expected O, but got Unknown
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Expected O, but got Unknown
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Expected O, but got Unknown
			if (open)
			{
				if (small == null)
				{
					small = new GUIStyle(GUI.skin.label)
					{
						fontSize = 11,
						wordWrap = true,
						richText = false
					};
					small.margin = new RectOffset(2, 2, 0, 0);
					small.padding = new RectOffset(2, 2, 1, 1);
					heading = new GUIStyle(small)
					{
						fontStyle = (FontStyle)1
					};
				}
				((Rect)(ref window)).width = 460f;
				((Rect)(ref window)).height = 540f;
				window = GUILayout.Window(((Object)this).GetInstanceID(), window, new WindowFunction(DrawWindow), "Sanity debug  (F7 closes)", (GUILayoutOption[])(object)new GUILayoutOption[2]
				{
					GUILayout.Width(460f),
					GUILayout.Height(540f)
				});
			}
		}

		private void DrawWindow(int id)
		{
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			PlayerControllerB player = LocalPlayer();
			GUIStyle val = small;
			string text = (SanityMeter.Frozen ? " FROZEN" : "") + (SanityDirector.Paused ? " PAUSED" : "") + (SanityDirector.IgnoreConditions ? " NO-CHECKS" : "") + (SanityDirector.ChainsAlways ? " CHAINS-ALWAYS" : "");
			GUILayout.Label(FormattableString.Invariant($"Sanity {SanityMeter.Value:F1} {SanityMeter.Tier}   {SanityMeter.Rate:+0.00;-0.00}/s   fear {SanityMeter.Fear:F2}{text}"), heading, Array.Empty<GUILayoutOption>());
			tab = GUILayout.Toolbar(tab, Tabs, Array.Empty<GUILayoutOption>());
			scroll = GUILayout.BeginScrollView(scroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(420f) });
			switch (tab)
			{
			case 0:
				DrawNow(val);
				break;
			case 1:
				DrawSymptoms(player, val);
				break;
			case 2:
				DrawLog(val);
				break;
			default:
				DrawTools(val);
				break;
			}
			GUILayout.EndScrollView();
			string text2 = ((GUI.tooltip.Length > 0) ? GUI.tooltip : message);
			GUILayout.Label(text2, val, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(34f) });
			GUI.DragWindow();
		}

		private void DrawNow(GUIStyle text)
		{
			foreach (string nowLine in nowLines)
			{
				if (nowLine.StartsWith("#"))
				{
					GUILayout.Label(nowLine.Substring(1), heading, Array.Empty<GUILayoutOption>());
				}
				else
				{
					GUILayout.Label(nowLine, text, Array.Empty<GUILayoutOption>());
				}
			}
		}

		private void DrawSymptoms(PlayerControllerB? player, GUIStyle text)
		{
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Expected O, but got Unknown
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label("Fire at:", text, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(48f) });
			strength = GUILayout.Toolbar(strength, StrengthNames, Array.Empty<GUILayoutOption>());
			GUILayout.EndHorizontal();
			GUILayout.Label("Tick = allowed naturally (saved). Button = fire / arm now. Hover a line for all of it.", text, Array.Empty<GUILayoutOption>());
			for (int i = 0; i < SanityDirector.Symptoms.Count; i++)
			{
				Symptom symptom = SanityDirector.Symptoms[i];
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				if (symptom.EnabledEntry != null)
				{
					bool flag = GUILayout.Toggle(symptom.EnabledEntry.Value, "", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(16f) });
					if (flag != symptom.EnabledEntry.Value)
					{
						symptom.EnabledEntry.Value = flag;
						statusTimer = 0f;
					}
				}
				if (GUILayout.Button(symptom.Name, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(112f) }))
				{
					message = (((Object)(object)player == (Object)null) ? "Not in a game." : SanityDirector.DebugFire(player, symptom, StrengthTier()));
					statusTimer = 0f;
				}
				string text2 = ((i < statuses.Length) ? statuses[i] : "");
				GUILayout.Label(new GUIContent((i < statusesShort.Length) ? statusesShort[i] : "", symptom.Name + ": " + text2), text, Array.Empty<GUILayoutOption>());
				GUILayout.EndHorizontal();
			}
		}

		private void DrawLog(GUIStyle text)
		{
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			logFilter = GUILayout.Toolbar(logFilter, LogFilters, Array.Empty<GUILayoutOption>());
			if (GUILayout.Button("Clear", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(48f) }))
			{
				DebugLog.Clear();
			}
			GUILayout.EndHorizontal();
			string text2 = LogKinds[logFilter];
			IReadOnlyList<DebugLog.Entry> all = DebugLog.All;
			int num = 0;
			for (int num2 = all.Count - 1; num2 >= 0; num2--)
			{
				DebugLog.Entry entry = all[num2];
				if (text2.Length <= 0 || !(entry.Kind != text2) || (text2 == "symptom" && entry.Kind == "chain"))
				{
					GUILayout.Label(FormattableString.Invariant($"{Ago(Time.time - entry.At),5} {entry.Mark,-2} {entry.Text}"), text, Array.Empty<GUILayoutOption>());
					num++;
				}
			}
			if (num == 0)
			{
				GUILayout.Label("Nothing yet.", text, Array.Empty<GUILayoutOption>());
			}
			GUILayout.Label("> played   + armed   -> chained follow-up   x dropped/failed   . detail   ! problem", text, Array.Empty<GUILayoutOption>());
		}

		private void DrawTools(GUIStyle text)
		{
			GUILayout.Label("Set sanity", heading, Array.Empty<GUILayoutOption>());
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			float[] sanityPresets = SanityPresets;
			for (int i = 0; i < sanityPresets.Length; i++)
			{
				float value = sanityPresets[i];
				if (GUILayout.Button(value.ToString("0"), Array.Empty<GUILayoutOption>()))
				{
					message = SanityMeter.DebugSetSanity(value);
				}
			}
			GUILayout.EndHorizontal();
			GUILayout.Label("Tests", heading, Array.Empty<GUILayoutOption>());
			Toggle(ref SanityMeter.Frozen, "Freeze sanity", "sanity frozen", "sanity unfrozen");
			Toggle(ref SanityDirector.Paused, "Pause natural symptoms", "director paused", "director resumed");
			Toggle(ref SanityDirector.IgnoreGapsNatural, "Ignore gaps: natural rolls (recovery check, fear gap)", "gaps ignored (natural)", "gaps restored (natural)");
			Toggle(ref SanityDirector.IgnoreGapsForced, "Ignore gaps: F8 and buttons", "gaps ignored (forced)", "gaps restored (forced)");
			Toggle(ref SanityDirector.IgnoreConditions, "Ignore conditions: F8 and buttons skip every check (may look wrong)", "conditions ignored (forced)", "conditions restored (forced)");
			Toggle(ref SanityDirector.ChainsAlways, "Chains always follow (every link rolls in)", "chains always", "chains by chance");
			GUILayout.Label("Armed from a button: no 5 s gap or relax wait (tier still applies unless Ignore conditions).", text, Array.Empty<GUILayoutOption>());
			GUILayout.Label("Display", heading, Array.Empty<GUILayoutOption>());
			ConfigToggle(Plugin.ShowReadout, "On-screen readout");
			ConfigToggle(Plugin.ShowPresence, "Show the Presence (a red ball)");
			if (PhantomAudio.Loudness != null)
			{
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				GUILayout.Label(FormattableString.Invariant($"Phantom loudness {PhantomAudio.Loudness.Value:0.00}"), text, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(150f) });
				float num = GUILayout.HorizontalSlider(PhantomAudio.Loudness.Value, 0.25f, 4f, Array.Empty<GUILayoutOption>());
				if (Mathf.Abs(num - PhantomAudio.Loudness.Value) > 0.049f)
				{
					PhantomAudio.Loudness.Value = Mathf.Round(num * 10f) / 10f;
				}
				GUILayout.EndHorizontal();
			}
		}

		private static void Toggle(ref bool value, string label, string on, string off)
		{
			bool flag = GUILayout.Toggle(value, " " + label, Array.Empty<GUILayoutOption>());
			if (flag != value)
			{
				value = flag;
				Telemetry.Event("DEBUG", flag ? 1f : 0f, flag ? on : off);
			}
		}

		private static void ConfigToggle(ConfigEntry<bool> entry, string label)
		{
			bool flag = GUILayout.Toggle(entry.Value, " " + label, Array.Empty<GUILayoutOption>());
			if (flag != entry.Value)
			{
				entry.Value = flag;
			}
		}

		private SanityTier StrengthTier()
		{
			if (strength > 0)
			{
				return (SanityTier)strength;
			}
			if (SanityMeter.Tier != SanityTier.Calm)
			{
				return SanityMeter.Tier;
			}
			return SanityTier.Uneasy;
		}

		private static string Short(string value, int length)
		{
			if (value.Length > length)
			{
				return value.Substring(0, length - 1) + "~";
			}
			return value;
		}

		private static string Ago(float seconds)
		{
			if (!(seconds > 9000f))
			{
				if (!(seconds >= 60f))
				{
					return FormattableString.Invariant($"{seconds:F0}s");
				}
				return FormattableString.Invariant($"{seconds / 60f:F0}m");
			}
			return "-";
		}

		private static PlayerControllerB? LocalPlayer()
		{
			GameNetworkManager instance = GameNetworkManager.Instance;
			if (!((Object)(object)instance != (Object)null))
			{
				return null;
			}
			return instance.localPlayerController;
		}
	}
	internal static class Diagnostics
	{
		private const float CheckSeconds = 0.5f;

		private const float ReportGapSeconds = 30f;

		private static float nextCheck;

		private static float lastReport = -9999f;

		private static int missingChecks;

		internal static string LastProblem { get; private set; } = "";

		internal static void Tick(PlayerControllerB player)
		{
			if (Time.time < nextCheck || player.isPlayerDead || (Object)(object)player.cursorTip == (Object)null)
			{
				return;
			}
			nextCheck = Time.time + 0.5f;
			if (player.isGrabbingObjectAnimation || player.inSpecialMenu || player.inTerminalMenu || player.isTypingChat || ((Object)(object)player.quickMenuManager != (Object)null && player.quickMenuManager.isMenuOpen) || player.inSpecialInteractAnimation || player.twoHanded)
			{
				missingChecks = 0;
				return;
			}
			string text = Expected(player);
			bool flag = ((Behaviour)player.cursorTip).enabled && ((Component)player.cursorTip).gameObject.activeInHierarchy && !string.IsNullOrEmpty(((TMP_Text)player.cursorTip).text);
			if (text.Length == 0 || flag)
			{
				missingChecks = 0;
				return;
			}
			missingChecks++;
			if (missingChecks >= 3 && !(Time.time - lastReport < 30f))
			{
				lastReport = Time.time;
				LastProblem = FormattableString.Invariant($"{Time.time:F0}s: no prompt over {text}");
				Telemetry.Event("PROMPT_MISSING", 0f, FormattableString.Invariant($"over {text}; tip enabled {((Behaviour)player.cursorTip).enabled} active {((Component)player.cursorTip).gameObject.activeInHierarchy} text \"{((TMP_Text)player.cursorTip).text}\"; ") + FormattableString.Invariant($"sanity: debug menu {(DebugMenu.IsOpen ? 1 : 0)}, presence {(PresenceSymptom.Active ? 1 : 0)}, last symptom {SanityDirector.LastSymptom}, fake hp {PhantomHitSymptom.Lost}"));
			}
		}

		private static string Expected(PlayerControllerB player)
		{
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			InteractTrigger hoveringOverTrigger = player.hoveringOverTrigger;
			if ((Object)(object)hoveringOverTrigger != (Object)null && (player.isHoldingInteract || hoveringOverTrigger.isPlayingSpecialAnimation))
			{
				return "";
			}
			if ((Object)(object)hoveringOverTrigger != (Object)null)
			{
				string value = (hoveringOverTrigger.interactable ? hoveringOverTrigger.hoverTip : hoveringOverTrigger.disabledHoverTip);
				if (!string.IsNullOrEmpty(value))
				{
					return "a trigger (" + ((Object)((Component)hoveringOverTrigger).gameObject).name + ")";
				}
				return "";
			}
			Transform transform = ((Component)player.gameplayCamera).transform;
			RaycastHit val = default(RaycastHit);
			if (Physics.Raycast(new Ray(transform.position, transform.forward), ref val, player.grabDistance, player.interactableObjectsMask) && ((Component)((RaycastHit)(ref val)).collider).gameObject.layer != 8 && ((Component)((RaycastHit)(ref val)).collider).gameObject.layer != 30 && ((Component)((RaycastHit)(ref val)).collider).CompareTag("PhysicsProp"))
			{
				GrabbableObject component = ((Component)((RaycastHit)(ref val)).collider).GetComponent<GrabbableObject>();
				if (!((Object)(object)component != (Object)null) || !component.grabbable)
				{
					return "";
				}
				return "an item (" + ((Object)component).name + ")";
			}
			return "";
		}
	}
	internal static class DayMood
	{
		private struct Mood
		{
			internal string Name;

			internal float Rate;

			internal float Sound;

			internal float Sight;

			internal float Body;

			internal float Odds;
		}

		private static readonly Mood[] Moods = new Mood[6]
		{
			new Mood
			{
				Name = "steady",
				Rate = 1f,
				Sound = 1f,
				Sight = 1f,
				Body = 1f,
				Odds = 0.3f
			},
			new Mood
			{
				Name = "whispers",
				Rate = 1f,
				Sound = 1.5f,
				Sight = 0.8f,
				Body = 0.8f,
				Odds = 0.175f
			},
			new Mood
			{
				Name = "tricks",
				Rate = 1f,
				Sound = 0.8f,
				Sight = 1.5f,
				Body = 0.8f,
				Odds = 0.175f
			},
			new Mood
			{
				Name = "nerves",
				Rate = 1f,
				Sound = 0.8f,
				Sight = 0.8f,
				Body = 1.5f,
				Odds = 0.175f
			},
			new Mood
			{
				Name = "quiet",
				Rate = 0.85f,
				Sound = 1f,
				Sight = 1f,
				Body = 1f,
				Odds = 0.0875f
			},
			new Mood
			{
				Name = "restless",
				Rate = 1.15f,
				Sound = 1f,
				Sight = 1f,
				Body = 1f,
				Odds = 0.0875f
			}
		};

		private static int index = -1;

		internal static string Name
		{
			get
			{
				if (index < 0)
				{
					return "none";
				}
				return Moods[index].Name;
			}
		}

		internal static float Rate
		{
			get
			{
				if (index < 0)
				{
					return 1f;
				}
				return Moods[index].Rate;
			}
		}

		internal static string TuningSummary
		{
			get
			{
				List<string> list = new List<string>();
				Mood[] moods = Moods;
				for (int i = 0; i < moods.Length; i++)
				{
					Mood mood = moods[i];
					list.Add(FormattableString.Invariant($"{mood.Name} {mood.Odds:0.###} rate x{mood.Rate} sound/sight/body x{mood.Sound}/{mood.Sight}/{mood.Body}"));
				}
				return "moods " + string.Join(", ", list);
			}
		}

		internal static float Tilt(string family)
		{
			if (index < 0)
			{
				return 1f;
			}
			Mood mood = Moods[index];
			return family switch
			{
				"body" => mood.Body, 
				"sight" => mood.Sight, 
				"sound" => mood.Sound, 
				_ => 1f, 
			};
		}

		internal static void EnsureRolled()
		{
			if (index >= 0)
			{
				return;
			}
			float num = Random.value;
			index = Moods.Length - 1;
			for (int i = 0; i < Moods.Length; i++)
			{
				num -= Moods[i].Odds;
				if (num <= 0f)
				{
					index = i;
					break;
				}
			}
			Mood mood = Moods[index];
			Telemetry.Event("MOOD", mood.Rate, FormattableString.Invariant($"{mood.Name}: rate x{mood.Rate} sound x{mood.Sound} sight x{mood.Sight} body x{mood.Body}"));
		}

		internal static void Reset()
		{
			index = -1;
		}
	}
	internal static class Waves
	{
		private const float HighMultiplier = 1.6f;

		private const float LowMultiplier = 0.4f;

		private const float HighMeanSeconds = 120f;

		private const float LowMeanSeconds = 90f;

		internal static bool High { get; private set; } = true;

		internal static float Multiplier
		{
			get
			{
				if (!High)
				{
					return 0.4f;
				}
				return 1.6f;
			}
		}

		internal static string TuningSummary => FormattableString.Invariant($"waves x{1.6f} ~{120f}s / x{0.4f} ~{90f}s");

		internal static void Tick(float dt)
		{
			if (Random.value < dt / (High ? 120f : 90f))
			{
				High = !High;
			}
		}

		internal static void Reset()
		{
			High = Random.value < 0.5714286f;
		}
	}
	internal static class Chains
	{
		private sealed class Link
		{
			internal string From = "";

			internal string Trigger = "";

			internal string To = "";

			internal float Chance;

			internal SanityTier MinTier;

			internal float MinDelay;

			internal float MaxDelay;

			internal Func<PlayerControllerB, SanityTier, Vector3, Vector3, string> Play = (PlayerControllerB _, SanityTier _, Vector3 _, Vector3 _) => "no action";
		}

		private struct Pending
		{
			internal Link Link;

			internal float At;

			internal SanityTier Tier;

			internal Vector3 Point;

			internal Vector3 Direction;
		}

		private static readonly List<Pending> Queue = new List<Pending>();

		private static readonly Link[] Links = new Link[4]
		{
			new Link
			{
				From = "PhantomSteps",
				Trigger = "reached you",
				To = "PhantomHit",
				Chance = 0.6f,
				MinTier = SanityTier.Breaking,
				MinDelay = 0.1f,
				MaxDelay = 0.3f,
				Play = (PlayerControllerB p, SanityTier t, Vector3 _, Vector3 _) => PlayHit(p, t, "the rush reached you")
			},
			new Link
			{
				From = "PhantomWalkie",
				Trigger = "death static",
				To = "PhantomDread",
				Chance = 0.5f,
				MinTier = SanityTier.Breaking,
				MinDelay = 3f,
				MaxDelay = 6f,
				Play = (PlayerControllerB p, SanityTier t, Vector3 _, Vector3 _) => PlayCard("PhantomDread", p, t)
			},
			new Link
			{
				From = "PhantomSound",
				Trigger = "approaching",
				To = "Flicker",
				Chance = 0.3f,
				MinTier = SanityTier.Breaking,
				MinDelay = 0.5f,
				MaxDelay = 1.5f,
				Play = (PlayerControllerB p, SanityTier t, Vector3 _, Vector3 _) => FlickerSymptom.FlickNow(p, t)
			},
			new Link
			{
				From = "LostTime",
				Trigger = "shown",
				To = "PhantomWalkie",
				Chance = 0.5f,
				MinTier = SanityTier.Uneasy,
				MinDelay = 3f,
				MaxDelay = 6f,
				Play = (PlayerControllerB p, SanityTier _, Vector3 _, Vector3 _) => PlayCard("PhantomWalkie", p, SanityTier.Uneasy)
			}
		};

		internal static string TuningSummary
		{
			get
			{
				List<string> list = new List<string>();
				Link[] links = Links;
				foreach (Link link in links)
				{
					list.Add(FormattableString.Invariant($"{link.From}:{link.Trigger}->{link.To} {link.Chance:0.##} {link.MinTier}+"));
				}
				return "chains " + string.Join(", ", list);
			}
		}

		internal static string PendingSummary
		{
			get
			{
				if (Queue.Count == 0)
				{
					return "";
				}
				List<string> list = new List<string>();
				foreach (Pending item in Queue)
				{
					list.Add(FormattableString.Invariant($"{item.Link.From} -> {item.Link.To} in {Mathf.Max(0f, item.At - Time.time):F1}s"));
				}
				return string.Join(", ", list);
			}
		}

		internal static void Trigger(Symptom from, string trigger, Vector3 point = default(Vector3), Vector3 direction = default(Vector3))
		{
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			SanityTier tier = SanityMeter.Tier;
			Link[] links = Links;
			foreach (Link link in links)
			{
				if (!(link.From != from.ConfigKey) && !(link.Trigger != trigger) && tier >= link.MinTier && (SanityDirector.ChainsAlways || !(Random.value >= link.Chance)))
				{
					Symptom symptom = SanityDirector.Find(link.To);
					if (symptom == null || symptom.Enabled)
					{
						Queue.Add(new Pending
						{
							Link = link,
							At = Time.time + Random.Range(link.MinDelay, link.MaxDelay),
							Tier = tier,
							Point = point,
							Direction = direction
						});
						Telemetry.Event("CHAIN", 0f, link.From + " " + link.Trigger + " -> " + link.To + " scheduled");
					}
				}
			}
		}

		internal static void Tick(PlayerControllerB player)
		{
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			for (int num = Queue.Count - 1; num >= 0; num--)
			{
				Pending pending = Queue[num];
				if (!(Time.time < pending.At))
				{
					Queue.RemoveAt(num);
					if (!player.isPlayerDead)
					{
						string text = pending.Link.Play(player, pending.Tier, pending.Point, pending.Direction);
						Link link = pending.Link;
						if (text.Length == 0)
						{
							Symptom symptom = SanityDirector.Find(link.To);
							if (symptom != null)
							{
								SanityDirector.NoteChained(symptom, pending.Tier, link.From);
							}
							Telemetry.Event("CHAIN", 1f, link.From + " " + link.Trigger + " -> " + link.To + " played");
						}
						else
						{
							Telemetry.Event("CHAIN", 0f, link.From + " " + link.Trigger + " -> " + link.To + " didn't play: " + text);
						}
					}
				}
			}
		}

		internal static void Reset()
		{
			Queue.Clear();
		}

		private static string PlayCard(string key, PlayerControllerB player, SanityTier tier)
		{
			Symptom symptom = SanityDirector.Find(key);
			if (symptom == null)
			{
				return "no such symptom";
			}
			string text = symptom.CantPlayReason(player);
			if (text.Length > 0)
			{
				return text;
			}
			symptom.LastFailure = "";
			if (!symptom.TryPlay(player, tier))
			{
				return symptom.LastFailure;
			}
			return "";
		}

		private static string PlayHit(PlayerControllerB player, SanityTier tier, string why)
		{
			if (!(SanityDirector.Find("PhantomHit") is PhantomHitSymptom phantomHitSymptom))
			{
				return "no such symptom";
			}
			string text = phantomHitSymptom.CantPlayReason(player);
			if (text.Length > 0)
			{
				return text;
			}
			if (!phantomHitSymptom.Start(player, tier, withCause: false, why))
			{
				return phantomHitSymptom.LastFailure;
			}
			return "";
		}
	}
	internal static class EligibilityLog
	{
		private const float SampleSeconds = 5f;

		private static readonly string[] TierLetters = new string[4] { "C", "U", "D", "B" };

		private static readonly string[] Places = new string[3] { "inside", "outside", "ship" };

		private static float timer;

		private static readonly float[] sampledByTier = new float[4];

		private static readonly float[] sampledByPlace = new float[3];

		private static float[,] eligibleByTier = new float[0, 4];

		private static float[,] eligibleByPlace = new float[0, 3];

		private static Dictionary<string, float>[] blockedBy = Array.Empty<Dictionary<string, float>>();

		private static readonly StringBuilder Text = new StringBuilder(640);

		internal static void Tick(PlayerControllerB player, float dt)
		{
			if (!Plugin.TelemetryEnabled.Value)
			{
				return;
			}
			timer += dt;
			if (timer < 5f)
			{
				return;
			}
			float num = timer;
			timer = 0f;
			IReadOnlyList<Symptom> symptoms = SanityDirector.Symptoms;
			EnsureSize(symptoms.Count);
			int tier = (int)SanityMeter.Tier;
			int num2 = ((!SanityMeter.Inside) ? ((!SanityMeter.InShip) ? 1 : 2) : 0);
			sampledByTier[tier] += num;
			sampledByPlace[num2] += num;
			for (int i = 0; i < symptoms.Count; i++)
			{
				string text = SanityDirector.NaturalWhyNot(symptoms[i], player);
				if (text.Length == 0)
				{
					eligibleByTier[i, tier] += num;
					eligibleByPlace[i, num2] += num;
				}
				else
				{
					string key = Kind(text);
					blockedBy[i].TryGetValue(key, out var value);
					blockedBy[i][key] = value + num;
				}
			}
		}

		internal static string Summary()
		{
			IReadOnlyList<Symptom> symptoms = SanityDirector.Symptoms;
			EnsureSize(symptoms.Count);
			Text.Clear();
			Text.Append("sampled");
			AppendTiersAndPlaces(sampledByTier, sampledByPlace);
			for (int i = 0; i < symptoms.Count; i++)
			{
				Text.Append(" | ").Append(symptoms[i].ConfigKey);
				for (int j = 1; j < TierLetters.Length; j++)
				{
					Text.Append(FormattableString.Invariant($" {TierLetters[j]}={eligibleByTier[i, j]:F0}"));
				}
				for (int k = 0; k < Places.Length; k++)
				{
					Text.Append(FormattableString.Invariant($" {Places[k]}={eligibleByPlace[i, k]:F0}"));
				}
				Text.Append(" blocked=");
				bool flag = true;
				foreach (KeyValuePair<string, float> item in blockedBy[i])
				{
					Text.Append(flag ? "" : "/").Append(item.Key).Append(FormattableString.Invariant($":{item.Value:F0}"));
					flag = false;
				}
			}
			return Text.ToString();
		}

		internal static void Reset()
		{
			timer = 0f;
			Array.Clear(sampledByTier, 0, sampledByTier.Length);
			Array.Clear(sampledByPlace, 0, sampledByPlace.Length);
			Array.Clear(eligibleByTier, 0, eligibleByTier.Length);
			Array.Clear(eligibleByPlace, 0, eligibleByPlace.Length);
			Dictionary<string, float>[] array = blockedBy;
			foreach (Dictionary<string, float> dictionary in array)
			{
				dictionary.Clear();
			}
		}

		private static void AppendTiersAndPlaces(float[] byTier, float[] byPlace)
		{
			for (int i = 1; i < TierLetters.Length; i++)
			{
				Text.Append(FormattableString.Invariant($" {TierLetters[i]}={byTier[i]:F0}"));
			}
			for (int j = 0; j < Places.Length; j++)
			{
				Text.Append(FormattableString.Invariant($" {Places[j]}={byPlace[j]:F0}"));
			}
		}

		private static string Kind(string reason)
		{
			int num = reason.IndexOf(':');
			if (num <= 0)
			{
				return reason;
			}
			return reason.Substring(0, num);
		}

		private static void EnsureSize(int count)
		{
			if (blockedBy.Length != count)
			{
				eligibleByTier = new float[count, TierLetters.Length];
				eligibleByPlace = new float[count, Places.Length];
				blockedBy = new Dictionary<string, float>[count];
				for (int i = 0; i < count; i++)
				{
					blockedBy[i] = new Dictionary<string, float>();
				}
			}
		}
	}
	internal static class InputWatch
	{
		private static readonly Dictionary<string, InputAction> Actions = new Dictionary<string, InputAction>();

		internal static bool Pressed(string name)
		{
			try
			{
				if (!Actions.TryGetValue(name, out InputAction value) || value == null)
				{
					InputActionAsset actions = InputSystem.actions;
					value = ((actions != null) ? actions.FindAction(name, false) : null);
					if (value == null)
					{
						return false;
					}
					Actions[name] = value;
				}
				return value.WasPressedThisFrame();
			}
			catch (Exception)
			{
				return false;
			}
		}
	}
	internal static class Insights
	{
		private static float alive;

		private static float inside;

		private static float outside;

		private static float ship;

		private static float aloneInside;

		private static float aloneOutside;

		private static float sprint;

		private static float distance;

		private static float turnInside;

		private static float turnOutside;

		private static float turnShip;

		private static float lightInside;

		private static float walkieCarried;

		private static float still;

		private static int scans;

		private static int uses;

		private static int walkiePresses;

		private static int trips;

		private static readonly List<float> TripLengths = new List<float>();

		private static float tripStart = -1f;

		private static float firstEntry = -1f;

		private static float lastFraction;

		private static bool wasInside;

		private static bool wasTalking;

		private static float lastYaw;

		private static bool hasYaw;

		private static Vector3 lastPos;

		private static bool hasPos;

		private static readonly Dictionary<string, int> DoorHovers = new Dictionary<string, int>();

		private static InteractTrigger? lastTrigger;

		private static int itemMoments;

		private static int lightLooks;

		private static int longExits;

		private static int longExitsBelievable;

		private static readonly int[] ItemByTier = new int[4];

		private static readonly int[] LightByTier = new int[4];

		private static readonly int[] DoorByTier = new int[4];

		private static readonly int[] ExitByTier = new int[4];

		private static float nextItemScan;

		private static float nextLightScan;

		private static GrabbableObject? lastLooked;

		private static bool lastInReach;

		private static bool lookingAtLight;

		private static readonly StringBuilder Text = new StringBuilder(600);

		internal static void Reset()
		{
			alive = (inside = (outside = (ship = (aloneInside = (aloneOutside = (sprint = (distance = 0f)))))));
			turnInside = (turnOutside = (turnShip = (lightInside = (walkieCarried = (still = 0f)))));
			scans = (uses = (walkiePresses = (trips = 0)));
			TripLengths.Clear();
			tripStart = (firstEntry = -1f);
			lastFraction = 0f;
			wasInside = (wasTalking = (hasYaw = (hasPos = false)));
			DoorHovers.Clear();
			lastTrigger = null;
			itemMoments = (lightLooks = (longExits = (longExitsBelievable = 0)));
			Array.Clear(ItemByTier, 0, 4);
			Array.Clear(LightByTier, 0, 4);
			Array.Clear(DoorByTier, 0, 4);
			Array.Clear(ExitByTier, 0, 4);
			lastLooked = null;
			lookingAtLight = false;
		}

		internal static void Tick(PlayerControllerB player, float dayTime)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			float deltaTime = Time.deltaTime;
			int tier = (int)SanityMeter.Tier;
			bool isInsideFactory = player.isInsideFactory;
			bool isInHangarShipRoom = player.isInHangarShipRoom;
			alive += deltaTime;
			lastFraction = SanityMeter.DayFraction;
			float y = ((Component)player.gameplayCamera).transform.eulerAngles.y;
			float num = (hasYaw ? Mathf.Abs(Mathf.DeltaAngle(lastYaw, y)) : 0f);
			lastYaw = y;
			hasYaw = true;
			Vector3 position = ((Component)player).transform.position;
			Vector3 val = (hasPos ? (position - lastPos) : Vector3.zero);
			val.y = 0f;
			float num2 = ((((Vector3)(ref val)).magnitude < 4f) ? ((Vector3)(ref val)).magnitude : 0f);
			lastPos = position;
			hasPos = true;
			distance += num2;
			if (deltaTime > 0f && num2 / deltaTime < 0.3f)
			{
				still += deltaTime;
			}
			if (isInsideFactory)
			{
				inside += deltaTime;
				turnInside += num;
				if (!SanityMeter.NearTeammate)
				{
					aloneInside += deltaTime;
				}
				if (Reactions.LightOn(player))
				{
					lightInside += deltaTime;
				}
			}
			else if (isInHangarShipRoom)
			{
				ship += deltaTime;
				turnShip += num;
			}
			else
			{
				outside += deltaTime;
				turnOutside += num;
				if (!SanityMeter.NearTeammate)
				{
					aloneOutside += deltaTime;
				}
			}
			if (player.isSprinting)
			{
				sprint += deltaTime;
			}
			if (InputWatch.Pressed("PingScan"))
			{
				scans++;
			}
			if (InputWatch.Pressed("Interact"))
			{
				uses++;
			}
			bool speakingToWalkieTalkie = player.speakingToWalkieTalkie;
			if (speakingToWalkieTalkie && !wasTalking)
			{
				walkiePresses++;
			}
			wasTalking = speakingToWalkieTalkie;
			if (CarriesWalkie(player))
			{
				walkieCarried += deltaTime;
			}
			if (isInsideFactory && !wasInside)
			{
				trips++;
				tripStart = alive;
				if (firstEntry < 0f)
				{
					firstEntry = dayTime;
				}
			}
			if (!isInsideFactory && wasInside && tripStart >= 0f)
			{
				float num3 = alive - tripStart;
				TripLengths.Add(num3);
				if (num3 >= 180f && !isInHangarShipRoom)
				{
					longExits++;
					ExitByTier[tier]++;
					if (LostTimeSymptom.LateEnough)
					{
						longExitsBelievable++;
					}
				}
			}
			wasInside = isInsideFactory;
			InteractTrigger hoveringOverTrigger = player.hoveringOverTrigger;
			if ((Object)(object)hoveringOverTrigger != (Object)(object)lastTrigger)
			{
				lastTrigger = hoveringOverTrigger;
				if ((Object)(object)hoveringOverTrigger != (Object)null && isInsideFactory)
				{
					string text = FakeLockedDoorSymptom.Assess(player, hoveringOverTrigger);
					if (text != "not a door")
					{
						string key = ((text.Length == 0) ? "would fire" : text);
						DoorHovers[key] = ((!DoorHovers.TryGetValue(key, out var value)) ? 1 : (value + 1));
						if (text.Length == 0)
						{
							DoorByTier[tier]++;
						}
					}
				}
			}
			if (Time.time >= nextItemScan && !isInHangarShipRoom)
			{
				nextItemScan = Time.time + 0.25f;
				float reachDistance;
				Bounds bounds;
				GrabbableObject val2 = VanishingItemSymptom.LookedAtItem(player, out reachDistance, out bounds);
				bool flag = (Object)(object)val2 != (Object)null && reachDistance <= player.grabDistance + 1f;
				if (flag && (!((Object)(object)val2 == (Object)(object)lastLooked) || !lastInReach))
				{
					itemMoments++;
					ItemByTier[tier]++;
				}
				lastLooked = val2;
				lastInReach = flag;
			}
			if (Time.time >= nextLightScan && isInsideFactory)
			{
				nextLightScan = Time.time + 0.5f;
				RoundManager instance = RoundManager.Instance;
				string reason;
				bool flag2 = (Object)(object)instance != (Object)null && FlickerSymptom.FacilityHasPower(instance) && FlickerSymptom.NearestVisibleLight(player, instance, out reason) >= 0f;
				if (flag2 && !lookingAtLight)
				{
					lightLooks++;
					LightByTier[tier]++;
				}
				lookingAtLight = flag2;
			}
		}

		internal static void WriteDay(int day)
		{
			if (alive <= 1f)
			{
				return;
			}
			float num = alive / 60f;
			TripLengths.Sort();
			float num2 = ((TripLengths.Count > 0) ? TripLengths[TripLengths.Count / 2] : 0f);
			Text.Clear();
			Text.Append(FormattableString.Invariant($"alive={alive:F0}s inside={Share(inside)} outside={Share(outside)} ship={Share(ship)} ")).Append(FormattableString.Invariant($"alone_inside={Share(aloneInside, inside)} alone_outside={Share(aloneOutside, outside)} ")).Append(FormattableString.Invariant($"speed={distance / Mathf.Max(1f, alive):F2} still={Share(still)} sprint={Share(sprint)} "))
				.Append(FormattableString.Invariant($"turn_inside={turnInside / Mathf.Max(1f, inside):F0} turn_outside={turnOutside / Mathf.Max(1f, outside):F0} turn_ship={turnShip / Mathf.Max(1f, ship):F0} "))
				.Append(FormattableString.Invariant($"scans_min={(float)scans / num:F2} use_min={(float)uses / num:F2} walkie_min={(float)walkiePresses / num:F2} walkie_carried={Share(walkieCarried)} "))
				.Append(FormattableString.Invariant($"light_inside={Share(lightInside, inside)} trips={trips} trip_median={num2:F0}s first_entry={firstEntry:F0}s left_at={lastFraction:F2}"));
			Telemetry.Event("DAY_PROFILE", day, Text.ToString());
			Text.Clear();
			Text.Append(FormattableString.Invariant($"minutes={num:F1} doors"));
			foreach (KeyValuePair<string, int> doorHover in DoorHovers)
			{
				Text.Append(' ').Append(doorHover.Key.Replace(' ', '_')).Append('=')
					.Append(doorHover.Value);
			}
			Text.Append(FormattableString.Invariant($" | items={itemMoments} lights={lightLooks} long_exits={longExits} long_exits_after_noon={longExitsBelievable}")).Append(" | by tier C/U/D/B: door ").Append(Join(DoorByTier))
				.Append(" item ")
				.Append(Join(ItemByTier))
				.Append(" light ")
				.Append(Join(LightByTier))
				.Append(" exit ")
				.Append(Join(ExitByTier));
			Telemetry.Event("DAY_OPPORTUNITIES", day, Text.ToString());
		}

		private static string Share(float part, float whole = -1f)
		{
			float num = ((whole < 0f) ? alive : whole);
			if (!(num > 0f))
			{
				return "0";
			}
			return FormattableString.Invariant($"{part / num:F2}");
		}

		private static string Join(int[] values)
		{
			return string.Join("/", values);
		}

		private static bool CarriesWalkie(PlayerControllerB player)
		{
			if (player.ItemSlots == null)
			{
				return false;
			}
			GrabbableObject[] itemSlots = player.ItemSlots;
			foreach (GrabbableObject val in itemSlots)
			{
				if (val is WalkieTalkie)
				{
					return true;
				}
			}
			return false;
		}
	}
	internal static class LootRecovery
	{
		private sealed class Haul
		{
			internal PlayerControllerB Finder;

			internal readonly List<PlayerControllerB> Haulers = new List<PlayerControllerB>();
		}

		private sealed class EarlyDelivery
		{
			internal string Name = "";

			internal int Value;

			internal float Share;

			internal string Role = "";
		}

		private const float Pool = 50f;

		private const float MinPerItem = 1f;

		private const float MaxPerItem = 15f;

		private const float FinderShare = 0.65f;

		private const float DailySoftLimit = 30f;

		private const float FallbackDelay = 20f;

		private const float StaleSeconds = 300f;

		private const float PollSeconds = 0.2f;

		private static readonly Dictionary<GrabbableObject, Haul> hauls = new Dictionary<GrabbableObject, Haul>();

		private static readonly List<GrabbableObject> arrived = new List<GrabbableObject>();

		private static readonly List<EarlyDelivery> early = new List<EarlyDelivery>();

		private static bool onMoon;

		private static float secondsOnMoon;

		private static float dayStartedAt;

		private static float pollTimer;

		private static int[]? syncedValues;

		private static float syncedAt;

		private static bool syncApplied;

		internal static int LevelTotal { get; private set; }

		internal static int LevelItems { get; private set; }

		internal static int PlayersAtLanding { get; private set; } = 1;

		internal static float GainedToday { get; private set; }

		internal static float FairShare
		{
			get
			{
				if (LevelTotal <= 0)
				{
					return 0f;
				}
				return (float)LevelTotal / (float)PlayersAtLanding;
			}
		}

		internal static string CountSource { get; private set; } = "";

		internal static string TuningSummary => FormattableString.Invariant($"pool {50f} per item {1f}-{15f} finder {0.65f} soft limit {30f} count from scrap sync (fallback {20f}s)");

		internal static void Tick(PlayerControllerB me)
		{
			StartOfRound instance = StartOfRound.Instance;
			if (!SanityMeter.OnMoon || (Object)(object)instance == (Object)null)
			{
				if (onMoon)
				{
					ResetDay();
				}
				return;
			}
			if (!onMoon)
			{
				onMoon = true;
				dayStartedAt = Time.time;
			}
			secondsOnMoon += Time.deltaTime;
			if (!syncApplied && syncedValues != null)
			{
				if (Time.time - syncedAt <= 300f)
				{
					ApplySync(instance);
				}
				else
				{
					syncedValues = null;
				}
			}
			if (CountSource.Length == 0 && secondsOnMoon >= 20f)
			{
				CountScene(instance, out var items, out var total);
				SetCount(instance, total, items, "fallback");
			}
			pollTimer -= Time.deltaTime;
			if (!(pollTimer > 0f))
			{
				pollTimer = 0.2f;
				TrackHolders(instance);
				CheckArrivals(instance, me);
			}
		}

		internal static void OnScrapValuesSynced(int[]? values)
		{
			if (values != null && !syncApplied)
			{
				syncedValues = values;
				syncedAt = Time.time;
				StartOfRound instance = StartOfRound.Instance;
				if (onMoon && (Object)(object)instance != (Object)null)
				{
					ApplySync(instance);
				}
			}
		}

		private static void ApplySync(StartOfRound startOfRound)
		{
			int[] array = syncedValues;
			syncedValues = null;
			syncApplied = true;
			int num = 0;
			int[] array2 = array;
			foreach (int num2 in array2)
			{
				num += num2;
			}
			CountScene(startOfRound, out var items, out var total);
			float num3 = syncedAt - dayStartedAt;
			Telemetry.Event("SCRAP_SYNC", num, FormattableString.Invariant(FormattableStringFactory.Create("items {0} total {1} at {2:F1}s after landing (scene now {3} items worth {4}) replaces {5}", array.Length, num, num3, items, total, (CountSource.Length > 0) ? CountSource : "nothing")));
			SetCount(startOfRound, num, array.Length, "sync");
		}

		private static void SetCount(StartOfRound startOfRound, int total, int items, string source)
		{
			LevelTotal = total;
			LevelItems = items;
			CountSource = source;
			PlayersAtLanding = startOfRound.connectedPlayersAmount + 1;
			float num = ((items > 0) ? ((float)total / (float)items) : 0f);
			Telemetry.Event("LEVEL_SCRAP", total, FormattableString.Invariant($"total {total} items {items} average {num:F0} players {PlayersAtLanding} fair share {FairShare:F0} source {source}"));
			if (early.Count == 0 || FairShare <= 0f)
			{
				return;
			}
			GameNetworkManager instance = GameNetworkManager.Instance;
			PlayerControllerB val = (((Object)(object)instance != (Object)null) ? instance.localPlayerController : null);
			if ((Object)(object)val != (Object)null && Alive(val))
			{
				foreach (EarlyDelivery item in early)
				{
					PayOut(item.Name, item.Value, item.Share, item.Role, " (let go before the count)");
				}
			}
			early.Clear();
		}

		private static void TrackHolders(StartOfRound startOfRound)
		{
			PlayerControllerB[] allPlayerScripts = startOfRound.allPlayerScripts;
			if (allPlayerScripts == null)
			{
				return;
			}
			PlayerControllerB[] array = allPlayerScripts;
			foreach (PlayerControllerB val in array)
			{
				if ((Object)(object)val == (Object)null || !val.isPlayerControlled || val.isPlayerDead || val.ItemSlots == null)
				{
					continue;
				}
				GrabbableObject[] itemSlots = val.ItemSlots;
				foreach (GrabbableObject val2 in itemSlots)
				{
					if ((Object)(object)val2 == (Object)null)
					{
						continue;
					}
					if (hauls.TryGetValue(val2, out Haul value))
					{
						if ((Object)(object)val != (Object)(object)value.Finder && !value.Haulers.Contains(val))
						{
							value.Haulers.Add(val);
						}
					}
					else if (IsFresh(val2, startOfRound))
					{
						hauls[val2] = new Haul
						{
							Finder = val
						};
					}
				}
			}
		}

		private static void CheckArrivals(StartOfRound startOfRound, PlayerControllerB me)
		{
			arrived.Clear();
			foreach (KeyValuePair<GrabbableObject, Haul> haul in hauls)
			{
				GrabbableObject key = haul.Key;
				if ((Object)(object)key == (Object)null || (!key.isHeld && !key.isPocketed && (key.isInShipRoom || InShip(key, startOfRound))))
				{
					arrived.Add(key);
				}
			}
			foreach (GrabbableObject item in arrived)
			{
				if ((Object)(object)item != (Object)null && hauls.TryGetValue(item, out Haul value))
				{
					Pay(item, value, me);
				}
				hauls.Remove(item);
			}
		}

		private static void Pay(GrabbableObject item, Haul haul, PlayerControllerB me)
		{
			float num = ((haul.Haulers.Count > 0) ? 0.65f : 1f);
			float num2 = ((haul.Haulers.Count > 0) ? (0.35000002f / (float)haul.Haulers.Count) : 0f);
			float num3 = (Alive(haul.Finder) ? num : 0f);
			foreach (PlayerControllerB hauler in haul.Haulers)
			{
				num3 += (Alive(hauler) ? num2 : 0f);
			}
			float num4 = 0f;
			string text = "";
			if ((Object)(object)me == (Object)(object)haul.Finder && Alive(me))
			{
				num4 += num;
				text = "finder";
			}
			if (haul.Haulers.Contains(me) && Alive(me))
			{
				num4 += num2;
				text = ((text.Length > 0) ? "finder+hauler" : "hauler");
			}
			if (!(num4 <= 0f) && !(num3 <= 0f) && Plugin.ModEnabled.Value)
			{
				float num5 = num4 / num3;
				string text2 = (((Object)(object)item.itemProperties != (Object)null) ? item.itemProperties.itemName : "item");
				if (FairShare <= 0f)
				{
					early.Add(new EarlyDelivery
					{
						Name = text2,
						Value = item.scrapValue,
						Share = num5,
						Role = text
					});
					Telemetry.Event("LOOT_QUEUED", item.scrapValue, FormattableString.Invariant($"{text2} value {item.scrapValue} share {num5 * 100f:F0}% ({text}): today's loot not counted yet, paid once it is"));
				}
				else
				{
					PayOut(text2, item.scrapValue, num5, text, "");
				}
			}
		}

		private static void PayOut(string name, int value, float share, string role, string note)
		{
			float num = Mathf.Clamp(50f * (float)value / FairShare, 1f, 15f);
			float num2 = num * share;
			float num3 = Mathf.Max(0f, 30f - GainedToday);
			num2 = ((num2 <= num3) ? num2 : (num3 + (num2 - num3) * 0.5f));
			num2 *= Plugin.RecoveryMultiplier.Value;
			float num4 = SanityMeter.LootGain(num2);
			GainedToday += num4;
			Telemetry.Event("LOOT_RECOVERY", num4, FormattableString.Invariant($"{name} value {value} fair share {FairShare:F0} full {num:F1} share {share * 100f:F0}% ({role}) day total {GainedToday:F1}{note}"));
		}

		private static void CountScene(StartOfRound startOfRound, out int items, out int total)
		{
			items = 0;
			total = 0;
			GrabbableObject[] array = Object.FindObjectsOfType<GrabbableObject>();
			foreach (GrabbableObject val in array)
			{
				if (IsFresh(val, startOfRound))
				{
					items++;
					total += val.scrapValue;
				}
			}
		}

		private static bool IsFresh(GrabbableObject item, StartOfRound startOfRound)
		{
			if ((Object)(object)item == (Object)null || (Object)(object)item.itemProperties == (Object)null || !item.itemProperties.isScrap || item.scrapValue <= 0 || item.scrapPersistedThroughRounds || item.isInShipRoom || InShip(item, startOfRound))
			{
				return false;
			}
			RoundManager instance = RoundManager.Instance;
			if (!((Object)(object)instance == (Object)null))
			{
				return !instance.scrapDroppedInShip.Contains(item);
			}
			return true;
		}

		private static bool InShip(GrabbableObject item, StartOfRound startOfRound)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			Collider shipInnerRoomBounds = startOfRound.shipInnerRoomBounds;
			if ((Object)(object)shipInnerRoomBounds != (Object)null)
			{
				Bounds bounds = shipInnerRoomBounds.bounds;
				return ((Bounds)(ref bounds)).Contains(((Component)item).transform.position);
			}
			return false;
		}

		private static bool Alive(PlayerControllerB player)
		{
			if ((Object)(object)player != (Object)null && player.isPlayerControlled)
			{
				return !player.isPlayerDead;
			}
			return false;
		}

		private static void ResetDay()
		{
			onMoon = false;
			secondsOnMoon = 0f;
			dayStartedAt = 0f;
			pollTimer = 0f;
			LevelTotal = 0;
			LevelItems = 0;
			PlayersAtLanding = 1;
			GainedToday = 0f;
			CountSource = "";
			syncedValues = null;
			syncApplied = false;
			hauls.Clear();
			early.Clear();
		}
	}
	internal sealed class PhantomAudio : MonoBehaviour
	{
		private static PhantomAudio? instance;

		internal const float CloserFactor = 1.8f;

		internal static ConfigEntry<float>? Loudness;

		internal static float Closer => 1.8f * ((Loudness != null) ? Mathf.Clamp(Loudness.Value, 0.25f, 4f) : 1f);

		internal static bool IsPlaying { get; private set; }

		internal static bool TryClaim()
		{
			if ((Object)(object)instance == (Object)null || IsPlaying)
			{
				return false;
			}
			IsPlaying = true;
			return true;
		}

		internal static void Release()
		{
			IsPlaying = false;
		}

		internal static bool Run(IEnumerator routine)
		{
			if ((Object)(object)instance == (Object)null)
			{
				return false;
			}
			((MonoBehaviour)instance).StartCoroutine(routine);
			return true;
		}

		internal static void Create()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			GameObject val = new GameObject("Sanity.PhantomAudio");
			((Object)val).hideFlags = (HideFlags)61;
			Object.DontDestroyOnLoad((Object)(object)val);
			instance = val.AddComponent<PhantomAudio>();
		}

		internal static bool Play(PlayerControllerB player, MonsterSound sound, AudioClip[] clips, AudioSource? template, Vector3[] positions)
		{
			if ((Object)(object)instance == (Object)null || IsPlaying || clips.Length == 0 || positions.Length == 0)
			{
				return false;
			}
			IsPlaying = true;
			((MonoBehaviour)instance).StartCoroutine(instance.PlayMonster(player, sound, clips, template, positions));
			return true;
		}

		internal static AudioSource CreateSource(string name, AudioSource? template, Vector3 position, bool louder = true, float extra = 1f)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Expected O, but got Unknown
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name);
			val.transform.position = position;
			AudioSource val2 = val.AddComponent<AudioSource>();
			CopySettings(template, val2);
			if (louder)
			{
				Stretch(val2, Closer * Mathf.Max(1f, extra));
			}
			OccludeAudio val3 = (((Object)(object)template != (Object)null) ? ((Component)template).GetComponent<OccludeAudio>() : null);
			if ((Object)(object)val3 != (Object)null)
			{
				AudioLowPassFilter val4 = val.AddComponent<AudioLowPassFilter>();
				val4.cutoffFrequency = StartingCutoff(position, val2.maxDistance);
				OccludeAudio val5 = val.AddComponent<OccludeAudio>();
				val5.useReverb = val3.useReverb;
			}
			return val2;
		}

		private IEnumerator PlayMonster(PlayerControllerB player, MonsterSound sound, AudioClip[] clips, AudioSource? template, Vector3[] positions)
		{
			GameObject host = null;
			float started = Time.time;
			try
			{
				AudioSource source = CreateSource("Sanity.PhantomSound", template, positions[0]);
				host = ((Component)source).gameObject;
				AudioClip lastClip = null;
				for (int i = 0; i < positions.Length; i++)
				{
					if (Gone(player))
					{
						yield break;
					}
					host.transform.position = positions[i];
					AudioClip val = clips[Random.Range(0, clips.Length)];
					if ((Object)(object)val != (Object)null)
					{
						source.pitch = Random.Range(sound.MinPitch, sound.MaxPitch);
						source.PlayOneShot(val, Random.Range(sound.MinVolume, sound.MaxVolume));
						lastClip = val;
					}
					if (i >= positions.Length - 1)
					{
						continue;
					}
					float gap = Random.Range(sound.MinGap, sound.MaxGap);
					if (sound.GapAfterClip && (Object)(object)val != (Object)null)
					{
						gap += val.length / Mathf.Max(0.1f, source.pitch);
					}
					for (float t = 0f; t < gap; t += Time.deltaTime)
					{
						yield return null;
						if (Gone(player))
						{
							yield break;
						}
					}
				}
				float tail = (((Object)(object)lastClip != (Object)null) ? (lastClip.length / Mathf.Max(0.1f, source.pitch)) : 0f) + 0.25f;
				for (float gap = 0f; gap < tail; gap += Time.deltaTime)
				{
					yield return null;
					if (Gone(player))
					{
						break;
					}
				}
			}
			finally
			{
				if ((Object)(object)host != (Object)null)
				{
					Object.Destroy((Object)(object)host);
				}
				IsPlaying = false;
				float value = Time.time - started;
				Telemetry.Event("PHANTOM_SOUND_END", value, sound.Name + " lasted " + value.ToString("F1", CultureInfo.InvariantCulture) + "s");
			}
		}

		private static bool Gone(PlayerControllerB? player)
		{
			StartOfRound val = StartOfRound.Instance;
			if (!((Object)(object)player == (Object)null) && !player.isPlayerDead && SanityMeter.OnMoon)
			{
				if ((Object)(object)val != (Object)null)
				{
					return val.shipIsLeaving;
				}
				return false;
			}
			return true;
		}

		private static float StartingCutoff(Vector3 position, float maxDistance)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			StartOfRound val = StartOfRound.Instance;
			if ((Object)(object)val == (Object)null || (Object)(object)val.audioListener == (Object)null)
			{
				return 10000f;
			}
			Vector3 position2 = ((Component)val.audioListener).transform.position;
			if (!Physics.Linecast(position, position2, 256, (QueryTriggerInteraction)1))
			{
				return 10000f;
			}
			float num = Vector3.Distance(position2, position);
			return Mathf.Clamp(2500f / (num / (Mathf.Max(1f, maxDistance) / 2f)), 900f, 4000f);
		}

		private static void Stretch(AudioSource source, float factor)
		{
			if (!(factor <= 1.001f))
			{
				source.minDistance *= factor;
				source.maxDistance *= factor;
				source.volume = 1f;
			}
		}

		private static void CopySettings(AudioSource? from, AudioSource to)
		{
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Invalid comparison between Unknown and I4
			to.playOnAwake = false;
			to.loop = false;
			to.spatialBlend = 1f;
			if ((Object)(object)from == (Object)null)
			{
				to.rolloffMode = (AudioRolloffMode)0;
				to.minDistance = 2f;
				to.maxDistance = 40f;
				return;
			}
			to.outputAudioMixerGroup = from.outputAudioMixerGroup;
			to.volume = ((from.volume > 0.05f) ? from.volume : 1f);
			to.rolloffMode = from.rolloffMode;
			to.minDistance = from.minDistance;
			to.maxDistance = from.maxDistance;
			to.dopplerLevel = from.dopplerLevel;
			to.spread = from.spread;
			to.priority = from.priority;
			to.reverbZoneMix = from.reverbZoneMix;
			if ((int)from.rolloffMode == 2)
			{
				to.SetCustomCurve((AudioSourceCurveType)0, from.GetCustomCurve((AudioSourceCurveType)0));
			}
		}
	}
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInPlugin("Lowlight.Sanity", "Sanity", "0.0.2")]
	public class Plugin : BaseUnityPlugin
	{
		public const string Id = "Lowlight.Sanity";

		internal static ManualLogSource Log { get; private set; }

		internal static ConfigEntry<bool> ModEnabled { get; private set; }

		internal static ConfigEntry<bool> ShowReadout { get; private set; }

		internal static ConfigEntry<bool> DebugKeys { get; private set; }

		internal static ConfigEntry<bool> TelemetryEnabled { get; private set; }

		internal static ConfigEntry<bool> ShowPresence { get; private set; }

		internal static ConfigEntry<float> DrainMultiplier { get; private set; }

		internal static ConfigEntry<float> RecoveryMultiplier { get; private set; }

		internal static string BuildVersion
		{
			get
			{
				string text = typeof(Plugin).Assembly.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), inherit: false).OfType<AssemblyInformationalVersionAttribute>().FirstOrDefault()?.InformationalVersion ?? "?";
				int num = text.IndexOf('+');
				if (num < 0 || text.Length <= num + 8)
				{
					return text;
				}
				return text.Substring(0, num + 8);
			}
		}

		public static string Name => "Sanity";

		public static string Version => "0.0.2";

		private void Awake()
		{
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Expected O, but got Unknown
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Expected O, but got Unknown
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Expected O, but got Unknown
			//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d5: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			ModEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableMod", true, "Master switch. Off: sanity stays at 100 and no symptoms happen. Takes effect immediately.");
			ModEnabled.SettingChanged += delegate
			{
				Telemetry.Event("MOD_TOGGLED", ModEnabled.Value ? 1f : 0f, ModEnabled.Value ? "mod enabled" : "mod disabled");
			};
			DrainMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Tuning", "DrainMultiplier", 1f, new ConfigDescription("Scales how fast sanity drops. 2 = twice as fast, 0.5 = half as fast.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>()));
			RecoveryMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Tuning", "RecoveryMultiplier", 1f, new ConfigDescription("Scales how fast sanity recovers. 2 = twice as fast, 0.5 = half as fast.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>()));
			ShowReadout = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "ShowSanityReadout", false, "Show the current sanity value on screen. For testing and tuning: it shows the hidden meter, so it spoils the mystery.");
			DebugKeys = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "EnableDebugKeys", false, "Testing keys: F7 opens the debug menu, F8 fires a random symptom that makes sense right now.");
			ShowPresence = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "ShowPresence", false, "Testing: draw a red ball where the Presence is while it's around (only on your screen).");
			TelemetryEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Telemetry", "EnableTelemetry", false, "Record sanity and the events that change it to a CSV file in BepInEx/Sanity_Telemetry. Local only: nothing is sent anywhere. The file includes in-game player names.");
			PhantomAudio.Loudness = ((BaseUnityPlugin)this).Config.Bind<float>("Tuning", "PhantomLoudness", 1f, new ConfigDescription("How loud phantom steps and sounds are. 1 = default (already louder than the real thing), 2 = louder, 0.5 = quieter. Takes effect on the next sound.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 4f), Array.Empty<object>()));
			PhantomAudio.Loudness.SettingChanged += delegate
			{
				Telemetry.Event("PHANTOM_LOUDNESS", PhantomAudio.Loudness.Value, $"phantom loudness set to {PhantomAudio.Loudness.Value:0.##}");
			};
			SanityDirector.BindConfig(((BaseUnityPlugin)this).Config);
			Weather.BindConfig(((BaseUnityPlugin)this).Config);
			Harmony val = new Harmony("Lowlight.Sanity");
			Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(typeof(Plugin).Assembly);
			foreach (Type type in typesFromAssembly)
			{
				try
				{
					val.CreateClassProcessor(type).Patch();
				}
				catch (Exception ex)
				{
					Log.LogWarning((object)("Couldn't apply " + type.Name + " (that part of Sanity is off): " + ex.Message));
				}
			}
			LethalHudCompat.Apply(val);
			GeneralImprovementsCompat.Apply(val);
			SanityReadout.Create();
			DebugMenu.Create();
			PhantomAudio.Create();
			Telemetry.Create();
			Log.LogInfo((object)("Plugin " + Name + " " + BuildVersion + " is loaded!"));
		}
	}
	internal static class Reactions
	{
		private sealed class Probe
		{
			internal bool Real;

			internal string Name = "";

			internal float T0;

			internal bool Directional;

			internal Vector3 Source;

			internal float StartAngle;

			internal bool Turned;

			internal float TurnTime = -1f;

			internal float YawBefore;

			internal float SpeedBefore;

			internal float YawAfter;

			internal float DistAfter;

			internal bool Stopped;

			internal bool Sprinted;

			internal bool SprintingAtStart;

			internal bool Scan;

			internal bool Use;

			internal bool Walkie;

			internal bool HasWalkie;

			internal bool Away;

			internal bool Toward;

			internal bool Moved;

			internal Vector3 StartPos;

			internal string Confound = "";

			internal string Context = "";
		}

		private const float Window = 6f;

		private const float WalkieWindow = 20f;

		private const float AfterSeconds = 3f;

		private const float BinSeconds = 0.1f;

		private const int Bins = 30;

		private const float ControlMeanGap = 40f;

		private const float ControlQuietBefore = 10f;

		private const float TurnCone = 30f;

		private const float TurnStartMin = 90f;

		private const float StopFrom = 2f;

		private const float StopTo = 0.6f;

		private const float MoveMetres = 2f;

		private static readonly List<Probe> Open = new List<Probe>();

		private static readonly float[] YawBins = new float[30];

		private static readonly float[] DistBins = new float[30];

		private static int bin;

		private static float binTime;

		private static float lastYaw;

		private static bool hasYaw;

		private static Vector3 lastPos;

		private static bool hasPos;

		private static float smoothSpeed;

		private static float nextControl = -1f;

		private static readonly StringBuilder Text = new StringBuilder(400);

		internal static void OnSymptom(Symptom symptom, Vector3? source)
		{
			foreach (Probe item in Open)
			{
				if (item.Confound.Length == 0)
				{
					item.Confound = "another symptom";
				}
			}
			Open.RemoveAll((Probe p) => !p.Real);
			PlayerControllerB val = Local();
			if (!((Object)(object)val == (Object)null) && Plugin.TelemetryEnabled.Value)
			{
				Open.Add(NewProbe(val, real: true, symptom.ConfigKey, source));
			}
		}

		internal static void NoteConfound(string why)
		{
			foreach (Probe item in Open)
			{
				if (item.Confound.Length == 0)
				{
					item.Confound = why;
				}
			}
		}

		internal static void Reset()
		{
			Open.Clear();
			hasYaw = false;
			hasPos = false;
			nextControl = -1f;
			Array.Clear(YawBins, 0, 30);
			Array.Clear(DistBins, 0, 30);
		}

		internal static void Tick(PlayerControllerB player)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01be: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cb: Unknown result type (might be due to invalid IL or missing references)
			float deltaTime = Time.deltaTime;
			float y = ((Component)player.gameplayCamera).transform.eulerAngles.y;
			float num = (hasYaw ? Mathf.Abs(Mathf.DeltaAngle(lastYaw, y)) : 0f);
			lastYaw = y;
			hasYaw = true;
			Vector3 position = ((Component)player).transform.position;
			Vector3 val = (hasPos ? (position - lastPos) : Vector3.zero);
			val.y = 0f;
			float num2 = ((((Vector3)(ref val)).magnitude < 4f) ? ((Vector3)(ref val)).magnitude : 0f);
			lastPos = position;
			hasPos = true;
			if (deltaTime > 0f)
			{
				smoothSpeed = Mathf.Lerp(smoothSpeed, num2 / deltaTime, 1f - Mathf.Exp((0f - deltaTime) / 0.3f));
			}
			binTime += deltaTime;
			YawBins[bin] += num;
			DistBins[bin] += num2;
			if (binTime >= 0.1f)
			{
				binTime = 0f;
				bin = (bin + 1) % 30;
				YawBins[bin] = 0f;
				DistBins[bin] = 0f;
			}
			if (nextControl < 0f)
			{
				nextControl = Time.time + NextGap();
			}
			if (Time.time >= nextControl)
			{
				nextControl = Time.time + NextGap();
				if (SanityDirector.SecondsSinceLast >= 10f && !player.isPlayerDead && !player.quickMenuManager.isMenuOpen)
				{
					Vector3 val2 = PhantomPlacement.RandomBehindDirection(PhantomPlacement.LookDirection(player));
					Open.Add(NewProbe(player, real: false, "control", position + val2 * 8f));
				}
			}
			for (int num3 = Open.Count - 1; num3 >= 0; num3--)
			{
				Probe probe = Open[num3];
				float num4 = Time.time - probe.T0;
				Update(player, probe, num4, num, num2);
				float num5 = (probe.HasWalkie ? 20f : 6f);
				if (num4 >= num5)
				{
					Emit(probe);
					Open.RemoveAt(num3);
				}
			}
		}

		internal static void Flush(string why)
		{
			foreach (Probe item in Open)
			{
				if (item.Confound.Length == 0)
				{
					item.Confound = why;
				}
				Emit(item);
			}
			Open.Clear();
		}

		private static float NextGap()
		{
			return (0f - Mathf.Log(Mathf.Max(0.0001f, Random.value))) * 40f;
		}

		private static Probe NewProbe(PlayerControllerB player, bool real, string name, Vector3? source)
		{
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			float num = 0f;
			float num2 = 0f;
			for (int i = 0; i < 30; i++)
			{
				num += YawBins[i];
				num2 += DistBins[i];
			}
			Probe probe = new Probe
			{
				Real = real,
				Name = name,
				T0 = Time.time,
				Directional = source.HasValue,
				Source = (Vector3)(((??)source) ?? Vector3.zero),
				YawBefore = num / 3f,
				SpeedBefore = num2 / 3f,
				SprintingAtStart = player.isSprinting,
				HasWalkie = CarriesPoweredWalkie(player),
				StartPos = ((Component)player).transform.position
			};
			if (probe.Directional)
			{
				probe.StartAngle = PhantomPlacement.AngleFromLook(player, probe.Source);
			}
			string text = (player.isInsideFactory ? "inside" : (player.isInHangarShipRoom ? "ship" : "outside"));
			probe.Context = FormattableString.Invariant($"place={text} company={(SanityMeter.NearTeammate ? 1 : 0)} moving={((probe.SpeedBefore >= 1f) ? 1 : 0)} tier={SanityMeter.Tier} fear={SanityMeter.Fear:F2} light={(LightOn(player) ? 1 : 0)} has_walkie={(probe.HasWalkie ? 1 : 0)}");
			return probe;
		}

		private static void Update(PlayerControllerB player, Probe probe, float elapsed, float yawDelta, float dist)
		{
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			//IL_0166: Unknown result type (might be due to invalid IL or missing references)
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			//IL_018a: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			if (player.isPlayerDead)
			{
				probe.Confound = ((probe.Confound.Length == 0) ? "died" : probe.Confound);
				return;
			}
			if (probe.Confound.Length == 0)
			{
				if (SanityMeter.SecondsSinceRealFear < elapsed)
				{
					probe.Confound = "real fear";
				}
				else if (SanityMeter.SecondsSinceDamage < elapsed)
				{
					probe.Confound = "damage";
				}
			}
			if (elapsed <= 3f)
			{
				probe.YawAfter += yawDelta;
				probe.DistAfter += dist;
				if (probe.SpeedBefore >= 2f && smoothSpeed <= 0.6f)
				{
					probe.Stopped = true;
				}
				if (!probe.SprintingAtStart && player.isSprinting)
				{
					probe.Sprinted = true;
				}
			}
			if (elapsed <= 6f)
			{
				probe.Scan |= InputWatch.Pressed("PingScan");
				probe.Use |= InputWatch.Pressed("Interact");
				if (probe.Directional && !probe.Turned && probe.StartAngle >= 90f && PhantomPlacement.AngleFromLook(player, probe.Source) <= 30f)
				{
					probe.Turned = true;
					probe.TurnTime = elapsed;
				}
				if (probe.Directional && !probe.Moved && elapsed >= 4f)
				{
					probe.Moved = true;
					Vector3 val = PhantomPlacement.Flat(probe.StartPos - probe.Source);
					Vector3 val2 = ((Component)player).transform.position - probe.StartPos;
					val2.y = 0f;
					float num = Vector3.Dot(val2, val);
					probe.Away = num >= 2f;
					probe.Toward = num <= -2f;
				}
			}
			if (probe.HasWalkie && player.speakingToWalkieTalkie)
			{
				probe.Walkie = true;
			}
		}

		private static void Emit(Probe probe)
		{
			Text.Clear();
			Text.Append("kind=").Append(probe.Real ? "real" : "control").Append(" name=")
				.Append(probe.Name)
				.Append(" dir=")
				.Append(probe.Directional ? 1 : 0);
			if (probe.Directional)
			{
				Text.Append(FormattableString.Invariant($" start_angle={probe.StartAngle:F0} turned={(probe.Turned ? 1 : 0)} turn_s={probe.TurnTime:F1} away={(probe.Away ? 1 : 0)} toward={(probe.Toward ? 1 : 0)}"));
			}
			Text.Append(FormattableString.Invariant($" yaw_b={probe.YawBefore:F0} yaw_a={probe.YawAfter / 3f:F0} v_b={probe.SpeedBefore:F1} v_a={probe.DistAfter / 3f:F1}")).Append(FormattableString.Invariant($" stopped={(probe.Stopped ? 1 : 0)} sprinted={(probe.Sprinted ? 1 : 0)} scan={(probe.Scan ? 1 : 0)} use={(probe.Use ? 1 : 0)} walkie={(probe.Walkie ? 1 : 0)}")).Append(' ')
				.Append(probe.Context)
				.Append(" confound=")
				.Append((probe.Confound.Length == 0) ? "none" : probe.Confound.Replace(' ', '_'));
			Telemetry.Event("REACTION", probe.Real ? 1f : 0f, Text.ToString());
		}

		private static bool CarriesPoweredWalkie(PlayerControllerB player)
		{
			if (player.ItemSlots == null)
			{
				return false;
			}
			GrabbableObject[] itemSlots = player.ItemSlots;
			foreach (GrabbableObject val in itemSlots)
			{
				WalkieTalkie val2 = (WalkieTalkie)(object)((val is WalkieTalkie) ? val : null);
				if (val2 != null && ((GrabbableObject)val2).isBeingUsed)
				{
					return true;
				}
			}
			return false;
		}

		internal static bool LightOn(PlayerControllerB player)
		{
			if ((Object)(object)player.helmetLight != (Object)null && ((Behaviour)player.helmetLight).enabled)
			{
				return true;
			}
			GrabbableObject currentlyHeldObjectServer = player.currentlyHeldObjectServer;
			return ((currentlyHeldObjectServer is FlashlightItem) ? currentlyHeldObjectServer : null)?.isBeingUsed ?? false;
		}

		private static PlayerControllerB? Local()
		{
			GameNetworkManager instance = GameNetworkManager.Instance;
			if (!((Object)(object)instance != (Object)null))
			{
				return null;
			}
			return instance.localPlayerController;
		}
	}
	internal static class Safety
	{
		private static readonly HashSet<string> Reported = new HashSet<string>();

		internal static void Report(string where, Exception error)
		{
			if (!Reported.Add(where))
			{
				return;
			}
			Plugin.Log.LogWarning((object)$"Error in {where} (logged once, Sanity keeps going): {error}");
			if (where != "telemetry")
			{
				try
				{
					Telemetry.Event("SANITY_ERROR", 0f, where + ": " + error.GetType().Name + ": " + error.Message);
				}
				catch (Exception)
				{
				}
			}
		}
	}
	internal static class SanityDirector
	{
		private const float MaxChancePerSecond = 0.044f;

		private const float DepthFloor = 0.4f;

		private const float DipFloor = 0.1f;

		private const float DipRecoverySeconds = 8f;

		private const float TensionDoublingSeconds = 120f;

		private const float PeakRelaxSeconds = 50f;

		private static readonly List<Symptom> AllSymptoms = new List<Symptom>
		{
			new FlickerSymptom(),
			new PhantomFearSymptom(),
			new PhantomDreadSymptom(),
			new PhantomSoundSymptom(),
			new PhantomHitSymptom(),
			new PhantomStepsSymptom(),
			new PhantomWalkieSymptom(),
			new FakeLockedDoorSymptom(),
			new VanishingItemSymptom(),
			new LostTimeSymptom(),
			new PresenceSymptom()
		};

		private static readonly List<Symptom> Eligible = new List<Symptom>();

		private static readonly HashSet<Symptom> HeldForSprint = new HashSet<Symptom>();

		private static readonly StringBuilder RollText = new StringBuilder(320);

		internal static bool Paused;

		internal static bool IgnoreConditions;

		internal static bool IgnoreGapsNatural;

		internal static bool IgnoreGapsForced;

		internal const string SprintingReason = "busy: sprinting";

		internal static bool SprintCheckOff;

		internal static bool ChainsAlways;

		internal static float ChancePerSecond;

		internal static float SecondsSinceLast = 9999f;

		internal static float SecondsSinceVisible = 9999f;

		internal static string LastSymptom = "none";

		private static float quietSeconds;

		private static float relaxUntil;

		private static float secondsOnMoon;

		internal static IReadOnlyList<Symptom> Symptoms => AllSymptoms;

		internal static bool GapsIgnored { get; private set; }

		internal static bool ConditionsIgnored { get; private set; }

		internal static bool ForcedContext { get; private set; }

		internal static float RelaxLeft => Mathf.Max(0f, relaxUntil - Time.time);

		internal static float QuietSeconds => quietSeconds;

		internal static string TuningSummary => FormattableString.Invariant($"chance {0.044f}/s floor {0.4f} dip {0.1f}/{8f}s tension x2 per {120f}s; ") + Waves.TuningSummary + "; " + DayMood.TuningSummary + "; " + Chains.TuningSummary;

		internal static bool Relaxing => Time.time < relaxUntil;

		internal static void Relax(float seconds, string why)
		{
			float num = Time.time + seconds;
			if (num > relaxUntil)
			{
				relaxUntil = num;
				Telemetry.Event("RELAX", seconds, why);
			}
		}

		internal static void BindConfig(ConfigFile config)
		{
			foreach (Symptom allSymptom in AllSymptoms)
			{
				ConfigEntry<bool> entry = config.Bind<bool>("Symptoms", allSymptom.ConfigKey, true, "Allow the " + allSymptom.Name + " symptom. Off: it never happens on its own (the debug menu can still fire it).");
				Symptom captured = allSymptom;
				entry.SettingChanged += delegate
				{
					Telemetry.Event("SYMPTOM_TOGGLED", entry.Value ? 1f : 0f, captured.Name + " " + (entry.Value ? "on" : "off"));
				};
				allSymptom.EnabledEntry = entry;
			}
		}

		internal static void Tick(PlayerControllerB player)
		{
			if (Plugin.DebugKeys.Value && Keyboard.current != null && ((ButtonControl)Keyboard.current.f8Key).wasPressedThisFrame)
			{
				TryPlay(player, forced: true);
			}
			if (!SanityMeter.OnMoon)
			{
				quietSeconds = 0f;
				ChancePerSecond = 0f;
				SecondsSinceLast = 9999f;
				SecondsSinceVisible = 9999f;
				LastSymptom = "none";
				relaxUntil = 0f;
				secondsOnMoon = 0f;
				DayMood.Reset();
				Waves.Reset();
				Chains.Reset();
				SprintHold.Reset();
				{
					foreach (Symptom allSymptom in AllSymptoms)
					{
						allSymptom.SecondsSincePlayed = 9999f;
						allSymptom.ResetDay();
					}
					return;
				}
			}
			float deltaTime = Time.deltaTime;
			SecondsSinceLast += deltaTime;
			SecondsSinceVisible += deltaTime;
			secondsOnMoon += deltaTime;
			if (secondsOnMoon > 2f)
			{
				DayMood.EnsureRolled();
			}
			Waves.Tick(deltaTime);
			foreach (Symptom allSymptom2 in AllSymptoms)
			{
				allSymptom2.SecondsSincePlayed += deltaTime;
				allSymptom2.Watch(player);
			}
			Chains.Tick(player);
			SprintHold.Tick(player);
			if (player.isPlayerDead || SanityMeter.Tier == SanityTier.Calm)
			{
				ChancePerSecond = 0f;
				return;
			}
			if (Relaxing)
			{
				ChancePerSecond = 0f;
				return;
			}
			quietSeconds += deltaTime;
			EligibilityLog.Tick(player, deltaTime);
			float calmLine = SanityMeter.CalmLine;
			float num = 0.4f + 0.6f * Mathf.Clamp01((calmLine - SanityMeter.Value) / calmLine);
			float num2 = 1f - 0.9f * Mathf.Exp((0f - SecondsSinceLast) / 8f);
			float num3 = 1f + quietSeconds / 120f;
			ChancePerSecond = 0.044f * num * num2 * num3 * Waves.Multiplier * DayMood.Rate;
			if (!Paused && Random.value < ChancePerSecond * deltaTime)
			{
				TryPlay(player, forced: false);
			}
		}

		private static void TryPlay(PlayerControllerB player, bool forced)
		{
			SetContext(forced);
			try
			{
				SanityTier sanityTier = SanityMeter.Tier;
				if (ConditionsIgnored && sanityTier == SanityTier.Calm)
				{
					sanityTier = SanityTier.Uneasy;
				}
				string text = (SanityMeter.Inside ? "inside" : (SanityMeter.InShip ? "ship" : "outside"));
				RollText.Clear();
				RollText.Append(FormattableString.Invariant(FormattableStringFactory.Create("{0} {1} {2} chance {3:F3} wave {4} mood {5}{6} |", sanityTier, text, SanityMeter.NearTeammate ? "company" : "alone", ChancePerSecond, Waves.High ? "high" : "low", DayMood.Name, forced ? " F8" : "")));
				Eligible.Clear();
				HeldForSprint.Clear();
				float num = 0f;
				foreach (Symptom allSymptom in AllSymptoms)
				{
					string text2 = WhyNot(allSymptom, player, sanityTier);
					if (text2 == "busy: sprinting" && allSymptom.HoldsForSprint && !SprintHold.Waiting && WouldPlayWithoutSprint(allSymptom, player, sanityTier))
					{
						HeldForSprint.Add(allSymptom);
						text2 = "";
					}
					if (text2.Length == 0)
					{
						Eligible.Add(allSymptom);
						float num2 = Weight(allSymptom);
						num += num2;
						RollText.Append(FormattableString.Invariant($" {allSymptom.ConfigKey}=w{num2:0.##}")).Append(HeldForSprint.Contains(allSymptom) ? "(sprint)" : "");
					}
					else
					{
						RollText.Append(' ').Append(allSymptom.ConfigKey).Append("=[")
							.Append(text2)
							.Append(']');
					}
				}
				RollText.Append(" |");
				if (Eligible.Count == 0)
				{
					Telemetry.Event("ROLL_NOTHING_ELIGIBLE", (float)sanityTier, forced ? "F8" : "");
				}
				string text3 = "none";
				while (Eligible.Count > 0)
				{
					Symptom symptom = Pick(num);
					float recovery = symptom.Recovery;
					float value = Random.value;
					if (!GapsIgnored && value >= recovery)
					{
						Telemetry.Event("SYMPTOM_REJECTED", recovery, FormattableString.Invariant($"{symptom.Name} turned down (recovery {recovery * 100f:F0}%)") + (forced ? " (F8)" : ""));
						RollText.Append(FormattableString.Invariant($" {symptom.ConfigKey}:rejected[recovery {recovery:F2} roll {value:F2}]"));
					}
					else
					{
						if (HeldForSprint.Contains(symptom))
						{
							HoldForSprint(symptom, sanityTier, forced ? "F8" : "");
							RollText.Append(' ').Append(symptom.ConfigKey).Append(":held");
							text3 = symptom.ConfigKey;
							break;
						}
						if (PlayChosen(player, symptom, sanityTier, forced ? "F8" : ""))
						{
							RollText.Append(' ').Append(symptom.ConfigKey).Append((symptom is ArmedSymptom) ? ":armed" : ":played");
							text3 = symptom.ConfigKey;
							break;
						}
						RollText.Append(' ').Append(symptom.ConfigKey).Append(":failed[")
							.Append(symptom.LastFailure)
							.Append(']');
					}
					num -= Weight(symptom);
					Eligible.Remove(symptom);
				}
				RollText.Append(" -> ").Append(text3);
				Telemetry.Event("ROLL", ChancePerSecond, RollText.ToString());
				if (forced && text3 == "none")
				{
					Plugin.Log.LogInfo((object)"Debug: no symptom could play here right now.");
				}
			}
			finally
			{
				SetContext(forced: false);
			}
		}

		internal static string DebugFire(PlayerControllerB player, Symptom symptom, SanityTier tier)
		{
			SetContext(forced: true);
			try
			{
				if (!ConditionsIgnored)
				{
					if (!symptom.Enabled)
					{
						return symptom.Name + " is off (tick it, or turn on Ignore conditions).";
					}
					if (SanityMeter.Tier < symptom.MinTier)
					{
						return $"{symptom.Name} needs {symptom.MinTier} or lower; you're {SanityMeter.Tier}.";
					}
					string text = symptom.CantPlayReason(player);
					if (text.Length > 0)
					{
						return symptom.Name + " can't play here: " + text + ".";
					}
				}
				if (!GapsIgnored && Random.value >= symptom.Recovery)
				{
					Telemetry.Event("SYMPTOM_REJECTED", symptom.Recovery, FormattableString.Invariant($"{symptom.Name} turned down (recovery {symptom.Recovery * 100f:F0}%) (debug menu)"));
					return FormattableString.Invariant($"{symptom.Name} was turned down by the recovery check ({symptom.Recovery * 100f:F0}% recovered).");
				}
				if (!PlayChosen(player, symptom, tier, "debug menu"))
				{
					return symptom.Name + " couldn't play: " + symptom.LastFailure + ".";
				}
				return (symptom is ArmedSymptom armedSymptom) ? ("Armed " + symptom.Name + ": it fires on its own at " + armedSymptom.Waits + ".") : $"Fired {symptom.Name} at {tier} strength.";
			}
			finally
			{
				SetContext(forced: false);
			}
		}

		internal static string NaturalCantPlayReason(Symptom symptom, PlayerControllerB player)
		{
			SetContext(force