ngunix/engine/bot.c

678 lines
20 KiB
C

/*
Copyright (C) 1996-1997 Id Software, Inc.
This program 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.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
//TODO: Support for COOP behaviour
// Handle botname parsing via COM routines
// Weapons - make them less Xuake specific
#include "globaldef.h"
#ifdef GLOBOT
#include "bot.h"
globot_t globot; // This struct is used to store global stuff that aint client specific
qboolean qUsesBotnames;
int iMaxBotnames;
char cBotName[32][16]; // eukara - 32 names max, 16 characters per name
/*
======
Random
This function only returns a random number in the range 0 - 1
======
*/
float Random (void)
{
return (rand ()&0x7fff) / ((float)0x7fff);
}
/*
===========
RandomRange
This function is pretty much the same as Random except
that it returns a value in the range min - max
===========
*/
float RandomRange (float min, float max)
{
return (Random() * (max - min)) + min;
}
/*
=======
Nextent
This function is used to cycle thro all avaliable clients
(this is just a recoded PF_nextent)
=======
*/
edict_t *Nextent (edict_t *edict)
{
int e;
edict_t *ent;
e = NUM_FOR_EDICT (edict); // Get the edictnum
while (1) // Loop until we get a return
{
e++; // Increase e with 1
if (e == sv.num_edicts) // If gone through all edict's
return sv.edicts; // then return
ent = EDICT_NUM (e); // Get the edict from the new edictnum
if (!ent->free) // If it exists
return ent; // then return it
}
}
/*
=========
Traceline
This function is used to decide if a bot can see his enemy or not
(this is just a recoded PF_traceline)
=========
*/
qboolean Traceline (vec3_t v1, vec3_t v2, edict_t *ent, edict_t *enemy)
{
trace_t trace;
trace = SV_Move (v1, vec3_origin, vec3_origin, v2, true, ent);
if (trace.fraction == 1) // If the trace found its way to the enemy
return true; // Then the bot can see him
return false; // Otherwise he cant
}
/*
==========
CalcAngles
This function is used to decide if the bot should turn around or not
(this is just a recoded PF_vectoangles)
==========
*/
void CalcAngles (vec3_t oldvector, vec3_t newvector)
{
float forward;
float yaw, pitch;
if (oldvector[1] == 0 && oldvector[0] == 0)
{
yaw = 0;
if (oldvector[2] > 0)
pitch = 90;
else
pitch = 270;
}
else
{
yaw = (int) (atan2(oldvector[1], oldvector[0]) * 180 / M_PI);
if (yaw < 0)
yaw += 360;
forward = sqrt (oldvector[0]*oldvector[0] + oldvector[1]*oldvector[1]);
pitch = (int) (atan2(oldvector[2], forward) * 180 / M_PI);
if (pitch < 0)
pitch += 360;
}
newvector[0] = pitch;
newvector[1] = yaw;
newvector[2] = 0;
}
/*
=======
BotInit
This function is used to calcualte the maximum
amount of clients and to set some default values
=======
*/
void BotInit (void)
{
client_t *client;
int i;
globot.world = PROG_TO_EDICT(pr_global_struct->world); // Find the world entity
globot.MaxClients = 0; // Reset the MaxClients counter
for (i=0, client=svs.clients; i<svs.maxclients; i++, client++) // Keep looping as long as there are clients
{
globot.MaxClients++; // Increase MaxClients with 1
client->edict->bot.ClientNo = -1; // We dont want anyone to have a client number until they have connected
client->edict->bot.menudone = false;// Of course we have not gone past the Team Fortress menu yet
client->edict->bot.Active = false;// Of course noone is active... the game havent started yet :)
client->edict->bot.isbot = false;// And noone is a bot either... or human...
}
for (i=1; i<16; i++)
globot.botactive[i] = false;
if (globot.MaxClients > 16) // We dont allow more then 16 players right now
globot.MaxClients = 16;
}
/*
===========
PickBotName
This function is used to give each bot a different name
TODO: Read and parse names from a file
===========
*/
char *PickBotName (int r)
{
if (r == 1) return "system32";
else if (r == 2) return "/dev/null";
else if (r == 3) return "Philip";
else if (r == 4) return "Travis";
else if (r == 5) return "Shawn";
else if (r == 6) return "Steve";
else if (r == 7) return "Dave";
else if (r == 8) return "Croft";
else if (r == 9) return "James";
else if (r == 10) return "Scott";
else if (r == 11) return "Ridley";
else if (r == 12) return "Mister";
else if (r == 13) return "Anubis";
else if (r == 14) return "Radon";
else if (r == 15) return "Maxwell";
return "player";
}
/*
============
UpdateClient
This function is used to "fake" a real
client so the bot shows up on the scoreboard
============
*/
void UpdateClient (client_t *client, int ClientNo)
{
MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
MSG_WriteByte (&sv.reliable_datagram, ClientNo);
MSG_WriteString (&sv.reliable_datagram, client->name);
MSG_WriteByte (&sv.reliable_datagram, svc_updatefrags);
MSG_WriteByte (&sv.reliable_datagram, ClientNo);
MSG_WriteShort (&sv.reliable_datagram, client->old_frags);
MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
MSG_WriteByte (&sv.reliable_datagram, ClientNo);
MSG_WriteByte (&sv.reliable_datagram, client->colors);
}
/*
==========
BotConnect
This function is used to connect the bot's
==========
*/
void BotConnect (client_t *client, int ClientNo, int color, char *name)
{
edict_t *self = PROG_TO_EDICT(pr_global_struct->self); // Make a backup of the current QC self
edict_t *bot = client->edict;
int randombot;
bot->bot.isbot = true; // And yes this is a bot
bot->bot.Active = true; // and hes active
bot->bot.enemy = bot; // Now why is he chasing himself?
bot->bot.connecttime = sv.time;
bot->bot.ClientNo = ClientNo; // Now we get a clientnumber
randombot = ceil (RandomRange (0, 15));
while (globot.botactive[randombot])
randombot = ceil (RandomRange (0, 15));
globot.botactive[randombot] = true;
if (name[0] != '0')
strcpy (client->name, name);
else
{
if(qUsesBotnames == true && cBotName[randombot])
strcpy (client->name, cBotName[randombot]);
else
strcpy (client->name, PickBotName (randombot));
}
if (color != 666)
{
client->colors = color * 14 + color; // The bot must have a color
bot->v.team = color + 1; // And to be in a team
}
else
{
client->colors = randombot * 14 + randombot; // The bot must have a color
bot->v.team = randombot + 1; // And to be in a team
}
client->old_frags = 0; // And since he just joined he cant have got any frags yet
bot->v.colormap = ClientNo; // Without this he wont be using any colored clothes
bot->v.netname = client->name - pr_strings; // Everyone wants a name
UpdateClient (client, ClientNo); // Update the scoreboard
pr_global_struct->self = EDICT_TO_PROG(bot); // Update the QC self to be the bot
PR_ExecuteProgram (pr_global_struct->SetNewParms); // Now call some QC functions
PR_ExecuteProgram (pr_global_struct->ClientConnect); // Now call some more QC functions
PR_ExecuteProgram (pr_global_struct->PutClientInServer); // Now call yet some more QC functions
pr_global_struct->self = EDICT_TO_PROG (self); // Get back to the backup
}
/*
==============
NextFreeClient
This function is used to find an empty client spot
==============
*/
void NextFreeClient (void)
{
client_t *client;
int i, color;
char name[32];
if (Cmd_Argc() == 2)
{
color = Q_atoi(Cmd_Argv(1));
sprintf (name, "0");
}
else if (Cmd_Argc() == 3)
{
color = Q_atoi(Cmd_Argv(1));
sprintf (name, "%s", Cmd_Argv(2));
}
else
{
color = 666;
sprintf (name, "0");
}
for (i=0, client=svs.clients; i<svs.maxclients; i++, client++) // Keep looping as long as there are free client slots
{
if (!client->edict->bot.Active) // We found a free client slot
{
BotConnect (client, i, color, name); // so why not connect a bot?
return; // We are done now
}
}
SV_BroadcastPrintf ("Unable to connect a bot, server is full.\n"); // No free client slots = no more bots allowed
}
/*
=======
MoveBot
This function is used to get the bot to actually
move and shoot and kill and destroy and wreak havoc and
slaughter and... oh sorry
=======
*/
void MoveBot (client_t *client, qboolean fire, edict_t *enemy)
{
if (fire) // If he has an enemy
{
vec3_t origin;
client->edict->v.button0 = 1; // Shoot, slaughter, kill, destroy!!!!
VectorSubtract (enemy->v.origin, client->edict->v.origin, origin);
if (Length(origin) > 200 || // If further away then 200 units from enemy
(int)client->edict->v.weapon & 4096) // or is using the axe
client->cmd.forwardmove = 400; // Then chase after the enemy
else // If closer then 200 units to enemy and not using the axe
client->cmd.forwardmove = -400; // Then stay at a distance
client->cmd.sidemove += (rand()&127)-64; // Make him strafe
if (client->cmd.sidemove > 400) // If strafing to fast
client->cmd.sidemove = 400; // Then limit the strafe speed
else if (client->cmd.sidemove < -400) // If strafing to fast
client->cmd.sidemove = -400; // Then limit the strafe speed
}
else // If he has no enemy
{
client->edict->v.button0 = 0; // Then why bother to shoot?
client->cmd.forwardmove = 400; // Let him run
}
switch ((int)skill->value)
{
case 1: // Medium
client->edict->v.v_angle[0] = client->edict->v.angles[0] + (rand()&15)-8;
client->edict->v.v_angle[1] = client->edict->v.angles[1] + (rand()&15)-8; // Adjust him to aim where he looks and make it not 100% accurate
client->edict->v.v_angle[2] = client->edict->v.angles[2] + (rand()&15)-8;
break;
case 2: // Hard
client->edict->v.v_angle[0] = client->edict->v.angles[0] + (rand()&7)-4;
client->edict->v.v_angle[1] = client->edict->v.angles[1] + (rand()&7)-4; // Adjust him to aim where he looks and make it not 100% accurate
client->edict->v.v_angle[2] = client->edict->v.angles[2] + (rand()&7)-4;
break;
case 3: // Nightmare
client->edict->v.v_angle[0] = client->edict->v.angles[0] + (rand()&3)-2;
client->edict->v.v_angle[1] = client->edict->v.angles[1] + (rand()&3)-2; // Adjust him to aim where he looks and make it not 100% accurate
client->edict->v.v_angle[2] = client->edict->v.angles[2] + (rand()&3)-2;
break;
default: // Easy
client->edict->v.v_angle[0] = client->edict->v.angles[0] + (rand()&31)-16;
client->edict->v.v_angle[1] = client->edict->v.angles[1] + (rand()&31)-16; // Adjust him to aim where he looks and make it not 100% accurate
client->edict->v.v_angle[2] = client->edict->v.angles[2] + (rand()&31)-16;
break;
}
}
/*
==============
SearchForEnemy
This function is used to search for
something for the bot to shoot at
==============
*/
void SearchForEnemy (client_t *client)
{
edict_t *bot = client->edict;
edict_t *nmy = bot->bot.enemy;
vec3_t eyes1, eyes2, origin, test;
int test2;
int num;
if (nmy != bot && // If he has an enemy that aint himself
nmy->v.health > 0) // and has some health
{
VectorAdd (nmy->v.origin, nmy->v.view_ofs, eyes1); // We want the origin of the enemies eyes
VectorAdd (bot->v.origin, bot->v.view_ofs, eyes2); // We want the origin of the bots eyes
if (Traceline (eyes1, eyes2, bot, nmy)) // If the bot can see his enemy
{
VectorSubtract (eyes1, eyes2, origin); // Get a nice vector
CalcAngles (origin, test); // And use it to see in what direction the enemy is
test2 = test[1] - bot->v.angles[1]; // Another shortcut
if (test2 > -80 && test2 < 80) // If enemy is in front of the bot so he can see him
{
VectorCopy (test, bot->v.angles); // Then turn towards the enemy
MoveBot (client, true, nmy); // and start running and shooting
return; // We are done here now...
}
}
}
// Guess we had no enemy or couldnt see him anymore
bot->bot.enemy = bot; // Set enemy to the bot himself again
nmy = Nextent(globot.world); // Prepare to loop through clients
num = 0;
while (num < globot.MaxClients) // Keep looping as long as there are clients
{
if (nmy->bot.Active && // Enemy is playing
nmy != bot && // and is not the bot himself
nmy->v.health > 0 && // and is alive
nmy->v.team != bot->v.team) // and in another team
{
VectorAdd (nmy->v.origin, nmy->v.view_ofs, eyes1); // We want the origin of the clients eyes
VectorAdd (bot->v.origin, bot->v.view_ofs, eyes2); // We want the origin of the bots eyes
if (Traceline (eyes1, eyes2, bot, nmy)) // If the bot can see his client
{
VectorSubtract (eyes1, eyes2, origin); // Get a nice vector
CalcAngles (origin, test); // And use it to see in what direction the client is
test2 = test[1] - bot->v.angles[1]; // Another shortcut
if (test2 > -60 && test2 < 60) // If client is in front of the bot so he can see him
{
bot->bot.enemy = nmy; // Then set him as the enemy
bot->bot.chase = -1; // and stop chasing
VectorCopy (test, bot->v.angles); // Then turn to the enemy
MoveBot (client, true, nmy); // and start running, jumping and shooting
return; // We are done here now...
}
}
}
num++;
nmy = Nextent(nmy); // If the client was'nt visible then continue the loop with the next client
}
// Guess we found no enemies
bot->v.button0 = 0; // So why waste ammo?
num = bot->bot.chase; // Are we already chasing someone?
if (num == -1) // If we arent
num = (ceil (rand()%globot.MaxClients)) + NUM_FOR_EDICT(globot.world); // Then find someone to chase
nmy = EDICT_NUM(num); // And find that client
if (nmy->bot.Active && // Enemy is playing
nmy != bot && // and is not the bot himself
nmy->v.health > 0 && // and is alive
nmy->v.team != bot->v.team) // and in another team
{
bot->bot.chase = num; // Then start chasing him
VectorSubtract (nmy->v.origin, bot->v.origin, origin); // Get a nice vector
CalcAngles (origin, test); // And use it to see in what direction the client is
bot->v.angles[0] = 0; // This is reset so it doesnt look like hes running into the floor if the chase client is below him
bot->v.angles[1] = test[1]; // Then turn the bot that way
bot->v.angles[2] = 0;
MoveBot (client, false, nmy); // And get him running
return;
}
bot->bot.chase = -1; // Guess we found noone to chase either... so we'll stand here and see if we find anyone the next frame
}
/*
============
SwitchWeapon
This is the function where the bot checks what ammo and weapons he has...
And switches to the best weapon avaliable...
============
*/
void SwitchWeapon (edict_t *bot)
{
int items = (int)bot->v.items;
int weapon = (int)bot->v.weapon;
if (bot->v.ammo_rockets >= 1 && // If the bot has some rockets
items & IT_ROCKET_LAUNCHER && // and the Rocket Launcher
weapon != IT_ROCKET_LAUNCHER) // and aint using it
{
bot->v.impulse = 7; // Then use the Rocket Launcher
return;
}
if (bot->v.ammo_cells >= 1 && // If the bot has some cells
bot->v.waterlevel <= 1 && // and is not in water
items & IT_LIGHTNING && // and the Lightning Gun
weapon != IT_LIGHTNING) // and aint using it
{
bot->v.impulse = 8; // Then use the Lightning Gun
return;
}
if (bot->v.ammo_nails >= 2 && // If the bot has some nails
items & IT_SUPER_NAILGUN && // and the Super Nailgun
weapon != IT_SUPER_NAILGUN) // and aint using it
{
bot->v.impulse = 5; // Then use the Super Nailgun
return;
}
if (bot->v.ammo_rockets >= 1 && // If the bot has some rockets
items & IT_GRENADE_LAUNCHER && // and the Grenade Launcher
weapon != IT_GRENADE_LAUNCHER) // and aint using it
{
bot->v.impulse = 6; // Then use the Grenade Launcher
return;
}
if (bot->v.ammo_nails >= 1 && // If the bot has some nails
items & IT_NAILGUN && // and the Nailgun
weapon != IT_NAILGUN) // and aint using it
{
bot->v.impulse = 4; // Then use the Nailgun
return;
}
if (bot->v.ammo_shells >= 2 && // If the bot has some sheels
items & IT_SUPER_SHOTGUN && // and the Super Shotgun
weapon != IT_SUPER_SHOTGUN) // and aint using it
{
bot->v.impulse = 3; // Then use the Super Shotgun
return;
}
if (bot->v.ammo_shells >= 1 && // If the bot has some shells
items & IT_SHOTGUN && // and the Shotgun
weapon != IT_SHOTGUN) // and aint using it
{
bot->v.impulse = 2; // Then use the Shotgun
return;
}
}
/*
===========
BotPreFrame
This function is where everything starts...
From here we search for enemies to hunt and shoot
and make the bot to respawn if killed...
===========
*/
void BotPreFrame (client_t *client)
{
edict_t *bot = client->edict;
client->cmd.forwardmove = 0; // Stop all bots from running
SwitchWeapon (bot);
if (bot->v.deadflag == DEAD_NO) // If the bot is alive
SearchForEnemy (client); // Then search for an enemy or someone to chase
else
{
bot->v.button0 = 0; // If dead then clear all buttons
bot->v.button1 = 0;
bot->v.button2 = 0;
}
if (bot->v.deadflag == DEAD_RESPAWNABLE) // If dead and ready to respawn
bot->v.button1 = 1; // Then respawn
}
/*
============
BotPostFrame
This function is used to check
if the bot is running into a wall
============
*/
void BotPostFrame (client_t *client)
{
edict_t *bot = client->edict;
if (bot->bot.chase != -1) // If we are chasing someone
{
if ((bot->v.velocity[0] < 20 && bot->v.velocity[0] > -20) && // And if our speed is slow
(bot->v.velocity[1] < 20 && bot->v.velocity[1] > -20)) // (running against a wall)
{
bot->bot.chase = -1; // Then find a new client to chase
}
}
// This last piece here is only used by Team Fortress
if (!bot->bot.menudone) // If we have not gone past the class selection menu
{
if (bot->v.health > 5) // If we have more health then 5
bot->bot.menudone = true; // Then this is not Team Fortress so there is no menu
// Looks like we are playing Team Fortress after all
else if (sv.time > bot->bot.connecttime + 2) // If bot has been active more then 2 seconds then choose a random class and leave the menu
bot->v.impulse = 10;
else if (sv.time > bot->bot.connecttime + 1 && // If bot has been active between 1 and 2 secs
teamplay->value) // and we are playing teamplay
bot->v.impulse = 5; // Then choose a random team
}
}
/*
========
Bot_Init
This function is what allows us to use
the command "addbot" from the console
========
*/
void Bot_Init (void)
{
char name[MAX_OSPATH];
Cmd_AddCommand ("addbot", NextFreeClient);
// eukara - loading botnames start
printf("Looking for botnames.cfg...\n");
sprintf (name, "%s/botnames.cfg", com_gamedir);
FILE *fd = fopen(name,"r");
if (fd == NULL)
{
printf("No bot definition file found.\n");
qUsesBotnames = false;
return;
}
qUsesBotnames = true;
iMaxBotnames = 0;
// id is OK as os
while ((fscanf(fd, "%16[^\n]\n", cBotName[iMaxBotnames]) == 1) && !feof(fd)) { // only load 16 chars...
iMaxBotnames++;
}
if(iMaxBotnames < svs.maxclients) // Just to be safe.
{
printf("Not enough bot names in botnames.cfg\n");
qUsesBotnames = false;
}
// eukara - loading botnames end
}
#endif