483 lines
12 KiB
C
483 lines
12 KiB
C
/*****************************************************************
|
|
|
|
Eraser Bot source code - by Ryan Feltrin, Added to by Acrid-
|
|
|
|
..............................................................
|
|
|
|
This file is Copyright(c) 1998, Ryan Feltrin, 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 Eraser, you MUST
|
|
include the following text (minus the BEGIN and END lines) in the
|
|
documentation for your modification.
|
|
|
|
--- BEGIN ---
|
|
|
|
The Eraser Bot is a product of Ryan Feltrin, and is available from
|
|
the Eraser Bot homepage, at http://impact.frag.com.
|
|
|
|
This program is a modification of the Eraser Bot, and is therefore
|
|
in NO WAY supported by Ryan Feltrin.
|
|
|
|
This program MUST NOT be sold in ANY form. If you have paid for
|
|
this product, you should contact Ryan Feltrin immediately, via
|
|
the Eraser Bot homepage.
|
|
|
|
--- END ---
|
|
|
|
..............................................................
|
|
|
|
You will find p_trail.c has not been included with the Eraser
|
|
source code release. This is NOT an error. I am unable to
|
|
distribute this file because it contains code that is bound by
|
|
legal documents, and signed by myself, never to be released
|
|
to the public. Sorry guys, but law is law.
|
|
|
|
I have therefore include the compiled version of these files
|
|
in .obj form in the src\Release and src\Debug directories.
|
|
So while you cannot edit and debug code within these files,
|
|
you can still compile this source as-is. Although these will only
|
|
work in MSVC v5.0, linux versions can be made available upon
|
|
request.
|
|
|
|
NOTE: When compiling this source, you will get a warning
|
|
message from the compiler, regarding the missing p_trail.c
|
|
file. Just ignore it, it will still compile fine.
|
|
|
|
..............................................................
|
|
|
|
I, Ryan Feltrin/Acrid-, hold no responsibility for any harm caused by the
|
|
use of this source code. I also am NOT willing to provide any form
|
|
of help or support for this source code. It is provided as-is,
|
|
as a service by me, with no documentation, other then the comments
|
|
contained within the code. If you have any queries, I suggest you
|
|
visit the "official" Eraser source web-board, at
|
|
http://www.telefragged.com/epidemic/. I will stop by there from
|
|
time to time, to answer questions and help with any problems that
|
|
may arise.
|
|
|
|
Otherwise, have fun, and I look forward to seeing what can be done
|
|
with this.
|
|
|
|
-Ryan Feltrin
|
|
-Acrid-
|
|
|
|
*****************************************************************/
|
|
/* bot_spawn.c */
|
|
|
|
#include "g_local.h"
|
|
#include "m_player.h"
|
|
#include "bot_procs.h"
|
|
///Q2 Camera Begin
|
|
#include "camclient.h"
|
|
///Q2 Camera End
|
|
#include "stdlog.h"
|
|
|
|
qboolean ClientConnect (edict_t *ent, char *userinfo, qboolean loadgame);
|
|
|
|
// BEGIN: SABIN code
|
|
edict_t *bot_GetLastFreeClient (void)
|
|
{
|
|
edict_t *bot;
|
|
int i;
|
|
|
|
for (i = maxclients->value; i > 0; i--)
|
|
{
|
|
bot = g_edicts + i + 1;
|
|
|
|
if (!bot->inuse)
|
|
break;
|
|
}
|
|
|
|
if (bot->inuse)
|
|
bot = NULL;
|
|
|
|
return bot;
|
|
}
|
|
// END: SABIN code
|
|
|
|
void respawn_bot (edict_t *self)
|
|
{
|
|
if (level.intermissiontime)
|
|
return;
|
|
|
|
self->s.event = EV_PLAYER_TELEPORT;
|
|
|
|
PutClientInServer(self);
|
|
|
|
self->last_goal = NULL;
|
|
self->enemy = self->goalentity = self->movetarget = NULL;
|
|
self->viewheight = 22;
|
|
|
|
// reset weapon to blaster
|
|
self->last_fire = level.time + 0.2;
|
|
self->fire_interval = FIRE_INTERVAL_BLASTER;
|
|
self->bot_fire = botBlaster;
|
|
|
|
self->bored_suicide_time = -1;
|
|
self->checkstuck_time = level.time;
|
|
self->last_reached_trail = level.time + 1;
|
|
|
|
self->client->killer_yaw = 0; // chaingun wind-up
|
|
self->avoid_ent = NULL;
|
|
self->flagpath_goal = NULL;
|
|
self->last_move_nocloser = level.time;
|
|
|
|
// go for it
|
|
walkmonster_start(self);
|
|
}
|
|
|
|
// Find an available edict, and initialize some default values
|
|
edict_t *G_SpawnBot ()
|
|
{
|
|
edict_t *bot;
|
|
|
|
if (!deathmatch->value)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if ((!bot_calc_nodes->value) && !loaded_trail_flag)
|
|
{
|
|
my_bprintf(PRINT_HIGH, "Route-table not found!\n");
|
|
return NULL;
|
|
}
|
|
|
|
last_bot_spawn = level.time;
|
|
|
|
// bot = G_Spawn();
|
|
bot = bot_GetLastFreeClient();
|
|
|
|
if (!bot)
|
|
{
|
|
gi.dprintf("No client spots available!\n");
|
|
return NULL;
|
|
}
|
|
|
|
// bot->bot_client = gi.TagMalloc (sizeof(struct gclient_s), TAG_GAME);
|
|
bot->bot_stats = gi.TagMalloc (sizeof(bot_stats_t), TAG_GAME);
|
|
|
|
if (!bot->bot_stats)
|
|
{
|
|
gi.dprintf("Could not allocate Bot Stats!\n");
|
|
return NULL;
|
|
}
|
|
|
|
|
|
bot->classname = "player";
|
|
|
|
bot->movetype = MOVETYPE_WALK;
|
|
bot->solid = SOLID_BBOX;
|
|
|
|
//K2:Begin//fixme remove
|
|
bot->takedamage = DAMAGE_YES;
|
|
//K2:End
|
|
|
|
VectorSet (bot->mins, -16, -16, -24);
|
|
VectorSet (bot->maxs, 16, 16, 32);
|
|
|
|
bot->health = bot->max_health = 100;
|
|
bot->mass = 200;
|
|
bot->gravity = 1;
|
|
|
|
bot->last_goal = NULL;
|
|
|
|
bot->pain = bot_pain;
|
|
bot->die = bot_die;
|
|
|
|
bot->monsterinfo.stand = bot_run; //bot_stand;
|
|
bot->monsterinfo.walk = bot_run; //bot_walk;
|
|
bot->monsterinfo.run = bot_run;
|
|
bot->monsterinfo.attack = bot_run;
|
|
bot->monsterinfo.melee = NULL;
|
|
bot->monsterinfo.sight = NULL;
|
|
|
|
bot->monsterinfo.scale = MODEL_SCALE;
|
|
|
|
bot->enemy = bot->goalentity = bot->movetarget = NULL;
|
|
|
|
players[num_players++] = bot;
|
|
|
|
bot->last_fire = level.time + 0.2;
|
|
bot->fire_interval = FIRE_INTERVAL_BLASTER;
|
|
bot->bot_fire = botBlaster;
|
|
|
|
bot->bored_suicide_time = -1;
|
|
bot->checkstuck_time = level.time;
|
|
|
|
bot->viewheight = 22;
|
|
bot->yaw_speed = 50; // turn at yaw_speed degrees per FRAME
|
|
|
|
bot->last_reached_trail = level.time + 1;
|
|
|
|
bot->avoid_ent = NULL;
|
|
bot->last_move_nocloser = level.time;
|
|
|
|
return bot;
|
|
}
|
|
|
|
// Perform the ClientBegin() and ClientBeginDeathmatch() functions in one routine, that is bot-specific
|
|
edict_t *spawn_bot (char *botname)
|
|
{
|
|
edict_t *bot, *chat;
|
|
bot_info_t *botdata=NULL;
|
|
//fixme debugger char skin[256];
|
|
char userinfo[MAX_INFO_STRING];
|
|
vec3_t spawn_origin, spawn_angles;
|
|
char WFclass[256];
|
|
|
|
if (!(botdata = GetBotData(botname)))
|
|
{
|
|
gi.dprintf("Unable to find bot, or no bots left\n");
|
|
return NULL;
|
|
}
|
|
|
|
|
|
bot = G_SpawnBot();
|
|
|
|
if (!bot)
|
|
{
|
|
gi.dprintf("Unable to spawn bot: cannot create entity\n");
|
|
return NULL;
|
|
}
|
|
|
|
bot->bot_client = true;
|
|
bot->client = &game.clients[bot-g_edicts-1];
|
|
|
|
memset(bot->client, 0, sizeof(*(bot->client)));
|
|
|
|
// copy the stats across from the bot config
|
|
botdata->ingame_count++;
|
|
bot->botdata = botdata;
|
|
|
|
strcpy(WFclass, botdata->wfclass);
|
|
|
|
// BEGIN: SABIN code
|
|
// initialise userinfo
|
|
memset (userinfo, 0, sizeof(userinfo));
|
|
|
|
// add bot's name/skin/hand to userinfo
|
|
Info_SetValueForKey (userinfo, "name", botdata->name);
|
|
Info_SetValueForKey (userinfo, "WFclass", botdata->wfclass);
|
|
Info_SetValueForKey (userinfo, "hand", "2"); // bot is center handed for now
|
|
// END: SABIN code
|
|
|
|
///Q2 Camera Begin
|
|
EntityListAdd(bot);
|
|
///Q2 Camera End
|
|
//WF START ACRID INITIAL CLASS SPAWNING
|
|
|
|
//set respawn protection time
|
|
bot->client->protecttime = level.time + RESPAWN_PROTECT_TIME;
|
|
// bot->client->pers.player_class = bot->client->pers.next_player_class;
|
|
if (strcmp("1", WFclass) == 0)
|
|
{ bot->client->pers.player_class = 1;
|
|
bot->client->pers.next_player_class = 1;}
|
|
else if (strcmp("2", WFclass) == 0)
|
|
{ bot->client->pers.player_class = 2;
|
|
bot->client->pers.next_player_class = 2;}
|
|
else if (strcmp("3", WFclass) == 0)
|
|
{ bot->client->pers.player_class = 3;
|
|
bot->client->pers.next_player_class = 3;}
|
|
else if (strcmp("4", WFclass) == 0)
|
|
{bot->client->pers.player_class = 4;
|
|
bot->client->pers.next_player_class = 4;}
|
|
else if (strcmp("5", WFclass) == 0)
|
|
{ bot->client->pers.player_class = 5;
|
|
bot->client->pers.next_player_class = 5;}
|
|
else if (strcmp("6", WFclass) == 0)
|
|
{ bot->client->pers.player_class = 6;
|
|
bot->client->pers.next_player_class = 6;}
|
|
else if (strcmp("7", WFclass) == 0)
|
|
{ bot->client->pers.player_class = 7;
|
|
bot->client->pers.next_player_class = 7;}
|
|
else if (strcmp("8", WFclass) == 0)
|
|
{ bot->client->pers.player_class = 8;
|
|
bot->client->pers.next_player_class = 8;}
|
|
else if (strcmp("9", WFclass) == 0)
|
|
{ bot->client->pers.player_class = 9;
|
|
bot->client->pers.next_player_class = 9;}
|
|
else if (strcmp("10", WFclass) == 0)
|
|
{ bot->client->pers.player_class = 10;
|
|
bot->client->pers.next_player_class = 10;}
|
|
//WF END ACRID FIXES SKIN BUGS,CLASS BUGS,WEAPONS BUG clean this up
|
|
ClientConnect (bot, userinfo, false);
|
|
|
|
if (ctf->value)
|
|
{
|
|
my_bprintf(PRINT_HIGH, "%s joined the %s team.\n",
|
|
bot->client->pers.netname, CTFTeamName(bot->client->resp.ctf_team));
|
|
sl_LogPlayerTeamChange( &gi,
|
|
bot->client->pers.netname,
|
|
CTFTeamName(bot->client->resp.ctf_team));
|
|
}
|
|
|
|
SelectSpawnPoint (bot, spawn_origin, spawn_angles);
|
|
//INSERT TESTS HERE
|
|
// botDebugPrint("SPAWN_BOT %s (ACRID)\n",userinfo);
|
|
|
|
VectorCopy(spawn_origin, bot->s.origin);
|
|
// bot->s.origin[2] += 1; // make sure off ground
|
|
|
|
VectorCopy(spawn_angles, bot->s.angles);
|
|
|
|
bot->client->killer_yaw = 0; // chaingun wind-up
|
|
|
|
// clear playerstate values
|
|
memset (&bot->client->ps, 0, sizeof(bot->client->ps));
|
|
|
|
bot->client->ps.pmove.origin[0] = bot->s.origin[0]*8;
|
|
bot->client->ps.pmove.origin[1] = bot->s.origin[1]*8;
|
|
bot->client->ps.pmove.origin[2] = bot->s.origin[2]*8;
|
|
|
|
bot->client->ps.fov = 90;
|
|
|
|
// send effect
|
|
gi.WriteByte (svc_muzzleflash);
|
|
gi.WriteShort (bot-g_edicts);
|
|
gi.WriteByte (MZ_LOGIN);
|
|
gi.multicast (bot->s.origin, MULTICAST_PVS);
|
|
|
|
// copy the bot stats
|
|
memcpy(bot->bot_stats, &(botdata->bot_stats), sizeof(bot_stats_t));
|
|
|
|
// set starting skill level
|
|
bot->skill_level = skill->value;
|
|
AdjustRatingsToSkill(bot);
|
|
|
|
// bot->s.modelindex = gi.modelindex(model);
|
|
bot->s.skinnum = bot-g_edicts - 1;
|
|
bot->s.modelindex = 255;
|
|
bot->s.modelindex2 = 255;
|
|
// botDebugPrint("SPAWN_BOT (ACRID)\n");
|
|
/*
|
|
bot->client->buttons = bot->s.modelindex;
|
|
bot->client->oldbuttons = bot->s.skinnum;
|
|
*/
|
|
bot->map = G_CopyString(botdata->name);
|
|
strcpy(bot->client->pers.netname, botdata->name);
|
|
my_bprintf(PRINT_HIGH, "%s entered the game", bot->client->pers.netname);
|
|
|
|
// set visible model vwep with 3.20
|
|
ShowGun(bot);
|
|
|
|
/*
|
|
if (view_weapons->value && (bot->s.modelindex2 == 255) && bot_show_connect_info->value)
|
|
{
|
|
my_bprintf(PRINT_HIGH, " (no view weapon)");
|
|
}
|
|
*/
|
|
my_bprintf(PRINT_HIGH, "\n");
|
|
|
|
bot_count++;
|
|
|
|
// generic bot stuff
|
|
if (!KillBox (bot))
|
|
{ // could't spawn in?
|
|
}
|
|
|
|
gi.linkentity (bot);
|
|
|
|
bot->viewheight = 22;
|
|
|
|
bot->inuse = true;
|
|
|
|
// go for it
|
|
walkmonster_start(bot);
|
|
|
|
if (random() < 0.3)
|
|
{
|
|
// spawn the greetings thinker
|
|
chat = G_Spawn();
|
|
chat->owner = bot;
|
|
chat->think = BotGreeting;
|
|
chat->nextthink = level.time + 1.5 + random();
|
|
}
|
|
bot->wf_team = bot->client->resp.ctf_team;
|
|
wf_InitPlayerClass(bot->client);//ACRID
|
|
|
|
//K2 botcam acrid
|
|
bot->client->resp.inServer=true;
|
|
|
|
return bot;
|
|
}
|
|
|
|
extern int num_clients;
|
|
//K2:Added for camera..must add player back to players array
|
|
void botAddPlayer(edict_t *self)
|
|
{
|
|
players[num_players] = self;
|
|
num_players++;
|
|
num_clients++;
|
|
return;
|
|
|
|
}
|
|
void botRemovePlayer(edict_t *self)
|
|
{
|
|
int i;
|
|
|
|
self->health = 0; // so other bots stop looking for us
|
|
|
|
// remove the client from the players array
|
|
for (i=0; i<num_players; i++)
|
|
if (players[i] == self)
|
|
break;
|
|
|
|
if (i == num_players) // didn't find them
|
|
{
|
|
gi.dprintf("WARNING: Unable to remove player from player[] array, problems will arise.\n");
|
|
return;
|
|
}
|
|
|
|
i++;
|
|
for (; i<num_players; i++)
|
|
players[i-1] = players[i];
|
|
|
|
players[i] = NULL;
|
|
|
|
|
|
// remove from team list
|
|
if (self->client->team)
|
|
{
|
|
self->client->team->num_players--;
|
|
self->client->team->num_bots--;
|
|
self->client->team = NULL;
|
|
}
|
|
|
|
self->client->resp.ctf_team = CTF_NOTEAM;
|
|
|
|
num_players--;
|
|
|
|
if (self->bot_client)
|
|
{
|
|
bot_count--;
|
|
self->botdata->ingame_count--;
|
|
}
|
|
else
|
|
{
|
|
num_clients--;
|
|
}
|
|
|
|
// tell all other bots not to look for this bot anymore
|
|
for (i=0; i<num_players; i++)
|
|
if (players[i]->enemy == self)
|
|
players[i]->enemy = players[i]->goalentity = NULL;
|
|
}
|
|
|
|
void botDisconnect(edict_t *self)
|
|
{ // disconnects a bot from the game
|
|
|
|
ClientDisconnect(self);
|
|
|
|
self->bot_client = false;
|
|
self->bot_stats = NULL;
|
|
}
|