2022-02-27 07:53:44 -05:00
# include <STDInclude.hpp>
2023-02-13 15:33:26 -05:00
2022-12-26 07:07:24 -05:00
# include "Bots.hpp"
2023-04-15 06:19:56 -04:00
# include "ClanTags.hpp"
2022-12-26 07:07:24 -05:00
2022-07-06 11:48:40 -04:00
# include "GSC/Script.hpp"
2017-01-19 16:23:59 -05:00
2023-01-09 03:37:56 -05:00
// From Quake-III
2023-04-19 09:53:07 -04:00
# define ANGLE2SHORT(x) ((int)((x) * (USHRT_MAX + 1) / 360.0f) & USHRT_MAX)
# define SHORT2ANGLE(x) ((x)* (360.0f / (USHRT_MAX + 1)))
2023-01-09 03:37:56 -05:00
2017-01-19 16:23:59 -05:00
namespace Components
{
2023-04-15 06:19:56 -04:00
constexpr std : : size_t MAX_NAME_LENGTH = 16 ;
2023-04-16 05:27:19 -04:00
const Game : : dvar_t * Bots : : sv_randomBotNames ;
const Game : : dvar_t * Bots : : sv_replaceBots ;
2023-03-25 19:14:31 -04:00
2023-04-26 09:57:54 -04:00
std : : size_t Bots : : BotDataIndex ;
2023-04-20 13:20:00 -04:00
2022-01-23 21:00:30 -05:00
struct BotMovementInfo
2020-11-14 03:47:35 -05:00
{
2022-11-29 09:18:10 -05:00
std : : int32_t buttons ; // Actions
std : : int8_t forward ;
std : : int8_t right ;
std : : uint16_t weapon ;
2022-02-14 13:14:07 -05:00
bool active ;
2022-01-23 14:32:20 -05:00
} ;
2020-11-14 03:47:35 -05:00
2023-03-18 18:08:23 -04:00
static BotMovementInfo g_botai [ Game : : MAX_CLIENTS ] ;
2020-11-14 03:47:35 -05:00
2022-01-23 14:32:20 -05:00
struct BotAction
2020-11-14 03:47:35 -05:00
{
2022-04-09 10:29:58 -04:00
std : : string action ;
2022-11-29 09:18:10 -05:00
std : : int32_t key ;
2020-11-14 03:47:35 -05:00
} ;
2022-01-23 14:32:20 -05:00
static const BotAction BotActions [ ] =
2020-11-14 03:47:35 -05:00
{
2022-12-15 10:13:49 -05:00
{ " gostand " , Game : : CMD_BUTTON_UP } ,
{ " gocrouch " , Game : : CMD_BUTTON_CROUCH } ,
{ " goprone " , Game : : CMD_BUTTON_PRONE } ,
{ " fire " , Game : : CMD_BUTTON_ATTACK } ,
{ " melee " , Game : : CMD_BUTTON_MELEE } ,
{ " frag " , Game : : CMD_BUTTON_FRAG } ,
{ " smoke " , Game : : CMD_BUTTON_OFFHAND_SECONDARY } ,
{ " reload " , Game : : CMD_BUTTON_RELOAD } ,
{ " sprint " , Game : : CMD_BUTTON_SPRINT } ,
{ " leanleft " , Game : : CMD_BUTTON_LEAN_LEFT } ,
{ " leanright " , Game : : CMD_BUTTON_LEAN_RIGHT } ,
{ " ads " , Game : : CMD_BUTTON_ADS } ,
{ " holdbreath " , Game : : CMD_BUTTON_BREATH } ,
{ " usereload " , Game : : CMD_BUTTON_USE_RELOAD } ,
{ " activate " , Game : : CMD_BUTTON_ACTIVATE } ,
2020-11-14 03:47:35 -05:00
} ;
2023-04-20 13:20:00 -04:00
std : : vector < Bots : : botData > Bots : : LoadBotNames ( )
2023-03-25 19:14:31 -04:00
{
2023-04-20 13:20:00 -04:00
std : : vector < botData > result ;
2023-03-25 19:14:31 -04:00
2023-02-13 15:33:26 -05:00
FileSystem : : File bots ( " bots.txt " ) ;
if ( ! bots . exists ( ) )
{
2023-04-20 13:20:00 -04:00
return result ;
2023-02-13 15:33:26 -05:00
}
auto data = Utils : : String : : Split ( bots . getBuffer ( ) , ' \n ' ) ;
for ( auto & entry : data )
{
2023-04-20 13:20:00 -04:00
// Take into account CR line endings
2023-02-13 15:33:26 -05:00
Utils : : String : : Replace ( entry , " \r " , " " ) ;
// Remove whitespace
Utils : : String : : Trim ( entry ) ;
2022-12-24 17:14:47 -05:00
2023-02-13 15:33:26 -05:00
if ( entry . empty ( ) )
{
continue ;
}
2017-01-19 16:23:59 -05:00
2023-02-13 15:33:26 -05:00
std : : string clanAbbrev ;
// Check if there is a clan tag
if ( const auto pos = entry . find ( ' , ' ) ; pos ! = std : : string : : npos )
{
// Only start copying over from non-null characters (otherwise it can be "<=")
if ( ( pos + 1 ) < entry . size ( ) )
{
2023-04-19 09:53:07 -04:00
clanAbbrev = entry . substr ( pos + 1 , ClanTags : : MAX_CLAN_NAME_LENGTH - 1 ) ;
2017-01-19 16:23:59 -05:00
}
2023-02-13 15:33:26 -05:00
entry = entry . substr ( 0 , pos ) ;
2017-01-19 16:23:59 -05:00
}
2023-02-13 15:33:26 -05:00
2023-04-19 09:53:07 -04:00
entry = entry . substr ( 0 , MAX_NAME_LENGTH - 1 ) ;
2023-04-15 06:19:56 -04:00
2023-04-20 13:20:00 -04:00
result . emplace_back ( entry , clanAbbrev ) ;
2023-02-13 15:33:26 -05:00
}
2023-03-25 19:14:31 -04:00
2023-04-20 13:20:00 -04:00
return result ;
2023-02-13 15:33:26 -05:00
}
int Bots : : BuildConnectString ( char * buffer , const char * connectString , int num , int , int protocol , int checksum , int statVer , int statStuff , int port )
{
std : : string botName ;
std : : string clanName ;
2023-04-20 13:20:00 -04:00
static const auto botNames = [ ] ( ) - > std : : vector < botData >
2023-02-13 15:33:26 -05:00
{
2023-04-20 13:20:00 -04:00
auto names = LoadBotNames ( ) ;
if ( sv_randomBotNames - > current . enabled )
{
std : : random_device rd ;
std : : mt19937 gen ( rd ( ) ) ;
std : : ranges : : shuffle ( names , gen ) ;
}
return names ;
} ( ) ;
2017-01-19 16:23:59 -05:00
2023-04-20 13:20:00 -04:00
if ( ! botNames . empty ( ) )
2020-11-14 04:37:58 -05:00
{
2023-04-26 09:57:54 -04:00
BotDataIndex % = botNames . size ( ) ;
const auto index = BotDataIndex + + ;
2023-04-20 13:20:00 -04:00
botName = botNames [ index ] . first ;
clanName = botNames [ index ] . second ;
2020-11-14 04:37:58 -05:00
}
else
{
2023-04-20 13:32:27 -04:00
botName = std : : format ( " bot{} " , num ) ;
2023-02-13 15:33:26 -05:00
clanName = " BOT " s ;
2017-01-19 16:23:59 -05:00
}
2023-02-13 15:33:26 -05:00
return _snprintf_s ( buffer , 0x400 , _TRUNCATE , connectString , num , botName . data ( ) , clanName . data ( ) , protocol , checksum , statVer , statStuff , port ) ;
2017-01-19 16:23:59 -05:00
}
2022-01-23 14:32:20 -05:00
void Bots : : Spawn ( unsigned int count )
2017-04-23 07:31:48 -04:00
{
2022-06-04 08:59:14 -04:00
for ( std : : size_t i = 0 ; i < count ; + + i )
2017-04-23 07:31:48 -04:00
{
2022-05-05 10:03:14 -04:00
Scheduler : : Once ( [ ]
2017-04-23 07:31:48 -04:00
{
2022-01-07 16:00:44 -05:00
auto * ent = Game : : SV_AddTestClient ( ) ;
2023-03-09 13:39:10 -05:00
if ( ! ent )
{
2022-01-07 16:00:44 -05:00
return ;
2023-03-09 13:39:10 -05:00
}
2022-01-07 16:00:44 -05:00
2022-06-04 08:59:14 -04:00
Scheduler : : Once ( [ ent ]
2017-04-23 07:31:48 -04:00
{
2022-01-07 16:00:44 -05:00
Game : : Scr_AddString ( " autoassign " ) ;
Game : : Scr_AddString ( " team_marinesopfor " ) ;
2022-11-27 14:20:07 -05:00
Game : : Scr_Notify ( ent , static_cast < std : : uint16_t > ( Game : : SL_GetString ( " menuresponse " , 0 ) ) , 2 ) ;
2022-01-07 16:00:44 -05:00
2022-06-04 08:59:14 -04:00
Scheduler : : Once ( [ ent ]
2017-04-24 15:14:08 -04:00
{
2023-04-16 04:47:02 -04:00
Game : : Scr_AddString ( Utils : : String : : Format ( " class{} " , std : : rand ( ) % 5 ) ) ;
2022-01-07 16:00:44 -05:00
Game : : Scr_AddString ( " changeclass " ) ;
2022-11-27 14:20:07 -05:00
Game : : Scr_Notify ( ent , static_cast < std : : uint16_t > ( Game : : SL_GetString ( " menuresponse " , 0 ) ) , 2 ) ;
2022-05-05 10:03:14 -04:00
} , Scheduler : : Pipeline : : SERVER , 1 s ) ;
} , Scheduler : : Pipeline : : SERVER , 1 s ) ;
} , Scheduler : : Pipeline : : SERVER , 500 ms * ( i + 1 ) ) ;
2017-04-23 07:31:48 -04:00
}
}
2022-11-24 10:30:06 -05:00
void Bots : : GScr_isTestClient ( const Game : : scr_entref_t entref )
2022-05-05 18:48:33 -04:00
{
2022-11-24 10:30:06 -05:00
const auto * ent = Game : : GetEntity ( entref ) ;
if ( ! ent - > client )
{
Game : : Scr_Error ( " isTestClient: entity must be a player entity " ) ;
return ;
}
2022-05-06 19:49:29 -04:00
Game : : Scr_AddBool ( Game : : SV_IsTestClient ( ent - > s . number ) ! = 0 ) ;
2022-05-05 18:48:33 -04:00
}
2023-03-18 18:08:23 -04:00
void Bots : : AddScriptMethods ( )
2020-11-14 03:58:05 -05:00
{
2023-03-05 08:14:47 -05:00
GSC : : Script : : AddMethMultiple ( GScr_isTestClient , false , { " IsTestClient " , " IsBot " } ) ; // Usage: self IsTestClient();
2022-05-05 18:48:33 -04:00
2023-03-28 15:06:46 -04:00
GSC : : Script : : AddMethod ( " BotStop " , [ ] ( const Game : : scr_entref_t entref ) // Usage: <bot> BotStop();
2020-11-14 04:20:56 -05:00
{
2023-03-10 15:55:22 -05:00
const auto * ent = GSC : : Script : : Scr_GetPlayerEntity ( entref ) ;
2023-03-18 18:08:23 -04:00
if ( ! Game : : SV_IsTestClient ( ent - > s . number ) )
2020-11-14 04:20:56 -05:00
{
2023-03-12 07:40:01 -04:00
Game : : Scr_Error ( " BotStop: Can only call on a bot! " ) ;
2020-11-14 04:20:56 -05:00
return ;
}
2022-09-08 11:11:54 -04:00
ZeroMemory ( & g_botai [ entref . entnum ] , sizeof ( BotMovementInfo ) ) ;
2022-01-17 19:21:25 -05:00
g_botai [ entref . entnum ] . weapon = 1 ;
2022-04-14 12:04:34 -04:00
g_botai [ entref . entnum ] . active = true ;
2020-11-14 04:20:56 -05:00
} ) ;
2023-03-28 15:06:46 -04:00
GSC : : Script : : AddMethod ( " BotWeapon " , [ ] ( const Game : : scr_entref_t entref ) // Usage: <bot> BotWeapon(<str>);
2020-11-14 04:20:56 -05:00
{
2023-03-10 15:55:22 -05:00
const auto * ent = GSC : : Script : : Scr_GetPlayerEntity ( entref ) ;
2023-03-18 18:08:23 -04:00
if ( ! Game : : SV_IsTestClient ( ent - > s . number ) )
2020-11-14 04:20:56 -05:00
{
2023-03-12 07:40:01 -04:00
Game : : Scr_Error ( " BotWeapon: Can only call on a bot! " ) ;
2020-11-14 04:20:56 -05:00
return ;
}
2022-05-06 19:49:29 -04:00
const auto * weapon = Game : : Scr_GetString ( 0 ) ;
2023-03-09 13:39:10 -05:00
if ( ! weapon | | ! * weapon )
2020-11-14 04:20:56 -05:00
{
2022-01-17 19:21:25 -05:00
g_botai [ entref . entnum ] . weapon = 1 ;
2020-11-14 04:20:56 -05:00
return ;
}
2022-01-07 16:00:44 -05:00
const auto weapId = Game : : G_GetWeaponIndexForName ( weapon ) ;
2022-01-23 21:00:30 -05:00
g_botai [ entref . entnum ] . weapon = static_cast < uint16_t > ( weapId ) ;
2022-02-14 13:14:07 -05:00
g_botai [ entref . entnum ] . active = true ;
2020-11-14 04:20:56 -05:00
} ) ;
2023-03-28 15:06:46 -04:00
GSC : : Script : : AddMethod ( " BotAction " , [ ] ( const Game : : scr_entref_t entref ) // Usage: <bot> BotAction(<str action>);
2020-11-14 04:20:56 -05:00
{
2023-03-10 15:55:22 -05:00
const auto * ent = GSC : : Script : : Scr_GetPlayerEntity ( entref ) ;
2023-03-18 18:08:23 -04:00
if ( ! Game : : SV_IsTestClient ( ent - > s . number ) )
2022-01-23 14:32:20 -05:00
{
2023-03-12 07:40:01 -04:00
Game : : Scr_Error ( " BotAction: Can only call on a bot! " ) ;
2022-01-23 14:32:20 -05:00
return ;
}
2022-05-06 19:49:29 -04:00
const auto * action = Game : : Scr_GetString ( 0 ) ;
2023-03-09 13:39:10 -05:00
if ( ! action )
2020-11-14 04:20:56 -05:00
{
2023-03-12 07:40:01 -04:00
Game : : Scr_ParamError ( 0 , " BotAction: Illegal parameter! " ) ;
2020-11-14 04:20:56 -05:00
return ;
}
2022-01-07 16:00:44 -05:00
2020-11-14 04:20:56 -05:00
if ( action [ 0 ] ! = ' + ' & & action [ 0 ] ! = ' - ' )
{
2023-03-12 07:40:01 -04:00
Game : : Scr_ParamError ( 0 , " BotAction: Sign for action must be '+' or '-' " ) ;
2020-11-14 04:20:56 -05:00
return ;
}
2022-07-06 11:48:40 -04:00
for ( std : : size_t i = 0 ; i < std : : extent_v < decltype ( BotActions ) > ; + + i )
2020-11-14 04:20:56 -05:00
{
2022-04-09 10:29:58 -04:00
if ( Utils : : String : : ToLower ( & action [ 1 ] ) ! = BotActions [ i ] . action )
2020-11-14 04:20:56 -05:00
continue ;
if ( action [ 0 ] = = ' + ' )
2022-01-17 19:21:25 -05:00
g_botai [ entref . entnum ] . buttons | = BotActions [ i ] . key ;
2020-11-14 04:20:56 -05:00
else
2022-04-09 10:29:58 -04:00
g_botai [ entref . entnum ] . buttons & = ~ BotActions [ i ] . key ;
2020-11-14 04:20:56 -05:00
2022-02-14 13:14:07 -05:00
g_botai [ entref . entnum ] . active = true ;
2020-11-14 04:20:56 -05:00
return ;
}
2023-03-12 07:40:01 -04:00
Game : : Scr_ParamError ( 0 , " BotAction: Unknown action " ) ;
2020-11-14 04:20:56 -05:00
} ) ;
2023-03-28 15:06:46 -04:00
GSC : : Script : : AddMethod ( " BotMovement " , [ ] ( const Game : : scr_entref_t entref ) // Usage: <bot> BotMovement(<int>, <int>);
2020-11-14 04:20:56 -05:00
{
2023-03-10 15:55:22 -05:00
const auto * ent = GSC : : Script : : Scr_GetPlayerEntity ( entref ) ;
2023-03-18 18:08:23 -04:00
if ( ! Game : : SV_IsTestClient ( ent - > s . number ) )
2020-11-14 04:20:56 -05:00
{
2023-03-12 07:40:01 -04:00
Game : : Scr_Error ( " BotMovement: Can only call on a bot! " ) ;
2020-11-14 04:20:56 -05:00
return ;
}
2022-05-06 19:49:29 -04:00
const auto forwardInt = std : : clamp < int > ( Game : : Scr_GetInt ( 0 ) , std : : numeric_limits < char > : : min ( ) , std : : numeric_limits < char > : : max ( ) ) ;
const auto rightInt = std : : clamp < int > ( Game : : Scr_GetInt ( 1 ) , std : : numeric_limits < char > : : min ( ) , std : : numeric_limits < char > : : max ( ) ) ;
2020-11-14 03:58:05 -05:00
2022-01-17 19:21:25 -05:00
g_botai [ entref . entnum ] . forward = static_cast < int8_t > ( forwardInt ) ;
g_botai [ entref . entnum ] . right = static_cast < int8_t > ( rightInt ) ;
2022-02-14 13:14:07 -05:00
g_botai [ entref . entnum ] . active = true ;
2020-11-14 04:20:56 -05:00
} ) ;
2020-11-14 03:58:05 -05:00
}
2023-05-03 12:31:24 -04:00
void Bots : : BotAiAction ( Game : : client_s * cl )
2017-01-19 16:23:59 -05:00
{
2023-03-09 13:39:10 -05:00
if ( ! cl - > gentity )
{
2022-01-24 07:15:33 -05:00
return ;
2023-03-09 13:39:10 -05:00
}
2020-11-14 03:44:59 -05:00
2022-02-14 13:14:07 -05:00
// Keep test client functionality
2023-04-07 05:52:46 -04:00
if ( ! g_botai [ cl - Game : : svs_clients ] . active )
2022-03-21 14:55:35 -04:00
{
Game : : SV_BotUserMove ( cl ) ;
2022-02-14 13:14:07 -05:00
return ;
2022-03-21 14:55:35 -04:00
}
2022-02-14 13:14:07 -05:00
2022-11-29 09:18:10 -05:00
Game : : usercmd_s userCmd ;
ZeroMemory ( & userCmd , sizeof ( Game : : usercmd_s ) ) ;
2022-02-14 13:14:07 -05:00
2022-03-21 14:55:35 -04:00
userCmd . serverTime = * Game : : svs_time ;
2022-01-23 21:00:30 -05:00
2023-04-07 05:52:46 -04:00
userCmd . buttons = g_botai [ cl - Game : : svs_clients ] . buttons ;
userCmd . forwardmove = g_botai [ cl - Game : : svs_clients ] . forward ;
userCmd . rightmove = g_botai [ cl - Game : : svs_clients ] . right ;
userCmd . weapon = g_botai [ cl - Game : : svs_clients ] . weapon ;
2022-01-23 21:00:30 -05:00
2023-01-09 03:37:56 -05:00
userCmd . angles [ 0 ] = ANGLE2SHORT ( ( cl - > gentity - > client - > ps . viewangles [ 0 ] - cl - > gentity - > client - > ps . delta_angles [ 0 ] ) ) ;
userCmd . angles [ 1 ] = ANGLE2SHORT ( ( cl - > gentity - > client - > ps . viewangles [ 1 ] - cl - > gentity - > client - > ps . delta_angles [ 1 ] ) ) ;
userCmd . angles [ 2 ] = ANGLE2SHORT ( ( cl - > gentity - > client - > ps . viewangles [ 2 ] - cl - > gentity - > client - > ps . delta_angles [ 2 ] ) ) ;
2022-03-21 14:55:35 -04:00
Game : : SV_ClientThink ( cl , & userCmd ) ;
2022-01-23 21:00:30 -05:00
}
2020-11-14 04:05:00 -05:00
2022-03-15 18:49:58 -04:00
__declspec ( naked ) void Bots : : SV_BotUserMove_Hk ( )
2022-01-23 21:00:30 -05:00
{
__asm
2020-11-14 04:05:00 -05:00
{
2022-01-24 07:03:35 -05:00
pushad
2020-11-14 04:05:00 -05:00
2022-01-24 07:15:33 -05:00
push edi
2022-11-29 09:18:10 -05:00
call BotAiAction
2022-01-24 07:15:33 -05:00
add esp , 4
2022-01-24 07:03:35 -05:00
2022-01-23 21:00:30 -05:00
popad
ret
}
}
2020-11-14 04:05:00 -05:00
2022-03-21 14:55:35 -04:00
void Bots : : G_SelectWeaponIndex ( int clientNum , int iWeaponIndex )
{
if ( g_botai [ clientNum ] . active )
{
g_botai [ clientNum ] . weapon = static_cast < uint16_t > ( iWeaponIndex ) ;
2022-01-23 21:00:30 -05:00
}
}
2020-11-14 04:05:00 -05:00
2022-03-21 14:55:35 -04:00
__declspec ( naked ) void Bots : : G_SelectWeaponIndex_Hk ( )
2022-01-23 21:00:30 -05:00
{
2022-03-21 14:55:35 -04:00
__asm
{
pushad
push [ esp + 0x20 + 0x8 ]
push [ esp + 0x20 + 0x8 ]
2022-11-29 09:18:10 -05:00
call G_SelectWeaponIndex
2022-03-21 14:55:35 -04:00
add esp , 0x8
popad
// Code skipped by hook
mov eax , [ esp + 0x8 ]
push eax
push 0x441B85
retn
}
}
2022-02-27 12:36:13 -05:00
2023-04-07 05:52:46 -04:00
int Bots : : SV_GetClientPing_Hk ( const int clientNum )
{
AssertIn ( clientNum , Game : : MAX_CLIENTS ) ;
if ( Game : : SV_IsTestClient ( clientNum ) )
{
return - 1 ;
}
return Game : : svs_clients [ clientNum ] . ping ;
}
2023-04-16 06:09:47 -04:00
bool Bots : : IsFull ( )
{
auto i = 0 ;
while ( i < * Game : : svs_clientCount )
{
if ( Game : : svs_clients [ i ] . header . state = = Game : : CS_FREE )
{
// Free slot was found
break ;
}
+ + i ;
}
return i = = * Game : : svs_clientCount ;
}
2023-04-16 05:27:19 -04:00
void Bots : : SV_DirectConnect_Full_Check ( )
{
2023-04-16 06:30:12 -04:00
if ( ! sv_replaceBots - > current . enabled | | ! IsFull ( ) )
2023-04-16 05:27:19 -04:00
{
return ;
}
for ( auto i = 0 ; i < ( * Game : : sv_maxclients ) - > current . integer ; + + i )
{
auto * cl = & Game : : svs_clients [ i ] ;
if ( cl - > bIsTestClient )
{
Game : : SV_DropClient ( cl , " EXE_DISCONNECTED " , false ) ;
2023-04-16 06:30:12 -04:00
cl - > header . state = Game : : CS_FREE ;
2023-04-16 05:27:19 -04:00
return ;
}
}
}
2023-04-16 07:01:58 -04:00
void Bots : : CleanBotArray ( )
{
ZeroMemory ( & g_botai , sizeof ( g_botai ) ) ;
for ( std : : size_t i = 0 ; i < std : : extent_v < decltype ( g_botai ) > ; + + i )
{
g_botai [ i ] . weapon = 1 ; // Prevent the bots from defaulting to the 'none' weapon
}
}
2023-05-06 09:20:56 -04:00
void Bots : : AddServerCommands ( )
2022-01-23 21:00:30 -05:00
{
2023-05-06 09:20:56 -04:00
Command : : AddSV ( " spawnBot " , [ ] ( const Command : : Params * params )
2017-04-23 07:31:48 -04:00
{
2023-03-11 19:02:15 -05:00
if ( ! Dedicated : : IsRunning ( ) )
{
Logger : : Print ( " Server is not running. \n " ) ;
return ;
}
2023-04-16 06:09:47 -04:00
if ( IsFull ( ) )
{
Logger : : Warning ( Game : : CON_CHANNEL_DONT_FILTER , " Server is full. \n " ) ;
return ;
}
2023-03-11 19:02:15 -05:00
std : : size_t count = 1 ;
2017-04-23 07:31:48 -04:00
2022-03-17 14:50:20 -04:00
if ( params - > size ( ) > 1 )
2017-04-23 07:31:48 -04:00
{
2022-01-07 16:00:44 -05:00
if ( params - > get ( 1 ) = = " all " s )
2022-01-23 14:32:20 -05:00
{
2023-03-11 19:02:15 -05:00
count = Game : : MAX_CLIENTS ;
2022-01-23 14:32:20 -05:00
}
2022-01-07 16:00:44 -05:00
else
2022-01-23 14:32:20 -05:00
{
2022-04-12 08:34:51 -04:00
char * end ;
2022-01-23 14:32:20 -05:00
const auto * input = params - > get ( 1 ) ;
2022-04-12 08:34:51 -04:00
count = std : : strtoul ( input , & end , 10 ) ;
2022-01-23 14:32:20 -05:00
2022-04-12 08:34:51 -04:00
if ( input = = end )
2022-01-23 14:32:20 -05:00
{
2023-05-06 09:20:56 -04:00
Logger : : Warning ( Game : : CON_CHANNEL_DONT_FILTER , " {} is not a valid input \n Usage: {} optional <number of bots> or optional < \" all \" > \n " , input , params - > get ( 0 ) ) ;
2022-04-09 10:29:58 -04:00
return ;
2022-01-23 14:32:20 -05:00
}
}
2017-04-23 07:31:48 -04:00
}
2023-03-11 19:02:15 -05:00
count = std : : min ( Game : : MAX_CLIENTS , count ) ;
2017-04-23 07:31:48 -04:00
2023-03-11 19:02:15 -05:00
Logger : : Print ( " Spawning {} {} " , count , ( count = = 1 ? " bot " : " bots " ) ) ;
2017-04-24 15:14:08 -04:00
2022-11-29 09:18:10 -05:00
Spawn ( count ) ;
2017-04-23 07:31:48 -04:00
} ) ;
2023-05-06 09:20:56 -04:00
}
Bots : : Bots ( )
{
AssertOffset ( Game : : client_s , bIsTestClient , 0x41AF0 ) ;
AssertOffset ( Game : : client_s , ping , 0x212C8 ) ;
AssertOffset ( Game : : client_s , gentity , 0x212A0 ) ;
// Replace connect string
Utils : : Hook : : Set < const char * > ( 0x48ADA6 , " connect bot%d \" \\ cg_predictItems \\ 1 \\ cl_anonymous \\ 0 \\ color \\ 4 \\ head \\ default \\ model \\ multi \\ snaps \\ 20 \\ rate \\ 5000 \\ name \\ %s \\ clanAbbrev \\ %s \\ protocol \\ %d \\ checksum \\ %d \\ statver \\ %d %u \\ qport \\ %d \" " ) ;
// Intercept sprintf for the connect string
Utils : : Hook ( 0x48ADAB , BuildConnectString , HOOK_CALL ) . install ( ) - > quick ( ) ;
Utils : : Hook ( 0x627021 , SV_BotUserMove_Hk , HOOK_CALL ) . install ( ) - > quick ( ) ;
Utils : : Hook ( 0x627241 , SV_BotUserMove_Hk , HOOK_CALL ) . install ( ) - > quick ( ) ;
Utils : : Hook ( 0x441B80 , G_SelectWeaponIndex_Hk , HOOK_JUMP ) . install ( ) - > quick ( ) ;
Utils : : Hook ( 0x459654 , SV_GetClientPing_Hk , HOOK_CALL ) . install ( ) - > quick ( ) ;
sv_randomBotNames = Game : : Dvar_RegisterBool ( " sv_randomBotNames " , false , Game : : DVAR_NONE , " Randomize the bots' names " ) ;
sv_replaceBots = Game : : Dvar_RegisterBool ( " sv_replaceBots " , false , Game : : DVAR_NONE , " Test clients will be replaced by connecting players when the server is full. " ) ;
// Reset BotMovementInfo.active when client is dropped
Events : : OnClientDisconnect ( [ ] ( const int clientNum ) - > void
{
g_botai [ clientNum ] . active = false ;
} ) ;
Events : : OnSVInit ( AddServerCommands ) ;
CleanBotArray ( ) ;
2020-11-14 03:58:05 -05:00
2023-03-18 18:08:23 -04:00
AddScriptMethods ( ) ;
2022-04-09 10:29:58 -04:00
2022-04-09 10:54:54 -04:00
// In case a loaded mod didn't call "BotStop" before the VM shutdown
2023-04-16 07:01:58 -04:00
Events : : OnVMShutdown ( CleanBotArray ) ;
2017-01-19 16:23:59 -05:00
}
}