2021-12-05 17:54:36 +00:00
# include "BaseCombatAIComponent.h"
2024-01-05 12:33:52 +00:00
# include "BitStream.h"
2021-12-05 17:54:36 +00:00
# include "Entity.h"
# include "EntityManager.h"
# include "ControllablePhysicsComponent.h"
# include "MovementAIComponent.h"
# include "dpWorld.h"
# include "GameMessages.h"
# include "dServer.h"
# include "Game.h"
# include "CDClientDatabase.h"
# include "CDClientManager.h"
# include "DestroyableComponent.h"
# include <algorithm>
2024-12-16 05:44:57 +00:00
# include <ranges>
2021-12-05 17:54:36 +00:00
# include <sstream>
# include <vector>
# include "SkillComponent.h"
2023-12-29 04:24:30 +00:00
# include "QuickBuildComponent.h"
2021-12-05 17:54:36 +00:00
# include "DestroyableComponent.h"
2023-03-04 02:45:01 +00:00
# include "Metrics.hpp"
2023-03-17 14:36:21 +00:00
# include "CDComponentsRegistryTable.h"
# include "CDPhysicsComponentTable.h"
2024-01-19 21:12:05 +00:00
# include "dNavMesh.h"
2021-12-05 17:54:36 +00:00
2024-12-16 05:44:57 +00:00
BaseCombatAIComponent : : BaseCombatAIComponent ( Entity * parent , const uint32_t id ) : Component ( parent ) {
2021-12-05 17:54:36 +00:00
m_Target = LWOOBJID_EMPTY ;
2024-05-31 04:53:03 +00:00
m_DirtyStateOrTarget = true ;
m_State = AiState : : spawn ;
2021-12-05 17:54:36 +00:00
m_Timer = 1.0f ;
m_StartPosition = parent - > GetPosition ( ) ;
m_MovementAI = nullptr ;
m_Disabled = false ;
m_SkillEntries = { } ;
m_SoftTimer = 5.0f ;
2024-12-16 05:44:57 +00:00
m_ForcedTetherTime = 0.0f ;
2021-12-05 17:54:36 +00:00
//Grab the aggro information from BaseCombatAI:
2022-01-13 03:48:27 +00:00
auto componentQuery = CDClientDatabase : : CreatePreppedStmt (
" SELECT aggroRadius, tetherSpeed, pursuitSpeed, softTetherRadius, hardTetherRadius FROM BaseCombatAIComponent WHERE id = ?; " ) ;
2023-12-28 04:18:20 +00:00
componentQuery . bind ( 1 , static_cast < int > ( id ) ) ;
2022-07-25 02:26:51 +00:00
2022-01-13 03:48:27 +00:00
auto componentResult = componentQuery . execQuery ( ) ;
2021-12-05 17:54:36 +00:00
if ( ! componentResult . eof ( ) ) {
2024-04-14 04:41:51 +00:00
if ( ! componentResult . fieldIsNull ( " aggroRadius " ) )
m_AggroRadius = componentResult . getFloatField ( " aggroRadius " ) ;
2021-12-05 17:54:36 +00:00
2024-04-14 04:41:51 +00:00
if ( ! componentResult . fieldIsNull ( " tetherSpeed " ) )
m_TetherSpeed = componentResult . getFloatField ( " tetherSpeed " ) ;
2021-12-05 17:54:36 +00:00
2024-04-14 04:41:51 +00:00
if ( ! componentResult . fieldIsNull ( " pursuitSpeed " ) )
m_PursuitSpeed = componentResult . getFloatField ( " pursuitSpeed " ) ;
2021-12-05 17:54:36 +00:00
2024-04-14 04:41:51 +00:00
if ( ! componentResult . fieldIsNull ( " softTetherRadius " ) )
m_SoftTetherRadius = componentResult . getFloatField ( " softTetherRadius " ) ;
2021-12-05 17:54:36 +00:00
2024-04-14 04:41:51 +00:00
if ( ! componentResult . fieldIsNull ( " hardTetherRadius " ) )
m_HardTetherRadius = componentResult . getFloatField ( " hardTetherRadius " ) ;
2021-12-05 17:54:36 +00:00
}
componentResult . finalize ( ) ;
2022-07-20 06:25:50 +00:00
// Get aggro and tether radius from settings and use this if it is present. Only overwrite the
// radii if it is greater than the one in the database.
if ( m_Parent ) {
auto aggroRadius = m_Parent - > GetVar < float > ( u " aggroRadius " ) ;
2022-07-21 03:26:52 +00:00
m_AggroRadius = aggroRadius ! = 0 ? aggroRadius : m_AggroRadius ;
2022-07-20 06:25:50 +00:00
auto tetherRadius = m_Parent - > GetVar < float > ( u " tetherRadius " ) ;
2022-07-21 03:26:52 +00:00
m_HardTetherRadius = tetherRadius ! = 0 ? tetherRadius : m_HardTetherRadius ;
2022-07-20 06:25:50 +00:00
}
2021-12-05 17:54:36 +00:00
/*
* Find skills
*/
2022-01-13 03:48:27 +00:00
auto skillQuery = CDClientDatabase : : CreatePreppedStmt (
" SELECT skillID, cooldown, behaviorID FROM SkillBehavior WHERE skillID IN (SELECT skillID FROM ObjectSkills WHERE objectTemplate = ?); " ) ;
2023-12-28 04:18:20 +00:00
skillQuery . bind ( 1 , static_cast < int > ( parent - > GetLOT ( ) ) ) ;
2022-07-25 02:26:51 +00:00
2022-01-13 03:48:27 +00:00
auto result = skillQuery . execQuery ( ) ;
2021-12-05 17:54:36 +00:00
while ( ! result . eof ( ) ) {
2024-04-14 04:41:51 +00:00
const auto skillId = static_cast < uint32_t > ( result . getIntField ( " skillID " ) ) ;
2021-12-05 17:54:36 +00:00
2024-04-14 04:41:51 +00:00
const auto abilityCooldown = static_cast < float > ( result . getFloatField ( " cooldown " ) ) ;
2021-12-05 17:54:36 +00:00
2024-04-14 04:41:51 +00:00
const auto behaviorId = static_cast < uint32_t > ( result . getIntField ( " behaviorID " ) ) ;
2021-12-05 17:54:36 +00:00
auto * behavior = Behavior : : CreateBehavior ( behaviorId ) ;
std : : stringstream behaviorQuery ;
AiSkillEntry entry = { skillId , 0 , abilityCooldown , behavior } ;
m_SkillEntries . push_back ( entry ) ;
result . nextRow ( ) ;
}
Stun ( 1.0f ) ;
/*
* Add physics
*/
int32_t collisionGroup = ( COLLISION_GROUP_DYNAMIC | COLLISION_GROUP_ENEMY ) ;
2024-02-09 05:40:43 +00:00
CDComponentsRegistryTable * componentRegistryTable = CDClientManager : : GetTable < CDComponentsRegistryTable > ( ) ;
2023-03-04 07:16:37 +00:00
auto componentID = componentRegistryTable - > GetByIDAndType ( parent - > GetLOT ( ) , eReplicaComponentType : : CONTROLLABLE_PHYSICS ) ;
2021-12-05 17:54:36 +00:00
2024-02-09 05:40:43 +00:00
CDPhysicsComponentTable * physicsComponentTable = CDClientManager : : GetTable < CDPhysicsComponentTable > ( ) ;
2021-12-05 17:54:36 +00:00
if ( physicsComponentTable ! = nullptr ) {
auto * info = physicsComponentTable - > GetByID ( componentID ) ;
if ( info ! = nullptr ) {
collisionGroup = info - > bStatic ? COLLISION_GROUP_NEUTRAL : info - > collisionGroup ;
}
}
//Create a phantom physics volume so we can detect when we're aggro'd.
m_dpEntity = new dpEntity ( m_Parent - > GetObjectID ( ) , m_AggroRadius ) ;
m_dpEntityEnemy = new dpEntity ( m_Parent - > GetObjectID ( ) , m_AggroRadius , false ) ;
m_dpEntity - > SetCollisionGroup ( collisionGroup ) ;
m_dpEntityEnemy - > SetCollisionGroup ( collisionGroup ) ;
m_dpEntity - > SetPosition ( m_Parent - > GetPosition ( ) ) ;
m_dpEntityEnemy - > SetPosition ( m_Parent - > GetPosition ( ) ) ;
2024-01-19 21:12:05 +00:00
dpWorld : : AddEntity ( m_dpEntity ) ;
dpWorld : : AddEntity ( m_dpEntityEnemy ) ;
2021-12-05 17:54:36 +00:00
}
BaseCombatAIComponent : : ~ BaseCombatAIComponent ( ) {
if ( m_dpEntity )
2024-01-19 21:12:05 +00:00
dpWorld : : RemoveEntity ( m_dpEntity ) ;
2021-12-14 15:24:48 +00:00
if ( m_dpEntityEnemy )
2024-01-19 21:12:05 +00:00
dpWorld : : RemoveEntity ( m_dpEntityEnemy ) ;
2021-12-05 17:54:36 +00:00
}
void BaseCombatAIComponent : : Update ( const float deltaTime ) {
//First, we need to process physics:
if ( ! m_dpEntity ) return ;
m_dpEntity - > SetPosition ( m_Parent - > GetPosition ( ) ) ; //make sure our position is synced with our dpEntity
m_dpEntityEnemy - > SetPosition ( m_Parent - > GetPosition ( ) ) ;
//Process enter events
2024-04-05 05:52:26 +00:00
for ( const auto id : m_dpEntity - > GetNewObjects ( ) ) {
m_Parent - > OnCollisionPhantom ( id ) ;
2021-12-05 17:54:36 +00:00
}
//Process exit events
2024-04-05 05:52:26 +00:00
for ( const auto id : m_dpEntity - > GetRemovedObjects ( ) ) {
m_Parent - > OnCollisionLeavePhantom ( id ) ;
2021-12-05 17:54:36 +00:00
}
// Check if we should stop the tether effect
if ( m_TetherEffectActive ) {
m_TetherTime - = deltaTime ;
if ( m_Target ! = LWOOBJID_EMPTY | | ( NiPoint3 : : DistanceSquared (
m_StartPosition ,
m_Parent - > GetPosition ( ) ) < 20 * 20 & & m_TetherTime < = 0 )
) {
GameMessages : : SendStopFXEffect ( m_Parent , true , " tether " ) ;
m_TetherEffectActive = false ;
}
2024-12-16 05:44:57 +00:00
m_ForcedTetherTime - = deltaTime ;
if ( m_ForcedTetherTime > = 0 ) return ;
}
for ( auto entry = m_RemovedThreatList . begin ( ) ; entry ! = m_RemovedThreatList . end ( ) ; ) {
entry - > second - = deltaTime ;
if ( entry - > second < = 0.0f ) {
entry = m_RemovedThreatList . erase ( entry ) ;
} else {
+ + entry ;
}
2021-12-05 17:54:36 +00:00
}
if ( m_SoftTimer < = 0.0f ) {
2023-07-15 20:56:33 +00:00
Game : : entityManager - > SerializeEntity ( m_Parent ) ;
2021-12-05 17:54:36 +00:00
m_SoftTimer = 5.0f ;
} else {
m_SoftTimer - = deltaTime ;
}
if ( m_Disabled | | m_Parent - > GetIsDead ( ) )
return ;
2023-01-03 17:21:57 +00:00
bool stunnedThisFrame = m_Stunned ;
2021-12-05 17:54:36 +00:00
CalculateCombat ( deltaTime ) ; // Putting this here for now
2024-01-29 07:53:12 +00:00
if ( m_StartPosition = = NiPoint3Constant : : ZERO ) {
2021-12-05 17:54:36 +00:00
m_StartPosition = m_Parent - > GetPosition ( ) ;
}
m_MovementAI = m_Parent - > GetComponent < MovementAIComponent > ( ) ;
if ( m_MovementAI = = nullptr ) {
return ;
}
2023-01-03 17:21:57 +00:00
if ( stunnedThisFrame ) {
2021-12-05 17:54:36 +00:00
m_MovementAI - > Stop ( ) ;
return ;
}
if ( m_Timer > 0.0f ) {
m_Timer - = deltaTime ;
return ;
}
switch ( m_State ) {
case AiState : : spawn :
Stun ( 2.0f ) ;
2023-01-03 17:22:04 +00:00
SetAiState ( AiState : : idle ) ;
2021-12-05 17:54:36 +00:00
break ;
case AiState : : idle :
Wander ( ) ;
break ;
case AiState : : aggro :
OnAggro ( ) ;
break ;
case AiState : : tether :
OnTether ( ) ;
break ;
default :
break ;
}
}
void BaseCombatAIComponent : : CalculateCombat ( const float deltaTime ) {
2023-03-04 02:45:01 +00:00
bool hasSkillToCast = false ;
for ( auto & entry : m_SkillEntries ) {
if ( entry . cooldown > 0.0f ) {
entry . cooldown - = deltaTime ;
} else {
hasSkillToCast = true ;
}
}
bool hadRemainingDowntime = m_SkillTime > 0.0f ;
if ( m_SkillTime > 0.0f ) m_SkillTime - = deltaTime ;
2023-12-29 04:24:30 +00:00
auto * rebuild = m_Parent - > GetComponent < QuickBuildComponent > ( ) ;
2021-12-05 17:54:36 +00:00
if ( rebuild ! = nullptr ) {
const auto state = rebuild - > GetState ( ) ;
2023-12-29 04:24:30 +00:00
if ( state ! = eQuickBuildState : : COMPLETED ) {
2021-12-05 17:54:36 +00:00
return ;
}
}
auto * skillComponent = m_Parent - > GetComponent < SkillComponent > ( ) ;
if ( skillComponent = = nullptr ) {
return ;
}
skillComponent - > CalculateUpdate ( deltaTime ) ;
2022-07-25 02:26:51 +00:00
2021-12-05 17:54:36 +00:00
if ( m_Disabled ) return ;
2023-01-03 17:21:57 +00:00
if ( m_Stunned ) {
2021-12-05 17:54:36 +00:00
m_StunTime - = deltaTime ;
if ( m_StunTime > 0.0f ) {
return ;
}
2023-01-03 17:21:57 +00:00
m_StunTime = 0.0f ;
2021-12-05 17:54:36 +00:00
m_Stunned = false ;
}
2023-03-04 02:45:01 +00:00
if ( m_Stunned | | hadRemainingDowntime ) return ;
2021-12-05 17:54:36 +00:00
auto newTarget = FindTarget ( ) ;
// Tether - reset enemy
if ( m_Target ! = LWOOBJID_EMPTY & & newTarget = = LWOOBJID_EMPTY ) {
m_OutOfCombat = true ;
m_OutOfCombatTime = 1.0f ;
} else if ( newTarget ! = LWOOBJID_EMPTY ) {
m_OutOfCombat = false ;
m_OutOfCombatTime = 0.0f ;
}
if ( ! m_TetherEffectActive & & m_OutOfCombat & & ( m_OutOfCombatTime - = deltaTime ) < = 0 ) {
2024-12-16 05:44:57 +00:00
TetherLogic ( ) ;
2021-12-05 17:54:36 +00:00
m_OutOfCombat = false ;
m_OutOfCombatTime = 0.0f ;
}
SetTarget ( newTarget ) ;
if ( m_Target ! = LWOOBJID_EMPTY ) {
if ( m_State = = AiState : : idle ) {
m_Timer = 0 ;
}
2023-01-03 17:22:04 +00:00
SetAiState ( AiState : : aggro ) ;
2021-12-05 17:54:36 +00:00
} else {
2023-01-03 17:22:04 +00:00
SetAiState ( AiState : : idle ) ;
2021-12-05 17:54:36 +00:00
}
2023-03-04 02:45:01 +00:00
if ( ! hasSkillToCast ) return ;
2021-12-05 17:54:36 +00:00
if ( m_Target = = LWOOBJID_EMPTY ) {
2023-01-03 17:22:04 +00:00
SetAiState ( AiState : : idle ) ;
2022-07-25 02:26:51 +00:00
2021-12-05 17:54:36 +00:00
return ;
}
auto * target = GetTargetEntity ( ) ;
if ( target ! = nullptr ) {
LookAt ( target - > GetPosition ( ) ) ;
}
2022-07-25 02:26:51 +00:00
2021-12-05 17:54:36 +00:00
for ( auto i = 0 ; i < m_SkillEntries . size ( ) ; + + i ) {
auto entry = m_SkillEntries . at ( i ) ;
if ( entry . cooldown > 0 ) {
continue ;
}
const auto result = skillComponent - > CalculateBehavior ( entry . skillId , entry . behavior - > m_behaviorId , LWOOBJID_EMPTY ) ;
if ( result . success ) {
if ( m_MovementAI ! = nullptr ) {
m_MovementAI - > Stop ( ) ;
}
2023-01-03 17:22:04 +00:00
SetAiState ( AiState : : aggro ) ;
2021-12-05 17:54:36 +00:00
m_Timer = 0 ;
m_SkillTime = result . skillTime ;
entry . cooldown = entry . abilityCooldown + m_SkillTime ;
m_SkillEntries [ i ] = entry ;
break ;
}
}
}
LWOOBJID BaseCombatAIComponent : : FindTarget ( ) {
NiPoint3 reference = m_StartPosition ;
if ( m_MovementAI ) reference = m_MovementAI - > ApproximateLocation ( ) ;
auto * target = GetTargetEntity ( ) ;
2022-07-25 02:26:51 +00:00
2021-12-05 17:54:36 +00:00
if ( target ! = nullptr & & ! m_DirtyThreat ) {
const auto targetPosition = target - > GetPosition ( ) ;
2022-07-25 02:26:51 +00:00
2021-12-05 17:54:36 +00:00
if ( Vector3 : : DistanceSquared ( targetPosition , m_StartPosition ) < m_HardTetherRadius * m_HardTetherRadius ) {
return m_Target ;
}
return LWOOBJID_EMPTY ;
}
auto possibleTargets = GetTargetWithinAggroRange ( ) ;
if ( possibleTargets . empty ( ) & & m_ThreatEntries . empty ( ) ) {
m_DirtyThreat = false ;
return LWOOBJID_EMPTY ;
}
Entity * optimalTarget = nullptr ;
float biggestThreat = 0 ;
for ( const auto & entry : possibleTargets ) {
2023-07-15 20:56:33 +00:00
auto * entity = Game : : entityManager - > GetEntity ( entry ) ;
2021-12-05 17:54:36 +00:00
if ( entity = = nullptr ) {
continue ;
}
const auto targetPosition = entity - > GetPosition ( ) ;
const auto threat = GetThreat ( entry ) ;
const auto maxDistanceSquared = m_HardTetherRadius * m_HardTetherRadius ;
if ( Vector3 : : DistanceSquared ( targetPosition , m_StartPosition ) > maxDistanceSquared ) {
if ( threat > 0 ) {
SetThreat ( entry , 0 ) ;
}
continue ;
}
if ( threat > biggestThreat ) {
biggestThreat = threat ;
optimalTarget = entity ;
continue ;
}
const auto proximityThreat = - ( Vector3 : : DistanceSquared ( targetPosition , reference ) - maxDistanceSquared ) / 100 ; // Proximity threat takes last priority
if ( proximityThreat > biggestThreat ) {
biggestThreat = proximityThreat ;
optimalTarget = entity ;
}
}
if ( ! m_DirtyThreat ) {
if ( optimalTarget = = nullptr ) {
return LWOOBJID_EMPTY ;
} else {
return optimalTarget - > GetObjectID ( ) ;
}
}
std : : vector < LWOOBJID > deadThreats { } ;
2022-07-25 02:26:51 +00:00
2021-12-05 17:54:36 +00:00
for ( const auto & threatTarget : m_ThreatEntries ) {
2023-07-15 20:56:33 +00:00
auto * entity = Game : : entityManager - > GetEntity ( threatTarget . first ) ;
2021-12-05 17:54:36 +00:00
if ( entity = = nullptr ) {
deadThreats . push_back ( threatTarget . first ) ;
continue ;
}
const auto targetPosition = entity - > GetPosition ( ) ;
if ( Vector3 : : DistanceSquared ( targetPosition , m_StartPosition ) > m_HardTetherRadius * m_HardTetherRadius ) {
deadThreats . push_back ( threatTarget . first ) ;
continue ;
}
if ( threatTarget . second > biggestThreat ) {
optimalTarget = entity ;
biggestThreat = threatTarget . second ;
}
}
for ( const auto & deadThreat : deadThreats ) {
SetThreat ( deadThreat , 0 ) ;
}
m_DirtyThreat = false ;
2022-07-25 02:26:51 +00:00
2021-12-05 17:54:36 +00:00
if ( optimalTarget = = nullptr ) {
return LWOOBJID_EMPTY ;
} else {
return optimalTarget - > GetObjectID ( ) ;
}
}
std : : vector < LWOOBJID > BaseCombatAIComponent : : GetTargetWithinAggroRange ( ) const {
std : : vector < LWOOBJID > targets ;
for ( auto id : m_Parent - > GetTargetsInPhantom ( ) ) {
2023-07-15 20:56:33 +00:00
auto * other = Game : : entityManager - > GetEntity ( id ) ;
2021-12-05 17:54:36 +00:00
const auto distance = Vector3 : : DistanceSquared ( m_Parent - > GetPosition ( ) , other - > GetPosition ( ) ) ;
2024-12-16 05:44:57 +00:00
if ( distance > m_AggroRadius * m_AggroRadius | | m_RemovedThreatList . contains ( id ) ) continue ;
2021-12-05 17:54:36 +00:00
targets . push_back ( id ) ;
}
return targets ;
}
bool BaseCombatAIComponent : : IsMech ( ) {
switch ( m_Parent - > GetLOT ( ) ) {
case 6253 :
return true ;
default :
return false ;
}
return false ;
}
2024-02-27 07:25:44 +00:00
void BaseCombatAIComponent : : Serialize ( RakNet : : BitStream & outBitStream , bool bIsInitialUpdate ) {
outBitStream . Write ( m_DirtyStateOrTarget | | bIsInitialUpdate ) ;
2023-01-03 17:22:04 +00:00
if ( m_DirtyStateOrTarget | | bIsInitialUpdate ) {
2024-02-27 07:25:44 +00:00
outBitStream . Write ( m_State ) ;
outBitStream . Write ( m_Target ) ;
2023-01-03 17:22:04 +00:00
m_DirtyStateOrTarget = false ;
}
2021-12-05 17:54:36 +00:00
}
2023-01-03 17:22:04 +00:00
void BaseCombatAIComponent : : SetAiState ( AiState newState ) {
if ( newState = = this - > m_State ) return ;
this - > m_State = newState ;
m_DirtyStateOrTarget = true ;
2023-07-15 20:56:33 +00:00
Game : : entityManager - > SerializeEntity ( m_Parent ) ;
2023-01-03 17:22:04 +00:00
}
2021-12-05 17:54:36 +00:00
bool BaseCombatAIComponent : : IsEnemy ( LWOOBJID target ) const {
2023-07-15 20:56:33 +00:00
auto * entity = Game : : entityManager - > GetEntity ( target ) ;
2021-12-05 17:54:36 +00:00
if ( entity = = nullptr ) {
2023-10-21 23:31:55 +00:00
LOG ( " Invalid entity for checking validity (%llu)! " , target ) ;
2021-12-05 17:54:36 +00:00
return false ;
}
auto * destroyable = entity - > GetComponent < DestroyableComponent > ( ) ;
if ( destroyable = = nullptr ) {
return false ;
}
auto * referenceDestroyable = m_Parent - > GetComponent < DestroyableComponent > ( ) ;
if ( referenceDestroyable = = nullptr ) {
2023-10-21 23:31:55 +00:00
LOG ( " Invalid reference destroyable component on (%llu)! " , m_Parent - > GetObjectID ( ) ) ;
2021-12-05 17:54:36 +00:00
return false ;
}
2023-12-29 04:24:30 +00:00
auto * quickbuild = entity - > GetComponent < QuickBuildComponent > ( ) ;
2021-12-05 17:54:36 +00:00
if ( quickbuild ! = nullptr ) {
const auto state = quickbuild - > GetState ( ) ;
2022-07-25 02:26:51 +00:00
2023-12-29 04:24:30 +00:00
if ( state ! = eQuickBuildState : : COMPLETED ) {
2021-12-05 17:54:36 +00:00
return false ;
}
}
auto enemyList = referenceDestroyable - > GetEnemyFactionsIDs ( ) ;
auto candidateList = destroyable - > GetFactionIDs ( ) ;
for ( auto value : candidateList ) {
if ( std : : find ( enemyList . begin ( ) , enemyList . end ( ) , value ) ! = enemyList . end ( ) ) {
return true ;
}
}
return false ;
}
void BaseCombatAIComponent : : SetTarget ( const LWOOBJID target ) {
2023-01-03 17:22:04 +00:00
if ( this - > m_Target = = target ) return ;
2021-12-05 17:54:36 +00:00
m_Target = target ;
2023-01-03 17:22:04 +00:00
m_DirtyStateOrTarget = true ;
2023-07-15 20:56:33 +00:00
Game : : entityManager - > SerializeEntity ( m_Parent ) ;
2021-12-05 17:54:36 +00:00
}
Entity * BaseCombatAIComponent : : GetTargetEntity ( ) const {
2023-07-15 20:56:33 +00:00
return Game : : entityManager - > GetEntity ( m_Target ) ;
2021-12-05 17:54:36 +00:00
}
void BaseCombatAIComponent : : Taunt ( LWOOBJID offender , float threat ) {
// Can't taunt self
if ( offender = = m_Parent - > GetObjectID ( ) )
return ;
m_ThreatEntries [ offender ] + = threat ;
m_DirtyThreat = true ;
}
float BaseCombatAIComponent : : GetThreat ( LWOOBJID offender ) {
const auto pair = m_ThreatEntries . find ( offender ) ;
if ( pair = = m_ThreatEntries . end ( ) ) return 0 ;
return pair - > second ;
}
void BaseCombatAIComponent : : SetThreat ( LWOOBJID offender , float threat ) {
if ( threat = = 0 ) {
m_ThreatEntries . erase ( offender ) ;
} else {
m_ThreatEntries [ offender ] = threat ;
}
m_DirtyThreat = true ;
}
const NiPoint3 & BaseCombatAIComponent : : GetStartPosition ( ) const {
return m_StartPosition ;
}
2022-07-25 02:26:51 +00:00
void BaseCombatAIComponent : : ClearThreat ( ) {
2021-12-05 17:54:36 +00:00
m_ThreatEntries . clear ( ) ;
2024-12-16 05:44:57 +00:00
m_Target = LWOOBJID_EMPTY ;
2021-12-05 17:54:36 +00:00
m_DirtyThreat = true ;
}
void BaseCombatAIComponent : : Wander ( ) {
if ( ! m_MovementAI - > AtFinalWaypoint ( ) ) {
return ;
}
m_MovementAI - > SetHaltDistance ( 0 ) ;
2022-07-25 02:26:51 +00:00
2021-12-05 17:54:36 +00:00
const auto & info = m_MovementAI - > GetInfo ( ) ;
const auto div = static_cast < int > ( info . wanderDelayMax ) ;
m_Timer = ( div = = 0 ? 0 : GeneralUtils : : GenerateRandomNumber < int > ( 0 , div ) ) + info . wanderDelayMin ; //set a random timer to stay put.
2022-07-25 02:26:51 +00:00
2021-12-05 17:54:36 +00:00
const float radius = info . wanderRadius * sqrt ( static_cast < double > ( GeneralUtils : : GenerateRandomNumber < float > ( 0 , 1 ) ) ) ; //our wander radius + a bit of random range
const float theta = ( ( static_cast < double > ( GeneralUtils : : GenerateRandomNumber < float > ( 0 , 1 ) ) * 2 * PI ) ) ;
const NiPoint3 delta =
{
radius * cos ( theta ) ,
0 ,
radius * sin ( theta )
} ;
auto destination = m_StartPosition + delta ;
2024-01-19 21:12:05 +00:00
if ( dpWorld : : IsLoaded ( ) ) {
destination . y = dpWorld : : GetNavMesh ( ) - > GetHeightAtPoint ( destination ) ;
2021-12-05 17:54:36 +00:00
}
2023-08-04 02:38:04 +00:00
if ( Vector3 : : DistanceSquared ( destination , m_MovementAI - > GetParent ( ) - > GetPosition ( ) ) < 2 * 2 ) {
2021-12-05 17:54:36 +00:00
m_MovementAI - > Stop ( ) ;
return ;
}
2023-08-04 02:38:04 +00:00
m_MovementAI - > SetMaxSpeed ( m_TetherSpeed ) ;
2021-12-05 17:54:36 +00:00
m_MovementAI - > SetDestination ( destination ) ;
2023-08-04 02:38:04 +00:00
m_Timer + = ( m_MovementAI - > GetParent ( ) - > GetPosition ( ) . x - destination . x ) / m_TetherSpeed ;
2021-12-05 17:54:36 +00:00
}
void BaseCombatAIComponent : : OnAggro ( ) {
if ( m_Target = = LWOOBJID_EMPTY ) return ;
auto * target = GetTargetEntity ( ) ;
if ( target = = nullptr ) {
return ;
}
m_MovementAI - > SetHaltDistance ( m_AttackRadius ) ;
2022-07-25 02:26:51 +00:00
2021-12-05 17:54:36 +00:00
NiPoint3 targetPos = target - > GetPosition ( ) ;
2023-08-04 02:38:04 +00:00
NiPoint3 currentPos = m_MovementAI - > GetParent ( ) - > GetPosition ( ) ;
2021-12-05 17:54:36 +00:00
// If the player's position is within range, attack
if ( Vector3 : : DistanceSquared ( currentPos , targetPos ) < = m_AttackRadius * m_AttackRadius ) {
m_MovementAI - > Stop ( ) ;
} else if ( Vector3 : : DistanceSquared ( m_StartPosition , targetPos ) > m_HardTetherRadius * m_HardTetherRadius ) //Return to spawn if we're too far
{
2023-08-04 02:38:04 +00:00
m_MovementAI - > SetMaxSpeed ( m_PursuitSpeed ) ;
2022-07-25 02:26:51 +00:00
2021-12-05 17:54:36 +00:00
m_MovementAI - > SetDestination ( m_StartPosition ) ;
} else //Chase the player's new position
{
if ( IsMech ( ) & & Vector3 : : DistanceSquared ( targetPos , currentPos ) > m_AttackRadius * m_AttackRadius * 3 * 3 ) return ;
2023-08-04 02:38:04 +00:00
m_MovementAI - > SetMaxSpeed ( m_PursuitSpeed ) ;
2021-12-05 17:54:36 +00:00
m_MovementAI - > SetDestination ( targetPos ) ;
2023-01-03 17:22:04 +00:00
SetAiState ( AiState : : tether ) ;
2021-12-05 17:54:36 +00:00
}
m_Timer + = 0.5f ;
}
void BaseCombatAIComponent : : OnTether ( ) {
auto * target = GetTargetEntity ( ) ;
if ( target = = nullptr ) {
return ;
}
m_MovementAI - > SetHaltDistance ( m_AttackRadius ) ;
NiPoint3 targetPos = target - > GetPosition ( ) ;
NiPoint3 currentPos = m_MovementAI - > ApproximateLocation ( ) ;
if ( Vector3 : : DistanceSquared ( currentPos , targetPos ) < = m_AttackRadius * m_AttackRadius ) {
m_MovementAI - > Stop ( ) ;
} else if ( Vector3 : : DistanceSquared ( m_StartPosition , targetPos ) > m_HardTetherRadius * m_HardTetherRadius ) //Return to spawn if we're too far
{
2023-08-04 02:38:04 +00:00
m_MovementAI - > SetMaxSpeed ( m_PursuitSpeed ) ;
2021-12-05 17:54:36 +00:00
m_MovementAI - > SetDestination ( m_StartPosition ) ;
2022-07-25 02:26:51 +00:00
2023-01-03 17:22:04 +00:00
SetAiState ( AiState : : aggro ) ;
2021-12-05 17:54:36 +00:00
} else {
if ( IsMech ( ) & & Vector3 : : DistanceSquared ( targetPos , currentPos ) > m_AttackRadius * m_AttackRadius * 3 * 3 ) return ;
2023-08-04 02:38:04 +00:00
m_MovementAI - > SetMaxSpeed ( m_PursuitSpeed ) ;
2021-12-05 17:54:36 +00:00
m_MovementAI - > SetDestination ( targetPos ) ;
}
m_Timer + = 0.5f ;
}
bool BaseCombatAIComponent : : GetStunned ( ) const {
return m_Stunned ;
}
void BaseCombatAIComponent : : SetStunned ( const bool value ) {
m_Stunned = value ;
}
bool BaseCombatAIComponent : : GetStunImmune ( ) const {
return m_StunImmune ;
}
2022-07-25 02:26:51 +00:00
void BaseCombatAIComponent : : SetStunImmune ( bool value ) {
2021-12-05 17:54:36 +00:00
m_StunImmune = value ;
}
float BaseCombatAIComponent : : GetTetherSpeed ( ) const {
return m_TetherSpeed ;
}
2022-07-25 02:26:51 +00:00
void BaseCombatAIComponent : : SetTetherSpeed ( float value ) {
2021-12-05 17:54:36 +00:00
m_TetherSpeed = value ;
}
void BaseCombatAIComponent : : Stun ( const float time ) {
if ( m_StunImmune | | m_StunTime > time ) {
return ;
}
m_StunTime = time ;
2022-07-25 02:26:51 +00:00
2021-12-05 17:54:36 +00:00
m_Stunned = true ;
}
float BaseCombatAIComponent : : GetAggroRadius ( ) const {
return m_AggroRadius ;
}
void BaseCombatAIComponent : : SetAggroRadius ( const float value ) {
m_AggroRadius = value ;
}
void BaseCombatAIComponent : : LookAt ( const NiPoint3 & point ) {
if ( m_Stunned ) {
return ;
}
m_Parent - > SetRotation ( NiQuaternion : : LookAt ( m_Parent - > GetPosition ( ) , point ) ) ;
}
void BaseCombatAIComponent : : SetDisabled ( bool value ) {
m_Disabled = value ;
}
bool BaseCombatAIComponent : : GetDistabled ( ) const {
return m_Disabled ;
}
2022-07-25 02:26:51 +00:00
void BaseCombatAIComponent : : Sleep ( ) {
2021-12-05 17:54:36 +00:00
m_dpEntity - > SetSleeping ( true ) ;
m_dpEntityEnemy - > SetSleeping ( true ) ;
}
2022-07-25 02:26:51 +00:00
void BaseCombatAIComponent : : Wake ( ) {
2021-12-05 17:54:36 +00:00
m_dpEntity - > SetSleeping ( false ) ;
m_dpEntityEnemy - > SetSleeping ( false ) ;
}
2024-12-16 05:44:57 +00:00
void BaseCombatAIComponent : : TetherLogic ( ) {
auto * destroyableComponent = m_Parent - > GetComponent < DestroyableComponent > ( ) ;
if ( destroyableComponent ! = nullptr & & destroyableComponent - > HasFaction ( 4 ) ) {
auto serilizationRequired = false ;
if ( destroyableComponent - > GetHealth ( ) ! = destroyableComponent - > GetMaxHealth ( ) ) {
destroyableComponent - > SetHealth ( destroyableComponent - > GetMaxHealth ( ) ) ;
serilizationRequired = true ;
}
if ( destroyableComponent - > GetArmor ( ) ! = destroyableComponent - > GetMaxArmor ( ) ) {
destroyableComponent - > SetArmor ( destroyableComponent - > GetMaxArmor ( ) ) ;
serilizationRequired = true ;
}
if ( serilizationRequired ) {
Game : : entityManager - > SerializeEntity ( m_Parent ) ;
}
GameMessages : : SendPlayFXEffect ( m_Parent - > GetObjectID ( ) , 6270 , u " tether " , " tether " ) ;
m_TetherEffectActive = true ;
m_TetherTime = 3.0f ;
}
// Speed towards start position
if ( m_MovementAI ! = nullptr ) {
m_MovementAI - > SetHaltDistance ( 0 ) ;
m_MovementAI - > SetMaxSpeed ( m_PursuitSpeed ) ;
m_MovementAI - > SetDestination ( m_StartPosition ) ;
}
}
void BaseCombatAIComponent : : ForceTether ( ) {
SetTarget ( LWOOBJID_EMPTY ) ;
m_ThreatEntries . clear ( ) ;
TetherLogic ( ) ;
m_ForcedTetherTime = m_TetherTime ;
SetAiState ( AiState : : aggro ) ;
}
void BaseCombatAIComponent : : IgnoreThreat ( const LWOOBJID threat , const float value ) {
m_RemovedThreatList [ threat ] = value ;
SetThreat ( threat , 0.0f ) ;
m_Target = LWOOBJID_EMPTY ;
}