From d8ac148cee4a52ecbb1cd08cdf293f152b36781c Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Mon, 9 Oct 2023 15:18:51 -0500 Subject: [PATCH] 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 | 136 +++++++++++---- dGame/dBehaviors/BehaviorContext.h | 7 +- dGame/dBehaviors/BehaviorSlot.h | 4 +- dGame/dBehaviors/TacArcBehavior.cpp | 183 +++++++++------------ dGame/dBehaviors/TacArcBehavior.h | 80 ++++----- 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, 474 insertions(+), 366 deletions(-) diff --git a/dGame/EntityManager.cpp b/dGame/EntityManager.cpp index 699cc2a1..4018aba8 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 693a4cc0..33d7aaff 100644 --- a/dGame/EntityManager.h +++ b/dGame/EntityManager.h @@ -28,6 +28,7 @@ public: 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 e43d542d..aef73223 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 (targetCount > this->m_maxTargets) { + if (this->m_useTargetPosition && branch.target == LWOOBJID_EMPTY) return; + + if (targetCount == 0){ + PlayFx(u"miss", context->originator); return; } - std::vector targets; + 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; + } + 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); + auto* caster = Game::entityManager->GetEntity(context->caster); + if (!caster) return; - 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(); + reference += this->m_offset; - std::vector targets; - - auto* presetTarget = Game::entityManager->GetEntity(branch.target); - - 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; - } - - auto* destroyableComponent = entity->GetComponent(); - - if (destroyableComponent == nullptr) { - continue; - } - - if (destroyableComponent->HasFaction(m_ignoreFaction)) { - continue; - } - - 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); - } - } + 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); + // sort by distance 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); + const auto aDistance = NiPoint3::Distance(a->GetPosition(), reference); + const auto bDistance = NiPoint3::Distance(b->GetPosition(), reference); + return aDistance < bDistance; + } + ); - return aDistance > bDistance; - }); + // resize if we have more than max targets allows + if (targets.size() > this->m_maxTargets) targets.resize(this->m_maxTargets); - const uint32_t size = targets.size(); + bitStream->Write(targets.size()); - bitStream->Write(size); - - if (size == 0) { + 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()); + } - 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); + // then cast all the actions + 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_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) + ); - 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"); + // 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 b5a48ddf..f0fbb18d 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 6fe84a9f..a51ad545 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 91276b80..4357548a 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); - - return targets; + // if we aren't targeting anything, then clear the targets vector + if (!targetSelf && !targetEnemy && !targetFriend && !targetTeam && ignoreFactionList.empty() && includeFactionList.empty()) { + targets.clear(); + return; } - if (!ignoreFaction && !includeFaction) { - for (auto entry : entity->GetTargetsInPhantom()) { - auto* instance = Game::entityManager->GetEntity(entry); - - if (instance == nullptr) { - continue; - } - - targets.push_back(entry); - } + // 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 || (!entity->HasComponent(eReplicaComponentType::PHANTOM_PHYSICS) && targets.empty())) { - DestroyableComponent* destroyableComponent; - if (!entity->TryGetComponent(eReplicaComponentType::DESTROYABLE, destroyableComponent)) { - return targets; + auto index = targets.begin(); + while (index != targets.end()) { + auto candidate = *index; + + // make sure we don't have a nullptr + if (!candidate) { + index = targets.erase(index); + continue; } - auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS); - for (auto* candidate : entities) { - const auto id = candidate->GetObjectID(); + // 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; + } - if ((id != entity->GetObjectID() || targetSelf) && destroyableComponent->CheckValidity(id, ignoreFaction || includeFaction, targetEnemy, targetFriend)) { - targets.push_back(id); + // 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 they are dead, then earse and continue + if (candidateDestroyableComponent->GetIsDead()){ + index = targets.erase(index); + continue; + } + + // if their faction is explicitly included, increment and continue + auto candidateFactions = candidateDestroyableComponent->GetFactionIDs(); + if (CheckFactionList(includeFactionList, candidateFactions)){ + index++; + continue; + } + + // 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; + } } } - } - return targets; + // 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; + + // 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 117f328d..333dc9a8 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 51219b80..5afddea7 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 b9d871f4..7629c377 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); + std::vector targets = {}; - return; + 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; + targets.clear(); - std::vector validTargets; + std::vector validTargets = Game::entityManager->GetEntitiesByProximity(reference, this->m_maxRange); - if (combatAi != nullptr) { - if (combatAi->GetTarget() != LWOOBJID_EMPTY) { - validTargets.push_back(combatAi->GetTarget()); - } - } - - // 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); - - 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 (validTarget->GetIsDead()) continue; - if (entity->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 87a22051..d9345272 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: - bool m_usePickedTarget; - - Behavior* m_action; - - bool m_checkEnv; - - Behavior* m_missAction; - - Behavior* m_blockedAction; - - float m_minDistance; - - float m_maxDistance; - - uint32_t m_maxTargets; - - 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) { - } - + 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; + bool m_useTargetPostion; + bool m_checkEnv; + bool m_useAttackPriority; + Behavior* m_action; + Behavior* m_missAction; + Behavior* m_blockedAction; + 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; }; diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 0adf62f3..35884361 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 ed091066..d9a2191d 100644 --- a/dGame/dComponents/DestroyableComponent.h +++ b/dGame/dComponents/DestroyableComponent.h @@ -371,14 +371,6 @@ public: */ 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 3625defc..512e3c90 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 ffb7a360..ab9de3e6 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -367,6 +367,11 @@ public: */ 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 83f832b2..fa858ab7 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 3a9963b4..93db23c1 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 95fe7472..a27b6278 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 85edfd3b..ea81ff56 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