Compare commits

...

15 Commits

Author SHA1 Message Date
David Markowitz
fd6045b5f2 Update MovementAIComponent.cpp 2023-07-27 21:27:02 -07:00
David Markowitz
dc4db901b4 Update MovementAIComponent.cpp 2023-07-27 21:24:15 -07:00
David Markowitz
8388fdf2a7 Merge branch 'main' into npc-pathing 2023-07-27 21:16:13 -07:00
582d5254b8 WIP 2023-05-05 23:51:10 -05:00
29dfe27799 Merge branch 'main' into npc-pathing 2023-05-05 22:58:33 -05:00
36779d207d Merge branch 'main' into npc-pathing 2023-04-10 22:19:41 -05:00
59ddeb095c Merge branch 'main' into npc-pathing 2022-11-05 20:43:35 -05:00
f546f4d18c Merge branch 'main' into npc-pathing 2022-11-05 13:35:49 -05:00
aee8075375 Merge branch 'main' into npc-pathing 2022-11-03 15:39:17 -05:00
b3c4b5a75c Merge branch 'main' into npc-pathing 2022-10-31 10:31:13 -05:00
f6467ad038 whitespace cleanup 2022-10-27 18:02:30 -05:00
e5233d5ce7 WIP refactor into movment AI
So that combat behavior isn't fighting pathing
2022-10-27 17:54:29 -05:00
e70e2025a8 base speed stuff
looping fixes
proper stopping
2022-10-25 09:48:16 -05:00
8ecfdb6256 Fixes 2022-10-24 22:14:42 -05:00
3386bf54a4 WIP 2022-10-24 21:09:23 -05:00
5 changed files with 332 additions and 147 deletions

View File

