#ifndef BASECOMBATAICOMPONENT_H #define BASECOMBATAICOMPONENT_H #include "RakNetTypes.h" #include "dCommonVars.h" #include "NiPoint3.h" #include "Behavior.h" #include "dpWorld.h" #include "dpEntity.h" #include "Component.h" #include "eReplicaComponentType.h" #include #include #include class MovementAIComponent; class Entity; /** * The current state of the AI */ enum class AiState : int32_t { Idle = 0, // Doing nothing Aggro, // Waiting for an enemy to cross / running back to spawn Tether, // Chasing an enemy Spawn, // Spawning into the world Dead // Killed }; /** * Represents a skill that can be cast by this enemy, including its cooldowns, which determines how often the skill * may be cast. */ struct AiSkillEntry { AiSkillEntry(uint32_t skillId, float cooldown, float abilityCooldown, Behavior* behavior) { this->skillId = skillId; this->cooldown = cooldown; this->abilityCooldown = abilityCooldown; this->behavior = behavior; } uint32_t skillId; float cooldown; float abilityCooldown; Behavior* behavior; }; /** * Handles the AI of entities, making them wander, tether and attack their enemies */ class BaseCombatAIComponent final : public Component { public: inline static const eReplicaComponentType ComponentType = eReplicaComponentType::BASE_COMBAT_AI; BaseCombatAIComponent(Entity* parentEntity, uint32_t id); ~BaseCombatAIComponent() override; void LoadTemplateData() override; void LoadConfigData() override; void Startup() override; void Update(float deltaTime) override; void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags); /** * Get the current behavioral state of the enemy * @return the current state */ AiState GetState() const { return m_State; } /** * Set the current behavioral state of the enemy * @param state the state to change to */ void SetState(AiState state) { m_State = state; } /** * Checks if the target may be an enemy of this entity * @param target the target to check for * @return whether the target is a valid enemy for this entity or not */ bool IsEnemy(LWOOBJID target) const; /** * Gets the current target ID that this entity will attack * @return the current target ID of this entity */ LWOOBJID GetTarget() const { return m_Target; } /** * Sets the target that this entity will attack * @param target the target to set */ void SetTarget(LWOOBJID target); /** * Gets the current target entity that this entity will attack * @return the current target entity of this entity */ Entity* GetTargetEntity() const; /** * Taunts this entity, making it a higher or lower threat for this entity. Increasing or decreasing the chance to * be attacked. * @param offender the entity that triggered the taunt * @param threat how high to increase the threat for the offender */ void Taunt(LWOOBJID offender, float threat); /** * Gets the current threat level for an offending entity * @param offender the entity to get the threat for * @return the current threat level of the offending entity, 0 if the entity is not a threat */ float GetThreat(LWOOBJID offender); /** * Sets the threat level for an entity * @param offender the entity to set the threat level for * @param threat the threat level to set */ void SetThreat(LWOOBJID offender, float threat); /** * Gets the position where the entity spawned * @return the position where the entity spawned */ const NiPoint3& GetStartPosition() const; /** * Removes all threats for this entities, and thus chances for it attacking other entities */ void ClearThreat(); /** * Makes the entity continue to wander to a random point around it's starting position */ void Wander(); /** * Continues a step in the aggro state, making sure that the entity is around its start position, if an entity * crosses its aggro range this will set the state to tether. */ void OnAggro(); /** * Continues a step in the tether state, making the entity run towards its target, if the target is outside of its * tether range, this will change the state to aggro */ void OnTether(); /** * Gets whether or not the entity is currently stunned * @return whether the entity is currently stunned */ bool GetStunned() const { return m_Stunned; } /** * (un)stuns the entity, determining whether it'll be able to attack other entities * @param value whether the enemy is stunned */ void SetStunned(bool value) { m_Stunned = value; } /** * Gets if this entity may be stunned * @return if this entity may be stunned */ bool GetStunImmune() const { return m_StunImmune; } /** * Set the stun immune value, determining if the entity may be stunned * @param value */ void SetStunImmune(bool value) { m_StunImmune = value; } /** * Gets the current speed at which an entity runs when tethering * @return the current speed at which an entity runs when tethering */ float GetTetherSpeed() const { return m_TetherSpeed; } /** * Sets the speed at which an entity will tether * @param value the new tether speed */ void SetTetherSpeed(float value) { m_TetherSpeed = value; } /** * Stuns the entity for a certain amount of time, will not work if the entity is stun immune * @param time the time to stun the entity, if stunnable */ void Stun(float time); /** * Gets the radius that will cause this entity to get aggro'd, causing a target chase * @return the aggro radius of the entity */ float GetAggroRadius() const { return m_AggroRadius; } /** * Sets the aggro radius, causing the entity to start chasing enemies in this range * @param value the aggro radius to set */ void SetAggroRadius(float value) { m_AggroRadius = value; } /** * Makes the entity look at a certain point in space * @param point the point to look at */ void LookAt(const NiPoint3& point); /** * (dis)ables the AI, causing it to stop/start attacking enemies * @param value */ void SetDisabled(bool value) { m_Disabled = value; } /** * Gets the current state of the AI, whether or not it's looking for enemies to attack * @return */ bool GetDistabled() const { return m_Disabled; } /** * Turns the entity asleep, stopping updates to its physics volumes */ void Sleep(); /** * Wakes the entity, allowing updates to its physics volumes */ void Wake(); private: /** * Returns the current target or the target that currently is the largest threat to this entity * @return the current highest priority enemy of this entity */ LWOOBJID FindTarget(); /** * Handles anything attack related for the game loop, e.g.: finding targets, sticking with targets and attacking * them, depending on cooldowns. * @param deltaTime the time since the last game tick */ void CalculateCombat(float deltaTime); /** * Gets all the targets that are in the aggro collision phantom of this entity * @return the targets within the aggro range of this entity */ std::vector GetTargetWithinAggroRange() const; /** * @brief Sets the AiState and prepares the entity for serialization next frame. * */ void SetAiState(AiState newState); /** * The current state of the AI */ AiState m_State; /** * The target this entity is currently trying to attack */ LWOOBJID m_Target; /** * The aggro physics volumes of this entity */ dpEntity* m_dpEntity; dpEntity* m_dpEntityEnemy; /** * The max radius of this entity to an enemy allowing it to be chased */ float m_HardTetherRadius = 100; /** * A soft radius for the tether, currently unused */ float m_SoftTetherRadius = 25; /** * The speed at which this entity chases enemies */ float m_PursuitSpeed = 2; /** * The radius that can cause enemies to aggro this entity */ float m_AggroRadius = 25; /** * The speed at which an enemy wanders around */ float m_TetherSpeed = 4; /** * How close this entity needs to be to an enemy to allow attacks */ float m_AttackRadius = 5.0f; /** * Timer before we start attacking others */ float m_Timer = 0.0f; /** * Timer to serializing this entity */ float m_SoftTimer = 0.0f; /** * The skills this entity can cast on enemies */ std::vector m_SkillEntries; /** * The current enemies and their respective threats to this entity */ std::map m_ThreatEntries; /** * The component that handles movement AI, also owned by this entity */ MovementAIComponent* m_MovementAI; /** * The position at which this entity spawned */ NiPoint3 m_StartPosition; /** * For how long this entity has been stunned */ float m_StunTime = 0; /** * If this entity is stunned */ bool m_Stunned = false; /** * If this entity is immune to stunds */ bool m_StunImmune = false; /** * How long this entity needs to execute its skill */ float m_SkillTime = 0; /** * If the entity is currently showing the exclamation mark icon above its head */ bool m_TetherEffectActive = false; /** * How long the tether effect will remain active */ float m_TetherTime = 0; /** * How long until we will consider this entity out of combat, resetting its health and armor */ float m_OutOfCombatTime = 0; /** * If the entity is currently out of combat, resetting its health and armor if it just came out of combat */ bool m_OutOfCombat = false; /** * If the AI is currently disabled */ bool m_Disabled = false; /** * If the threat list should be updated */ bool m_DirtyThreat = false; /** * Whether or not the Component has dirty information and should update next frame * */ bool m_DirtyStateOrTarget = false; /** * Whether the current entity is a mech enemy, needed as mechs tether radius works differently * @return whether this entity is a mech */ bool IsMech() const { return m_ParentEntity->GetLOT() == 6253; }; int32_t m_ComponentId; }; #endif // BASECOMBATAICOMPONENT_H