diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4e7e04c67..535998dfc 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -13,5 +13,5 @@ jobs: - uses: actions/upload-artifact@v2 with: - name: QSB + name: Raicuparta.QuantumSpaceBuddies path: .\QSB\Bin\Debug \ No newline at end of file diff --git a/EpicOnlineTransport/EpicOnlineTransport.csproj b/EpicOnlineTransport/EpicOnlineTransport.csproj index 5655714a4..a1563cdf6 100644 --- a/EpicOnlineTransport/EpicOnlineTransport.csproj +++ b/EpicOnlineTransport/EpicOnlineTransport.csproj @@ -3,7 +3,7 @@ EpicTransport + - diff --git a/EpicRerouter/EpicRerouter.csproj b/EpicRerouter/EpicRerouter.csproj index dbebee21d..3cff5ff9a 100644 --- a/EpicRerouter/EpicRerouter.csproj +++ b/EpicRerouter/EpicRerouter.csproj @@ -3,7 +3,7 @@ Exe - - + + diff --git a/MirrorWeaver/MirrorWeaver.csproj b/MirrorWeaver/MirrorWeaver.csproj index 737bc8f83..61a059994 100644 --- a/MirrorWeaver/MirrorWeaver.csproj +++ b/MirrorWeaver/MirrorWeaver.csproj @@ -3,9 +3,8 @@ Exe - - - + + diff --git a/QSB/Animation/NPC/Patches/CharacterAnimationPatches.cs b/QSB/Animation/NPC/Patches/CharacterAnimationPatches.cs index cc93e9c6a..3d5ddd531 100644 --- a/QSB/Animation/NPC/Patches/CharacterAnimationPatches.cs +++ b/QSB/Animation/NPC/Patches/CharacterAnimationPatches.cs @@ -36,7 +36,7 @@ public static bool AnimatorIKReplacement( return true; } - var qsbObj = __instance.playerTrackingZone.GetWorldObject(); // OPTIMIZE : maybe cache this somewhere... or assess how slow this is + var qsbObj = __instance.playerTrackingZone.GetWorldObject(); PlayerInfo playerToUse = null; if (__instance._inConversation) diff --git a/QSB/Animation/Player/AnimationSync.cs b/QSB/Animation/Player/AnimationSync.cs index e7e1d18a2..ec8370388 100644 --- a/QSB/Animation/Player/AnimationSync.cs +++ b/QSB/Animation/Player/AnimationSync.cs @@ -12,6 +12,7 @@ namespace QSB.Animation.Player; +[UsedInUnityProject] public class AnimationSync : PlayerSyncObject { private RuntimeAnimatorController _suitedAnimController; @@ -92,6 +93,7 @@ public void InitRemote(Transform body) 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 7c41161e8..856d2e671 100644 --- a/QSB/Animation/Player/Messages/PlayerSuitMessage.cs +++ b/QSB/Animation/Player/Messages/PlayerSuitMessage.cs @@ -37,6 +37,15 @@ public override void OnReceiveRemote() 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/PlayerHeadRotationSync.cs b/QSB/Animation/Player/PlayerHeadRotationSync.cs index cc41f8d56..d5f4867b1 100644 --- a/QSB/Animation/Player/PlayerHeadRotationSync.cs +++ b/QSB/Animation/Player/PlayerHeadRotationSync.cs @@ -4,6 +4,7 @@ namespace QSB.Animation.Player; +[UsedInUnityProject] public class PlayerHeadRotationSync : MonoBehaviour { private Animator _attachedAnimator; diff --git a/QSB/Animation/Player/Thrusters/JetpackAccelerationSync.cs b/QSB/Animation/Player/Thrusters/JetpackAccelerationSync.cs index 4ce656965..c8b6c55a2 100644 --- a/QSB/Animation/Player/Thrusters/JetpackAccelerationSync.cs +++ b/QSB/Animation/Player/Thrusters/JetpackAccelerationSync.cs @@ -1,8 +1,10 @@ using Mirror; +using QSB.Utility; using QSB.Utility.VariableSync; namespace QSB.Animation.Player.Thrusters; +[UsedInUnityProject] public class JetpackAccelerationSync : NetworkBehaviour { public Vector3VariableSyncer AccelerationVariableSyncer; diff --git a/QSB/Animation/Player/Thrusters/RemoteThrusterFlameController.cs b/QSB/Animation/Player/Thrusters/RemoteThrusterFlameController.cs index 999e3914a..3f3e2280b 100644 --- a/QSB/Animation/Player/Thrusters/RemoteThrusterFlameController.cs +++ b/QSB/Animation/Player/Thrusters/RemoteThrusterFlameController.cs @@ -1,9 +1,11 @@ using QSB.Player; +using QSB.Utility; using QSB.WorldSync; using UnityEngine; namespace QSB.Animation.Player.Thrusters; +[UsedInUnityProject] internal class RemoteThrusterFlameController : MonoBehaviour { [SerializeField] diff --git a/QSB/Animation/Player/Thrusters/RemoteThrusterParticlesBehaviour.cs b/QSB/Animation/Player/Thrusters/RemoteThrusterParticlesBehaviour.cs index b28c9f66f..535ce1a4d 100644 --- a/QSB/Animation/Player/Thrusters/RemoteThrusterParticlesBehaviour.cs +++ b/QSB/Animation/Player/Thrusters/RemoteThrusterParticlesBehaviour.cs @@ -4,6 +4,7 @@ namespace QSB.Animation.Player.Thrusters; +[UsedInUnityProject] internal class RemoteThrusterParticlesBehaviour : MonoBehaviour { [SerializeField] diff --git a/QSB/Animation/Player/Thrusters/RemoteThrusterWashController.cs b/QSB/Animation/Player/Thrusters/RemoteThrusterWashController.cs index ffa1dd606..0b41d5196 100644 --- a/QSB/Animation/Player/Thrusters/RemoteThrusterWashController.cs +++ b/QSB/Animation/Player/Thrusters/RemoteThrusterWashController.cs @@ -4,6 +4,7 @@ namespace QSB.Animation.Player.Thrusters; +[UsedInUnityProject] internal class RemoteThrusterWashController : MonoBehaviour { [SerializeField] diff --git a/QSB/Animation/Player/Thrusters/ThrusterManager.cs b/QSB/Animation/Player/Thrusters/ThrusterManager.cs index e0bc0b3d5..227fa7666 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 @@ public static void CreateRemotePlayerVFX(PlayerInfo player) 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/AssetBundles/qsb_network b/QSB/AssetBundles/qsb_network index 5af36f0d5..cbf886f50 100644 Binary files a/QSB/AssetBundles/qsb_network and b/QSB/AssetBundles/qsb_network differ diff --git a/QSB/AssetBundles/qsb_network_big b/QSB/AssetBundles/qsb_network_big index 4688aa523..22247a5b6 100644 Binary files a/QSB/AssetBundles/qsb_network_big and b/QSB/AssetBundles/qsb_network_big differ diff --git a/QSB/Audio/Messages/PlayerAudioControllerOneShotMessage.cs b/QSB/Audio/Messages/PlayerAudioControllerOneShotMessage.cs new file mode 100644 index 000000000..5c3b8c541 --- /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/PlayerAudioControllerUpdateHazardDamageMessage.cs b/QSB/Audio/Messages/PlayerAudioControllerUpdateHazardDamageMessage.cs new file mode 100644 index 000000000..b7ce4ca51 --- /dev/null +++ b/QSB/Audio/Messages/PlayerAudioControllerUpdateHazardDamageMessage.cs @@ -0,0 +1,12 @@ +using QSB.Messaging; +using QSB.Player; + +namespace QSB.Audio.Messages; + +internal class PlayerAudioControllerUpdateHazardDamageMessage : QSBMessage<(uint userID, HazardVolume.HazardType latestHazardType)> +{ + public PlayerAudioControllerUpdateHazardDamageMessage((uint userID, HazardVolume.HazardType latestHazardType) data) : base(data) { } + + public override void OnReceiveRemote() => + QSBPlayerManager.GetPlayer(Data.userID)?.AudioController.SetHazardDamage(Data.latestHazardType); +} diff --git a/QSB/Audio/Messages/PlayerMovementAudioFootstepMessage.cs b/QSB/Audio/Messages/PlayerMovementAudioFootstepMessage.cs new file mode 100644 index 000000000..f17503ec2 --- /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 000000000..adae81493 --- /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 000000000..156e51b2f --- /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 000000000..cf10f8234 --- /dev/null +++ b/QSB/Audio/Patches/PlayerAudioControllerPatches.cs @@ -0,0 +1,64 @@ +using HarmonyLib; +using QSB.Audio.Messages; +using QSB.Messaging; +using QSB.Patches; +using QSB.Player; +using UnityEngine; + +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); + + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerAudioController), nameof(PlayerAudioController.OnArtifactFocus))] + public static void PlayerAudioController_OnArtifactFocus() => PlayOneShot(AudioType.Artifact_Focus); + + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerAudioController), nameof(PlayerAudioController.OnArtifactUnfocus))] + public static void PlayerAudioController_OnArtifactUnfocus() => PlayOneShot(AudioType.Artifact_Unfocus); + + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerAudioController), nameof(PlayerAudioController.OnArtifactConceal))] + public static void PlayerAudioController_OnArtifactConceal() => PlayOneShot(AudioType.Artifact_Conceal); + + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerAudioController), nameof(PlayerAudioController.OnArtifactUnconceal))] + public static void PlayerAudioController_OnArtifactUnconceal() => PlayOneShot(AudioType.Artifact_Unconceal); + + [HarmonyPrefix] + [HarmonyPatch(typeof(PlayerAudioController), nameof(PlayerAudioController.UpdateHazardDamage))] + public static void PlayerAudioController_UpdateHazardDamage(PlayerAudioController __instance, float damage, HazardDetector hazardDetector) + { + var hazardType = damage > 0f ? hazardDetector.GetLatestHazardType() : HazardVolume.HazardType.NONE; + if (hazardType != __instance._hazardTypePlaying) + { + new PlayerAudioControllerUpdateHazardDamageMessage((QSBPlayerManager.LocalPlayerId, hazardDetector.GetLatestHazardType())).Send(); + } + } +} \ 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 000000000..64cfea29d --- /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 000000000..ecdfa51bb --- /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 000000000..be09b0d94 --- /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 000000000..d3c8d6159 --- /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 e43f0b410..6a93b3110 100644 --- a/QSB/Audio/QSBJetpackThrusterAudio.cs +++ b/QSB/Audio/QSBJetpackThrusterAudio.cs @@ -1,8 +1,99 @@ -namespace QSB.Audio; +using QSB.Player; +using QSB.Utility; +using UnityEngine; +namespace QSB.Audio; + +[UsedInUnityProject] 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 c5ebe457a..0c50def97 100644 --- a/QSB/Audio/QSBPlayerAudioController.cs +++ b/QSB/Audio/QSBPlayerAudioController.cs @@ -1,11 +1,34 @@ -using UnityEngine; +using QSB.PlayerBodySetup.Remote; +using QSB.Utility; +using UnityEngine; namespace QSB.Audio; +[UsedInUnityProject] public class QSBPlayerAudioController : MonoBehaviour { public OWAudioSource _oneShotExternalSource; public OWAudioSource _repairToolSource; + public OWAudioSource _damageAudioSource; + + private AudioManager _audioManager; + + public void Start() + { + _audioManager = Locator.GetAudioManager(); + + // TODO: This should be done in the Unity project + var damageAudio = new GameObject("DamageAudioSource"); + damageAudio.SetActive(false); + damageAudio.transform.SetParent(transform, false); + damageAudio.transform.localPosition = Vector3.zero; + _damageAudioSource = damageAudio.AddComponent(); + _damageAudioSource._audioSource = damageAudio.GetAddComponent(); + _damageAudioSource.SetTrack(_repairToolSource.GetTrack()); + _damageAudioSource.spatialBlend = 1f; + _damageAudioSource.gameObject.GetAddComponent(); + damageAudio.SetActive(true); + } public void PlayEquipTool() => _oneShotExternalSource?.PlayOneShot(AudioType.ToolTranslatorEquip); @@ -18,4 +41,62 @@ public void PlayTurnOnFlashlight() 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); + + private void StartHazardDamage(HazardVolume.HazardType latestHazardType) + { + var type = AudioType.EnterVolumeDamageHeat_LP; + if (latestHazardType == HazardVolume.HazardType.DARKMATTER) + { + type = AudioType.EnterVolumeDamageGhostfire_LP; + } + else if (latestHazardType == HazardVolume.HazardType.FIRE) + { + type = AudioType.EnterVolumeDamageFire_LP; + } + + _damageAudioSource.clip = _audioManager.GetSingleAudioClip(type, true); + _damageAudioSource.Stop(); + _damageAudioSource.FadeIn(0.2f, true, true, 1f); + } + + private void EndHazardDamage() + { + if (_damageAudioSource.isPlaying) + { + _damageAudioSource.FadeOut(0.5f, OWAudioSource.FadeOutCompleteAction.STOP, 0f); + } + } + + public void SetHazardDamage(HazardVolume.HazardType latestHazardType) + { + if (latestHazardType == HazardVolume.HazardType.NONE) + { + EndHazardDamage(); + } + else + { + StartHazardDamage(latestHazardType); + } + } } \ No newline at end of file diff --git a/QSB/ConversationSync/WorldObjects/QSBCharacterDialogueTree.cs b/QSB/ConversationSync/WorldObjects/QSBCharacterDialogueTree.cs index 17e8d329b..45fac711c 100644 --- a/QSB/ConversationSync/WorldObjects/QSBCharacterDialogueTree.cs +++ b/QSB/ConversationSync/WorldObjects/QSBCharacterDialogueTree.cs @@ -4,6 +4,9 @@ namespace QSB.ConversationSync.WorldObjects; +/// +/// BUG: do conversation leave on player leave so other people can actually talk lol +/// public class QSBCharacterDialogueTree : WorldObject { public override void SendInitialState(uint to) diff --git a/QSB/EchoesOfTheEye/DreamCandles/Messages/SetLitMessage.cs b/QSB/EchoesOfTheEye/DreamCandles/Messages/SetLitMessage.cs index 175c51ed5..2910c4b47 100644 --- a/QSB/EchoesOfTheEye/DreamCandles/Messages/SetLitMessage.cs +++ b/QSB/EchoesOfTheEye/DreamCandles/Messages/SetLitMessage.cs @@ -1,6 +1,5 @@ using QSB.EchoesOfTheEye.DreamCandles.WorldObjects; using QSB.Messaging; -using QSB.Patches; namespace QSB.EchoesOfTheEye.DreamCandles.Messages; @@ -11,5 +10,5 @@ public SetLitMessage(bool lit, bool playAudio, bool instant) : base((lit, playAudio, instant)) { } public override void OnReceiveRemote() => - QSBPatch.RemoteCall(() => WorldObject.AttachedObject.SetLit(Data.Lit, Data.PlayAudio, Data.Instant)); + WorldObject.AttachedObject.SetLit(Data.Lit, Data.PlayAudio, Data.Instant); } diff --git a/QSB/EchoesOfTheEye/DreamLantern/DreamLanternManager.cs b/QSB/EchoesOfTheEye/DreamLantern/DreamLanternManager.cs index 700f0865c..bdb6d5b43 100644 --- a/QSB/EchoesOfTheEye/DreamLantern/DreamLanternManager.cs +++ b/QSB/EchoesOfTheEye/DreamLantern/DreamLanternManager.cs @@ -11,5 +11,5 @@ public class DreamLanternManager : WorldObjectManager public override bool DlcOnly => true; public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct) => - QSBWorldSync.Init(); + QSBWorldSync.Init(); } diff --git a/QSB/EchoesOfTheEye/DreamLantern/Messages/SetConcealedMessage.cs b/QSB/EchoesOfTheEye/DreamLantern/Messages/SetConcealedMessage.cs index a06f1d595..802606a6c 100644 --- a/QSB/EchoesOfTheEye/DreamLantern/Messages/SetConcealedMessage.cs +++ b/QSB/EchoesOfTheEye/DreamLantern/Messages/SetConcealedMessage.cs @@ -1,13 +1,12 @@ using QSB.EchoesOfTheEye.DreamLantern.WorldObjects; using QSB.Messaging; -using QSB.Patches; namespace QSB.EchoesOfTheEye.DreamLantern.Messages; -internal class SetConcealedMessage : QSBWorldObjectMessage +internal class SetConcealedMessage : QSBWorldObjectMessage { public SetConcealedMessage(bool concealed) : base(concealed) { } - public override void OnReceiveRemote() - => QSBPatch.RemoteCall(() => WorldObject.AttachedObject.SetConcealed(Data)); + public override void OnReceiveRemote() => + WorldObject.AttachedObject.SetConcealed(Data); } diff --git a/QSB/EchoesOfTheEye/DreamLantern/Messages/SetFocusMessage.cs b/QSB/EchoesOfTheEye/DreamLantern/Messages/SetFocusMessage.cs index 1fa7c5e39..762820f70 100644 --- a/QSB/EchoesOfTheEye/DreamLantern/Messages/SetFocusMessage.cs +++ b/QSB/EchoesOfTheEye/DreamLantern/Messages/SetFocusMessage.cs @@ -1,13 +1,12 @@ using QSB.EchoesOfTheEye.DreamLantern.WorldObjects; using QSB.Messaging; -using QSB.Patches; namespace QSB.EchoesOfTheEye.DreamLantern.Messages; -internal class SetFocusMessage : QSBWorldObjectMessage +internal class SetFocusMessage : QSBWorldObjectMessage { public SetFocusMessage(float focus) : base(focus) { } public override void OnReceiveRemote() - => QSBPatch.RemoteCall(() => WorldObject.AttachedObject.SetFocus(Data)); + => WorldObject.AttachedObject.SetFocus(Data); } diff --git a/QSB/EchoesOfTheEye/DreamLantern/Messages/SetLitMessage.cs b/QSB/EchoesOfTheEye/DreamLantern/Messages/SetLitMessage.cs index 48e4395a7..0cd54f897 100644 --- a/QSB/EchoesOfTheEye/DreamLantern/Messages/SetLitMessage.cs +++ b/QSB/EchoesOfTheEye/DreamLantern/Messages/SetLitMessage.cs @@ -1,13 +1,21 @@ using QSB.EchoesOfTheEye.DreamLantern.WorldObjects; using QSB.Messaging; -using QSB.Patches; namespace QSB.EchoesOfTheEye.DreamLantern.Messages; -internal class SetLitMessage : QSBWorldObjectMessage +internal class SetLitMessage : QSBWorldObjectMessage { public SetLitMessage(bool lit) : base(lit) { } public override void OnReceiveRemote() - => QSBPatch.RemoteCall(() => WorldObject.AttachedObject.SetLit(Data)); + { + WorldObject.AttachedObject.SetLit(Data); + WorldObject.DreamLanternItem?._oneShotSource?.PlayOneShot(Data ? AudioType.Artifact_Light : AudioType.Artifact_Extinguish); + + // If a lantern is already lit you shouldn't be able to pick it up + if (Data) + { + WorldObject.DreamLanternItem?.EnableInteraction(false); + } + } } diff --git a/QSB/EchoesOfTheEye/DreamLantern/Messages/SetRangeMessage.cs b/QSB/EchoesOfTheEye/DreamLantern/Messages/SetRangeMessage.cs index 093fde836..b5e7cdb94 100644 --- a/QSB/EchoesOfTheEye/DreamLantern/Messages/SetRangeMessage.cs +++ b/QSB/EchoesOfTheEye/DreamLantern/Messages/SetRangeMessage.cs @@ -1,13 +1,12 @@ using QSB.EchoesOfTheEye.DreamLantern.WorldObjects; using QSB.Messaging; -using QSB.Patches; namespace QSB.EchoesOfTheEye.DreamLantern.Messages; -internal class SetRangeMessage : QSBWorldObjectMessage +internal class SetRangeMessage : QSBWorldObjectMessage { public SetRangeMessage(float minRange, float maxRange) : base((minRange, maxRange)) { } public override void OnReceiveRemote() - => QSBPatch.RemoteCall(() => WorldObject.AttachedObject.SetRange(Data.minRange, Data.maxRange)); + => WorldObject.AttachedObject.SetRange(Data.minRange, Data.maxRange); } diff --git a/QSB/EchoesOfTheEye/DreamLantern/Patches/DreamLanternPatches.cs b/QSB/EchoesOfTheEye/DreamLantern/Patches/DreamLanternPatches.cs index 2b83b6d6d..f2ed4886e 100644 --- a/QSB/EchoesOfTheEye/DreamLantern/Patches/DreamLanternPatches.cs +++ b/QSB/EchoesOfTheEye/DreamLantern/Patches/DreamLanternPatches.cs @@ -31,7 +31,13 @@ public static void SetLit(DreamLanternController __instance, bool lit) return; } - __instance.GetWorldObject().SendMessage(new SetLitMessage(lit)); + var qsbDreamLantern = __instance.GetWorldObject(); + // ghost lanterns should only be controlled by the host + if (qsbDreamLantern.IsGhostLantern && !QSBCore.IsHost) + { + return; + } + qsbDreamLantern.SendMessage(new SetLitMessage(lit)); } [HarmonyPrefix] @@ -53,7 +59,13 @@ public static void SetConcealed(DreamLanternController __instance, bool conceale return; } - __instance.GetWorldObject().SendMessage(new SetConcealedMessage(concealed)); + var qsbDreamLantern = __instance.GetWorldObject(); + // ghost lanterns should only be controlled by the host + if (qsbDreamLantern.IsGhostLantern && !QSBCore.IsHost) + { + return; + } + qsbDreamLantern.SendMessage(new SetConcealedMessage(concealed)); } [HarmonyPrefix] @@ -76,7 +88,13 @@ public static void SetFocus(DreamLanternController __instance, float focus) return; } - __instance.GetWorldObject().SendMessage(new SetFocusMessage(focus)); + var qsbDreamLantern = __instance.GetWorldObject(); + // ghost lanterns should only be controlled by the host + if (qsbDreamLantern.IsGhostLantern && !QSBCore.IsHost) + { + return; + } + qsbDreamLantern.SendMessage(new SetFocusMessage(focus)); } [HarmonyPrefix] @@ -98,6 +116,12 @@ public static void SetRange(DreamLanternController __instance, float minRange, f return; } - __instance.GetWorldObject().SendMessage(new SetRangeMessage(minRange, maxRange)); + var qsbDreamLantern = __instance.GetWorldObject(); + // ghost lanterns should only be controlled by the host + if (qsbDreamLantern.IsGhostLantern && !QSBCore.IsHost) + { + return; + } + qsbDreamLantern.SendMessage(new SetRangeMessage(minRange, maxRange)); } } diff --git a/QSB/EchoesOfTheEye/DreamLantern/WorldObjects/QSBDreamLantern.cs b/QSB/EchoesOfTheEye/DreamLantern/WorldObjects/QSBDreamLantern.cs deleted file mode 100644 index 1f5fe10b2..000000000 --- a/QSB/EchoesOfTheEye/DreamLantern/WorldObjects/QSBDreamLantern.cs +++ /dev/null @@ -1,19 +0,0 @@ -using QSB.EchoesOfTheEye.DreamLantern.Messages; -using QSB.Messaging; -using QSB.WorldSync; - -namespace QSB.EchoesOfTheEye.DreamLantern.WorldObjects; - -/// -/// TODO: lanterns held by ghosts should only be controlled by the host (to prevent it from visually freaking out) -/// -public class QSBDreamLantern : WorldObject -{ - public override void SendInitialState(uint to) - { - this.SendMessage(new SetLitMessage(AttachedObject._lit) { To = to }); - this.SendMessage(new SetConcealedMessage(AttachedObject._concealed) { To = to }); - this.SendMessage(new SetFocusMessage(AttachedObject._focus) { To = to }); - this.SendMessage(new SetRangeMessage(AttachedObject._minRange, AttachedObject._maxRange) { To = to }); - } -} diff --git a/QSB/EchoesOfTheEye/DreamLantern/WorldObjects/QSBDreamLanternController.cs b/QSB/EchoesOfTheEye/DreamLantern/WorldObjects/QSBDreamLanternController.cs new file mode 100644 index 000000000..ce484d02d --- /dev/null +++ b/QSB/EchoesOfTheEye/DreamLantern/WorldObjects/QSBDreamLanternController.cs @@ -0,0 +1,31 @@ +using Cysharp.Threading.Tasks; +using QSB.EchoesOfTheEye.DreamLantern.Messages; +using QSB.Messaging; +using QSB.WorldSync; +using System.Threading; + +namespace QSB.EchoesOfTheEye.DreamLantern.WorldObjects; + +public class QSBDreamLanternController : WorldObject +{ + public DreamLanternItem DreamLanternItem { get; private set; } + + public override async UniTask Init(CancellationToken ct) + { + // Ghosts don't have the item and instead the effects are controlled by GhostEffects + if (!IsGhostLantern) + { + DreamLanternItem = AttachedObject.GetComponent(); + } + } + + public override void SendInitialState(uint to) + { + this.SendMessage(new SetLitMessage(AttachedObject._lit) { To = to }); + this.SendMessage(new SetConcealedMessage(AttachedObject._concealed) { To = to }); + this.SendMessage(new SetFocusMessage(AttachedObject._focus) { To = to }); + this.SendMessage(new SetRangeMessage(AttachedObject._minRange, AttachedObject._maxRange) { To = to }); + } + + public bool IsGhostLantern => AttachedObject.name == "GhostLantern"; // it's as shrimple as that +} diff --git a/QSB/EchoesOfTheEye/DreamObjectProjectors/Messages/ProjectorLitMessage.cs b/QSB/EchoesOfTheEye/DreamObjectProjectors/Messages/ProjectorLitMessage.cs index bad80e3f4..3715abd11 100644 --- a/QSB/EchoesOfTheEye/DreamObjectProjectors/Messages/ProjectorLitMessage.cs +++ b/QSB/EchoesOfTheEye/DreamObjectProjectors/Messages/ProjectorLitMessage.cs @@ -1,6 +1,5 @@ using QSB.EchoesOfTheEye.DreamObjectProjectors.WorldObject; using QSB.Messaging; -using QSB.Patches; namespace QSB.EchoesOfTheEye.DreamObjectProjectors.Messages; @@ -8,6 +7,5 @@ internal class ProjectorLitMessage : QSBWorldObjectMessage QSBPatch.RemoteCall(() => WorldObject.AttachedObject.SetLit(Data)); + public override void OnReceiveRemote() => WorldObject.AttachedObject.SetLit(Data); } diff --git a/QSB/EchoesOfTheEye/DreamRafts/Messages/RespawnRaftMessage.cs b/QSB/EchoesOfTheEye/DreamRafts/Messages/RespawnRaftMessage.cs index 6dcf31055..ebf7a4c9e 100644 --- a/QSB/EchoesOfTheEye/DreamRafts/Messages/RespawnRaftMessage.cs +++ b/QSB/EchoesOfTheEye/DreamRafts/Messages/RespawnRaftMessage.cs @@ -1,6 +1,5 @@ using QSB.EchoesOfTheEye.DreamObjectProjectors.WorldObject; using QSB.Messaging; -using QSB.Patches; namespace QSB.EchoesOfTheEye.DreamRafts.Messages; @@ -9,6 +8,6 @@ public class RespawnRaftMessage : QSBWorldObjectMessage public override void OnReceiveRemote() { var attachedObject = (DreamRaftProjector)WorldObject.AttachedObject; - QSBPatch.RemoteCall(attachedObject.RespawnRaft); + attachedObject.RespawnRaft(); } } diff --git a/QSB/EchoesOfTheEye/DreamRafts/Patches/DreamRaftPatches.cs b/QSB/EchoesOfTheEye/DreamRafts/Patches/DreamRaftPatches.cs index d1afc72eb..a0e9cd55a 100644 --- a/QSB/EchoesOfTheEye/DreamRafts/Patches/DreamRaftPatches.cs +++ b/QSB/EchoesOfTheEye/DreamRafts/Patches/DreamRaftPatches.cs @@ -60,19 +60,26 @@ private static void RespawnRaft_Postfix(DreamRaftProjector __instance) /// this is to suspend the raft so it doesn't fall endlessly. /// however, it's okay if it does that, /// and we don't want it to extinguish with other players on it. + /// + /// BUG: this breaks when going thru the volume as a non auth player sometimes. oh well. + /// TODO: use in-raft-volume trigger volume instead. just copy from the ringworld rafts. use this for fake sectors as well /// [HarmonyPrefix] - [HarmonyPatch(typeof(DreamRaftProjector), nameof(DreamRaftProjector.ExtinguishImmediately))] - private static bool ExtinguishImmediately(DreamRaftProjector __instance) + [HarmonyPatch(typeof(DreamWorldController), nameof(DreamWorldController.ExtinguishDreamRaft))] + private static bool ExtinguishDreamRaft(DreamWorldController __instance) { if (!QSBWorldSync.AllObjectsReady) { return true; } - // still release authority over the raft tho - __instance._dreamRaftProjection.GetComponent().GetWorldObject() - .NetworkBehaviour.netIdentity.UpdateAuthQueue(AuthQueueAction.Remove); + if (__instance._lastUsedRaftProjector) + { + // still release authority over the raft tho + __instance._lastUsedRaftProjector + ._dreamRaftProjection.GetComponent().GetWorldObject() + .NetworkBehaviour.netIdentity.UpdateAuthQueue(AuthQueueAction.Remove); + } return false; } diff --git a/QSB/EchoesOfTheEye/EclipseCodeControllers/WorldObjects/QSBEclipseCodeController.cs b/QSB/EchoesOfTheEye/EclipseCodeControllers/WorldObjects/QSBEclipseCodeController.cs index 0f8b1beb3..36ab0cf15 100644 --- a/QSB/EchoesOfTheEye/EclipseCodeControllers/WorldObjects/QSBEclipseCodeController.cs +++ b/QSB/EchoesOfTheEye/EclipseCodeControllers/WorldObjects/QSBEclipseCodeController.cs @@ -32,8 +32,17 @@ public override void OnRemoval() } } - private void OnPlayerLeave(PlayerInfo obj) => - this.SendMessage(new UseControllerMessage(false)); + private void OnPlayerLeave(PlayerInfo player) + { + if (!QSBCore.IsHost) + { + return; + } + if (PlayerInControl == player) + { + this.SendMessage(new UseControllerMessage(false)); + } + } public void SetUser(uint user) { diff --git a/QSB/EchoesOfTheEye/Ghosts/Actions/QSBIdentifyIntruderAction.cs b/QSB/EchoesOfTheEye/Ghosts/Actions/QSBIdentifyIntruderAction.cs index e79417cc4..1934de9ec 100644 --- a/QSB/EchoesOfTheEye/Ghosts/Actions/QSBIdentifyIntruderAction.cs +++ b/QSB/EchoesOfTheEye/Ghosts/Actions/QSBIdentifyIntruderAction.cs @@ -56,7 +56,11 @@ public override float CalculateUtility() return -100f; } - if (_running || (_data.interestedPlayer.sensor.isPlayerHeldLanternVisible && (_data.threatAwareness > GhostData.ThreatAwareness.EverythingIsNormal || _data.interestedPlayer.playerLocation.distance < 20f)) || _data.interestedPlayer.sensor.isIlluminatedByPlayer) + if (_running + || (_data.interestedPlayer.sensor.isPlayerHeldLanternVisible + && (_data.threatAwareness > GhostData.ThreatAwareness.EverythingIsNormal || _data.interestedPlayer.playerLocation.distance < 20f) + && _controller.AttachedObject.GetNodeMap().CheckLocalPointInBounds(_data.interestedPlayer.playerLocation.localPosition)) + || _data.interestedPlayer.sensor.isIlluminatedByPlayer) { return 80f; } diff --git a/QSB/EchoesOfTheEye/Ghosts/Actions/QSBStalkAction.cs b/QSB/EchoesOfTheEye/Ghosts/Actions/QSBStalkAction.cs index 2dd843468..9d9f1a450 100644 --- a/QSB/EchoesOfTheEye/Ghosts/Actions/QSBStalkAction.cs +++ b/QSB/EchoesOfTheEye/Ghosts/Actions/QSBStalkAction.cs @@ -37,8 +37,7 @@ protected override void OnEnterAction() { var flag = _data.interestedPlayer.player.AssignedSimulationLantern.AttachedObject.GetLanternController().IsConcealed(); _wasPlayerLanternConcealed = flag; - _isFocusingLight = flag; - _shouldFocusLightOnPlayer = flag; + _isFocusingLight = _shouldFocusLightOnPlayer = flag || !_data.interestedPlayer.sensor.isPlayerHoldingLantern; _changeFocusTime = 0f; _controller.ChangeLanternFocus(_isFocusingLight ? 1f : 0f, 2f); _controller.SetLanternConcealed(!_isFocusingLight, true); @@ -80,15 +79,16 @@ public override void FixedUpdate_Action() && _data.interestedPlayer.wasPlayerLocationKnown; _wasPlayerLanternConcealed = isPlayerLanternConcealed; - if (sawPlayerLanternConceal && !_shouldFocusLightOnPlayer) + var flag3 = (!_data.interestedPlayer.sensor.isPlayerHoldingLantern && _data.interestedPlayer.wasPlayerLocationKnown) || _data.interestedPlayer.sensor.isPlayerDroppedLanternVisible; + if ((sawPlayerLanternConceal || flag3) && !_shouldFocusLightOnPlayer) { _shouldFocusLightOnPlayer = true; - _changeFocusTime = Time.time + 1f; + _changeFocusTime = Time.time + 0.5f; } else if (_data.interestedPlayer.sensor.isPlayerHeldLanternVisible && _shouldFocusLightOnPlayer) { _shouldFocusLightOnPlayer = false; - _changeFocusTime = Time.time + 1f; + _changeFocusTime = Time.time + 0.5f; } if (_isFocusingLight != _shouldFocusLightOnPlayer && Time.time > _changeFocusTime) diff --git a/QSB/EchoesOfTheEye/Ghosts/Messages/FaceLocalDirectionMessage.cs b/QSB/EchoesOfTheEye/Ghosts/Messages/FaceLocalDirectionMessage.cs deleted file mode 100644 index 72eec1c1c..000000000 --- a/QSB/EchoesOfTheEye/Ghosts/Messages/FaceLocalDirectionMessage.cs +++ /dev/null @@ -1,22 +0,0 @@ -using QSB.EchoesOfTheEye.Ghosts.WorldObjects; -using QSB.Messaging; -using QSB.Utility; -using UnityEngine; - -namespace QSB.EchoesOfTheEye.Ghosts.Messages; - -internal class FaceLocalDirectionMessage : QSBWorldObjectMessage -{ - public FaceLocalDirectionMessage(Vector3 localDirection, float degreesPerSecond, float turnAcceleration) : base((localDirection, degreesPerSecond, turnAcceleration)) { } - - public override void OnReceiveRemote() - { - if (QSBCore.IsHost) - { - DebugLog.ToConsole("Error - Received FaceLocalDirectionMessage on host. Something has gone horribly wrong!", OWML.Common.MessageType.Error); - return; - } - - WorldObject.FaceLocalDirection(Data.localDirection, Data.degreesPerSecond, Data.turnAcceleration, true); - } -} diff --git a/QSB/EchoesOfTheEye/Ghosts/Patches/GhostPartyPathDirectorPatches.cs b/QSB/EchoesOfTheEye/Ghosts/Patches/GhostPartyPathDirectorPatches.cs index 9db6902ad..68491445b 100644 --- a/QSB/EchoesOfTheEye/Ghosts/Patches/GhostPartyPathDirectorPatches.cs +++ b/QSB/EchoesOfTheEye/Ghosts/Patches/GhostPartyPathDirectorPatches.cs @@ -81,12 +81,15 @@ public static bool Update(GhostPartyPathDirector __instance) ghostBrain.AttachedObject.transform.eulerAngles = Vector3.up * __instance._ghostSpawns[Random.Range(0, __instance._ghostSpawns.Length)].spawnTransform.eulerAngles.y; ghostBrain.TabulaRasa(); partyPathAction.ResetPath(); - if (__instance._numEnabledGhostProxies < __instance._ghostFinalDestinations.Length && __instance._ghostFinalDestinations[__instance._numEnabledGhostProxies].proxyGhost != null) + if (!__instance._disableGhostProxies && __instance._numEnabledGhostProxies < __instance._ghostFinalDestinations.Length) { - __instance._ghostFinalDestinations[__instance._numEnabledGhostProxies].proxyGhost.gameObject.SetActive(true); + if (__instance._ghostFinalDestinations[__instance._numEnabledGhostProxies].proxyGhost != null) + { + __instance._ghostFinalDestinations[__instance._numEnabledGhostProxies].proxyGhost.Reveal(); + } + __instance._numEnabledGhostProxies++; } - __instance._numEnabledGhostProxies++; __instance._waitingGhosts.Add(ghostBrain.AttachedObject); } } diff --git a/QSB/EchoesOfTheEye/Ghosts/WorldObjects/QSBGhostController.cs b/QSB/EchoesOfTheEye/Ghosts/WorldObjects/QSBGhostController.cs index 228ad9837..311376f84 100644 --- a/QSB/EchoesOfTheEye/Ghosts/WorldObjects/QSBGhostController.cs +++ b/QSB/EchoesOfTheEye/Ghosts/WorldObjects/QSBGhostController.cs @@ -222,26 +222,6 @@ public void FaceNode(GhostNode node, TurnSpeed turnSpeed, float nodeDelay, bool AttachedObject.FaceNode(node, turnSpeed, nodeDelay, autoFocusLantern); } - public void FaceLocalDirection(Vector3 localDirection, TurnSpeed turnSpeed) - { - FaceLocalDirection(localDirection, GhostConstants.GetTurnSpeed(turnSpeed), GhostConstants.GetTurnAcceleration(turnSpeed)); - } - - public void FaceLocalDirection(Vector3 localDirection, float degreesPerSecond, float turnAcceleration = 360f, bool remote = false) - { - if (!remote) - { - if (!QSBCore.IsHost) - { - return; - } - - this.SendMessage(new FaceLocalDirectionMessage(localDirection, degreesPerSecond, turnAcceleration)); - } - - AttachedObject.FaceLocalDirection(localDirection, degreesPerSecond, turnAcceleration); - } - public void StopMoving() { if (!QSBCore.IsHost) diff --git a/QSB/EchoesOfTheEye/Ghosts/WorldObjects/QSBGhostEffects.cs b/QSB/EchoesOfTheEye/Ghosts/WorldObjects/QSBGhostEffects.cs index bb31271fc..eeaf87693 100644 --- a/QSB/EchoesOfTheEye/Ghosts/WorldObjects/QSBGhostEffects.cs +++ b/QSB/EchoesOfTheEye/Ghosts/WorldObjects/QSBGhostEffects.cs @@ -79,20 +79,30 @@ public void Update_Effects() var relativeVelocity = AttachedObject._controller.GetRelativeVelocity(); var num = (AttachedObject._movementStyle == GhostEffects.MovementStyle.Chase) ? 8f : 2f; - var targetValue = new Vector2(relativeVelocity.x / num, relativeVelocity.z / num); + float num2 = new Vector2(relativeVelocity.y, relativeVelocity.z).magnitude * Mathf.Sign(relativeVelocity.z); + Vector2 targetValue = new Vector2(relativeVelocity.x / num, num2 / num); AttachedObject._smoothedMoveSpeed = AttachedObject._moveSpeedSpring.Update(AttachedObject._smoothedMoveSpeed, targetValue, Time.deltaTime); AttachedObject._animator.SetFloat(GhostEffects.AnimatorKeys.Float_MoveDirectionX, AttachedObject._smoothedMoveSpeed.x); AttachedObject._animator.SetFloat(GhostEffects.AnimatorKeys.Float_MoveDirectionY, AttachedObject._smoothedMoveSpeed.y); + float num3 = Vector3.SignedAngle(new Vector3(relativeVelocity.x, 0f, relativeVelocity.z), relativeVelocity, Vector3.left); + float targetValue2 = Mathf.Clamp(num3 / 30f, -1f, 1f); + if (num3 > 15f && AttachedObject._controller.IsApproachingEndOfIncline()) + { + targetValue2 = 0f; + } + AttachedObject._smoothedMoveSlope = AttachedObject._moveSlopeSpring.Update(AttachedObject._smoothedMoveSlope, targetValue2, Time.deltaTime); + AttachedObject._animator.SetFloat(GhostEffects.AnimatorKeys.Float_MoveSlope, AttachedObject._smoothedMoveSlope); + AttachedObject._smoothedTurnSpeed = AttachedObject._turnSpeedSpring.Update(AttachedObject._smoothedTurnSpeed, AttachedObject._controller.GetAngularVelocity() / 90f, Time.deltaTime); AttachedObject._animator.SetFloat(GhostEffects.AnimatorKeys.Float_TurnSpeed, AttachedObject._smoothedTurnSpeed); var target = _data.isIlluminated ? 1f : 0f; - var num2 = _data.isIlluminated ? 8f : 0.8f; - AttachedObject._eyeGlow = Mathf.MoveTowards(AttachedObject._eyeGlow, target, Time.deltaTime * num2); + var num4 = _data.isIlluminated ? 8f : 0.8f; + AttachedObject._eyeGlow = Mathf.MoveTowards(AttachedObject._eyeGlow, target, Time.deltaTime * num4); var closestPlayer = QSBPlayerManager.GetClosestPlayerToWorldPoint(AttachedObject.transform.position, true); - var num3 = (closestPlayer?.AssignedSimulationLantern?.AttachedObject?.GetLanternController()?.GetLight()?.GetFlickerScale() - 1f + 0.07f) / 0.14f ?? 0; - num3 = Mathf.Lerp(0.7f, 1f, num3); + var num5 = (closestPlayer?.AssignedSimulationLantern?.AttachedObject?.GetLanternController()?.GetLight()?.GetFlickerScale() - 1f + 0.07f) / 0.14f ?? 0; + num5 = Mathf.Lerp(0.7f, 1f, num5); AttachedObject.SetEyeGlow(AttachedObject._eyeGlow * num3); if (AttachedObject._playingDeathSequence) diff --git a/QSB/EchoesOfTheEye/Ghosts/WorldObjects/QSBGhostSensors.cs b/QSB/EchoesOfTheEye/Ghosts/WorldObjects/QSBGhostSensors.cs index af8b8d108..e38b02830 100644 --- a/QSB/EchoesOfTheEye/Ghosts/WorldObjects/QSBGhostSensors.cs +++ b/QSB/EchoesOfTheEye/Ghosts/WorldObjects/QSBGhostSensors.cs @@ -46,8 +46,10 @@ public void Initialize(QSBGhostData data) } public bool CanGrabPlayer(GhostPlayer player) - => !PlayerState.IsAttached() + => !PlayerState.IsAttached() // TODO : check for each player && player.playerLocation.distanceXZ < 2f + AttachedObject._grabDistanceBuff + && player.playerLocation.toPosition.y > -2f + && player.playerLocation.toPosition.y < 3f && player.playerLocation.degreesToPositionXZ < 20f + AttachedObject._grabAngleBuff && AttachedObject._animator.GetFloat("GrabWindow") > 0.5f; diff --git a/QSB/EchoesOfTheEye/LightSensorSync/Messages/IlluminatingLanternsMessage.cs b/QSB/EchoesOfTheEye/LightSensorSync/Messages/IlluminatingLanternsMessage.cs index f557cbf6a..a9b657ad5 100644 --- a/QSB/EchoesOfTheEye/LightSensorSync/Messages/IlluminatingLanternsMessage.cs +++ b/QSB/EchoesOfTheEye/LightSensorSync/Messages/IlluminatingLanternsMessage.cs @@ -10,12 +10,12 @@ namespace QSB.EchoesOfTheEye.LightSensorSync.Messages; internal class IlluminatingLanternsMessage : QSBWorldObjectMessage { public IlluminatingLanternsMessage(IEnumerable lanterns) : - base(lanterns.Select(x => x.GetWorldObject().ObjectId).ToArray()) { } + base(lanterns.Select(x => x.GetWorldObject().ObjectId).ToArray()) { } public override void OnReceiveRemote() { WorldObject.AttachedObject._illuminatingDreamLanternList.Clear(); WorldObject.AttachedObject._illuminatingDreamLanternList.AddRange( - Data.Select(x => x.GetWorldObject().AttachedObject)); + Data.Select(x => x.GetWorldObject().AttachedObject)); } } diff --git a/QSB/EchoesOfTheEye/LightSensorSync/Messages/PlayerIlluminatingLanternsMessage.cs b/QSB/EchoesOfTheEye/LightSensorSync/Messages/PlayerIlluminatingLanternsMessage.cs index 4cc78e694..40bde54f3 100644 --- a/QSB/EchoesOfTheEye/LightSensorSync/Messages/PlayerIlluminatingLanternsMessage.cs +++ b/QSB/EchoesOfTheEye/LightSensorSync/Messages/PlayerIlluminatingLanternsMessage.cs @@ -12,7 +12,7 @@ internal class PlayerIlluminatingLanternsMessage : QSBMessage<(uint playerId, in public PlayerIlluminatingLanternsMessage(uint playerId, IEnumerable lanterns) : base(( playerId, - lanterns.Select(x => x.GetWorldObject().ObjectId).ToArray() + lanterns.Select(x => x.GetWorldObject().ObjectId).ToArray() )) { } public override void OnReceiveRemote() @@ -21,6 +21,6 @@ public override void OnReceiveRemote() lightSensor._illuminatingDreamLanternList.Clear(); lightSensor._illuminatingDreamLanternList.AddRange( - Data.lanterns.Select(x => x.GetWorldObject().AttachedObject)); + Data.lanterns.Select(x => x.GetWorldObject().AttachedObject)); } } diff --git a/QSB/EchoesOfTheEye/LightSensorSync/Patches/LightSensorPatches.cs b/QSB/EchoesOfTheEye/LightSensorSync/Patches/LightSensorPatches.cs index 20ffaf748..dd0a42476 100644 --- a/QSB/EchoesOfTheEye/LightSensorSync/Patches/LightSensorPatches.cs +++ b/QSB/EchoesOfTheEye/LightSensorSync/Patches/LightSensorPatches.cs @@ -111,6 +111,11 @@ private static bool ManagedFixedUpdate(SingleLightSensor __instance) _illuminatingDreamLanternList.AddRange(__instance._illuminatingDreamLanternList); } __instance.UpdateIllumination(); + if (__instance._illuminatingDreamLanternList != null && + !__instance._illuminatingDreamLanternList.SequenceEqual(_illuminatingDreamLanternList)) + { + qsbLightSensor.SendMessage(new IlluminatingLanternsMessage(__instance._illuminatingDreamLanternList)); + } if (!illuminated && __instance._illuminated) { __instance.OnDetectLight.Invoke(); @@ -121,11 +126,6 @@ private static bool ManagedFixedUpdate(SingleLightSensor __instance) __instance.OnDetectDarkness.Invoke(); qsbLightSensor.SendMessage(new SetIlluminatedMessage(false)); } - if (__instance._illuminatingDreamLanternList != null && - !__instance._illuminatingDreamLanternList.SequenceEqual(_illuminatingDreamLanternList)) - { - qsbLightSensor.SendMessage(new IlluminatingLanternsMessage(__instance._illuminatingDreamLanternList)); - } } var locallyIlluminated = qsbLightSensor._locallyIlluminated; @@ -165,6 +165,11 @@ private static bool Player_ManagedFixedUpdate(SingleLightSensor __instance) _illuminatingDreamLanternList.AddRange(__instance._illuminatingDreamLanternList); } __instance.UpdateIllumination(); + if (__instance._illuminatingDreamLanternList != null && + !__instance._illuminatingDreamLanternList.SequenceEqual(_illuminatingDreamLanternList)) + { + new PlayerIlluminatingLanternsMessage(QSBPlayerManager.LocalPlayerId, __instance._illuminatingDreamLanternList).Send(); + } if (!illuminated && __instance._illuminated) { __instance.OnDetectLight.Invoke(); @@ -175,11 +180,6 @@ private static bool Player_ManagedFixedUpdate(SingleLightSensor __instance) __instance.OnDetectDarkness.Invoke(); new PlayerSetIlluminatedMessage(QSBPlayerManager.LocalPlayerId, false).Send(); } - if (__instance._illuminatingDreamLanternList != null && - !__instance._illuminatingDreamLanternList.SequenceEqual(_illuminatingDreamLanternList)) - { - new PlayerIlluminatingLanternsMessage(QSBPlayerManager.LocalPlayerId, __instance._illuminatingDreamLanternList).Send(); - } return false; } @@ -247,7 +247,7 @@ private static bool UpdateIllumination(SingleLightSensor __instance) } case LightSourceType.PROBE: { - if (lightSource is QSBProbe qsbProbe) + if (lightSource is QSBSurveyorProbe qsbProbe) { var probe = qsbProbe; if (probe != null && @@ -350,7 +350,7 @@ private static void UpdateLocalIllumination(SingleLightSensor __instance) } case LightSourceType.PROBE: { - if (lightSource is not QSBProbe) + if (lightSource is not QSBSurveyorProbe) { var probe = Locator.GetProbe(); if (probe != null && diff --git a/QSB/EchoesOfTheEye/RaftSync/TransformSync/RaftTransformSync.cs b/QSB/EchoesOfTheEye/RaftSync/TransformSync/RaftTransformSync.cs index dc5307d06..0e436c4ab 100644 --- a/QSB/EchoesOfTheEye/RaftSync/TransformSync/RaftTransformSync.cs +++ b/QSB/EchoesOfTheEye/RaftSync/TransformSync/RaftTransformSync.cs @@ -10,7 +10,8 @@ namespace QSB.EchoesOfTheEye.RaftSync.TransformSync; public class RaftTransformSync : UnsectoredRigidbodySync, ILinkedNetworkBehaviour { - protected override bool UseInterpolation => false; + private bool IsRidingRaft => Locator.GetPlayerController() && Locator.GetPlayerController().GetGroundBody() == AttachedRigidbody; + protected override bool UseInterpolation => !IsRidingRaft; private float _lastSetPositionTime; private const float ForcePositionAfterTime = 1; @@ -67,6 +68,10 @@ protected override void Uninit() private void OnUnsuspend(OWRigidbody suspendedBody) => netIdentity.UpdateAuthQueue(AuthQueueAction.Add); private void OnSuspend(OWRigidbody suspendedBody) => netIdentity.UpdateAuthQueue(AuthQueueAction.Remove); + + + public override void OnStartAuthority() => DebugLog.DebugWrite($"{this} + AUTH"); + public override void OnStopAuthority() => DebugLog.DebugWrite($"{this} - AUTH"); /// /// replacement for base method @@ -74,11 +79,10 @@ protected override void Uninit() /// protected override void ApplyToAttached() { - var targetPos = ReferenceTransform.FromRelPos(transform.position); - var targetRot = ReferenceTransform.FromRelRot(transform.rotation); + var targetPos = ReferenceTransform.FromRelPos(UseInterpolation ? SmoothPosition : transform.position); + var targetRot = ReferenceTransform.FromRelRot(UseInterpolation ? SmoothRotation : transform.rotation); - var onRaft = Locator.GetPlayerController().GetGroundBody() == AttachedRigidbody; - if (onRaft) + if (IsRidingRaft) { if (Time.unscaledTime >= _lastSetPositionTime + ForcePositionAfterTime) { diff --git a/QSB/EchoesOfTheEye/RaftSync/WorldObjects/QSBRaftDock.cs b/QSB/EchoesOfTheEye/RaftSync/WorldObjects/QSBRaftDock.cs index ef1dc465b..7eccf6e36 100644 --- a/QSB/EchoesOfTheEye/RaftSync/WorldObjects/QSBRaftDock.cs +++ b/QSB/EchoesOfTheEye/RaftSync/WorldObjects/QSBRaftDock.cs @@ -1,5 +1,4 @@ using QSB.ItemSync.WorldObjects; -using QSB.Patches; using QSB.WorldSync; namespace QSB.EchoesOfTheEye.RaftSync.WorldObjects; @@ -8,6 +7,5 @@ public class QSBRaftDock : WorldObject, IQSBDropTarget { IItemDropTarget IQSBDropTarget.AttachedObject => AttachedObject; - public void OnPressInteract() => - QSBPatch.RemoteCall(AttachedObject.OnPressInteract); + public void OnPressInteract() => AttachedObject.OnPressInteract(); } diff --git a/QSB/EchoesOfTheEye/Sarcophagus/Messages/OpenMessage.cs b/QSB/EchoesOfTheEye/Sarcophagus/Messages/OpenMessage.cs index 1a13c105c..7964383c9 100644 --- a/QSB/EchoesOfTheEye/Sarcophagus/Messages/OpenMessage.cs +++ b/QSB/EchoesOfTheEye/Sarcophagus/Messages/OpenMessage.cs @@ -1,11 +1,9 @@ using QSB.EchoesOfTheEye.Sarcophagus.WorldObjects; using QSB.Messaging; -using QSB.Patches; namespace QSB.EchoesOfTheEye.Sarcophagus.Messages; public class OpenMessage : QSBWorldObjectMessage { - public override void OnReceiveRemote() => - QSBPatch.RemoteCall(WorldObject.AttachedObject.OnPressInteract); + public override void OnReceiveRemote() => WorldObject.AttachedObject.OnPressInteract(); } diff --git a/QSB/EchoesOfTheEye/SlideProjectors/Messages/NextSlideMessage.cs b/QSB/EchoesOfTheEye/SlideProjectors/Messages/NextSlideMessage.cs index 211b214d4..4da96ec11 100644 --- a/QSB/EchoesOfTheEye/SlideProjectors/Messages/NextSlideMessage.cs +++ b/QSB/EchoesOfTheEye/SlideProjectors/Messages/NextSlideMessage.cs @@ -5,5 +5,5 @@ namespace QSB.EchoesOfTheEye.SlideProjectors.Messages; internal class NextSlideMessage : QSBWorldObjectMessage { - public override void OnReceiveRemote() => WorldObject.NextSlide(); -} \ No newline at end of file + public override void OnReceiveRemote() => WorldObject.AttachedObject.NextSlide(); +} diff --git a/QSB/EchoesOfTheEye/SlideProjectors/Messages/PreviousSlideMessage.cs b/QSB/EchoesOfTheEye/SlideProjectors/Messages/PreviousSlideMessage.cs index 92b3ffb62..e581363c5 100644 --- a/QSB/EchoesOfTheEye/SlideProjectors/Messages/PreviousSlideMessage.cs +++ b/QSB/EchoesOfTheEye/SlideProjectors/Messages/PreviousSlideMessage.cs @@ -5,5 +5,5 @@ namespace QSB.EchoesOfTheEye.SlideProjectors.Messages; internal class PreviousSlideMessage : QSBWorldObjectMessage { - public override void OnReceiveRemote() => WorldObject.PreviousSlide(); -} \ No newline at end of file + public override void OnReceiveRemote() => WorldObject.AttachedObject.PreviousSlide(); +} diff --git a/QSB/EchoesOfTheEye/SlideProjectors/Patches/SlideProjectorPatches.cs b/QSB/EchoesOfTheEye/SlideProjectors/Patches/SlideProjectorPatches.cs index a52c755b7..8c3e6def6 100644 --- a/QSB/EchoesOfTheEye/SlideProjectors/Patches/SlideProjectorPatches.cs +++ b/QSB/EchoesOfTheEye/SlideProjectors/Patches/SlideProjectorPatches.cs @@ -12,27 +12,55 @@ internal class SlideProjectorPatches : QSBPatch { public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect; - [HarmonyPostfix] + [HarmonyPrefix] [HarmonyPatch(nameof(SlideProjector.OnPressInteract))] - public static void OnPressInteract(SlideProjector __instance) => - __instance.GetWorldObject() - .SendMessage(new UseSlideProjectorMessage(true)); + public static void OnPressInteract(SlideProjector __instance) + { + if (!QSBWorldSync.AllObjectsReady) + { + return; + } + __instance.GetWorldObject().SendMessage(new UseSlideProjectorMessage(true)); + } - [HarmonyPostfix] + [HarmonyPrefix] [HarmonyPatch(nameof(SlideProjector.CancelInteraction))] - public static void CancelInteraction(SlideProjector __instance) => - __instance.GetWorldObject() - .SendMessage(new UseSlideProjectorMessage(false)); + public static void CancelInteraction(SlideProjector __instance) + { + if (!QSBWorldSync.AllObjectsReady) + { + return; + } + __instance.GetWorldObject().SendMessage(new UseSlideProjectorMessage(false)); + } - [HarmonyPostfix] + [HarmonyPrefix] [HarmonyPatch(nameof(SlideProjector.NextSlide))] - public static void NextSlide(SlideProjector __instance) => - __instance.GetWorldObject() - .SendMessage(new NextSlideMessage()); + public static void NextSlide(SlideProjector __instance) + { + if (Remote) + { + return; + } + if (!QSBWorldSync.AllObjectsReady) + { + return; + } + __instance.GetWorldObject().SendMessage(new NextSlideMessage()); + } - [HarmonyPostfix] + [HarmonyPrefix] [HarmonyPatch(nameof(SlideProjector.PreviousSlide))] - public static void PreviousSlide(SlideProjector __instance) => - __instance.GetWorldObject() - .SendMessage(new PreviousSlideMessage()); + public static void PreviousSlide(SlideProjector __instance) + { + if (Remote) + { + return; + } + if (!QSBWorldSync.AllObjectsReady) + { + return; + } + __instance.GetWorldObject().SendMessage(new PreviousSlideMessage()); + } } diff --git a/QSB/EchoesOfTheEye/SlideProjectors/WorldObjects/QSBSlideProjector.cs b/QSB/EchoesOfTheEye/SlideProjectors/WorldObjects/QSBSlideProjector.cs index 292e0bfe9..a0b34ae2e 100644 --- a/QSB/EchoesOfTheEye/SlideProjectors/WorldObjects/QSBSlideProjector.cs +++ b/QSB/EchoesOfTheEye/SlideProjectors/WorldObjects/QSBSlideProjector.cs @@ -2,7 +2,6 @@ using QSB.EchoesOfTheEye.SlideProjectors.Messages; using QSB.Messaging; using QSB.Player; -using QSB.Utility; using QSB.WorldSync; using System.Threading; @@ -18,8 +17,17 @@ public override async UniTask Init(CancellationToken ct) => public override void OnRemoval() => QSBPlayerManager.OnRemovePlayer -= OnPlayerLeave; - private void OnPlayerLeave(PlayerInfo obj) => - this.SendMessage(new UseSlideProjectorMessage(false)); + private void OnPlayerLeave(PlayerInfo player) + { + if (!QSBCore.IsHost) + { + return; + } + if (_user == player.PlayerId) + { + this.SendMessage(new UseSlideProjectorMessage(false)); + } + } public override void SendInitialState(uint to) => this.SendMessage(new UseSlideProjectorMessage(_user) { To = to }); @@ -31,61 +39,17 @@ public void SetUser(uint user) { AttachedObject._interactReceiver.SetInteractionEnabled(user == 0 || user == _user); _user = user; - } - public void NextSlide() - { - var hasChangedSlide = false; - if (AttachedObject._slideItem != null && AttachedObject._slideItem.slidesContainer.NextSlideAvailable()) + if (user != 0) { - hasChangedSlide = AttachedObject._slideItem.slidesContainer.IncreaseSlideIndex(); - if (hasChangedSlide) + if (AttachedObject._slideItem != null && AttachedObject.IsProjectorFullyLit()) { - if (AttachedObject._oneShotSource != null) - { - AttachedObject._oneShotSource.PlayOneShot(AudioType.Projector_Next); - } - - if (AttachedObject.IsProjectorFullyLit()) - { - AttachedObject._slideItem.slidesContainer.SetCurrentRead(); - AttachedObject._slideItem.slidesContainer.TryPlayMusicForCurrentSlideTransition(true); - } + AttachedObject._slideItem.slidesContainer.TryPlayMusicForCurrentSlideInclusive(); } } - - if (AttachedObject._gearInterface != null) - { - var audioVolume = hasChangedSlide ? 0f : 0.5f; - AttachedObject._gearInterface.AddRotation(45f, audioVolume); - } - } - - public void PreviousSlide() - { - var hasChangedSlide = false; - if (AttachedObject._slideItem != null && AttachedObject._slideItem.slidesContainer.PrevSlideAvailable()) - { - hasChangedSlide = AttachedObject._slideItem.slidesContainer.DecreaseSlideIndex(); - if (hasChangedSlide) - { - if (AttachedObject._oneShotSource != null) - { - AttachedObject._oneShotSource.PlayOneShot(AudioType.Projector_Prev); - } - - if (AttachedObject.IsProjectorFullyLit()) - { - AttachedObject._slideItem.slidesContainer.SetCurrentRead(); - AttachedObject._slideItem.slidesContainer.TryPlayMusicForCurrentSlideTransition(false); - } - } - } - - if (AttachedObject._gearInterface != null) + else { - var audioVolume = hasChangedSlide ? 0f : 0.5f; - AttachedObject._gearInterface.AddRotation(-45f, audioVolume); + Locator.GetSlideReelMusicManager().OnExitSlideProjector(); } } } diff --git a/QSB/EchoesOfTheEye/WineCellar/Messages/WineCellarSwitchMessage.cs b/QSB/EchoesOfTheEye/WineCellar/Messages/WineCellarSwitchMessage.cs new file mode 100644 index 000000000..88c82005d --- /dev/null +++ b/QSB/EchoesOfTheEye/WineCellar/Messages/WineCellarSwitchMessage.cs @@ -0,0 +1,9 @@ +using QSB.EchoesOfTheEye.WineCellar.WorldObjects; +using QSB.Messaging; + +namespace QSB.EchoesOfTheEye.WineCellar.Messages; + +internal class WineCellarSwitchMessage : QSBWorldObjectMessage +{ + public override void OnReceiveRemote() => WorldObject.AttachedObject.OnPressInteract(); +} diff --git a/QSB/EchoesOfTheEye/WineCellar/Patches/WineCellarPatches.cs b/QSB/EchoesOfTheEye/WineCellar/Patches/WineCellarPatches.cs new file mode 100644 index 000000000..40a4100c0 --- /dev/null +++ b/QSB/EchoesOfTheEye/WineCellar/Patches/WineCellarPatches.cs @@ -0,0 +1,26 @@ +using HarmonyLib; +using QSB.EchoesOfTheEye.WineCellar.Messages; +using QSB.EchoesOfTheEye.WineCellar.WorldObjects; +using QSB.Messaging; +using QSB.Patches; +using QSB.WorldSync; + +namespace QSB.EchoesOfTheEye.WineCellar.Patches; + +internal class WineCellarPatches : QSBPatch +{ + public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect; + + [HarmonyPostfix] + [HarmonyPatch(typeof(WineCellarSwitch), nameof(WineCellarSwitch.OnPressInteract))] + public static void OnPressInteract(WineCellarSwitch __instance) + { + if (Remote) + { + return; + } + + var worldObject = __instance.GetWorldObject(); + worldObject.SendMessage(new WineCellarSwitchMessage()); + } +} diff --git a/QSB/EchoesOfTheEye/WineCellar/WineCellarManager.cs b/QSB/EchoesOfTheEye/WineCellar/WineCellarManager.cs new file mode 100644 index 000000000..e9b523578 --- /dev/null +++ b/QSB/EchoesOfTheEye/WineCellar/WineCellarManager.cs @@ -0,0 +1,15 @@ +using Cysharp.Threading.Tasks; +using QSB.EchoesOfTheEye.WineCellar.WorldObjects; +using QSB.WorldSync; +using System.Threading; + +namespace QSB.EchoesOfTheEye.WineCellar; + +internal class WineCellarManager : WorldObjectManager +{ + public override WorldObjectScene WorldObjectScene => WorldObjectScene.SolarSystem; + public override bool DlcOnly => true; + + public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct) + => QSBWorldSync.Init(); +} diff --git a/QSB/EchoesOfTheEye/WineCellar/WorldObjects/QSBWineCellarSwitch.cs b/QSB/EchoesOfTheEye/WineCellar/WorldObjects/QSBWineCellarSwitch.cs new file mode 100644 index 000000000..58219a004 --- /dev/null +++ b/QSB/EchoesOfTheEye/WineCellar/WorldObjects/QSBWineCellarSwitch.cs @@ -0,0 +1,16 @@ +using QSB.EchoesOfTheEye.WineCellar.Messages; +using QSB.Messaging; +using QSB.WorldSync; + +namespace QSB.EchoesOfTheEye.WineCellar.WorldObjects; + +internal class QSBWineCellarSwitch : WorldObject +{ + public override void SendInitialState(uint to) + { + if (AttachedObject.enabled) + { + this.SendMessage(new WineCellarSwitchMessage { To = to }); + } + } +} diff --git a/QSB/ElevatorSync/WorldObjects/QSBElevator.cs b/QSB/ElevatorSync/WorldObjects/QSBElevator.cs index 65b891e5f..d1413d923 100644 --- a/QSB/ElevatorSync/WorldObjects/QSBElevator.cs +++ b/QSB/ElevatorSync/WorldObjects/QSBElevator.cs @@ -43,7 +43,7 @@ public void RemoteCall(bool isGoingUp) } } - QSBPatch.RemoteCall(AttachedObject.StartLift); + AttachedObject.StartLift(); } private void SetDirection(bool isGoingUp) diff --git a/QSB/GetAttachedOWRigidbodyPatch.cs b/QSB/GetAttachedOWRigidbodyPatch.cs new file mode 100644 index 000000000..7ee82ab1e --- /dev/null +++ b/QSB/GetAttachedOWRigidbodyPatch.cs @@ -0,0 +1,43 @@ +using HarmonyLib; +using QSB.Patches; +using UnityEngine; + +namespace QSB; + +/// +/// TODO: TEST THIS. see if things horribly break. this could be huge. +/// +[HarmonyPatch(typeof(OWExtensions))] +public class GetAttachedOWRigidbodyPatch : QSBPatch +{ + public override QSBPatchTypes Type => QSBPatchTypes.OnModStart; + + [HarmonyPrefix] + [HarmonyPatch(nameof(OWExtensions.GetAttachedOWRigidbody), typeof(GameObject), typeof(bool))] + private static bool GetAttachedOWRigidbody(GameObject obj, bool ignoreThisTransform, out OWRigidbody __result) + { + OWRigidbody owrigidbody = null; + var transform = obj.transform; + if (ignoreThisTransform) + { + transform = obj.transform.parent; + } + while (owrigidbody == null) + { + owrigidbody = transform.GetComponent(); + /* + if (owrigidbody != null && !owrigidbody.gameObject.activeInHierarchy) + { + owrigidbody = null; + } + */ + if ((transform == obj.transform.root && owrigidbody == null) || owrigidbody != null) + { + break; + } + transform = transform.parent; + } + __result = owrigidbody; + return false; + } +} diff --git a/QSB/ItemSync/Messages/SocketItemMessage.cs b/QSB/ItemSync/Messages/SocketItemMessage.cs index 3b824ee9b..57a9243ba 100644 --- a/QSB/ItemSync/Messages/SocketItemMessage.cs +++ b/QSB/ItemSync/Messages/SocketItemMessage.cs @@ -7,16 +7,13 @@ namespace QSB.ItemSync.Messages; -internal class SocketItemMessage : QSBMessage<(SocketMessageType Type, int SocketId, int ItemId)> +internal class SocketItemMessage : QSBWorldObjectMessage { - public SocketItemMessage(SocketMessageType type, OWItemSocket socket, OWItem item) : base(( + public SocketItemMessage(SocketMessageType type, OWItemSocket socket) : base(( type, - socket ? socket.GetWorldObject().ObjectId : -1, - item ? item.GetWorldObject().ObjectId : -1 + socket ? socket.GetWorldObject().ObjectId : -1 )) { } - public override bool ShouldReceive => QSBWorldSync.AllObjectsReady; - public override void OnReceiveRemote() { switch (Data.Type) @@ -24,12 +21,11 @@ public override void OnReceiveRemote() case SocketMessageType.Socket: { var qsbItemSocket = Data.SocketId.GetWorldObject(); - var qsbItem = Data.ItemId.GetWorldObject(); - qsbItemSocket.PlaceIntoSocket(qsbItem); - qsbItem.ItemState.HasBeenInteractedWith = true; - qsbItem.ItemState.State = ItemStateType.Socketed; - qsbItem.ItemState.Socket = qsbItemSocket.AttachedObject; + qsbItemSocket.PlaceIntoSocket(WorldObject); + WorldObject.ItemState.HasBeenInteractedWith = true; + WorldObject.ItemState.State = ItemStateType.Socketed; + WorldObject.ItemState.Socket = qsbItemSocket.AttachedObject; var player = QSBPlayerManager.GetPlayer(From); player.HeldItem = null; @@ -39,7 +35,6 @@ public override void OnReceiveRemote() case SocketMessageType.StartUnsocket: { var qsbItemSocket = Data.SocketId.GetWorldObject(); - var qsbItem = Data.ItemId.GetWorldObject(); if (!qsbItemSocket.IsSocketOccupied()) { @@ -47,19 +42,17 @@ public override void OnReceiveRemote() return; } - qsbItem.StoreLocation(); + WorldObject.StoreLocation(); var player = QSBPlayerManager.GetPlayer(From); - player.HeldItem = qsbItem; + player.HeldItem = WorldObject; qsbItemSocket.RemoveFromSocket(); return; } case SocketMessageType.CompleteUnsocket: { - var qsbItem = Data.ItemId.GetWorldObject(); - - qsbItem.OnCompleteUnsocket(); + WorldObject.OnCompleteUnsocket(); return; } } diff --git a/QSB/ItemSync/Patches/ItemToolPatches.cs b/QSB/ItemSync/Patches/ItemToolPatches.cs index 56acc639d..e8730cd08 100644 --- a/QSB/ItemSync/Patches/ItemToolPatches.cs +++ b/QSB/ItemSync/Patches/ItemToolPatches.cs @@ -35,7 +35,7 @@ public static void SocketItem(ItemTool __instance, OWItemSocket socket) var qsbItem = item.GetWorldObject(); qsbItem.ItemState.State = ItemStateType.Socketed; qsbItem.ItemState.Socket = socket; - new SocketItemMessage(SocketMessageType.Socket, socket, item).Send(); + qsbItem.SendMessage(new SocketItemMessage(SocketMessageType.Socket, socket)); } [HarmonyPrefix] @@ -46,7 +46,7 @@ public static void StartUnsocketItem(OWItemSocket socket) var qsbItem = item.GetWorldObject(); qsbItem.ItemState.HasBeenInteractedWith = true; QSBPlayerManager.LocalPlayer.HeldItem = qsbItem; - new SocketItemMessage(SocketMessageType.StartUnsocket, socket, item).Send(); + qsbItem.SendMessage(new SocketItemMessage(SocketMessageType.StartUnsocket, socket)); } [HarmonyPrefix] @@ -54,7 +54,8 @@ public static void StartUnsocketItem(OWItemSocket socket) public static void CompleteUnsocketItem(ItemTool __instance) { var item = __instance._heldItem; - new SocketItemMessage(SocketMessageType.CompleteUnsocket, null, item).Send(); + var qsbItem = item.GetWorldObject(); + qsbItem.SendMessage(new SocketItemMessage(SocketMessageType.CompleteUnsocket, null)); } [HarmonyPrefix] diff --git a/QSB/ItemSync/WorldObjects/Items/QSBDreamLanternItem.cs b/QSB/ItemSync/WorldObjects/Items/QSBDreamLanternItem.cs index 5e7a263cf..f3a33e816 100644 --- a/QSB/ItemSync/WorldObjects/Items/QSBDreamLanternItem.cs +++ b/QSB/ItemSync/WorldObjects/Items/QSBDreamLanternItem.cs @@ -1,3 +1,67 @@ -namespace QSB.ItemSync.WorldObjects.Items; +using Cysharp.Threading.Tasks; +using System.Linq; +using System.Threading; +using UnityEngine; -public class QSBDreamLanternItem : QSBItem { } +namespace QSB.ItemSync.WorldObjects.Items; + +public class QSBDreamLanternItem : QSBItem +{ + private Material[] _materials; + + public override async UniTask Init(CancellationToken ct) + { + await base.Init(ct); + + // Some lanterns (ie, nonfunctioning) don't have a view model group + if (AttachedObject._lanternType != DreamLanternType.Nonfunctioning) + { + _materials = AttachedObject._lanternController._viewModelGroup?.GetComponentsInChildren(true)?.SelectMany(x => x.materials)?.ToArray(); + } + } + + public override void PickUpItem(Transform holdTransform) + { + base.PickUpItem(holdTransform); + + // Fixes #502: Artifact is visible through the walls + if (AttachedObject._lanternType != DreamLanternType.Nonfunctioning) + { + foreach (var m in _materials) + { + if (m.renderQueue >= 2000) + { + m.renderQueue -= 2000; + } + } + + // The view model looks much smaller than the dropped item + AttachedObject.gameObject.transform.localScale = Vector3.one * 2f; + } + + AttachedObject.EnableInteraction(true); + } + + public override void DropItem(Vector3 worldPosition, Vector3 worldNormal, Transform parent, Sector sector, IItemDropTarget customDropTarget) + { + base.DropItem(worldPosition, worldNormal, parent, sector, customDropTarget); + + if (AttachedObject._lanternType != DreamLanternType.Nonfunctioning) + { + foreach (var m in _materials) + { + if (m.renderQueue < 2000) + { + m.renderQueue += 2000; + } + } + + AttachedObject.gameObject.transform.localScale = Vector3.one; + } + + // If in the DreamWorld, don't let other people pick up your lantern + // Since this method is only called on the remote, this only makes other players unable to pick it up + // If the lantern is lit, the user is in the DreamWorld + AttachedObject.EnableInteraction(!AttachedObject.GetLanternController().IsLit()); + } +} diff --git a/QSB/ItemSync/WorldObjects/Items/QSBItem.cs b/QSB/ItemSync/WorldObjects/Items/QSBItem.cs index 45503a031..bfb676953 100644 --- a/QSB/ItemSync/WorldObjects/Items/QSBItem.cs +++ b/QSB/ItemSync/WorldObjects/Items/QSBItem.cs @@ -2,7 +2,6 @@ using QSB.ItemSync.Messages; using QSB.ItemSync.WorldObjects.Sockets; using QSB.Messaging; -using QSB.Patches; using QSB.Player; using QSB.SectorSync.WorldObjects; using QSB.WorldSync; @@ -71,7 +70,7 @@ private void OnPlayerLeave(PlayerInfo player) if (_lastSocket != null) { - QSBPatch.RemoteCall(() => _lastSocket.PlaceIntoSocket(this)); + _lastSocket.PlaceIntoSocket(this); } else { @@ -95,10 +94,10 @@ public override void SendInitialState(uint to) switch (ItemState.State) { case ItemStateType.Held: - ((IQSBItem)this).SendMessage(new MoveToCarryMessage(ItemState.HoldingPlayer.PlayerId)); + ((IQSBItem)this).SendMessage(new MoveToCarryMessage(ItemState.HoldingPlayer.PlayerId) { To = to }); break; case ItemStateType.Socketed: - new SocketItemMessage(SocketMessageType.Socket, ItemState.Socket, AttachedObject).Send(); + ((IQSBItem)this).SendMessage(new SocketItemMessage(SocketMessageType.Socket, ItemState.Socket) { To = to }); break; case ItemStateType.OnGround: ((IQSBItem)this).SendMessage( @@ -108,18 +107,18 @@ public override void SendInitialState(uint to) ItemState.Parent, ItemState.Sector, ItemState.CustomDropTarget, - ItemState.Rigidbody)); + ItemState.Rigidbody) { To = to }); break; } } public ItemType GetItemType() => AttachedObject.GetItemType(); - public void PickUpItem(Transform holdTransform) => - QSBPatch.RemoteCall(() => AttachedObject.PickUpItem(holdTransform)); + public virtual void PickUpItem(Transform holdTransform) => + AttachedObject.PickUpItem(holdTransform); - public void DropItem(Vector3 worldPosition, Vector3 worldNormal, Transform parent, Sector sector, IItemDropTarget customDropTarget) => - QSBPatch.RemoteCall(() => AttachedObject.DropItem(worldPosition, worldNormal, parent, sector, customDropTarget)); + public virtual void DropItem(Vector3 worldPosition, Vector3 worldNormal, Transform parent, Sector sector, IItemDropTarget customDropTarget) => + AttachedObject.DropItem(worldPosition, worldNormal, parent, sector, customDropTarget); public void OnCompleteUnsocket() => AttachedObject.OnCompleteUnsocket(); } diff --git a/QSB/ItemSync/WorldObjects/Sockets/QSBItemSocket.cs b/QSB/ItemSync/WorldObjects/Sockets/QSBItemSocket.cs index 5aff8bc81..a475e522b 100644 --- a/QSB/ItemSync/WorldObjects/Sockets/QSBItemSocket.cs +++ b/QSB/ItemSync/WorldObjects/Sockets/QSBItemSocket.cs @@ -1,21 +1,25 @@ using QSB.ItemSync.WorldObjects.Items; -using QSB.Patches; using QSB.WorldSync; namespace QSB.ItemSync.WorldObjects.Sockets; internal class QSBItemSocket : WorldObject { - public override void SendInitialState(uint to) - { - // todo SendInitialState - } - public bool IsSocketOccupied() => AttachedObject.IsSocketOccupied(); public void PlaceIntoSocket(IQSBItem item) - => QSBPatch.RemoteCall(() => AttachedObject.PlaceIntoSocket((OWItem)item.AttachedObject)); + { + AttachedObject.PlaceIntoSocket((OWItem)item.AttachedObject); + + // Don't let other users unsocket a DreamLantern in the dreamworld that doesn't belong to them + // DreamLanternSockets only exist in the DreamWorld + AttachedObject.EnableInteraction(AttachedObject is not DreamLanternSocket); + } public void RemoveFromSocket() - => QSBPatch.RemoteCall(AttachedObject.RemoveFromSocket); -} \ No newline at end of file + { + AttachedObject.RemoveFromSocket(); + + AttachedObject.EnableInteraction(true); + } +} diff --git a/QSB/Menus/MenuManager.cs b/QSB/Menus/MenuManager.cs index d98290ca6..007e59108 100644 --- a/QSB/Menus/MenuManager.cs +++ b/QSB/Menus/MenuManager.cs @@ -42,7 +42,7 @@ internal class MenuManager : MonoBehaviour, IAddComponentOnStart private const int _titleButtonIndex = 2; private float _connectPopupOpenTime; - private const string UpdateChangelog = "QSB Version 0.21.1\r\nFixed gamepass not working, and fixed a small bug with light sensors."; + private const string UpdateChangelog = "QSB Version 0.22.0\r\nFixed lots of bugs, and added lots of SFX and VFX stuff."; private Action PopupClose; @@ -361,7 +361,16 @@ private void CreateCommonPopups() ConnectPopup.EnableMenu(false); Connect(); }; - ConnectPopup.OnActivateMenu += () => _connectPopupOpenTime = Time.time; + + ConnectPopup.OnActivateMenu += () => + { + _connectPopupOpenTime = Time.time; + if (QSBCore.Helper.Interaction.ModExists("Raicuparta.NomaiVR")) + { + // ClearInputTextField is called AFTER OnActivateMenu + Delay.RunNextFrame(() => ConnectPopup._inputField.SetTextWithoutNotify(GUIUtility.systemCopyBuffer)); + } + }; OneButtonInfoPopup = QSBCore.MenuApi.MakeInfoPopup("", ""); OneButtonInfoPopup.OnPopupConfirm += () => OnCloseInfoPopup(true); @@ -480,6 +489,11 @@ private void InitPauseMenus() langController.AddTextElement(DisconnectPopup._labelText, false); langController.AddTextElement(DisconnectPopup._confirmButton._buttonText, false); langController.AddTextElement(DisconnectPopup._cancelButton._buttonText, false); + langController.AddTextElement(OneButtonInfoPopup._labelText, false); + langController.AddTextElement(OneButtonInfoPopup._confirmButton._buttonText, false); + langController.AddTextElement(TwoButtonInfoPopup._labelText, false); + langController.AddTextElement(TwoButtonInfoPopup._confirmButton._buttonText, false); + langController.AddTextElement(TwoButtonInfoPopup._cancelButton._buttonText, false); } private void MakeTitleMenus() @@ -498,25 +512,6 @@ private void MakeTitleMenus() Delay.RunWhen(PlayerData.IsLoaded, () => SetButtonActive(ResumeGameButton, PlayerData.LoadLoopCount() > 1)); SetButtonActive(NewGameButton, true); - if (QSBCore.DebugSettings.SkipTitleScreen) - { - Application.runInBackground = true; - var titleScreenManager = FindObjectOfType(); - var titleScreenAnimation = titleScreenManager._cameraController; - const float small = 1 / 1000f; - titleScreenAnimation._gamepadSplash = false; - titleScreenAnimation._introPan = false; - titleScreenAnimation._fadeDuration = small; - titleScreenAnimation.Start(); - var titleAnimationController = titleScreenManager._gfxController; - titleAnimationController._logoFadeDelay = small; - titleAnimationController._logoFadeDuration = small; - titleAnimationController._echoesFadeDelay = small; - titleAnimationController._optionsFadeDelay = small; - titleAnimationController._optionsFadeDuration = small; - titleAnimationController._optionsFadeSpacing = small; - } - var mainMenuFontController = GameObject.Find("MainMenu").GetComponent(); mainMenuFontController.AddTextElement(HostButton.transform.GetChild(0).GetChild(1).GetComponent()); mainMenuFontController.AddTextElement(ConnectButton.transform.GetChild(0).GetChild(1).GetComponent()); @@ -566,8 +561,8 @@ private void Disconnect() private void PreHost() { - bool doesSingleplayerSaveExist = false; - bool doesMultiplayerSaveExist = false; + var doesSingleplayerSaveExist = false; + var doesMultiplayerSaveExist = false; if (!QSBCore.IsStandalone) { var manager = QSBMSStoreProfileManager.SharedInstance; @@ -681,7 +676,7 @@ private void Connect() PlayerData.Init(manager.currentProfileMultiplayerGameSave, manager.currentProfileGameSettings, manager.currentProfileGraphicsSettings, manager.currentProfileInputJSON); } - var address = ConnectPopup.GetInputText(); + var address = ConnectPopup.GetInputText().Trim(); if (address == string.Empty) { address = QSBCore.DefaultServerIP; @@ -695,7 +690,7 @@ private void Connect() Locator.GetMenuInputModule().DisableInputs(); QSBNetworkManager.singleton.networkAddress = address; - // hack to get disconnect call if start client fails immediately + // hack to get disconnect call if start client fails immediately (happens on kcp transport when failing to resolve host name) typeof(NetworkClient).GetProperty(nameof(NetworkClient.connection))!.SetValue(null, new NetworkConnectionToServer()); QSBNetworkManager.singleton.StartClient(); } diff --git a/QSB/Messaging/OWEvents.cs b/QSB/Messaging/OWEvents.cs index 5820a4f5d..865b0b120 100644 --- a/QSB/Messaging/OWEvents.cs +++ b/QSB/Messaging/OWEvents.cs @@ -33,4 +33,8 @@ 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 ProbeSnapshotRemoved = "Probe Snapshot Removed"; // pain + 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/Messaging/QSBMessageManager.cs b/QSB/Messaging/QSBMessageManager.cs index b67976c10..2a96c406d 100644 --- a/QSB/Messaging/QSBMessageManager.cs +++ b/QSB/Messaging/QSBMessageManager.cs @@ -2,6 +2,7 @@ using OWML.Common; using QSB.ClientServerStateSync; using QSB.ClientServerStateSync.Messages; +using QSB.Patches; using QSB.Player; using QSB.Player.Messages; using QSB.Player.TransformSync; @@ -86,7 +87,9 @@ private static void OnClientReceive(QSBMessage msg) if (msg.From != QSBPlayerManager.LocalPlayerId) { + QSBPatch.Remote = true; msg.OnReceiveRemote(); + QSBPatch.Remote = false; } else { diff --git a/QSB/ModelShip/Messages/CrashModelShipMessage.cs b/QSB/ModelShip/Messages/CrashModelShipMessage.cs new file mode 100644 index 000000000..4eda49410 --- /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 241e40a71..d3d34fbff 100644 --- a/QSB/ModelShip/Messages/RespawnModelShipMessage.cs +++ b/QSB/ModelShip/Messages/RespawnModelShipMessage.cs @@ -1,5 +1,4 @@ using QSB.Messaging; -using QSB.Patches; using QSB.WorldSync; namespace QSB.ModelShip.Messages; @@ -8,6 +7,13 @@ 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(); + 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 703d0c65a..464e7f3b6 100644 --- a/QSB/ModelShip/Messages/UseFlightConsoleMessage.cs +++ b/QSB/ModelShip/Messages/UseFlightConsoleMessage.cs @@ -26,20 +26,14 @@ private static void Handler(bool active) 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 +56,24 @@ public override void OnReceiveRemote() 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(); + console.RespawnModelShip(false); } } diff --git a/QSB/ModelShip/ModelShipManager.cs b/QSB/ModelShip/ModelShipManager.cs index 6b60fc9c9..23417fb3d 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/ModelShipThrusterManager.cs b/QSB/ModelShip/ModelShipThrusterManager.cs new file mode 100644 index 000000000..7541cc91b --- /dev/null +++ b/QSB/ModelShip/ModelShipThrusterManager.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace QSB.ModelShip; + +public static class ModelShipThrusterManager +{ + public static readonly List ThrusterFlameControllers = new(); + public static ThrusterWashController ThrusterWashController { get; private set; } + + public static void CreateModelShipVFX(GameObject modelShip) + { + ThrusterFlameControllers.Clear(); + foreach (var item in modelShip.GetComponentsInChildren()) + { + ThrusterFlameControllers.Add(item); + } + + ThrusterWashController = modelShip.GetComponentInChildren(); + } +} diff --git a/QSB/ModelShip/ModelShipThrusterVariableSyncer.cs b/QSB/ModelShip/ModelShipThrusterVariableSyncer.cs new file mode 100644 index 000000000..30c827842 --- /dev/null +++ b/QSB/ModelShip/ModelShipThrusterVariableSyncer.cs @@ -0,0 +1,69 @@ +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 void Init(GameObject modelShip) + { + ThrusterModel = modelShip.GetComponent(); + _thrusterAudio = modelShip.GetComponentInChildren(); + + ModelShipThrusterManager.CreateModelShipVFX(modelShip); + } + + public void Update() + { + if (QSBPlayerManager.LocalPlayer.FlyingModelShip) + { + GetFromShip(); + return; + } + + if (AccelerationSyncer.public_HasChanged()) + { + if (AccelerationSyncer.Value == Vector3.zero) + { + foreach (var item in ModelShipThrusterManager.ThrusterFlameControllers) + { + item.OnStopTranslationalThrust(); + } + + _thrusterAudio.OnStopTranslationalThrust(); + + ModelShipThrusterManager.ThrusterWashController.OnStopTranslationalThrust(); + } + else + { + foreach (var item in ModelShipThrusterManager.ThrusterFlameControllers) + { + item.OnStartTranslationalThrust(); + } + + _thrusterAudio.OnStartTranslationalThrust(); + + ModelShipThrusterManager.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 eebe63701..b434641a9 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 @@ private static void RemoteFlightConsole_RespawnModelShip(bool playEffects) 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 000000000..8d4a76b6e --- /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 000000000..8697105c3 --- /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 (ModelShipThrusterManager.ThrusterFlameControllers == null) return true; + + if (ModelShipThrusterManager.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 23c72c266..29b08c093 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 @@ protected override OWRigidbody InitAttachedRigidbody() 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/OrbSync/TransformSync/NomaiOrbTransformSync.cs b/QSB/OrbSync/TransformSync/NomaiOrbTransformSync.cs index 7fc2bb43e..e3b092fd8 100644 --- a/QSB/OrbSync/TransformSync/NomaiOrbTransformSync.cs +++ b/QSB/OrbSync/TransformSync/NomaiOrbTransformSync.cs @@ -59,17 +59,9 @@ protected override void Uninit() { base.Uninit(); - // this is null sometimes on here, but not on other similar transforms syncs (like anglers) - // idk why, but whatever - if (AttachedTransform) - { - var body = AttachedTransform.GetAttachedOWRigidbody(); - if (body) - { - body.OnUnsuspendOWRigidbody -= OnUnsuspend; - body.OnSuspendOWRigidbody -= OnSuspend; - } - } + var body = AttachedTransform.GetAttachedOWRigidbody(); + body.OnUnsuspendOWRigidbody -= OnUnsuspend; + body.OnSuspendOWRigidbody -= OnSuspend; } private void OnUnsuspend(OWRigidbody suspendedBody) => netIdentity.UpdateAuthQueue(AuthQueueAction.Add); diff --git a/QSB/Patches/QSBPatch.cs b/QSB/Patches/QSBPatch.cs index 42853c67c..618ba1ebe 100644 --- a/QSB/Patches/QSBPatch.cs +++ b/QSB/Patches/QSBPatch.cs @@ -1,6 +1,4 @@ using HarmonyLib; -using QSB.Utility; -using System; namespace QSB.Patches; @@ -12,30 +10,8 @@ public abstract class QSBPatch public void DoPatches(Harmony instance) => instance.PatchAll(GetType()); - #region remote calls - - protected static bool Remote { get; private set; } - protected static object RemoteData { get; private set; } - - public static void RemoteCall(Action call, object data = null) - { - Remote = true; - RemoteData = data; - nameof(QSBPatch).Try("doing remote call", call); - Remote = false; - RemoteData = null; - } - - public static T RemoteCall(Func call, object data = null) - { - Remote = true; - RemoteData = data; - var t = default(T); - nameof(QSBPatch).Try("doing remote call", () => t = call()); - Remote = false; - RemoteData = null; - return t; - } - - #endregion + /// + /// this is true when a message is received remotely (OnReceiveRemote) or a player leaves (OnRemovePlayer) + /// + public static bool Remote; } diff --git a/QSB/Player/Messages/PlayerJoinMessage.cs b/QSB/Player/Messages/PlayerJoinMessage.cs index 5a49644be..e5281c2b6 100644 --- a/QSB/Player/Messages/PlayerJoinMessage.cs +++ b/QSB/Player/Messages/PlayerJoinMessage.cs @@ -71,6 +71,13 @@ public override void OnReceiveRemote() { if (QSBCore.IsHost) { + if (QSBCore.DebugSettings.KickEveryone) + { + DebugLog.ToConsole($"Kicking {PlayerName} because of DebugSettings.KickEveryone", MessageType.Error); + new PlayerKickMessage(From, "This server has DebugSettings.KickEveryone enabled.").Send(); + return; + } + if (QSBVersion != QSBCore.QSBVersion) { DebugLog.ToConsole($"Error - Client {PlayerName} connecting with wrong QSB version. (Client:{QSBVersion}, Server:{QSBCore.QSBVersion})", MessageType.Error); @@ -92,7 +99,7 @@ public override void OnReceiveRemote() return; } - if (QSBPlayerManager.PlayerList.Any(x => x.EyeState >= EyeState.Observatory)) + if (QSBPlayerManager.PlayerList.Any(x => x.EyeState > EyeState.Observatory)) { DebugLog.ToConsole($"Error - Client {PlayerName} connecting too late into eye scene.", MessageType.Error); new PlayerKickMessage(From, QSBLocalization.Current.GameProgressLimit).Send(); diff --git a/QSB/Player/Messages/PlayerKickMessage.cs b/QSB/Player/Messages/PlayerKickMessage.cs index 26cad6577..3374e7624 100644 --- a/QSB/Player/Messages/PlayerKickMessage.cs +++ b/QSB/Player/Messages/PlayerKickMessage.cs @@ -28,19 +28,6 @@ public override void Deserialize(NetworkReader reader) PlayerId = reader.Read(); } - public override void OnReceiveLocal() - { - if (!QSBCore.IsHost) - { - return; - } - - Delay.RunFramesLater(10, KickPlayer); - } - - private void KickPlayer() - => PlayerId.GetNetworkConnection().Disconnect(); - public override void OnReceiveRemote() { if (PlayerId != QSBPlayerManager.LocalPlayerId) @@ -57,5 +44,7 @@ public override void OnReceiveRemote() DebugLog.ToAll(string.Format(QSBLocalization.Current.KickedFromServer, Data)); MenuManager.Instance.OnKicked(Data); + + NetworkClient.Disconnect(); } } \ No newline at end of file diff --git a/QSB/Player/Patches/VolumePatches.cs b/QSB/Player/Patches/VolumePatches.cs index 45f23b93f..841316035 100644 --- a/QSB/Player/Patches/VolumePatches.cs +++ b/QSB/Player/Patches/VolumePatches.cs @@ -1,5 +1,6 @@ using HarmonyLib; using QSB.Patches; +using QSB.Utility; using UnityEngine; namespace QSB.Player.Patches; @@ -43,8 +44,15 @@ public static void OnEffectVolumeEnter(RingRiverFluidVolume __instance, GameObje [HarmonyPrefix] [HarmonyPatch(typeof(ElectricityVolume), nameof(ElectricityVolume.OnEffectVolumeEnter))] - public static bool OnEffectVolumeEnter(ElectricityVolume __instance, GameObject hitObj) => + [HarmonyPatch(typeof(DreamWarpVolume), nameof(DreamWarpVolume.OnEnterTriggerVolume))] + [HarmonyPatch(typeof(NomaiWarpPlatform), nameof(NomaiWarpPlatform.OnEntry))] + public static bool PreventRemotePlayerEnter(object __instance, GameObject hitObj) + { + DebugLog.DebugWrite($"{__instance} funny prevent enter {hitObj}"); // this is a dogshit fix to a bug where this would ApplyShock to remote players, // which would actually apply the shock affects to the entire planet / sector - hitObj.name != "REMOTE_PlayerDetector"; + // + // TODO: also do this with remote probes + return hitObj.name is not ("REMOTE_PlayerDetector" or "REMOTE_CameraDetector"); + } } diff --git a/QSB/Player/PlayerHUDMarker.cs b/QSB/Player/PlayerHUDMarker.cs index 58c23b292..82131999e 100644 --- a/QSB/Player/PlayerHUDMarker.cs +++ b/QSB/Player/PlayerHUDMarker.cs @@ -3,6 +3,7 @@ namespace QSB.Player; +[UsedInUnityProject] public class PlayerHUDMarker : HUDDistanceMarker { private PlayerInfo _player; @@ -33,7 +34,7 @@ private bool ShouldBeVisible() return false; } - return _player.IsReady && !_player.IsDead && !_player.InDreamWorld && _player.Visible; + return _player.IsReady && !_player.IsDead && (!_player.InDreamWorld || QSBPlayerManager.LocalPlayer.InDreamWorld) && _player.Visible; } private void Update() diff --git a/QSB/Player/PlayerInfo.cs b/QSB/Player/PlayerInfo.cs index f84c14cef..06715acd8 100644 --- a/QSB/Player/PlayerInfo.cs +++ b/QSB/Player/PlayerInfo.cs @@ -3,6 +3,7 @@ using QSB.Audio; using QSB.ClientServerStateSync; using QSB.Messaging; +using QSB.ModelShip; using QSB.Player.Messages; using QSB.Player.TransformSync; using QSB.QuantumSync.WorldObjects; @@ -31,9 +32,10 @@ public partial class PlayerInfo public bool IsInEyeShuttle { get; set; } public IQSBQuantumObject EntangledObject { get; set; } public QSBPlayerAudioController AudioController { get; set; } - public bool IsLocalPlayer => TransformSync.isLocalPlayer; + 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) { @@ -155,4 +157,4 @@ public void SetVisible(bool visible, float seconds = 0) } public override string ToString() => $"{PlayerId}:{GetType().Name} ({Name})"; -} \ No newline at end of file +} diff --git a/QSB/Player/PlayerInfoParts/LocalTools.cs b/QSB/Player/PlayerInfoParts/LocalTools.cs index 7af6d84e3..bf25ff0dd 100644 --- a/QSB/Player/PlayerInfoParts/LocalTools.cs +++ b/QSB/Player/PlayerInfoParts/LocalTools.cs @@ -15,7 +15,7 @@ public PlayerProbeLauncher LocalProbeLauncher return null; } - return CameraBody?.transform.Find("ProbeLauncher").GetComponent(); + return (PlayerProbeLauncher)Locator.GetToolModeSwapper().GetProbeLauncher(); } } @@ -43,7 +43,7 @@ public Signalscope LocalSignalscope return null; } - return CameraBody?.transform.Find("Signalscope").GetComponent(); + return Locator.GetToolModeSwapper().GetSignalScope(); } } @@ -57,7 +57,7 @@ public NomaiTranslator LocalTranslator return null; } - return CameraBody?.transform.Find("NomaiTranslatorProp").GetComponent(); + return Locator.GetToolModeSwapper().GetTranslator(); } } } diff --git a/QSB/Player/PlayerInfoParts/Tools.cs b/QSB/Player/PlayerInfoParts/Tools.cs index 65788e416..04141b5cd 100644 --- a/QSB/Player/PlayerInfoParts/Tools.cs +++ b/QSB/Player/PlayerInfoParts/Tools.cs @@ -15,7 +15,7 @@ namespace QSB.Player; public partial class PlayerInfo { public GameObject ProbeBody { get; set; } - public QSBProbe Probe { get; set; } + public QSBSurveyorProbe Probe { get; set; } public QSBFlashlight FlashLight => CameraBody == null ? null : CameraBody.GetComponentInChildren(); public QSBTool Signalscope => GetToolByType(ToolType.Signalscope); public QSBTool Translator => GetToolByType(ToolType.Translator); diff --git a/QSB/Player/PlayerMapMarker.cs b/QSB/Player/PlayerMapMarker.cs index 2a5f48f83..4d35a7340 100644 --- a/QSB/Player/PlayerMapMarker.cs +++ b/QSB/Player/PlayerMapMarker.cs @@ -1,7 +1,9 @@ -using UnityEngine; +using QSB.Utility; +using UnityEngine; namespace QSB.Player; +[UsedInUnityProject] public class PlayerMapMarker : MonoBehaviour { private PlayerInfo _player; @@ -58,7 +60,7 @@ private bool ShouldBeVisible() var playerScreenPos = Locator.GetActiveCamera().WorldToScreenPoint(transform.position); var isInfrontOfCamera = playerScreenPos.z > 0f; - return _player.IsReady && !_player.IsDead && !_player.InDreamWorld && _player.Visible && isInfrontOfCamera; + return _player.IsReady && !_player.IsDead && (!_player.InDreamWorld || QSBPlayerManager.LocalPlayer.InDreamWorld) && _player.Visible && isInfrontOfCamera; } public void LateUpdate() diff --git a/QSB/Player/QSBPlayerManager.cs b/QSB/Player/QSBPlayerManager.cs index b6d32ade4..a7216c8e1 100644 --- a/QSB/Player/QSBPlayerManager.cs +++ b/QSB/Player/QSBPlayerManager.cs @@ -5,7 +5,6 @@ using QSB.Tools.ProbeTool; using QSB.Utility; using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; @@ -22,7 +21,7 @@ public static PlayerInfo LocalPlayer if (localInstance == null) { DebugLog.ToConsole("Error - Trying to get LocalPlayer when the local PlayerTransformSync instance is null." + - $"{Environment.NewLine} Stacktrace : {Environment.StackTrace} ", MessageType.Error); + $"{Environment.NewLine} Stacktrace : {Environment.StackTrace} ", MessageType.Error); return null; } @@ -66,8 +65,8 @@ public static List GetPlayersWithCameras(bool includeLocalCamera = t { var cameraList = PlayerList.Where(x => x.Camera != null && x.PlayerId != LocalPlayerId).ToList(); if (includeLocalCamera - && LocalPlayer != default - && LocalPlayer.Camera != null) + && LocalPlayer != default + && LocalPlayer.Camera != null) { cameraList.Add(LocalPlayer); } @@ -75,11 +74,11 @@ public static List GetPlayersWithCameras(bool includeLocalCamera = t { if (LocalPlayer == default) { - DebugLog.ToConsole($"Error - LocalPlayer is null.", MessageType.Error); + DebugLog.ToConsole("Error - LocalPlayer is null.", MessageType.Error); return cameraList; } - DebugLog.ToConsole($"Error - LocalPlayer.Camera is null.", MessageType.Error); + DebugLog.ToConsole("Error - LocalPlayer.Camera is null.", MessageType.Error); LocalPlayer.Camera = Locator.GetPlayerCamera(); } @@ -89,7 +88,7 @@ public static List GetPlayersWithCameras(bool includeLocalCamera = t public static (Flashlight LocalFlashlight, IEnumerable RemoteFlashlights) GetPlayerFlashlights() => (Locator.GetFlashlight(), PlayerList.Where(x => x.FlashLight != null).Select(x => x.FlashLight)); - public static (SurveyorProbe LocalProbe, IEnumerable RemoteProbes) GetPlayerProbes() + public static (SurveyorProbe LocalProbe, IEnumerable RemoteProbes) GetPlayerProbes() => (Locator.GetProbe(), PlayerList.Where(x => x.Probe != null).Select(x => x.Probe)); public static IEnumerable GetThrusterLightTrackers() @@ -109,7 +108,7 @@ public static PlayerInfo GetClosestPlayerToWorldPoint(List playerLis { if (playerList == null) { - DebugLog.ToConsole($"Error - Cannot get closest player from null player list.", MessageType.Error); + DebugLog.ToConsole("Error - Cannot get closest player from null player list.", MessageType.Error); return null; } @@ -117,7 +116,7 @@ public static PlayerInfo GetClosestPlayerToWorldPoint(List playerLis if (playerList.Count == 0) { - DebugLog.ToConsole($"Error - Cannot get closest player from empty (ready) player list.", MessageType.Error); + DebugLog.ToConsole("Error - Cannot get closest player from empty (ready) player list.", MessageType.Error); return null; } @@ -126,67 +125,4 @@ public static PlayerInfo GetClosestPlayerToWorldPoint(List playerLis public static IEnumerable<(PlayerInfo Player, IQSBItem HeldItem)> GetPlayerCarryItems() => PlayerList.Select(x => (x, x.HeldItem)); - - private static Dictionary _connectionIdToPlayer = new(); - - public static IEnumerator ValidatePlayers() - { - while (true) - { - if (QSBCore.IsInMultiplayer && QSBCore.IsHost) - { - _connectionIdToPlayer.Clear(); - - var playersToRemove = new List(); - - foreach (var player in PlayerList) - { - var transformSync = player.TransformSync; - - if (transformSync == null) - { - DebugLog.ToConsole($"Error - {player.PlayerId}'s TransformSync is null.", MessageType.Error); - playersToRemove.Add(player); - continue; - } - - var networkIdentity = transformSync.netIdentity; - - if (networkIdentity == null) - { - DebugLog.ToConsole($"Error - {player.PlayerId}'s TransformSync's NetworkIdentity is null.", MessageType.Error); - playersToRemove.Add(player); - continue; - } - - var connectionToClient = networkIdentity.connectionToClient; - - if (_connectionIdToPlayer.ContainsKey(connectionToClient.connectionId)) - { - // oh god oh fuck - DebugLog.ToConsole($"Error - {player.PlayerId}'s connectionToClient.connectionId is already being used?!?", MessageType.Error); - playersToRemove.Add(player); - continue; - } - - _connectionIdToPlayer.Add(connectionToClient.connectionId, player); - } - - if (playersToRemove.Count != 0) - { - DebugLog.DebugWrite($"Removing {playersToRemove.Count} invalid players.", MessageType.Success); - - foreach (var player in playersToRemove) - { - OnRemovePlayer?.Invoke(player); - player.HudMarker?.Remove(); - PlayerList.Remove(player); - DebugLog.DebugWrite($"Remove Invalid Player : {player}", MessageType.Info); - } - } - } - - yield return new WaitForSecondsRealtime(5); - } - } -} \ No newline at end of file +} diff --git a/QSB/Player/RemotePlayerFluidDetector.cs b/QSB/Player/RemotePlayerFluidDetector.cs index 50def10e5..a732999cb 100644 --- a/QSB/Player/RemotePlayerFluidDetector.cs +++ b/QSB/Player/RemotePlayerFluidDetector.cs @@ -7,6 +7,7 @@ namespace QSB.Player; +[UsedInUnityProject] public class RemotePlayerFluidDetector : PriorityDetector { private SplashEffect[] _splashEffects; diff --git a/QSB/Player/RemotePlayerVelocity.cs b/QSB/Player/RemotePlayerVelocity.cs index 5b5c87f00..80d6c251e 100644 --- a/QSB/Player/RemotePlayerVelocity.cs +++ b/QSB/Player/RemotePlayerVelocity.cs @@ -1,7 +1,9 @@ -using UnityEngine; +using QSB.Utility; +using UnityEngine; namespace QSB.Player; +[UsedInUnityProject] public class RemotePlayerVelocity : MonoBehaviour { private Vector3 _prevRelPosition; diff --git a/QSB/Player/TransformSync/PlayerTransformSync.cs b/QSB/Player/TransformSync/PlayerTransformSync.cs index a0e0dcd6a..ee2bc73d3 100644 --- a/QSB/Player/TransformSync/PlayerTransformSync.cs +++ b/QSB/Player/TransformSync/PlayerTransformSync.cs @@ -1,5 +1,6 @@ using OWML.Common; using QSB.Messaging; +using QSB.Patches; using QSB.Player.Messages; using QSB.PlayerBodySetup.Local; using QSB.PlayerBodySetup.Remote; @@ -12,6 +13,7 @@ namespace QSB.Player.TransformSync; +[UsedInUnityProject] public class PlayerTransformSync : SectoredTransformSync { protected override bool IsPlayerObject => true; @@ -29,25 +31,8 @@ public class PlayerTransformSync : SectoredTransformSync private Transform _visibleStickTip; private Transform _networkStickTip => _networkStickPivot.GetChild(0); - private bool _hasRanOnStartClient; - public override void OnStartClient() { - if (_hasRanOnStartClient) - { - DebugLog.ToConsole($"ERROR - OnStartClient is being called AGAIN for {Player.PlayerId}'s PlayerTransformSync!", MessageType.Error); - return; - } - - _hasRanOnStartClient = true; - if (QSBPlayerManager.PlayerList.Any(x => x.TransformSync == this)) - { - // this really shouldnt happen... - DebugLog.ToConsole($"Error - A PlayerInfo already exists with TransformSync {name}", MessageType.Error); - Destroy(gameObject); // probably bad - return; - } - var player = new PlayerInfo(this); QSBPlayerManager.PlayerList.SafeAdd(player); base.OnStartClient(); @@ -57,26 +42,16 @@ public override void OnStartClient() JoinLeaveSingularity.Create(Player, true); } - public override void OnStartLocalPlayer() - { - if (LocalInstance != null) - { - DebugLog.ToConsole($"ERROR - LocalInstance is already non-null in OnStartLocalPlayer!", MessageType.Error); - Destroy(gameObject); // probably bad - return; - } - - LocalInstance = this; - } - - public override void OnStopLocalPlayer() => LocalInstance = null; + public override void OnStartLocalPlayer() => LocalInstance = this; public override void OnStopClient() { JoinLeaveSingularity.Create(Player, false); // TODO : Maybe move this to a leave event...? Would ensure everything could finish up before removing the player + QSBPatch.Remote = true; QSBPlayerManager.OnRemovePlayer?.Invoke(Player); + QSBPatch.Remote = false; base.OnStopClient(); Player.HudMarker?.Remove(); QSBPlayerManager.PlayerList.Remove(Player); diff --git a/QSB/PlayerBodySetup/Remote/DreamWorldSpawnAnimator.cs b/QSB/PlayerBodySetup/Remote/DreamWorldSpawnAnimator.cs index 9f9d52d90..b43db3e71 100644 --- a/QSB/PlayerBodySetup/Remote/DreamWorldSpawnAnimator.cs +++ b/QSB/PlayerBodySetup/Remote/DreamWorldSpawnAnimator.cs @@ -1,7 +1,9 @@ -using UnityEngine; +using QSB.Utility; +using UnityEngine; namespace QSB.PlayerBodySetup.Remote; +[UsedInUnityProject] public class DreamWorldSpawnAnimator : MonoBehaviour { [SerializeField] diff --git a/QSB/PlayerBodySetup/Remote/QSBDitheringAnimator.cs b/QSB/PlayerBodySetup/Remote/QSBDitheringAnimator.cs index d982c35ab..6889e7fad 100644 --- a/QSB/PlayerBodySetup/Remote/QSBDitheringAnimator.cs +++ b/QSB/PlayerBodySetup/Remote/QSBDitheringAnimator.cs @@ -1,9 +1,11 @@ -using System.Linq; +using QSB.Utility; +using System.Linq; using UnityEngine; using UnityEngine.Rendering; namespace QSB.PlayerBodySetup.Remote; +[UsedInUnityProject] public class QSBDitheringAnimator : MonoBehaviour { public bool FullyVisible => !enabled && OWMath.ApproxEquals(_visibleFraction, 1); diff --git a/QSB/PoolSync/CustomNomaiRemoteCameraPlatform.cs b/QSB/PoolSync/CustomNomaiRemoteCameraPlatform.cs index 14c93d43f..a08833778 100644 --- a/QSB/PoolSync/CustomNomaiRemoteCameraPlatform.cs +++ b/QSB/PoolSync/CustomNomaiRemoteCameraPlatform.cs @@ -138,6 +138,23 @@ private void OnDestroy() QSBPlayerManager.OnRemovePlayer -= OnRemovePlayer; } + private void LateUpdate() + { + // can't put this stuff in Update/UpdateHologramTransforms as + // manual bone rotations need to happen after the animator has changed them + if ((_platformActive && _anyoneStillOnPlatform) || _cameraState == CameraState.Disconnecting_FadeIn) + { + foreach (var item in _playerToHologram) + { + var hologram = item.Value.transform; + var anim = hologram.GetChild(0).gameObject.GetComponent(); + var cameraRotation = item.Key.CameraBody.transform.localRotation.eulerAngles; + var rotation = Quaternion.Euler(-cameraRotation.y, -cameraRotation.z, cameraRotation.x); // wtf why + anim.GetBoneTransform(HumanBodyBones.Head).localRotation = rotation; + } + } + } + private void Update() { if (_platformActive) @@ -719,10 +736,6 @@ public void OnRemotePlayerEnter(uint playerId) hologramCopy.GetChild(0).Find("player_mesh_noSuit:Traveller_HEA_Player/player_mesh_noSuit:Player_Head").gameObject.layer = 0; hologramCopy.GetChild(0).Find("Traveller_Mesh_v01:Traveller_Geo/Traveller_Mesh_v01:PlayerSuit_Helmet").gameObject.layer = 0; - // BUG : Look at this again... probably need to sync head rotation to something else - //var ikSync = hologramCopy.GetChild(0).gameObject.AddComponent(); - //ikSync.Init(player.CameraBody.transform); - if (player.AnimationSync.VisibleAnimator == null) { DebugLog.ToConsole($"Warning - {playerId}'s VisibleAnimator is null!", MessageType.Error); diff --git a/QSB/QSB.csproj b/QSB/QSB.csproj index 597afd068..f3bdfee4f 100644 --- a/QSB/QSB.csproj +++ b/QSB/QSB.csproj @@ -3,7 +3,7 @@ Quantum Space Buddies Quantum Space Buddies Multiplayer mod for Outer Wilds - $(OwmlDir)\Mods\QSB + $(OwmlDir)\Mods\Raicuparta.QuantumSpaceBuddies CS1998;CS0649 @@ -50,7 +50,6 @@ <_Files Include="$(OutputPath)\*.pdb" /> <_Files Include="$(GameDllsDir)\EOS-SDK.dll" /> - <_Files Include="$(GameDllsDir)\UniSense.dll" /> <_Files Include="$(GameDllsDir)\Autofac.dll" /> <_Files Include="$(GameDllsDir)\Newtonsoft.Json.dll" /> <_Files Include="$(GameDllsDir)\0Harmony.dll" /> @@ -77,9 +76,9 @@ PreserveNewest - - PreserveNewest - + + PreserveNewest + PreserveNewest @@ -90,13 +89,12 @@ - + + - - diff --git a/QSB/QSBCore.cs b/QSB/QSBCore.cs index b8b9429f7..b7107571e 100644 --- a/QSB/QSBCore.cs +++ b/QSB/QSBCore.cs @@ -5,7 +5,6 @@ using QSB.Localization; using QSB.Menus; using QSB.Patches; -using QSB.Player; using QSB.QuantumSync; using QSB.SaveSync; using QSB.Utility; @@ -68,18 +67,13 @@ public class QSBCore : ModBehaviour public static readonly string[] IncompatibleMods = { - // cheats mods - "Glitch.AltDebugMenu", - "PacificEngine.CheatsMod", // incompatible mods "Raicuparta.NomaiVR", "xen.NewHorizons", "Vesper.AutoResume", "Vesper.OuterWildsMMO", "_nebula.StopTime", - "Leadpogrommer.PeacefulGhosts", "PacificEngine.OW_Randomizer", - "xen.DayDream" }; private static void DetermineGameVendor() @@ -153,7 +147,6 @@ public void Start() if (DebugSettings.AutoStart) { DebugSettings.UseKcpTransport = true; - DebugSettings.SkipTitleScreen = true; DebugSettings.DebugMode = true; } @@ -188,8 +181,6 @@ public void Start() QSBWorldSync.Managers = components.OfType().ToArray(); QSBPatchManager.OnPatchType += OnPatchType; QSBPatchManager.OnUnpatchType += OnUnpatchType; - - StartCoroutine(QSBPlayerManager.ValidatePlayers()); } private static void OnPatchType(QSBPatchTypes type) @@ -253,10 +244,9 @@ public override void Configure(IModConfig config) IncompatibleModsAllowed = config.GetSettingsValue("incompatibleModsAllowed"); } -#if DEBUG private void Update() { - if (Keyboard.current[Key.Q].isPressed && Keyboard.current[Key.D].wasPressedThisFrame) + if (Keyboard.current[Key.Q].isPressed && Keyboard.current[Key.NumpadEnter].wasPressedThisFrame) { DebugSettings.DebugMode = !DebugSettings.DebugMode; @@ -268,7 +258,6 @@ private void Update() DebugLog.ToConsole($"DEBUG MODE = {DebugSettings.DebugMode}"); } } -#endif } /* diff --git a/QSB/QSBNetworkManager.cs b/QSB/QSBNetworkManager.cs index 78fb5359d..28a05a52a 100644 --- a/QSB/QSBNetworkManager.cs +++ b/QSB/QSBNetworkManager.cs @@ -13,6 +13,7 @@ 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; @@ -26,6 +27,7 @@ using QSB.ShipSync.TransformSync; using QSB.Syncs.Occasional; using QSB.TimeSync; +using QSB.Tools.ProbeLauncherTool.VariableSync; using QSB.Tools.ProbeTool.TransformSync; using QSB.Utility; using QSB.Utility.VariableSync; @@ -56,6 +58,7 @@ public class QSBNetworkManager : NetworkManager, IAddComponentOnStart public GameObject ShipModulePrefab { get; private set; } public GameObject ShipLegPrefab { get; private set; } public GameObject ModelShipPrefab { get; private set; } + public GameObject StationaryProbeLauncherPrefab { get; private set; } private string PlayerName { get; set; } private GameObject _probePrefab; @@ -74,7 +77,9 @@ public override void Awake() if (QSBCore.DebugSettings.UseKcpTransport) { - transport = gameObject.AddComponent(); + var kcpTransport = gameObject.AddComponent(); + kcpTransport.Timeout = int.MaxValue; // effectively disables kcp ping and timeout (good for testing) + transport = kcpTransport; } else { @@ -147,8 +152,14 @@ public override void Awake() 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)); + spawnPrefabs.Add(StationaryProbeLauncherPrefab); + ConfigureNetworkManager(); } @@ -347,8 +358,18 @@ public override void OnServerDisconnect(NetworkConnectionToClient conn) // Calle identity.SetAuthority(QSBPlayerManager.LocalPlayerId); } } + // revert authority from model ship + if (ModelShipTransformSync.LocalInstance != null) + { + var identity = ModelShipTransformSync.LocalInstance.netIdentity; + if (identity != null && identity.connectionToClient == conn) + { + identity.SetAuthority(QSBPlayerManager.LocalPlayerId); + } + } // stop dragging for the orbs this player was dragging + // i THINK this is here because orb authority is in network behavior, which may not work properly in OnPlayerLeave foreach (var qsbOrb in QSBWorldSync.GetWorldObjects()) { if (qsbOrb.NetworkBehaviour == null) diff --git a/QSB/QuantumSync/Patches/QuantumPatches.cs b/QSB/QuantumSync/Patches/QuantumPatches.cs index 0da7ade9f..a37e418ba 100644 --- a/QSB/QuantumSync/Patches/QuantumPatches.cs +++ b/QSB/QuantumSync/Patches/QuantumPatches.cs @@ -478,4 +478,17 @@ public static bool QuantumSkeletonTower_ChangeQuantumState(QuantumSkeletonTower return false; } + [HarmonyPrefix] + [HarmonyPatch(typeof(QuantumObject), nameof(QuantumObject.OnProbeSnapshot))] + public static bool OnProbeSnapshot() + { + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(QuantumObject), nameof(QuantumObject.OnProbeSnapshotRemoved))] + public static bool OnProbeSnapshotRemoved() + { + return false; + } } \ No newline at end of file diff --git a/QSB/QuantumSync/QuantumManager.cs b/QSB/QuantumSync/QuantumManager.cs index 1d932e6eb..ae1429d2e 100644 --- a/QSB/QuantumSync/QuantumManager.cs +++ b/QSB/QuantumSync/QuantumManager.cs @@ -126,6 +126,28 @@ public static IEnumerable GetEntangledPlayers(QuantumObject obj) return QSBPlayerManager.PlayerList.Where(x => x.EntangledObject == worldObj); } + public static void OnTakeProbeSnapshot(PlayerInfo player, ProbeCamera.ID cameraId) + { + foreach (var quantumObject in QSBWorldSync.GetWorldObjects()) + { + if (quantumObject.ControllingPlayer == QSBPlayerManager.LocalPlayerId) + { + quantumObject.OnTakeProbeSnapshot(player, cameraId); + } + } + } + + public static void OnRemoveProbeSnapshot(PlayerInfo player) + { + foreach (var quantumObject in QSBWorldSync.GetWorldObjects()) + { + if (quantumObject.ControllingPlayer == QSBPlayerManager.LocalPlayerId) + { + quantumObject.OnRemoveProbeSnapshot(player); + } + } + } + #region debug shapes private static GameObject _debugSphere, _debugCube, _debugCapsule; diff --git a/QSB/QuantumSync/WorldObjects/IQSBQuantumObject.cs b/QSB/QuantumSync/WorldObjects/IQSBQuantumObject.cs index 55d43df07..ebf3a9899 100644 --- a/QSB/QuantumSync/WorldObjects/IQSBQuantumObject.cs +++ b/QSB/QuantumSync/WorldObjects/IQSBQuantumObject.cs @@ -1,4 +1,5 @@ -using QSB.WorldSync; +using QSB.Player; +using QSB.WorldSync; using System.Collections.Generic; namespace QSB.QuantumSync.WorldObjects; @@ -12,4 +13,6 @@ public interface IQSBQuantumObject : IWorldObject void SetIsQuantum(bool isQuantum); VisibilityObject GetVisibilityObject(); + void OnTakeProbeSnapshot(PlayerInfo player, ProbeCamera.ID cameraId); + void OnRemoveProbeSnapshot(PlayerInfo player); } \ No newline at end of file diff --git a/QSB/QuantumSync/WorldObjects/QSBQuantumObject.cs b/QSB/QuantumSync/WorldObjects/QSBQuantumObject.cs index 9f9011a22..a9c1e1084 100644 --- a/QSB/QuantumSync/WorldObjects/QSBQuantumObject.cs +++ b/QSB/QuantumSync/WorldObjects/QSBQuantumObject.cs @@ -3,6 +3,7 @@ using QSB.Messaging; using QSB.Player; using QSB.QuantumSync.Messages; +using QSB.Tools.ProbeTool; using QSB.Utility; using QSB.WorldSync; using System.Collections.Generic; @@ -28,6 +29,8 @@ internal abstract class QSBQuantumObject : WorldObject, IQSBQuantumObject public uint ControllingPlayer { get; set; } public bool IsEnabled { get; private set; } + private List _visibleToProbes = new(); + public override void OnRemoval() { if (HostControls) @@ -174,6 +177,99 @@ private void OnDisable(Shape s) => ((IQSBQuantumObject)this).SendMessage(new QuantumAuthorityMessage(0u)); }); + public void OnTakeProbeSnapshot(PlayerInfo player, ProbeCamera.ID cameraId) + { + if (player.IsLocalPlayer) + { + var probe = Locator.GetProbe(); + ProbeCamera probeCamera = default; + switch (cameraId) + { + case ProbeCamera.ID.Forward: + probeCamera = probe.GetForwardCamera(); + break; + case ProbeCamera.ID.Reverse: + probeCamera = probe.GetReverseCamera(); + break; + case ProbeCamera.ID.Rotating: + probeCamera = probe.GetRotatingCamera(); + break; + case ProbeCamera.ID.PreLaunch: + probeCamera = player.LocalProbeLauncher._preLaunchCamera; + break; + } + + var distance = Vector3.Distance(AttachedObject.transform.position, probeCamera.transform.position); + if (distance < AttachedObject._maxSnapshotLockRange + && AttachedObject.IsIlluminated() + && !probeCamera.HasInterference() + && AttachedObject.CheckVisibilityFromProbe(probeCamera.GetOWCamera())) + { + if (!_visibleToProbes.Contains(player)) + { + _visibleToProbes.Add(player); + } + + AttachedObject._visibleInProbeSnapshot = _visibleToProbes.Any(x => x != null); + return; + } + } + else + { + var probe = player.Probe; + QSBProbeCamera probeCamera = default; + switch (cameraId) + { + case ProbeCamera.ID.Forward: + probeCamera = probe.GetForwardCamera(); + break; + case ProbeCamera.ID.Reverse: + probeCamera = probe.GetReverseCamera(); + break; + case ProbeCamera.ID.Rotating: + probeCamera = probe.GetRotatingCamera(); + break; + case ProbeCamera.ID.PreLaunch: + //TODO : uhhhh yeah do this lol + probeCamera = null; + break; + } + + var distance = Vector3.Distance(AttachedObject.transform.position, probeCamera.transform.position); + if (distance < AttachedObject._maxSnapshotLockRange + && AttachedObject.IsIlluminated() + && !probeCamera.HasInterference() + && AttachedObject.CheckVisibilityFromProbe(probeCamera.GetOWCamera())) + { + if (!_visibleToProbes.Contains(player)) + { + _visibleToProbes.Add(player); + } + + _visibleToProbes.Add(player); + AttachedObject._visibleInProbeSnapshot = _visibleToProbes.Any(x => x != null); + return; + } + } + + if (_visibleToProbes.Contains(player)) + { + _visibleToProbes.Remove(player); + } + + AttachedObject._visibleInProbeSnapshot = _visibleToProbes.Any(x => x != null); + } + + public void OnRemoveProbeSnapshot(PlayerInfo player) + { + if (_visibleToProbes.Contains(player)) + { + _visibleToProbes.Remove(player); + } + + AttachedObject._visibleInProbeSnapshot = _visibleToProbes.Any(x => x != null); + } + public override void DisplayLines() { if (AttachedObject == null) diff --git a/QSB/RoastingSync/QSBMarshmallow.cs b/QSB/RoastingSync/QSBMarshmallow.cs index 747770c8b..b6f01a44f 100644 --- a/QSB/RoastingSync/QSBMarshmallow.cs +++ b/QSB/RoastingSync/QSBMarshmallow.cs @@ -1,9 +1,11 @@ using QSB.Player; +using QSB.Utility; using System.Linq; using UnityEngine; namespace QSB.RoastingSync; +[UsedInUnityProject] public class QSBMarshmallow : MonoBehaviour { public const float RAW_TOASTED_FRACTION = 0.2f; @@ -51,6 +53,8 @@ public void SpawnMallow() _mallowRenderer.enabled = true; _mallowState = Marshmallow.MallowState.Default; enabled = true; + + _attachedPlayer.AudioController.PlayOneShot(AudioType.ToolMarshmallowReplace); } public void Disable() @@ -66,6 +70,8 @@ public void Extinguish() { _fireRenderer.enabled = false; _mallowState = Marshmallow.MallowState.Default; + + _attachedPlayer.AudioController.PlayOneShot(AudioType.ToolMarshmallowBlowOut); } } @@ -140,6 +146,8 @@ public void Burn() _toastedFraction = 1f; _initBurnTime = Time.time; _mallowState = Marshmallow.MallowState.Burning; + + _attachedPlayer.AudioController.PlayOneShot(AudioType.ToolMarshmallowIgnite); } } diff --git a/QSB/SaveSync/QSBStandaloneProfileManager.cs b/QSB/SaveSync/QSBStandaloneProfileManager.cs index 06ef63f6f..b78c5e9f6 100644 --- a/QSB/SaveSync/QSBStandaloneProfileManager.cs +++ b/QSB/SaveSync/QSBStandaloneProfileManager.cs @@ -278,7 +278,7 @@ private void LoadSaveFilesFromProfiles() Debug.LogError("Could not find graphics settings for " + profile.profileName); } - if (inputJSON == "") + if (string.IsNullOrEmpty(inputJSON)) { profile.brokenRebindingData = File.Exists(inputsPath); inputJSON = ((InputManager)OWInput.SharedInputManager).commandManager.DefaultInputActions.ToJson(); diff --git a/QSB/ShipSync/Messages/FlyShipMessage.cs b/QSB/ShipSync/Messages/FlyShipMessage.cs index 144efd708..e1083c139 100644 --- a/QSB/ShipSync/Messages/FlyShipMessage.cs +++ b/QSB/ShipSync/Messages/FlyShipMessage.cs @@ -39,10 +39,12 @@ public override void OnReceiveRemote() 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/LegDetachMessage.cs b/QSB/ShipSync/Messages/LegDetachMessage.cs index cc3a9608b..f0d87dd04 100644 --- a/QSB/ShipSync/Messages/LegDetachMessage.cs +++ b/QSB/ShipSync/Messages/LegDetachMessage.cs @@ -1,5 +1,4 @@ using QSB.Messaging; -using QSB.Patches; using QSB.ShipSync.WorldObjects; namespace QSB.ShipSync.Messages; @@ -7,5 +6,5 @@ namespace QSB.ShipSync.Messages; internal class LegDetachMessage : QSBWorldObjectMessage { public override void OnReceiveRemote() => - QSBPatch.RemoteCall(WorldObject.AttachedObject.Detach); + WorldObject.AttachedObject.Detach(); } diff --git a/QSB/ShipSync/Messages/ModuleDetachMessage.cs b/QSB/ShipSync/Messages/ModuleDetachMessage.cs index e47c242c4..b7ab2e811 100644 --- a/QSB/ShipSync/Messages/ModuleDetachMessage.cs +++ b/QSB/ShipSync/Messages/ModuleDetachMessage.cs @@ -1,5 +1,4 @@ using QSB.Messaging; -using QSB.Patches; using QSB.ShipSync.WorldObjects; namespace QSB.ShipSync.Messages; @@ -7,5 +6,5 @@ namespace QSB.ShipSync.Messages; internal class ModuleDetachMessage : QSBWorldObjectMessage { public override void OnReceiveRemote() => - QSBPatch.RemoteCall(WorldObject.AttachedObject.Detach); + WorldObject.AttachedObject.Detach(); } diff --git a/QSB/ShipSync/Messages/ShipIgnitionMessage.cs b/QSB/ShipSync/Messages/ShipIgnitionMessage.cs new file mode 100644 index 000000000..ff7bdd181 --- /dev/null +++ b/QSB/ShipSync/Messages/ShipIgnitionMessage.cs @@ -0,0 +1,49 @@ +using QSB.Messaging; +using QSB.Player; +using QSB.Player.TransformSync; +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 (PlayerTransformSync.LocalInstance && 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/Messages/ShipLightMessage.cs b/QSB/ShipSync/Messages/ShipLightMessage.cs index 4e52bafb0..e81fdec04 100644 --- a/QSB/ShipSync/Messages/ShipLightMessage.cs +++ b/QSB/ShipSync/Messages/ShipLightMessage.cs @@ -1,5 +1,4 @@ using QSB.Messaging; -using QSB.Patches; using QSB.ShipSync.WorldObjects; namespace QSB.ShipSync.Messages; @@ -9,5 +8,5 @@ internal class ShipLightMessage : QSBWorldObjectMessage public ShipLightMessage(bool on) : base(on) { } public override void OnReceiveRemote() => - QSBPatch.RemoteCall(() => WorldObject.AttachedObject.SetOn(Data)); + WorldObject.AttachedObject.SetOn(Data); } diff --git a/QSB/ShipSync/Patches/ShipAudioPatches.cs b/QSB/ShipSync/Patches/ShipAudioPatches.cs new file mode 100644 index 000000000..1e76d57ab --- /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 ddf2eac80..525c2c380 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 @@ public static bool GetThrustFraction(ThrusterFlameController __instance, ref flo [HarmonyPatch(typeof(ThrusterWashController), nameof(ThrusterWashController.Update))] public static bool Update(ThrusterWashController __instance) { - if (ShipThrusterManager.ShipWashController != __instance) + var isShip = ShipThrusterManager.ShipWashController == __instance; + var isModelShip = ModelShipThrusterManager.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/ShipCustomAttach.cs b/QSB/ShipSync/ShipCustomAttach.cs index e8106d096..2d6a0afb8 100644 --- a/QSB/ShipSync/ShipCustomAttach.cs +++ b/QSB/ShipSync/ShipCustomAttach.cs @@ -19,9 +19,14 @@ public class ShipCustomAttach : MonoBehaviour private PlayerAttachPoint _playerAttachPoint; + /// + /// uses a static field instead of a persistent condition cuz those are synced + /// + private static bool _tutorialPrompt = true; + private void Awake() { - Locator.GetPromptManager().AddScreenPrompt(_attachPrompt, PromptPosition.UpperRight); + Locator.GetPromptManager().AddScreenPrompt(_attachPrompt, _tutorialPrompt ? PromptPosition.Center : PromptPosition.UpperRight); Locator.GetPromptManager().AddScreenPrompt(_detachPrompt, PromptPosition.UpperRight); _playerAttachPoint = gameObject.AddComponent(); @@ -34,8 +39,8 @@ private void OnDestroy() { if (Locator.GetPromptManager()) { - Locator.GetPromptManager().RemoveScreenPrompt(_attachPrompt, PromptPosition.UpperRight); - Locator.GetPromptManager().RemoveScreenPrompt(_detachPrompt, PromptPosition.UpperRight); + Locator.GetPromptManager().RemoveScreenPrompt(_attachPrompt); + Locator.GetPromptManager().RemoveScreenPrompt(_detachPrompt); } } @@ -43,6 +48,11 @@ private void Update() { _attachPrompt.SetVisibility(false); _detachPrompt.SetVisibility(false); + // dont show prompt if paused or something + if (!OWInput.IsInputMode(InputMode.Character)) + { + return; + } var attachedToUs = _playerAttachPoint.enabled; _detachPrompt.SetVisibility(attachedToUs); @@ -78,6 +88,13 @@ private void Update() transform.position = Locator.GetPlayerTransform().position; _playerAttachPoint.AttachPlayer(); ShipManager.Instance.CockpitController._shipAudioController.PlayBuckle(); + + if (_tutorialPrompt) + { + _tutorialPrompt = false; + Locator.GetPromptManager().RemoveScreenPrompt(_attachPrompt); + Locator.GetPromptManager().AddScreenPrompt(_attachPrompt, PromptPosition.UpperRight); + } } } } diff --git a/QSB/ShipSync/ShipManager.cs b/QSB/ShipSync/ShipManager.cs index c85a27516..405059c4c 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 @@ public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken return; } + ShipThrusterAudio = QSBWorldSync.GetUnityObject(); HatchInteractZone = HatchController.GetComponent(); ShipTractorBeam = QSBWorldSync.GetUnityObject(); CockpitController = QSBWorldSync.GetUnityObject(); @@ -136,6 +138,10 @@ public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken 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 79053946a..c8e316730 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 void Update() item.OnStopTranslationalThrust(); } + _thrusterAudio.OnStopTranslationalThrust(); + ShipThrusterManager.ShipWashController.OnStopTranslationalThrust(); } else @@ -42,6 +47,8 @@ public void Update() item.OnStartTranslationalThrust(); } + _thrusterAudio.OnStartTranslationalThrust(); + ShipThrusterManager.ShipWashController.OnStartTranslationalThrust(); } } diff --git a/QSB/ShipSync/TransformSync/ShipLegTransformSync.cs b/QSB/ShipSync/TransformSync/ShipLegTransformSync.cs index 3efd0912d..39e4db8fe 100644 --- a/QSB/ShipSync/TransformSync/ShipLegTransformSync.cs +++ b/QSB/ShipSync/TransformSync/ShipLegTransformSync.cs @@ -15,7 +15,11 @@ protected override bool CheckValid() => AttachedTransform && base.CheckValid(); - protected override bool CheckReady() => base.CheckReady() && _qsbModule.AttachedObject.isDetached; + protected override bool CheckReady() + => base.CheckReady() + && _qsbModule != null // not sure how either of these can be null, but i guess better safe than sorry + && _qsbModule.AttachedObject != null + && _qsbModule.AttachedObject.isDetached; protected override bool UseInterpolation => true; diff --git a/QSB/ShipSync/TransformSync/ShipModuleTransformSync.cs b/QSB/ShipSync/TransformSync/ShipModuleTransformSync.cs index 1f655cd71..72cb2d2df 100644 --- a/QSB/ShipSync/TransformSync/ShipModuleTransformSync.cs +++ b/QSB/ShipSync/TransformSync/ShipModuleTransformSync.cs @@ -15,7 +15,11 @@ protected override bool CheckValid() => AttachedTransform && base.CheckValid(); - protected override bool CheckReady() => base.CheckReady() && _qsbModule.AttachedObject.isDetached; + protected override bool CheckReady() + => base.CheckReady() + && _qsbModule != null // not sure how either of these can be null, but i guess better safe than sorry + && _qsbModule.AttachedObject != null + && _qsbModule.AttachedObject.isDetached; protected override bool UseInterpolation => true; diff --git a/QSB/ShipSync/TransformSync/ShipTransformSync.cs b/QSB/ShipSync/TransformSync/ShipTransformSync.cs index 29d6597ba..772bc9952 100644 --- a/QSB/ShipSync/TransformSync/ShipTransformSync.cs +++ b/QSB/ShipSync/TransformSync/ShipTransformSync.cs @@ -56,10 +56,10 @@ protected override void ApplyToAttached() return; } - var targetPos = ReferenceTransform.FromRelPos(transform.position); - var targetRot = ReferenceTransform.FromRelRot(transform.rotation); + var targetPos = ReferenceTransform.FromRelPos(UseInterpolation ? SmoothPosition : transform.position); + var targetRot = ReferenceTransform.FromRelRot(UseInterpolation ? SmoothRotation : transform.rotation); - if (PlayerState.IsInsideShip()) + if (IsInsideShip) { if (Time.unscaledTime >= _lastSetPositionTime + ForcePositionAfterTime) { @@ -98,5 +98,6 @@ private static void SetVelocity(OWRigidbody rigidbody, Vector3 newVelocity) rigidbody._currentVelocity = newVelocity; } - protected override bool UseInterpolation => false; + private static bool IsInsideShip => PlayerState.IsInsideShip(); + protected override bool UseInterpolation => !IsInsideShip; } diff --git a/QSB/Syncs/QSBNetworkTransformChild.cs b/QSB/Syncs/QSBNetworkTransformChild.cs index b96fc084b..b3336e011 100644 --- a/QSB/Syncs/QSBNetworkTransformChild.cs +++ b/QSB/Syncs/QSBNetworkTransformChild.cs @@ -4,6 +4,7 @@ namespace QSB.Syncs; +[UsedInUnityProject] public class QSBNetworkTransformChild : QSBNetworkBehaviour { public Transform Target; diff --git a/QSB/TimeSync/Messages/SetSecondsRemainingMessage.cs b/QSB/TimeSync/Messages/SetSecondsRemainingMessage.cs index c8fbe2740..fd99e1608 100644 --- a/QSB/TimeSync/Messages/SetSecondsRemainingMessage.cs +++ b/QSB/TimeSync/Messages/SetSecondsRemainingMessage.cs @@ -1,5 +1,4 @@ using QSB.Messaging; -using QSB.Patches; namespace QSB.TimeSync.Messages; @@ -9,5 +8,5 @@ namespace QSB.TimeSync.Messages; public class SetSecondsRemainingMessage : QSBMessage { public SetSecondsRemainingMessage(float secondsRemaining) : base(secondsRemaining) => To = 0; - public override void OnReceiveRemote() => QSBPatch.RemoteCall(() => TimeLoop.SetSecondsRemaining(Data)); + public override void OnReceiveRemote() => TimeLoop.SetSecondsRemaining(Data); } diff --git a/QSB/TimeSync/Patches/TimePatches.cs b/QSB/TimeSync/Patches/TimePatches.cs index 2c21229a3..24eab5fa0 100644 --- a/QSB/TimeSync/Patches/TimePatches.cs +++ b/QSB/TimeSync/Patches/TimePatches.cs @@ -37,10 +37,10 @@ is OWTime.PauseType.Initializing or OWTime.PauseType.Streaming or OWTime.PauseType.Loading; - [HarmonyPrefix] + [HarmonyPostfix] [HarmonyPatch(typeof(SubmitActionSkipToNextLoop), nameof(SubmitActionSkipToNextLoop.AdvanceToNewTimeLoop))] - public static bool StopMeditation() - => false; + public static void PreventMeditationSoftlock() + => OWInput.ChangeInputMode(InputMode.Character); } internal class ClientTimePatches : QSBPatch diff --git a/QSB/TimeSync/StopMeditation.cs b/QSB/TimeSync/StopMeditation.cs index 4a7dddc18..4187670db 100644 --- a/QSB/TimeSync/StopMeditation.cs +++ b/QSB/TimeSync/StopMeditation.cs @@ -1,23 +1,8 @@ -using UnityEngine; +using QSB.Utility; +using UnityEngine; namespace QSB.TimeSync; -public class StopMeditation : MonoBehaviour -{ - public void Init() - { - var menuManager = Locator.GetSceneMenuManager(); - - if (menuManager == null) - { - return; - } - - if (menuManager._pauseMenu == null || menuManager.pauseMenu._skipToNextLoopButton == null) - { - return; - } - - menuManager.pauseMenu._skipToNextLoopButton.SetActive(false); - } -} \ No newline at end of file +// TODO remove from unity project eventually +[UsedInUnityProject] +public class StopMeditation : MonoBehaviour { } diff --git a/QSB/TimeSync/TimeSyncUI.cs b/QSB/TimeSync/TimeSyncUI.cs index 46706b1e6..d57833e49 100644 --- a/QSB/TimeSync/TimeSyncUI.cs +++ b/QSB/TimeSync/TimeSyncUI.cs @@ -36,6 +36,9 @@ private void OnUniverseSceneLoad(OWScene oldScene, OWScene newScene) _canvas = obj._canvas; _text = obj._text; _canvas.enabled = false; + + var langController = QSBWorldSync.GetUnityObject().transform.GetChild(0).GetComponent(); + langController.AddTextElement(_text); } public void OnDestroy() diff --git a/QSB/TimeSync/WakeUpSync.cs b/QSB/TimeSync/WakeUpSync.cs index 9b8ef5894..8366a9958 100644 --- a/QSB/TimeSync/WakeUpSync.cs +++ b/QSB/TimeSync/WakeUpSync.cs @@ -5,7 +5,6 @@ using QSB.DeathSync; using QSB.Inputs; using QSB.Messaging; -using QSB.Patches; using QSB.Player; using QSB.Player.Messages; using QSB.TimeSync.Messages; @@ -16,6 +15,10 @@ namespace QSB.TimeSync; +/// +/// BUG: this runs on remote players = BAD! can we move this off of network player? +/// +[UsedInUnityProject] public class WakeUpSync : NetworkBehaviour { public static WakeUpSync LocalInstance { get; private set; } @@ -117,7 +120,6 @@ private void Init() { new RequestStateResyncMessage().Send(); CurrentState = State.Loaded; - gameObject.GetRequiredComponent().Init(); if (isServer) { SendServerTime(); @@ -146,7 +148,11 @@ public void OnClientReceiveMessage(float time, int count, float secondsRemaining { _serverTime = time; _serverLoopCount = count; - QSBPatch.RemoteCall(() => TimeLoop.SetSecondsRemaining(secondsRemaining)); + // prevents accidental supernova at start of loop + if (_serverLoopCount == PlayerData.LoadLoopCount()) + { + TimeLoop.SetSecondsRemaining(secondsRemaining); + } } private void WakeUpOrSleep() diff --git a/QSB/Tools/FlashlightTool/QSBFlashlight.cs b/QSB/Tools/FlashlightTool/QSBFlashlight.cs index 6f1b60dc1..440c24f67 100644 --- a/QSB/Tools/FlashlightTool/QSBFlashlight.cs +++ b/QSB/Tools/FlashlightTool/QSBFlashlight.cs @@ -4,6 +4,7 @@ namespace QSB.Tools.FlashlightTool; +[UsedInUnityProject] public class QSBFlashlight : MonoBehaviour, ILightSource { [SerializeField] diff --git a/QSB/Tools/ProbeLauncherTool/Messages/ChangeModeMessage.cs b/QSB/Tools/ProbeLauncherTool/Messages/ChangeModeMessage.cs new file mode 100644 index 000000000..169560f22 --- /dev/null +++ b/QSB/Tools/ProbeLauncherTool/Messages/ChangeModeMessage.cs @@ -0,0 +1,11 @@ +using QSB.Messaging; +using QSB.Tools.ProbeLauncherTool.WorldObjects; + +namespace QSB.Tools.ProbeLauncherTool.Messages; + +internal class ChangeModeMessage : QSBWorldObjectMessage +{ + public ChangeModeMessage() : base() { } + + public override void OnReceiveRemote() => WorldObject.ChangeMode(); +} diff --git a/QSB/Tools/ProbeLauncherTool/Messages/LaunchProbeMessage.cs b/QSB/Tools/ProbeLauncherTool/Messages/LaunchProbeMessage.cs index c0988b792..535c1a2f0 100644 --- a/QSB/Tools/ProbeLauncherTool/Messages/LaunchProbeMessage.cs +++ b/QSB/Tools/ProbeLauncherTool/Messages/LaunchProbeMessage.cs @@ -3,9 +3,9 @@ namespace QSB.Tools.ProbeLauncherTool.Messages; -internal class LaunchProbeMessage : QSBWorldObjectMessage +internal class LaunchProbeMessage : QSBWorldObjectMessage { - public LaunchProbeMessage(bool playEffects) : base(playEffects) { } + public LaunchProbeMessage(bool playEffects, uint probeOwnerID) : base((playEffects, probeOwnerID)) { } - public override void OnReceiveRemote() => WorldObject.LaunchProbe(Data); + public override void OnReceiveRemote() => WorldObject.LaunchProbe(Data.playEffects, Data.probeOwnerID); } \ No newline at end of file diff --git a/QSB/Tools/ProbeLauncherTool/Messages/PlayerLauncherChangeModeMessage.cs b/QSB/Tools/ProbeLauncherTool/Messages/PlayerLauncherChangeModeMessage.cs new file mode 100644 index 000000000..14fd7da71 --- /dev/null +++ b/QSB/Tools/ProbeLauncherTool/Messages/PlayerLauncherChangeModeMessage.cs @@ -0,0 +1,16 @@ +using QSB.Messaging; +using QSB.Player; +using QSB.WorldSync; + +namespace QSB.Tools.ProbeLauncherTool.Messages; + +internal class PlayerLauncherChangeModeMessage : QSBMessage +{ + public override bool ShouldReceive => QSBWorldSync.AllObjectsReady; + + public override void OnReceiveRemote() + { + var player = QSBPlayerManager.GetPlayer(From); + player.ProbeLauncherTool.ChangeMode(); + } +} diff --git a/QSB/Tools/ProbeLauncherTool/Messages/PlayerLauncherTakeSnapshotMessage.cs b/QSB/Tools/ProbeLauncherTool/Messages/PlayerLauncherTakeSnapshotMessage.cs new file mode 100644 index 000000000..93b090d0c --- /dev/null +++ b/QSB/Tools/ProbeLauncherTool/Messages/PlayerLauncherTakeSnapshotMessage.cs @@ -0,0 +1,22 @@ +using QSB.Messaging; +using QSB.Player; +using QSB.QuantumSync; +using QSB.WorldSync; + +namespace QSB.Tools.ProbeLauncherTool.Messages; + +internal class PlayerLauncherTakeSnapshotMessage : QSBMessage +{ + public PlayerLauncherTakeSnapshotMessage(ProbeCamera.ID cameraId) : base(cameraId) { } + + public override bool ShouldReceive => QSBWorldSync.AllObjectsReady; + + public override void OnReceiveLocal() => QuantumManager.OnTakeProbeSnapshot(QSBPlayerManager.LocalPlayer, Data); + + public override void OnReceiveRemote() + { + var player = QSBPlayerManager.GetPlayer(From); + player.ProbeLauncherTool.TakeSnapshot(); + QuantumManager.OnTakeProbeSnapshot(player, Data); + } +} diff --git a/QSB/Tools/ProbeLauncherTool/Messages/RemoveSnapshotMessage.cs b/QSB/Tools/ProbeLauncherTool/Messages/RemoveSnapshotMessage.cs new file mode 100644 index 000000000..3d75101bd --- /dev/null +++ b/QSB/Tools/ProbeLauncherTool/Messages/RemoveSnapshotMessage.cs @@ -0,0 +1,26 @@ +using QSB.Messaging; +using QSB.Player; +using QSB.Player.TransformSync; +using QSB.QuantumSync; + +namespace QSB.Tools.ProbeLauncherTool.Messages; + +internal class RemoveSnapshotMessage : QSBMessage +{ + static RemoveSnapshotMessage() + => GlobalMessenger.AddListener(OWEvents.ProbeSnapshotRemoved, Handle); + + private static void Handle() + { + if (PlayerTransformSync.LocalInstance) + { + new RemoveSnapshotMessage().Send(); + } + } + + public override void OnReceiveLocal() + => QuantumManager.OnRemoveProbeSnapshot(QSBPlayerManager.LocalPlayer); + + public override void OnReceiveRemote() + => QuantumManager.OnRemoveProbeSnapshot(QSBPlayerManager.GetPlayer(From)); +} diff --git a/QSB/Tools/ProbeLauncherTool/Messages/StationaryProbeLauncherMessage.cs b/QSB/Tools/ProbeLauncherTool/Messages/StationaryProbeLauncherMessage.cs new file mode 100644 index 000000000..dcb5fe07a --- /dev/null +++ b/QSB/Tools/ProbeLauncherTool/Messages/StationaryProbeLauncherMessage.cs @@ -0,0 +1,11 @@ +using QSB.Messaging; +using QSB.Tools.ProbeLauncherTool.WorldObjects; + +namespace QSB.Tools.ProbeLauncherTool.Messages; + +public class StationaryProbeLauncherMessage : QSBWorldObjectMessage +{ + public StationaryProbeLauncherMessage(bool inUse, uint userID) : base((inUse, userID)) { } + + public override void OnReceiveRemote() => WorldObject.OnRemoteUseStateChanged(Data.Item1, Data.Item2); +} diff --git a/QSB/Tools/ProbeLauncherTool/Messages/TakeSnapshotMessage.cs b/QSB/Tools/ProbeLauncherTool/Messages/TakeSnapshotMessage.cs new file mode 100644 index 000000000..f0d1e77cf --- /dev/null +++ b/QSB/Tools/ProbeLauncherTool/Messages/TakeSnapshotMessage.cs @@ -0,0 +1,17 @@ +using QSB.Messaging; +using QSB.Player; +using QSB.QuantumSync; +using QSB.Tools.ProbeLauncherTool.WorldObjects; + +namespace QSB.Tools.ProbeLauncherTool.Messages; + +internal class TakeSnapshotMessage : QSBWorldObjectMessage +{ + public TakeSnapshotMessage(ProbeCamera.ID cameraId) : base(cameraId) { } + + public override void OnReceiveLocal() + => QuantumManager.OnTakeProbeSnapshot(QSBPlayerManager.LocalPlayer, Data); + + public override void OnReceiveRemote() + => WorldObject.TakeSnapshot(QSBPlayerManager.GetPlayer(From), Data); +} diff --git a/QSB/Tools/ProbeLauncherTool/Patches/LauncherPatches.cs b/QSB/Tools/ProbeLauncherTool/Patches/LauncherPatches.cs index 66e165375..7b04554e0 100644 --- a/QSB/Tools/ProbeLauncherTool/Patches/LauncherPatches.cs +++ b/QSB/Tools/ProbeLauncherTool/Patches/LauncherPatches.cs @@ -89,4 +89,57 @@ public static bool ProbeLauncherEffects_PlayLaunchClip(ProbeLauncherEffects __in __instance._owAudioSource.GetAudioSource().spatialBlend = 1f; return true; } + + [HarmonyPostfix] + [HarmonyPatch(typeof(ProbeLauncherEffects), nameof(ProbeLauncherEffects.PlayChangeModeClip))] + public static void ProbeLauncherEffects_PlayChangeModeClip(ProbeLauncherEffects __instance) + { + if (__instance != QSBPlayerManager.LocalPlayer.LocalProbeLauncher._effects) + { + __instance.gameObject + .GetComponent() + ?.GetWorldObject() + ?.SendMessage(new ChangeModeMessage()); + } + else + { + new PlayerLauncherChangeModeMessage().Send(); + } + } + + /*[HarmonyPostfix] + [HarmonyPatch(typeof(ProbeLauncherEffects), nameof(ProbeLauncherEffects.PlaySnapshotClip))] + public static void ProbeLauncherEffects_PlaySnapshotClip(ProbeLauncherEffects __instance) + { + if (__instance != QSBPlayerManager.LocalPlayer.LocalProbeLauncher._effects) + { + __instance.gameObject + .GetComponent() + ?.GetWorldObject() + ?.SendMessage(new TakeSnapshotMessage()); + } + else + { + new PlayerLauncherTakeSnapshotMessage().Send(); + } + }*/ + + [HarmonyPostfix] + [HarmonyPatch(typeof(ProbeLauncher), nameof(ProbeLauncher.TakeSnapshotWithCamera))] + public static void TakeSnapshotWithCamera(ProbeLauncher __instance, ProbeCamera camera) + { + DebugLog.DebugWrite($"TakeSnapshotWithCamera - cameraid:{camera.GetID()}"); + if (__instance != QSBPlayerManager.LocalPlayer.LocalProbeLauncher) + { + DebugLog.DebugWrite($"- not local launcher"); + __instance + ?.GetWorldObject() + ?.SendMessage(new TakeSnapshotMessage(camera.GetID())); + } + else + { + DebugLog.DebugWrite($"- local launcher"); + new PlayerLauncherTakeSnapshotMessage(camera.GetID()).Send(); + } + } } \ No newline at end of file diff --git a/QSB/Tools/ProbeLauncherTool/Patches/StationaryProbeLauncherPatches.cs b/QSB/Tools/ProbeLauncherTool/Patches/StationaryProbeLauncherPatches.cs new file mode 100644 index 000000000..cb07642a6 --- /dev/null +++ b/QSB/Tools/ProbeLauncherTool/Patches/StationaryProbeLauncherPatches.cs @@ -0,0 +1,17 @@ +using HarmonyLib; +using QSB.Patches; +using QSB.Tools.ProbeLauncherTool.WorldObjects; +using QSB.WorldSync; + +namespace QSB.Tools.ProbeLauncherTool.Patches; + +[HarmonyPatch] +public class StationaryProbeLauncherPatches : QSBPatch +{ + public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect; + + [HarmonyPostfix] + [HarmonyPatch(typeof(StationaryProbeLauncher), nameof(StationaryProbeLauncher.FinishExitSequence))] + public static void StationaryProbeLauncher_FinishExitSequence(StationaryProbeLauncher __instance) => + __instance.GetWorldObject().OnLocalUseStateChanged(false); +} diff --git a/QSB/Tools/ProbeLauncherTool/ProbeLauncherManager.cs b/QSB/Tools/ProbeLauncherTool/ProbeLauncherManager.cs index cff58709d..761232559 100644 --- a/QSB/Tools/ProbeLauncherTool/ProbeLauncherManager.cs +++ b/QSB/Tools/ProbeLauncherTool/ProbeLauncherManager.cs @@ -1,5 +1,6 @@ using Cysharp.Threading.Tasks; using QSB.Tools.ProbeLauncherTool.WorldObjects; +using QSB.Utility; using QSB.WorldSync; using System.Linq; using System.Threading; @@ -12,7 +13,9 @@ internal class ProbeLauncherManager : WorldObjectManager public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct) { - QSBWorldSync.Init(typeof(PlayerProbeLauncher)); + QSBWorldSync.Init(typeof(PlayerProbeLauncher), typeof(StationaryProbeLauncher)); + // Using ProbeLaunchers here so we can do inheritance, put only applying it to found StationaryProbeLauncher + QSBWorldSync.Init(QSBWorldSync.GetUnityObjects().SortDeterministic()); if (scene == OWScene.SolarSystem) { QSBWorldSync.Init(new[] diff --git a/QSB/Tools/ProbeLauncherTool/QSBProbeLauncherTool.cs b/QSB/Tools/ProbeLauncherTool/QSBProbeLauncherTool.cs index 244b6b0d0..afafbd11f 100644 --- a/QSB/Tools/ProbeLauncherTool/QSBProbeLauncherTool.cs +++ b/QSB/Tools/ProbeLauncherTool/QSBProbeLauncherTool.cs @@ -1,19 +1,26 @@ -using UnityEngine; +using QSB.Utility; +using UnityEngine; namespace QSB.Tools.ProbeLauncherTool; +[UsedInUnityProject] public class QSBProbeLauncherTool : QSBTool { public GameObject PreLaunchProbeProxy; public ProbeLauncherEffects Effects; public SingularityWarpEffect ProbeRetrievalEffect; - public void RetrieveProbe(bool playEffects) + private void VerifyAudioSource() { if (Effects._owAudioSource == null) { Effects._owAudioSource = Player.AudioController._repairToolSource; } + } + + public void RetrieveProbe(bool playEffects) + { + VerifyAudioSource(); PreLaunchProbeProxy.SetActive(true); if (playEffects) @@ -27,13 +34,28 @@ public void LaunchProbe() { PreLaunchProbeProxy.SetActive(false); - if (Effects._owAudioSource == null) - { - Effects._owAudioSource = Player.AudioController._repairToolSource; - } + VerifyAudioSource(); // TODO : make this do underwater stuff correctly Effects.PlayLaunchClip(false); Effects.PlayLaunchParticles(false); } + + public void ChangeMode() + { + VerifyAudioSource(); + + Effects.PlayChangeModeClip(); + } + + public void TakeSnapshot() + { + VerifyAudioSource(); + + // Vanilla method uses the global player audio controller -> bad + Effects._owAudioSource.PlayOneShot(AudioType.ToolProbeTakePhoto, 1f); + + // Also make the probe itself play the sound effect + if (Player.Probe.IsLaunched()) Player.Probe.TakeSnapshot(); + } } \ No newline at end of file diff --git a/QSB/Tools/ProbeLauncherTool/VariableSync/StationaryProbeLauncherVariableSyncer.cs b/QSB/Tools/ProbeLauncherTool/VariableSync/StationaryProbeLauncherVariableSyncer.cs new file mode 100644 index 000000000..b857afec4 --- /dev/null +++ b/QSB/Tools/ProbeLauncherTool/VariableSync/StationaryProbeLauncherVariableSyncer.cs @@ -0,0 +1,51 @@ +using Mirror; +using QSB.Tools.ProbeLauncherTool.WorldObjects; +using QSB.Utility.LinkedWorldObject; +using QSB.Utility.VariableSync; +using QSB.WorldSync; +using UnityEngine; + +namespace QSB.Tools.ProbeLauncherTool.VariableSync; + +public class StationaryProbeLauncherVariableSyncer : BaseVariableSyncer<(float, float, float)>, ILinkedNetworkBehaviour +{ + protected override bool HasChanged() + { + var launcher = (StationaryProbeLauncher)WorldObject.AttachedObject; + + Value = (launcher._degreesX, launcher._degreesY, launcher._audioSource._localVolume); + + return Value != PrevValue; + } + + protected override void Serialize(NetworkWriter writer) + { + base.Serialize(writer); + + var launcher = (StationaryProbeLauncher)WorldObject.AttachedObject; + + writer.Write(launcher._degreesX); + writer.Write(launcher._degreesY); + writer.Write(launcher._audioSource.GetLocalVolume()); + } + + protected override void Deserialize(NetworkReader reader) + { + base.Deserialize(reader); + + var launcher = (StationaryProbeLauncher)WorldObject.AttachedObject; + + launcher._degreesX = reader.Read(); + launcher._degreesY = reader.Read(); + launcher._audioSource.SetLocalVolume(reader.Read()); + + // Update rotation based on x and y degrees + launcher.transform.localRotation = Quaternion.AngleAxis(launcher._degreesX, launcher._localUpAxis) * launcher._initRotX; + launcher._verticalPivot.localRotation = Quaternion.AngleAxis(launcher._degreesY, -Vector3.right) * launcher._initRotY; + + Value = (launcher._degreesX, launcher._degreesY, launcher._audioSource._localVolume); + } + + protected QSBStationaryProbeLauncher WorldObject { get; private set; } + public void SetWorldObject(IWorldObject worldObject) => WorldObject = (QSBStationaryProbeLauncher)worldObject; +} diff --git a/QSB/Tools/ProbeLauncherTool/WorldObjects/QSBProbeLauncher.cs b/QSB/Tools/ProbeLauncherTool/WorldObjects/QSBProbeLauncher.cs index 80742acad..3c714a24b 100644 --- a/QSB/Tools/ProbeLauncherTool/WorldObjects/QSBProbeLauncher.cs +++ b/QSB/Tools/ProbeLauncherTool/WorldObjects/QSBProbeLauncher.cs @@ -1,13 +1,20 @@ using Cysharp.Threading.Tasks; using QSB.Messaging; +using QSB.Player; +using QSB.QuantumSync; using QSB.Tools.ProbeLauncherTool.Messages; +using QSB.Tools.ProbeTool; using QSB.WorldSync; using System.Threading; +using UnityEngine; namespace QSB.Tools.ProbeLauncherTool.WorldObjects; public class QSBProbeLauncher : WorldObject { + private uint _probeOwnerID = uint.MaxValue; + protected QSBSurveyorProbe LaunchedProbe { get; private set; } + public override async UniTask Init(CancellationToken ct) => AttachedObject.OnLaunchProbe += OnLaunchProbe; @@ -16,21 +23,27 @@ public override void OnRemoval() => public override void SendInitialState(uint to) { + // Retrieval resets the probe owner ID + var probeOwnerID = _probeOwnerID; + if (AttachedObject._preLaunchProbeProxy.activeSelf) { this.SendMessage(new RetrieveProbeMessage(false)); } else { - this.SendMessage(new LaunchProbeMessage(false)); + this.SendMessage(new LaunchProbeMessage(false, probeOwnerID)); } } private void OnLaunchProbe(SurveyorProbe probe) => - this.SendMessage(new LaunchProbeMessage(true)); + this.SendMessage(new LaunchProbeMessage(true, QSBPlayerManager.LocalPlayerId)); public void RetrieveProbe(bool playEffects) { + _probeOwnerID = uint.MaxValue; + LaunchedProbe = null; + if (AttachedObject._preLaunchProbeProxy.activeSelf) { return; @@ -44,8 +57,13 @@ public void RetrieveProbe(bool playEffects) } } - public void LaunchProbe(bool playEffects) + public void LaunchProbe(bool playEffects, uint probeOwnerID) { + _probeOwnerID = probeOwnerID; + LaunchedProbe = QSBPlayerManager.GetPlayer(_probeOwnerID)?.Probe; + + if (LaunchedProbe == null) Debug.LogError($"Could not find probe owner with ID {_probeOwnerID}"); + if (!AttachedObject._preLaunchProbeProxy.activeSelf) { return; @@ -60,4 +78,20 @@ public void LaunchProbe(bool playEffects) AttachedObject._effects.PlayLaunchParticles(false); } } + + public void ChangeMode() + { + AttachedObject._effects.PlayChangeModeClip(); + } + + public void TakeSnapshot(PlayerInfo player, ProbeCamera.ID cameraId) + { + // Not using PlaySnapshotClip because that uses Locator.GetPlayerAudioController() instead of owAudioSource for some reason + AttachedObject._effects._owAudioSource.PlayOneShot(AudioType.ToolProbeTakePhoto, 1f); + + // If their probe is launched also play a snapshot from it + if (LaunchedProbe && LaunchedProbe.IsLaunched()) LaunchedProbe.TakeSnapshot(); + + QuantumManager.OnTakeProbeSnapshot(player, cameraId); + } } \ No newline at end of file diff --git a/QSB/Tools/ProbeLauncherTool/WorldObjects/QSBStationaryProbeLauncher.cs b/QSB/Tools/ProbeLauncherTool/WorldObjects/QSBStationaryProbeLauncher.cs new file mode 100644 index 000000000..e170e4a04 --- /dev/null +++ b/QSB/Tools/ProbeLauncherTool/WorldObjects/QSBStationaryProbeLauncher.cs @@ -0,0 +1,116 @@ +using Cysharp.Threading.Tasks; +using Mirror; +using QSB.AuthoritySync; +using QSB.Messaging; +using QSB.Player; +using QSB.Tools.ProbeLauncherTool.Messages; +using QSB.Tools.ProbeLauncherTool.VariableSync; +using QSB.Utility.LinkedWorldObject; +using System.Threading; + +namespace QSB.Tools.ProbeLauncherTool.WorldObjects; + +// TODO John - i think this can be simplified, i might be able to remove the variable syncer +// TODO: on player leave? idk if this will be needed if i ever simplify this +public class QSBStationaryProbeLauncher : QSBProbeLauncher, ILinkedWorldObject +{ + private uint _currentUser = uint.MaxValue; + + public StationaryProbeLauncherVariableSyncer NetworkBehaviour { get; private set; } + public void SetNetworkBehaviour(NetworkBehaviour networkBehaviour) => NetworkBehaviour = (StationaryProbeLauncherVariableSyncer)networkBehaviour; + + private bool _isInUse; + private StationaryProbeLauncher _stationaryProbeLauncher; + + public override async UniTask Init(CancellationToken ct) + { + // This is implemented by inheriting LinkedWorldObject normally, + // However I want to inherit from QSBProbeLauncher or else we'd have to redo the sync for the VFX/SFX + if (QSBCore.IsHost) + { + this.SpawnLinked(QSBNetworkManager.singleton.StationaryProbeLauncherPrefab, false); + } + else + { + await this.WaitForLink(ct); + } + + await base.Init(ct); + + _stationaryProbeLauncher = (StationaryProbeLauncher)AttachedObject; + _stationaryProbeLauncher._interactVolume.OnPressInteract += OnPressInteract; + + // Fix spatial blend of sound effects + _stationaryProbeLauncher._effects._owAudioSource.spatialBlend = 1; + _stationaryProbeLauncher._audioSource.spatialBlend = 1; + + UpdateUse(); + } + + public override void OnRemoval() + { + if (QSBCore.IsHost) + { + NetworkServer.Destroy(NetworkBehaviour.gameObject); + } + + base.OnRemoval(); + + _stationaryProbeLauncher._interactVolume.OnPressInteract -= OnPressInteract; + } + + private void OnPressInteract() => OnLocalUseStateChanged(true); + + public override void SendInitialState(uint to) + { + base.SendInitialState(to); + + this.SendMessage(new StationaryProbeLauncherMessage(_isInUse, _currentUser) { To = to }); + } + + public void OnRemoteUseStateChanged(bool isInUse, uint user) + { + _isInUse = isInUse; + + _currentUser = isInUse ? user : uint.MaxValue; + + // Whoever is using it needs authority to be able to rotate it + if (QSBCore.IsHost) + { + NetworkBehaviour.netIdentity.SetAuthority(_currentUser); + } + + UpdateUse(); + } + + public void OnLocalUseStateChanged(bool isInUse) + { + _isInUse = isInUse; + + _currentUser = isInUse ? QSBPlayerManager.LocalPlayerId : uint.MaxValue; + + // Whoever is using it needs authority to be able to rotate it + if (QSBCore.IsHost) + { + NetworkBehaviour.netIdentity.SetAuthority(_currentUser); + } + + this.SendMessage(new StationaryProbeLauncherMessage(isInUse, _currentUser)); + } + + private void UpdateUse() + { + // If somebody is using this we disable the interaction shape + _stationaryProbeLauncher._interactVolume.SetInteractionEnabled(!_isInUse); + + if (_isInUse) + { + _stationaryProbeLauncher._audioSource.SetLocalVolume(0f); + _stationaryProbeLauncher._audioSource.Play(); + } + else + { + _stationaryProbeLauncher._audioSource.Stop(); + } + } +} diff --git a/QSB/Tools/ProbeTool/Messages/RotateProbeMessage.cs b/QSB/Tools/ProbeTool/Messages/RotateProbeMessage.cs new file mode 100644 index 000000000..50c7bc42e --- /dev/null +++ b/QSB/Tools/ProbeTool/Messages/RotateProbeMessage.cs @@ -0,0 +1,32 @@ +using QSB.Messaging; +using QSB.Player; +using QSB.WorldSync; +using UnityEngine; + +namespace QSB.Tools.ProbeTool.Messages; + +internal class RotateProbeMessage : QSBMessage<(RotationType rotationType, Vector2 cameraRotation)> +{ + public RotateProbeMessage(RotationType rotationType, Vector2 cameraRotation) : base((rotationType, cameraRotation)) { } + + public override bool ShouldReceive => QSBWorldSync.AllObjectsReady; + + public override void OnReceiveRemote() + { + var playerProbe = QSBPlayerManager.GetPlayer(From).Probe; + var rotatingCamera = playerProbe.GetRotatingCamera(); + + if (Data.rotationType == RotationType.Horizontal) + { + rotatingCamera.RotateHorizontal(Data.cameraRotation.x); + } + else if (Data.rotationType == RotationType.Vertical) + { + rotatingCamera.RotateVertical(Data.cameraRotation.y); + } + else + { + rotatingCamera.ResetRotation(); + } + } +} diff --git a/QSB/Tools/ProbeTool/Patches/ProbeToolPatches.cs b/QSB/Tools/ProbeTool/Patches/ProbeToolPatches.cs index db5c5fba0..fcfc9d821 100644 --- a/QSB/Tools/ProbeTool/Patches/ProbeToolPatches.cs +++ b/QSB/Tools/ProbeTool/Patches/ProbeToolPatches.cs @@ -1,5 +1,9 @@ using HarmonyLib; +using QSB.Messaging; using QSB.Patches; +using QSB.Tools.ProbeTool.Messages; +using QSB.Utility; +using UnityEngine; namespace QSB.Tools.ProbeTool.Patches; @@ -37,4 +41,53 @@ public static bool FixedUpdateOverride(NomaiWarpStreaming __instance) return false; } + + [HarmonyPrefix] + [HarmonyPatch(typeof(ProbeCamera), nameof(ProbeCamera.RotateHorizontal))] + public static bool RotateHorizontal(ProbeCamera __instance, float degrees) + { + if (__instance._id != ProbeCamera.ID.Rotating) + { + Debug.LogWarning("Tried to rotate a non-rotating ProbeCamera!", __instance); + return false; + } + + __instance._cameraRotation.x += degrees; + __instance.transform.parent.localRotation = __instance._origParentLocalRotation * Quaternion.AngleAxis(__instance._cameraRotation.x, Vector3.up); + __instance.RaiseEvent(nameof(__instance.OnRotateCamera), __instance._cameraRotation); + new RotateProbeMessage(RotationType.Horizontal, __instance._cameraRotation).Send(); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(ProbeCamera), nameof(ProbeCamera.RotateVertical))] + public static bool RotateVertical(ProbeCamera __instance, float degrees) + { + if (__instance._id != ProbeCamera.ID.Rotating) + { + Debug.LogWarning("Tried to rotate a non-rotating ProbeCamera!", __instance); + return false; + } + + __instance._cameraRotation.y = Mathf.Clamp(__instance._cameraRotation.y + degrees, -90f, 0f); + __instance.transform.localRotation = __instance._origLocalRotation * Quaternion.AngleAxis(__instance._cameraRotation.y, Vector3.right); + __instance.RaiseEvent(nameof(__instance.OnRotateCamera), __instance._cameraRotation); + new RotateProbeMessage(RotationType.Vertical, __instance._cameraRotation).Send(); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(ProbeCamera), nameof(ProbeCamera.ResetRotation))] + public static bool ResetRotation(ProbeCamera __instance) + { + if (__instance._id == ProbeCamera.ID.Rotating) + { + __instance._cameraRotation = Vector2.zero; + __instance.transform.localRotation = __instance._origLocalRotation; + __instance.transform.parent.localRotation = __instance._origParentLocalRotation; + new RotateProbeMessage(RotationType.Reset, __instance._cameraRotation).Send(); + } + + return false; + } } \ No newline at end of file diff --git a/QSB/Tools/ProbeTool/ProbeCreator.cs b/QSB/Tools/ProbeTool/ProbeCreator.cs index 4a82e84bd..8742ac34f 100644 --- a/QSB/Tools/ProbeTool/ProbeCreator.cs +++ b/QSB/Tools/ProbeTool/ProbeCreator.cs @@ -26,7 +26,7 @@ public static Transform CreateProbe(PlayerInfo player) { var REMOTE_Probe_Body = Object.Instantiate(GetPrefab()); - var qsbProbe = REMOTE_Probe_Body.GetComponent(); + var qsbProbe = REMOTE_Probe_Body.GetComponent(); player.Probe = qsbProbe; qsbProbe.SetOwner(player); diff --git a/QSB/Tools/ProbeTool/QSBProbeAnimatorController.cs b/QSB/Tools/ProbeTool/QSBProbeAnimatorController.cs new file mode 100644 index 000000000..7a4915af8 --- /dev/null +++ b/QSB/Tools/ProbeTool/QSBProbeAnimatorController.cs @@ -0,0 +1,92 @@ +using UnityEngine; + +namespace QSB.Tools.ProbeTool; + +[RequireComponent(typeof(Animator))] +internal class QSBProbeAnimatorController : MonoBehaviour +{ + private Animator _animator; + private QSBSurveyorProbe _probe; + + [SerializeField] + private Transform _centerBone; + + private Quaternion _startCenterBoneRotation = Quaternion.identity; + private Quaternion _targetCenterBoneRotation = Quaternion.identity; + private Quaternion _currentCenterBoneRotation = Quaternion.identity; + + private float _rotationT; + + private void Awake() + { + this._animator = base.GetComponent(); + this._probe = transform.parent.parent.parent.GetRequiredComponent(); + this._probe.OnLaunchProbe += this.OnProbeFire; + this._probe.OnAnchorProbe += this.OnProbeAnchor; + this._probe.OnRetrieveProbe += this.OnProbeReset; + } + + private void Start() + { + this._probe.GetRotatingCamera().OnRotateCamera += this.OnRotateCamera; + base.enabled = false; + } + + private void OnDestroy() + { + this._probe.OnLaunchProbe -= this.OnProbeFire; + this._probe.OnAnchorProbe -= this.OnProbeAnchor; + this._probe.OnRetrieveProbe -= this.OnProbeReset; + } + + public Quaternion GetCenterBoneLocalRotation() + { + return this._centerBone.localRotation; + } + + private void OnProbeFire() + { + this._animator.SetTrigger("Fire"); + } + + private void OnProbeAnchor() + { + this._animator.SetTrigger("Impact"); + } + + private void OnProbeReset() + { + this._animator.SetTrigger("Reset"); + this._centerBone.localRotation = Quaternion.identity; + this._startCenterBoneRotation = Quaternion.identity; + this._targetCenterBoneRotation = Quaternion.identity; + this._currentCenterBoneRotation = Quaternion.identity; + this._rotationT = 0f; + base.enabled = false; + } + + private void OnRotateCamera(Vector2 rotation) + { + this._startCenterBoneRotation = this._currentCenterBoneRotation; + this._targetCenterBoneRotation = Quaternion.AngleAxis(rotation.x, Vector3.up); + this._rotationT = 0f; + base.enabled = true; + } + + private void LateUpdate() + { + this._rotationT += 10f * Time.deltaTime; + if (this._rotationT >= 1f) + { + this._rotationT = 1f; + this._currentCenterBoneRotation = this._targetCenterBoneRotation; + base.enabled = false; + } + else + { + float t = Mathf.Sqrt(Mathf.SmoothStep(0f, 1f, this._rotationT)); + this._currentCenterBoneRotation = Quaternion.Lerp(this._startCenterBoneRotation, this._targetCenterBoneRotation, t); + } + this._centerBone.localRotation = this._currentCenterBoneRotation; + } +} diff --git a/QSB/Tools/ProbeTool/QSBProbeCamera.cs b/QSB/Tools/ProbeTool/QSBProbeCamera.cs new file mode 100644 index 000000000..8efaa1661 --- /dev/null +++ b/QSB/Tools/ProbeTool/QSBProbeCamera.cs @@ -0,0 +1,94 @@ +using UnityEngine; + +namespace QSB.Tools.ProbeTool; + +[RequireComponent(typeof(OWCamera))] +public class QSBProbeCamera : MonoBehaviour +{ + [SerializeField] + private ProbeCamera.ID _id; + + private OWCamera _camera; + private RenderTexture _snapshotTexture; + private NoiseImageEffect _noiseEffect; + private static OWCamera _lastSnapshotCamera; + private Quaternion _origLocalRotation; + private Quaternion _origParentLocalRotation; + private Vector2 _cameraRotation = Vector2.zero; + private QuantumMoon _quantumMoon; + private SandLevelController _sandLevelController; + + public event ProbeCamera.RotateCameraEvent OnRotateCamera; + + private void Awake() + { + _camera = this.GetRequiredComponent(); + _camera.enabled = false; + _noiseEffect = GetComponent(); + //this._snapshotTexture = ProbeCamera.GetSharedSnapshotTexture(); + } + + private void OnDestroy() + => _snapshotTexture = null; + + private void Start() + { + var astroObject = Locator.GetAstroObject(AstroObject.Name.QuantumMoon); + if (astroObject != null) + { + _quantumMoon = astroObject.GetComponent(); + } + } + + public static OWCamera GetLastSnapshotCamera() => + _lastSnapshotCamera; + + public OWCamera GetOWCamera() + => _camera; + + public ProbeCamera.ID GetID() + => _id; + + public void SetSandLevelController(SandLevelController sandLevelController) + => _sandLevelController = sandLevelController; + + public bool HasInterference() => + _id != ProbeCamera.ID.PreLaunch + //&& ((this._quantumMoon != null && this._quantumMoon.IsPlayerInside() != this._quantumMoon.IsProbeInside()) + || (_sandLevelController != null && _sandLevelController.IsPointBuried(transform.position)); + //|| (Locator.GetCloakFieldController() != null && Locator.GetCloakFieldController().isPlayerInsideCloak != Locator.GetCloakFieldController().isProbeInsideCloak)); + + public RenderTexture TakeSnapshot() + { + _lastSnapshotCamera = _camera; + if (_noiseEffect != null) + { + _noiseEffect.enabled = HasInterference(); + } + + _camera.targetTexture = _snapshotTexture; + _camera.Render(); + return _snapshotTexture; + } + + public void RotateHorizontal(float cameraRotationX) + { + _cameraRotation.x = cameraRotationX; + transform.parent.localRotation = _origParentLocalRotation * Quaternion.AngleAxis(_cameraRotation.x, Vector3.up); + OnRotateCamera?.Invoke(_cameraRotation); + } + + public void RotateVertical(float cameraRotationY) + { + _cameraRotation.y = cameraRotationY; + transform.localRotation = _origLocalRotation * Quaternion.AngleAxis(_cameraRotation.y, Vector3.right); + OnRotateCamera?.Invoke(_cameraRotation); + } + + public void ResetRotation() + { + _cameraRotation = Vector2.zero; + transform.localRotation = _origLocalRotation; + transform.parent.localRotation = _origParentLocalRotation; + } +} diff --git a/QSB/Tools/ProbeTool/QSBProbeEffects.cs b/QSB/Tools/ProbeTool/QSBProbeEffects.cs index bc0f5bad7..e517f75ff 100644 --- a/QSB/Tools/ProbeTool/QSBProbeEffects.cs +++ b/QSB/Tools/ProbeTool/QSBProbeEffects.cs @@ -5,17 +5,18 @@ namespace QSB.Tools.ProbeTool; +[UsedInUnityProject] internal class QSBProbeEffects : MonoBehaviour { public OWAudioSource _flightLoopAudio; public OWAudioSource _anchorAudio; public ParticleSystem _anchorParticles; - private QSBProbe _probe; + private QSBSurveyorProbe _probe; private void Awake() { - _probe = QSBWorldSync.GetUnityObjects().First(x => gameObject.transform.IsChildOf(x.transform)); + _probe = QSBWorldSync.GetUnityObjects().First(x => gameObject.transform.IsChildOf(x.transform)); if (_probe == null) { DebugLog.ToConsole($"Error - Couldn't find QSBProbe!", OWML.Common.MessageType.Error); @@ -25,6 +26,7 @@ private void Awake() _probe.OnAnchorProbe += OnAnchor; _probe.OnUnanchorProbe += OnUnanchor; _probe.OnStartRetrieveProbe += OnStartRetrieve; + _probe.OnTakeSnapshot += OnTakeSnapshot; } private void OnDestroy() @@ -33,6 +35,7 @@ private void OnDestroy() _probe.OnAnchorProbe -= OnAnchor; _probe.OnUnanchorProbe -= OnUnanchor; _probe.OnStartRetrieveProbe -= OnStartRetrieve; + _probe.OnTakeSnapshot -= OnTakeSnapshot; } private void OnLaunch() => _flightLoopAudio.FadeIn(0.1f, true, true); @@ -57,5 +60,11 @@ private void OnUnanchor() => _flightLoopAudio.FadeIn(0.5f); private void OnStartRetrieve(float retrieveLength) - => _flightLoopAudio.FadeOut(retrieveLength); + { + _flightLoopAudio.FadeOut(retrieveLength); + _anchorAudio.PlayOneShot(AudioType.ToolProbeRetrieve); + } + + private void OnTakeSnapshot() + => _anchorAudio.PlayOneShot(AudioType.ToolProbeTakePhoto); } \ No newline at end of file diff --git a/QSB/Tools/ProbeTool/QSBProbeLantern.cs b/QSB/Tools/ProbeTool/QSBProbeLantern.cs index 74365302f..728033aaa 100644 --- a/QSB/Tools/ProbeTool/QSBProbeLantern.cs +++ b/QSB/Tools/ProbeTool/QSBProbeLantern.cs @@ -5,6 +5,7 @@ namespace QSB.Tools.ProbeTool; +[UsedInUnityProject] internal class QSBProbeLantern : MonoBehaviour { public float _fadeInDuration; @@ -13,7 +14,7 @@ internal class QSBProbeLantern : MonoBehaviour public OWEmissiveRenderer _emissiveRenderer; public float _originalRange; - private QSBProbe _probe; + private QSBSurveyorProbe _probe; private OWLight2 _light; private float _fadeFraction; private float _targetFade; @@ -23,7 +24,7 @@ internal class QSBProbeLantern : MonoBehaviour private void Awake() { - _probe = QSBWorldSync.GetUnityObjects().First(x => gameObject.transform.IsChildOf(x.transform)); + _probe = QSBWorldSync.GetUnityObjects().First(x => gameObject.transform.IsChildOf(x.transform)); if (_probe == null) { DebugLog.ToConsole($"Error - Couldn't find QSBProbe!", OWML.Common.MessageType.Error); diff --git a/QSB/Tools/ProbeTool/QSBProbeSpotlight.cs b/QSB/Tools/ProbeTool/QSBProbeSpotlight.cs index 2e235cd20..00c600168 100644 --- a/QSB/Tools/ProbeTool/QSBProbeSpotlight.cs +++ b/QSB/Tools/ProbeTool/QSBProbeSpotlight.cs @@ -5,19 +5,20 @@ namespace QSB.Tools.ProbeTool; +[UsedInUnityProject] internal class QSBProbeSpotlight : MonoBehaviour { public ProbeCamera.ID _id; public float _fadeInLength = 1f; public float _intensity; - private QSBProbe _probe; + private QSBSurveyorProbe _probe; private OWLight2 _light; private float _timer; private void Awake() { - _probe = QSBWorldSync.GetUnityObjects().First(x => gameObject.transform.IsChildOf(x.transform)); + _probe = QSBWorldSync.GetUnityObjects().First(x => gameObject.transform.IsChildOf(x.transform)); if (_probe == null) { DebugLog.ToConsole($"Error - Couldn't find QSBProbe!", OWML.Common.MessageType.Error); diff --git a/QSB/Tools/ProbeTool/QSBProbe.cs b/QSB/Tools/ProbeTool/QSBSurveyorProbe.cs similarity index 81% rename from QSB/Tools/ProbeTool/QSBProbe.cs rename to QSB/Tools/ProbeTool/QSBSurveyorProbe.cs index 45881b8ca..1869b7fa2 100644 --- a/QSB/Tools/ProbeTool/QSBProbe.cs +++ b/QSB/Tools/ProbeTool/QSBSurveyorProbe.cs @@ -4,7 +4,8 @@ namespace QSB.Tools.ProbeTool; -public class QSBProbe : MonoBehaviour, ILightSource +[UsedInUnityProject] +public class QSBSurveyorProbe : MonoBehaviour, ILightSource { public delegate void SurveyorProbeEvent(); public delegate void RetrieveEvent(float retrieveLength); @@ -17,11 +18,15 @@ public class QSBProbe : MonoBehaviour, ILightSource public event SurveyorProbeEvent OnUnanchorProbe; public event SurveyorProbeEvent OnRetrieveProbe; public event SurveyorProbeEvent OnProbeDestroyed; + public event SurveyorProbeEvent OnTakeSnapshot; public event RetrieveEvent OnStartRetrieveProbe; private GameObject _detectorObj; private RulesetDetector _rulesetDetector; private SingularityWarpEffect _warpEffect; + private QSBProbeCamera _forwardCam; + private QSBProbeCamera _reverseCam; + private QSBProbeCamera _rotatingCam; private bool _isRetrieving; private PlayerInfo _owner; private bool _anchored; @@ -30,6 +35,21 @@ public class QSBProbe : MonoBehaviour, ILightSource public RulesetDetector GetRulesetDetector() => _rulesetDetector; + public QSBProbeCamera GetForwardCamera() + { + return _forwardCam; + } + + public QSBProbeCamera GetReverseCamera() + { + return _reverseCam; + } + + public QSBProbeCamera GetRotatingCamera() + { + return _rotatingCam; + } + private void Awake() { _detectorObj = GetComponentInChildren().gameObject; @@ -37,6 +57,23 @@ private void Awake() _warpEffect = GetComponentInChildren(); _warpEffect.OnWarpComplete += OnWarpComplete; _isRetrieving = false; + + var probeCameras = GetComponentsInChildren(); + for (var i = 0; i < probeCameras.Length; i++) + { + if (probeCameras[i].GetID() == ProbeCamera.ID.Forward) + { + _forwardCam = probeCameras[i]; + } + else if (probeCameras[i].GetID() == ProbeCamera.ID.Reverse) + { + _reverseCam = probeCameras[i]; + } + else if (probeCameras[i].GetID() == ProbeCamera.ID.Rotating) + { + _rotatingCam = probeCameras[i]; + } + } } private void Start() @@ -127,6 +164,9 @@ public void HandleEvent(ProbeEvent probeEvent) case ProbeEvent.Unanchor: _anchored = false; _owner.ProbeActive = true; // just in case it was missed + _forwardCam.SetSandLevelController(null); + _reverseCam.SetSandLevelController(null); + _rotatingCam.SetSandLevelController(null); OnUnanchorProbe(); break; case ProbeEvent.Retrieve: @@ -165,6 +205,9 @@ private void Deactivate() _lightSourceVol.SetVolumeActivation(false); gameObject.SetActive(false); _isRetrieving = false; + _forwardCam.SetSandLevelController(null); + _reverseCam.SetSandLevelController(null); + _rotatingCam.SetSandLevelController(null); } public void OnStartRetrieve(float duration) @@ -204,4 +247,6 @@ public bool CheckIlluminationAtPoint(Vector3 point, float buffer = 0f, float max public LightSourceType GetLightSourceType() => LightSourceType.PROBE; public OWLight2[] GetLights() => _illuminationCheckLights; public Vector3 GetLightSourcePosition() => _lightSourceVol.transform.position; + + public void TakeSnapshot() => OnTakeSnapshot?.Invoke(); } \ No newline at end of file diff --git a/QSB/Tools/ProbeTool/RotationType.cs b/QSB/Tools/ProbeTool/RotationType.cs new file mode 100644 index 000000000..30883d710 --- /dev/null +++ b/QSB/Tools/ProbeTool/RotationType.cs @@ -0,0 +1,8 @@ +namespace QSB.Tools.ProbeTool; + +public enum RotationType +{ + Horizontal, + Vertical, + Reset +} diff --git a/QSB/Tools/QSBTool.cs b/QSB/Tools/QSBTool.cs index d36336736..0885143e0 100644 --- a/QSB/Tools/QSBTool.cs +++ b/QSB/Tools/QSBTool.cs @@ -5,6 +5,7 @@ namespace QSB.Tools; +[UsedInUnityProject] public class QSBTool : PlayerTool { public PlayerInfo Player { get; set; } diff --git a/QSB/Tools/TranslatorTool/QSBNomaiTranslator.cs b/QSB/Tools/TranslatorTool/QSBNomaiTranslator.cs index 46209451c..aaf41bf77 100644 --- a/QSB/Tools/TranslatorTool/QSBNomaiTranslator.cs +++ b/QSB/Tools/TranslatorTool/QSBNomaiTranslator.cs @@ -1,7 +1,9 @@ -using UnityEngine; +using QSB.Utility; +using UnityEngine; namespace QSB.Tools.TranslatorTool; +[UsedInUnityProject] public class QSBNomaiTranslator : QSBTool { public static float distToClosestTextCenter = 1f; diff --git a/QSB/Tools/TranslatorTool/QSBNomaiTranslatorProp.cs b/QSB/Tools/TranslatorTool/QSBNomaiTranslatorProp.cs index 8a266dd3d..c825e5b8c 100644 --- a/QSB/Tools/TranslatorTool/QSBNomaiTranslatorProp.cs +++ b/QSB/Tools/TranslatorTool/QSBNomaiTranslatorProp.cs @@ -1,10 +1,12 @@ -using System.Collections.Generic; +using QSB.Utility; +using System.Collections.Generic; using System.Text; using UnityEngine; using UnityEngine.UI; namespace QSB.Tools.TranslatorTool; +[UsedInUnityProject] internal class QSBNomaiTranslatorProp : MonoBehaviour { private static MaterialPropertyBlock s_matPropBlock; diff --git a/QSB/Tools/TranslatorTool/QSBTranslatorScanBeam.cs b/QSB/Tools/TranslatorTool/QSBTranslatorScanBeam.cs index a94e6a27e..adc484cac 100644 --- a/QSB/Tools/TranslatorTool/QSBTranslatorScanBeam.cs +++ b/QSB/Tools/TranslatorTool/QSBTranslatorScanBeam.cs @@ -1,8 +1,10 @@ -using QSB.WorldSync; +using QSB.Utility; +using QSB.WorldSync; using UnityEngine; namespace QSB.Tools.TranslatorTool; +[UsedInUnityProject] internal class QSBTranslatorScanBeam : MonoBehaviour { public Renderer _projectorRenderer; diff --git a/QSB/Translations/de.json b/QSB/Translations/de.json new file mode 100644 index 000000000..60fd0e3f2 --- /dev/null +++ b/QSB/Translations/de.json @@ -0,0 +1,126 @@ +{ + "Language": "GERMAN", + "MainMenuHost": "MEHRSPIELER HOSTEN", + "MainMenuConnect": "VERBINDEN ZUM MEHRSPIELER", + "PauseMenuDisconnect": "VERBINDUNG TRENNEN", + "PauseMenuStopHosting": "HOSTEN BEENDEN", + "PublicIPAddress": "Öffentliche IP Addresse\n\n(DEIN MEHRSPIELER SPIELSTAND WIRD ÜBERSCHRIEBEN)", + "ProductUserID": "Produktnutzer ID\n\n(DEIN MEHRSPIELER SPIELSTAND WIRD ÜBERSCHRIEBEN)", + "Connect": "VERBINDEN", + "Cancel": "ABBRECHEN", + "HostExistingOrNewOrCopy": "Willst du eine bestehende Mehrspieler Expedition, eine neue Expedition oder eine Kopie einer bestehenden Einzelspieler Expedition hosten?", + "HostNewOrCopy": "Willst du eine neue Expedition oder eine Kopie einer bestehenden Einzelspieler Expedition hosten?", + "HostExistingOrNew": "Willst du eine bestehende Mehrspieler Expedition oder eine neue Expedition hosten?", + "ExistingSave": "BESTEHENDER SPIELSTAND", + "NewSave": "NEUER SPIELSTAND", + "CopySave": "SPIELSTAND KOPIEREN", + "DisconnectAreYouSure": "Bist du sicher, dass du die Verbindung trennen willst?\nDies wird dich zurück in das Hauptmenü bringen.", + "Yes": "JA", + "No": "NEIN", + "StopHostingAreYouSure": "Bist du sicher, dass du aufhören willst zu hosten?\nDies wird die Verbindung alle anderen Spieler trennen und sie zurück in das Hauptmenü bringen.", + "CopyProductUserIDToClipboard": "Server wird gehostet.\nAndere Spieler werden sich mit Hilfe deiner Produktnutzer ID verbinden. Sie lautet :\n{0}\nWillst du das in die Zwischenablage kopieren?", + "Connecting": "VERBINDEN...", + "OK": "BESTÄTIGEN", + "ServerRefusedConnection": "Der Server hat die Verbindung abgelehnt.\n{0}", + "ClientDisconnectWithError": "Der Client wurde mit einem Fehler getrennt!\n{0}", + "QSBVersionMismatch": "QSB-Version stimmt nicht überein. (Client:{0}, Server:{1})", + "OWVersionMismatch": "Outer Wilds-Version stimmt nicht überein. (Client:{0}, Server:{1})", + "DLCMismatch": "DLC Installationsstatus stimmt nicht überein. (Client:{0}, Server:{1})", + "GameProgressLimit": "Spiel ist zu weit fortgeschritten.", + "AddonMismatch": "Addons stimmen nicht überein. (Client:{0} addons, Server:{1} addons)", + "IncompatibleMod": "Es wird eine inkompatible/unerlaubte Modifikation genutzt. Die erste gefundene Modifikation war {0}", + "PlayerJoinedTheGame": "{0} trat bei!", + "PlayerWasKicked": "{0} wurde gekickt.", + "KickedFromServer": "Vom Server gekickt. Grund : {0}", + "RespawnPlayer": "Spieler respawnen", + "TimeSyncTooFarBehind": "{0}\nVorspulen zur Anpassung an die Serverzeit...", + "TimeSyncWaitingForStartOfServer": "Es wird auf den Start des Servers gewartet...", + "TimeSyncTooFarAhead": "{0}\nPausieren zur Anpassung an die Serverzeit...", + "TimeSyncWaitForAllToReady": "Es wird auf den Beginn der Schleife gewartet...", + "TimeSyncWaitForAllToDie": "Es wird auf das Ende der Schleife gewartet...", + "GalaxyMapEveryoneNotPresent": "Es ist noch nicht soweit. Alle sollten hier sein um das zu erleben.", + "YouAreDead": "Du bist tot.", + "WaitingForRespawn": "Es wird auf jemanden gewartet, der dich respawnt...", + "WaitingForAllToDie": "Es wird darauf gewartet, dass {0} Spieler sterben...", + "AttachToShip": "Am Schiff befestigen", + "DetachFromShip": "Vom Schiff lösen", + "DeathMessages": { + "Default": [ + "{0} starb", + "{0} wurde getötet" + ], + "Impact": [ + "{0} vergaß Bremsraketen zu benutzen", + "{0} ist zu hart auf den Boden geknallt", + "{0} schlug zu hart auf den Boden auf", + "{0} wurde geplättet", + "{0} starb durch einen Aufprall", + "{0} ist zu hart auf den Boden aufgeschlagen" + ], + "Asphyxiation": [ + "{0} vergaß zu atmen", + "{0} erstickte", + "{0} starb an Erstickung", + "{0} vergaß wie man atmet", + "{0} vergaß den Sauerstoff zu prüfen", + "{0} ging die Luft aus", + "{0} ging der Sauerstoff aus", + "{0} brauchte sowieso keine Luft" + ], + "Energy": [ + "{0} wurde gekocht" + ], + "Supernova": [ + "{0} ging die Zeit aus", + "{0} burnt up", + "{0} wurde gekocht", + "{0} wurde verdampft", + "{0} verlor die Zeit aus den Augen" + ], + "Digestion": [ + "{0} wurde gegessen", + "{0} fand einen Fisch", + "{0} traf eine Böse Kreatur", + "{0} ärgerte den falschen Fisch", + "{0} wurde verdaut", + "{0} ist an Verdauung gestorben" + ], + "Crushed": [ + "{0} wurde zerquetscht", + "{0} wurde gequetscht", + "{0} wurde begraben", + "{0} ist nicht rechtzeitig entkommen", + "{0} ging im Sand schwimmen", + "{0} unterschätzte Sand", + "{0} wurde unter Sand gefangen" + ], + "Lava": [ + "{0} starb in Lava", + "{0} wurde geschmolzen", + "{0} versuchte in Lava zu schwimmen", + "{0} fiel in Lava", + "{0} ist an Lava gestorben", + "{0} schwamm in Lava", + "{0} wurde in Lava verbrannt" + ], + "BlackHole": [ + "{0} jagte die Erinnerungen" + ], + "DreamExplosion": [ + "{0} explodierte", + "{0} war ein Betatester", + "{0} ging in die Luft", + "{0} wurde frittiert", + "{0} starb wegen einer Explosion", + "{0} benutzte das falsche Artefakt" + ], + "CrushedByElevator": [ + "{0} wurde zerquetscht", + "{0} wurde gequetscht", + "{0} wurde von einem Aufzug zerquetscht", + "{0} stand unter einem Aufzug", + "{0} wurde ein Flach-Kamina", + "{0} wurde von einem Aufzug gequetscht" + ] + } +} diff --git a/QSB/Translations/fr.json b/QSB/Translations/fr.json index f1fda671f..efa92c3a6 100644 --- a/QSB/Translations/fr.json +++ b/QSB/Translations/fr.json @@ -3,7 +3,7 @@ "MainMenuHost": "OUVRIR AU MULTIJOUEUR", "MainMenuConnect": "SE CONNECTER AU MULTIJOUEUR", "PauseMenuDisconnect": "DÉCONNECTER", - "PauseMenuStopHosting": "ARRÊTEZ L'HÉBERGEMENT", + "PauseMenuStopHosting": "ARRÊTER L'HÉBERGEMENT", "PublicIPAddress": "Adresse IP publique\n\n(CELA EFFACERA VOTRE PROGRESSION MULTIJOUEUR)", "ProductUserID": "ID utilisateur\n\n(CELA EFFACERA VOTRE PROGRESSION MULTIJOUEUR)", "Connect": "SE CONNECTER", @@ -28,7 +28,7 @@ "DLCMismatch": "Les états d'installation du DLC ne correspondent pas. (Client:{0}, Serveur:{1})", "GameProgressLimit": "Le jeu a trop progressé.", "AddonMismatch": "Non-concordance des addons. (Client:{0} addons, Serveur:{1} addons)", - "IncompatibleMod": "Utiliser un mod incompatible/non autorisé. Le premier mod trouvé était {0}", + "IncompatibleMod": "Tu utilises un mod incompatible/non autorisé. Le premier mod trouvé était {0}", "PlayerJoinedTheGame": "{0} a rejoint!", "PlayerWasKicked": "{0} a été expulsé.", "KickedFromServer": "Tu as été expulsé du serveur. Raison : {0}", @@ -39,6 +39,11 @@ "TimeSyncWaitForAllToReady": "Attendez le début de la boucle...", "TimeSyncWaitForAllToDie": "Attendez la fin de la boucle...", "GalaxyMapEveryoneNotPresent": "Ce n’est pas encore le moment. Tout le monde doit être ici pour continuer.", + "YouAreDead": "Tu es mort.", + "WaitingForRespawn": "Attendez que quelqu'un vouz ressuscité...", + "WaitingForAllToDie": "Attendez que {0} joueur(s) meurt/meurent...", + "AttachToShip": "S'attacher à la fusée", + "DetachFromShip": "Se détacher de la fusée", "DeathMessages": { "Default": [ "{0} est mort", diff --git a/QSB/Utility/CustomRelativisticParticleSystem.cs b/QSB/Utility/CustomRelativisticParticleSystem.cs index c93ee048a..65520af13 100644 --- a/QSB/Utility/CustomRelativisticParticleSystem.cs +++ b/QSB/Utility/CustomRelativisticParticleSystem.cs @@ -4,6 +4,7 @@ namespace QSB.Utility; +[UsedInUnityProject] internal class CustomRelativisticParticleSystem : MonoBehaviour { private ParticleSystem _particleSystem; diff --git a/QSB/Utility/DebugActions.cs b/QSB/Utility/DebugActions.cs index 66d1476a4..46f85d5a7 100644 --- a/QSB/Utility/DebugActions.cs +++ b/QSB/Utility/DebugActions.cs @@ -2,6 +2,7 @@ using QSB.ItemSync.WorldObjects.Items; using QSB.Messaging; using QSB.Player; +using QSB.QuantumSync.WorldObjects; using QSB.RespawnSync; using QSB.ShipSync; using QSB.Utility.Messages; @@ -15,7 +16,7 @@ namespace QSB.Utility; public class DebugActions : MonoBehaviour, IAddComponentOnStart { - public static Type WorldObjectSelection; + public static Type WorldObjectSelection = typeof(QSBSocketedQuantumObject); private static void GoToVessel() { @@ -166,7 +167,8 @@ public void Update() { var dreamLanternItem = QSBWorldSync.GetWorldObjects().First(x => x.AttachedObject._lanternType == DreamLanternType.Functioning && - QSBPlayerManager.PlayerList.All(y => y.HeldItem != x) + QSBPlayerManager.PlayerList.All(y => y.HeldItem != x) && + !x.AttachedObject.GetLanternController().IsLit() ).AttachedObject; Locator.GetToolModeSwapper().GetItemCarryTool().PickUpItemInstantly(dreamLanternItem); } @@ -191,9 +193,7 @@ public void Update() sarcoController.secondSealProjector.SetLit(false); sarcoController.thirdSealProjector.SetLit(false); - sarcoController._attemptOpenAfterDelay = true; - sarcoController._openAttemptTime = Time.time + 0.5f; - sarcoController.enabled = true; + sarcoController.OnPressInteract(); } if (Keyboard.current[Key.Numpad4].wasPressedThisFrame) @@ -225,7 +225,7 @@ public void Update() var player = new PlayerInfo(QSBPlayerManager.LocalPlayer.TransformSync); QSBPlayerManager.PlayerList.SafeAdd(player); QSBPlayerManager.OnAddPlayer?.Invoke(player); - DebugLog.DebugWrite($"Create Player : {player}", MessageType.Info); + DebugLog.DebugWrite($"CREATING FAKE PLAYER : {player}", MessageType.Info); JoinLeaveSingularity.Create(player, true); } diff --git a/QSB/Utility/DebugGUI.cs b/QSB/Utility/DebugGUI.cs index 5d237c002..253968d11 100644 --- a/QSB/Utility/DebugGUI.cs +++ b/QSB/Utility/DebugGUI.cs @@ -238,7 +238,7 @@ private static void DrawGui() #endregion - if (QSBWorldSync.AllObjectsReady) + if (QSBWorldSync.AllObjectsReady && QSBCore.DLCInstalled) { var ghost = QSBWorldSync.GetWorldObjects().First(x => x.AttachedObject._name == "Kamaji"); WriteLine(4, ghost.AttachedObject._name); diff --git a/QSB/Utility/DebugLog.cs b/QSB/Utility/DebugLog.cs index 5f01ec8e5..efbbfb5af 100644 --- a/QSB/Utility/DebugLog.cs +++ b/QSB/Utility/DebugLog.cs @@ -14,6 +14,13 @@ public static class DebugLog public static void ToConsole(string message, MessageType type = MessageType.Message) { + if (QSBCore.Helper == null) + { + // yes i know this is only meant for OWML, but it's useful as a backup + ModConsole.OwmlConsole.WriteLine(message, type, GetCallingType()); + return; + } + if (QSBCore.DebugSettings.InstanceIdInLogs) { message = $"[{ProcessInstanceId}] " + message; diff --git a/QSB/Utility/DebugSettings.cs b/QSB/Utility/DebugSettings.cs index e7216fa63..8933a6217 100644 --- a/QSB/Utility/DebugSettings.cs +++ b/QSB/Utility/DebugSettings.cs @@ -23,8 +23,8 @@ public class DebugSettings [JsonProperty("autoStart")] public bool AutoStart; - [JsonProperty("skipTitleScreen")] - public bool SkipTitleScreen; + [JsonProperty("kickEveryone")] + public bool KickEveryone; [JsonProperty("debugMode")] public bool DebugMode; diff --git a/QSB/Utility/Messages/DebugTriggerSupernovaMessage.cs b/QSB/Utility/Messages/DebugTriggerSupernovaMessage.cs index 1dbba5479..614c0d10e 100644 --- a/QSB/Utility/Messages/DebugTriggerSupernovaMessage.cs +++ b/QSB/Utility/Messages/DebugTriggerSupernovaMessage.cs @@ -1,5 +1,4 @@ using QSB.Messaging; -using QSB.Patches; namespace QSB.Utility.Messages; @@ -12,6 +11,6 @@ public override void OnReceiveRemote() PlayerData.SaveLoopCount(2); TimeLoop.SetTimeLoopEnabled(true); TimeLoop._isTimeFlowing = true; - QSBPatch.RemoteCall(() => TimeLoop.SetSecondsRemaining(0)); + TimeLoop.SetSecondsRemaining(0); } } diff --git a/QSB/Utility/UsedInUnityProjectAttribute.cs b/QSB/Utility/UsedInUnityProjectAttribute.cs new file mode 100644 index 000000000..c6f21a2f3 --- /dev/null +++ b/QSB/Utility/UsedInUnityProjectAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace QSB.Utility; + +/// +/// denotes that the given type is used in the unity project +/// and therefore caution should be used when moving/renaming/deleting +/// +public class UsedInUnityProjectAttribute : Attribute { } diff --git a/QSB/Utility/VariableSync/BoolVariableSyncer.cs b/QSB/Utility/VariableSync/BoolVariableSyncer.cs index 73338da55..e021aab52 100644 --- a/QSB/Utility/VariableSync/BoolVariableSyncer.cs +++ b/QSB/Utility/VariableSync/BoolVariableSyncer.cs @@ -1,5 +1,4 @@ namespace QSB.Utility.VariableSync; -public class BoolVariableSyncer : BaseVariableSyncer -{ -} \ No newline at end of file +[UsedInUnityProject] +public class BoolVariableSyncer : BaseVariableSyncer { } diff --git a/QSB/Utility/VariableSync/Vector3VariableSyncer.cs b/QSB/Utility/VariableSync/Vector3VariableSyncer.cs index 884714996..1ca8a5a40 100644 --- a/QSB/Utility/VariableSync/Vector3VariableSyncer.cs +++ b/QSB/Utility/VariableSync/Vector3VariableSyncer.cs @@ -2,6 +2,7 @@ namespace QSB.Utility.VariableSync; +[UsedInUnityProject] public class Vector3VariableSyncer : BaseVariableSyncer { /// diff --git a/QSB/debugsettings.template.json b/QSB/debugsettings.template.json index 4afa74251..83abb2f6e 100644 --- a/QSB/debugsettings.template.json +++ b/QSB/debugsettings.template.json @@ -5,7 +5,7 @@ "hookDebugLogs": false, "avoidTimeSync": false, "autoStart": false, - "skipTitleScreen": false, + "kickEveryone": false, "debugMode": false, "drawGui": false, "drawLines": false, diff --git a/QSB/manifest.json b/QSB/manifest.json index 053552817..e0551f00f 100644 --- a/QSB/manifest.json +++ b/QSB/manifest.json @@ -7,8 +7,8 @@ "body": "- Disable *all* other mods. (Can heavily affect performance)\n- Make sure you are not running any other network-intensive applications." }, "uniqueName": "Raicuparta.QuantumSpaceBuddies", - "version": "0.21.1", - "owmlVersion": "2.5.2", + "version": "0.22.0", + "owmlVersion": "2.7.0", "dependencies": [ "_nebula.MenuFramework", "JohnCorby.VanillaFix" ], "pathsToPreserve": [ "debugsettings.json", "storage.json" ] } diff --git a/README.md b/README.md index 05d88efe1..0c11f2cc6 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,7 @@ Spoilers within! #### Hosting a server -- Enter a game. This can be a new expedition or an existing save file. -- On the pause screen, click the option `OPEN TO MULTIPLAYER`. +- On the title screen, click the option `OPEN TO MULTIPLAYER`. - Share your Product User ID with the people who want to connect. - Enjoy! @@ -139,7 +138,6 @@ The template for this file is this : "hookDebugLogs": false, "avoidTimeSync": false, "autoStart": false, - "skipTitleScreen": false, "debugMode": false, "drawGui": false, "drawLines": false, @@ -156,7 +154,6 @@ The template for this file is this : - hookDebugLogs - Print Unity logs and warnings. - avoidTimeSync - Disables the syncing of time. - autoStart - Host/connect automatically for faster testing. -- skipTitleScreen - Auto-skips the splash screen. - debugMode - Enables debug mode. If this is set to `false`, none of the following settings do anything. - drawGui - Draws a GUI at the top of the screen that gives information on many things. - drawLines - Draws gizmo-esque lines around things. Indicates reference sectors/transforms, triggers, etc. LAGGY. @@ -185,6 +182,7 @@ The template for this file is this : - [Tlya](https://github.com/Tllya) - Russian translation. - [Xen](https://github.com/xen-42) - French translation. - [ShoosGun](https://github.com/ShoosGun) - Portuguese translation. +- [DertolleDude](https://github.com/DertolleDude) - German translation. ### Special Thanks - Thanks to Logan Ver Hoef for help with the game code, and for helping make the damn game in the first place. diff --git a/TRANSLATING.md b/TRANSLATING.md index bd1f8c92a..ce4437c20 100644 --- a/TRANSLATING.md +++ b/TRANSLATING.md @@ -9,10 +9,10 @@ QSB can only be translated to the languages Outer Wilds supports - so if you don - French - Russian - Portuguese (Brazil) +- German ### Un-translated languages : - Spanish (Latin American) -- German - Italian - Polish - Japanese