Decompiled source of CymruJester v1.0.0

BepInEx/plugins/CymruJester.dll

Decompiled 3 hours ago
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.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.");
				}
			}
		}
	}
}