Initial bot commit

* Added server commands and cvars for adding AI players to the game.
* Added auto modes for automating the adding and removal of bots
* Bots connect to the server and join teams correctly
This commit is contained in:
RGreenlees 2023-09-21 20:35:18 +01:00 committed by pierow
parent 4b1ec43d81
commit 777025e794
11 changed files with 739 additions and 51 deletions

View file

@ -353,7 +353,12 @@ void ClientPutInServer( edict_t *pEntity )
// Allocate a CBasePlayer for pev, and call spawn
pPlayer->SetPlayMode(PLAYMODE_READYROOM);
//pPlayer->Spawn();
//if (pev->flags & FL_FAKECLIENT)
//{
// pPlayer->Spawn();
//}
// Reset interpolation during first frame
pPlayer->pev->effects |= EF_NOINTERP;

View file

@ -118,13 +118,15 @@ void WRITE_STRING(const char* inData);
#ifdef AVH_SERVER
#define WRITE_ENTITY (*g_engfuncs.pfnWriteEntity)
#define CVAR_REGISTER (*g_engfuncs.pfnCVarRegister)
#define CVAR_GET_FLOAT (*g_engfuncs.pfnCVarGetFloat)
#define CVAR_GET_STRING (*g_engfuncs.pfnCVarGetString)
#define CVAR_SET_FLOAT (*g_engfuncs.pfnCVarSetFloat)
#define CVAR_SET_STRING (*g_engfuncs.pfnCVarSetString)
#define CVAR_GET_POINTER (*g_engfuncs.pfnCVarGetPointer)
#define WRITE_ENTITY (*g_engfuncs.pfnWriteEntity)
#define CVAR_REGISTER (*g_engfuncs.pfnCVarRegister)
#define CVAR_GET_FLOAT (*g_engfuncs.pfnCVarGetFloat)
#define CVAR_GET_STRING (*g_engfuncs.pfnCVarGetString)
#define CVAR_SET_FLOAT (*g_engfuncs.pfnCVarSetFloat)
#define CVAR_SET_STRING (*g_engfuncs.pfnCVarSetString)
#define CVAR_GET_POINTER (*g_engfuncs.pfnCVarGetPointer)
#define REGISTER_SERVER_FUNCTION (*g_engfuncs.pfnAddServerCommand)
#define RUN_AI_MOVE (*g_engfuncs.pfnRunPlayerMove)
#endif // AVH_SERVER

View file

@ -123,6 +123,15 @@ cvar_t avh_mapvoteratio = {kvMapVoteRatio, ".6", FCVAR_SERVER};
cvar_t avh_blockscripts = {kvBlockScripts, "1", FCVAR_SERVER};
cvar_t avh_jumpmode = {kvJumpMode, "1", FCVAR_SERVER};
cvar_t avh_version = {kvVersion, "330", FCVAR_SERVER};
// AI Player Settings
cvar_t avh_botsenabled = { kvBotsEnabled,"0", FCVAR_SERVER }; // Bots can be added to the server Y/N
cvar_t avh_botautomode = { kvBotAutoMode,"0", FCVAR_SERVER }; // Defines automated behaviour for adding/removing bots
cvar_t avh_botminplayers = { kvBotMinPlayers,"0", FCVAR_SERVER }; // If bots are enabled and auto mode == 2 then it will maintain this player count by adding/removing as needed
cvar_t avh_botskill = { kvBotSkill,"0", FCVAR_SERVER }; // Sets the skill for the bots (0 = easiest, 3 = hardest)
cvar_t avh_botusemapdefaults = { kvBotUseMapDefaults,"0", FCVAR_SERVER }; // Defines automated behaviour for adding/removing bots
//playtest cvars
cvar_t avh_fastjp = {kvfastjp, "0", FCVAR_SERVER};
cvar_t avh_randomrfk = {kvRandomRfk, "1", FCVAR_SERVER};
@ -204,6 +213,13 @@ void GameDLLInit( void )
CVAR_REGISTER (&defaultteam);
CVAR_REGISTER (&allowmonsters);
CVAR_REGISTER (&mp_chattime);
// Register AI player settings
CVAR_REGISTER(&avh_botsenabled);
CVAR_REGISTER(&avh_botautomode);
CVAR_REGISTER(&avh_botminplayers);
CVAR_REGISTER(&avh_botusemapdefaults);
CVAR_REGISTER(&avh_botskill);
// Register AvH variables
CVAR_REGISTER (&avh_drawdamage);

View file

