From bd9b790e1db5dd1e7e5d6127f4f5a970f052176c Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Tue, 26 Mar 2024 19:06:22 -0700 Subject: [PATCH] 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 --- dGame/Entity.cpp | 20 ++-- dGame/dComponents/MovementAIComponent.cpp | 99 +++++++++++++++---- dGame/dComponents/MovementAIComponent.h | 25 ++++- .../dComponents/ProximityMonitorComponent.cpp | 1 + dScripts/02_server/Map/AM/WanderingVendor.cpp | 5 +- .../ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp | 6 +- .../LUPs/RobotCity_Intro/WblRobotCitizen.cpp | 9 +- dZoneManager/Zone.h | 84 ++++++++-------- 8 files changed, 164 insertions(+), 85 deletions(-) diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 23f65540..90763b4c 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -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(pathName); - // else if we are a movement path - } /*else if (path->pathType == PathType::Movement) { - auto movementAIcomp = GetComponent(); - if (movementAIcomp){ - // TODO: set path in existing movementAIComp + } else if (path->pathType == PathType::Movement) { + auto movementAIcomponent = GetComponent(); + 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(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); diff --git a/dGame/dComponents/MovementAIComponent.cpp b/dGame/dComponents/MovementAIComponent.cpp index 8aefdb5b..358d8d11 100644 --- a/dGame/dComponents/MovementAIComponent.cpp +++ b/dGame/dComponents/MovementAIComponent.cpp @@ -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()) 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 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 path) { +void MovementAIComponent::SetPath(std::vector 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(); + + if (controllablePhysicsComponent != nullptr) { + return controllablePhysicsComponent->GetVelocity(); + } + + auto* simplePhysicsComponent = m_Parent->GetComponent(); + + if (simplePhysicsComponent != nullptr) { + return simplePhysicsComponent->GetVelocity(); + } + + return NiPoint3Constant::ZERO; + +} + void MovementAIComponent::SetVelocity(const NiPoint3& value) { auto* controllablePhysicsComponent = m_Parent->GetComponent(); @@ -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 computedPath; if (dpWorld::IsLoaded()) { computedPath = dpWorld::GetNavMesh()->GetPath(m_Parent->GetPosition(), destination, m_Info.wanderSpeed); diff --git a/dGame/dComponents/MovementAIComponent.h b/dGame/dComponents/MovementAIComponent.h index 852f7001..15b5aaed 100644 --- a/dGame/dComponents/MovementAIComponent.h +++ b/dGame/dComponents/MovementAIComponent.h @@ -14,11 +14,14 @@ #include "Logger.h" #include "Component.h" #include "eReplicaComponentType.h" +#include "Zone.h" #include 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 path); + void SetPath(std::vector 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 m_CurrentPath; + std::stack m_CurrentPath; + + const Path* m_Path = nullptr; + + NiPoint3 m_SourcePosition; + + bool m_Paused; + + NiPoint3 m_SavedVelocity; }; #endif // MOVEMENTAICOMPONENT_H diff --git a/dGame/dComponents/ProximityMonitorComponent.cpp b/dGame/dComponents/ProximityMonitorComponent.cpp index fbac8ddb..9ab3f1db 100644 --- a/dGame/dComponents/ProximityMonitorComponent.cpp +++ b/dGame/dComponents/ProximityMonitorComponent.cpp @@ -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"); diff --git a/dScripts/02_server/Map/AM/WanderingVendor.cpp b/dScripts/02_server/Map/AM/WanderingVendor.cpp index ae49aa94..77259e2f 100644 --- a/dScripts/02_server/Map/AM/WanderingVendor.cpp +++ b/dScripts/02_server/Map/AM/WanderingVendor.cpp @@ -5,7 +5,6 @@ void WanderingVendor::OnStartup(Entity* self) { auto movementAIComponent = self->GetComponent(); 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(); if (!movementAIComponent) return; - // movementAIComponent->Pause(); + movementAIComponent->Pause(); self->CancelTimer("startWalking"); } else if (status == "LEAVE") { auto* proximityMonitorComponent = self->GetComponent(); @@ -28,6 +27,6 @@ void WanderingVendor::OnTimerDone(Entity* self, std::string timerName) { if (timerName == "startWalking") { auto movementAIComponent = self->GetComponent(); if (!movementAIComponent) return; - // movementAIComponent->Resume(); + movementAIComponent->Resume(); } } diff --git a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp index 1952831a..56f5b257 100644 --- a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp +++ b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp @@ -307,11 +307,7 @@ void SGCannon::DoSpawnTimerFunc(Entity* self, const std::string& name) { movementAI->SetCurrentSpeed(toSpawn.initialSpeed); movementAI->SetHaltDistance(0.0f); - std::vector pathWaypoints; - - for (const auto& waypoint : path->pathWaypoints) { - pathWaypoints.push_back(waypoint.position); - } + std::vector pathWaypoints = path->pathWaypoints; if (GeneralUtils::GenerateRandomNumber(0, 1) < 0.5f) { std::reverse(pathWaypoints.begin(), pathWaypoints.end()); diff --git a/dScripts/zone/LUPs/RobotCity_Intro/WblRobotCitizen.cpp b/dScripts/zone/LUPs/RobotCity_Intro/WblRobotCitizen.cpp index 93bf2576..8ad303a6 100644 --- a/dScripts/zone/LUPs/RobotCity_Intro/WblRobotCitizen.cpp +++ b/dScripts/zone/LUPs/RobotCity_Intro/WblRobotCitizen.cpp @@ -5,20 +5,19 @@ void WblRobotCitizen::OnStartup(Entity* self) { auto movementAIComponent = self->GetComponent(); if (!movementAIComponent) return; - // movementAIComponent->Resume(); } void WblRobotCitizen::OnUse(Entity* self, Entity* user) { - // auto movementAIComponent = self->GetComponent(); - // if (!movementAIComponent) movementAIComponent->Pause(); + auto movementAIComponent = self->GetComponent(); + 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(); if (!movementAIComponent) return; - // movementAIComponent->Resume(); + movementAIComponent->Resume(); } diff --git a/dZoneManager/Zone.h b/dZoneManager/Zone.h index c5bac6a6..299d675b 100644 --- a/dZoneManager/Zone.h +++ b/dZoneManager/Zone.h @@ -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 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 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 config; std::vector 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 pathWaypoints; SpawnerPath spawner; MovingPlatformPath movingPlatform;