DarkflameServer/dGame/dBehaviors/TacArcBehavior.cpp
David Markowitz a2ca273370
Cleanup behavior bitstream reads (#888)
* Add failArmor server side

Address out of bounds reading in behavior

Address the basicAttackBehavior reading out of bounds memory and reading bits that didnt exist, which occasionally caused crashes and also caused the behavior to do undefined behavior due to the bad reads.

Tested that attacking a wall anywhere with a projectile now does not crash the game.  Tested with logs that the behavior correctly returned when there were no allocated bits or returned when other states were met.

Add back logs and add fail handle

Remove comment block

Revert "Add back logs and add fail handle"

This reverts commit db19be0906fc8bf35bf89037e2bfba39f5ef9c0c.

Split out checks

* Cleanup Behavior streams
2022-12-16 15:23:02 -06:00

267 lines
7.1 KiB
C++

#include "TacArcBehavior.h"
#include "BehaviorBranchContext.h"
#include "Game.h"
#include "dLogger.h"
#include "Entity.h"
#include "BehaviorContext.h"
#include "BaseCombatAIComponent.h"
#include "EntityManager.h"
#include "RebuildComponent.h"
#include "DestroyableComponent.h"
#include <vector>
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;
}
bool hit = false;
if (!bitStream->Read(hit)) {
Game::logger->Log("TacArcBehavior", "Unable to read hit from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
};
if (this->m_checkEnv) {
bool blocked = false;
if (!bitStream->Read(blocked)) {
Game::logger->Log("TacArcBehavior", "Unable to read blocked from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
};
if (blocked) {
this->m_blockedAction->Handle(context, bitStream, branch);
return;
}
}
if (hit) {
uint32_t count = 0;
if (!bitStream->Read(count)) {
Game::logger->Log("TacArcBehavior", "Unable to read count from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
};
if (count > m_maxTargets && m_maxTargets > 0) {
count = m_maxTargets;
}
std::vector<LWOOBJID> targets;
for (auto i = 0u; i < count; ++i) {
LWOOBJID id{};
if (!bitStream->Read(id)) {
Game::logger->Log("TacArcBehavior", "Unable to read id from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
};
targets.push_back(id);
}
for (auto target : targets) {
branch.target = target;
this->m_action->Handle(context, bitStream, branch);
}
} else {
this->m_missAction->Handle(context, bitStream, branch);
}
}
void TacArcBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) {
auto* self = EntityManager::Instance()->GetEntity(context->originator);
if (self == nullptr) {
Game::logger->Log("TacArcBehavior", "Invalid self for (%llu)!", context->originator);
return;
}
const auto* destroyableComponent = self->GetComponent<DestroyableComponent>();
if ((this->m_usePickedTarget || context->clientInitalized) && branch.target > 0) {
const auto* target = EntityManager::Instance()->GetEntity(branch.target);
if (target == nullptr) {
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<BaseCombatAIComponent>();
const auto casterPosition = self->GetPosition();
auto reference = self->GetPosition(); //+ m_offset;
std::vector<Entity*> targets;
std::vector<LWOOBJID> validTargets;
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 = EntityManager::Instance()->GetEntity(contextTarget);
if (m_targetEnemy && destroyableComponent->IsEnemy(targetEntity)
|| m_targetFriend && destroyableComponent->IsFriend(targetEntity)) {
validTargets.push_back(contextTarget);
}
} else {
validTargets.push_back(contextTarget);
}
}
for (auto validTarget : validTargets) {
if (targets.size() >= this->m_maxTargets) {
break;
}
auto* entity = EntityManager::Instance()->GetEntity(validTarget);
if (entity == nullptr) {
Game::logger->Log("TacArcBehavior", "Invalid target (%llu) for (%llu)!", validTarget, context->originator);
continue;
}
if (std::find(targets.begin(), targets.end(), entity) != targets.end()) {
continue;
}
if (entity->GetIsDead()) continue;
const auto otherPosition = entity->GetPosition();
const auto heightDifference = std::abs(otherPosition.y - casterPosition.y);
/*if (otherPosition.y > reference.y && heightDifference > this->m_upperBound || otherPosition.y < reference.y && heightDifference > this->m_lowerBound)
{
continue;
}*/
const auto forward = self->GetRotation().GetForwardVector();
// forward is a normalized vector of where the caster is facing.
// otherPosition is the position of the target.
// reference is the position of the caster.
// If we cast a ray forward from the caster, does it come within m_farWidth of the target?
const auto distance = Vector3::Distance(reference, otherPosition);
if (m_method == 2) {
NiPoint3 rayPoint = casterPosition + forward * distance;
if (m_farWidth > 0 && Vector3::DistanceSquared(rayPoint, otherPosition) > this->m_farWidth * this->m_farWidth) {
continue;
}
}
auto normalized = (reference - otherPosition) / distance;
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);
}
}
std::sort(targets.begin(), targets.end(), [reference](Entity* a, Entity* b) {
const auto aDistance = Vector3::DistanceSquared(reference, a->GetPosition());
const auto bDistance = Vector3::DistanceSquared(reference, b->GetPosition());
return aDistance > bDistance;
});
const auto hit = !targets.empty();
bitStream->Write(hit);
if (this->m_checkEnv) {
const auto blocked = false; // TODO
bitStream->Write(blocked);
}
if (hit) {
if (combatAi != nullptr) {
combatAi->LookAt(targets[0]->GetPosition());
}
context->foundTarget = true; // We want to continue with this behavior
const auto count = static_cast<uint32_t>(targets.size());
bitStream->Write(count);
for (auto* target : targets) {
bitStream->Write(target->GetObjectID());
}
for (auto* target : targets) {
branch.target = target->GetObjectID();
this->m_action->Calculate(context, bitStream, branch);
}
} else {
this->m_missAction->Calculate(context, bitStream, branch);
}
}
void TacArcBehavior::Load() {
this->m_usePickedTarget = GetBoolean("use_picked_target");
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_maxDistance = GetFloat("max range");
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")
};
}