using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Logging; using GameNetcodeStuff; using LethalNetworkAPI; using PeashooterSoundboardremake.Audio; using PeashooterSoundboardremake.Runtime; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("PeashooterSoundboardremake")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("PeashooterSoundboardremake")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("9d878c88-8e69-45ad-80f3-897a0d8733f4")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.0.0")] namespace PeashooterSoundboardremake { [BepInPlugin("Che3Bee-PeashooterSoundboard", "PeashooterSoundboard", "2.0.0")] public class SoundboardMod : BaseUnityPlugin { public struct SoundEventData { public string group; public int index; public Vector3 position; public bool deadAudio; } private Dictionary> groups; private CooldownController cooldown; private SoundPlayback playback; private static LNetworkMessage soundMessage; private void Awake() { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Initializing Peashooter Soundboard API"); cooldown = new CooldownController(); playback = new SoundPlayback(((Component)this).gameObject); groups = SoundGroupBuilder.BuildAll(((BaseUnityPlugin)this).Logger); RegisterNetworkMessage(); } private void RegisterNetworkMessage() { soundMessage = LNetworkMessage.Connect("CheePeashooterSoundboard", (Action)null, (Action)null, (Action)OnClientReceivedFromClient); } private void OnClientReceivedFromClient(SoundEventData data, ulong senderId) { playback.PlayReceived(groups, data); } private void Update() { cooldown.Tick(); Keyboard current = Keyboard.current; if (current != null) { if (((ButtonControl)current.numpad7Key).wasPressedThisFrame) { TryPlay("ps_idle"); } if (((ButtonControl)current.numpad8Key).wasPressedThisFrame) { TryPlay("ps_excited"); } if (((ButtonControl)current.numpad9Key).wasPressedThisFrame) { TryPlay("ps_taunt"); } if (((ButtonControl)current.numpad4Key).wasPressedThisFrame) { TryPlay("pc_excited"); } if (((ButtonControl)current.numpad5Key).wasPressedThisFrame) { TryPlay("pc_death"); } if (((ButtonControl)current.numpad6Key).wasPressedThisFrame) { TryPlay("ps_yes"); } if (((ButtonControl)current.numpad3Key).wasPressedThisFrame) { TryPlay("ps_no"); } if (((ButtonControl)current.enterKey).wasPressedThisFrame) { TryPlay("ps_silly"); } if (((ButtonControl)current.rightCtrlKey).isPressed && ((ButtonControl)current.numpad0Key).wasPressedThisFrame) { TryPlay("scream"); } } } private void TryPlay(string group) { //IL_0086: 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_00bb: Unknown result type (might be due to invalid IL or missing references) if (cooldown.CanPlay(group)) { PlayerControllerB localPlayerController = StartOfRound.Instance.localPlayerController; if (!((Object)(object)localPlayerController == (Object)null) && groups.ContainsKey(group) && groups[group].Count != 0) { int index = Random.Range(0, groups[group].Count); AudioClip clip = groups[group][index]; bool isPlayerDead = localPlayerController.isPlayerDead; playback.PlayLocal(clip, group, ((Component)localPlayerController).transform.position, isPlayerDead); soundMessage.SendOtherClients(new SoundEventData { group = group, index = index, position = ((Component)localPlayerController).transform.position, deadAudio = isPlayerDead }); cooldown.OnPlay(group); } } } } } namespace PeashooterSoundboardremake.Runtime { public class CooldownController { private float lastPlay; private float dynamicCooldown = 1.6f; private const float baseCooldown = 1.6f; private const float maxCooldown = 4f; private const float recoveryRate = 0.5f; public void Tick() { if (dynamicCooldown > 1.6f) { dynamicCooldown -= Time.deltaTime * 0.5f; if (dynamicCooldown < 1.6f) { dynamicCooldown = 1.6f; } } } private float GetCooldownFor(string group) { if (SoundGroupRules.Rules.TryGetValue(group, out var value) && value.CooldownOverride > 0f) { return value.CooldownOverride; } return dynamicCooldown; } public bool CanPlay(string group) { float cooldownFor = GetCooldownFor(group); return Time.time - lastPlay >= cooldownFor; } public void OnPlay(string group) { lastPlay = Time.time; if (!SoundGroupRules.Rules.TryGetValue(group, out var value) || !value.IgnoreSpamPenalty) { dynamicCooldown = Mathf.Min(dynamicCooldown + 0.25f, 4f); } } } public class SoundPlayback { private readonly AudioSource deadSource; public SoundPlayback(GameObject host) { deadSource = host.AddComponent(); deadSource.spatialBlend = 0f; } private float ApplyVolume(string group, float baseVolume) { if (SoundGroupRules.Rules.TryGetValue(group, out var value)) { return baseVolume * value.VolumeMultiplier; } return baseVolume; } private float ApplyRadius(string group, float baseRadius) { if (SoundGroupRules.Rules.TryGetValue(group, out var value)) { return baseRadius * value.RadiusMultiplier; } return baseRadius; } public void PlayLocal(AudioClip clip, string group, Vector3 pos, bool dead) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) float num = ApplyVolume(group, 1f); if (dead) { deadSource.PlayOneShot(clip, num); } else { PlayWorld(clip, pos, group, num); } } public void PlayReceived(Dictionary> groups, SoundboardMod.SoundEventData data) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) if (groups.ContainsKey(data.group)) { AudioClip val = groups[data.group][data.index]; float num = ApplyVolume(data.group, 1f); if (data.deadAudio) { deadSource.PlayOneShot(val, num); } else { PlayWorld(val, data.position, data.group, num); } } } private void PlayWorld(AudioClip clip, Vector3 pos, string group, float volume) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown GameObject val = new GameObject("Sound_" + ((Object)clip).name); val.transform.position = pos; AudioSource obj = val.AddComponent(); obj.spatialBlend = 1f; obj.minDistance = 2f; obj.maxDistance = 25f * ApplyRadius(group, 1f); obj.PlayOneShot(clip, volume); Object.Destroy((Object)val, clip.length + 0.1f); } } } namespace PeashooterSoundboardremake.Audio { public class EmbeddedAudioLoader { private readonly ManualLogSource _logger; private readonly Assembly _assembly; private readonly string[] _resources; public EmbeddedAudioLoader(ManualLogSource logger) { _logger = logger; _assembly = Assembly.GetExecutingAssembly(); _resources = _assembly.GetManifestResourceNames(); } public void LoadGroup(Dictionary> groups, string groupName, string prefix) { List list = new List(); string[] resources = _resources; foreach (string text in resources) { if (text.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) && text.EndsWith(".wav", StringComparison.OrdinalIgnoreCase)) { AudioClip val = LoadWav(text); if ((Object)(object)val != (Object)null) { list.Add(val); } } } groups[groupName] = list; _logger.LogInfo((object)$"Loaded {list.Count} embedded clips for '{groupName}'"); } public void AddSpecific(List list, string resourceName) { if (!_resources.Contains(resourceName)) { _logger.LogWarning((object)("Missing embedded clip: " + resourceName)); return; } AudioClip val = LoadWav(resourceName); if ((Object)(object)val != (Object)null) { list.Add(val); } } private AudioClip LoadWav(string resourceName) { try { using Stream stream = _assembly.GetManifestResourceStream(resourceName); if (stream == null) { _logger.LogError((object)("Null stream for: " + resourceName)); return null; } byte[] array = new byte[stream.Length]; stream.Read(array, 0, array.Length); string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(resourceName); return WavUtility.ToAudioClip(array, fileNameWithoutExtension); } catch (Exception arg) { _logger.LogError((object)$"Failed to load WAV '{resourceName}': {arg}"); return null; } } } public static class SoundGroupBuilder { public static Dictionary> BuildAll(ManualLogSource logger) { EmbeddedAudioLoader embeddedAudioLoader = new EmbeddedAudioLoader(logger); Dictionary> dictionary = new Dictionary>(); string text = "PeashooterSoundboardremake.EmbeddedAudio"; embeddedAudioLoader.LoadGroup(dictionary, "ps_idle", text + ".ps_idle"); embeddedAudioLoader.LoadGroup(dictionary, "ps_excited", text + ".ps_excited"); embeddedAudioLoader.LoadGroup(dictionary, "ps_taunt", text + ".ps_taunt"); embeddedAudioLoader.LoadGroup(dictionary, "pc_excited", text + ".pc_excited"); embeddedAudioLoader.LoadGroup(dictionary, "pc_death", text + ".pc_death"); embeddedAudioLoader.LoadGroup(dictionary, "scream", text + ".scream"); BuildCustomGroups(dictionary, embeddedAudioLoader, text); return dictionary; } private static void BuildCustomGroups(Dictionary> groups, EmbeddedAudioLoader loader, string root) { groups["ps_yes"] = new List(); loader.AddSpecific(groups["ps_yes"], root + ".ps_taunt.ps_taunt_25.wav"); loader.AddSpecific(groups["ps_yes"], root + ".ps_taunt.ps_taunt_42.wav"); loader.AddSpecific(groups["ps_yes"], root + ".ps_taunt.ps_taunt_37.wav"); groups["ps_no"] = new List(); loader.AddSpecific(groups["ps_no"], root + ".ps_taunt.ps_taunt_23.wav"); loader.AddSpecific(groups["ps_no"], root + ".ps_taunt.ps_taunt_24.wav"); groups["ps_silly"] = new List(); loader.AddSpecific(groups["ps_silly"], root + ".ps_taunt.ps_taunt_11.wav"); loader.AddSpecific(groups["ps_silly"], root + ".ps_taunt.ps_taunt_15.wav"); loader.AddSpecific(groups["ps_silly"], root + ".ps_taunt.ps_taunt_16.wav"); } } public static class WavUtility { public static AudioClip ToAudioClip(byte[] wav, string name) { int num = BitConverter.ToInt16(wav, 22); int num2 = BitConverter.ToInt32(wav, 24); int num3 = FindDataChunk(wav); int num4 = BitConverter.ToInt32(wav, num3 + 4) / 2; float[] array = new float[num4]; int num5 = num3 + 8; for (int i = 0; i < num4; i++) { short num6 = BitConverter.ToInt16(wav, num5 + i * 2); array[i] = (float)num6 / 32768f; } AudioClip obj = AudioClip.Create(name, num4 / num, num, num2, false); obj.SetData(array, 0); return obj; } private static int FindDataChunk(byte[] wav) { for (int i = 12; i < wav.Length - 8; i++) { if (wav[i] == 100 && wav[i + 1] == 97 && wav[i + 2] == 116 && wav[i + 3] == 97) { return i; } } throw new Exception("WAV missing data chunk"); } } public static class SoundGroupRules { public class Rule { public float VolumeMultiplier = 1f; public float RadiusMultiplier = 1f; public float CooldownOverride = -1f; public bool IgnoreSpamPenalty; } public static readonly Dictionary Rules = new Dictionary { { "scream", new Rule { VolumeMultiplier = 1.5f, RadiusMultiplier = 2.5f, CooldownOverride = 20f, IgnoreSpamPenalty = true } }, { "ps_excited", new Rule { VolumeMultiplier = 0.85f, RadiusMultiplier = 1.35f } } }; } }