feat: Add MovingAI pathing for NPCs without combatAI (#1509)

* remove goto

* Update MovementAIComponent.cpp

* convert to PathWaypoint

Easier for usage with paths

* add path parsing

* ref removal, simplification of work

* it works

* Update MovementAIComponent.cpp

* disable pathing for combat

we just need it for npcs for now, combat ai can be done later

* fixed stuttery enemies

wow

* start at ramped up speed

* add pausing and resuming

* Update MovementAIComponent.cpp

* Update MovementAIComponent.h

* Update CMakeLists.txt
This commit is contained in:
David Markowitz 2024-03-26 19:06:22 -07:00 committed by GitHub
parent 39b81b6263
commit bd9b790e1d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 164 additions and 85 deletions

View File

@ -731,15 +731,21 @@ void Entity::Initialize() {
// if we have a moving platform path, then we need a moving platform component
if (path->pathType == PathType::MovingPlatform) {
AddComponent<MovingPlatformComponent>(pathName);
// else if we are a movement path
} /*else if (path->pathType == PathType::Movement) {
auto movementAIcomp = GetComponent<MovementAIComponent>();
if (movementAIcomp){
// TODO: set path in existing movementAIComp
} else if (path->pathType == PathType::Movement) {
auto movementAIcomponent = GetComponent<MovementAIComponent>();
if (movementAIcomponent && combatAiId == 0) {
movementAIcomponent->SetPath(pathName);
} else {
// TODO: create movementAIcomp and set path
MovementAIInfo moveInfo = MovementAIInfo();
moveInfo.movementType = "";
moveInfo.wanderChance = 0;
moveInfo.wanderRadius = 16;
moveInfo.wanderSpeed = 2.5f;
moveInfo.wanderDelayMax = 5;
moveInfo.wanderDelayMin = 2;
AddComponent<MovementAIComponent>(moveInfo);
}
}*/
}
} else {
// else we still need to setup moving platform if it has a moving platform comp but no path
int32_t movingPlatformComponentId = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MOVING_PLATFORM, -1);

View File