@ -3139,8 +3139,8 @@ void CBasePlayer::Spawn( void )
pev->takedamage = DAMAGE_AIM;
pev->solid = SOLID_SLIDEBOX;
pev->movetype = MOVETYPE_WALK;
pev->flags &= FL_PROXY; // keep proxy flag sey by engine
pev->flags |= FL_CLIENT;
pev->flags &= FL_PROXY | FL_FAKECLIENT; // keep proxy and fakeclient flags set by engine
pev->flags |= FL_CLIENT;
pev->air_finished = gpGlobals->time + 12;
pev->dmg = 2; // initial water damage
pev->effects = 0;

View file

@ -0,0 +1,56 @@
#ifndef AVH_AI_PLAYER_H
#define AVH_AI_PLAYER_H
#include "../AvHPlayer.h"
typedef struct AVH_AI_PLAYER
{
AvHPlayer* Player = nullptr;
edict_t* Edict = nullptr;
AvHTeamNumber Team = TEAM_IND;
float ForwardMove = 0.0f;
float SideMove = 0.0f;
float UpMove = 0.0f;
int Button = 0.0f;
int Impulse = 0.0f;
byte AdjustedMsec = 0;
float f_previous_command_time = 0.0f;
} AvHAIPlayer;
string BotNames[MAX_PLAYERS] = { "MrRobot",
"Wall-E",
"BeepBoop",
"Robotnik",
"JonnyAutomaton",
"Burninator",
"SteelDeath",
"Meatbag",
"Undertaker",
"Botini",
"Robottle",
"Rusty",
"HeavyMetal",
"Combot",
"BagelLover",
"Screwdriver",
"LoveBug",
"iSmash",
"Chippy",
"Baymax",
"BoomerBot",
"Jarvis",
"Marvin",
"Data",
"Scrappy",
"Mortis",
"TerrorHertz",
"Omicron",
"Herbie",
"Robogeddon",
"Velociripper",
"TerminalFerocity"
};
#endif

View file

