mirror of
https://github.com/openmoh/openmohaa.git
synced 2025-04-28 13:47:58 +03:00
Rework bot spawning
- Add a sv_sharedbots to specify if real client slots should be used - Removed m_bSpawnBot from level, bots are spawned directly in G_BotBegin instead - sv_numbots can be used to directly configure the number of bots to spawn - Fix the way bots spawn/unspawn on-demand - Documented g_bot functions
This commit is contained in:
parent
295c267c31
commit
0d53adf9bd
8 changed files with 499 additions and 183 deletions
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 2023 the OpenMoHAA team
|
||||
Copyright (C) 2024 the OpenMoHAA team
|
||||
|
||||
This file is part of OpenMoHAA source code.
|
||||
|
||||
|
@ -26,25 +26,47 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|||
#include "playerbot.h"
|
||||
#include "g_bot.h"
|
||||
|
||||
static gentity_t *firstBot = NULL;
|
||||
static saved_bot_t *saved_bots = NULL;
|
||||
static unsigned int num_saved_bots = 0;
|
||||
static unsigned int current_bot_count = 0;
|
||||
static unsigned int botId = 0;
|
||||
static char **modelList = NULL;
|
||||
|
||||
Container<str> alliedModelList;
|
||||
Container<str> germanModelList;
|
||||
|
||||
/*
|
||||
===========
|
||||
IsAlliedPlayerModel
|
||||
|
||||
Return whether or not the specified filename is for allies
|
||||
============
|
||||
*/
|
||||
bool IsAlliedPlayerModel(const char *filename)
|
||||
{
|
||||
return !Q_stricmpn(filename, "/allied_", 8) || !Q_stricmpn(filename, "/american_", 10);
|
||||
}
|
||||
|
||||
/*
|
||||
===========
|
||||
IsGermanPlayerModel
|
||||
|
||||
Return whether or not the specified filename is for axis
|
||||
============
|
||||
*/
|
||||
bool IsGermanPlayerModel(const char *filename)
|
||||
{
|
||||
return !Q_stricmpn(filename, "/german_", 8) || !Q_stricmpn(filename, "/IT_", 4) || !Q_stricmpn(filename, "/SC_", 4);
|
||||
}
|
||||
|
||||
/*
|
||||
===========
|
||||
IsPlayerModel
|
||||
|
||||
Return whether or not the specified filename
|
||||
is a player model that can be chosen
|
||||
============
|
||||
*/
|
||||
bool IsPlayerModel(const char *filename)
|
||||
{
|
||||
size_t len = strlen(filename);
|
||||
|
@ -60,12 +82,27 @@ bool IsPlayerModel(const char *filename)
|
|||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
===========
|
||||
ClearModelList
|
||||
|
||||
Clear the allied and axis model list
|
||||
============
|
||||
*/
|
||||
void ClearModelList()
|
||||
{
|
||||
alliedModelList.FreeObjectList();
|
||||
germanModelList.FreeObjectList();
|
||||
}
|
||||
|
||||
/*
|
||||
===========
|
||||
InitModelList
|
||||
|
||||
Initialize the list of allied and axis player models
|
||||
that bots can use
|
||||
============
|
||||
*/
|
||||
void InitModelList()
|
||||
{
|
||||
char **fileList;
|
||||
|
@ -113,12 +150,28 @@ void InitModelList()
|
|||
gi.FS_FreeFileList(fileList);
|
||||
}
|
||||
|
||||
/*
|
||||
===========
|
||||
G_BotBegin
|
||||
|
||||
Begin spawning a new bot entity
|
||||
============
|
||||
*/
|
||||
void G_BotBegin(gentity_t *ent)
|
||||
{
|
||||
level.m_bSpawnBot = true;
|
||||
level.spawn_entnum = ent->s.number;
|
||||
new PlayerBot;
|
||||
|
||||
G_ClientBegin(ent, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
===========
|
||||
G_BotThink
|
||||
|
||||
Called each server frame to make bots think
|
||||
============
|
||||
*/
|
||||
void G_BotThink(gentity_t *ent, int msec)
|
||||
{
|
||||
usercmd_t ucmd;
|
||||
|
@ -138,47 +191,222 @@ void G_BotThink(gentity_t *ent, int msec)
|
|||
G_ClientThink(ent, &ucmd, &eyeinfo);
|
||||
}
|
||||
|
||||
gentity_t *G_GetFirstBot()
|
||||
/*
|
||||
===========
|
||||
G_FindFreeEntityForBot
|
||||
|
||||
Find a free client slot
|
||||
============
|
||||
*/
|
||||
gentity_t *G_FindFreeEntityForBot()
|
||||
{
|
||||
return firstBot;
|
||||
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;
|
||||
}
|
||||
|
||||
void G_AddBot(unsigned int num, saved_bot_t *saved)
|
||||
/*
|
||||
===========
|
||||
G_ChangeParent
|
||||
|
||||
Fix parenting for entities that use the old number
|
||||
============
|
||||
*/
|
||||
void G_ChangeParent(int oldNum, int newNum)
|
||||
{
|
||||
int n;
|
||||
gentity_t *ent;
|
||||
int i;
|
||||
int clientNum = -1;
|
||||
|
||||
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
|
||||
============
|
||||
*/
|
||||
gentity_t *G_GetFirstBot()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
===========
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
===========
|
||||
G_AddBot
|
||||
|
||||
Add the specified bot, optionally its saved state
|
||||
============
|
||||
*/
|
||||
void G_AddBot(saved_bot_t *saved)
|
||||
{
|
||||
int i;
|
||||
int clientNum;
|
||||
gentity_t *e;
|
||||
char botName[MAX_NETNAME];
|
||||
char challenge[MAX_STRING_TOKENS];
|
||||
Event *teamEv;
|
||||
|
||||
num = Q_min(num, sv_maxbots->integer);
|
||||
for (n = 0; n < num; n++) {
|
||||
char userinfo[MAX_INFO_STRING] {0};
|
||||
|
||||
for (i = maxclients->integer; i < game.maxclients; i++) {
|
||||
e = &g_entities[i];
|
||||
|
||||
if (!e->inuse && e->client) {
|
||||
clientNum = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (clientNum == -1) {
|
||||
e = G_FindFreeEntityForBot();
|
||||
if (!e) {
|
||||
gi.Printf("No free slot for a bot\n");
|
||||
return;
|
||||
}
|
||||
|
||||
clientNum = e - g_entities;
|
||||
|
||||
if (gi.Argc() > 2) {
|
||||
Q_strncpyz(botName, gi.Argv(2), sizeof(botName));
|
||||
} else {
|
||||
Com_sprintf(botName, sizeof(botName), "bot%d", clientNum - maxclients->integer + 1);
|
||||
Com_sprintf(botName, sizeof(botName), "bot%d", botId);
|
||||
}
|
||||
|
||||
Com_sprintf(challenge, sizeof(challenge), "%d", clientNum - maxclients->integer + 1);
|
||||
|
||||
e->s.clientNum = clientNum;
|
||||
e->s.number = clientNum;
|
||||
|
||||
|
@ -191,25 +419,20 @@ void G_AddBot(unsigned int num, saved_bot_t *saved)
|
|||
// Choose a random model
|
||||
//
|
||||
if (alliedModelList.NumObjects()) {
|
||||
Info_SetValueForKey(userinfo, "dm_playermodel", alliedModelList[rand() % alliedModelList.NumObjects()]);
|
||||
const unsigned int index = rand() % alliedModelList.NumObjects();
|
||||
Info_SetValueForKey(userinfo, "dm_playermodel", alliedModelList[index]);
|
||||
}
|
||||
if (germanModelList.NumObjects()) {
|
||||
Info_SetValueForKey(
|
||||
userinfo, "dm_playergermanmodel", germanModelList[rand() % germanModelList.NumObjects()]
|
||||
);
|
||||
const unsigned int index = rand() % germanModelList.NumObjects();
|
||||
Info_SetValueForKey(userinfo, "dm_playergermanmodel", germanModelList[index]);
|
||||
}
|
||||
|
||||
Info_SetValueForKey(userinfo, "fov", "80");
|
||||
Info_SetValueForKey(userinfo, "protocol", "8");
|
||||
Info_SetValueForKey(userinfo, "ip", "0.0.0.0");
|
||||
Info_SetValueForKey(userinfo, "qport", "0");
|
||||
Info_SetValueForKey(userinfo, "challenge", challenge);
|
||||
Info_SetValueForKey(userinfo, "snaps", "1");
|
||||
Info_SetValueForKey(userinfo, "rate", "1");
|
||||
Info_SetValueForKey(userinfo, "dmprimary", "smg");
|
||||
Info_SetValueForKey(userinfo, "ip", "localhost");
|
||||
}
|
||||
|
||||
current_bot_count++;
|
||||
botId++;
|
||||
|
||||
G_BotConnect(clientNum, userinfo);
|
||||
|
||||
|
@ -217,64 +440,71 @@ void G_AddBot(unsigned int num, saved_bot_t *saved)
|
|||
e->client->pers = saved->pers;
|
||||
}
|
||||
|
||||
if (!firstBot) {
|
||||
firstBot = e;
|
||||
}
|
||||
|
||||
G_BotBegin(e);
|
||||
}
|
||||
|
||||
if (saved) {
|
||||
/*
|
||||
switch (saved->team)
|
||||
{
|
||||
case TEAM_ALLIES:
|
||||
teamEv = new Event(EV_Player_JoinDMTeam);
|
||||
teamEv->AddString("allies");
|
||||
break;
|
||||
case TEAM_AXIS:
|
||||
teamEv = new Event(EV_Player_JoinDMTeam);
|
||||
teamEv->AddString("axis");
|
||||
break;
|
||||
default:
|
||||
teamEv = new Event(EV_Player_AutoJoinDMTeam);
|
||||
break;
|
||||
}
|
||||
*/
|
||||
} else {
|
||||
teamEv = new Event(EV_Player_AutoJoinDMTeam);
|
||||
e->entity->PostEvent(teamEv, level.frametime);
|
||||
/*
|
||||
===========
|
||||
G_AddBots
|
||||
|
||||
Event *ev = new Event(EV_Player_PrimaryDMWeapon);
|
||||
ev->AddString("auto");
|
||||
Add the specified number of bots
|
||||
============
|
||||
*/
|
||||
void G_AddBots(unsigned int num)
|
||||
{
|
||||
int n;
|
||||
|
||||
e->entity->PostEvent(ev, level.frametime);
|
||||
}
|
||||
for (n = 0; n < num; n++) {
|
||||
G_AddBot(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void G_RemoveBot(unsigned int num)
|
||||
/*
|
||||
===========
|
||||
G_RemoveBot
|
||||
|
||||
Remove the specified bot
|
||||
============
|
||||
*/
|
||||
void G_RemoveBot(gentity_t *ent)
|
||||
{
|
||||
G_ClientDisconnect(ent);
|
||||
current_bot_count--;
|
||||
}
|
||||
|
||||
/*
|
||||
===========
|
||||
G_RemoveBots
|
||||
|
||||
Remove the specified number of bots
|
||||
============
|
||||
*/
|
||||
void G_RemoveBots(unsigned int num)
|
||||
{
|
||||
unsigned int removed = 0;
|
||||
unsigned int n;
|
||||
unsigned int teamCount[2]{ 0 };
|
||||
unsigned int teamCount[2] {0};
|
||||
bool bNoMoreToRemove = false;
|
||||
|
||||
num = Q_min(num, sv_maxbots->integer);
|
||||
|
||||
teamCount[0] = dmManager.GetTeamAllies()->m_players.NumObjects();
|
||||
teamCount[1] = dmManager.GetTeamAxis()->m_players.NumObjects();
|
||||
|
||||
while (!bNoMoreToRemove) {
|
||||
bNoMoreToRemove = true;
|
||||
|
||||
// First remove bots that are in the team
|
||||
// with the higest player count
|
||||
for (n = 0; n < game.maxclients && removed < num; n++) {
|
||||
gentity_t *e = &g_entities[game.maxclients - sv_maxbots->integer + n];
|
||||
if (e->inuse && e->client) {
|
||||
Player* player = static_cast<Player*>(e->entity);
|
||||
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]) {
|
||||
// Skip bots in the lowest team
|
||||
// Not enough players in that team, don't remove the bot
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -282,26 +512,32 @@ void G_RemoveBot(unsigned int num)
|
|||
bNoMoreToRemove = false;
|
||||
}
|
||||
|
||||
G_ClientDisconnect(e);
|
||||
current_bot_count--;
|
||||
G_RemoveBot(e);
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Remove all bots regardless
|
||||
// Remove all bots that haven't been removed earlier
|
||||
//
|
||||
for (n = 0; n < game.maxclients && removed < num; n++) {
|
||||
gentity_t *e = &g_entities[game.maxclients - sv_maxbots->integer + n];
|
||||
if (e->inuse && e->client) {
|
||||
G_ClientDisconnect(e);
|
||||
current_bot_count--;
|
||||
removed++;
|
||||
gentity_t *e = &g_entities[n];
|
||||
if (!G_IsBot(e)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
G_RemoveBot(e);
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
===========
|
||||
G_SaveBots
|
||||
|
||||
Save bot persistent data
|
||||
============
|
||||
*/
|
||||
void G_SaveBots()
|
||||
{
|
||||
unsigned int n;
|
||||
|
@ -318,9 +554,11 @@ void G_SaveBots()
|
|||
saved_bots = new saved_bot_t[current_bot_count];
|
||||
num_saved_bots = 0;
|
||||
for (n = 0; n < game.maxclients; n++) {
|
||||
gentity_t *e = &g_entities[game.maxclients - sv_maxbots->integer + n];
|
||||
gentity_t *e = &g_entities[n];
|
||||
if (!G_IsBot(e)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (e->inuse && e->client) {
|
||||
Player *player = static_cast<Player *>(e->entity);
|
||||
saved_bot_t& saved = saved_bots[num_saved_bots++];
|
||||
|
||||
|
@ -328,9 +566,15 @@ void G_SaveBots()
|
|||
//saved.team = player->GetTeam();
|
||||
saved.pers = player->client->pers;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
===========
|
||||
G_RestoreBots
|
||||
|
||||
Restore bot persistent data, such as their team
|
||||
============
|
||||
*/
|
||||
void G_RestoreBots()
|
||||
{
|
||||
unsigned int n;
|
||||
|
@ -342,28 +586,60 @@ void G_RestoreBots()
|
|||
for (n = 0; n < num_saved_bots; n++) {
|
||||
saved_bot_t& saved = saved_bots[n];
|
||||
|
||||
G_AddBot(1, &saved);
|
||||
G_AddBot(&saved);
|
||||
}
|
||||
|
||||
delete[] saved_bots;
|
||||
saved_bots = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
===========
|
||||
G_CountPlayingClients
|
||||
|
||||
Count the number of real clients that are playing
|
||||
============
|
||||
*/
|
||||
int G_CountPlayingClients()
|
||||
{
|
||||
gentity_t *other;
|
||||
unsigned int n;
|
||||
unsigned int count = 0;
|
||||
|
||||
for (n = 0; n < game.maxclients; n++) {
|
||||
other = &g_entities[n];
|
||||
if (G_IsPlayer(other)) {
|
||||
Player *p = static_cast<Player *>(other->entity);
|
||||
// Ignore spectators
|
||||
if (p->GetTeam() != teamtype_t::TEAM_NONE && p->GetTeam() != teamtype_t::TEAM_SPECTATOR) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 < maxclients->integer; n++) {
|
||||
for (n = 0; n < game.maxclients; n++) {
|
||||
other = &g_entities[n];
|
||||
if (other->inuse && other->client) {
|
||||
Player *p = static_cast<Player *>(other->entity);
|
||||
if (p->GetTeam() == teamtype_t::TEAM_NONE || p->GetTeam() == teamtype_t::TEAM_SPECTATOR) {
|
||||
// ignore spectators
|
||||
if (G_IsBot(other)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (other->client && other->client->pers.userinfo[0]) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
@ -371,13 +647,28 @@ int G_CountClients()
|
|||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
===========
|
||||
G_ResetBots
|
||||
|
||||
Save and reset the bot count
|
||||
============
|
||||
*/
|
||||
void G_ResetBots()
|
||||
{
|
||||
G_SaveBots();
|
||||
|
||||
current_bot_count = 0;
|
||||
botId = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
===========
|
||||
G_SpawnBots
|
||||
|
||||
Called each frame to manage bot spawning
|
||||
============
|
||||
*/
|
||||
void G_SpawnBots()
|
||||
{
|
||||
unsigned int numClients;
|
||||
|
@ -392,19 +683,30 @@ void G_SpawnBots()
|
|||
//
|
||||
// Check the minimum bot count
|
||||
//
|
||||
numClients = G_CountClients();
|
||||
numClients = G_CountPlayingClients();
|
||||
if (numClients < sv_minPlayers->integer) {
|
||||
numBotsToSpawn = sv_minPlayers->integer - numClients + sv_numbots->integer;
|
||||
} else {
|
||||
numBotsToSpawn = sv_numbots->integer;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
//
|
||||
// Spawn bots
|
||||
//
|
||||
if (numBotsToSpawn > current_bot_count) {
|
||||
G_AddBot(numBotsToSpawn - current_bot_count);
|
||||
G_AddBots(numBotsToSpawn - current_bot_count);
|
||||
} else if (numBotsToSpawn < current_bot_count) {
|
||||
G_RemoveBot(current_bot_count - numBotsToSpawn);
|
||||
G_RemoveBots(current_bot_count - numBotsToSpawn);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 2023 the OpenMoHAA team
|
||||
Copyright (C) 2024 the OpenMoHAA team
|
||||
|
||||
This file is part of OpenMoHAA source code.
|
||||
|
||||
|
@ -31,10 +31,15 @@ struct saved_bot_t {
|
|||
{}
|
||||
};
|
||||
|
||||
void G_BotBegin(gentity_t* ent);
|
||||
void G_BotThink(gentity_t* ent, int msec);
|
||||
gentity_t* G_GetFirstBot();
|
||||
void G_AddBot(unsigned int num, saved_bot_t* saved = NULL);
|
||||
void G_RemoveBot(unsigned int num);
|
||||
void G_BotBegin(gentity_t *ent);
|
||||
void G_BotThink(gentity_t *ent, int msec);
|
||||
void G_BotShift(int clientNum);
|
||||
gentity_t *G_GetFirstBot();
|
||||
void G_AddBot(saved_bot_t *saved = NULL);
|
||||
void G_AddBots(unsigned int num);
|
||||
void G_RemoveBot(gentity_t *ent);
|
||||
void G_RemoveBots(unsigned int num);
|
||||
bool G_IsBot(gentity_t *ent);
|
||||
bool G_IsPlayer(gentity_t *ent);
|
||||
void G_ResetBots();
|
||||
void G_SpawnBots();
|
|
@ -26,6 +26,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|||
#include "playerstart.h"
|
||||
#include "scriptmaster.h"
|
||||
#include "g_spawn.h"
|
||||
#include "g_bot.h"
|
||||
|
||||
// g_client.c -- client functions that don't happen every frame
|
||||
|
||||
|
@ -833,6 +834,9 @@ void G_BotConnect(int clientNum, const char *userinfo)
|
|||
memset(client, 0, sizeof(*client));
|
||||
G_InitClientPersistant(client, userinfo);
|
||||
|
||||
//
|
||||
// Use "localhost" as some code relies on it to check whether or not it should be kicked
|
||||
//
|
||||
Q_strncpyz(client->pers.ip, "localhost", sizeof(client->pers.ip));
|
||||
client->pers.port = 0;
|
||||
|
||||
|
@ -873,6 +877,9 @@ const char *G_ClientConnect(int clientNum, qboolean firstTime, qboolean differen
|
|||
// return NULL;
|
||||
//}
|
||||
|
||||
// Added in OPM
|
||||
G_BotShift(clientNum);
|
||||
|
||||
ent = &g_entities[clientNum];
|
||||
|
||||
gi.GetUserinfo(clientNum, userinfo, sizeof(userinfo));
|
||||
|
@ -969,14 +976,8 @@ void G_ClientBegin(gentity_t *ent, usercmd_t *cmd)
|
|||
} else {
|
||||
// a spawn point will completely reinitialize the entity
|
||||
level.spawn_entnum = ent->s.number;
|
||||
|
||||
if (level.m_bSpawnBot) {
|
||||
level.m_bSpawnBot = false;
|
||||
PlayerBot *player = new PlayerBot;
|
||||
} else {
|
||||
Player *player = new Player;
|
||||
}
|
||||
}
|
||||
|
||||
if (level.intermissiontime && ent->entity) {
|
||||
G_MoveClientToIntermission(ent->entity);
|
||||
|
|
|
@ -265,9 +265,20 @@ cvar_t *g_obituarylocation;
|
|||
|
||||
cvar_t *sv_scriptfiles;
|
||||
|
||||
// The maximum number of allocated bot clients
|
||||
cvar_t *sv_maxbots;
|
||||
// The number of bots that should be spawned
|
||||
cvar_t *sv_numbots;
|
||||
// The minimum number of players that should be present in-game.
|
||||
// If the number of real players is below this number,
|
||||
// the game will automatically add bots to fill the gap
|
||||
cvar_t *sv_minPlayers;
|
||||
// Whether or not the bots use a shared player slots
|
||||
// NOTE: Setting this cvar is not recommended
|
||||
// because when a client connects and the slot is used by a bot
|
||||
// the bot will be relocated to a free entity slot
|
||||
cvar_t *sv_sharedbots;
|
||||
|
||||
cvar_t *g_rankedserver;
|
||||
cvar_t *g_spectatefollow_firstperson;
|
||||
|
||||
|
@ -632,7 +643,8 @@ void CVAR_Init(void)
|
|||
|
||||
sv_scriptfiles = gi.Cvar_Get("sv_scriptfiles", "0", 0);
|
||||
sv_maxbots = gi.Cvar_Get("sv_maxbots", "0", CVAR_LATCH);
|
||||
sv_numbots = gi.Cvar_Get("sv_numbots", "0", CVAR_LATCH);
|
||||
sv_sharedbots = gi.Cvar_Get("sv_sharedbots", "0", CVAR_LATCH);
|
||||
sv_numbots = gi.Cvar_Get("sv_numbots", "0", 0);
|
||||
sv_minPlayers = gi.Cvar_Get("sv_minPlayers", "0", 0);
|
||||
g_rankedserver = gi.Cvar_Get("g_rankedserver", "0", 0);
|
||||
g_spectatefollow_firstperson = gi.Cvar_Get("g_spectatefollow_firstperson", "0", 0);
|
||||
|
@ -646,11 +658,6 @@ void CVAR_Init(void)
|
|||
gi.Printf("sv_maxbots reached max clients, lowering the value to %u\n", lowered);
|
||||
}
|
||||
|
||||
if (sv_numbots->integer > sv_maxbots->integer) {
|
||||
gi.Printf("numbots overflow, setting to %d\n", sv_maxbots->integer);
|
||||
gi.cvar_set("sv_numbots", sv_maxbots->string);
|
||||
}
|
||||
|
||||
g_instamsg_allowed = gi.Cvar_Get("g_instamsg_allowed", "1", 0);
|
||||
g_instamsg_minDelay = gi.Cvar_Get("g_instamsg_minDelay", "1000", 0);
|
||||
g_textmsg_allowed = gi.Cvar_Get("g_textmsg_allowed", "1", 0);
|
||||
|
|
|
@ -269,6 +269,7 @@ extern cvar_t *sv_scriptfiles;
|
|||
extern cvar_t *sv_maxbots;
|
||||
extern cvar_t *sv_numbots;
|
||||
extern cvar_t *sv_minPlayers;
|
||||
extern cvar_t *sv_sharedbots;
|
||||
extern cvar_t *g_rankedserver;
|
||||
extern cvar_t *g_spectatefollow_firstperson;
|
||||
|
||||
|
|
|
@ -834,7 +834,6 @@ void Level::Init(void)
|
|||
|
||||
m_pAIStats = NULL;
|
||||
|
||||
m_bSpawnBot = false;
|
||||
m_bScriptSpawn = false;
|
||||
m_bRejectSpawn = false;
|
||||
}
|
||||
|
|
|
@ -251,8 +251,6 @@ public:
|
|||
|
||||
// New Stuff
|
||||
|
||||
bool m_bSpawnBot;
|
||||
|
||||
// Script stuff
|
||||
bool m_bScriptSpawn;
|
||||
bool m_bRejectSpawn;
|
||||
|
|
|
@ -95,7 +95,10 @@ This feature is passive: it only checks the team sizes when someone tries to joi
|
|||
### Bots
|
||||
|
||||
Bots can be used for testing. They don't move by default, so a mod will be needed, like [eaglear bots](https://www.moddb.com/mods/medal-of-honor-world-war-1/downloads/moh-eaglear-bots):
|
||||
- `set sv_maxbots x`: Configure and allocate the maximum number of bots.
|
||||
- `set sv_minPlayers x`: optional, can be used to set the minimum number of players that the server should have. Bots will be spawned based on the minimum number of players.
|
||||
- `addbot x`: x is the number of bots to add.
|
||||
- `removebot x`: x is the number of bots to remove.
|
||||
- `set sv_maxbots x`: Required, configure the maximum number of bots allowed in the game. Since the game can only handle a total of 64 players (clients), the number of bots will be limited to 64 minus the number of real players (`sv_maxclients`). For example, if you set `sv_maxclients` to 48, the maximum number of bots (sv_maxbots) can be 16.
|
||||
- `set sv_numbots x`: Set the number of bots to spawn. It will be capped to the value of `sv_maxbots`.
|
||||
- `set sv_minPlayers x`: Configure the minimum number of players required. If the number of real players is below the specified value, the game will automatically add bots to fill the gap. For example, if `sv_minPlayers` is set to 8 and only 5 real players are connected, the game will spawn 3 bots to make sure there are always 8 players in the game.
|
||||
|
||||
Commands:
|
||||
- `addbot x`: x is the number of bots to add. It only changes the `sv_numbots` variable.
|
||||
- `removebot x`: x is the number of bots to remove. It only changes the `sv_numbots` variable.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue