diff --git a/QSB/Animation/Player/AnimationSync.cs b/QSB/Animation/Player/AnimationSync.cs index 035ff2d3..ec837038 100644 --- a/QSB/Animation/Player/AnimationSync.cs +++ b/QSB/Animation/Player/AnimationSync.cs @@ -93,6 +93,7 @@ public class AnimationSync : PlayerSyncObject SetSuitState(QSBSceneManager.CurrentScene == OWScene.EyeOfTheUniverse); InitAccelerationSync(); ThrusterManager.CreateRemotePlayerVFX(Player); + ThrusterManager.CreateRemotePlayerSFX(Player); Delay.RunWhen(() => Player.CameraBody != null, () => body.GetComponent().Init(Player.CameraBody.transform)); diff --git a/QSB/Animation/Player/Messages/PlayerSuitMessage.cs b/QSB/Animation/Player/Messages/PlayerSuitMessage.cs index 7c41161e..856d2e67 100644 --- a/QSB/Animation/Player/Messages/PlayerSuitMessage.cs +++ b/QSB/Animation/Player/Messages/PlayerSuitMessage.cs @@ -37,6 +37,15 @@ public class PlayerSuitMessage : QSBMessage var animator = player.AnimationSync; animator.SetSuitState(Data); + + if (player.SuitedUp) + { + player.AudioController.PlayWearSuit(); + } + else + { + player.AudioController.PlayRemoveSuit(); + } } public override void OnReceiveLocal() diff --git a/QSB/Animation/Player/Thrusters/ThrusterManager.cs b/QSB/Animation/Player/Thrusters/ThrusterManager.cs index e0bc0b3d..227fa766 100644 --- a/QSB/Animation/Player/Thrusters/ThrusterManager.cs +++ b/QSB/Animation/Player/Thrusters/ThrusterManager.cs @@ -1,4 +1,5 @@ -using QSB.Player; +using QSB.Audio; +using QSB.Player; using UnityEngine; namespace QSB.Animation.Player.Thrusters; @@ -14,6 +15,11 @@ internal static class ThrusterManager InitParticleControllers(newVfx, player); } + public static void CreateRemotePlayerSFX(PlayerInfo player) + { + player.Body.GetComponentInChildren(true)?.Init(player); + } + private static void InitFlameControllers(GameObject root, PlayerInfo player) { var existingControllers = root.GetComponentsInChildren(true); diff --git a/QSB/Audio/Messages/PlayerAudioControllerOneShotMessage.cs b/QSB/Audio/Messages/PlayerAudioControllerOneShotMessage.cs new file mode 100644 index 00000000..5c3b8c54 --- /dev/null +++ b/QSB/Audio/Messages/PlayerAudioControllerOneShotMessage.cs @@ -0,0 +1,16 @@ +using QSB.Messaging; +using QSB.Player; +using QSB.WorldSync; + +namespace QSB.Audio.Messages; + + +public class PlayerAudioControllerOneShotMessage : QSBMessage<(AudioType audioType, uint userID, float pitch, float volume)> +{ + public PlayerAudioControllerOneShotMessage(AudioType audioType, uint userID, float pitch = 1f, float volume = 1f) : base((audioType, userID, pitch, volume)) { } + + public override bool ShouldReceive => QSBWorldSync.AllObjectsReady; + + public override void OnReceiveRemote() => + QSBPlayerManager.GetPlayer(Data.userID)?.AudioController?.PlayOneShot(Data.audioType, Data.pitch, Data.volume); +} diff --git a/QSB/Audio/Messages/PlayerMovementAudioFootstepMessage.cs b/QSB/Audio/Messages/PlayerMovementAudioFootstepMessage.cs new file mode 100644 index 00000000..f17503ec --- /dev/null +++ b/QSB/Audio/Messages/PlayerMovementAudioFootstepMessage.cs @@ -0,0 +1,16 @@ +using QSB.Messaging; +using QSB.Player; +using QSB.WorldSync; + +namespace QSB.Audio.Messages; + + +public class PlayerMovementAudioFootstepMessage : QSBMessage<(AudioType audioType, float pitch, uint userID)> +{ + public PlayerMovementAudioFootstepMessage(AudioType audioType, float pitch, uint userID) : base((audioType, pitch, userID)) { } + + public override bool ShouldReceive => QSBWorldSync.AllObjectsReady; + + public override void OnReceiveRemote() => + QSBPlayerManager.GetPlayer(Data.userID)?.AudioController?.PlayFootstep(Data.audioType, Data.pitch); +} diff --git a/QSB/Audio/Messages/PlayerMovementAudioJumpMessage.cs b/QSB/Audio/Messages/PlayerMovementAudioJumpMessage.cs new file mode 100644 index 00000000..adae8149 --- /dev/null +++ b/QSB/Audio/Messages/PlayerMovementAudioJumpMessage.cs @@ -0,0 +1,16 @@ +using QSB.Messaging; +using QSB.Player; +using QSB.WorldSync; + +namespace QSB.Audio.Messages; + + +public class PlayerMovementAudioJumpMessage : QSBMessage<(float pitch, uint userID)> +{ + public PlayerMovementAudioJumpMessage(float pitch, uint userID) : base((pitch, userID)) { } + + public override bool ShouldReceive => QSBWorldSync.AllObjectsReady; + + public override void OnReceiveRemote() => + QSBPlayerManager.GetPlayer(Data.userID)?.AudioController?.OnJump(Data.pitch); +} diff --git a/QSB/Audio/Messages/ShipThrusterAudioOneShotMessage.cs b/QSB/Audio/Messages/ShipThrusterAudioOneShotMessage.cs new file mode 100644 index 00000000..156e51b2 --- /dev/null +++ b/QSB/Audio/Messages/ShipThrusterAudioOneShotMessage.cs @@ -0,0 +1,21 @@ +using QSB.Messaging; +using QSB.ShipSync; +using QSB.WorldSync; + +namespace QSB.Audio.Messages; + + +public class ShipThrusterAudioOneShotMessage : QSBMessage<(AudioType audioType, float pitch, float volume)> +{ + public ShipThrusterAudioOneShotMessage(AudioType audioType, float pitch = 1f, float volume = 1f) : base((audioType, pitch, volume)) { } + + public override bool ShouldReceive => QSBWorldSync.AllObjectsReady; + + public override void OnReceiveRemote() + { + var source = ShipManager.Instance.ShipThrusterAudio._rotationalSource; + source.pitch = Data.pitch; + source.PlayOneShot(Data.audioType, Data.volume); + } + +} diff --git a/QSB/Audio/Patches/PlayerAudioControllerPatches.cs b/QSB/Audio/Patches/PlayerAudioControllerPatches.cs new file mode 100644 index 00000000..7432baaa --- /dev/null +++ b/QSB/Audio/Patches/PlayerAudioControllerPatches.cs @@ -0,0 +1,36 @@ +using HarmonyLib; +using QSB.Audio.Messages; +using QSB.Messaging; +using QSB.Patches; +using QSB.Player; + +namespace QSB.Audio.Patches; + +[HarmonyPatch] +internal class PlayerAudioControllerPatches : QSBPatch +{ + public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect; + + private static void PlayOneShot(AudioType audioType) => + new PlayerAudioControllerOneShotMessage(audioType, QSBPlayerManager.LocalPlayerId).Send(); + + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerAudioController), nameof(PlayerAudioController.PlayMarshmallowEat))] + public static void PlayerAudioController_PlayMarshmallowEat() => PlayOneShot(AudioType.ToolMarshmallowEat); + + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerAudioController), nameof(PlayerAudioController.PlayMarshmallowEatBurnt))] + public static void PlayerAudioController_PlayMarshmallowEatBurnt() => PlayOneShot(AudioType.ToolMarshmallowEatBurnt); + + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerAudioController), nameof(PlayerAudioController.PlayPatchPuncture))] + public static void PlayerAudioController_PlayPatchPuncture() => PlayOneShot(AudioType.PlayerSuitPatchPuncture); + + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerAudioController), nameof(PlayerAudioController.PlayMedkit))] + public static void PlayerAudioController_PlayMedkit() => PlayOneShot(AudioType.ShipCabinUseMedkit); + + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerAudioController), nameof(PlayerAudioController.PlayRefuel))] + public static void PlayerAudioController_PlayRefuel() => PlayOneShot(AudioType.ShipCabinUseRefueller); +} \ No newline at end of file diff --git a/QSB/Audio/Patches/PlayerImpactAudioPatches.cs b/QSB/Audio/Patches/PlayerImpactAudioPatches.cs new file mode 100644 index 00000000..64cfea29 --- /dev/null +++ b/QSB/Audio/Patches/PlayerImpactAudioPatches.cs @@ -0,0 +1,40 @@ +using HarmonyLib; +using QSB.Audio.Messages; +using QSB.Messaging; +using QSB.Patches; +using QSB.Player; + +namespace QSB.Audio.Patches; + +internal class PlayerImpactAudioPatches : QSBPatch +{ + // Since we patch Start we do it when the mod starts, else it won't run + public override QSBPatchTypes Type => QSBPatchTypes.OnModStart; + + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerImpactAudio), nameof(PlayerImpactAudio.Start))] + public static void PlayerImpactAudio_Start(PlayerImpactAudio __instance) + { + __instance.gameObject.AddComponent(); + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(PlayerImpactAudio), nameof(PlayerImpactAudio.OnImpact))] + public static void PlayerImpactAudio_OnImpact_Prefix(PlayerImpactAudio __instance) => + // First we reset in case no audio is actually played + __instance.gameObject.GetComponent()?.Reset(); + + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerImpactAudio), nameof(PlayerImpactAudio.OnImpact))] + public static void PlayerImpactAudio_OnImpact_Postfix(PlayerImpactAudio __instance) + { + var tracker = __instance.gameObject.GetComponent(); + if (tracker) + { + if (tracker.LastPlayed != AudioType.None) + { + new PlayerAudioControllerOneShotMessage(tracker.LastPlayed, QSBPlayerManager.LocalPlayerId, tracker.Pitch, tracker.Volume).Send(); + } + } + } +} diff --git a/QSB/Audio/Patches/PlayerMovementAudioPatches.cs b/QSB/Audio/Patches/PlayerMovementAudioPatches.cs new file mode 100644 index 00000000..ecdfa51b --- /dev/null +++ b/QSB/Audio/Patches/PlayerMovementAudioPatches.cs @@ -0,0 +1,32 @@ +using HarmonyLib; +using QSB.Audio.Messages; +using QSB.Messaging; +using QSB.Patches; +using QSB.Player; + +namespace QSB.Audio.Patches; + +internal class PlayerMovementAudioPatches : QSBPatch +{ + public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect; + + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerMovementAudio), nameof(PlayerMovementAudio.PlayFootstep))] + public static void PlayerMovementAudio_PlayFootstep(PlayerMovementAudio __instance) + { + var underwater = !PlayerState.IsCameraUnderwater() && __instance._fluidDetector.InFluidType(FluidVolume.Type.WATER); + var audioType = underwater ? AudioType.MovementShallowWaterFootstep : PlayerMovementAudio.GetFootstepAudioType(__instance._playerController.GetGroundSurface()); + + if (audioType != AudioType.None) + { + new PlayerMovementAudioFootstepMessage(audioType, __instance._footstepAudio.pitch, QSBPlayerManager.LocalPlayerId).Send(); + } + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerMovementAudio), nameof(PlayerMovementAudio.OnJump))] + public static void PlayerMovementAudio_OnJump(PlayerMovementAudio __instance) + { + new PlayerMovementAudioJumpMessage(__instance._jumpAudio.pitch, QSBPlayerManager.LocalPlayerId).Send(); + } +} \ No newline at end of file diff --git a/QSB/Audio/Patches/ThrusterAudioPatches.cs b/QSB/Audio/Patches/ThrusterAudioPatches.cs new file mode 100644 index 00000000..be09b0d9 --- /dev/null +++ b/QSB/Audio/Patches/ThrusterAudioPatches.cs @@ -0,0 +1,42 @@ +using HarmonyLib; +using QSB.Audio.Messages; +using QSB.Messaging; +using QSB.Patches; + +namespace QSB.Audio.Patches; + +internal class ThrusterAudioPatches : QSBPatch +{ + // Since we patch Start we do it when the mod starts, else it won't run + public override QSBPatchTypes Type => QSBPatchTypes.OnModStart; + + [HarmonyPostfix] + [HarmonyPatch(typeof(ThrusterAudio), nameof(ThrusterAudio.Start))] + public static void ThrusterAudio_Start(ThrusterAudio __instance) + { + __instance._rotationalSource.gameObject.AddComponent(); + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(ThrusterAudio), nameof(ThrusterAudio.OnFireRotationalThruster))] + public static void ThrusterAudio_OnFireRotationalThruster_Prefix(ThrusterAudio __instance) => + // First we reset in case no audio is actually played + __instance._rotationalSource.gameObject.GetComponent()?.Reset(); + + [HarmonyPostfix] + [HarmonyPatch(typeof(ThrusterAudio), nameof(ThrusterAudio.OnFireRotationalThruster))] + public static void ThrusterAudio_OnFireRotationalThruster_Postfix(ThrusterAudio __instance) + { + if (__instance._rotationalSource.gameObject.TryGetComponent(out var tracker)) + { + if (tracker.LastPlayed != AudioType.None) + { + if (__instance is ShipThrusterAudio) + { + new ShipThrusterAudioOneShotMessage(tracker.LastPlayed, tracker.Pitch, tracker.Volume).Send(); + } + // TODO: Apply to player jetpack thruster? + } + } + } +} diff --git a/QSB/Audio/QSBAudioSourceOneShotTracker.cs b/QSB/Audio/QSBAudioSourceOneShotTracker.cs new file mode 100644 index 00000000..d3c8d615 --- /dev/null +++ b/QSB/Audio/QSBAudioSourceOneShotTracker.cs @@ -0,0 +1,63 @@ +using HarmonyLib; +using QSB.Patches; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace QSB.Audio; + +/// +/// tracks what audioType was last played on when PlayOneShot is called on an OWAudioSource +/// makes it easier to send a message afterwards syncing what was just played +/// +[RequireComponent(typeof(OWAudioSource))] +public class QSBAudioSourceOneShotTracker : MonoBehaviour +{ + public AudioType LastPlayed { get; internal set; } + public float Pitch { get => _source.pitch; } + public float Volume { get; internal set; } + public int Index { get; internal set; } + + public void Reset() => LastPlayed = AudioType.None; + + private OWAudioSource _source; + public void Awake() + { + _source = GetComponent(); + } +} + +[HarmonyPatch(typeof(OWAudioSource))] +internal class OneShotTrackerPatches : QSBPatch +{ + public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect; + + [HarmonyPostfix] + [HarmonyPatch(nameof(OWAudioSource.PlayOneShot), new[] { typeof(AudioType), typeof(float) })] + private static void TrackOneShot_AudioType(OWAudioSource __instance, AudioType type, float volume) + { + var tracker = __instance.gameObject.GetComponent(); + if (tracker) + { + tracker.LastPlayed = type; + tracker.Volume = volume; + tracker.Index = -1; + } + } + + [HarmonyPostfix] + [HarmonyPatch(nameof(OWAudioSource.PlayOneShot), new[] { typeof(AudioType), typeof(int), typeof(float) })] + private static void TrackOneShot_AudioType(OWAudioSource __instance, AudioType type, int index, float volume) + { + var tracker = __instance.gameObject.GetComponent(); + if (tracker) + { + tracker.LastPlayed = type; + tracker.Volume = volume; + tracker.Index = index; + } + } +} diff --git a/QSB/Audio/QSBJetpackThrusterAudio.cs b/QSB/Audio/QSBJetpackThrusterAudio.cs index 8cd38ae6..6a93b311 100644 --- a/QSB/Audio/QSBJetpackThrusterAudio.cs +++ b/QSB/Audio/QSBJetpackThrusterAudio.cs @@ -1,4 +1,6 @@ -using QSB.Utility; +using QSB.Player; +using QSB.Utility; +using UnityEngine; namespace QSB.Audio; @@ -8,4 +10,90 @@ internal class QSBJetpackThrusterAudio : QSBThrusterAudio public OWAudioSource _underwaterSource; public OWAudioSource _oxygenSource; public OWAudioSource _boostSource; + + private PlayerInfo _attachedPlayer; + private bool _wasBoosting; + + // Taken from Player_Body settings + private const float maxTranslationalThrust = 6f; + + private bool _underwater; + private RemotePlayerFluidDetector _fluidDetector; + + public void Init(PlayerInfo player) + { + _attachedPlayer = player; + enabled = true; + + _fluidDetector = player.FluidDetector; + _fluidDetector.OnEnterFluidType += OnEnterExitFluidType; + _fluidDetector.OnExitFluidType += OnEnterExitFluidType; + } + + private void OnDestroy() + { + if (_fluidDetector != null) + { + _fluidDetector.OnEnterFluidType -= OnEnterExitFluidType; + _fluidDetector.OnExitFluidType -= OnEnterExitFluidType; + } + } + + private void OnEnterExitFluidType(FluidVolume.Type type) + { + _underwater = _fluidDetector.InFluidType(FluidVolume.Type.WATER); + } + + private void Update() + { + if(_attachedPlayer == null) + { + enabled = false; + return; + } + + var acc = _attachedPlayer.JetpackAcceleration.AccelerationVariableSyncer.Value; + var thrustFraction = acc.magnitude / maxTranslationalThrust; + + // TODO: Sync + var usingBooster = false; + var usingOxygen = false; + + float targetVolume = usingBooster ? 0f : thrustFraction; + float targetPan = -acc.x / maxTranslationalThrust * 0.4f; + UpdateTranslationalSource(_translationalSource, targetVolume, targetPan, !_underwater && !usingOxygen); + UpdateTranslationalSource(_underwaterSource, targetVolume, targetPan, _underwater); + UpdateTranslationalSource(_oxygenSource, targetVolume, targetPan, !_underwater && usingOxygen); + + if (!_wasBoosting && usingBooster) + { + _boostSource.FadeIn(0.3f, false, false, 1f); + } + else if (_wasBoosting && !usingBooster) + { + _boostSource.FadeOut(0.3f, OWAudioSource.FadeOutCompleteAction.STOP, 0f); + } + + _wasBoosting = usingBooster; + } + + private void UpdateTranslationalSource(OWAudioSource source, float targetVolume, float targetPan, bool active) + { + if (!active) + { + targetVolume = 0f; + targetPan = 0f; + } + if (!source.isPlaying && targetVolume > 0f) + { + source.SetLocalVolume(0f); + source.Play(); + } + else if (source.isPlaying && source.volume <= 0f) + { + source.Stop(); + } + source.SetLocalVolume(Mathf.MoveTowards(source.GetLocalVolume(), targetVolume, 5f * Time.deltaTime)); + source.panStereo = Mathf.MoveTowards(source.panStereo, targetPan, 5f * Time.deltaTime); + } } \ No newline at end of file diff --git a/QSB/Audio/QSBPlayerAudioController.cs b/QSB/Audio/QSBPlayerAudioController.cs index 9030065c..4a967902 100644 --- a/QSB/Audio/QSBPlayerAudioController.cs +++ b/QSB/Audio/QSBPlayerAudioController.cs @@ -20,4 +20,25 @@ public class QSBPlayerAudioController : MonoBehaviour public void PlayTurnOffFlashlight() => _oneShotExternalSource?.PlayOneShot(AudioType.ToolFlashlightOff); + + public void PlayWearSuit() + => PlayOneShot(AudioType.PlayerSuitWearSuit); + + public void PlayRemoveSuit() + => PlayOneShot(AudioType.PlayerSuitRemoveSuit); + + public void PlayOneShot(AudioType audioType, float pitch = 1f, float volume = 1f) + { + if (_oneShotExternalSource) + { + _oneShotExternalSource.pitch = pitch; + _oneShotExternalSource.PlayOneShot(audioType, volume); + } + } + + public void PlayFootstep(AudioType audioType, float pitch) => + PlayOneShot(audioType, pitch, 0.7f); + + public void OnJump(float pitch) => + PlayOneShot(AudioType.MovementJump, pitch, 0.7f); } \ No newline at end of file diff --git a/QSB/Messaging/OWEvents.cs b/QSB/Messaging/OWEvents.cs index 5820a4f5..dc89f647 100644 --- a/QSB/Messaging/OWEvents.cs +++ b/QSB/Messaging/OWEvents.cs @@ -33,4 +33,7 @@ public static class OWEvents public const string ExitDreamWorld = nameof(ExitDreamWorld); public const string EnterRemoteFlightConsole = nameof(EnterRemoteFlightConsole); public const string ExitRemoteFlightConsole = nameof(ExitRemoteFlightConsole); + public const string StartShipIgnition = nameof(StartShipIgnition); + public const string CompleteShipIgnition = nameof(CompleteShipIgnition); + public const string CancelShipIgnition = nameof(CancelShipIgnition); } \ No newline at end of file diff --git a/QSB/ModelShip/Messages/CrashModelShipMessage.cs b/QSB/ModelShip/Messages/CrashModelShipMessage.cs new file mode 100644 index 00000000..4eda4941 --- /dev/null +++ b/QSB/ModelShip/Messages/CrashModelShipMessage.cs @@ -0,0 +1,16 @@ +using QSB.Messaging; +using QSB.WorldSync; + +namespace QSB.ModelShip.Messages; + +internal class CrashModelShipMessage : QSBMessage +{ + public CrashModelShipMessage() { } + + public override void OnReceiveRemote() + { + var crashBehaviour = QSBWorldSync.GetUnityObject(); + crashBehaviour._crashEffect.Play(); + crashBehaviour.gameObject.GetComponent().PlayOneShot(AudioType.TH_ModelShipCrash); + } +} diff --git a/QSB/ModelShip/Messages/RespawnModelShipMessage.cs b/QSB/ModelShip/Messages/RespawnModelShipMessage.cs index 241e40a7..59d1d3bc 100644 --- a/QSB/ModelShip/Messages/RespawnModelShipMessage.cs +++ b/QSB/ModelShip/Messages/RespawnModelShipMessage.cs @@ -8,6 +8,10 @@ internal class RespawnModelShipMessage : QSBMessage { public RespawnModelShipMessage(bool playEffects) : base(playEffects) { } - public override void OnReceiveRemote() => - QSBPatch.RemoteCall(() => QSBWorldSync.GetUnityObject().RespawnModelShip(Data)); + public override void OnReceiveRemote() + { + var flightConsole = QSBWorldSync.GetUnityObject(); + QSBPatch.RemoteCall(() => flightConsole.RespawnModelShip(Data)); + if (Data) flightConsole._modelShipBody.GetComponent().PlayOneShot(AudioType.TH_RetrieveModelShip); + } } diff --git a/QSB/ModelShip/Messages/UseFlightConsoleMessage.cs b/QSB/ModelShip/Messages/UseFlightConsoleMessage.cs index 703d0c65..ad78d06a 100644 --- a/QSB/ModelShip/Messages/UseFlightConsoleMessage.cs +++ b/QSB/ModelShip/Messages/UseFlightConsoleMessage.cs @@ -1,6 +1,7 @@ using QSB.AuthoritySync; using QSB.Messaging; using QSB.ModelShip.TransformSync; +using QSB.Patches; using QSB.Player; using QSB.Player.TransformSync; using QSB.Utility; @@ -26,20 +27,14 @@ internal class UseFlightConsoleMessage : QSBMessage private UseFlightConsoleMessage(bool active) : base(active) { } - public override void OnReceiveLocal() - { - if (QSBCore.IsHost) - { - ModelShipTransformSync.LocalInstance.netIdentity.SetAuthority(Data - ? From - : QSBPlayerManager.LocalPlayerId); - } - } + public override void OnReceiveLocal() => SetCurrentFlyer(From, Data); public override void OnReceiveRemote() { var console = QSBWorldSync.GetUnityObject(); + SetCurrentFlyer(From, Data); + if (Data) { console._modelShipBody.Unsuspend(); @@ -62,12 +57,24 @@ internal class UseFlightConsoleMessage : QSBMessage QSBWorldSync.GetUnityObject()._detector.SetActive(Data); QSBWorldSync.GetUnityObjects().ForEach(x => x._owCollider.SetActivation(Data)); + } + + private void SetCurrentFlyer(uint flyer, bool isFlying) + { + ModelShipManager.Instance.CurrentFlyer = isFlying + ? flyer + : uint.MaxValue; if (QSBCore.IsHost) { - ModelShipTransformSync.LocalInstance.netIdentity.SetAuthority(Data - ? From - : QSBPlayerManager.LocalPlayerId); + ModelShipTransformSync.LocalInstance.netIdentity.SetAuthority(isFlying + ? flyer + : QSBPlayerManager.LocalPlayerId); // Host gets authority when its not in use } + + // Client messes up its position when they start flying it + // We can just recall it immediately so its in the right place. + var console = QSBWorldSync.GetUnityObject(); + QSBPatch.RemoteCall(() => console.RespawnModelShip(false)); } } diff --git a/QSB/ModelShip/ModelShipManager.cs b/QSB/ModelShip/ModelShipManager.cs index 6b60fc9c..23417fb3 100644 --- a/QSB/ModelShip/ModelShipManager.cs +++ b/QSB/ModelShip/ModelShipManager.cs @@ -13,12 +13,37 @@ internal class ModelShipManager : WorldObjectManager public override WorldObjectScene WorldObjectScene => WorldObjectScene.SolarSystem; public override bool DlcOnly => false; + public static ModelShipManager Instance; + + public uint CurrentFlyer + { + get => _currentFlyer; + set + { + if (_currentFlyer != uint.MaxValue && value != uint.MaxValue) + { + DebugLog.ToConsole($"Warning - Trying to set current model ship flyer while someone is still flying? Current:{_currentFlyer}, New:{value}", MessageType.Warning); + } + + _currentFlyer = value; + } + } + private uint _currentFlyer = uint.MaxValue; + + public void Start() + { + Instance = this; + } + public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct) { if (QSBCore.IsHost) { Instantiate(QSBNetworkManager.singleton.ModelShipPrefab).SpawnWithServerAuthority(); } + + // Is 0 by default -> 2D (bad) + QSBWorldSync.GetUnityObject()._consoleAudio.spatialBlend = 1; } public override void UnbuildWorldObjects() diff --git a/QSB/ModelShip/ModelShipThrusterVariableSyncer.cs b/QSB/ModelShip/ModelShipThrusterVariableSyncer.cs new file mode 100644 index 00000000..bc07f23f --- /dev/null +++ b/QSB/ModelShip/ModelShipThrusterVariableSyncer.cs @@ -0,0 +1,76 @@ +using Mirror; +using QSB.Player; +using QSB.Utility; +using QSB.Utility.VariableSync; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace QSB.ModelShip; + +public class ModelShipThrusterVariableSyncer : MonoBehaviour +{ + public Vector3VariableSyncer AccelerationSyncer; + + public ThrusterModel ThrusterModel { get; private set; } + private ThrusterAudio _thrusterAudio; + public List ThrusterFlameControllers = new(); + public ThrusterWashController ThrusterWashController { get; private set; } + + public void Init(GameObject modelShip) + { + ThrusterModel = modelShip.GetComponent(); + _thrusterAudio = modelShip.GetComponentInChildren(); + + ThrusterFlameControllers.Clear(); + foreach (var item in modelShip.GetComponentsInChildren()) + { + ThrusterFlameControllers.Add(item); + } + + ThrusterWashController = modelShip.GetComponentInChildren(); + } + + public void Update() + { + if (QSBPlayerManager.LocalPlayer.FlyingModelShip) + { + GetFromShip(); + return; + } + + if (AccelerationSyncer.public_HasChanged()) + { + if (AccelerationSyncer.Value == Vector3.zero) + { + foreach (var item in ThrusterFlameControllers) + { + item.OnStopTranslationalThrust(); + } + + _thrusterAudio.OnStopTranslationalThrust(); + + ThrusterWashController.OnStopTranslationalThrust(); + } + else + { + foreach (var item in ThrusterFlameControllers) + { + item.OnStartTranslationalThrust(); + } + + _thrusterAudio.OnStartTranslationalThrust(); + + ThrusterWashController.OnStartTranslationalThrust(); + } + } + } + + private void GetFromShip() + { + if (ThrusterModel) + { + AccelerationSyncer.Value = ThrusterModel.GetLocalAcceleration(); + } + } +} diff --git a/QSB/ModelShip/Patches/ModelShipPatches.cs b/QSB/ModelShip/Patches/ModelShipPatches.cs index eebe6370..b434641a 100644 --- a/QSB/ModelShip/Patches/ModelShipPatches.cs +++ b/QSB/ModelShip/Patches/ModelShipPatches.cs @@ -2,6 +2,7 @@ using QSB.Messaging; using QSB.ModelShip.Messages; using QSB.Patches; +using UnityEngine; namespace QSB.ModelShip.Patches; @@ -20,4 +21,14 @@ public class ModelShipPatches : QSBPatch new RespawnModelShipMessage(playEffects).Send(); } + + [HarmonyPrefix] + [HarmonyPatch(typeof(ModelShipCrashBehavior), nameof(ModelShipCrashBehavior.OnImpact))] + private static void ModelShipCrashBehavior_OnImpact(ModelShipCrashBehavior __instance, ImpactData impactData) + { + if (impactData.speed > 10f && Time.time > __instance._lastCrashTime + 1f) + { + new CrashModelShipMessage().Send(); + } + } } diff --git a/QSB/ModelShip/Patches/ModelShipThrusterAudioPatches.cs b/QSB/ModelShip/Patches/ModelShipThrusterAudioPatches.cs new file mode 100644 index 00000000..b372c6b7 --- /dev/null +++ b/QSB/ModelShip/Patches/ModelShipThrusterAudioPatches.cs @@ -0,0 +1,31 @@ +using HarmonyLib; +using QSB.ModelShip.TransformSync; +using QSB.Patches; +using QSB.Player; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace QSB.ModelShip.Patches; + +internal class ModelShipThrusterAudioPatches : QSBPatch +{ + public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect; + + [HarmonyPrefix] + [HarmonyPatch(typeof(ThrusterModel), nameof(ThrusterModel.GetThrustFraction))] + public static bool ThrusterModel_GetThrustFraction(ThrusterModel __instance, ref float __result) + { + if (__instance == ModelShipTransformSync.LocalInstance.ThrusterVariableSyncer.ThrusterModel && !QSBPlayerManager.LocalPlayer.FlyingModelShip) + { + __result = ModelShipTransformSync.LocalInstance.ThrusterVariableSyncer.AccelerationSyncer.Value.magnitude / __instance._maxTranslationalThrust; + return false; + } + else + { + return true; + } + } +} diff --git a/QSB/ModelShip/Patches/ModelShipThrusterPatches.cs b/QSB/ModelShip/Patches/ModelShipThrusterPatches.cs new file mode 100644 index 00000000..d396a925 --- /dev/null +++ b/QSB/ModelShip/Patches/ModelShipThrusterPatches.cs @@ -0,0 +1,32 @@ +using HarmonyLib; +using QSB.ModelShip.TransformSync; +using QSB.Patches; +using QSB.Player; +using System.Linq; +using UnityEngine; + +namespace QSB.ModelShip.Patches; + +internal class ModelShipThrusterPatches : QSBPatch +{ + public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect; + + [HarmonyPrefix] + [HarmonyPatch(typeof(ThrusterFlameController), nameof(ThrusterFlameController.GetThrustFraction))] + public static bool GetThrustFraction(ThrusterFlameController __instance, ref float __result) + { + var modelShipThrusters = ModelShipTransformSync.LocalInstance?.ThrusterVariableSyncer; + + if (modelShipThrusters == null) return true; + + if (modelShipThrusters.ThrusterFlameControllers.Contains(__instance) && !QSBPlayerManager.LocalPlayer.FlyingModelShip) + { + if(__instance._thrusterModel.IsThrusterBankEnabled(OWUtilities.GetShipThrusterBank(__instance._thruster))) + { + __result = Vector3.Dot(modelShipThrusters.AccelerationSyncer.Value, __instance._thrusterFilter); + return false; + } + } + return true; + } +} diff --git a/QSB/ModelShip/TransformSync/ModelShipTransformSync.cs b/QSB/ModelShip/TransformSync/ModelShipTransformSync.cs index 23c72c26..29b08c09 100644 --- a/QSB/ModelShip/TransformSync/ModelShipTransformSync.cs +++ b/QSB/ModelShip/TransformSync/ModelShipTransformSync.cs @@ -1,4 +1,5 @@ -using QSB.Syncs.Sectored.Rigidbodies; +using QSB.ShipSync; +using QSB.Syncs.Sectored.Rigidbodies; using QSB.Utility; using QSB.WorldSync; @@ -8,6 +9,8 @@ internal class ModelShipTransformSync : SectoredRigidbodySync { public static ModelShipTransformSync LocalInstance { get; private set; } + public ModelShipThrusterVariableSyncer ThrusterVariableSyncer { get; private set; } + public override void OnStartClient() { base.OnStartClient(); @@ -31,6 +34,14 @@ internal class ModelShipTransformSync : SectoredRigidbodySync return modelShip; } + protected override void Init() + { + base.Init(); + + ThrusterVariableSyncer = this.GetRequiredComponent(); + ThrusterVariableSyncer.Init(AttachedRigidbody.gameObject); + } + /// /// replacement for base method /// using SetPos/Rot instead of Move diff --git a/QSB/Player/PlayerInfo.cs b/QSB/Player/PlayerInfo.cs index 164e8f46..06715acd 100644 --- a/QSB/Player/PlayerInfo.cs +++ b/QSB/Player/PlayerInfo.cs @@ -3,6 +3,7 @@ using QSB.Animation.Player; using QSB.Audio; using QSB.ClientServerStateSync; using QSB.Messaging; +using QSB.ModelShip; using QSB.Player.Messages; using QSB.Player.TransformSync; using QSB.QuantumSync.WorldObjects; @@ -34,6 +35,7 @@ public partial class PlayerInfo public bool IsLocalPlayer => TransformSync.isLocalPlayer; // if TransformSync is ever null, i give permission for nebula to make fun of me about it for the rest of time - johncorby public ThrusterLightTracker ThrusterLightTracker; public bool FlyingShip => ShipManager.Instance.CurrentFlyer == PlayerId; + public bool FlyingModelShip => ModelShipManager.Instance.CurrentFlyer == PlayerId; public PlayerInfo(PlayerTransformSync transformSync) { diff --git a/QSB/QSBNetworkManager.cs b/QSB/QSBNetworkManager.cs index df92f9ae..531d3365 100644 --- a/QSB/QSBNetworkManager.cs +++ b/QSB/QSBNetworkManager.cs @@ -13,6 +13,7 @@ using QSB.EchoesOfTheEye.EclipseElevators.VariableSync; using QSB.EchoesOfTheEye.RaftSync.TransformSync; using QSB.JellyfishSync.TransformSync; using QSB.Messaging; +using QSB.ModelShip; using QSB.ModelShip.TransformSync; using QSB.OrbSync.Messages; using QSB.OrbSync.TransformSync; @@ -149,6 +150,9 @@ public class QSBNetworkManager : NetworkManager, IAddComponentOnStart spawnPrefabs.Add(ShipLegPrefab); ModelShipPrefab = MakeNewNetworkObject(14, "NetworkModelShip", typeof(ModelShipTransformSync)); + var modelShipVector3Syncer = ModelShipPrefab.AddComponent(); + var modelShipThrusterVariableSyncer = ModelShipPrefab.AddComponent(); + modelShipThrusterVariableSyncer.AccelerationSyncer = modelShipVector3Syncer; spawnPrefabs.Add(ModelShipPrefab); StationaryProbeLauncherPrefab = MakeNewNetworkObject(15, "NetworkStationaryProbeLauncher", typeof(StationaryProbeLauncherVariableSyncer)); diff --git a/QSB/RoastingSync/QSBMarshmallow.cs b/QSB/RoastingSync/QSBMarshmallow.cs index db755f58..b6f01a44 100644 --- a/QSB/RoastingSync/QSBMarshmallow.cs +++ b/QSB/RoastingSync/QSBMarshmallow.cs @@ -53,6 +53,8 @@ public class QSBMarshmallow : MonoBehaviour _mallowRenderer.enabled = true; _mallowState = Marshmallow.MallowState.Default; enabled = true; + + _attachedPlayer.AudioController.PlayOneShot(AudioType.ToolMarshmallowReplace); } public void Disable() @@ -68,6 +70,8 @@ public class QSBMarshmallow : MonoBehaviour { _fireRenderer.enabled = false; _mallowState = Marshmallow.MallowState.Default; + + _attachedPlayer.AudioController.PlayOneShot(AudioType.ToolMarshmallowBlowOut); } } @@ -142,6 +146,8 @@ public class QSBMarshmallow : MonoBehaviour _toastedFraction = 1f; _initBurnTime = Time.time; _mallowState = Marshmallow.MallowState.Burning; + + _attachedPlayer.AudioController.PlayOneShot(AudioType.ToolMarshmallowIgnite); } } diff --git a/QSB/ShipSync/Messages/FlyShipMessage.cs b/QSB/ShipSync/Messages/FlyShipMessage.cs index 144efd70..e1083c13 100644 --- a/QSB/ShipSync/Messages/FlyShipMessage.cs +++ b/QSB/ShipSync/Messages/FlyShipMessage.cs @@ -39,10 +39,12 @@ internal class FlyShipMessage : QSBMessage var shipCockpitController = ShipManager.Instance.CockpitController; if (Data) { + QSBPlayerManager.GetPlayer(From)?.AudioController?.PlayOneShot(AudioType.ShipCockpitBuckleUp); shipCockpitController._interactVolume.DisableInteraction(); } else { + QSBPlayerManager.GetPlayer(From)?.AudioController?.PlayOneShot(AudioType.ShipCockpitUnbuckle); shipCockpitController._interactVolume.EnableInteraction(); } } diff --git a/QSB/ShipSync/Messages/ShipIgnitionMessage.cs b/QSB/ShipSync/Messages/ShipIgnitionMessage.cs new file mode 100644 index 00000000..1069c49f --- /dev/null +++ b/QSB/ShipSync/Messages/ShipIgnitionMessage.cs @@ -0,0 +1,48 @@ +using QSB.Messaging; +using QSB.Player; +using static QSB.ShipSync.Messages.ShipIgnitionMessage; + +namespace QSB.ShipSync.Messages; + +internal class ShipIgnitionMessage : QSBMessage +{ + public enum ShipIgnitionType + { + START_IGNITION, + COMPLETE_IGNITION, + CANCEL_IGNITION + } + + static ShipIgnitionMessage() + { + GlobalMessenger.AddListener(OWEvents.StartShipIgnition, () => Handler(ShipIgnitionType.START_IGNITION)); + GlobalMessenger.AddListener(OWEvents.CompleteShipIgnition, () => Handler(ShipIgnitionType.COMPLETE_IGNITION)); + GlobalMessenger.AddListener(OWEvents.CancelShipIgnition, () => Handler(ShipIgnitionType.CANCEL_IGNITION)); + } + + public ShipIgnitionMessage(ShipIgnitionType data) : base(data) { } + + private static void Handler(ShipIgnitionType type) + { + if (QSBPlayerManager.LocalPlayer.FlyingShip) + { + new ShipIgnitionMessage(type).Send(); + } + } + + public override void OnReceiveRemote() + { + switch (Data) + { + case ShipIgnitionType.START_IGNITION: + GlobalMessenger.FireEvent(OWEvents.StartShipIgnition); + break; + case ShipIgnitionType.COMPLETE_IGNITION: + GlobalMessenger.FireEvent(OWEvents.CompleteShipIgnition); + break; + case ShipIgnitionType.CANCEL_IGNITION: + GlobalMessenger.FireEvent(OWEvents.CancelShipIgnition); + break; + } + } +} diff --git a/QSB/ShipSync/Patches/ShipAudioPatches.cs b/QSB/ShipSync/Patches/ShipAudioPatches.cs new file mode 100644 index 00000000..1e76d57a --- /dev/null +++ b/QSB/ShipSync/Patches/ShipAudioPatches.cs @@ -0,0 +1,44 @@ +using HarmonyLib; +using QSB.Patches; +using QSB.Player; +using QSB.ShipSync.TransformSync; +using UnityEngine; + +namespace QSB.ShipSync.Patches; + +internal class ShipAudioPatches : QSBPatch +{ + public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect; + + [HarmonyPrefix] + [HarmonyPatch(typeof(ShipThrusterAudio), nameof(ShipThrusterAudio.Update))] + public static bool ShipThrusterAudio_Update(ShipThrusterAudio __instance) + { + if (!QSBPlayerManager.LocalPlayer.FlyingShip) + { + // Just copy pasted the original method with this one line changed + Vector3 localAcceleration = ShipTransformSync.LocalInstance.ThrusterVariableSyncer.AccelerationSyncer.Value; + localAcceleration.y *= 0.5f; + localAcceleration.z *= 0.5f; + Vector3 vector = __instance._thrusterModel.IsThrusterBankEnabled(ThrusterBank.Left) ? localAcceleration : Vector3.zero; + vector.x = Mathf.Max(0f, vector.x); + Vector3 vector2 = __instance._thrusterModel.IsThrusterBankEnabled(ThrusterBank.Right) ? localAcceleration : Vector3.zero; + vector2.x = Mathf.Min(0f, vector2.x); + float maxTranslationalThrust = __instance._thrusterModel.GetMaxTranslationalThrust(); + __instance.UpdateTranslationalSourceVolume(__instance._leftTranslationalSource, __instance._thrustToVolumeCurve.Evaluate(vector.magnitude / maxTranslationalThrust), !__instance._underwater); + __instance.UpdateTranslationalSourceVolume(__instance._rightTranslationalSource, __instance._thrustToVolumeCurve.Evaluate(vector2.magnitude / maxTranslationalThrust), !__instance._underwater); + __instance.UpdateTranslationalSourceVolume(__instance._leftUnderwaterSource, __instance._thrustToVolumeCurve.Evaluate(vector.magnitude / maxTranslationalThrust), __instance._underwater); + __instance.UpdateTranslationalSourceVolume(__instance._rightUnderwaterSource, __instance._thrustToVolumeCurve.Evaluate(vector2.magnitude / maxTranslationalThrust), __instance._underwater); + if (!__instance._thrustersFiring && !__instance._leftTranslationalSource.isPlaying && !__instance._rightTranslationalSource.isPlaying && !__instance._leftUnderwaterSource.isPlaying && !__instance._rightUnderwaterSource.isPlaying) + { + __instance.enabled = false; + } + + return false; + } + else + { + return true; + } + } +} diff --git a/QSB/ShipSync/Patches/ShipFlameWashPatches.cs b/QSB/ShipSync/Patches/ShipFlameWashPatches.cs index ddf2eac8..50e25f8b 100644 --- a/QSB/ShipSync/Patches/ShipFlameWashPatches.cs +++ b/QSB/ShipSync/Patches/ShipFlameWashPatches.cs @@ -1,4 +1,6 @@ using HarmonyLib; +using QSB.ModelShip; +using QSB.ModelShip.TransformSync; using QSB.Patches; using QSB.Player; using QSB.ShipSync.TransformSync; @@ -41,16 +43,34 @@ internal class ShipFlameWashPatches : QSBPatch [HarmonyPatch(typeof(ThrusterWashController), nameof(ThrusterWashController.Update))] public static bool Update(ThrusterWashController __instance) { - if (ShipThrusterManager.ShipWashController != __instance) + var isShip = ShipThrusterManager.ShipWashController == __instance; + var isModelShip = ModelShipTransformSync.LocalInstance.ThrusterVariableSyncer.ThrusterWashController == __instance; + + if (!isShip && !isModelShip) { return true; } + bool isLocal; + Vector3 remoteAcceleration; + if (isShip) + { + isLocal = QSBPlayerManager.LocalPlayer.FlyingShip; + remoteAcceleration = ShipTransformSync.LocalInstance.ThrusterVariableSyncer.AccelerationSyncer.Value; + } + else + { + isLocal = QSBPlayerManager.LocalPlayer.FlyingModelShip; + remoteAcceleration = ModelShipTransformSync.LocalInstance.ThrusterVariableSyncer.AccelerationSyncer.Value; + } + + var localAcceleration = isLocal + ? __instance._thrusterModel.GetLocalAcceleration() + : remoteAcceleration; + + // The rest of this is just copy pasted from the original method var hitInfo = default(RaycastHit); var aboveGround = false; - var localAcceleration = QSBPlayerManager.LocalPlayer.FlyingShip - ? __instance._thrusterModel.GetLocalAcceleration() - : ShipTransformSync.LocalInstance.ThrusterVariableSyncer.AccelerationSyncer.Value; var emissionScale = __instance._emissionThrusterScale.Evaluate(localAcceleration.y); if (emissionScale > 0f) { diff --git a/QSB/ShipSync/ShipManager.cs b/QSB/ShipSync/ShipManager.cs index c85a2751..405059c4 100644 --- a/QSB/ShipSync/ShipManager.cs +++ b/QSB/ShipSync/ShipManager.cs @@ -22,6 +22,7 @@ internal class ShipManager : WorldObjectManager public static ShipManager Instance; + public ShipThrusterAudio ShipThrusterAudio; public InteractZone HatchInteractZone; public HatchController HatchController; public ShipTractorBeamSwitch ShipTractorBeam; @@ -92,6 +93,7 @@ internal class ShipManager : WorldObjectManager return; } + ShipThrusterAudio = QSBWorldSync.GetUnityObject(); HatchInteractZone = HatchController.GetComponent(); ShipTractorBeam = QSBWorldSync.GetUnityObject(); CockpitController = QSBWorldSync.GetUnityObject(); @@ -136,6 +138,10 @@ internal class ShipManager : WorldObjectManager QSBWorldSync.Init(); QSBWorldSync.Init(); + + // Make sure all relevant audio sources are 3D + QSBWorldSync.GetUnityObject()._ignitionSource.spatialBlend = 1f; + QSBWorldSync.GetUnityObject()._rotationalSource.spatialBlend = 1f; } public override void UnbuildWorldObjects() diff --git a/QSB/ShipSync/ShipThrusterVariableSyncer.cs b/QSB/ShipSync/ShipThrusterVariableSyncer.cs index 79053946..c8e31673 100644 --- a/QSB/ShipSync/ShipThrusterVariableSyncer.cs +++ b/QSB/ShipSync/ShipThrusterVariableSyncer.cs @@ -1,6 +1,7 @@ using Mirror; using QSB.Player; using QSB.Utility.VariableSync; +using QSB.WorldSync; using UnityEngine; namespace QSB.ShipSync; @@ -10,10 +11,12 @@ public class ShipThrusterVariableSyncer : NetworkBehaviour public Vector3VariableSyncer AccelerationSyncer; private ShipThrusterModel _thrusterModel; + private ShipThrusterAudio _thrusterAudio; public void Init() { _thrusterModel = Locator.GetShipBody().GetComponent(); + _thrusterAudio = Locator.GetShipBody().GetComponentInChildren(); } public void Update() @@ -33,6 +36,8 @@ public class ShipThrusterVariableSyncer : NetworkBehaviour item.OnStopTranslationalThrust(); } + _thrusterAudio.OnStopTranslationalThrust(); + ShipThrusterManager.ShipWashController.OnStopTranslationalThrust(); } else @@ -42,6 +47,8 @@ public class ShipThrusterVariableSyncer : NetworkBehaviour item.OnStartTranslationalThrust(); } + _thrusterAudio.OnStartTranslationalThrust(); + ShipThrusterManager.ShipWashController.OnStartTranslationalThrust(); } }