@ -0,0 +1,477 @@
#include "AvHAIPlayerManager.h"
#include "AvHAIPlayer.h"
#include "../AvHGamerules.h"
#include "../dlls/client.h"
#include <time.h>
float last_think_time = 0.0f;
float BotDeltaTime = 0.01666667f;
AvHAIPlayer ActiveAIPlayers[MAX_PLAYERS];
extern cvar_t avh_botautomode;
extern cvar_t avh_botsenabled;
extern cvar_t avh_botminplayers;
extern cvar_t avh_botusemapdefaults;
float LastAIPlayerCountUpdate = 0.0f;
int BotNameIndex = 0;
void AIMGR_UpdateAIPlayerCounts()
{
// Don't add or remove bots too quickly, otherwise it can cause lag or even overflows
if (gpGlobals->time - LastAIPlayerCountUpdate < 0.2f) { return; }
// If game has ended, kick bots that have dropped back to the ready room
if (GetGameRules()->GetVictoryTeam() != TEAM_IND)
{
AIMGR_RemoveBotsInReadyRoom();
return;
}
LastAIPlayerCountUpdate = gpGlobals->time;
if (avh_botsenabled.value == 0)
{
if (AIMGR_GetNumAIPlayers() > 0)
{
AIMGR_RemoveAIPlayerFromTeam(0);
}
return;
}
if (avh_botautomode.value == 0) // Manual mode: do nothing, server can manually add/remove as they want
{
return;
}
if (avh_botautomode.value == 1) // Balance only: bots will only be added and removed to ensure teams remain balanced
{
AIMGR_UpdateTeamBalance();
return;
}
if (avh_botautomode.value == 2) // Fill teams: bots will be added and removed to maintain a minimum player count
{
AIMGR_UpdateFillTeams();
return;
}
}
void AIMGR_UpdateTeamBalance()
{
AvHTeamNumber teamA = GetGameRules()->GetTeamANumber();
AvHTeamNumber teamB = GetGameRules()->GetTeamBNumber();
// If team A has more players, then either remove a bot from team A, or add one to B to bring them in line
if (GetGameRules()->GetTeamAPlayerCount() > GetGameRules()->GetTeamBPlayerCount())
{
// Favour removing bots to balance teams over adding more
if (AIMGR_AIPlayerExistsOnTeam(teamA))
{
AIMGR_RemoveAIPlayerFromTeam(1);
return;
}
else
{
AIMGR_AddAIPlayerToTeam(2);
return;
}
}
// Do the same if team B outmatches team A
if (GetGameRules()->GetTeamBPlayerCount() > GetGameRules()->GetTeamAPlayerCount())
{
// Again, favour removing bots over adding more
if (AIMGR_AIPlayerExistsOnTeam(teamB))
{
AIMGR_RemoveAIPlayerFromTeam(2);
return;
}
else
{
AIMGR_AddAIPlayerToTeam(1);
return;
}
}
// If both teams are evenly matched, check to ensure we don't have bots on both sides. The purpose of balance mode
// is to maintain the minimum bots required to keep teams even, so get rid of extras if needed
if (AIMGR_AIPlayerExistsOnTeam(teamA) && AIMGR_AIPlayerExistsOnTeam(teamB))
{
AIMGR_RemoveAIPlayerFromTeam(teamB);
return;
}
}
void AIMGR_UpdateFillTeams()
{
AvHTeamNumber teamA = GetGameRules()->GetTeamANumber();
AvHTeamNumber teamB = GetGameRules()->GetTeamBNumber();
int TeamSizeA = GetGameRules()->GetTeamAPlayerCount();
int TeamSizeB = GetGameRules()->GetTeamBPlayerCount();
int TotalPlayers = TeamSizeA + TeamSizeB;
int NumDesiredPlayers = min(gpGlobals->maxClients, (int)floorf(avh_botminplayers.value));
// We have exceeded our minimum desired player count, start removing bots to make more room for humans
if (TotalPlayers > NumDesiredPlayers)
{
AIMGR_RemoveAIPlayerFromTeam(0);
return;
}
// Add bots to ensure we reach the number of desired players
if (TotalPlayers < NumDesiredPlayers)
{
AIMGR_AddAIPlayerToTeam(0);
return;
}
if (TeamSizeA > (TeamSizeB + 1))
{
if (AIMGR_AIPlayerExistsOnTeam(teamA))
{
AIMGR_RemoveAIPlayerFromTeam(1);
}
return;
}
if (TeamSizeB > (TeamSizeA + 1))
{
if (AIMGR_AIPlayerExistsOnTeam(teamB))
{
AIMGR_RemoveAIPlayerFromTeam(2);
}
return;
}
}
void AIMGR_RemoveAIPlayerFromTeam(int Team)
{
if (AIMGR_GetNumAIPlayers() == 0) { return; }
AvHTeamNumber DesiredTeam = TEAM_IND;
AvHTeamNumber teamA = GetGameRules()->GetTeamANumber();
AvHTeamNumber teamB = GetGameRules()->GetTeamBNumber();
if (Team > 0)
{
DesiredTeam = (Team == 1) ? teamA : teamB;
}
else
{
if (GetGameRules()->GetTeamAPlayerCount() > GetGameRules()->GetTeamBPlayerCount())
{
DesiredTeam = teamA;
}
else
{
DesiredTeam = teamB;
}
}
// We will go through the potential bots we could kick. We want to avoid kicking bots which have a lot of
// resources tied up in them or are commanding, which could cause big disruption to the team they're leaving
int MinValue = 0; // Track the least valuable bot on the desired team.
int IndexToKick = -1; // Current bot to be kicked
for (int i = 0; i < MAX_PLAYERS; i++)
{
// Don't kick if the slot is empty, or the bot in that slot isn't on the right team
if (!ActiveAIPlayers[i].Player || ActiveAIPlayers[i].Player->GetTeam() != DesiredTeam) { continue; }
AvHPlayer* theAIPlayer = ActiveAIPlayers[i].Player;
float BotValue = theAIPlayer->GetResources();
AvHPlayerClass theAIPlayerClass = (AvHPlayerClass)theAIPlayer->GetEffectivePlayerClass();
switch (theAIPlayerClass)
{
case PLAYERCLASS_COMMANDER:
BotValue += 1000.0f; // Ensure this guy isn't kicked unless he's the only bot on the team!
break;
case PLAYERCLASS_ALIVE_HEAVY_MARINE:
BotValue += kHeavyArmorCost;
break;
case PLAYERCLASS_ALIVE_JETPACK_MARINE:
BotValue += kJetpackCost;
break;
case PLAYERCLASS_ALIVE_LEVEL2:
BotValue += kGorgeCost;
break;
case PLAYERCLASS_ALIVE_LEVEL3:
BotValue += kLerkCost;
break;
case PLAYERCLASS_ALIVE_LEVEL4:
BotValue += kFadeCost;
break;
case PLAYERCLASS_ALIVE_LEVEL5:
BotValue += kOnosCost;
break;
case PLAYERCLASS_ALIVE_GESTATING:
BotValue += 10.0f;
break;
case PLAYERCLASS_DEAD_ALIEN:
case PLAYERCLASS_DEAD_MARINE:
BotValue -= 10.0f; // Favour kicking bots who are dead rather than alive
break;
case PLAYERCLASS_REINFORCING:
BotValue -= 5.0f;
break;
default:
break;
}
if (IndexToKick < 0 || BotValue < MinValue)
{
IndexToKick = i;
MinValue = BotValue;
}
}
if (IndexToKick > -1)
{
ActiveAIPlayers[IndexToKick].Player->Kick();
memset(&ActiveAIPlayers[IndexToKick], 0, sizeof(AvHAIPlayer));
}
}
void AIMGR_AddAIPlayerToTeam(int Team)
{
int NewBotIndex = -1;
edict_t* BotEnt = nullptr;
for (int i = 0; i < MAX_PLAYERS; i++)
{
if (!ActiveAIPlayers[i].Player)
{
NewBotIndex = i;
break;
}
}
if (NewBotIndex < 0)
{
ALERT(at_console, "Bot limit reached, cannot add more\n");
return;
}
if (AIMGR_GetNumAIPlayers() == 0)
{
// Initialise the name index to a random number so we don't always get the same bot names
BotNameIndex = RANDOM_LONG(0, 31);
}
AvHTeamNumber DesiredTeam = TEAM_IND;
AvHTeamNumber teamA = GetGameRules()->GetTeamANumber();
AvHTeamNumber teamB = GetGameRules()->GetTeamBNumber();
if (Team > 0)
{
DesiredTeam = (Team == 1) ? teamA : teamB;
}
// Retrieve the current bot name and then cycle the index so the names are always unique
string NewName = "[BOT]" + BotNames[BotNameIndex];
BotEnt = (*g_engfuncs.pfnCreateFakeClient)(NewName.c_str());
BotNameIndex++;
if (BotNameIndex > 31)
{
BotNameIndex = 0;
}
if (!BotEnt)
{
ALERT(at_console, "Failed to create AI player: server is full\n");
return;
}
char ptr[128]; // allocate space for message from ClientConnect
int clientIndex;
char* infobuffer = (*g_engfuncs.pfnGetInfoKeyBuffer)(BotEnt);
clientIndex = ENTINDEX(BotEnt);
(*g_engfuncs.pfnSetClientKeyValue)(clientIndex, infobuffer, "model", "");
(*g_engfuncs.pfnSetClientKeyValue)(clientIndex, infobuffer, "rate", "3500.000000");
(*g_engfuncs.pfnSetClientKeyValue)(clientIndex, infobuffer, "cl_updaterate", "20");
(*g_engfuncs.pfnSetClientKeyValue)(clientIndex, infobuffer, "cl_lw", "0");
(*g_engfuncs.pfnSetClientKeyValue)(clientIndex, infobuffer, "cl_lc", "0");
(*g_engfuncs.pfnSetClientKeyValue)(clientIndex, infobuffer, "tracker", "0");
(*g_engfuncs.pfnSetClientKeyValue)(clientIndex, infobuffer, "cl_dlmax", "128");
(*g_engfuncs.pfnSetClientKeyValue)(clientIndex, infobuffer, "lefthand", "1");
(*g_engfuncs.pfnSetClientKeyValue)(clientIndex, infobuffer, "friends", "0");
(*g_engfuncs.pfnSetClientKeyValue)(clientIndex, infobuffer, "dm", "0");
(*g_engfuncs.pfnSetClientKeyValue)(clientIndex, infobuffer, "ah", "1");
(*g_engfuncs.pfnSetClientKeyValue)(clientIndex, infobuffer, "_vgui_menus", "0");
ClientConnect(BotEnt, STRING(BotEnt->v.netname), "127.0.0.1", ptr);
ClientPutInServer(BotEnt);
BotEnt->v.flags |= FL_FAKECLIENT; // Shouldn't be needed but just to be sure
BotEnt->v.idealpitch = BotEnt->v.v_angle.x;
BotEnt->v.ideal_yaw = BotEnt->v.v_angle.y;
BotEnt->v.pitch_speed = 270; // slightly faster than HLDM of 225
BotEnt->v.yaw_speed = 250; // slightly faster than HLDM of 210
AvHPlayer* theNewAIPlayer = GetClassPtr((AvHPlayer*)&BotEnt->v);
if (theNewAIPlayer)
{
if (DesiredTeam != TEAM_IND)
{
ALERT(at_console, "Adding AI Player to team: %d\n", (int)Team);
GetGameRules()->AttemptToJoinTeam(theNewAIPlayer, DesiredTeam, false);
}
else
{
ALERT(at_console, "Auto-assigning AI Player to team\n");
GetGameRules()->AutoAssignPlayer(theNewAIPlayer);
}
ActiveAIPlayers[NewBotIndex].Player = theNewAIPlayer;
ActiveAIPlayers[NewBotIndex].Edict = BotEnt;
ActiveAIPlayers[NewBotIndex].Team = theNewAIPlayer->GetTeam();
}
else
{
ALERT(at_console, "Failed to create AI player: invalid AvHPlayer instance\n");
}
}
byte BotThrottledMsec(AvHAIPlayer* inAIPlayer)
{
// Thanks to The Storm (ePODBot) for this one, finally fixed the bot running speed!
int newmsec = (int)((gpGlobals->time - inAIPlayer->f_previous_command_time) * 1000);
if (newmsec > 255)
{
newmsec = 255;
}
return (byte)newmsec;
}
void AIMGR_UpdateAIPlayers()
{
// If bots are not enabled then do nothing
if (avh_botsenabled.value == 0) { return; }
static clock_t prevtime = 0.0f;
static clock_t currTime = 0.0f;
currTime = clock();
double timeSinceLastThink = (double)((currTime - last_think_time) / CLOCKS_PER_SEC);
if (IS_DEDICATED_SERVER() || timeSinceLastThink >= BOT_MIN_FRAME_TIME)
{
BotDeltaTime = timeSinceLastThink;
for (int bot_index = 0; bot_index < MAX_PLAYERS; bot_index++)
{
if (!ActiveAIPlayers[bot_index].Player) { continue; } // Slot isn't filled
AvHAIPlayer* bot = &ActiveAIPlayers[bot_index];
// Needed to correctly handle client prediction and physics calculations
byte adjustedmsec = BotThrottledMsec(bot);
// save the command time
bot->f_previous_command_time = gpGlobals->time;
// Simulate PM_PlayerMove so client prediction and stuff can be executed correctly.
RUN_AI_MOVE(bot->Edict, bot->Edict->v.v_angle, bot->ForwardMove,
bot->SideMove, bot->UpMove, bot->Button, bot->Impulse, adjustedmsec);
}
}
prevtime = currTime;
}
float AIMGR_GetBotDeltaTime()
{
return BotDeltaTime;
}
int AIMGR_GetNumAIPlayers()
{
int Result = 0;
for (int i = 0; i < MAX_PLAYERS; i++)
{
if (ActiveAIPlayers[i].Player != nullptr)
{
Result++;
}
}
return Result;
}
int AIMGR_GetNumAIPlayersOnTeam(AvHTeamNumber Team)
{
int Result = 0;
for (int i = 0; i < MAX_PLAYERS; i++)
{
if (ActiveAIPlayers[i].Player != nullptr && ActiveAIPlayers[i].Team == Team)
{
Result++;
}
}
return Result;
}
int AIMGR_AIPlayerExistsOnTeam(AvHTeamNumber Team)
{
for (int i = 0; i < MAX_PLAYERS; i++)
{
if (ActiveAIPlayers[i].Player != nullptr && ActiveAIPlayers[i].Team == Team)
{
return true;
}
}
return false;
}
void AIMGR_RemoveBotsInReadyRoom()
{
for (int i = 0; i < MAX_PLAYERS; i++)
{
if (ActiveAIPlayers[i].Player != nullptr && ActiveAIPlayers[i].Player->GetInReadyRoom())
{
ActiveAIPlayers[i].Player->Kick();
memset(&ActiveAIPlayers[i], 0, sizeof(AvHAIPlayer));
}
}
}

