2016-03-27 11:49:47 +02:00
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
2024-10-08 22:16:57 +02:00
Copyright ( C ) 2024 the OpenMoHAA team
2016-03-27 11:49:47 +02:00
This file is part of OpenMoHAA source code .
OpenMoHAA source code is free software ; you can redistribute it
and / or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation ; either version 2 of the License ,
or ( at your option ) any later version .
OpenMoHAA source code is distributed in the hope that it will be
useful , but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with OpenMoHAA source code ; if not , write to the Free Software
Foundation , Inc . , 51 Franklin St , Fifth Floor , Boston , MA 02110 - 1301 USA
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
// playerbot.cpp: Multiplayer bot system.
# include "g_local.h"
# include "actor.h"
# include "playerbot.h"
2023-01-29 20:59:31 +01:00
# include "consoleevent.h"
2023-04-29 21:56:38 +02:00
# include "debuglines.h"
# include "scriptexception.h"
2024-10-03 22:50:39 +02:00
# include "vehicleturret.h"
# include "weaputils.h"
2024-12-25 15:08:57 +01:00
# include "g_bot.h"
2016-03-27 11:49:47 +02:00
// We assume that we have limited access to the server-side
// and that most logic come from the playerstate_s structure
cvar_t * bot_manualmove ;
2024-10-08 22:16:57 +02:00
CLASS_DECLARATION ( Listener , BotController , NULL ) {
{ NULL , NULL }
} ;
BotController : : botfunc_t BotController : : botfuncs [ MAX_BOT_FUNCTIONS ] ;
BotController : : BotController ( )
{
2023-11-13 20:33:06 +01:00
if ( LoadingSavegame ) {
return ;
}
m_botCmd . serverTime = 0 ;
m_botCmd . msec = 0 ;
m_botCmd . buttons = 0 ;
m_botCmd . angles [ 0 ] = ANGLE2SHORT ( 0 ) ;
m_botCmd . angles [ 1 ] = ANGLE2SHORT ( 0 ) ;
m_botCmd . angles [ 2 ] = ANGLE2SHORT ( 0 ) ;
m_botCmd . forwardmove = 0 ;
m_botCmd . rightmove = 0 ;
m_botCmd . upmove = 0 ;
m_botEyes . angles [ 0 ] = 0 ;
m_botEyes . angles [ 1 ] = 0 ;
m_botEyes . ofs [ 0 ] = 0 ;
m_botEyes . ofs [ 1 ] = 0 ;
2024-10-08 22:16:57 +02:00
m_botEyes . ofs [ 2 ] = DEFAULT_VIEWHEIGHT ;
2023-11-13 20:33:06 +01:00
2024-12-16 23:05:35 +01:00
m_iCuriousTime = 0 ;
m_iAttackTime = 0 ;
m_iConfirmTime = 0 ;
2024-12-16 19:45:42 +01:00
m_iEnemyEyesTag = - 1 ;
2023-11-13 20:33:06 +01:00
2024-10-02 21:53:54 +02:00
m_iNextTauntTime = 0 ;
2023-11-13 20:33:06 +01:00
m_StateFlags = 0 ;
m_RunLabel . TrySetScript ( " global/bot_run.scr " ) ;
2016-03-27 11:49:47 +02:00
}
2025-02-02 16:02:56 +01:00
BotController : : ~ BotController ( )
{
if ( controlledEnt ) {
controlledEnt - > delegate_gotKill . Remove ( delegateHandle_gotKill ) ;
controlledEnt - > delegate_killed . Remove ( delegateHandle_killed ) ;
controlledEnt - > delegate_stufftext . Remove ( delegateHandle_stufftext ) ;
}
}
2024-10-09 20:46:39 +02:00
BotMovement & BotController : : GetMovement ( )
{
return movement ;
}
2024-10-08 22:16:57 +02:00
void BotController : : Init ( void )
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
bot_manualmove = gi . Cvar_Get ( " bot_manualmove " , " 0 " , 0 ) ;
for ( int i = 0 ; i < MAX_BOT_FUNCTIONS ; i + + ) {
2024-10-08 22:16:57 +02:00
botfuncs [ i ] . BeginState = & BotController : : State_DefaultBegin ;
botfuncs [ i ] . EndState = & BotController : : State_DefaultEnd ;
2023-11-13 20:33:06 +01:00
}
InitState_Attack ( & botfuncs [ 0 ] ) ;
InitState_Curious ( & botfuncs [ 1 ] ) ;
InitState_Grenade ( & botfuncs [ 2 ] ) ;
InitState_Idle ( & botfuncs [ 3 ] ) ;
2024-12-24 20:19:08 +01:00
//InitState_Weapon(&botfuncs[4]);
2016-03-27 11:49:47 +02:00
}
2024-10-08 22:16:57 +02:00
void BotController : : GetUsercmd ( usercmd_t * ucmd )
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
* ucmd = m_botCmd ;
2016-03-27 11:49:47 +02:00
}
2024-10-08 22:16:57 +02:00
void BotController : : GetEyeInfo ( usereyes_t * eyeinfo )
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
* eyeinfo = m_botEyes ;
2016-03-27 11:49:47 +02:00
}
2024-10-08 22:16:57 +02:00
void BotController : : UpdateBotStates ( void )
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
if ( bot_manualmove - > integer ) {
memset ( & m_botCmd , 0 , sizeof ( usercmd_t ) ) ;
return ;
}
2024-10-08 22:16:57 +02:00
if ( ! controlledEnt - > client - > pers . dm_primary [ 0 ] ) {
2024-10-06 22:30:28 +02:00
Event * event ;
//
// Primary weapon
//
event = new Event ( EV_Player_PrimaryDMWeapon ) ;
event - > AddString ( " auto " ) ;
2024-10-08 22:16:57 +02:00
controlledEnt - > ProcessEvent ( event ) ;
2024-10-06 22:30:28 +02:00
}
2024-10-08 22:16:57 +02:00
if ( controlledEnt - > GetTeam ( ) = = TEAM_NONE | | controlledEnt - > GetTeam ( ) = = TEAM_SPECTATOR ) {
2024-10-09 21:21:19 +02:00
float time ;
2024-10-06 22:30:28 +02:00
// Add some delay to avoid telefragging
2024-10-08 22:16:57 +02:00
time = controlledEnt - > entnum / 20.0 ;
2024-10-06 22:30:28 +02:00
2024-10-08 22:16:57 +02:00
if ( controlledEnt - > EventPending ( EV_Player_AutoJoinDMTeam ) ) {
2024-10-06 22:30:28 +02:00
return ;
}
//
// Team
//
2024-10-08 22:16:57 +02:00
controlledEnt - > PostEvent ( EV_Player_AutoJoinDMTeam , time ) ;
2024-10-06 22:30:28 +02:00
return ;
}
2024-10-08 22:16:57 +02:00
if ( controlledEnt - > IsDead ( ) | | controlledEnt - > IsSpectator ( ) ) {
2023-11-13 20:33:06 +01:00
// The bot should respawn
m_botCmd . buttons ^ = BUTTON_ATTACKLEFT ;
return ;
}
m_botCmd . buttons | = BUTTON_RUN ;
2024-10-02 18:29:37 +02:00
m_botCmd . serverTime = level . svsTime ;
2023-11-13 20:33:06 +01:00
m_botEyes . ofs [ 0 ] = 0 ;
m_botEyes . ofs [ 1 ] = 0 ;
2024-10-08 22:16:57 +02:00
m_botEyes . ofs [ 2 ] = controlledEnt - > viewheight ;
2023-11-13 20:33:06 +01:00
m_botEyes . angles [ 0 ] = 0 ;
m_botEyes . angles [ 1 ] = 0 ;
CheckStates ( ) ;
2024-10-09 20:46:39 +02:00
movement . MoveThink ( m_botCmd ) ;
2024-10-09 21:21:19 +02:00
rotation . TurnThink ( m_botCmd , m_botEyes ) ;
2023-11-13 20:33:06 +01:00
CheckUse ( ) ;
2024-12-24 20:19:08 +01:00
CheckValidWeapon ( ) ;
2016-03-27 11:49:47 +02:00
}
2024-10-09 20:46:39 +02:00
void BotController : : CheckUse ( void )
{
Vector dir ;
Vector start ;
Vector end ;
trace_t trace ;
controlledEnt - > angles . AngleVectorsLeft ( & dir ) ;
start = controlledEnt - > origin + Vector ( 0 , 0 , controlledEnt - > viewheight ) ;
end = controlledEnt - > origin + Vector ( 0 , 0 , controlledEnt - > viewheight ) + dir * 32 ;
trace = G_Trace ( start , vec_zero , vec_zero , end , controlledEnt , MASK_USABLE , false , " BotController::CheckUse " ) ;
// It may be a door
2024-12-09 22:02:48 +01:00
if ( ( trace . allsolid | | trace . startsolid | | trace . fraction ! = 1.0f ) & & trace . ent ) {
2024-10-09 20:46:39 +02:00
if ( trace . ent - > entity - > IsSubclassOfDoor ( ) ) {
Door * door = static_cast < Door * > ( trace . ent - > entity ) ;
if ( door - > isOpen ( ) ) {
m_botCmd . buttons & = ~ BUTTON_USE ;
return ;
}
}
//
// Toggle the use button
//
m_botCmd . buttons ^ = BUTTON_USE ;
} else {
m_botCmd . buttons & = ~ BUTTON_USE ;
}
}
2024-12-24 20:19:08 +01:00
void BotController : : CheckValidWeapon ( ) {
Weapon * weapon = controlledEnt - > GetActiveWeapon ( WEAPON_MAIN ) ;
if ( ! weapon ) {
// If holstered, use the best weapon available
UseWeaponWithAmmo ( ) ;
} else if ( ! weapon - > HasAmmo ( FIRE_PRIMARY ) & & ! controlledEnt - > GetNewActiveWeapon ( ) ) {
// In case the current weapon has no ammo, use the best available weapon
UseWeaponWithAmmo ( ) ;
}
}
2024-10-08 22:16:57 +02:00
void BotController : : SendCommand ( const char * text )
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
char * buffer ;
char * data ;
size_t len ;
ConsoleEvent ev ;
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
len = strlen ( text ) + 1 ;
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
buffer = ( char * ) gi . Malloc ( len ) ;
data = buffer ;
Q_strncpyz ( data , text , len ) ;
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
const char * com_token = COM_Parse ( & data ) ;
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
if ( ! com_token ) {
return ;
}
2016-03-27 11:49:47 +02:00
2024-10-08 22:16:57 +02:00
controlledEnt - > m_lastcommand = com_token ;
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
if ( ! Event : : GetEvent ( com_token ) ) {
return ;
}
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
ev = ConsoleEvent ( com_token ) ;
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
if ( ! ( ev . GetEventFlags ( ev . eventnum ) & EV_CONSOLE ) ) {
gi . Free ( buffer ) ;
return ;
}
2016-03-27 11:49:47 +02:00
2024-10-08 22:16:57 +02:00
ev . SetConsoleEdict ( controlledEnt - > edict ) ;
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
while ( 1 ) {
com_token = COM_Parse ( & data ) ;
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
if ( ! com_token | | ! * com_token ) {
break ;
}
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
ev . AddString ( com_token ) ;
}
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
gi . Free ( buffer ) ;
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
try {
2024-10-08 22:16:57 +02:00
controlledEnt - > ProcessEvent ( ev ) ;
2023-11-13 20:33:06 +01:00
} catch ( ScriptException & exc ) {
gi . DPrintf ( " *** Bot Command Exception *** %s \n " , exc . string . c_str ( ) ) ;
}
2016-03-27 11:49:47 +02:00
}
/*
= = = = = = = = = = = = = = = = = = = =
AimAtAimNode
Make the bot face toward the current path
= = = = = = = = = = = = = = = = = = = =
*/
2024-10-08 22:16:57 +02:00
void BotController : : AimAtAimNode ( void )
2016-03-27 11:49:47 +02:00
{
2024-10-09 20:46:39 +02:00
Vector goal ;
if ( ! movement . IsMoving ( ) ) {
2024-10-03 21:57:29 +02:00
return ;
}
2024-10-09 20:46:39 +02:00
goal = movement . GetCurrentGoal ( ) ;
if ( goal ! = controlledEnt - > origin ) {
2024-10-09 21:21:19 +02:00
rotation . AimAt ( goal ) ;
2023-11-13 20:33:06 +01:00
}
2024-10-09 21:21:19 +02:00
Vector targetAngles = rotation . GetTargetAngles ( ) ;
targetAngles . x = 0 ;
rotation . SetTargetAngles ( targetAngles ) ;
2016-03-27 11:49:47 +02:00
}
/*
= = = = = = = = = = = = = = = = = = = =
CheckReload
Make the bot reload if necessary
= = = = = = = = = = = = = = = = = = = =
*/
2024-10-08 22:16:57 +02:00
void BotController : : CheckReload ( void )
2016-03-27 11:49:47 +02:00
{
2024-10-08 22:16:57 +02:00
Weapon * weap = controlledEnt - > GetActiveWeapon ( WEAPON_MAIN ) ;
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
if ( weap & & weap - > CheckReload ( FIRE_PRIMARY ) ) {
SendCommand ( " reload " ) ;
}
2016-03-27 11:49:47 +02:00
}
/*
= = = = = = = = = = = = = = = = = = = =
NoticeEvent
Warn the bot of an event
= = = = = = = = = = = = = = = = = = = =
*/
2024-10-08 22:16:57 +02:00
void BotController : : NoticeEvent ( Vector vPos , int iType , Entity * pEnt , float fDistanceSquared , float fRadiusSquared )
2016-03-27 11:49:47 +02:00
{
2024-10-08 22:16:57 +02:00
Sentient * pSentOwner ;
float fRangeFactor ;
2024-10-06 01:01:03 +02:00
2024-10-06 14:24:32 +02:00
fRangeFactor = 1.0 - ( fDistanceSquared / fRadiusSquared ) ;
2024-10-06 01:01:03 +02:00
if ( fRangeFactor < random ( ) ) {
return ;
}
2024-10-03 22:50:39 +02:00
if ( pEnt - > IsSubclassOfSentient ( ) ) {
2024-10-08 22:16:57 +02:00
pSentOwner = static_cast < Sentient * > ( pEnt ) ;
2024-10-03 22:50:39 +02:00
} else if ( pEnt - > IsSubclassOfVehicleTurretGun ( ) ) {
2024-10-08 22:16:57 +02:00
VehicleTurretGun * pVTG = static_cast < VehicleTurretGun * > ( pEnt ) ;
pSentOwner = pVTG - > GetSentientOwner ( ) ;
2024-10-03 22:50:39 +02:00
} else if ( pEnt - > IsSubclassOfItem ( ) ) {
2024-10-08 22:16:57 +02:00
Item * pItem = static_cast < Item * > ( pEnt ) ;
pSentOwner = pItem - > GetOwner ( ) ;
2024-10-03 22:50:39 +02:00
} else if ( pEnt - > IsSubclassOfProjectile ( ) ) {
2024-10-08 22:16:57 +02:00
Projectile * pProj = static_cast < Projectile * > ( pEnt ) ;
pSentOwner = pProj - > GetOwner ( ) ;
2024-10-03 22:50:39 +02:00
} else {
pSentOwner = NULL ;
}
if ( pSentOwner ) {
2024-10-08 22:16:57 +02:00
if ( pSentOwner = = controlledEnt ) {
2024-10-03 22:50:39 +02:00
// Ignore self
return ;
}
2023-11-13 20:33:06 +01:00
2024-10-03 22:50:39 +02:00
if ( ( pSentOwner - > flags & FL_NOTARGET ) | | pSentOwner - > getSolidType ( ) = = SOLID_NOT ) {
2023-11-13 20:33:06 +01:00
return ;
}
2024-10-03 22:50:39 +02:00
// Ignore teammates
if ( pSentOwner - > IsSubclassOfPlayer ( ) ) {
2024-10-08 22:16:57 +02:00
Player * p = static_cast < Player * > ( pSentOwner ) ;
2023-11-13 20:33:06 +01:00
2024-10-08 22:16:57 +02:00
if ( g_gametype - > integer > = GT_TEAM & & p - > GetTeam ( ) = = controlledEnt - > GetTeam ( ) ) {
2023-11-13 20:33:06 +01:00
return ;
}
}
}
switch ( iType ) {
case AI_EVENT_MISC :
case AI_EVENT_MISC_LOUD :
break ;
case AI_EVENT_WEAPON_FIRE :
case AI_EVENT_WEAPON_IMPACT :
case AI_EVENT_EXPLOSION :
case AI_EVENT_AMERICAN_VOICE :
case AI_EVENT_GERMAN_VOICE :
case AI_EVENT_AMERICAN_URGENT :
case AI_EVENT_GERMAN_URGENT :
case AI_EVENT_FOOTSTEP :
case AI_EVENT_GRENADE :
default :
2024-10-08 22:16:57 +02:00
m_iCuriousTime = level . inttime + 20000 ;
m_vNewCuriousPos = vPos ;
2023-11-13 20:33:06 +01:00
break ;
}
2016-03-27 11:49:47 +02:00
}
/*
= = = = = = = = = = = = = = = = = = = =
ClearEnemy
Clear the bot ' s enemy
= = = = = = = = = = = = = = = = = = = =
*/
2024-10-08 22:16:57 +02:00
void BotController : : ClearEnemy ( void )
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
m_iAttackTime = 0 ;
2024-12-16 23:05:35 +01:00
m_iConfirmTime = 0 ;
2023-11-13 20:33:06 +01:00
m_pEnemy = NULL ;
2024-12-16 19:45:42 +01:00
m_iEnemyEyesTag = - 1 ;
2023-11-13 20:33:06 +01:00
m_vOldEnemyPos = vec_zero ;
m_vLastEnemyPos = vec_zero ;
2016-03-27 11:49:47 +02:00
}
/*
= = = = = = = = = = = = = = = = = = = =
Bot states
- - - - - - - - - - - - - - - - - - - -
____________________
- - - - - - - - - - - - - - - - - - - -
____________________
- - - - - - - - - - - - - - - - - - - -
____________________
- - - - - - - - - - - - - - - - - - - -
____________________
= = = = = = = = = = = = = = = = = = = =
*/
2024-10-08 22:16:57 +02:00
void BotController : : CheckStates ( void )
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
m_StateCount = 0 ;
for ( int i = 0 ; i < MAX_BOT_FUNCTIONS ; i + + ) {
botfunc_t * func = & botfuncs [ i ] ;
if ( func - > CheckCondition ) {
if ( ( this - > * func - > CheckCondition ) ( ) ) {
if ( ! ( m_StateFlags & ( 1 < < i ) ) ) {
m_StateFlags | = 1 < < i ;
if ( func - > BeginState ) {
( this - > * func - > BeginState ) ( ) ;
}
}
if ( func - > ThinkState ) {
m_StateCount + + ;
( this - > * func - > ThinkState ) ( ) ;
}
} else {
if ( ( m_StateFlags & ( 1 < < i ) ) ) {
m_StateFlags & = ~ ( 1 < < i ) ;
if ( func - > EndState ) {
( this - > * func - > EndState ) ( ) ;
}
}
}
} else {
if ( func - > ThinkState ) {
m_StateCount + + ;
( this - > * func - > ThinkState ) ( ) ;
}
}
}
assert ( m_StateCount ) ;
if ( ! m_StateCount ) {
2024-10-08 22:16:57 +02:00
gi . DPrintf ( " *** WARNING *** %s was stuck with no states !!! " , controlledEnt - > client - > pers . netname ) ;
2023-11-13 20:33:06 +01:00
State_Reset ( ) ;
}
2016-03-27 11:49:47 +02:00
}
/*
= = = = = = = = = = = = = = = = = = = =
Default state
= = = = = = = = = = = = = = = = = = = =
*/
2024-10-08 22:16:57 +02:00
void BotController : : State_DefaultBegin ( void )
2016-03-27 11:49:47 +02:00
{
2024-10-09 20:46:39 +02:00
movement . ClearMove ( ) ;
2016-03-27 11:49:47 +02:00
}
2024-10-08 22:16:57 +02:00
void BotController : : State_DefaultEnd ( void ) { }
2016-03-27 11:49:47 +02:00
2024-10-08 22:16:57 +02:00
void BotController : : State_Reset ( void )
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
m_iCuriousTime = 0 ;
m_iAttackTime = 0 ;
m_vLastCuriousPos = vec_zero ;
m_vOldEnemyPos = vec_zero ;
m_vLastEnemyPos = vec_zero ;
m_vLastDeathPos = vec_zero ;
m_pEnemy = NULL ;
2024-12-16 19:45:42 +01:00
m_iEnemyEyesTag = - 1 ;
2016-03-27 11:49:47 +02:00
}
/*
= = = = = = = = = = = = = = = = = = = =
Idle state
Make the bot move to random directions
= = = = = = = = = = = = = = = = = = = =
*/
2024-10-08 22:16:57 +02:00
void BotController : : InitState_Idle ( botfunc_t * func )
2016-03-27 11:49:47 +02:00
{
2024-10-08 22:16:57 +02:00
func - > CheckCondition = & BotController : : CheckCondition_Idle ;
func - > ThinkState = & BotController : : State_Idle ;
2016-03-27 11:49:47 +02:00
}
2024-10-08 22:16:57 +02:00
bool BotController : : CheckCondition_Idle ( void )
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
if ( m_iCuriousTime ) {
return false ;
}
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
if ( m_iAttackTime ) {
return false ;
}
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
return true ;
2016-03-27 11:49:47 +02:00
}
2024-10-08 22:16:57 +02:00
void BotController : : State_Idle ( void )
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
AimAtAimNode ( ) ;
CheckReload ( ) ;
2024-10-09 20:46:39 +02:00
if ( ! movement . MoveToBestAttractivePoint ( ) & & ! movement . IsMoving ( ) ) {
2023-11-13 20:33:06 +01:00
if ( m_vLastDeathPos ! = vec_zero ) {
2024-10-09 20:46:39 +02:00
movement . MoveTo ( m_vLastDeathPos ) ;
2023-11-13 20:33:06 +01:00
2024-10-09 20:46:39 +02:00
if ( movement . MoveDone ( ) ) {
2023-11-13 20:33:06 +01:00
m_vLastDeathPos = vec_zero ;
}
} else {
2024-10-03 22:23:46 +02:00
Vector randomDir ( G_CRandom ( 16 ) , G_CRandom ( 16 ) , G_CRandom ( 16 ) ) ;
2024-10-08 22:16:57 +02:00
Vector preferredDir = Vector ( controlledEnt - > orientation [ 0 ] ) * ( rand ( ) % 5 ? 1024 : - 1024 ) ;
float radius = 512 + G_Random ( 2048 ) ;
2024-10-03 20:22:54 +02:00
2024-10-09 20:46:39 +02:00
movement . AvoidPath ( controlledEnt - > origin + randomDir , radius , preferredDir ) ;
2023-11-13 20:33:06 +01:00
}
}
2016-03-27 11:49:47 +02:00
}
/*
= = = = = = = = = = = = = = = = = = = =
Curious state
Forward to the last event position
= = = = = = = = = = = = = = = = = = = =
*/
2024-10-08 22:16:57 +02:00
void BotController : : InitState_Curious ( botfunc_t * func )
2016-03-27 11:49:47 +02:00
{
2024-10-08 22:16:57 +02:00
func - > CheckCondition = & BotController : : CheckCondition_Curious ;
func - > ThinkState = & BotController : : State_Curious ;
2016-03-27 11:49:47 +02:00
}
2024-10-08 22:16:57 +02:00
bool BotController : : CheckCondition_Curious ( void )
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
if ( m_iAttackTime ) {
m_iCuriousTime = 0 ;
return false ;
}
if ( level . inttime > m_iCuriousTime ) {
if ( m_iCuriousTime ) {
2024-10-09 20:46:39 +02:00
movement . ClearMove ( ) ;
2023-11-13 20:33:06 +01:00
m_iCuriousTime = 0 ;
}
return false ;
}
return true ;
2016-03-27 11:49:47 +02:00
}
2024-10-08 22:16:57 +02:00
void BotController : : State_Curious ( void )
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
AimAtAimNode ( ) ;
2024-10-09 20:46:39 +02:00
if ( ! movement . MoveToBestAttractivePoint ( 3 ) & & ( ! movement . IsMoving ( ) | | m_vLastCuriousPos ! = m_vNewCuriousPos ) ) {
movement . MoveTo ( m_vNewCuriousPos ) ;
2024-10-06 14:24:32 +02:00
m_vLastCuriousPos = m_vNewCuriousPos ;
2023-11-13 20:33:06 +01:00
}
2024-10-09 20:46:39 +02:00
if ( movement . MoveDone ( ) ) {
2023-11-13 20:33:06 +01:00
m_iCuriousTime = 0 ;
}
2016-03-27 11:49:47 +02:00
}
/*
= = = = = = = = = = = = = = = = = = = =
Attack state
Attack the enemy
= = = = = = = = = = = = = = = = = = = =
*/
2024-10-08 22:16:57 +02:00
void BotController : : InitState_Attack ( botfunc_t * func )
2016-03-27 11:49:47 +02:00
{
2024-10-08 22:16:57 +02:00
func - > CheckCondition = & BotController : : CheckCondition_Attack ;
func - > EndState = & BotController : : State_EndAttack ;
func - > ThinkState = & BotController : : State_Attack ;
2016-03-27 11:49:47 +02:00
}
static Vector bot_origin ;
2023-11-13 20:33:06 +01:00
static int sentients_compare ( const void * elem1 , const void * elem2 )
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
Entity * e1 , * e2 ;
float delta [ 3 ] ;
float d1 , d2 ;
e1 = * ( Entity * * ) elem1 ;
e2 = * ( Entity * * ) elem2 ;
VectorSubtract ( bot_origin , e1 - > origin , delta ) ;
d1 = VectorLengthSquared ( delta ) ;
VectorSubtract ( bot_origin , e2 - > origin , delta ) ;
d2 = VectorLengthSquared ( delta ) ;
if ( d2 < = d1 ) {
return d1 > d2 ;
} else {
return - 1 ;
}
2016-03-27 11:49:47 +02:00
}
2024-10-08 22:16:57 +02:00
bool BotController : : IsValidEnemy ( Sentient * sent ) const
{
if ( sent = = controlledEnt ) {
2024-10-03 20:45:23 +02:00
return false ;
}
if ( sent - > hidden ( ) | | ( sent - > flags & FL_NOTARGET ) ) {
// Ignore hidden / non-target enemies
return false ;
}
if ( sent - > IsDead ( ) ) {
// Ignore dead enemies
return false ;
}
if ( sent - > getSolidType ( ) = = SOLID_NOT ) {
// Ignore non-solid, like spectators
return false ;
}
if ( sent - > IsSubclassOfPlayer ( ) ) {
2024-10-08 22:16:57 +02:00
Player * player = static_cast < Player * > ( sent ) ;
2024-10-03 20:45:23 +02:00
2024-10-08 22:16:57 +02:00
if ( g_gametype - > integer > = GT_TEAM & & player - > GetTeam ( ) = = controlledEnt - > GetTeam ( ) ) {
2024-10-03 20:45:23 +02:00
return false ;
}
2024-10-08 22:16:57 +02:00
} else {
if ( sent - > m_Team = = controlledEnt - > m_Team ) {
2024-10-03 20:45:23 +02:00
return false ;
}
}
return true ;
}
2024-10-08 22:16:57 +02:00
bool BotController : : CheckCondition_Attack ( void )
2016-03-27 11:49:47 +02:00
{
2024-10-08 22:16:57 +02:00
Container < Sentient * > sents = SentientList ;
float maxDistance = 0 ;
2023-11-13 20:33:06 +01:00
2024-10-08 22:16:57 +02:00
bot_origin = controlledEnt - > origin ;
2023-11-13 20:33:06 +01:00
sents . Sort ( sentients_compare ) ;
for ( int i = 1 ; i < = sents . NumObjects ( ) ; i + + ) {
2024-10-08 22:16:57 +02:00
Sentient * sent = sents . ObjectAt ( i ) ;
2023-11-13 20:33:06 +01:00
2024-10-03 20:45:23 +02:00
if ( ! IsValidEnemy ( sent ) ) {
2023-11-13 20:33:06 +01:00
continue ;
}
2024-10-02 19:41:25 +02:00
maxDistance = Q_min ( world - > m_fAIVisionDistance , world - > farplane_distance * 0.828 ) ;
2024-10-08 22:16:57 +02:00
if ( controlledEnt - > CanSee ( sent , 80 , maxDistance , false ) ) {
2024-12-16 19:45:42 +01:00
if ( m_pEnemy ! = sent ) {
m_iEnemyEyesTag = - 1 ;
}
2024-12-16 23:05:35 +01:00
if ( ! m_pEnemy ) {
// Slight reaction time
m_iConfirmTime = level . inttime + ( 200 + G_Random ( 200 ) ) ;
2024-12-17 14:01:13 +01:00
m_iAttackTime = 0 ;
2024-12-16 23:05:35 +01:00
}
m_pEnemy = sent ;
m_vLastEnemyPos = m_pEnemy - > origin ;
if ( level . inttime < m_iConfirmTime ) {
return false ;
}
}
if ( m_pEnemy & & level . inttime > = m_iConfirmTime ) {
2024-10-05 14:47:09 +02:00
m_iAttackTime = level . inttime + 1000 ;
2023-11-13 20:33:06 +01:00
return true ;
}
}
if ( level . inttime > m_iAttackTime ) {
if ( m_iAttackTime ) {
2024-10-09 20:46:39 +02:00
movement . ClearMove ( ) ;
2023-11-13 20:33:06 +01:00
m_iAttackTime = 0 ;
}
return false ;
}
return true ;
2016-03-27 11:49:47 +02:00
}
2024-10-08 22:16:57 +02:00
void BotController : : State_EndAttack ( void )
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
m_botCmd . buttons & = ~ ( BUTTON_ATTACKLEFT | BUTTON_ATTACKRIGHT ) ;
2024-10-18 20:03:39 +02:00
controlledEnt - > ZoomOff ( ) ;
2016-03-27 11:49:47 +02:00
}
2024-10-08 22:16:57 +02:00
void BotController : : State_Attack ( void )
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
bool bMelee = false ;
2024-12-14 23:32:17 +01:00
bool bCanSee = false ;
2023-11-13 20:33:06 +01:00
float fMinDistance = 128 ;
float fMinDistanceSquared = fMinDistance * fMinDistance ;
2024-12-16 19:45:42 +01:00
float fEnemyDistanceSquared ;
2024-12-16 23:05:35 +01:00
Weapon * pWeap = controlledEnt - > GetActiveWeapon ( WEAPON_MAIN ) ;
bool bNoMove = false ;
2023-11-13 20:33:06 +01:00
2024-10-03 20:45:23 +02:00
if ( ! m_pEnemy | | ! IsValidEnemy ( m_pEnemy ) ) {
// Ignore dead enemies
2023-11-13 20:33:06 +01:00
m_iAttackTime = 0 ;
return ;
}
2024-10-08 22:16:57 +02:00
float fDistanceSquared = ( m_pEnemy - > origin - controlledEnt - > origin ) . lengthSquared ( ) ;
2023-11-13 20:33:06 +01:00
2024-12-14 23:32:17 +01:00
m_vOldEnemyPos = m_vLastEnemyPos ;
2024-12-16 23:05:35 +01:00
bCanSee =
controlledEnt - > CanSee ( m_pEnemy , 20 , Q_min ( world - > m_fAIVisionDistance , world - > farplane_distance * 0.828 ) , false ) ;
2024-12-14 23:32:17 +01:00
if ( bCanSee ) {
2023-11-13 20:33:06 +01:00
if ( ! pWeap ) {
return ;
}
float fPrimaryBulletRange = pWeap - > GetBulletRange ( FIRE_PRIMARY ) / 1.25f ;
float fPrimaryBulletRangeSquared = fPrimaryBulletRange * fPrimaryBulletRange ;
float fSecondaryBulletRange = pWeap - > GetBulletRange ( FIRE_SECONDARY ) ;
float fSecondaryBulletRangeSquared = fSecondaryBulletRange * fSecondaryBulletRange ;
2024-10-18 20:03:39 +02:00
float fSpreadFactor = pWeap - > GetSpreadFactor ( FIRE_PRIMARY ) ;
2023-11-13 20:33:06 +01:00
2024-10-04 19:04:00 +02:00
//
// check the fire movement speed if the weapon has a max fire movement
//
2024-10-08 22:16:57 +02:00
if ( pWeap - > GetMaxFireMovement ( ) < 1 & & pWeap - > HasAmmoInClip ( FIRE_PRIMARY ) ) {
2024-10-04 19:04:00 +02:00
float length ;
2024-10-08 22:16:57 +02:00
length = controlledEnt - > velocity . length ( ) ;
2024-11-25 22:12:29 +01:00
if ( ( length / sv_runspeed - > value ) > ( pWeap - > GetMaxFireMovementMult ( ) ) ) {
2024-10-04 19:04:00 +02:00
bNoMove = true ;
2024-10-09 20:46:39 +02:00
movement . ClearMove ( ) ;
2024-10-04 19:04:00 +02:00
}
}
2023-11-13 20:33:06 +01:00
fMinDistance = fPrimaryBulletRange ;
if ( fMinDistance > 256 ) {
fMinDistance = 256 ;
}
fMinDistanceSquared = fMinDistance * fMinDistance ;
2024-10-08 22:16:57 +02:00
if ( controlledEnt - > client - > ps . stats [ STAT_AMMO ] > 0 | | controlledEnt - > client - > ps . stats [ STAT_CLIPAMMO ] > 0 ) {
2023-11-13 20:33:06 +01:00
if ( fDistanceSquared < = fPrimaryBulletRangeSquared ) {
if ( pWeap - > IsSemiAuto ( ) ) {
2024-12-16 23:05:35 +01:00
if ( controlledEnt - > client - > ps . iViewModelAnim = = VM_ANIM_IDLE
| | controlledEnt - > client - > ps . iViewModelAnim > = VM_ANIM_IDLE_0
& & controlledEnt - > client - > ps . iViewModelAnim < = VM_ANIM_IDLE_2 ) {
2024-10-18 20:03:39 +02:00
if ( fSpreadFactor < 0.25 ) {
m_botCmd . buttons ^ = BUTTON_ATTACKLEFT ;
if ( pWeap - > GetZoom ( ) ) {
if ( ! controlledEnt - > IsZoomed ( ) ) {
m_botCmd . buttons | = BUTTON_ATTACKRIGHT ;
} else {
m_botCmd . buttons & = ~ BUTTON_ATTACKRIGHT ;
}
}
} else {
bNoMove = true ;
movement . ClearMove ( ) ;
}
} else {
m_botCmd . buttons & = ~ ( BUTTON_ATTACKLEFT | BUTTON_ATTACKRIGHT ) ;
controlledEnt - > ZoomOff ( ) ;
}
2023-11-13 20:33:06 +01:00
} else {
m_botCmd . buttons | = BUTTON_ATTACKLEFT ;
}
} else {
2024-10-18 20:03:39 +02:00
m_botCmd . buttons & = ~ ( BUTTON_ATTACKLEFT | BUTTON_ATTACKRIGHT ) ;
controlledEnt - > ZoomOff ( ) ;
2023-11-13 20:33:06 +01:00
}
2024-12-24 19:53:20 +01:00
} else {
m_botCmd . buttons & = ~ ( BUTTON_ATTACKLEFT | BUTTON_ATTACKRIGHT ) ;
controlledEnt - > ZoomOff ( ) ;
}
if ( pWeap - > GetFireType ( FIRE_SECONDARY ) = = FT_MELEE ) {
if ( controlledEnt - > client - > ps . stats [ STAT_AMMO ] < = 0 & & controlledEnt - > client - > ps . stats [ STAT_CLIPAMMO ] < = 0 ) {
bMelee = true ;
} else if ( fDistanceSquared < = fSecondaryBulletRangeSquared ) {
bMelee = true ;
}
}
if ( bMelee ) {
m_botCmd . buttons & = ~ BUTTON_ATTACKLEFT ;
2023-11-13 20:33:06 +01:00
if ( fDistanceSquared < = fSecondaryBulletRangeSquared ) {
m_botCmd . buttons ^ = BUTTON_ATTACKRIGHT ;
} else {
m_botCmd . buttons & = ~ BUTTON_ATTACKRIGHT ;
}
}
2024-10-05 14:47:09 +02:00
2024-12-16 23:05:35 +01:00
m_iAttackTime = level . inttime + 1000 ;
m_iAttackStopAimTime = level . inttime + 3000 ;
m_vLastEnemyPos = m_pEnemy - > centroid ;
2023-11-13 20:33:06 +01:00
} else {
m_botCmd . buttons & = ~ ( BUTTON_ATTACKLEFT | BUTTON_ATTACKRIGHT ) ;
fMinDistanceSquared = 0 ;
}
2024-12-14 23:32:17 +01:00
if ( bCanSee | | level . inttime < m_iAttackStopAimTime ) {
2024-12-16 23:05:35 +01:00
Vector vRandomOffset ;
Vector vTarget ;
2024-12-16 19:45:42 +01:00
orientation_t eyes_or ;
2024-12-16 23:05:35 +01:00
2024-12-16 19:45:42 +01:00
if ( m_iEnemyEyesTag = = - 1 ) {
// Cache the tag
m_iEnemyEyesTag = gi . Tag_NumForName ( m_pEnemy - > edict - > tiki , " eyes bone " ) ;
}
if ( m_iEnemyEyesTag ! = - 1 ) {
// Use the enemy's eyes bone
m_pEnemy - > GetTag ( m_iEnemyEyesTag , & eyes_or ) ;
vRandomOffset = Vector ( G_CRandom ( 8 ) , G_CRandom ( 8 ) , - G_Random ( 32 ) ) ;
2024-12-16 23:05:35 +01:00
vTarget = eyes_or . origin + vRandomOffset ;
2024-12-16 19:45:42 +01:00
rotation . AimAt ( eyes_or . origin + vRandomOffset ) ;
} else {
vRandomOffset = Vector ( G_CRandom ( 8 ) , G_CRandom ( 8 ) , 16 + G_Random ( m_pEnemy - > viewheight - 16 ) ) ;
2024-12-16 23:05:35 +01:00
vTarget = m_pEnemy - > origin + vRandomOffset ;
2024-12-16 19:45:42 +01:00
}
rotation . AimAt ( vTarget ) ;
2024-12-14 23:32:17 +01:00
} else {
AimAtAimNode ( ) ;
}
2024-10-04 21:36:42 +02:00
2024-10-04 19:04:00 +02:00
if ( bNoMove ) {
return ;
}
2024-12-16 19:45:42 +01:00
fEnemyDistanceSquared = ( controlledEnt - > origin - m_vLastEnemyPos ) . lengthSquared ( ) ;
2024-10-09 21:21:19 +02:00
if ( ( ! movement . MoveToBestAttractivePoint ( 5 ) & & ! movement . IsMoving ( ) )
2024-12-16 23:05:35 +01:00
| | ( m_vOldEnemyPos ! = m_vLastEnemyPos & & ! movement . MoveDone ( ) ) | | fEnemyDistanceSquared < fMinDistanceSquared ) {
2024-12-14 23:32:17 +01:00
if ( ! bMelee | | ! bCanSee ) {
2024-12-16 19:45:42 +01:00
if ( fEnemyDistanceSquared < fMinDistanceSquared ) {
2024-10-08 22:16:57 +02:00
Vector vDir = controlledEnt - > origin - m_vLastEnemyPos ;
2023-11-13 20:33:06 +01:00
VectorNormalizeFast ( vDir ) ;
2024-10-09 20:46:39 +02:00
movement . AvoidPath ( m_vLastEnemyPos , fMinDistance , Vector ( controlledEnt - > orientation [ 1 ] ) * 512 ) ;
2023-11-13 20:33:06 +01:00
} else {
2024-12-14 23:32:17 +01:00
movement . MoveTo ( m_vLastEnemyPos ) ;
}
if ( ! bCanSee & & movement . MoveDone ( ) ) {
// Lost track of the enemy
2024-12-16 19:45:42 +01:00
ClearEnemy ( ) ;
2024-12-14 23:32:17 +01:00
return ;
2023-11-13 20:33:06 +01:00
}
} else {
2024-10-09 20:46:39 +02:00
movement . MoveTo ( m_vLastEnemyPos ) ;
2023-11-13 20:33:06 +01:00
}
}
2024-10-06 01:13:12 +02:00
2024-10-09 20:46:39 +02:00
if ( movement . IsMoving ( ) ) {
2024-10-06 01:13:12 +02:00
m_iAttackTime = level . inttime + 1000 ;
}
2016-03-27 11:49:47 +02:00
}
/*
= = = = = = = = = = = = = = = = = = = =
Grenade state
Avoid any grenades
= = = = = = = = = = = = = = = = = = = =
*/
2024-10-08 22:16:57 +02:00
void BotController : : InitState_Grenade ( botfunc_t * func )
2016-03-27 11:49:47 +02:00
{
2024-10-08 22:16:57 +02:00
func - > CheckCondition = & BotController : : CheckCondition_Grenade ;
func - > ThinkState = & BotController : : State_Grenade ;
2016-03-27 11:49:47 +02:00
}
2024-10-08 22:16:57 +02:00
bool BotController : : CheckCondition_Grenade ( void )
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
// FIXME: TODO
return false ;
2016-03-27 11:49:47 +02:00
}
2024-10-08 22:16:57 +02:00
void BotController : : State_Grenade ( void )
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
// FIXME: TODO
2016-03-27 11:49:47 +02:00
}
/*
= = = = = = = = = = = = = = = = = = = =
Weapon state
Change weapon when necessary
= = = = = = = = = = = = = = = = = = = =
*/
2024-10-08 22:16:57 +02:00
void BotController : : InitState_Weapon ( botfunc_t * func )
2016-03-27 11:49:47 +02:00
{
2024-10-08 22:16:57 +02:00
func - > CheckCondition = & BotController : : CheckCondition_Weapon ;
func - > BeginState = & BotController : : State_BeginWeapon ;
2016-03-27 11:49:47 +02:00
}
2024-10-08 22:16:57 +02:00
bool BotController : : CheckCondition_Weapon ( void )
2016-03-27 11:49:47 +02:00
{
2024-10-08 22:16:57 +02:00
return controlledEnt - > GetActiveWeapon ( WEAPON_MAIN )
! = controlledEnt - > BestWeapon ( NULL , false , WEAPON_CLASS_THROWABLE ) ;
2016-03-27 11:49:47 +02:00
}
2024-10-08 22:16:57 +02:00
void BotController : : State_BeginWeapon ( void )
2016-03-27 11:49:47 +02:00
{
2024-10-08 22:16:57 +02:00
Weapon * weap = controlledEnt - > BestWeapon ( NULL , false , WEAPON_CLASS_THROWABLE ) ;
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
if ( weap = = NULL ) {
SendCommand ( " safeholster 1 " ) ;
return ;
}
2016-03-27 11:49:47 +02:00
2023-11-13 20:33:06 +01:00
SendCommand ( va ( " use \" %s \" " , weap - > model . c_str ( ) ) ) ;
2016-03-27 11:49:47 +02:00
}
2024-12-24 20:19:08 +01:00
Weapon * BotController : : FindWeaponWithAmmo ( ) {
Weapon * next ;
int n ;
int j ;
int bestrank ;
Weapon * bestweapon ;
const Container < int > & inventory = controlledEnt - > getInventory ( ) ;
n = inventory . NumObjects ( ) ;
// Search until we find the best weapon with ammo
bestweapon = NULL ;
bestrank = - 999999 ;
for ( j = 1 ; j < = n ; j + + ) {
next = ( Weapon * ) G_GetEntity ( inventory . ObjectAt ( j ) ) ;
assert ( next ) ;
if ( ! next - > IsSubclassOfWeapon ( ) | | next - > IsSubclassOfInventoryItem ( ) ) {
continue ;
}
if ( next - > GetWeaponClass ( ) & WEAPON_CLASS_THROWABLE ) {
continue ;
}
if ( next - > GetRank ( ) < bestrank ) {
continue ;
}
if ( ! next - > HasAmmo ( FIRE_PRIMARY ) ) {
continue ;
}
bestweapon = ( Weapon * ) next ;
bestrank = bestweapon - > GetRank ( ) ;
}
return bestweapon ;
}
Weapon * BotController : : FindMeleeWeapon ( ) {
Weapon * next ;
int n ;
int j ;
int bestrank ;
Weapon * bestweapon ;
const Container < int > & inventory = controlledEnt - > getInventory ( ) ;
n = inventory . NumObjects ( ) ;
// Search until we find the best weapon with ammo
bestweapon = NULL ;
bestrank = - 999999 ;
for ( j = 1 ; j < = n ; j + + ) {
next = ( Weapon * ) G_GetEntity ( inventory . ObjectAt ( j ) ) ;
assert ( next ) ;
if ( ! next - > IsSubclassOfWeapon ( ) | | next - > IsSubclassOfInventoryItem ( ) ) {
continue ;
}
if ( next - > GetRank ( ) < bestrank ) {
continue ;
}
if ( next - > GetFireType ( FIRE_SECONDARY ) ! = FT_MELEE ) {
continue ;
}
bestweapon = ( Weapon * ) next ;
bestrank = bestweapon - > GetRank ( ) ;
}
return bestweapon ;
}
void BotController : : UseWeaponWithAmmo ( ) {
Weapon * bestWeapon = FindWeaponWithAmmo ( ) ;
if ( ! bestWeapon ) {
//
// If there is no weapon with ammo, fallback to a weapon that can melee
//
bestWeapon = FindMeleeWeapon ( ) ;
}
if ( ! bestWeapon | | bestWeapon = = controlledEnt - > GetActiveWeapon ( WEAPON_MAIN ) ) {
return ;
}
controlledEnt - > useWeapon ( bestWeapon , WEAPON_MAIN ) ;
}
2024-10-08 22:16:57 +02:00
void BotController : : Spawned ( void )
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
ClearEnemy ( ) ;
2024-10-08 22:16:57 +02:00
m_iCuriousTime = 0 ;
2024-10-02 20:14:24 +02:00
m_botCmd . buttons = 0 ;
2016-03-27 11:49:47 +02:00
}
2024-10-08 22:16:57 +02:00
void BotController : : Think ( )
2016-03-27 11:49:47 +02:00
{
2024-10-08 22:16:57 +02:00
usercmd_t ucmd ;
usereyes_t eyeinfo ;
2024-10-03 21:33:12 +02:00
2024-10-08 22:16:57 +02:00
UpdateBotStates ( ) ;
GetUsercmd ( & ucmd ) ;
GetEyeInfo ( & eyeinfo ) ;
G_ClientThink ( controlledEnt - > edict , & ucmd , & eyeinfo ) ;
}
2025-02-02 16:02:56 +01:00
void BotController : : Killed ( const Event & ev )
2024-10-08 22:16:57 +02:00
{
Entity * attacker ;
2023-11-13 20:33:06 +01:00
// send the respawn buttons
if ( ! ( m_botCmd . buttons & BUTTON_ATTACKLEFT ) ) {
m_botCmd . buttons | = BUTTON_ATTACKLEFT ;
} else {
m_botCmd . buttons & = ~ BUTTON_ATTACKLEFT ;
}
m_botEyes . ofs [ 0 ] = 0 ;
m_botEyes . ofs [ 1 ] = 0 ;
m_botEyes . ofs [ 2 ] = 0 ;
m_botEyes . angles [ 0 ] = 0 ;
m_botEyes . angles [ 1 ] = 0 ;
2025-02-02 16:02:56 +01:00
attacker = ev . GetEntity ( 1 ) ;
2024-10-03 21:33:12 +02:00
if ( attacker & & rand ( ) % 5 = = 0 ) {
// 1/5 chance to go back to the attacker position
m_vLastDeathPos = attacker - > origin ;
2024-10-02 18:29:37 +02:00
} else {
m_vLastDeathPos = vec_zero ;
}
2024-10-02 17:02:59 +02:00
// Choose a new random primary weapon
Event event ( EV_Player_PrimaryDMWeapon ) ;
event . AddString ( " auto " ) ;
2024-10-02 18:29:37 +02:00
2024-10-08 22:16:57 +02:00
controlledEnt - > ProcessEvent ( event ) ;
2024-12-25 15:08:57 +01:00
//
// This is useful to change nationality in Spearhead and Breakthrough
// this allows the AI to use more weapons
//
Info_SetValueForKey ( controlledEnt - > client - > pers . userinfo , " dm_playermodel " , G_GetRandomAlliedPlayerModel ( ) ) ;
Info_SetValueForKey ( controlledEnt - > client - > pers . userinfo , " dm_playergermanmodel " , G_GetRandomGermanPlayerModel ( ) ) ;
G_ClientUserinfoChanged ( controlledEnt - > edict , controlledEnt - > client - > pers . userinfo ) ;
2016-03-27 11:49:47 +02:00
}
2025-02-02 16:02:56 +01:00
void BotController : : GotKill ( const Event & ev )
2016-03-27 11:49:47 +02:00
{
2023-11-13 20:33:06 +01:00
ClearEnemy ( ) ;
m_iCuriousTime = 0 ;
2024-10-02 21:53:54 +02:00
if ( level . inttime > = m_iNextTauntTime & & ( rand ( ) % 5 ) = = 0 ) {
//
// Randomly play a taunt
//
Event event ( " dmmessage " ) ;
event . AddInteger ( 0 ) ;
if ( g_protocol > = protocol_e : : PROTOCOL_MOHTA_MIN ) {
event . AddString ( " *5 " + str ( 1 + ( rand ( ) % 8 ) ) ) ;
} else {
2024-10-02 22:18:07 +02:00
event . AddString ( " *4 " + str ( 1 + ( rand ( ) % 9 ) ) ) ;
2024-10-02 21:53:54 +02:00
}
2024-10-08 22:16:57 +02:00
controlledEnt - > ProcessEvent ( event ) ;
2024-10-02 21:53:54 +02:00
m_iNextTauntTime = level . inttime + 5000 ;
}
2016-03-27 11:49:47 +02:00
}
2025-02-02 16:02:56 +01:00
void BotController : : EventStuffText ( const str & text )
2016-03-27 11:49:47 +02:00
{
2025-02-02 16:02:56 +01:00
SendCommand ( text ) ;
2016-03-27 11:49:47 +02:00
}
2024-10-08 22:16:57 +02:00
void BotController : : setControlledEntity ( Player * player )
{
controlledEnt = player ;
2024-10-09 20:46:39 +02:00
movement . SetControlledEntity ( player ) ;
2024-10-09 21:21:19 +02:00
rotation . SetControlledEntity ( player ) ;
2025-02-02 16:02:56 +01:00
delegateHandle_gotKill = player - > delegate_gotKill . Add ( std : : bind ( & BotController : : GotKill , this , std : : placeholders : : _1 ) ) ;
delegateHandle_killed = player - > delegate_killed . Add ( std : : bind ( & BotController : : Killed , this , std : : placeholders : : _1 ) ) ;
delegateHandle_stufftext = player - > delegate_stufftext . Add ( std : : bind ( & BotController : : EventStuffText , this , std : : placeholders : : _1 ) ) ;
2024-10-08 22:16:57 +02:00
}
Player * BotController : : getControlledEntity ( ) const
{
return controlledEnt ;
}
BotController * BotControllerManager : : createController ( Player * player )
{
BotController * controller = new BotController ( ) ;
controller - > setControlledEntity ( player ) ;
controllers . AddObject ( controller ) ;
return controller ;
}
void BotControllerManager : : removeController ( BotController * controller )
{
controllers . RemoveObject ( controller ) ;
delete controller ;
}
BotController * BotControllerManager : : findController ( Entity * ent )
{
int i ;
for ( i = 1 ; i < = controllers . NumObjects ( ) ; i + + ) {
BotController * controller = controllers . ObjectAt ( i ) ;
if ( controller - > getControlledEntity ( ) = = ent ) {
return controller ;
}
}
return nullptr ;
}
const Container < BotController * > & BotControllerManager : : getControllers ( ) const
{
return controllers ;
}
BotControllerManager : : ~ BotControllerManager ( )
{
Cleanup ( ) ;
}
void BotControllerManager : : Init ( )
{
BotController : : Init ( ) ;
}
void BotControllerManager : : Cleanup ( )
{
int i ;
BotController : : Init ( ) ;
for ( i = 1 ; i < = controllers . NumObjects ( ) ; i + + ) {
BotController * controller = controllers . ObjectAt ( i ) ;
delete controller ;
}
controllers . FreeObjectList ( ) ;
}
void BotControllerManager : : ThinkControllers ( )
{
int i ;
2025-01-16 22:28:37 +01:00
// Delete controllers that don't have associated player entity
// This cannot happen unless some mods remove them
for ( i = controllers . NumObjects ( ) ; i > 0 ; i - - ) {
BotController * controller = controllers . ObjectAt ( i ) ;
if ( ! controller - > getControlledEntity ( ) ) {
gi . DPrintf ( " Bot %d has no associated player entity. This shouldn't happen unless the entity has been removed by a script. The controller will be removed, please fix. \n " , i ) ;
// Remove the controller, it will be recreated later to match `sv_numbots`
delete controller ;
controllers . RemoveObjectAt ( i ) ;
}
}
2024-10-08 22:16:57 +02:00
for ( i = 1 ; i < = controllers . NumObjects ( ) ; i + + ) {
BotController * controller = controllers . ObjectAt ( i ) ;
controller - > Think ( ) ;
}
}