@ -50,9 +50,42 @@ MovementAIComponent::MovementAIComponent(Entity* parent, MovementAIInfo info) :
m_CurrentSpeed = 0;
m_MaxSpeed = 0;
m_LockRotation = false;
m_Path = nullptr;
m_SourcePosition = m_Parent->GetPosition();
m_Paused = false;
m_SavedVelocity = NiPoint3Constant::ZERO;
if (!m_Parent->GetComponent<BaseCombatAIComponent>()) SetPath(m_Parent->GetVarAsString(u"attached_path"));
}
void MovementAIComponent::SetPath(const std::string pathName) {
m_Path = Game::zoneManager->GetZone()->GetPath(pathName);
if (!pathName.empty()) LOG("WARNING: %s path %s", m_Path ? "Found" : "Failed to find", pathName.c_str());
if (!m_Path) return;
SetMaxSpeed(1);
SetCurrentSpeed(m_BaseSpeed);
SetPath(m_Path->pathWaypoints);
}
void MovementAIComponent::Pause() {
m_Paused = true;
SetPosition(ApproximateLocation());
m_SavedVelocity = GetVelocity();
SetVelocity(NiPoint3Constant::ZERO);
Game::entityManager->SerializeEntity(m_Parent);
}
void MovementAIComponent::Resume() {
m_Paused = false;
SetVelocity(m_SavedVelocity);
m_SavedVelocity = NiPoint3Constant::ZERO;
SetRotation(NiQuaternion::LookAt(m_Parent->GetPosition(), m_NextWaypoint));
Game::entityManager->SerializeEntity(m_Parent);
}
void MovementAIComponent::Update(const float deltaTime) {
if (m_Paused) return;
if (m_PullingToPoint) {
const auto source = GetCurrentWaypoint();
@ -81,27 +114,24 @@ void MovementAIComponent::Update(const float deltaTime) {
}
m_TimeTravelled += deltaTime;
SetPosition(ApproximateLocation());
if (m_TimeTravelled < m_TimeToTravel) return;
m_TimeTravelled = 0.0f;
const auto source = GetCurrentWaypoint();
SetPosition(source);
NiPoint3 velocity = NiPoint3Constant::ZERO;
m_SourcePosition = source;
if (m_Acceleration > 0 && m_BaseSpeed > 0 && AdvanceWaypointIndex()) // Do we have another waypoint to seek?
{
m_NextWaypoint = GetCurrentWaypoint();
if (m_NextWaypoint == source) {
m_TimeToTravel = 0.0f;
} else {
if (m_CurrentSpeed < m_MaxSpeed) {
m_CurrentSpeed += m_Acceleration;
}
m_CurrentSpeed = std::min(m_CurrentSpeed, m_MaxSpeed);
m_CurrentSpeed = std::min(m_CurrentSpeed + m_Acceleration, m_MaxSpeed);
const auto speed = m_CurrentSpeed * m_BaseSpeed; // scale speed based on base speed
@ -110,7 +140,7 @@ void MovementAIComponent::Update(const float deltaTime) {
// Normalize the vector
const auto length = delta.Length();
if (length > 0.0f) {
velocity = (delta / length) * speed;
SetVelocity((delta / length) * speed);
}
// Calclute the time it will take to reach the next waypoint with the current speed
@ -122,17 +152,27 @@ void MovementAIComponent::Update(const float deltaTime) {
} else {
// Check if there are more waypoints in the queue, if so set our next destination to the next waypoint
if (m_CurrentPath.empty()) {
Stop();
return;
if (m_Path) {
if (m_Path->pathBehavior == PathBehavior::Loop) {
SetPath(m_Path->pathWaypoints);
} else if (m_Path->pathBehavior == PathBehavior::Bounce) {
std::vector<PathWaypoint> waypoints = m_Path->pathWaypoints;
std::reverse(waypoints.begin(), waypoints.end());
SetPath(waypoints);
} else if (m_Path->pathBehavior == PathBehavior::Once) {
Stop();
return;
}
} else {
Stop();
return;
}
}
SetDestination(m_CurrentPath.top());
SetDestination(m_CurrentPath.top().position);
m_CurrentPath.pop();
}
SetVelocity(velocity);
Game::entityManager->SerializeEntity(m_Parent);
}
@ -155,7 +195,7 @@ NiPoint3 MovementAIComponent::GetCurrentWaypoint() const {
}
NiPoint3 MovementAIComponent::ApproximateLocation() const {
auto source = m_Parent->GetPosition();
auto source = m_SourcePosition;
if (AtFinalWaypoint()) return source;
@ -221,13 +261,13 @@ void MovementAIComponent::PullToPoint(const NiPoint3& point) {
m_PullPoint = point;
}
void MovementAIComponent::SetPath(std::vector<NiPoint3> path) {
void MovementAIComponent::SetPath(std::vector<PathWaypoint> path) {
if (path.empty()) return;
std::for_each(path.rbegin(), path.rend() - 1, [this](const NiPoint3& point) {
std::for_each(path.rbegin(), path.rend() - 1, [this](const PathWaypoint& point) {
this->m_CurrentPath.push(point);
});
SetDestination(path.front());
SetDestination(path.front().position);
}
float MovementAIComponent::GetBaseSpeed(LOT lot) {
@ -272,6 +312,23 @@ void MovementAIComponent::SetRotation(const NiQuaternion& value) {
if (!m_LockRotation) m_Parent->SetRotation(value);
}
NiPoint3 MovementAIComponent::GetVelocity() const {
auto* controllablePhysicsComponent = m_Parent->GetComponent<ControllablePhysicsComponent>();
if (controllablePhysicsComponent != nullptr) {
return controllablePhysicsComponent->GetVelocity();
}
auto* simplePhysicsComponent = m_Parent->GetComponent<SimplePhysicsComponent>();
if (simplePhysicsComponent != nullptr) {
return simplePhysicsComponent->GetVelocity();
}
return NiPoint3Constant::ZERO;
}
void MovementAIComponent::SetVelocity(const NiPoint3& value) {
auto* controllablePhysicsComponent = m_Parent->GetComponent<ControllablePhysicsComponent>();
@ -288,7 +345,7 @@ void MovementAIComponent::SetVelocity(const NiPoint3& value) {
}
}
void MovementAIComponent::SetDestination(const NiPoint3& destination) {
void MovementAIComponent::SetDestination(const NiPoint3 destination) {
if (m_PullingToPoint) return;
const auto location = ApproximateLocation();
@ -297,6 +354,8 @@ void MovementAIComponent::SetDestination(const NiPoint3& destination) {
SetPosition(location);
}
m_SourcePosition = location;
std::vector<NiPoint3> computedPath;
if (dpWorld::IsLoaded()) {
computedPath = dpWorld::GetNavMesh()->GetPath(m_Parent->GetPosition(), destination, m_Info.wanderSpeed);

View File

@ -14,11 +14,14 @@
#include "Logger.h"
#include "Component.h"
#include "eReplicaComponentType.h"
#include "Zone.h"
#include <vector>
class ControllablePhysicsComponent;
class BaseCombatAIComponent;
struct Path;
/**
* Information that describes the different variables used to make an entity move around
*/
@ -61,6 +64,8 @@ public:
MovementAIComponent(Entity* parentEntity, MovementAIInfo info);
void SetPath(const std::string pathName);
void Update(float deltaTime) override;
/**
@ -73,7 +78,7 @@ public:
* Set a destination point for the entity to move towards
* @param value the destination point to move towards
*/
void SetDestination(const NiPoint3& value);
void SetDestination(const NiPoint3 value);
/**
* Returns the current rotation this entity is moving towards
@ -189,7 +194,13 @@ public:
* Sets a path to follow for the AI
* @param path the path to follow
*/
void SetPath(std::vector<NiPoint3> path);
void SetPath(std::vector<PathWaypoint> path);
void Pause();
void Resume();
NiPoint3 GetVelocity() const;
/**
* Returns the base speed from the DB for a given LOT
@ -301,7 +312,15 @@ private:
/**
* The path from the current position to the destination.
*/
std::stack<NiPoint3> m_CurrentPath;
std::stack<PathWaypoint> m_CurrentPath;
const Path* m_Path = nullptr;
NiPoint3 m_SourcePosition;
bool m_Paused;
NiPoint3 m_SavedVelocity;
};
#endif // MOVEMENTAICOMPONENT_H

View File

@ -64,6 +64,7 @@ void ProximityMonitorComponent::Update(float deltaTime) {
for (const auto& prox : m_ProximitiesData) {
if (!prox.second) continue;
prox.second->SetPosition(m_Parent->GetPosition());
//Process enter events
for (auto* en : prox.second->GetNewObjects()) {
m_Parent->OnCollisionProximity(en->GetObjectID(), prox.first, "ENTER");

View File

@ -5,7 +5,6 @@
void WanderingVendor::OnStartup(Entity* self) {
auto movementAIComponent = self->GetComponent<MovementAIComponent>();
if (!movementAIComponent) return;
// movementAIComponent->Resume();
self->SetProximityRadius(10, "playermonitor");
}
@ -13,7 +12,7 @@ void WanderingVendor::OnProximityUpdate(Entity* self, Entity* entering, std::str
if (status == "ENTER" && entering->IsPlayer()) {
auto movementAIComponent = self->GetComponent<MovementAIComponent>();
if (!movementAIComponent) return;
// movementAIComponent->Pause();
movementAIComponent->Pause();
self->CancelTimer("startWalking");
} else if (status == "LEAVE") {
auto* proximityMonitorComponent = self->GetComponent<ProximityMonitorComponent>();
@ -28,6 +27,6 @@ void WanderingVendor::OnTimerDone(Entity* self, std::string timerName) {
if (timerName == "startWalking") {
auto movementAIComponent = self->GetComponent<MovementAIComponent>();
if (!movementAIComponent) return;
// movementAIComponent->Resume();
movementAIComponent->Resume();
}
}

View File

@ -307,11 +307,7 @@ void SGCannon::DoSpawnTimerFunc(Entity* self, const std::string& name) {
movementAI->SetCurrentSpeed(toSpawn.initialSpeed);
movementAI->SetHaltDistance(0.0f);
std::vector<NiPoint3> pathWaypoints;
for (const auto& waypoint : path->pathWaypoints) {
pathWaypoints.push_back(waypoint.position);
}
std::vector<PathWaypoint> pathWaypoints = path->pathWaypoints;
if (GeneralUtils::GenerateRandomNumber<float_t>(0, 1) < 0.5f) {
std::reverse(pathWaypoints.begin(), pathWaypoints.end());

View File

@ -5,20 +5,19 @@
void WblRobotCitizen::OnStartup(Entity* self) {
auto movementAIComponent = self->GetComponent<MovementAIComponent>();
if (!movementAIComponent) return;
// movementAIComponent->Resume();
}
void WblRobotCitizen::OnUse(Entity* self, Entity* user) {
// auto movementAIComponent = self->GetComponent<MovementAIComponent>();
// if (!movementAIComponent) movementAIComponent->Pause();
auto movementAIComponent = self->GetComponent<MovementAIComponent>();
if (movementAIComponent) movementAIComponent->Pause();
auto face = NiQuaternion::LookAt(self->GetPosition(), user->GetPosition());
self->SetRotation(face);
auto timer = RenderComponent::PlayAnimation(self, "wave");
auto timer = RenderComponent::PlayAnimation(self, "wave", 0.4f);
self->AddTimer("animation time", timer);
}
void WblRobotCitizen::OnTimerDone(Entity* self, std::string timerName) {
auto movementAIComponent = self->GetComponent<MovementAIComponent>();
if (!movementAIComponent) return;
// movementAIComponent->Resume();
movementAIComponent->Resume();
}

View File

@ -16,57 +16,57 @@ class Level;
enum class eWaypointCommandType : uint32_t;
struct WaypointCommand {
eWaypointCommandType command;
eWaypointCommandType command{};
std::string data;
};
struct SceneRef {
std::string filename;
uint32_t id;
uint32_t sceneType; //0 = general, 1 = audio?
uint32_t id{};
uint32_t sceneType{}; //0 = general, 1 = audio?
std::string name;
NiPoint3 unknown1;
float unknown2;
uint8_t color_r;
uint8_t color_g;
uint8_t color_b;
float unknown2{};
uint8_t color_r{};
uint8_t color_g{};
uint8_t color_b{};
Level* level;
std::map<uint32_t, LUTriggers::Trigger*> triggers;
};
struct SceneTransitionInfo {
uint64_t sceneID; //id of the scene being transitioned to.
uint64_t sceneID{}; //id of the scene being transitioned to.
NiPoint3 position;
};
struct SceneTransition {
std::string name;
std::vector<SceneTransitionInfo> points;
float width;
float width{};
};
struct MovingPlatformPathWaypoint {
uint8_t lockPlayer;
float wait;
uint8_t lockPlayer{};
float wait{};
std::string departSound;
std::string arriveSound;
};
struct CameraPathWaypoint {
float time;
float fov;
float tension;
float continuity;
float bias;
float time{};
float fov{};
float tension{};
float continuity{};
float bias{};
};
struct RacingPathWaypoint {
uint8_t isResetNode;
uint8_t isNonHorizontalCamera;
float planeWidth;
float planeHeight;
float shortestDistanceToEnd;
uint8_t isResetNode{};
uint8_t isNonHorizontalCamera{};
float planeWidth{};
float planeHeight{};
float shortestDistanceToEnd{};
};
struct PathWaypoint {
@ -75,7 +75,7 @@ struct PathWaypoint {
MovingPlatformPathWaypoint movingPlatform;
CameraPathWaypoint camera;
RacingPathWaypoint racing;
float speed;
float speed{};
std::vector<LDFBaseData*> config;
std::vector<WaypointCommand> commands;
};
@ -137,49 +137,49 @@ enum class PropertyAchievmentRequired : uint32_t {
struct MovingPlatformPath {
std::string platformTravelSound;
uint8_t timeBasedMovement;
uint8_t timeBasedMovement{};
};
struct PropertyPath {
PropertyPathType pathType;
int32_t price;
uint32_t rentalTime;
uint64_t associatedZone;
PropertyPathType pathType{};
int32_t price{};
uint32_t rentalTime{};
uint64_t associatedZone{};
std::string displayName;
std::string displayDesc;
PropertyType type;
uint32_t cloneLimit;
float repMultiplier;
PropertyRentalPeriod rentalPeriod;
PropertyAchievmentRequired achievementRequired;
PropertyType type{};
uint32_t cloneLimit{};
float repMultiplier{};
PropertyRentalPeriod rentalPeriod{};
PropertyAchievmentRequired achievementRequired{};
// Player respawn coordinates in the main zone (not the property zone)
NiPoint3 playerZoneCoords;
float maxBuildHeight;
float maxBuildHeight{};
};
struct CameraPath {
std::string nextPath;
uint8_t rotatePlayer;
uint8_t rotatePlayer{};
};
struct SpawnerPath {
LOT spawnedLOT;
uint32_t respawnTime;
int32_t maxToSpawn;
uint32_t amountMaintained;
LOT spawnedLOT{};
uint32_t respawnTime{};
int32_t maxToSpawn{};
uint32_t amountMaintained{};
LWOOBJID spawnerObjID;
uint8_t spawnerNetActive;
uint8_t spawnerNetActive{};
};
struct Path {
uint32_t pathVersion;
uint32_t pathVersion{};
PathType pathType;
std::string pathName;
uint32_t flags;
uint32_t flags{};
PathBehavior pathBehavior;
uint32_t waypointCount;
uint32_t waypointCount{};
std::vector<PathWaypoint> pathWaypoints;
SpawnerPath spawner;
MovingPlatformPath movingPlatform;