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 NobleMod v1.1.1
BepInEx/plugins/NobleMod/NobleMod.dll
Decompiled 4 months ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; 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.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using NobleMod.Multiplayer; using NobleMod.Patches; using NobleMod.Replacers; using NobleMod.SoundPack; using Photon.Pun; using Photon.Realtime; using REPOLib.Modules; using UnityEngine; using loaforcsSoundAPI; using loaforcsSoundAPI.Core; using loaforcsSoundAPI.Core.Data; using loaforcsSoundAPI.SoundPacks.Data; using loaforcsSoundAPI.SoundPacks.Data.Conditions; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("Raisery")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("NobleMod")] [assembly: AssemblyTitle("NobleMod")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] 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 NobleMod { internal static class LevelEnemyOverrideBank { private static readonly Dictionary<int, string> MobByLevel = new Dictionary<int, string>(); private static ManualLogSource _logger; private static string _spawnConfigRootPath; public static void Initialize(ManualLogSource logger) { _logger = logger; Reload(); } public static bool TryGetMobKey(int levelNumber, out string mobKey) { if (MobByLevel.TryGetValue(levelNumber, out mobKey) && !string.IsNullOrWhiteSpace(mobKey)) { return true; } mobKey = null; return false; } private static void Reload() { MobByLevel.Clear(); _spawnConfigRootPath = Path.Combine(Plugin.AssemblyDirectory, "SpawnConfig"); Directory.CreateDirectory(_spawnConfigRootPath); string text = ModConfig.SpawnOverridesFileName.Value; if (string.IsNullOrWhiteSpace(text)) { text = "level_enemy_overrides.json"; } string text2 = Path.Combine(_spawnConfigRootPath, text); if (!File.Exists(text2)) { WriteDefaultTemplate(text2); _logger.LogWarning((object)("Missing spawn override file: " + text2 + ". Template created.")); return; } try { foreach (Match item in Regex.Matches(File.ReadAllText(text2, Encoding.UTF8), "(?:\"(?<key1>[^\"]+)\"|(?<key2>\\d+))\\s*:\\s*\"(?<value>[^\"]+)\"", RegexOptions.IgnoreCase)) { string text3 = (item.Groups["key1"].Success ? item.Groups["key1"].Value : item.Groups["key2"].Value); string value = item.Groups["value"].Value.Trim(); if (!string.IsNullOrWhiteSpace(text3) && !string.IsNullOrWhiteSpace(value) && TryParseLevelKey(text3, out var levelNumber) && levelNumber > 0) { MobByLevel[levelNumber] = NormalizeMobKey(value); } } if (ModConfig.LogSpawnOverrides.Value) { _logger.LogInfo((object)$"Loaded {MobByLevel.Count} level mob override(s) from '{text2}'."); } } catch (Exception ex) { _logger.LogError((object)("Failed to parse spawn override file '" + text2 + "': " + ex.Message)); } } private static bool TryParseLevelKey(string rawKey, out int levelNumber) { levelNumber = 0; if (string.IsNullOrWhiteSpace(rawKey)) { return false; } Match match = Regex.Match(rawKey, "\\d+"); if (!match.Success) { return false; } return int.TryParse(match.Value, out levelNumber); } private static string NormalizeMobKey(string value) { return value.Trim().ToLowerInvariant(); } private static void WriteDefaultTemplate(string outputPath) { File.WriteAllText(outputPath, "{\n \"1\": \"huntsman\",\n \"2\": \"headman\"\n}\n", Encoding.UTF8); } } internal static class ModConfig { public static ConfigEntry<bool> EnableSpawnOverrides; public static ConfigEntry<string> SpawnOverridesFileName; public static ConfigEntry<bool> LogSpawnOverrides; public static ConfigEntry<bool> DumpEnemyMobKeysOnce; public static ConfigEntry<bool> EnableNobleModSoundPackConditions; public static ConfigEntry<bool> LogRandomSlotRange; public static ConfigEntry<bool> LogStickyAttachDetach; public static ConfigEntry<bool> LogSoundPickEachMatch; public static ConfigEntry<bool> EnableMultiplayerSoundSync; public static ConfigEntry<int> MultiplayerSoundPoolSize; public static ConfigEntry<int> MultiplayerSoundPoolRefreshIntervalSeconds; public static ConfigEntry<bool> LogMultiplayerSoundSync; public static void Bind(ConfigFile config) { EnableSpawnOverrides = config.Bind<bool>("Spawning", "EnableSpawnOverrides", false, "Active le remplacement du mob principal via un mapping niveau -> mob (fichier JSON dans SpawnConfig)."); SpawnOverridesFileName = config.Bind<string>("Spawning", "SpawnOverridesFileName", "level_enemy_overrides.json", "Nom du fichier JSON contenant le mapping niveau -> identifiant de mob."); LogSpawnOverrides = config.Bind<bool>("Spawning", "LogSpawnOverrides", true, "Log les decisions de remplacement des mobs principaux (selection, fallback, erreurs)."); DumpEnemyMobKeysOnce = config.Bind<bool>("Spawning", "DumpEnemyMobKeysOnce", false, "Debug : au prochain reveil d'EnemyDirector en partie, log une ligne par mob (blob utilise pour matcher le JSON spawn override), puis repasse a false et sauvegarde la config. Utile pour choisir les chaines dans level_enemy_overrides.json."); EnableNobleModSoundPackConditions = config.Bind<bool>("SoundPack", "EnableNobleModSoundPackConditions", true, "Enregistre au demarrage les conditions SoundAPI NobleMod (ex. NobleMod:random_slot dans replacers/*.json). OFF : ne pas utiliser ces conditions dans le JSON (sinon echec chargement pack). Redemarrage jeu pour appliquer."); LogRandomSlotRange = config.Bind<bool>("SoundPack", "LogRandomSlotRange", false, "Debug : pour NobleMod:random_slot en mode plage, log BepInEx « R = … -> son ….ogg » uniquement quand R change (par AudioSource). Peut rester verbeux si R change souvent (ex. boucle)."); LogStickyAttachDetach = config.Bind<bool>("SoundPack", "LogStickyAttachDetach", false, "Debug : pour NobleMod:random_slot avec sticky, log quand le sticky se branche sur l'AudioSource (premier play, nouveau clip) et quand il se débranche (boucle détectée, changement de clip avant re-branchement)."); LogSoundPickEachMatch = config.Bind<bool>("SoundPack", "LogSoundPickEachMatch", false, "Debug : à chaque fois qu'une ligne NobleMod:random_slot matche (condition vraie), log BepInEx le nom du son (fichier .ogg). Active via le menu NobleMod. Si le nom n'est pas lisible, un log indique le type du Parent SoundAPI (reflexion sur proprietes/champs courants). Tres verbeux avec update_every_frame."); EnableMultiplayerSoundSync = config.Bind<bool>("Multiplayer", "EnableMultiplayerSoundSync", true, "Active la coherence multijoueur des tirages aleatoires de sons (conditions NobleMod:random_slot + tirages internes SoundAPI quand plusieurs sons d'un meme groupe sont possibles, ex. headman). Le master diffuse un pool partage via REPOLib NetworkedEvent, chaque client le consulte avec une cle stable (matches du groupe + ViewID + occurrence temporelle). En solo : fallback Random.Range local (comportement actuel)."); MultiplayerSoundPoolSize = config.Bind<int>("Multiplayer", "MultiplayerSoundPoolSize", 256, "Taille du pool partage (nombre d'entrees int). Borne [16..4096]. Plus grand = moins de collisions de cles (sons differents qui tomberaient sur la meme entree). 256 est largement suffisant."); MultiplayerSoundPoolRefreshIntervalSeconds = config.Bind<int>("Multiplayer", "MultiplayerSoundPoolRefreshIntervalSeconds", 300, "Intervalle entre deux regenerations completes du pool par le master, en secondes (0 = jamais apres init / changement de niveau). Defaut 300s = 5 minutes."); LogMultiplayerSoundSync = config.Bind<bool>("Multiplayer", "LogMultiplayerSoundSync", false, "Debug : log VERBEUX par evaluation (ex. seed Random utilisee pour chaque appel SoundAPI). Les events critiques (broadcast pool, reception, transitions room/master/player count) sont TOUJOURS logues quel que soit ce reglage."); } } internal static class NobleModMenuIntegration { private static Assembly _menuLib; private static Type _menuApiType; private static MethodInfo _miCreateButton; private static MethodInfo _miCreateToggle; private static MethodInfo _miCreatePopup; private static Type _presetSideType; private static object _popup; private static object _toggleSpawn; private static object _toggleSoundPackConditions; private static object _toggleLogSoundPickEachMatch; private static readonly List<KeyValuePair<string, object>> _replacerToggles = new List<KeyValuePair<string, object>>(); private static bool _methodsResolved; private static void ClearNoblePopupCache() { _popup = null; _toggleSpawn = null; _toggleSoundPackConditions = null; _toggleLogSoundPickEachMatch = null; _replacerToggles.Clear(); } private static bool IsUnityObjAlive(object u) { Object val = (Object)((u is Object) ? u : null); if (val != null) { return val != (Object)null; } return false; } private static object GetRepoPopupMenuPage(object repoPopupPage) { return repoPopupPage?.GetType().GetField("menuPage", BindingFlags.Instance | BindingFlags.Public)?.GetValue(repoPopupPage); } internal static void TryRegister(ManualLogSource log) { try { _menuLib = Array.Find(AppDomain.CurrentDomain.GetAssemblies(), (Assembly a) => string.Equals(a.GetName().Name, "MenuLib", StringComparison.Ordinal)); if (_menuLib == null) { log.LogWarning((object)"[NobleMod] MenuLib introuvable. Installez la dependance 'MenuLib' (Thunderstore, auteur nickklmao). Le bouton NobleMod dans Parametres ne sera pas ajoute."); return; } _menuApiType = _menuLib.GetType("MenuLib.MenuAPI", throwOnError: false); if (_menuApiType == null) { log.LogError((object)"[NobleMod] Type MenuLib.MenuAPI introuvable."); return; } MethodInfo method = _menuApiType.GetMethod("AddElementToSettingsMenu", BindingFlags.Static | BindingFlags.Public); if (method == null) { log.LogError((object)"[NobleMod] MenuAPI.AddElementToSettingsMenu introuvable."); return; } Type parameterType = method.GetParameters()[0].ParameterType; MethodInfo method2 = typeof(NobleModMenuIntegration).GetMethod("OnSettingsMenuBuilt", BindingFlags.Static | BindingFlags.NonPublic); Delegate @delegate = Delegate.CreateDelegate(parameterType, method2); method.Invoke(null, new object[1] { @delegate }); log.LogInfo((object)"[NobleMod] MenuLib: bouton ajoute au menu Parametres du jeu."); } catch (Exception arg) { log.LogError((object)$"[NobleMod] Integration MenuLib: {arg}"); } } private static void OnSettingsMenuBuilt(Transform parent) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) try { ResolveMenuApiMethods(); Transform val = FindSettingsLeftNavControlsRow(parent); if ((Object)(object)val != (Object)null && (Object)(object)val.parent != (Object)null) { Transform parent2 = val.parent; object? obj = _miCreateButton.Invoke(null, new object[4] { "NOBLEMOD", new Action(OpenNobleModPopup), parent2, Vector2.zero }); PlaceNobleButtonUnderControlsNav((Component)((obj is Component) ? obj : null), val); return; } ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)"[NobleMod] Entree CONTROLS (liste gauche) introuvable; placement repli sur le scroll."); } Transform val2 = FindMenuScrollScroller(parent) ?? parent; Vector2 val3 = ComputeAppendLocalPosition(val2); object? obj2 = _miCreateButton.Invoke(null, new object[4] { "NobleMod — reglages du mod", new Action(OpenNobleModPopup), val2, val3 }); object? obj3 = ((obj2 is Component) ? obj2 : null); if (obj3 != null) { ((Component)obj3).transform.SetAsLastSibling(); } } catch (Exception arg) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogError((object)$"[NobleMod] OnSettingsMenuBuilt: {arg}"); } } } private static Transform FindSettingsLeftNavControlsRow(Transform pageRoot) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) Transform result = null; float num = float.MaxValue; Transform[] componentsInChildren = ((Component)pageRoot).GetComponentsInChildren<Transform>(true); foreach (Transform val in componentsInChildren) { Component val2 = TryGetTmpTextComponent(val); if ((Object)(object)val2 == (Object)null) { continue; } string tmpText = GetTmpText(val2); if (string.IsNullOrEmpty(tmpText)) { continue; } string text = tmpText.Trim(); if (!text.Equals("CONTROLS", StringComparison.OrdinalIgnoreCase) && !text.Equals("CONTRÔLES", StringComparison.OrdinalIgnoreCase)) { continue; } Transform parent = val.parent; if (!((Object)(object)parent == (Object)null)) { float x = parent.position.x; if (x < num) { num = x; result = parent; } } } return result; } private static Component TryGetTmpTextComponent(Transform tr) { Component component = ((Component)tr).GetComponent("TMPro.TextMeshProUGUI"); if ((Object)(object)component != (Object)null) { return component; } return ((Component)tr).GetComponent("TextMeshProUGUI"); } private static string GetTmpText(Component tmp) { return ((object)tmp).GetType().GetProperty("text")?.GetValue(tmp) as string; } private static void PlaceNobleButtonUnderControlsNav(Component btnComponent, Transform controlsRow) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007e: 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_0096: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: 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_011a: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) Transform obj = ((btnComponent != null) ? btnComponent.transform : null); RectTransform val = (RectTransform)(object)((obj is RectTransform) ? obj : null); RectTransform val2 = (RectTransform)(object)((controlsRow is RectTransform) ? controlsRow : null); Transform val3 = ((controlsRow != null) ? controlsRow.parent : null); if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null || (Object)(object)val3 == (Object)null) { return; } int siblingIndex = controlsRow.GetSiblingIndex(); RectTransform val4 = null; if (siblingIndex + 1 < val3.childCount) { Transform child = val3.GetChild(siblingIndex + 1); val4 = (RectTransform)(object)((child is RectTransform) ? child : null); } ((Transform)val).SetSiblingIndex(siblingIndex + 1); val.anchorMin = val2.anchorMin; val.anchorMax = val2.anchorMax; val.pivot = val2.pivot; val.sizeDelta = val2.sizeDelta; float num; if (siblingIndex > 0) { Transform child2 = val3.GetChild(siblingIndex - 1); RectTransform val5 = (RectTransform)(object)((child2 is RectTransform) ? child2 : null); if (val5 != null) { num = ((Transform)val2).localPosition.y - ((Transform)val5).localPosition.y; goto IL_012d; } } num = ((!((Object)(object)val4 != (Object)null)) ? (0f - (((val2.sizeDelta.y > 8f) ? val2.sizeDelta.y : 40f) + 6f)) : ((((Transform)val4).localPosition.y - ((Transform)val2).localPosition.y) * 0.5f)); goto IL_012d; IL_012d: ((Transform)val).localPosition = ((Transform)val2).localPosition + new Vector3(0f, num, 0f); if ((Object)(object)val4 != (Object)null && Mathf.Abs(num) > 0.01f) { float num2 = ((Transform)val).localPosition.y + num; RectTransform val6 = val4; ((Transform)val6).localPosition = new Vector3(((Transform)val6).localPosition.x, num2, ((Transform)val6).localPosition.z); } } private static Vector2 ComputeAppendLocalPosition(Transform content) { //IL_0013: 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) if ((Object)(object)content == (Object)null) { return new Vector2(250f, -40f); } float num = float.MaxValue; bool flag = false; for (int i = 0; i < content.childCount; i++) { Transform child = content.GetChild(i); RectTransform val = (RectTransform)(object)((child is RectTransform) ? child : null); if (val != null && !IsChromeScrollChild(child)) { float localBottomY = GetLocalBottomY(val, content); if (localBottomY < num) { num = localBottomY; flag = true; } } } float num2 = (flag ? (num - 22f) : (-40f)); return new Vector2(250f, num2); } private static bool IsChromeScrollChild(Transform child) { string text = ((Object)child).name ?? string.Empty; if (text.IndexOf("scroll", StringComparison.OrdinalIgnoreCase) >= 0 && text.IndexOf("bar", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } return false; } private static float GetLocalBottomY(RectTransform rt, Transform contentParent) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) Vector3[] array = (Vector3[])(object)new Vector3[4]; rt.GetWorldCorners(array); float num = float.MaxValue; for (int i = 0; i < 4; i++) { Vector3 val = contentParent.InverseTransformPoint(array[i]); if (val.y < num) { num = val.y; } } return num; } private static void ResolveMenuApiMethods() { if (_methodsResolved) { return; } MethodInfo[] methods = _menuApiType.GetMethods(BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo methodInfo in methods) { if (methodInfo.Name == "CreateREPOButton") { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 4 && parameters[0].ParameterType == typeof(string) && parameters[1].ParameterType == typeof(Action) && parameters[2].ParameterType == typeof(Transform) && parameters[3].ParameterType == typeof(Vector2)) { _miCreateButton = methodInfo; } } else if (methodInfo.Name == "CreateREPOToggle") { ParameterInfo[] parameters2 = methodInfo.GetParameters(); if (parameters2.Length == 7 && parameters2[0].ParameterType == typeof(string) && parameters2[1].ParameterType == typeof(Action<bool>) && parameters2[2].ParameterType == typeof(Transform) && parameters2[3].ParameterType == typeof(Vector2) && parameters2[4].ParameterType == typeof(string) && parameters2[5].ParameterType == typeof(string) && parameters2[6].ParameterType == typeof(bool)) { _miCreateToggle = methodInfo; } } else if (methodInfo.Name == "CreateREPOPopupPage") { ParameterInfo[] parameters3 = methodInfo.GetParameters(); if (parameters3.Length >= 5 && parameters3[0].ParameterType == typeof(string) && parameters3[1].ParameterType.IsEnum && parameters3[2].ParameterType == typeof(bool) && parameters3[3].ParameterType == typeof(bool) && parameters3[4].ParameterType == typeof(float)) { _miCreatePopup = methodInfo; _presetSideType = parameters3[1].ParameterType; } } } if (_miCreateButton == null || _miCreateToggle == null || _miCreatePopup == null || _presetSideType == null) { throw new InvalidOperationException("Signatures MenuAPI inattendues (mettre a jour NobleModMenuIntegration)."); } _methodsResolved = true; } private static Transform FindMenuScrollScroller(Transform pageRoot) { MonoBehaviour[] componentsInChildren = ((Component)pageRoot).GetComponentsInChildren<MonoBehaviour>(true); foreach (MonoBehaviour val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && !(((object)val).GetType().Name != "MenuScrollBox")) { object? obj = ((object)val).GetType().GetField("scroller", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(val); Transform val2 = (Transform)((obj is Transform) ? obj : null); if (val2 != null) { return val2; } } } return null; } private static void OpenNobleModPopup() { try { EnsureNoblePopup(); if (!IsUnityObjAlive(_popup)) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogError((object)"[NobleMod] Popup NobleMod introuvable apres EnsureNoblePopup."); } return; } if (IsUnityObjAlive(_toggleSpawn)) { _toggleSpawn.GetType().GetMethod("SetState", new Type[2] { typeof(bool), typeof(bool) })?.Invoke(_toggleSpawn, new object[2] { ModConfig.EnableSpawnOverrides.Value, false }); } if (IsUnityObjAlive(_toggleSoundPackConditions)) { _toggleSoundPackConditions.GetType().GetMethod("SetState", new Type[2] { typeof(bool), typeof(bool) })?.Invoke(_toggleSoundPackConditions, new object[2] { ModConfig.EnableNobleModSoundPackConditions.Value, false }); } if (IsUnityObjAlive(_toggleLogSoundPickEachMatch)) { _toggleLogSoundPickEachMatch.GetType().GetMethod("SetState", new Type[2] { typeof(bool), typeof(bool) })?.Invoke(_toggleLogSoundPickEachMatch, new object[2] { ModConfig.LogSoundPickEachMatch.Value, false }); } foreach (KeyValuePair<string, object> replacerToggle in _replacerToggles) { if (!IsUnityObjAlive(replacerToggle.Value)) { continue; } MethodInfo method = replacerToggle.Value.GetType().GetMethod("SetState", new Type[2] { typeof(bool), typeof(bool) }); if (method == null) { continue; } foreach (ReplacerToggleRegistry.ReplacerEntry item in ReplacerToggleRegistry.SnapshotEntries()) { if (string.Equals(item.FilePath, replacerToggle.Key, StringComparison.OrdinalIgnoreCase)) { method.Invoke(replacerToggle.Value, new object[2] { item.Entry.Value, false }); break; } } } _popup.GetType().GetMethod("OpenPage", new Type[1] { typeof(bool) })?.Invoke(_popup, new object[1] { false }); } catch (Exception arg) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogError((object)$"[NobleMod] OpenNobleModPopup: {arg}"); } } } private static void EnsureNoblePopup() { //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Unknown result type (might be due to invalid IL or missing references) //IL_02da: Unknown result type (might be due to invalid IL or missing references) //IL_03a4: Unknown result type (might be due to invalid IL or missing references) //IL_047e: Unknown result type (might be due to invalid IL or missing references) ReplacerToggleRegistry.DiscoverAlreadyLoadedPacks(); int count = ReplacerToggleRegistry.SnapshotEntries().Count; if (IsUnityObjAlive(_popup) && IsUnityObjAlive(GetRepoPopupMenuPage(_popup))) { if (count <= 0 || _replacerToggles.Count != 0) { return; } ClearNoblePopupCache(); } ClearNoblePopupCache(); ResolveMenuApiMethods(); object obj = Enum.Parse(_presetSideType, "Left"); _popup = _miCreatePopup.Invoke(null, new object[5] { "NobleMod", obj, true, true, 0f }); Type type = _popup.GetType(); object obj2 = type.GetField("menuScrollBox", BindingFlags.Instance | BindingFlags.Public)?.GetValue(_popup); if (obj2 == null) { throw new InvalidOperationException("REPOPopupPage.menuScrollBox null (Awake pas encore execute ?)."); } object? obj3 = obj2.GetType().GetField("scroller", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj2); Transform val = (Transform)((obj3 is Transform) ? obj3 : null); if ((Object)(object)val == (Object)null) { throw new InvalidOperationException("MenuScrollBox.scroller null."); } MethodInfo method = type.GetMethod("AddElementToScrollView", BindingFlags.Instance | BindingFlags.Public, null, new Type[4] { typeof(RectTransform), typeof(Vector2), typeof(float), typeof(float) }, null); if (method == null) { throw new InvalidOperationException("REPOPopupPage.AddElementToScrollView(RectTransform, ...) introuvable."); } RegisterPopupScrollElement(ctrl: (Component)(_toggleSpawn = (object)/*isinst with value type is only supported in some contexts*/), popupPage: _popup, addElementToScrollView: method, topPad: 14f, bottomPad: 0f); RegisterPopupScrollElement(ctrl: (Component)(_toggleSoundPackConditions = (object)/*isinst with value type is only supported in some contexts*/), popupPage: _popup, addElementToScrollView: method, topPad: 0f, bottomPad: 0f); RegisterPopupScrollElement(ctrl: (Component)(_toggleLogSoundPickEachMatch = (object)/*isinst with value type is only supported in some contexts*/), popupPage: _popup, addElementToScrollView: method, topPad: 0f, bottomPad: 0f); _replacerToggles.Clear(); IReadOnlyList<ReplacerToggleRegistry.ReplacerEntry> readOnlyList = ReplacerToggleRegistry.SnapshotEntries(); bool flag = true; foreach (ReplacerToggleRegistry.ReplacerEntry item in readOnlyList) { ReplacerToggleRegistry.ReplacerEntry capturedEntry = item; string text = "Replacer : " + item.DisplayName; object? obj4 = _miCreateToggle.Invoke(null, new object[7] { text, (Action<bool>)delegate(bool v) { capturedEntry.Entry.Value = v; SaveCfg(); if (!v) { int num = ReplacerToggleRegistry.StopActiveSourcesFor(capturedEntry.FilePath); if (num > 0) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)$"[NobleMod] Replacer '{capturedEntry.DisplayName}' desactive : {num} source(s) audio coupee(s)."); } } } }, val, Vector2.zero, "ON", "OFF", item.Entry.Value }); Component val2 = (Component)((obj4 is Component) ? obj4 : null); if (!((Object)(object)val2 == (Object)null)) { _replacerToggles.Add(new KeyValuePair<string, object>(item.FilePath, val2)); RegisterPopupScrollElement(_popup, method, val2, flag ? 14f : 0f, 0f); flag = false; } } object? obj5 = _miCreateButton.Invoke(null, new object[4] { "Fermer", (Action)delegate { _popup.GetType().GetMethod("ClosePage", new Type[1] { typeof(bool) })?.Invoke(_popup, new object[1] { false }); }, val, Vector2.zero }); Component ctrl4 = (Component)((obj5 is Component) ? obj5 : null); RegisterPopupScrollElement(_popup, method, ctrl4, 0f, 20f); object obj6 = type.GetField("scrollView", BindingFlags.Instance | BindingFlags.Public)?.GetValue(_popup); (obj6?.GetType().GetMethod("UpdateElements", BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null))?.Invoke(obj6, null); (obj6?.GetType().GetMethod("SetScrollPosition", BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { typeof(float) }, null))?.Invoke(obj6, new object[1] { 0f }); } private static void RegisterPopupScrollElement(object popupPage, MethodInfo addElementToScrollView, Component ctrl, float topPad, float bottomPad) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)ctrl == (Object)null) && !(addElementToScrollView == null)) { Transform transform = ctrl.transform; RectTransform val = (RectTransform)(object)((transform is RectTransform) ? transform : null); if (!((Object)(object)val == (Object)null)) { addElementToScrollView.Invoke(popupPage, new object[4] { val, Vector2.zero, topPad, bottomPad }); } } } private static void SaveCfg() { try { Plugin instance = Plugin.Instance; if (instance != null) { ((BaseUnityPlugin)instance).Config.Save(); } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("[NobleMod] Sauvegarde config: " + ex.Message)); } } } } [BepInPlugin("raisery.noblemod", "NobleMod", "1.1.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { internal static Plugin Instance; internal static Harmony Harmony; internal static ManualLogSource Log; internal static string AssemblyDirectory { get; private set; } = ""; public void Awake() { //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected O, but got Unknown try { ((BaseUnityPlugin)this).Logger.LogInfo((object)("[NobleMod] Awake() begin (dll=" + typeof(Plugin).Assembly.Location + ").")); AssemblyDirectory = Path.GetDirectoryName(typeof(Plugin).Assembly.Location) ?? ""; Instance = this; Log = ((BaseUnityPlugin)this).Logger; ModConfig.Bind(((BaseUnityPlugin)this).Config); NobleModSoundPackConditionBootstrap.TryRegister(((BaseUnityPlugin)this).Logger); LevelEnemyOverrideBank.Initialize(((BaseUnityPlugin)this).Logger); ReplacerToggleRegistry.Bootstrap(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger); Harmony = new Harmony("raisery.noblemod"); Harmony.PatchAll(); SoundApiReplacementSeedPatch.TryInstall(Harmony); SoundPackLoadedDiscoveryPatch.TryInstall(Harmony); ReplacerToggleFilterPatch.TryInstall(Harmony); ReplacerToggleRegistry.DiscoverAlreadyLoadedPacks(); NobleModSoundSyncNet.Initialize(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"NobleMod v1.1.1 loaded."); NobleModMenuIntegration.TryRegister(((BaseUnityPlugin)this).Logger); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)ex); } } } internal static class PluginInfo { public const string Guid = "raisery.noblemod"; public const string Name = "NobleMod"; public const string Version = "1.1.1"; } internal static class RepolibSpawnSupport { internal static bool IsRepolibLoaded { get { if (Chainloader.PluginInfos != null && Chainloader.PluginInfos.TryGetValue("REPOLib", out var value)) { if (value != null) { return value.Instance != null; } return false; } return false; } } internal static bool TryGetAllEnemySetupsAsObjects(Type expectedSetupType, out List<object> setups) { setups = null; if (!IsRepolibLoaded || expectedSetupType == null) { return false; } try { IReadOnlyList<EnemySetup> allEnemies = Enemies.AllEnemies; if (allEnemies == null) { return false; } List<object> list = new List<object>(); foreach (EnemySetup item in allEnemies) { if (!((Object)(object)item == (Object)null) && expectedSetupType.IsInstanceOfType(item) && !list.Contains(item)) { list.Add(item); } } setups = list; return list.Count > 0; } catch { setups = null; return false; } } } internal static class RunLevel { internal static int TryGetCurrentLevelNumber() { try { if ((Object)(object)RunManager.instance == (Object)null) { return 0; } return RunManager.instance.levelsCompleted + 1; } catch { return 0; } } } } namespace NobleMod.SoundPack { [SoundAPICondition("NobleMod:random_slot", false, null)] public sealed class NobleModRandomSlotCondition : Condition { private sealed class StickyChoice { internal int ChosenPick; internal float LastTime = -1f; internal int ClipFingerprint; internal bool Initialized; internal int LastStickyAttachFrame = int.MinValue; internal bool HasNamedAnchor; internal int NamedAnchorFingerprint; internal float NamedAnchorLength; } private sealed class LastLoggedPickHolder { internal int Value = int.MinValue; } private sealed class RangeStickyWarmupHolder { internal int R; } private sealed class SlotStickyWarmupHolder { internal int SlotPick; internal int N; internal int[] WeightKey; } public const int RangeRMin = 1; public const int RangeRMax = 1000; private const int StickyClipChurnGraceFrames = 64; private const int StickyLoopQuietFramesAfterAttach = 40; private const float StickyUnnamedClipLengthSlopSeconds = 0.35f; private static readonly ConditionalWeakTable<AudioSource, StickyChoice> StickyBySource = new ConditionalWeakTable<AudioSource, StickyChoice>(); private static readonly ConditionalWeakTable<AudioSource, LastLoggedPickHolder> LastLoggedRBySource = new ConditionalWeakTable<AudioSource, LastLoggedPickHolder>(); private static int _lastLoggedRWithoutSource = int.MinValue; private static readonly ConditionalWeakTable<SoundReplacementGroup, RangeStickyWarmupHolder> RangeStickyWarmupByGroup = new ConditionalWeakTable<SoundReplacementGroup, RangeStickyWarmupHolder>(); private static readonly ConditionalWeakTable<SoundReplacementGroup, SlotStickyWarmupHolder> SlotStickyWarmupByGroup = new ConditionalWeakTable<SoundReplacementGroup, SlotStickyWarmupHolder>(); private static int _coalesceFrame = -1; private static int _coalescePick; private static int[] _coalesceWeightsKey; private static int _rollCoalesceFrame = -1; private static int _rollCoalescePick; private const int KeyVariantRange = 0; private const int KeyVariantSlot = 1; public int Slot { get; private set; } public int Count { get; private set; } public bool Sticky { get; private set; } public List<int> SlotWeights { get; private set; } public string RandomMatch { get; private set; } public string RandomNumber { get; private set; } private bool IsRangeMode => !string.IsNullOrEmpty(RangeExpr()); private static bool MultiplayerSyncEnabled { get { if (ModConfig.EnableMultiplayerSoundSync != null && ModConfig.EnableMultiplayerSoundSync.Value) { return NobleModSoundSyncNet.IsActiveInRoom; } return false; } } private string RangeExpr() { string text = RandomMatch?.Trim(); if (string.IsNullOrEmpty(text)) { text = RandomNumber?.Trim(); } if (string.IsNullOrEmpty(text)) { return null; } while (text.Contains("...")) { text = text.Replace("...", ".."); } return text; } private static int PickUniform(int n) { if (n <= 1) { return 0; } return Random.Range(0, n); } private static int PickWeighted(int n, IReadOnlyList<int> weights) { if (weights == null || weights.Count < n) { return PickUniform(n); } int num = 0; for (int i = 0; i < n; i++) { num += Mathf.Max(0, weights[i]); } if (num <= 0) { return PickUniform(n); } int num2 = Random.Range(0, num); for (int j = 0; j < n; j++) { num2 -= Mathf.Max(0, weights[j]); if (num2 < 0) { return j; } } return n - 1; } private static bool WeightsKeyEquals(int[] a, IReadOnlyList<int> b, int n) { if (a == null && (b == null || b.Count < n)) { return true; } if (a == null || b == null || a.Length != n || b.Count < n) { return false; } for (int i = 0; i < n; i++) { if (a[i] != b[i]) { return false; } } return true; } private static int[] CopyWeightsKey(IReadOnlyList<int> b, int n) { if (b == null || b.Count < n) { return null; } int[] array = new int[n]; for (int i = 0; i < n; i++) { array[i] = b[i]; } return array; } private static int CoalescedSlotPickThisFrame(int n, IReadOnlyList<int> w) { int frameCount = Time.frameCount; if (_coalesceFrame != frameCount || !WeightsKeyEquals(_coalesceWeightsKey, w, n)) { _coalesceFrame = frameCount; _coalesceWeightsKey = CopyWeightsKey(w, n); _coalescePick = ((_coalesceWeightsKey != null) ? PickWeighted(n, w) : PickUniform(n)); } return _coalescePick; } private static int CoalescedRollThisFrame() { int frameCount = Time.frameCount; if (_rollCoalesceFrame != frameCount) { _rollCoalesceFrame = frameCount; _rollCoalescePick = Random.Range(1, 1001); } return _rollCoalescePick; } private static int PickR1To1000() { return Random.Range(1, 1001); } private static int RollRangeR_Sticky(SoundReplacementGroup g, AudioSource src, float clipLengthSec) { if (MultiplayerSyncEnabled && NobleModSoundSyncNet.TryRange(NobleModSoundSyncKey.BuildStickyKey(g, src, clipLengthSec, 0), 1, 1000, out var value)) { return value; } return PickR1To1000(); } private static int RollRangeR_NonSticky(SoundReplacementGroup g, AudioSource src) { if (MultiplayerSyncEnabled && NobleModSoundSyncNet.TryRange(NobleModSoundSyncKey.BuildNonStickyKey(g, src, 0), 1, 1000, out var value)) { return value; } return CoalescedRollThisFrame(); } private static int RollSlot_Sticky(SoundReplacementGroup g, AudioSource src, float clipLengthSec, int n, IReadOnlyList<int> weights) { if (MultiplayerSyncEnabled && NobleModSoundSyncNet.TryLookup(NobleModSoundSyncKey.BuildStickyKey(g, src, clipLengthSec, 1), out var value)) { return DeterministicSlotPick(value, n, weights); } if (weights != null) { int count = weights.Count; if (count >= n) { return PickWeighted(n, weights); } } return PickUniform(n); } private static int RollSlot_NonSticky(SoundReplacementGroup g, AudioSource src, int n, IReadOnlyList<int> weights) { if (MultiplayerSyncEnabled && NobleModSoundSyncNet.TryLookup(NobleModSoundSyncKey.BuildNonStickyKey(g, src, 1), out var value)) { return DeterministicSlotPick(value, n, weights); } return CoalescedSlotPickThisFrame(n, weights); } internal static int DeterministicSlotPick(int seed, int n, IReadOnlyList<int> weights) { if (n <= 1) { return 0; } if (weights == null || weights.Count < n) { return (int)((uint)seed % (uint)n); } int num = 0; for (int i = 0; i < n; i++) { num += Mathf.Max(0, weights[i]); } if (num <= 0) { return (int)((uint)seed % (uint)n); } int num2 = (int)((uint)seed % (uint)num); for (int j = 0; j < n; j++) { num2 -= Mathf.Max(0, weights[j]); if (num2 < 0) { return j; } } return n - 1; } private SoundReplacementGroup GetReplacementGroup() { Conditional parent = ((Condition)this).Parent; SoundInstance val = (SoundInstance)(object)((parent is SoundInstance) ? parent : null); if (val == null) { return null; } return val.Parent; } private static int GetStickyRangeWarmupPick(SoundReplacementGroup g) { if (g == null) { return CoalescedRollThisFrame(); } if (!RangeStickyWarmupByGroup.TryGetValue(g, out var value)) { value = new RangeStickyWarmupHolder { R = PickR1To1000() }; RangeStickyWarmupByGroup.Add(g, value); } return value.R; } private static void RefreshStickyRangeWarmup(SoundReplacementGroup g) { if (g != null) { if (!RangeStickyWarmupByGroup.TryGetValue(g, out var value)) { value = new RangeStickyWarmupHolder { R = PickR1To1000() }; RangeStickyWarmupByGroup.Add(g, value); } else { value.R = PickR1To1000(); } } } private static int GetStickySlotWarmupPick(SoundReplacementGroup g, int n, IReadOnlyList<int> weights) { if (g == null) { return CoalescedSlotPickThisFrame(n, weights); } if (SlotStickyWarmupByGroup.TryGetValue(g, out var value)) { if (value.N != n || !WeightsKeyEquals(value.WeightKey, weights, n)) { value.N = n; value.WeightKey = CopyWeightsKey(weights, n); value.SlotPick = ((value.WeightKey != null) ? PickWeighted(n, weights) : PickUniform(n)); } return value.SlotPick; } value = new SlotStickyWarmupHolder(); value.N = n; value.WeightKey = CopyWeightsKey(weights, n); value.SlotPick = ((value.WeightKey != null) ? PickWeighted(n, weights) : PickUniform(n)); SlotStickyWarmupByGroup.Add(g, value); return value.SlotPick; } private static void RefreshStickySlotWarmup(SoundReplacementGroup g, int n, IReadOnlyList<int> weights) { if (g != null) { if (!SlotStickyWarmupByGroup.TryGetValue(g, out var value)) { GetStickySlotWarmupPick(g, n, weights); return; } value.N = n; value.WeightKey = CopyWeightsKey(weights, n); value.SlotPick = ((value.WeightKey != null) ? PickWeighted(n, weights) : PickUniform(n)); } } internal static bool ValueMatchesRange(int value, string expr) { if (string.IsNullOrWhiteSpace(expr)) { return false; } string text = expr.Trim(); while (text.Contains("...")) { text = text.Replace("...", ".."); } if (!text.Contains("..")) { if (!int.TryParse(text, out var result)) { return false; } return value == result; } string[] array = text.Split(new string[1] { ".." }, StringSplitOptions.None); if (array.Length != 2) { return false; } string text2 = array[0].Trim(); string text3 = array[1].Trim(); bool flag = text2.Length > 0; bool flag2 = text3.Length > 0; if (!flag && !flag2) { return false; } if (flag && flag2) { if (!int.TryParse(text2, out var result2) || !int.TryParse(text3, out var result3)) { return false; } if (value >= result2) { return value <= result3; } return false; } if (flag) { if (!int.TryParse(text2, out var result4)) { return false; } if (value >= result4) { return value <= 1000; } return false; } if (!int.TryParse(text3, out var result5)) { return false; } if (value >= 1) { return value <= result5; } return false; } internal static bool TryGetRangeMaxInclusive(string expr, out int maxInclusive, out bool upperOpen) { maxInclusive = -1; upperOpen = false; if (string.IsNullOrWhiteSpace(expr)) { return false; } string text = expr.Trim(); while (text.Contains("...")) { text = text.Replace("...", ".."); } if (!text.Contains("..")) { if (!int.TryParse(text, out var result)) { return false; } maxInclusive = result; return true; } string[] array = text.Split(new string[1] { ".." }, StringSplitOptions.None); if (array.Length != 2) { return false; } string text2 = array[0].Trim(); string text3 = array[1].Trim(); bool flag = text2.Length > 0; bool flag2 = text3.Length > 0; if (!flag && !flag2) { return false; } if (flag && flag2) { if (!int.TryParse(text3, out var result2)) { return false; } maxInclusive = result2; return true; } if (flag && !flag2) { upperOpen = true; return true; } if (!int.TryParse(text3, out var result3)) { return false; } maxInclusive = result3; return true; } private static void TryLogRangePickIfChanged(AudioSource src, int pick, string soundFile) { if ((Object)(object)src != (Object)null && Object.op_Implicit((Object)(object)src)) { if (!LastLoggedRBySource.TryGetValue(src, out var value)) { value = new LastLoggedPickHolder { Value = pick }; LastLoggedRBySource.Add(src, value); Plugin.Log.LogInfo((object)$"[NobleMod] R = {pick} -> son {soundFile}"); } else if (value.Value != pick) { value.Value = pick; Plugin.Log.LogInfo((object)$"[NobleMod] R = {pick} -> son {soundFile}"); } } else if (_lastLoggedRWithoutSource != pick) { _lastLoggedRWithoutSource = pick; Plugin.Log.LogInfo((object)$"[NobleMod] R = {pick} -> son {soundFile}"); } } private void TryLogSoundPickEachMatch(bool matched) { if (matched && ModConfig.LogSoundPickEachMatch != null && ModConfig.LogSoundPickEachMatch.Value) { string text = TryResolvePickSoundLabel(); AudioSource currentAudioSource = NobleModSoundEvalContext.CurrentAudioSource; string text2 = (((Object)(object)currentAudioSource != (Object)null && Object.op_Implicit((Object)(object)currentAudioSource)) ? $"AudioSource #{((Object)currentAudioSource).GetInstanceID()}" : "sans source"); if (!string.IsNullOrEmpty(text)) { Plugin.Log.LogInfo((object)("[NobleMod] Son selectionne : " + text + " (" + text2 + ")")); } else { Plugin.Log.LogInfo((object)("[NobleMod] Son selectionne : (libelle introuvable, Parent=" + ((((Condition)this).Parent == null) ? "null" : ((object)((Condition)this).Parent).GetType().FullName) + ") (" + text2 + ")")); } } } private string TryResolvePickSoundLabel() { Conditional parent = ((Condition)this).Parent; SoundInstance val = (SoundInstance)(object)((parent is SoundInstance) ? parent : null); if (val != null && !string.IsNullOrEmpty(val.Sound)) { return val.Sound; } Conditional parent2 = ((Condition)this).Parent; if (parent2 == null) { return null; } Type type = ((object)parent2).GetType(); string[] array = new string[6] { "Sound", "sound", "SoundFile", "soundFile", "File", "file" }; foreach (string name in array) { if (type.GetProperty(name, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(parent2) is string text && !string.IsNullOrEmpty(text)) { return text; } } array = new string[4] { "Sound", "sound", "soundFile", "_sound" }; foreach (string name2 in array) { if (type.GetField(name2, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(parent2) is string text2 && !string.IsNullOrEmpty(text2)) { return text2; } } return null; } private static void TryLogStickyTransition(AudioSource src, string modeLabel, bool attach, string detail) { if (ModConfig.LogStickyAttachDetach != null && ModConfig.LogStickyAttachDetach.Value && !((Object)(object)src == (Object)null) && Object.op_Implicit((Object)(object)src)) { string text = (((Object)(object)src.clip != (Object)null) ? $"{((Object)src.clip).name}#{((Object)src.clip).GetInstanceID()}" : "null"); string text2 = (attach ? "branché" : "débranché"); Plugin.Log.LogInfo((object)$"[NobleMod] Sticky {modeLabel}: {text2} (AudioSource #{((Object)src).GetInstanceID()}, clip={text}) — {detail}"); } } private static int GetStickyClipFingerprint(AudioClip clip, StickyChoice st) { if ((Object)(object)clip == (Object)null) { return 0; } string text = ((Object)clip).name ?? string.Empty; if (text.StartsWith("NobleMod ", StringComparison.OrdinalIgnoreCase)) { text = text.Substring("NobleMod ".Length).TrimStart(); } if (!string.IsNullOrEmpty(text)) { int hashCode = StringComparer.OrdinalIgnoreCase.GetHashCode(text); st.HasNamedAnchor = true; st.NamedAnchorFingerprint = hashCode; st.NamedAnchorLength = clip.length; return hashCode; } int result = Mathf.RoundToInt(clip.length * 1000f) ^ (clip.frequency * 397) ^ (clip.channels * 7919); if (st.HasNamedAnchor && Mathf.Abs(clip.length - st.NamedAnchorLength) <= 0.35f) { return st.NamedAnchorFingerprint; } return result; } private bool EvaluateRangeMode(string expr, IContext context) { int num; SoundReplacementGroup replacementGroup2; AudioSource currentAudioSource2; StickyChoice value2; float time; float length; bool flag; int num4; int num5; if (!Sticky) { SoundReplacementGroup replacementGroup = GetReplacementGroup(); AudioSource currentAudioSource = NobleModSoundEvalContext.CurrentAudioSource; num = RollRangeR_NonSticky(replacementGroup, currentAudioSource); } else { replacementGroup2 = GetReplacementGroup(); currentAudioSource2 = NobleModSoundEvalContext.CurrentAudioSource; StickyChoice value; if ((Object)(object)currentAudioSource2 == (Object)null || !Object.op_Implicit((Object)(object)currentAudioSource2) || (Object)(object)currentAudioSource2.clip == (Object)null) { num = GetStickyRangeWarmupPick(replacementGroup2); } else if (!currentAudioSource2.isPlaying && StickyBySource.TryGetValue(currentAudioSource2, out value) && value.Initialized && value.ClipFingerprint == GetStickyClipFingerprint(currentAudioSource2.clip, value)) { num = value.ChosenPick; } else { if (currentAudioSource2.isPlaying) { if (!StickyBySource.TryGetValue(currentAudioSource2, out value2)) { value2 = new StickyChoice(); StickyBySource.Add(currentAudioSource2, value2); } time = currentAudioSource2.time; length = currentAudioSource2.clip.length; int stickyClipFingerprint = GetStickyClipFingerprint(currentAudioSource2.clip, value2); flag = ModConfig.LogStickyAttachDetach != null && ModConfig.LogStickyAttachDetach.Value; if (!value2.Initialized || value2.ClipFingerprint != stickyClipFingerprint) { bool initialized = value2.Initialized; bool flag2 = initialized && value2.ClipFingerprint != stickyClipFingerprint; int num2 = ((value2.LastStickyAttachFrame == int.MinValue) ? int.MaxValue : (Time.frameCount - value2.LastStickyAttachFrame)); if (flag2 && num2 >= 0 && num2 < 64) { value2.ClipFingerprint = stickyClipFingerprint; value2.LastTime = time; } else { bool flag3 = flag2; if (flag3) { value2.HasNamedAnchor = false; stickyClipFingerprint = GetStickyClipFingerprint(currentAudioSource2.clip, value2); } if (flag && flag3) { TryLogStickyTransition(currentAudioSource2, "plage", attach: false, "changement de clip (fin du cycle précédent)"); } if (flag3) { RefreshStickyRangeWarmup(replacementGroup2); } value2.Initialized = true; value2.ClipFingerprint = stickyClipFingerprint; value2.ChosenPick = RollRangeR_Sticky(replacementGroup2, currentAudioSource2, length); if (replacementGroup2 != null && RangeStickyWarmupByGroup.TryGetValue(replacementGroup2, out var value3)) { value3.R = value2.ChosenPick; } value2.LastTime = time; value2.LastStickyAttachFrame = Time.frameCount; if (flag) { if (!initialized) { TryLogStickyTransition(currentAudioSource2, "plage", attach: true, "premier attache sur la source"); } else if (flag3) { TryLogStickyTransition(currentAudioSource2, "plage", attach: true, "nouveau clip"); } } } goto IL_0390; } int num3 = ((value2.LastStickyAttachFrame == int.MinValue) ? int.MaxValue : (Time.frameCount - value2.LastStickyAttachFrame)); if (num3 >= 0) { num4 = ((num3 < 40) ? 1 : 0); if (num4 != 0) { goto IL_02df; } } else { num4 = 0; } if (!(value2.LastTime > 0.08f) || !(value2.LastTime - time > 0.18f)) { goto IL_02df; } num5 = ((time < 0.4f) ? 1 : 0); goto IL_02e0; } num = GetStickyRangeWarmupPick(replacementGroup2); } } goto IL_0398; IL_0390: num = value2.ChosenPick; goto IL_0398; IL_0398: bool flag4 = ValueMatchesRange(num, expr); if (flag4 && ModConfig.LogRandomSlotRange != null && ModConfig.LogRandomSlotRange.Value) { Conditional parent = ((Condition)this).Parent; SoundInstance val = (SoundInstance)(object)((parent is SoundInstance) ? parent : null); if (val != null && !string.IsNullOrEmpty(val.Sound)) { TryLogRangePickIfChanged(NobleModSoundEvalContext.CurrentAudioSource, num, val.Sound); } } TryLogSoundPickEachMatch(flag4); return flag4; IL_02e0: bool flag5 = (byte)num5 != 0; bool flag6 = num4 == 0 && length > 0.1f && value2.LastTime > length * 0.88f && time < length * 0.22f; if (flag5 || flag6) { if (flag) { TryLogStickyTransition(currentAudioSource2, "plage", attach: false, "boucle détectée (nouveau tirage R)"); } value2.ChosenPick = RollRangeR_Sticky(replacementGroup2, currentAudioSource2, length); if (replacementGroup2 != null && RangeStickyWarmupByGroup.TryGetValue(replacementGroup2, out var value4)) { value4.R = value2.ChosenPick; } if (flag) { TryLogStickyTransition(currentAudioSource2, "plage", attach: true, $"nouveau R={value2.ChosenPick}"); } } value2.LastTime = time; goto IL_0390; IL_02df: num5 = 0; goto IL_02e0; } public override bool Evaluate(IContext context) { string text = RangeExpr(); if (!string.IsNullOrEmpty(text)) { return EvaluateRangeMode(text, context); } int num = ((Count < 1) ? 4 : Count); num = Mathf.Max(1, num); int num2 = Mathf.Clamp(Slot, 0, num - 1); if (!Sticky) { SoundReplacementGroup replacementGroup = GetReplacementGroup(); AudioSource currentAudioSource = NobleModSoundEvalContext.CurrentAudioSource; bool flag = RollSlot_NonSticky(replacementGroup, currentAudioSource, num, SlotWeights) == num2; TryLogSoundPickEachMatch(flag); return flag; } SoundReplacementGroup replacementGroup2 = GetReplacementGroup(); AudioSource currentAudioSource2 = NobleModSoundEvalContext.CurrentAudioSource; if ((Object)(object)currentAudioSource2 == (Object)null || !Object.op_Implicit((Object)(object)currentAudioSource2) || (Object)(object)currentAudioSource2.clip == (Object)null) { bool flag2 = GetStickySlotWarmupPick(replacementGroup2, num, SlotWeights) == num2; TryLogSoundPickEachMatch(flag2); return flag2; } if (!currentAudioSource2.isPlaying && StickyBySource.TryGetValue(currentAudioSource2, out var value) && value.Initialized && value.ClipFingerprint == GetStickyClipFingerprint(currentAudioSource2.clip, value)) { bool flag3 = value.ChosenPick == num2; TryLogSoundPickEachMatch(flag3); return flag3; } if (!currentAudioSource2.isPlaying) { bool flag4 = GetStickySlotWarmupPick(replacementGroup2, num, SlotWeights) == num2; TryLogSoundPickEachMatch(flag4); return flag4; } if (!StickyBySource.TryGetValue(currentAudioSource2, out var value2)) { value2 = new StickyChoice(); StickyBySource.Add(currentAudioSource2, value2); } float time = currentAudioSource2.time; float length = currentAudioSource2.clip.length; int stickyClipFingerprint = GetStickyClipFingerprint(currentAudioSource2.clip, value2); bool flag5 = ModConfig.LogStickyAttachDetach != null && ModConfig.LogStickyAttachDetach.Value; if (!value2.Initialized || value2.ClipFingerprint != stickyClipFingerprint) { bool initialized = value2.Initialized; bool flag6 = initialized && value2.ClipFingerprint != stickyClipFingerprint; int num3 = ((value2.LastStickyAttachFrame == int.MinValue) ? int.MaxValue : (Time.frameCount - value2.LastStickyAttachFrame)); if (flag6 && num3 >= 0 && num3 < 64) { value2.ClipFingerprint = stickyClipFingerprint; value2.LastTime = time; } else { bool flag7 = flag6; if (flag7) { value2.HasNamedAnchor = false; stickyClipFingerprint = GetStickyClipFingerprint(currentAudioSource2.clip, value2); } if (flag5 && flag7) { TryLogStickyTransition(currentAudioSource2, "slot", attach: false, "changement de clip (fin du cycle précédent)"); } if (flag7) { RefreshStickySlotWarmup(replacementGroup2, num, SlotWeights); } value2.Initialized = true; value2.ClipFingerprint = stickyClipFingerprint; value2.ChosenPick = RollSlot_Sticky(replacementGroup2, currentAudioSource2, length, num, SlotWeights); if (replacementGroup2 != null && SlotStickyWarmupByGroup.TryGetValue(replacementGroup2, out var value3)) { value3.SlotPick = value2.ChosenPick; } value2.LastTime = time; value2.LastStickyAttachFrame = Time.frameCount; if (flag5) { if (!initialized) { TryLogStickyTransition(currentAudioSource2, "slot", attach: true, "premier attache sur la source"); } else if (flag7) { TryLogStickyTransition(currentAudioSource2, "slot", attach: true, "nouveau clip"); } } } bool flag8 = value2.ChosenPick == num2; TryLogSoundPickEachMatch(flag8); return flag8; } int num4 = ((value2.LastStickyAttachFrame == int.MinValue) ? int.MaxValue : (Time.frameCount - value2.LastStickyAttachFrame)); int num5; if (num4 >= 0) { num5 = ((num4 < 40) ? 1 : 0); if (num5 != 0) { goto IL_0371; } } else { num5 = 0; } if (!(value2.LastTime > 0.08f) || !(value2.LastTime - time > 0.18f)) { goto IL_0371; } int num6 = ((time < 0.4f) ? 1 : 0); goto IL_0372; IL_0372: bool flag9 = (byte)num6 != 0; bool flag10 = num5 == 0 && length > 0.1f && value2.LastTime > length * 0.88f && time < length * 0.22f; if (flag9 || flag10) { if (flag5) { TryLogStickyTransition(currentAudioSource2, "slot", attach: false, "boucle détectée (nouveau tirage slot)"); } value2.ChosenPick = RollSlot_Sticky(replacementGroup2, currentAudioSource2, length, num, SlotWeights); if (replacementGroup2 != null && SlotStickyWarmupByGroup.TryGetValue(replacementGroup2, out var value4)) { value4.SlotPick = value2.ChosenPick; } if (flag5) { TryLogStickyTransition(currentAudioSource2, "slot", attach: true, $"nouveau slot={value2.ChosenPick}"); } } value2.LastTime = time; bool flag11 = value2.ChosenPick == num2; TryLogSoundPickEachMatch(flag11); return flag11; IL_0371: num6 = 0; goto IL_0372; } private static bool GroupHasSlotModeNoble(SoundReplacementGroup g) { foreach (SoundInstance sound in g.Sounds) { if (((Conditional)sound).Condition is NobleModRandomSlotCondition nobleModRandomSlotCondition && !nobleModRandomSlotCondition.IsRangeMode) { return true; } } return false; } private static bool GroupHasRangeModeNoble(SoundReplacementGroup g) { foreach (SoundInstance sound in g.Sounds) { if (((Conditional)sound).Condition is NobleModRandomSlotCondition nobleModRandomSlotCondition && nobleModRandomSlotCondition.IsRangeMode) { return true; } } return false; } private static void WarnIfRangeNeverMatches(string expr, List<ValidationResult> results) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Expected O, but got Unknown if (string.IsNullOrWhiteSpace(expr)) { return; } string text = expr.Trim(); while (text.Contains("...")) { text = text.Replace("...", ".."); } if (!text.Contains("..")) { if (int.TryParse(text, out var result) && (result < 1 || result > 1000)) { results.Add(new ValidationResult((ResultType)0, $"NobleMod:random_slot : '{expr}' ne peut jamais matcher (R est toujours entre {1} et {1000}).")); } return; } string[] array = text.Split(new string[1] { ".." }, StringSplitOptions.None); if (array.Length == 2 && int.TryParse(array[0].Trim(), out var result2) && int.TryParse(array[1].Trim(), out var result3) && (result3 < 1 || result2 > 1000)) { results.Add(new ValidationResult((ResultType)0, $"NobleMod:random_slot : la plage '{expr}' ne croise pas [{1}..{1000}] (R ne matchera jamais).")); } } public override List<ValidationResult> Validate() { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Expected O, but got Unknown //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Expected O, but got Unknown //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Expected O, but got Unknown //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Expected O, but got Unknown //IL_0251: Unknown result type (might be due to invalid IL or missing references) //IL_025b: Expected O, but got Unknown //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Expected O, but got Unknown //IL_0238: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Expected O, but got Unknown //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Expected O, but got Unknown //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Expected O, but got Unknown List<ValidationResult> list = ((Condition)this).Validate(); bool? constant = ((Condition)this).Constant; bool upperOpen = true; if (constant == upperOpen) { list.Add(new ValidationResult((ResultType)1, "NobleMod:random_slot ne doit pas être utilisé avec constant: true (le tirage serait figé au chargement).")); } if (IsRangeMode) { string text = RangeExpr(); if (!TryGetRangeMaxInclusive(text, out var _, out upperOpen)) { list.Add(new ValidationResult((ResultType)1, "NobleMod:random_slot : random_match / random_number invalide : '" + text + "'.")); } else { WarnIfRangeNeverMatches(text, list); } SoundReplacementGroup replacementGroup = GetReplacementGroup(); if (replacementGroup != null && GroupHasSlotModeNoble(replacementGroup) && GroupHasRangeModeNoble(replacementGroup)) { list.Add(new ValidationResult((ResultType)1, "NobleMod:random_slot : ne pas melanger dans le meme groupe des lignes mode plage (random_match) et mode slot (slot/count).")); } if (Sticky) { list.Add(new ValidationResult((ResultType)0, "NobleMod:random_slot (plage) avec sticky: true : prevu pour update_every_frame + patch NobleMod sur SoundAPI.Update.")); } return list; } int num = ((Count < 1) ? 4 : Count); if (Count < 1) { list.Add(new ValidationResult((ResultType)0, "NobleMod:random_slot : count absent ou inferieur a 1, defaut 4 utilise pour la validation.")); } if (Slot < 0 || Slot >= num) { list.Add(new ValidationResult((ResultType)1, $"NobleMod:random_slot : slot ({Slot}) invalide pour count={num} (attendu 0 .. {num - 1}).")); } if (SlotWeights != null && SlotWeights.Count > 0) { if (SlotWeights.Count != num) { list.Add(new ValidationResult((ResultType)1, $"NobleMod:random_slot : slot_weights doit avoir exactement {num} entrees (count={num}), trouve {SlotWeights.Count}.")); } else { for (int i = 0; i < num; i++) { if (SlotWeights[i] < 0) { list.Add(new ValidationResult((ResultType)1, $"NobleMod:random_slot : slot_weights[{i}] ne peut pas etre negatif.")); break; } } int num2 = 0; for (int j = 0; j < num; j++) { num2 += Mathf.Max(0, SlotWeights[j]); } if (num2 <= 0) { list.Add(new ValidationResult((ResultType)1, "NobleMod:random_slot : la somme des slot_weights doit etre strictement positive.")); } } } SoundReplacementGroup replacementGroup2 = GetReplacementGroup(); if (replacementGroup2 != null && GroupHasSlotModeNoble(replacementGroup2) && GroupHasRangeModeNoble(replacementGroup2)) { list.Add(new ValidationResult((ResultType)1, "NobleMod:random_slot : ne pas melanger dans le meme groupe des lignes mode plage et mode slot.")); } if (Sticky) { list.Add(new ValidationResult((ResultType)0, "NobleMod:random_slot avec sticky: true : prevu pour update_every_frame + patch NobleMod sur SoundAPI.Update (fin de boucle detectee via AudioSource.time).")); } return list; } } internal static class NobleModSoundEvalContext { private static int _depth; private static AudioSource _current; internal static AudioSource CurrentAudioSource { get { if (_depth <= 0) { return null; } return _current; } } internal static void Enter(AudioSource source) { _current = source; _depth++; } internal static void Exit() { _depth--; if (_depth <= 0) { _depth = 0; _current = null; } } } internal static class NobleModSoundPackConditionBootstrap { internal static void TryRegister(ManualLogSource log) { if (!ModConfig.EnableNobleModSoundPackConditions.Value) { log.LogInfo((object)"[NobleMod] Conditions pack NobleMod desactivees (config) : pas d'enregistrement SoundAPI."); return; } try { SoundAPI.RegisterAll(Assembly.GetExecutingAssembly()); log.LogInfo((object)"[NobleMod] Conditions SoundAPI NobleMod enregistrees (ex. NobleMod:random_slot)."); } catch (Exception arg) { log.LogError((object)$"[NobleMod] Echec enregistrement conditions SoundAPI : {arg}"); } } } } namespace NobleMod.Replacers { internal static class ReplacerToggleRegistry { private sealed class ActiveBinding { internal string FilePath; } internal sealed class ReplacerEntry { internal string FilePath { get; } internal string DisplayName { get; } internal ConfigEntry<bool> Entry { get; } internal ReplacerEntry(string filePath, string displayName, ConfigEntry<bool> entry) { FilePath = filePath; DisplayName = displayName; Entry = entry; } } private const string NobleModPackName = "NobleMod"; private static readonly object Sync = new object(); private static readonly Dictionary<string, ReplacerEntry> Entries = new Dictionary<string, ReplacerEntry>(StringComparer.OrdinalIgnoreCase); private static readonly ConditionalWeakTable<AudioSource, ActiveBinding> ActiveBindings = new ConditionalWeakTable<AudioSource, ActiveBinding>(); private static ConfigFile _config; private static ManualLogSource _log; private static volatile bool _ready; internal static bool IsReady => _ready; internal static IReadOnlyList<ReplacerEntry> SnapshotEntries() { lock (Sync) { return Entries.Values.OrderBy<ReplacerEntry, string>((ReplacerEntry e) => e.DisplayName, StringComparer.OrdinalIgnoreCase).ToArray(); } } internal static void Bootstrap(ConfigFile config, ManualLogSource log) { _config = config; _log = log; } internal static void DiscoverAlreadyLoadedPacks() { try { Type type = AccessTools.TypeByName("loaforcsSoundAPI.SoundPacks.SoundPackDataHandler"); if (type == null) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)"[NobleMod] Replacers : SoundPackDataHandler introuvable au scan."); } return; } FieldInfo fieldInfo = AccessTools.Field(type, "SoundReplacements") ?? type.GetField("SoundReplacements", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); if (fieldInfo == null) { ManualLogSource log2 = _log; if (log2 != null) { log2.LogWarning((object)"[NobleMod] Replacers : champ SoundReplacements introuvable (reflection)."); } return; } if (!(fieldInfo.GetValue(null) is IDictionary dictionary)) { ManualLogSource log3 = _log; if (log3 != null) { log3.LogWarning((object)"[NobleMod] Replacers : SoundReplacements null / type inattendu au scan."); } return; } string text = NormalizeDir(Plugin.AssemblyDirectory); HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); int num = 0; int num2 = 0; foreach (DictionaryEntry item in dictionary) { if (!(item.Value is IEnumerable enumerable)) { continue; } foreach (object item2 in enumerable) { if (item2 == null) { continue; } object obj = item2.GetType().GetProperty("Parent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(item2); if (obj == null) { continue; } string text2 = obj.GetType().GetProperty("FilePath", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj) as string; if (!string.IsNullOrEmpty(text2) && hashSet.Add(text2)) { if (string.IsNullOrEmpty(text) || !NormalizePath(text2).StartsWith(text, StringComparison.OrdinalIgnoreCase)) { num2++; } else if (TryRegisterFile(text2)) { num++; } } } } int count; lock (Sync) { count = Entries.Count; } ManualLogSource log4 = _log; if (log4 != null) { log4.LogInfo((object)($"[NobleMod] Replacers : scan SoundReplacements — {hashSet.Count} fichier(s) source(s) unique(s) vu(s), " + $"{num2} hors-NobleMod ignore(s), +{num} ajoute(s) ce scan, total={count} (rootDir='{text}').")); } if (count > 0) { _ready = true; } } catch (Exception ex) { ManualLogSource log5 = _log; if (log5 != null) { log5.LogWarning((object)("[NobleMod] Replacers : exception scan ignoree : " + ex.Message)); } } } internal static void DiscoverPack(object soundPack) { if (soundPack == null || _config == null) { return; } try { if (!(soundPack.GetType().GetProperty("ReplacementCollections", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(soundPack) is IEnumerable enumerable)) { return; } string value = NormalizeDir(Plugin.AssemblyDirectory); int num = 0; foreach (object item in enumerable) { if (item != null) { string text = item.GetType().GetProperty("FilePath", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(item) as string; if (!string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(value) && NormalizePath(text).StartsWith(value, StringComparison.OrdinalIgnoreCase) && TryRegisterFile(text)) { num++; } } } if (num > 0) { _ready = true; ManualLogSource log = _log; if (log != null) { log.LogInfo((object)$"[NobleMod] Replacers DiscoverPack : +{num} fichier(s) via pack.ReplacementCollections."); } } } catch (Exception ex) { ManualLogSource log2 = _log; if (log2 != null) { log2.LogWarning((object)("[NobleMod] Replacers DiscoverPack : exception ignoree : " + ex.Message)); } } } private static string NormalizeDir(string dir) { if (string.IsNullOrEmpty(dir)) { return string.Empty; } string text = NormalizePath(dir); if (text.Length > 0 && text[text.Length - 1] != Path.DirectorySeparatorChar) { text += Path.DirectorySeparatorChar; } return text; } private static string NormalizePath(string p) { if (!string.IsNullOrEmpty(p)) { return Path.GetFullPath(p.Replace('/', Path.DirectorySeparatorChar)); } return string.Empty; } internal static bool IsEnabled(string filePath) { if (string.IsNullOrEmpty(filePath)) { return true; } lock (Sync) { ReplacerEntry value; return !Entries.TryGetValue(filePath, out value) || value.Entry.Value; } } internal static void TrackPlayback(AudioSource source, string filePath) { if (!((Object)(object)source == (Object)null) && !string.IsNullOrEmpty(filePath)) { if (ActiveBindings.TryGetValue(source, out var value)) { value.FilePath = filePath; return; } ActiveBindings.Add(source, new ActiveBinding { FilePath = filePath }); } } internal static int StopActiveSourcesFor(string filePath) { if (string.IsNullOrEmpty(filePath)) { return 0; } int num = 0; List<AudioSource> list = new List<AudioSource>(); foreach (KeyValuePair<AudioSource, ActiveBinding> item in (IEnumerable<KeyValuePair<AudioSource, ActiveBinding>>)ActiveBindings) { if ((Object)(object)item.Key == (Object)null || !Object.op_Implicit((Object)(object)item.Key)) { list.Add(item.Key); } else { if (!string.Equals(item.Value.FilePath, filePath, StringComparison.OrdinalIgnoreCase)) { continue; } try { if (item.Key.isPlaying) { item.Key.Stop(); num++; } } catch { } } } foreach (AudioSource item2 in list) { if ((Object)(object)item2 != (Object)null) { ActiveBindings.Remove(item2); } } return num; } private static bool TryRegisterFile(string absolutePath) { lock (Sync) { if (Entries.ContainsKey(absolutePath)) { return false; } string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(absolutePath); if (string.IsNullOrEmpty(fileNameWithoutExtension)) { return false; } string text = "Enable_" + SanitizeKey(fileNameWithoutExtension); ConfigEntry<bool> entry = _config.Bind<bool>("Replacers", text, true, "Active/desactive le replacer NobleMod issu de '" + fileNameWithoutExtension + ".json'. Modifiable en jeu via le menu NobleMod (section Replacers). OFF : aucun son de ce fichier ne remplace le son vanilla. Toggle strictement local : ne se replique pas aux autres joueurs en multi."); Entries[absolutePath] = new ReplacerEntry(absolutePath, fileNameWithoutExtension, entry); return true; } } private static string SanitizeKey(string s) { return new string(s.Select((char c) => (!char.IsLetterOrDigit(c) && c != '_') ? '_' : c).ToArray()); } } } namespace NobleMod.Patches { [HarmonyPatch] internal static class LevelEnemyOverridePatches { [HarmonyPatch(typeof(EnemyDirector))] private static class EnemyDirectorAwakeMobDumpPatch { [HarmonyPatch("Awake")] [HarmonyPostfix] [HarmonyPriority(0)] private static void AfterAwake(EnemyDirector __instance) { if (!ModConfig.DumpEnemyMobKeysOnce.Value || (Object)(object)__instance == (Object)null) { return; } try { List<object> list = CollectEnemySetupsViaReflection(__instance, typeof(EnemySetup)); Plugin.Log.LogInfo((object)$"[SpawnOverride:Dump] {list.Count} setup(s) — sous-chaine du blob a utiliser dans level_enemy_overrides.json :"); for (int i = 0; i < list.Count; i++) { Plugin.Log.LogInfo((object)$"[SpawnOverride:Dump] [{i}] {BuildMobSearchBlob(list[i])}"); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[SpawnOverride:Dump] " + ex.Message)); } finally { ModConfig.DumpEnemyMobKeysOnce.Value = false; Plugin instance = Plugin.Instance; if (instance != null) { ((BaseUnityPlugin)instance).Config.Save(); } } } } private static readonly HashSet<int> LoggedLevelNoRule = new HashSet<int>(); private static readonly HashSet<string> LoggedFallbacks = new HashSet<string>(); private static readonly HashSet<int> AppliedOverrideLevels = new HashSet<int>(); private static int? _lastObservedLevelsCompleted; private static MethodBase TargetMethod() { return AccessTools.Method(typeof(EnemyDirector), "GetEnemy", (Type[])null, (Type[])null); } [HarmonyPostfix] private static void AfterGetEnemy(MethodBase __originalMethod, object __instance, ref object __result) { if (__instance == null) { return; } try { MaybeResetAppliedOverridesForNewRun(); if (!ModConfig.EnableSpawnOverrides.Value || __result == null || !LooksLikeEnemySetupResult(__result)) { return; } int num = RunLevel.TryGetCurrentLevelNumber(); if (num <= 0 || AppliedOverrideLevels.Contains(num)) { return; } if (!LevelEnemyOverrideBank.TryGetMobKey(num, out var mobKey)) { if (ModConfig.LogSpawnOverrides.Value && LoggedLevelNoRule.Add(num)) { Plugin.Log.LogInfo((object)$"[SpawnOverride] Aucun override pour niveau {num}. Spawn vanilla conserve."); } return; } object obj = FindBestSetupMatch(__instance, mobKey, __result); if (obj == null) { if (ModConfig.LogSpawnOverrides.Value) { string item = $"{num}:{mobKey}"; if (LoggedFallbacks.Add(item)) { Plugin.Log.LogWarning((object)($"[SpawnOverride] Niveau {num}: mob '{mobKey}' introuvable, fallback vanilla conserve. " + "Astuce : Spawning -> DumpEnemyMobKeysOnce = true puis lancer une partie pour lister les blobs utilisables.")); } } } else { __result = obj; AppliedOverrideLevels.Add(num); if (ModConfig.LogSpawnOverrides.Value) { Plugin.Log.LogInfo((object)$"[SpawnOverride] Niveau {num}: mob principal force vers '{mobKey}' (applique une seule fois)."); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[SpawnOverride] Erreur runtime: " + ex.Message)); } } private static void MaybeResetAppliedOverridesForNewRun() { try { if ((Object)(object)RunManager.instance == (Object)null) { return; } int levelsCompleted = RunManager.instance.levelsCompleted; if (_lastObservedLevelsCompleted.HasValue && levelsCompleted == 0 && _lastObservedLevelsCompleted.Value > 0) { if (AppliedOverrideLevels.Count > 0 && ModConfig.LogSpawnOverrides.Value) { Plugin.Log.LogInfo((object)"[SpawnOverride] Nouvelle run (levelsCompleted repasse a 0) : reinitialisation des overrides par niveau."); } AppliedOverrideLevels.Clear(); LoggedLevelNoRule.Clear(); LoggedFallbacks.Clear(); } _lastObservedLevelsCompleted = levelsCompleted; } catch { } } private static bool LooksLikeEnemySetupResult(object result) { string name = result.GetType().Name; if (name.IndexOf("Enemy", StringComparison.OrdinalIgnoreCase) >= 0 && name.IndexOf("Setup", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } try { return AccessTools.Field(result.GetType(), "spawnObjects") != null; } catch { return false; } } private static object FindBestSetupMatch(object enemyDirector, string desiredMobKey, object currentResult) { Type type = currentResult.GetType(); List<object> list = CollectEnemySetups(enemyDirector, type); if (list.Count == 0) { return null; } string token = desiredMobKey.Trim().ToLowerInvariant(); foreach (object item in list) { if (SetupContainsToken(item, token)) { return item; } } foreach (object item2 in list) { if (SetupMatchesAlias(item2, token)) { return item2; } } return null; } private static List<object> CollectEnemySetups(object enemyDirector, Type expectedSetupType) { if (RepolibSpawnSupport.TryGetAllEnemySetupsAsObjects(expectedSetupType, out var setups) && setups.Count > 0) { return setups; } return CollectEnemySetupsViaReflection(enemyDirector, expectedSetupType); } private static List<object> CollectEnemySetupsViaReflection(object enemyDirector, Type expectedSetupType) { List<object> list = new List<object>(); List<FieldInfo> declaredFields = AccessTools.GetDeclaredFields(enemyDirector.GetType()); for (int i = 0; i < declaredFields.Count; i++) { FieldInfo fieldInfo = declaredFields[i]; object value; try { value = fieldInfo.GetValue(enemyDirector); } catch { continue; } if (value is IEnumerable enumerable && !(value is string)) { foreach (object item in enumerable) { if (item != null && expectedSetupType.IsInstanceOfType(item) && !list.Contains(item)) { list.Add(item); } } } else if (value != null && expectedSetupType.IsInstanceOfType(value) && !list.Contains(value)) { list.Add(value); } } return list; } private static bool SetupContainsToken(object setup, string token) { return BuildMobSearchBlob(setup).IndexOf(token, StringComparison.OrdinalIgnoreCase) >= 0; } private static bool SetupMatchesAlias(object setup, string token) { string text = BuildMobSearchBlob(setup).ToLowerInvariant(); switch (token) { case "huntsman": case "hunter": return text.Contains("hunter"); case "headman": return text.Contains("headman"); default: return false; } } private static string BuildMobSearchBlob(object setup) { List<string> list = new List<string>(); Type type = setup.GetType(); list.Add(type.Name); string value = Traverse.Create(setup).Field("name").GetValue<string>(); if (!string.IsNullOrWhiteSpace(value)) { list.Add(value); } if (Traverse.Create(setup).Field("spawnObjects").GetValue() is IEnumerable enumerable) { foreach (object item in enumerable) { if (item != null) { AppendSpawnRefTokens(item, list); } } } return string.Join(" ", list); } private static void AppendSpawnRefTokens(object spawnObj, List<string> pieces) { Traverse val = Traverse.Create(spawnObj); GameObject val2 = null; try { object? obj = spawnObj.GetType().GetProperty("Prefab", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(spawnObj, null); val2 = (GameObject)((obj is GameObject) ? obj : null); } catch { } if ((Object)(object)val2 == (Object)null) { string[] array = new string[4] { "prefab", "m_Prefab", "gamePrefab", "m_GamePrefab" }; foreach (string text in array) { try { FieldInfo fieldInfo = AccessTools.Field(spawnObj.GetType(), text); if (!(fieldInfo == null)) { object? value = fieldInfo.GetValue(spawnObj); val2 = (GameObject)((value is GameObject) ? value : null); if ((Object)(object)val2 != (Object)null) { break; } } } catch { } } } if ((Object)(object)val2 != (Object)null) { pieces.Add(((Object)val2).name); EnemyParent component = val2.GetComponent<EnemyParent>(); if ((Object)(object)component != (Object)null && !string.IsNullOrWhiteSpace(component.enemyName)) { pieces.Add(component.enemyName); } } try { string value2 = val.Field("prefabName").GetValue<string>(); if (!string.IsNullOrWhiteSpace(value2)) { pieces.Add(value2); } } catch { } try { string value3 = val.Field("resourcePath").GetValue<string>(); if (!string.IsNullOrWhiteSpace(value3)) { pieces.Add(value3); } } catch { } } } [HarmonyPatch(typeof(PhotonHandler), "LateUpdate")] internal static class PhotonHandlerSoundSyncTickPatch { private static void Postfix() { NobleModSoundSyncNet.DriverTick(); } } internal static class ReplacerToggleFilterPatch { internal static void TryInstall(Harmony harmony) { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Expected O, but got Unknown try { Type type = AccessTools.TypeByName("loaforcsSoundAPI.SoundPacks.SoundReplacementHandler"); if (type == null) { Plugin.Log.LogWarning((object)"[NobleMod] Replacers filter : SoundReplacementHandler introuvable, patch ignore."); return; } MethodInfo[] array = (from m in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where m.Name == "TryGetReplacementClip" select m).ToArray(); if (array.Length == 0) { Plugin.Log.LogWarning((object)"[NobleMod] Replacers filter : TryGetReplacementClip introuvable, patch ignore."); return; } HarmonyMethod val = new HarmonyMethod(typeof(ReplacerToggleFilterPatch), "Postfix", (Type[])null); MethodInfo[] array2 = array; foreach (MethodInfo methodInfo in array2) { try { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)("[NobleMod] Replacers filter : patch installe sur " + methodInfo.DeclaringType?.FullName + "." + methodInfo.Name + ".")); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[NobleMod] Replacers filter : echec patch d'une surcharge : " + ex.Message)); } } } catch (Exception ex2) { Plugin.Log.LogWarning((object)("[NobleMod] Replacers filter : exception install ignoree : " + ex2.Message)); } } private static void Postfix(ref bool __result, ref SoundReplacementGroup group, ref AudioClip clip) { if (!__result || !ReplacerToggleRegistry.IsReady) { return; } try { string text = TryGetFilePath(group); if (string.IsNullOrEmpty(text)) { return; } if (!ReplacerToggleRegistry.IsEnabled(text)) { __result = false; group = null; clip = null; return; } AudioSource currentAudioSource = NobleModSoundEvalContext.CurrentAudioSource; if ((Object)(object)currentAudioSource != (Object)null) { ReplacerToggleRegistry.TrackPlayback(currentAudioSource, text); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[NobleMod] Replacers filter postfix : exception ignoree : " + ex.Message)); } } private static string TryGetFilePath(SoundReplacementGroup group) { if (group == null) { return null; } SoundReplacementCollection parent = group.Parent; if (parent == null) { return null; } return ((object)parent).GetType().GetProperty("FilePath", BindingFlags.Instance | BindingFlags.Public)?.GetValue(parent) as string; } } internal static class SoundApiReplacementSeedPatch { private const int KeyVariantSoundApiReplacement = 7; internal static void TryInstall(Harmony harmony) { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Expected O, but got Unknown //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Expected O, but got Unknown try { Type type = AccessTools.TypeByName("loaforcsSoundAPI.SoundPacks.SoundReplacementHandler"); if (type == null) { Plugin.Log.LogWarning((object)"[NobleMod] SoundApi seed patch : type SoundReplacementHandler introuvable, patch ignore."); return; } MethodInfo[] array = (from m in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where m.Name == "TryGetReplacementClip" select m).ToArray(); if (array.Length == 0) { Plugin.Log.LogWarning((object)"[NobleMod] SoundApi seed patch : methode TryGetReplacementClip introuvable, patch ignore."); return; } if (array.Length > 1) { Plugin.Log.LogWarning((object)$"[NobleMod] SoundApi seed patch : {array.Length} surcharges TryGetReplacementClip trouvees, on patch toutes."); } HarmonyMethod val = new HarmonyMethod(typeof(SoundApiReplacementSeedPatch), "Prefix", (Type[])null); HarmonyMethod val2 = new HarmonyMethod(typeof(SoundApiReplacementSeedPatch), "Postfix", (Type[])null); MethodInfo[] array2 = array; foreach (MethodInfo methodInfo in array2) { try { harmony.Patch((MethodBase)methodInfo, val, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)("[NobleMod] SoundApi seed patch : patch installe sur " + methodInfo.DeclaringType?.FullName + "." + methodInfo.Name + ".")); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[NobleMod] SoundApi seed patch : echec patch d'une surcharge : " + ex.Message)); } } } catch (Exception ex2) { Plugin.Log.LogWarning((object)("[NobleMod] SoundApi seed patch : exception inattendue lors de l'install, patch ignore : " + ex2.Message)); } } private static void Prefix(string[] name, ref State __state) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) __state = Random.state; if (ModConfig.EnableMultiplayerSoundSync == null || !ModConfig.EnableMultiplayerSoundSync.Value || !NobleModSoundSyncNet.IsActiveInRoom || name == null || name.Length < 3) { return; } try { AudioSource currentAudioSource = NobleModSoundEvalContext.CurrentAudioSource; uint num = NobleModSoundSyncKey.BuildSnapshotKeyFromTokens(name[0], name[1], name[2], currentAudioSource, 7); if (NobleModSoundSyncNet.TryLookup(num, out var value)) { Random.InitState((int)num ^ value); if (ModConfig.LogMultiplayerSoundSync != null && ModConfig.LogMultiplayerSoundSync.Value) { Plugin.Log.LogInfo((object)$"[NobleMod] SoundApi seed patch : seed Random pour {name[0]}:{name[1]}:{name[2]} (key=0x{num:X8})."); } } } catch (Exception arg) { Plugin.Log.LogError((object)$"[NobleMod] SoundApi seed patch : exception Prefix : {arg}"); } } private static void Postfix(State __state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) Random.state = __state; } } internal static class AudioSourceTimeToClipClamp { private const float UnderEndEpsilon = 0.0001f; internal static void Clamp(AudioSource src) { if ((Object)(object)src == (Object)null || !Object.op_Implicit((Object)(object)src) || !Object.op_Implicit((Object)(object)src.clip)) { return; } float length = src.clip.length; if (!(length < 0.001f)) { float time = src.time; if (float.IsNaN(time) || float.IsInfinity(time)) { src.time = 0f; } else if (time < 0f) { src.time = 0f; } else if (time >= length) { src.time = Mathf.Max(0f, length - 0.0001f); } } } } [HarmonyPatch(typeof(AudioSourceAdditionalData), "Update")] internal static class SoundApiUpdateEvalContextPatch { [HarmonyPrefix] private static void Prefix(AudioSourceAdditionalData __instance) { if ((Object)(object)((__instance != null) ? __instance.Source : null) != (Object)null) { AudioSourceTimeToClipClamp.Clamp(__instance.Source); NobleModSoundEvalContext.Enter(__instance.Source); } } [HarmonyPostfix] private static void Postfix(AudioSourceAdditionalData __instance) { try { if ((Object)(object)((__instance != null) ? __instance.Source : null) != (Object)null) { AudioSourceTimeToClipClamp.Clamp(__instance.Source); } } finally { if ((Object)(object)((__instance != null) ? __instance.Source : null) != (Object)null) { NobleModSoundEvalContext.Exit(); } } } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] internal static class AudioSourceClipSetterClampPatch { private static void Postfix(AudioSource __instance) { AudioSourceTimeToClipClamp.Clamp(__instance); } } internal static class SoundPackLoadedDiscoveryPatch { internal static void TryInstall(Harmony harmony) { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Expected O, but got Unknown try { Type type = AccessTools.TypeByName("loaforcsSoundAPI.SoundPacks.SoundPackDataHandler"); if (type == null) { Plugin.Log.LogWarning((object)"[NobleMod] Replacers discovery : SoundPackDataHandler introuvable, decouverte ignoree."); return; } MethodInfo[] array = (from m in type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where m.Name == "AddLoadedPack" select m).ToArray(); if (array.Length == 0) { Plugin.Log.LogWarning((object)"[NobleMod] Replacers discovery : AddLoadedPack introuvable."); return; } HarmonyMethod val = new HarmonyMethod(typeof(SoundPackLoadedDiscoveryPatch), "Postfix", (Type[])null); MethodInfo[] array2 = array; foreach (MethodInfo methodInfo in array2) { try { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)("[NobleMod] Replacers discovery : patch postfix installe sur " + methodInfo.DeclaringType?.FullName + "." + methodInfo.Name + ".")); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[NobleMod] Replacers discovery : echec patch surcharge : " + ex.Message)); } } } catch (Exception ex2) { Plugin.Log.LogWarning((object)("[NobleMod] Replacers discovery : exception install ignoree : " + ex2.Message)); } } private static void Postfix(SoundPack pack) { if (pack == null) { return; } try { ReplacerToggleRegistry.DiscoverPack(pack); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[NobleMod] Replacers discovery postfix : " + ex.Message)); } } } } namespace NobleMod.Multiplayer { internal static class NobleModSoundSyncKey { private const double QuantumNonStickySeconds = 0.25; private const double MinStickyQuantumSeconds = 0.5; private const string NobleModPackName = "noblemod"; private static readonly StringBuilder _sb = new StringBuilder(); internal static uint BuildStickyKey(SoundReplacementGroup group, AudioSource source, float clipLengthSec, int variant) { long occurrenceBucket = ComputeStickyOccurrence(clipLengthSec); return BuildInternal(group, source, occurrenceBucket, variant); } internal static uint BuildNonStickyKey(SoundReplacementGroup group, AudioSource source, int variant) { long occurrenceBucket = ComputeNonStickyOccurrence(); return BuildInternal(group, source, occurrenceBucket, variant); } internal static uint BuildSnapshotKey(SoundReplacementGroup group, AudioSource source, int variant) { return BuildInternal(group, source, 0L, variant); } internal static uint BuildSnapshotKeyFromTokens(string parent, string objectName, string clip, AudioSource source, int variant) { int value = TryGetViewId(source); _sb.Clear(); _sb.Append("tk|"); _sb.Append(parent ?? string.Empty); _sb.Append(':'); _sb.Append(objectName ?? string.Empty); _sb.Append(':'); _sb.Append(clip ?? string.Empty); _sb.Append('|'); _sb.Append(value); _sb.Append('|'); _sb.Append(variant); return Fnv1a32(_sb.ToString()); } private static uint BuildInternal(SoundReplacementGroup group, AudioSource source, long occurrenceBucket, int variant) { int value = TryGetViewId(source); string value2 = TryGetVanillaClipName(source); _sb.Clear(); _sb.Append("gr|"); if (group != null && group.Matches != null && group.Matches.Count > 0) { for (int i = 0; i < group.Matches.Count; i++) { if (i > 0) { _sb.Append(','); } _sb.Append(group.Matches[i] ?? string.Empty); } } else { _sb.Append('*'); } _sb.Append('|'); _sb.Append(value); _sb.Append('|'); _sb.Append(value2); _sb.Append('|'); _sb.Append(occurrenceBucket); _sb.Append('|'); _sb.Append(variant); return Fnv1a32(_sb.ToString()); } private static long ComputeStickyOccurrence(float clipLengthSec) { double num = Math.Max(0.5, clipLengthSec); return (long)Math.Floor(SafePhotonTime() / num); } private static long ComputeNonStickyOccurrence() { return (long)Math.Floor(SafePhotonTime() / 0.25); } private static double SafePhotonTime() { try { return PhotonNetwork.Time; } catch { return 0.0; } } private static int TryGetViewId(AudioSource source) { if ((Object)(object)source == (Object)null || !Object.op_Implicit((Object)(object)source)) { return 0; } try { PhotonView componentInParent = ((Component)source).GetComponentInParent<PhotonView>(); return ((Object)(object)componentInParent != (Object)null) ? componentInParent.ViewID : 0; } catch { return 0; } } private static string TryGetVanillaClipName(AudioSource source) { try { if ((Object)(object)source == (Object)null || !Object.op_Implicit((Object)(object)source) || (Object)(object)source.clip == (Object)null) { return string.Empty; } string text = ((Object)source.clip).name ?? string.Empty; if (text.StartsWith("noblemod ", StringComparison.OrdinalIgnoreCase)) { return text.Substring("noblemod".Length + 1).Trim(); } return text; } catch { return string.Empty; } } internal static uint Fnv1a32(string s) { uint num = 2166136261u; if (s == null) { return num; } for (int i = 0; i < s.Length; i++) { num ^= s[i]; num *= 16777619; } return num; } } internal static class NobleModSoundSyncNet { private const byte ProtocolVersion = 1; private const float RefreshDebounceSeconds = 0.5f; private const float MasterPollingIntervalSeconds = 1f; private const float HeartbeatLogIntervalSeconds = 15f; private static int[] _pool; private static long _poolGeneration; private static float _localPoolBornAtRealtime = -1f; private static bool _initialized; private static NetworkedEvent _event; private static int _lastBroadcastFrame = int.MinValue; private static bool _lastInRoom; private static bool _lastIsMaster; private static int _lastPlayerCount; private static float _nextMasterPollAt; private static float _nextHeartbeatLogAt; private static bool _firstTickLogged; private static readonly RaiseEventOptions PoolRaiseOptions = new RaiseEventOptions { Receivers = (ReceiverGroup)1, CachingOption = (EventCaching)5 }; internal static bool IsActiveInRoom { get { try { return PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom != null && PhotonNetwork.CurrentRoom.PlayerCount > 1; } catch { return false; } } } internal static int CurrentPoolSize { get { int[] pool = _pool; if (pool == null) { return 0; } return pool.Length; } } internal static long CurrentPoolGeneration => _poolGeneration; internal static void Initialize() { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown if (_initialized) { return; } _initialized = true; try { _event = new NetworkedEvent("NobleMod:SoundSyncPool", (Action<EventData>)OnEventReceived); Plugin.Log.LogInfo((object)$"[NobleMod] SoundSyncNet : NetworkedEvent enregistre (eventCode={_event.EventCode}). IMPORTANT : les