View file

@ -0,0 +1,22 @@
#ifndef AVH_AI_PLAYER_MANAGER_H
#define AVH_AI_PLAYER_MANAGER_H
#include "../AvHConstants.h"
// Max rate bot can run its logic, default is 1/60th second. WARNING: Increasing the rate past 100hz causes bots to move and turn slowly due to GoldSrc limits!
static const double BOT_MIN_FRAME_TIME = (1.0 / 60.0);
void AIMGR_AddAIPlayerToTeam(int Team);
void AIMGR_RemoveAIPlayerFromTeam(int Team);
void AIMGR_UpdateAIPlayers();
void AIMGR_RemoveBotsInReadyRoom();
float AIMGR_GetBotDeltaTime();
void AIMGR_UpdateAIPlayerCounts();
void AIMGR_UpdateTeamBalance();
void AIMGR_UpdateFillTeams();
int AIMGR_GetNumAIPlayers();
int AIMGR_AIPlayerExistsOnTeam(AvHTeamNumber Team);
#endif

View file

@ -333,14 +333,11 @@ BOOL AvHGamerules::ClientCommand( CBasePlayer *pPlayer, const char *pcmd )
{
if(theAvHPlayer)
{
if(!(theAvHPlayer->pev->flags & FL_FAKECLIENT))
if (!theAvHPlayer->GetIsBeingDigested())
{
if(!theAvHPlayer->GetIsBeingDigested())
{
theAvHPlayer->SetPlayMode(PLAYMODE_READYROOM, true);
}
theSuccess = true;
theAvHPlayer->SetPlayMode(PLAYMODE_READYROOM, true);
}
theSuccess = true;
}
theSuccess = true;
}
@ -348,26 +345,23 @@ BOOL AvHGamerules::ClientCommand( CBasePlayer *pPlayer, const char *pcmd )
{
if(theAvHPlayer)
{
if(!(theAvHPlayer->pev->flags & FL_FAKECLIENT))
if (!theAvHPlayer->GetIsBeingDigested())
{
if(!theAvHPlayer->GetIsBeingDigested())
// : 984
// Add a throttle on the readyroom key
const static int kReadyRoomThrottleTimeout = 2.0f;
if ((theAvHPlayer->GetTimeLastF4() == -1.0f) ||
(gpGlobals->time > theAvHPlayer->GetTimeLastF4() + kReadyRoomThrottleTimeout))
{
// : 984
// Add a throttle on the readyroom key
const static int kReadyRoomThrottleTimeout=2.0f;
if ( (theAvHPlayer->GetTimeLastF4() == -1.0f) ||
(gpGlobals->time > theAvHPlayer->GetTimeLastF4() + kReadyRoomThrottleTimeout) )
{
theAvHPlayer->SendMessage(kReadyRoomThrottleMessage);
theAvHPlayer->SetTimeLastF4(gpGlobals->time);
}
else if ( gpGlobals->time < theAvHPlayer->GetTimeLastF4() + kReadyRoomThrottleTimeout )
{
theAvHPlayer->SetPlayMode(PLAYMODE_READYROOM, true);
}
theAvHPlayer->SendMessage(kReadyRoomThrottleMessage);
theAvHPlayer->SetTimeLastF4(gpGlobals->time);
}
else if (gpGlobals->time < theAvHPlayer->GetTimeLastF4() + kReadyRoomThrottleTimeout)
{
theAvHPlayer->SetPlayMode(PLAYMODE_READYROOM, true);
}
theSuccess = true;
}
theSuccess = true;
}
theSuccess = true;
}
@ -1369,24 +1363,12 @@ BOOL AvHGamerules::ClientCommand( CBasePlayer *pPlayer, const char *pcmd )
#ifdef WIN32
else if(FStrEq(pcmd, "createfake"))
{
if(this->GetCheatsEnabled())
if(!theAvHPlayer || theIsServerOp || theIsPlaytest || theIsDedicatedServer || this->GetCheatsEnabled())
{
char theFakeClientName[256];
sprintf(theFakeClientName, "Bot%d", RANDOM_LONG(0, 2000));
edict_t* BotEnt = (*g_engfuncs.pfnCreateFakeClient)(theFakeClientName);
// create the player entity by calling MOD's player function
// (from LINK_ENTITY_TO_CLASS for player object)
player( VARS(BotEnt) );
char ptr[128]; // allocate space for message from ClientConnect
ClientConnect( BotEnt, theFakeClientName, "127.0.0.1", ptr );
// Pieter van Dijk - use instead of DispatchSpawn() - Hip Hip Hurray!
ClientPutInServer( BotEnt );
BotEnt->v.flags |= FL_FAKECLIENT;
this->CreateAIPlayer(TEAM_IND);
}
return true;
}
#endif
#endif

