Decompiled source of Unified Chaos Pack v0.0.15
BepInEx/plugins/TeamChaos.UnifiedChaosPack.dll
Decompiled a month 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.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using CSync.Lib; using CSync.Util; using FacilityMeltdown; using GameNetcodeStuff; using HarmonyLib; using JetBrains.Annotations; using LCSoundTool; using LethalConfig; using LethalConfig.ConfigItems; using LethalConfig.ConfigItems.Options; using Microsoft.CodeAnalysis; using MoreShipUpgrades.Managers; using Newtonsoft.Json; using TMPro; using TeamChaos.UnifiedChaosPack.Config; using TeamChaos.UnifiedChaosPack.NetcodePatcher; using TeamChaos.UnifiedChaosPack.NetworkBehaviors; using TeamChaos.UnifiedChaosPack.PluginManagers; using TeamChaos.UnifiedChaosPack.PluginManagers.MobPlus; using TeamChaos.UnifiedChaosPack.PluginManagers.MobPlus.Coroutines; using TeamChaos.UnifiedChaosPack.PluginManagers.MobPlus.EventBrokers; using TeamChaos.UnifiedChaosPack.PluginManagers.MobPlus.Notifiers; using TeamChaos.UnifiedChaosPack.Schema; using Unity.Netcode; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UIElements; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: AssemblyCompany("Pirahtays")] [assembly: AssemblyConfiguration("release")] [assembly: AssemblyDescription("Unified Mod Pack / Tweaks created by Pirahtays")] [assembly: AssemblyFileVersion("0.0.1.0")] [assembly: AssemblyInformationalVersion("0.0.1")] [assembly: AssemblyProduct("TeamChaos.UnifiedChaosPack")] [assembly: AssemblyTitle("TeamChaos.UnifiedChaosPack")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] [module: NetcodePatchedAssembly] internal class <Module> { static <Module>() { } } 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 TeamChaos.UnifiedChaosPack { public static class GeneralLethalPatches { private static Harmony HarmonyInstance { get; } = new Harmony(Guid.NewGuid().ToString()); private static List<T> FindByName<T>(Func<IEnumerable<T>> selector, params string[] names) where T : MemberInfo { return (from o in selector() where names.Contains(o.Name) select o).ToList(); } public static List<ConstructorInfo> FindConstructorsByName(Type type, params string[] methodNames) { return FindByName(() => AccessTools.GetDeclaredConstructors(type, (bool?)null), methodNames); } public static List<MethodInfo> FindMethodsByName(Type type, params string[] methodNames) { return FindByName(() => AccessTools.GetDeclaredMethods(type), methodNames); } public static List<PropertyInfo> FindPropertiesByName(Type type, params string[] methodNames) { return FindByName(() => AccessTools.GetDeclaredProperties(type), methodNames); } public static void PatchMethods(GeneralConfig config) { } public static bool __AnnoyingLogStopper() { return false; } } public static class PluginPackageInfo { public const string PLUGIN_GUID = "TeamChaos.UnifiedChaosPack"; public const string PLUGIN_NAME = "UnifiedChaosPack"; public const string PLUGIN_VERSION = "0.0.1"; } [BepInPlugin("TeamChaos.UnifiedChaosPack", "UnifiedChaosPack", "0.0.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class UnifiedChaosPlugin : BaseUnityPlugin { private const string DEPENDENCY_CSYNC = "com.sigurd.csync"; private const string DEPENDENCY_LCSOUNDTOOL = "LCSoundTool"; private const string DEPENDENCY_LETHALCONFIG = "ainavt.lc.lethalconfig"; public const string DEPENDENCY_LGU = "com.malco.lethalcompany.moreshipupgrades"; private const string DEPENDENCY_FACILITY_MELTDOWN = "me.loaforc.facilitymeltdown"; private readonly GeneralConfig _config = new GeneralConfig(); private readonly Dictionary<Type, UnifiedChaosPluginManager> _pluginMap = new Dictionary<Type, UnifiedChaosPluginManager>(); private GameObject _gameObject; public AssetBundle UnityAssetBundle { get; } public static GeneralConfig GeneralConfig => Instance._config; public static UnifiedChaosPlugin Instance { get; private set; } public UnifiedChaosPlugin() { Instance = this; UnityAssetBundle = AssetBundle.LoadFromFile(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "unifiedchaospack")); } private void Awake() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown ((BaseUnityPlugin)this).Logger.LogInfo((object)"Plugin TeamChaos.UnifiedChaosPack is initializing!"); NetcodePatcher(); _gameObject = new GameObject("TeamChaos.UnifiedChaosPack"); _config.Init(); GeneralLethalPatches.PatchMethods(_config); UnifiedChaosPluginManager<OpenDoorsInSpacePluginManager, OpenDoorsInSpaceConfig>.Create(this); UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Create(this); UnifiedChaosPluginManager<MoreMobSoundsPluginManager, MobPlusPluginManagerConfig>.Create(this); ((Object)_gameObject).hideFlags = (HideFlags)61; Object.DontDestroyOnLoad((Object)(object)_gameObject); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Plugin TeamChaos.UnifiedChaosPack is loaded!"); } public T GetPlugin<T>() where T : UnifiedChaosPluginManager { Type typeFromHandle = typeof(T); if (!_pluginMap.ContainsKey(typeFromHandle)) { T val = (((Object)(object)_gameObject != (Object)null) ? _gameObject.GetComponent<T>() : null); if ((Object)(object)val == (Object)null) { return null; } _pluginMap.Add(typeFromHandle, val); } return (T)_pluginMap[typeFromHandle]; } public T RegisterPlugin<T>() where T : UnifiedChaosPluginManager { Type typeFromHandle = typeof(T); if (!_pluginMap.ContainsKey(typeFromHandle)) { _pluginMap.Remove(typeFromHandle); } return _gameObject.AddComponent<T>(); } public void Log(LogLevel logLevel, string callingPlugin, string message, bool verboseOnly = false) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (!verboseOnly || GeneralConfig.IsVerboseLogging) { ((BaseUnityPlugin)this).Logger.Log(logLevel, (object)("[UnifiedChaos::" + callingPlugin + "]:" + message)); } } public void LogDebug(string callingPlugin, string message, bool verboseOnly = false) { Log((LogLevel)32, callingPlugin, message, verboseOnly); } public void LogMessage(string callingPlugin, string message, bool verboseOnly = false) { Log((LogLevel)8, callingPlugin, message, verboseOnly); } public void LogAll(string callingPlugin, string message, bool verboseOnly = false) { Log((LogLevel)63, callingPlugin, message, verboseOnly); } public void LogError(string callingPlugin, string message, bool verboseOnly = false) { Log((LogLevel)2, callingPlugin, message, verboseOnly); } public void LogFatal(string callingPlugin, string message, bool verboseOnly = false) { Log((LogLevel)1, callingPlugin, message, verboseOnly); } public void LogInfo(string callingPlugin, string message, bool verboseOnly = false) { Log((LogLevel)16, callingPlugin, message, verboseOnly); } public void LogNone(string callingPlugin, string message, bool verboseOnly = false) { Log((LogLevel)0, callingPlugin, message, verboseOnly); } public void LogWarning(string callingPlugin, string message, bool verboseOnly = false) { Log((LogLevel)4, callingPlugin, message, verboseOnly); } private void NetcodePatcher() { Type[] types = Assembly.GetExecutingAssembly().GetTypes(); Type[] array = types; foreach (Type type in array) { MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic); MethodInfo[] array2 = methods; foreach (MethodInfo methodInfo in array2) { object[] customAttributes = methodInfo.GetCustomAttributes(typeof(RuntimeInitializeOnLoadMethodAttribute), inherit: false); if (customAttributes.Length != 0) { Instance.LogDebug(((object)this).GetType().Name, "NetcodePatcher - Invoking " + methodInfo.DeclaringType.Name + "::" + methodInfo.Name + " "); methodInfo.Invoke(null, null); MethodInfo methodInfo2 = AccessTools.FindIncludingBaseTypes<MethodInfo>(methodInfo.DeclaringType, (Func<Type, MethodInfo>)((Type t) => t.GetMethod("Patch"))); if (!(methodInfo2 == null)) { Instance.LogDebug(((object)this).GetType().Name, "NetcodePatcher - Patching " + methodInfo.DeclaringType.Name + "(" + methodInfo2.DeclaringType.Name + ")::" + methodInfo2.Name + " "); methodInfo2.Invoke(null, null); } } } } } } public static class MyPluginInfo { public const string PLUGIN_GUID = "TeamChaos.UnifiedChaosPack"; public const string PLUGIN_NAME = "TeamChaos.UnifiedChaosPack"; public const string PLUGIN_VERSION = "0.0.1"; } } namespace TeamChaos.UnifiedChaosPack.Schema { public class AudioFileInfo { [JsonProperty(PropertyName = "file")] [CanBeNull] public string Filename { get; set; } [JsonProperty(PropertyName = "chance")] public int Chance { get; set; } = 100; [JsonProperty(PropertyName = "is-silence")] public bool IsSilence { get; set; } [JsonProperty(PropertyName = "is-default")] public bool IsDefault { get; set; } } public class AudioMetadata { [JsonRequired] [JsonProperty(PropertyName = "type")] public string Type { get; set; } [JsonRequired] [JsonProperty(PropertyName = "field")] public string Field { get; set; } [JsonProperty(PropertyName = "exclusive")] public bool IsPlayerExclusive { get; set; } [JsonRequired] [JsonProperty(PropertyName = "replacement-file-data")] public List<AudioFileInfo> AudioFileInfo { get; set; } = new List<AudioFileInfo>(); } public class AudioMetadataCollection { private const string BASE_FILENAME = "sound-data"; private readonly Dictionary<ulong, List<PlayerSpecificAudioMetadata>> _playerSpecificData; private List<AudioMetadata> _defaultAudioMetadata; private AudioMetadataCollection() { _defaultAudioMetadata = Enumerable.Empty<AudioMetadata>().ToList(); _playerSpecificData = new Dictionary<ulong, List<PlayerSpecificAudioMetadata>>(); } public void Dump() { UnifiedChaosPlugin.Instance.LogDebug("AudioMetadataCollection", $"Dumping all AudioMetadata - _default:{_defaultAudioMetadata.Count}, _playerSpecific:{_playerSpecificData.SelectMany((KeyValuePair<ulong, List<PlayerSpecificAudioMetadata>> p) => p.Value).Count()}"); IEnumerable<IGrouping<(string, string), (ulong?, AudioMetadata)>> enumerable = from a in _playerSpecificData.SelectMany((KeyValuePair<ulong, List<PlayerSpecificAudioMetadata>> d) => ((IEnumerable<PlayerSpecificAudioMetadata>)d.Value).Select((Func<PlayerSpecificAudioMetadata, (ulong?, AudioMetadata)>)((PlayerSpecificAudioMetadata i) => (d.Key, i)))).Union(((IEnumerable<AudioMetadata>)_defaultAudioMetadata).Select((Func<AudioMetadata, (ulong?, AudioMetadata)>)((AudioMetadata d) => (null, d)))) group a by (a.Value.Field, a.Value.Type); foreach (IGrouping<(string, string), (ulong?, AudioMetadata)> item in enumerable) { UnifiedChaosPlugin.Instance.LogDebug("AudioMetadataCollection", "Audio Metadata " + item.Key.Item2 + "::" + item.Key.Item1); foreach (var item2 in item) { foreach (AudioFileInfo item3 in item2.Item2.AudioFileInfo) { string message = ((!(item2.Item2 is PlayerSpecificAudioMetadata)) ? $"\t Available files: {item3.Filename}[{item3.Chance}] - Default:{item3.IsDefault}, Silent:{item3.IsSilence}" : $"\t Available files[{item2.Item1}]: {item3.Filename}[{item3.Chance}] - Default:{item3.IsDefault}, Silent:{item3.IsSilence}, IsExclusive:{item2.Item2.IsPlayerExclusive}"); UnifiedChaosPlugin.Instance.LogDebug("AudioMetadataCollection", message); } } } } public void Dump<T>(string field, ulong? playerId = null) { List<AudioFileInfo> filteredData = GetFilteredData<T>(field, playerId); UnifiedChaosPlugin.Instance.LogDebug("AudioMetadataCollection", $"Dumping filtered AudioMetadata - _data:{filteredData.Count}"); foreach (AudioFileInfo item in filteredData) { UnifiedChaosPlugin.Instance.LogDebug("AudioMetadataCollection", "Audio Metadata " + typeof(T).Name + "::" + field); string message = $"\t Available files: {item.Filename}[{item.Chance}] - Default:{item.IsDefault}, Silent:{item.IsSilence}"; UnifiedChaosPlugin.Instance.LogDebug("AudioMetadataCollection", message); } } public IEnumerable<string> GetAudioIdsToLoad() { return (from a in _defaultAudioMetadata.SelectMany((AudioMetadata r) => r.AudioFileInfo).Union(_playerSpecificData.Values.SelectMany((List<PlayerSpecificAudioMetadata> r) => r.SelectMany((PlayerSpecificAudioMetadata p) => p.AudioFileInfo))) where !string.IsNullOrWhiteSpace(a.Filename) select a.Filename).Distinct(); } private static List<T> GetAudioMetadataFromFile<T>(string file) where T : AudioMetadata { if (!File.Exists(file)) { throw new FileNotFoundException("Json file not found!", file); } string text = File.ReadAllText(file); return JsonConvert.DeserializeObject<List<T>>(text); } public List<AudioFileInfo> GetFilteredData<T>(string field, ulong? playerId = null) { string type = typeof(T).Name; IEnumerable<AudioFileInfo> enumerable = _defaultAudioMetadata.FirstOrDefault((AudioMetadata a) => a.Field == field && a.Type == type)?.AudioFileInfo; IEnumerable<AudioFileInfo> enumerable2 = enumerable ?? Enumerable.Empty<AudioFileInfo>(); if (playerId.HasValue && _playerSpecificData.ContainsKey(playerId.Value)) { PlayerSpecificAudioMetadata playerSpecificAudioMetadata = _playerSpecificData[playerId.Value].FirstOrDefault((PlayerSpecificAudioMetadata a) => a.Field == field && a.Type == type); enumerable = playerSpecificAudioMetadata?.AudioFileInfo; IEnumerable<AudioFileInfo> enumerable3 = enumerable ?? Enumerable.Empty<AudioFileInfo>(); enumerable2 = ((playerSpecificAudioMetadata != null && playerSpecificAudioMetadata.IsPlayerExclusive) ? enumerable3 : enumerable2.Union(enumerable3)); } return enumerable2.ToList(); } public static AudioMetadataCollection LoadAudioMetadataCollection(string path) { Regex regex = new Regex("sound-data\\.?(\\d+)*\\.json"); DirectoryInfo directoryInfo = new DirectoryInfo(path); IEnumerable<FileInfo> enumerable = directoryInfo.EnumerateFiles("sound-data*.json"); AudioMetadataCollection audioMetadataCollection = new AudioMetadataCollection(); UnifiedChaosPlugin.Instance.LogInfo("AudioMetadataCollection", "[LoadAudioMetadataCollection]: Begin Load from '" + path + "'"); foreach (FileInfo item in enumerable) { UnifiedChaosPlugin.Instance.LogInfo("AudioMetadataCollection", string.Format("[{0}]: Loading file: '{1}'", "LoadAudioMetadataCollection", item)); if (string.Equals(item.Name, "sound-data.json", StringComparison.InvariantCultureIgnoreCase)) { audioMetadataCollection._defaultAudioMetadata = GetAudioMetadataFromFile<AudioMetadata>(item.FullName); continue; } Group group = regex.Match(item.Name.ToLower())?.Groups[1]; if (group != null && group.Success && ulong.TryParse(group.Value, out var result)) { List<PlayerSpecificAudioMetadata> audioMetadataFromFile = GetAudioMetadataFromFile<PlayerSpecificAudioMetadata>(item.FullName); audioMetadataCollection._playerSpecificData.Add(result, audioMetadataFromFile); } } if (audioMetadataCollection._defaultAudioMetadata.SelectMany((AudioMetadata r) => r.AudioFileInfo).Union(audioMetadataCollection._playerSpecificData.Values.SelectMany((List<PlayerSpecificAudioMetadata> r) => r.SelectMany((PlayerSpecificAudioMetadata p) => p.AudioFileInfo))).Any((AudioFileInfo a) => string.IsNullOrWhiteSpace(a.Filename) && !a.IsSilence && !a.IsDefault)) { UnifiedChaosPlugin.Instance.LogError("AudioMetadataCollection", "Audio Manifest has empty filenames for records not flagged as default or silence!"); } return audioMetadataCollection; } } public class AudioPatchMetadata : AudioMetadata { [JsonRequired] [JsonProperty(PropertyName = "method")] public string Method { get; set; } [JsonRequired] [JsonProperty(PropertyName = "patch-method")] public string PatchMethod { get; set; } } public class PlayerSpecificAudioMetadata : AudioMetadata { [JsonProperty(PropertyName = "exclusive")] public new bool IsPlayerExclusive { get; set; } } } namespace TeamChaos.UnifiedChaosPack.PluginManagers { public class MoreMobSoundsPluginManager : UnifiedChaosPluginManager<MoreMobSoundsPluginManager, MobPlusPluginManagerConfig> { private const string PATCH_DATA_FILE = "sound-data.json"; private readonly Dictionary<Type, MobPlusPatcher> _patchers = new Dictionary<Type, MobPlusPatcher>(); private readonly Dictionary<Type, EventBroker> _patchersNew = new Dictionary<Type, EventBroker>(); private AudioMetadataCollection _audioMetadata; protected override string PluginFriendlyName => "MobPlus"; private EnemyAI GetNetworkEnemyInstance(Type type, ulong id) { if (!_patchersNew.TryGetValue(type, out var value)) { return null; } return value?.GetEnemyInstanceByNetworkId(id); } private void HandlePlayAudioEvent(RandomAudioSyncNetworkHandler.AudioEventArgs e) { LogWarning(string.Format("[{0}]: {1}:'{2}', {3}:'{4}', {5}:'{6}'", "HandlePlayAudioEvent", "SourceType", e.SourceType, "SourceID", e.SourceID, "AudioKey", e.AudioKey)); EnemyAI networkEnemyInstance = GetNetworkEnemyInstance(e.SourceType, e.SourceID); if (!Object.op_Implicit((Object)(object)networkEnemyInstance)) { LogError(string.Format("[{0}]: Enemy instance not found! ({1}:{2})", "HandlePlayAudioEvent", e.SourceType, e.SourceID)); } else if (!e.Fallback) { HandlePlayAudioEvent(networkEnemyInstance, e.AudioKey); } } private void HandlePlayAudioEvent(EnemyAI ai, string audioKey) { bool flag = !string.IsNullOrWhiteSpace(audioKey); AudioClip preloadedAudioClip; if (!flag || !Object.op_Implicit((Object)(object)(preloadedAudioClip = GetPreloadedAudioClip(audioKey)))) { string message = (flag ? ("[HandlePlayAudioEvent]: Audioclip not found/loaded! (" + ((object)ai).GetType().Name + ":" + audioKey + ")") : "[HandlePlayAudioEvent]: Audiokey is null or blank!"); LogInfo(message); } else { ((MonoBehaviour)this).StartCoroutine(PlayAndWait(ai.creatureVoice, preloadedAudioClip)); } } private IEnumerator PlayAndWait(AudioSource audioSource, AudioClip clip) { AudioSource.PlayOneShotHelper(audioSource, clip, 1f); yield return (object)new WaitForSeconds(clip.length); } private void PlayOneShot<T>(T ai, string field, Action<T> fallback = null, ulong? playerId = null) where T : EnemyAI { UnifiedChaosPluginNetworkHandler<RandomAudioSyncNetworkHandler>.Instance.PlayRandomOneShot<T>(ai, field, _audioMetadata, (Action<string>)delegate(string audioKey) { HandlePlayAudioEvent((EnemyAI)(object)ai, audioKey); }, fallback, playerId); } public override void Start() { _config.Init(); LogInfo(((object)this).GetType().Name + " Start"); _audioMetadata = AudioMetadataCollection.LoadAudioMetadataCollection(base.FullSoundPathForPlugin); _audioMetadata.Dump(); LoadAudio(_audioMetadata.GetAudioIdsToLoad()); _patchersNew.InitEventBroker(EventBroker<HoarderBugAINotifier, HoarderBugAI, HoarderBugEventBroker>.GetInstance(), InitHoarderBugPatches).InitEventBroker(EventBroker<FlowermanAINotifier, FlowermanAI, FlowermanEventBroker>.GetInstance(), InitFlowermanPatches).InitEventBroker(EventBroker<SpringManAINotifier, SpringManAI, SpringManEventBroker>.GetInstance(), InitSpringManPatches); UnifiedChaosPluginNetworkHandler<RandomAudioSyncNetworkHandler>.NetworkSpawn += delegate { UnifiedChaosPluginManager<MoreMobSoundsPluginManager, MobPlusPluginManagerConfig>.Instance.LogError("Called: NetworkSpawn"); RandomAudioSyncNetworkHandler.PlayAudioEvent += UnifiedChaosPluginManager<MoreMobSoundsPluginManager, MobPlusPluginManagerConfig>.Instance.HandlePlayAudioEvent; }; UnifiedChaosPluginNetworkHandler<RandomAudioSyncNetworkHandler>.NetworkDespawn += delegate { UnifiedChaosPluginManager<MoreMobSoundsPluginManager, MobPlusPluginManagerConfig>.Instance.LogError("Called: NetworkDespawn"); RandomAudioSyncNetworkHandler.PlayAudioEvent -= UnifiedChaosPluginManager<MoreMobSoundsPluginManager, MobPlusPluginManagerConfig>.Instance.HandlePlayAudioEvent; }; } private void InitBaboonBirdPatches(BaboonBirdExtender obj) { } private void InitBlobPatches(BlobExtender obj) { } private void InitCentipedePatches(CentipedeExtender obj) { } private void InitCrawlerPatches(CrawlerExtender obj) { } private void InitDocileLocustBeesPatches(DocileLocustBeesExtender obj) { } private void InitDoublewingPatches(DoublewingExtender obj) { } private void InitDressGirlPatches(DressGirlExtender obj) { } public void InitFlowermanPatches(FlowermanEventBroker broker) { broker.AngryAtPlayer += delegate(FlowermanAI ai, PlayerControllerB player) { PlayOneShot<FlowermanAI>(ai, "AngryAtPlayer", (Action<FlowermanAI>)null, player?.playerSteamId); }; broker.DroppingBody += delegate(FlowermanAI ai, bool isHome) { PlayOneShot<FlowermanAI>(ai, isHome ? "DroppingBodyAtHome" : "DroppingBody", (Action<FlowermanAI>)null, (ulong?)null); }; broker.EvadingPlayer += delegate(FlowermanAI ai, PlayerControllerB player) { PlayOneShot<FlowermanAI>(ai, "EvadingPlayer", (Action<FlowermanAI>)null, player?.playerSteamId); }; broker.KilledPlayer += delegate(FlowermanAI ai, bool carryingBody, PlayerControllerB player) { PlayOneShot<FlowermanAI>(ai, carryingBody ? "KilledPlayerCarryingBody" : "KilledPlayer", (Action<FlowermanAI>)null, player?.playerSteamId); }; broker.StaringDownPlayer += delegate(FlowermanAI ai, PlayerControllerB player) { PlayOneShot<FlowermanAI>(ai, "StaringDownPlayer", (Action<FlowermanAI>)null, player?.playerSteamId); }; } private void InitForestGiantPatches(ForestGiantExtender obj) { } private void InitHoarderBugPatches(HoarderBugEventBroker broker) { broker.AngryAtPlayer += delegate(HoarderBugAI ai, PlayerControllerB player) { PlayOneShot<HoarderBugAI>(ai, "AngryAtPlayer", (Action<HoarderBugAI>)null, player?.playerSteamId); }; broker.ChangedWatchingPlayer += delegate(HoarderBugAI ai, PlayerControllerB player) { PlayOneShot<HoarderBugAI>(ai, "ChangedWatchingPlayer", (Action<HoarderBugAI>)delegate(HoarderBugAI bugAI) { RoundManager.PlayRandomClip(((EnemyAI)bugAI).creatureVoice, bugAI.chitterSFX, true, 1f, 0, 1000); }, player?.playerSteamId); }; broker.ItemDroppedInNest += delegate(HoarderBugAI ai, HoarderBugItem item) { PlayOneShot<HoarderBugAI>(ai, "ItemDroppedInNest", (Action<HoarderBugAI>)null, (ulong?)null); }; broker.HuntingItem += delegate(HoarderBugAI ai, GrabbableObject item) { PlayOneShot<HoarderBugAI>(ai, "HuntingItem", (Action<HoarderBugAI>)null, (ulong?)null); }; broker.ItemGrabbed += delegate(HoarderBugAI ai, HoarderBugItem item) { PlayOneShot<HoarderBugAI>(ai, "ItemGrabbed", (Action<HoarderBugAI>)null, (ulong?)null); }; } private void InitJesterPatches(JesterExtender obj) { } private void InitLassoManPatches(LassoManExtender obj) { } private void InitMaskedPlayerEnemyPatches(MaskedPlayerEnemyExtender obj) { } private void InitMouthDogPatches(MouthDogExtender obj) { } private void InitNutcrackerEnemyPatches(NutcrackerEnemyExtender obj) { } private void InitPufferAIPatches(PufferExtender obj) { } private void InitRedLocustBeesPatches(RedLocustBeesExtender obj) { } private void InitSandSpiderPatches(SandSpiderExtender obj) { } private void InitSandWormPatches(SandWormExtender obj) { } private void InitSpringManPatches(SpringManEventBroker broker) { broker.StaringAtPlayer += delegate(SpringManAI ai, PlayerControllerB player) { PlayOneShot<SpringManAI>(ai, "StaringAtPlayer", (Action<SpringManAI>)null, player?.playerSteamId); }; broker.StaringAtPlayerForAWhile += delegate(SpringManAI ai, PlayerControllerB player) { PlayOneShot<SpringManAI>(ai, "StaringAtPlayerForAWhile", (Action<SpringManAI>)null, player?.playerSteamId); }; broker.StaringAtPlayerForALongTime += delegate(SpringManAI ai, PlayerControllerB player) { PlayOneShot<SpringManAI>(ai, "StaringAtPlayerForALongTime", (Action<SpringManAI>)null, player?.playerSteamId); }; } } public class MultiSoundReplacePluginManager : UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig> { private const string PATCH_DATA_FILE = "soundpatch.json"; protected List<AudioPatchMetadata> _replacementData = new List<AudioPatchMetadata>(); protected override string PluginFriendlyName => "MultiSoundReplacer"; public override void Start() { _config.Init(); LogInfo(((object)this).GetType().Name + " Start"); LoadDynamicPatchData(); LoadAudio(GetAudioIdsToLoad()); DynamicPatch(); } private void LoadDynamicPatchData() { string path = Path.Combine(base.FullSoundPathForPlugin, "soundpatch.json"); if (File.Exists(path)) { string text = File.ReadAllText(path); _replacementData = JsonConvert.DeserializeObject<List<AudioPatchMetadata>>(text); } } private void DynamicPatch() { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Expected O, but got Unknown string contents = JsonConvert.SerializeObject((object)_replacementData, (Formatting)1); File.WriteAllText(Path.Combine(base.FullSoundPathForPlugin, "debug-test.json"), contents); Harmony val = new Harmony(Guid.NewGuid().ToString()); foreach (var item2 in GetMethodsToPatch()) { string item = item2.PatchMethod; if (!(item == "FacilityMeltdownAssetReplace")) { if (item == "StaticAudioClipArrayReplace") { StaticAudioClipArrayReplace(item2); continue; } UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.LogInfo("MultiSoundReplacePluginManager::DynamicPatch - Patching " + item2.Type + "::" + item2.Method + " with " + item2.PatchMethod); Type type = AccessTools.TypeByName(item2.Type); MethodInfo methodInfo = AccessTools.Method(type, item2.Method, (Type[])null, (Type[])null); MethodInfo method = ((object)this).GetType().GetMethod(item2.PatchMethod); val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.LogInfo("MultiSoundReplacePluginManager::DynamicPatch - Patching " + item2.Type + "::" + item2.Method + " with " + item2.PatchMethod); FacilityMeltdownAssetReplace(item2); } } } public static void TestPatch(ref object __instance, MethodBase __originalMethod) { } private IEnumerable<(string Type, string Method, string PatchMethod)> GetMethodsToPatch() { return _replacementData.Select((AudioPatchMetadata r) => (r.Type, r.Method, r.PatchMethod)).Distinct(); } private IEnumerable<AudioPatchMetadata> GetAudioPatchesForMethod(string type, string method, [CallerMemberName] string patchMethod = null) { return _replacementData.Where((AudioPatchMetadata r) => r.PatchMethod == patchMethod && r.Type == type && r.Method == method); } private IEnumerable<string> GetAudioIdsToLoad() { return _replacementData.SelectMany((AudioPatchMetadata r) => r.AudioFileInfo.Select((AudioFileInfo a) => a.Filename)).Distinct(); } public static void FacilityMeltdownAssetReplace((string Type, string Method, string PatchMethod) methodInfo) { Type type = AccessTools.TypeByName(methodInfo.Type); if (type == null) { UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.LogError("'" + methodInfo.Type + "' was not found - mod may not be installed!"); return; } MeltdownPlugin val = AccessTools.StaticFieldRefAccess<MeltdownPlugin>(type, "instance"); if ((Object)(object)val == (Object)null) { UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.LogError("'" + methodInfo.Type + "' instance was not found - mod may not be installed!"); return; } PropertyInfo propertyInfo = AccessTools.Property(type, "assets"); object value = propertyInfo.GetValue(val); foreach (AudioPatchMetadata item in UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.GetAudioPatchesForMethod(methodInfo.Type, methodInfo.Method, "FacilityMeltdownAssetReplace")) { UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.LogWarning("Implementing Patches on " + type.Name + "::" + item.Method); Traverse val2 = Traverse.Create(value).Property(methodInfo.Method, (object[])null); if (item.AudioFileInfo.Count == 1) { AudioFileInfo audioFileInfo = item.AudioFileInfo.Single(); val2.SetValue((object)UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.GetPreloadedAudioClips(audioFileInfo.Filename, val2.GetValue<AudioClip[]>())); continue; } AudioClip preloadedAudioClip = UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.GetPreloadedAudioClip(item.AudioFileInfo.OrderByDescending((AudioFileInfo a) => a.Chance).First().Filename); val2.SetValue((object)new AudioClip[1] { preloadedAudioClip }); foreach (AudioFileInfo item2 in item.AudioFileInfo) { SoundTool.ReplaceAudioClip(preloadedAudioClip, UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.GetPreloadedAudioClip(item2.Filename), (float)item2.Chance / 100f); } } } public static void StaticAudioClipArrayReplace((string Type, string Method, string PatchMethod) methodInfo) { UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.LogInfo("MultiSoundReplacePluginManager::StaticAudioClipArrayReplace - Patching " + methodInfo.Type + "::" + methodInfo.Method); Type type = AccessTools.TypeByName(methodInfo.Type); if (type == null) { UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.LogError("'" + methodInfo.Type + "' was not found - mod may not be installed!"); return; } foreach (AudioPatchMetadata item in UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.GetAudioPatchesForMethod(methodInfo.Type, string.Empty, "StaticAudioClipArrayReplace")) { UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.LogWarning("Implementing Patches on " + type.Name + "::" + item.Field); FieldInfo fieldInfo = AccessTools.Field(type, item.Field); if (fieldInfo == null) { UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.LogError("'" + type.Name + "." + item.Field + "' was not found - mod may be incompatible or not found!"); break; } if (fieldInfo.FieldType != typeof(AudioClip[])) { UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.LogError("'" + type.Name + "." + item.Field + "' is not of type AudioClip[]!"); break; } AudioClip[] original = AccessTools.StaticFieldRefAccess<AudioClip[]>(type, item.Field); Traverse val = Traverse.Create(type).Field(item.Field); if (item.AudioFileInfo.Count == 1) { AudioFileInfo audioFileInfo = item.AudioFileInfo.Single(); val.SetValue((object)UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.GetPreloadedAudioClips(audioFileInfo.Filename, original)); continue; } AudioClip preloadedAudioClip = UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.GetPreloadedAudioClip(item.AudioFileInfo.OrderByDescending((AudioFileInfo a) => a.Chance).First().Filename); val.SetValue((object)new AudioClip[1] { preloadedAudioClip }); foreach (AudioFileInfo item2 in item.AudioFileInfo) { SoundTool.ReplaceAudioClip(preloadedAudioClip, UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.GetPreloadedAudioClip(item2.Filename), (float)item2.Chance / 100f); } } } public static void InstanceAudioClipArrayPatch(ref object __instance, MethodBase __originalMethod) { Type type = __instance.GetType(); UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.LogInfo("MultiSoundReplacePluginManager::InstanceAudioClipArrayPatch - Patching " + type.Name + "::" + __originalMethod.Name); foreach (AudioPatchMetadata item in UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.GetAudioPatchesForMethod(type.Name, __originalMethod.Name, "InstanceAudioClipArrayPatch")) { FieldInfo fieldInfo = AccessTools.Field(type, item.Field); if (fieldInfo == null) { UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.LogError("'" + type.Name + "." + item.Field + "' was not found!"); break; } if (fieldInfo.FieldType != typeof(AudioClip[])) { UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.LogError("'" + type.Name + "." + item.Field + "' is not of type AudioClip[]!"); break; } AudioClip[] original = fieldInfo.GetValue(__instance) as AudioClip[]; if (item.AudioFileInfo.Count == 1) { AudioFileInfo audioFileInfo = item.AudioFileInfo.Single(); fieldInfo.SetValue(__instance, UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.GetPreloadedAudioClips(audioFileInfo.Filename, original)); continue; } AudioClip preloadedAudioClip = UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.GetPreloadedAudioClip(item.AudioFileInfo.OrderByDescending((AudioFileInfo a) => a.Chance).First().Filename); fieldInfo.SetValue(__instance, new AudioClip[1] { preloadedAudioClip }); foreach (AudioFileInfo item2 in item.AudioFileInfo) { SoundTool.ReplaceAudioClip(preloadedAudioClip, UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.GetPreloadedAudioClip(item2.Filename), (float)item2.Chance / 100f); } } } public static void InstanceAudioClipPatch(ref object __instance, MethodBase __originalMethod) { Type type = __instance.GetType(); UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.LogInfo("MultiSoundReplacePluginManager::InstanceAudioClipPatch - Patching " + type.Name + "::" + __originalMethod.Name); foreach (AudioPatchMetadata item in UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.GetAudioPatchesForMethod(type.Name, __originalMethod.Name, "InstanceAudioClipPatch")) { FieldInfo fieldInfo = AccessTools.Field(type, item.Field); if (fieldInfo == null) { UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.LogError("'" + type.Name + "." + item.Field + "' was not found!"); break; } if (fieldInfo.FieldType != typeof(AudioClip)) { UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.LogError("'" + type.Name + "." + item.Field + "' is not of type AudioClip!"); break; } object? value = fieldInfo.GetValue(__instance); AudioClip val = (AudioClip)((value is AudioClip) ? value : null); if (item.AudioFileInfo.Count == 1) { AudioFileInfo audioFileInfo = item.AudioFileInfo.Single(); fieldInfo.SetValue(__instance, UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.GetPreloadedAudioClip(audioFileInfo.Filename) ?? val); continue; } AudioClip preloadedAudioClip = UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.GetPreloadedAudioClip(item.AudioFileInfo.OrderByDescending((AudioFileInfo a) => a.Chance).First().Filename); fieldInfo.SetValue(__instance, preloadedAudioClip); foreach (AudioFileInfo item2 in item.AudioFileInfo) { SoundTool.ReplaceAudioClip(preloadedAudioClip, UnifiedChaosPluginManager<MultiSoundReplacePluginManager, MultiSoundReplaceConfig>.Instance.GetPreloadedAudioClip(item2.Filename), (float)item2.Chance / 100f); } } } } public class OpenDoorsInSpacePluginManager : UnifiedChaosPluginManager<OpenDoorsInSpacePluginManager, OpenDoorsInSpaceConfig> { private class FiredMessagePatcher { private const string ELEMENT_TEXT = "MaskImage/HeaderText"; private const string ELEMENT_SUBTEXT = "MaskImage/HeaderText (1)"; private TextMeshProUGUI _youAreFiredDueToNegligenceSubtext; private TextMeshProUGUI _youAreFiredDueToNegligenceText; private GameObject _youAreFiredSubtext; private GameObject _youAreFiredText; private GameObject FindOriginalGameOverElement(string element) { return GameObject.Find("/Systems/UI/Canvas/GameOverScreen/" + element); } public void Init(bool force = false) { if ((Object)(object)_youAreFiredText == (Object)null) { _youAreFiredText = FindOriginalGameOverElement("MaskImage/HeaderText"); } if ((Object)(object)_youAreFiredSubtext == (Object)null) { _youAreFiredSubtext = FindOriginalGameOverElement("MaskImage/HeaderText (1)"); } if (force && (Object)(object)_youAreFiredDueToNegligenceText != (Object)null) { _youAreFiredDueToNegligenceText = null; } if ((Object)(object)_youAreFiredDueToNegligenceText == (Object)null) { _youAreFiredDueToNegligenceText = ReplaceTextElement("MaskImage/HeaderText", "(OpenDoorsInSpace fired text)", UnifiedChaosPluginManager<OpenDoorsInSpacePluginManager, OpenDoorsInSpaceConfig>.Instance._config.EjectedMessageText); } if (force && (Object)(object)_youAreFiredDueToNegligenceSubtext != (Object)null) { _youAreFiredDueToNegligenceSubtext = null; } if ((Object)(object)_youAreFiredDueToNegligenceSubtext == (Object)null) { _youAreFiredDueToNegligenceSubtext = ReplaceTextElement("MaskImage/HeaderText (1)", "(OpenDoorsInSpace fired subtext)", UnifiedChaosPluginManager<OpenDoorsInSpacePluginManager, OpenDoorsInSpaceConfig>.Instance._config.EjectedMessageSubtext); } } public void Activate() { _youAreFiredText.gameObject.SetActive(false); ((Component)_youAreFiredDueToNegligenceText).gameObject.SetActive(true); _youAreFiredSubtext.gameObject.SetActive(false); ((Component)_youAreFiredDueToNegligenceSubtext).gameObject.SetActive(true); } public IEnumerator Deactivate() { yield return (object)new WaitForSeconds(1f); GameObject youAreFiredText = _youAreFiredText; if (youAreFiredText != null) { GameObject gameObject = youAreFiredText.gameObject; if (gameObject != null) { gameObject.SetActive(true); } } TextMeshProUGUI youAreFiredDueToNegligenceText = _youAreFiredDueToNegligenceText; if (youAreFiredDueToNegligenceText != null) { GameObject gameObject2 = ((Component)youAreFiredDueToNegligenceText).gameObject; if (gameObject2 != null) { gameObject2.SetActive(false); } } GameObject youAreFiredSubtext = _youAreFiredSubtext; if (youAreFiredSubtext != null) { GameObject gameObject3 = youAreFiredSubtext.gameObject; if (gameObject3 != null) { gameObject3.SetActive(true); } } TextMeshProUGUI youAreFiredDueToNegligenceSubtext = _youAreFiredDueToNegligenceSubtext; if (youAreFiredDueToNegligenceSubtext != null) { GameObject gameObject4 = ((Component)youAreFiredDueToNegligenceSubtext).gameObject; if (gameObject4 != null) { gameObject4.SetActive(false); } } } private TextMeshProUGUI ReplaceTextElement(string element, string suffix, string text) { GameObject val = FindOriginalGameOverElement(element); GameObject val2 = Object.Instantiate<GameObject>(val, val.transform.parent); ((Object)val2).name = ((Object)val2).name + " " + suffix; val2.SetActive(false); TextMeshProUGUI component = val2.GetComponent<TextMeshProUGUI>(); ((TMP_Text)component).text = text; return component; } } public class LguPatches { [HarmonyPatch(typeof(LguStore), "PlayersFiredServerRpc")] [HarmonyPrefix] public static bool LguStore_PlayersFiredServerRpc_Prefix() { return !UnifiedChaosPluginManager<OpenDoorsInSpacePluginManager, OpenDoorsInSpaceConfig>.Instance.IsEjectingDueToNegligence; } } private const string SOUND_DOORWARN = "door-open-warning"; private const string SOUND_DOORERROR = "door-error"; private const string SOUND_AIRBLAST = "fartwithreverb"; private AudioSource _airBlastAudioSource; private FiredMessagePatcher _firedMessagePatcher; public bool IsEjectingDueToNegligence { get; internal set; } protected override string PluginFriendlyName => "OpenDoorsInSpace"; public override void Start() { _config.Init(); _config.ConfigValueUpdated += ConfigValueUpdated; LogInfo(((object)this).GetType().Name + " Start"); SceneManager.activeSceneChanged += HandleActiveSceneChanged; _airBlastAudioSource = ((Component)this).gameObject.AddComponent<AudioSource>(); _airBlastAudioSource.loop = false; _airBlastAudioSource.spatialBlend = 0f; _firedMessagePatcher = new FiredMessagePatcher(); LoadAudio(new string[3] { "door-open-warning", "fartwithreverb", "door-error" }); UnifiedChaosPluginNetworkHandler<OpenDoorsInSpaceNetworkHandler>.NetworkSpawn += delegate { UnifiedChaosPluginManager<OpenDoorsInSpacePluginManager, OpenDoorsInSpaceConfig>.Instance._firedMessagePatcher.Init(); OpenDoorsInSpaceNetworkHandler.OpenDoorEvent += UnifiedChaosPluginManager<OpenDoorsInSpacePluginManager, OpenDoorsInSpaceConfig>.Instance.HandleOpenDoorEvent; }; UnifiedChaosPluginNetworkHandler<OpenDoorsInSpaceNetworkHandler>.NetworkDespawn += delegate { OpenDoorsInSpaceNetworkHandler.OpenDoorEvent -= UnifiedChaosPluginManager<OpenDoorsInSpacePluginManager, OpenDoorsInSpaceConfig>.Instance.HandleOpenDoorEvent; }; } private void ConfigValueUpdated(object sender, string configKey) { } private void HandleActiveSceneChanged(Scene prevScene, Scene activeScene) { ResetEjectingDueToNegligance(); } public void PressDoorOpenServerRPC() { if ((NetworkManager.Singleton.IsHost || NetworkManager.Singleton.IsServer) && !StartOfRound.Instance.firingPlayersCutsceneRunning && !IsEjectingDueToNegligence) { if (!_config.IsEnabled || Random.Range(0f, 100f) > _config.DoorOpenPercent) { UnifiedChaosPluginNetworkHandler<OpenDoorsInSpaceNetworkHandler>.Instance.DoorOpenErrorServerRPC(); } else if (StartOfRound.Instance.firingPlayersCutsceneRunning || IsEjectingDueToNegligence) { LogInfo("Could not eject due to negligence: cutscene is already running"); } else { UnifiedChaosPluginNetworkHandler<OpenDoorsInSpaceNetworkHandler>.Instance.EjectDueToNegligenceServerRPC(); } } } private IEnumerator PlayDoorError() { if (_config.PlayDoorError) { AudioClip preloadedAudioClip = GetPreloadedAudioClip("door-error"); if (!((Object)(object)preloadedAudioClip == (Object)null)) { StartOfRound.Instance.shipDoorAudioSource.PlayOneShot(preloadedAudioClip, _config.DoorErrorVolume); yield return (object)new WaitForSeconds(preloadedAudioClip.length); } } } private IEnumerator PlayDoorOpenWarning() { IsEjectingDueToNegligence = true; StartOfRound shipManager = StartOfRound.Instance; AudioClip preloadedAudioClip = GetPreloadedAudioClip("door-open-warning"); if ((Object)(object)preloadedAudioClip != (Object)null) { StartOfRound.Instance.shipDoorAudioSource.PlayOneShot(preloadedAudioClip, 1f); yield return (object)new WaitForSeconds(preloadedAudioClip.length); } yield return (object)new WaitForSeconds(0.1f); _firedMessagePatcher.Activate(); if (NetworkManager.Singleton.IsHost || NetworkManager.Singleton.IsServer) { shipManager.ManuallyEjectPlayersServerRpc(); } } public void ResetEjectingDueToNegligance() { LogInfo("Reseting ..."); if ((Object)(object)StartOfRound.Instance != (Object)null) { ((MonoBehaviour)this).StartCoroutine(_firedMessagePatcher.Deactivate()); } IsEjectingDueToNegligence = false; } public IEnumerator PlayersEjected(bool abridgedVersion) { AudioClip airBlast = GetPreloadedAudioClip("fartwithreverb"); StartOfRound startOfRound = StartOfRound.Instance; startOfRound.shipDoorsAnimator.SetBool("OpenInOrbit", true); startOfRound.shipDoorAudioSource.PlayOneShot(startOfRound.airPressureSFX); startOfRound.starSphereObject.SetActive(true); startOfRound.starSphereObject.transform.position = ((Component)GameNetworkManager.Instance.localPlayerController).transform.position; yield return (object)new WaitForSeconds(0.25f); startOfRound.suckingPlayersOutOfShip = true; startOfRound.suckingFurnitureOutOfShip = true; PlaceableShipObject[] array = Object.FindObjectsOfType<PlaceableShipObject>(); for (int i = 0; i < array.Length; i++) { if ((Object)(object)array[i].parentObject == (Object)null) { Debug.Log((object)("Error! No parentObject for placeable object: " + startOfRound.unlockablesList.unlockables[array[i].unlockableID].unlockableName)); } array[i].parentObject.StartSuckingOutOfShip(); if (startOfRound.unlockablesList.unlockables[array[i].unlockableID].spawnPrefab) { Collider[] componentsInChildren = ((Component)array[i].parentObject).GetComponentsInChildren<Collider>(); foreach (Collider val in componentsInChildren) { val.enabled = false; } } } GameNetworkManager.Instance.localPlayerController.inSpecialInteractAnimation = true; GameNetworkManager.Instance.localPlayerController.DropAllHeldItems(true, false); HUDManager.Instance.UIAudio.PlayOneShot(startOfRound.suckedIntoSpaceSFX); yield return (object)new WaitForSeconds(6f); SoundManager.Instance.SetDiageticMixerSnapshot(3, 2f); HUDManager.Instance.ShowPlayersFiredScreen(true); _airBlastAudioSource.PlayOneShot(airBlast, 1f); yield return (object)new WaitForSeconds(2f); startOfRound.starSphereObject.SetActive(false); startOfRound.shipDoorAudioSource.Stop(); startOfRound.speakerAudioSource.Stop(); _airBlastAudioSource.Stop(); startOfRound.suckingFurnitureOutOfShip = false; yield return (object)new WaitForSeconds(4f); GameNetworkManager.Instance.localPlayerController.TeleportPlayer(startOfRound.playerSpawnPositions[GameNetworkManager.Instance.localPlayerController.playerClientId].position, false, 0f, false, true); startOfRound.shipDoorsAnimator.SetBool("OpenInOrbit", false); startOfRound.currentPlanetPrefab.transform.position = ((Component)startOfRound.planetContainer).transform.position; startOfRound.suckingPlayersOutOfShip = false; startOfRound.choseRandomFlyDirForPlayer = false; startOfRound.suckingPower = 0f; startOfRound.shipRoomLights.SetShipLightsOnLocalClientOnly(true); yield return (object)new WaitForSeconds(2f); if (((NetworkBehaviour)startOfRound).IsServer) { startOfRound.playersRevived++; startOfRound.playersRevived = 0; startOfRound.EndPlayersFiredSequenceClientRpc(); } else { startOfRound.PlayerHasRevivedServerRpc(); } } public override void Patch() { AddPatchForSoftDependency("com.malco.lethalcompany.moreshipupgrades", typeof(LguPatches)); } private void HandleOpenDoorEvent(OpenDoorsInSpaceNetworkHandler.NetworkEvent networkEvent) { switch (networkEvent) { case OpenDoorsInSpaceNetworkHandler.NetworkEvent.DoorOpenPressed: PressDoorOpenServerRPC(); break; case OpenDoorsInSpaceNetworkHandler.NetworkEvent.DoorActivatedPlayError: ((MonoBehaviour)this).StartCoroutine(PlayDoorError()); break; case OpenDoorsInSpaceNetworkHandler.NetworkEvent.DoorActivatedEjectPlayers: ((MonoBehaviour)this).StartCoroutine(PlayDoorOpenWarning()); break; } } [HarmonyPatch(typeof(HangarShipDoor), "Update")] [HarmonyPostfix] public static void HangarShipDoor_Update_Postfix(HangarShipDoor __instance) { if (!__instance.triggerScript.interactable && StartOfRound.Instance.inShipPhase) { UnifiedChaosPluginManager<OpenDoorsInSpacePluginManager, OpenDoorsInSpaceConfig>.Instance.LogInfo("Forcing door buttons to be interactable"); __instance.triggerScript.interactable = true; } } [HarmonyPatch(typeof(HangarShipDoor), "PlayDoorAnimation")] [HarmonyPrefix] public static bool HangarShipDoor_PlayDoorAnimation_Prefix(bool closed) { if (closed || !StartOfRound.Instance.inShipPhase) { return true; } UnifiedChaosPluginNetworkHandler<OpenDoorsInSpaceNetworkHandler>.Instance.PressDoorOpenServerRPC(); return true; } [HarmonyPatch(typeof(StartOfRound), "ResetShip")] [HarmonyPrefix] public static bool StartOfRound_ResetShip_Prefix() { return !UnifiedChaosPluginManager<OpenDoorsInSpacePluginManager, OpenDoorsInSpaceConfig>.Instance.IsEjectingDueToNegligence; } [HarmonyPatch(typeof(StartOfRound), "PlayFirstDayShipAnimation")] [HarmonyPrefix] public static bool StartOfRound_PlayFirstDayShipAnimation_Prefix() { if (!UnifiedChaosPluginManager<OpenDoorsInSpacePluginManager, OpenDoorsInSpaceConfig>.Instance.IsEjectingDueToNegligence) { return true; } UnifiedChaosPluginManager<OpenDoorsInSpacePluginManager, OpenDoorsInSpaceConfig>.Instance.ResetEjectingDueToNegligance(); return false; } [HarmonyPatch(typeof(StartOfRound), "playersFiredGameOver")] [HarmonyPrefix] public static bool StartOfRound_playersFiredGameOver_Enumerator_Prefix(bool abridgedVersion, ref IEnumerator __result) { if (!UnifiedChaosPluginManager<OpenDoorsInSpacePluginManager, OpenDoorsInSpaceConfig>.Instance.IsEjectingDueToNegligence) { return true; } __result = UnifiedChaosPluginManager<OpenDoorsInSpacePluginManager, OpenDoorsInSpaceConfig>.Instance.PlayersEjected(abridgedVersion); return false; } } public abstract class UnifiedChaosPluginManager<TPlugin, TConfig> : UnifiedChaosPluginManager where TPlugin : UnifiedChaosPluginManager<TPlugin, TConfig> where TConfig : new() { protected TConfig _config = new TConfig(); public static TPlugin Instance => UnifiedChaosPlugin.Instance.GetPlugin<TPlugin>(); public static void Create(UnifiedChaosPlugin basePlugin) { UnifiedChaosPluginManager.Log(basePlugin, (LogLevel)16, "UnifiedChaosPlugin", $"Registering instance of \"{typeof(TPlugin)}\""); TPlugin val = basePlugin.RegisterPlugin<TPlugin>(); val._basePlugin = basePlugin; Harmony.CreateAndPatchAll(((object)val).GetType(), (string)null); val.Patch(); } public virtual void Patch() { } protected void AddPatchForSoftDependency(string key, Type patches) { if (Chainloader.PluginInfos.ContainsKey(key)) { LogInfo("Found '" + Chainloader.PluginInfos[key].Metadata.Name + "' - Adding Patches"); Harmony.CreateAndPatchAll(patches, (string)null); } } } public abstract class UnifiedChaosPluginManager : MonoBehaviour { private readonly Dictionary<string, List<AudioClip>> _audioClips = new Dictionary<string, List<AudioClip>>(); protected UnifiedChaosPlugin _basePlugin; protected abstract string PluginFriendlyName { get; } protected string PluginDir => Path.GetRelativePath(Paths.PluginPath, Path.GetDirectoryName(((BaseUnityPlugin)_basePlugin).Info.Location)); protected string SoundPathForPlugin => Path.Combine("Sounds", PluginFriendlyName); protected string FullSoundPathForPlugin => Path.Combine(Paths.PluginPath, PluginDir, Path.Combine("Sounds", PluginFriendlyName)); public abstract void Start(); protected static void Log(UnifiedChaosPlugin basePlugin, LogLevel logLevel, string pluginName, string message) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) basePlugin.Log(logLevel, pluginName, message); } protected void Log(LogLevel logLevel, string message) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) Log(_basePlugin, logLevel, PluginFriendlyName, message); } protected void LogDebug(string message) { Log((LogLevel)32, message); } protected void LogMessage(string message) { Log((LogLevel)8, message); } protected void LogAll(string message) { Log((LogLevel)63, message); } protected void LogError(string message) { Log((LogLevel)2, message); } protected void LogFatal(string message) { Log((LogLevel)1, message); } protected void LogInfo(string message) { Log((LogLevel)16, message); } protected void LogNone(string message) { Log((LogLevel)0, message); } protected void LogWarning(string message) { Log((LogLevel)4, message); } protected void LoadAudio(IEnumerable<string> audioIds) { string[] validExtensions = new string[3] { ".ogg", ".wav", ".mp3" }; Func<FileInfo, string, bool> selector = (FileInfo f, string s) => f.Name.StartsWith(s, ignoreCase: true, CultureInfo.InvariantCulture); Func<FileInfo, bool> filter = (FileInfo f) => validExtensions.Contains(f.Extension.ToLower()); if (!audioIds.Any()) { return; } string fullSoundPathForPlugin = FullSoundPathForPlugin; if (!Directory.Exists(Path.Combine(fullSoundPathForPlugin))) { throw new DirectoryNotFoundException(fullSoundPathForPlugin); } DirectoryInfo directoryInfo = new DirectoryInfo(fullSoundPathForPlugin); FileInfo[] source = (from f in directoryInfo.EnumerateFiles() where audioIds.Any((string i) => filter(f) && selector(f, i)) select f).ToArray(); foreach (string audioId in audioIds) { if (!_audioClips.ContainsKey(audioId)) { _audioClips.Add(audioId, new List<AudioClip>()); } foreach (FileInfo item in source.Where((FileInfo f) => selector(f, audioId))) { LogDebug("Audio Found:('" + audioId + "')" + item.Name); AudioClip audioClip = SoundTool.GetAudioClip(PluginDir, SoundPathForPlugin, item.Name); _audioClips[audioId].Add(audioClip); } } } [CanBeNull] protected AudioClip GetPreloadedAudioClip(string key) { if (!_audioClips.ContainsKey(key) || !_audioClips[key].Any()) { return null; } return _audioClips[key].First(); } protected AudioClip[] GetPreloadedAudioClips(string key, [CanBeNull] AudioClip[] original = null) { object source; if (!_audioClips.ContainsKey(key) || !_audioClips[key].Any()) { IEnumerable<AudioClip> enumerable = original; source = enumerable ?? Enumerable.Empty<AudioClip>(); } else { IEnumerable<AudioClip> enumerable = _audioClips[key]; source = enumerable; } return ((IEnumerable<AudioClip>)source).ToArray(); } protected AudioClip[] GetPreloadedAudioClips(IEnumerable<string> keys, [CanBeNull] AudioClip[] original = null) { IEnumerable<AudioClip> enumerable = _audioClips.Where((KeyValuePair<string, List<AudioClip>> c) => keys.Contains(c.Key)).SelectMany((KeyValuePair<string, List<AudioClip>> c) => c.Value); object source; if (!enumerable.Any()) { source = original ?? Enumerable.Empty<AudioClip>(); } else { source = enumerable; } return ((IEnumerable<AudioClip>)source).ToArray(); } } } namespace TeamChaos.UnifiedChaosPack.PluginManagers.MobPlus { public class BaboonBirdExtender : MobPlusPatcher<BaboonBirdExtender, BaboonBirdAI> { protected BaboonBirdExtender() { } } public class BlobExtender : MobPlusPatcher<BlobExtender, BlobAI> { protected BlobExtender() { } } public class CentipedeExtender : MobPlusPatcher<CentipedeExtender, CentipedeAI> { protected CentipedeExtender() { } } public class CrawlerExtender : MobPlusPatcher<CrawlerExtender, CrawlerAI> { protected CrawlerExtender() { } } public class DocileLocustBeesExtender : MobPlusPatcher<DocileLocustBeesExtender, DocileLocustBeesAI> { protected DocileLocustBeesExtender() { } } public class DoublewingExtender : MobPlusPatcher<DoublewingExtender, DoublewingAI> { protected DoublewingExtender() { } } public class DressGirlExtender : MobPlusPatcher<DressGirlExtender, DressGirlAI> { protected DressGirlExtender() { } } public class FlowermanExtender : MobPlusPatcher<FlowermanExtender, FlowermanAI> { public enum BehaviorState { Search, Moving, Angry } public event Action<FlowermanAI, PlayerControllerB> AngryAtPlayer; public event Action<FlowermanAI, bool> DroppingBody; public event Action<FlowermanAI, PlayerControllerB> EvadingPlayer; public event Action<FlowermanAI, bool, PlayerControllerB> KilledPlayer; public event Action<FlowermanAI, PlayerControllerB> StaringDownPlayer; protected FlowermanExtender() { base.ChangeBehaviorStateLocalClient += OnChangeBehaviorStateLocalClient; } [HarmonyPrefix] [HarmonyPatch(typeof(FlowermanAI), "DropPlayerBody")] public static void __DropPlayerBody(FlowermanAI __instance) { if (__instance.carryingPlayerBody) { MobPlusPatcher<FlowermanExtender, FlowermanAI>._instance.DroppingBody?.Invoke(__instance, MobPlusPatcher<FlowermanExtender, FlowermanAI>._instance.NearFavoriteSpot(__instance)); } } [HarmonyPrefix] [HarmonyPatch(typeof(FlowermanAI), "FinishKillAnimation")] public static void __FinishKillAnimation(FlowermanAI __instance, bool carryingBody) { MobPlusPatcher<FlowermanExtender, FlowermanAI>._instance.KilledPlayer?.Invoke(__instance, carryingBody, __instance.bodyBeingCarried?.playerScript); } [HarmonyPostfix] [HarmonyPatch(typeof(FlowermanAI), "LookAtFlowermanTrigger")] public static void __LookAtFlowermanTrigger(FlowermanAI __instance, bool __state) { if (__instance.evadeModeStareDown && !__state) { MobPlusPatcher<FlowermanExtender, FlowermanAI>._instance.StaringDownPlayer?.Invoke(__instance, ((EnemyAI)__instance).targetPlayer); } } [HarmonyPrefix] [HarmonyPatch(typeof(FlowermanAI), "LookAtFlowermanTrigger")] public static void __LookAtFlowermanTriggerPrefix(FlowermanAI __instance, ref bool __state) { __state = __instance.evadeModeStareDown; } [HarmonyPostfix] [HarmonyPatch(typeof(FlowermanAI), "Update")] public static void __Update(FlowermanAI __instance, bool __state) { if (__instance.wasInEvadeMode && !__state) { MobPlusPatcher<FlowermanExtender, FlowermanAI>._instance.EvadingPlayer?.Invoke(__instance, ((EnemyAI)__instance).targetPlayer); } } [HarmonyPrefix] [HarmonyPatch(typeof(FlowermanAI), "Update")] public static void __UpdatePrefix(FlowermanAI __instance, ref bool __state) { __state = __instance.wasInEvadeMode; } private bool NearFavoriteSpot(FlowermanAI ai) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) return (double)Vector3.Distance(((Component)ai).transform.position, ((EnemyAI)ai).favoriteSpot.position) < 7.0; } private void OnChangeBehaviorStateLocalClient(FlowermanAI ai, int state) { switch ((BehaviorState)state) { case BehaviorState.Angry: this.AngryAtPlayer?.Invoke(ai, ((EnemyAI)ai).targetPlayer); break; default: UnifiedChaosPlugin.Instance.LogWarning("FlowermanExtender", $"Unknown behavior state: {state}"); break; case BehaviorState.Search: case BehaviorState.Moving: break; } } } public class ForestGiantExtender : MobPlusPatcher<ForestGiantExtender, ForestGiantAI> { protected ForestGiantExtender() { } } public class HoarderBugExtender : MobPlusPatcher<HoarderBugExtender, HoarderBugAI> { public enum BehaviorState { Searching, Resting, Angry } public event Action<HoarderBugAI, PlayerControllerB> AngryAtPlayer; public event Action<HoarderBugAI, PlayerControllerB> ChangedWatchingPlayer; public event Action<HoarderBugAI, GrabbableObject> HuntingItem; public event Action<HoarderBugAI, HoarderBugItem> ItemDroppedInNest; public event Action<HoarderBugAI, HoarderBugItem> ItemGrabbed; protected HoarderBugExtender() { base.ChangeBehaviorStateLocalClient += OnChangeBehaviorStateLocalClient; } [HarmonyPatch(typeof(HoarderBugAI), "DetectAndLookAtPlayers")] [HarmonyTranspiler] public static IEnumerable<CodeInstruction> __ChangedWatchingPlayer(IEnumerable<CodeInstruction> instructions) { MethodInfo methodInfo = AccessTools.Method(typeof(HoarderBugExtender), "__ChangedWatchingPlayerHook", new Type[1] { typeof(HoarderBugAI) }, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(RoundManager), "PlayRandomClip", new Type[5] { typeof(AudioSource), typeof(AudioClip[]), typeof(bool), typeof(float), typeof(int) }, (Type[])null); List<CodeInstruction> list = new List<CodeInstruction>(); foreach (CodeInstruction instruction in instructions) { if (CodeInstructionExtensions.Calls(instruction, methodInfo2)) { list.RemoveRange(list.Count - 7, 7); list.Add(new CodeInstruction(OpCodes.Ldarg_0, (object)null)); list.Add(new CodeInstruction(OpCodes.Call, (object)methodInfo)); } else { list.Add(instruction); } } foreach (CodeInstruction item in list) { yield return item; } } private static int __ChangedWatchingPlayerHook(HoarderBugAI ai) { MobPlusPatcher<HoarderBugExtender, HoarderBugAI>._instance.OnChangedWatchingPlayer(ai); return int.MinValue; } [HarmonyPostfix] [HarmonyPatch(typeof(HoarderBugAI), "DropItem")] public static void __DropItem(HoarderBugAI __instance, bool droppingInNest, HoarderBugItem __state) { if (droppingInNest) { MobPlusPatcher<HoarderBugExtender, HoarderBugAI>._instance.ItemDroppedInNest?.Invoke(__instance, __state); } } [HarmonyPrefix] [HarmonyPatch(typeof(HoarderBugAI), "DropItem")] public static void __DropItemPrefix(HoarderBugAI __instance, ref HoarderBugItem __state) { __state = __instance.heldItem; } [HarmonyPostfix] [HarmonyPatch(typeof(HoarderBugAI), "GrabTargetItemIfClose")] public static void __GrabTargetItemIfClose(HoarderBugAI __instance, bool __result) { if (__result) { MobPlusPatcher<HoarderBugExtender, HoarderBugAI>._instance.ItemGrabbed?.Invoke(__instance, __instance.heldItem); } } private void OnChangeBehaviorStateLocalClient(HoarderBugAI ai, int state) { switch ((BehaviorState)state) { case BehaviorState.Searching: this.HuntingItem?.Invoke(ai, ai.targetItem); break; case BehaviorState.Angry: this.AngryAtPlayer?.Invoke(ai, ((EnemyAI)ai).targetPlayer); break; default: UnifiedChaosPlugin.Instance.LogWarning("HoarderBugExtender", $"Unknown behavior state: {state}"); break; case BehaviorState.Resting: break; } } private void OnChangedWatchingPlayer(HoarderBugAI ai) { this.ChangedWatchingPlayer?.Invoke(ai, ai.watchingPlayer); } } public class JesterExtender : MobPlusPatcher<JesterExtender, JesterAI> { protected JesterExtender() { } } public class LassoManExtender : MobPlusPatcher<LassoManExtender, LassoManAI> { protected LassoManExtender() { } } public class MaskedPlayerEnemyExtender : MobPlusPatcher<MaskedPlayerEnemyExtender, MaskedPlayerEnemy> { protected MaskedPlayerEnemyExtender() { } } public static class Extensions { public static Dictionary<Type, EventBroker> InitEventBroker<T>(this Dictionary<Type, EventBroker> collection, T instance, Action<T> initializer) where T : EventBroker, new() { if (collection.ContainsKey(typeof(T))) { return collection; } collection.Add(instance.EnemyType, instance); initializer(instance); return collection; } public static Dictionary<Type, MobPlusPatcher> InitPatcher<T>(this Dictionary<Type, MobPlusPatcher> collection, T instance, Action<T> initializer) where T : MobPlusPatcher { if (collection.ContainsKey(typeof(T))) { return collection; } collection.Add(instance.EnemyType, instance); initializer(instance); return collection; } } public abstract class MobPlusPatcher<TMobPlus, TEnemyAI> : MobPlusPatcher<TEnemyAI> where TMobPlus : MobPlusPatcher<TMobPlus, TEnemyAI> where TEnemyAI : EnemyAI { protected class CommonModPlusPatches { public static Harmony HarmonyInstance { get; } = new Harmony(Guid.NewGuid().ToString()); public static void __OnDestroy(TEnemyAI __instance) { UnifiedChaosPlugin.Instance.LogDebug("CommonModPlusPatches+" + typeof(TEnemyAI).Name + "::__OnDestroy", $"Instance:'{MobPlusPatcher<TMobPlus, TEnemyAI>._instance.EnemyType.Name}', IsNull:'{(Object)(object)__instance == (Object)null}'", verboseOnly: true); MobPlusPatcher<TMobPlus, TEnemyAI>._instance.EndTrackObject(__instance); } public static void __Start(TEnemyAI __instance) { } public static void __SwitchToBehaviourStateOnLocalClient(TEnemyAI __instance, int stateIndex) { if (!((Object)(object)MobPlusPatcher<TMobPlus, TEnemyAI>._instance.GetNetworkObject(((NetworkBehaviour)(object)__instance).NetworkObjectId) == (Object)null)) { UnifiedChaosPlugin.Instance.LogDebug("CommonModPlusPatches+" + typeof(TEnemyAI).Name + "::__SwitchToBehaviourStateOnLocalClient", $"Instance:'{MobPlusPatcher<TMobPlus, TEnemyAI>._instance.EnemyType.Name}', IsNull:'{(Object)(object)__instance == (Object)null}'", verboseOnly: true); ((MobPlusPatcher<TMobPlus, TEnemyAI>)MobPlusPatcher<TMobPlus, TEnemyAI>._instance).ChangeBehaviorStateLocalClient?.Invoke(__instance, stateIndex); } } private static MethodInfo FindMethod(string type, string methodName) { MethodInfo method = AccessTools.TypeByName(type).GetMethod(methodName); if (method?.DeclaringType != null && method.DeclaringType?.Name != type) { method = AccessTools.TypeByName(method.DeclaringType.Name).GetMethod(methodName); } return method; } public static void PatchMethods() { PostfixMethod(typeof(TEnemyAI).Name, "Start", "__Start"); PostfixMethod(typeof(TEnemyAI).Name, "OnDestroy", "__OnDestroy"); PrefixMethod(typeof(TEnemyAI).Name, "SwitchToBehaviourStateOnLocalClient", "__SwitchToBehaviourStateOnLocalClient"); } public static void PostfixMethod(string targetType, string methodName, string newMethodName) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown MethodInfo methodInfo = FindMethod(targetType, methodName); MethodInfo method = typeof(CommonModPlusPatches).GetMethod(newMethodName); HarmonyInstance.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } public static void PrefixMethod(string targetType, string methodName, string newMethodName) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown MethodInfo methodInfo = FindMethod(targetType, methodName); MethodInfo method = typeof(CommonModPlusPatches).GetMethod(newMethodName); HarmonyInstance.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } protected static TMobPlus _instance; private static readonly object _createLock = new object(); public event Action<TEnemyAI, int> ChangeBehaviorStateLocalClient; public virtual void ApplyPatches() { Harmony.CreateAndPatchAll(typeof(TMobPlus), (string)null); CommonModPlusPatches.PatchMethods(); } public static TMobPlus GetInstance() { if (_instance == null) { lock (_createLock) { ConstructorInfo constructor = typeof(TMobPlus).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, Array.Empty<Type>(), Array.Empty<ParameterModifier>()); if (constructor == null) { throw new Exception("Type '" + typeof(TMobPlus).Name + "' missing protected constructor!"); } _instance = constructor.Invoke(Array.Empty<object>()) as TMobPlus; _instance.ApplyPatches(); } } return _instance; } } public abstract class MobPlusPatcher<TEnemyAI> : MobPlusPatcher where TEnemyAI : EnemyAI { public override Type EnemyType => typeof(TEnemyAI); protected void BeginTrackObject(TEnemyAI trackedObject) { if (!((Object)(object)trackedObject == (Object)null)) { ulong? networkId = GetNetworkId(trackedObject); if (networkId.HasValue && !_trackedObjects.ContainsKey(networkId.Value)) { _trackedObjects.Add(networkId.Value, (EnemyAI)(object)trackedObject); } } } protected void EndTrackObject(TEnemyAI trackedObject) { if (!((Object)(object)trackedObject == (Object)null)) { ulong? networkId = GetNetworkId(trackedObject); if (networkId.HasValue && _trackedObjects.ContainsKey(networkId.Value)) { _trackedObjects.Remove(networkId.Value); } } } protected ulong? GetNetworkId(TEnemyAI ai) { object obj = ai; if (obj == null) { return null; } NetworkObject networkObject = ((NetworkBehaviour)obj).NetworkObject; if (networkObject == null) { return null; } return networkObject.NetworkObjectId; } public new TEnemyAI GetNetworkObject(ulong id) { if (!_trackedObjects.TryGetValue(id, out var value)) { return default(TEnemyAI); } return (TEnemyAI)(object)((value is TEnemyAI) ? value : null); } } public abstract class MobPlusPatcher { protected Dictionary<ulong, EnemyAI> _trackedObjects = new Dictionary<ulong, EnemyAI>(); public abstract Type EnemyType { get; } protected void BeginTrackObject(EnemyAI trackedObject) { if (!((Object)(object)trackedObject == (Object)null)) { ulong networkId = GetNetworkId(trackedObject); if (!_trackedObjects.ContainsKey(networkId)) { _trackedObjects.Add(networkId, trackedObject); } } } protected void EndTrackObject(EnemyAI trackedObject) { if (!((Object)(object)trackedObject == (Object)null)) { ulong networkId = GetNetworkId(trackedObject); if (_trackedObjects.ContainsKey(networkId)) { _trackedObjects.Remove(networkId); } } } public static ulong GetNetworkId(EnemyAI ai) { return ((NetworkBehaviour)ai).NetworkObject.NetworkObjectId; } public EnemyAI GetNetworkObject(ulong id) { if (!_trackedObjects.TryGetValue(id, out var value)) { return null; } return value; } public void TestDumpTracked() { UnifiedChaosPlugin.Instance.LogDebug(GetType().Name, "Dumping known objects:"); foreach (KeyValuePair<ulong, EnemyAI> trackedObject in _trackedObjects) { UnifiedChaosPlugin instance = UnifiedChaosPlugin.Instance; string name = GetType().Name; object arg = trackedObject.Key; EnemyAI value = trackedObject.Value; instance.LogDebug(name, $"\tKey:'{arg}', Value:'{((value != null) ? ((Object)value).name : null)}'"); } } } public class MouthDogExtender : MobPlusPatcher<MouthDogExtender, MouthDogAI> { protected MouthDogExtender() { } } public class NutcrackerEnemyExtender : MobPlusPatcher<NutcrackerEnemyExtender, NutcrackerEnemyAI> { protected NutcrackerEnemyExtender() { } } public class PufferExtender : MobPlusPatcher<PufferExtender, PufferAI> { protected PufferExtender() { } } public class RedLocustBeesExtender : MobPlusPatcher<RedLocustBeesExtender, RedLocustBees> { protected RedLocustBeesExtender() { } } public class SandSpiderExtender : MobPlusPatcher<SandSpiderExtender, SandSpiderAI> { protected SandSpiderExtender() { } } public class SandWormExtender : MobPlusPatcher<SandWormExtender, SandWormAI> { protected SandWormExtender() { } } } namespace TeamChaos.UnifiedChaosPack.PluginManagers.MobPlus.Notifiers { public class FlowermanAINotifier : Notifier<FlowermanAI, FlowermanEventBroker> { public enum BehaviorState { Sneaking, Evading, Threaten } private class FlowermanNotifierCoroutineManager : CoroutineManager<FlowermanAINotifier> { private const float DEFAULT_DELAY = 1.5f; private const float DEFAULT_DEVIATION = 0.5f; private readonly RandomDelayCoroutine _chase; private readonly RandomDelayCoroutine _dropItem; protected internal event Action ChaseEventTrigger; protected internal event Action DropItemEventTrigger; public FlowermanNotifierCoroutineManager(FlowermanAINotifier notifier) : base(notifier) { _chase = new RandomDelayCoroutine((MonoBehaviour)(object)notifier, 1f, 2f); _dropItem = new RandomDelayCoroutine((MonoBehaviour)(object)notifier, 1f, 2f); } public void ChaseStart(bool restart = false) { _chase.Start(restart, delegate { this.ChaseEventTrigger?.Invoke(); }); } public void ChaseStop() { _chase.Stop(); } public void DropItemStart(Action action, bool restart = false) { _dropItem.Start(restart, action); } public void DropItemStop() { _dropItem.Stop(); } } private readonly FlowermanNotifierCoroutineManager _coroutineManager; private BehaviorState _behaviorState; public PlayerControllerB PlayerKilled => _ai?.bodyBeingCarried?.playerScript; public PlayerControllerB TargetPlayer => ((EnemyAI)(_ai?)).targetPlayer; public bool AtFavoriteSpot => (double)Vector3.Distance(((Component)_ai).transform.position, ((EnemyAI)_ai).favoriteSpot.position) < 7.0; public bool CarryingBody { get; private set; } public FlowermanAINotifier() { _coroutineManager = new FlowermanNotifierCoroutineManager(this); _coroutineManager.ChaseEventTrigger += CoroutineManagerOnChaseEventTrigger; } public void KilledPlayer(bool carryingBody) { CarryingBody = carryingBody; _broker.AIEventNotify(this, "KilledPlayer"); } public void DroppingBody() { _broker.AIEventNotify(this, "KilledPlayer"); } public void ChangedWatchingPlayer() { _broker.AIEventNotify(this, "ChangedWatchingPlayer"); } private void CoroutineManagerOnChaseEventTrigger() { _broker.AIEventNotify(this, "AngryAtPlayer"); } public override void Update() { } protected override void Initialize() { } protected override void OnChangeBehaviorStateLocalClient(int stateIndex) { BehaviorState enumValue = GetEnumValue<BehaviorState>(stateIndex); BehaviorState previousBehaviorState = GetPreviousBehaviorState<BehaviorState>(); switch (enumValue) { case BehaviorState.Threaten: _broker.AIEventNotify(this, "AngryAtPlayer"); break; default: UnifiedChaosPlugin.Instance.LogWarning(((object)this).GetType().Name + "::OnChangeBehaviorStateLocalClient", $"Unknown behavior state: {stateIndex}"); break; case BehaviorState.Sneaking: case BehaviorState.Evading: break; } } public void StaringDownPlayer() { _broker.AIEventNotify(this, "StaringDownPlayer"); } public void EvadingPlayer() { _broker.AIEventNotify(this, "EvadingPlayer"); } } public class HoarderBugAINotifier : Notifier<HoarderBugAI, HoarderBugEventBroker> { public enum BehaviorState { Scavenging, Returning, Chasing } private class HoarderBugNotifierCoroutineManager : CoroutineManager<HoarderBugAINotifier> { private const float DEFAULT_DELAY = 1.5f; private const float DEFAULT_DEVIATION = 0.5f; private readonly RandomDelayCoroutine _chase; private readonly RandomDelayCoroutine _dropItem; protected internal event Action ChaseEventTrigger; protected internal event Action DropItemEventTrigger; public HoarderBugNotifierCoroutineManager(HoarderBugAINotifier notifier) : base(notifier) { _chase = new RandomDelayCoroutine((MonoBehaviour)(object)notifier, 1f, 2f); _dropItem = new RandomDelayCoroutine((MonoBehaviour)(object)notifier, 1f, 2f); } public void ChaseStart(bool restart = false) { _chase.Start(restart, delegate { this.ChaseEventTrigger?.Invoke(); }); } public void ChaseStop() { _chase.Stop(); } public void DropItemStart(Action action, bool restart = false) { _dropItem.Start(restart, action); } public void DropItemStop() { _dropItem.Stop(); } } private readonly HoarderBugNotifierCoroutineManager _coroutineManager; private PlayerControllerB _angryAtPlayer; private BehaviorState _behaviorState; private HoarderBugItem _heldItem; private Notifier<HoarderBugAI, HoarderBugEventBroker> _notifierImplementation; public HoarderBugAINotifier() { _coroutineManager = new HoarderBugNotifierCoroutineManager(this); _coroutineManager.ChaseEventTrigger += CoroutineManagerOnChaseEventTrigger; } public void ChangedWatchingPlayer() { _broker.AIEventNotify(this, "ChangedWatchingPlayer"); } private void CheckForAngryPlayerChanged() { BehaviorState behaviorState = GetBehaviorState<BehaviorState>(); if (_behaviorState != behaviorState) { _behaviorState = behaviorState; } if (behaviorState == BehaviorState.Chasing && (Object)(object)_angryAtPlayer != (Object)(object)_ai.angryAtPlayer) { UnifiedChaosPlugin.Instance.LogWarning(((object)this).GetType().Name + "::CheckForAngryPlayerChanged", "Trigger Change Target"); _angryAtPlayer = _ai.angryAtPlayer; _coroutineManager.ChaseStart(restart: true); } } private void CheckForChangedWatchingPlayer() { } private void CheckForHuntingItem() { } private void CheckForItemDroppedInNest() { } private void CheckForItemGrabbed() { if (_heldItem != _ai.heldItem) { _heldItem = _ai.heldItem; _broker.AIEventNotify(this, "ItemGrabbed"); } } private void CoroutineManagerOnChaseEventTrigger() { _broker.AIEventNotify(this, "AngryAtPlayer"); } public void DropItem(bool droppingInNest) { _coroutineManager.DropItemStart(delegate { _heldItem = _ai.heldItem; _broker.AIEventNotify(this, droppingInNest ? "ItemDroppedInNest" : "ItemDropped"); }); } protected override void Initialize() { } protected override void OnChangeBehaviorStateLocalClient(int stateIndex) { BehaviorState enumValue = GetEnumValue<BehaviorState>(stateIndex); BehaviorState previousBehaviorState = GetPreviousBehaviorState<BehaviorState>(); switch (enumValue) { case BehaviorState.Scavenging: _coroutineManager.ChaseStop(); _coroutineManager.DropItemStop(); break; case BehaviorState.Returning: _coroutineManager.ChaseStop(); _coroutineManager.DropItemStop(); break; case BehaviorState.Chasing: UnifiedChaosPlugin.Instance.LogInfo(((object)this).GetType().Name + "::OnChangeBehaviorStateLocalClient", "Chase Start"); _coroutineManager.DropItemStop(); _angryAtPlayer = _ai.angryAtPlayer; _coroutineManager.ChaseStart(); break; default: UnifiedChaosPlugin.Instance.LogWarning(((object)this).GetType().Name + "::OnChangeBehaviorStateLocalClient", $"Unknown behavior state: {stateIndex}"); break; } } public override void Update() { CheckForAngryPlayerChanged(); CheckForChangedWatchingPlayer(); CheckForHuntingItem(); CheckForItemDroppedInNest(); CheckForItemGrabbed(); } } public abstract class Notifier : MonoBehaviour { public abstract ulong NetworkObjectId { get; } public abstract void Update(); protected abstract void Initialize(); } public abstract class Notifier<TEnemyAI> : Notifier where TEnemyAI : EnemyAI { protected TEnemyAI _ai; public TEnemyAI AiInstance => _ai; public override ulong NetworkObjectId => ((NetworkBehaviour)(object)_ai).NetworkObjectId; protected T GetBehaviorState<T>() where T : struct, Enum { return GetEnumValue<T>(((EnemyAI)_ai).currentBehaviourStateIndex); } protected T GetEnumValue<T>(object value) where T : struct, Enum { if (!Enum.TryParse<T>((value as string) ?? Convert.ToString(value), out var result)) { return default(T); } return result; } protected T GetPreviousBehaviorState<T>() where T : struct, Enum { return GetEnumValue<T>(((EnemyAI)_ai).previousBehaviourStateIndex); } } public abstract class Notifier<TEnemyAI, TEventBroker> : Notifier<TEnemyAI> where TEnemyAI : EnemyAI where TEventBroker : EventBroker { protected class NotifierPatches { [HarmonyPostfix] [HarmonyPatch(typeof(EnemyAI), "SwitchToBehaviourStateOnLocalClient")] public static void __EnemyAI_SwitchToBehaviourStateOnLocalClient(EnemyAI __instance, int stateIndex) { if (__instance is TEnemyAI) { UnifiedChaosPlugin.Instance.LogDebug(string.Format("{0}::{1}", typeof(TEnemyAI), "__EnemyAI_SwitchToBehaviourStateOnLocalClient"), "Method called!", verboseOnly: true); Notifier<TEnemyAI, TEventBroker> component = ((Component)__instance).gameObject.GetComponent<Notifier<TEnemyAI, TEventBroker>>(); if (Object.op_Implicit((Object)(object)component)) { component.OnChangeBehaviorStateLocalClient(stateIndex); } } } } private static Guid? _patchId; private static bool _isPatched; private readonly object _patchLock = new object(); protected TEventBroker _broker; static Notifier() { } protected virtual void OnDestroy() { UnifiedChaosPlugin.Instance.LogDebug(((object)this).GetType().Name + "::OnDestroy", "Method called!"); _broker.UnregisterNotifier(this); } protected virtual void ApplyPatches() { Harmony.CreateAndPatchAll(typeof(NotifierPatches), (string)null); } public void Initialize(TEnemyAI aiInstance, TEventBroker brokerInstance) { _ai = aiInstance; _broker = brokerInstance; Initialize(); _broker.RegisterNotifier(this); lock (_patchLock) { if (_isPatched) { return; } _isPatched = true; } ApplyPatches(); } protected abstract void OnChangeBehaviorStateLocalClient(int stateIndex); } public class SpringManAINotifier : Notifier<SpringManAI, SpringManEventBroker> { public enum BehaviorState { Roaming, Chasing } private class SpringManNotifierCoroutineManager : CoroutineManager<SpringManAINotifier> { public enum StareEventArgs { Short, Medium, Long } private readonly RandomDelayCoroutine _stareLong; private readonly RandomDelayCoroutine _stareMedium; private readonly RandomDelayCoroutine _stareShort; protected internal event Action<StareEventArgs> StareEventTrigger; protected internal SpringManNotifierCoroutineManager(SpringManAINotifier notifier) : base(notifier) { _stareShort = new RandomDelayCoroutine((MonoBehaviour)(object)notifier, 1f, 3f); _stareMedium = new RandomDelayCoroutine((MonoBehaviour)(object)notifier, 5f, 15f); _stareLong = new RandomDelayCoroutine((MonoBehaviour)(object)notifier, 30f, 60f); } private void StareLStart() { _stareMedium.Start(delegate { this.StareEventTrigger?.Invoke(StareEventArgs.Long); }); } private void StareMStart() { _stareMedium.Start(delegate { this.StareEventTrigger?.Invoke(StareEventArgs.Medium); StareLStart(); }); } private void StareSStart() { _stareShort.Start(delegate { this.StareEventTrigger?.Invoke(StareEventArgs.Short); StareMStart(); }); } public void StareStart() { StareStart(restart: false); } public void StareStart(bool restart) { if (restart) { StareStop(); } _stareShort.Start(StareSStart); } public void StareStop() { _stareShort.Stop(); _stareMedium.Stop(); _stareLong.Stop(); } } private readonly SpringManNotifierCoroutineManager _coroutineManager; private bool _lastStoppingMovement; public SpringManAINotifier() { _coroutineManager = new SpringManNotifierCoroutineManager(this); _coroutineManager.StareEventTrigger += CoroutineManagerOnStareEventTrigger; } private void CoroutineManagerOnStareEventTrigger(SpringManNotifierCoroutineManager.StareEventArgs e) { switch (e) { case SpringManNotifierCoroutineManager.StareEventArgs.Short: _broker.AIEventNotify(this, "StaringAtPlayer"); break; case SpringManNotifierCoroutineManager.StareEventArgs.Medium: _broker.AIEventNotify(this, "StaringAtPlayerForAWhile"); break; case SpringManNotifierCoroutineManager.StareEventArgs.Long: _broker.AIEventNotify(this, "StaringAtPlayerForALongTime"); break; default: throw new ArgumentOutOfRangeException("e", e, null); } } public override void Update() { CheckForStaringAtPlayer(); } private void CheckForStaringAtPlayer() { if (_lastStoppingMovement != _ai.stoppingMovement) { _lastStoppingMovement = _ai.stoppingMovement; Action action = (_lastStoppingMovement ? new Action(_coroutineManager.StareStart) : new Action(_coroutineManager.StareStop)); action(); } } protected override void Initialize() { } protected override void OnChangeBehaviorStateLocalClient(int stateIndex) { BehaviorState enumValue = GetEnumValue<BehaviorState>(stateIndex); if ((uint)enumValue > 1u) { UnifiedChaosPlugin.Instance.LogWarning(((object)this).GetType().Name + "::OnChangeBehaviorStateLocalClient", $"Unknown behavior state: {stateIndex}"); } } } } namespace TeamChaos.UnifiedChaosPack.PluginManagers.MobPlus.EventBrokers { public abstract class EventBroker { public abstract Type EnemyType { get; } public abstract EnemyAI GetEnemyInstanceByNetworkId(ulong id); public abstract void RegisterNotifier(Notifier notifier); public abstract void UnregisterNotifier(Notifier notifier); } public abstract class EventBroker<TNotifier> : EventBroker where TNotifier : Notifier { protected readonly Dictionary<ulong, TNotifier> _notifiers = new Dictionary<ulong, TNotifier>(); protected internal abstract void ApplyPatches(); public override void RegisterNotifier(Notifier notifier) { if (notifier is TNotifier) { if (_notifiers.ContainsKey(notifier.NetworkObjectId)) { _notifiers.Remove(notifier.NetworkObjectId); } _notifiers.Add(notifier.NetworkObjectId, (TNotifier)notifier); } } public override void UnregisterNotifier(Notifier notifier) { if (notifier is TNotifier && _notifiers.ContainsKey(notifier.NetworkObjectId)) { _notifiers.Remove(notifier.NetworkObjectId); } } } public abstract class EventBroker<TNotifier, TEnemyAI, TEventBroker> : EventBroker<TNotifier> where TNotifier : Notifier<TEnemyAI, TEventBroker> where TEnemyAI : EnemyAI where TEventBroker : EventBroker<TNotifier>, new() { protected class EventBrokerPatches { [HarmonyPostfix] [HarmonyPatch(typeof(EnemyAI), "Start")] public static void __EnemyAI_Start(EnemyAI __instance) { if (__instance is TEnemyAI) { UnifiedChaosPlugin.Instance.LogDebug(string.Format("{0}::{1}", typeof(TEnemyAI), "__EnemyAI_Start"), "Method called!"); ((Component)__instance).gameObject.AddComponent<TNotifier>().Initialize((TEnemyAI)(object)__instance, EventBroker<TNotifier, TEnemyAI, TEventBroker>._instance); DumpBehaviourStates(__instance); } } public static void Apply() { throw new NotImplementedException(); } private static void DumpBehaviourStates(EnemyAI instance) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(); stringBuilder.AppendLine(" /// <summary>"); stringBuilder.AppendLine(" /// EnemyBehaviorStates:"); for (int j = 0; j < instance.enemyBehaviourStates.Length; j++) { EnemyBehaviourState val = instance.enemyBehaviourStates[j]; stringBuilder.AppendLine(" /// --------"); stringBuilder.AppendLine($" /// index:{j}"); stringBuilder.AppendLine(" /// Name:" + val.name); stringBuilder.AppendLine($" /// playOneShotSFX:{val.playOneShotSFX}"); AudioClip sFXClip = val.SFXClip; stringBuilder.AppendLine(" /// SFXClip:" + (((sFXClip != null) ? ((Object)sFXClip).name : null) ?? "null")); stringBuilder.AppendLine($" /// playOneShotVoice:{val.playOneShotVoice}"); AudioClip voiceClip = val.VoiceClip; stringBuilder.AppendLine(" /// VoiceClip:" + (((voiceClip != null) ? ((Object)voiceClip).name : null) ?? "null")); stringBuilder.AppendLine($" /// boolValue:{val.boolValue}"); stringBuilder.AppendLine($" /// IsAnimTrigger:{val.IsAnimTrigger}"); stringBuilder.AppendLine(" /// parameterString:" + val.parameterString); } stringBuilder.AppendLine(" /// </summary>"); stringBuilder.Append(" private enum BehaviorState { ").AppendJoin(", ", instance.enemyBehaviourStates.Select((EnemyBehaviourState s, int i) => $"{StringUtilsExtensions.ToPascalCase(s.name)} = {i}")).AppendLine(" }"); UnifiedChaosPlugin.Instance.LogDebug("EventBrokerPatches+" + typeof(TEnemyAI).Name + "::enemyBehaviourStates", stringBuilder.ToString()); } } private static TEventBroker _instance; private static readonly object _createLock = new object(); public override Type EnemyType => typeof(TEnemyAI); protected static TNotifier GetNotifierInstance(TEnemyAI enemyAI) { return ((Component)(object)enemyAI).gameObject.GetComponent<TNotifier>(); } internal abstract void AIEventNotify(TNotifier notifier, string evtName); protected internal override void ApplyPatches() { Harmony.CreateAndPatchAll(typeof(TEventBroker), (string)null); Harmony.CreateAndPatchAll(typeof(EventBrokerPatches), (string)null); } public override EnemyAI GetEnemyInstanceByNetworkId(ulong id) { object obj; if (!_notifiers.TryGetValue(id, out var value)) { obj = default(TEnemyAI); } else { TNotifier val = value; obj = ((val != null) ? val.AiInstance : default(TEnemyAI)); } return (EnemyAI)(object)(TEnemyAI)obj; } public static TEventBroker GetInstance() { if (_instance == null) { lock (_createLock) { UnifiedChaosPlugin.Instance.LogDebug("SpringManAINotifier::GetInstance", "Creating Instance."); _instance = new TEventBroker(); _instance.ApplyPatches(); } } return _instance; } } public class FlowermanEventBroker : EventBroker<FlowermanAINotifier, FlowermanAI, FlowermanEventBroker> { public event Action<FlowermanAI, PlayerControllerB> AngryAtPlayer; public event Action<FlowermanAI, bool, PlayerControllerB> KilledPlayer; public event Action<FlowermanAI, PlayerControllerB> EvadingPlayer; public event Action<FlowermanAI, PlayerControllerB> StaringDownPlayer; public event Action<FlowermanAI, bool> DroppingBody; [HarmonyPrefix] [HarmonyPatch(typeof(FlowermanAI), "DropPlayerBody")] public static void __DropPlayerBody(FlowermanAI __instance) { if (__instance.carryingPlayerBody) { FlowermanAINotifier notifierInstance = EventBroker<FlowermanAINotifier, FlowermanAI, FlowermanEventBroker>.GetNotifierInstance(__instance); notifierInstance.DroppingBody(); } } [HarmonyPrefix] [HarmonyPatch(typeof(FlowermanAI), "FinishKillAnimation")] public static void __FinishKillAnimation(FlowermanAI __instance, bool carryingBody) { FlowermanAINotifier notifierInstance = EventBroker<FlowermanAINotifier, FlowermanAI, FlowermanEventBroker>.GetNotifierInstance(__instance); notifierInstance.KilledPlayer(carryingBody); } [HarmonyPostfix] [HarmonyPatch(typeof(FlowermanAI), "LookAtFlowermanTrigger")] public static void __LookAtFlowermanTrigger(FlowermanAI __instance, bool __state) { if (!(!__instance.evadeModeStareDown || __state)) { FlowermanAINotifier notifierInstance = EventBroker<FlowermanAINotifier, FlowermanAI, FlowermanEventBroker>.GetNotifierInstance(__instance); notifierInstance.StaringDownPlayer(); } } [HarmonyPrefix] [HarmonyPatch(typeof(FlowermanAI), "LookAtFlowermanTrigger")] public static void __LookAtFlowermanTriggerPrefix(FlowermanAI __instance, ref bool __state) { __state = __instance.evadeModeStareDown; } [HarmonyPostfix] [HarmonyPatch(typeof(FlowermanAI), "Update")] public static void __Update(FlowermanAI __instance, bool __state) { if (!(!__instance.wasInEvadeMode || __state)) { FlowermanAINotifier notifierInstance = EventBroker<FlowermanAINotifier, FlowermanAI, FlowermanEventBroker>.GetNotifierInstance(__instance); notifierInstance.EvadingPlayer(); } } [HarmonyPrefix] [HarmonyPatch(typeof(FlowermanAI), "Update")] public static void __UpdatePrefix(FlowermanAI __instance, ref bool __state) { __state = __instance.wasInEvadeMode; } internal override void AIEventNotify(FlowermanAINotifier notifier, string evtName) { UnifiedChaosPlugin.Instance.LogError(GetType().Name + "::AIEventNotify", string.Format("Event '{0} fired for {1}[{2}]'!", evtName, "HoarderBugAI", ((NetworkBehaviour)notifier.AiInstance).NetworkObjectId)); switch (evtName) { case "KilledPlayer": this.KilledPlayer?.Invoke(notifier.AiInstance, notifier.CarryingBody, notifier.PlayerKilled); break; case "DroppingBody": this.DroppingBody?.Invoke(notifier.AiInstance, notifier.AtFavoriteSpot); break; case "StaringDownPlayer": this.StaringDownPlayer?.Invoke(notifier.AiInstance, notifier.TargetPlayer); break; case "EvadingPlayer": this.StaringDownPlayer?.Invoke(notifier.AiInstance, notifier.TargetPlayer); break; case "AngryAtPlayer": this.AngryAtPlayer?.Invoke(notifier.AiInstance, notifier.TargetPlayer); break; default: UnifiedChaosPlugin.Instance.LogWarning(GetType().Name + "::AIEventNotify", string.Format("Unhandled event '{0} fired for {1}[{2}]'!", evtName, "HoarderBugAI", ((NetworkBehaviour)notifier.AiInstance).NetworkObjectId)); break; } } } public class HoarderBugEventBroker : EventBroker<HoarderBugAINotifier, HoarderBugAI, HoarderBugEventBroker> { public event Action<HoarderBugAI, PlayerControllerB> AngryAtPlayer; public event Action<HoarderBugAI, PlayerControllerB> ChangedWatchingPlayer; public event Action<HoarderBugAI, GrabbableObject> HuntingItem; public event Action<HoarderBugAI> GaveUpChase; public event Action<HoarderBugAI> GaveUpSearch; public event Action<HoarderBugAI, HoarderBugItem> ItemDropped; public event Action<HoarderBugAI, HoarderBugItem> ItemDroppedInNest; public event Action<HoarderBugAI, HoarderBugItem> ItemGrabbed; [HarmonyPatch(typeof(HoarderBugAI), "DetectAndLookAtPlayers")] [HarmonyTranspiler] public static IEnumerable<CodeInstruction> __ChangedWatchingPlayer(IEnumerable<CodeInstruction> instructions) { MethodInfo methodInfo = AccessTools.Method(typeof(HoarderBugEventBroker), "__ChangedWatchingPlayerHook", new Type[1] { typeof(HoarderBugAI) }, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(RoundManager), "PlayRandomClip", new Type[6] { typeof(AudioSource), typeof(AudioClip[]), typeof(bool), typeof(float), typeof(int), typeof(int) }, (Type[])null); List<CodeInstruction> list = new List<CodeInstruction>(); foreach (CodeInstruction instruction in instructions) { if (CodeInstructionExtensions.Calls(instruction, methodInfo2)) { list.RemoveRange(list.Count - 8, 8); list.Add(new CodeInstruction(OpCodes.Ldarg_0, (object)null)); list.Add(new CodeInstruction(OpCodes.Call, (object)methodInfo)); } else { list.Add(instruction); } } foreach (CodeInstruction item in list) { yield return item; } } private static int __ChangedWatchingPlayerHook(HoarderBugAI __instance) { UnifiedChaosPlugin.Instance.LogDebug("HoarderBugEventBroker::__ChangedWatchingPlayerHook", "Method called!"); HoarderBugAINotifier component = ((Component)__instance).gameObject.GetComponent<HoarderBugAINotifier>(); component.ChangedWatchingPlayer(); return int.MinValue; } [HarmonyPostfix] [HarmonyPatch(typeof(HoarderBugAI), "DropItemClientRpc")] public static void __DropItemClientRpc(HoarderBugAI __instance, bool droppedInNest) { UnifiedChaosPlugin.Instance.LogDebug("HoarderBugEventBroker::__DropItemClientRpc", "Method called!"); HoarderBugAINotifier component = ((Component)__instance).gameObject.GetComponent<HoarderBugAINotifier>(); component.DropItem(droppedInNest); } internal override void AIEventNotify(HoarderBugAINotifier notifier, string evtName) { UnifiedChaosPlugin.Instance.LogError(GetType().Name + "::AIEventNotify", string.Format("Event '{0} fired for {1}[{2}]'!", evtName, "HoarderBugAI", ((NetworkBehaviour)notifier.AiInstance).NetworkObjectId)); switch (evtName) { case "AngryAtPlayer": this.AngryAtPlayer?.Invoke(notifier.AiInstance, notifier.AiInstance.angryAtPlayer); break; case "ChangedWatchingPlayer": this.ChangedWatchingPlayer?.Invoke(notifier.AiInstance, notifier.AiInstance.watchingPlayer); break; case "GaveUpChase": this.GaveUpChase?.Invoke(notifier.AiInstance); break; case "GaveUpSearch": this.GaveUpSearch?.Invoke(notifier.AiInstance); break; case "ItemDropped": this.ItemDropped?.Invoke(notifier.AiInstance, notifier.AiInstance.heldItem); break; case "ItemDroppedInNest": this.ItemDroppedInNest?.Invoke(notifier.AiInstance, notifier.AiInstance.heldItem); break; default: UnifiedChaosPlugin.Instance.LogWarning(GetType().Name + "::AIEventNotify", string.Format("Unhandled event '{0} fired for {1}[{2}]'!", evtName, "HoarderBugAI", ((NetworkBehaviour)notifier.AiInstance).NetworkObjectId)); break; case "HuntingItem": break; case "ItemGrabbed": break; } } } public class SpringManEventBroker : EventBroker<SpringManAINotifier, SpringManAI, SpringManEventBroker> { public event Action<SpringManAI, PlayerControllerB> StaringAtPlayer; public event Action<SpringManAI, PlayerControllerB> StaringAtPlayerForALongTime; public event Action<SpringManAI, PlayerControllerB> StaringAtPlayerForAWhile; internal override void AIEventNotify(SpringManAINotifier notifier, string evtName) { UnifiedChaosPlugin.Instance.LogDebug(GetType().Name + "::AIEventNotify", string.Format("Event '{0} fired for {1}[{2}]'!", evtName, "SpringManAI", ((NetworkBehaviour)notifier.AiInstance).NetworkObjectId)); switch (evtName) { case "StaringAtPlayer": this.StaringAtPlayer?.Invoke(notifier.AiInstance, IsLookingAtSpringMan(notifier.AiInstance)); break; case "StaringAtPlayerForAWhile": this.StaringAtPlayerForAWhile?.Invoke(notifier.AiInstance, IsLookingAtSpringMan(notifier.AiInstance)); break; case "StaringAtPlayerForALongTime": this.StaringAtPlayerForALongTime?.Invoke(notifier.AiInstance, IsLookingAtSpringMan(notifier.AiInstance)); break; default: UnifiedChaosPlugin.Instance.LogWarning(GetType().Name + "::AIEventNotify", string.Format("Unhandled event '{0} fired for {1}[{2}]'!", evtName, "SpringManAI", ((NetworkBehaviour)notifier.AiInstance).NetworkObjectId)); break; } } [CanBeNull] private PlayerControllerB IsLookingAtSpringMan(SpringManAI ai) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) Vector3 lineOfSightPosition = ((Component)ai).transform.position + Vector3.up * 1.6f; float lineOfSightRange = 68