using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using FMOD; using FMOD.Studio; using FMODUnity; using Features.AIModule.Scripts; using Features.AIModule.Scripts.ManInShadows; using Fusion; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Shadowman")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("Shadowman")] [assembly: AssemblyTitle("Shadowman")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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 LastPirates.Shadowman { [BepInPlugin("com.bwadd.lastpirates.shadowman", "Shadowman", "1.1.1")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.bwadd.lastpirates.shadowman"; public const string PluginName = "Shadowman"; public const string PluginVersion = "1.1.1"; private const string AudioFileName = "Shadowman_18s.wav"; private const string DeathAudioFileName = "Shadowman_Death.mp3"; private const uint DeathAudioStartMilliseconds = 54000u; internal static Plugin Instance; internal static ConfigEntry Volume; internal static ConfigEntry MinimumDistance; internal static ConfigEntry MaximumDistance; internal static ConfigEntry DeathVolume; internal static ConfigEntry EchoWetLevel; private readonly Dictionary _channels = new Dictionary(); private readonly Dictionary _echoes = new Dictionary(); private Sound _replacementSound; private Sound _deathSound; private bool _soundReady; private bool _deathSoundReady; private void Awake() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Expected O, but got Unknown //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Expected O, but got Unknown //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Expected O, but got Unknown //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Expected O, but got Unknown //IL_013c: Unknown result type (might be due to invalid IL or missing references) Instance = this; Volume = ((BaseUnityPlugin)this).Config.Bind("Audio", "Volume", 0.8f, new ConfigDescription("Replacement whisper volume.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); MinimumDistance = ((BaseUnityPlugin)this).Config.Bind("Audio", "MinimumDistance", 3f, new ConfigDescription("Distance where the replacement plays at full volume.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 50f), Array.Empty())); MaximumDistance = ((BaseUnityPlugin)this).Config.Bind("Audio", "MaximumDistance", 35f, new ConfigDescription("Distance where the replacement becomes inaudible.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 200f), Array.Empty())); DeathVolume = ((BaseUnityPlugin)this).Config.Bind("Audio", "DeathVolume", 1f, new ConfigDescription("Volume of the Shadowman death clip.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); EchoWetLevel = ((BaseUnityPlugin)this).Config.Bind("Audio", "EchoWetLevel", -18f, new ConfigDescription("Echo level in decibels. Set to -80 to disable it.", (AcceptableValueBase)(object)new AcceptableValueRange(-80f, 0f), Array.Empty())); new Harmony("com.bwadd.lastpirates.shadowman").PatchAll(typeof(Plugin).Assembly); ((MonoBehaviour)this).StartCoroutine(LoadSounds()); } private IEnumerator LoadSounds() { string path = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), "Shadowman_18s.wav"); string deathPath = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), "Shadowman_Death.mp3"); if (!File.Exists(path)) { ((BaseUnityPlugin)this).Logger.LogError((object)("Replacement audio not found: " + path)); yield break; } float deadline = Time.realtimeSinceStartup + 30f; string lastError = "FMOD is not initialized."; while (Time.realtimeSinceStartup < deadline) { try { System coreSystem = RuntimeManager.CoreSystem; if (((System)(ref coreSystem)).hasHandle()) { RESULT val = ((System)(ref coreSystem)).createSound(path, (MODE)1048722, ref _replacementSound); if ((int)val == 0) { ((Sound)(ref _replacementSound)).setLoopCount(-1); ((Sound)(ref _replacementSound)).set3DMinMaxDistance(MinimumDistance.Value, MaximumDistance.Value); _soundReady = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Loaded Shadowman_18s.wav; Stalker whispers will be replaced locally."); break; } lastError = $"{val}: {Error.String(val)}"; } } catch (Exception ex) { lastError = ex.ToString(); } yield return (object)new WaitForSecondsRealtime(0.5f); } if (!_soundReady) { ((BaseUnityPlugin)this).Logger.LogError((object)("Could not load replacement through FMOD within 30 seconds: " + lastError)); } if (!File.Exists(deathPath)) { ((BaseUnityPlugin)this).Logger.LogError((object)("Death audio not found: " + deathPath)); yield break; } System coreSystem2 = RuntimeManager.CoreSystem; RESULT val2 = ((System)(ref coreSystem2)).createSound(deathPath, (MODE)1048721, ref _deathSound); if ((int)val2 != 0) { ((BaseUnityPlugin)this).Logger.LogError((object)$"Could not load Shadowman death audio: {val2}: {Error.String(val2)}"); yield break; } ((Sound)(ref _deathSound)).set3DMinMaxDistance(MinimumDistance.Value, MaximumDistance.Value); _deathSoundReady = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Loaded Shadowman_Death.mp3; playback begins at 54 seconds on Stalker death."); } internal void PlayDeathSound(Vector3 worldPosition) { //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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) if (!_deathSoundReady) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Shadowman died before the death audio was ready."); return; } System coreSystem = RuntimeManager.CoreSystem; Channel channel = default(Channel); RESULT val = ((System)(ref coreSystem)).playSound(_deathSound, default(ChannelGroup), true, ref channel); if ((int)val != 0) { ((BaseUnityPlugin)this).Logger.LogError((object)$"Could not start Shadowman death audio: {val}: {Error.String(val)}"); return; } VECTOR val2 = new VECTOR { x = worldPosition.x, y = worldPosition.y, z = worldPosition.z }; VECTOR val3 = default(VECTOR); ((Channel)(ref channel)).set3DAttributes(ref val2, ref val3); ((Channel)(ref channel)).setVolume(DeathVolume.Value); AddSubtleEcho(channel); ((Channel)(ref channel)).setPosition(54000u, (TIMEUNIT)1); ((Channel)(ref channel)).setPaused(false); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Played Shadowman death audio from 54 seconds."); } internal void UpdateReplacement(ManInShadowsBehaviour enemy, float visibilityVolume) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) if (!_soundReady || (Object)(object)enemy == (Object)null) { return; } int instanceID = ((Object)enemy).GetInstanceID(); if (!_channels.TryGetValue(instanceID, out var value) || !((Channel)(ref value)).hasHandle()) { System coreSystem = RuntimeManager.CoreSystem; RESULT val = ((System)(ref coreSystem)).playSound(_replacementSound, default(ChannelGroup), false, ref value); if ((int)val != 0) { ((BaseUnityPlugin)this).Logger.LogError((object)$"Could not start Shadowman whisper: {val}: {Error.String(val)}"); return; } _channels[instanceID] = value; AddSubtleEcho(value, instanceID); ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Started replacement whisper for Stalker instance {instanceID}."); } Vector3 position = ((Component)enemy).transform.position; VECTOR val2 = new VECTOR { x = position.x, y = position.y, z = position.z }; VECTOR val3 = default(VECTOR); ((Channel)(ref value)).set3DAttributes(ref val2, ref val3); ((Channel)(ref value)).setVolume(Mathf.Clamp01(visibilityVolume) * Volume.Value); } internal void StopReplacement(ManInShadowsBehaviour enemy) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)enemy == (Object)null) { return; } int instanceID = ((Object)enemy).GetInstanceID(); if (_channels.TryGetValue(instanceID, out var value)) { if (((Channel)(ref value)).hasHandle()) { ((Channel)(ref value)).stop(); ((Channel)(ref value)).clearHandle(); } _channels.Remove(instanceID); } if (_echoes.TryGetValue(instanceID, out var value2)) { if (((DSP)(ref value2)).hasHandle()) { ((DSP)(ref value2)).release(); } _echoes.Remove(instanceID); } } private void AddSubtleEcho(Channel channel, int trackedInstanceId = 0) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) if (EchoWetLevel.Value <= -79.9f) { return; } System coreSystem = RuntimeManager.CoreSystem; DSP val2 = default(DSP); RESULT val = ((System)(ref coreSystem)).createDSPByType((DSP_TYPE)6, ref val2); if ((int)val != 0) { ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Could not create Shadowman echo: {val}: {Error.String(val)}"); return; } ((DSP)(ref val2)).setParameterFloat(0, 115f); ((DSP)(ref val2)).setParameterFloat(1, 8f); ((DSP)(ref val2)).setParameterFloat(2, 0f); ((DSP)(ref val2)).setParameterFloat(3, EchoWetLevel.Value); val = ((Channel)(ref channel)).addDSP(0, val2); if ((int)val != 0) { ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Could not attach Shadowman echo: {val}: {Error.String(val)}"); ((DSP)(ref val2)).release(); } else if (trackedInstanceId != 0) { _echoes[trackedInstanceId] = val2; } else { ((DSP)(ref val2)).release(); } } private void OnDestroy() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) foreach (Channel value in _channels.Values) { Channel current = value; if (((Channel)(ref current)).hasHandle()) { ((Channel)(ref current)).stop(); } } _channels.Clear(); foreach (DSP value2 in _echoes.Values) { DSP current2 = value2; if (((DSP)(ref current2)).hasHandle()) { ((DSP)(ref current2)).release(); } } _echoes.Clear(); if (((Sound)(ref _replacementSound)).hasHandle()) { ((Sound)(ref _replacementSound)).release(); ((Sound)(ref _replacementSound)).clearHandle(); } if (((Sound)(ref _deathSound)).hasHandle()) { ((Sound)(ref _deathSound)).release(); ((Sound)(ref _deathSound)).clearHandle(); } Instance = null; } } [HarmonyPatch(typeof(SimpleEnemyHealthController), "ReceiveDamage")] internal static class ShadowmanDeathPatch { private struct DeathState { internal bool WasAlive; internal bool IsShadowman; internal Vector3 Position; } private static void Prefix(SimpleEnemyHealthController __instance, ref DeathState __state) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) __state.WasAlive = __instance.CurrentHealth > 0f; __state.IsShadowman = (Object)(object)((Component)__instance).GetComponent() != (Object)null; __state.Position = ((Component)__instance).transform.position; } private static void Postfix(SimpleEnemyHealthController __instance, DeathState __state) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (__state.WasAlive && __state.IsShadowman && __instance.CurrentHealth <= 0f) { Plugin.Instance?.PlayDeathSound(__state.Position); } } } [HarmonyPatch(typeof(ManInShadowsBehaviour), "FixedUpdate")] internal static class ReplaceWhisperUpdatePatch { private static void Postfix(ManInShadowsBehaviour __instance, ref EventInstance ____localVisibilitySoundInstance) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) float visibilityVolume = 1f; if (((EventInstance)(ref ____localVisibilitySoundInstance)).isValid()) { ((EventInstance)(ref ____localVisibilitySoundInstance)).getVolume(ref visibilityVolume); ((EventInstance)(ref ____localVisibilitySoundInstance)).setVolume(0f); } Plugin.Instance?.UpdateReplacement(__instance, visibilityVolume); } } [HarmonyPatch(typeof(ManInShadowsBehaviour), "Despawned")] internal static class ReplaceWhisperDespawnPatch { private static void Prefix(ManInShadowsBehaviour __instance, NetworkRunner runner, bool hasState) { Plugin.Instance?.StopReplacement(__instance); } } }