thirtyflightsofloving/game/acebot_spawn.c
Knightmare66 d16b46e3cf Added re-implementation of func_plat2 and func_door2 from rogue to default Lazarus DLL.
Overhauled child entity movement in default Lazarus DLL.
Added bbox versions of various triggers to default Lazarus DLL.
Added level.maptype field to default Lazarus DLL.
Added entity class IDs to default Lazarus DLL.
Incremented savegame version for default Lazarus DLL.
2020-10-27 02:00:05 -04:00

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, token, sizeof(botname));
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, botname, sizeof(bot_info[num_botinfo].name));
Q_strncpyz (bot_info[num_botinfo].skin, token, sizeof(bot_info[num_botinfo].skin));
//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, name, sizeof(bot_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, skin, sizeof(bot_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
}