@@ -9,6 +9,7 @@
#include "dpWorld.h"
#include "EntityManager.h"
#include "SimplePhysicsComponent.h"
#include "dZoneManager.h"
#include "CDClientManager.h"
#include "CDComponentsRegistryTable.h"
@@ -18,11 +19,11 @@ std::map<LOT, float> MovementAIComponent::m_PhysicsSpeedCache = {};
MovementAIComponent::MovementAIComponent(Entity* parent, MovementAIInfo info) : Component(parent) {
m_Info = std::move(info);
m_Done = true;
m_Done = false;
m_BaseCombatAI = nullptr;
m_BaseCombatAI = reinterpret_cast<BaseCombatAIComponent*>(m_Parent->GetComponent(eReplicaComponentType::BASE_COMBAT_AI));
m_BaseCombatAI = m_Parent->GetComponent<BaseCombatAIComponent>();
//Try and fix the insane values:
if (m_Info.wanderRadius > 5.0f) m_Info.wanderRadius = m_Info.wanderRadius * 0.5f;
@@ -36,18 +37,30 @@ MovementAIComponent::MovementAIComponent(Entity* parent, MovementAIInfo info) :
m_Interrupted = false;
m_PullPoint = {};
m_HaltDistance = 0;
m_Timer = 0;
m_CurrentSpeed = 0;
m_Speed = 0;
m_WaypointPathSpeed = 1.0f;
m_TotalTime = 0;
m_Timer = 0;
m_LockRotation = false;
m_WaypointPathIndex = parent->GetVarAs<int>(u"attached_path_start");
m_NavPathIndex = 0;
}
MovementAIComponent::~MovementAIComponent() = default;
void MovementAIComponent::Update(const float deltaTime) {
// pull to point update take priority
if (m_Interrupted) {
const auto source = GetCurrentWaypoint();
const auto source = GetCurrentPosition();
const auto speed = deltaTime * 2.5f;
@@ -66,112 +79,254 @@ void MovementAIComponent::Update(const float deltaTime) {
return;
}
if (AtFinalWaypoint()) // Are we done?
{
// Navmesh pathing logic
if (!m_Queue.empty()) {
PullToPoint(m_Queue.top());
m_Queue.pop();
return;
}
if (m_HaltDistance > 0) {
if (Vector3::DistanceSquared(ApproximateLocation(), GetDestination()) < m_HaltDistance * m_HaltDistance) // Prevent us from hugging the target
{
Stop();
// waypoint pathing logic
if (m_CurrentPath){
if (!m_Done && !m_Waiting){
if (m_CurrentPath->pathWaypoints.size() > m_WaypointPathIndex) {
auto speed = m_BaseSpeed * m_PathSpeed;
auto source = m_Parent->GetPosition();
auto dest = m_CurrentPath->pathWaypoints.at(m_WaypointPathIndex).position;
return;
// TODO: do this better and more sanely
auto hasNavMesh = dpWorld::Instance().IsLoaded();
if (hasNavMesh) dest.y = dpWorld::Instance().GetNavMesh()->GetHeightAtPoint(dest);
if (abs(dest.y - source.y) > 10) dest.y = source.y; // hacky way to not glitch with weird heights without nav meshes
// if we are close enough to the destination waypoint
if (Vector3::DistanceSquared(source, dest) < 2 * 2) {
if (!AdvancePathWaypointIndex()) return;
}
const auto delta = dest - source;
const auto length = sqrtf(delta.x * delta.x + delta.y * delta.y + delta.z * delta.z);
NiPoint3 velocity;
if (length > 0) {
velocity.x = (delta.x / length) * m_BaseSpeed;
velocity.y = (delta.y / length) * m_BaseSpeed;
velocity.z = (delta.z / length) * m_BaseSpeed;
}
NiPoint3 velocity_pos;
if (length > 0) {
velocity_pos.x = (delta.x / length) * speed * deltaTime;
velocity_pos.y = (delta.y / length) * speed * deltaTime;
velocity_pos.z = (delta.z / length) * speed * deltaTime;
}
// Game::logger->Log("ControllablePhysicsComponent", "v.x %f v.y %f v.z %f", velocity.x, velocity.y, velocity.z);
SetRotation(NiQuaternion::LookAt(source, dest));
SetVelocity(velocity);
SetPosition(source + velocity_pos);
Game::entityManager->SerializeEntity(m_Parent);
}
} else if (!m_Done && m_Waiting) { // waiting, meaing we are at a waypoint, and we want to do something
// handle waiting
if (m_WaitingTime > 0) {
m_WaitingTime = m_WaitingTime - deltaTime;
} else if (m_WaitingTime < 0){
m_Waiting = false;
m_WaitingTime = 0;
// end handle waiting
} else if (m_CurrentPath->pathWaypoints.size() > m_WaypointPathIndex) ArrivedAtPathWaypoint();
}
}
if (m_Timer > 0) {
m_Timer -= deltaTime;
if (m_Timer > 0) {
return;
}
m_Timer = 0;
}
const auto source = GetCurrentWaypoint();
SetPosition(source);
NiPoint3 velocity = NiPoint3::ZERO;
if (m_Acceleration > 0 && m_BaseSpeed > 0 && AdvanceWaypointIndex()) // Do we have another waypoint to seek?
{
m_NextWaypoint = GetCurrentWaypoint();
if (m_NextWaypoint == source) {
m_Timer = 0;
goto nextAction;
}
if (m_CurrentSpeed < m_Speed) {
m_CurrentSpeed += m_Acceleration;
}
if (m_CurrentSpeed > m_Speed) {
m_CurrentSpeed = m_Speed;
}
const auto speed = m_CurrentSpeed * m_BaseSpeed;
const auto delta = m_NextWaypoint - source;
// Normalize the vector
const auto length = sqrtf(delta.x * delta.x + delta.y * delta.y + delta.z * delta.z);
if (length > 0) {
velocity.x = (delta.x / length) * speed;
velocity.y = (delta.y / length) * speed;
velocity.z = (delta.z / length) * speed;
}
// Calclute the time it will take to reach the next waypoint with the current speed
m_TotalTime = m_Timer = length / speed;
SetRotation(NiQuaternion::LookAt(source, m_NextWaypoint));
} else {
// Check if there are more waypoints in the queue, if so set our next destination to the next waypoint
if (!m_Queue.empty()) {
SetDestination(m_Queue.top());
m_Queue.pop();
} else {
// We have reached our final waypoint
Stop();
return;
}
}
nextAction:
SetVelocity(velocity);
Game::entityManager->SerializeEntity(m_Parent);
}
// if (m_HaltDistance > 0) {
// if (Vector3::DistanceSquared(ApproximateLocation(), GetDestination()) < m_HaltDistance * m_HaltDistance) // Prevent us from hugging the target
// {
// Stop();
// return;
// }
// }
// if (m_Timer > 0) {
// m_Timer -= deltaTime;
// if (m_Timer > 0) {
// return;
// }
// m_Timer = 0;
// }
// const auto source = GetCurrentWaypoint();
// SetPosition(source);
// NiPoint3 velocity = NiPoint3::ZERO;
// if (AdvanceWaypointIndex()) // Do we have another waypoint to seek?
// {
// m_NextWaypoint = GetCurrentWaypoint();
// if (m_NextWaypoint == source) {
// m_Timer = 0;
// goto nextAction;
// }
// if (m_CurrentSpeed < m_Speed) {
// m_CurrentSpeed += m_Acceleration;
// }
// if (m_CurrentSpeed > m_Speed) {
// m_CurrentSpeed = m_Speed;
// }
// const auto speed = m_CurrentSpeed * m_BaseSpeed;
// const auto delta = m_NextWaypoint - source;
// // Normalize the vector
// const auto length = sqrtf(delta.x * delta.x + delta.y * delta.y + delta.z * delta.z);
// if (length > 0) {
// velocity.x = (delta.x / length) * speed;
// velocity.y = (delta.y / length) * speed;
// velocity.z = (delta.z / length) * speed;
// }
// // Calclute the time it will take to reach the next waypoint with the current speed
// m_TotalTime = m_Timer = length / speed;
// SetRotation(NiQuaternion::LookAt(source, m_NextWaypoint));
// } else {
// // Check if there are more waypoints in the queue, if so set our next destination to the next waypoint
// if (!m_Queue.empty()) {
// SetDestination(m_Queue.top());
// m_Queue.pop();
// } else {
// // We have reached our final waypoint
// Stop();
// return;
// }
// }
// nextAction:
// SetVelocity(velocity);
// EntityManager::Instance()->SerializeEntity(m_Parent);
const MovementAIInfo& MovementAIComponent::GetInfo() const {
return m_Info;
}
bool MovementAIComponent::AdvanceWaypointIndex() {
if (m_PathIndex >= m_CurrentPath.size()) {
return false;
void MovementAIComponent::ArrivedAtPathWaypoint(){
// TODO: Call scripts here
PathWaypoint waypoint = m_CurrentPath->pathWaypoints.at(m_WaypointPathIndex);
if (waypoint.config.size() > 0) {
for (LDFBaseData* action : waypoint.config) {
if (action) {
// delay: has time as float
if (action->GetKey() == u"delay"){
m_WaitingTime += std::stof(action->GetValueAsString());
SetVelocity(NiPoint3::ZERO);
Game::entityManager->SerializeEntity(m_Parent);
// emote: has name of animation to play
} else if (action->GetKey() == u"emote"){
GameMessages::SendPlayAnimation(m_Parent, GeneralUtils::UTF8ToUTF16(action->GetValueAsString()));
// TODO Get proper animation time and add to wait
m_WaitingTime += 1;
SetVelocity(NiPoint3::ZERO);
Game::entityManager->SerializeEntity(m_Parent);
// pathspeed: has pathing speed as a float
} else if (action->GetKey() == u"pathspeed") {
m_PathSpeed = std::stof(action->GetValueAsString());
// changeWP: <path to change to>,<waypoint to use> the command and waypoint are optional
} else if (action->GetKey() == u"changeWP") {
// use an intermediate value since it can be one or two things
auto intermed = action->GetValueAsString();
std::string path_string = "";
// sometimes there's a path and what waypoint to start, which are comma separated
if (intermed.find(",") != std::string::npos){
auto datas = GeneralUtils::SplitString(intermed, ',');
path_string = datas[0];
m_WaypointPathIndex = stoi(datas[1]) - 1; // becuase 0 vs 1 indexed
} else {
path_string = intermed;
m_WaypointPathIndex = 0;
}
if (path_string != "") {
m_CurrentPath = const_cast<Path*>(Game::zoneManager->GetZone()->GetPath(path_string));
} else m_CurrentPath = nullptr;
} else {
// We don't recognize the action, let a dev know
Game::logger->LogDebug("ControllablePhysicsComponent", "Unhandled action %s", GeneralUtils::UTF16ToWTF8(action->GetKey()).c_str());
}
}
}
}
m_PathIndex++;
if (m_WaitingTime == 0) { // if we don't have any time to wait
m_Waiting = false;
}
}
bool MovementAIComponent::AdvancePathWaypointIndex() {
if (m_CurrentPath->pathBehavior == PathBehavior::Loop) {
if (m_CurrentPath->pathWaypoints.size() < m_WaypointPathIndex + 1) {
// If we reach the end, go back to the starting index since the path is a loop
m_WaypointPathIndex = 0;
} else m_WaypointPathIndex++; // Otherwise continue
} else if (m_CurrentPath->pathBehavior == PathBehavior::Bounce){ // Ping Pong
// Are we going in reverse already?
if (m_Reverse){ // Then we're subtracting
if (m_WaypointPathIndex - 1 < 0){ // Stop reversing if we are at the beginning
m_Reverse = false;
m_WaypointPathIndex++;
} else m_WaypointPathIndex--; // Otherwise continue reverseing
} else { // Then we're adding
if (m_CurrentPath->pathWaypoints.size() < m_WaypointPathIndex + 1){ // Start reversing if we are at the end
m_Reverse = true;
m_WaypointPathIndex--;
} else m_WaypointPathIndex++; // Otherwise continue going up
}
} else if (m_CurrentPath->pathBehavior == PathBehavior::Once){
if (m_CurrentPath->pathWaypoints.size() < m_WaypointPathIndex + 1) {
m_CurrentPath = nullptr; // If we reach the end, we don't continue
return false;
} else m_WaypointPathIndex++; // Otherwise continue
}
m_Waiting = true;
return true;
}
bool MovementAIComponent::AdvanceNavWaypointIndex() {
}
NiPoint3 MovementAIComponent::GetCurrentWaypoint() const {
if (m_PathIndex >= m_CurrentPath.size()) {
if (m_PathIndex >= m_CurrentPath->pathWaypoints.size()) {
return GetCurrentPosition();
}
return m_CurrentPath[m_PathIndex];
return m_CurrentPath->pathWaypoints[m_PathIndex].position;
}
NiPoint3 MovementAIComponent::GetNextWaypoint() const {
@@ -182,6 +337,7 @@ NiPoint3 MovementAIComponent::GetCurrentPosition() const {
return m_Parent->GetPosition();
}
// get's the approximate location where the entity should be on the path
NiPoint3 MovementAIComponent::ApproximateLocation() const {
auto source = GetCurrentPosition();
@@ -239,7 +395,8 @@ void MovementAIComponent::Stop() {
return;
}
SetPosition(ApproximateLocation());
// SetPosition(ApproximateLocation());
SetPosition(GetCurrentPosition());
SetVelocity(NiPoint3::ZERO);
@@ -247,10 +404,6 @@ void MovementAIComponent::Stop() {
m_Done = true;
m_CurrentPath = {};
m_PathIndex = 0;
m_CurrentSpeed = 0;
Game::entityManager->SerializeEntity(m_Parent);
@@ -263,18 +416,6 @@ void MovementAIComponent::PullToPoint(const NiPoint3& point) {
m_PullPoint = point;
}
void MovementAIComponent::SetPath(std::vector<NiPoint3> path) {
std::reverse(path.begin(), path.end());
for (const auto& point : path) {
m_Queue.push(point);
}
SetDestination(m_Queue.top());
m_Queue.pop();
}
float MovementAIComponent::GetBaseSpeed(LOT lot) {
// Check if the lot is in the cache
const auto& it = m_PhysicsSpeedCache.find(lot);
@@ -414,9 +555,7 @@ void MovementAIComponent::SetDestination(const NiPoint3& value) {
return;
}
m_CurrentPath.clear();
m_CurrentPath.push_back(location);
m_Queue.push(location);
// Simply path
for (auto point : computedPath) {
@@ -424,10 +563,10 @@ void MovementAIComponent::SetDestination(const NiPoint3& value) {
point.y = dpWorld::Instance().GetNavMesh()->GetHeightAtPoint(point);
}
m_CurrentPath.push_back(point);
m_Queue.push(point);
}
m_CurrentPath.push_back(computedPath[computedPath.size() - 1]);
m_Queue.push(computedPath[computedPath.size() - 1]);
m_PathIndex = 0;
@@ -437,11 +576,11 @@ void MovementAIComponent::SetDestination(const NiPoint3& value) {
}
NiPoint3 MovementAIComponent::GetDestination() const {
if (m_CurrentPath.empty()) {
if (!m_CurrentPath) {
return GetCurrentPosition();
}
return m_CurrentPath[m_CurrentPath.size() - 1];
return m_CurrentPath->pathWaypoints[m_CurrentPath->pathWaypoints.size() - 1].position;
}
void MovementAIComponent::SetSpeed(const float value) {

View File

@@ -18,6 +18,7 @@
class ControllablePhysicsComponent;
class BaseCombatAIComponent;
struct Path;
/**
* Information that describes the different variables used to make an entity move around
@@ -146,7 +147,11 @@ public:
* Attempts to update the waypoint index, making the entity move to the next waypoint
* @return true if the waypoint could be increased, false if the entity is at the last waypoint already
*/
bool AdvanceWaypointIndex();
bool AdvancePathWaypointIndex();
bool AdvanceNavWaypointIndex();
void ArrivedAtPathWaypoint();
/**
* Returns the waypoint the entity is currently moving towards
@@ -208,7 +213,7 @@ public:
* Sets a path to follow for the AI
* @param path the path to follow
*/
void SetPath(std::vector<NiPoint3> path);
void SetPath(Path* path) {if (path) {m_CurrentPath = path; m_Done = false; m_Timer = 0;};};
/**
* Returns the base speed from the DB for a given LOT
@@ -217,6 +222,36 @@ public:
*/
static float GetBaseSpeed(LOT lot);
// /**
// * @brief tell an npc how to use it's given path
// *
// * @param paused if they are not moving
// */
// void FollowWaypoints(bool paused) {m_Paused = paused;};
// /**
// * @brief tell an npc how to use it's given path
// *
// * @param paused if they are not moving
// * @param newPathName the new path to use
// * @param newPathStart the waypoint on the new path to start at
// */
// void FollowWaypoints(bool paused, std::string newPathName, int newPathStart = 0);
// /**
// * @brief tell an npc how to use it's given path
// *
// * @param newPathName the new path to use
// * @param newPathStart the waypoint on the new path to start at
// */
// void FollowWaypoints(std::string newPathName, int newPathStart = 0);
// /**
// * @brief starts pathing
// *
// */
// void FollowWaypoints(){m_Paused = false;};
private:
/**
@@ -315,7 +350,7 @@ private:
/**
* The path the entity is currently following
*/
std::vector<NiPoint3> m_CurrentPath;
Path* m_CurrentPath;
/**
* Queue of positions to traverse
@@ -326,6 +361,32 @@ private:
* Cache of all lots and their respective speeds
*/
static std::map<LOT, float> m_PhysicsSpeedCache;
/**
* If we are waiting for some reason
*/
bool m_Waiting = false;
/**
* If we are waiting on a delay
*/
float m_WaitingTime = 0.0;
/**
* The speed at which they will path
*/
float m_PathSpeed;
/**
* If we are traverseing a waypoint path in reverse
*/
bool m_Reverse = false;
int m_WaypointPathIndex;
float m_WaypointPathSpeed;
int m_NavPathIndex;
};
#endif // MOVEMENTAICOMPONENT_H

View File

@@ -128,9 +128,6 @@ void ActivityManager::ActivityTimerStart(Entity* self, const std::string& timerN
const float_t stopTime) {
auto* timer = new ActivityTimer{ timerName, updateInterval, stopTime };
activeTimers.push_back(timer);
Game::logger->LogDebug("ActivityManager", "Starting timer '%s', %f, %f", timerName.c_str(), updateInterval, stopTime);
self->AddTimer(GetPrefixedName(timer->name), timer->updateInterval);
}
@@ -210,10 +207,8 @@ void ActivityManager::OnTimerDone(Entity* self, std::string timerName) {
activeTimers.erase(std::remove(activeTimers.begin(), activeTimers.end(), timer),
activeTimers.end());
delete timer;
Game::logger->LogDebug("ActivityManager", "Executing timer '%s'", activityTimerName.c_str());
OnActivityTimerDone(self, activityTimerName);
} else {
Game::logger->LogDebug("ActivityManager", "Updating timer '%s'", activityTimerName.c_str());
OnActivityTimerUpdate(self, timer->name, timer->stopTime - timer->runTime, timer->runTime);
self->AddTimer(GetPrefixedName(timer->name), timer->updateInterval);
}

View File

@@ -17,6 +17,7 @@
#include "eReplicaComponentType.h"
#include "RenderComponent.h"
#include "eGameActivity.h"
#include "MovingPlatformComponent.h"
void SGCannon::OnStartup(Entity* self) {
Game::logger->Log("SGCannon", "OnStartup");
@@ -285,36 +286,26 @@ void SGCannon::OnActivityTimerDone(Entity* self, const std::string& name) {
Game::logger->Log("SGCannon", "Spawning enemy %i on path %s", toSpawn.lot, path->pathName.c_str());
auto* enemy = Game::entityManager->CreateEntity(info, nullptr, self);
Game::entityManager->ConstructEntity(enemy);
if (enemy) {
Game::entityManager->ConstructEntity(enemy);
auto* movementAI = enemy->GetComponent<MovementAIComponent>();
if (!movementAI) return;
auto* movementAI = new MovementAIComponent(enemy, {});
movementAI->SetSpeed(toSpawn.initialSpeed);
movementAI->SetCurrentSpeed(toSpawn.initialSpeed);
movementAI->SetHaltDistance(0.0f);
enemy->AddComponent(eReplicaComponentType::MOVEMENT_AI, movementAI);
enemy->AddDieCallback([this, self, enemy, name]() {
RegisterHit(self, enemy, name);
}
);
movementAI->SetSpeed(toSpawn.initialSpeed);
movementAI->SetCurrentSpeed(toSpawn.initialSpeed);
movementAI->SetHaltDistance(0.0f);
std::vector<NiPoint3> pathWaypoints;
for (const auto& waypoint : path->pathWaypoints) {
pathWaypoints.push_back(waypoint.position);
}
if (GeneralUtils::GenerateRandomNumber<float_t>(0, 1) < 0.5f) {
std::reverse(pathWaypoints.begin(), pathWaypoints.end());
}
movementAI->SetPath(pathWaypoints);
enemy->AddDieCallback([this, self, enemy, name]() {
RegisterHit(self, enemy, name);
});
// Save the enemy and tell it to start pathing
if (enemy != nullptr) {
// Save the enemy
const_cast<std::vector<LWOOBJID>&>(self->GetVar<std::vector<LWOOBJID>>(SpawnedObjects)).push_back(enemy->GetObjectID());
GameMessages::SendPlatformResync(enemy, UNASSIGNED_SYSTEM_ADDRESS);
// if we are a moving platform, tell it to move
auto* movingPlatformComponent = enemy->GetComponent<MovingPlatformComponent>();
if (movingPlatformComponent) GameMessages::SendPlatformResync(enemy, UNASSIGNED_SYSTEM_ADDRESS);
}
}
} else if (name == EndGameBufferTimer) {

View File

@@ -533,7 +533,6 @@ void Zone::LoadPath(std::istream& file) {
BinaryIO::BinaryRead(file, character);
value.push_back(character);
}
LDFBaseData* ldfConfig = nullptr;
if (path.pathType == PathType::Movement || path.pathType == PathType::Rail) {
ldfConfig = LDFBaseData::DataFromString(parameter + "=0:" + value);