using System; using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; using FMOD.Studio; using HarmonyLib; using NitroxClient.Communication.Abstract; using NitroxClient.GameLogic; using NitroxClient.GameLogic.FMOD; using NitroxModel.GameLogic.FMOD; using NitroxModel.Helper; using NitroxModel.Packets; using UnityEngine; namespace NitroxPatcher.Patches.Dynamic; public sealed partial class FootstepSounds_OnStep_Patch : NitroxPatch, IDynamicPatch { private const string PRECURSOR_STEP_SOUND_PATH = "event:/player/footstep_precursor_base"; private const string METAL_STEP_SOUND_PATH = "event:/player/footstep_metal"; private const string LAND_STEP_SOUND_PATH = "event:/player/footstep_dirt"; internal static readonly MethodInfo TARGET_METHOD = Reflect.Method((FootstepSounds t) => t.OnStep(default)); public static IEnumerable Transpiler(IEnumerable instructions) { /* From: evt.setVolume(volume); To: evt.setVolume(CalculateVolume(volume, this, asset, xform)); From: event.release(); To: event.release(); SendFootstepPacket(asset); */ return new CodeMatcher(instructions) .MatchEndForward( new CodeMatch(OpCodes.Ldloc_1), new CodeMatch(OpCodes.Call, Reflect.Method((EventInstance evt) => evt.setVolume(default))) ) .Insert( new CodeInstruction(OpCodes.Ldarg_0), new CodeInstruction(OpCodes.Ldloc_0), new CodeInstruction(OpCodes.Ldarg_1), new CodeInstruction(OpCodes.Call, Reflect.Method(() => CalculateVolume(default, default, default, default))) ) .MatchEndForward( new CodeMatch(OpCodes.Call, Reflect.Method((EventInstance evt) => evt.release())), new CodeMatch(OpCodes.Pop) ) .Advance(1) .InsertAndAdvance(new CodeInstruction(OpCodes.Ldloc_0)) .Insert(new CodeInstruction(OpCodes.Call, Reflect.Method(() => SendFootstepPacket(default)))) .InstructionEnumeration(); } // This method is called very often and should therefore be performant private static float CalculateVolume(float originalVolume, FootstepSounds instance, FMODAsset asset, Transform xform) { // For exosuits only "event:/sub/exo/step" is used inside FootstepSounds if (asset.path.Equals(LAND_STEP_SOUND_PATH, StringComparison.Ordinal) && (!Player.main.currentMountedVehicle || Player.main.currentMountedVehicle.gameObject != instance.gameObject)) { // Origin is Exosuit which is controlled by remote player return FMODSystem.CalculateVolume(xform.position, Player.main.transform.position, stepSoundRadius.Value, originalVolume); } return originalVolume; } private static readonly Lazy stepSoundRadius = new(() => { Resolve().TryGetSoundData(LAND_STEP_SOUND_PATH, out SoundData soundData); return soundData.Radius; }); private static void SendFootstepPacket(FMODAsset asset) { if (!Resolve().PlayerId.HasValue) { return; } FootstepPacket.StepSounds assetIndex; switch (asset.path) { case PRECURSOR_STEP_SOUND_PATH: assetIndex = FootstepPacket.StepSounds.PRECURSOR; break; case METAL_STEP_SOUND_PATH: assetIndex = FootstepPacket.StepSounds.METAL; break; case LAND_STEP_SOUND_PATH: assetIndex = FootstepPacket.StepSounds.LAND; break; default: return; } FootstepPacket footstepPacket = new(Resolve().PlayerId.Value, assetIndex); Resolve().Send(footstepPacket); } }