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 CymruJester v1.0.0
BepInEx/plugins/CymruJester.dll
Decompiled 3 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using CymruJester.Patches; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using UnityEngine; using UnityEngine.Networking; [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("CymruJester")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("CymruJester")] [assembly: AssemblyTitle("CymruJester")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] [module: UnverifiableCode] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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 CymruJester { [BepInPlugin("Cymru.CymruJester", "CymruJester", "1.1.0")] public class Plugin : BaseUnityPlugin { internal AudioClip[] loadedWindup = Array.Empty<AudioClip>(); internal AudioClip[] loadedScreaming = Array.Empty<AudioClip>(); internal AudioClip[] loadedPopUp = Array.Empty<AudioClip>(); internal float[] loadedPopTime = Array.Empty<float>(); private string[] screamingClips = Array.Empty<string>(); private string[] windupClips = Array.Empty<string>(); private string[] popUpClips = Array.Empty<string>(); private readonly SemaphoreSlim clipLoadSemaphore = new SemaphoreSlim(1, 1); private readonly object clipLoadLock = new object(); private CancellationTokenSource clipLoadCts = new CancellationTokenSource(); internal Random rand; internal static ManualLogSource LoggerInstance; private Harmony harmony = new Harmony("Cymru.CymruJester"); internal int TotalSets => loadedWindup.Length; public static Config Config { get; internal set; } internal static Plugin Instance { get; private set; } internal List<LinkedSongs> LinkedSongs { get; set; } = new List<LinkedSongs>(); internal string ExecutingPath => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); private void Awake() { Config = new Config(((BaseUnityPlugin)this).Config); rand = new Random(0); Instance = this; LoggerInstance = ((BaseUnityPlugin)this).Logger; ((BaseUnityPlugin)this).Logger.LogMessage((object)"Loading audio files"); if (Directory.Exists(ExecutingPath + "/Screaming")) { screamingClips = (from f in Directory.GetFiles(ExecutingPath + "/Screaming") orderby f select f).ToArray(); } if (Directory.Exists(ExecutingPath + "/Windup")) { windupClips = (from f in Directory.GetFiles(ExecutingPath + "/Windup") orderby f select f).ToArray(); } if (Directory.Exists(ExecutingPath + "/Popup")) { popUpClips = (from f in Directory.GetFiles(ExecutingPath + "/Popup") orderby f select f).ToArray(); } GetLinkedSongs(); QueueLoadAllSets(); ((BaseUnityPlugin)this).Logger.LogMessage((object)"Patching"); harmony.PatchAll(typeof(JesterAIPatch)); harmony.PatchAll(typeof(RoundManagerPatch)); harmony.PatchAll(typeof(Plugin)); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Plugin CymruJester is loaded!"); } public int PickNextSet(int lastIndex) { int totalSets = TotalSets; if (totalSets <= 0) { return -1; } if (totalSets == 1) { return 0; } int num; do { num = rand.Next(0, totalSets); } while (num == lastIndex); return num; } public void QueueLoadAllSets() { LoadAllSetsSafeAsync(); } private async Task LoadAllSetsSafeAsync() { CancellationTokenSource cancellationTokenSource; CancellationTokenSource newCts; lock (clipLoadLock) { cancellationTokenSource = clipLoadCts; newCts = (clipLoadCts = new CancellationTokenSource()); } cancellationTokenSource.Cancel(); cancellationTokenSource.Dispose(); await clipLoadSemaphore.WaitAsync(); try { await LoadAllSets(newCts.Token); } catch (OperationCanceledException) { ((BaseUnityPlugin)this).Logger.LogDebug((object)"Clip load canceled due to a newer load request"); } catch (Exception ex2) { ((BaseUnityPlugin)this).Logger.LogError((object)("Unexpected clip loading error: " + ex2)); } finally { clipLoadSemaphore.Release(); } } private async Task LoadAllSets(CancellationToken ct) { int linkedCount = LinkedSongs.Count; bool flag = Config.IncludeDefaultWindup.Value || Config.IncludeDefaultScreaming.Value || Config.IncludeDefaultPopUp.Value; int vanillaSlots = (flag ? 1 : 0); int totalSlots = vanillaSlots + linkedCount; if (totalSlots == 0) { totalSlots = 1; } AudioClip[] newWindup = (AudioClip[])(object)new AudioClip[totalSlots]; AudioClip[] newScreaming = (AudioClip[])(object)new AudioClip[totalSlots]; AudioClip[] newPopUp = (AudioClip[])(object)new AudioClip[totalSlots]; float[] newPopTime = new float[totalSlots]; for (int i = 0; i < totalSlots; i++) { newPopTime[i] = -1f; } for (int li = 0; li < linkedCount; li++) { ct.ThrowIfCancellationRequested(); int slot = vanillaSlots + li; LinkedSongs entry = LinkedSongs[li]; if (!string.IsNullOrWhiteSpace(entry.WindupName)) { string text = FindFilePath(windupClips, entry.WindupName); if (text != null) { AudioClip[] array = newWindup; int num = slot; array[num] = await LoadClip(text, ct); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Windup file not found: " + entry.WindupName)); } } if (!string.IsNullOrWhiteSpace(entry.ScreamingName)) { string text2 = FindFilePath(screamingClips, entry.ScreamingName); if (text2 != null) { AudioClip[] array = newScreaming; int num = slot; array[num] = await LoadClip(text2, ct); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Screaming file not found: " + entry.ScreamingName)); } } if (!string.IsNullOrWhiteSpace(entry.PopupName)) { string text3 = FindFilePath(popUpClips, entry.PopupName); if (text3 != null) { AudioClip[] array = newPopUp; int num = slot; array[num] = await LoadClip(text3, ct); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Popup file not found: " + entry.PopupName)); } } if (entry.PopTime.HasValue && entry.PopTime.Value > 0f) { newPopTime[slot] = entry.PopTime.Value; } } loadedWindup = newWindup; loadedScreaming = newScreaming; loadedPopUp = newPopUp; loadedPopTime = newPopTime; ((BaseUnityPlugin)this).Logger.LogMessage((object)$"All song sets loaded. Total selectable sets: {totalSlots}"); } private string FindFilePath(string[] clips, string name) { foreach (string text in clips) { if (Path.GetFileName(text).Equals(name, StringComparison.OrdinalIgnoreCase)) { return text; } } return null; } public void GetLinkedSongs() { try { if (!File.Exists(ExecutingPath + "/linkedSongs.json")) { ((BaseUnityPlugin)this).Logger.LogMessage((object)"No linkedSongs.json file found"); return; } string text = File.ReadAllText(ExecutingPath + "/linkedSongs.json"); LinkedSongs = JsonConvert.DeserializeObject<List<LinkedSongs>>(text); if (LinkedSongs == null) { LinkedSongs = new List<LinkedSongs>(); } ((BaseUnityPlugin)this).Logger.LogMessage((object)$"Loaded {LinkedSongs.Count} linked song entries"); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)ex.Message); } } public AudioType GetAudioType(string extension) { string text = extension.ToLowerInvariant(); int num = ((text == ".mp3") ? 13 : ((!(text == ".wav")) ? 14 : 20)); return (AudioType)num; } public async Task<AudioClip> LoadClip(string path, CancellationToken cancellationToken) { AudioType audioType = GetAudioType(Path.GetExtension(path)); string text = (Path.IsPathRooted(path) ? new Uri(path).AbsoluteUri : path); UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(text, audioType); request.timeout = 15; try { request.SendWebRequest(); while (!request.isDone) { cancellationToken.ThrowIfCancellationRequested(); await Task.Delay(50); } if ((int)request.result != 1) { ((BaseUnityPlugin)this).Logger.LogError((object)("Failed to load file: " + path + ", " + request.error)); return null; } AudioClip content = DownloadHandlerAudioClip.GetContent(request); ((BaseUnityPlugin)this).Logger.LogMessage((object)("Loaded file: " + path)); return content; } catch (OperationCanceledException) { request.Abort(); throw; } finally { if (request != null) { request.Dispose(); } } } } public class Config { public static ConfigEntry<bool> IncludeDefaultWindup; public static ConfigEntry<bool> IncludeDefaultScreaming; public static ConfigEntry<bool> IncludeDefaultPopUp; public Config(ConfigFile config) { IncludeDefaultWindup = config.Bind<bool>("General", "IncludeDefaultWindup", true, "Allows the randomizer to pick the game's windup sound"); IncludeDefaultScreaming = config.Bind<bool>("General", "IncludeDefaultScreaming", true, "Allows the randomizer to pick the game's screaming sound"); IncludeDefaultPopUp = config.Bind<bool>("General", "IncludeDefaultPopUp", true, "Allows the randomizer to pick the game's popup sound"); } } public struct LinkedSongs { public string WindupName { get; set; } public string ScreamingName { get; set; } public string PopupName { get; set; } public float? PopTime { get; set; } } public static class PluginInfo { public const string PLUGIN_GUID = "Cymru.CymruJester"; public const string PLUGIN_NAME = "CymruJester"; public const string PLUGIN_VERSION = "1.1.0"; } } namespace CymruJester.Patches { [HarmonyPatch(typeof(RoundManager))] internal class RoundManagerPatch { [HarmonyPatch("InitializeRandomNumberGenerators")] [HarmonyPostfix] public static void SeedPatch(ref RoundManager __instance) { Plugin.LoggerInstance.LogInfo((object)"Reseeding randomizer and preloading all song sets"); Plugin.LoggerInstance.LogInfo((object)$"Seed: {__instance.playersManager.randomMapSeed}"); Plugin.Instance.rand = new Random(__instance.playersManager.randomMapSeed); Plugin.Instance.QueueLoadAllSets(); } } [HarmonyPatch(typeof(JesterAI))] internal class JesterAIPatch { private static readonly Dictionary<int, int> lastSetByJester = new Dictionary<int, int>(); private static readonly Dictionary<int, int> prevStateByJester = new Dictionary<int, int>(); private static int Key(JesterAI j) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return ((Object)j).GetInstanceID(); } [HarmonyPatch("Start")] [HarmonyPostfix] public static void StartPatch(JesterAI __instance, ref AudioClip ___screamingSFX, ref AudioClip ___popGoesTheWeaselTheme, ref AudioClip ___popUpSFX) { int key = Key(__instance); int num = Plugin.Instance.PickNextSet(-1); lastSetByJester[key] = num; prevStateByJester[key] = ((EnemyAI)__instance).currentBehaviourStateIndex; ApplySet(num, ref ___screamingSFX, ref ___popGoesTheWeaselTheme, ref ___popUpSFX, __instance); } [HarmonyPatch("Update")] [HarmonyPostfix] public static void UpdatePatch(JesterAI __instance, ref AudioClip ___screamingSFX, ref AudioClip ___popGoesTheWeaselTheme, ref AudioClip ___popUpSFX) { int num = Key(__instance); int currentBehaviourStateIndex = ((EnemyAI)__instance).currentBehaviourStateIndex; int value = 0; prevStateByJester.TryGetValue(num, out value); if (value != 1 && currentBehaviourStateIndex == 1) { int value2 = -1; lastSetByJester.TryGetValue(num, out value2); int num2 = Plugin.Instance.PickNextSet(value2); lastSetByJester[num] = num2; ApplySet(num2, ref ___screamingSFX, ref ___popGoesTheWeaselTheme, ref ___popUpSFX, __instance); Plugin.LoggerInstance.LogInfo((object)$"[CymruJester] Jester {num} entered wind-up — playing set {num2}"); } prevStateByJester[num] = currentBehaviourStateIndex; } private static void ApplySet(int setIdx, ref AudioClip screaming, ref AudioClip windup, ref AudioClip popup, JesterAI instance) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown if (setIdx < 0 || setIdx >= Plugin.Instance.TotalSets) { return; } AudioClip val = Plugin.Instance.loadedWindup[setIdx]; AudioClip val2 = Plugin.Instance.loadedScreaming[setIdx]; AudioClip val3 = Plugin.Instance.loadedPopUp[setIdx]; float num = Plugin.Instance.loadedPopTime[setIdx]; if ((Object)val != (Object)null) { windup = val; } if ((Object)val2 != (Object)null) { screaming = val2; } if ((Object)val3 != (Object)null) { popup = val3; } if (num > 0f) { FieldInfo field = typeof(JesterAI).GetField("popUpTimer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { field.SetValue(instance, num); } else { Plugin.LoggerInstance.LogWarning((object)"[CymruJester] Could not find popUpTimer field on JesterAI — PopTime will have no effect."); } } } } }