Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Silence v0.3.2
BepInEx/plugins/Silence/Silence.dll
Decompiled 2 hours agousing 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); } } } }