From 51847f691b819a99c3729b42000328238e825b70 Mon Sep 17 00:00:00 2001 From: nanho-lee Date: Tue, 10 Dec 2024 13:56:41 +0900 Subject: [PATCH 1/3] [MTTB-409] Fix a Healer ability doesn't work (#893) --- Assets/GameData/Action/Mage/MageHeal.asset | 4 +-- Assets/Scripts/Gameplay/Action/ActionUtils.cs | 35 +++++++++++++------ .../ConcreteActions/DashAttackAction.cs | 2 +- .../Action/ConcreteActions/MeleeAction.cs | 33 ++++++++++++++--- .../Gameplay/GameplayObjects/Breakable.cs | 5 +++ .../Character/ServerCharacter.cs | 8 +++++ .../GameplayObjects/DamageReceiver.cs | 12 +++++++ .../Gameplay/GameplayObjects/IDamageable.cs | 6 ++++ .../Gameplay/UserInput/ClientInputSender.cs | 6 +++- 9 files changed, 92 insertions(+), 19 deletions(-) diff --git a/Assets/GameData/Action/Mage/MageHeal.asset b/Assets/GameData/Action/Mage/MageHeal.asset index f1d5f0daf5..b30161aaf7 100644 --- a/Assets/GameData/Action/Mage/MageHeal.asset +++ b/Assets/GameData/Action/Mage/MageHeal.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6c16b018e4bfa01b23bea3e0f6ef366ac529f16a749e38c93384e1e38a5c7ab8 -size 1159 +oid sha256:5f28c042dceb2a438a0faec834feaf3b3470fc34629e4ab5e489af33b780ed32 +size 1185 diff --git a/Assets/Scripts/Gameplay/Action/ActionUtils.cs b/Assets/Scripts/Gameplay/Action/ActionUtils.cs index 19ed37412d..b3d39dc6a7 100644 --- a/Assets/Scripts/Gameplay/Action/ActionUtils.cs +++ b/Assets/Scripts/Gameplay/Action/ActionUtils.cs @@ -30,20 +30,35 @@ public static class ActionUtils const float k_VeryCloseTeleportRange = k_CloseDistanceOffset + 1; /// - /// Does a melee foe hit detect. + /// Detects friends and/or foes near us. /// - /// true if the attacker is an NPC (and therefore should hit PCs). False for the reverse. + /// true if we should detect PCs + /// true if we should detect NPCs /// The collider of the attacking GameObject. - /// The range in meters to check for foes. + /// The range in meters to check. + /// The radius in meters to check. /// Place an uninitialized RayCastHit[] ref in here. It will be set to the results array. - /// - /// This method does not alloc. It returns a maximum of 4 results. Consume the results immediately, as the array will be overwritten with - /// the next similar query. - /// - /// Total number of foes encountered. - public static int DetectMeleeFoe(bool isNPC, Collider attacker, float range, out RaycastHit[] results) + /// + public static int DetectNearbyEntitiesUseSphere(bool wantPcs, bool wantNpcs, Collider attacker, float range, float radius, out RaycastHit[] results) { - return DetectNearbyEntities(isNPC, !isNPC, attacker, range, out results); + var myBounds = attacker.bounds; + + if (s_PCLayer == -1) + s_PCLayer = LayerMask.NameToLayer("PCs"); + if (s_NpcLayer == -1) + s_NpcLayer = LayerMask.NameToLayer("NPCs"); + + int mask = 0; + if (wantPcs) + mask |= (1 << s_PCLayer); + if (wantNpcs) + mask |= (1 << s_NpcLayer); + + int numResults = Physics.SphereCastNonAlloc(attacker.transform.position, radius, + attacker.transform.forward, s_Hits, range, mask); + + results = s_Hits; + return numResults; } /// diff --git a/Assets/Scripts/Gameplay/Action/ConcreteActions/DashAttackAction.cs b/Assets/Scripts/Gameplay/Action/ConcreteActions/DashAttackAction.cs index 4cde3d5229..6f2493e1c2 100644 --- a/Assets/Scripts/Gameplay/Action/ConcreteActions/DashAttackAction.cs +++ b/Assets/Scripts/Gameplay/Action/ConcreteActions/DashAttackAction.cs @@ -98,7 +98,7 @@ private void PerformMeleeAttack(ServerCharacter parent) // perform a typical melee-hit. But note that we are using the Radius field for range, not the Range field! IDamageable foe = MeleeAction.GetIdealMeleeFoe(Config.IsFriendly ^ parent.IsNpc, parent.physicsWrapper.DamageCollider, - Config.Radius, + Config.Radius, 0.0f, (Data.TargetIds != null && Data.TargetIds.Length > 0 ? Data.TargetIds[0] : 0)); if (foe != null) diff --git a/Assets/Scripts/Gameplay/Action/ConcreteActions/MeleeAction.cs b/Assets/Scripts/Gameplay/Action/ConcreteActions/MeleeAction.cs index b19f26b9d0..e24196ffd8 100644 --- a/Assets/Scripts/Gameplay/Action/ConcreteActions/MeleeAction.cs +++ b/Assets/Scripts/Gameplay/Action/ConcreteActions/MeleeAction.cs @@ -85,7 +85,7 @@ public override bool OnUpdate(ServerCharacter clientCharacter) /// private IDamageable DetectFoe(ServerCharacter parent, ulong foeHint = 0) { - return GetIdealMeleeFoe(Config.IsFriendly ^ parent.IsNpc, parent.physicsWrapper.DamageCollider, Config.Range, foeHint); + return GetIdealMeleeFoe(Config.IsFriendly ^ parent.IsNpc, parent.physicsWrapper.DamageCollider, Config.Range, Config.Radius, foeHint); } /// @@ -96,25 +96,48 @@ private IDamageable DetectFoe(ServerCharacter parent, ulong foeHint = 0) /// true if the attacker is an NPC (and therefore should hit PCs). False for the reverse. /// The collider of the attacking GameObject. /// The range in meters to check for foes. + /// The radius in meters to check for foes. /// The NetworkObjectId of our preferred foe, or 0 if no preference /// ideal target's IDamageable, or null if no valid target found - public static IDamageable GetIdealMeleeFoe(bool isNPC, Collider ourCollider, float meleeRange, ulong preferredTargetNetworkId) + /// + /// If a Radius value is set (greater than 0), collision checking will be done with a Sphere the size of the Radius, not the size of the Box. + /// Also, if multiple targets collide as a result, the target with the highest total damage is prioritized. + /// + public static IDamageable GetIdealMeleeFoe(bool isNPC, Collider ourCollider, float meleeRange, float meleeRadius, ulong preferredTargetNetworkId) { RaycastHit[] results; - int numResults = ActionUtils.DetectMeleeFoe(isNPC, ourCollider, meleeRange, out results); + int numResults = 0.0f < meleeRadius + ? ActionUtils.DetectNearbyEntitiesUseSphere(isNPC, !isNPC, ourCollider, meleeRange, meleeRadius, out results) + : ActionUtils.DetectNearbyEntities(isNPC, !isNPC, ourCollider, meleeRange, out results); IDamageable foundFoe = null; //everything that got hit by the raycast should have an IDamageable component, so we can retrieve that and see if they're appropriate targets. //we always prefer the hinted foe. If he's still in range, he should take the damage, because he's who the client visualization //system will play the hit-react on (in case there's any ambiguity). + //if that is not the case, we prioritize the target with the highest total damage. + int maxDamage = int.MinValue; + for (int i = 0; i < numResults; i++) { var damageable = results[i].collider.GetComponent(); - if (damageable != null && damageable.IsDamageable() && - (damageable.NetworkObjectId == preferredTargetNetworkId || foundFoe == null)) + if (damageable == null || !damageable.IsDamageable()) + { + continue; + } + + if (damageable.NetworkObjectId == preferredTargetNetworkId) + { + foundFoe = damageable; + maxDamage = int.MaxValue; + continue; + } + + var totalDamage = damageable.GetTotalDamage(); + if (foundFoe == null || maxDamage < totalDamage) { foundFoe = damageable; + maxDamage = totalDamage; } } diff --git a/Assets/Scripts/Gameplay/GameplayObjects/Breakable.cs b/Assets/Scripts/Gameplay/GameplayObjects/Breakable.cs index 246811311c..7249ef775d 100644 --- a/Assets/Scripts/Gameplay/GameplayObjects/Breakable.cs +++ b/Assets/Scripts/Gameplay/GameplayObjects/Breakable.cs @@ -116,6 +116,11 @@ public void ReceiveHP(ServerCharacter inflicter, int HP) } } + public int GetTotalDamage() + { + return Math.Max(0, m_MaxHealth.Value - m_NetworkHealthState.HitPoints.Value); + } + private void Break() { IsBroken.Value = true; diff --git a/Assets/Scripts/Gameplay/GameplayObjects/Character/ServerCharacter.cs b/Assets/Scripts/Gameplay/GameplayObjects/Character/ServerCharacter.cs index 3edac2f84b..623af424e8 100644 --- a/Assets/Scripts/Gameplay/GameplayObjects/Character/ServerCharacter.cs +++ b/Assets/Scripts/Gameplay/GameplayObjects/Character/ServerCharacter.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using Unity.BossRoom.ConnectionManagement; using Unity.BossRoom.Gameplay.Actions; @@ -156,6 +157,7 @@ public override void OnNetworkSpawn() NetLifeState.LifeState.OnValueChanged += OnLifeStateChanged; m_DamageReceiver.DamageReceived += ReceiveHP; m_DamageReceiver.CollisionEntered += CollisionEntered; + m_DamageReceiver.GetTotalDamageFunc += GetTotalDamage; if (IsNpc) { @@ -179,6 +181,7 @@ public override void OnNetworkDespawn() { m_DamageReceiver.DamageReceived -= ReceiveHP; m_DamageReceiver.CollisionEntered -= CollisionEntered; + m_DamageReceiver.GetTotalDamageFunc -= GetTotalDamage; } } @@ -393,6 +396,11 @@ void CollisionEntered(Collision collision) } } + int GetTotalDamage() + { + return Math.Max(0, CharacterClass.BaseHP.Value - HitPoints); + } + /// /// This character's AIBrain. Will be null if this is not an NPC. /// diff --git a/Assets/Scripts/Gameplay/GameplayObjects/DamageReceiver.cs b/Assets/Scripts/Gameplay/GameplayObjects/DamageReceiver.cs index cc4e95c6c6..5d76422407 100644 --- a/Assets/Scripts/Gameplay/GameplayObjects/DamageReceiver.cs +++ b/Assets/Scripts/Gameplay/GameplayObjects/DamageReceiver.cs @@ -11,6 +11,8 @@ public class DamageReceiver : NetworkBehaviour, IDamageable public event Action CollisionEntered; + public event Func GetTotalDamageFunc; + [SerializeField] NetworkLifeState m_NetworkLifeState; @@ -22,6 +24,16 @@ public void ReceiveHP(ServerCharacter inflicter, int HP) } } + public int GetTotalDamage() + { + if (!IsDamageable()) + { + return 0; + } + + return GetTotalDamageFunc?.Invoke() ?? 0; + } + public IDamageable.SpecialDamageFlags GetSpecialDamageFlags() { return IDamageable.SpecialDamageFlags.None; diff --git a/Assets/Scripts/Gameplay/GameplayObjects/IDamageable.cs b/Assets/Scripts/Gameplay/GameplayObjects/IDamageable.cs index 23185d6313..786eaa2790 100644 --- a/Assets/Scripts/Gameplay/GameplayObjects/IDamageable.cs +++ b/Assets/Scripts/Gameplay/GameplayObjects/IDamageable.cs @@ -17,6 +17,12 @@ public interface IDamageable /// The damage done. Negative value is damage, positive is healing. void ReceiveHP(ServerCharacter inflicter, int HP); + /// + /// Get the total damage value. + /// + /// The return value is your total health minus your current health. + int GetTotalDamage(); + /// /// The NetworkId of this object. /// diff --git a/Assets/Scripts/Gameplay/UserInput/ClientInputSender.cs b/Assets/Scripts/Gameplay/UserInput/ClientInputSender.cs index 5dd2f0df1d..9c2006aced 100644 --- a/Assets/Scripts/Gameplay/UserInput/ClientInputSender.cs +++ b/Assets/Scripts/Gameplay/UserInput/ClientInputSender.cs @@ -420,7 +420,11 @@ void PopulateSkillRequest(Vector3 hitPoint, ActionID actionID, ref ActionRequest // figure out the Direction in case we want to send it Vector3 offset = hitPoint - m_PhysicsWrapper.Transform.position; offset.y = 0; - Vector3 direction = offset.normalized; + + //there is a bug where the direction is flipped if the hitPos and current position are almost the same, + //so we use the character's direction instead. + float directionLength = offset.magnitude; + Vector3 direction = 1.0f/*epsilon*/ <= directionLength ? (offset / directionLength) : m_PhysicsWrapper.Transform.forward; switch (actionConfig.Logic) { From df837b116d337fcefae1756102b8a0d51e5840cd Mon Sep 17 00:00:00 2001 From: nanho-lee Date: Tue, 10 Dec 2024 14:15:22 +0900 Subject: [PATCH 2/3] Updated CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04dde187f2..d1ce37867d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,9 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Cleanup * Removed ParrelSync from the project +### Fixed +* Fix a Healer ability doesn't work (#893) + ## [2.5.0] - 2024-04-18 From 470f4c58198367de3b143338a50c37a5dee0f1ba Mon Sep 17 00:00:00 2001 From: nanho-lee Date: Thu, 12 Dec 2024 10:44:02 +0900 Subject: [PATCH 3/3] Updated CHANGELOG.md (A richer description of this PR) --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1ce37867d..1237536d22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,10 @@ Additional documentation and release notes are available at [Multiplayer Documen * Removed ParrelSync from the project ### Fixed -* Fix a Healer ability doesn't work (#893) +* Fix a Healer ability doesn't work (#893) + * Changed the way characters are oriented when using skills. + * Added the GetTotalDamage API to the IDamagble interface. This number is your maximum health minus your current health. + * Changed the way MeleeAction selects a target when there are multiple targets to collide with. The target with the highest GetTotalDamage value (mentioned above) will be selected. ## [2.5.0] - 2024-04-18