mirror of
https://github.com/blendogames/thirtyflightsofloving.git
synced 2024-11-14 16:40:57 +00:00
764 lines
21 KiB
C
764 lines
21 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1998 Steve Yeager
|
|
|
|
This file is part of ACE Bot source code.
|
|
|
|
ACE Bot 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.
|
|
|
|
ACE Bot 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 ACE Bot source code; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
//
|
|
// ACE - Quake II Bot Base Code
|
|
//
|
|
// Version 1.0
|
|
//
|
|
// This file is Copyright(c), Steve Yeager 1998, All Rights Reserved
|
|
//
|
|
//
|
|
// All other files are Copyright(c) Id Software, Inc.
|
|
//
|
|
// Please see liscense.txt in the source directory for the copyright
|
|
// information regarding those files belonging to Id Software, Inc.
|
|
//
|
|
// Should you decide to release a modified version of ACE, you MUST
|
|
// include the following text (minus the BEGIN and END lines) in the
|
|
// documentation for your modification.
|
|
//
|
|
// --- BEGIN ---
|
|
//
|
|
// The ACE Bot is a product of Steve Yeager, and is available from
|
|
// the ACE Bot homepage, at http://www.axionfx.com/ace.
|
|
//
|
|
// This program is a modification of the ACE Bot, and is therefore
|
|
// in NO WAY supported by Steve Yeager.
|
|
//
|
|
// --- END ---
|
|
//
|
|
// I, Steve Yeager, hold no responsibility for any harm caused by the
|
|
// use of this source code, especially to small children and animals.
|
|
// It is provided as-is with no implied warranty or support.
|
|
//
|
|
// I also wish to thank and acknowledge the great work of others
|
|
// that has helped me to develop this code.
|
|
//
|
|
// John Cricket - For ideas and swapping code.
|
|
// Ryan Feltrin - For ideas and swapping code.
|
|
// SABIN - For showing how to do true client based movement.
|
|
// BotEpidemic - For keeping us up to date.
|
|
// Telefragged.com - For giving ACE a home.
|
|
// Microsoft - For giving us such a wonderful crash free OS.
|
|
// id - Need I say more.
|
|
//
|
|
// And to all the other testers, pathers, and players and people
|
|
// who I can't remember who the heck they were, but helped out.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
//
|
|
// acebot_spawn.c - This file contains all of the
|
|
// spawing support routines for the ACE bot.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
#include "g_local.h"
|
|
#include "m_player.h"
|
|
#include "acebot.h"
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Had to add this function in this version for some reason.
|
|
// any globals are wiped out between level changes....so
|
|
// save the bots out to a file.
|
|
//
|
|
// NOTE: There is still a bug when the bots are saved for
|
|
// a dm game and then reloaded into a CTF game.
|
|
///////////////////////////////////////////////////////////////////////
|
|
/*void ACESP_SaveBots()
|
|
{
|
|
edict_t *bot;
|
|
FILE *pOut;
|
|
int i,count = 0;
|
|
|
|
if((pOut = fopen("ace\\bots.tmp", "wb" )) == NULL)
|
|
return; // bail
|
|
|
|
// Get number of bots
|
|
for (i = maxclients->value; i > 0; i--)
|
|
{
|
|
bot = g_edicts + i + 1;
|
|
|
|
if (bot->inuse && bot->is_bot)
|
|
count++;
|
|
}
|
|
|
|
fwrite(&count,sizeof (int),1,pOut); // Write number of bots
|
|
|
|
for (i = maxclients->value; i > 0; i--)
|
|
{
|
|
bot = g_edicts + i + 1;
|
|
|
|
if (bot->inuse && bot->is_bot)
|
|
fwrite(bot->client->pers.userinfo,sizeof (char) * MAX_INFO_STRING,1,pOut);
|
|
}
|
|
|
|
fclose(pOut);
|
|
}*/
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Had to add this function in this version for some reason.
|
|
// any globals are wiped out between level changes....so
|
|
// load the bots from a file.
|
|
//
|
|
// Side effect/benefit is that the bots persist between games.
|
|
///////////////////////////////////////////////////////////////////////
|
|
/*void ACESP_LoadBots()
|
|
{
|
|
FILE *pIn;
|
|
char userinfo[MAX_INFO_STRING];
|
|
int i, count;
|
|
|
|
if((pIn = fopen("ace\\bots.tmp", "rb" )) == NULL)
|
|
return; // bail
|
|
|
|
fread(&count,sizeof (int),1,pIn);
|
|
|
|
for(i=0;i<count;i++)
|
|
{
|
|
fread(userinfo,sizeof(char) * MAX_INFO_STRING,1,pIn);
|
|
ACESP_SpawnBot (NULL, NULL, NULL, userinfo);
|
|
}
|
|
|
|
fclose(pIn);
|
|
}*/
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Called by PutClient in Server to actually release the bot into the game
|
|
// Keep from killin' each other when all spawned at once
|
|
///////////////////////////////////////////////////////////////////////
|
|
void ACESP_HoldSpawn(edict_t *self)
|
|
{
|
|
if (!KillBox (self))
|
|
{ // could't spawn in?
|
|
}
|
|
|
|
gi.linkentity (self);
|
|
|
|
self->think = ACEAI_Think;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
|
|
// send effect
|
|
gi.WriteByte (svc_muzzleflash);
|
|
gi.WriteShort (self-g_edicts);
|
|
gi.WriteByte (MZ_LOGIN);
|
|
gi.multicast (self->s.origin, MULTICAST_PVS);
|
|
|
|
if(ctf->value)
|
|
safe_bprintf(PRINT_MEDIUM, "%s joined the %s team.\n",
|
|
self->client->pers.netname, CTFTeamName(self->client->resp.ctf_team));
|
|
else
|
|
safe_bprintf (PRINT_MEDIUM, "%s entered the game\n", self->client->pers.netname);
|
|
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Modified version of id's code
|
|
///////////////////////////////////////////////////////////////////////
|
|
void ACESP_PutClientInServer (edict_t *bot, qboolean respawn, int team)
|
|
{
|
|
vec3_t mins = {-16, -16, -24};
|
|
vec3_t maxs = {16, 16, 32};
|
|
int index;
|
|
vec3_t spawn_origin, spawn_angles;
|
|
gclient_t *client;
|
|
int i;
|
|
client_persistant_t saved;
|
|
client_respawn_t resp;
|
|
char *s;
|
|
int spawn_style;
|
|
int spawn_health;
|
|
|
|
// find a spawn point
|
|
// do it before setting health back up, so farthest
|
|
// ranging doesn't count this client
|
|
SelectSpawnPoint (bot, spawn_origin, spawn_angles, &spawn_style, &spawn_health);
|
|
|
|
index = bot-g_edicts-1;
|
|
client = bot->client;
|
|
|
|
// deathmatch wipes most client data every spawn
|
|
if (deathmatch->value)
|
|
{
|
|
char userinfo[MAX_INFO_STRING];
|
|
|
|
resp = bot->client->resp;
|
|
memcpy (userinfo, client->pers.userinfo, sizeof(userinfo));
|
|
InitClientPersistant (client, spawn_style);
|
|
ClientUserinfoChanged (bot, userinfo);
|
|
}
|
|
else
|
|
memset (&resp, 0, sizeof(resp));
|
|
|
|
// clear everything but the persistant data
|
|
saved = client->pers;
|
|
memset (client, 0, sizeof(*client));
|
|
client->pers = saved;
|
|
client->resp = resp;
|
|
|
|
// copy some data from the client to the entity
|
|
FetchClientEntData (bot);
|
|
|
|
// clear entity values
|
|
bot->groundentity = NULL;
|
|
bot->client = &game.clients[index];
|
|
bot->takedamage = DAMAGE_AIM;
|
|
bot->movetype = MOVETYPE_WALK;
|
|
bot->viewheight = 24;
|
|
bot->classname = "bot";
|
|
bot->mass = 200;
|
|
bot->solid = SOLID_BBOX;
|
|
bot->deadflag = DEAD_NO;
|
|
bot->air_finished = level.time + 12;
|
|
bot->clipmask = MASK_PLAYERSOLID;
|
|
bot->model = "players/male/tris.md2";
|
|
bot->pain = player_pain;
|
|
bot->die = player_die;
|
|
bot->waterlevel = 0;
|
|
bot->watertype = 0;
|
|
bot->flags &= ~FL_NO_KNOCKBACK;
|
|
bot->svflags &= ~SVF_DEADMONSTER;
|
|
bot->is_jumping = false;
|
|
|
|
if (ctf->value)
|
|
{
|
|
client->resp.ctf_team = team;
|
|
client->resp.ctf_state = CTF_STATE_START;
|
|
s = Info_ValueForKey (client->pers.userinfo, "skin");
|
|
CTFAssignSkin(bot, s);
|
|
}
|
|
|
|
VectorCopy (mins, bot->mins);
|
|
VectorCopy (maxs, bot->maxs);
|
|
VectorClear (bot->velocity);
|
|
|
|
// clear playerstate values
|
|
memset (&bot->client->ps, 0, sizeof(client->ps));
|
|
|
|
client->ps.pmove.origin[0] = spawn_origin[0]*8;
|
|
client->ps.pmove.origin[1] = spawn_origin[1]*8;
|
|
client->ps.pmove.origin[2] = spawn_origin[2]*8;
|
|
|
|
//ZOID
|
|
client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
|
|
//ZOID
|
|
|
|
if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV))
|
|
{
|
|
client->ps.fov = 90;
|
|
}
|
|
else
|
|
{
|
|
client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov"));
|
|
if (client->ps.fov < 1)
|
|
client->ps.fov = 90;
|
|
else if (client->ps.fov > 160)
|
|
client->ps.fov = 160;
|
|
}
|
|
|
|
// Knightmare- fix for null model
|
|
if (client->pers.weapon && client->pers.weapon->view_model)
|
|
client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model);
|
|
|
|
// clear entity state values
|
|
bot->s.effects = 0;
|
|
bot->s.skinnum = bot - g_edicts - 1;
|
|
bot->s.modelindex = MAX_MODELS-1; // will use the skin specified model
|
|
bot->s.modelindex2 = MAX_MODELS-1; // custom gun model
|
|
bot->s.frame = 0;
|
|
VectorCopy (spawn_origin, bot->s.origin);
|
|
bot->s.origin[2] += 1; // make sure off ground
|
|
VectorCopy (bot->s.origin, bot->s.old_origin); // Knightmare- was missing oldorigin!
|
|
|
|
// set the delta angle
|
|
for (i=0 ; i<3 ; i++) {
|
|
client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - client->resp.cmd_angles[i]);
|
|
}
|
|
|
|
bot->s.angles[PITCH] = 0;
|
|
bot->s.angles[YAW] = spawn_angles[YAW];
|
|
bot->s.angles[ROLL] = 0;
|
|
VectorCopy (bot->s.angles, client->ps.viewangles);
|
|
VectorCopy (bot->s.angles, client->v_angle);
|
|
|
|
// force the current weapon up
|
|
client->newweapon = client->pers.weapon;
|
|
ChangeWeapon (bot);
|
|
|
|
bot->enemy = NULL;
|
|
bot->movetarget = NULL;
|
|
bot->state = STATE_MOVE;
|
|
|
|
// Set the current node
|
|
bot->current_node = ACEND_FindClosestReachableNode(bot,NODE_DENSITY, NODE_ALL);
|
|
bot->goal_node = bot->current_node;
|
|
bot->next_node = bot->current_node;
|
|
bot->next_move_time = level.time;
|
|
bot->suicide_timeout = level.time + 15.0;
|
|
|
|
// If we are not respawning hold off for up to three seconds before releasing into game
|
|
if (!respawn)
|
|
{
|
|
bot->think = ACESP_HoldSpawn;
|
|
bot->nextthink = level.time + 0.1;
|
|
bot->nextthink = level.time + random()*3.0; // up to three seconds
|
|
}
|
|
else
|
|
{
|
|
if (!KillBox (bot))
|
|
{ // could't spawn in?
|
|
}
|
|
|
|
gi.linkentity (bot);
|
|
|
|
bot->think = ACEAI_Think;
|
|
bot->nextthink = level.time + FRAMETIME;
|
|
|
|
// send effect
|
|
gi.WriteByte (svc_muzzleflash);
|
|
gi.WriteShort (bot-g_edicts);
|
|
gi.WriteByte (MZ_LOGIN);
|
|
gi.multicast (bot->s.origin, MULTICAST_PVS);
|
|
}
|
|
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Respawn the bot
|
|
///////////////////////////////////////////////////////////////////////
|
|
void ACESP_Respawn (edict_t *self)
|
|
{
|
|
CopyToBodyQue (self);
|
|
|
|
if (ctf->value)
|
|
ACESP_PutClientInServer (self ,true, self->client->resp.ctf_team);
|
|
else
|
|
ACESP_PutClientInServer (self, true, 0);
|
|
|
|
// add a teleportation effect
|
|
self->s.event = EV_PLAYER_TELEPORT;
|
|
|
|
// hold in place briefly
|
|
self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
|
|
self->client->ps.pmove.pm_time = 14;
|
|
|
|
self->client->respawn_time = level.time;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Find a free client spot
|
|
///////////////////////////////////////////////////////////////////////
|
|
edict_t *ACESP_FindFreeClient (void)
|
|
{
|
|
edict_t *bot;
|
|
int i;
|
|
int max_count=0;
|
|
|
|
// This is for the naming of the bots
|
|
for (i = maxclients->value; i > 0; i--)
|
|
{
|
|
bot = g_edicts + i + 1;
|
|
|
|
if(bot->count > max_count)
|
|
max_count = bot->count;
|
|
}
|
|
|
|
// Check for free spot
|
|
for (i = maxclients->value; i > 0; i--)
|
|
{
|
|
bot = g_edicts + i + 1;
|
|
|
|
if (!bot->inuse)
|
|
break;
|
|
}
|
|
|
|
bot->count = max_count + 1; // Will become bot name...
|
|
|
|
if (bot->inuse)
|
|
bot = NULL;
|
|
|
|
return bot;
|
|
}
|
|
|
|
// Knightmare- added bot name/skin table
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Bot name/skin table
|
|
///////////////////////////////////////////////////////////////////////
|
|
struct botinfo_s
|
|
{
|
|
char name[128];
|
|
char skin[128];
|
|
int ingame_count;
|
|
};
|
|
typedef struct botinfo_s botinfo_t;
|
|
botinfo_t bot_info[MAX_BOTS];
|
|
int num_botinfo = 0;
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Load a table of bots from bots.cfg.
|
|
///////////////////////////////////////////////////////////////////////
|
|
void ACESP_LoadBotInfo()
|
|
{
|
|
FILE *pIn;
|
|
char filename[MAX_OSPATH] = "";
|
|
char line[256], *parseline;
|
|
char *token;
|
|
char botname[128];
|
|
|
|
if (num_botinfo > 0) // already loaded
|
|
return;
|
|
// Knightmare- rewote this
|
|
GameDirRelativePath ("bots.cfg", filename, sizeof(filename));
|
|
if((pIn = fopen(filename, "rb" )) == NULL)
|
|
{
|
|
safe_bprintf(PRINT_MEDIUM,"ACE: No bots.cfg file found, using default bots.\n");
|
|
return; // bail
|
|
}
|
|
safe_bprintf(PRINT_MEDIUM,"ACE: Loading bot data...");
|
|
|
|
while (fgets(line, sizeof(line), pIn) && num_botinfo < MAX_BOTS)
|
|
{
|
|
parseline = line;
|
|
token = COM_Parse (&parseline);
|
|
if (!token || !strlen(token)) // catch bad line
|
|
continue;
|
|
// strncpy (botname, token, sizeof(botname)-1);
|
|
Q_strncpyz (botname, sizeof(botname), token);
|
|
token = COM_Parse (&parseline);
|
|
if (!token || !strlen(token)) // catch bad line
|
|
continue;
|
|
// strncpy (bot_info[num_botinfo].name, botname, sizeof(bot_info[num_botinfo].name)-1);
|
|
// strncpy (bot_info[num_botinfo].skin, token, sizeof(bot_info[num_botinfo].skin)-1);
|
|
Q_strncpyz (bot_info[num_botinfo].name, sizeof(bot_info[num_botinfo].name), botname);
|
|
Q_strncpyz (bot_info[num_botinfo].skin, sizeof(bot_info[num_botinfo].skin), token);
|
|
//gi.dprintf("%s %s\n", bot_info[num_botinfo].name, bot_info[num_botinfo].skin);
|
|
bot_info[num_botinfo].ingame_count = 0;
|
|
num_botinfo++;
|
|
}
|
|
//gi.dprintf("Number of bots loaded: %d\n\n", num_botinfo);
|
|
safe_bprintf(PRINT_MEDIUM, "done.\n");
|
|
fclose(pIn);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Hardcoded bot skin list
|
|
///////////////////////////////////////////////////////////////////////
|
|
char *skinnames[] = {
|
|
"female/athena",
|
|
"female/brianna",
|
|
"female/cobalt",
|
|
"female/ensign",
|
|
"female/jezebel",
|
|
"female/jungle",
|
|
"female/lotus",
|
|
"female/stiletto",
|
|
"female/venus",
|
|
"female/voodoo",
|
|
"male/cipher",
|
|
"male/claymore",
|
|
"male/flak",
|
|
"male/grunt",
|
|
"male/howitzer",
|
|
"male/major",
|
|
"male/nightops",
|
|
"male/pointman",
|
|
"male/psycho",
|
|
"male/rampage",
|
|
"male/razor",
|
|
"male/recon",
|
|
"male/scout",
|
|
"male/sniper",
|
|
"male/viper",
|
|
"cyborg/oni911",
|
|
"cyborg/ps9000",
|
|
"cyborg/tyr574",
|
|
0
|
|
};
|
|
|
|
int listSize (char* list[])
|
|
{
|
|
int i=0;
|
|
while (list[i]) i++;
|
|
return i;
|
|
}
|
|
static int NUM_BOT_SKINS = 0;
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Set the name of the bot and update the userinfo
|
|
///////////////////////////////////////////////////////////////////////
|
|
void ACESP_SetName(edict_t *bot, char *name, char *skin, char *team)
|
|
{
|
|
float rnd;
|
|
char userinfo[MAX_INFO_STRING] = "";
|
|
char bot_skin[MAX_INFO_STRING] = "";
|
|
char bot_name[MAX_INFO_STRING] = "";
|
|
int i, r;
|
|
|
|
if (NUM_BOT_SKINS == 0)
|
|
NUM_BOT_SKINS = listSize(skinnames);
|
|
|
|
// Set the name for the bot.
|
|
// name
|
|
if(strlen(name) == 0)
|
|
{
|
|
// Randomly select from the name/skin table
|
|
if (num_botinfo > 0)
|
|
{
|
|
//int numtries = 0;
|
|
rnd = random();
|
|
for (i = 0; i < num_botinfo; i++)
|
|
if (rnd < ((float)(i+1)/(float)num_botinfo) )
|
|
{ r=i; break; }
|
|
// Search for an unused name starting at selected pos
|
|
for (i = r; i < num_botinfo; i++)
|
|
if (!bot_info[i].ingame_count)
|
|
{
|
|
Com_sprintf(bot_name, sizeof(bot_name), bot_info[i].name);
|
|
bot_info[i].ingame_count++;
|
|
break;
|
|
}
|
|
// If none found, loop back
|
|
if (strlen(bot_name) == 0)
|
|
for (i = 0; i < r; i++)
|
|
if (!bot_info[i].ingame_count)
|
|
{
|
|
Com_sprintf(bot_name, sizeof(bot_name), bot_info[i].name);
|
|
bot_info[i].ingame_count++;
|
|
break;
|
|
}
|
|
// If no more free bots in table, use a numbered name
|
|
if (strlen(bot_name) == 0)
|
|
Com_sprintf(bot_name, sizeof(bot_name), "ACEBot_%d",bot->count);
|
|
}
|
|
else
|
|
Com_sprintf(bot_name, sizeof(bot_name), "ACEBot_%d",bot->count);
|
|
}
|
|
else
|
|
// strncpy(bot_name, name);
|
|
Q_strncpyz(bot_name, sizeof(bot_name), name);
|
|
|
|
// skin
|
|
if (strlen(skin) == 0)
|
|
{ // check if this bot is in the table
|
|
for (i = 0; i < num_botinfo; i++)
|
|
if (!Q_stricmp(bot_name, bot_info[i].name))
|
|
{
|
|
Com_sprintf(bot_name, sizeof(bot_name), bot_info[i].name); // fix capitalization
|
|
Com_sprintf(bot_skin, sizeof(bot_skin), bot_info[i].skin);
|
|
bot_info[i].ingame_count++;
|
|
break;
|
|
}
|
|
if (strlen(bot_skin) == 0)
|
|
{ // randomly choose skin
|
|
rnd = random();
|
|
for (i = 0; i < NUM_BOT_SKINS; i++)
|
|
if (rnd < ((float)(i+1)/(float)NUM_BOT_SKINS) )
|
|
{ r=i; break; }
|
|
Com_sprintf(bot_skin, sizeof(bot_skin), skinnames[r]);
|
|
}
|
|
}
|
|
else
|
|
// strncpy(bot_skin, skin);
|
|
Q_strncpyz(bot_skin, sizeof(bot_skin), skin);
|
|
|
|
// initialise userinfo
|
|
memset (userinfo, 0, sizeof(userinfo));
|
|
|
|
// add bot's name/skin/hand to userinfo
|
|
Info_SetValueForKey (userinfo, "name", bot_name);
|
|
Info_SetValueForKey (userinfo, "skin", bot_skin);
|
|
Info_SetValueForKey (userinfo, "hand", "2"); // bot is center handed for now!
|
|
|
|
ClientConnect (bot, userinfo);
|
|
// Knightmare- removed this
|
|
//ACESP_SaveBots(); // make sure to save the bots
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Spawn the bot
|
|
///////////////////////////////////////////////////////////////////////
|
|
void ACESP_SpawnBot (char *team, char *name, char *skin, char *userinfo)
|
|
{
|
|
edict_t *bot;
|
|
|
|
bot = ACESP_FindFreeClient ();
|
|
|
|
if (!bot)
|
|
{
|
|
safe_bprintf (PRINT_MEDIUM, "Server is full, increase Maxclients.\n");
|
|
return;
|
|
}
|
|
|
|
bot->yaw_speed = 100; // yaw speed
|
|
bot->inuse = true;
|
|
bot->is_bot = true;
|
|
|
|
// To allow bots to respawn
|
|
if(userinfo == NULL)
|
|
ACESP_SetName(bot, name, skin, team);
|
|
else
|
|
ClientConnect (bot, userinfo);
|
|
|
|
G_InitEdict (bot);
|
|
|
|
InitClientResp (bot->client);
|
|
|
|
// locate ent at a spawn point
|
|
if (ctf->value)
|
|
{ // Knightmare- rewrote this
|
|
edict_t *player;
|
|
int i;
|
|
int team1count = 0, team2count = 0, team3count = 0;
|
|
int jointeam;
|
|
float r = random();
|
|
|
|
for (i = 1; i <= maxclients->value; i++)
|
|
{
|
|
player = &g_edicts[i];
|
|
if (!player->inuse || !player->client || player == bot)
|
|
continue;
|
|
switch (player->client->resp.ctf_team)
|
|
{
|
|
case CTF_TEAM1:
|
|
team1count++;
|
|
break;
|
|
case CTF_TEAM2:
|
|
team2count++;
|
|
break;
|
|
case CTF_TEAM3:
|
|
team3count++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ttctf->value)
|
|
{
|
|
if (team != NULL && strcmp(team,"red")==0)
|
|
jointeam = CTF_TEAM1;
|
|
else if (team != NULL && strcmp(team,"blue")==0)
|
|
jointeam = CTF_TEAM2;
|
|
else if (team != NULL && strcmp(team,"green")==0)
|
|
jointeam = CTF_TEAM3;
|
|
// join either of the outnumbered teams
|
|
else if (team1count == team2count && team1count < team3count)
|
|
jointeam = (r < 0.5) ? CTF_TEAM1 : CTF_TEAM2;
|
|
else if (team1count == team3count && team1count < team2count)
|
|
jointeam = (r < 0.5) ? CTF_TEAM1 : CTF_TEAM3;
|
|
else if (team2count == team3count && team2count < team1count)
|
|
jointeam = (r < 0.5) ? CTF_TEAM2 : CTF_TEAM3;
|
|
// join outnumbered team
|
|
else if (team1count < team2count && team1count < team3count)
|
|
jointeam = CTF_TEAM1;
|
|
else if (team2count < team1count && team2count < team3count)
|
|
jointeam = CTF_TEAM2;
|
|
else if (team3count < team1count && team3count < team2count)
|
|
jointeam = CTF_TEAM3;
|
|
// pick random team
|
|
else if (r < 0.33)
|
|
jointeam = CTF_TEAM1;
|
|
else if (r < 0.66)
|
|
jointeam = CTF_TEAM2;
|
|
else
|
|
jointeam = CTF_TEAM3;
|
|
}
|
|
else
|
|
{
|
|
if (team != NULL && strcmp(team,"red")==0)
|
|
jointeam = CTF_TEAM1;
|
|
else if (team != NULL && strcmp(team,"blue")==0)
|
|
jointeam = CTF_TEAM2;
|
|
// join outnumbered team
|
|
else if (team1count < team2count)
|
|
jointeam = CTF_TEAM1;
|
|
else if (team2count < team1count)
|
|
jointeam = CTF_TEAM2;
|
|
// pick random team
|
|
else if (r < 0.5)
|
|
jointeam = CTF_TEAM1;
|
|
else
|
|
jointeam = CTF_TEAM2;
|
|
}
|
|
ACESP_PutClientInServer (bot,false, jointeam);
|
|
}
|
|
else
|
|
ACESP_PutClientInServer (bot,false,0);
|
|
|
|
// make sure all view stuff is valid
|
|
ClientEndServerFrame (bot);
|
|
|
|
ACEIT_PlayerAdded (bot); // let the world know we added another
|
|
|
|
ACEAI_PickLongRangeGoal(bot); // pick a new goal
|
|
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Remove a bot by name or all bots
|
|
///////////////////////////////////////////////////////////////////////
|
|
void ACESP_RemoveBot(char *name)
|
|
{
|
|
int i, j;
|
|
qboolean freed=false;
|
|
edict_t *bot;
|
|
|
|
for(i=0;i<maxclients->value;i++)
|
|
{
|
|
bot = g_edicts + i + 1;
|
|
if(bot->inuse)
|
|
{
|
|
if(bot->is_bot && (Q_stricmp(bot->client->pers.netname,name)==0 || Q_stricmp(name,"all")==0))
|
|
{
|
|
bot->health = 0;
|
|
player_die (bot, bot, bot, 100000, vec3_origin);
|
|
// don't even bother waiting for death frames
|
|
bot->deadflag = DEAD_DEAD;
|
|
bot->inuse = false;
|
|
freed = true;
|
|
ACEIT_PlayerRemoved (bot);
|
|
|
|
safe_bprintf (PRINT_MEDIUM, "%s removed\n", bot->client->pers.netname);
|
|
// Knightmare- decrement this bot name's counter and exit loop
|
|
if (Q_stricmp(name,"all"))
|
|
{
|
|
for (j = 0; j < num_botinfo; j++)
|
|
if (!Q_stricmp(name, bot_info[j].name))
|
|
{
|
|
bot_info[j].ingame_count--;
|
|
bot_info[j].ingame_count = max(bot_info[j].ingame_count, 0);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!freed)
|
|
safe_bprintf (PRINT_MEDIUM, "%s not found\n", name);
|
|
// Knightmare- removed this
|
|
//ACESP_SaveBots(); // Save them again
|
|
}
|
|
|