From cd0c85ecdd35ef00420bc913a1980a4276685139 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Thu, 11 Jul 2024 18:11:24 +0200 Subject: [PATCH 01/15] Add Instant Launch profile to automatize testing --- .../MonoBehaviours/Gui/MainMenu/JoinServer.cs | 9 ++++- .../Gui/MainMenu/MainMenuMultiplayerPanel.cs | 4 +-- NitroxLauncher/LauncherLogic.cs | 4 +-- NitroxLauncher/MainWindow.xaml.cs | 10 ++++++ NitroxLauncher/Properties/launchSettings.json | 11 ++++++ .../Persistent/uGUI_MainMenu_Start_Patch.cs | 35 +++++++++++++++++++ 6 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 NitroxLauncher/Properties/launchSettings.json create mode 100644 NitroxPatcher/Patches/Persistent/uGUI_MainMenu_Start_Patch.cs diff --git a/NitroxClient/MonoBehaviours/Gui/MainMenu/JoinServer.cs b/NitroxClient/MonoBehaviours/Gui/MainMenu/JoinServer.cs index d66b1bef1c..38c9145aa0 100644 --- a/NitroxClient/MonoBehaviours/Gui/MainMenu/JoinServer.cs +++ b/NitroxClient/MonoBehaviours/Gui/MainMenu/JoinServer.cs @@ -38,6 +38,8 @@ public class JoinServer : MonoBehaviour private GameObject joinServerMenu; public string MenuName => joinServerMenu.AliveOrNull()?.name ?? throw new Exception("Menu not yet initialized"); + public bool InstantLaunch; + public void Setup(GameObject saveGameMenu) { JoinServerServerList.InitializeServerList(saveGameMenu, out joinServerMenu, out RectTransform joinServerBackground); @@ -47,7 +49,7 @@ public void Setup(GameObject saveGameMenu) Hide(); } - public async Task ShowAsync(string ip, int port) + public async Task ShowAsync(string ip, int port, bool instantLaunch = false) { NitroxServiceLocator.BeginNewLifetimeScope(); multiplayerSession = NitroxServiceLocator.LocateService(); @@ -58,6 +60,7 @@ public async Task ShowAsync(string ip, int port) gameObject.SetActive(true); serverIp = ip; serverPort = port; + InstantLaunch = instantLaunch; //Set Server IP in info label joinWindow.SetIP(serverIp); @@ -209,6 +212,10 @@ private void SessionConnectionStateChangedHandler(IMultiplayerSessionConnectionS Log.InGame(Language.main.Get("Nitrox_WaitingUserInput")); MainMenuRightSide.main.OpenGroup("Join Server"); FocusPlayerNameTextBox(); + if (InstantLaunch) + { + OnJoinClick(); + } break; case MultiplayerSessionConnectionStage.SESSION_RESERVED: diff --git a/NitroxClient/MonoBehaviours/Gui/MainMenu/MainMenuMultiplayerPanel.cs b/NitroxClient/MonoBehaviours/Gui/MainMenu/MainMenuMultiplayerPanel.cs index 596457fa77..eb16f21a2c 100644 --- a/NitroxClient/MonoBehaviours/Gui/MainMenu/MainMenuMultiplayerPanel.cs +++ b/NitroxClient/MonoBehaviours/Gui/MainMenu/MainMenuMultiplayerPanel.cs @@ -111,7 +111,7 @@ await OpenJoinServerMenuAsync(joinIp, joinPort) } } - public static async System.Threading.Tasks.Task OpenJoinServerMenuAsync(string serverIp, string serverPort) + public static async System.Threading.Tasks.Task OpenJoinServerMenuAsync(string serverIp, string serverPort, bool instantLaunch = false) { if (Main == null) { @@ -126,7 +126,7 @@ public static async System.Threading.Tasks.Task OpenJoinServerMenuAsync(string s return; } - await Main.JoinServer.ShowAsync(endpoint.Address.ToString(), endpoint.Port); + await Main.JoinServer.ShowAsync(endpoint.Address.ToString(), endpoint.Port, instantLaunch); } private void ShowAddServerWindow() diff --git a/NitroxLauncher/LauncherLogic.cs b/NitroxLauncher/LauncherLogic.cs index 076a43be06..b175727fb0 100644 --- a/NitroxLauncher/LauncherLogic.cs +++ b/NitroxLauncher/LauncherLogic.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.IO; using System.Reflection; @@ -248,7 +248,7 @@ internal async Task StartMultiplayerAsync() private async Task StartSubnauticaAsync() { string subnauticaPath = Config.SubnauticaPath; - string subnauticaLaunchArguments = Config.SubnauticaLaunchArguments; + string subnauticaLaunchArguments = $"{Config.SubnauticaLaunchArguments} {string.Join(" ", Environment.GetCommandLineArgs())}"; string subnauticaExe = Path.Combine(subnauticaPath, GameInfo.Subnautica.ExeName); IGamePlatform platform = GamePlatforms.GetPlatformByGameDir(subnauticaPath); diff --git a/NitroxLauncher/MainWindow.xaml.cs b/NitroxLauncher/MainWindow.xaml.cs index e5b68460fe..da97973e57 100644 --- a/NitroxLauncher/MainWindow.xaml.cs +++ b/NitroxLauncher/MainWindow.xaml.cs @@ -111,6 +111,16 @@ public MainWindow() { LauncherNotifier.Warning("You're now using Nitrox DEV build"); } + + string[] launchArgs = Environment.GetCommandLineArgs(); + for (int i = 0; i < launchArgs.Length; i++) + { + if (launchArgs[i].Equals("-instantlaunch", StringComparison.OrdinalIgnoreCase) && launchArgs.Length > i + 1) + { + _ = LauncherLogic.Instance.StartMultiplayerAsync(); + LauncherLogic.Server.StartServer(true, launchArgs[i + 1]); + } + } }; logic.SetTargetedSubnauticaPath(NitroxUser.GamePath) diff --git a/NitroxLauncher/Properties/launchSettings.json b/NitroxLauncher/Properties/launchSettings.json new file mode 100644 index 0000000000..e169bceeec --- /dev/null +++ b/NitroxLauncher/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "NitroxLauncher": { + "commandName": "Project" + }, + "InstantLaunch": { + "commandName": "Project", + "commandLineArgs": "-instantlaunch \"world\"" + } + } +} \ No newline at end of file diff --git a/NitroxPatcher/Patches/Persistent/uGUI_MainMenu_Start_Patch.cs b/NitroxPatcher/Patches/Persistent/uGUI_MainMenu_Start_Patch.cs new file mode 100644 index 0000000000..7c8e888bf5 --- /dev/null +++ b/NitroxPatcher/Patches/Persistent/uGUI_MainMenu_Start_Patch.cs @@ -0,0 +1,35 @@ +using System; +using System.Reflection; +using HarmonyLib; +using NitroxClient.MonoBehaviours.Gui.MainMenu; +using NitroxModel.Helper; + +namespace NitroxPatcher.Patches.Persistent; + +public sealed partial class uGUI_MainMenu_Start_Patch : NitroxPatch, IPersistentPatch +{ + private static readonly MethodInfo TARGET_METHOD_ENUMERATOR = Reflect.Method((uGUI_MainMenu t) => t.Start()); + public static readonly MethodInfo TARGET_METHOD = AccessTools.EnumeratorMoveNext(TARGET_METHOD_ENUMERATOR); + + private static bool Applied; + + public static void Postfix() + { + if (Applied) + { + return; + } + Applied = true; + + string[] args = Environment.GetCommandLineArgs(); + Log.Info(string.Join(" ", args)); + for (int i = 0; i < args.Length; i++) + { + if (args[i].Equals("-instantlaunch", StringComparison.OrdinalIgnoreCase) && args.Length > i + 1) + { + Log.Info($"Detected instant launch, connecting to 127.0.0.1:11000"); + _ = MainMenuMultiplayerPanel.OpenJoinServerMenuAsync("127.0.0.1", "11000", true); + } + } + } +} From 7e4de499913024fe03f86e5b75481e09d19bc1b5 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Thu, 1 Aug 2024 19:07:41 +0200 Subject: [PATCH 02/15] Fix cyclops not restoring motor state on spawn --- .../Metadata/Processor/CyclopsMetadataProcessor.cs | 9 ++++++--- NitroxLauncher/MainWindow.xaml.cs | 2 ++ .../Patches/Persistent/uGUI_MainMenu_Start_Patch.cs | 2 ++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/NitroxClient/GameLogic/Spawning/Metadata/Processor/CyclopsMetadataProcessor.cs b/NitroxClient/GameLogic/Spawning/Metadata/Processor/CyclopsMetadataProcessor.cs index 473f688f95..000db33213 100644 --- a/NitroxClient/GameLogic/Spawning/Metadata/Processor/CyclopsMetadataProcessor.cs +++ b/NitroxClient/GameLogic/Spawning/Metadata/Processor/CyclopsMetadataProcessor.cs @@ -42,15 +42,18 @@ private void SetEngineState(GameObject cyclops, bool isOn) return; } + // During initial sync or when the cyclops HUD isn't shown (from outside of the cyclops) if (Player.main.currentSub != engineState.subRoot) { - engineState.startEngine = !isOn; + engineState.startEngine = isOn; + engineState.subRoot.BroadcastMessage("InvokeChangeEngineState", isOn, SendMessageOptions.RequireReceiver); engineState.invalidButton = true; engineState.Invoke(nameof(CyclopsEngineChangeState.ResetInvalidButton), 2.5f); - engineState.subRoot.BroadcastMessage("InvokeChangeEngineState", !isOn, SendMessageOptions.RequireReceiver); } + // When inside of the cyclops, we play the cinematics else { + // To invoke the whole OnClick method we need to set the right parameters first engineState.invalidButton = false; using (PacketSuppressor.Suppress()) { @@ -64,7 +67,7 @@ private void SetEngineMode(GameObject cyclops, CyclopsMotorMode.CyclopsMotorMode foreach (CyclopsMotorModeButton button in cyclops.GetComponentsInChildren(true)) { // At initial sync, this kind of processor is executed before the Start() - if (button.subRoot == null) + if (!button.subRoot) { button.Start(); } diff --git a/NitroxLauncher/MainWindow.xaml.cs b/NitroxLauncher/MainWindow.xaml.cs index da97973e57..b6fbaab49e 100644 --- a/NitroxLauncher/MainWindow.xaml.cs +++ b/NitroxLauncher/MainWindow.xaml.cs @@ -112,6 +112,7 @@ public MainWindow() LauncherNotifier.Warning("You're now using Nitrox DEV build"); } +# if DEBUG string[] launchArgs = Environment.GetCommandLineArgs(); for (int i = 0; i < launchArgs.Length; i++) { @@ -121,6 +122,7 @@ public MainWindow() LauncherLogic.Server.StartServer(true, launchArgs[i + 1]); } } +#endif }; logic.SetTargetedSubnauticaPath(NitroxUser.GamePath) diff --git a/NitroxPatcher/Patches/Persistent/uGUI_MainMenu_Start_Patch.cs b/NitroxPatcher/Patches/Persistent/uGUI_MainMenu_Start_Patch.cs index 7c8e888bf5..f9aaf54f47 100644 --- a/NitroxPatcher/Patches/Persistent/uGUI_MainMenu_Start_Patch.cs +++ b/NitroxPatcher/Patches/Persistent/uGUI_MainMenu_Start_Patch.cs @@ -1,3 +1,4 @@ +#if DEBUG using System; using System.Reflection; using HarmonyLib; @@ -33,3 +34,4 @@ public static void Postfix() } } } +#endif From d3c714c9bbe45e387b074c79f42fed63e270b925 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Sat, 3 Aug 2024 20:31:19 +0200 Subject: [PATCH 03/15] Implemented a CyclopsMotor responsible for excellently improving player movements in cyclops --- NitroxClient/GameLogic/CyclopsPawn.cs | 99 ++++++ NitroxClient/GameLogic/RemotePlayer.cs | 5 +- NitroxClient/GameLogic/Vehicles.cs | 1 + .../MonoBehaviours/Cyclops/CyclopsMotor.cs | 312 ++++++++++++++++++ .../Cyclops/CyclopsMotorGroundChecker.cs | 116 +++++++ .../Cyclops/MultiplayerCyclops.cs | 56 ++++ .../MonoBehaviours/Cyclops/NitroxCyclops.cs | 224 +++++++++++++ .../MonoBehaviours/Cyclops/VirtualCyclops.cs | 144 ++++++++ NitroxClient/MonoBehaviours/Multiplayer.cs | 2 + .../MonoBehaviours/MultiplayerCyclops.cs | 57 ---- ...psDestructionEvent_DestroyCyclops_Patch.cs | 11 +- ...DestructionEvent_OnConsoleCommand_Patch.cs | 1 + .../CyclopsSonarButton_Update_Patch.cs | 2 +- .../Dynamic/FPSCounter_UpdateDisplay_Patch.cs | 3 + .../FreezeRigidbodyWhenFar_Awake_Patch.cs | 18 + ...oundMotor_OnControllerColliderHit_Patch.cs | 24 ++ .../Openable_PlayOpenAnimation_Patch.cs | 12 +- .../Dynamic/Player_EnterPilotingMode_Patch.cs | 22 ++ .../Dynamic/Player_ExitLockedMode_Patch.cs | 14 + .../Dynamic/Player_ExitPilotingMode_Patch.cs | 22 ++ ...ayer_RequiresHighPrecisionPhysics_Patch.cs | 20 +- .../Patches/Dynamic/Player_Start_Patch.cs | 23 ++ .../Patches/Dynamic/SubControl_Start_Patch.cs | 21 ++ .../SubFire_EngineOverheatSimulation_Patch.cs | 23 ++ .../Dynamic/SubFire_FireSimulation_Patch.cs | 16 + .../Dynamic/SubRoot_OnPlayerEntered_Patch.cs | 14 +- .../Dynamic/SubRoot_OnPlayerExited_Patch.cs | 21 ++ .../VFXConstructing_WakeUpSubmarine_Patch.cs | 24 -- 28 files changed, 1203 insertions(+), 104 deletions(-) create mode 100644 NitroxClient/GameLogic/CyclopsPawn.cs create mode 100644 NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs create mode 100644 NitroxClient/MonoBehaviours/Cyclops/CyclopsMotorGroundChecker.cs create mode 100644 NitroxClient/MonoBehaviours/Cyclops/MultiplayerCyclops.cs create mode 100644 NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs create mode 100644 NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs delete mode 100644 NitroxClient/MonoBehaviours/MultiplayerCyclops.cs create mode 100644 NitroxPatcher/Patches/Dynamic/FreezeRigidbodyWhenFar_Awake_Patch.cs create mode 100644 NitroxPatcher/Patches/Dynamic/GroundMotor_OnControllerColliderHit_Patch.cs create mode 100644 NitroxPatcher/Patches/Dynamic/Player_EnterPilotingMode_Patch.cs create mode 100644 NitroxPatcher/Patches/Dynamic/Player_ExitLockedMode_Patch.cs create mode 100644 NitroxPatcher/Patches/Dynamic/Player_ExitPilotingMode_Patch.cs create mode 100644 NitroxPatcher/Patches/Dynamic/Player_Start_Patch.cs create mode 100644 NitroxPatcher/Patches/Dynamic/SubControl_Start_Patch.cs create mode 100644 NitroxPatcher/Patches/Dynamic/SubFire_EngineOverheatSimulation_Patch.cs create mode 100644 NitroxPatcher/Patches/Dynamic/SubFire_FireSimulation_Patch.cs create mode 100644 NitroxPatcher/Patches/Dynamic/SubRoot_OnPlayerExited_Patch.cs delete mode 100644 NitroxPatcher/Patches/Dynamic/VFXConstructing_WakeUpSubmarine_Patch.cs diff --git a/NitroxClient/GameLogic/CyclopsPawn.cs b/NitroxClient/GameLogic/CyclopsPawn.cs new file mode 100644 index 0000000000..5267b45b21 --- /dev/null +++ b/NitroxClient/GameLogic/CyclopsPawn.cs @@ -0,0 +1,99 @@ +using System; +using NitroxClient.GameLogic.PlayerLogic.PlayerModel.Abstract; +using NitroxClient.MonoBehaviours; +using NitroxClient.MonoBehaviours.Cyclops; +using UnityEngine; + +namespace NitroxClient.GameLogic; + +/// +/// A virtual entity responsible for one player's movement in the cyclops. +/// It simulates the local player's movements by creating a fake player in a 's instance and then giving data about the real movement. +/// +public class CyclopsPawn +{ + private readonly Transform parentTransform; + private readonly Transform realCyclopsTransform; + private readonly bool isLocalPlayer; + public readonly GameObject RealObject; + public GameObject Handle; + public CharacterController Controller; + public Func MaintainPredicate; + + public Vector3 Position + { + get => Handle.transform.position; + set { Handle.transform.position = value; } + } + + public CyclopsPawn(INitroxPlayer player, VirtualCyclops virtualCyclops, Transform realCyclopsTransform) + { + parentTransform = virtualCyclops.transform; + this.realCyclopsTransform = realCyclopsTransform; + + if (player is ILocalNitroxPlayer) + { + isLocalPlayer = true; + RealObject = Player.mainObject; + CyclopsMotor cyclopsMotor = Player.mainObject.GetComponent(); + MaintainPredicate = () => cyclopsMotor.canControl && !Player.main.isPiloting; + } + else if (player is RemotePlayer remotePlayer) + { + RealObject = player.Body; + MaintainPredicate = () => true; + } + + Initialize($"{player.PlayerName}-Pawn", RealObject.transform.localPosition); + } + + public void Initialize(string name, Vector3 localPosition) + { + PlayerController playerController = Player.main.GetComponent(); + GroundMotor groundMotor = Player.main.GetComponent(); + CharacterController reference = Player.main.GetComponent(); + + Handle = GameObject.CreatePrimitive(PrimitiveType.Capsule); + Handle.layer = LayerMask.NameToLayer("Player"); + Handle.name = name; + Handle.transform.parent = parentTransform; + Handle.transform.localPosition = localPosition; + GameObject.DestroyImmediate(Handle.GetComponent()); + + Controller = Handle.AddComponent(); + Controller.height = playerController.standheight - playerController.cameraOffset; + // Calculation from Groundmotor.SetControllerHeight + Vector3 center = groundMotor.colliderCenter; + center.y = -Controller.height * 0.5f - playerController.cameraOffset; + Controller.center = center; + Controller.radius = playerController.controllerRadius; + Controller.skinWidth = reference.skinWidth; + Controller.stepOffset = groundMotor.controllerSetup.stepOffset; + Controller.slopeLimit = groundMotor.controllerSetup.slopeLimit; + Log.Debug($"Pawn: height: {Controller.height}, center {center}, radius: {Controller.radius}, skinWidth: {Controller.skinWidth}"); + } + + public void SetReference() + { + Handle.transform.localPosition = RealObject.transform.localPosition; + if (!isLocalPlayer) + { + Handle.transform.localRotation = RealObject.transform.localRotation; + } + } + + public void MaintainPosition() + { + RealObject.transform.localPosition = Handle.transform.localPosition; + RealObject.transform.rotation = realCyclopsTransform.rotation; + if (!isLocalPlayer) + { + RealObject.transform.localRotation = Handle.transform.localRotation; + } + } + + public void Terminate() + { + GameObject.Destroy(Handle); + } +} diff --git a/NitroxClient/GameLogic/RemotePlayer.cs b/NitroxClient/GameLogic/RemotePlayer.cs index 94e88bd8b5..2c1733c5a2 100644 --- a/NitroxClient/GameLogic/RemotePlayer.cs +++ b/NitroxClient/GameLogic/RemotePlayer.cs @@ -5,6 +5,7 @@ using NitroxClient.GameLogic.PlayerLogic.PlayerModel; using NitroxClient.GameLogic.PlayerLogic.PlayerModel.Abstract; using NitroxClient.MonoBehaviours; +using NitroxClient.MonoBehaviours.Cyclops; using NitroxClient.MonoBehaviours.Gui.HUD; using NitroxClient.Unity.Helper; using NitroxModel.GameLogic.FMOD; @@ -130,8 +131,8 @@ public void UpdatePosition(Vector3 position, Vector3 velocity, Quaternion bodyRo aimingRotation = vehicleAngle * aimingRotation; } - RigidBody.velocity = AnimationController.Velocity = MovementHelper.GetCorrectedVelocity(position, velocity, Body, Time.fixedDeltaTime * (PlayerMovementBroadcaster.LOCATION_BROADCAST_TICK_SKIPS + 1)); - RigidBody.angularVelocity = MovementHelper.GetCorrectedAngularVelocity(bodyRotation, Vector3.zero, Body, Time.fixedDeltaTime * (PlayerMovementBroadcaster.LOCATION_BROADCAST_TICK_SKIPS + 1)); + RigidBody.velocity = AnimationController.Velocity = MovementHelper.GetCorrectedVelocity(position, velocity, Body, Time.fixedDeltaTime); + RigidBody.angularVelocity = MovementHelper.GetCorrectedAngularVelocity(bodyRotation, Vector3.zero, Body, Time.fixedDeltaTime); AnimationController.AimingRotation = aimingRotation; AnimationController.UpdatePlayerAnimations = true; diff --git a/NitroxClient/GameLogic/Vehicles.cs b/NitroxClient/GameLogic/Vehicles.cs index 3132b8b7b3..54593fb69d 100644 --- a/NitroxClient/GameLogic/Vehicles.cs +++ b/NitroxClient/GameLogic/Vehicles.cs @@ -3,6 +3,7 @@ using NitroxClient.Communication.Abstract; using NitroxClient.GameLogic.Helper; using NitroxClient.MonoBehaviours; +using NitroxClient.MonoBehaviours.Cyclops; using NitroxClient.Unity.Helper; using NitroxModel.DataStructures; using NitroxModel.DataStructures.GameLogic; diff --git a/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs b/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs new file mode 100644 index 0000000000..17f036d31d --- /dev/null +++ b/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs @@ -0,0 +1,312 @@ +using NitroxClient.GameLogic; +using NitroxClient.MonoBehaviours.Cyclops; +using UnityEngine; +using UnityEngine.XR; + +namespace NitroxClient.MonoBehaviours; + +/// +/// A replacement for while Local Player is in a Cyclops. +/// +public partial class CyclopsMotor : GroundMotor +{ + public GroundMotor ActualMotor { get; private set; } + public CyclopsPawn Pawn { get; private set; } + + private Transform body; + private NitroxCyclops cyclops; + private SubRoot sub; + private Transform realAxis; + private Transform virtualAxis; + private WorldForces worldForces; + + public Vector3 Up => virtualAxis.up; + public float DeltaTime => Time.fixedDeltaTime; + + private Vector3 verticalVelocity; + + public new void Awake() + { + controller = GetComponent(); + controller.enabled = false; + controller.stepOffset = controllerSetup.stepOffset; + controller.slopeLimit = controllerSetup.slopeLimit; + + rb = GetComponent(); + playerController = GetComponent(); + worldForces = GetComponent(); + + body = Player.mainObject.transform.Find("body"); + } + + public override void SetEnabled(bool enabled) + { + base.SetEnabled(enabled); + Setup(enabled); + } + + public void Initialize(GroundMotor reference) + { + ActualMotor = reference; + movement = reference.movement; + jumping = reference.jumping; + movingPlatform = reference.movingPlatform; + sliding = reference.sliding; + controllerSetup = reference.controllerSetup; + floatingModeSetup = reference.floatingModeSetup; + allowMidAirJumping = reference.allowMidAirJumping; + minWindSpeedToAffectMovement = reference.minWindSpeedToAffectMovement; + percentWindDampeningOnGround = reference.percentWindDampeningOnGround; + percentWindDampeningInAir = reference.percentWindDampeningInAir; + floatingModeEnabled = reference.floatingModeEnabled; + forwardMaxSpeed = reference.forwardMaxSpeed; + backwardMaxSpeed = reference.backwardMaxSpeed; + strafeMaxSpeed = reference.strafeMaxSpeed; + verticalMaxSpeed = reference.verticalMaxSpeed; + climbSpeed = reference.climbSpeed; + gravity = reference.gravity; + forwardSprintModifier = reference.forwardSprintModifier; + strafeSprintModifier = reference.strafeSprintModifier; + groundAcceleration = reference.groundAcceleration; + airAcceleration = reference.airAcceleration; + jumpHeight = reference.jumpHeight; + SetEnabled(false); + RecalculateConstants(); + } + + public void SetCyclops(SubRoot subRoot, CyclopsPawn pawn) + { + cyclops = subRoot.GetComponent(); + sub = subRoot; + realAxis = sub.subAxis; + virtualAxis = cyclops.Virtual.axis; + Pawn = pawn; + } + + public void Setup(bool enabled) + { + verticalVelocity = Vector3.zero; + + if (enabled) + { + rb.isKinematic = false; + Player.wantInterpolate = false; + rb.detectCollisions = false; + worldForces.lockInterpolation = true; + worldForces.enabled = false; + controller.detectCollisions = false; + Player.mainCollider.isTrigger = true; + UWE.Utils.EnterPhysicsSyncSection(); + } + else + { + rb.isKinematic = true; + Player.wantInterpolate = true; + worldForces.lockInterpolation = false; + rb.detectCollisions = true; + worldForces.enabled = true; + controller.detectCollisions = true; + Player.mainCollider.isTrigger = false; + UWE.Utils.ExitPhysicsSyncSection(); + } + + Pawn?.SetReference(); + } + + public override Vector3 UpdateMove() + { + if (!canControl) + { + return Vector3.zero; + } + + // Compute movements velocities based on inputs and previous movement + Position = Pawn.Position; + Center = cyclops.Virtual.transform.TransformVector(Pawn.Controller.center); + Pawn.Handle.transform.localRotation = body.localRotation; + + sprinting = false; + verticalVelocity += CalculateVerticalVelocity(); + Vector3 horizontalVelocity = CalculateInputVelocity(); + + text2 = $"movementInputDirection: {movementInputDirection}\nVerticalVel: {verticalVelocity}\nHorizontalVelocity: {horizontalVelocity}\nGrounded/Controller: {grounded}/{controller.isGrounded}\nCollision: {Collision}"; + // Apply the physics computations + return Move(horizontalVelocity); + } + + public static string text2; + + /// + /// Simulates player movement on its pawn and update the grounded state + /// + /// Pawn's local velocity + public Vector3 Move(Vector3 horizontalVelocity) + { + // TODO: Replicate modules' colliders in the virtual cyclops + // TODO: Try to improve collisions with closing doors (maybe set pawns to be kinematic) + Vector3 beforePosition = Pawn.Position; + + Vector3 move = (horizontalVelocity + verticalVelocity) * DeltaTime; + + Collision = Pawn.Controller.Move(move); + + float verticalDot = Vector3.Dot(verticalVelocity, Up); + + bool previouslyGrounded = grounded; + CheckGrounded(Collision, verticalDot <= 0f); + + if (grounded) + { + verticalVelocity = Vector3.zero; + + if (!previouslyGrounded) + { + jumping.jumping = false; + // Prefilled data is made to not hurt the player at any time when colliding with cyclops, but only to + SendMessage(nameof(Player.OnLand), new MovementCollisionData + { + impactVelocity = Vector3.one, + surfaceType = VFXSurfaceTypes.metal + }); + } + } + + // Give velocity info for the animations + return (Pawn.Position - beforePosition) / DeltaTime; + } + + /// + /// Calculates vertical velocity variation based on the grounded state. + /// Code adapted from . + /// + public Vector3 CalculateVerticalVelocity() + { + if (!jumpPressed || !canControl) + { + jumping.holdingJumpButton = false; + jumping.lastButtonDownTime = -100f; + } + if (jumpPressed && (jumping.lastButtonDownTime < 0f || flyCheatEnabled) && canControl) + { + jumping.lastButtonDownTime = Time.time; + } + + Vector3 verticalMove = Vector3.zero; + + if (!grounded) + { + verticalMove = -gravity * Up * DeltaTime; + } + if (grounded || allowMidAirJumping || flyCheatEnabled) + { + if (canControl && Time.time - jumping.lastButtonDownTime < 0.2) + { + grounded = false; + jumping.jumping = true; + jumping.lastStartTime = Time.time; + jumping.lastButtonDownTime = -100f; + jumping.holdingJumpButton = true; + SendMessage("OnJump", SendMessageOptions.DontRequireReceiver); + verticalMove = Up * CalculateJumpVerticalSpeed(jumping.baseHeight); + } + else + { + jumping.holdingJumpButton = false; + } + } + return verticalMove; + } + + /// + /// Calculates instantaneous horizontal velocity from input for the object. + /// Code adapted from . + /// + public Vector3 CalculateInputVelocity() + { + // Project the movement input to the right rotation + float moveMinMagnitude = Mathf.Min(1f, movementInputDirection.magnitude); + + // We rotate the input in the right basis + Vector3 input = movementInputDirection._X0Z(); + + Transform forwardRef = Pawn.Handle.transform; + + Vector3 projectedForward = Vector3.ProjectOnPlane(forwardRef.forward, Up).normalized; + Vector3 projectedRight = Vector3.ProjectOnPlane(forwardRef.right, Up).normalized; + + Vector3 moveDir = (projectedForward * input.z + projectedRight * input.x).normalized; + + Vector3 velocity; + // Manage sliding on slopes + if (grounded && TooSteep()) + { + velocity = GetSlidingDirection(); + Vector3 moveProjectedOnSlope = Vector3.Project(moveDir, velocity); + velocity += moveProjectedOnSlope * sliding.speedControl + (moveDir - moveProjectedOnSlope) * sliding.sidewaysControl; + velocity *= sliding.slidingSpeed; + } + else + { + // Apply speed modifiers + float modifier = 1f; + if (sprintPressed && grounded) + { + float z = movementInputDirection.z; + if (z > 0f) + { + modifier *= forwardSprintModifier; + } + else if (z == 0f) + { + modifier *= strafeSprintModifier; + } + sprinting = true; + } + velocity = moveDir * forwardMaxSpeed * modifier * moveMinMagnitude; + } + if (XRSettings.enabled) + { + velocity *= VROptions.groundMoveScale; + } + + if (grounded) + { + return AdjustGroundVelocityToNormal(velocity, groundNormal); + } + + velocity.y = 0; + return velocity; + } + + public static string text; + + private new Vector3 GetSlidingDirection() + { + return Vector3.ProjectOnPlane(groundNormal, Up).normalized; + } + + private new bool TooSteep() + { + float dotUp = Vector3.Dot(groundNormal, Up); + return dotUp <= Mathf.Cos(controller.slopeLimit * Mathf.Deg2Rad); + } + + public void ToggleCyclopsMotor(bool toggled) + { + GroundMotor groundMotor = toggled ? this : ActualMotor; + Player.main.playerController.SetEnabled(false); + Player.main.groundMotor = groundMotor; + + Player.main.footStepSounds.groundMoveable = groundMotor; + Player.main.groundMotor = groundMotor; + Player.main.playerController.groundController = groundMotor; + if (Player.main.playerController.activeController is GroundMotor) + { + Player.main.playerController.activeController = groundMotor; + } + // SetMotorMode sets some important variables in the motor abstract class PlayerMotor + Player.main.playerController.SetMotorMode(Player.MotorMode.Walk); + + Player.main.playerController.SetEnabled(true); + } +} diff --git a/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotorGroundChecker.cs b/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotorGroundChecker.cs new file mode 100644 index 0000000000..5352747b4e --- /dev/null +++ b/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotorGroundChecker.cs @@ -0,0 +1,116 @@ +using UnityEngine; + +namespace NitroxClient.MonoBehaviours; + +/// +/// Ground detection adapted from +/// +public partial class CyclopsMotor +{ + private const float CAST_DISTANCE = 0.001f; + private const float CAST_EXTRA_DISTANCE = 0.001f; + + public const QueryTriggerInteraction QuerySetting = QueryTriggerInteraction.Ignore; + public static readonly int LayerMaskExceptPlayer = ~(1 << LayerMask.NameToLayer("Player")); + + /// + /// Latest snapshot of the Pawn's global position. It is updated every frame before being used. + /// + public Vector3 Position; + /// + /// Latest snapshot of the globally transformed center offset. It is updated every frame before being used. + /// + public Vector3 Center; + /// + /// scaled by the transform's y global scale + /// + public float Height; + /// + /// scaled by the transform's maximum global scale parameter + /// + public float Radius; + /// + /// Unscaled + /// + public float SkinWidth; + /// + /// Snapshot of the latest obtained when simulating movement on the pawn. + /// + private CollisionFlags Collision { get; set; } + + /// + /// Checks if Pawn is grounded by up to 2 sphere casts. Updates the registered ground normal accordingly. + /// + public void CheckGrounded(CollisionFlags flags, bool cast) + { + if (cast) + { + Vector3 lowerPoint = GetLowerPoint(); + + grounded = false; + if (SphereCast(-Up, SkinWidth + CAST_DISTANCE, out RaycastHit hitInfo, lowerPoint, false)) + { + grounded = true; + hitInfo.distance = Mathf.Max(0f, hitInfo.distance - SkinWidth); + } + + if (!grounded && SphereCast(-Up, CAST_DISTANCE + CAST_EXTRA_DISTANCE, out hitInfo, lowerPoint + Up * CAST_EXTRA_DISTANCE, true)) + { + grounded = true; + hitInfo.distance = Mathf.Max(0f, hitInfo.distance - SkinWidth); + } + + groundNormal = hitInfo.normal; + return; + } + + // Exceptional case in which movement was made on the ground but the casts failed + if (flags == CollisionFlags.Below) + { + grounded = true; + groundNormal = Up; + return; + } + + grounded = false; + groundNormal = Vector3.zero; + } + + public bool SphereCast(Vector3 direction, float distance, out RaycastHit hitInfo, Vector3 spherePosition, bool big) + { + float radius = big ? Radius + SkinWidth : Radius; + + if (Physics.SphereCast(spherePosition, radius, direction, out hitInfo, distance + radius, LayerMaskExceptPlayer, QuerySetting)) + { + return hitInfo.distance <= distance; + } + + return false; + } + + public Vector3 GetLowerPoint() + { + return Position + Center - Up * (Height * 0.5f - Radius); + } + + public override void SetControllerHeight(float height, float cameraOffset) + { + base.SetControllerHeight(height, cameraOffset); + RecalculateConstants(); + } + + public override void SetControllerRadius(float radius) + { + base.SetControllerRadius(radius); + RecalculateConstants(); + } + + private void RecalculateConstants() + { + Vector3 scale = transform.lossyScale; + Height = controller.height * scale.y; + Radius = controller.radius * Mathf.Max(Mathf.Max(scale.x, scale.y), scale.z); + SkinWidth = controller.skinWidth; + // SkinWidth: 0.08, Rad: 0.3, Height: 1.75 + } +} diff --git a/NitroxClient/MonoBehaviours/Cyclops/MultiplayerCyclops.cs b/NitroxClient/MonoBehaviours/Cyclops/MultiplayerCyclops.cs new file mode 100644 index 0000000000..1379e906e0 --- /dev/null +++ b/NitroxClient/MonoBehaviours/Cyclops/MultiplayerCyclops.cs @@ -0,0 +1,56 @@ +using NitroxClient.GameLogic; + +namespace NitroxClient.MonoBehaviours.Cyclops; + +public class MultiplayerCyclops : MultiplayerVehicleControl +{ + private ISubTurnHandler[] subTurnHandlers; + private ISubThrottleHandler[] subThrottleHandlers; + private float previousAbsYaw; + + public RemotePlayer CurrentPlayer { get; set; } + + protected override void Awake() + { + SubControl subControl = GetComponent(); + WheelYawSetter = value => subControl.steeringWheelYaw = value; + WheelPitchSetter = value => subControl.steeringWheelPitch = value; + + subTurnHandlers = subControl.turnHandlers; + subThrottleHandlers = subControl.throttleHandlers; + base.Awake(); + } + + protected override void FixedUpdate() + { + /*base.FixedUpdate(); + if (CurrentPlayer != null) + { + // These values are set by the game code, but they do not seem to have any impact on animations. + CurrentPlayer.AnimationController.SetFloat("cyclops_yaw", SmoothYaw.SmoothValue); + CurrentPlayer.AnimationController.SetFloat("cyclops_pitch", SmoothPitch.SmoothValue); + }*/ + } + + internal override void SetSteeringWheel(float yaw, float pitch) + { + base.SetSteeringWheel(yaw, pitch); + + ShipSide useShipSide = yaw > 0 ? ShipSide.Port : ShipSide.Starboard; + yaw = UnityEngine.Mathf.Abs(yaw); + if (yaw > .1f && yaw >= previousAbsYaw) + { + subTurnHandlers?.ForEach(turnHandler => turnHandler.OnSubTurn(useShipSide)); + } + + previousAbsYaw = yaw; + } + + internal override void SetThrottle(bool isOn) + { + if (isOn) + { + subThrottleHandlers?.ForEach(throttleHandlers => throttleHandlers.OnSubAppliedThrottle()); + } + } +} diff --git a/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs b/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs new file mode 100644 index 0000000000..dad0b6b2d8 --- /dev/null +++ b/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs @@ -0,0 +1,224 @@ +using NitroxClient.GameLogic; +using NitroxClient.GameLogic.PlayerLogic; +using NitroxClient.GameLogic.PlayerLogic.PlayerModel.Abstract; +using UnityEngine; + +namespace NitroxClient.MonoBehaviours.Cyclops; + +/// +/// Script responsible for managing all player movement-related interactions. +/// +public class NitroxCyclops : MonoBehaviour +{ + public VirtualCyclops Virtual { get; private set; } + private CyclopsMotor cyclopsMotor; + private SubRoot subRoot; + private SubControl subControl; + private Rigidbody rigidbody; + private WorldForces worldForces; + private Stabilizer stabilizer; + private CharacterController controller; + private int ballasts; + + public SubControl.Mode Mode; + + public void Start() + { + cyclopsMotor = Player.mainObject.GetComponent(); + subRoot = GetComponent(); + subControl = GetComponent(); + rigidbody = GetComponent(); + worldForces = GetComponent(); + stabilizer = GetComponent(); + controller = cyclopsMotor.controller; + ballasts = GetComponentsInChildren(true).Length; + + UWE.Utils.SetIsKinematicAndUpdateInterpolation(rigidbody, false, true); + + GetComponent().enabled = false; + SetReceiving(); + + Virtual = VirtualCyclops.CreateVirtualInstance(gameObject); + } + + /// + /// Triggered by sending a "OnKill" message when cyclops is destroyed. + /// This might need to be adapted once the "restore" command is synced (for now a destroyed cyclops can't be restored) + /// + public void OnKill() + { + VirtualCyclops.Terminate(gameObject); + } + + /// + /// Triggers required on-remove callbacks on children player objects, including the local player. + /// + public void RemoveAllPlayers() + { + foreach (RemotePlayerIdentifier remotePlayerIdentifier in GetComponentsInChildren(true)) + { + remotePlayerIdentifier.RemotePlayer.ResetStates(); + OnPlayerExit(remotePlayerIdentifier.RemotePlayer); + } + OnLocalPlayerExit(); + } + + /// + /// Parents local player to the cyclops and registers it in the virtual cyclops. + /// + public void OnLocalPlayerEnter() + { + Player.mainObject.transform.parent = subRoot.transform; + CyclopsPawn pawn = Virtual.AddPawnForPlayer(this.Resolve()); + cyclopsMotor.SetCyclops(subRoot, pawn); + cyclopsMotor.ToggleCyclopsMotor(true); + } + + /// + /// Unregisters the local player from the cyclops (and from the virtual one). Ensures the player is not weirdly rotated when it leaves the cyclops. + /// + public void OnLocalPlayerExit() + { + Virtual.RemovePawnForPlayer(this.Resolve()); + Player.main.transform.parent = null; + Player.main.transform.rotation = Quaternion.identity; + cyclopsMotor.ToggleCyclopsMotor(false); + } + + /// + /// Registers a remote player in the virtual cyclops. + /// + public void OnPlayerEnter(RemotePlayer remotePlayer) + { + Virtual.AddPawnForPlayer(remotePlayer); + } + + /// + /// Unregisters a remote player from the virtual cyclops. + /// + public void OnPlayerExit(RemotePlayer remotePlayer) + { + Virtual.RemovePawnForPlayer(remotePlayer); + } + + public void MaintainPawns() + { + foreach (CyclopsPawn pawn in Virtual.Pawns.Values) + { + if (pawn.MaintainPredicate()) + { + pawn.MaintainPosition(); + } + } + } + + public void SetBroadcasting() + { + worldForces.OnDisable(); + worldForces.enabled = true; + stabilizer.stabilizerEnabled = true; + } + + public void SetReceiving() + { + worldForces.enabled = false; + stabilizer.stabilizerEnabled = false; + } + + // TODO: all of the below stuff is purely for testing and will probably get removed before merge + // EXCEPT for the MaintainPawns line in Update() + private Vector3 forward => subRoot.subAxis.forward; + private Vector3 up => subRoot.subAxis.up; + private Vector3 right => subRoot.subAxis.right; + + public bool Autopilot; + public bool Sinus; + public bool Rolling; + public bool Torqing; + public bool RenderersToggled; + + public float Forward; + public float Up; + public float VerticalPeriod = 1f; + public float Torque; + public float Roll; + + public void ResetAll() + { + Torqing = false; + Rolling = false; + rigidbody.velocity = Vector3.zero; + rigidbody.angularVelocity = Vector3.zero; + transform.position = new(70f, -16f, 0f); + transform.rotation = Quaternion.Euler(new(360f, 270f, 0f)); + + Player.main.SetCurrentSub(subRoot, true); + Player.main.SetPosition(transform.position + up); + cyclopsMotor.ToggleCyclopsMotor(true); + cyclopsMotor.Pawn.SetReference(); + + Log.InGame("Reset player"); + } + + public void Update() + { + if (!DevConsole.instance.state) + { + if (Input.GetKeyUp(KeyCode.R)) + { + ResetAll(); + } + if (Input.GetKeyUp(KeyCode.N)) + { + SetReceiving(); + Rolling = !Rolling; + Autopilot = true; + Roll = Rolling.ToFloat(); + Log.InGame($"Rolling: {Rolling}"); + } + if (Input.GetKeyUp(KeyCode.B)) + { + SetReceiving(); + Torqing = !Torqing; + Autopilot = true; + Torque = Torqing.ToFloat(); + Log.InGame($"Torqing: {Torqing}"); + } + } + + MaintainPawns(); + } + + public void FixedUpdate() + { + MoveAutopilot(); + } + + public void MoveAutopilot() + { + if (!Autopilot) + { + return; + } + + // https://docs.unity3d.com/ScriptReference/Rigidbody.AddTorque.html + Vector3 cyclopsTorqueFactor = up * subControl.BaseTurningTorque * subControl.turnScale; + rigidbody.angularVelocity += cyclopsTorqueFactor * Torque * Time.fixedDeltaTime; + + Vector3 cyclopsRollFactor = right * subControl.BaseTurningTorque * subControl.turnScale; + rigidbody.angularVelocity += cyclopsRollFactor * Roll * Time.fixedDeltaTime; + + // https://docs.unity3d.com/ScriptReference/Rigidbody.AddForce.html + Vector3 cyclopsVerticalFactor = up * (subControl.BaseVerticalAccel + ballasts * subControl.AccelPerBallast) * subControl.accelScale; + if (Sinus) + { + cyclopsVerticalFactor *= Mathf.Sin(2 * Mathf.PI * Time.fixedTime / VerticalPeriod); + } + rigidbody.velocity += cyclopsVerticalFactor * Up * Time.fixedDeltaTime; + + Vector3 cyclopsForwardFactor = forward * subControl.BaseForwardAccel * subControl.accelScale; + rigidbody.velocity += cyclopsForwardFactor * Forward * Time.fixedDeltaTime; + + subControl.appliedThrottle = true; + } +} diff --git a/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs b/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs new file mode 100644 index 0000000000..957a65403f --- /dev/null +++ b/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs @@ -0,0 +1,144 @@ +using System.Collections.Generic; +using NitroxClient.Communication; +using NitroxClient.GameLogic; +using NitroxClient.GameLogic.PlayerLogic.PlayerModel.Abstract; +using NitroxModel.Packets; +using UnityEngine; + +namespace NitroxClient.MonoBehaviours.Cyclops; + +/// +/// Script responsible for creating a virtual counterpart of every cyclops, which will always be horizontal and motionless so that simulated movement is always clear. +/// Contains a pawn for each player entering the regular cyclops. +/// +public class VirtualCyclops : MonoBehaviour +{ + private static GameObject Prefab; + private static float Offset; + public const string NAME = "VirtualCyclops"; + + public static readonly Dictionary VirtualCyclopsByObject = []; + + private readonly Dictionary openableByName = []; + public readonly Dictionary Pawns = []; + public NitroxCyclops Cyclops; + public Transform axis; + + public static void Initialize() + { + CreateVirtualPrefab(); + } + + /// + /// Initializes the object with reduced utility to ensure the virtual cyclops won't be eating too much performance. + /// + public static void CreateVirtualPrefab() + { + if (Prefab) + { + return; + } + + LightmappedPrefabs.main.RequestScenePrefab("cyclops", (cyclopsPrefab) => + { + SubConsoleCommand.main.OnSubPrefabLoaded(cyclopsPrefab); + Prefab = SubConsoleCommand.main.GetLastCreatedSub(); + Prefab.name = NAME; + Prefab.GetComponent().enabled = false; + Prefab.transform.parent = null; + + GameObject.Destroy(Prefab.GetComponent()); + GameObject.Destroy(Prefab.GetComponent()); + Prefab.AddComponent(); + Prefab.SetActive(false); + }); + } + + /// + /// Instantiates the object associated with a regular cyclops and links references where required. + /// + public static VirtualCyclops CreateVirtualInstance(GameObject cyclopsObject) + { + if (!VirtualCyclopsByObject.TryGetValue(cyclopsObject, out VirtualCyclops virtualCyclops)) + { + Vector3 position = Vector3.up * 500 + Vector3.right * (Offset - 100); + Offset += 10; + + GameObject instance = GameObject.Instantiate(Prefab, position, Quaternion.identity, false); + instance.name = NAME; + LargeWorldEntity.Register(instance); + virtualCyclops = instance.GetComponent(); + virtualCyclops.Cyclops = cyclopsObject.GetComponent(); + virtualCyclops.axis = instance.GetComponent().subAxis; + virtualCyclops.RegisterOpenables(); + + instance.GetComponent().enabled = false; + instance.GetComponent().lockInterpolation = false; + instance.GetComponent().stabilizerEnabled = false; + instance.GetComponent().isKinematic = true; + + instance.SetActive(true); + virtualCyclops.ToggleRenderers(false); + VirtualCyclopsByObject.Add(cyclopsObject, virtualCyclops); + } + return virtualCyclops; + } + + /// + /// Destroys the virtual cyclops instance associated with a regular cyclops object. + /// + public static void Terminate(GameObject cyclopsObject) + { + if (VirtualCyclopsByObject.TryGetValue(cyclopsObject, out VirtualCyclops associatedVirtualCyclops)) + { + Destroy(associatedVirtualCyclops.gameObject); + VirtualCyclopsByObject.Remove(cyclopsObject); + } + } + + public void ToggleRenderers(bool toggled) + { + foreach (Renderer renderer in transform.GetComponentsInChildren(true)) + { + renderer.enabled = toggled; + } + } + + public CyclopsPawn AddPawnForPlayer(INitroxPlayer player) + { + if (!Pawns.TryGetValue(player, out CyclopsPawn pawn)) + { + pawn = new(player, this, Cyclops.transform); + Pawns.Add(player, pawn); + } + return pawn; + } + + public void RemovePawnForPlayer(INitroxPlayer player) + { + if (Pawns.TryGetValue(player, out CyclopsPawn pawn)) + { + pawn.Terminate(); + } + Pawns.Remove(player); + } + + public void RegisterOpenables() + { + foreach (Openable openable in transform.GetComponentsInChildren(true)) + { + openableByName.Add(openable.name, openable); + } + } + + public void ReplicateOpening(Openable openable, bool openState) + { + if (openableByName.TryGetValue(openable.name, out Openable virtualOpenable)) + { + using (PacketSuppressor.Suppress()) + { + virtualOpenable.PlayOpenAnimation(openState, virtualOpenable.animTime); + } + } + } +} diff --git a/NitroxClient/MonoBehaviours/Multiplayer.cs b/NitroxClient/MonoBehaviours/Multiplayer.cs index c39389ac48..890f1d2b75 100644 --- a/NitroxClient/MonoBehaviours/Multiplayer.cs +++ b/NitroxClient/MonoBehaviours/Multiplayer.cs @@ -10,6 +10,7 @@ using NitroxClient.GameLogic.ChatUI; using NitroxClient.GameLogic.PlayerLogic.PlayerModel.Abstract; using NitroxClient.GameLogic.PlayerLogic.PlayerModel.ColorSwap; +using NitroxClient.MonoBehaviours.Cyclops; using NitroxClient.MonoBehaviours.Discord; using NitroxClient.MonoBehaviours.Gui.MainMenu; using NitroxModel.Core; @@ -167,6 +168,7 @@ public void InitMonoBehaviours() gameObject.AddComponent(); gameObject.AddComponent(); gameObject.AddComponent(); + VirtualCyclops.Initialize(); } public void StopCurrentSession() diff --git a/NitroxClient/MonoBehaviours/MultiplayerCyclops.cs b/NitroxClient/MonoBehaviours/MultiplayerCyclops.cs deleted file mode 100644 index a7158b37c8..0000000000 --- a/NitroxClient/MonoBehaviours/MultiplayerCyclops.cs +++ /dev/null @@ -1,57 +0,0 @@ -using NitroxClient.GameLogic; - -namespace NitroxClient.MonoBehaviours -{ - public class MultiplayerCyclops : MultiplayerVehicleControl - { - private ISubTurnHandler[] subTurnHandlers; - private ISubThrottleHandler[] subThrottleHandlers; - private float previousAbsYaw; - - public RemotePlayer CurrentPlayer { get; set; } - - protected override void Awake() - { - SubControl subControl = GetComponent(); - WheelYawSetter = value => subControl.steeringWheelYaw = value; - WheelPitchSetter = value => subControl.steeringWheelPitch = value; - - subTurnHandlers = subControl.turnHandlers; - subThrottleHandlers = subControl.throttleHandlers; - base.Awake(); - } - - protected override void FixedUpdate() - { - base.FixedUpdate(); - if (CurrentPlayer != null) - { - // These values are set by the game code, but they do not seem to have any impact on animations. - CurrentPlayer.AnimationController.SetFloat("cyclops_yaw", SmoothYaw.SmoothValue); - CurrentPlayer.AnimationController.SetFloat("cyclops_pitch", SmoothPitch.SmoothValue); - } - } - - internal override void SetSteeringWheel(float yaw, float pitch) - { - base.SetSteeringWheel(yaw, pitch); - - ShipSide useShipSide = yaw > 0 ? ShipSide.Port : ShipSide.Starboard; - yaw = UnityEngine.Mathf.Abs(yaw); - if (yaw > .1f && yaw >= previousAbsYaw) - { - subTurnHandlers?.ForEach(turnHandler => turnHandler.OnSubTurn(useShipSide)); - } - - previousAbsYaw = yaw; - } - - internal override void SetThrottle(bool isOn) - { - if (isOn) - { - subThrottleHandlers?.ForEach(throttleHandlers => throttleHandlers.OnSubAppliedThrottle()); - } - } - } -} diff --git a/NitroxPatcher/Patches/Dynamic/CyclopsDestructionEvent_DestroyCyclops_Patch.cs b/NitroxPatcher/Patches/Dynamic/CyclopsDestructionEvent_DestroyCyclops_Patch.cs index 2499d66bb5..724d61d28c 100644 --- a/NitroxPatcher/Patches/Dynamic/CyclopsDestructionEvent_DestroyCyclops_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/CyclopsDestructionEvent_DestroyCyclops_Patch.cs @@ -1,11 +1,14 @@ -using System.Reflection; +using System.Reflection; using NitroxClient.GameLogic; -using NitroxClient.GameLogic.PlayerLogic; +using NitroxClient.MonoBehaviours.Cyclops; using NitroxModel.DataStructures; using NitroxModel.Helper; namespace NitroxPatcher.Patches.Dynamic; +/// +/// Broadcasts the cyclops destruction by calling , and safely remove every player from it. +/// public sealed partial class CyclopsDestructionEvent_DestroyCyclops_Patch : NitroxPatch, IDynamicPatch { private static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsDestructionEvent t) => t.DestroyCyclops()); @@ -19,9 +22,9 @@ public static void Prefix(CyclopsDestructionEvent __instance) } // Before the cyclops destruction, we move out the remote players so that they aren't stuck in its hierarchy - foreach (RemotePlayerIdentifier remotePlayerIdentifier in __instance.GetComponentsInChildren(true)) + if (Player.main && Player.main.currentSub == __instance.subRoot && __instance.subRoot.TryGetComponent(out NitroxCyclops nitroxCyclops)) { - remotePlayerIdentifier.RemotePlayer.ResetStates(); + nitroxCyclops.RemoveAllPlayers(); } } } diff --git a/NitroxPatcher/Patches/Dynamic/CyclopsDestructionEvent_OnConsoleCommand_Patch.cs b/NitroxPatcher/Patches/Dynamic/CyclopsDestructionEvent_OnConsoleCommand_Patch.cs index 4704e909ba..a37d36372a 100644 --- a/NitroxPatcher/Patches/Dynamic/CyclopsDestructionEvent_OnConsoleCommand_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/CyclopsDestructionEvent_OnConsoleCommand_Patch.cs @@ -14,6 +14,7 @@ public sealed class CyclopsDestructionEvent_OnConsoleCommand_Patch : NitroxPatch public static bool PrefixRestore() { + // TODO: If this is implemented someday, adapt behaviour of VirtualCyclops to not be deleted when a cyclops is destroyed Log.InGame(Language.main.Get("Nitrox_CommandNotAvailable")); return false; } diff --git a/NitroxPatcher/Patches/Dynamic/CyclopsSonarButton_Update_Patch.cs b/NitroxPatcher/Patches/Dynamic/CyclopsSonarButton_Update_Patch.cs index 24bf75fcd9..3f0ed47208 100644 --- a/NitroxPatcher/Patches/Dynamic/CyclopsSonarButton_Update_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/CyclopsSonarButton_Update_Patch.cs @@ -3,7 +3,7 @@ using System.Reflection; using System.Reflection.Emit; using HarmonyLib; -using NitroxClient.MonoBehaviours; +using NitroxClient.MonoBehaviours.Cyclops; using NitroxModel.Helper; namespace NitroxPatcher.Patches.Dynamic; diff --git a/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs b/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs index 33b51f6e15..c0ca9fae4d 100644 --- a/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs @@ -18,6 +18,9 @@ public static void Postfix(FPSCounter __instance) } __instance.strBuffer.Append("Loading entities: ").AppendLine(Resolve().EntitiesToSpawn.Count.ToString()); __instance.strBuffer.Append("Real time elapsed: ").AppendLine(Resolve().RealTimeElapsed.ToString()); + // TODO: Get rid of the two lines below + __instance.strBuffer.AppendLine(CyclopsMotor.text); + __instance.strBuffer.AppendLine(CyclopsMotor.text2); __instance.text.SetText(__instance.strBuffer); } } diff --git a/NitroxPatcher/Patches/Dynamic/FreezeRigidbodyWhenFar_Awake_Patch.cs b/NitroxPatcher/Patches/Dynamic/FreezeRigidbodyWhenFar_Awake_Patch.cs new file mode 100644 index 0000000000..8e3b0003c2 --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/FreezeRigidbodyWhenFar_Awake_Patch.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using NitroxModel.Helper; + +namespace NitroxPatcher.Patches.Dynamic; + +/// +/// Disables 's behaviour. +/// +public sealed partial class FreezeRigidbodyWhenFar_Awake_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((FreezeRigidbodyWhenFar t) => t.Awake()); + + public static bool Prefix(FreezeRigidbodyWhenFar __instance) + { + __instance.enabled = false; + return false; + } +} diff --git a/NitroxPatcher/Patches/Dynamic/GroundMotor_OnControllerColliderHit_Patch.cs b/NitroxPatcher/Patches/Dynamic/GroundMotor_OnControllerColliderHit_Patch.cs new file mode 100644 index 0000000000..e28309ae02 --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/GroundMotor_OnControllerColliderHit_Patch.cs @@ -0,0 +1,24 @@ +using System.Reflection; +using NitroxClient.MonoBehaviours; +using NitroxModel.Helper; +using UnityEngine; + +namespace NitroxPatcher.Patches.Dynamic; + +/// +/// Disables a useless callback for . +/// +public sealed partial class GroundMotor_OnControllerColliderHit_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((GroundMotor t) => t.OnControllerColliderHit(default)); + + public static bool Prefix(GroundMotor __instance, ControllerColliderHit hit) + { + if (__instance is CyclopsMotor) + { + // Just cancel this as we don't need this on CyclopsMotor + return false; + } + return true; + } +} diff --git a/NitroxPatcher/Patches/Dynamic/Openable_PlayOpenAnimation_Patch.cs b/NitroxPatcher/Patches/Dynamic/Openable_PlayOpenAnimation_Patch.cs index 1426b17f31..35eb4d044c 100644 --- a/NitroxPatcher/Patches/Dynamic/Openable_PlayOpenAnimation_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/Openable_PlayOpenAnimation_Patch.cs @@ -1,16 +1,26 @@ -using System.Reflection; +using System.Reflection; using NitroxClient.GameLogic; +using NitroxClient.MonoBehaviours.Cyclops; +using NitroxClient.Unity.Helper; using NitroxModel.DataStructures; using NitroxModel.Helper; namespace NitroxPatcher.Patches.Dynamic; +/// +/// Broadcasts openables (door, traps) being opened/closed. Replicates this state for the associated virtual cyclops. +/// public sealed partial class Openable_PlayOpenAnimation_Patch : NitroxPatch, IDynamicPatch { private static readonly MethodInfo TARGET_METHOD = Reflect.Method((Openable t) => t.PlayOpenAnimation(default(bool), default(float))); public static bool Prefix(Openable __instance, bool openState, float duration) { + if (__instance.TryGetComponentInParent(out NitroxCyclops nitroxCyclops)) + { + nitroxCyclops.Virtual.ReplicateOpening(__instance, openState); + } + if (__instance.isOpen != openState && __instance.TryGetIdOrWarn(out NitroxId id)) { Resolve().OpenableStateChanged(id, openState, duration); diff --git a/NitroxPatcher/Patches/Dynamic/Player_EnterPilotingMode_Patch.cs b/NitroxPatcher/Patches/Dynamic/Player_EnterPilotingMode_Patch.cs new file mode 100644 index 0000000000..33b6dde4e5 --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/Player_EnterPilotingMode_Patch.cs @@ -0,0 +1,22 @@ +using System.Reflection; +using NitroxClient.MonoBehaviours.Cyclops; +using NitroxModel.Helper; + +namespace NitroxPatcher.Patches.Dynamic; + +/// +/// Setups the cyclops and the local player when it starts piloting it. +/// +public sealed partial class Player_EnterPilotingMode_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((Player t) => t.EnterPilotingMode(default, default)); + + public static void Postfix(Player __instance) + { + if (__instance.currentSub && __instance.currentSub.TryGetComponent(out NitroxCyclops nitroxCyclops)) + { + __instance.rigidBody.isKinematic = true; + nitroxCyclops.SetBroadcasting(); + } + } +} diff --git a/NitroxPatcher/Patches/Dynamic/Player_ExitLockedMode_Patch.cs b/NitroxPatcher/Patches/Dynamic/Player_ExitLockedMode_Patch.cs new file mode 100644 index 0000000000..b45564b15c --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/Player_ExitLockedMode_Patch.cs @@ -0,0 +1,14 @@ +using System.Reflection; +using NitroxModel.Helper; + +namespace NitroxPatcher.Patches.Dynamic; + +/// +/// Setups the local player when it stops being locked (e.g. when using the freecam command) +/// +public sealed partial class Player_ExitLockedMode_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((Player t) => t.ExitLockedMode(default, default)); + + public static void Postfix(Player __instance) => Player_ExitPilotingMode_Patch.Postfix(__instance); +} diff --git a/NitroxPatcher/Patches/Dynamic/Player_ExitPilotingMode_Patch.cs b/NitroxPatcher/Patches/Dynamic/Player_ExitPilotingMode_Patch.cs new file mode 100644 index 0000000000..c77bad613d --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/Player_ExitPilotingMode_Patch.cs @@ -0,0 +1,22 @@ +using System.Reflection; +using NitroxClient.MonoBehaviours; +using NitroxModel.Helper; + +namespace NitroxPatcher.Patches.Dynamic; + +/// +/// Setups the local player when it stops piloting a cyclops +/// +public sealed partial class Player_ExitPilotingMode_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((Player t) => t.ExitPilotingMode(default)); + + public static void Postfix(Player __instance) + { + if (__instance.currentSub && __instance.currentSub.isCyclops && __instance.TryGetComponent(out CyclopsMotor cyclopsMotor)) + { + __instance.transform.parent = __instance.currentSub.transform; + cyclopsMotor.ToggleCyclopsMotor(true); + } + } +} diff --git a/NitroxPatcher/Patches/Dynamic/Player_RequiresHighPrecisionPhysics_Patch.cs b/NitroxPatcher/Patches/Dynamic/Player_RequiresHighPrecisionPhysics_Patch.cs index 9b1a287d49..87512d6bfc 100644 --- a/NitroxPatcher/Patches/Dynamic/Player_RequiresHighPrecisionPhysics_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/Player_RequiresHighPrecisionPhysics_Patch.cs @@ -1,30 +1,22 @@ using System.Reflection; -using NitroxClient.MonoBehaviours; using NitroxModel.Helper; namespace NitroxPatcher.Patches.Dynamic; /// -/// Subnautica enables high precision physics any time a player is in a cyclops that is undergoing physics and is not actively anchored to the pilot seat. -/// This is usually rare in the base game (mostly a player disconnecting from the pilot seat); however, it is the normal case for a passanger in nitrox. We -/// disable the switching as it causing oscillation of the interpolation. Instead, any time someone is in the submarine we require high precision physics -/// if there is a remote player actively piloting. +/// Subnautica enables high precision physics any time the local player is in a moving cyclops and is not piloting it. +/// We force a higher fixed timestep as long as the local player is inside of a Cyclops to avoid it stuttering. /// public sealed partial class Player_RequiresHighPrecisionPhysics_Patch : NitroxPatch, IDynamicPatch { private static readonly MethodInfo TARGET_METHOD = Reflect.Method((Player t) => t.RequiresHighPrecisionPhysics()); - public static bool Prefix(ref bool __result) + public static bool Prefix(Player __instance, ref bool __result) { - if (Player.main.currentSub) + if (__instance.currentSub && __instance.currentSub.isCyclops) { - MultiplayerCyclops cyclops = Player.main.currentSub.GetComponent(); - - if (cyclops) - { - __result = (cyclops.CurrentPlayer != null); - return false; - } + __result = true; + return false; } return true; diff --git a/NitroxPatcher/Patches/Dynamic/Player_Start_Patch.cs b/NitroxPatcher/Patches/Dynamic/Player_Start_Patch.cs new file mode 100644 index 0000000000..036f6d1ada --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/Player_Start_Patch.cs @@ -0,0 +1,23 @@ +using System.Reflection; +using NitroxClient.MonoBehaviours; +using NitroxModel.Helper; + +namespace NitroxPatcher.Patches.Dynamic; + +/// +/// Adds to the Player's object when it's initialized +/// +public sealed partial class Player_Start_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((Player t) => t.Start()); + + public static void Postfix() + { + if (Player.mainObject.GetComponent()) + { + return; + } + GroundMotor groundMotor = Player.mainObject.GetComponent(); + Player.mainObject.AddComponent().Initialize(groundMotor); + } +} diff --git a/NitroxPatcher/Patches/Dynamic/SubControl_Start_Patch.cs b/NitroxPatcher/Patches/Dynamic/SubControl_Start_Patch.cs new file mode 100644 index 0000000000..a4f621056c --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/SubControl_Start_Patch.cs @@ -0,0 +1,21 @@ +using System.Reflection; +using NitroxClient.MonoBehaviours.Cyclops; +using NitroxModel.Helper; + +namespace NitroxPatcher.Patches.Dynamic; + +/// +/// Ensures every spawned Cyclops (except for virtual ones) has a script attached to it. +/// +public sealed partial class SubControl_Start_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((SubControl t) => t.Start()); + + public static void Postfix(SubControl __instance) + { + if (__instance.name != VirtualCyclops.NAME && __instance.name != LightmappedPrefabs.StandardMainObjectName) + { + __instance.gameObject.EnsureComponent(); + } + } +} diff --git a/NitroxPatcher/Patches/Dynamic/SubFire_EngineOverheatSimulation_Patch.cs b/NitroxPatcher/Patches/Dynamic/SubFire_EngineOverheatSimulation_Patch.cs new file mode 100644 index 0000000000..d2f9417506 --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/SubFire_EngineOverheatSimulation_Patch.cs @@ -0,0 +1,23 @@ +# if DEBUG +using System.Reflection; +using NitroxModel.Helper; + +namespace NitroxPatcher.Patches.Dynamic; + +/// +/// Disables cyclops overheating from moving while in creative mode (really annoying for no reason) +/// +public sealed partial class SubFire_EngineOverheatSimulation_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((SubFire t) => t.EngineOverheatSimulation()); + + public static bool Prefix() + { + if (GameModeUtils.IsCheatActive(GameModeOption.Creative)) + { + return false; + } + return true; + } +} +#endif diff --git a/NitroxPatcher/Patches/Dynamic/SubFire_FireSimulation_Patch.cs b/NitroxPatcher/Patches/Dynamic/SubFire_FireSimulation_Patch.cs new file mode 100644 index 0000000000..79a5c3123a --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/SubFire_FireSimulation_Patch.cs @@ -0,0 +1,16 @@ +# if DEBUG +using System.Reflection; +using NitroxModel.Helper; + +namespace NitroxPatcher.Patches.Dynamic; + +/// +/// Disables cyclops overheating from moving while in creative mode (really annoying for no reason) +/// +public sealed partial class SubFire_FireSimulation_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((SubFire t) => t.FireSimulation()); + + public static bool Prefix() => SubFire_EngineOverheatSimulation_Patch.Prefix(); +} +#endif diff --git a/NitroxPatcher/Patches/Dynamic/SubRoot_OnPlayerEntered_Patch.cs b/NitroxPatcher/Patches/Dynamic/SubRoot_OnPlayerEntered_Patch.cs index d5821e4ce6..08ff254502 100644 --- a/NitroxPatcher/Patches/Dynamic/SubRoot_OnPlayerEntered_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/SubRoot_OnPlayerEntered_Patch.cs @@ -1,12 +1,16 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; using HarmonyLib; +using NitroxClient.MonoBehaviours.Cyclops; using NitroxModel.Helper; namespace NitroxPatcher.Patches.Dynamic; +/// +/// Registers the local player in the cyclops it enters. +/// public sealed partial class SubRoot_OnPlayerEntered_Patch : NitroxPatch, IDynamicPatch { private static readonly MethodInfo TARGET_METHOD = Reflect.Method((SubRoot t) => t.OnPlayerEntered(default(Player))); @@ -60,4 +64,12 @@ public static IEnumerable Transpiler(MethodBase original, IEnum } return instructionList; } + + public static void Postfix(SubRoot __instance) + { + if (__instance.TryGetComponent(out NitroxCyclops nitroxCyclops)) + { + nitroxCyclops.OnLocalPlayerEnter(); + } + } } diff --git a/NitroxPatcher/Patches/Dynamic/SubRoot_OnPlayerExited_Patch.cs b/NitroxPatcher/Patches/Dynamic/SubRoot_OnPlayerExited_Patch.cs new file mode 100644 index 0000000000..846dcde5f1 --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/SubRoot_OnPlayerExited_Patch.cs @@ -0,0 +1,21 @@ +using System.Reflection; +using NitroxClient.MonoBehaviours.Cyclops; +using NitroxModel.Helper; + +namespace NitroxPatcher.Patches.Dynamic; + +/// +/// Unregisters the local player from the cyclops it leaves. +/// +public sealed partial class SubRoot_OnPlayerExited_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((SubRoot t) => t.OnPlayerExited(default)); + + public static void Postfix(SubRoot __instance) + { + if (__instance.TryGetComponent(out NitroxCyclops nitroxCyclops)) + { + nitroxCyclops.OnLocalPlayerExit(); + } + } +} diff --git a/NitroxPatcher/Patches/Dynamic/VFXConstructing_WakeUpSubmarine_Patch.cs b/NitroxPatcher/Patches/Dynamic/VFXConstructing_WakeUpSubmarine_Patch.cs deleted file mode 100644 index a1e53e42d1..0000000000 --- a/NitroxPatcher/Patches/Dynamic/VFXConstructing_WakeUpSubmarine_Patch.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Reflection; -using NitroxModel.Helper; - -namespace NitroxPatcher.Patches.Dynamic; - -/// -/// In the base game, subnautica uses a script to freeze all rigid bodies when they get out of distance. However, this script -/// also messes with interpolation of objects - especially those being driven by other players. We disable the functionality -/// when the Cyclops is fully loaded because there are validations that fail if removing it earlier (i.e. exceptions in code). -/// -public sealed partial class VFXConstructing_WakeUpSubmarine_Patch : NitroxPatch, IDynamicPatch -{ - public static readonly MethodInfo TARGET_METHOD = Reflect.Method((VFXConstructing t) => t.WakeUpSubmarine()); - - public static void Postfix(VFXConstructing __instance) - { - FreezeRigidbodyWhenFar freezer = __instance.GetComponent(); - - if (freezer) - { - freezer.enabled = false; - } - } -} From 1d010c9c42788e010e0393a6c0634a4889108939 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Mon, 5 Aug 2024 16:38:57 +0200 Subject: [PATCH 04/15] Implement remote players movements in cyclops --- .../Processors/PlayerMovementProcessor.cs | 25 ++----- NitroxClient/GameLogic/CyclopsPawn.cs | 42 +++++++++++- .../PlayerPositionInitialSyncProcessor.cs | 13 +--- NitroxClient/GameLogic/RemotePlayer.cs | 66 ++++++++++++++++--- NitroxClient/GameLogic/Vehicles.cs | 22 ++++++- .../MonoBehaviours/AnimationController.cs | 8 ++- .../MonoBehaviours/Cyclops/CyclopsMotor.cs | 12 ++++ .../Cyclops/CyclopsMotorGroundChecker.cs | 3 +- .../Cyclops/MultiplayerCyclops.cs | 4 +- .../MonoBehaviours/Cyclops/NitroxCyclops.cs | 3 +- .../MonoBehaviours/Cyclops/VirtualCyclops.cs | 17 ++++- .../MultiplayerVehicleControl.cs | 4 +- .../PlayerMovementBroadcaster.cs | 22 +------ 13 files changed, 169 insertions(+), 72 deletions(-) diff --git a/NitroxClient/Communication/Packets/Processors/PlayerMovementProcessor.cs b/NitroxClient/Communication/Packets/Processors/PlayerMovementProcessor.cs index 896f965556..6b7ab5d142 100644 --- a/NitroxClient/Communication/Packets/Processors/PlayerMovementProcessor.cs +++ b/NitroxClient/Communication/Packets/Processors/PlayerMovementProcessor.cs @@ -1,11 +1,7 @@ -using System.Collections; using NitroxClient.Communication.Packets.Processors.Abstract; using NitroxClient.GameLogic; -using NitroxClient.MonoBehaviours; -using NitroxClient.Unity.Helper; -using NitroxModel_Subnautica.DataStructures; -using NitroxModel.DataStructures.Util; using NitroxModel.Packets; +using NitroxModel_Subnautica.DataStructures; namespace NitroxClient.Communication.Packets.Processors; @@ -20,21 +16,12 @@ public PlayerMovementProcessor(PlayerManager remotePlayerManager) public override void Process(PlayerMovement movement) { - Optional remotePlayer = remotePlayerManager.Find(movement.PlayerId); - if (!remotePlayer.HasValue) + if (remotePlayerManager.TryFind(movement.PlayerId, out RemotePlayer remotePlayer)) { - return; + remotePlayer.UpdatePosition(movement.Position.ToUnity(), + movement.Velocity.ToUnity(), + movement.BodyRotation.ToUnity(), + movement.AimingRotation.ToUnity()); } - - Multiplayer.Main.StartCoroutine(QueueForFixedUpdate(remotePlayer.Value, movement)); - } - - private IEnumerator QueueForFixedUpdate(RemotePlayer player, PlayerMovement movement) - { - yield return Yielders.WaitForFixedUpdate; - player.UpdatePosition(movement.Position.ToUnity(), - movement.Velocity.ToUnity(), - movement.BodyRotation.ToUnity(), - movement.AimingRotation.ToUnity()); } } diff --git a/NitroxClient/GameLogic/CyclopsPawn.cs b/NitroxClient/GameLogic/CyclopsPawn.cs index 5267b45b21..c772864fd2 100644 --- a/NitroxClient/GameLogic/CyclopsPawn.cs +++ b/NitroxClient/GameLogic/CyclopsPawn.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using NitroxClient.GameLogic.PlayerLogic.PlayerModel.Abstract; using NitroxClient.MonoBehaviours; using NitroxClient.MonoBehaviours.Cyclops; @@ -12,6 +13,11 @@ namespace NitroxClient.GameLogic; /// public class CyclopsPawn { + private static readonly List controllers = []; + public static readonly int PLAYER_LAYER = 1 << LayerMask.NameToLayer("Player"); + + private readonly INitroxPlayer player; + private readonly VirtualCyclops virtualCyclops; private readonly Transform parentTransform; private readonly Transform realCyclopsTransform; private readonly bool isLocalPlayer; @@ -28,6 +34,8 @@ public Vector3 Position public CyclopsPawn(INitroxPlayer player, VirtualCyclops virtualCyclops, Transform realCyclopsTransform) { + this.player = player; + this.virtualCyclops = virtualCyclops; parentTransform = virtualCyclops.transform; this.realCyclopsTransform = realCyclopsTransform; @@ -40,8 +48,8 @@ public CyclopsPawn(INitroxPlayer player, VirtualCyclops virtualCyclops, Transfor } else if (player is RemotePlayer remotePlayer) { - RealObject = player.Body; - MaintainPredicate = () => true; + RealObject = remotePlayer.Body; + MaintainPredicate = () => !remotePlayer.PilotingChair; } Initialize($"{player.PlayerName}-Pawn", RealObject.transform.localPosition); @@ -54,7 +62,7 @@ public void Initialize(string name, Vector3 localPosition) CharacterController reference = Player.main.GetComponent(); Handle = GameObject.CreatePrimitive(PrimitiveType.Capsule); - Handle.layer = LayerMask.NameToLayer("Player"); + Handle.layer = 1 << PLAYER_LAYER; Handle.name = name; Handle.transform.parent = parentTransform; Handle.transform.localPosition = localPosition; @@ -70,9 +78,21 @@ public void Initialize(string name, Vector3 localPosition) Controller.skinWidth = reference.skinWidth; Controller.stepOffset = groundMotor.controllerSetup.stepOffset; Controller.slopeLimit = groundMotor.controllerSetup.slopeLimit; + + RegisterController(); + Log.Debug($"Pawn: height: {Controller.height}, center {center}, radius: {Controller.radius}, skinWidth: {Controller.skinWidth}"); } + public void RegisterController() + { + foreach (CharacterController controller in controllers) + { + Physics.IgnoreCollision(controller, Controller); + } + controllers.Add(Controller); + } + public void SetReference() { Handle.transform.localPosition = RealObject.transform.localPosition; @@ -92,8 +112,24 @@ public void MaintainPosition() } } + public void Unregister() + { + if (virtualCyclops) + { + if (isLocalPlayer) + { + virtualCyclops.Cyclops.OnLocalPlayerExit(); + } + else + { + virtualCyclops.Cyclops.OnPlayerExit((RemotePlayer)player); + } + } + } + public void Terminate() { + controllers.Remove(Controller); GameObject.Destroy(Handle); } } diff --git a/NitroxClient/GameLogic/InitialSync/PlayerPositionInitialSyncProcessor.cs b/NitroxClient/GameLogic/InitialSync/PlayerPositionInitialSyncProcessor.cs index d7411fae0a..3105476f6d 100644 --- a/NitroxClient/GameLogic/InitialSync/PlayerPositionInitialSyncProcessor.cs +++ b/NitroxClient/GameLogic/InitialSync/PlayerPositionInitialSyncProcessor.cs @@ -71,18 +71,7 @@ public override IEnumerator Process(InitialPlayerSync packet, WaitScreen.ManualW } Player.main.SetCurrentSub(subRoot, true); - if (subRoot.isBase) - { - // If the player's in a base, we don't need to wait for the world to load - Player.main.cinematicModeActive = false; - yield break; - } - - Transform rootTransform = subRoot.transform; - Quaternion vehicleAngle = rootTransform.rotation; - // "position" is a relative position and "positionInVehicle" an absolute position - Vector3 positionInVehicle = vehicleAngle * position + rootTransform.position; - Player.main.SetPosition(positionInVehicle, rotation * vehicleAngle); + // If the player's in a base/cyclops we don't need to wait for the world to load Player.main.cinematicModeActive = false; Player.main.UpdateIsUnderwater(); } diff --git a/NitroxClient/GameLogic/RemotePlayer.cs b/NitroxClient/GameLogic/RemotePlayer.cs index 2c1733c5a2..bd927759f9 100644 --- a/NitroxClient/GameLogic/RemotePlayer.cs +++ b/NitroxClient/GameLogic/RemotePlayer.cs @@ -48,6 +48,8 @@ public class RemotePlayer : INitroxPlayer public readonly Event PlayerDisconnectEvent = new(); + public CyclopsPawn Pawn { get; set; } + public RemotePlayer(PlayerContext playerContext, PlayerModelManager playerModelManager, PlayerVitalsManager playerVitalsManager, FMODWhitelist fmodWhitelist) { PlayerContext = playerContext; @@ -94,6 +96,17 @@ public void InitializeGameObject(GameObject playerBody) vitals = playerVitalsManager.CreateOrFindForPlayer(this); RefreshVitalsVisibility(); + + PlayerDisconnectEvent.AddHandler(Body, _ => + { + Pawn?.Unregister(); + Pawn = null; + }); + + PlayerDeathEvent.AddHandler(Body, _ => + { + ResetStates(); + }); } public void Attach(Transform transform, bool keepWorldTransform = false) @@ -121,9 +134,24 @@ public void UpdatePosition(Vector3 position, Vector3 velocity, Quaternion bodyRo // When receiving movement packets, a player can not be controlling a vehicle (they can walk through subroots though). SetVehicle(null); SetPilotingChair(null); + + AnimationController.AimingRotation = aimingRotation; + AnimationController.UpdatePlayerAnimations = true; + AnimationController.Velocity = MovementHelper.GetCorrectedVelocity(position, velocity, Body, Time.fixedDeltaTime); + // If in a subroot the position will be relative to the subroot if (SubRoot && !SubRoot.isBase) { + if (Pawn != null) + { + Pawn.Handle.transform.localPosition = SubRoot.transform.InverseTransformPoint(position); + Pawn.Handle.transform.localRotation = bodyRotation; + + AnimationController.AimingRotation = aimingRotation; + AnimationController.UpdatePlayerAnimations = true; + return; + } + Quaternion vehicleAngle = SubRoot.transform.rotation; position = vehicleAngle * position; position += SubRoot.transform.position; @@ -131,11 +159,8 @@ public void UpdatePosition(Vector3 position, Vector3 velocity, Quaternion bodyRo aimingRotation = vehicleAngle * aimingRotation; } - RigidBody.velocity = AnimationController.Velocity = MovementHelper.GetCorrectedVelocity(position, velocity, Body, Time.fixedDeltaTime); + RigidBody.velocity = AnimationController.Velocity; RigidBody.angularVelocity = MovementHelper.GetCorrectedAngularVelocity(bodyRotation, Vector3.zero, Body, Time.fixedDeltaTime); - - AnimationController.AimingRotation = aimingRotation; - AnimationController.UpdatePlayerAnimations = true; } public void SetPilotingChair(PilotingChair newPilotingChair) @@ -163,10 +188,15 @@ public void SetPilotingChair(PilotingChair newPilotingChair) mpCyclops.CurrentPlayer = this; mpCyclops.Enter(); + + if (SubRoot) + { + SkyEnvironmentChanged.Broadcast(Body, SubRoot); + } } else { - SetSubRoot(SubRoot); + SetSubRoot(SubRoot, true); ArmsController.SetWorldIKTarget(null, null); if (mpCyclops) @@ -176,17 +206,29 @@ public void SetPilotingChair(PilotingChair newPilotingChair) } } - RigidBody.isKinematic = AnimationController["cyclops_steering"] = newPilotingChair; + bool isKinematic = newPilotingChair; + UWE.Utils.SetIsKinematicAndUpdateInterpolation(RigidBody, isKinematic, true); + AnimationController["cyclops_steering"] = newPilotingChair; } } - public void SetSubRoot(SubRoot newSubRoot) + public void SetSubRoot(SubRoot newSubRoot, bool force = false) { - if (SubRoot != newSubRoot) + if (SubRoot != newSubRoot || force) { + // Unregister from previous cyclops + Pawn?.Unregister(); + Pawn = null; + if (newSubRoot) { Attach(newSubRoot.transform, true); + + // Register in new cyclops + if (newSubRoot.TryGetComponent(out NitroxCyclops nitroxCyclops)) + { + nitroxCyclops.OnPlayerEnter(this); + } } else { @@ -250,7 +292,8 @@ public void SetVehicle(Vehicle newVehicle) } } - RigidBody.isKinematic = newVehicle; + bool isKinematic = newVehicle; + UWE.Utils.SetIsKinematicAndUpdateInterpolation(RigidBody, isKinematic, true); Vehicle = newVehicle; @@ -260,13 +303,16 @@ public void SetVehicle(Vehicle newVehicle) } /// - /// Drops the remote player, swimming where he is + /// Drops the remote player, swimming where he is. Resets its animator. /// public void ResetStates() { + SetPilotingChair(null); SetVehicle(null); SetSubRoot(null); AnimationController.UpdatePlayerAnimations = true; + AnimationController.Reset(); + ArmsController.SetWorldIKTarget(null, null); } public void Destroy() diff --git a/NitroxClient/GameLogic/Vehicles.cs b/NitroxClient/GameLogic/Vehicles.cs index 54593fb69d..5d61eaacc6 100644 --- a/NitroxClient/GameLogic/Vehicles.cs +++ b/NitroxClient/GameLogic/Vehicles.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Collections.Generic; using NitroxClient.Communication; using NitroxClient.Communication.Abstract; using NitroxClient.GameLogic.Helper; @@ -20,6 +21,7 @@ public class Vehicles { private readonly IPacketSender packetSender; private readonly IMultiplayerSession multiplayerSession; + private readonly Dictionary pilotingChairByTechType = []; public Vehicles(IPacketSender packetSender, IMultiplayerSession multiplayerSession) { @@ -101,11 +103,29 @@ public void UpdateVehiclePosition(VehicleMovementData vehicleModel, Optional()); + playerInstance.SetPilotingChair(FindPilotingChairWithCache(opGameObject.Value, vehicleModel.TechType.ToUnity())); playerInstance.AnimationController.UpdatePlayerAnimations = false; } } + private PilotingChair FindPilotingChairWithCache(GameObject parent, TechType techType) + { + if (!parent) + { + return null; + } + if (pilotingChairByTechType.TryGetValue(techType, out string path)) + { + return parent.transform.Find(path).GetComponent(); + } + else + { + PilotingChair chair = parent.GetComponentInChildren(true); + pilotingChairByTechType.Add(techType, chair.gameObject.GetHierarchyPath(parent)); + return chair; + } + } + public void BroadcastDestroyedVehicle(NitroxId id) { using (PacketSuppressor.Suppress()) diff --git a/NitroxClient/MonoBehaviours/AnimationController.cs b/NitroxClient/MonoBehaviours/AnimationController.cs index 09fde7a211..1d4cf130a7 100644 --- a/NitroxClient/MonoBehaviours/AnimationController.cs +++ b/NitroxClient/MonoBehaviours/AnimationController.cs @@ -1,4 +1,4 @@ -using UnityEngine; +using UnityEngine; namespace NitroxClient.MonoBehaviours { @@ -58,5 +58,11 @@ internal void SetFloat(string name, float value) { animator.SetFloat(name, value); } + + public void Reset() + { + animator.Rebind(); + animator.Update(0f); + } } } diff --git a/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs b/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs index 17f036d31d..7269bd1d12 100644 --- a/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs +++ b/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs @@ -148,6 +148,12 @@ public Vector3 Move(Vector3 horizontalVelocity) Vector3 move = (horizontalVelocity + verticalVelocity) * DeltaTime; + float num = Mathf.Max(Pawn.Controller.stepOffset, Mathf.Sqrt(move.x * move.x + move.z * move.z)); + if (grounded) + { + move -= num * Up; + } + Collision = Pawn.Controller.Move(move); float verticalDot = Vector3.Dot(verticalVelocity, Up); @@ -170,6 +176,12 @@ public Vector3 Move(Vector3 horizontalVelocity) }); } } + // If player is no longer grounded after move + else if (previouslyGrounded) + { + SendMessage("OnFall", SendMessageOptions.DontRequireReceiver); + Pawn.Handle.transform.localPosition += num * Up; + } // Give velocity info for the animations return (Pawn.Position - beforePosition) / DeltaTime; diff --git a/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotorGroundChecker.cs b/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotorGroundChecker.cs index 5352747b4e..1a96b13965 100644 --- a/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotorGroundChecker.cs +++ b/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotorGroundChecker.cs @@ -1,3 +1,4 @@ +using NitroxClient.GameLogic; using UnityEngine; namespace NitroxClient.MonoBehaviours; @@ -11,7 +12,7 @@ public partial class CyclopsMotor private const float CAST_EXTRA_DISTANCE = 0.001f; public const QueryTriggerInteraction QuerySetting = QueryTriggerInteraction.Ignore; - public static readonly int LayerMaskExceptPlayer = ~(1 << LayerMask.NameToLayer("Player")); + public static readonly int LayerMaskExceptPlayer = ~CyclopsPawn.PLAYER_LAYER; /// /// Latest snapshot of the Pawn's global position. It is updated every frame before being used. diff --git a/NitroxClient/MonoBehaviours/Cyclops/MultiplayerCyclops.cs b/NitroxClient/MonoBehaviours/Cyclops/MultiplayerCyclops.cs index 1379e906e0..7114006918 100644 --- a/NitroxClient/MonoBehaviours/Cyclops/MultiplayerCyclops.cs +++ b/NitroxClient/MonoBehaviours/Cyclops/MultiplayerCyclops.cs @@ -23,13 +23,13 @@ protected override void Awake() protected override void FixedUpdate() { - /*base.FixedUpdate(); + base.FixedUpdate(); if (CurrentPlayer != null) { // These values are set by the game code, but they do not seem to have any impact on animations. CurrentPlayer.AnimationController.SetFloat("cyclops_yaw", SmoothYaw.SmoothValue); CurrentPlayer.AnimationController.SetFloat("cyclops_pitch", SmoothPitch.SmoothValue); - }*/ + } } internal override void SetSteeringWheel(float yaw, float pitch) diff --git a/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs b/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs index dad0b6b2d8..f84a7b2721 100644 --- a/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs +++ b/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs @@ -90,7 +90,7 @@ public void OnLocalPlayerExit() /// public void OnPlayerEnter(RemotePlayer remotePlayer) { - Virtual.AddPawnForPlayer(remotePlayer); + remotePlayer.Pawn = Virtual.AddPawnForPlayer(remotePlayer); } /// @@ -99,6 +99,7 @@ public void OnPlayerEnter(RemotePlayer remotePlayer) public void OnPlayerExit(RemotePlayer remotePlayer) { Virtual.RemovePawnForPlayer(remotePlayer); + remotePlayer.Pawn = null; } public void MaintainPawns() diff --git a/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs b/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs index 957a65403f..8f33ba0e82 100644 --- a/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs +++ b/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs @@ -24,6 +24,10 @@ public class VirtualCyclops : MonoBehaviour public NitroxCyclops Cyclops; public Transform axis; + private Rigidbody rigidbody; + private Vector3 InitialPosition; + private Quaternion InitialRotation; + public static void Initialize() { CreateVirtualPrefab(); @@ -63,14 +67,19 @@ public static VirtualCyclops CreateVirtualInstance(GameObject cyclopsObject) { Vector3 position = Vector3.up * 500 + Vector3.right * (Offset - 100); Offset += 10; + Quaternion rotation = Quaternion.identity; - GameObject instance = GameObject.Instantiate(Prefab, position, Quaternion.identity, false); + GameObject instance = GameObject.Instantiate(Prefab, position, rotation, false); instance.name = NAME; LargeWorldEntity.Register(instance); virtualCyclops = instance.GetComponent(); virtualCyclops.Cyclops = cyclopsObject.GetComponent(); virtualCyclops.axis = instance.GetComponent().subAxis; virtualCyclops.RegisterOpenables(); + virtualCyclops.InitialPosition = position; + virtualCyclops.InitialRotation = rotation; + virtualCyclops.rigidbody = instance.GetComponent(); + virtualCyclops.rigidbody.constraints = RigidbodyConstraints.FreezeAll; instance.GetComponent().enabled = false; instance.GetComponent().lockInterpolation = false; @@ -96,6 +105,12 @@ public static void Terminate(GameObject cyclopsObject) } } + public void Update() + { + transform.position = InitialPosition; + transform.rotation = InitialRotation; + } + public void ToggleRenderers(bool toggled) { foreach (Renderer renderer in transform.GetComponentsInChildren(true)) diff --git a/NitroxClient/MonoBehaviours/MultiplayerVehicleControl.cs b/NitroxClient/MonoBehaviours/MultiplayerVehicleControl.cs index f0bd70e127..d2cd0e5f50 100644 --- a/NitroxClient/MonoBehaviours/MultiplayerVehicleControl.cs +++ b/NitroxClient/MonoBehaviours/MultiplayerVehicleControl.cs @@ -1,4 +1,4 @@ -using System; +using System; using NitroxClient.GameLogic; using NitroxClient.Unity.Smoothing; using UnityEngine; @@ -38,7 +38,7 @@ protected virtual void FixedUpdate() SmoothPosition.FixedUpdate(); SmoothVelocity.FixedUpdate(); - rigidbody.isKinematic = false; // we should maybe find a way to remove UWE's FreezeRigidBodyWhenFar component...tried removing it but caused a bunch of issues. + //rigidbody.isKinematic = false; // we should maybe find a way to remove UWE's FreezeRigidBodyWhenFar component...tried removing it but caused a bunch of issues. rigidbody.velocity = MovementHelper.GetCorrectedVelocity(SmoothPosition.Current, SmoothVelocity.Current, gameObject, Time.fixedDeltaTime); SmoothRotation.FixedUpdate(); SmoothAngularVelocity.FixedUpdate(); diff --git a/NitroxClient/MonoBehaviours/PlayerMovementBroadcaster.cs b/NitroxClient/MonoBehaviours/PlayerMovementBroadcaster.cs index d2e02aea98..fb6ba89197 100644 --- a/NitroxClient/MonoBehaviours/PlayerMovementBroadcaster.cs +++ b/NitroxClient/MonoBehaviours/PlayerMovementBroadcaster.cs @@ -11,30 +11,15 @@ namespace NitroxClient.MonoBehaviours; public class PlayerMovementBroadcaster : MonoBehaviour { - /// - /// Amount of physics updates to skip for sending location broadcasts. - /// TODO: Allow servers to set this value for clients. With many clients connected to the server, a higher value can be preferred. - /// - public const int LOCATION_BROADCAST_TICK_SKIPS = 1; - private LocalPlayer localPlayer; - private int locationBroadcastSkipThreshold = LOCATION_BROADCAST_TICK_SKIPS; public void Awake() { - localPlayer = NitroxServiceLocator.LocateService(); + localPlayer = this.Resolve(); } - public void FixedUpdate() + public void Update() { - // Throttle location broadcasts to not run on every physics tick. - if (locationBroadcastSkipThreshold-- > 0) - { - return; - } - // Reset skip threshold. - locationBroadcastSkipThreshold = LOCATION_BROADCAST_TICK_SKIPS; - // Freecam does disable main camera control // But it's also disabled when driving the cyclops through a cyclops camera (content.activeSelf is only true when controlling through a cyclops camera) if (!MainCameraControl.main.isActiveAndEnabled && @@ -59,10 +44,9 @@ public void FixedUpdate() // Rotate relative player position relative to the subroot (else there are problems with respawning) Transform subRootTransform = subRoot.transform; Quaternion undoVehicleAngle = subRootTransform.rotation.GetInverse(); - currentPosition = currentPosition - subRootTransform.position; - currentPosition = undoVehicleAngle * currentPosition; bodyRotation = undoVehicleAngle * bodyRotation; aimingRotation = undoVehicleAngle * aimingRotation; + currentPosition = subRootTransform.TransformPoint(currentPosition); if (Player.main.isPiloting && subRoot.isCyclops) { From 7c4f5a7c0df9721c86d60f99919445c64d7becea Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Tue, 6 Aug 2024 20:21:12 +0200 Subject: [PATCH 05/15] Replicate a cyclop's constructables' colliders in the virtual one --- .../Spawning/Bases/ModuleEntitySpawner.cs | 8 ++ .../MonoBehaviours/Cyclops/CyclopsMotor.cs | 2 - .../MonoBehaviours/Cyclops/VirtualCyclops.cs | 73 +++++++++++++++++++ .../PlayerMovementBroadcaster.cs | 2 + .../Patches/Dynamic/Builder_TryPlace_Patch.cs | 6 ++ .../Dynamic/Constructable_Construct_Patch.cs | 13 +++- .../Constructable_DeconstructAsync_Patch.cs | 3 +- ...structable_ProgressDeconstruction_Patch.cs | 22 ++++++ 8 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 NitroxPatcher/Patches/Dynamic/Constructable_ProgressDeconstruction_Patch.cs diff --git a/NitroxClient/GameLogic/Spawning/Bases/ModuleEntitySpawner.cs b/NitroxClient/GameLogic/Spawning/Bases/ModuleEntitySpawner.cs index 9e76a39aa2..ea57a91a1e 100644 --- a/NitroxClient/GameLogic/Spawning/Bases/ModuleEntitySpawner.cs +++ b/NitroxClient/GameLogic/Spawning/Bases/ModuleEntitySpawner.cs @@ -5,6 +5,7 @@ using NitroxClient.GameLogic.Spawning.Abstract; using NitroxClient.GameLogic.Spawning.WorldEntities; using NitroxClient.MonoBehaviours; +using NitroxClient.MonoBehaviours.Cyclops; using NitroxClient.Unity.Helper; using NitroxModel.DataStructures; using NitroxModel.DataStructures.GameLogic; @@ -80,6 +81,12 @@ public static IEnumerator RestoreModule(Transform parent, ModuleEntity moduleEnt moduleTransform.localRotation = moduleEntity.Transform.LocalRotation.ToUnity(); moduleTransform.localScale = moduleEntity.Transform.LocalScale.ToUnity(); ApplyModuleData(moduleEntity, moduleObject, result); + + if (parent && parent.TryGetComponent(out NitroxCyclops nitroxCyclops)) + { + nitroxCyclops.Virtual.ReplicateConstructable(moduleObject.GetComponent()); + } + yield return BuildingPostSpawner.ApplyPostSpawner(moduleObject, moduleEntity.Id); } @@ -99,6 +106,7 @@ public static void ApplyModuleData(ModuleEntity moduleEntity, GameObject moduleO constructable.SetState(moduleEntity.ConstructedAmount >= 1f, false); constructable.UpdateMaterial(); NitroxEntity.SetNewId(moduleObject, moduleEntity.Id); + result?.Set(moduleObject); } diff --git a/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs b/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs index 7269bd1d12..61463d6aef 100644 --- a/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs +++ b/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs @@ -142,8 +142,6 @@ public override Vector3 UpdateMove() /// Pawn's local velocity public Vector3 Move(Vector3 horizontalVelocity) { - // TODO: Replicate modules' colliders in the virtual cyclops - // TODO: Try to improve collisions with closing doors (maybe set pawns to be kinematic) Vector3 beforePosition = Pawn.Position; Vector3 move = (horizontalVelocity + verticalVelocity) * DeltaTime; diff --git a/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs b/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs index 8f33ba0e82..ce7ee76075 100644 --- a/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs +++ b/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using NitroxClient.Communication; using NitroxClient.GameLogic; using NitroxClient.GameLogic.PlayerLogic.PlayerModel.Abstract; @@ -20,6 +21,8 @@ public class VirtualCyclops : MonoBehaviour public static readonly Dictionary VirtualCyclopsByObject = []; private readonly Dictionary openableByName = []; + private readonly Dictionary virtualConstructableByRealGameObject = []; + private readonly Dictionary cacheColliderCopy = []; public readonly Dictionary Pawns = []; public NitroxCyclops Cyclops; public Transform axis; @@ -156,4 +159,74 @@ public void ReplicateOpening(Openable openable, bool openState) } } } + + public void ReplicateConstructable(Constructable constructable) + { + if (virtualConstructableByRealGameObject.ContainsKey(constructable.gameObject)) + { + return; + } + GameObject colliderCopy = CreateColliderCopy(constructable.gameObject, constructable.techType); + // WorldPositionStays is set to false so we keep the same local parameters + colliderCopy.transform.parent = transform; + colliderCopy.transform.localPosition = constructable.transform.localPosition; + colliderCopy.transform.localRotation = constructable.transform.localRotation; + colliderCopy.transform.localScale = constructable.transform.localScale; + + virtualConstructableByRealGameObject.Add(constructable.gameObject, colliderCopy); + } + + /// + /// Creates an empty shell simulating the presence of modules by copying its children containing a collider. + /// + public GameObject CreateColliderCopy(GameObject realObject, TechType techType) + { + if (cacheColliderCopy.TryGetValue(techType, out GameObject colliderCopy)) + { + return GameObject.Instantiate(colliderCopy); + } + colliderCopy = new GameObject($"{realObject.name}-collidercopy"); + + Transform transform = realObject.transform; + + Dictionary copiedTransformByRealTransform = []; + copiedTransformByRealTransform[transform] = colliderCopy.transform; + + IEnumerable uniqueColliderObjects = realObject.GetComponentsInChildren(true).Select(c => c.gameObject).Distinct(); + foreach (GameObject colliderObject in uniqueColliderObjects) + { + GameObject copiedCollider = Instantiate(colliderObject); + copiedCollider.name = colliderObject.name; + + // "child" is always a copied transform looking for its copied parent + Transform child = copiedCollider.transform; + // "parent" is always the real parent of the real transform corresponding to "child" + Transform parent = colliderObject.transform.parent; + + while (!copiedTransformByRealTransform.ContainsKey(parent)) + { + Transform copiedParent = copiedTransformByRealTransform[parent] = Instantiate(parent); + + child.SetParent(copiedParent, false); + + child = copiedParent; + parent = parent.parent; + } + + // At the top of the tree we can simply stick the latest child to the collider + child.SetParent(colliderCopy.transform, false); + } + + cacheColliderCopy.Add(techType, colliderCopy); + return GameObject.Instantiate(colliderCopy); + } + + public void UnregisterConstructable(GameObject realObject) + { + if (virtualConstructableByRealGameObject.TryGetValue(realObject, out GameObject virtualConstructable)) + { + Destroy(virtualConstructable); + virtualConstructableByRealGameObject.Remove(realObject); + } + } } diff --git a/NitroxClient/MonoBehaviours/PlayerMovementBroadcaster.cs b/NitroxClient/MonoBehaviours/PlayerMovementBroadcaster.cs index fb6ba89197..24ecc7af12 100644 --- a/NitroxClient/MonoBehaviours/PlayerMovementBroadcaster.cs +++ b/NitroxClient/MonoBehaviours/PlayerMovementBroadcaster.cs @@ -44,6 +44,8 @@ public void Update() // Rotate relative player position relative to the subroot (else there are problems with respawning) Transform subRootTransform = subRoot.transform; Quaternion undoVehicleAngle = subRootTransform.rotation.GetInverse(); + currentPosition = currentPosition - subRootTransform.position; + currentPosition = undoVehicleAngle * currentPosition; bodyRotation = undoVehicleAngle * bodyRotation; aimingRotation = undoVehicleAngle * aimingRotation; currentPosition = subRootTransform.TransformPoint(currentPosition); diff --git a/NitroxPatcher/Patches/Dynamic/Builder_TryPlace_Patch.cs b/NitroxPatcher/Patches/Dynamic/Builder_TryPlace_Patch.cs index 59cdb963c9..902242fd3e 100644 --- a/NitroxPatcher/Patches/Dynamic/Builder_TryPlace_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/Builder_TryPlace_Patch.cs @@ -4,6 +4,7 @@ using NitroxClient.Communication.Abstract; using NitroxClient.GameLogic.Spawning.Bases; using NitroxClient.MonoBehaviours; +using NitroxClient.MonoBehaviours.Cyclops; using NitroxClient.Unity.Helper; using NitroxModel.DataStructures; using NitroxModel.DataStructures.GameLogic.Entities.Bases; @@ -93,6 +94,11 @@ public static void GhostCreated(Constructable constructable) module.Id = ghostId; module.ParentId = parentId; Resolve().Send(new PlaceModule(module)); + + if (constructable.transform.parent && constructable.transform.parent.TryGetComponent(out NitroxCyclops nitroxCyclops)) + { + nitroxCyclops.Virtual.ReplicateConstructable(constructable); + } } } } diff --git a/NitroxPatcher/Patches/Dynamic/Constructable_Construct_Patch.cs b/NitroxPatcher/Patches/Dynamic/Constructable_Construct_Patch.cs index b3632a2963..d344ff8a1b 100644 --- a/NitroxPatcher/Patches/Dynamic/Constructable_Construct_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/Constructable_Construct_Patch.cs @@ -43,7 +43,8 @@ public sealed partial class Constructable_Construct_Patch : NitroxPatch, IDynami public static readonly List InstructionsToAdd = new() { new(Ldarg_0), - new(Call, Reflect.Method(() => ConstructionAmountModified(default))) + new(Ldc_I4_1), // True for "constructing" + new(Call, Reflect.Method(() => ConstructionAmountModified(default, default))) }; public static IEnumerable Transpiler(MethodBase original, IEnumerable instructions) => @@ -56,7 +57,7 @@ public static IEnumerable Transpiler(MethodBase original, IEnum return null; }); - public static void ConstructionAmountModified(Constructable constructable) + public static void ConstructionAmountModified(Constructable constructable, bool constructing) { // We only manage the amount change, not the deconstruction/construction action if (!constructable.TryGetNitroxId(out NitroxId entityId)) @@ -65,6 +66,14 @@ public static void ConstructionAmountModified(Constructable constructable) return; } float amount = NitroxModel.Helper.Mathf.Clamp01(constructable.constructedAmount); + + // An object is destroyed when amount = 0 AND if we are destructing + // so we don't need the broadcast if we are trying to construct with not enough resources (amount = 0) + if (amount == 0f && constructing) + { + return; + } + /* * Different cases: * - Normal module (only Constructable), just let it go to 1.0f normally diff --git a/NitroxPatcher/Patches/Dynamic/Constructable_DeconstructAsync_Patch.cs b/NitroxPatcher/Patches/Dynamic/Constructable_DeconstructAsync_Patch.cs index 51aee6a461..28ca273039 100644 --- a/NitroxPatcher/Patches/Dynamic/Constructable_DeconstructAsync_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/Constructable_DeconstructAsync_Patch.cs @@ -22,7 +22,8 @@ public sealed partial class Constructable_DeconstructAsync_Patch : NitroxPatch, public static readonly List InstructionsToAdd = new() { new(Ldloc_1), - new(Call, Reflect.Method(() => Constructable_Construct_Patch.ConstructionAmountModified(default))) + new(Ldc_I4_0), // False for "constructing" + new(Call, Reflect.Method(() => Constructable_Construct_Patch.ConstructionAmountModified(default, default))) }; public static IEnumerable Transpiler(MethodBase original, IEnumerable instructions) => diff --git a/NitroxPatcher/Patches/Dynamic/Constructable_ProgressDeconstruction_Patch.cs b/NitroxPatcher/Patches/Dynamic/Constructable_ProgressDeconstruction_Patch.cs new file mode 100644 index 0000000000..1cd5fc81bf --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/Constructable_ProgressDeconstruction_Patch.cs @@ -0,0 +1,22 @@ +using System.Reflection; +using NitroxClient.MonoBehaviours.Cyclops; +using NitroxModel.Helper; + +namespace NitroxPatcher.Patches.Dynamic; + +/// +/// Unregisters constructables from virtual cyclops when they're fully deconstructed. +/// +public sealed partial class Constructable_ProgressDeconstruction_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((Constructable t) => t.ProgressDeconstruction()); + + public static void Prefix(Constructable __instance) + { + if (__instance.constructedAmount <= 0f && + __instance.transform.parent && __instance.transform.parent.TryGetComponent(out NitroxCyclops nitroxCyclops)) + { + nitroxCyclops.Virtual.UnregisterConstructable(__instance.gameObject); + } + } +} From 7892ebfad12236397c09b00dc16e7bc3e69e4801 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Wed, 7 Aug 2024 01:02:50 +0200 Subject: [PATCH 06/15] Improvements: only one virtual cyclops for all cyclops, openables no longer push players, disconnect event is now fired --- .../Packets/Processors/DisconnectProcessor.cs | 3 +- NitroxClient/GameLogic/CyclopsPawn.cs | 39 ++-- .../Spawning/Bases/ModuleEntitySpawner.cs | 2 +- .../MonoBehaviours/Cyclops/CyclopsMotor.cs | 4 +- .../MonoBehaviours/Cyclops/NitroxCyclops.cs | 61 ++++--- .../MonoBehaviours/Cyclops/VirtualCyclops.cs | 169 ++++++++++-------- .../MultiplayerVehicleControl.cs | 1 - .../Patches/Dynamic/Builder_TryPlace_Patch.cs | 2 +- ...DestructionEvent_OnConsoleCommand_Patch.cs | 1 - .../Dynamic/Openable_OnTriggerEnter_Patch.cs | 22 +++ .../Dynamic/Openable_OnTriggerExit_Patch.cs | 22 +++ .../Openable_PlayOpenAnimation_Patch.cs | 8 +- 12 files changed, 213 insertions(+), 121 deletions(-) create mode 100644 NitroxPatcher/Patches/Dynamic/Openable_OnTriggerEnter_Patch.cs create mode 100644 NitroxPatcher/Patches/Dynamic/Openable_OnTriggerExit_Patch.cs diff --git a/NitroxClient/Communication/Packets/Processors/DisconnectProcessor.cs b/NitroxClient/Communication/Packets/Processors/DisconnectProcessor.cs index a7f9001874..92cc4036dd 100644 --- a/NitroxClient/Communication/Packets/Processors/DisconnectProcessor.cs +++ b/NitroxClient/Communication/Packets/Processors/DisconnectProcessor.cs @@ -1,4 +1,4 @@ -using NitroxClient.Communication.Packets.Processors.Abstract; +using NitroxClient.Communication.Packets.Processors.Abstract; using NitroxClient.GameLogic; using NitroxClient.GameLogic.HUD; using NitroxModel.DataStructures.Util; @@ -26,6 +26,7 @@ public override void Process(Disconnect disconnect) Optional remotePlayer = remotePlayerManager.Find(disconnect.PlayerId); if (remotePlayer.HasValue) { + remotePlayer.Value.PlayerDisconnectEvent.Trigger(remotePlayer.Value); remotePlayerManager.RemovePlayer(disconnect.PlayerId); Log.Info($"{remotePlayer.Value.PlayerName} disconnected"); Log.InGame(Language.main.Get("Nitrox_PlayerDisconnected").Replace("{PLAYER}", remotePlayer.Value.PlayerName)); diff --git a/NitroxClient/GameLogic/CyclopsPawn.cs b/NitroxClient/GameLogic/CyclopsPawn.cs index c772864fd2..7e295359ee 100644 --- a/NitroxClient/GameLogic/CyclopsPawn.cs +++ b/NitroxClient/GameLogic/CyclopsPawn.cs @@ -17,8 +17,8 @@ public class CyclopsPawn public static readonly int PLAYER_LAYER = 1 << LayerMask.NameToLayer("Player"); private readonly INitroxPlayer player; - private readonly VirtualCyclops virtualCyclops; - private readonly Transform parentTransform; + private readonly NitroxCyclops cyclops; + private readonly Transform virtualTransform; private readonly Transform realCyclopsTransform; private readonly bool isLocalPlayer; public readonly GameObject RealObject; @@ -32,12 +32,12 @@ public Vector3 Position set { Handle.transform.position = value; } } - public CyclopsPawn(INitroxPlayer player, VirtualCyclops virtualCyclops, Transform realCyclopsTransform) + public CyclopsPawn(INitroxPlayer player, NitroxCyclops cyclops) { this.player = player; - this.virtualCyclops = virtualCyclops; - parentTransform = virtualCyclops.transform; - this.realCyclopsTransform = realCyclopsTransform; + this.cyclops = cyclops; + virtualTransform = VirtualCyclops.Instance.transform; + realCyclopsTransform = cyclops.transform; if (player is ILocalNitroxPlayer) { @@ -64,7 +64,7 @@ public void Initialize(string name, Vector3 localPosition) Handle = GameObject.CreatePrimitive(PrimitiveType.Capsule); Handle.layer = 1 << PLAYER_LAYER; Handle.name = name; - Handle.transform.parent = parentTransform; + Handle.transform.parent = virtualTransform; Handle.transform.localPosition = localPosition; GameObject.DestroyImmediate(Handle.GetComponent()); @@ -81,6 +81,8 @@ public void Initialize(string name, Vector3 localPosition) RegisterController(); + Handle.AddComponent().Pawn = this; + Log.Debug($"Pawn: height: {Controller.height}, center {center}, radius: {Controller.radius}, skinWidth: {Controller.skinWidth}"); } @@ -114,22 +116,39 @@ public void MaintainPosition() public void Unregister() { - if (virtualCyclops) + if (cyclops) { if (isLocalPlayer) { - virtualCyclops.Cyclops.OnLocalPlayerExit(); + cyclops.OnLocalPlayerExit(); } else { - virtualCyclops.Cyclops.OnPlayerExit((RemotePlayer)player); + cyclops.OnPlayerExit((RemotePlayer)player); } } } + /// + /// Replicates openable being blocked only if the pawn causing the block is in the cyclops associated to the virtual one. + /// + public void BlockOpenable(Openable openable, bool blockState) + { + if (cyclops.Virtual) + { + openable.blocked = blockState; + cyclops.Virtual.ReplicateBlock(openable, blockState); + } + } + public void Terminate() { controllers.Remove(Controller); GameObject.Destroy(Handle); } } + +public class CyclopsPawnIdentifier : MonoBehaviour +{ + public CyclopsPawn Pawn; +} diff --git a/NitroxClient/GameLogic/Spawning/Bases/ModuleEntitySpawner.cs b/NitroxClient/GameLogic/Spawning/Bases/ModuleEntitySpawner.cs index ea57a91a1e..a7b81298ad 100644 --- a/NitroxClient/GameLogic/Spawning/Bases/ModuleEntitySpawner.cs +++ b/NitroxClient/GameLogic/Spawning/Bases/ModuleEntitySpawner.cs @@ -82,7 +82,7 @@ public static IEnumerator RestoreModule(Transform parent, ModuleEntity moduleEnt moduleTransform.localScale = moduleEntity.Transform.LocalScale.ToUnity(); ApplyModuleData(moduleEntity, moduleObject, result); - if (parent && parent.TryGetComponent(out NitroxCyclops nitroxCyclops)) + if (parent && parent.TryGetComponent(out NitroxCyclops nitroxCyclops) && nitroxCyclops.Virtual) { nitroxCyclops.Virtual.ReplicateConstructable(moduleObject.GetComponent()); } diff --git a/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs b/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs index 61463d6aef..97abd146e3 100644 --- a/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs +++ b/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs @@ -74,9 +74,9 @@ public void Initialize(GroundMotor reference) RecalculateConstants(); } - public void SetCyclops(SubRoot subRoot, CyclopsPawn pawn) + public void SetCyclops(NitroxCyclops cyclops, SubRoot subRoot, CyclopsPawn pawn) { - cyclops = subRoot.GetComponent(); + this.cyclops = cyclops; sub = subRoot; realAxis = sub.subAxis; virtualAxis = cyclops.Virtual.axis; diff --git a/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs b/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs index f84a7b2721..767d09497b 100644 --- a/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs +++ b/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using NitroxClient.GameLogic; using NitroxClient.GameLogic.PlayerLogic; using NitroxClient.GameLogic.PlayerLogic.PlayerModel.Abstract; @@ -10,7 +11,7 @@ namespace NitroxClient.MonoBehaviours.Cyclops; /// public class NitroxCyclops : MonoBehaviour { - public VirtualCyclops Virtual { get; private set; } + public VirtualCyclops Virtual; private CyclopsMotor cyclopsMotor; private SubRoot subRoot; private SubControl subControl; @@ -20,7 +21,7 @@ public class NitroxCyclops : MonoBehaviour private CharacterController controller; private int ballasts; - public SubControl.Mode Mode; + public readonly Dictionary Pawns = []; public void Start() { @@ -36,18 +37,6 @@ public void Start() UWE.Utils.SetIsKinematicAndUpdateInterpolation(rigidbody, false, true); GetComponent().enabled = false; - SetReceiving(); - - Virtual = VirtualCyclops.CreateVirtualInstance(gameObject); - } - - /// - /// Triggered by sending a "OnKill" message when cyclops is destroyed. - /// This might need to be adapted once the "restore" command is synced (for now a destroyed cyclops can't be restored) - /// - public void OnKill() - { - VirtualCyclops.Terminate(gameObject); } /// @@ -64,47 +53,52 @@ public void RemoveAllPlayers() } /// - /// Parents local player to the cyclops and registers it in the virtual cyclops. + /// Parents local player to the cyclops and registers it in the current cyclops. /// public void OnLocalPlayerEnter() { + Virtual = VirtualCyclops.Instance; + Virtual.SetCurrentCyclops(this); + Player.mainObject.transform.parent = subRoot.transform; - CyclopsPawn pawn = Virtual.AddPawnForPlayer(this.Resolve()); - cyclopsMotor.SetCyclops(subRoot, pawn); + CyclopsPawn pawn = AddPawnForPlayer(this.Resolve()); + cyclopsMotor.SetCyclops(this, subRoot, pawn); cyclopsMotor.ToggleCyclopsMotor(true); } /// - /// Unregisters the local player from the cyclops (and from the virtual one). Ensures the player is not weirdly rotated when it leaves the cyclops. + /// Unregisters the local player from the current cyclops. Ensures the player is not weirdly rotated when it leaves the cyclops. /// public void OnLocalPlayerExit() { - Virtual.RemovePawnForPlayer(this.Resolve()); + RemovePawnForPlayer(this.Resolve()); Player.main.transform.parent = null; Player.main.transform.rotation = Quaternion.identity; cyclopsMotor.ToggleCyclopsMotor(false); + + Virtual.SetCurrentCyclops(null); } /// - /// Registers a remote player in the virtual cyclops. + /// Registers a remote player for it to get a pawn in the current cyclops. /// public void OnPlayerEnter(RemotePlayer remotePlayer) { - remotePlayer.Pawn = Virtual.AddPawnForPlayer(remotePlayer); + remotePlayer.Pawn = AddPawnForPlayer(remotePlayer); } /// - /// Unregisters a remote player from the virtual cyclops. + /// Unregisters a remote player from the current cyclops. /// public void OnPlayerExit(RemotePlayer remotePlayer) { - Virtual.RemovePawnForPlayer(remotePlayer); + RemovePawnForPlayer(remotePlayer); remotePlayer.Pawn = null; } public void MaintainPawns() { - foreach (CyclopsPawn pawn in Virtual.Pawns.Values) + foreach (CyclopsPawn pawn in Pawns.Values) { if (pawn.MaintainPredicate()) { @@ -113,6 +107,25 @@ public void MaintainPawns() } } + public CyclopsPawn AddPawnForPlayer(INitroxPlayer player) + { + if (!Pawns.TryGetValue(player, out CyclopsPawn pawn)) + { + pawn = new(player, this); + Pawns.Add(player, pawn); + } + return pawn; + } + + public void RemovePawnForPlayer(INitroxPlayer player) + { + if (Pawns.TryGetValue(player, out CyclopsPawn pawn)) + { + pawn.Terminate(); + } + Pawns.Remove(player); + } + public void SetBroadcasting() { worldForces.OnDisable(); diff --git a/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs b/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs index ce7ee76075..d918c22226 100644 --- a/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs +++ b/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs @@ -1,8 +1,6 @@ using System.Collections.Generic; using System.Linq; using NitroxClient.Communication; -using NitroxClient.GameLogic; -using NitroxClient.GameLogic.PlayerLogic.PlayerModel.Abstract; using NitroxModel.Packets; using UnityEngine; @@ -14,16 +12,13 @@ namespace NitroxClient.MonoBehaviours.Cyclops; /// public class VirtualCyclops : MonoBehaviour { - private static GameObject Prefab; - private static float Offset; + public static VirtualCyclops Instance; public const string NAME = "VirtualCyclops"; - public static readonly Dictionary VirtualCyclopsByObject = []; - - private readonly Dictionary openableByName = []; + private readonly Dictionary virtualOpenableByName = []; + private readonly Dictionary realOpenableByName = []; private readonly Dictionary virtualConstructableByRealGameObject = []; private readonly Dictionary cacheColliderCopy = []; - public readonly Dictionary Pawns = []; public NitroxCyclops Cyclops; public Transform axis; @@ -33,15 +28,23 @@ public class VirtualCyclops : MonoBehaviour public static void Initialize() { - CreateVirtualPrefab(); + CreateVirtualCyclops(); + Multiplayer.OnAfterMultiplayerEnd += Dispose; + } + + public static void Dispose() + { + Destroy(Instance.gameObject); + Instance = null; + Multiplayer.OnAfterMultiplayerEnd -= Dispose; } /// /// Initializes the object with reduced utility to ensure the virtual cyclops won't be eating too much performance. /// - public static void CreateVirtualPrefab() + public static void CreateVirtualCyclops() { - if (Prefab) + if (Instance) { return; } @@ -49,109 +52,108 @@ public static void CreateVirtualPrefab() LightmappedPrefabs.main.RequestScenePrefab("cyclops", (cyclopsPrefab) => { SubConsoleCommand.main.OnSubPrefabLoaded(cyclopsPrefab); - Prefab = SubConsoleCommand.main.GetLastCreatedSub(); - Prefab.name = NAME; - Prefab.GetComponent().enabled = false; - Prefab.transform.parent = null; - - GameObject.Destroy(Prefab.GetComponent()); - GameObject.Destroy(Prefab.GetComponent()); - Prefab.AddComponent(); - Prefab.SetActive(false); + GameObject model = SubConsoleCommand.main.GetLastCreatedSub(); + model.name = NAME; + LargeWorldEntity.Register(model); + Vector3 position = Vector3.up * 500; + Quaternion rotation = Quaternion.identity; + model.transform.position = position; + model.transform.rotation = rotation; + + Instance = model.AddComponent(); + + Instance.axis = model.GetComponent().subAxis; + + GameObject.Destroy(model.GetComponent()); + GameObject.Destroy(model.GetComponent()); + + Instance.InitialPosition = position; + Instance.InitialRotation = rotation; + Instance.rigidbody = Instance.GetComponent(); + Instance.rigidbody.constraints = RigidbodyConstraints.FreezeAll; + + model.GetComponent().enabled = false; + model.GetComponent().lockInterpolation = false; + model.GetComponent().stabilizerEnabled = false; + model.GetComponent().isKinematic = true; + + Instance.RegisterVirtualOpenables(); + Instance.ToggleRenderers(false); + model.SetActive(true); }); } - /// - /// Instantiates the object associated with a regular cyclops and links references where required. - /// - public static VirtualCyclops CreateVirtualInstance(GameObject cyclopsObject) + public void Populate() { - if (!VirtualCyclopsByObject.TryGetValue(cyclopsObject, out VirtualCyclops virtualCyclops)) + foreach (Constructable constructable in Cyclops.GetComponentsInChildren(true)) { - Vector3 position = Vector3.up * 500 + Vector3.right * (Offset - 100); - Offset += 10; - Quaternion rotation = Quaternion.identity; + ReplicateConstructable(constructable); + } - GameObject instance = GameObject.Instantiate(Prefab, position, rotation, false); - instance.name = NAME; - LargeWorldEntity.Register(instance); - virtualCyclops = instance.GetComponent(); - virtualCyclops.Cyclops = cyclopsObject.GetComponent(); - virtualCyclops.axis = instance.GetComponent().subAxis; - virtualCyclops.RegisterOpenables(); - virtualCyclops.InitialPosition = position; - virtualCyclops.InitialRotation = rotation; - virtualCyclops.rigidbody = instance.GetComponent(); - virtualCyclops.rigidbody.constraints = RigidbodyConstraints.FreezeAll; - - instance.GetComponent().enabled = false; - instance.GetComponent().lockInterpolation = false; - instance.GetComponent().stabilizerEnabled = false; - instance.GetComponent().isKinematic = true; - - instance.SetActive(true); - virtualCyclops.ToggleRenderers(false); - VirtualCyclopsByObject.Add(cyclopsObject, virtualCyclops); + foreach (Openable openable in Cyclops.GetComponentsInChildren(true)) + { + openable.blocked = false; + ReplicateOpening(openable, openable.isOpen); + realOpenableByName.Add(openable.name, openable); } - return virtualCyclops; } - /// - /// Destroys the virtual cyclops instance associated with a regular cyclops object. - /// - public static void Terminate(GameObject cyclopsObject) + public void Depopulate() { - if (VirtualCyclopsByObject.TryGetValue(cyclopsObject, out VirtualCyclops associatedVirtualCyclops)) + foreach (GameObject virtualObject in virtualConstructableByRealGameObject.Values) + { + Destroy(virtualObject); + } + virtualConstructableByRealGameObject.Clear(); + + foreach (Openable openable in realOpenableByName.Values) { - Destroy(associatedVirtualCyclops.gameObject); - VirtualCyclopsByObject.Remove(cyclopsObject); + openable.blocked = false; } + realOpenableByName.Clear(); } - public void Update() + public void SetCurrentCyclops(NitroxCyclops nitroxCyclops) { - transform.position = InitialPosition; - transform.rotation = InitialRotation; - } + if (Cyclops) + { + Cyclops.Virtual = null; + Depopulate(); + Cyclops = null; + } - public void ToggleRenderers(bool toggled) - { - foreach (Renderer renderer in transform.GetComponentsInChildren(true)) + Cyclops = nitroxCyclops; + if (Cyclops) { - renderer.enabled = toggled; + Populate(); } } - public CyclopsPawn AddPawnForPlayer(INitroxPlayer player) + public void Update() { - if (!Pawns.TryGetValue(player, out CyclopsPawn pawn)) - { - pawn = new(player, this, Cyclops.transform); - Pawns.Add(player, pawn); - } - return pawn; + transform.position = InitialPosition; + transform.rotation = InitialRotation; } - public void RemovePawnForPlayer(INitroxPlayer player) + public void ToggleRenderers(bool toggled) { - if (Pawns.TryGetValue(player, out CyclopsPawn pawn)) + foreach (Renderer renderer in transform.GetComponentsInChildren(true)) { - pawn.Terminate(); + renderer.enabled = toggled; } - Pawns.Remove(player); } - public void RegisterOpenables() + public void RegisterVirtualOpenables() { foreach (Openable openable in transform.GetComponentsInChildren(true)) { - openableByName.Add(openable.name, openable); + virtualOpenableByName.Add(openable.name, openable); } } public void ReplicateOpening(Openable openable, bool openState) { - if (openableByName.TryGetValue(openable.name, out Openable virtualOpenable)) + if (virtualOpenableByName.TryGetValue(openable.name, out Openable virtualOpenable)) { using (PacketSuppressor.Suppress()) { @@ -160,6 +162,14 @@ public void ReplicateOpening(Openable openable, bool openState) } } + public void ReplicateBlock(Openable openable, bool blockState) + { + if (realOpenableByName.TryGetValue(openable.name, out Openable realOpenable)) + { + realOpenable.blocked = blockState; + } + } + public void ReplicateConstructable(Constructable constructable) { if (virtualConstructableByRealGameObject.ContainsKey(constructable.gameObject)) @@ -167,7 +177,6 @@ public void ReplicateConstructable(Constructable constructable) return; } GameObject colliderCopy = CreateColliderCopy(constructable.gameObject, constructable.techType); - // WorldPositionStays is set to false so we keep the same local parameters colliderCopy.transform.parent = transform; colliderCopy.transform.localPosition = constructable.transform.localPosition; colliderCopy.transform.localRotation = constructable.transform.localRotation; @@ -186,6 +195,8 @@ public GameObject CreateColliderCopy(GameObject realObject, TechType techType) return GameObject.Instantiate(colliderCopy); } colliderCopy = new GameObject($"{realObject.name}-collidercopy"); + // This will act as a prefab but will stay in the material world so we put it out of hands in the meantime + colliderCopy.transform.position = Vector3.up * 1000 + Vector3.right * 10 * cacheColliderCopy.Count; Transform transform = realObject.transform; diff --git a/NitroxClient/MonoBehaviours/MultiplayerVehicleControl.cs b/NitroxClient/MonoBehaviours/MultiplayerVehicleControl.cs index d2cd0e5f50..eb50337982 100644 --- a/NitroxClient/MonoBehaviours/MultiplayerVehicleControl.cs +++ b/NitroxClient/MonoBehaviours/MultiplayerVehicleControl.cs @@ -38,7 +38,6 @@ protected virtual void FixedUpdate() SmoothPosition.FixedUpdate(); SmoothVelocity.FixedUpdate(); - //rigidbody.isKinematic = false; // we should maybe find a way to remove UWE's FreezeRigidBodyWhenFar component...tried removing it but caused a bunch of issues. rigidbody.velocity = MovementHelper.GetCorrectedVelocity(SmoothPosition.Current, SmoothVelocity.Current, gameObject, Time.fixedDeltaTime); SmoothRotation.FixedUpdate(); SmoothAngularVelocity.FixedUpdate(); diff --git a/NitroxPatcher/Patches/Dynamic/Builder_TryPlace_Patch.cs b/NitroxPatcher/Patches/Dynamic/Builder_TryPlace_Patch.cs index 902242fd3e..739c5cefcc 100644 --- a/NitroxPatcher/Patches/Dynamic/Builder_TryPlace_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/Builder_TryPlace_Patch.cs @@ -95,7 +95,7 @@ public static void GhostCreated(Constructable constructable) module.ParentId = parentId; Resolve().Send(new PlaceModule(module)); - if (constructable.transform.parent && constructable.transform.parent.TryGetComponent(out NitroxCyclops nitroxCyclops)) + if (constructable.transform.parent && constructable.transform.parent.TryGetComponent(out NitroxCyclops nitroxCyclops) && nitroxCyclops.Virtual) { nitroxCyclops.Virtual.ReplicateConstructable(constructable); } diff --git a/NitroxPatcher/Patches/Dynamic/CyclopsDestructionEvent_OnConsoleCommand_Patch.cs b/NitroxPatcher/Patches/Dynamic/CyclopsDestructionEvent_OnConsoleCommand_Patch.cs index a37d36372a..4704e909ba 100644 --- a/NitroxPatcher/Patches/Dynamic/CyclopsDestructionEvent_OnConsoleCommand_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/CyclopsDestructionEvent_OnConsoleCommand_Patch.cs @@ -14,7 +14,6 @@ public sealed class CyclopsDestructionEvent_OnConsoleCommand_Patch : NitroxPatch public static bool PrefixRestore() { - // TODO: If this is implemented someday, adapt behaviour of VirtualCyclops to not be deleted when a cyclops is destroyed Log.InGame(Language.main.Get("Nitrox_CommandNotAvailable")); return false; } diff --git a/NitroxPatcher/Patches/Dynamic/Openable_OnTriggerEnter_Patch.cs b/NitroxPatcher/Patches/Dynamic/Openable_OnTriggerEnter_Patch.cs new file mode 100644 index 0000000000..e7f84452be --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/Openable_OnTriggerEnter_Patch.cs @@ -0,0 +1,22 @@ +using System.Reflection; +using NitroxClient.GameLogic; +using NitroxModel.Helper; +using UnityEngine; + +namespace NitroxPatcher.Patches.Dynamic; + +/// +/// Allow for openables to detect collisions with pawns in the virtual cyclops. +/// +public sealed partial class Openable_OnTriggerEnter_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((Openable t) => t.OnTriggerEnter(default)); + + public static void Prefix(Openable __instance, Collider collider) + { + if (collider.TryGetComponent(out CyclopsPawnIdentifier identifier)) + { + identifier.Pawn.BlockOpenable(__instance, true); + } + } +} diff --git a/NitroxPatcher/Patches/Dynamic/Openable_OnTriggerExit_Patch.cs b/NitroxPatcher/Patches/Dynamic/Openable_OnTriggerExit_Patch.cs new file mode 100644 index 0000000000..247fe7cf68 --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/Openable_OnTriggerExit_Patch.cs @@ -0,0 +1,22 @@ +using System.Reflection; +using NitroxClient.GameLogic; +using NitroxModel.Helper; +using UnityEngine; + +namespace NitroxPatcher.Patches.Dynamic; + +/// +/// Allow for openables to detect collisions with pawns in the virtual cyclops. +/// +public sealed partial class Openable_OnTriggerExit_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((Openable t) => t.OnTriggerExit(default)); + + public static void Prefix(Openable __instance, Collider collider) + { + if (collider.TryGetComponent(out CyclopsPawnIdentifier identifier)) + { + identifier.Pawn.BlockOpenable(__instance, false); + } + } +} diff --git a/NitroxPatcher/Patches/Dynamic/Openable_PlayOpenAnimation_Patch.cs b/NitroxPatcher/Patches/Dynamic/Openable_PlayOpenAnimation_Patch.cs index 35eb4d044c..08e20c52bc 100644 --- a/NitroxPatcher/Patches/Dynamic/Openable_PlayOpenAnimation_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/Openable_PlayOpenAnimation_Patch.cs @@ -16,11 +16,17 @@ public sealed partial class Openable_PlayOpenAnimation_Patch : NitroxPatch, IDyn public static bool Prefix(Openable __instance, bool openState, float duration) { - if (__instance.TryGetComponentInParent(out NitroxCyclops nitroxCyclops)) + if (__instance.TryGetComponentInParent(out NitroxCyclops nitroxCyclops) && nitroxCyclops.Virtual) { nitroxCyclops.Virtual.ReplicateOpening(__instance, openState); } + // Do not try to sync + if (__instance.GetComponentInParent()) + { + return true; + } + if (__instance.isOpen != openState && __instance.TryGetIdOrWarn(out NitroxId id)) { Resolve().OpenableStateChanged(id, openState, duration); From b426e3669d371cbbf0fc101f87b697248fe53a40 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Wed, 7 Aug 2024 15:00:01 +0200 Subject: [PATCH 07/15] Log errors from tasks by .ContinueWith Co-authored-by: Measurity --- .../MonoBehaviours/Discord/DiscordClient.cs | 4 +-- NitroxLauncher/MainWindow.xaml.cs | 2 +- NitroxModel/Extensions.cs | 25 +++++++++++++++++++ .../Persistent/uGUI_MainMenu_Start_Patch.cs | 6 ++++- NitroxServer/Server.cs | 8 +----- 5 files changed, 34 insertions(+), 11 deletions(-) diff --git a/NitroxClient/MonoBehaviours/Discord/DiscordClient.cs b/NitroxClient/MonoBehaviours/Discord/DiscordClient.cs index 8fb6a0dd1b..98aa019308 100644 --- a/NitroxClient/MonoBehaviours/Discord/DiscordClient.cs +++ b/NitroxClient/MonoBehaviours/Discord/DiscordClient.cs @@ -43,7 +43,7 @@ private void StartDiscordHook() discord = new DiscordGameSDKWrapper.Discord(CLIENT_ID, (ulong)CreateFlags.NoRequireDiscord); discord.SetLogHook(DiscordGameSDKWrapper.LogLevel.Debug, (level, message) => Log.Write((NitroxModel.Logger.LogLevel)level, $"[Discord] {message}")); activityManager = discord.GetActivityManager(); - + activityManager.RegisterSteam((uint)GameInfo.Subnautica.SteamAppId); activityManager.OnActivityJoinRequest += ActivityJoinRequest; activityManager.OnActivityJoin += ActivityJoin; @@ -109,7 +109,7 @@ private void ActivityJoin(string secret) string[] splitSecret = secret.Split(':'); string ip = string.Join(":", splitSecret.Take(splitSecret.Length - 1)); string port = splitSecret.Last(); - _ = MainMenuMultiplayerPanel.OpenJoinServerMenuAsync(ip, port); + MainMenuMultiplayerPanel.OpenJoinServerMenuAsync(ip, port).ContinueWithHandleError(); } private void ActivityJoinRequest(ref User user) diff --git a/NitroxLauncher/MainWindow.xaml.cs b/NitroxLauncher/MainWindow.xaml.cs index b6fbaab49e..ab54094dd1 100644 --- a/NitroxLauncher/MainWindow.xaml.cs +++ b/NitroxLauncher/MainWindow.xaml.cs @@ -118,7 +118,7 @@ public MainWindow() { if (launchArgs[i].Equals("-instantlaunch", StringComparison.OrdinalIgnoreCase) && launchArgs.Length > i + 1) { - _ = LauncherLogic.Instance.StartMultiplayerAsync(); + LauncherLogic.Instance.StartMultiplayerAsync().ContinueWithHandleError(); LauncherLogic.Server.StartServer(true, launchArgs[i + 1]); } } diff --git a/NitroxModel/Extensions.cs b/NitroxModel/Extensions.cs index fb12bfad55..d8cb88cfe2 100644 --- a/NitroxModel/Extensions.cs +++ b/NitroxModel/Extensions.cs @@ -2,6 +2,7 @@ using System.Buffers; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; namespace NitroxModel; @@ -85,6 +86,30 @@ public static string AsByteUnitText(this uint byteSize) return num + suf[place]; } + /// + /// Calls an action if an error happens. If null, logs the error as-is. + /// + /// + /// Use this for fire-and-forget tasks so that errors aren't hidden when they happen. + /// + public static Task ContinueWithHandleError(this Task task, Action onError = null) => + task.ContinueWith(t => + { + if (t is not { IsFaulted: true, Exception: { } ex }) + { + return; + } + + if (onError != null) + { + onError(ex); + } + else + { + Log.Error(ex); + } + }); + public static string GetFirstNonAggregateMessage(this Exception exception) => exception switch { AggregateException ex => ex.InnerExceptions.FirstOrDefault(e => e is not AggregateException)?.Message ?? ex.Message, diff --git a/NitroxPatcher/Patches/Persistent/uGUI_MainMenu_Start_Patch.cs b/NitroxPatcher/Patches/Persistent/uGUI_MainMenu_Start_Patch.cs index f9aaf54f47..876020e616 100644 --- a/NitroxPatcher/Patches/Persistent/uGUI_MainMenu_Start_Patch.cs +++ b/NitroxPatcher/Patches/Persistent/uGUI_MainMenu_Start_Patch.cs @@ -29,7 +29,11 @@ public static void Postfix() if (args[i].Equals("-instantlaunch", StringComparison.OrdinalIgnoreCase) && args.Length > i + 1) { Log.Info($"Detected instant launch, connecting to 127.0.0.1:11000"); - _ = MainMenuMultiplayerPanel.OpenJoinServerMenuAsync("127.0.0.1", "11000", true); + MainMenuMultiplayerPanel.OpenJoinServerMenuAsync("127.0.0.1", "11000", true).ContinueWithHandleError(ex => + { + Log.Error(ex); + Log.InGame(ex.Message); + }); } } } diff --git a/NitroxServer/Server.cs b/NitroxServer/Server.cs index 8588fed275..4f9164486f 100644 --- a/NitroxServer/Server.cs +++ b/NitroxServer/Server.cs @@ -189,13 +189,7 @@ public bool Start(CancellationTokenSource cancellationToken) return false; } - LogHowToConnectAsync().ContinueWith(t => - { - if (t is { IsFaulted: true, Exception: {} ex}) - { - Log.Warn($"Failed to show how to connect: {ex.GetFirstNonAggregateMessage()}"); - } - }); + LogHowToConnectAsync().ContinueWithHandleError(ex => Log.Warn($"Failed to show how to connect: {ex.GetFirstNonAggregateMessage()}")); Log.Info($"Server is listening on port {Port} UDP"); Log.Info($"Using {serverConfig.SerializerMode} as save file serializer"); Log.InfoSensitive("Server Password: {password}", string.IsNullOrEmpty(serverConfig.ServerPassword) ? "None. Public Server." : serverConfig.ServerPassword); From e86d72bed791a4c6184658ba00850ed96db07583 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Mon, 12 Aug 2024 02:39:25 +0200 Subject: [PATCH 08/15] Added CharacterController debug drawer, improve RigidbodyDrawer, fix player movement in cyclops broadcasting, fix cyclops name and color editor screen not appearing, fix entity reparenting not setting the transform parent accordingly --- .../PlayerInCyclopsMovementProcessor.cs | 24 ++++ .../Debuggers/Drawer/DrawerManager.cs | 1 + .../Drawer/Unity/CharacterControllerDrawer.cs | 119 ++++++++++++++++++ .../Debuggers/Drawer/Unity/RigidbodyDrawer.cs | 11 +- NitroxClient/Debuggers/NetworkDebugger.cs | 2 +- NitroxClient/GameLogic/CyclopsPawn.cs | 6 +- .../PlayerPositionInitialSyncProcessor.cs | 2 +- NitroxClient/GameLogic/RemotePlayer.cs | 37 ++++-- .../Processor/CyclopsMetadataProcessor.cs | 2 +- .../Cyclops/CyclopsMotorGroundChecker.cs | 1 - .../Cyclops/MultiplayerCyclops.cs | 6 + .../MonoBehaviours/Cyclops/NitroxCyclops.cs | 93 ++++++++++++-- .../PlayerMovementBroadcaster.cs | 22 +++- .../Packets/PlayerInCyclopsMovement.cs | 22 ++++ .../PlayerInCyclopsMovementProcessor.cs | 36 ++++++ .../GameLogic/Entities/EntityRegistry.cs | 8 ++ 16 files changed, 361 insertions(+), 31 deletions(-) create mode 100644 NitroxClient/Communication/Packets/Processors/PlayerInCyclopsMovementProcessor.cs create mode 100644 NitroxClient/Debuggers/Drawer/Unity/CharacterControllerDrawer.cs create mode 100644 NitroxModel/Packets/PlayerInCyclopsMovement.cs create mode 100644 NitroxServer/Communication/Packets/Processors/PlayerInCyclopsMovementProcessor.cs diff --git a/NitroxClient/Communication/Packets/Processors/PlayerInCyclopsMovementProcessor.cs b/NitroxClient/Communication/Packets/Processors/PlayerInCyclopsMovementProcessor.cs new file mode 100644 index 0000000000..6e4e9d3f91 --- /dev/null +++ b/NitroxClient/Communication/Packets/Processors/PlayerInCyclopsMovementProcessor.cs @@ -0,0 +1,24 @@ +using NitroxClient.Communication.Packets.Processors.Abstract; +using NitroxClient.GameLogic; +using NitroxModel.Packets; +using NitroxModel_Subnautica.DataStructures; + +namespace NitroxClient.Communication.Packets.Processors; + +public class PlayerInCyclopsMovementProcessor : ClientPacketProcessor +{ + private readonly PlayerManager remotePlayerManager; + + public PlayerInCyclopsMovementProcessor(PlayerManager remotePlayerManager) + { + this.remotePlayerManager = remotePlayerManager; + } + + public override void Process(PlayerInCyclopsMovement movement) + { + if (remotePlayerManager.TryFind(movement.PlayerId, out RemotePlayer remotePlayer) && remotePlayer.Pawn != null) + { + remotePlayer.UpdatePositionInCyclops(movement.LocalPosition.ToUnity(), movement.LocalRotation.ToUnity()); + } + } +} diff --git a/NitroxClient/Debuggers/Drawer/DrawerManager.cs b/NitroxClient/Debuggers/Drawer/DrawerManager.cs index fffe2e7791..b13485cfb2 100644 --- a/NitroxClient/Debuggers/Drawer/DrawerManager.cs +++ b/NitroxClient/Debuggers/Drawer/DrawerManager.cs @@ -65,6 +65,7 @@ public DrawerManager(SceneDebugger sceneDebugger) AddDrawer(unityEventDrawer); AddDrawer>(unityEventDrawer); AddDrawer(); + AddDrawer(new(vectorDrawer)); AddEditor(vectorDrawer); AddEditor(vectorDrawer); diff --git a/NitroxClient/Debuggers/Drawer/Unity/CharacterControllerDrawer.cs b/NitroxClient/Debuggers/Drawer/Unity/CharacterControllerDrawer.cs new file mode 100644 index 0000000000..36e1c08fdb --- /dev/null +++ b/NitroxClient/Debuggers/Drawer/Unity/CharacterControllerDrawer.cs @@ -0,0 +1,119 @@ +using NitroxModel.Helper; +using UnityEngine; + +namespace NitroxClient.Debuggers.Drawer.Unity; + +/// +/// Draws a component on the gameobjects in the +/// +public class CharacterControllerDrawer : IDrawer +{ + private readonly VectorDrawer vectorDrawer; + private const float LABEL_WIDTH = 120; + private const float VALUE_MAX_WIDTH = 405; + + public CharacterControllerDrawer(VectorDrawer vectorDrawer) + { + Validate.NotNull(vectorDrawer); + + this.vectorDrawer = vectorDrawer; + } + + public void Draw(CharacterController cc) + { + using (new GUILayout.HorizontalScope()) + { + GUILayout.Label("Slope Limit (°)", NitroxGUILayout.DrawerLabel, GUILayout.Width(LABEL_WIDTH)); + NitroxGUILayout.Separator(); + cc.slopeLimit = NitroxGUILayout.FloatField(cc.slopeLimit, VALUE_MAX_WIDTH); + } + + GUILayout.Space(10); + + using (new GUILayout.HorizontalScope()) + { + GUILayout.Label("Step Offset", NitroxGUILayout.DrawerLabel, GUILayout.Width(LABEL_WIDTH)); + NitroxGUILayout.Separator(); + cc.stepOffset = NitroxGUILayout.FloatField(cc.stepOffset, VALUE_MAX_WIDTH); + } + + GUILayout.Space(10); + + using (new GUILayout.HorizontalScope()) + { + GUILayout.Label("Skin Width", NitroxGUILayout.DrawerLabel, GUILayout.Width(LABEL_WIDTH)); + NitroxGUILayout.Separator(); + cc.skinWidth = NitroxGUILayout.FloatField(cc.skinWidth, VALUE_MAX_WIDTH); + } + + GUILayout.Space(10); + + using (new GUILayout.HorizontalScope()) + { + GUILayout.Label("Min Move Distance", NitroxGUILayout.DrawerLabel, GUILayout.Width(LABEL_WIDTH)); + NitroxGUILayout.Separator(); + cc.minMoveDistance = NitroxGUILayout.FloatField(cc.minMoveDistance, VALUE_MAX_WIDTH); + } + + GUILayout.Space(10); + + using (new GUILayout.HorizontalScope()) + { + GUILayout.Label("Center", NitroxGUILayout.DrawerLabel, GUILayout.Width(LABEL_WIDTH)); + NitroxGUILayout.Separator(); + cc.center = vectorDrawer.Draw(cc.center); + } + + GUILayout.Space(10); + + using (new GUILayout.HorizontalScope()) + { + GUILayout.Label("Radius", NitroxGUILayout.DrawerLabel, GUILayout.Width(LABEL_WIDTH)); + NitroxGUILayout.Separator(); + cc.radius = NitroxGUILayout.FloatField(cc.radius, VALUE_MAX_WIDTH); + } + + GUILayout.Space(10); + + using (new GUILayout.HorizontalScope()) + { + GUILayout.Label("Height", NitroxGUILayout.DrawerLabel, GUILayout.Width(LABEL_WIDTH)); + NitroxGUILayout.Separator(); + cc.height = NitroxGUILayout.FloatField(cc.height, VALUE_MAX_WIDTH); + } + + using (new GUILayout.HorizontalScope()) + { + GUILayout.Label("Velocity", NitroxGUILayout.DrawerLabel, GUILayout.Width(LABEL_WIDTH)); + NitroxGUILayout.Separator(); + vectorDrawer.Draw(cc.velocity); + } + + GUILayout.Space(10); + + using (new GUILayout.HorizontalScope()) + { + GUILayout.Label("Is Grounded", NitroxGUILayout.DrawerLabel, GUILayout.Width(LABEL_WIDTH)); + NitroxGUILayout.Separator(); + NitroxGUILayout.BoolField(cc.isGrounded, VALUE_MAX_WIDTH); + } + + GUILayout.Space(10); + + using (new GUILayout.HorizontalScope()) + { + GUILayout.Label("Detect Collisions", NitroxGUILayout.DrawerLabel, GUILayout.Width(LABEL_WIDTH)); + NitroxGUILayout.Separator(); + cc.detectCollisions = NitroxGUILayout.BoolField(cc.detectCollisions, VALUE_MAX_WIDTH); + } + + GUILayout.Space(10); + + using (new GUILayout.HorizontalScope()) + { + GUILayout.Label("Enable Overlap Recovery", NitroxGUILayout.DrawerLabel, GUILayout.Width(LABEL_WIDTH + 30)); + NitroxGUILayout.Separator(); + cc.enableOverlapRecovery = NitroxGUILayout.BoolField(cc.enableOverlapRecovery, VALUE_MAX_WIDTH); + } + } +} diff --git a/NitroxClient/Debuggers/Drawer/Unity/RigidbodyDrawer.cs b/NitroxClient/Debuggers/Drawer/Unity/RigidbodyDrawer.cs index 3d780a69d4..452d823446 100644 --- a/NitroxClient/Debuggers/Drawer/Unity/RigidbodyDrawer.cs +++ b/NitroxClient/Debuggers/Drawer/Unity/RigidbodyDrawer.cs @@ -1,4 +1,4 @@ -using NitroxModel.Helper; +using NitroxModel.Helper; using UnityEngine; namespace NitroxClient.Debuggers.Drawer.Unity; @@ -105,5 +105,14 @@ public void Draw(Rigidbody rb) NitroxGUILayout.Separator(); vectorDrawer.Draw(rb.angularVelocity, new VectorDrawer.DrawOptions(VALUE_MAX_WIDTH)); } + + GUILayout.Space(10); + + using (new GUILayout.HorizontalScope()) + { + GUILayout.Label("Detect Collisions", NitroxGUILayout.DrawerLabel, GUILayout.Width(LABEL_WIDTH)); + NitroxGUILayout.Separator(); + rb.detectCollisions = NitroxGUILayout.BoolField(rb.detectCollisions, NitroxGUILayout.VALUE_WIDTH); + } } } diff --git a/NitroxClient/Debuggers/NetworkDebugger.cs b/NitroxClient/Debuggers/NetworkDebugger.cs index 7bec20fbf9..abd37c73b1 100644 --- a/NitroxClient/Debuggers/NetworkDebugger.cs +++ b/NitroxClient/Debuggers/NetworkDebugger.cs @@ -19,7 +19,7 @@ public class NetworkDebugger : BaseDebugger, INetworkDebugger { nameof(PlayerMovement), nameof(EntityTransformUpdates), nameof(PlayerStats), nameof(SpawnEntities), nameof(VehicleMovement), nameof(PlayerCinematicControllerCall), nameof(FMODAssetPacket), nameof(FMODEventInstancePacket), nameof(FMODCustomEmitterPacket), nameof(FMODStudioEmitterPacket), nameof(FMODCustomLoopingEmitterPacket), - nameof(SimulationOwnershipChange), nameof(CellVisibilityChanged) + nameof(SimulationOwnershipChange), nameof(CellVisibilityChanged), nameof(PlayerInCyclopsMovement) }; private readonly List packets = new List(PACKET_STORED_COUNT); diff --git a/NitroxClient/GameLogic/CyclopsPawn.cs b/NitroxClient/GameLogic/CyclopsPawn.cs index 7e295359ee..e9c8279e5b 100644 --- a/NitroxClient/GameLogic/CyclopsPawn.cs +++ b/NitroxClient/GameLogic/CyclopsPawn.cs @@ -76,14 +76,12 @@ public void Initialize(string name, Vector3 localPosition) Controller.center = center; Controller.radius = playerController.controllerRadius; Controller.skinWidth = reference.skinWidth; - Controller.stepOffset = groundMotor.controllerSetup.stepOffset; - Controller.slopeLimit = groundMotor.controllerSetup.slopeLimit; + Controller.stepOffset = groundMotor.controller.stepOffset; + Controller.slopeLimit = groundMotor.controller.slopeLimit; RegisterController(); Handle.AddComponent().Pawn = this; - - Log.Debug($"Pawn: height: {Controller.height}, center {center}, radius: {Controller.radius}, skinWidth: {Controller.skinWidth}"); } public void RegisterController() diff --git a/NitroxClient/GameLogic/InitialSync/PlayerPositionInitialSyncProcessor.cs b/NitroxClient/GameLogic/InitialSync/PlayerPositionInitialSyncProcessor.cs index 3105476f6d..49b8998650 100644 --- a/NitroxClient/GameLogic/InitialSync/PlayerPositionInitialSyncProcessor.cs +++ b/NitroxClient/GameLogic/InitialSync/PlayerPositionInitialSyncProcessor.cs @@ -72,8 +72,8 @@ public override IEnumerator Process(InitialPlayerSync packet, WaitScreen.ManualW Player.main.SetCurrentSub(subRoot, true); // If the player's in a base/cyclops we don't need to wait for the world to load - Player.main.cinematicModeActive = false; Player.main.UpdateIsUnderwater(); + Player.main.cinematicModeActive = false; } private void AttachPlayerToEscapePod(NitroxId escapePodId) diff --git a/NitroxClient/GameLogic/RemotePlayer.cs b/NitroxClient/GameLogic/RemotePlayer.cs index bd927759f9..5c9c0577a5 100644 --- a/NitroxClient/GameLogic/RemotePlayer.cs +++ b/NitroxClient/GameLogic/RemotePlayer.cs @@ -129,6 +129,12 @@ public void Detach() public void UpdatePosition(Vector3 position, Vector3 velocity, Quaternion bodyRotation, Quaternion aimingRotation) { + // It might happen that we get movement packets before the body is actually initialized which is not too bad + if (!Body) + { + return; + } + Body.SetActive(true); // When receiving movement packets, a player can not be controlling a vehicle (they can walk through subroots though). @@ -142,16 +148,6 @@ public void UpdatePosition(Vector3 position, Vector3 velocity, Quaternion bodyRo // If in a subroot the position will be relative to the subroot if (SubRoot && !SubRoot.isBase) { - if (Pawn != null) - { - Pawn.Handle.transform.localPosition = SubRoot.transform.InverseTransformPoint(position); - Pawn.Handle.transform.localRotation = bodyRotation; - - AnimationController.AimingRotation = aimingRotation; - AnimationController.UpdatePlayerAnimations = true; - return; - } - Quaternion vehicleAngle = SubRoot.transform.rotation; position = vehicleAngle * position; position += SubRoot.transform.position; @@ -163,6 +159,27 @@ public void UpdatePosition(Vector3 position, Vector3 velocity, Quaternion bodyRo RigidBody.angularVelocity = MovementHelper.GetCorrectedAngularVelocity(bodyRotation, Vector3.zero, Body, Time.fixedDeltaTime); } + public void UpdatePositionInCyclops(Vector3 localPosition, Quaternion localRotation) + { + if (Pawn == null) + { + return; + } + + SetVehicle(null); + SetPilotingChair(null); + + AnimationController.AimingRotation = localRotation; + AnimationController.UpdatePlayerAnimations = true; + AnimationController.Velocity = (localPosition - Pawn.Handle.transform.localPosition) / Time.fixedDeltaTime; + + Pawn.Handle.transform.localPosition = localPosition; + Pawn.Handle.transform.localRotation = localRotation; + + AnimationController.UpdatePlayerAnimations = true; + AnimationController.AimingRotation = localRotation; + } + public void SetPilotingChair(PilotingChair newPilotingChair) { if (PilotingChair != newPilotingChair) diff --git a/NitroxClient/GameLogic/Spawning/Metadata/Processor/CyclopsMetadataProcessor.cs b/NitroxClient/GameLogic/Spawning/Metadata/Processor/CyclopsMetadataProcessor.cs index 000db33213..eb75f98a99 100644 --- a/NitroxClient/GameLogic/Spawning/Metadata/Processor/CyclopsMetadataProcessor.cs +++ b/NitroxClient/GameLogic/Spawning/Metadata/Processor/CyclopsMetadataProcessor.cs @@ -46,7 +46,7 @@ private void SetEngineState(GameObject cyclops, bool isOn) if (Player.main.currentSub != engineState.subRoot) { engineState.startEngine = isOn; - engineState.subRoot.BroadcastMessage("InvokeChangeEngineState", isOn, SendMessageOptions.RequireReceiver); + engineState.subRoot.BroadcastMessage(nameof(CyclopsMotorMode.InvokeChangeEngineState), isOn, SendMessageOptions.RequireReceiver); engineState.invalidButton = true; engineState.Invoke(nameof(CyclopsEngineChangeState.ResetInvalidButton), 2.5f); } diff --git a/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotorGroundChecker.cs b/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotorGroundChecker.cs index 1a96b13965..69c4ce9a42 100644 --- a/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotorGroundChecker.cs +++ b/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotorGroundChecker.cs @@ -112,6 +112,5 @@ private void RecalculateConstants() Height = controller.height * scale.y; Radius = controller.radius * Mathf.Max(Mathf.Max(scale.x, scale.y), scale.z); SkinWidth = controller.skinWidth; - // SkinWidth: 0.08, Rad: 0.3, Height: 1.75 } } diff --git a/NitroxClient/MonoBehaviours/Cyclops/MultiplayerCyclops.cs b/NitroxClient/MonoBehaviours/Cyclops/MultiplayerCyclops.cs index 7114006918..1c2509fc42 100644 --- a/NitroxClient/MonoBehaviours/Cyclops/MultiplayerCyclops.cs +++ b/NitroxClient/MonoBehaviours/Cyclops/MultiplayerCyclops.cs @@ -53,4 +53,10 @@ internal override void SetThrottle(bool isOn) subThrottleHandlers?.ForEach(throttleHandlers => throttleHandlers.OnSubAppliedThrottle()); } } + + public override void Exit() + { + base.Exit(); + CurrentPlayer = null; + } } diff --git a/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs b/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs index 767d09497b..022bad5677 100644 --- a/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs +++ b/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using NitroxClient.GameLogic; +using NitroxClient.GameLogic.ChatUI; using NitroxClient.GameLogic.PlayerLogic; using NitroxClient.GameLogic.PlayerLogic.PlayerModel.Abstract; using UnityEngine; @@ -37,6 +38,8 @@ public void Start() UWE.Utils.SetIsKinematicAndUpdateInterpolation(rigidbody, false, true); GetComponent().enabled = false; + + FixSubNameEditor(); } /// @@ -75,6 +78,7 @@ public void OnLocalPlayerExit() Player.main.transform.parent = null; Player.main.transform.rotation = Quaternion.identity; cyclopsMotor.ToggleCyclopsMotor(false); + cyclopsMotor.Pawn = null; Virtual.SetCurrentCyclops(null); } @@ -128,7 +132,6 @@ public void RemovePawnForPlayer(INitroxPlayer player) public void SetBroadcasting() { - worldForces.OnDisable(); worldForces.enabled = true; stabilizer.stabilizerEnabled = true; } @@ -139,6 +142,57 @@ public void SetReceiving() stabilizer.stabilizerEnabled = false; } + private void FixSubNameEditor() + { + CyclopsSubNameScreen cyclopsSubNameScreen = transform.GetComponentInChildren(true); + + cyclopsSubNameScreen.gameObject.AddComponent().Initialize(this, cyclopsSubNameScreen); + } + + /// + /// With the changes to the Player's colliders, the sub name screen doesn't detect the player coming close to it. + /// The easiest workaround is to replace proximity detection by distance checks. + /// + class SubNameFixer : MonoBehaviour + { + private const float DETECTION_RANGE = 3f; + private const string ANIMATOR_PARAM = "PanelActive"; + private NitroxCyclops cyclops; + private CyclopsSubNameScreen cyclopsSubNameScreen; + private bool playerIn; + + public void Initialize(NitroxCyclops cyclops, CyclopsSubNameScreen cyclopsSubNameScreen) + { + this.cyclops = cyclops; + this.cyclopsSubNameScreen = cyclopsSubNameScreen; + } + + /// + /// Code adapted from and + /// + public void Update() + { + // Virtual is not null only when the local player is aboard + if (cyclops.Virtual && Vector3.Distance(Player.main.transform.position, transform.position) < DETECTION_RANGE) + { + if (!playerIn) + { + playerIn = true; + cyclopsSubNameScreen.animator.SetBool(ANIMATOR_PARAM, true); + cyclopsSubNameScreen.ContentOn(); + } + return; + } + + if (playerIn) + { + playerIn = false; + cyclopsSubNameScreen.animator.SetBool(ANIMATOR_PARAM, false); + cyclopsSubNameScreen.Invoke(nameof(cyclopsSubNameScreen.ContentOff), 0.5f); + } + } + } + // TODO: all of the below stuff is purely for testing and will probably get removed before merge // EXCEPT for the MaintainPawns line in Update() private Vector3 forward => subRoot.subAxis.forward; @@ -157,30 +211,49 @@ public void SetReceiving() public float Torque; public float Roll; - public void ResetAll() + public void Reset(Vector3 positionOffset) { Torqing = false; Rolling = false; rigidbody.velocity = Vector3.zero; rigidbody.angularVelocity = Vector3.zero; - transform.position = new(70f, -16f, 0f); + transform.position = new Vector3(70f, -16f, 0f) + positionOffset; transform.rotation = Quaternion.Euler(new(360f, 270f, 0f)); - Player.main.SetCurrentSub(subRoot, true); - Player.main.SetPosition(transform.position + up); - cyclopsMotor.ToggleCyclopsMotor(true); - cyclopsMotor.Pawn.SetReference(); + if (Player.main.currentSub == subRoot || !Player.main.currentSub) + { + Player.main.SetCurrentSub(subRoot, true); + Player.main.SetPosition(transform.position + up); + cyclopsMotor.ToggleCyclopsMotor(true); + cyclopsMotor.Pawn.SetReference(); + } + } + + private double latestReset; + + public void ResetAll() + { + Vector3 offset = Vector3.zero; + foreach (NitroxCyclops cyclops in LargeWorldStreamer.main.globalRoot.GetComponentsInChildren()) + { + offset += new Vector3(60f, 30f, 0); + cyclops.Reset(offset); + } - Log.InGame("Reset player"); + Log.InGame("Reset all cyclops"); } public void Update() { - if (!DevConsole.instance.state) + if (!this.Resolve().IsChatSelected && !DevConsole.instance.selected) { if (Input.GetKeyUp(KeyCode.R)) { - ResetAll(); + if (latestReset + 10d < DayNightCycle.main.timePassed) + { + latestReset = DayNightCycle.main.timePassed; + ResetAll(); + } } if (Input.GetKeyUp(KeyCode.N)) { diff --git a/NitroxClient/MonoBehaviours/PlayerMovementBroadcaster.cs b/NitroxClient/MonoBehaviours/PlayerMovementBroadcaster.cs index 24ecc7af12..35cc31af92 100644 --- a/NitroxClient/MonoBehaviours/PlayerMovementBroadcaster.cs +++ b/NitroxClient/MonoBehaviours/PlayerMovementBroadcaster.cs @@ -1,10 +1,11 @@ +using NitroxClient.Communication.Abstract; using NitroxClient.GameLogic; using NitroxModel_Subnautica.DataStructures; using NitroxModel_Subnautica.Helper; -using NitroxModel.Core; using NitroxModel.DataStructures; using NitroxModel.DataStructures.GameLogic; using NitroxModel.DataStructures.Util; +using NitroxModel.Packets; using UnityEngine; namespace NitroxClient.MonoBehaviours; @@ -28,6 +29,11 @@ public void Update() return; } + if (BroadcastPlayerInCyclopsMovement()) + { + return; + } + Vector3 currentPosition = Player.main.transform.position; Vector3 playerVelocity = Player.main.playerController.velocity; @@ -39,7 +45,7 @@ public void Update() SubRoot subRoot = Player.main.GetCurrentSub(); // If in a subroot the position will be relative to the subroot - if (subRoot && !subRoot.isBase) + if (subRoot) { // Rotate relative player position relative to the subroot (else there are problems with respawning) Transform subRootTransform = subRoot.transform; @@ -60,6 +66,18 @@ public void Update() localPlayer.BroadcastLocation(currentPosition, playerVelocity, bodyRotation, aimingRotation, vehicle); } + private bool BroadcastPlayerInCyclopsMovement() + { + if (!Player.main.isPiloting && Player.main.TryGetComponent(out CyclopsMotor cyclopsMotor) && cyclopsMotor.Pawn != null) + { + Transform pawnTransform = cyclopsMotor.Pawn.Handle.transform; + PlayerInCyclopsMovement packet = new(this.Resolve().PlayerId.Value, pawnTransform.localPosition.ToDto(), pawnTransform.localRotation.ToDto()); + this.Resolve().Send(packet); + return true; + } + return false; + } + private Optional GetVehicleMovement() { Vehicle vehicle = Player.main.GetVehicle(); diff --git a/NitroxModel/Packets/PlayerInCyclopsMovement.cs b/NitroxModel/Packets/PlayerInCyclopsMovement.cs new file mode 100644 index 0000000000..8284704fd4 --- /dev/null +++ b/NitroxModel/Packets/PlayerInCyclopsMovement.cs @@ -0,0 +1,22 @@ +using System; +using NitroxModel.DataStructures.Unity; +using NitroxModel.Networking; + +namespace NitroxModel.Packets; + +[Serializable] +public class PlayerInCyclopsMovement : Packet +{ + public ushort PlayerId { get; } + public NitroxVector3 LocalPosition { get; } + public NitroxQuaternion LocalRotation { get; } + + public PlayerInCyclopsMovement(ushort playerId, NitroxVector3 localPosition, NitroxQuaternion localRotation) + { + PlayerId = playerId; + LocalPosition = localPosition; + LocalRotation = localRotation; + DeliveryMethod = NitroxDeliveryMethod.DeliveryMethod.UNRELIABLE_SEQUENCED; + UdpChannel = UdpChannelId.PLAYER_MOVEMENT; + } +} diff --git a/NitroxServer/Communication/Packets/Processors/PlayerInCyclopsMovementProcessor.cs b/NitroxServer/Communication/Packets/Processors/PlayerInCyclopsMovementProcessor.cs new file mode 100644 index 0000000000..fceda859b3 --- /dev/null +++ b/NitroxServer/Communication/Packets/Processors/PlayerInCyclopsMovementProcessor.cs @@ -0,0 +1,36 @@ +using NitroxModel.DataStructures.GameLogic.Entities; +using NitroxModel.Packets; +using NitroxServer.Communication.Packets.Processors.Abstract; +using NitroxServer.GameLogic; +using NitroxServer.GameLogic.Entities; + +namespace NitroxServer.Communication.Packets.Processors; + +public class PlayerInCyclopsMovementProcessor : AuthenticatedPacketProcessor +{ + private readonly PlayerManager playerManager; + private readonly EntityRegistry entityRegistry; + + public PlayerInCyclopsMovementProcessor(PlayerManager playerManager, EntityRegistry entityRegistry) + { + this.playerManager = playerManager; + this.entityRegistry = entityRegistry; + } + + public override void Process(PlayerInCyclopsMovement packet, Player player) + { + if (entityRegistry.TryGetEntityById(player.PlayerContext.PlayerNitroxId, out PlayerWorldEntity playerWorldEntity)) + { + playerWorldEntity.Transform.LocalPosition = packet.LocalPosition; + playerWorldEntity.Transform.LocalRotation = packet.LocalRotation; + + player.Position = playerWorldEntity.Transform.Position; + player.Rotation = playerWorldEntity.Transform.Rotation; + playerManager.SendPacketToOtherPlayers(packet, player); + } + else + { + Log.ErrorOnce($"{nameof(PlayerWorldEntity)} couldn't be found for player {player.Name}. It is adviced the player reconnects before losing too much progression."); + } + } +} diff --git a/NitroxServer/GameLogic/Entities/EntityRegistry.cs b/NitroxServer/GameLogic/Entities/EntityRegistry.cs index 941690d002..7bbe348590 100644 --- a/NitroxServer/GameLogic/Entities/EntityRegistry.cs +++ b/NitroxServer/GameLogic/Entities/EntityRegistry.cs @@ -152,6 +152,10 @@ public void RemoveFromParent(Entity entity) { parentEntity.ChildEntities.RemoveAll(childEntity => childEntity.Id.Equals(entity.Id)); entity.ParentId = null; + if (entity is WorldEntity worldEntity) + { + worldEntity.Transform.SetParent(null, true); + } } } @@ -188,6 +192,10 @@ public void ReparentEntity(Entity entity, Entity newParent) { return; } + if (entity is WorldEntity worldEntity && newParent is WorldEntity parentWorldEntity) + { + worldEntity.Transform.SetParent(parentWorldEntity.Transform, true); + } entity.ParentId = newParent.Id; newParent.ChildEntities.Add(entity); } From 990dd36258a61a1020e865b6b45f88d1bc0f2c88 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Mon, 12 Aug 2024 12:42:35 +0200 Subject: [PATCH 09/15] Greatly improve player movement in cyclops to fit the normal one, but without the bugs --- .../MonoBehaviours/Cyclops/CyclopsMotor.cs | 101 ++++++++++++++---- 1 file changed, 81 insertions(+), 20 deletions(-) diff --git a/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs b/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs index 97abd146e3..7f47192e87 100644 --- a/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs +++ b/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs @@ -11,7 +11,7 @@ namespace NitroxClient.MonoBehaviours; public partial class CyclopsMotor : GroundMotor { public GroundMotor ActualMotor { get; private set; } - public CyclopsPawn Pawn { get; private set; } + public CyclopsPawn Pawn; private Transform body; private NitroxCyclops cyclops; @@ -24,6 +24,7 @@ public partial class CyclopsMotor : GroundMotor public float DeltaTime => Time.fixedDeltaTime; private Vector3 verticalVelocity; + private Vector3 latestVelocity; public new void Awake() { @@ -86,6 +87,7 @@ public void SetCyclops(NitroxCyclops cyclops, SubRoot subRoot, CyclopsPawn pawn) public void Setup(bool enabled) { verticalVelocity = Vector3.zero; + latestVelocity = Vector3.zero; if (enabled) { @@ -139,26 +141,66 @@ public override Vector3 UpdateMove() /// /// Simulates player movement on its pawn and update the grounded state /// + /// + /// Adapted from + /// /// Pawn's local velocity public Vector3 Move(Vector3 horizontalVelocity) { Vector3 beforePosition = Pawn.Position; - Vector3 move = (horizontalVelocity + verticalVelocity) * DeltaTime; + Vector3 velocity = new(horizontalVelocity.x, verticalVelocity.y, horizontalVelocity.z); + Vector3 movementThisFrame = velocity * DeltaTime; - float num = Mathf.Max(Pawn.Controller.stepOffset, Mathf.Sqrt(move.x * move.x + move.z * move.z)); + float step = Mathf.Max(Pawn.Controller.stepOffset, Mathf.Sqrt(movementThisFrame.x * movementThisFrame.x + movementThisFrame.z * movementThisFrame.z)); if (grounded) { - move -= num * Up; + movementThisFrame -= step * Up; } - Collision = Pawn.Controller.Move(move); + Collision = Pawn.Controller.Move(movementThisFrame); float verticalDot = Vector3.Dot(verticalVelocity, Up); bool previouslyGrounded = grounded; CheckGrounded(Collision, verticalDot <= 0f); + Vector3 velocityXZ = velocity._X0Z(); + Vector3 instantVelocity = (Pawn.Position - beforePosition) / DeltaTime; + if (instantVelocity.sqrMagnitude <= 0.2f) + { + instantVelocity = velocity; + } + if (instantVelocity.y > 0f || Collision == CollisionFlags.None) + { + instantVelocity.y = velocity.y; + } + + latestVelocity = instantVelocity; + + Vector3 instantVelocityXZ = instantVelocity._X0Z(); + if (velocityXZ == Vector3.zero) + { + latestVelocity = latestVelocity._0Y0(); + } + else + { + float deviation = Vector3.Dot(instantVelocityXZ, velocityXZ) / velocityXZ.sqrMagnitude; + latestVelocity = velocityXZ * Mathf.Clamp01(deviation) + latestVelocity.y * Up; + } + + if (latestVelocity.y < velocity.y - 0.001) + { + if (latestVelocity.y < 0f) + { + latestVelocity.y = velocity.y; + } + else + { + jumping.holdingJumpButton = false; + } + } + if (grounded) { verticalVelocity = Vector3.zero; @@ -166,23 +208,23 @@ public Vector3 Move(Vector3 horizontalVelocity) if (!previouslyGrounded) { jumping.jumping = false; - // Prefilled data is made to not hurt the player at any time when colliding with cyclops, but only to + // Prefilled data is made to not hurt the player at any time when colliding with cyclops, but only to play the noise SendMessage(nameof(Player.OnLand), new MovementCollisionData { impactVelocity = Vector3.one, surfaceType = VFXSurfaceTypes.metal - }); + }, SendMessageOptions.DontRequireReceiver); } } // If player is no longer grounded after move else if (previouslyGrounded) { SendMessage("OnFall", SendMessageOptions.DontRequireReceiver); - Pawn.Handle.transform.localPosition += num * Up; + Pawn.Handle.transform.localPosition += step * Up; } // Give velocity info for the animations - return (Pawn.Position - beforePosition) / DeltaTime; + return cyclops.transform.rotation * latestVelocity; } /// @@ -191,12 +233,12 @@ public Vector3 Move(Vector3 horizontalVelocity) /// public Vector3 CalculateVerticalVelocity() { - if (!jumpPressed || !canControl) + if (!jumpPressed) { jumping.holdingJumpButton = false; jumping.lastButtonDownTime = -100f; } - if (jumpPressed && (jumping.lastButtonDownTime < 0f || flyCheatEnabled) && canControl) + if (jumpPressed && (jumping.lastButtonDownTime < 0f || flyCheatEnabled)) { jumping.lastButtonDownTime = Time.time; } @@ -206,18 +248,20 @@ public Vector3 CalculateVerticalVelocity() if (!grounded) { verticalMove = -gravity * Up * DeltaTime; + verticalMove.y = Mathf.Max(verticalMove.y, -movement.maxFallSpeed); } if (grounded || allowMidAirJumping || flyCheatEnabled) { - if (canControl && Time.time - jumping.lastButtonDownTime < 0.2) + if (Time.time - jumping.lastButtonDownTime < 0.2) { grounded = false; jumping.jumping = true; jumping.lastStartTime = Time.time; jumping.lastButtonDownTime = -100f; jumping.holdingJumpButton = true; + Vector3 jumpDirection = Vector3.Slerp(Up, groundNormal, TooSteep() ? jumping.steepPerpAmount : jumping.perpAmount); + verticalMove = jumpDirection * CalculateJumpVerticalSpeed(jumping.baseHeight); SendMessage("OnJump", SendMessageOptions.DontRequireReceiver); - verticalMove = Up * CalculateJumpVerticalSpeed(jumping.baseHeight); } else { @@ -244,15 +288,15 @@ public Vector3 CalculateInputVelocity() Vector3 projectedForward = Vector3.ProjectOnPlane(forwardRef.forward, Up).normalized; Vector3 projectedRight = Vector3.ProjectOnPlane(forwardRef.right, Up).normalized; - Vector3 moveDir = (projectedForward * input.z + projectedRight * input.x).normalized; + Vector3 moveDirection = (projectedForward * input.z + projectedRight * input.x).normalized; Vector3 velocity; // Manage sliding on slopes if (grounded && TooSteep()) { velocity = GetSlidingDirection(); - Vector3 moveProjectedOnSlope = Vector3.Project(moveDir, velocity); - velocity += moveProjectedOnSlope * sliding.speedControl + (moveDir - moveProjectedOnSlope) * sliding.sidewaysControl; + Vector3 moveProjectedOnSlope = Vector3.Project(movementInputDirection, velocity); + velocity += moveProjectedOnSlope * sliding.speedControl + (movementInputDirection - moveProjectedOnSlope) * sliding.sidewaysControl; velocity *= sliding.slidingSpeed; } else @@ -272,7 +316,7 @@ public Vector3 CalculateInputVelocity() } sprinting = true; } - velocity = moveDir * forwardMaxSpeed * modifier * moveMinMagnitude; + velocity = moveDirection * forwardMaxSpeed * modifier * moveMinMagnitude; } if (XRSettings.enabled) { @@ -281,11 +325,28 @@ public Vector3 CalculateInputVelocity() if (grounded) { - return AdjustGroundVelocityToNormal(velocity, groundNormal); + velocity = AdjustGroundVelocityToNormal(velocity, groundNormal); + } + else + { + latestVelocity.y = 0f; + } + + float maxSpeed = GetMaxAcceleration(grounded) * DeltaTime; + + Vector3 difference = velocity - latestVelocity; + if (difference.sqrMagnitude > maxSpeed * maxSpeed) + { + difference = difference.normalized * maxSpeed; + } + latestVelocity += difference; + + if (grounded) + { + latestVelocity.y = Mathf.Min(latestVelocity.y, 0f); } - velocity.y = 0; - return velocity; + return latestVelocity; } public static string text; From 1729d1bc19fb1384630d179316718a33d6b8e232 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Mon, 12 Aug 2024 12:43:44 +0200 Subject: [PATCH 10/15] Add pings for all players in the small cyclops sonar HUD --- ...SonarDisplay_NewEntityOnSonar_PatchTest.cs | 18 ++++++ NitroxClient/GameLogic/PlayerManager.cs | 8 +++ ...Detector_CheckForCreaturesInRange_Patch.cs | 17 +++++ ...lopsSonarDisplay_NewEntityOnSonar_Patch.cs | 63 +++++++++++++++++++ 4 files changed, 106 insertions(+) create mode 100644 Nitrox.Test/Patcher/Patches/Dynamic/CyclopsSonarDisplay_NewEntityOnSonar_PatchTest.cs create mode 100644 NitroxPatcher/Patches/Dynamic/CyclopsSonarCreatureDetector_CheckForCreaturesInRange_Patch.cs create mode 100644 NitroxPatcher/Patches/Dynamic/CyclopsSonarDisplay_NewEntityOnSonar_Patch.cs diff --git a/Nitrox.Test/Patcher/Patches/Dynamic/CyclopsSonarDisplay_NewEntityOnSonar_PatchTest.cs b/Nitrox.Test/Patcher/Patches/Dynamic/CyclopsSonarDisplay_NewEntityOnSonar_PatchTest.cs new file mode 100644 index 0000000000..a66fac0c5d --- /dev/null +++ b/Nitrox.Test/Patcher/Patches/Dynamic/CyclopsSonarDisplay_NewEntityOnSonar_PatchTest.cs @@ -0,0 +1,18 @@ +using HarmonyLib; +using NitroxPatcher.PatternMatching; +using NitroxTest.Patcher; + +namespace NitroxPatcher.Patches.Dynamic; + +[TestClass] +public class CyclopsSonarDisplay_NewEntityOnSonar_PatchTest +{ + [TestMethod] + public void Sanity() + { + IEnumerable originalIl = PatchTestHelper.GetInstructionsFromMethod(CyclopsSonarDisplay_NewEntityOnSonar_Patch.TARGET_METHOD); + IEnumerable transformedIl = CyclopsSonarDisplay_NewEntityOnSonar_Patch.Transpiler(originalIl); + transformedIl.Count().Should().Be(originalIl.Count() + 3); + Console.WriteLine(transformedIl.ToPrettyString()); + } +} diff --git a/NitroxClient/GameLogic/PlayerManager.cs b/NitroxClient/GameLogic/PlayerManager.cs index 7ace0dc8ac..994c331a9e 100644 --- a/NitroxClient/GameLogic/PlayerManager.cs +++ b/NitroxClient/GameLogic/PlayerManager.cs @@ -8,6 +8,7 @@ using NitroxModel.GameLogic.FMOD; using NitroxModel.Helper; using NitroxModel.MultiplayerSession; +using UnityEngine; namespace NitroxClient.GameLogic; @@ -47,6 +48,13 @@ internal IEnumerable GetAll() return playersById.Values; } + public HashSet GetAllPlayerObjects() + { + HashSet remotePlayerObjects = GetAll().Select(player => player.Body).ToSet(); + remotePlayerObjects.Add(Player.mainObject); + return remotePlayerObjects; + } + public RemotePlayer Create(PlayerContext playerContext) { Validate.NotNull(playerContext); diff --git a/NitroxPatcher/Patches/Dynamic/CyclopsSonarCreatureDetector_CheckForCreaturesInRange_Patch.cs b/NitroxPatcher/Patches/Dynamic/CyclopsSonarCreatureDetector_CheckForCreaturesInRange_Patch.cs new file mode 100644 index 0000000000..1d33520e05 --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/CyclopsSonarCreatureDetector_CheckForCreaturesInRange_Patch.cs @@ -0,0 +1,17 @@ +using System.Reflection; +using NitroxClient.GameLogic; +using NitroxModel.Helper; + +namespace NitroxPatcher.Patches.Persistent; + +public sealed partial class CyclopsSonarCreatureDetector_CheckForCreaturesInRange_Patch : NitroxPatch, IPersistentPatch +{ + public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsSonarCreatureDetector t) => t.CheckForCreaturesInRange()); + + public const CyclopsSonarDisplay.EntityType PLAYER_TYPE = (CyclopsSonarDisplay.EntityType)2; + + public static void Postfix(CyclopsSonarCreatureDetector __instance) + { + __instance.ChekItemsOnHashSet(Resolve().GetAllPlayerObjects(), PLAYER_TYPE); + } +} diff --git a/NitroxPatcher/Patches/Dynamic/CyclopsSonarDisplay_NewEntityOnSonar_Patch.cs b/NitroxPatcher/Patches/Dynamic/CyclopsSonarDisplay_NewEntityOnSonar_Patch.cs new file mode 100644 index 0000000000..3e6d71a2aa --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/CyclopsSonarDisplay_NewEntityOnSonar_Patch.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; +using HarmonyLib; +using NitroxClient.GameLogic; +using NitroxClient.GameLogic.PlayerLogic; +using NitroxModel.Helper; +using NitroxModel_Subnautica.DataStructures; +using NitroxPatcher.Patches.Persistent; +using UnityEngine; + +namespace NitroxPatcher.Patches.Dynamic; + +public sealed partial class CyclopsSonarDisplay_NewEntityOnSonar_Patch : NitroxPatch, IPersistentPatch +{ + public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsSonarDisplay t) => t.NewEntityOnSonar(default)); + + /* + * } + * this.entitysOnSonar.Add(entityPing2); + * SetupPing(component, entityData); <----- INSERTED LINE + */ + public static IEnumerable Transpiler(IEnumerable instructions) + { + return new CodeMatcher(instructions).End() + .InsertAndAdvance(TARGET_METHOD.Ldloc()) + .InsertAndAdvance(new CodeInstruction(OpCodes.Ldarg_1)) + .InsertAndAdvance(new CodeInstruction(OpCodes.Call, Reflect.Method(() => SetupPing(default, default)))) + .InstructionEnumeration(); + } + + public static void SetupPing(CyclopsHUDSonarPing ping, CyclopsSonarCreatureDetector.EntityData entityData) + { + if (entityData.entityType != CyclopsSonarCreatureDetector_CheckForCreaturesInRange_Patch.PLAYER_TYPE) + { + return; + } + + Color color; + if (entityData.gameObject == Player.mainObject) + { + color = Resolve().PlayerSettings.PlayerColor.ToUnity(); + } + else if (entityData.gameObject.TryGetComponent(out RemotePlayerIdentifier remotePlayerIdentifier)) + { + color = remotePlayerIdentifier.RemotePlayer.PlayerSettings.PlayerColor.ToUnity(); + } + else + { + return; + } + + CyclopsHUDSonarPing sonarPing = ping.GetComponent(); + // Set isCreaturePing to true so that CyclopsHUDSonarPing.Start runs the SetColor code + sonarPing.isCreaturePing = true; + sonarPing.passiveColor = color; + sonarPing.Start(); + sonarPing.isCreaturePing = false; + + // We remove the pulse to be able to differentiate those signals from the creatures and decoy ones + GameObject.Destroy(sonarPing.transform.Find("Ping/PingPulse").gameObject); + } +} From f42a3e8eb8d8e97dceeda331b4a937e479b9d5f8 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Tue, 13 Aug 2024 02:29:25 +0200 Subject: [PATCH 11/15] Add caching during initial sync and fix constructables-collider-copy, fix promity detection for both cyclops name/color editor and light panel --- .../GlobalRootInitialSyncProcessor.cs | 2 + ...cs => CyclopsLightingMetadataExtractor.cs} | 2 +- NitroxClient/Helpers/UnityObjectExtensions.cs | 29 +++++++++ .../MonoBehaviours/Cyclops/NitroxCyclops.cs | 54 +++-------------- .../Cyclops/TriggerWorkaround.cs | 59 +++++++++++++++++++ .../MonoBehaviours/Cyclops/VirtualCyclops.cs | 38 +++++++++--- 6 files changed, 128 insertions(+), 56 deletions(-) rename NitroxClient/GameLogic/Spawning/Metadata/Extractor/{CyclopsLightningMetadataExtractor.cs => CyclopsLightingMetadataExtractor.cs} (74%) create mode 100644 NitroxClient/MonoBehaviours/Cyclops/TriggerWorkaround.cs diff --git a/NitroxClient/GameLogic/InitialSync/GlobalRootInitialSyncProcessor.cs b/NitroxClient/GameLogic/InitialSync/GlobalRootInitialSyncProcessor.cs index 7df62aa00f..1703b9306b 100644 --- a/NitroxClient/GameLogic/InitialSync/GlobalRootInitialSyncProcessor.cs +++ b/NitroxClient/GameLogic/InitialSync/GlobalRootInitialSyncProcessor.cs @@ -1,6 +1,7 @@ using System.Collections; using NitroxClient.GameLogic.Bases; using NitroxClient.GameLogic.InitialSync.Abstract; +using NitroxClient.MonoBehaviours.Cyclops; using NitroxModel.Packets; using UnityEngine; @@ -36,6 +37,7 @@ public override IEnumerator Process(InitialPlayerSync packet, WaitScreen.ManualW yield return Base.InitializeAsync(); yield return BaseGhost.InitializeAsync(); yield return BaseDeconstructable.InitializeAsync(); + yield return VirtualCyclops.InitializeConstructablesCache(); BuildingHandler.Main.InitializeOperations(packet.BuildOperationIds); diff --git a/NitroxClient/GameLogic/Spawning/Metadata/Extractor/CyclopsLightningMetadataExtractor.cs b/NitroxClient/GameLogic/Spawning/Metadata/Extractor/CyclopsLightingMetadataExtractor.cs similarity index 74% rename from NitroxClient/GameLogic/Spawning/Metadata/Extractor/CyclopsLightningMetadataExtractor.cs rename to NitroxClient/GameLogic/Spawning/Metadata/Extractor/CyclopsLightingMetadataExtractor.cs index db7cc25d27..b4193145fa 100644 --- a/NitroxClient/GameLogic/Spawning/Metadata/Extractor/CyclopsLightningMetadataExtractor.cs +++ b/NitroxClient/GameLogic/Spawning/Metadata/Extractor/CyclopsLightingMetadataExtractor.cs @@ -3,7 +3,7 @@ namespace NitroxClient.GameLogic.Spawning.Metadata.Extractor; -public class CyclopsLightningMetadataExtractor : EntityMetadataExtractor +public class CyclopsLightingMetadataExtractor : EntityMetadataExtractor { public override CyclopsLightingMetadata Extract(CyclopsLightingPanel lighting) { diff --git a/NitroxClient/Helpers/UnityObjectExtensions.cs b/NitroxClient/Helpers/UnityObjectExtensions.cs index 40aa736223..ab09104b49 100644 --- a/NitroxClient/Helpers/UnityObjectExtensions.cs +++ b/NitroxClient/Helpers/UnityObjectExtensions.cs @@ -1,4 +1,8 @@ +using System; +using System.Collections.Generic; +using System.Reflection; using NitroxModel.Core; +using UnityEngine; namespace NitroxClient.Helpers; @@ -17,4 +21,29 @@ public static T Resolve(this UnityEngine.Object _, bool prelifeTime = false) { return prelifeTime ? NitroxServiceLocator.Cache.ValuePreLifetime : NitroxServiceLocator.Cache.Value; } + + /// + /// Copies a whole component by using reflection. Please note this takes considerable time and every use of this should be thoughtful. + /// + public static Component CopyComponent(this Component original, GameObject destination) + { + Type type = original.GetType(); + Component copy = destination.AddComponent(type); + + FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + foreach (FieldInfo field in fields) + { + field.SetValue(copy, field.GetValue(original)); + } + + PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + foreach (PropertyInfo property in properties) + { + if (property.GetSetMethod(true) != null) + { + property.SetValue(copy, property.GetValue(original)); + } + } + return copy; + } } diff --git a/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs b/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs index 022bad5677..d16a20dae4 100644 --- a/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs +++ b/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs @@ -39,7 +39,7 @@ public void Start() GetComponent().enabled = false; - FixSubNameEditor(); + WorkaroundColliders(); } /// @@ -142,55 +142,15 @@ public void SetReceiving() stabilizer.stabilizerEnabled = false; } - private void FixSubNameEditor() + private void WorkaroundColliders() { CyclopsSubNameScreen cyclopsSubNameScreen = transform.GetComponentInChildren(true); + TriggerWorkaround subNameTriggerWorkaround = cyclopsSubNameScreen.gameObject.AddComponent(); + subNameTriggerWorkaround.Initialize(this,cyclopsSubNameScreen.animator, cyclopsSubNameScreen.ContentOn, nameof(CyclopsSubNameScreen.ContentOff), cyclopsSubNameScreen); - cyclopsSubNameScreen.gameObject.AddComponent().Initialize(this, cyclopsSubNameScreen); - } - - /// - /// With the changes to the Player's colliders, the sub name screen doesn't detect the player coming close to it. - /// The easiest workaround is to replace proximity detection by distance checks. - /// - class SubNameFixer : MonoBehaviour - { - private const float DETECTION_RANGE = 3f; - private const string ANIMATOR_PARAM = "PanelActive"; - private NitroxCyclops cyclops; - private CyclopsSubNameScreen cyclopsSubNameScreen; - private bool playerIn; - - public void Initialize(NitroxCyclops cyclops, CyclopsSubNameScreen cyclopsSubNameScreen) - { - this.cyclops = cyclops; - this.cyclopsSubNameScreen = cyclopsSubNameScreen; - } - - /// - /// Code adapted from and - /// - public void Update() - { - // Virtual is not null only when the local player is aboard - if (cyclops.Virtual && Vector3.Distance(Player.main.transform.position, transform.position) < DETECTION_RANGE) - { - if (!playerIn) - { - playerIn = true; - cyclopsSubNameScreen.animator.SetBool(ANIMATOR_PARAM, true); - cyclopsSubNameScreen.ContentOn(); - } - return; - } - - if (playerIn) - { - playerIn = false; - cyclopsSubNameScreen.animator.SetBool(ANIMATOR_PARAM, false); - cyclopsSubNameScreen.Invoke(nameof(cyclopsSubNameScreen.ContentOff), 0.5f); - } - } + CyclopsLightingPanel cyclopsLightingPanel = transform.GetComponentInChildren(true); + TriggerWorkaround lightingTriggerWorkaround = cyclopsLightingPanel.gameObject.AddComponent(); + lightingTriggerWorkaround.Initialize(this, cyclopsLightingPanel.uiPanel, cyclopsLightingPanel.ButtonsOn, nameof(CyclopsLightingPanel.ButtonsOff), cyclopsLightingPanel); } // TODO: all of the below stuff is purely for testing and will probably get removed before merge diff --git a/NitroxClient/MonoBehaviours/Cyclops/TriggerWorkaround.cs b/NitroxClient/MonoBehaviours/Cyclops/TriggerWorkaround.cs new file mode 100644 index 0000000000..9a2410a3ac --- /dev/null +++ b/NitroxClient/MonoBehaviours/Cyclops/TriggerWorkaround.cs @@ -0,0 +1,59 @@ +using System; +using UnityEngine; + +namespace NitroxClient.MonoBehaviours.Cyclops; + +/// +/// With the changes to the Player's colliders, the cyclops doesn't detect the player entering or leaving to triggers +/// The easiest workaround is to replace proximity detection by distance checks. +/// +/// +/// Works for and . +/// +public class TriggerWorkaround : MonoBehaviour +{ + private const float DETECTION_RANGE = 5f; + private const string ANIMATOR_PARAM = "PanelActive"; + private bool playerIn; + + private NitroxCyclops cyclops; + private Animator animator; + private Action onEnterCallback; + private string onExitInvokeCallback; + private MonoBehaviour targetBehaviour; + + public void Initialize(NitroxCyclops cyclops, Animator animator, Action onEnterCallback, string onExitInvokeCallback, MonoBehaviour targetBehaviour) + { + this.cyclops = cyclops; + this.animator = animator; + this.onEnterCallback = onEnterCallback; + this.onExitInvokeCallback = onExitInvokeCallback; + this.targetBehaviour = targetBehaviour; + } + + /// + /// Code adapted from and + /// + public void Update() + { + // Virtual is not null only when the local player is aboard + if (cyclops.Virtual && Vector3.Distance(Player.main.transform.position, transform.position) < DETECTION_RANGE) + { + if (!playerIn) + { + playerIn = true; + animator.SetBool(ANIMATOR_PARAM, true); + onEnterCallback(); + targetBehaviour.CancelInvoke(onExitInvokeCallback); + } + return; + } + + if (playerIn) + { + playerIn = false; + animator.SetBool(ANIMATOR_PARAM, false); + targetBehaviour.Invoke(onExitInvokeCallback, 0.5f); + } + } +} diff --git a/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs b/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs index d918c22226..1f5dfc55f6 100644 --- a/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs +++ b/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs @@ -1,6 +1,8 @@ +using System.Collections; using System.Collections.Generic; using System.Linq; using NitroxClient.Communication; +using NitroxClient.GameLogic.Spawning.WorldEntities; using NitroxModel.Packets; using UnityEngine; @@ -15,10 +17,10 @@ public class VirtualCyclops : MonoBehaviour public static VirtualCyclops Instance; public const string NAME = "VirtualCyclops"; + private static readonly Dictionary cacheColliderCopy = []; private readonly Dictionary virtualOpenableByName = []; private readonly Dictionary realOpenableByName = []; private readonly Dictionary virtualConstructableByRealGameObject = []; - private readonly Dictionary cacheColliderCopy = []; public NitroxCyclops Cyclops; public Transform axis; @@ -83,6 +85,24 @@ public static void CreateVirtualCyclops() }); } + public static IEnumerator InitializeConstructablesCache() + { + List constructableTechTypes = []; + CraftData.GetBuilderGroupTech(TechGroup.InteriorModules, constructableTechTypes, true); + CraftData.GetBuilderGroupTech(TechGroup.Miscellaneous, constructableTechTypes, true); + + TaskResult result = new(); + foreach (TechType techType in constructableTechTypes) + { + yield return DefaultWorldEntitySpawner.RequestPrefab(techType, result); + if (result.value && result.value.GetComponent()) + { + // We immediately destroy the copy because we only want to cache it for now + Destroy(CreateColliderCopy(result.value, techType)); + } + } + } + public void Populate() { foreach (Constructable constructable in Cyclops.GetComponentsInChildren(true)) @@ -178,9 +198,7 @@ public void ReplicateConstructable(Constructable constructable) } GameObject colliderCopy = CreateColliderCopy(constructable.gameObject, constructable.techType); colliderCopy.transform.parent = transform; - colliderCopy.transform.localPosition = constructable.transform.localPosition; - colliderCopy.transform.localRotation = constructable.transform.localRotation; - colliderCopy.transform.localScale = constructable.transform.localScale; + colliderCopy.transform.CopyLocals(constructable.transform); virtualConstructableByRealGameObject.Add(constructable.gameObject, colliderCopy); } @@ -188,7 +206,7 @@ public void ReplicateConstructable(Constructable constructable) /// /// Creates an empty shell simulating the presence of modules by copying its children containing a collider. /// - public GameObject CreateColliderCopy(GameObject realObject, TechType techType) + public static GameObject CreateColliderCopy(GameObject realObject, TechType techType) { if (cacheColliderCopy.TryGetValue(techType, out GameObject colliderCopy)) { @@ -206,11 +224,15 @@ public GameObject CreateColliderCopy(GameObject realObject, TechType techType) IEnumerable uniqueColliderObjects = realObject.GetComponentsInChildren(true).Select(c => c.gameObject).Distinct(); foreach (GameObject colliderObject in uniqueColliderObjects) { - GameObject copiedCollider = Instantiate(colliderObject); - copiedCollider.name = colliderObject.name; + GameObject copiedColliderObject = new(colliderObject.name); + copiedColliderObject.transform.CopyLocals(colliderObject.transform); + foreach (Collider collider in colliderObject.GetComponents()) + { + collider.CopyComponent(copiedColliderObject); + } // "child" is always a copied transform looking for its copied parent - Transform child = copiedCollider.transform; + Transform child = copiedColliderObject.transform; // "parent" is always the real parent of the real transform corresponding to "child" Transform parent = colliderObject.transform.parent; From 8b6edda62a1e04bf3adc80041e94b1ea691a1035 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Tue, 13 Aug 2024 17:58:21 +0200 Subject: [PATCH 12/15] Fix not hearing local player's footsteps in cyclops, fix remote player entering cyclops by seamoth docking --- NitroxClient/GameLogic/RemotePlayer.cs | 7 +++++++ NitroxClient/GameLogic/Vehicles.cs | 18 ++++++++++++++++-- .../MonoBehaviours/Cyclops/CyclopsMotor.cs | 7 ++++--- .../MonoBehaviours/Cyclops/NitroxCyclops.cs | 1 + 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/NitroxClient/GameLogic/RemotePlayer.cs b/NitroxClient/GameLogic/RemotePlayer.cs index 5c9c0577a5..696f7e4c29 100644 --- a/NitroxClient/GameLogic/RemotePlayer.cs +++ b/NitroxClient/GameLogic/RemotePlayer.cs @@ -316,6 +316,13 @@ public void SetVehicle(Vehicle newVehicle) AnimationController["in_seamoth"] = newVehicle is SeaMoth; AnimationController["in_exosuit"] = AnimationController["using_mechsuit"] = newVehicle is Exosuit; + + // In case we are dismissing the current seamoth to enter the cyclops through a docking, + // we need to setup the player back in the cyclops + if (!newVehicle && SubRoot) + { + SetSubRoot(SubRoot, true); + } } } diff --git a/NitroxClient/GameLogic/Vehicles.cs b/NitroxClient/GameLogic/Vehicles.cs index 5d61eaacc6..43b0b3cb78 100644 --- a/NitroxClient/GameLogic/Vehicles.cs +++ b/NitroxClient/GameLogic/Vehicles.cs @@ -102,7 +102,10 @@ public void UpdateVehiclePosition(VehicleMovementData vehicleModel, Optional(); } else { PilotingChair chair = parent.GetComponentInChildren(true); - pilotingChairByTechType.Add(techType, chair.gameObject.GetHierarchyPath(parent)); + if (chair) + { + pilotingChairByTechType.Add(techType, chair.gameObject.GetHierarchyPath(parent)); + } + else + { + pilotingChairByTechType.Add(techType, string.Empty); + } return chair; } } diff --git a/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs b/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs index 7f47192e87..bd5d2d323b 100644 --- a/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs +++ b/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs @@ -132,8 +132,10 @@ public override Vector3 UpdateMove() Vector3 horizontalVelocity = CalculateInputVelocity(); text2 = $"movementInputDirection: {movementInputDirection}\nVerticalVel: {verticalVelocity}\nHorizontalVelocity: {horizontalVelocity}\nGrounded/Controller: {grounded}/{controller.isGrounded}\nCollision: {Collision}"; - // Apply the physics computations - return Move(horizontalVelocity); + + // movement.velocity gives velocity info for the animations and footsteps + movement.velocity = Move(horizontalVelocity); + return movement.velocity; } public static string text2; @@ -223,7 +225,6 @@ public Vector3 Move(Vector3 horizontalVelocity) Pawn.Handle.transform.localPosition += step * Up; } - // Give velocity info for the animations return cyclops.transform.rotation * latestVelocity; } diff --git a/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs b/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs index d16a20dae4..8d2ba0a922 100644 --- a/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs +++ b/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs @@ -130,6 +130,7 @@ public void RemovePawnForPlayer(INitroxPlayer player) Pawns.Remove(player); } + // TODO: Use SetBroadcasting and SetReceiving when we finally have a cyclops movements rework public void SetBroadcasting() { worldForces.enabled = true; From 10586e9c197e34ee765d4063a48429eb38712fda Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Tue, 13 Aug 2024 18:50:55 +0200 Subject: [PATCH 13/15] Fix subfire disabling in creative mode and clear latests debug traces --- ...SonarDisplay_NewEntityOnSonar_PatchTest.cs | 2 - .../MonoBehaviours/Cyclops/CyclopsMotor.cs | 6 - .../MonoBehaviours/Cyclops/NitroxCyclops.cs | 122 +----------------- .../Dynamic/FPSCounter_UpdateDisplay_Patch.cs | 3 - .../SubFire_EngineOverheatSimulation_Patch.cs | 2 +- 5 files changed, 6 insertions(+), 129 deletions(-) diff --git a/Nitrox.Test/Patcher/Patches/Dynamic/CyclopsSonarDisplay_NewEntityOnSonar_PatchTest.cs b/Nitrox.Test/Patcher/Patches/Dynamic/CyclopsSonarDisplay_NewEntityOnSonar_PatchTest.cs index a66fac0c5d..1514ececa2 100644 --- a/Nitrox.Test/Patcher/Patches/Dynamic/CyclopsSonarDisplay_NewEntityOnSonar_PatchTest.cs +++ b/Nitrox.Test/Patcher/Patches/Dynamic/CyclopsSonarDisplay_NewEntityOnSonar_PatchTest.cs @@ -1,5 +1,4 @@ using HarmonyLib; -using NitroxPatcher.PatternMatching; using NitroxTest.Patcher; namespace NitroxPatcher.Patches.Dynamic; @@ -13,6 +12,5 @@ public void Sanity() IEnumerable originalIl = PatchTestHelper.GetInstructionsFromMethod(CyclopsSonarDisplay_NewEntityOnSonar_Patch.TARGET_METHOD); IEnumerable transformedIl = CyclopsSonarDisplay_NewEntityOnSonar_Patch.Transpiler(originalIl); transformedIl.Count().Should().Be(originalIl.Count() + 3); - Console.WriteLine(transformedIl.ToPrettyString()); } } diff --git a/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs b/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs index bd5d2d323b..b5d78f0249 100644 --- a/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs +++ b/NitroxClient/MonoBehaviours/Cyclops/CyclopsMotor.cs @@ -131,15 +131,11 @@ public override Vector3 UpdateMove() verticalVelocity += CalculateVerticalVelocity(); Vector3 horizontalVelocity = CalculateInputVelocity(); - text2 = $"movementInputDirection: {movementInputDirection}\nVerticalVel: {verticalVelocity}\nHorizontalVelocity: {horizontalVelocity}\nGrounded/Controller: {grounded}/{controller.isGrounded}\nCollision: {Collision}"; - // movement.velocity gives velocity info for the animations and footsteps movement.velocity = Move(horizontalVelocity); return movement.velocity; } - public static string text2; - /// /// Simulates player movement on its pawn and update the grounded state /// @@ -350,8 +346,6 @@ public Vector3 CalculateInputVelocity() return latestVelocity; } - public static string text; - private new Vector3 GetSlidingDirection() { return Vector3.ProjectOnPlane(groundNormal, Up).normalized; diff --git a/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs b/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs index 8d2ba0a922..9c65765a39 100644 --- a/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs +++ b/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using NitroxClient.GameLogic; -using NitroxClient.GameLogic.ChatUI; using NitroxClient.GameLogic.PlayerLogic; using NitroxClient.GameLogic.PlayerLogic.PlayerModel.Abstract; using UnityEngine; @@ -42,6 +41,11 @@ public void Start() WorkaroundColliders(); } + public void Update() + { + MaintainPawns(); + } + /// /// Triggers required on-remove callbacks on children player objects, including the local player. /// @@ -153,120 +157,4 @@ private void WorkaroundColliders() TriggerWorkaround lightingTriggerWorkaround = cyclopsLightingPanel.gameObject.AddComponent(); lightingTriggerWorkaround.Initialize(this, cyclopsLightingPanel.uiPanel, cyclopsLightingPanel.ButtonsOn, nameof(CyclopsLightingPanel.ButtonsOff), cyclopsLightingPanel); } - - // TODO: all of the below stuff is purely for testing and will probably get removed before merge - // EXCEPT for the MaintainPawns line in Update() - private Vector3 forward => subRoot.subAxis.forward; - private Vector3 up => subRoot.subAxis.up; - private Vector3 right => subRoot.subAxis.right; - - public bool Autopilot; - public bool Sinus; - public bool Rolling; - public bool Torqing; - public bool RenderersToggled; - - public float Forward; - public float Up; - public float VerticalPeriod = 1f; - public float Torque; - public float Roll; - - public void Reset(Vector3 positionOffset) - { - Torqing = false; - Rolling = false; - rigidbody.velocity = Vector3.zero; - rigidbody.angularVelocity = Vector3.zero; - transform.position = new Vector3(70f, -16f, 0f) + positionOffset; - transform.rotation = Quaternion.Euler(new(360f, 270f, 0f)); - - if (Player.main.currentSub == subRoot || !Player.main.currentSub) - { - Player.main.SetCurrentSub(subRoot, true); - Player.main.SetPosition(transform.position + up); - cyclopsMotor.ToggleCyclopsMotor(true); - cyclopsMotor.Pawn.SetReference(); - } - } - - private double latestReset; - - public void ResetAll() - { - Vector3 offset = Vector3.zero; - foreach (NitroxCyclops cyclops in LargeWorldStreamer.main.globalRoot.GetComponentsInChildren()) - { - offset += new Vector3(60f, 30f, 0); - cyclops.Reset(offset); - } - - Log.InGame("Reset all cyclops"); - } - - public void Update() - { - if (!this.Resolve().IsChatSelected && !DevConsole.instance.selected) - { - if (Input.GetKeyUp(KeyCode.R)) - { - if (latestReset + 10d < DayNightCycle.main.timePassed) - { - latestReset = DayNightCycle.main.timePassed; - ResetAll(); - } - } - if (Input.GetKeyUp(KeyCode.N)) - { - SetReceiving(); - Rolling = !Rolling; - Autopilot = true; - Roll = Rolling.ToFloat(); - Log.InGame($"Rolling: {Rolling}"); - } - if (Input.GetKeyUp(KeyCode.B)) - { - SetReceiving(); - Torqing = !Torqing; - Autopilot = true; - Torque = Torqing.ToFloat(); - Log.InGame($"Torqing: {Torqing}"); - } - } - - MaintainPawns(); - } - - public void FixedUpdate() - { - MoveAutopilot(); - } - - public void MoveAutopilot() - { - if (!Autopilot) - { - return; - } - - // https://docs.unity3d.com/ScriptReference/Rigidbody.AddTorque.html - Vector3 cyclopsTorqueFactor = up * subControl.BaseTurningTorque * subControl.turnScale; - rigidbody.angularVelocity += cyclopsTorqueFactor * Torque * Time.fixedDeltaTime; - - Vector3 cyclopsRollFactor = right * subControl.BaseTurningTorque * subControl.turnScale; - rigidbody.angularVelocity += cyclopsRollFactor * Roll * Time.fixedDeltaTime; - - // https://docs.unity3d.com/ScriptReference/Rigidbody.AddForce.html - Vector3 cyclopsVerticalFactor = up * (subControl.BaseVerticalAccel + ballasts * subControl.AccelPerBallast) * subControl.accelScale; - if (Sinus) - { - cyclopsVerticalFactor *= Mathf.Sin(2 * Mathf.PI * Time.fixedTime / VerticalPeriod); - } - rigidbody.velocity += cyclopsVerticalFactor * Up * Time.fixedDeltaTime; - - Vector3 cyclopsForwardFactor = forward * subControl.BaseForwardAccel * subControl.accelScale; - rigidbody.velocity += cyclopsForwardFactor * Forward * Time.fixedDeltaTime; - - subControl.appliedThrottle = true; - } } diff --git a/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs b/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs index c0ca9fae4d..33b51f6e15 100644 --- a/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs @@ -18,9 +18,6 @@ public static void Postfix(FPSCounter __instance) } __instance.strBuffer.Append("Loading entities: ").AppendLine(Resolve().EntitiesToSpawn.Count.ToString()); __instance.strBuffer.Append("Real time elapsed: ").AppendLine(Resolve().RealTimeElapsed.ToString()); - // TODO: Get rid of the two lines below - __instance.strBuffer.AppendLine(CyclopsMotor.text); - __instance.strBuffer.AppendLine(CyclopsMotor.text2); __instance.text.SetText(__instance.strBuffer); } } diff --git a/NitroxPatcher/Patches/Dynamic/SubFire_EngineOverheatSimulation_Patch.cs b/NitroxPatcher/Patches/Dynamic/SubFire_EngineOverheatSimulation_Patch.cs index d2f9417506..5d4b38fada 100644 --- a/NitroxPatcher/Patches/Dynamic/SubFire_EngineOverheatSimulation_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/SubFire_EngineOverheatSimulation_Patch.cs @@ -13,7 +13,7 @@ public sealed partial class SubFire_EngineOverheatSimulation_Patch : NitroxPatch public static bool Prefix() { - if (GameModeUtils.IsCheatActive(GameModeOption.Creative)) + if ((GameModeUtils.currentGameMode & GameModeOption.Creative) != 0) { return false; } From a83d5da41664be1e372a3e2fdb19133562fa15b2 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Wed, 14 Aug 2024 02:00:35 +0200 Subject: [PATCH 14/15] Fix destroyed vehicle also destroying their children player entities --- NitroxClient/GameLogic/Vehicles.cs | 2 +- .../MonoBehaviours/Cyclops/NitroxCyclops.cs | 12 +++++- .../MonoBehaviours/Cyclops/VirtualCyclops.cs | 2 + NitroxModel/Packets/VehicleDestroyed.cs | 15 +++++++ ...psDestructionEvent_DestroyCyclops_Patch.cs | 15 ++++++- ...DestructionEvent_OnConsoleCommand_Patch.cs | 25 +++++++---- .../EntityDestroyedPacketProcessor.cs | 4 +- ...layerJoiningMultiplayerSessionProcessor.cs | 2 +- .../RemoveCreatureCorpseProcessor.cs | 4 +- .../VehicleDestroyedPacketProcessor.cs | 43 +++++++++++++++++++ .../GameLogic/Entities/WorldEntityManager.cs | 35 +++++++++------ NitroxServer/Player.cs | 2 +- 12 files changed, 127 insertions(+), 34 deletions(-) create mode 100644 NitroxModel/Packets/VehicleDestroyed.cs create mode 100644 NitroxServer/Communication/Packets/Processors/VehicleDestroyedPacketProcessor.cs diff --git a/NitroxClient/GameLogic/Vehicles.cs b/NitroxClient/GameLogic/Vehicles.cs index 43b0b3cb78..bcba85f5dc 100644 --- a/NitroxClient/GameLogic/Vehicles.cs +++ b/NitroxClient/GameLogic/Vehicles.cs @@ -144,7 +144,7 @@ public void BroadcastDestroyedVehicle(NitroxId id) { using (PacketSuppressor.Suppress()) { - EntityDestroyed vehicleDestroyed = new(id); + VehicleDestroyed vehicleDestroyed = new(id); packetSender.Send(vehicleDestroyed); } } diff --git a/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs b/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs index 9c65765a39..f056be41e4 100644 --- a/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs +++ b/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs @@ -51,12 +51,17 @@ public void Update() /// public void RemoveAllPlayers() { + // This will call OnLocalPlayerExit + if (Player.main.currentSub == subRoot) + { + Player.main.SetCurrentSub(null); + } + foreach (RemotePlayerIdentifier remotePlayerIdentifier in GetComponentsInChildren(true)) { remotePlayerIdentifier.RemotePlayer.ResetStates(); OnPlayerExit(remotePlayerIdentifier.RemotePlayer); } - OnLocalPlayerExit(); } /// @@ -84,7 +89,10 @@ public void OnLocalPlayerExit() cyclopsMotor.ToggleCyclopsMotor(false); cyclopsMotor.Pawn = null; - Virtual.SetCurrentCyclops(null); + if (Virtual) + { + Virtual.SetCurrentCyclops(null); + } } /// diff --git a/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs b/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs index 1f5dfc55f6..1421b0888f 100644 --- a/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs +++ b/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs @@ -68,6 +68,7 @@ public static void CreateVirtualCyclops() GameObject.Destroy(model.GetComponent()); GameObject.Destroy(model.GetComponent()); + GameObject.Destroy(model.GetComponent()); Instance.InitialPosition = position; Instance.InitialRotation = rotation; @@ -78,6 +79,7 @@ public static void CreateVirtualCyclops() model.GetComponent().lockInterpolation = false; model.GetComponent().stabilizerEnabled = false; model.GetComponent().isKinematic = true; + model.GetComponent().invincible = true; Instance.RegisterVirtualOpenables(); Instance.ToggleRenderers(false); diff --git a/NitroxModel/Packets/VehicleDestroyed.cs b/NitroxModel/Packets/VehicleDestroyed.cs new file mode 100644 index 0000000000..3a8f64b8e5 --- /dev/null +++ b/NitroxModel/Packets/VehicleDestroyed.cs @@ -0,0 +1,15 @@ +using System; +using NitroxModel.DataStructures; + +namespace NitroxModel.Packets; + +[Serializable] +public class VehicleDestroyed : Packet +{ + public NitroxId Id { get; } + + public VehicleDestroyed(NitroxId id) + { + Id = id; + } +} diff --git a/NitroxPatcher/Patches/Dynamic/CyclopsDestructionEvent_DestroyCyclops_Patch.cs b/NitroxPatcher/Patches/Dynamic/CyclopsDestructionEvent_DestroyCyclops_Patch.cs index 724d61d28c..35c6bb9d46 100644 --- a/NitroxPatcher/Patches/Dynamic/CyclopsDestructionEvent_DestroyCyclops_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/CyclopsDestructionEvent_DestroyCyclops_Patch.cs @@ -1,5 +1,6 @@ using System.Reflection; using NitroxClient.GameLogic; +using NitroxClient.MonoBehaviours; using NitroxClient.MonoBehaviours.Cyclops; using NitroxModel.DataStructures; using NitroxModel.Helper; @@ -15,16 +16,26 @@ public sealed partial class CyclopsDestructionEvent_DestroyCyclops_Patch : Nitro public static void Prefix(CyclopsDestructionEvent __instance) { - __instance.subLiveMixin.Kill(); if (__instance.TryGetNitroxId(out NitroxId nitroxId)) { Resolve().StopSimulatingEntity(nitroxId); + EntityPositionBroadcaster.StopWatchingEntity(nitroxId); } + bool wasInCyclops = Player.main.currentSub == __instance.subRoot; + // Before the cyclops destruction, we move out the remote players so that they aren't stuck in its hierarchy - if (Player.main && Player.main.currentSub == __instance.subRoot && __instance.subRoot.TryGetComponent(out NitroxCyclops nitroxCyclops)) + if (__instance.subRoot && __instance.subRoot.TryGetComponent(out NitroxCyclops nitroxCyclops)) { nitroxCyclops.RemoveAllPlayers(); } + + if (wasInCyclops) + { + // Particular case here, this is not broadcasted and should not be, it's just there to have player be really inside the cyclops while not being registered by NitroxCyclops + Player.main._currentSub = __instance.subRoot; + } + + __instance.subLiveMixin.Kill(); } } diff --git a/NitroxPatcher/Patches/Dynamic/CyclopsDestructionEvent_OnConsoleCommand_Patch.cs b/NitroxPatcher/Patches/Dynamic/CyclopsDestructionEvent_OnConsoleCommand_Patch.cs index 4704e909ba..80ca4dc681 100644 --- a/NitroxPatcher/Patches/Dynamic/CyclopsDestructionEvent_OnConsoleCommand_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/CyclopsDestructionEvent_OnConsoleCommand_Patch.cs @@ -4,6 +4,7 @@ using NitroxClient.GameLogic; using NitroxModel.DataStructures; using NitroxModel.Helper; +using static NitroxModel.Helper.Reflect; namespace NitroxPatcher.Patches.Dynamic; @@ -18,25 +19,31 @@ public static bool PrefixRestore() return false; } - public static bool PrefixDestroy(CyclopsDestructionEvent __instance) + public static bool PrefixDestroy(CyclopsDestructionEvent __instance, out bool __state) { // We only apply the destroy to the current Cyclops - if (!Player.main.currentSub || Player.main.currentSub.gameObject != __instance.gameObject) - { - return false; - } + __state = Player.main.currentSub == __instance.subRoot; + return __state; + } - if (__instance.TryGetIdOrWarn(out NitroxId id)) + /// + /// This must happen at postfix so that the SubRootChanged packet are sent before the destroyed vehicle one, + /// thus saving player entities from deletion. + /// + public static void PostfixDestroy(CyclopsDestructionEvent __instance, bool __state) + { + if (__state && __instance.TryGetIdOrWarn(out NitroxId id)) { Resolve().BroadcastDestroyedVehicle(id); } - - return true; } public override void Patch(Harmony harmony) { + MethodInfo destroyPrefixInfo = Method(() => PrefixDestroy(default, out Ref.Field)); + PatchPrefix(harmony, TARGET_METHOD_RESTORE, ((Func)PrefixRestore).Method); - PatchPrefix(harmony, TARGET_METHOD_DESTROY, ((Func)PrefixDestroy).Method); + PatchPrefix(harmony, TARGET_METHOD_DESTROY, destroyPrefixInfo); + PatchPostfix(harmony, TARGET_METHOD_DESTROY, ((Action)PostfixDestroy).Method); } } diff --git a/NitroxServer/Communication/Packets/Processors/EntityDestroyedPacketProcessor.cs b/NitroxServer/Communication/Packets/Processors/EntityDestroyedPacketProcessor.cs index 5ce2700356..26f8f71178 100644 --- a/NitroxServer/Communication/Packets/Processors/EntityDestroyedPacketProcessor.cs +++ b/NitroxServer/Communication/Packets/Processors/EntityDestroyedPacketProcessor.cs @@ -24,12 +24,12 @@ public override void Process(EntityDestroyed packet, Player destroyingPlayer) { entitySimulation.EntityDestroyed(packet.Id); - if (worldEntityManager.TryDestroyEntity(packet.Id, out Optional entity)) + if (worldEntityManager.TryDestroyEntity(packet.Id, out Entity entity)) { foreach (Player player in playerManager.GetConnectedPlayers()) { bool isOtherPlayer = player != destroyingPlayer; - if (isOtherPlayer && player.CanSee(entity.Value)) + if (isOtherPlayer && player.CanSee(entity)) { player.SendPacket(packet); } diff --git a/NitroxServer/Communication/Packets/Processors/PlayerJoiningMultiplayerSessionProcessor.cs b/NitroxServer/Communication/Packets/Processors/PlayerJoiningMultiplayerSessionProcessor.cs index b4e7228556..66cbaed868 100644 --- a/NitroxServer/Communication/Packets/Processors/PlayerJoiningMultiplayerSessionProcessor.cs +++ b/NitroxServer/Communication/Packets/Processors/PlayerJoiningMultiplayerSessionProcessor.cs @@ -57,7 +57,7 @@ public override void Process(PlayerJoiningMultiplayerSession packet, INitroxConn List simulations = world.EntitySimulation.AssignGlobalRootEntitiesAndGetData(player); - player.Entity = wasBrandNewPlayer ? SetupPlayerEntity(player) : RespawnExistingEntity(player); ; + player.Entity = wasBrandNewPlayer ? SetupPlayerEntity(player) : RespawnExistingEntity(player); List globalRootEntities = world.WorldEntityManager.GetGlobalRootEntities(true); bool isFirstPlayer = playerManager.GetConnectedPlayers().Count == 1; diff --git a/NitroxServer/Communication/Packets/Processors/RemoveCreatureCorpseProcessor.cs b/NitroxServer/Communication/Packets/Processors/RemoveCreatureCorpseProcessor.cs index 3da2d214bd..b49aaf2f7b 100644 --- a/NitroxServer/Communication/Packets/Processors/RemoveCreatureCorpseProcessor.cs +++ b/NitroxServer/Communication/Packets/Processors/RemoveCreatureCorpseProcessor.cs @@ -26,12 +26,12 @@ public override void Process(RemoveCreatureCorpse packet, Player destroyingPlaye // So that even players rejoining can see it (before it despawns) entitySimulation.EntityDestroyed(packet.CreatureId); - if (worldEntityManager.TryDestroyEntity(packet.CreatureId, out Optional entity)) + if (worldEntityManager.TryDestroyEntity(packet.CreatureId, out Entity entity)) { foreach (Player player in playerManager.GetConnectedPlayers()) { bool isOtherPlayer = player != destroyingPlayer; - if (isOtherPlayer && player.CanSee(entity.Value)) + if (isOtherPlayer && player.CanSee(entity)) { player.SendPacket(packet); } diff --git a/NitroxServer/Communication/Packets/Processors/VehicleDestroyedPacketProcessor.cs b/NitroxServer/Communication/Packets/Processors/VehicleDestroyedPacketProcessor.cs new file mode 100644 index 0000000000..4562db50e2 --- /dev/null +++ b/NitroxServer/Communication/Packets/Processors/VehicleDestroyedPacketProcessor.cs @@ -0,0 +1,43 @@ +using NitroxModel.DataStructures.GameLogic; +using NitroxModel.DataStructures.GameLogic.Entities; +using NitroxModel.Packets; +using NitroxServer.Communication.Packets.Processors.Abstract; +using NitroxServer.GameLogic; +using NitroxServer.GameLogic.Entities; + +namespace NitroxServer.Communication.Packets.Processors; + +public class VehicleDestroyedPacketProcessor : AuthenticatedPacketProcessor +{ + private readonly PlayerManager playerManager; + private readonly EntitySimulation entitySimulation; + private readonly WorldEntityManager worldEntityManager; + + public VehicleDestroyedPacketProcessor(PlayerManager playerManager, EntitySimulation entitySimulation, WorldEntityManager worldEntityManager) + { + this.playerManager = playerManager; + this.worldEntityManager = worldEntityManager; + this.entitySimulation = entitySimulation; + } + + public override void Process(VehicleDestroyed packet, Player destroyingPlayer) + { + entitySimulation.EntityDestroyed(packet.Id); + + if (worldEntityManager.TryDestroyEntity(packet.Id, out Entity entity)) + { + if (entity is VehicleWorldEntity vehicleWorldEntity) + { + worldEntityManager.MovePlayerChildrenToRoot(vehicleWorldEntity); + } + foreach (Player player in playerManager.GetConnectedPlayers()) + { + bool isOtherPlayer = player != destroyingPlayer; + if (isOtherPlayer && player.CanSee(entity)) + { + player.SendPacket(packet); + } + } + } + } +} diff --git a/NitroxServer/GameLogic/Entities/WorldEntityManager.cs b/NitroxServer/GameLogic/Entities/WorldEntityManager.cs index 9a57d30b99..7bcab59c4d 100644 --- a/NitroxServer/GameLogic/Entities/WorldEntityManager.cs +++ b/NitroxServer/GameLogic/Entities/WorldEntityManager.cs @@ -116,16 +116,7 @@ public Optional RemoveGlobalRootEntity(NitroxId entityId, bool removeFro // to make sure that they won't be removed if (entityRegistry.TryGetEntityById(entityId, out GlobalRootEntity globalRootEntity)) { - List playerEntities = FindPlayerEntitiesInChildren(globalRootEntity); - foreach (PlayerWorldEntity childPlayerEntity in playerEntities) - { - // Reparent the entity on top of GlobalRoot - globalRootEntity.ChildEntities.Remove(childPlayerEntity); - childPlayerEntity.ParentId = null; - - // Make sure the PlayerEntity is correctly registered - AddOrUpdateGlobalRootEntity(childPlayerEntity); - } + MovePlayerChildrenToRoot(globalRootEntity); } removedEntity = entityRegistry.RemoveEntity(entityId); } @@ -134,6 +125,20 @@ public Optional RemoveGlobalRootEntity(NitroxId entityId, bool removeFro return removedEntity; } + public void MovePlayerChildrenToRoot(GlobalRootEntity globalRootEntity) + { + List playerEntities = FindPlayerEntitiesInChildren(globalRootEntity); + foreach (PlayerWorldEntity childPlayerEntity in playerEntities) + { + // Reparent the entity on top of GlobalRoot + globalRootEntity.ChildEntities.Remove(childPlayerEntity); + childPlayerEntity.ParentId = null; + + // Make sure the PlayerEntity is correctly registered + AddOrUpdateGlobalRootEntity(childPlayerEntity); + } + } + public void TrackEntityInTheWorld(WorldEntity entity) { if (entity is GlobalRootEntity globalRootEntity) @@ -284,16 +289,18 @@ public void StopTrackingEntity(WorldEntity entity) } } - public bool TryDestroyEntity(NitroxId entityId, out Optional entity) + public bool TryDestroyEntity(NitroxId entityId, out Entity entity) { - entity = entityRegistry.RemoveEntity(entityId); + Optional optEntity = entityRegistry.RemoveEntity(entityId); - if (!entity.HasValue) + if (!optEntity.HasValue) { + entity = null; return false; } + entity = optEntity.Value; - if (entity.Value is WorldEntity worldEntity) + if (entity is WorldEntity worldEntity) { StopTrackingEntity(worldEntity); } diff --git a/NitroxServer/Player.cs b/NitroxServer/Player.cs index e7a2d27ac9..fe8996b380 100644 --- a/NitroxServer/Player.cs +++ b/NitroxServer/Player.cs @@ -29,7 +29,7 @@ public class Player : IProcessorContext public bool IsPermaDeath { get; set; } public NitroxVector3 Position { get; set; } public NitroxQuaternion Rotation { get; set; } - public NitroxId GameObjectId { get; } + public NitroxId GameObjectId { get; set; } public Optional SubRootId { get; set; } public Perms Permissions { get; set; } public PlayerStatsData Stats { get; set; } From 3720ed0b7b0365deb065dae2217af81582d80f93 Mon Sep 17 00:00:00 2001 From: dartasen Date: Wed, 21 Aug 2024 22:51:46 +0200 Subject: [PATCH 15/15] Review changes --- .../Processors/PlayerMovementProcessor.cs | 2 -- .../SubFire_EngineOverheatSimulation_Patch.cs | 23 ------------------- .../Dynamic/SubFire_FireSimulation_Patch.cs | 16 ------------- .../Persistent/uGUI_MainMenu_Start_Patch.cs | 1 + 4 files changed, 1 insertion(+), 41 deletions(-) delete mode 100644 NitroxPatcher/Patches/Dynamic/SubFire_EngineOverheatSimulation_Patch.cs delete mode 100644 NitroxPatcher/Patches/Dynamic/SubFire_FireSimulation_Patch.cs diff --git a/NitroxClient/Communication/Packets/Processors/PlayerMovementProcessor.cs b/NitroxClient/Communication/Packets/Processors/PlayerMovementProcessor.cs index 6b7ab5d142..5c37c4227f 100644 --- a/NitroxClient/Communication/Packets/Processors/PlayerMovementProcessor.cs +++ b/NitroxClient/Communication/Packets/Processors/PlayerMovementProcessor.cs @@ -1,7 +1,5 @@ using NitroxClient.Communication.Packets.Processors.Abstract; using NitroxClient.GameLogic; -using NitroxModel.Packets; -using NitroxModel_Subnautica.DataStructures; namespace NitroxClient.Communication.Packets.Processors; diff --git a/NitroxPatcher/Patches/Dynamic/SubFire_EngineOverheatSimulation_Patch.cs b/NitroxPatcher/Patches/Dynamic/SubFire_EngineOverheatSimulation_Patch.cs deleted file mode 100644 index 5d4b38fada..0000000000 --- a/NitroxPatcher/Patches/Dynamic/SubFire_EngineOverheatSimulation_Patch.cs +++ /dev/null @@ -1,23 +0,0 @@ -# if DEBUG -using System.Reflection; -using NitroxModel.Helper; - -namespace NitroxPatcher.Patches.Dynamic; - -/// -/// Disables cyclops overheating from moving while in creative mode (really annoying for no reason) -/// -public sealed partial class SubFire_EngineOverheatSimulation_Patch : NitroxPatch, IDynamicPatch -{ - private static readonly MethodInfo TARGET_METHOD = Reflect.Method((SubFire t) => t.EngineOverheatSimulation()); - - public static bool Prefix() - { - if ((GameModeUtils.currentGameMode & GameModeOption.Creative) != 0) - { - return false; - } - return true; - } -} -#endif diff --git a/NitroxPatcher/Patches/Dynamic/SubFire_FireSimulation_Patch.cs b/NitroxPatcher/Patches/Dynamic/SubFire_FireSimulation_Patch.cs deleted file mode 100644 index 79a5c3123a..0000000000 --- a/NitroxPatcher/Patches/Dynamic/SubFire_FireSimulation_Patch.cs +++ /dev/null @@ -1,16 +0,0 @@ -# if DEBUG -using System.Reflection; -using NitroxModel.Helper; - -namespace NitroxPatcher.Patches.Dynamic; - -/// -/// Disables cyclops overheating from moving while in creative mode (really annoying for no reason) -/// -public sealed partial class SubFire_FireSimulation_Patch : NitroxPatch, IDynamicPatch -{ - private static readonly MethodInfo TARGET_METHOD = Reflect.Method((SubFire t) => t.FireSimulation()); - - public static bool Prefix() => SubFire_EngineOverheatSimulation_Patch.Prefix(); -} -#endif diff --git a/NitroxPatcher/Patches/Persistent/uGUI_MainMenu_Start_Patch.cs b/NitroxPatcher/Patches/Persistent/uGUI_MainMenu_Start_Patch.cs index 876020e616..087ab93bf8 100644 --- a/NitroxPatcher/Patches/Persistent/uGUI_MainMenu_Start_Patch.cs +++ b/NitroxPatcher/Patches/Persistent/uGUI_MainMenu_Start_Patch.cs @@ -7,6 +7,7 @@ namespace NitroxPatcher.Patches.Persistent; +// TODO: Rework this to be less ad hoc and more robust with command line arguments public sealed partial class uGUI_MainMenu_Start_Patch : NitroxPatch, IPersistentPatch { private static readonly MethodInfo TARGET_METHOD_ENUMERATOR = Reflect.Method((uGUI_MainMenu t) => t.Start());