View file

@ -201,6 +201,7 @@
#include "AvHNetworkMessages.h"
#include "AvHNexusServer.h"
#include "AvHParticleTemplateClient.h"
#include "AIPlayers/AvHAIPlayerManager.h"
// : 0001073
#ifdef USE_OLDAUTH
@ -231,6 +232,11 @@ extern cvar_t avh_mapvoteratio;
extern cvar_t avh_structurelimit;
extern cvar_t avh_version;
extern cvar_t avh_botsenabled;
extern cvar_t avh_botautomode;
extern cvar_t avh_botminplayers;
extern cvar_t avh_botskill;
BOOL IsSpawnPointValid( CBaseEntity *pPlayer, CBaseEntity *pSpot );
inline int FNullEnt( CBaseEntity *ent ) { return (ent == NULL) || FNullEnt( ent->edict() ); }
//extern void ResetCachedEntities();
@ -245,7 +251,6 @@ extern cvar_t avh_countdowntime;
extern int gCommanderPointsAwardedEventID;
extern cvar_t avh_networkmeterrate;
std::string gPlayerNames[128];
cvar_t* cocName;
cvar_t* cocExp;
int gStartPlayerID = 0;
@ -267,6 +272,7 @@ int kProfileRunConfig = 0xFFFFFFFF;
std::string GetLogStringForPlayer( edict_t *pEntity );
const AvHMapExtents& GetMapExtents()
{
return GetGameRules()->GetMapExtents();
@ -342,6 +348,49 @@ AvHGamerules::AvHGamerules() : mTeamA(TEAM_ONE), mTeamB(TEAM_TWO)
RegisterServerVariable(&avh_fastjp);
RegisterServerVariable(&avh_randomrfk);
RegisterServerVariable(&avh_parasiteonmap);
RegisterServerVariable(&avh_botsenabled);
RegisterServerVariable(&avh_botautomode);
RegisterServerVariable(&avh_botminplayers);
RegisterServerVariable(&avh_botskill);
REGISTER_SERVER_FUNCTION("sv_addaiplayer", []()
{
if (avh_botsenabled.value == 0)
{
return;
}
int DesiredTeam = 0;
if (CMD_ARGC() >= 2)
{
const char* TeamInput = CMD_ARGV(1);
if (TeamInput != NULL)
{
DesiredTeam = atoi(TeamInput);
}
}
AIMGR_AddAIPlayerToTeam(DesiredTeam);
});
REGISTER_SERVER_FUNCTION("sv_removeaiplayer", []()
{
int DesiredTeam = 0;
if (CMD_ARGC() >= 2)
{
const char* TeamInput = CMD_ARGV(1);
if (TeamInput != NULL)
{
DesiredTeam = atoi(TeamInput);
}
}
AIMGR_RemoveAIPlayerFromTeam(DesiredTeam);
});
g_VoiceGameMgr.Init(&gVoiceHelper, gpGlobals->maxClients);
@ -1205,6 +1254,30 @@ bool AvHGamerules::GetArePlayersAllowedToJoinImmediately(void) const
return thePlayerIsAllowedToJoinImmediately;
}
bool AvHGamerules::GetTeamHasRoomToJoin(AvHTeamNumber inTeamNumber) const
{
AvHTeamNumber teamA = this->mTeamA.GetTeamNumber();
AvHTeamNumber teamB = this->mTeamB.GetTeamNumber();
AvHTeamNumber theOtherTeamNumber = (inTeamNumber == teamA) ? teamB : teamA;
const AvHTeam* theTeam = this->GetTeam(inTeamNumber);
const AvHTeam* theOtherTeam = this->GetTeam(theOtherTeamNumber);
if (theTeam && theOtherTeam)
{
int theWouldBeNumPlayersOnTeam = theTeam->GetPlayerCount();
int theWouldBeNumPlayersOnOtherTeam = theOtherTeam->GetPlayerCount();
int theDiscrepancyAllowed = max(1.0f, avh_limitteams.value);
if (((theWouldBeNumPlayersOnTeam - theWouldBeNumPlayersOnOtherTeam) <= theDiscrepancyAllowed) || this->GetIsTournamentMode() || this->GetCheatsEnabled())
{
return true;
}
}
return false;
}
bool AvHGamerules::GetCanJoinTeamInFuture(AvHPlayer* inPlayer, AvHTeamNumber inTeamNumber, string& outString) const
{
// You can switch teams, unless
@ -1438,6 +1511,21 @@ bool AvHGamerules::GetIsTrainingMode(void) const
#endif
}
bool AvHGamerules::GetBotsEnabled(void) const
{
return (ns_cvar_float(&avh_botsenabled) > 0);
}
int AvHGamerules::GetBotMinPlayerCount(void) const
{
return (int)ns_cvar_float(&avh_botminplayers);
}
int AvHGamerules::GetBotSkill(void) const
{
return (int)ns_cvar_float(&avh_botskill);
}
AvHMapMode AvHGamerules::GetMapMode(void) const
{
return this->mMapMode;
@ -1600,6 +1688,26 @@ const AvHTeam* AvHGamerules::GetTeamB(void) const
return &this->mTeamB;
}
AvHTeamNumber AvHGamerules::GetTeamANumber()
{
return this->mTeamA.GetTeamNumber();
}
AvHTeamNumber AvHGamerules::GetTeamBNumber()
{
return this->mTeamB.GetTeamNumber();
}
int AvHGamerules::GetTeamAPlayerCount()
{
return this->mTeamA.GetPlayerCount();
}
int AvHGamerules::GetTeamBPlayerCount()
{
return this->mTeamB.GetPlayerCount();
}
AvHTeam* AvHGamerules::GetTeam(AvHTeamNumber inTeamNumber)
{
AvHTeam* theTeam = NULL;
@ -3492,6 +3600,9 @@ void AvHGamerules::Think(void)
if(GET_RUN_CODE(4))
{
AIMGR_UpdateAIPlayerCounts();
AIMGR_UpdateAIPlayers();
if(!this->GetGameStarted())
{
if(!this->GetIsTournamentMode())
@ -4544,4 +4655,3 @@ void AvHGamerules::BalanceChanged()
END_FOR_ALL_ENTITIES(kAvHPlayerClassName)
}

View file

@ -228,6 +228,7 @@ public:
bool GetIsTesting(void) const;
bool GetIsValidFutureTeam(AvHPlayer inPlayer, int inTeamNumber) const;
bool GetCanJoinTeamInFuture(AvHPlayer* inPlayer, AvHTeamNumber theTeamNumber, string& outString) const;
bool GetTeamHasRoomToJoin(AvHTeamNumber theTeamNumber) const;
const AvHBaseInfoLocationListType& GetInfoLocations() const;
int GetMaxWeight(void) const;
const char* GetSpawnEntityName(AvHPlayer* inPlayer) const;
@ -236,9 +237,14 @@ public:
int GetTimeLimit() const;
int GetWeightForItemAndAmmo(AvHWeaponID inWeapon, int inNumRounds) const;
bool AttemptToJoinTeam(AvHPlayer* inPlayer, AvHTeamNumber theTeamNumber, bool inDisplayErrorMessage = true);
void AutoAssignPlayer(AvHPlayer* inPlayer);
const AvHTeam* GetTeam(AvHTeamNumber inTeamNumber) const;
const AvHTeam* GetTeamA() const;
const AvHTeam* GetTeamB() const;
AvHTeamNumber GetTeamANumber();
AvHTeamNumber GetTeamBNumber();
int GetTeamAPlayerCount();
int GetTeamBPlayerCount();
AvHTeam* GetTeam(AvHTeamNumber inTeamNumber);
AvHTeam* GetTeamA();
AvHTeam* GetTeamB();
@ -266,6 +272,12 @@ public:
virtual bool GetIsNSMode(void) const;
virtual bool GetIsTrainingMode(void) const;
virtual bool GetBotsEnabled(void) const;
virtual int GetBotMinPlayerCount(void) const;
virtual int GetBotSkill(void) const;
int GetBaseHealthForMessageID(AvHMessageID inMessageID) const;
int GetBuildTimeForMessageID(AvHMessageID inMessageID) const;
int GetCostForMessageID(AvHMessageID inMessageID) const;
@ -292,7 +304,7 @@ public:
int GetStructureLimit();
void RemoveEntityUnderAttack(int entIndex);
protected:
void AutoAssignPlayer(AvHPlayer* inPlayer);
void PerformMapValidityCheck();
virtual void RecalculateMapMode( void );
bool GetDeathMatchMode(void) const;

View file

@ -142,6 +142,12 @@ float ns_cvar_float(const cvar_t *cvar);
#define kvIronMan "mp_ironman"
#define kvIronManTime "mp_ironmantime"
#define kvBotsEnabled "mp_botsenabled"
#define kvBotMinPlayers "mp_botminplayers"
#define kvBotUseMapDefaults "mp_botusemapdefaults"
#define kvBotSkill "mp_botskill"
#define kvBotAutoMode "mp_botautomode"
#define kvEasterEggChance "mp_eastereggchance"
#define kvUplink "mp_uplink"
#define kvMapVoteRatio "mp_mapvoteratio"