Files
DarkflameServer/dGame/dComponents/MovementAIComponent.h
Aaron Kimbrell 8a238ab1d0 fix(shooting gallery): a target at the end of its path respawns and despawns, without a score
SG_TARGET.lua, the targets' server script: at the last waypoint of its
path (onArrived with isLastPoint, which the client's PushToLua
0x00b1a910 sets for a moving platform's Arrived as for a waypoint
follower's) a target notifies the cannon, FinishedPath, and sg_cannon.lua
spawns the next one as after a hit (checkSpawn). A target set to despawn
there then dies silently after 1 s, a moving platform after 2 s.

Here ships and subs sat at the end of their path until the game ended,
and nothing came after them. The script cannot load (the targets have a
script row of their own), so the cannon does it:
- the respawn is split out of RegisterHit (CheckSpawn), and the die
  callback only registers a hit when the death is not the silent one;
- a moving platform target tells through PlatformAtLastWaypoint, a
  waypoint follower through a new MovementAIComponent waypoint callback.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:50:32 -06:00

411 lines
11 KiB
C++

/*
* Darkflame Universe
* Copyright 2023
*/
#ifndef MOVEMENTAICOMPONENT_H
#define MOVEMENTAICOMPONENT_H
#include "BitStream.h"
#include "Entity.h"
#include "GameMessages.h"
#include "EntityManager.h"
#include "Game.h"
#include "Logger.h"
#include "Component.h"
#include "eReplicaComponentType.h"
#include "Zone.h"
#include "dpKnockback.h"
#include <functional>
#include <optional>
#include <vector>
struct ObjectLoadState;
class ControllablePhysicsComponent;
class BaseCombatAIComponent;
struct Path;
/**
* Information that describes the different variables used to make an entity move around
*/
struct MovementAIInfo {
std::string movementType;
/**
* The radius that the entity can wander in
*/
float wanderRadius{};
/**
* The speed at which the entity wanders
*/
float wanderSpeed{};
/**
* The percent chance to move each time the wander timer runs out
*/
float wanderChance{};
/**
* The min amount of delay before wandering
*/
float wanderDelayMin{};
/**
* The max amount of delay before wandering
*/
float wanderDelayMax{};
};
/**
* Component that handles the movement settings of an entity. Not to be confused with the BaseCombatAI component that
* actually handles attackig and following enemy entities.
*/
class MovementAIComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::MOVEMENT_AI;
// The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order)
static void Allocator(Entity& entity, ObjectLoadState& state);
MovementAIComponent(Entity* parentEntity, const int32_t componentID, MovementAIInfo info);
void LoadDataFromTemplate() override;
void LoadConfigData() override;
void SetPath(const std::string pathName);
void Run(float deltaTime) override;
/**
* Returns the basic settings that this entity uses to move around
* @return the basic settings that this entity uses to move around
*/
const MovementAIInfo& GetInfo() const;
/**
* Set a destination point for the entity to move towards
* @param value the destination point to move towards
*/
void SetDestination(const NiPoint3 value);
/**
* Returns the current rotation this entity is moving towards
* @return the current rotation this entity is moving towards
*/
NiPoint3 GetDestination() const;
/**
* Sets the max speed at which this entity may run
* @param value the speed value to set
*/
void SetMaxSpeed(float value);
/**
* Sets how fast the entity will accelerate when not running at full speed
* @param value the acceleration to set
*/
void SetAcceleration(float value) { m_Acceleration = value; };
/**
* Returns the current speed at which this entity accelerates when not running at full speed
* @return the current speed at which this entity accelerates when not running at full speed
*/
float GetAcceleration() const { return m_Acceleration; };
/**
* Sets the halting distance (the distance at which we consider the target to be reached)
* @param value the halting distance to set
*/
void SetHaltDistance(float value) { m_HaltDistance = value; }
/**
* Returns the current halting distance (the distance at which we consider the target to be reached)
* @return the current halting distance
*/
float GetHaltDistance() const { return m_HaltDistance; }
/**
* Sets the speed the entity is currently running at
* @param value the speed value to set
*/
void SetCurrentSpeed(float value) { m_CurrentSpeed = value; }
/**
* Returns the speed the entity is currently running at
* @return the speed the entity is currently running at
*/
float GetCurrentSpeed() const { return m_CurrentSpeed; }
/**
* Locks the rotation of this entity in place, depending on the argument
* @param value if true, the entity will be rotationally locked
*/
void SetLockRotation(bool value) { m_LockRotation = value; }
/**
* Returns whether this entity is currently rotationally locked
* @return true if the entity is rotationally locked, false otherwise
*/
bool GetLockRotation() const { return m_LockRotation; };
/**
* 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();
/**
* Returns the waypoint the entity is currently moving towards
* @return the waypoint the entity is currently moving towards
*/
NiPoint3 GetCurrentWaypoint() const;
/**
* Returns the waypoint this entity is supposed to move towards next
* @return the waypoint this entity is supposed to move towards next
*/
NiPoint3 GetNextWaypoint() const { return m_NextWaypoint; }
/**
* Returns the approximate current location of the entity, including y coordinates
* @return the approximate current location of the entity
*/
NiPoint3 ApproximateLocation() const;
/**
* Teleports this entity to a position. If the distance between the provided point and the y it should have
* according to map data, this will not succeed (to avoid teleporting entities into the sky).
* @param point the point to teleport to
* @return true if the warp was successful, false otherwise
*/
bool Warp(const NiPoint3& point);
/**
* Returns if the entity is at its final waypoint
* @return if the entity is at its final waypoint
*/
bool AtFinalWaypoint() const { return m_AtFinalWaypoint; }
/**
* Calls back at each waypoint of the path the entity follows, with the waypoint's number (what the Arrived game
* message tells an object's script)
*/
void AddWaypointReachedCallback(const std::function<void(uint32_t)>& callback) { m_WaypointReachedCallbacks.push_back(callback); }
/**
* Renders the entity stationary
*/
void Stop();
/**
* Stops the current movement and moves the entity to a certain point. Will continue until it's close enough,
* after which its AI is enabled again.
* @param point the point to move towards
*/
void PullToPoint(const NiPoint3& point);
/**
* Sets a path to follow for the AI
* @param path the path to follow
*/
void SetPath(std::vector<PathWaypoint> path);
void Pause();
void Resume();
NiPoint3 GetVelocity() const;
/**
* Returns the base speed from the DB for a given LOT
* @param lot the lot to check for
* @return the base speed of the lot
*/
static float GetBaseSpeed(LOT lot);
/**
* Returns the total distance remaining along the current path, following the
* interpolated waypoints from the entity's current position to the final destination.
* @return the total remaining path distance in world units
*/
float GetRemainingPathDistance() const;
bool IsPaused() const { return m_Paused; }
bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
bool HasPath() const { return m_Path != nullptr; }
void FollowTarget(const LWOOBJID target);
/**
* Knocks this entity back the way the client knocks back the character it controls (the Knockback game
* message): a vector longer than dpKnockback::MIN_LAUNCH_SPEED throws it in an arc under the zone's gravity
* until it lands on the navmesh, a shorter one moves it by the vector. Pathing waits until it lands.
* @param vector the knockback velocity
*/
void Knockback(const NiPoint3& vector);
/**
* Returns whether this entity is flying from a knockback
*/
bool IsKnockedBack() const { return m_Knockback.IsActive(); }
private:
/**
* Moves the entity along its knockback arc, landing it when it reaches the ground
*/
void UpdateKnockback(float deltaTime);
/**
* The height of the ground under a point: the navmesh if the zone has one, else the height the knockback started at
*/
float GetGroundHeight(const NiPoint3& point) const;
/**
* @brief
* Runs the commands on a waypoint if a path exists
*/
void RunWaypointCommands(uint32_t waypointNum);
/**
* Sets the current position of the entity
* @param value the position to set
*/
void SetPosition(const NiPoint3& value);
/**
* Sets the current rotation of the entity
* @param value the rotation to set
*/
void SetRotation(const NiQuaternion& value);
/**
* Sets the current velocity of the entityes
* @param value the velocity to set
*/
void SetVelocity(const NiPoint3& value);
/**
* Base information regarding the movement information for this entity
*/
MovementAIInfo m_Info;
/**
* The point this entity is moving towards
*/
NiPoint3 m_NextWaypoint;
/**
* The max speed this entity may move at
*/
float m_MaxSpeed;
/**
* The time it will take to reach the next waypoint using the current speed
*/
float m_TimeTravelled;
/**
* The path this entity is currently traversing
*/
uint32_t m_PathIndex;
/**
* If the entity has reached it last waypoint
*/
bool m_AtFinalWaypoint;
/**
* The speed the entity is currently moving at
*/
float m_CurrentSpeed;
/**
* The acceleration this entity has when not moving at its top speed yet
*/
float m_Acceleration;
/**
* The distance between the current position and the target waypoint to consider it reached (to not ghost into it).
*/
float m_HaltDistance;
/**
* The total time it will take to reach the waypoint form its starting point
*/
float m_TimeToTravel;
/**
* The base speed this entity has
*/
float m_BaseSpeed;
/**
* If the AI is currently turned of (e.g. when teleporting to some location)
*/
bool m_PullingToPoint;
/**
* A position that the entity is currently moving towards while being interrupted
*/
NiPoint3 m_PullPoint;
/**
* If the entity is currently rotationally locked
*/
bool m_LockRotation;
/**
* Optional direct link to the combat AI component of the parent entity
*/
BaseCombatAIComponent* m_BaseCombatAI = nullptr;
/**
* The path the entity is currently following
*/
std::vector<NiPoint3> m_InterpolatedWaypoints;
/**
* The path from the current position to the destination.
*/
std::stack<PathWaypoint> m_CurrentPath;
const Path* m_Path = nullptr;
NiPoint3 m_SourcePosition;
bool m_Paused;
NiPoint3 m_SavedVelocity;
bool m_IsBounced{};
// The number of waypoints that were on the path in the call to SetPath
uint32_t m_CurrentPathWaypointCount{ 0 };
std::vector<std::function<void(uint32_t)>> m_WaypointReachedCallbacks;
LWOOBJID m_FollowedTarget{ LWOOBJID_EMPTY };
/**
* The knockback this entity is flying through, if any
*/
dpKnockback::Arc m_Knockback;
/**
* The height the knockback started at, the ground used when the zone has no navmesh
*/
float m_KnockbackStartHeight{};
/**
* A destination set while flying, walked to after landing
*/
std::optional<NiPoint3> m_DestinationAfterKnockback;
};
#endif // MOVEMENTAICOMPONENT_H