From 1ec8da8bf7c93baccfb6b081ae4baec5570b4446 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Sat, 30 Sep 2023 06:22:32 -0500 Subject: [PATCH 01/11] fix: remove foreign key on name in cheat_detection table (#1202) --- migrations/dlu/11_fix_cheat_detection_table.sql | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 migrations/dlu/11_fix_cheat_detection_table.sql diff --git a/migrations/dlu/11_fix_cheat_detection_table.sql b/migrations/dlu/11_fix_cheat_detection_table.sql new file mode 100644 index 000000000..214648344 --- /dev/null +++ b/migrations/dlu/11_fix_cheat_detection_table.sql @@ -0,0 +1,9 @@ +DROP TABLE IF EXISTS `player_cheat_detections`; +CREATE TABLE IF NOT EXISTS player_cheat_detections ( + id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + account_id INT REFERENCES accounts(id), + name TEXT NOT NULL, + violation_msg TEXT NOT NULL, + violation_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP(), + violation_system_address TEXT NOT NULL +); From a8820c14f2c43dd817347044764dda9335f4c3a0 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sat, 30 Sep 2023 16:48:12 -0700 Subject: [PATCH 02/11] AssetManager: Match allocators (#1205) Currently on line 116 of AssetManager.cpp, we have the following `*data = (char*)malloc(*len);` however on line 45 of AssetManager.h we have `delete m_Base;` which is not the same allocator as we used to allocate the memory. This PR matches the malloc and free to be the correct calls. --- dCommon/dClient/AssetManager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dCommon/dClient/AssetManager.h b/dCommon/dClient/AssetManager.h index bc7e5ff76..d25434892 100644 --- a/dCommon/dClient/AssetManager.h +++ b/dCommon/dClient/AssetManager.h @@ -42,7 +42,7 @@ struct AssetMemoryBuffer : std::streambuf { } void close() { - delete m_Base; + free(m_Base); } }; From 258ee5c1eebeaf0aefadad94c55c8c5e78b649b0 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 5 Oct 2023 15:31:05 -0700 Subject: [PATCH 03/11] CheatDetection: Move player access (#1209) --- dGame/dUtilities/CheatDetection.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dGame/dUtilities/CheatDetection.cpp b/dGame/dUtilities/CheatDetection.cpp index 6459bb2c0..11a682633 100644 --- a/dGame/dUtilities/CheatDetection.cpp +++ b/dGame/dUtilities/CheatDetection.cpp @@ -55,17 +55,18 @@ void LogAndSaveFailedAntiCheatCheck(const LWOOBJID& id, const SystemAddress& sys player->GetCharacter()->GetName().c_str(), player->GetObjectID(), sysAddr.ToString(), entity->GetCharacter()->GetName().c_str(), entity->GetObjectID()); + toReport = player->GetParentUser(); // In the case that the target entity id did not exist, just log the player info. } else if (player) { Game::logger->Log("CheatDetection", "Player (%s) (%llu) at system address (%s) with sending player (%llu) does not match their own.", player->GetCharacter()->GetName().c_str(), player->GetObjectID(), sysAddr.ToString(), id); + toReport = player->GetParentUser(); // In the rare case that the player does not exist, just log the system address and who the target id was. } else { Game::logger->Log("CheatDetection", "Player at system address (%s) with sending player (%llu) does not match their own.", sysAddr.ToString(), id); } - toReport = player->GetParentUser(); break; } case CheckType::User: { From b33e9935f54d702eadc919163e5db7d9f40e10f4 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Fri, 6 Oct 2023 01:20:20 -0500 Subject: [PATCH 04/11] fix: GM 1272 was labled as 1273 (#1210) --- dCommon/dEnums/eGameMessageType.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dCommon/dEnums/eGameMessageType.h b/dCommon/dEnums/eGameMessageType.h index d5751b515..df822c52b 100644 --- a/dCommon/dEnums/eGameMessageType.h +++ b/dCommon/dEnums/eGameMessageType.h @@ -1152,7 +1152,7 @@ enum class eGameMessageType : uint16_t { COLLISION_POINT_REMOVED = 1269, SET_ATTACHED = 1270, SET_DESTROYABLE_MODEL_BRICKS = 1271, - VEHICLE_SET_POWERSLIDE_LOCK_WHEELS = 1273, + VEHICLE_SET_POWERSLIDE_LOCK_WHEELS = 1272, VEHICLE_SET_WHEEL_LOCK_STATE = 1273, SHOW_HEALTH_BAR = 1274, GET_SHOWS_HEALTH_BAR = 1275, From 74cc4176e197c0330ef1b083f70d065a8c7c97a2 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 8 Oct 2023 13:58:47 -0700 Subject: [PATCH 05/11] Update RigidbodyPhantomPhysicsComponent.h (#1213) --- dGame/dComponents/RigidbodyPhantomPhysicsComponent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dGame/dComponents/RigidbodyPhantomPhysicsComponent.h b/dGame/dComponents/RigidbodyPhantomPhysicsComponent.h index ba6a54a08..6e38cfd0e 100644 --- a/dGame/dComponents/RigidbodyPhantomPhysicsComponent.h +++ b/dGame/dComponents/RigidbodyPhantomPhysicsComponent.h @@ -19,7 +19,7 @@ */ class RigidbodyPhantomPhysicsComponent : public Component { public: - static const eReplicaComponentType ComponentType = eReplicaComponentType::PHANTOM_PHYSICS; + static const eReplicaComponentType ComponentType = eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS; RigidbodyPhantomPhysicsComponent(Entity* parent); ~RigidbodyPhantomPhysicsComponent() override; From 288991ef49dd915278b31c343a2f4426a3bf269c Mon Sep 17 00:00:00 2001 From: TAHuntling <38479763+TAHuntling@users.noreply.github.com> Date: Sun, 8 Oct 2023 19:38:48 -0500 Subject: [PATCH 06/11] fix: Players are able to join a race without having a race car (#1149) * Fixed Scrapped Racecar Stuck Issue Changed RacingControlComponent to boot players back to the hub world when trying to race after dismantling a vehicle. * Modified OnPlayerLoaded to fix Vehicle Inventory issue * Change 3 --------- Co-authored-by: David Markowitz --- dGame/dComponents/RacingControlComponent.cpp | 27 +++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/dGame/dComponents/RacingControlComponent.cpp b/dGame/dComponents/RacingControlComponent.cpp index 7a4d98ea6..12f4201a3 100644 --- a/dGame/dComponents/RacingControlComponent.cpp +++ b/dGame/dComponents/RacingControlComponent.cpp @@ -61,23 +61,27 @@ RacingControlComponent::RacingControlComponent(Entity* parent) RacingControlComponent::~RacingControlComponent() {} void RacingControlComponent::OnPlayerLoaded(Entity* player) { - // If the race has already started, send the player back to the main world. - if (m_Loaded) { - auto* playerInstance = dynamic_cast(player); + auto* inventoryComponent = player->GetComponent(); + if (!inventoryComponent) { + return; + } - playerInstance->SendToZone(m_MainWorld); + auto* vehicle = inventoryComponent->FindItemByLot(8092); + // If the race has already started, send the player back to the main world. + if (m_Loaded || !vehicle) { + auto* playerInstance = dynamic_cast(player); + if(playerInstance){ + playerInstance->SendToZone(m_MainWorld); + } return; } - const auto objectID = player->GetObjectID(); - m_LoadedPlayers++; Game::logger->Log("RacingControlComponent", "Loading player %i", m_LoadedPlayers); - - m_LobbyPlayers.push_back(objectID); + m_LobbyPlayers.push_back(player->GetObjectID()); } void RacingControlComponent::LoadPlayerVehicle(Entity* player, @@ -100,8 +104,13 @@ void RacingControlComponent::LoadPlayerVehicle(Entity* player, if (item == nullptr) { Game::logger->Log("RacingControlComponent", "Failed to find item"); - + auto* playerInstance = dynamic_cast(player); + if(playerInstance){ + m_LoadedPlayers--; + playerInstance->SendToZone(m_MainWorld); + } return; + } // Calculate the vehicle's starting position. From 94f8a99fbaec9105f98e8350867ddd2b2d0aaed1 Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Sun, 8 Oct 2023 23:23:14 -0700 Subject: [PATCH 07/11] Revert: Fix wisp lee mission This reverts commit d7e16ab589697fd1a0a270a02c622b8fa752638f. Fixes an issue where the incorrect mission is marked as being offerable and this overwrote the original mission offered by wisp lee and forced him to offer a different, unacceptable mission. Tested that completing the mission twice from a new daily state and a repeatable state both completed the chain correctly. Unsure what the original bug was, but it does not appear to be present. On top of that, there is no pre-requisite for mission 1883 anywhere in the cdclient, so at best, this check was always false, but the correct behavior is exact equivalence for the mission state. --- dGame/dMission/MissionPrerequisites.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/dGame/dMission/MissionPrerequisites.cpp b/dGame/dMission/MissionPrerequisites.cpp index ec4522b9d..8dab9a2db 100644 --- a/dGame/dMission/MissionPrerequisites.cpp +++ b/dGame/dMission/MissionPrerequisites.cpp @@ -105,9 +105,7 @@ bool PrerequisiteExpression::Execute(const std::unordered_mapsub != 0) { // Special case for one Wisp Lee repeatable mission. - a = mission->GetClientInfo().id == 1883 ? - mission->GetMissionState() == static_cast(this->sub) : - mission->GetMissionState() >= static_cast(this->sub); + a = mission->GetMissionState() == static_cast(this->sub); } else if (mission->IsComplete()) { a = true; } From d8ac148cee4a52ecbb1cd08cdf293f152b36781c Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Mon, 9 Oct 2023 15:18:51 -0500 Subject: [PATCH 08/11] refactor: re-write AOE, add FilterTargets, Update TacArc Reading (#1035) * Re-write AOE behavior for new filter targets Update Tacarc to use new filter targets Added dev commands for skill and attack debugging * Get all entities by detroyable rather than controllable physics Since destroyables are what can be hit * Re-work filter targets to be 100% live accurate reduce memory usage by only using one vector and removing invalid entries get entities in the proximity rather than all entities with des comps in the instance, as was done in live * remove debuging longs and remove oopsie * address feedback * make log more useful * make filter more flat * Add some more checks to filter targets add pvp checks to isenemy * fix typing * Add filter target to TacArc and update filter target * fix double declaration * Some debugging logs * Update TacArc reading * make log clearer * logs * Update TacArcBehavior.cpp * banana * fix max targets * remove extreanous parenthesesuuesdsds * make behavior slot use a real type --------- Co-authored-by: David Markowitz --- dGame/EntityManager.cpp | 10 ++ dGame/EntityManager.h | 1 + dGame/dBehaviors/AreaOfEffectBehavior.cpp | 166 ++++++++---------- dGame/dBehaviors/AreaOfEffectBehavior.h | 40 ++--- dGame/dBehaviors/Behavior.cpp | 2 +- dGame/dBehaviors/BehaviorContext.cpp | 128 +++++++++++--- dGame/dBehaviors/BehaviorContext.h | 7 +- dGame/dBehaviors/BehaviorSlot.h | 4 +- dGame/dBehaviors/TacArcBehavior.cpp | 185 +++++++++------------ dGame/dBehaviors/TacArcBehavior.h | 68 +++----- dGame/dComponents/DestroyableComponent.cpp | 52 ++---- dGame/dComponents/DestroyableComponent.h | 8 - dGame/dComponents/InventoryComponent.cpp | 42 +++-- dGame/dComponents/InventoryComponent.h | 5 + dGame/dGameMessages/GameMessages.cpp | 6 +- dGame/dGameMessages/GameMessages.h | 3 +- dGame/dUtilities/SlashCommandHandler.cpp | 88 ++++++++++ docs/Commands.md | 7 +- 18 files changed, 465 insertions(+), 357 deletions(-) diff --git a/dGame/EntityManager.cpp b/dGame/EntityManager.cpp index 699cc2a18..4018aba85 100644 --- a/dGame/EntityManager.cpp +++ b/dGame/EntityManager.cpp @@ -298,6 +298,16 @@ std::vector EntityManager::GetEntitiesByLOT(const LOT& lot) const { return entities; } +std::vector EntityManager::GetEntitiesByProximity(NiPoint3 reference, float radius) const{ + std::vector entities = {}; + if (radius > 1000.0f) return entities; + for (const auto& entity : m_Entities) { + if (NiPoint3::Distance(reference, entity.second->GetPosition()) <= radius) entities.push_back(entity.second); + } + return entities; +} + + Entity* EntityManager::GetZoneControlEntity() const { return m_ZoneControlEntity; } diff --git a/dGame/EntityManager.h b/dGame/EntityManager.h index 693a4cc01..33d7aaff6 100644 --- a/dGame/EntityManager.h +++ b/dGame/EntityManager.h @@ -28,6 +28,7 @@ class EntityManager { std::vector GetEntitiesInGroup(const std::string& group); std::vector GetEntitiesByComponent(eReplicaComponentType componentType) const; std::vector GetEntitiesByLOT(const LOT& lot) const; + std::vector GetEntitiesByProximity(NiPoint3 reference, float radius) const; Entity* GetZoneControlEntity() const; // Get spawn point entity by spawn name diff --git a/dGame/dBehaviors/AreaOfEffectBehavior.cpp b/dGame/dBehaviors/AreaOfEffectBehavior.cpp index e43d542d7..aef732230 100644 --- a/dGame/dBehaviors/AreaOfEffectBehavior.cpp +++ b/dGame/dBehaviors/AreaOfEffectBehavior.cpp @@ -20,134 +20,114 @@ void AreaOfEffectBehavior::Handle(BehaviorContext* context, RakNet::BitStream* b return; } + if (this->m_useTargetPosition && branch.target == LWOOBJID_EMPTY) return; + + if (targetCount == 0){ + PlayFx(u"miss", context->originator); + return; + } + if (targetCount > this->m_maxTargets) { + Game::logger->Log("AreaOfEffectBehavior", "Serialized size is greater than max targets! Size: %i, Max: %i", targetCount, this->m_maxTargets); return; } - std::vector targets; + auto caster = context->caster; + if (this->m_useTargetAsCaster) context->caster = branch.target; + std::vector targets; targets.reserve(targetCount); for (auto i = 0u; i < targetCount; ++i) { LWOOBJID target{}; - if (!bitStream->Read(target)) { Game::logger->Log("AreaOfEffectBehavior", "failed to read in target %i from bitStream, aborting target Handle!", i); - return; }; - targets.push_back(target); } for (auto target : targets) { branch.target = target; - this->m_action->Handle(context, bitStream, branch); } + context->caster = caster; + PlayFx(u"cast", context->originator); } void AreaOfEffectBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) { - auto* self = Game::entityManager->GetEntity(context->caster); - if (self == nullptr) { - Game::logger->Log("AreaOfEffectBehavior", "Invalid self for (%llu)!", context->originator); - - return; + auto* caster = Game::entityManager->GetEntity(context->caster); + if (!caster) return; + + // determine the position we are casting the AOE from + auto reference = branch.isProjectile ? branch.referencePosition : caster->GetPosition(); + if (this->m_useTargetPosition) { + if (branch.target == LWOOBJID_EMPTY) return; + auto branchTarget = Game::entityManager->GetEntity(branch.target); + if (branchTarget) reference = branchTarget->GetPosition(); } - auto reference = branch.isProjectile ? branch.referencePosition : self->GetPosition(); - - std::vector targets; + reference += this->m_offset; - auto* presetTarget = Game::entityManager->GetEntity(branch.target); + std::vector targets {}; + targets = Game::entityManager->GetEntitiesByProximity(reference, this->m_radius); + context->FilterTargets(targets, this->m_ignoreFactionList, this->m_includeFactionList, this->m_targetSelf, this->m_targetEnemy, this->m_targetFriend, this->m_targetTeam); - if (presetTarget != nullptr) { - if (this->m_radius * this->m_radius >= Vector3::DistanceSquared(reference, presetTarget->GetPosition())) { - targets.push_back(presetTarget); - } - } - - int32_t includeFaction = m_includeFaction; - - if (self->GetLOT() == 14466) // TODO: Fix edge case - { - includeFaction = 1; - } - - // Gets all of the valid targets, passing in if should target enemies and friends - for (auto validTarget : context->GetValidTargets(m_ignoreFaction, includeFaction, m_TargetSelf == 1, m_targetEnemy == 1, m_targetFriend == 1)) { - auto* entity = Game::entityManager->GetEntity(validTarget); - - if (entity == nullptr) { - Game::logger->Log("AreaOfEffectBehavior", "Invalid target (%llu) for (%llu)!", validTarget, context->originator); - - continue; - } - - if (std::find(targets.begin(), targets.end(), entity) != targets.end()) { - continue; + // sort by distance + std::sort(targets.begin(), targets.end(), [reference](Entity* a, Entity* b) { + const auto aDistance = NiPoint3::Distance(a->GetPosition(), reference); + const auto bDistance = NiPoint3::Distance(b->GetPosition(), reference); + return aDistance < bDistance; } + ); - auto* destroyableComponent = entity->GetComponent(); + // resize if we have more than max targets allows + if (targets.size() > this->m_maxTargets) targets.resize(this->m_maxTargets); - if (destroyableComponent == nullptr) { - continue; - } + bitStream->Write(targets.size()); - if (destroyableComponent->HasFaction(m_ignoreFaction)) { - continue; + if (targets.size() == 0) { + PlayFx(u"miss", context->originator); + return; + } else { + context->foundTarget = true; + // write all the targets to the bitstream + for (auto* target : targets) { + bitStream->Write(target->GetObjectID()); } - const auto distance = Vector3::DistanceSquared(reference, entity->GetPosition()); - - if (this->m_radius * this->m_radius >= distance && (this->m_maxTargets == 0 || targets.size() < this->m_maxTargets)) { - targets.push_back(entity); + // then cast all the actions + for (auto* target : targets) { + branch.target = target->GetObjectID(); + this->m_action->Calculate(context, bitStream, branch); } - } - - std::sort(targets.begin(), targets.end(), [reference](Entity* a, Entity* b) { - const auto aDistance = Vector3::DistanceSquared(a->GetPosition(), reference); - const auto bDistance = Vector3::DistanceSquared(b->GetPosition(), reference); - - return aDistance > bDistance; - }); - - const uint32_t size = targets.size(); - - bitStream->Write(size); - - if (size == 0) { - return; - } - - context->foundTarget = true; - - for (auto* target : targets) { - bitStream->Write(target->GetObjectID()); - - PlayFx(u"cast", context->originator, target->GetObjectID()); - } - - for (auto* target : targets) { - branch.target = target->GetObjectID(); - - this->m_action->Calculate(context, bitStream, branch); + PlayFx(u"cast", context->originator); } } void AreaOfEffectBehavior::Load() { - this->m_action = GetAction("action"); - - this->m_radius = GetFloat("radius"); - - this->m_maxTargets = GetInt("max targets"); - - this->m_ignoreFaction = GetInt("ignore_faction"); - - this->m_includeFaction = GetInt("include_faction"); - - this->m_TargetSelf = GetInt("target_self"); - - this->m_targetEnemy = GetInt("target_enemy"); - - this->m_targetFriend = GetInt("target_friend"); + this->m_action = GetAction("action"); // required + this->m_radius = GetFloat("radius", 0.0f); // required + this->m_maxTargets = GetInt("max targets", 100); + if (this->m_maxTargets == 0) this->m_maxTargets = 100; + this->m_useTargetPosition = GetBoolean("use_target_position", false); + this->m_useTargetAsCaster = GetBoolean("use_target_as_caster", false); + this->m_offset = NiPoint3( + GetFloat("offset_x", 0.0f), + GetFloat("offset_y", 0.0f), + GetFloat("offset_z", 0.0f) + ); + + // params after this are needed for filter targets + const auto parameters = GetParameterNames(); + for (const auto& parameter : parameters) { + if (parameter.first.rfind("include_faction", 0) == 0) { + this->m_includeFactionList.push_front(parameter.second); + } else if (parameter.first.rfind("ignore_faction", 0) == 0) { + this->m_ignoreFactionList.push_front(parameter.second); + } + } + this->m_targetSelf = GetBoolean("target_self", false); + this->m_targetEnemy = GetBoolean("target_enemy", false); + this->m_targetFriend = GetBoolean("target_friend", false); + this->m_targetTeam = GetBoolean("target_team", false); } diff --git a/dGame/dBehaviors/AreaOfEffectBehavior.h b/dGame/dBehaviors/AreaOfEffectBehavior.h index b5a48ddf1..f0fbb18d3 100644 --- a/dGame/dBehaviors/AreaOfEffectBehavior.h +++ b/dGame/dBehaviors/AreaOfEffectBehavior.h @@ -1,34 +1,26 @@ #pragma once #include "Behavior.h" +#include class AreaOfEffectBehavior final : public Behavior { public: - Behavior* m_action; - - uint32_t m_maxTargets; - - float m_radius; - - int32_t m_ignoreFaction; - - int32_t m_includeFaction; - - int32_t m_TargetSelf; - - int32_t m_targetEnemy; - - int32_t m_targetFriend; - - /* - * Inherited - */ - explicit AreaOfEffectBehavior(const uint32_t behaviorId) : Behavior(behaviorId) { - } - + explicit AreaOfEffectBehavior(const uint32_t behaviorId) : Behavior(behaviorId) {} void Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override; - void Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override; - void Load() override; +private: + Behavior* m_action; + uint32_t m_maxTargets; + float m_radius; + bool m_useTargetPosition; + bool m_useTargetAsCaster; + NiPoint3 m_offset; + + std::forward_list m_ignoreFactionList {}; + std::forward_list m_includeFactionList {}; + bool m_targetSelf; + bool m_targetEnemy; + bool m_targetFriend; + bool m_targetTeam; }; diff --git a/dGame/dBehaviors/Behavior.cpp b/dGame/dBehaviors/Behavior.cpp index 6fe84a9f1..a51ad5455 100644 --- a/dGame/dBehaviors/Behavior.cpp +++ b/dGame/dBehaviors/Behavior.cpp @@ -175,7 +175,7 @@ Behavior* Behavior::CreateBehavior(const uint32_t behaviorId) { case BehaviorTemplates::BEHAVIOR_SPEED: behavior = new SpeedBehavior(behaviorId); break; - case BehaviorTemplates::BEHAVIOR_DARK_INSPIRATION: + case BehaviorTemplates::BEHAVIOR_DARK_INSPIRATION: behavior = new DarkInspirationBehavior(behaviorId); break; case BehaviorTemplates::BEHAVIOR_LOOT_BUFF: diff --git a/dGame/dBehaviors/BehaviorContext.cpp b/dGame/dBehaviors/BehaviorContext.cpp index 91276b801..4357548a0 100644 --- a/dGame/dBehaviors/BehaviorContext.cpp +++ b/dGame/dBehaviors/BehaviorContext.cpp @@ -15,6 +15,7 @@ #include "PhantomPhysicsComponent.h" #include "RebuildComponent.h" #include "eReplicaComponentType.h" +#include "TeamManager.h" #include "eConnectionType.h" BehaviorSyncEntry::BehaviorSyncEntry() { @@ -307,46 +308,123 @@ void BehaviorContext::Reset() { this->scheduledUpdates.clear(); } -std::vector BehaviorContext::GetValidTargets(int32_t ignoreFaction, int32_t includeFaction, bool targetSelf, bool targetEnemy, bool targetFriend) const { - auto* entity = Game::entityManager->GetEntity(this->caster); +void BehaviorContext::FilterTargets(std::vector& targets, std::forward_list& ignoreFactionList, std::forward_list& includeFactionList, bool targetSelf, bool targetEnemy, bool targetFriend, bool targetTeam) const { - std::vector targets; - - if (entity == nullptr) { - Game::logger->Log("BehaviorContext", "Invalid entity for (%llu)!", this->originator); + // if we aren't targeting anything, then clear the targets vector + if (!targetSelf && !targetEnemy && !targetFriend && !targetTeam && ignoreFactionList.empty() && includeFactionList.empty()) { + targets.clear(); + return; + } - return targets; + // if the caster is not there, return empty targets list + auto* caster = Game::entityManager->GetEntity(this->caster); + if (!caster) { + Game::logger->LogDebug("BehaviorContext", "Invalid caster for (%llu)!", this->originator); + targets.clear(); + return; } - if (!ignoreFaction && !includeFaction) { - for (auto entry : entity->GetTargetsInPhantom()) { - auto* instance = Game::entityManager->GetEntity(entry); + auto index = targets.begin(); + while (index != targets.end()) { + auto candidate = *index; - if (instance == nullptr) { - continue; - } + // make sure we don't have a nullptr + if (!candidate) { + index = targets.erase(index); + continue; + } - targets.push_back(entry); + // handle targeting the caster + if (candidate == caster){ + // if we aren't targeting self, erase, otherise increment and continue + if (!targetSelf) index = targets.erase(index); + else index++; + continue; + } + + // make sure that the entity is targetable + if (!CheckTargetingRequirements(candidate)) { + index = targets.erase(index); + continue; + } + + // get factions to check against + // CheckTargetingRequirements checks for a destroyable component + // but we check again because bounds check are necessary + auto candidateDestroyableComponent = candidate->GetComponent(); + if (!candidateDestroyableComponent) { + index = targets.erase(index); + continue; } - } - if (ignoreFaction || includeFaction || (!entity->HasComponent(eReplicaComponentType::PHANTOM_PHYSICS) && targets.empty())) { - DestroyableComponent* destroyableComponent; - if (!entity->TryGetComponent(eReplicaComponentType::DESTROYABLE, destroyableComponent)) { - return targets; + // if they are dead, then earse and continue + if (candidateDestroyableComponent->GetIsDead()){ + index = targets.erase(index); + continue; } - auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS); - for (auto* candidate : entities) { - const auto id = candidate->GetObjectID(); + // if their faction is explicitly included, increment and continue + auto candidateFactions = candidateDestroyableComponent->GetFactionIDs(); + if (CheckFactionList(includeFactionList, candidateFactions)){ + index++; + continue; + } - if ((id != entity->GetObjectID() || targetSelf) && destroyableComponent->CheckValidity(id, ignoreFaction || includeFaction, targetEnemy, targetFriend)) { - targets.push_back(id); + // check if they are a team member + if (targetTeam){ + auto* team = TeamManager::Instance()->GetTeam(this->caster); + if (team){ + // if we find a team member keep it and continue to skip enemy checks + if(std::find(team->members.begin(), team->members.end(), candidate->GetObjectID()) != team->members.end()){ + index++; + continue; + } } } + + // if the caster doesn't have a destroyable component, return an empty targets list + auto* casterDestroyableComponent = caster->GetComponent(); + if (!casterDestroyableComponent) { + targets.clear(); + return; + } + + // if we arent targeting a friend, and they are a friend OR + // if we are not targeting enemies and they are an enemy OR. + // if we are ignoring their faction is explicitly ignored + // erase and continue + auto isEnemy = casterDestroyableComponent->IsEnemy(candidate); + if ((!targetFriend && !isEnemy) || + (!targetEnemy && isEnemy) || + CheckFactionList(ignoreFactionList, candidateFactions)) { + index = targets.erase(index); + continue; + } + + index++; } + return; +} + +// some basic checks as well as the check that matters for this: if the quickbuild is complete +bool BehaviorContext::CheckTargetingRequirements(const Entity* target) const { + // if the target is a nullptr, then it's not valid + if (!target) return false; - return targets; + // ignore quickbuilds that aren't completed + auto* targetQuickbuildComponent = target->GetComponent(); + if (targetQuickbuildComponent && targetQuickbuildComponent->GetState() != eRebuildState::COMPLETED) return false; + + return true; +} + +// returns true if any of the object factions are in the faction list +bool BehaviorContext::CheckFactionList(std::forward_list& factionList, std::vector& objectsFactions) const { + if (factionList.empty() || objectsFactions.empty()) return false; + for (auto faction : factionList){ + if(std::find(objectsFactions.begin(), objectsFactions.end(), faction) != objectsFactions.end()) return true; + } + return false; } diff --git a/dGame/dBehaviors/BehaviorContext.h b/dGame/dBehaviors/BehaviorContext.h index 117f328d9..333dc9a8d 100644 --- a/dGame/dBehaviors/BehaviorContext.h +++ b/dGame/dBehaviors/BehaviorContext.h @@ -6,6 +6,7 @@ #include "GameMessages.h" #include +#include class Behavior; @@ -106,7 +107,11 @@ struct BehaviorContext void Reset(); - std::vector GetValidTargets(int32_t ignoreFaction = 0, int32_t includeFaction = 0, const bool targetSelf = false, const bool targetEnemy = true, const bool targetFriend = false) const; + void FilterTargets(std::vector& targetsReference, std::forward_list& ignoreFaction, std::forward_list& includeFaction, const bool targetSelf = false, const bool targetEnemy = true, const bool targetFriend = false, const bool targetTeam = false) const; + + bool CheckTargetingRequirements(const Entity* target) const; + + bool CheckFactionList(std::forward_list& factionList, std::vector& objectsFactions) const; explicit BehaviorContext(LWOOBJID originator, bool calculation = false); diff --git a/dGame/dBehaviors/BehaviorSlot.h b/dGame/dBehaviors/BehaviorSlot.h index 51219b803..5afddea73 100644 --- a/dGame/dBehaviors/BehaviorSlot.h +++ b/dGame/dBehaviors/BehaviorSlot.h @@ -2,9 +2,9 @@ #ifndef BEHAVIORSLOT_H #define BEHAVIORSLOT_H +#include -enum class BehaviorSlot -{ +enum class BehaviorSlot : int32_t { Invalid = -1, Primary, Offhand, diff --git a/dGame/dBehaviors/TacArcBehavior.cpp b/dGame/dBehaviors/TacArcBehavior.cpp index b9d871f4b..7629c3775 100644 --- a/dGame/dBehaviors/TacArcBehavior.cpp +++ b/dGame/dBehaviors/TacArcBehavior.cpp @@ -12,16 +12,24 @@ #include void TacArcBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) { - if (this->m_targetEnemy && this->m_usePickedTarget && branch.target > 0) { - this->m_action->Handle(context, bitStream, branch); - - return; + std::vector targets = {}; + + if (this->m_usePickedTarget && branch.target != LWOOBJID_EMPTY) { + auto target = Game::entityManager->GetEntity(branch.target); + if (!target) Game::logger->Log("TacArcBehavior", "target %llu is null", branch.target); + else { + targets.push_back(target); + context->FilterTargets(targets, this->m_ignoreFactionList, this->m_includeFactionList, this->m_targetSelf, this->m_targetEnemy, this->m_targetFriend, this->m_targetTeam); + if (!targets.empty()) { + this->m_action->Handle(context, bitStream, branch); + return; + } + } } - bool hit = false; - - if (!bitStream->Read(hit)) { - Game::logger->Log("TacArcBehavior", "Unable to read hit from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits()); + bool hasTargets = false; + if (!bitStream->Read(hasTargets)) { + Game::logger->Log("TacArcBehavior", "Unable to read hasTargets from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits()); return; }; @@ -35,26 +43,23 @@ void TacArcBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStre if (blocked) { this->m_blockedAction->Handle(context, bitStream, branch); - return; } } - if (hit) { + if (hasTargets) { uint32_t count = 0; - if (!bitStream->Read(count)) { Game::logger->Log("TacArcBehavior", "Unable to read count from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits()); return; }; - if (count > m_maxTargets && m_maxTargets > 0) { - count = m_maxTargets; + if (count > m_maxTargets) { + Game::logger->Log("TacArcBehavior", "Bitstream has too many targets Max:%i Recv:%i", this->m_maxTargets, count); + return; } - std::vector targets; - - for (auto i = 0u; i < count; ++i) { + for (auto i = 0u; i < count; i++) { LWOOBJID id{}; if (!bitStream->Read(id)) { @@ -62,17 +67,19 @@ void TacArcBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStre return; }; - targets.push_back(id); + if (id != LWOOBJID_EMPTY) { + auto* canidate = Game::entityManager->GetEntity(id); + if (canidate) targets.push_back(canidate); + } else { + Game::logger->Log("TacArcBehavior", "Bitstream has LWOOBJID_EMPTY as a target!"); + } } for (auto target : targets) { - branch.target = target; - + branch.target = target->GetObjectID(); this->m_action->Handle(context, bitStream, branch); } - } else { - this->m_missAction->Handle(context, bitStream, branch); - } + } else this->m_missAction->Handle(context, bitStream, branch); } void TacArcBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) { @@ -82,23 +89,15 @@ void TacArcBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitS return; } - const auto* destroyableComponent = self->GetComponent(); - - if ((this->m_usePickedTarget || context->clientInitalized) && branch.target > 0) { - const auto* target = Game::entityManager->GetEntity(branch.target); - - if (target == nullptr) { + std::vector targets = {}; + if (this->m_usePickedTarget && branch.target != LWOOBJID_EMPTY) { + auto target = Game::entityManager->GetEntity(branch.target); + targets.push_back(target); + context->FilterTargets(targets, this->m_ignoreFactionList, this->m_includeFactionList, this->m_targetSelf, this->m_targetEnemy, this->m_targetFriend, this->m_targetTeam); + if (!targets.empty()) { + this->m_action->Handle(context, bitStream, branch); return; } - - // If the game is specific about who to target, check that - if (destroyableComponent == nullptr || ((!m_targetFriend && !m_targetEnemy - || m_targetFriend && destroyableComponent->IsFriend(target) - || m_targetEnemy && destroyableComponent->IsEnemy(target)))) { - this->m_action->Calculate(context, bitStream, branch); - } - - return; } auto* combatAi = self->GetComponent(); @@ -107,50 +106,25 @@ void TacArcBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitS auto reference = self->GetPosition(); //+ m_offset; - std::vector targets; - - std::vector validTargets; - - if (combatAi != nullptr) { - if (combatAi->GetTarget() != LWOOBJID_EMPTY) { - validTargets.push_back(combatAi->GetTarget()); - } - } + targets.clear(); - // Find all valid targets, based on whether we target enemies or friends - for (const auto& contextTarget : context->GetValidTargets()) { - if (destroyableComponent != nullptr) { - const auto* targetEntity = Game::entityManager->GetEntity(contextTarget); + std::vector validTargets = Game::entityManager->GetEntitiesByProximity(reference, this->m_maxRange); - if (m_targetEnemy && destroyableComponent->IsEnemy(targetEntity) - || m_targetFriend && destroyableComponent->IsFriend(targetEntity)) { - validTargets.push_back(contextTarget); - } - } else { - validTargets.push_back(contextTarget); - } - } + // filter all valid targets, based on whether we target enemies or friends + context->FilterTargets(validTargets, this->m_ignoreFactionList, this->m_includeFactionList, this->m_targetSelf, this->m_targetEnemy, this->m_targetFriend, this->m_targetTeam); for (auto validTarget : validTargets) { if (targets.size() >= this->m_maxTargets) { break; } - auto* entity = Game::entityManager->GetEntity(validTarget); - - if (entity == nullptr) { - Game::logger->Log("TacArcBehavior", "Invalid target (%llu) for (%llu)!", validTarget, context->originator); - + if (std::find(targets.begin(), targets.end(), validTarget) != targets.end()) { continue; } - if (std::find(targets.begin(), targets.end(), entity) != targets.end()) { - continue; - } - - if (entity->GetIsDead()) continue; + if (validTarget->GetIsDead()) continue; - const auto otherPosition = entity->GetPosition(); + const auto otherPosition = validTarget->GetPosition(); const auto heightDifference = std::abs(otherPosition.y - casterPosition.y); @@ -180,8 +154,8 @@ void TacArcBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitS const float degreeAngle = std::abs(Vector3::Angle(forward, normalized) * (180 / 3.14) - 180); - if (distance >= this->m_minDistance && this->m_maxDistance >= distance && degreeAngle <= 2 * this->m_angle) { - targets.push_back(entity); + if (distance >= this->m_minRange && this->m_maxRange >= distance && degreeAngle <= 2 * this->m_angle) { + targets.push_back(validTarget); } } @@ -228,43 +202,48 @@ void TacArcBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitS } void TacArcBehavior::Load() { - this->m_usePickedTarget = GetBoolean("use_picked_target"); + this->m_maxRange = GetFloat("max range"); + this->m_height = GetFloat("height", 2.2f); + this->m_distanceWeight = GetFloat("distance_weight", 0.0f); + this->m_angleWeight = GetFloat("angle_weight", 0.0f); + this->m_angle = GetFloat("angle", 45.0f); + this->m_minRange = GetFloat("min range", 0.0f); + this->m_offset = NiPoint3( + GetFloat("offset_x", 0.0f), + GetFloat("offset_y", 0.0f), + GetFloat("offset_z", 0.0f) + ); + this->m_method = GetInt("method", 1); + this->m_upperBound = GetFloat("upper_bound", 4.4f); + this->m_lowerBound = GetFloat("lower_bound", 0.4f); + this->m_usePickedTarget = GetBoolean("use_picked_target", false); + this->m_useTargetPostion = GetBoolean("use_target_position", false); + this->m_checkEnv = GetBoolean("check_env", false); + this->m_useAttackPriority = GetBoolean("use_attack_priority", false); this->m_action = GetAction("action"); - this->m_missAction = GetAction("miss action"); - - this->m_checkEnv = GetBoolean("check_env"); - this->m_blockedAction = GetAction("blocked action"); - this->m_minDistance = GetFloat("min range"); + this->m_maxTargets = GetInt("max targets", 100); + if (this->m_maxTargets == 0) this->m_maxTargets = 100; - this->m_maxDistance = GetFloat("max range"); + this->m_farHeight = GetFloat("far_height", 5.0f); + this->m_farWidth = GetFloat("far_width", 5.0f); + this->m_nearHeight = GetFloat("near_height", 5.0f); + this->m_nearWidth = GetFloat("near_width", 5.0f); - this->m_maxTargets = GetInt("max targets"); - - this->m_targetEnemy = GetBoolean("target_enemy"); - - this->m_targetFriend = GetBoolean("target_friend"); - - this->m_targetTeam = GetBoolean("target_team"); - - this->m_angle = GetFloat("angle"); - - this->m_upperBound = GetFloat("upper_bound"); - - this->m_lowerBound = GetFloat("lower_bound"); - - this->m_farHeight = GetFloat("far_height"); - - this->m_farWidth = GetFloat("far_width"); - - this->m_method = GetInt("method"); - - this->m_offset = { - GetFloat("offset_x"), - GetFloat("offset_y"), - GetFloat("offset_z") - }; + // params after this are needed for filter targets + const auto parameters = GetParameterNames(); + for (const auto& parameter : parameters) { + if (parameter.first.rfind("include_faction", 0) == 0) { + this->m_includeFactionList.push_front(parameter.second); + } else if (parameter.first.rfind("ignore_faction", 0) == 0) { + this->m_ignoreFactionList.push_front(parameter.second); + } + } + this->m_targetSelf = GetBoolean("target_caster", false); + this->m_targetEnemy = GetBoolean("target_enemy", false); + this->m_targetFriend = GetBoolean("target_friend", false); + this->m_targetTeam = GetBoolean("target_team", false); } diff --git a/dGame/dBehaviors/TacArcBehavior.h b/dGame/dBehaviors/TacArcBehavior.h index 87a22051e..d93452727 100644 --- a/dGame/dBehaviors/TacArcBehavior.h +++ b/dGame/dBehaviors/TacArcBehavior.h @@ -2,56 +2,42 @@ #include "Behavior.h" #include "dCommonVars.h" #include "NiPoint3.h" +#include -class TacArcBehavior final : public Behavior -{ +class TacArcBehavior final : public Behavior { public: + explicit TacArcBehavior(const uint32_t behavior_id) : Behavior(behavior_id) {} + void Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override; + void Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override; + void Load() override; +private: + float m_maxRange; + float m_height; + float m_distanceWeight; + float m_angleWeight; + float m_angle; + float m_minRange; + NiPoint3 m_offset; + uint32_t m_method; + float m_upperBound; + float m_lowerBound; bool m_usePickedTarget; - - Behavior* m_action; - + bool m_useTargetPostion; bool m_checkEnv; - + bool m_useAttackPriority; + Behavior* m_action; Behavior* m_missAction; - Behavior* m_blockedAction; - - float m_minDistance; - - float m_maxDistance; - uint32_t m_maxTargets; + float m_farHeight; + float m_farWidth; + float m_nearHeight; + float m_nearWidth; + std::forward_list m_ignoreFactionList {}; + std::forward_list m_includeFactionList {}; + bool m_targetSelf; bool m_targetEnemy; - bool m_targetFriend; - bool m_targetTeam; - - float m_angle; - - float m_upperBound; - - float m_lowerBound; - - float m_farHeight; - - float m_farWidth; - - uint32_t m_method; - - NiPoint3 m_offset; - - /* - * Inherited - */ - - explicit TacArcBehavior(const uint32_t behavior_id) : Behavior(behavior_id) { - } - - void Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override; - - void Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override; - - void Load() override; }; diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 0adf62f3a..358843616 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -363,9 +363,10 @@ void DestroyableComponent::SetIsShielded(bool value) { void DestroyableComponent::AddFaction(const int32_t factionID, const bool ignoreChecks) { // Ignore factionID -1 - if (factionID == -1 && !ignoreChecks) { - return; - } + if (factionID == -1 && !ignoreChecks) return; + + // if we already have that faction, don't add it again + if (std::find(m_FactionIDs.begin(), m_FactionIDs.end(), factionID) != m_FactionIDs.end()) return; m_FactionIDs.push_back(factionID); m_DirtyHealth = true; @@ -407,6 +408,14 @@ void DestroyableComponent::AddFaction(const int32_t factionID, const bool ignore } bool DestroyableComponent::IsEnemy(const Entity* other) const { + if (m_Parent->IsPlayer() && other->IsPlayer()){ + auto* thisCharacterComponent = m_Parent->GetComponent(); + if (!thisCharacterComponent) return false; + auto* otherCharacterComponent = other->GetComponent(); + if (!otherCharacterComponent) return false; + if (thisCharacterComponent->GetPvpEnabled() && otherCharacterComponent->GetPvpEnabled()) return true; + return false; + } const auto* otherDestroyableComponent = other->GetComponent(); if (otherDestroyableComponent != nullptr) { for (const auto enemyFaction : m_EnemyFactionIDs) { @@ -485,43 +494,6 @@ Entity* DestroyableComponent::GetKiller() const { return Game::entityManager->GetEntity(m_KillerID); } -bool DestroyableComponent::CheckValidity(const LWOOBJID target, const bool ignoreFactions, const bool targetEnemy, const bool targetFriend) const { - auto* targetEntity = Game::entityManager->GetEntity(target); - - if (targetEntity == nullptr) { - Game::logger->Log("DestroyableComponent", "Invalid entity for checking validity (%llu)!", target); - return false; - } - - auto* targetDestroyable = targetEntity->GetComponent(); - - if (targetDestroyable == nullptr) { - return false; - } - - auto* targetQuickbuild = targetEntity->GetComponent(); - - if (targetQuickbuild != nullptr) { - const auto state = targetQuickbuild->GetState(); - - if (state != eRebuildState::COMPLETED) { - return false; - } - } - - if (ignoreFactions) { - return true; - } - - // Get if the target entity is an enemy and friend - bool isEnemy = IsEnemy(targetEntity); - bool isFriend = IsFriend(targetEntity); - - // Return true if the target type matches what we are targeting - return (isEnemy && targetEnemy) || (isFriend && targetFriend); -} - - void DestroyableComponent::Heal(const uint32_t health) { auto current = static_cast(GetHealth()); const auto max = static_cast(GetMaxHealth()); diff --git a/dGame/dComponents/DestroyableComponent.h b/dGame/dComponents/DestroyableComponent.h index ed0910664..d9a2191de 100644 --- a/dGame/dComponents/DestroyableComponent.h +++ b/dGame/dComponents/DestroyableComponent.h @@ -371,14 +371,6 @@ class DestroyableComponent : public Component { */ Entity* GetKiller() const; - /** - * Checks if the target ID is a valid enemy of this entity - * @param target the target ID to check for - * @param ignoreFactions whether or not check for the factions, e.g. just return true if the entity cannot be smashed - * @return if the target ID is a valid enemy - */ - bool CheckValidity(LWOOBJID target, bool ignoreFactions = false, bool targetEnemy = true, bool targetFriend = false) const; - /** * Attempt to damage this entity, handles everything from health and armor to absorption, immunity and callbacks. * @param damage the damage to attempt to apply diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 3625defce..512e3c907 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -1158,19 +1158,7 @@ void InventoryComponent::AddItemSkills(const LOT lot) { const auto skill = FindSkill(lot); - if (skill == 0) { - return; - } - - if (index != m_Skills.end()) { - const auto old = index->second; - - GameMessages::SendRemoveSkill(m_Parent, old); - } - - GameMessages::SendAddSkill(m_Parent, skill, static_cast(slot)); - - m_Skills.insert_or_assign(slot, skill); + SetSkill(slot, skill); } void InventoryComponent::RemoveItemSkills(const LOT lot) { @@ -1197,7 +1185,7 @@ void InventoryComponent::RemoveItemSkills(const LOT lot) { if (slot == BehaviorSlot::Primary) { m_Skills.insert_or_assign(BehaviorSlot::Primary, 1); - GameMessages::SendAddSkill(m_Parent, 1, static_cast(BehaviorSlot::Primary)); + GameMessages::SendAddSkill(m_Parent, 1, BehaviorSlot::Primary); } } @@ -1627,3 +1615,29 @@ void InventoryComponent::UpdatePetXml(tinyxml2::XMLDocument* document) { petInventoryElement->LinkEndChild(petElement); } } + + +bool InventoryComponent::SetSkill(int32_t slot, uint32_t skillId){ + BehaviorSlot behaviorSlot = BehaviorSlot::Invalid; + if (slot == 1 ) behaviorSlot = BehaviorSlot::Primary; + else if (slot == 2 ) behaviorSlot = BehaviorSlot::Offhand; + else if (slot == 3 ) behaviorSlot = BehaviorSlot::Neck; + else if (slot == 4 ) behaviorSlot = BehaviorSlot::Head; + else if (slot == 5 ) behaviorSlot = BehaviorSlot::Consumable; + else return false; + return SetSkill(behaviorSlot, skillId); +} + +bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId){ + if (skillId == 0) return false; + const auto index = m_Skills.find(slot); + if (index != m_Skills.end()) { + const auto old = index->second; + GameMessages::SendRemoveSkill(m_Parent, old); + } + + GameMessages::SendAddSkill(m_Parent, skillId, slot); + m_Skills.insert_or_assign(slot, skillId); + return true; +} + diff --git a/dGame/dComponents/InventoryComponent.h b/dGame/dComponents/InventoryComponent.h index ffb7a3603..ab9de3e64 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -367,6 +367,11 @@ class InventoryComponent : public Component */ void UnequipScripts(Item* unequippedItem); + std::map GetSkills(){ return m_Skills; }; + + bool SetSkill(int slot, uint32_t skillId); + bool SetSkill(BehaviorSlot slot, uint32_t skillId); + ~InventoryComponent() override; private: diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 83f832b2b..fa858ab7a 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -1159,7 +1159,7 @@ void GameMessages::SendPlayerReachedRespawnCheckpoint(Entity* entity, const NiPo SEND_PACKET; } -void GameMessages::SendAddSkill(Entity* entity, TSkillID skillID, int slotID) { +void GameMessages::SendAddSkill(Entity* entity, TSkillID skillID, BehaviorSlot slotID) { int AICombatWeight = 0; bool bFromSkillSet = false; int castType = 0; @@ -1189,8 +1189,8 @@ void GameMessages::SendAddSkill(Entity* entity, TSkillID skillID, int slotID) { bitStream.Write(skillID); - bitStream.Write(slotID != -1); - if (slotID != -1) bitStream.Write(slotID); + bitStream.Write(slotID != BehaviorSlot::Invalid); + if (slotID != BehaviorSlot::Invalid) bitStream.Write(slotID); bitStream.Write(temporary); diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 3a9963b47..93db23c18 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -36,6 +36,7 @@ enum class ePetTamingNotifyType : uint32_t; enum class eUseItemResponse : uint32_t; enum class eQuickBuildFailReason : uint32_t; enum class eRebuildState : uint32_t; +enum class BehaviorSlot : int32_t; namespace GameMessages { class PropertyDataMessage; @@ -119,7 +120,7 @@ namespace GameMessages { void SendSetPlayerControlScheme(Entity* entity, eControlScheme controlScheme); void SendPlayerReachedRespawnCheckpoint(Entity* entity, const NiPoint3& position, const NiQuaternion& rotation); - void SendAddSkill(Entity* entity, TSkillID skillID, int slotID); + void SendAddSkill(Entity* entity, TSkillID skillID, BehaviorSlot slotID); void SendRemoveSkill(Entity* entity, TSkillID skillID); void SendFinishArrangingWithItem(Entity* entity, const LWOOBJID& buildAreaID); diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 95fe74722..a27b62786 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -1841,6 +1841,86 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ChatPackets::SendSystemMessage(sysAddr, u"Deleted inventory " + GeneralUtils::UTF8ToUTF16(args[0])); } + if (chatCommand == "castskill" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { + auto* skillComponent = entity->GetComponent(); + if (skillComponent){ + uint32_t skillId; + + if (!GeneralUtils::TryParse(args[0], skillId)) { + ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill ID."); + return; + } else { + skillComponent->CastSkill(skillId, entity->GetObjectID(), entity->GetObjectID()); + ChatPackets::SendSystemMessage(sysAddr, u"Cast skill"); + } + } + } + + if (chatCommand == "setskillslot" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 2) { + uint32_t skillId; + int slot; + auto* inventoryComponent = entity->GetComponent(); + if (inventoryComponent){ + if (!GeneralUtils::TryParse(args[0], slot)) { + ChatPackets::SendSystemMessage(sysAddr, u"Error getting slot."); + return; + } else { + if (!GeneralUtils::TryParse(args[1], skillId)) { + ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill."); + return; + } else { + if(inventoryComponent->SetSkill(slot, skillId)) ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot successfully"); + else ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot failed"); + } + } + } + } + + if (chatCommand == "setfaction" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { + auto* destroyableComponent = entity->GetComponent(); + if (destroyableComponent){ + int32_t faction; + + if (!GeneralUtils::TryParse(args[0], faction)) { + ChatPackets::SendSystemMessage(sysAddr, u"Error getting faction."); + return; + } else { + destroyableComponent->SetFaction(faction); + ChatPackets::SendSystemMessage(sysAddr, u"Set faction and updated enemies list"); + } + } + } + + if (chatCommand == "addfaction" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { + auto* destroyableComponent = entity->GetComponent(); + if (destroyableComponent){ + int32_t faction; + + if (!GeneralUtils::TryParse(args[0], faction)) { + ChatPackets::SendSystemMessage(sysAddr, u"Error getting faction."); + return; + } else { + destroyableComponent->AddFaction(faction); + ChatPackets::SendSystemMessage(sysAddr, u"Added faction and updated enemies list"); + } + } + } + + if (chatCommand == "getfactions" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + auto* destroyableComponent = entity->GetComponent(); + if (destroyableComponent){ + ChatPackets::SendSystemMessage(sysAddr, u"Friendly factions:"); + for (const auto entry : destroyableComponent->GetFactionIDs()) { + ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry))); + } + + ChatPackets::SendSystemMessage(sysAddr, u"Enemy factions:"); + for (const auto entry : destroyableComponent->GetEnemyFactionsIDs()) { + ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry))); + } + } + } + if (chatCommand == "inspect" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { Entity* closest = nullptr; @@ -1980,6 +2060,14 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit destuctable->SetFaction(-1); destuctable->AddFaction(faction, true); } + } else if (args[1] == "-cf") { + auto* destuctable = entity->GetComponent(); + if (!destuctable) { + ChatPackets::SendSystemMessage(sysAddr, u"No destroyable component on this entity!"); + return; + } + if (destuctable->IsEnemy(closest)) ChatPackets::SendSystemMessage(sysAddr, u"They are our enemy"); + else ChatPackets::SendSystemMessage(sysAddr, u"They are NOT our enemy"); } else if (args[1] == "-t") { auto* phantomPhysicsComponent = closest->GetComponent(); diff --git a/docs/Commands.md b/docs/Commands.md index 85edfd3b4..ea81ff569 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -106,7 +106,11 @@ These commands are primarily for development and testing. The usage of many of t |Set Level|`/setlevel (username)`|Sets the using entities level to the requested level. Takes an optional parameter of an in-game players username to set the level of.|8| |crash|`/crash`|Crashes the server.|9| |rollloot|`/rollloot `|Given a `loot matrix index`, look for `item id` in that matrix `amount` times and print to the chat box statistics of rolling that loot matrix.|9| - +|castskill|`/castskill `|Casts the skill as the player|9| +|setskillslot|`/setskillslot `||8| +|setfaction|`/setfaction `|Clears the users current factions and sets it|8| +|addfaction|`/addfaction `|Add the faction to the users list of factions|8| +|getfactions|`/getfactions`|Shows the player's factions|8| ## Detailed `/inspect` Usage `/inspect (-m | -a | -s | -p | -f (faction) | -t)` @@ -120,6 +124,7 @@ Finds the closest entity with the given component or LDF variable (ignoring play * `-s`: Prints the entity's settings and spawner ID. * `-p`: Prints the entity's position * `-f`: If the entity has a destroyable component, prints whether the entity is smashable and its friendly and enemy faction IDs; if `faction` is specified, adds that faction to the entity. +* `-cf`: check if the entity is enemy or friend * `-t`: If the entity has a phantom physics component, prints the effect type, direction, directional multiplier, and whether the effect is active; in any case, if the entity has a trigger, prints the trigger ID. ## Game Master Levels From ad003634f417f6f8ce2ef3eadb402edc0128936e Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 9 Oct 2023 13:19:38 -0700 Subject: [PATCH 09/11] chore: Physics Component abstraction and addition of tests (#1159) * Make serialize actually virtual yep * Abstract to PhysicsComponent Move shared functionality of all physics related classes to a base class. Tested that there were no failed to unserialize errors when in main gameplay in Gnarled Forest or in a race. Tested that 2 players were able to see each other in the above scenarios just fine as well. * Update PhantomPhysicsComponent.cpp * Add SimplePhysicsTest * Add construction test * Update SimplePhysicsComponentTests.cpp * remove flags and fix override * Update VendorComponent.h --- dGame/dComponents/CMakeLists.txt | 1 + .../ControllablePhysicsComponent.cpp | 25 +-- .../ControllablePhysicsComponent.h | 35 +--- dGame/dComponents/PhantomPhysicsComponent.cpp | 22 +-- dGame/dComponents/PhantomPhysicsComponent.h | 36 +--- dGame/dComponents/PhysicsComponent.cpp | 21 +++ dGame/dComponents/PhysicsComponent.h | 32 ++++ .../RigidbodyPhantomPhysicsComponent.cpp | 22 +-- .../RigidbodyPhantomPhysicsComponent.h | 54 +----- dGame/dComponents/SimplePhysicsComponent.cpp | 17 +- dGame/dComponents/SimplePhysicsComponent.h | 44 +---- dGame/dComponents/VehiclePhysicsComponent.cpp | 24 +-- dGame/dComponents/VehiclePhysicsComponent.h | 33 +--- .../dComponentsTests/CMakeLists.txt | 1 + .../SimplePhysicsComponentTests.cpp | 154 ++++++++++++++++++ 15 files changed, 242 insertions(+), 279 deletions(-) create mode 100644 dGame/dComponents/PhysicsComponent.cpp create mode 100644 dGame/dComponents/PhysicsComponent.h create mode 100644 tests/dGameTests/dComponentsTests/SimplePhysicsComponentTests.cpp diff --git a/dGame/dComponents/CMakeLists.txt b/dGame/dComponents/CMakeLists.txt index 6c02cf0bf..229de5870 100644 --- a/dGame/dComponents/CMakeLists.txt +++ b/dGame/dComponents/CMakeLists.txt @@ -18,6 +18,7 @@ set(DGAME_DCOMPONENTS_SOURCES "BaseCombatAIComponent.cpp" "MovingPlatformComponent.cpp" "PetComponent.cpp" "PhantomPhysicsComponent.cpp" + "PhysicsComponent.cpp" "PlayerForcedMovementComponent.cpp" "PossessableComponent.cpp" "PossessorComponent.cpp" diff --git a/dGame/dComponents/ControllablePhysicsComponent.cpp b/dGame/dComponents/ControllablePhysicsComponent.cpp index de27149ef..fe3c12484 100644 --- a/dGame/dComponents/ControllablePhysicsComponent.cpp +++ b/dGame/dComponents/ControllablePhysicsComponent.cpp @@ -15,15 +15,12 @@ #include "LevelProgressionComponent.h" #include "eStateChangeType.h" -ControllablePhysicsComponent::ControllablePhysicsComponent(Entity* entity) : Component(entity) { - m_Position = {}; - m_Rotation = NiQuaternion::IDENTITY; +ControllablePhysicsComponent::ControllablePhysicsComponent(Entity* entity) : PhysicsComponent(entity) { m_Velocity = {}; m_AngularVelocity = {}; m_InJetpackMode = false; m_IsOnGround = true; m_IsOnRail = false; - m_DirtyPosition = true; m_DirtyVelocity = true; m_DirtyAngularVelocity = true; m_dpEntity = nullptr; @@ -202,26 +199,14 @@ void ControllablePhysicsComponent::UpdateXml(tinyxml2::XMLDocument* doc) { } void ControllablePhysicsComponent::SetPosition(const NiPoint3& pos) { - if (m_Static) { - return; - } - - m_Position.x = pos.x; - m_Position.y = pos.y; - m_Position.z = pos.z; - m_DirtyPosition = true; - + if (m_Static) return; + PhysicsComponent::SetPosition(pos); if (m_dpEntity) m_dpEntity->SetPosition(pos); } void ControllablePhysicsComponent::SetRotation(const NiQuaternion& rot) { - if (m_Static) { - return; - } - - m_Rotation = rot; - m_DirtyPosition = true; - + if (m_Static) return; + PhysicsComponent::SetRotation(rot); if (m_dpEntity) m_dpEntity->SetRotation(rot); } diff --git a/dGame/dComponents/ControllablePhysicsComponent.h b/dGame/dComponents/ControllablePhysicsComponent.h index 384dfdac0..897ced1c9 100644 --- a/dGame/dComponents/ControllablePhysicsComponent.h +++ b/dGame/dComponents/ControllablePhysicsComponent.h @@ -6,7 +6,7 @@ #include "NiPoint3.h" #include "NiQuaternion.h" #include "tinyxml2.h" -#include "Component.h" +#include "PhysicsComponent.h" #include "dpCollisionChecks.h" #include "PhantomPhysicsComponent.h" #include "eBubbleType.h" @@ -19,7 +19,7 @@ enum class eStateChangeType : uint32_t; /** * Handles the movement of controllable Entities, e.g. enemies and players */ -class ControllablePhysicsComponent : public Component { +class ControllablePhysicsComponent : public PhysicsComponent { public: static const eReplicaComponentType ComponentType = eReplicaComponentType::CONTROLLABLE_PHYSICS; @@ -36,26 +36,14 @@ class ControllablePhysicsComponent : public Component { * If the entity is static, this is a no-op. * @param pos The position to set */ - void SetPosition(const NiPoint3& pos); - - /** - * Returns the current position of the entity - * @return The current position of the entity - */ - const NiPoint3& GetPosition() const { return m_Position; } + void SetPosition(const NiPoint3& pos) override; /** * Sets the rotation of this entity, ensures this change is serialized next tick. If the entity is static, this is * a no-op. * @param rot the rotation to set */ - void SetRotation(const NiQuaternion& rot); - - /** - * Returns the current rotation of this entity - * @return the current rotation of this entity - */ - const NiQuaternion& GetRotation() const { return m_Rotation; } + void SetRotation(const NiQuaternion& rot) override; /** * Sets the current velocity of this entity, ensures that this change is serialized next tick. If the entity is @@ -322,21 +310,6 @@ class ControllablePhysicsComponent : public Component { */ dpEntity* m_dpEntity; - /** - * Whether or not the position is dirty, forcing a serialization update of the position - */ - bool m_DirtyPosition; - - /** - * The current position of the entity - */ - NiPoint3 m_Position; - - /** - * The current rotation of the entity - */ - NiQuaternion m_Rotation; - /** * Whether or not the velocity is dirty, forcing a serialization of the velocity */ diff --git a/dGame/dComponents/PhantomPhysicsComponent.cpp b/dGame/dComponents/PhantomPhysicsComponent.cpp index 866543b37..7b7eaf6bb 100644 --- a/dGame/dComponents/PhantomPhysicsComponent.cpp +++ b/dGame/dComponents/PhantomPhysicsComponent.cpp @@ -27,14 +27,13 @@ #include "dpShapeBox.h" #include "dpShapeSphere.h" -PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent) : Component(parent) { +PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent) : PhysicsComponent(parent) { m_Position = m_Parent->GetDefaultPosition(); m_Rotation = m_Parent->GetDefaultRotation(); m_Scale = m_Parent->GetDefaultScale(); m_dpEntity = nullptr; m_EffectInfoDirty = false; - m_PositionInfoDirty = false; m_IsPhysicsEffectActive = false; m_EffectType = ePhysicsEffectType::PUSH; @@ -307,18 +306,7 @@ void PhantomPhysicsComponent::CreatePhysics() { } void PhantomPhysicsComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate) { - outBitStream->Write(m_PositionInfoDirty || bIsInitialUpdate); - if (m_PositionInfoDirty || bIsInitialUpdate) { - outBitStream->Write(m_Position.x); - outBitStream->Write(m_Position.y); - outBitStream->Write(m_Position.z); - outBitStream->Write(m_Rotation.x); - outBitStream->Write(m_Rotation.y); - outBitStream->Write(m_Rotation.z); - outBitStream->Write(m_Rotation.w); - - m_PositionInfoDirty = false; - } + PhysicsComponent::Serialize(outBitStream, bIsInitialUpdate); outBitStream->Write(m_EffectInfoDirty || bIsInitialUpdate); if (m_EffectInfoDirty || bIsInitialUpdate) { @@ -426,13 +414,11 @@ void PhantomPhysicsComponent::SetMax(uint32_t max) { } void PhantomPhysicsComponent::SetPosition(const NiPoint3& pos) { - m_Position = pos; - + PhysicsComponent::SetPosition(pos); if (m_dpEntity) m_dpEntity->SetPosition(pos); } void PhantomPhysicsComponent::SetRotation(const NiQuaternion& rot) { - m_Rotation = rot; - + PhysicsComponent::SetRotation(rot); if (m_dpEntity) m_dpEntity->SetRotation(rot); } diff --git a/dGame/dComponents/PhantomPhysicsComponent.h b/dGame/dComponents/PhantomPhysicsComponent.h index e5769f40e..3387c9cfc 100644 --- a/dGame/dComponents/PhantomPhysicsComponent.h +++ b/dGame/dComponents/PhantomPhysicsComponent.h @@ -11,8 +11,8 @@ #include #include "CppScripts.h" #include "InvalidScript.h" -#include "Component.h" #include "eReplicaComponentType.h" +#include "PhysicsComponent.h" class LDFBaseData; class Entity; @@ -25,7 +25,7 @@ enum class ePhysicsEffectType : uint32_t ; * trigger gameplay events, for example the bus in Avant Gardens that moves around when the player touches its physics * body. Optionally this object can also have effects, like the fans in AG. */ -class PhantomPhysicsComponent : public Component { +class PhantomPhysicsComponent : public PhysicsComponent { public: static const eReplicaComponentType ComponentType = eReplicaComponentType::PHANTOM_PHYSICS; @@ -75,29 +75,17 @@ class PhantomPhysicsComponent : public Component { */ void SetPhysicsEffectActive(bool val) { m_IsPhysicsEffectActive = val; m_EffectInfoDirty = true; } - /** - * Returns the position of this physics object - * @return the position of this physics object - */ - const NiPoint3& GetPosition() const { return m_Position; } - /** * Sets the position of this physics object * @param pos the position to set */ - void SetPosition(const NiPoint3& pos); - - /** - * Returns the rotation of this physics object - * @return the rotation of this physics object - */ - const NiQuaternion& GetRotation() const { return m_Rotation; } + void SetPosition(const NiPoint3& pos) override; /** * Sets the rotation of this physics object * @param rot the rotation to set */ - void SetRotation(const NiQuaternion& rot); + void SetRotation(const NiQuaternion& rot) override; /** * Returns the effect that's currently active, defaults to 0 @@ -134,27 +122,11 @@ class PhantomPhysicsComponent : public Component { void SetMax(uint32_t max); private: - - /** - * The position of the physics object - */ - NiPoint3 m_Position; - - /** - * The rotation of the physics object - */ - NiQuaternion m_Rotation; - /** * A scale to apply to the size of the physics object */ float m_Scale; - /** - * Whether or not the position has changed and needs to be serialized - */ - bool m_PositionInfoDirty; - /** * Whether or not the effect has changed and needs to be serialized */ diff --git a/dGame/dComponents/PhysicsComponent.cpp b/dGame/dComponents/PhysicsComponent.cpp new file mode 100644 index 000000000..a66c422a1 --- /dev/null +++ b/dGame/dComponents/PhysicsComponent.cpp @@ -0,0 +1,21 @@ +#include "PhysicsComponent.h" + +PhysicsComponent::PhysicsComponent(Entity* parent) : Component(parent) { + m_Position = NiPoint3::ZERO; + m_Rotation = NiQuaternion::IDENTITY; + m_DirtyPosition = false; +} + +void PhysicsComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate) { + outBitStream->Write(bIsInitialUpdate || m_DirtyPosition); + if (bIsInitialUpdate || m_DirtyPosition) { + outBitStream->Write(m_Position.x); + outBitStream->Write(m_Position.y); + outBitStream->Write(m_Position.z); + outBitStream->Write(m_Rotation.x); + outBitStream->Write(m_Rotation.y); + outBitStream->Write(m_Rotation.z); + outBitStream->Write(m_Rotation.w); + if (!bIsInitialUpdate) m_DirtyPosition = false; + } +} diff --git a/dGame/dComponents/PhysicsComponent.h b/dGame/dComponents/PhysicsComponent.h new file mode 100644 index 000000000..c1a7f34a9 --- /dev/null +++ b/dGame/dComponents/PhysicsComponent.h @@ -0,0 +1,32 @@ +#ifndef __PHYSICSCOMPONENT__H__ +#define __PHYSICSCOMPONENT__H__ + +#include "Component.h" +#include "NiPoint3.h" +#include "NiQuaternion.h" + +namespace Raknet { + class BitStream; +}; + +class PhysicsComponent : public Component { +public: + PhysicsComponent(Entity* parent); + virtual ~PhysicsComponent() = default; + + void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate) override; + + const NiPoint3& GetPosition() const { return m_Position; } + virtual void SetPosition(const NiPoint3& pos) { if (m_Position == pos) return; m_Position = pos; m_DirtyPosition = true; } + + const NiQuaternion& GetRotation() const { return m_Rotation; } + virtual void SetRotation(const NiQuaternion& rot) { if (m_Rotation == rot) return; m_Rotation = rot; m_DirtyPosition = true; } +protected: + NiPoint3 m_Position; + + NiQuaternion m_Rotation; + + bool m_DirtyPosition; +}; + +#endif //!__PHYSICSCOMPONENT__H__ diff --git a/dGame/dComponents/RigidbodyPhantomPhysicsComponent.cpp b/dGame/dComponents/RigidbodyPhantomPhysicsComponent.cpp index 5390db3a7..414ce2e83 100644 --- a/dGame/dComponents/RigidbodyPhantomPhysicsComponent.cpp +++ b/dGame/dComponents/RigidbodyPhantomPhysicsComponent.cpp @@ -1,32 +1,16 @@ /* * Darkflame Universe - * Copyright 2019 + * Copyright 2023 */ #include "RigidbodyPhantomPhysicsComponent.h" #include "Entity.h" -RigidbodyPhantomPhysicsComponent::RigidbodyPhantomPhysicsComponent(Entity* parent) : Component(parent) { +RigidbodyPhantomPhysicsComponent::RigidbodyPhantomPhysicsComponent(Entity* parent) : PhysicsComponent(parent) { m_Position = m_Parent->GetDefaultPosition(); m_Rotation = m_Parent->GetDefaultRotation(); - m_IsDirty = true; -} - -RigidbodyPhantomPhysicsComponent::~RigidbodyPhantomPhysicsComponent() { } void RigidbodyPhantomPhysicsComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate) { - outBitStream->Write(m_IsDirty || bIsInitialUpdate); - if (m_IsDirty || bIsInitialUpdate) { - outBitStream->Write(m_Position.x); - outBitStream->Write(m_Position.y); - outBitStream->Write(m_Position.z); - - outBitStream->Write(m_Rotation.x); - outBitStream->Write(m_Rotation.y); - outBitStream->Write(m_Rotation.z); - outBitStream->Write(m_Rotation.w); - - m_IsDirty = false; - } + PhysicsComponent::Serialize(outBitStream, bIsInitialUpdate); } diff --git a/dGame/dComponents/RigidbodyPhantomPhysicsComponent.h b/dGame/dComponents/RigidbodyPhantomPhysicsComponent.h index 6e38cfd0e..66e87f4ad 100644 --- a/dGame/dComponents/RigidbodyPhantomPhysicsComponent.h +++ b/dGame/dComponents/RigidbodyPhantomPhysicsComponent.h @@ -1,71 +1,29 @@ /* * Darkflame Universe - * Copyright 2019 + * Copyright 2023 */ -#ifndef RIGIDBODYPHANTOMPHYSICS_H -#define RIGIDBODYPHANTOMPHYSICS_H +#ifndef __RIGIDBODYPHANTOMPHYSICS_H__ +#define __RIGIDBODYPHANTOMPHYSICS_H__ #include "BitStream.h" #include "dCommonVars.h" #include "NiPoint3.h" #include "NiQuaternion.h" -#include "Component.h" +#include "PhysicsComponent.h" #include "eReplicaComponentType.h" /** * Component that handles rigid bodies that can be interacted with, mostly client-side rendered. An example is the * bananas that fall from trees in GF. */ -class RigidbodyPhantomPhysicsComponent : public Component { +class RigidbodyPhantomPhysicsComponent : public PhysicsComponent { public: static const eReplicaComponentType ComponentType = eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS; RigidbodyPhantomPhysicsComponent(Entity* parent); - ~RigidbodyPhantomPhysicsComponent() override; void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate) override; - - /** - * Returns the position of this entity - * @return the position of this entity - */ - NiPoint3& GetPosition() { return m_Position; } - - /** - * Sets the position of this entity - * @param pos the position to set - */ - void SetPosition(const NiPoint3& pos) { m_Position = pos; m_IsDirty = true; } - - /** - * Returns the rotation of this entity - * @return the rotation of this entity - */ - NiQuaternion& GetRotation() { return m_Rotation; } - - /** - * Sets the rotation for this entity - * @param rot the rotation to tset - */ - void SetRotation(const NiQuaternion& rot) { m_Rotation = rot; m_IsDirty = true; } - -private: - - /** - * The position of this entity - */ - NiPoint3 m_Position; - - /** - * The rotation of this entity - */ - NiQuaternion m_Rotation; - - /** - * Whether or not the component should be serialized - */ - bool m_IsDirty; }; -#endif // RIGIDBODYPHANTOMPHYSICS_H +#endif // __RIGIDBODYPHANTOMPHYSICS_H__ diff --git a/dGame/dComponents/SimplePhysicsComponent.cpp b/dGame/dComponents/SimplePhysicsComponent.cpp index 3cd951691..9a9553040 100644 --- a/dGame/dComponents/SimplePhysicsComponent.cpp +++ b/dGame/dComponents/SimplePhysicsComponent.cpp @@ -13,10 +13,9 @@ #include "Entity.h" -SimplePhysicsComponent::SimplePhysicsComponent(uint32_t componentID, Entity* parent) : Component(parent) { +SimplePhysicsComponent::SimplePhysicsComponent(uint32_t componentID, Entity* parent) : PhysicsComponent(parent) { m_Position = m_Parent->GetDefaultPosition(); m_Rotation = m_Parent->GetDefaultRotation(); - m_IsDirty = true; const auto& climbable_type = m_Parent->GetVar(u"climbable"); if (climbable_type == u"wall") { @@ -54,19 +53,7 @@ void SimplePhysicsComponent::Serialize(RakNet::BitStream* outBitStream, bool bIs } else { outBitStream->Write0(); } - - outBitStream->Write(m_IsDirty || bIsInitialUpdate); - if (m_IsDirty || bIsInitialUpdate) { - outBitStream->Write(m_Position.x); - outBitStream->Write(m_Position.y); - outBitStream->Write(m_Position.z); - outBitStream->Write(m_Rotation.x); - outBitStream->Write(m_Rotation.y); - outBitStream->Write(m_Rotation.z); - outBitStream->Write(m_Rotation.w); - - m_IsDirty = false; - } + PhysicsComponent::Serialize(outBitStream, bIsInitialUpdate); } uint32_t SimplePhysicsComponent::GetPhysicsMotionState() const { diff --git a/dGame/dComponents/SimplePhysicsComponent.h b/dGame/dComponents/SimplePhysicsComponent.h index 4908c8efe..581e5be4d 100644 --- a/dGame/dComponents/SimplePhysicsComponent.h +++ b/dGame/dComponents/SimplePhysicsComponent.h @@ -10,7 +10,7 @@ #include "RakNetTypes.h" #include "NiPoint3.h" #include "NiQuaternion.h" -#include "Component.h" +#include "PhysicsComponent.h" #include "eReplicaComponentType.h" class Entity; @@ -26,7 +26,7 @@ enum class eClimbableType : int32_t { /** * Component that serializes locations of entities to the client */ -class SimplePhysicsComponent : public Component { +class SimplePhysicsComponent : public PhysicsComponent { public: static const eReplicaComponentType ComponentType = eReplicaComponentType::SIMPLE_PHYSICS; @@ -35,30 +35,6 @@ class SimplePhysicsComponent : public Component { void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate) override; - /** - * Returns the position of this entity - * @return the position of this entity - */ - NiPoint3& GetPosition() { return m_Position; } - - /** - * Sets the position of this entity - * @param pos the position to set - */ - void SetPosition(const NiPoint3& pos) { m_Position = pos; m_IsDirty = true; } - - /** - * Returns the rotation of this entity - * @return the rotation of this entity - */ - NiQuaternion& GetRotation() { return m_Rotation; } - - /** - * Sets the rotation of this entity - * @param rot - */ - void SetRotation(const NiQuaternion& rot) { m_Rotation = rot; m_IsDirty = true; } - /** * Returns the velocity of this entity * @return the velocity of this entity @@ -108,17 +84,6 @@ class SimplePhysicsComponent : public Component { void SetClimbableType(const eClimbableType& value) { m_ClimbableType = value; } private: - - /** - * The current position of the entity - */ - NiPoint3 m_Position = NiPoint3::ZERO; - - /** - * The current rotation of the entity - */ - NiQuaternion m_Rotation = NiQuaternion::IDENTITY; - /** * The current velocity of the entity */ @@ -134,11 +99,6 @@ class SimplePhysicsComponent : public Component { */ bool m_DirtyVelocity = true; - /** - * Whether or not the position has changed - */ - bool m_IsDirty = true; - /** * The current physics motion state */ diff --git a/dGame/dComponents/VehiclePhysicsComponent.cpp b/dGame/dComponents/VehiclePhysicsComponent.cpp index 79b6cc829..0e93782db 100644 --- a/dGame/dComponents/VehiclePhysicsComponent.cpp +++ b/dGame/dComponents/VehiclePhysicsComponent.cpp @@ -1,9 +1,7 @@ #include "VehiclePhysicsComponent.h" #include "EntityManager.h" -VehiclePhysicsComponent::VehiclePhysicsComponent(Entity* parent) : Component(parent) { - m_Position = NiPoint3::ZERO; - m_Rotation = NiQuaternion::IDENTITY; +VehiclePhysicsComponent::VehiclePhysicsComponent(Entity* parent) : PhysicsComponent(parent) { m_Velocity = NiPoint3::ZERO; m_AngularVelocity = NiPoint3::ZERO; m_IsOnGround = true; @@ -14,22 +12,6 @@ VehiclePhysicsComponent::VehiclePhysicsComponent(Entity* parent) : Component(par m_EndBehavior = GeneralUtils::GenerateRandomNumber(0, 7); } -VehiclePhysicsComponent::~VehiclePhysicsComponent() { - -} - -void VehiclePhysicsComponent::SetPosition(const NiPoint3& pos) { - if (pos == m_Position) return; - m_DirtyPosition = true; - m_Position = pos; -} - -void VehiclePhysicsComponent::SetRotation(const NiQuaternion& rot) { - if (rot == m_Rotation) return; - m_DirtyPosition = true; - m_Rotation = rot; -} - void VehiclePhysicsComponent::SetVelocity(const NiPoint3& vel) { if (vel == m_Velocity) return; m_DirtyPosition = true; @@ -60,10 +42,6 @@ void VehiclePhysicsComponent::SetRemoteInputInfo(const RemoteInputInfo& remoteIn m_DirtyRemoteInput = true; } -void VehiclePhysicsComponent::SetDirtyPosition(bool val) { - m_DirtyPosition = val; -} - void VehiclePhysicsComponent::SetDirtyVelocity(bool val) { m_DirtyVelocity = val; } diff --git a/dGame/dComponents/VehiclePhysicsComponent.h b/dGame/dComponents/VehiclePhysicsComponent.h index 602fc1358..86d9af43d 100644 --- a/dGame/dComponents/VehiclePhysicsComponent.h +++ b/dGame/dComponents/VehiclePhysicsComponent.h @@ -2,7 +2,7 @@ #include "BitStream.h" #include "Entity.h" -#include "Component.h" +#include "PhysicsComponent.h" #include "eReplicaComponentType.h" struct RemoteInputInfo { @@ -26,41 +26,16 @@ struct RemoteInputInfo { /** * Physics component for vehicles. */ -class VehiclePhysicsComponent : public Component { +class VehiclePhysicsComponent : public PhysicsComponent { public: static const eReplicaComponentType ComponentType = eReplicaComponentType::VEHICLE_PHYSICS; VehiclePhysicsComponent(Entity* parentEntity); - ~VehiclePhysicsComponent() override; void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate) override; void Update(float deltaTime) override; - /** - * Sets the position - * @param pos the new position - */ - void SetPosition(const NiPoint3& pos); - - /** - * Gets the position - * @return the position - */ - const NiPoint3& GetPosition() const { return m_Position; } - - /** - * Sets the rotation - * @param rot the new rotation - */ - void SetRotation(const NiQuaternion& rot); - - /** - * Gets the rotation - * @return the rotation - */ - const NiQuaternion& GetRotation() const { return m_Rotation; } - /** * Sets the velocity * @param vel the new velocity @@ -115,10 +90,6 @@ class VehiclePhysicsComponent : public Component { void SetRemoteInputInfo(const RemoteInputInfo&); private: - bool m_DirtyPosition; - NiPoint3 m_Position; - NiQuaternion m_Rotation; - bool m_DirtyVelocity; NiPoint3 m_Velocity; diff --git a/tests/dGameTests/dComponentsTests/CMakeLists.txt b/tests/dGameTests/dComponentsTests/CMakeLists.txt index 17e69a2f7..e38f7a535 100644 --- a/tests/dGameTests/dComponentsTests/CMakeLists.txt +++ b/tests/dGameTests/dComponentsTests/CMakeLists.txt @@ -1,5 +1,6 @@ set(DCOMPONENTS_TESTS "DestroyableComponentTests.cpp" + "SimplePhysicsComponentTests.cpp" ) # Get the folder name and prepend it to the files above diff --git a/tests/dGameTests/dComponentsTests/SimplePhysicsComponentTests.cpp b/tests/dGameTests/dComponentsTests/SimplePhysicsComponentTests.cpp new file mode 100644 index 000000000..eb906e7f5 --- /dev/null +++ b/tests/dGameTests/dComponentsTests/SimplePhysicsComponentTests.cpp @@ -0,0 +1,154 @@ +#include "GameDependencies.h" +#include + +#include "BitStream.h" +#include "SimplePhysicsComponent.h" +#include "Entity.h" +#include "eReplicaComponentType.h" +#include "eStateChangeType.h" + +class SimplePhysicsTest : public GameDependenciesTest { +protected: + std::unique_ptr baseEntity; + SimplePhysicsComponent* simplePhysicsComponent; + CBITSTREAM; + void SetUp() override { + SetUpDependencies(); + baseEntity = std::make_unique(15, GameDependenciesTest::info); + simplePhysicsComponent = new SimplePhysicsComponent(1, baseEntity.get()); + baseEntity->AddComponent(SimplePhysicsComponent::ComponentType, simplePhysicsComponent); + simplePhysicsComponent->SetClimbableType(eClimbableType::CLIMBABLE_TYPE_WALL); + simplePhysicsComponent->SetPosition(NiPoint3(1.0f, 2.0f, 3.0f)); + simplePhysicsComponent->SetRotation(NiQuaternion(1.0f, 2.0f, 3.0f, 4.0f)); + simplePhysicsComponent->SetVelocity(NiPoint3(5.0f, 6.0f, 7.0f)); + simplePhysicsComponent->SetAngularVelocity(NiPoint3(5.0f, 6.0f, 7.0f)); + simplePhysicsComponent->SetPhysicsMotionState(2); + } + + void TearDown() override { + TearDownDependencies(); + } +}; + +TEST_F(SimplePhysicsTest, SimplePhysicsSerializeTest) { + simplePhysicsComponent->Serialize(&bitStream, false); + constexpr uint32_t sizeOfStream = 3 + BYTES_TO_BITS(3 * sizeof(NiPoint3)) + BYTES_TO_BITS(1 * sizeof(NiQuaternion)) + 1 * BYTES_TO_BITS(sizeof(uint32_t)); + ASSERT_EQ(bitStream.GetNumberOfBitsUsed(), sizeOfStream); + + bool dirtyVelocityFlag; + bitStream.Read(dirtyVelocityFlag); + ASSERT_EQ(dirtyVelocityFlag, true); + + NiPoint3 velocity; + bitStream.Read(velocity.x); + bitStream.Read(velocity.y); + bitStream.Read(velocity.z); + ASSERT_EQ(velocity, NiPoint3(5.0f, 6.0f, 7.0f)); + + NiPoint3 angularVelocity; + bitStream.Read(angularVelocity.x); + bitStream.Read(angularVelocity.y); + bitStream.Read(angularVelocity.z); + ASSERT_EQ(angularVelocity, NiPoint3(5.0f, 6.0f, 7.0f)); + + bool dirtyPhysicsMotionStateFlag; + bitStream.Read(dirtyPhysicsMotionStateFlag); + ASSERT_EQ(dirtyPhysicsMotionStateFlag, true); + + uint32_t physicsMotionState; + bitStream.Read(physicsMotionState); + ASSERT_EQ(physicsMotionState, 2.0f); + + bool dirtyPositionFlag; + bitStream.Read(dirtyPositionFlag); + ASSERT_EQ(dirtyPositionFlag, true); + + NiPoint3 position; + bitStream.Read(position.x); + bitStream.Read(position.y); + bitStream.Read(position.z); + ASSERT_EQ(position, NiPoint3(1.0f, 2.0f, 3.0f)); + + NiQuaternion rotation; + bitStream.Read(rotation.x); + bitStream.Read(rotation.y); + bitStream.Read(rotation.z); + bitStream.Read(rotation.w); + ASSERT_EQ(rotation, NiQuaternion(1.0f, 2.0f, 3.0f, 4.0f)); +} + +TEST_F(SimplePhysicsTest, SimplePhysicsConstructionTest) { + simplePhysicsComponent->Serialize(&bitStream, true); + constexpr uint32_t sizeOfStream = 4 + BYTES_TO_BITS(1 * sizeof(int32_t)) + BYTES_TO_BITS(3 * sizeof(NiPoint3)) + BYTES_TO_BITS(1 * sizeof(NiQuaternion)) + 1 * BYTES_TO_BITS(sizeof(uint32_t)); + ASSERT_EQ(bitStream.GetNumberOfBitsUsed(), sizeOfStream); + + bool dirtyClimbableTypeFlag; + bitStream.Read(dirtyClimbableTypeFlag); + ASSERT_EQ(dirtyClimbableTypeFlag, true); + + int32_t climbableType; + bitStream.Read(climbableType); + ASSERT_EQ(climbableType, 2); + + bool dirtyVelocityFlag; + bitStream.Read(dirtyVelocityFlag); + ASSERT_EQ(dirtyVelocityFlag, true); + + NiPoint3 velocity; + bitStream.Read(velocity.x); + bitStream.Read(velocity.y); + bitStream.Read(velocity.z); + ASSERT_EQ(velocity, NiPoint3(5.0f, 6.0f, 7.0f)); + + NiPoint3 angularVelocity; + bitStream.Read(angularVelocity.x); + bitStream.Read(angularVelocity.y); + bitStream.Read(angularVelocity.z); + ASSERT_EQ(angularVelocity, NiPoint3(5.0f, 6.0f, 7.0f)); + + bool dirtyPhysicsMotionStateFlag; + bitStream.Read(dirtyPhysicsMotionStateFlag); + ASSERT_EQ(dirtyPhysicsMotionStateFlag, true); + + uint32_t physicsMotionState; + bitStream.Read(physicsMotionState); + ASSERT_EQ(physicsMotionState, 2.0f); + + bool dirtyPositionFlag; + bitStream.Read(dirtyPositionFlag); + ASSERT_EQ(dirtyPositionFlag, true); + + NiPoint3 position; + bitStream.Read(position.x); + bitStream.Read(position.y); + bitStream.Read(position.z); + ASSERT_EQ(position, NiPoint3(1.0f, 2.0f, 3.0f)); + + NiQuaternion rotation; + bitStream.Read(rotation.x); + bitStream.Read(rotation.y); + bitStream.Read(rotation.z); + bitStream.Read(rotation.w); + ASSERT_EQ(rotation, NiQuaternion(1.0f, 2.0f, 3.0f, 4.0f)); +} + +TEST_F(SimplePhysicsTest, SimplePhysicsGettersAndSettersTest) { + ASSERT_EQ(simplePhysicsComponent->GetClimabbleType(), eClimbableType::CLIMBABLE_TYPE_WALL); + ASSERT_EQ(simplePhysicsComponent->GetPosition(), NiPoint3(1.0f, 2.0f, 3.0f)); + ASSERT_EQ(simplePhysicsComponent->GetRotation(), NiQuaternion(1.0f, 2.0f, 3.0f, 4.0f)); + ASSERT_EQ(simplePhysicsComponent->GetVelocity(), NiPoint3(5.0f, 6.0f, 7.0f)); + ASSERT_EQ(simplePhysicsComponent->GetAngularVelocity(), NiPoint3(5.0f, 6.0f, 7.0f)); + ASSERT_EQ(simplePhysicsComponent->GetPhysicsMotionState(), 2); + simplePhysicsComponent->SetClimbableType(eClimbableType::CLIMBABLE_TYPE_LADDER); + simplePhysicsComponent->SetPosition(NiPoint3(4.0f, 5.0f, 6.0f)); + simplePhysicsComponent->SetRotation(NiQuaternion(4.0f, 5.0f, 6.0f, 7.0f)); + simplePhysicsComponent->SetVelocity(NiPoint3(6.0f, 7.0f, 8.0f)); + simplePhysicsComponent->SetAngularVelocity(NiPoint3(6.0f, 7.0f, 8.0f)); + simplePhysicsComponent->SetPhysicsMotionState(3); + ASSERT_EQ(simplePhysicsComponent->GetClimabbleType(), eClimbableType::CLIMBABLE_TYPE_LADDER); + ASSERT_EQ(simplePhysicsComponent->GetPosition(), NiPoint3(4.0f, 5.0f, 6.0f)); + ASSERT_EQ(simplePhysicsComponent->GetRotation(), NiQuaternion(4.0f, 5.0f, 6.0f, 7.0f)); + ASSERT_EQ(simplePhysicsComponent->GetVelocity(), NiPoint3(6.0f, 7.0f, 8.0f)); + ASSERT_EQ(simplePhysicsComponent->GetAngularVelocity(), NiPoint3(6.0f, 7.0f, 8.0f)); + ASSERT_EQ(simplePhysicsComponent->GetPhysicsMotionState(), 3); +} From 570c597148545e8d24868424db9b4e3cb29f0870 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Mon, 9 Oct 2023 15:20:56 -0500 Subject: [PATCH 10/11] WIP (#1203) --- dGame/dComponents/RenderComponent.cpp | 26 ++++++++++++++------------ dGame/dComponents/RenderComponent.h | 15 ++++++++------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/dGame/dComponents/RenderComponent.cpp b/dGame/dComponents/RenderComponent.cpp index 44663a17a..6f32dd79f 100644 --- a/dGame/dComponents/RenderComponent.cpp +++ b/dGame/dComponents/RenderComponent.cpp @@ -60,33 +60,35 @@ void RenderComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitial outBitStream->Write(m_Effects.size()); for (Effect* eff : m_Effects) { - // Check that the effect is non-null - assert(eff); + // we still need to write 0 as the size for name if it is a nullptr + if (!eff) { + outBitStream->Write(0); + continue; + } outBitStream->Write(eff->name.size()); - for (const auto& value : eff->name) - outBitStream->Write(value); + // if there is no name, then we don't write anything else + if (eff->name.empty()) continue; + + for (const auto& value : eff->name) outBitStream->Write(value); outBitStream->Write(eff->effectID); outBitStream->Write(eff->type.size()); - for (const auto& value : eff->type) - outBitStream->Write(value); + for (const auto& value : eff->type) outBitStream->Write(value); - outBitStream->Write(eff->scale); + outBitStream->Write(eff->priority); outBitStream->Write(eff->secondary); } } -Effect* RenderComponent::AddEffect(const int32_t effectId, const std::string& name, const std::u16string& type) { +Effect* RenderComponent::AddEffect(const int32_t effectId, const std::string& name, const std::u16string& type, const float priority) { auto* eff = new Effect(); eff->effectID = effectId; - eff->name = name; - eff->type = type; - + eff->priority = priority; m_Effects.push_back(eff); return eff; @@ -143,7 +145,7 @@ void RenderComponent::PlayEffect(const int32_t effectId, const std::u16string& e GameMessages::SendPlayFXEffect(m_Parent, effectId, effectType, name, secondary, priority, scale, serialize); - auto* effect = AddEffect(effectId, name, effectType); + auto* effect = AddEffect(effectId, name, effectType, priority); const auto& pair = m_DurationCache.find(effectId); diff --git a/dGame/dComponents/RenderComponent.h b/dGame/dComponents/RenderComponent.h index 24cfd16d4..54c0da25a 100644 --- a/dGame/dComponents/RenderComponent.h +++ b/dGame/dComponents/RenderComponent.h @@ -17,7 +17,7 @@ class Entity; * here. */ struct Effect { - Effect() { scale = 1.0f; } + Effect() { priority = 1.0f; } /** * The ID of the effect @@ -35,9 +35,9 @@ struct Effect { std::u16string type = u""; /** - * How scaled (enlarged) the effect is + * The importantness of the effect */ - float scale = 1.0f; + float priority = 1.0f; /** * Some related entity that casted the effect @@ -69,9 +69,10 @@ class RenderComponent : public Component { * @param effectId the ID of the effect * @param name the name of the effect * @param type the type of the effect + * @param priority the priority of the effect * @return if successful, the effect that was created */ - Effect* AddEffect(int32_t effectId, const std::string& name, const std::u16string& type); + Effect* AddEffect(int32_t effectId, const std::string& name, const std::u16string& type, const float priority); /** * Removes an effect for this entity @@ -109,15 +110,15 @@ class RenderComponent : public Component { * if it has the animation assigned to its group. If it does, the animation is echo'd * down to all clients to be played and the duration of the played animation is returned. * If the animation did not exist or the function was called in an invalid state, 0 is returned. - * + * * The logic here matches the exact client logic. - * + * * @param self The entity that wants to play an animation * @param animation The animation_type (animationID in the client) to be played. * @param sendAnimation Whether or not to echo the animation down to all clients. * @param priority The priority of the animation. Only used if sendAnimation is true. * @param scale The scale of the animation. Only used if sendAnimation is true. - * + * * @return The duration of the animation that was played. */ static float DoAnimation(Entity* self, const std::string& animation, bool sendAnimation, float priority = 0.0f, float scale = 1.0f); From 3dd279106671d36eebbc7a1ee20d53dcee2e32e0 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 9 Oct 2023 13:22:40 -0700 Subject: [PATCH 11/11] chore: Use TryParse for LDF parsing (#1206) * LDF: Simplify parsing * Update GeneralUtils.h --- dCommon/GeneralUtils.h | 5 ++++ dCommon/LDFFormat.cpp | 44 ++++++++++++---------------- tests/dCommonTests/TestLDFFormat.cpp | 2 +- 3 files changed, 24 insertions(+), 27 deletions(-) diff --git a/dCommon/GeneralUtils.h b/dCommon/GeneralUtils.h index e9e20ba00..ec41c19ba 100644 --- a/dCommon/GeneralUtils.h +++ b/dCommon/GeneralUtils.h @@ -126,6 +126,11 @@ namespace GeneralUtils { template T Parse(const char* value); + template <> + inline bool Parse(const char* value) { + return std::stoi(value); + } + template <> inline int32_t Parse(const char* value) { return std::stoi(value); diff --git a/dCommon/LDFFormat.cpp b/dCommon/LDFFormat.cpp index cb921842b..5278747ca 100644 --- a/dCommon/LDFFormat.cpp +++ b/dCommon/LDFFormat.cpp @@ -61,35 +61,33 @@ LDFBaseData* LDFBaseData::DataFromString(const std::string_view& format) { } case LDF_TYPE_S32: { - try { - int32_t data = static_cast(strtoul(ldfTypeAndValue.second.data(), &storage, 10)); - returnValue = new LDFData(key, data); - } catch (std::exception) { + int32_t data; + if (!GeneralUtils::TryParse(ldfTypeAndValue.second.data(), data)) { Game::logger->Log("LDFFormat", "Warning: Attempted to process invalid int32 value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data()); return nullptr; } + returnValue = new LDFData(key, data); + break; } case LDF_TYPE_FLOAT: { - try { - float data = strtof(ldfTypeAndValue.second.data(), &storage); - returnValue = new LDFData(key, data); - } catch (std::exception) { + float data; + if (!GeneralUtils::TryParse(ldfTypeAndValue.second.data(), data)) { Game::logger->Log("LDFFormat", "Warning: Attempted to process invalid float value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data()); return nullptr; } + returnValue = new LDFData(key, data); break; } case LDF_TYPE_DOUBLE: { - try { - double data = strtod(ldfTypeAndValue.second.data(), &storage); - returnValue = new LDFData(key, data); - } catch (std::exception) { + double data; + if (!GeneralUtils::TryParse(ldfTypeAndValue.second.data(), data)) { Game::logger->Log("LDFFormat", "Warning: Attempted to process invalid double value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data()); return nullptr; } + returnValue = new LDFData(key, data); break; } @@ -102,9 +100,7 @@ LDFBaseData* LDFBaseData::DataFromString(const std::string_view& format) { } else if (ldfTypeAndValue.second == "false") { data = 0; } else { - try { - data = static_cast(strtoul(ldfTypeAndValue.second.data(), &storage, 10)); - } catch (std::exception) { + if (!GeneralUtils::TryParse(ldfTypeAndValue.second.data(), data)) { Game::logger->Log("LDFFormat", "Warning: Attempted to process invalid uint32 value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data()); return nullptr; } @@ -122,9 +118,7 @@ LDFBaseData* LDFBaseData::DataFromString(const std::string_view& format) { } else if (ldfTypeAndValue.second == "false") { data = false; } else { - try { - data = static_cast(strtol(ldfTypeAndValue.second.data(), &storage, 10)); - } catch (std::exception) { + if (!GeneralUtils::TryParse(ldfTypeAndValue.second.data(), data)) { Game::logger->Log("LDFFormat", "Warning: Attempted to process invalid bool value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data()); return nullptr; } @@ -135,24 +129,22 @@ LDFBaseData* LDFBaseData::DataFromString(const std::string_view& format) { } case LDF_TYPE_U64: { - try { - uint64_t data = static_cast(strtoull(ldfTypeAndValue.second.data(), &storage, 10)); - returnValue = new LDFData(key, data); - } catch (std::exception) { + uint64_t data; + if (!GeneralUtils::TryParse(ldfTypeAndValue.second.data(), data)) { Game::logger->Log("LDFFormat", "Warning: Attempted to process invalid uint64 value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data()); return nullptr; } + returnValue = new LDFData(key, data); break; } case LDF_TYPE_OBJID: { - try { - LWOOBJID data = static_cast(strtoll(ldfTypeAndValue.second.data(), &storage, 10)); - returnValue = new LDFData(key, data); - } catch (std::exception) { + LWOOBJID data; + if (!GeneralUtils::TryParse(ldfTypeAndValue.second.data(), data)) { Game::logger->Log("LDFFormat", "Warning: Attempted to process invalid LWOOBJID value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data()); return nullptr; } + returnValue = new LDFData(key, data); break; } diff --git a/tests/dCommonTests/TestLDFFormat.cpp b/tests/dCommonTests/TestLDFFormat.cpp index 36326e38d..9ca847759 100644 --- a/tests/dCommonTests/TestLDFFormat.cpp +++ b/tests/dCommonTests/TestLDFFormat.cpp @@ -17,7 +17,7 @@ class LDFTests : public dCommonDependenciesTest { } }; -#define LdfUniquePtr std::unique_ptr +typedef std::unique_ptr LdfUniquePtr; // Suite of tests for parsing LDF values