2016-03-27 11:49:47 +02:00
|
|
|
/*
|
|
|
|
===========================================================================
|
2024-10-06 22:57:39 +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
|
|
|
|
===========================================================================
|
|
|
|
*/
|
|
|
|
// g_bot.cpp
|
|
|
|
|
|
|
|
#include "g_local.h"
|
|
|
|
#include "entity.h"
|
|
|
|
#include "playerbot.h"
|
2023-08-19 17:58:54 +02:00
|
|
|
#include "g_bot.h"
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2024-10-02 19:42:03 +02:00
|
|
|
static saved_bot_t *saved_bots = NULL;
|
2024-10-05 13:54:53 +02:00
|
|
|
static unsigned int num_saved_bots = 0;
|
2024-10-06 22:57:39 +02:00
|
|
|
static unsigned int botId = 0;
|
2023-08-19 16:03:42 +02:00
|
|
|
|
2024-10-02 19:42:03 +02:00
|
|
|
Container<str> alliedModelList;
|
|
|
|
Container<str> germanModelList;
|
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
IsAlliedPlayerModel
|
|
|
|
|
|
|
|
Return whether or not the specified filename is for allies
|
|
|
|
============
|
|
|
|
*/
|
2024-10-02 19:42:03 +02:00
|
|
|
bool IsAlliedPlayerModel(const char *filename)
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
2024-12-25 14:50:07 +01:00
|
|
|
return !Q_stricmpn(filename, "allied_", 7) || !Q_stricmpn(filename, "american_", 9);
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
IsGermanPlayerModel
|
|
|
|
|
|
|
|
Return whether or not the specified filename is for axis
|
|
|
|
============
|
|
|
|
*/
|
2024-10-02 19:42:03 +02:00
|
|
|
bool IsGermanPlayerModel(const char *filename)
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
2024-12-25 14:50:07 +01:00
|
|
|
return !Q_stricmpn(filename, "german_", 7) || !Q_stricmpn(filename, "IT_", 3) || !Q_stricmpn(filename, "SC_", 3);
|
2024-10-02 19:42:03 +02:00
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
IsPlayerModel
|
|
|
|
|
|
|
|
Return whether or not the specified filename
|
|
|
|
is a player model that can be chosen
|
|
|
|
============
|
|
|
|
*/
|
2024-10-02 19:42:03 +02:00
|
|
|
bool IsPlayerModel(const char *filename)
|
|
|
|
{
|
|
|
|
size_t len = strlen(filename);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2024-10-02 19:42:03 +02:00
|
|
|
if (len >= 8 && !Q_stricmp(&filename[len - 8], "_fps.tik")) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2024-10-02 19:42:03 +02:00
|
|
|
if (!IsAlliedPlayerModel(filename) && !IsGermanPlayerModel(filename)) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2024-10-02 19:42:03 +02:00
|
|
|
return true;
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
2023-08-19 16:03:42 +02:00
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
ClearModelList
|
|
|
|
|
|
|
|
Clear the allied and axis model list
|
|
|
|
============
|
|
|
|
*/
|
2024-10-02 19:42:03 +02:00
|
|
|
void ClearModelList()
|
2023-08-19 16:03:42 +02:00
|
|
|
{
|
2024-10-02 19:42:03 +02:00
|
|
|
alliedModelList.FreeObjectList();
|
|
|
|
germanModelList.FreeObjectList();
|
2023-08-19 16:03:42 +02:00
|
|
|
}
|
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
InitModelList
|
|
|
|
|
|
|
|
Initialize the list of allied and axis player models
|
|
|
|
that bots can use
|
|
|
|
============
|
|
|
|
*/
|
2024-10-02 19:42:03 +02:00
|
|
|
void InitModelList()
|
2023-08-19 16:03:42 +02:00
|
|
|
{
|
2024-10-02 19:42:03 +02:00
|
|
|
char **fileList;
|
|
|
|
int numFiles;
|
|
|
|
int i;
|
|
|
|
size_t numAlliedModels = 0, numGermanModels = 0;
|
|
|
|
byte *p;
|
|
|
|
|
|
|
|
ClearModelList();
|
|
|
|
|
|
|
|
fileList = gi.FS_ListFiles("models/player", ".tik", qfalse, &numFiles);
|
|
|
|
|
|
|
|
for (i = 0; i < numFiles; i++) {
|
|
|
|
const char *filename = fileList[i];
|
|
|
|
|
|
|
|
if (!IsPlayerModel(filename)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (IsAlliedPlayerModel(filename)) {
|
|
|
|
numAlliedModels++;
|
|
|
|
} else {
|
|
|
|
numGermanModels++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
alliedModelList.Resize(numAlliedModels);
|
|
|
|
germanModelList.Resize(numGermanModels);
|
|
|
|
|
|
|
|
for (i = 0; i < numFiles; i++) {
|
|
|
|
const char *filename = fileList[i];
|
|
|
|
size_t len = strlen(filename);
|
|
|
|
|
|
|
|
if (!IsPlayerModel(filename)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (IsAlliedPlayerModel(filename)) {
|
2024-12-25 14:50:07 +01:00
|
|
|
alliedModelList.AddObject(str(filename, 0, len - 4));
|
2024-10-02 19:42:03 +02:00
|
|
|
} else {
|
2024-12-25 14:50:07 +01:00
|
|
|
germanModelList.AddObject(str(filename, 0, len - 4));
|
2024-10-02 19:42:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
gi.FS_FreeFileList(fileList);
|
|
|
|
}
|
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
G_BotBegin
|
|
|
|
|
|
|
|
Begin spawning a new bot entity
|
|
|
|
============
|
|
|
|
*/
|
2024-10-02 19:42:03 +02:00
|
|
|
void G_BotBegin(gentity_t *ent)
|
|
|
|
{
|
2024-10-08 22:16:57 +02:00
|
|
|
PlayerBot *player;
|
|
|
|
BotController *controller;
|
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
level.spawn_entnum = ent->s.number;
|
2024-10-08 22:16:57 +02:00
|
|
|
player = new PlayerBot;
|
2024-10-06 22:57:39 +02:00
|
|
|
|
2024-10-02 19:42:03 +02:00
|
|
|
G_ClientBegin(ent, NULL);
|
2024-10-08 22:16:57 +02:00
|
|
|
|
|
|
|
controller = botManager.getControllerManager().createController(player);
|
|
|
|
player->setController(controller);
|
2024-10-02 19:42:03 +02:00
|
|
|
}
|
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
G_BotThink
|
|
|
|
|
|
|
|
Called each server frame to make bots think
|
|
|
|
============
|
|
|
|
*/
|
2024-10-02 19:42:03 +02:00
|
|
|
void G_BotThink(gentity_t *ent, int msec)
|
|
|
|
{
|
2024-10-08 22:16:57 +02:00
|
|
|
/*
|
2024-10-02 19:42:03 +02:00
|
|
|
usercmd_t ucmd;
|
|
|
|
usereyes_t eyeinfo;
|
|
|
|
PlayerBot *bot;
|
|
|
|
|
|
|
|
assert(ent);
|
|
|
|
assert(ent->entity);
|
|
|
|
assert(ent->entity->IsSubclassOfBot());
|
|
|
|
|
|
|
|
bot = (PlayerBot *)ent->entity;
|
|
|
|
|
|
|
|
bot->UpdateBotStates();
|
|
|
|
bot->GetUsercmd(&ucmd);
|
|
|
|
bot->GetEyeInfo(&eyeinfo);
|
|
|
|
|
|
|
|
G_ClientThink(ent, &ucmd, &eyeinfo);
|
2024-10-08 22:16:57 +02:00
|
|
|
*/
|
2024-10-02 19:42:03 +02:00
|
|
|
}
|
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
G_FindFreeEntityForBot
|
|
|
|
|
|
|
|
Find a free client slot
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
gentity_t *G_FindFreeEntityForBot()
|
|
|
|
{
|
|
|
|
gentity_t *ent;
|
|
|
|
int minNum = 0;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (sv_sharedbots->integer) {
|
|
|
|
minNum = 0;
|
|
|
|
} else {
|
|
|
|
minNum = maxclients->integer;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = minNum; i < game.maxclients; i++) {
|
|
|
|
ent = &g_entities[i];
|
|
|
|
if (!ent->inuse && ent->client && !ent->client->pers.userinfo[0]) {
|
|
|
|
return ent;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===========
|
|
|
|
G_ChangeParent
|
|
|
|
|
|
|
|
Fix parenting for entities that use the old number
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void G_ChangeParent(int oldNum, int newNum)
|
|
|
|
{
|
|
|
|
gentity_t *ent;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < game.maxentities; i++) {
|
|
|
|
ent = &g_entities[i];
|
|
|
|
if (!ent->inuse || !ent->entity) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ent->s.parent == oldNum) {
|
|
|
|
ent->s.parent = newNum;
|
|
|
|
}
|
|
|
|
if (ent->r.ownerNum == oldNum) {
|
|
|
|
ent->r.ownerNum = newNum;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===========
|
|
|
|
G_BotShift
|
|
|
|
|
|
|
|
If the specified slot is used, the bot will be relocated
|
|
|
|
to the next free entity slot
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void G_BotShift(int clientNum)
|
|
|
|
{
|
|
|
|
gentity_t *ent;
|
|
|
|
gentity_t *newEnt;
|
|
|
|
|
|
|
|
ent = &g_entities[clientNum];
|
|
|
|
if (!ent->inuse || !ent->client || !ent->entity) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ent->entity->IsSubclassOfBot()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
newEnt = G_FindFreeEntityForBot();
|
|
|
|
if (!newEnt) {
|
|
|
|
G_RemoveBot(ent);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Allocate the new entity
|
|
|
|
//
|
|
|
|
level.spawn_entnum = newEnt - g_entities;
|
|
|
|
level.AllocEdict(ent->entity);
|
|
|
|
|
|
|
|
//
|
|
|
|
// Copy all fields
|
|
|
|
//
|
|
|
|
newEnt->s = ent->s;
|
|
|
|
newEnt->s.number = newEnt - g_entities;
|
|
|
|
memcpy(newEnt->client, ent->client, sizeof(*newEnt->client));
|
|
|
|
newEnt->r = ent->r;
|
|
|
|
newEnt->solid = ent->solid;
|
|
|
|
newEnt->tiki = ent->tiki;
|
|
|
|
AxisCopy(ent->mat, newEnt->mat);
|
|
|
|
|
|
|
|
newEnt->freetime = ent->freetime;
|
|
|
|
newEnt->spawntime = ent->spawntime;
|
|
|
|
newEnt->radius2 = ent->radius2;
|
|
|
|
memcpy(newEnt->entname, ent->entname, sizeof(newEnt->entname));
|
|
|
|
newEnt->clipmask = ent->clipmask;
|
|
|
|
newEnt->entity = ent->entity;
|
|
|
|
newEnt->entity->edict = newEnt;
|
|
|
|
newEnt->entity->client = newEnt->client;
|
|
|
|
newEnt->entity->entnum = newEnt->s.number;
|
|
|
|
newEnt->client->ps.clientNum = newEnt->s.number;
|
|
|
|
|
|
|
|
G_ChangeParent(ent->s.number, newEnt->s.number);
|
|
|
|
|
|
|
|
//
|
|
|
|
// Free the old entity so the real client will use it
|
|
|
|
//
|
|
|
|
level.FreeEdict(ent);
|
|
|
|
memset(ent->client, 0, sizeof(*ent->client));
|
|
|
|
|
|
|
|
G_SetClientConfigString(newEnt);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===========
|
|
|
|
G_GetFirstBot
|
|
|
|
|
|
|
|
Return the first bot
|
|
|
|
============
|
|
|
|
*/
|
2024-10-02 19:42:03 +02:00
|
|
|
gentity_t *G_GetFirstBot()
|
|
|
|
{
|
2024-10-06 22:57:39 +02:00
|
|
|
gentity_t *ent;
|
|
|
|
unsigned int n;
|
|
|
|
|
|
|
|
for (n = 0; n < game.maxclients; n++) {
|
|
|
|
ent = &g_entities[n];
|
|
|
|
if (G_IsBot(ent)) {
|
|
|
|
return ent;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
2024-10-02 19:42:03 +02:00
|
|
|
}
|
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
G_IsBot
|
|
|
|
|
|
|
|
Return whether or not the gentity is a bot
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
bool G_IsBot(gentity_t *ent)
|
|
|
|
{
|
|
|
|
if (!ent->inuse || !ent->client) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ent->entity || !ent->entity->IsSubclassOfBot()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===========
|
|
|
|
G_IsPlayer
|
|
|
|
|
|
|
|
Return whether or not the gentity is a player
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
bool G_IsPlayer(gentity_t *ent)
|
|
|
|
{
|
|
|
|
if (!ent->inuse || !ent->client) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ent->entity || ent->entity->IsSubclassOfBot()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-12-25 15:08:57 +01:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
G_GetRandomAlliedPlayerModel
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
const char* G_GetRandomAlliedPlayerModel()
|
|
|
|
{
|
|
|
|
if (!alliedModelList.NumObjects()) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
const unsigned int index = rand() % alliedModelList.NumObjects();
|
|
|
|
return alliedModelList[index];
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===========
|
|
|
|
G_GetRandomGermanPlayerModel
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
const char* G_GetRandomGermanPlayerModel()
|
|
|
|
{
|
|
|
|
if (!germanModelList.NumObjects()) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
const unsigned int index = rand() % germanModelList.NumObjects();
|
|
|
|
return germanModelList[index];
|
|
|
|
}
|
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
G_AddBot
|
|
|
|
|
|
|
|
Add the specified bot, optionally its saved state
|
|
|
|
============
|
|
|
|
*/
|
2024-10-11 22:54:40 +02:00
|
|
|
void G_AddBot(const saved_bot_t *saved)
|
2024-10-02 19:42:03 +02:00
|
|
|
{
|
|
|
|
int i;
|
2024-10-06 22:57:39 +02:00
|
|
|
int clientNum;
|
2024-10-02 19:42:03 +02:00
|
|
|
gentity_t *e;
|
|
|
|
char botName[MAX_NETNAME];
|
|
|
|
char challenge[MAX_STRING_TOKENS];
|
|
|
|
Event *teamEv;
|
2024-10-06 22:57:39 +02:00
|
|
|
char userinfo[MAX_INFO_STRING] {0};
|
2024-10-02 19:42:03 +02:00
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
e = G_FindFreeEntityForBot();
|
|
|
|
if (!e) {
|
|
|
|
gi.Printf("No free slot for a bot\n");
|
|
|
|
return;
|
|
|
|
}
|
2024-10-02 19:42:03 +02:00
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
clientNum = e - g_entities;
|
2024-10-02 19:42:03 +02:00
|
|
|
|
2024-10-11 22:54:40 +02:00
|
|
|
// increase the unique ID
|
|
|
|
botId++;
|
|
|
|
|
|
|
|
if (saved) {
|
|
|
|
G_BotConnect(clientNum, qfalse, saved->userinfo);
|
|
|
|
G_BotBegin(e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
if (gi.Argc() > 2) {
|
|
|
|
Q_strncpyz(botName, gi.Argv(2), sizeof(botName));
|
|
|
|
} else {
|
|
|
|
Com_sprintf(botName, sizeof(botName), "bot%d", botId);
|
|
|
|
}
|
2023-08-19 16:03:42 +02:00
|
|
|
|
2024-10-11 22:54:40 +02:00
|
|
|
Info_SetValueForKey(userinfo, "name", botName);
|
2023-08-19 16:03:42 +02:00
|
|
|
|
2024-10-11 22:54:40 +02:00
|
|
|
//
|
|
|
|
// Choose a random model
|
|
|
|
//
|
2024-12-25 15:08:57 +01:00
|
|
|
Info_SetValueForKey(userinfo, "dm_playermodel", G_GetRandomAlliedPlayerModel());
|
|
|
|
Info_SetValueForKey(userinfo, "dm_playergermanmodel", G_GetRandomGermanPlayerModel());
|
2023-08-19 16:03:42 +02:00
|
|
|
|
2024-10-11 22:54:40 +02:00
|
|
|
Info_SetValueForKey(userinfo, "fov", "80");
|
|
|
|
Info_SetValueForKey(userinfo, "ip", "localhost");
|
2024-10-02 19:42:03 +02:00
|
|
|
|
2024-10-11 22:54:40 +02:00
|
|
|
// Connect the bot for the first time
|
|
|
|
// setup user info and stuff
|
|
|
|
G_BotConnect(clientNum, qtrue, userinfo);
|
2023-08-19 16:03:42 +02:00
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
G_BotBegin(e);
|
|
|
|
}
|
2023-08-19 17:58:54 +02:00
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
G_AddBots
|
2023-08-19 16:03:42 +02:00
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
Add the specified number of bots
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void G_AddBots(unsigned int num)
|
|
|
|
{
|
|
|
|
int n;
|
2023-08-19 16:03:42 +02:00
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
for (n = 0; n < num; n++) {
|
|
|
|
G_AddBot(NULL);
|
|
|
|
}
|
|
|
|
}
|
2023-08-19 16:03:42 +02:00
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
G_RemoveBot
|
2023-08-19 16:03:42 +02:00
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
Remove the specified bot
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void G_RemoveBot(gentity_t *ent)
|
|
|
|
{
|
2024-10-08 22:16:57 +02:00
|
|
|
if (ent->entity) {
|
|
|
|
BotController *controller = botManager.getControllerManager().findController(ent->entity);
|
|
|
|
|
|
|
|
botManager.getControllerManager().removeController(controller);
|
|
|
|
}
|
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
G_ClientDisconnect(ent);
|
2023-08-19 16:03:42 +02:00
|
|
|
}
|
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
G_RemoveBots
|
|
|
|
|
|
|
|
Remove the specified number of bots
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void G_RemoveBots(unsigned int num)
|
2023-08-19 16:03:42 +02:00
|
|
|
{
|
2024-10-05 13:54:53 +02:00
|
|
|
unsigned int removed = 0;
|
|
|
|
unsigned int n;
|
2024-10-06 22:57:39 +02:00
|
|
|
unsigned int teamCount[2] {0};
|
|
|
|
bool bNoMoreToRemove = false;
|
2024-10-02 19:42:03 +02:00
|
|
|
|
2024-10-06 18:46:02 +02:00
|
|
|
teamCount[0] = dmManager.GetTeamAllies()->m_players.NumObjects();
|
|
|
|
teamCount[1] = dmManager.GetTeamAxis()->m_players.NumObjects();
|
|
|
|
|
|
|
|
while (!bNoMoreToRemove) {
|
|
|
|
bNoMoreToRemove = true;
|
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
// First remove bots that are in the team
|
|
|
|
// with the higest player count
|
2024-10-06 18:46:02 +02:00
|
|
|
for (n = 0; n < game.maxclients && removed < num; n++) {
|
2024-10-06 22:57:39 +02:00
|
|
|
gentity_t *e = &g_entities[n];
|
|
|
|
if (!G_IsBot(e)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
Player *player = static_cast<Player *>(e->entity);
|
|
|
|
if (player->GetTeam() == TEAM_ALLIES || player->GetTeam() == TEAM_AXIS) {
|
|
|
|
unsigned int teamIndex = (player->GetTeam() - TEAM_ALLIES);
|
|
|
|
if (teamCount[teamIndex] < teamCount[1 - teamIndex]) {
|
|
|
|
// Not enough players in that team, don't remove the bot
|
|
|
|
continue;
|
2024-10-06 18:46:02 +02:00
|
|
|
}
|
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
teamCount[teamIndex]--;
|
|
|
|
bNoMoreToRemove = false;
|
2024-10-06 18:46:02 +02:00
|
|
|
}
|
2024-10-06 22:57:39 +02:00
|
|
|
|
|
|
|
G_RemoveBot(e);
|
|
|
|
removed++;
|
2024-10-06 18:46:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
2024-10-06 22:57:39 +02:00
|
|
|
// Remove all bots that haven't been removed earlier
|
2024-10-06 18:46:02 +02:00
|
|
|
//
|
2024-10-05 13:54:53 +02:00
|
|
|
for (n = 0; n < game.maxclients && removed < num; n++) {
|
2024-10-06 22:57:39 +02:00
|
|
|
gentity_t *e = &g_entities[n];
|
|
|
|
if (!G_IsBot(e)) {
|
|
|
|
continue;
|
2024-10-02 19:42:03 +02:00
|
|
|
}
|
2024-10-06 22:57:39 +02:00
|
|
|
|
|
|
|
G_RemoveBot(e);
|
|
|
|
removed++;
|
2024-10-02 19:42:03 +02:00
|
|
|
}
|
2023-08-19 16:03:42 +02:00
|
|
|
}
|
2023-08-19 17:58:54 +02:00
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
G_SaveBots
|
|
|
|
|
|
|
|
Save bot persistent data
|
|
|
|
============
|
|
|
|
*/
|
2024-10-02 19:42:03 +02:00
|
|
|
void G_SaveBots()
|
|
|
|
{
|
2024-10-11 22:54:40 +02:00
|
|
|
unsigned int count;
|
2024-10-02 19:42:03 +02:00
|
|
|
unsigned int n;
|
2023-08-19 17:58:54 +02:00
|
|
|
|
|
|
|
if (saved_bots) {
|
|
|
|
delete[] saved_bots;
|
2024-10-02 19:42:03 +02:00
|
|
|
saved_bots = NULL;
|
2023-08-19 17:58:54 +02:00
|
|
|
}
|
|
|
|
|
2025-01-16 22:27:51 +01:00
|
|
|
const BotControllerManager& manager = botManager.getControllerManager();
|
|
|
|
unsigned int numSpawnedBots = manager.getControllers().NumObjects();
|
|
|
|
|
|
|
|
if (!numSpawnedBots) {
|
2024-10-02 19:42:03 +02:00
|
|
|
return;
|
|
|
|
}
|
2023-08-19 17:58:54 +02:00
|
|
|
|
2025-01-16 22:27:51 +01:00
|
|
|
saved_bots = new saved_bot_t[numSpawnedBots];
|
2024-10-05 13:54:53 +02:00
|
|
|
num_saved_bots = 0;
|
2024-10-11 22:54:40 +02:00
|
|
|
|
|
|
|
|
|
|
|
count = manager.getControllers().NumObjects();
|
2025-01-16 22:27:51 +01:00
|
|
|
assert(count <= numSpawnedBots);
|
2024-10-11 22:54:40 +02:00
|
|
|
|
|
|
|
for (n = 1; n <= count; n++) {
|
|
|
|
const BotController *controller = manager.getControllers().ObjectAt(n);
|
|
|
|
Player *player = controller->getControlledEntity();
|
|
|
|
if (!player) {
|
|
|
|
// this shouldn't happen
|
2024-10-06 22:57:39 +02:00
|
|
|
continue;
|
|
|
|
}
|
2024-10-02 19:42:03 +02:00
|
|
|
|
2024-10-11 22:54:40 +02:00
|
|
|
saved_bot_t& saved = saved_bots[num_saved_bots++];
|
|
|
|
memcpy(saved.userinfo, player->client->pers.userinfo, sizeof(saved.userinfo));
|
2024-10-02 19:42:03 +02:00
|
|
|
}
|
2023-08-19 17:58:54 +02:00
|
|
|
}
|
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
G_RestoreBots
|
|
|
|
|
|
|
|
Restore bot persistent data, such as their team
|
|
|
|
============
|
|
|
|
*/
|
2024-10-02 19:42:03 +02:00
|
|
|
void G_RestoreBots()
|
|
|
|
{
|
|
|
|
unsigned int n;
|
2023-08-19 17:58:54 +02:00
|
|
|
|
|
|
|
if (!saved_bots) {
|
2024-10-02 19:42:03 +02:00
|
|
|
return;
|
2023-08-19 17:58:54 +02:00
|
|
|
}
|
|
|
|
|
2024-10-05 13:54:53 +02:00
|
|
|
for (n = 0; n < num_saved_bots; n++) {
|
2024-10-11 22:54:40 +02:00
|
|
|
const saved_bot_t& saved = saved_bots[n];
|
2023-08-19 17:58:54 +02:00
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
G_AddBot(&saved);
|
2024-10-02 19:42:03 +02:00
|
|
|
}
|
2023-08-19 17:58:54 +02:00
|
|
|
|
2024-10-02 19:42:03 +02:00
|
|
|
delete[] saved_bots;
|
|
|
|
saved_bots = NULL;
|
2023-08-19 17:58:54 +02:00
|
|
|
}
|
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
G_CountPlayingClients
|
|
|
|
|
|
|
|
Count the number of real clients that are playing
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
int G_CountPlayingClients()
|
2024-10-02 19:42:03 +02:00
|
|
|
{
|
|
|
|
gentity_t *other;
|
|
|
|
unsigned int n;
|
|
|
|
unsigned int count = 0;
|
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
for (n = 0; n < game.maxclients; n++) {
|
2024-10-02 19:42:03 +02:00
|
|
|
other = &g_entities[n];
|
2024-10-06 22:57:39 +02:00
|
|
|
if (G_IsPlayer(other)) {
|
2024-10-02 19:42:03 +02:00
|
|
|
Player *p = static_cast<Player *>(other->entity);
|
2024-10-06 22:57:39 +02:00
|
|
|
// Ignore spectators
|
|
|
|
if (p->GetTeam() != teamtype_t::TEAM_NONE && p->GetTeam() != teamtype_t::TEAM_SPECTATOR) {
|
|
|
|
count++;
|
2024-10-06 18:46:23 +02:00
|
|
|
}
|
2024-10-06 22:57:39 +02:00
|
|
|
}
|
|
|
|
}
|
2024-10-02 19:42:03 +02:00
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===========
|
|
|
|
G_CountClients
|
|
|
|
|
|
|
|
Count the number of real clients
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
int G_CountClients()
|
|
|
|
{
|
|
|
|
gentity_t *other;
|
|
|
|
unsigned int n;
|
|
|
|
unsigned int count = 0;
|
|
|
|
|
|
|
|
for (n = 0; n < game.maxclients; n++) {
|
|
|
|
other = &g_entities[n];
|
|
|
|
if (G_IsBot(other)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (other->client && other->client->pers.userinfo[0]) {
|
2024-10-02 19:42:03 +02:00
|
|
|
count++;
|
|
|
|
}
|
|
|
|
}
|
2024-04-09 21:43:56 +02:00
|
|
|
|
2024-10-02 19:42:03 +02:00
|
|
|
return count;
|
2024-04-09 21:43:56 +02:00
|
|
|
}
|
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
G_ResetBots
|
|
|
|
|
|
|
|
Save and reset the bot count
|
|
|
|
============
|
|
|
|
*/
|
2024-10-02 19:42:03 +02:00
|
|
|
void G_ResetBots()
|
|
|
|
{
|
|
|
|
G_SaveBots();
|
2023-08-19 17:58:54 +02:00
|
|
|
|
2024-10-08 22:16:57 +02:00
|
|
|
botManager.Cleanup();
|
|
|
|
|
2025-01-16 22:27:51 +01:00
|
|
|
botId = 0;
|
2023-08-19 17:58:54 +02:00
|
|
|
}
|
|
|
|
|
2024-10-08 22:16:57 +02:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
G_BotInit
|
|
|
|
|
|
|
|
Called to initialize bots
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void G_BotInit()
|
|
|
|
{
|
2024-10-10 20:11:12 +02:00
|
|
|
InitModelList();
|
2024-10-08 22:16:57 +02:00
|
|
|
botManager.Init();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===========
|
|
|
|
G_BotFrame
|
|
|
|
|
|
|
|
Called each frame to manage bots
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void G_BotFrame()
|
|
|
|
{
|
|
|
|
botManager.Frame();
|
|
|
|
}
|
|
|
|
|
2024-10-11 22:54:40 +02:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
G_BotPostInit
|
|
|
|
|
|
|
|
Called after the server has spawned
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void G_BotPostInit()
|
|
|
|
{
|
|
|
|
G_RestoreBots();
|
|
|
|
|
|
|
|
G_SpawnBots();
|
|
|
|
}
|
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
G_SpawnBots
|
|
|
|
|
|
|
|
Called each frame to manage bot spawning
|
|
|
|
============
|
|
|
|
*/
|
2024-10-02 19:42:03 +02:00
|
|
|
void G_SpawnBots()
|
|
|
|
{
|
|
|
|
unsigned int numClients;
|
|
|
|
unsigned int numBotsToSpawn;
|
2025-01-16 22:27:51 +01:00
|
|
|
unsigned int numSpawnedBots;
|
2024-10-02 19:42:03 +02:00
|
|
|
|
|
|
|
//
|
|
|
|
// Check the minimum bot count
|
|
|
|
//
|
2024-10-06 22:57:39 +02:00
|
|
|
numClients = G_CountPlayingClients();
|
2024-10-02 19:42:03 +02:00
|
|
|
if (numClients < sv_minPlayers->integer) {
|
2024-10-06 18:50:42 +02:00
|
|
|
numBotsToSpawn = sv_minPlayers->integer - numClients + sv_numbots->integer;
|
2024-10-02 19:42:03 +02:00
|
|
|
} else {
|
|
|
|
numBotsToSpawn = sv_numbots->integer;
|
|
|
|
}
|
|
|
|
|
2024-10-06 22:57:39 +02:00
|
|
|
if (sv_sharedbots->integer) {
|
|
|
|
unsigned int numClients = G_CountClients();
|
|
|
|
|
|
|
|
//
|
|
|
|
// Cap to the maximum number of possible clients
|
|
|
|
//
|
|
|
|
numBotsToSpawn = Q_min(numBotsToSpawn, maxclients->integer - numClients + sv_maxbots->integer);
|
|
|
|
} else {
|
|
|
|
numBotsToSpawn = Q_min(numBotsToSpawn, sv_maxbots->integer);
|
|
|
|
}
|
|
|
|
|
2025-01-16 22:27:51 +01:00
|
|
|
numSpawnedBots = botManager.getControllerManager().getControllers().NumObjects();
|
|
|
|
|
2024-10-02 19:42:03 +02:00
|
|
|
//
|
|
|
|
// Spawn bots
|
|
|
|
//
|
2025-01-16 22:27:51 +01:00
|
|
|
if (numBotsToSpawn > numSpawnedBots) {
|
|
|
|
G_AddBots(numBotsToSpawn - numSpawnedBots);
|
|
|
|
} else if (numBotsToSpawn < numSpawnedBots) {
|
|
|
|
G_RemoveBots(numSpawnedBots - numBotsToSpawn);
|
2024-10-02 19:42:03 +02:00
|
|
|
}
|
2023-08-19 17:58:54 +02:00
|
|
|
}
|