From 1fcd2d9012aac0a74adfb5c62f132491bf7d7f48 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Wed, 17 Jan 2024 20:48:12 +0100 Subject: [PATCH] Sync stasis rifles and torpedos (precise and desync-proof). Fix seamoth inventory not storing torpedos --- .../Dynamic/Bullet_Update_PatchTest.cs | 22 +++ NitroxClient/ClientAutoFacRegistrar.cs | 1 + .../Processors/ExosuitArmActionProcessor.cs | 6 +- .../SeamothModuleActionProcessor.cs | 41 +---- .../Processors/StasisSphereHitProcessor.cs | 21 +++ .../Processors/StasisSphereShotProcessor.cs | 21 +++ .../Packets/Processors/TorpedoHitProcessor.cs | 21 +++ .../Processors/TorpedoShotProcessor.cs | 21 +++ .../TorpedoTargetAcquiredProcessor.cs | 21 +++ NitroxClient/GameLogic/BulletManager.cs | 166 ++++++++++++++++++ NitroxClient/GameLogic/ExosuitModuleEvent.cs | 60 ------- .../HUD/PdaTabs/uGUI_PlayerListTab.cs | 11 +- .../GlobalRootInitialSyncProcessor.cs | 5 +- NitroxClient/GameLogic/PlayerManager.cs | 8 +- NitroxClient/GameLogic/SeamothModulesEvent.cs | 29 +-- .../Spawning/InstalledModuleEntitySpawner.cs | 2 +- NitroxModel/Helper/Mathf.cs | 8 + NitroxModel/Packets/StasisSphereHit.cs | 23 +++ NitroxModel/Packets/StasisSphereShot.cs | 25 +++ NitroxModel/Packets/TorpedoHit.cs | 20 +++ NitroxModel/Packets/TorpedoShot.cs | 27 +++ NitroxModel/Packets/TorpedoTargetAcquired.cs | 22 +++ .../Patches/Dynamic/Bullet_Update_Patch.cs | 65 +++++++ .../ExosuitTorpedoArm_OnUseUp_Patch.cs | 16 -- .../Dynamic/ExosuitTorpedoArm_Shoot_Patch.cs | 34 ---- .../Patches/Dynamic/Flare_Update_Patch.cs | 14 +- .../SeaMoth_OnUpgradeModuleUse_Patch.cs | 3 - .../SeamothTorpedoWhirlpool_Register_Patch.cs | 24 +++ .../Dynamic/SeamothTorpedo_Explode_Patch.cs | 35 ++++ ...SeamothTorpedo_RepeatingTargeting_Patch.cs | 38 ++++ .../Dynamic/SeamothTorpedo_Update_Patch.cs | 21 +++ .../Dynamic/StasisSphere_Freeze_Patch.cs | 19 ++ .../Dynamic/StasisSphere_LateUpdate_Patch.cs | 22 +++ .../Dynamic/StasisSphere_OnHit_Patch.cs | 35 ++++ .../Dynamic/StasisSphere_Shoot_Patch.cs | 26 +++ .../Dynamic/Vehicle_TorpedoShot_Patch.cs | 41 +++++ NitroxPatcher/TranspilerHelper.cs | 27 ++- .../DefaultServerPacketProcessor.cs | 7 +- 38 files changed, 796 insertions(+), 212 deletions(-) create mode 100644 Nitrox.Test/Patcher/Patches/Dynamic/Bullet_Update_PatchTest.cs create mode 100644 NitroxClient/Communication/Packets/Processors/StasisSphereHitProcessor.cs create mode 100644 NitroxClient/Communication/Packets/Processors/StasisSphereShotProcessor.cs create mode 100644 NitroxClient/Communication/Packets/Processors/TorpedoHitProcessor.cs create mode 100644 NitroxClient/Communication/Packets/Processors/TorpedoShotProcessor.cs create mode 100644 NitroxClient/Communication/Packets/Processors/TorpedoTargetAcquiredProcessor.cs create mode 100644 NitroxClient/GameLogic/BulletManager.cs create mode 100644 NitroxModel/Packets/StasisSphereHit.cs create mode 100644 NitroxModel/Packets/StasisSphereShot.cs create mode 100644 NitroxModel/Packets/TorpedoHit.cs create mode 100644 NitroxModel/Packets/TorpedoShot.cs create mode 100644 NitroxModel/Packets/TorpedoTargetAcquired.cs create mode 100644 NitroxPatcher/Patches/Dynamic/Bullet_Update_Patch.cs delete mode 100644 NitroxPatcher/Patches/Dynamic/ExosuitTorpedoArm_OnUseUp_Patch.cs delete mode 100644 NitroxPatcher/Patches/Dynamic/ExosuitTorpedoArm_Shoot_Patch.cs create mode 100644 NitroxPatcher/Patches/Dynamic/SeamothTorpedoWhirlpool_Register_Patch.cs create mode 100644 NitroxPatcher/Patches/Dynamic/SeamothTorpedo_Explode_Patch.cs create mode 100644 NitroxPatcher/Patches/Dynamic/SeamothTorpedo_RepeatingTargeting_Patch.cs create mode 100644 NitroxPatcher/Patches/Dynamic/SeamothTorpedo_Update_Patch.cs create mode 100644 NitroxPatcher/Patches/Dynamic/StasisSphere_Freeze_Patch.cs create mode 100644 NitroxPatcher/Patches/Dynamic/StasisSphere_LateUpdate_Patch.cs create mode 100644 NitroxPatcher/Patches/Dynamic/StasisSphere_OnHit_Patch.cs create mode 100644 NitroxPatcher/Patches/Dynamic/StasisSphere_Shoot_Patch.cs create mode 100644 NitroxPatcher/Patches/Dynamic/Vehicle_TorpedoShot_Patch.cs diff --git a/Nitrox.Test/Patcher/Patches/Dynamic/Bullet_Update_PatchTest.cs b/Nitrox.Test/Patcher/Patches/Dynamic/Bullet_Update_PatchTest.cs new file mode 100644 index 0000000000..15dd8cb2d5 --- /dev/null +++ b/Nitrox.Test/Patcher/Patches/Dynamic/Bullet_Update_PatchTest.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Emit; +using FluentAssertions; +using HarmonyLib; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NitroxTest.Patcher; + +namespace NitroxPatcher.Patches.Dynamic; + +[TestClass] +public class Bullet_Update_PatchTest +{ + [TestMethod] + public void Sanity() + { + ILGenerator generator = Bullet_Update_Patch.TARGET_METHOD.GetILGenerator(); + IEnumerable originalIl = PatchTestHelper.GetInstructionsFromMethod(Bullet_Update_Patch.TARGET_METHOD); + IEnumerable transformedIl = Bullet_Update_Patch.Transpiler(originalIl, generator); + transformedIl.Count().Should().Be(originalIl.Count() + 3); + } +} diff --git a/NitroxClient/ClientAutoFacRegistrar.cs b/NitroxClient/ClientAutoFacRegistrar.cs index 1e10299749..1f4bca3f8d 100644 --- a/NitroxClient/ClientAutoFacRegistrar.cs +++ b/NitroxClient/ClientAutoFacRegistrar.cs @@ -132,6 +132,7 @@ private void RegisterCoreDependencies(ContainerBuilder containerBuilder) containerBuilder.RegisterType().InstancePerLifetimeScope(); containerBuilder.RegisterType().InstancePerLifetimeScope(); containerBuilder.RegisterType().InstancePerLifetimeScope(); + containerBuilder.RegisterType().InstancePerLifetimeScope(); } private void RegisterMetadataDependencies(ContainerBuilder containerBuilder) diff --git a/NitroxClient/Communication/Packets/Processors/ExosuitArmActionProcessor.cs b/NitroxClient/Communication/Packets/Processors/ExosuitArmActionProcessor.cs index 3e8f75ac91..f1b19c9798 100644 --- a/NitroxClient/Communication/Packets/Processors/ExosuitArmActionProcessor.cs +++ b/NitroxClient/Communication/Packets/Processors/ExosuitArmActionProcessor.cs @@ -1,10 +1,9 @@ -using System; using NitroxClient.Communication.Abstract; using NitroxClient.Communication.Packets.Processors.Abstract; using NitroxClient.GameLogic; using NitroxClient.MonoBehaviours; -using NitroxModel_Subnautica.DataStructures; using NitroxModel.DataStructures.Util; +using NitroxModel_Subnautica.DataStructures; using NitroxModel_Subnautica.Packets; using UnityEngine; @@ -42,9 +41,6 @@ public override void Process(ExosuitArmActionPacket packet) case TechType.ExosuitGrapplingArmModule: exosuitModuleEvent.UseGrappling(gameObject.GetComponent(), packet.ArmAction, packet.OpVector?.ToUnity()); break; - case TechType.ExosuitTorpedoArmModule: - exosuitModuleEvent.UseTorpedo(gameObject.GetComponent(), packet.ArmAction, packet.OpVector?.ToUnity(), packet.OpRotation?.ToUnity()); - break; default: Log.Error($"Got an arm tech that is not handled: {packet.TechType} with action: {packet.ArmAction} for id {packet.ArmId}"); break; diff --git a/NitroxClient/Communication/Packets/Processors/SeamothModuleActionProcessor.cs b/NitroxClient/Communication/Packets/Processors/SeamothModuleActionProcessor.cs index 55299f12b6..0c676dd121 100644 --- a/NitroxClient/Communication/Packets/Processors/SeamothModuleActionProcessor.cs +++ b/NitroxClient/Communication/Packets/Processors/SeamothModuleActionProcessor.cs @@ -1,4 +1,4 @@ -using NitroxClient.Communication.Abstract; +using NitroxClient.Communication.Abstract; using NitroxClient.Communication.Packets.Processors.Abstract; using NitroxClient.MonoBehaviours; using NitroxModel.Packets; @@ -35,47 +35,8 @@ public override void Process(SeamothModulesAction packet) component.charge = charge; component.chargeScalar = slotCharge; } - - if (techType == TechType.SeamothTorpedoModule) - { - Transform muzzle = (packet.SlotID != seamoth.GetSlotIndex("SeamothModule1") && packet.SlotID != seamoth.GetSlotIndex("SeamothModule3")) ? seamoth.torpedoTubeRight : seamoth.torpedoTubeLeft; - ItemsContainer storageInSlot = seamoth.GetStorageInSlot(packet.SlotID, TechType.SeamothTorpedoModule); - TorpedoType torpedoType = null; - - for (int i = 0; i < seamoth.torpedoTypes.Length; i++) - { - if (storageInSlot.Contains(seamoth.torpedoTypes[i].techType)) - { - torpedoType = seamoth.torpedoTypes[i]; - break; - } - } - - //Original Function use Player Camera need parse owner camera values - TorpedoShot(storageInSlot, torpedoType, muzzle, packet.Forward.ToUnity(), packet.Rotation.ToUnity()); - } } } } - - //Copied this from the Vehicle class - public static bool TorpedoShot(ItemsContainer container, TorpedoType torpedoType, Transform muzzle, Vector3 forward, Quaternion rotation) - { - if (torpedoType != null && container.DestroyItem(torpedoType.techType)) - { - GameObject gameObject = UnityEngine.Object.Instantiate(torpedoType.prefab); - Transform component = gameObject.GetComponent(); - SeamothTorpedo component2 = gameObject.GetComponent(); - Vector3 zero = Vector3.zero; - Rigidbody componentInParent = muzzle.GetComponentInParent(); - Vector3 rhs = (!(componentInParent != null)) ? Vector3.zero : componentInParent.velocity; - float speed = Vector3.Dot(forward, rhs); - component2.Shoot(muzzle.position, rotation, speed, -1f); - - return true; - } - - return false; - } } } diff --git a/NitroxClient/Communication/Packets/Processors/StasisSphereHitProcessor.cs b/NitroxClient/Communication/Packets/Processors/StasisSphereHitProcessor.cs new file mode 100644 index 0000000000..994270f9f2 --- /dev/null +++ b/NitroxClient/Communication/Packets/Processors/StasisSphereHitProcessor.cs @@ -0,0 +1,21 @@ +using NitroxClient.Communication.Packets.Processors.Abstract; +using NitroxClient.GameLogic; +using NitroxModel.Packets; +using NitroxModel_Subnautica.DataStructures; + +namespace NitroxClient.Communication.Packets.Processors; + +public class StasisSphereHitProcessor : ClientPacketProcessor +{ + private readonly BulletManager bulletManager; + + public StasisSphereHitProcessor(BulletManager bulletManager) + { + this.bulletManager = bulletManager; + } + + public override void Process(StasisSphereHit packet) + { + bulletManager.StasisSphereHit(packet.PlayerId, packet.Position.ToUnity(), packet.Rotation.ToUnity(), packet.ChargeNormalized, packet.Consumption); + } +} diff --git a/NitroxClient/Communication/Packets/Processors/StasisSphereShotProcessor.cs b/NitroxClient/Communication/Packets/Processors/StasisSphereShotProcessor.cs new file mode 100644 index 0000000000..78a8dc1625 --- /dev/null +++ b/NitroxClient/Communication/Packets/Processors/StasisSphereShotProcessor.cs @@ -0,0 +1,21 @@ +using NitroxClient.Communication.Packets.Processors.Abstract; +using NitroxClient.GameLogic; +using NitroxModel.Packets; +using NitroxModel_Subnautica.DataStructures; + +namespace NitroxClient.Communication.Packets.Processors; + +public class StasisSphereShotProcessor : ClientPacketProcessor +{ + private readonly BulletManager bulletManager; + + public StasisSphereShotProcessor(BulletManager bulletManager) + { + this.bulletManager = bulletManager; + } + + public override void Process(StasisSphereShot packet) + { + bulletManager.ShootStasisSphere(packet.PlayerId, packet.Position.ToUnity(), packet.Rotation.ToUnity(), packet.Speed, packet.LifeTime, packet.ChargeNormalized); + } +} diff --git a/NitroxClient/Communication/Packets/Processors/TorpedoHitProcessor.cs b/NitroxClient/Communication/Packets/Processors/TorpedoHitProcessor.cs new file mode 100644 index 0000000000..274be2ae7a --- /dev/null +++ b/NitroxClient/Communication/Packets/Processors/TorpedoHitProcessor.cs @@ -0,0 +1,21 @@ +using NitroxClient.Communication.Packets.Processors.Abstract; +using NitroxClient.GameLogic; +using NitroxModel.Packets; +using NitroxModel_Subnautica.DataStructures; + +namespace NitroxClient.Communication.Packets.Processors; + +public class TorpedoHitProcessor : ClientPacketProcessor +{ + private readonly BulletManager bulletManager; + + public TorpedoHitProcessor(BulletManager bulletManager) + { + this.bulletManager = bulletManager; + } + + public override void Process(TorpedoHit packet) + { + bulletManager.TorpedoHit(packet.BulletId, packet.Position.ToUnity(), packet.Rotation.ToUnity()); + } +} diff --git a/NitroxClient/Communication/Packets/Processors/TorpedoShotProcessor.cs b/NitroxClient/Communication/Packets/Processors/TorpedoShotProcessor.cs new file mode 100644 index 0000000000..aff9d1dd95 --- /dev/null +++ b/NitroxClient/Communication/Packets/Processors/TorpedoShotProcessor.cs @@ -0,0 +1,21 @@ +using NitroxClient.Communication.Packets.Processors.Abstract; +using NitroxClient.GameLogic; +using NitroxModel.Packets; +using NitroxModel_Subnautica.DataStructures; + +namespace NitroxClient.Communication.Packets.Processors; + +public class TorpedoShotProcessor : ClientPacketProcessor +{ + private readonly BulletManager bulletManager; + + public TorpedoShotProcessor(BulletManager bulletManager) + { + this.bulletManager = bulletManager; + } + + public override void Process(TorpedoShot packet) + { + bulletManager.ShootSeamothTorpedo(packet.BulletId, packet.TechType.ToUnity(), packet.Position.ToUnity(), packet.Rotation.ToUnity(), packet.Speed, packet.LifeTime); + } +} diff --git a/NitroxClient/Communication/Packets/Processors/TorpedoTargetAcquiredProcessor.cs b/NitroxClient/Communication/Packets/Processors/TorpedoTargetAcquiredProcessor.cs new file mode 100644 index 0000000000..cc63151f93 --- /dev/null +++ b/NitroxClient/Communication/Packets/Processors/TorpedoTargetAcquiredProcessor.cs @@ -0,0 +1,21 @@ +using NitroxClient.Communication.Packets.Processors.Abstract; +using NitroxClient.GameLogic; +using NitroxModel.Packets; +using NitroxModel_Subnautica.DataStructures; + +namespace NitroxClient.Communication.Packets.Processors; + +public class TorpedoTargetAcquiredProcessor : ClientPacketProcessor +{ + private readonly BulletManager bulletManager; + + public TorpedoTargetAcquiredProcessor(BulletManager bulletManager) + { + this.bulletManager = bulletManager; + } + + public override void Process(TorpedoTargetAcquired packet) + { + bulletManager.TorpedoTargetAcquired(packet.BulletId, packet.TargetId, packet.Position.ToUnity(), packet.Rotation.ToUnity()); + } +} diff --git a/NitroxClient/GameLogic/BulletManager.cs b/NitroxClient/GameLogic/BulletManager.cs new file mode 100644 index 0000000000..01659b6df2 --- /dev/null +++ b/NitroxClient/GameLogic/BulletManager.cs @@ -0,0 +1,166 @@ +using System.Collections; +using System.Collections.Generic; +using NitroxClient.GameLogic.Spawning.WorldEntities; +using NitroxClient.MonoBehaviours; +using NitroxClient.Unity.Helper; +using NitroxModel.DataStructures; +using UnityEngine; + +namespace NitroxClient.GameLogic; + +public class BulletManager +{ + private readonly PlayerManager playerManager; + + // This only allows for one stasis sphere per player + // (which is the normal capacity, but could be adapted for a mod letting multiple stasis spheres) + private readonly Dictionary stasisSpherePerPlayerId = []; + + /// + /// TechTypes of objects which should have a Vehicle MB + /// + public static readonly HashSet PreloadedVehicleTypes = [ + TechType.Seamoth, TechType.Exosuit + ]; + + private readonly Dictionary torpedoPrefabByTechType = []; + + private GameObject stasisSpherePrefab { get; set; } + + public BulletManager(PlayerManager playerManager) + { + this.playerManager = playerManager; + } + + public void ShootSeamothTorpedo(NitroxId bulletId, TechType techType, Vector3 position, Quaternion rotation, float speed, float lifeTime) + { + if (!torpedoPrefabByTechType.TryGetValue(techType, out GameObject prefab)) + { + Log.ErrorOnce($"[{nameof(BulletManager)}] Received ShootSeamothTorpedo request with TechType: {techType} but no prefab was loaded for it"); + return; + } + + GameObject torpedoClone = GameObjectHelper.SpawnFromPrefab(prefab, bulletId); + // We mark it to be able to ignore events from remote bullets + torpedoClone.AddComponent(); + + // We cast it to Bullet to ensure we're calling the same method as in Vehicle.TorpedoShot + Bullet seamothTorpedo = torpedoClone.GetComponent(); + seamothTorpedo.Shoot(position, rotation, speed, lifeTime); + } + + public void TorpedoHit(NitroxId bulletId, Vector3 position, Quaternion rotation) + { + if (NitroxEntity.TryGetComponentFrom(bulletId, out SeamothTorpedo torpedo)) + { + torpedo.tr.position = position; + torpedo.tr.rotation = rotation; + torpedo.OnHit(default); + torpedo.Deactivate(); + } + } + + public void TorpedoTargetAcquired(NitroxId bulletId, NitroxId targetId, Vector3 position, Quaternion rotation) + { + // The target object might not be findable in which case we'll just ignore it + // because the explosion will still be moved to the right spot + if (NitroxEntity.TryGetComponentFrom(bulletId, out SeamothTorpedo torpedo) && + NitroxEntity.TryGetObjectFrom(targetId, out GameObject targetObject)) + { + torpedo.tr.position = position; + torpedo.tr.rotation = rotation; + // Stuff from SeamothTorpedo.RepeatingTargeting + torpedo.homingTarget = targetObject; + torpedo.CancelInvoke(); + } + } + + public void ShootStasisSphere(ushort playerId, Vector3 position, Quaternion rotation, float speed, float lifeTime, float chargeNormalized) + { + if (!stasisSpherePerPlayerId.TryGetValue(playerId, out StasisSphere cloneSphere) || !cloneSphere) + { + cloneSphere = EnsurePlayerHasSphere(playerId); + } + + cloneSphere.Shoot(position, rotation, speed, lifeTime, chargeNormalized); + } + + public void StasisSphereHit(ushort playerId, Vector3 position, Quaternion rotation, float chargeNormalized, float consumption) + { + StasisSphere cloneSphere = EnsurePlayerHasSphere(playerId); + + // Setup the sphere in case it the shot was sent earlier + cloneSphere.Shoot(position, rotation, 0, 0, chargeNormalized); + // We override this field (set by .Shoot) with the right data + cloneSphere._consumption = consumption; + + // Code from Bullet.Update when finding an object to hit + cloneSphere._visible = true; + cloneSphere.OnMadeVisible(); + cloneSphere.OnHit(default); + cloneSphere.Deactivate(); + } + + private StasisSphere EnsurePlayerHasSphere(ushort playerId) + { + if (stasisSpherePerPlayerId.TryGetValue(playerId, out StasisSphere remoteSphere) && remoteSphere) + { + return remoteSphere; + } + + // It should be set to inactive automatically in Bullet.Awake + GameObject playerSphereClone = GameObject.Instantiate(stasisSpherePrefab); + // We mark it to be able to ignore events from remote bullets + playerSphereClone.AddComponent(); + StasisSphere stasisSphere = playerSphereClone.GetComponent(); + + stasisSpherePerPlayerId[playerId] = stasisSphere; + return stasisSphere; + } + + private void DestroyPlayerSphere(ushort playerId) + { + if (stasisSpherePerPlayerId.TryGetValue(playerId, out StasisSphere stasisSphere) && stasisSphere) + { + GameObject.Destroy(stasisSphere.gameObject); + } + stasisSpherePerPlayerId.Remove(playerId); + } + + public IEnumerator Initialize() + { + TaskResult result = new(); + + // Load torpedo types prefab and store them by tech type + foreach (TechType techType in PreloadedVehicleTypes) + { + yield return DefaultWorldEntitySpawner.RequestPrefab(techType, result); + if (result.value && result.value.TryGetComponent(out Vehicle vehicle) && vehicle.torpedoTypes != null) + { + foreach (TorpedoType torpedoType in vehicle.torpedoTypes) + { + torpedoPrefabByTechType[torpedoType.techType] = torpedoType.prefab; + } + } + } + + // Load the stasis sphere prefab + yield return DefaultWorldEntitySpawner.RequestPrefab(TechType.StasisRifle, result); + StasisRifle rifle = result.value.GetComponent(); + if (rifle) + { + stasisSpherePrefab = rifle.effectSpherePrefab; + } + + // Setup remote players' stasis spheres + foreach (RemotePlayer remotePlayer in playerManager.GetAll()) + { + EnsurePlayerHasSphere(remotePlayer.PlayerId); + } + + playerManager.onCreate += (playerId, _) => { EnsurePlayerHasSphere(playerId); }; + playerManager.onRemove += (playerId, _) => { DestroyPlayerSphere(playerId); }; + } + + public class RemotePlayerBullet : MonoBehaviour { } +} diff --git a/NitroxClient/GameLogic/ExosuitModuleEvent.cs b/NitroxClient/GameLogic/ExosuitModuleEvent.cs index 2c93ba5ccb..82dcab5a19 100644 --- a/NitroxClient/GameLogic/ExosuitModuleEvent.cs +++ b/NitroxClient/GameLogic/ExosuitModuleEvent.cs @@ -123,65 +123,5 @@ public void UseGrappling(ExosuitGrapplingArm grapplingArm, ExosuitArmAction armA Log.Error($"Grappling arm got an arm action he should not get: {armAction}"); } } - - public void UseTorpedo(ExosuitTorpedoArm torpedoArm, ExosuitArmAction armAction, Vector3? opVector, Quaternion? opRotation) - { - if (armAction == ExosuitArmAction.START_USE_TOOL || armAction == ExosuitArmAction.ALT_HIT) - { - if (!opVector.HasValue || !opRotation.HasValue) - { - Log.Error("Torpedo arm action shoot: no vector or rotation present"); - return; - } - - Vector3 forward = opVector.Value; - Quaternion rotation = opRotation.Value; - Transform silo; - if (armAction == ExosuitArmAction.START_USE_TOOL) - { - silo = torpedoArm.siloFirst; - } - else - { - silo = torpedoArm.siloSecond; - } - - ItemsContainer container = torpedoArm.container; - Exosuit exosuit = torpedoArm.GetComponentInParent(); - TorpedoType[] torpedoTypes = exosuit.torpedoTypes; - - TorpedoType torpedoType = null; - for (int i = 0; i < torpedoTypes.Length; i++) - { - if (container.Contains(torpedoTypes[i].techType)) - { - torpedoType = torpedoTypes[i]; - break; - } - } - - // Copied from SeamothModuleActionProcessor. We need to synchronize both methods - GameObject gameObject = UnityEngine.Object.Instantiate(torpedoType.prefab); - SeamothTorpedo component2 = gameObject.GetComponent(); - Rigidbody componentInParent = silo.GetComponentInParent(); - Vector3 rhs = (!(componentInParent != null)) ? Vector3.zero : componentInParent.velocity; - float speed = Vector3.Dot(forward, rhs); - component2.Shoot(silo.position, rotation, speed, -1f); - - torpedoArm.animator.SetBool("use_tool", true); - if (container.count == 0) - { - Utils.PlayFMODAsset(torpedoArm.torpedoDisarmed, torpedoArm.transform, 1f); - } - } - else if (armAction == ExosuitArmAction.END_USE_TOOL) - { - torpedoArm.animator.SetBool("use_tool", false); - } - else - { - Log.Error($"Torpedo arm got an arm action he should not get: {armAction}"); - } - } } } diff --git a/NitroxClient/GameLogic/HUD/PdaTabs/uGUI_PlayerListTab.cs b/NitroxClient/GameLogic/HUD/PdaTabs/uGUI_PlayerListTab.cs index 7973e58c6b..3517fcb14c 100644 --- a/NitroxClient/GameLogic/HUD/PdaTabs/uGUI_PlayerListTab.cs +++ b/NitroxClient/GameLogic/HUD/PdaTabs/uGUI_PlayerListTab.cs @@ -224,19 +224,20 @@ private void AddNewEntry(string playerId, INitroxPlayer player) entries.Add(playerId, entry); } - private void OnAdd(string playerId, RemotePlayer remotePlayer) + private void OnAdd(ushort playerId, RemotePlayer remotePlayer) { _isDirty = true; } - private void OnRemove(string playerId, RemotePlayer remotePlayers) + private void OnRemove(ushort playerId, RemotePlayer remotePlayers) { - if (!entries.ContainsKey(playerId)) + string playerIdString = playerId.ToString(); + if (!entries.ContainsKey(playerIdString)) { return; } - uGUI_PlayerPingEntry entry = entries[playerId]; - entries.Remove(playerId); + uGUI_PlayerPingEntry entry = entries[playerIdString]; + entries.Remove(playerIdString); pool.Release(entry); _isDirty = true; } diff --git a/NitroxClient/GameLogic/InitialSync/GlobalRootInitialSyncProcessor.cs b/NitroxClient/GameLogic/InitialSync/GlobalRootInitialSyncProcessor.cs index e51daec4ff..8a185754c0 100644 --- a/NitroxClient/GameLogic/InitialSync/GlobalRootInitialSyncProcessor.cs +++ b/NitroxClient/GameLogic/InitialSync/GlobalRootInitialSyncProcessor.cs @@ -16,10 +16,12 @@ namespace NitroxClient.GameLogic.InitialSync; public class GlobalRootInitialSyncProcessor : InitialSyncProcessor { private readonly Entities entities; + private readonly BulletManager bulletManager; - public GlobalRootInitialSyncProcessor(Entities entities) + public GlobalRootInitialSyncProcessor(Entities entities, BulletManager bulletManager) { this.entities = entities; + this.bulletManager = bulletManager; // As we migrate systems over to entities, we want to ensure the required components are in place to spawn these entities. // For example, migrating inventories to the entity system requires players are spawned in the world before we try to add @@ -35,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 bulletManager.Initialize(); BuildingHandler.Main.InitializeOperations(packet.BuildOperationIds); diff --git a/NitroxClient/GameLogic/PlayerManager.cs b/NitroxClient/GameLogic/PlayerManager.cs index 5acf1014bf..88cbf55f3a 100644 --- a/NitroxClient/GameLogic/PlayerManager.cs +++ b/NitroxClient/GameLogic/PlayerManager.cs @@ -53,7 +53,7 @@ public RemotePlayer Create(PlayerContext playerContext) RemotePlayer remotePlayer = new(playerContext, playerModelManager, playerVitalsManager); playersById.Add(remotePlayer.PlayerId, remotePlayer); - onCreate(remotePlayer.PlayerId.ToString(), remotePlayer); + onCreate(remotePlayer.PlayerId, remotePlayer); DiscordClient.UpdatePartySize(GetTotalPlayerCount()); @@ -67,7 +67,7 @@ public void RemovePlayer(ushort playerId) { opPlayer.Value.Destroy(); playersById.Remove(playerId); - onRemove(playerId.ToString(), opPlayer.Value); + onRemove(playerId, opPlayer.Value); DiscordClient.UpdatePartySize(GetTotalPlayerCount()); } } @@ -77,7 +77,7 @@ public int GetTotalPlayerCount() return playersById.Count + 1; //Multiplayer-player(s) + you } - public delegate void OnCreate(string playerId, RemotePlayer remotePlayer); - public delegate void OnRemove(string playerId, RemotePlayer remotePlayer); + public delegate void OnCreate(ushort playerId, RemotePlayer remotePlayer); + public delegate void OnRemove(ushort playerId, RemotePlayer remotePlayer); } } diff --git a/NitroxClient/GameLogic/SeamothModulesEvent.cs b/NitroxClient/GameLogic/SeamothModulesEvent.cs index 818add9f3b..1eb4114897 100644 --- a/NitroxClient/GameLogic/SeamothModulesEvent.cs +++ b/NitroxClient/GameLogic/SeamothModulesEvent.cs @@ -1,4 +1,4 @@ -using NitroxClient.Communication.Abstract; +using NitroxClient.Communication.Abstract; using NitroxModel.DataStructures; using NitroxModel.Packets; using NitroxModel_Subnautica.DataStructures; @@ -15,33 +15,6 @@ public SeamothModulesEvent(IPacketSender packetSender) this.packetSender = packetSender; } - public void BroadcastTorpedoLaunch(TechType techType, int slotID, SeaMoth instance) - { - if (!instance.TryGetIdOrWarn(out NitroxId id)) - { - return; - } - - TorpedoType torpedoType = null; - ItemsContainer storageInSlot = instance.GetStorageInSlot(slotID, TechType.SeamothTorpedoModule); - - for (int i = 0; i < instance.torpedoTypes.Length; i++) - { - if (storageInSlot.Contains(instance.torpedoTypes[i].techType)) - { - torpedoType = instance.torpedoTypes[i]; - break; - } - } - - if (torpedoType != null) // Dont send packet if torpedo storage is empty - { - Transform aimingTransform = Player.main.camRoot.GetAimingTransform(); - SeamothModulesAction changed = new SeamothModulesAction(techType.ToDto(), slotID, id, aimingTransform.forward.ToDto(), aimingTransform.rotation.ToDto()); - packetSender.Send(changed); - } - } - public void BroadcastElectricalDefense(TechType techType, int slotID, SeaMoth instance) { if (!instance.TryGetIdOrWarn(out NitroxId id)) diff --git a/NitroxClient/GameLogic/Spawning/InstalledModuleEntitySpawner.cs b/NitroxClient/GameLogic/Spawning/InstalledModuleEntitySpawner.cs index fb9ee3ad6a..87f3f958c4 100644 --- a/NitroxClient/GameLogic/Spawning/InstalledModuleEntitySpawner.cs +++ b/NitroxClient/GameLogic/Spawning/InstalledModuleEntitySpawner.cs @@ -51,7 +51,7 @@ protected override bool SpawnSync(InstalledModuleEntity entity, TaskResult true; + protected override bool SpawnsOwnChildren(InstalledModuleEntity entity) => false; private bool CanSpawn(InstalledModuleEntity entity, out GameObject parentObject, out Equipment equipment, out string errorLog) { diff --git a/NitroxModel/Helper/Mathf.cs b/NitroxModel/Helper/Mathf.cs index 4949d1a7e2..71fc06905e 100644 --- a/NitroxModel/Helper/Mathf.cs +++ b/NitroxModel/Helper/Mathf.cs @@ -70,5 +70,13 @@ public static float Lerp(float a, float b, float t) { return a + (b - a) * t; } + + /// + /// Reciprocal function of . Unlerp(a, b, Lerp(a, b, t)) = t + /// + public static float Unlerp(float a, float b, float lerpedResult) + { + return (lerpedResult - a) / (b - a); + } } } diff --git a/NitroxModel/Packets/StasisSphereHit.cs b/NitroxModel/Packets/StasisSphereHit.cs new file mode 100644 index 0000000000..b28ad5560e --- /dev/null +++ b/NitroxModel/Packets/StasisSphereHit.cs @@ -0,0 +1,23 @@ +using System; +using NitroxModel.DataStructures.Unity; + +namespace NitroxModel.Packets; + +[Serializable] +public class StasisSphereHit : Packet +{ + public ushort PlayerId { get; } + public NitroxVector3 Position { get; } + public NitroxQuaternion Rotation { get; } + public float ChargeNormalized { get; } + public float Consumption { get; } + + public StasisSphereHit(ushort playerId, NitroxVector3 position, NitroxQuaternion rotation, float chargeNormalized, float consumption) + { + PlayerId = playerId; + Position = position; + Rotation = rotation; + ChargeNormalized = chargeNormalized; + Consumption = consumption; + } +} diff --git a/NitroxModel/Packets/StasisSphereShot.cs b/NitroxModel/Packets/StasisSphereShot.cs new file mode 100644 index 0000000000..dfb2bf5dca --- /dev/null +++ b/NitroxModel/Packets/StasisSphereShot.cs @@ -0,0 +1,25 @@ +using System; +using NitroxModel.DataStructures.Unity; + +namespace NitroxModel.Packets; + +[Serializable] +public class StasisSphereShot : Packet +{ + public ushort PlayerId { get; } + public NitroxVector3 Position { get; } + public NitroxQuaternion Rotation { get; } + public float Speed { get; } + public float LifeTime { get; } + public float ChargeNormalized { get; } + + public StasisSphereShot(ushort playerId, NitroxVector3 position, NitroxQuaternion rotation, float speed, float lifeTime, float chargeNormalized) + { + PlayerId = playerId; + Position = position; + Rotation = rotation; + Speed = speed; + LifeTime = lifeTime; + ChargeNormalized = chargeNormalized; + } +} diff --git a/NitroxModel/Packets/TorpedoHit.cs b/NitroxModel/Packets/TorpedoHit.cs new file mode 100644 index 0000000000..12c44fc845 --- /dev/null +++ b/NitroxModel/Packets/TorpedoHit.cs @@ -0,0 +1,20 @@ +using System; +using NitroxModel.DataStructures; +using NitroxModel.DataStructures.Unity; + +namespace NitroxModel.Packets; + +[Serializable] +public class TorpedoHit : Packet +{ + public NitroxId BulletId { get; } + public NitroxVector3 Position { get; } + public NitroxQuaternion Rotation { get; } + + public TorpedoHit(NitroxId bulletId, NitroxVector3 position, NitroxQuaternion rotation) + { + BulletId = bulletId; + Position = position; + Rotation = rotation; + } +} diff --git a/NitroxModel/Packets/TorpedoShot.cs b/NitroxModel/Packets/TorpedoShot.cs new file mode 100644 index 0000000000..aaa99692be --- /dev/null +++ b/NitroxModel/Packets/TorpedoShot.cs @@ -0,0 +1,27 @@ +using System; +using NitroxModel.DataStructures; +using NitroxModel.DataStructures.GameLogic; +using NitroxModel.DataStructures.Unity; + +namespace NitroxModel.Packets; + +[Serializable] +public class TorpedoShot : Packet +{ + public NitroxId BulletId { get; } + public NitroxTechType TechType { get; } + public NitroxVector3 Position { get; } + public NitroxQuaternion Rotation { get; } + public float Speed { get; } + public float LifeTime { get; } + + public TorpedoShot(NitroxId bulletId, NitroxTechType techType, NitroxVector3 position, NitroxQuaternion rotation, float speed, float lifeTime) + { + BulletId = bulletId; + TechType = techType; + Position = position; + Rotation = rotation; + Speed = speed; + LifeTime = lifeTime; + } +} diff --git a/NitroxModel/Packets/TorpedoTargetAcquired.cs b/NitroxModel/Packets/TorpedoTargetAcquired.cs new file mode 100644 index 0000000000..bba38f6bdd --- /dev/null +++ b/NitroxModel/Packets/TorpedoTargetAcquired.cs @@ -0,0 +1,22 @@ +using System; +using NitroxModel.DataStructures; +using NitroxModel.DataStructures.Unity; + +namespace NitroxModel.Packets; + +[Serializable] +public class TorpedoTargetAcquired : Packet +{ + public NitroxId BulletId { get; } + public NitroxId TargetId { get; } + public NitroxVector3 Position { get; } + public NitroxQuaternion Rotation { get; } + + public TorpedoTargetAcquired(NitroxId bulletId, NitroxId targetId, NitroxVector3 position, NitroxQuaternion rotation) + { + BulletId = bulletId; + TargetId = targetId; + Position = position; + Rotation = rotation; + } +} diff --git a/NitroxPatcher/Patches/Dynamic/Bullet_Update_Patch.cs b/NitroxPatcher/Patches/Dynamic/Bullet_Update_Patch.cs new file mode 100644 index 0000000000..3545d6440c --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/Bullet_Update_Patch.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; +using HarmonyLib; +using NitroxClient.GameLogic; +using NitroxModel.Helper; +using UnityEngine; + +namespace NitroxPatcher.Patches.Dynamic; + +/// +/// Replaces local use of by and prevents remote bullets from detecting collisions +/// +public sealed partial class Bullet_Update_Patch : NitroxPatch, IDynamicPatch +{ + internal static readonly MethodInfo TARGET_METHOD = Reflect.Method((Bullet t) => t.Update()); + + /* + * RaycastHit raycastHit; + * REPLACE: + * if (Physics.SphereCast(this.tr.position, this.shellRadius, this.tr.forward, out raycastHit, num, this.layerMask.value)) + * { + * num = raycastHit.distance; + * BY: + * if (!Bullet_Update_Patch.IsRemoteObject(this) && Physics.SphereCast(this.tr.position, this.shellRadius, this.tr.forward, out raycastHit, num, this.layerMask.value)) + * { + * num = raycastHit.distance; + */ + public static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + Label label = generator.DefineLabel(); + + // Replace the two occurences of Time.deltaTime + return new CodeMatcher(instructions).ReplaceDeltaTime() + .ReplaceDeltaTime() + .MatchStartForward([ + new CodeMatch(OpCodes.Ldarg_0), + new CodeMatch(OpCodes.Call, Reflect.Property((Bullet t) => t.tr).GetGetMethod()), + new CodeMatch(OpCodes.Callvirt, Reflect.Property((Transform t) => t.position).GetGetMethod()), + new CodeMatch(OpCodes.Ldarg_0), + ]) + // Skip the Ldarg_0 because it is the previous ifs' jump target + .Advance(1) + // Insert if (!Bullet_Update_Patch.IsRemoteObject(this)) before the condition + .InsertAndAdvance([ + new CodeInstruction(OpCodes.Call, Reflect.Method(() => IsRemoteObject(default))), + new CodeInstruction(OpCodes.Brtrue_S, label), + new CodeInstruction(OpCodes.Ldarg_0), + ]) + // Find the destination of the position to go to + .MatchStartForward([ + new CodeMatch(OpCodes.Ldarg_0), + new CodeMatch(OpCodes.Call, Reflect.Property((Bullet t) => t.tr).GetGetMethod()), + new CodeMatch(OpCodes.Dup), + new CodeMatch(OpCodes.Callvirt, Reflect.Property((Transform t) => t.position).GetGetMethod()) + ]) + .AddLabels([label]) + .InstructionEnumeration(); + } + + public static bool IsRemoteObject(Bullet bullet) + { + return bullet.GetComponent(); + } +} diff --git a/NitroxPatcher/Patches/Dynamic/ExosuitTorpedoArm_OnUseUp_Patch.cs b/NitroxPatcher/Patches/Dynamic/ExosuitTorpedoArm_OnUseUp_Patch.cs deleted file mode 100644 index 0cb5554087..0000000000 --- a/NitroxPatcher/Patches/Dynamic/ExosuitTorpedoArm_OnUseUp_Patch.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Reflection; -using NitroxClient.GameLogic; -using NitroxModel_Subnautica.Packets; -using NitroxModel.Helper; - -namespace NitroxPatcher.Patches.Dynamic; - -public sealed partial class ExosuitTorpedoArm_OnUseUp_Patch : NitroxPatch, IDynamicPatch -{ - public static readonly MethodInfo TARGET_METHOD = Reflect.Method((ExosuitTorpedoArm t) => ((IExosuitArm)t).OnUseUp(out Reflect.Ref.Field)); - - public static void Prefix(ExosuitTorpedoArm __instance) - { - Resolve().BroadcastArmAction(TechType.ExosuitTorpedoArmModule, __instance, ExosuitArmAction.END_USE_TOOL); - } -} diff --git a/NitroxPatcher/Patches/Dynamic/ExosuitTorpedoArm_Shoot_Patch.cs b/NitroxPatcher/Patches/Dynamic/ExosuitTorpedoArm_Shoot_Patch.cs deleted file mode 100644 index ab64fe9db1..0000000000 --- a/NitroxPatcher/Patches/Dynamic/ExosuitTorpedoArm_Shoot_Patch.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Reflection; -using NitroxClient.GameLogic; -using NitroxModel.Helper; -using NitroxModel_Subnautica.Packets; -using UnityEngine; - -namespace NitroxPatcher.Patches.Dynamic; - -public sealed partial class ExosuitTorpedoArm_Shoot_Patch : NitroxPatch, IDynamicPatch -{ - public static readonly MethodInfo TARGET_METHOD = Reflect.Method((ExosuitTorpedoArm t) => t.Shoot(default(TorpedoType), default(Transform), default(bool))); - - public static void Prefix(ExosuitTorpedoArm __instance, bool __result, TorpedoType torpedoType, Transform siloTransform) - { - if (torpedoType != null) - { - ExosuitArmAction action = ExosuitArmAction.START_USE_TOOL; - if (siloTransform == __instance.siloSecond) - { - action = ExosuitArmAction.ALT_HIT; - } - if (siloTransform != __instance.siloFirst && siloTransform != __instance.siloSecond) - { - Log.Error($"Exosuit torpedo arm siloTransform is not first or second silo {__instance.GetId()}"); - } - Resolve().BroadcastArmAction(TechType.ExosuitTorpedoArmModule, - __instance, - action, - Player.main.camRoot.GetAimingTransform().forward, - Player.main.camRoot.GetAimingTransform().rotation - ); - } - } -} diff --git a/NitroxPatcher/Patches/Dynamic/Flare_Update_Patch.cs b/NitroxPatcher/Patches/Dynamic/Flare_Update_Patch.cs index 50e884c60d..54f2ced231 100644 --- a/NitroxPatcher/Patches/Dynamic/Flare_Update_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/Flare_Update_Patch.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Reflection; -using System.Reflection.Emit; using HarmonyLib; using NitroxClient.GameLogic; using NitroxModel.Helper; @@ -14,21 +13,10 @@ namespace NitroxPatcher.Patches.Dynamic; public sealed partial class Flare_Update_Patch : NitroxPatch, IDynamicPatch { public static readonly MethodInfo TARGET_METHOD = Reflect.Method((Flare t) => t.Update()); - private static readonly MethodInfo INSERTED_METHOD = Reflect.Method(() => GetDeltaTime()); - private static readonly MethodInfo MATCHING_FIELD = Reflect.Property(() => Time.deltaTime).GetGetMethod(); public static IEnumerable Transpiler(IEnumerable instructions) { - return new CodeMatcher(instructions).MatchStartForward(new CodeMatch(OpCodes.Call, MATCHING_FIELD)) - .SetOperandAndAdvance(INSERTED_METHOD) + return new CodeMatcher(instructions).ReplaceDeltaTime() .InstructionEnumeration(); } - - /// - /// Wrapper for dependency resolving and variable querying - /// - public static float GetDeltaTime() - { - return Resolve().DeltaTime; - } } diff --git a/NitroxPatcher/Patches/Dynamic/SeaMoth_OnUpgradeModuleUse_Patch.cs b/NitroxPatcher/Patches/Dynamic/SeaMoth_OnUpgradeModuleUse_Patch.cs index 0ea6d2ba9e..93a81ce54f 100644 --- a/NitroxPatcher/Patches/Dynamic/SeaMoth_OnUpgradeModuleUse_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/SeaMoth_OnUpgradeModuleUse_Patch.cs @@ -15,9 +15,6 @@ public static bool Prefix(SeaMoth __instance, TechType techType, int slotID) case TechType.SeamothElectricalDefense: Resolve().BroadcastElectricalDefense(techType, slotID, __instance); break; - case TechType.SeamothTorpedoModule: - Resolve().BroadcastTorpedoLaunch(techType, slotID, __instance); - break; } return true; diff --git a/NitroxPatcher/Patches/Dynamic/SeamothTorpedoWhirlpool_Register_Patch.cs b/NitroxPatcher/Patches/Dynamic/SeamothTorpedoWhirlpool_Register_Patch.cs new file mode 100644 index 0000000000..65c9d517b0 --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/SeamothTorpedoWhirlpool_Register_Patch.cs @@ -0,0 +1,24 @@ +using System.Reflection; +using NitroxClient.GameLogic.PlayerLogic; +using NitroxModel.Helper; +using UnityEngine; + +namespace NitroxPatcher.Patches.Dynamic; + +/// +/// Prevents remote players from being drawn by torpedo's whirlpools +/// +public sealed partial class SeamothTorpedoWhirlpool_Register_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((SeamothTorpedoWhirlpool t) => t.Register(default, ref Reflect.Ref.Field)); + + public static bool Prefix(Collider other, ref bool __result) + { + if (other.GetComponentInParent()) + { + __result = false; + return false; + } + return true; + } +} diff --git a/NitroxPatcher/Patches/Dynamic/SeamothTorpedo_Explode_Patch.cs b/NitroxPatcher/Patches/Dynamic/SeamothTorpedo_Explode_Patch.cs new file mode 100644 index 0000000000..2ef9d56151 --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/SeamothTorpedo_Explode_Patch.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using NitroxClient.Communication.Abstract; +using NitroxClient.GameLogic; +using NitroxModel.DataStructures; +using NitroxModel.DataStructures.Unity; +using NitroxModel.Helper; +using NitroxModel.Packets; +using NitroxModel_Subnautica.DataStructures; + +namespace NitroxPatcher.Patches.Dynamic; + +/// +/// Broadcasts torpedo explosion +/// +public sealed partial class SeamothTorpedo_Explode_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((SeamothTorpedo t) => t.Explode()); + + public static void Prefix(SeamothTorpedo __instance) + { + if (__instance.GetComponent() || !__instance.TryGetNitroxId(out NitroxId bulletId)) + { + return; + } + // When Bullet.Update detects a collision with the spherecast, it calls Deactivate which sets _energy to 0 + // So Bullet.OnEnergyDepleted is also called by Bullet.Update, therefore this patch is executed twice + // We can mark the torpedo as if it was a remote torpedo so we don't send a hit packet twice + __instance.gameObject.AddComponent(); + + NitroxVector3 position = __instance.tr.position.ToDto(); + NitroxQuaternion rotation = __instance.tr.rotation.ToDto(); + + Resolve().Send(new TorpedoHit(bulletId, position, rotation)); + } +} diff --git a/NitroxPatcher/Patches/Dynamic/SeamothTorpedo_RepeatingTargeting_Patch.cs b/NitroxPatcher/Patches/Dynamic/SeamothTorpedo_RepeatingTargeting_Patch.cs new file mode 100644 index 0000000000..7addc1aac2 --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/SeamothTorpedo_RepeatingTargeting_Patch.cs @@ -0,0 +1,38 @@ +using System.Reflection; +using NitroxClient.Communication.Abstract; +using NitroxClient.GameLogic; +using NitroxModel.DataStructures; +using NitroxModel.DataStructures.Unity; +using NitroxModel.Helper; +using NitroxModel.Packets; +using NitroxModel_Subnautica.DataStructures; + +namespace NitroxPatcher.Patches.Dynamic; + +/// +/// Prevents remote torpedos from acquiring target on their own, and broadcast the new target for simulated torpedos +/// +public sealed partial class SeamothTorpedo_RepeatingTargeting_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((SeamothTorpedo t) => t.RepeatingTargeting()); + + public static bool Prefix(SeamothTorpedo __instance) + { + return !__instance.GetComponent(); + } + + public static void Postfix(SeamothTorpedo __instance) + { + // This function's last iteration is marked by SeamothTorpedo.homingTorpedo being defined + if (__instance.GetComponent() || !__instance.homingTarget || + !__instance.TryGetNitroxId(out NitroxId bulletId) || !__instance.homingTarget.TryGetNitroxId(out NitroxId targetId)) + { + return; + } + + NitroxVector3 position = __instance.tr.position.ToDto(); + NitroxQuaternion rotation = __instance.tr.rotation.ToDto(); + + Resolve().Send(new TorpedoTargetAcquired(bulletId, targetId, position, rotation)); + } +} diff --git a/NitroxPatcher/Patches/Dynamic/SeamothTorpedo_Update_Patch.cs b/NitroxPatcher/Patches/Dynamic/SeamothTorpedo_Update_Patch.cs new file mode 100644 index 0000000000..dd456c69f2 --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/SeamothTorpedo_Update_Patch.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Reflection; +using HarmonyLib; +using NitroxClient.GameLogic; +using NitroxModel.Helper; +using UnityEngine; + +namespace NitroxPatcher.Patches.Dynamic; +/// +/// Replaces local use of by +/// +public sealed partial class SeamothTorpedo_Update_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((SeamothTorpedo t) => t.Update()); + + public static IEnumerable Transpiler(IEnumerable instructions) + { + return new CodeMatcher(instructions).ReplaceDeltaTime() + .InstructionEnumeration(); + } +} diff --git a/NitroxPatcher/Patches/Dynamic/StasisSphere_Freeze_Patch.cs b/NitroxPatcher/Patches/Dynamic/StasisSphere_Freeze_Patch.cs new file mode 100644 index 0000000000..25752088c6 --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/StasisSphere_Freeze_Patch.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using NitroxClient.GameLogic.PlayerLogic; +using NitroxModel.Helper; +using UnityEngine; + +namespace NitroxPatcher.Patches.Dynamic; + +/// +/// Prevents remote players from being frozen by stasis fields +/// +public sealed partial class StasisSphere_Freeze_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((StasisSphere t) => t.Freeze(default, ref Reflect.Ref.Field)); + + public static bool Prefix(Collider other, ref bool __result) + { + return other.GetComponentInParent(); + } +} diff --git a/NitroxPatcher/Patches/Dynamic/StasisSphere_LateUpdate_Patch.cs b/NitroxPatcher/Patches/Dynamic/StasisSphere_LateUpdate_Patch.cs new file mode 100644 index 0000000000..28bf9833d7 --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/StasisSphere_LateUpdate_Patch.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Reflection; +using HarmonyLib; +using NitroxClient.GameLogic; +using NitroxModel.Helper; +using UnityEngine; + +namespace NitroxPatcher.Patches.Dynamic; + +/// +/// Replaces local use of by +/// +public sealed partial class StasisSphere_LateUpdate_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((StasisSphere t) => t.LateUpdate()); + + public static IEnumerable Transpiler(IEnumerable instructions) + { + return new CodeMatcher(instructions).ReplaceDeltaTime() + .InstructionEnumeration(); + } +} diff --git a/NitroxPatcher/Patches/Dynamic/StasisSphere_OnHit_Patch.cs b/NitroxPatcher/Patches/Dynamic/StasisSphere_OnHit_Patch.cs new file mode 100644 index 0000000000..967e73d86b --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/StasisSphere_OnHit_Patch.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using NitroxClient.Communication.Abstract; +using NitroxClient.GameLogic; +using NitroxModel.DataStructures.Unity; +using NitroxModel.Helper; +using NitroxModel.Packets; +using NitroxModel_Subnautica.DataStructures; + +namespace NitroxPatcher.Patches.Dynamic; + +/// +/// Prevents remote stasis sphere from triggering hit effets (they're triggered with packets) +/// +public sealed partial class StasisSphere_OnHit_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((StasisSphere t) => t.OnHit(default)); + + private static ushort LocalPlayerId => Resolve().PlayerId; + + public static void Prefix(StasisSphere __instance) + { + if (__instance.GetComponent()) + { + return; + } + + NitroxVector3 position = __instance.tr.position.ToDto(); + NitroxQuaternion rotation = __instance.tr.rotation.ToDto(); + // Calculate the chargeNormalized value which was passed to StasisSphere.Shoot + float chargeNormalized = Mathf.Unlerp(__instance.minRadius, __instance.maxRadius, __instance.radius); + + Resolve().Send(new StasisSphereHit(LocalPlayerId, position, rotation, chargeNormalized, __instance._consumption)); + return; + } +} diff --git a/NitroxPatcher/Patches/Dynamic/StasisSphere_Shoot_Patch.cs b/NitroxPatcher/Patches/Dynamic/StasisSphere_Shoot_Patch.cs new file mode 100644 index 0000000000..0d30c9bb3b --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/StasisSphere_Shoot_Patch.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using NitroxClient.Communication.Abstract; +using NitroxClient.GameLogic; +using NitroxModel.Helper; +using NitroxModel.Packets; +using NitroxModel_Subnautica.DataStructures; +using UnityEngine; + +namespace NitroxPatcher.Patches.Dynamic; + +public sealed partial class StasisSphere_Shoot_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((StasisSphere t) => t.Shoot(default, default, default, default, default)); + + private static ushort LocalPlayerId => Resolve().PlayerId; + + public static void Prefix(StasisSphere __instance, Vector3 position, Quaternion rotation, float speed, float lifeTime, float chargeNormalized) + { + if (__instance.GetComponent()) + { + return; + } + + Resolve().Send(new StasisSphereShot(LocalPlayerId, position.ToDto(), rotation.ToDto(), speed, lifeTime, chargeNormalized)); + } +} diff --git a/NitroxPatcher/Patches/Dynamic/Vehicle_TorpedoShot_Patch.cs b/NitroxPatcher/Patches/Dynamic/Vehicle_TorpedoShot_Patch.cs new file mode 100644 index 0000000000..f70f2f85fa --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/Vehicle_TorpedoShot_Patch.cs @@ -0,0 +1,41 @@ +using System.Reflection; +using NitroxClient.Communication.Abstract; +using NitroxClient.MonoBehaviours; +using NitroxModel.DataStructures; +using NitroxModel.Helper; +using NitroxModel.Packets; +using NitroxModel_Subnautica.DataStructures; +using UnityEngine; + +namespace NitroxPatcher.Patches.Dynamic; + +public sealed partial class Vehicle_TorpedoShot_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method(() => Vehicle.TorpedoShot(default, default, default)); + + public static bool Prefix(Vehicle __instance, ref bool __result, ItemsContainer container, TorpedoType torpedoType, Transform muzzle) + { + // (almost) Exact code copy from Vehicle.TorpedoShot because it's impossible to make a transpiler for it without modifying most of the instructions + // (the transpiler wouldn't be readable at all) so the best possibility is to copy the exact and out the created torpedo GameObject + if (torpedoType == null || !container.DestroyItem(torpedoType.techType)) + { + return false; + } + GameObject gameObject = GameObject.Instantiate(torpedoType.prefab); + Bullet component = gameObject.GetComponent(); + Transform aimingTransform = Player.main.camRoot.GetAimingTransform(); + Rigidbody componentInParent = muzzle.GetComponentInParent(); + Vector3 vector = componentInParent ? componentInParent.velocity : Vector3.zero; + float num = Vector3.Dot(aimingTransform.forward, vector); + component.Shoot(muzzle.position, aimingTransform.rotation, num, -1f); + __result = true; + + // Broadcast code + + NitroxId bulletId = new(); + NitroxEntity.SetNewId(gameObject, bulletId); + + Resolve().Send(new TorpedoShot(bulletId, torpedoType.techType.ToDto(), muzzle.position.ToDto(), aimingTransform.rotation.ToDto(), num, -1)); + return false; + } +} diff --git a/NitroxPatcher/TranspilerHelper.cs b/NitroxPatcher/TranspilerHelper.cs index 72b21bc520..cb311395f5 100644 --- a/NitroxPatcher/TranspilerHelper.cs +++ b/NitroxPatcher/TranspilerHelper.cs @@ -1,12 +1,14 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; using HarmonyLib; +using NitroxClient.GameLogic; using NitroxClient.MonoBehaviours; using NitroxModel.Core; using NitroxModel.Helper; +using UnityEngine; namespace NitroxPatcher { @@ -15,6 +17,9 @@ internal static class TranspilerHelper private static readonly MethodInfo serviceLocator = typeof(NitroxServiceLocator) .GetMethod(nameof(NitroxServiceLocator.LocateService), BindingFlags.Static | BindingFlags.Public, null, Array.Empty(), null); + private static readonly MethodInfo DELTA_TIME_INSERTED_METHOD = Reflect.Method(() => GetDeltaTime()); + private static readonly MethodInfo DELTA_TIME_MATCHING_FIELD = Reflect.Property(() => Time.deltaTime).GetGetMethod(); + public static CodeInstruction LocateService() { return new CodeInstruction(OpCodes.Call, serviceLocator.MakeGenericMethod(typeof(T))); @@ -119,5 +124,25 @@ public static CodeInstruction Ldloc(this MethodBase method, int i) return Ldloc(method.GetLocalVariableIndex(i)); } + + /// + /// Replaces first use of by . + /// Doesn't change . + /// + public static CodeMatcher ReplaceDeltaTime(this CodeMatcher codeMatcher) + { + int pos = codeMatcher.Pos; + return codeMatcher.MatchStartForward(new CodeMatch(OpCodes.Call, DELTA_TIME_MATCHING_FIELD)) + .SetOperandAndAdvance(DELTA_TIME_INSERTED_METHOD) + .Advance(pos - codeMatcher.Pos); + } + + /// + /// Wrapper for dependency resolving and variable querying + /// + public static float GetDeltaTime() + { + return NitroxServiceLocator.Cache.Value.DeltaTime; + } } } diff --git a/NitroxServer/Communication/Packets/Processors/DefaultServerPacketProcessor.cs b/NitroxServer/Communication/Packets/Processors/DefaultServerPacketProcessor.cs index bee98e027e..0f85c37b15 100644 --- a/NitroxServer/Communication/Packets/Processors/DefaultServerPacketProcessor.cs +++ b/NitroxServer/Communication/Packets/Processors/DefaultServerPacketProcessor.cs @@ -25,7 +25,12 @@ public class DefaultServerPacketProcessor : AuthenticatedPacketProcessor typeof(PlayerCinematicControllerCall), typeof(CreatureActionChanged), typeof(AggressiveWhenSeeTargetChanged), - typeof(AttackCyclopsTargetChanged) + typeof(AttackCyclopsTargetChanged), + typeof(TorpedoShot), + typeof(TorpedoHit), + typeof(TorpedoTargetAcquired), + typeof(StasisSphereShot), + typeof(StasisSphereHit), }; ///