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