Decompiled source of Silence v0.3.2

BepInEx/plugins/Silence/Silence.dll

Decompiled 2 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using Dissonance;
using Dissonance.Audio.Capture;
using Dissonance.VAD;
using GameNetcodeStuff;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using NAudio.Wave;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;

[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("Silence")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.3.2.0")]
[assembly: AssemblyInformationalVersion("0.3.2")]
[assembly: AssemblyProduct("Silence")]
[assembly: AssemblyTitle("Silence")]
[assembly: AssemblyVersion("0.3.2.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Silence
{
	internal sealed class Blackout
	{
		private sealed class SavedLight
		{
			internal int Mask;

			internal float Intensity;
		}

		private readonly Dictionary<Light, SavedLight> masks = new Dictionary<Light, SavedLight>();

		private readonly List<Light> extra = new List<Light>();

		internal int Count => masks.Count;

		internal void Apply()
		{
			List<Light> list = (Object.op_Implicit((Object)(object)RoundManager.Instance) ? RoundManager.Instance.allPoweredLights : null);
			if (list == null)
			{
				return;
			}
			foreach (Light item in list)
			{
				Hide(item);
			}
			List<Animator> allPoweredLightsAnimators = RoundManager.Instance.allPoweredLightsAnimators;
			if (allPoweredLightsAnimators == null)
			{
				return;
			}
			foreach (Animator item2 in allPoweredLightsAnimators)
			{
				if (!Object.op_Implicit((Object)(object)item2))
				{
					continue;
				}
				extra.Clear();
				((Component)item2).GetComponentsInChildren<Light>(true, extra);
				foreach (Light item3 in extra)
				{
					Hide(item3);
				}
			}
		}

		private void Hide(Light light)
		{
			if (!Object.op_Implicit((Object)(object)light) || Object.op_Implicit((Object)(object)((Component)light).GetComponentInParent<GrabbableObject>()) || Object.op_Implicit((Object)(object)((Component)light).GetComponentInParent<PlayerControllerB>()))
			{
				return;
			}
			if (!masks.TryGetValue(light, out var value))
			{
				Dictionary<Light, SavedLight> dictionary = masks;
				SavedLight obj = new SavedLight
				{
					Mask = light.cullingMask,
					Intensity = light.intensity
				};
				value = obj;
				dictionary.Add(light, obj);
			}
			else
			{
				if (light.cullingMask != 0)
				{
					value.Mask = light.cullingMask;
				}
				if (light.intensity != 0f)
				{
					value.Intensity = light.intensity;
				}
			}
			light.cullingMask = 0;
			light.intensity = 0f;
		}

		internal void Restore()
		{
			foreach (KeyValuePair<Light, SavedLight> mask in masks)
			{
				if (Object.op_Implicit((Object)(object)mask.Key))
				{
					if (mask.Key.cullingMask == 0)
					{
						mask.Key.cullingMask = mask.Value.Mask;
					}
					if (mask.Key.intensity == 0f)
					{
						mask.Key.intensity = mask.Value.Intensity;
					}
				}
			}
			masks.Clear();
		}
	}
	internal sealed class ShhAudio
	{
		private readonly Plugin plugin;

		private readonly AudioSource source;

		private AudioClip clip;

		internal string Status { get; private set; } = "Not loaded";

		internal ShhAudio(Plugin owner)
		{
			plugin = owner;
			source = ((Component)owner).gameObject.AddComponent<AudioSource>();
			source.playOnAwake = false;
			source.loop = false;
			source.spatialBlend = 0f;
			source.panStereo = 0f;
			source.dopplerLevel = 0f;
			source.bypassReverbZones = true;
		}

		internal IEnumerator Load()
		{
			string path = null;
			try
			{
				string directoryName = Path.GetDirectoryName(((BaseUnityPlugin)plugin).Info.Location);
				string value = plugin.Options.SoundFile.Value;
				if (string.IsNullOrWhiteSpace(value) || Path.IsPathRooted(value))
				{
					throw new IOException("Use a relative audio filename.");
				}
				path = Path.GetFullPath(Path.Combine(directoryName, value));
				if (!path.StartsWith(Path.GetFullPath(directoryName) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))
				{
					throw new IOException("Audio must be inside the plugin directory.");
				}
				if (!File.Exists(path))
				{
					throw new FileNotFoundException("Sound file missing", path);
				}
				if (new FileInfo(path).Length > 33554432)
				{
					throw new IOException("Sound exceeds 32 MiB.");
				}
				string text = Path.GetExtension(path).ToLowerInvariant();
				if (text != ".wav" && text != ".ogg")
				{
					throw new IOException("Use WAV or OGG.");
				}
			}
			catch (Exception ex)
			{
				Status = ex.Message;
				plugin.Warn("Sound skipped: " + Status);
			}
			if (path == null || Status != "Not loaded")
			{
				yield break;
			}
			UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(new Uri(path).AbsoluteUri, (AudioType)(Path.GetExtension(path).Equals(".ogg", StringComparison.OrdinalIgnoreCase) ? 14 : 20));
			try
			{
				yield return request.SendWebRequest();
				try
				{
					if ((int)request.result != 1)
					{
						throw new IOException(request.error);
					}
					AudioClip content = DownloadHandlerAudioClip.GetContent(request);
					if (!Object.op_Implicit((Object)(object)content) || content.samples == 0 || content.length > 30f)
					{
						throw new IOException("Invalid sound or longer than 30 seconds.");
					}
					if (content.channels > 1)
					{
						float[] array = new float[content.samples * content.channels];
						if (!content.GetData(array, 0))
						{
							throw new IOException("Could not decode sound samples.");
						}
						float[] array2 = new float[content.samples];
						for (int i = 0; i < array2.Length; i++)
						{
							for (int j = 0; j < content.channels; j++)
							{
								array2[i] += array[i * content.channels + j] / (float)content.channels;
							}
						}
						clip = AudioClip.Create("Silence shh", array2.Length, 1, content.frequency, false);
						clip.SetData(array2, 0);
						Object.Destroy((Object)(object)content);
					}
					else
					{
						clip = content;
					}
					Status = "Ready: " + Path.GetFileName(path);
					plugin.Log(Status);
				}
				catch (Exception ex2)
				{
					Status = ex2.Message;
					plugin.Warn("Sound skipped: " + Status);
				}
			}
			finally
			{
				((IDisposable)request)?.Dispose();
			}
		}

		internal void Play()
		{
			if (Object.op_Implicit((Object)(object)clip) && plugin.Options.Sound.Value)
			{
				source.PlayOneShot(clip, Settings.Safe(plugin.Options.Volume.Value, 0.8f, 0f, 1f));
			}
		}

		internal void Stop()
		{
			source.Stop();
		}

		internal void Dispose()
		{
			source.Stop();
			if (Object.op_Implicit((Object)(object)clip))
			{
				Object.Destroy((Object)(object)clip);
			}
			Object.Destroy((Object)(object)source);
		}
	}
	internal enum EventPhase
	{
		Idle,
		Warning,
		Active,
		Cooldown
	}
	internal sealed class EventTimeline
	{
		internal int Revision { get; private set; } = -1;

		internal bool Scheduled { get; private set; }

		internal double Start { get; private set; }

		internal double GraceEnd { get; private set; }

		internal double End { get; private set; }

		internal double CooldownEnd { get; private set; }

		internal bool Accept(int revision, bool scheduled, double start, double end, double cooldownEnd, double? graceEnd = null)
		{
			double num = graceEnd ?? start;
			if (!Finite(num) || num < start || num > end)
			{
				return false;
			}
			if (revision <= Revision || !Finite(start) || !Finite(end) || !Finite(cooldownEnd) || end < start || cooldownEnd < end || (scheduled && (end - start < 1.0 || end - start > 300.0)))
			{
				return false;
			}
			Revision = revision;
			Scheduled = scheduled;
			Start = start;
			End = end;
			GraceEnd = num;
			CooldownEnd = cooldownEnd;
			return true;
		}

		internal EventPhase Phase(double now)
		{
			if (!Scheduled || !(now >= Start) || !(now < End))
			{
				if (!(now >= End) || !(now < CooldownEnd))
				{
					return EventPhase.Idle;
				}
				return EventPhase.Cooldown;
			}
			if (!(now < GraceEnd))
			{
				return EventPhase.Active;
			}
			return EventPhase.Warning;
		}

		internal bool BlackedOut(double now)
		{
			if (Scheduled && now >= Start)
			{
				return now < End;
			}
			return false;
		}

		internal bool Pending(double now)
		{
			if (Scheduled)
			{
				return now < Start;
			}
			return false;
		}

		internal static double ResumeNaturalChecks(double now, double cooldownEnd)
		{
			return Math.Max(now, cooldownEnd);
		}

		private static bool Finite(double x)
		{
			if (!double.IsNaN(x))
			{
				return !double.IsInfinity(x);
			}
			return false;
		}
	}
	[BepInPlugin("local.silence", "Silence", "0.3.2")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private const string StateMessage = "Silence.v032.state";

		private const string RequestMessage = "Silence.v032.sync";

		internal static Plugin Instance;

		internal Settings Options;

		private Harmony harmony;

		private NetworkManager network;

		private CustomMessagingManager messaging;

		private EventTimeline timeline = new EventTimeline();

		private readonly Blackout blackout = new Blackout();

		private readonly VoiceMonitor voice = new VoiceMonitor();

		private readonly Random random = new Random();

		private ShhAudio audio;

		private PowerOffAudio powerAudio;

		private bool effectActive;

		private bool killed;

		private bool detectionMovement;

		private bool detectionVoice;

		private bool sampled;

		private bool wasRound;

		private float voiceThreshold;

		private float nextLog;

		private float nextSync;

		private double nextCheck;

		private readonly ReactionWindow reaction = new ReactionWindow();

		private bool detectionArmed;

		private static readonly FieldInfo JumpingField = AccessTools.Field(typeof(PlayerControllerB), "isJumping");

		private int soundedRevision = -1;

		private Vector3 previousPosition;

		private PlayerControllerB sampledPlayer;

		private PlayerControllerB deathRequestedFor;

		private bool Lethal
		{
			get
			{
				if (Active && effectActive)
				{
					return reaction.Lethal(Now, timeline.End);
				}
				return false;
			}
		}

		private double Now
		{
			get
			{
				//IL_001d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0022: Unknown result type (might be due to invalid IL or missing references)
				if (!Object.op_Implicit((Object)(object)network))
				{
					return 0.0;
				}
				NetworkTime serverTime = network.ServerTime;
				return ((NetworkTime)(ref serverTime)).Time;
			}
		}

		private PlayerControllerB Local
		{
			get
			{
				if (!Object.op_Implicit((Object)(object)GameNetworkManager.Instance))
				{
					return null;
				}
				return GameNetworkManager.Instance.localPlayerController;
			}
		}

		private bool RoundRunning
		{
			get
			{
				if (Object.op_Implicit((Object)(object)StartOfRound.Instance) && StartOfRound.Instance.shipHasLanded)
				{
					return !StartOfRound.Instance.inShipPhase;
				}
				return false;
			}
		}

		private bool Active
		{
			get
			{
				if (Options.EnableMod.Value && Object.op_Implicit((Object)(object)network) && network.IsListening && RoundRunning)
				{
					return timeline.BlackedOut(Now);
				}
				return false;
			}
		}

		private float Interval => Settings.Safe(Options.Interval.Value, 25f, 1f, 3600f);

		internal static bool Inside(PlayerControllerB p)
		{
			if (Object.op_Implicit((Object)(object)p) && p.isPlayerControlled && !p.isPlayerDead && p.isInsideFactory)
			{
				return !p.isInHangarShipRoom;
			}
			return false;
		}

		private void Awake()
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Expected O, but got Unknown
			Instance = this;
			Options = new Settings(((BaseUnityPlugin)this).Config);
			audio = new ShhAudio(this);
			((MonoBehaviour)this).StartCoroutine(audio.Load());
			powerAudio = new PowerOffAudio(this);
			((MonoBehaviour)this).StartCoroutine(powerAudio.LoadCustom());
			harmony = new Harmony("local.silence");
			harmony.PatchAll(typeof(Plugin).Assembly);
			Log("Silence 0.3.2 development build loaded. Debug keys disabled by default.");
		}

		internal void Log(string message)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)message);
		}

		internal void Warn(string message)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)message);
		}

		private unsafe void Update()
		{
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			NetworkManager singleton = NetworkManager.Singleton;
			if (!Object.op_Implicit((Object)(object)singleton) || !singleton.IsListening || (!singleton.IsServer && !singleton.IsConnectedClient))
			{
				if (messaging != null)
				{
					Detach();
				}
				return;
			}
			if ((Object)(object)singleton != (Object)(object)network || messaging != singleton.CustomMessagingManager)
			{
				Attach(singleton);
			}
			if (messaging == null)
			{
				return;
			}
			voice.Bind(Object.op_Implicit((Object)(object)StartOfRound.Instance) ? StartOfRound.Instance.voiceChatModule : null);
			if (!network.IsServer && timeline.Revision < 0 && Time.unscaledTime >= nextSync)
			{
				nextSync = Time.unscaledTime + 2f;
				FastBufferWriter val = default(FastBufferWriter);
				((FastBufferWriter)(ref val))..ctor(1, (Allocator)2, -1);
				try
				{
					messaging.SendNamedMessage("Silence.v032.sync", 0uL, val, (NetworkDelivery)3);
				}
				finally
				{
					((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose();
				}
			}
			bool roundRunning = RoundRunning;
			if (Object.op_Implicit((Object)(object)deathRequestedFor) && deathRequestedFor.isPlayerDead)
			{
				deathRequestedFor = null;
			}
			if (roundRunning && !wasRound)
			{
				deathRequestedFor = null;
			}
			if (roundRunning)
			{
				powerAudio.Prepare();
			}
			else if (wasRound)
			{
				powerAudio.ResetCache();
			}
			if (network.IsServer)
			{
				if ((!roundRunning || !Options.EnableMod.Value) && (timeline.Scheduled || timeline.Phase(Now) == EventPhase.Cooldown))
				{
					EndEvent(reset: true);
				}
				if (roundRunning && !wasRound)
				{
					nextCheck = Now + (double)Interval;
				}
				if (roundRunning && Options.EnableMod.Value && timeline.Scheduled && Now >= timeline.End)
				{
					EndEvent(reset: false);
				}
				if (roundRunning && Options.EnableMod.Value && Options.Natural.Value && !timeline.Pending(Now) && timeline.Phase(Now) == EventPhase.Idle && Now >= nextCheck)
				{
					nextCheck = Now + (double)Interval;
					if (HasIndoorPlayer() && random.NextDouble() < (double)Settings.Safe(Options.Chance.Value, 0.03f, 0f, 1f))
					{
						StartEvent();
					}
				}
			}
			wasRound = roundRunning;
			if (Active && !effectActive)
			{
				effectActive = true;
				killed = false;
				sampled = false;
				reaction.Begin(Now, timeline.Start, timeline.GraceEnd, timeline.End);
				detectionArmed = false;
				voice.Disarm();
				blackout.Apply();
				if (soundedRevision != timeline.Revision)
				{
					soundedRevision = timeline.Revision;
					if (Inside(Local))
					{
						powerAudio.Play();
						audio.Play();
					}
				}
				Log($"Event active; revision={timeline.Revision}, lights={blackout.Count}");
			}
			if (!Active && effectActive)
			{
				RestoreEffects();
			}
			DebugKeys();
		}

		private bool HasIndoorPlayer()
		{
			PlayerControllerB[] array = (Object.op_Implicit((Object)(object)StartOfRound.Instance) ? StartOfRound.Instance.allPlayerScripts : null);
			if (array != null)
			{
				PlayerControllerB[] array2 = array;
				for (int i = 0; i < array2.Length; i++)
				{
					if (Inside(array2[i]))
					{
						return true;
					}
				}
			}
			return false;
		}

		private void LateUpdate()
		{
			//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_00ae: 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_00b5: 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_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_0185: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Unknown result type (might be due to invalid IL or missing references)
			//IL_01db: Unknown result type (might be due to invalid IL or missing references)
			if (!Active || !effectActive)
			{
				return;
			}
			blackout.Apply();
			PlayerControllerB local = Local;
			if (!Inside(local))
			{
				sampled = false;
				return;
			}
			if (!Lethal)
			{
				sampled = false;
				voice.Disarm();
				return;
			}
			if (!detectionArmed)
			{
				detectionArmed = true;
				sampled = false;
				voice.Arm();
			}
			bool flag = detectionVoice && voice.Violates(voiceThreshold);
			Vector3 position = ((Component)local).transform.position;
			Vector3 val = ((sampled && (Object)(object)sampledPlayer == (Object)(object)local) ? (position - previousPosition) : Vector3.zero);
			float magnitude;
			if (!local.isClimbingLadder)
			{
				Vector2 val2 = new Vector2(val.x, val.z);
				magnitude = ((Vector2)(ref val2)).magnitude;
			}
			else
			{
				magnitude = ((Vector3)(ref val)).magnitude;
			}
			float num = magnitude;
			int num3;
			if (!local.isFallingFromJump)
			{
				object obj = JumpingField?.GetValue(local);
				bool flag2 = default(bool);
				int num2;
				if (obj is bool)
				{
					flag2 = (bool)obj;
					num2 = 1;
				}
				else
				{
					num2 = 0;
				}
				num3 = num2 & (flag2 ? 1 : 0);
			}
			else
			{
				num3 = 1;
			}
			bool flag3 = (byte)num3 != 0;
			bool flag4 = detectionMovement && ReactionWindow.Movement(!local.disableMoveInput && !local.isTypingChat && ((Vector2)(ref local.moveInputVector)).sqrMagnitude > 0.01f, num, flag3 && Object.op_Implicit((Object)(object)local.thisController) && !local.thisController.isGrounded);
			previousPosition = position;
			sampledPlayer = local;
			sampled = true;
			if (Options.Logs.Value && Time.unscaledTime >= nextLog)
			{
				nextLog = Time.unscaledTime + 0.5f;
				Log($"Detection: input={local.moveInputVector}, displacement={num:F5}, VAD={voice.IsVoiceActive}, mic={voice.Amplitude:F4}, threshold={voiceThreshold:F4}");
			}
			if (flag4 || flag)
			{
				Kill(local, flag4 ? "movement" : "microphone");
			}
		}

		internal void Jump(PlayerControllerB player)
		{
			if (Lethal && detectionMovement && (Object)(object)player == (Object)(object)Local)
			{
				Kill(player, "jump");
			}
		}

		private void Kill(PlayerControllerB player, string reason)
		{
			if (Lethal && !killed && !((Object)(object)player != (Object)(object)Local) && ((NetworkBehaviour)player).IsOwner && Inside(player))
			{
				killed = true;
				SpecialDeath(player, reason);
			}
		}

		private void SpecialDeath(PlayerControllerB player, string reason)
		{
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			if (Object.op_Implicit((Object)(object)player) && !((Object)(object)player != (Object)(object)Local) && ((NetworkBehaviour)player).IsOwner && player.isPlayerControlled && !player.isPlayerDead && !((Object)(object)deathRequestedFor == (Object)(object)player))
			{
				List<GameObject> list = (Object.op_Implicit((Object)(object)StartOfRound.Instance) ? StartOfRound.Instance.playerRagdolls : null);
				if (list == null || list.Count <= 1 || !Object.op_Implicit((Object)(object)list[1]))
				{
					Warn("Head-burst ragdoll (index 1) unavailable; death skipped to avoid a partial vanilla death.");
					return;
				}
				deathRequestedFor = player;
				Log("Special death: " + reason + "; localOwner=true; spawnBody=true; deathAnimation=1; prefab=" + ((Object)list[1]).name + "; single KillPlayer call");
				player.KillPlayer(Vector3.zero, true, (CauseOfDeath)0, 1, Vector3.zero, false);
			}
		}

		private void StartEvent()
		{
			if (Object.op_Implicit((Object)(object)network) && network.IsServer && Options.EnableMod.Value && RoundRunning && HasIndoorPlayer() && !timeline.Pending(Now) && !timeline.BlackedOut(Now))
			{
				float num = Settings.Safe(Options.MinDuration.Value, 8f, 1f, 300f);
				float num2 = Math.Max(num, Settings.Safe(Options.MaxDuration.Value, 12f, 1f, 300f));
				double num3 = Now + 0.75;
				double num4 = num3 + (double)num + random.NextDouble() * (double)(num2 - num);
				double num5 = ReactionWindow.ChooseGrace(Settings.Safe(Options.GraceMin.Value, 1f, 0f, 300f), Settings.Safe(Options.GraceMax.Value, 1.5f, 0f, 300f), num4 - num3, random.NextDouble());
				timeline.Accept(timeline.Revision + 1, scheduled: true, num3, num4, num4 + (double)Settings.Safe(Options.Cooldown.Value, 60f, 0f, 7200f), num3 + num5);
				detectionMovement = Options.Movement.Value;
				detectionVoice = Options.Voice.Value;
				voiceThreshold = Settings.Safe(Options.Threshold.Value, 0.02f, 0.001f, 1f);
				Broadcast();
				Log($"Event scheduled: {num3:F3} -> {num4:F3}; grace ends={timeline.GraceEnd:F3}");
			}
		}

		private void EndEvent(bool reset)
		{
			if (Object.op_Implicit((Object)(object)network) && network.IsServer)
			{
				double now = Now;
				double num = (reset ? now : ((timeline.Scheduled && now >= timeline.End) ? timeline.CooldownEnd : (now + (double)Settings.Safe(Options.Cooldown.Value, 60f, 0f, 7200f))));
				timeline.Accept(timeline.Revision + 1, scheduled: false, now, now, Math.Max(now, num));
				nextCheck = (reset ? (now + (double)Interval) : EventTimeline.ResumeNaturalChecks(now, num));
				Broadcast();
				RestoreEffects();
			}
		}

		private void Broadcast()
		{
			foreach (ulong connectedClientsId in network.ConnectedClientsIds)
			{
				if (connectedClientsId != network.LocalClientId)
				{
					SendState(connectedClientsId);
				}
			}
		}

		private unsafe void SendState(ulong id, bool snapshot = false)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			FastBufferWriter val = default(FastBufferWriter);
			((FastBufferWriter)(ref val))..ctor(64, (Allocator)2, -1);
			try
			{
				int num = 3;
				((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref num, default(ForPrimitives));
				int revision = timeline.Revision;
				((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref revision, default(ForPrimitives));
				bool scheduled = timeline.Scheduled;
				((FastBufferWriter)(ref val)).WriteValueSafe<bool>(ref scheduled, default(ForPrimitives));
				double start = timeline.Start;
				((FastBufferWriter)(ref val)).WriteValueSafe<double>(ref start, default(ForPrimitives));
				start = timeline.GraceEnd;
				((FastBufferWriter)(ref val)).WriteValueSafe<double>(ref start, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe<bool>(ref snapshot, default(ForPrimitives));
				start = timeline.End;
				((FastBufferWriter)(ref val)).WriteValueSafe<double>(ref start, default(ForPrimitives));
				start = timeline.CooldownEnd;
				((FastBufferWriter)(ref val)).WriteValueSafe<double>(ref start, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe<bool>(ref detectionMovement, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe<bool>(ref detectionVoice, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe<float>(ref voiceThreshold, default(ForPrimitives));
				messaging.SendNamedMessage("Silence.v032.state", id, val, (NetworkDelivery)3);
			}
			finally
			{
				((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose();
			}
		}

		private void ReceiveState(ulong sender, FastBufferReader reader)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: 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_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: 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_00b6: 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_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			if (network.IsServer || sender != 0L)
			{
				return;
			}
			try
			{
				int num = default(int);
				((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num, default(ForPrimitives));
				int revision = default(int);
				((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref revision, default(ForPrimitives));
				bool scheduled = default(bool);
				((FastBufferReader)(ref reader)).ReadValueSafe<bool>(ref scheduled, default(ForPrimitives));
				double num2 = default(double);
				((FastBufferReader)(ref reader)).ReadValueSafe<double>(ref num2, default(ForPrimitives));
				double value = default(double);
				((FastBufferReader)(ref reader)).ReadValueSafe<double>(ref value, default(ForPrimitives));
				bool flag = default(bool);
				((FastBufferReader)(ref reader)).ReadValueSafe<bool>(ref flag, default(ForPrimitives));
				double end = default(double);
				((FastBufferReader)(ref reader)).ReadValueSafe<double>(ref end, default(ForPrimitives));
				double cooldownEnd = default(double);
				((FastBufferReader)(ref reader)).ReadValueSafe<double>(ref cooldownEnd, default(ForPrimitives));
				bool flag2 = default(bool);
				((FastBufferReader)(ref reader)).ReadValueSafe<bool>(ref flag2, default(ForPrimitives));
				bool flag3 = default(bool);
				((FastBufferReader)(ref reader)).ReadValueSafe<bool>(ref flag3, default(ForPrimitives));
				float value2 = default(float);
				((FastBufferReader)(ref reader)).ReadValueSafe<float>(ref value2, default(ForPrimitives));
				if (num == 3 && timeline.Accept(revision, scheduled, num2, end, cooldownEnd, value))
				{
					detectionMovement = flag2;
					detectionVoice = flag3;
					voiceThreshold = Settings.Safe(value2, 0.02f, 0.001f, 1f);
					RestoreEffects();
					if (flag && Now >= num2)
					{
						soundedRevision = revision;
					}
				}
			}
			catch (Exception ex)
			{
				Warn("Invalid state message ignored: " + ex.Message);
			}
		}

		private void ReceiveRequest(ulong sender, FastBufferReader reader)
		{
			if (network.IsServer && network.ConnectedClients.ContainsKey(sender))
			{
				SendState(sender, snapshot: true);
			}
		}

		private void Attach(NetworkManager manager)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Expected O, but got Unknown
			Detach();
			network = manager;
			messaging = manager.CustomMessagingManager;
			if (messaging != null)
			{
				messaging.RegisterNamedMessageHandler("Silence.v032.state", new HandleNamedMessageDelegate(ReceiveState));
				messaging.RegisterNamedMessageHandler("Silence.v032.sync", new HandleNamedMessageDelegate(ReceiveRequest));
				if (network.IsServer)
				{
					timeline.Accept(0, scheduled: false, Now, Now, Now);
				}
				nextCheck = Now + (double)Interval;
				nextSync = 0f;
				Log("Network handlers ready.");
			}
		}

		private void RestoreEffects()
		{
			blackout.Restore();
			audio?.Stop();
			powerAudio?.Stop();
			effectActive = false;
			sampled = false;
			killed = false;
			detectionArmed = false;
			reaction.Reset();
			voice.Disarm();
		}

		private void Detach()
		{
			RestoreEffects();
			voice.Bind(null);
			powerAudio?.ResetCache();
			deathRequestedFor = null;
			if (messaging != null)
			{
				messaging.UnregisterNamedMessageHandler("Silence.v032.state");
				messaging.UnregisterNamedMessageHandler("Silence.v032.sync");
			}
			messaging = null;
			network = null;
			timeline = new EventTimeline();
			soundedRevision = -1;
			wasRound = false;
		}

		private void DebugKeys()
		{
			Keyboard current = Keyboard.current;
			if (Options.Hotkeys.Value && current != null)
			{
				if (((ButtonControl)current.f6Key).wasPressedThisFrame && network.IsServer)
				{
					StartEvent();
				}
				if (((ButtonControl)current.f7Key).wasPressedThisFrame && network.IsServer)
				{
					EndEvent(reset: false);
				}
				if (((ButtonControl)current.f8Key).wasPressedThisFrame)
				{
					Log($"State={timeline.Phase(Now)}, pending={timeline.Pending(Now)}, revision={timeline.Revision}, remaining={Math.Max(0.0, timeline.End - Now):F2}, lights={blackout.Count}, audio={audio.Status}, power={powerAudio.Status}");
				}
				if (((ButtonControl)current.f9Key).wasPressedThisFrame && !Active && Inside(Local))
				{
					audio.Play();
				}
				if (((ButtonControl)current.f10Key).wasPressedThisFrame)
				{
					powerAudio.CyclePreview();
				}
				if (((ButtonControl)current.f11Key).wasPressedThisFrame && !Active && Inside(Local))
				{
					powerAudio.Preview();
				}
				if (((ButtonControl)current.f12Key).wasPressedThisFrame && Options.EnableMod.Value && RoundRunning)
				{
					SpecialDeath(Local, "F12 local debug test");
				}
			}
		}

		private void OnDisable()
		{
			if (Options != null)
			{
				Detach();
			}
		}

		private void OnDestroy()
		{
			Detach();
			audio?.Dispose();
			powerAudio?.Dispose();
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}
	}
	[HarmonyPatch(typeof(PlayerControllerB), "Jump_performed")]
	internal static class JumpPatch
	{
		private static void Prefix(bool ___isJumping, out bool __state)
		{
			__state = ___isJumping;
		}

		private static void Postfix(PlayerControllerB __instance, bool ___isJumping, bool __state)
		{
			if (!__state && ___isJumping)
			{
				Plugin.Instance?.Jump(__instance);
			}
		}
	}
	internal enum PowerOffSoundMode
	{
		VanillaBreaker,
		VanillaLightOff,
		VanillaApparatusDisconnect,
		VanillaApparatusRemoval,
		Custom,
		Disabled
	}
	internal sealed class PowerOffAudio
	{
		private readonly Plugin plugin;

		private readonly AudioSource source;

		private readonly Dictionary<PowerOffSoundMode, AudioClip> clips = new Dictionary<PowerOffSoundMode, AudioClip>();

		private readonly Dictionary<PowerOffSoundMode, Object> owners = new Dictionary<PowerOffSoundMode, Object>();

		private readonly Dictionary<PowerOffSoundMode, float> nextSearch = new Dictionary<PowerOffSoundMode, float>();

		private readonly HashSet<PowerOffSoundMode> warned = new HashSet<PowerOffSoundMode>();

		private AudioClip custom;

		private RoundManager round;

		private PowerOffSoundMode preview;

		private string customStatus = "Not loaded (Custom mode loads at startup)";

		internal string Status { get; private set; } = "Waiting for facility";

		internal PowerOffAudio(Plugin plugin)
		{
			this.plugin = plugin;
			source = ((Component)plugin).gameObject.AddComponent<AudioSource>();
			source.playOnAwake = false;
			source.loop = false;
			source.spatialBlend = 0f;
			source.panStereo = 0f;
			source.dopplerLevel = 0f;
			source.bypassReverbZones = true;
			SceneManager.sceneLoaded += OnSceneLoaded;
			SceneManager.sceneUnloaded += OnSceneUnloaded;
		}

		private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			ResetCache();
		}

		private void OnSceneUnloaded(Scene scene)
		{
			ResetCache();
		}

		internal void ResetCache()
		{
			clips.Clear();
			owners.Clear();
			nextSearch.Clear();
			warned.Clear();
			round = null;
			Status = "Waiting for facility";
		}

		internal void Prepare()
		{
			try
			{
				if ((Object)(object)round != (Object)(object)RoundManager.Instance)
				{
					ResetCache();
					round = RoundManager.Instance;
				}
				Resolve(plugin.Options.PowerMode.Value);
			}
			catch (Exception ex)
			{
				WarnOnce(plugin.Options.PowerMode.Value, ex.Message);
			}
		}

		private static AudioClip ReadClip(object owner, string field)
		{
			object? obj = owner.GetType().GetField(field, BindingFlags.Instance | BindingFlags.Public)?.GetValue(owner);
			return (AudioClip)((obj is AudioClip) ? obj : null);
		}

		private AudioClip Resolve(PowerOffSoundMode mode)
		{
			switch (mode)
			{
			case PowerOffSoundMode.Disabled:
				Status = "Disabled";
				return null;
			case PowerOffSoundMode.Custom:
				Status = customStatus;
				return custom;
			default:
			{
				if (clips.TryGetValue(mode, out var value) && Object.op_Implicit((Object)(object)value) && owners.TryGetValue(mode, out var value2) && Object.op_Implicit(value2))
				{
					Status = mode.ToString() + ": " + ((Object)value).name;
					return value;
				}
				if (nextSearch.TryGetValue(mode, out var value3) && Time.unscaledTime < value3)
				{
					return null;
				}
				nextSearch[mode] = Time.unscaledTime + 2f;
				AudioClip val = null;
				Object value4 = null;
				switch (mode)
				{
				case PowerOffSoundMode.VanillaBreaker:
				{
					BreakerBox[] array3 = Object.FindObjectsOfType<BreakerBox>();
					foreach (BreakerBox val4 in array3)
					{
						val = ReadClip(val4, "switchPowerSFX");
						if (Object.op_Implicit((Object)(object)val))
						{
							value4 = (Object)(object)val4;
							break;
						}
					}
					break;
				}
				case PowerOffSoundMode.VanillaApparatusDisconnect:
				case PowerOffSoundMode.VanillaApparatusRemoval:
				{
					LungProp[] array2 = Object.FindObjectsOfType<LungProp>();
					foreach (LungProp val3 in array2)
					{
						val = ReadClip(val3, (mode == PowerOffSoundMode.VanillaApparatusDisconnect) ? "disconnectSFX" : "removeFromMachineSFX");
						if (Object.op_Implicit((Object)(object)val))
						{
							value4 = (Object)(object)val3;
							break;
						}
					}
					break;
				}
				case PowerOffSoundMode.VanillaLightOff:
				{
					AudioClip[] array = Resources.FindObjectsOfTypeAll<AudioClip>();
					foreach (AudioClip val2 in array)
					{
						if (Object.op_Implicit((Object)(object)val2) && !(((Object)val2).name != "LightOff"))
						{
							if (Object.op_Implicit((Object)(object)val) && (Object)(object)val != (Object)(object)val2)
							{
								Status = "Ambiguous LightOff clips; skipped";
								return null;
							}
							val = val2;
						}
					}
					value4 = (Object)(object)val;
					break;
				}
				}
				if (Object.op_Implicit((Object)(object)val))
				{
					clips[mode] = val;
					owners[mode] = value4;
					Status = mode.ToString() + ": " + ((Object)val).name;
					if (plugin.Options.Logs.Value)
					{
						plugin.Log("Power sound resolved: " + Status);
					}
				}
				else
				{
					Status = mode.ToString() + ": clip unavailable";
				}
				return val;
			}
			}
		}

		private void WarnOnce(PowerOffSoundMode mode, string reason)
		{
			Status = mode.ToString() + ": " + reason;
			if (warned.Add(mode))
			{
				plugin.Warn("Power-off sound skipped: " + Status + ". Shh and event logic remain enabled.");
			}
		}

		internal void Play()
		{
			Play(plugin.Options.PowerMode.Value);
		}

		private void Play(PowerOffSoundMode mode)
		{
			if (mode == PowerOffSoundMode.Disabled)
			{
				return;
			}
			try
			{
				AudioClip val = Resolve(mode);
				if (!Object.op_Implicit((Object)(object)val))
				{
					WarnOnce(mode, "no valid clip (" + Status + ")");
				}
				else
				{
					source.PlayOneShot(val, Settings.Safe(plugin.Options.PowerVolume.Value, 0.8f, 0f, 1f));
				}
			}
			catch (Exception ex)
			{
				WarnOnce(mode, ex.Message);
			}
		}

		internal void CyclePreview()
		{
			preview = (PowerOffSoundMode)((int)(preview + 1) % 6);
			plugin.Log("F11 power sound preview: " + preview.ToString() + ". This does not change saved settings.");
		}

		internal void Preview()
		{
			Play(preview);
			plugin.Log("Power preview " + preview.ToString() + ": " + Status);
		}

		internal IEnumerator LoadCustom()
		{
			if (plugin.Options.PowerMode.Value != PowerOffSoundMode.Custom)
			{
				yield break;
			}
			string path;
			try
			{
				string fullPath = Path.GetFullPath(Path.GetDirectoryName(((BaseUnityPlugin)plugin).Info.Location));
				string value = plugin.Options.PowerFile.Value;
				if (string.IsNullOrWhiteSpace(value) || Path.IsPathRooted(value))
				{
					throw new IOException("Use a relative filename");
				}
				path = Path.GetFullPath(Path.Combine(fullPath, value));
				if (!path.StartsWith(fullPath + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))
				{
					throw new IOException("Sound must be inside plugin folder");
				}
				if (!File.Exists(path) || new FileInfo(path).Length > 33554432)
				{
					throw new IOException("File missing or larger than 32 MiB");
				}
				string text = Path.GetExtension(path).ToLowerInvariant();
				if (text != ".wav" && text != ".ogg")
				{
					throw new IOException("Use WAV or OGG");
				}
			}
			catch (Exception ex)
			{
				customStatus = ex.Message;
				WarnOnce(PowerOffSoundMode.Custom, ex.Message);
				yield break;
			}
			UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(new Uri(path).AbsoluteUri, (AudioType)(Path.GetExtension(path).Equals(".ogg", StringComparison.OrdinalIgnoreCase) ? 14 : 20));
			try
			{
				yield return request.SendWebRequest();
				try
				{
					if ((int)request.result != 1)
					{
						throw new IOException(request.error);
					}
					custom = DownloadHandlerAudioClip.GetContent(request);
					if (!Object.op_Implicit((Object)(object)custom) || custom.samples == 0 || custom.length > 30f)
					{
						throw new IOException("Invalid clip or longer than 30 seconds");
					}
					customStatus = "Custom ready: " + Path.GetFileName(path);
				}
				catch (Exception ex2)
				{
					if (Object.op_Implicit((Object)(object)custom))
					{
						Object.Destroy((Object)(object)custom);
					}
					custom = null;
					customStatus = ex2.Message;
					WarnOnce(PowerOffSoundMode.Custom, ex2.Message);
				}
			}
			finally
			{
				((IDisposable)request)?.Dispose();
			}
		}

		internal void Stop()
		{
			if (Object.op_Implicit((Object)(object)source))
			{
				source.Stop();
			}
		}

		internal void Dispose()
		{
			SceneManager.sceneLoaded -= OnSceneLoaded;
			SceneManager.sceneUnloaded -= OnSceneUnloaded;
			Stop();
			ResetCache();
			if (Object.op_Implicit((Object)(object)custom))
			{
				Object.Destroy((Object)(object)custom);
			}
			if (Object.op_Implicit((Object)(object)source))
			{
				Object.Destroy((Object)(object)source);
			}
		}
	}
	internal sealed class ReactionWindow
	{
		private double deadline = double.PositiveInfinity;

		internal static double ChooseGrace(double min, double max, double duration, double random)
		{
			return Math.Min(duration, min + random * (Math.Max(min, max) - min));
		}

		internal void Begin(double renderedAt, double start, double graceEnd, double end)
		{
			deadline = Math.Min(end, Math.Max(graceEnd, renderedAt + Math.Min(1.0, graceEnd - start)));
		}

		internal bool Lethal(double now, double end)
		{
			if (now >= deadline)
			{
				return now < end;
			}
			return false;
		}

		internal void Reset()
		{
			deadline = double.PositiveInfinity;
		}

		internal static bool Movement(bool input, float distance, bool airborneJump)
		{
			if (!airborneJump)
			{
				if (input)
				{
					return distance > 0.0005f;
				}
				return false;
			}
			return true;
		}
	}
	internal sealed class FreshVoiceFrame
	{
		private double armedAt = double.PositiveInfinity;

		private double frameStart;

		private double receivedAt;

		private float amplitude;

		private bool pending;

		internal void Arm(double now)
		{
			armedAt = now;
			pending = false;
		}

		internal void Disarm()
		{
			armedAt = double.PositiveInfinity;
			pending = false;
		}

		internal void Receive(double now, double duration, double latency, float value)
		{
			frameStart = now - duration - latency;
			receivedAt = now;
			amplitude = value;
			pending = true;
		}

		internal bool Consume(double now, bool vad, float threshold)
		{
			bool result = pending && frameStart >= armedAt && now >= receivedAt && now - receivedAt <= 0.25 && vad && !float.IsNaN(amplitude) && !float.IsInfinity(amplitude) && amplitude >= threshold;
			pending = false;
			return result;
		}
	}
	internal sealed class Settings
	{
		internal readonly ConfigEntry<bool> EnableMod;

		internal readonly ConfigEntry<bool> Natural;

		internal readonly ConfigEntry<bool> Movement;

		internal readonly ConfigEntry<bool> Voice;

		internal readonly ConfigEntry<bool> Sound;

		internal readonly ConfigEntry<bool> Logs;

		internal readonly ConfigEntry<bool> Hotkeys;

		internal readonly ConfigEntry<float> Chance;

		internal readonly ConfigEntry<float> Interval;

		internal readonly ConfigEntry<float> MinDuration;

		internal readonly ConfigEntry<float> MaxDuration;

		internal readonly ConfigEntry<float> Cooldown;

		internal readonly ConfigEntry<float> Threshold;

		internal readonly ConfigEntry<float> Volume;

		internal readonly ConfigEntry<float> GraceMin;

		internal readonly ConfigEntry<float> GraceMax;

		internal readonly ConfigEntry<string> SoundFile;

		internal readonly ConfigEntry<PowerOffSoundMode> PowerMode;

		internal readonly ConfigEntry<float> PowerVolume;

		internal readonly ConfigEntry<string> PowerFile;

		internal Settings(ConfigFile c)
		{
			EnableMod = c.Bind<bool>("General", "EnableMod", true, "Enable Silence. All participants must install the mod.");
			Natural = c.Bind<bool>("General", "EnableNaturalSpawning", true, "Host: random events.");
			Chance = Number(c, "Event", "EventChance", 0.03f, 0f, 1f, "Independent probability per host check: 0.03 = 3%.");
			Interval = Number(c, "Event", "CheckInterval", 25f, 1f, 3600f, "Seconds between checks; first post-event check at cooldown expiry.");
			MinDuration = Number(c, "Event", "MinimumDuration", 8f, 1f, 300f, "Minimum seconds.");
			MaxDuration = Number(c, "Event", "MaximumDuration", 12f, 1f, 300f, "Maximum seconds; at least MinimumDuration.");
			Cooldown = Number(c, "Event", "CooldownSeconds", 60f, 0f, 7200f, "No natural checks during these seconds after event ends.");
			GraceMin = Number(c, "Event", "GracePeriodMin", 1f, 0f, 300f, "Host: minimum reaction seconds, included in blackout duration.");
			GraceMax = Number(c, "Event", "GracePeriodMax", 1.5f, 0f, 300f, "Host: maximum reaction seconds; at least GracePeriodMin, capped to event duration.");
			Movement = c.Bind<bool>("Detection", "DetectMovement", true, "Host: punish voluntary movement.");
			Voice = c.Bind<bool>("Detection", "DetectVoice", true, "Host: punish local microphone voice activity.");
			Threshold = Number(c, "Detection", "VoiceThreshold", 0.02f, 0.001f, 1f, "Local microphone amplitude plus Dissonance VAD; host setting.");
			Sound = c.Bind<bool>("Audio", "EnableShhSound", true, "Play local 2D sound at blackout.");
			Volume = Number(c, "Audio", "ShhSoundVolume", 0.8f, 0f, 1f, "Local sound volume.");
			SoundFile = c.Bind<string>("Audio", "ShhSoundFile", "silence_shh.wav", "WAV or OGG filename relative to Silence.dll; restart to reload.");
			PowerMode = c.Bind<PowerOffSoundMode>("Audio", "PowerOffSoundMode", PowerOffSoundMode.VanillaBreaker, "Blackout sound. Existing saved choices are preserved.");
			PowerVolume = Number(c, "Audio", "PowerOffSoundVolume", 0.8f, 0f, 1f, "Blackout sound volume, independent of the shh sound.");
			PowerFile = c.Bind<string>("Audio", "PowerOffSoundFile", "silence_poweroff.wav", "Custom mode only: WAV/OGG relative to Silence.dll; restart to reload.");
			Logs = c.Bind<bool>("Debug", "EnableDebugLogs", false, "Detection samples and event logging.");
			Hotkeys = c.Bind<bool>("Debug", "EnableDebugHotkeys", false, "F6 start (host), F7 stop (host), F8 status, F9 shh preview, F10 cycle power preview, F11 power preview, F12 kill LOCAL player with head-burst corpse (landed round only).");
		}

		private static ConfigEntry<float> Number(ConfigFile c, string section, string key, float value, float min, float max, string help)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			return c.Bind<float>(section, key, value, new ConfigDescription(help, (AcceptableValueBase)(object)new AcceptableValueRange<float>(min, max), Array.Empty<object>()));
		}

		internal static float Safe(float value, float fallback, float min, float max)
		{
			if (!float.IsNaN(value) && !float.IsInfinity(value))
			{
				return Math.Max(min, Math.Min(max, value));
			}
			return fallback;
		}
	}
	internal sealed class VoiceMonitor : IVoiceActivationListener, IMicrophoneSubscriber
	{
		private DissonanceComms comms;

		private IMicrophoneCapture capture;

		private volatile bool active;

		private readonly object sync = new object();

		private readonly FreshVoiceFrame frames = new FreshVoiceFrame();

		private double latency;

		private static double Clock => (double)Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;

		internal float Amplitude { get; private set; }

		internal bool IsVoiceActive => active;

		public void VoiceActivationStart()
		{
			active = true;
		}

		public void VoiceActivationStop()
		{
			active = false;
		}

		internal void Bind(DissonanceComms next)
		{
			if ((Object)(object)comms != (Object)(object)next)
			{
				if (Object.op_Implicit((Object)(object)comms))
				{
					comms.UnsubscribeFromVoiceActivation((IVoiceActivationListener)(object)this);
				}
				comms = next;
				active = false;
				if (Object.op_Implicit((Object)(object)comms))
				{
					comms.SubcribeToVoiceActivation((IVoiceActivationListener)(object)this);
				}
			}
			IMicrophoneCapture val = (Object.op_Implicit((Object)(object)comms) ? comms.MicrophoneCapture : null);
			if (capture != val)
			{
				IMicrophoneCapture obj = capture;
				if (obj != null)
				{
					obj.Unsubscribe((IMicrophoneSubscriber)(object)this);
				}
				Disarm();
				capture = val;
				IMicrophoneCapture obj2 = capture;
				if (obj2 != null)
				{
					obj2.Subscribe((IMicrophoneSubscriber)(object)this);
				}
			}
			lock (sync)
			{
				latency = ((capture == null) ? 0.0 : Math.Max(0.0, capture.Latency.TotalSeconds));
			}
		}

		internal void Arm()
		{
			lock (sync)
			{
				frames.Arm(Clock);
			}
		}

		internal void Disarm()
		{
			lock (sync)
			{
				frames.Disarm();
				Amplitude = 0f;
			}
		}

		public void Reset()
		{
			lock (sync)
			{
				frames.Arm(Clock);
				Amplitude = 0f;
			}
		}

		public void ReceiveMicrophoneData(ArraySegment<float> data, WaveFormat format)
		{
			if (data.Array == null || data.Count == 0 || format.SampleRate <= 0 || format.Channels <= 0)
			{
				return;
			}
			double num = 0.0;
			for (int i = data.Offset; i < data.Offset + data.Count; i++)
			{
				num += (double)Math.Abs(data.Array[i]);
			}
			lock (sync)
			{
				Amplitude = (float)(num / (double)data.Count);
				frames.Receive(Clock, (double)data.Count / (double)format.SampleRate / (double)format.Channels, latency, Amplitude);
			}
		}

		internal bool Violates(float threshold)
		{
			if (!Object.op_Implicit((Object)(object)comms) || !((Behaviour)comms).isActiveAndEnabled || capture == null || !capture.IsRecording || string.IsNullOrEmpty(comms.LocalPlayerName))
			{
				return false;
			}
			VoicePlayerState val = comms.FindPlayer(comms.LocalPlayerName);
			if (val == null || !val.IsLocalPlayer)
			{
				return false;
			}
			lock (sync)
			{
				return frames.Consume(Clock, active, threshold);
			}
		}
	}
}