1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3 Copyright (C) 1998 Steve Yeager
4 Copyright (C) 2010 COR Entertainment, LLC.
5 
6 See below for Steve Yeager's original copyright notice.
7 Modified to GPL in 2002.
8 
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 
18 See the GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License along
21 with this program; if not, write to the Free Software Foundation, Inc.,
22 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 */
24 ///////////////////////////////////////////////////////////////////////
25 //
26 //  ACE - Quake II Bot Base Code
27 //
28 //  Version 1.0
29 //
30 //  This file is Copyright(c), Steve Yeager 1998, All Rights Reserved
31 //
32 //
33 //	All other files are Copyright(c) Id Software, Inc.
34 //
35 //	Please see liscense.txt in the source directory for the copyright
36 //	information regarding those files belonging to Id Software, Inc.
37 //
38 //	Should you decide to release a modified version of ACE, you MUST
39 //	include the following text (minus the BEGIN and END lines) in the
40 //	documentation for your modification.
41 //
42 //	--- BEGIN ---
43 //
44 //	The ACE Bot is a product of Steve Yeager, and is available from
45 //	the ACE Bot homepage, at http://www.axionfx.com/ace.
46 //
47 //	This program is a modification of the ACE Bot, and is therefore
48 //	in NO WAY supported by Steve Yeager.
49 
50 //	This program MUST NOT be sold in ANY form. If you have paid for
51 //	this product, you should contact Steve Yeager immediately, via
52 //	the ACE Bot homepage.
53 //
54 //	--- END ---
55 //
56 //	I, Steve Yeager, hold no responsibility for any harm caused by the
57 //	use of this source code, especially to small children and animals.
58 //  It is provided as-is with no implied warranty or support.
59 //
60 //  I also wish to thank and acknowledge the great work of others
61 //  that has helped me to develop this code.
62 //
63 //  John Cricket    - For ideas and swapping code.
64 //  Ryan Feltrin    - For ideas and swapping code.
65 //  SABIN           - For showing how to do true client based movement.
66 //  BotEpidemic     - For keeping us up to date.
67 //  Telefragged.com - For giving ACE a home.
68 //  Microsoft       - For giving us such a wonderful crash free OS.
69 //  id              - Need I say more.
70 //
71 //  And to all the other testers, pathers, and players and people
72 //  who I can't remember who the heck they were, but helped out.
73 //
74 ///////////////////////////////////////////////////////////////////////
75 
76 ///////////////////////////////////////////////////////////////////////
77 //
78 //  acebot_spawn.c - This file contains all of the
79 //                   spawing support routines for the ACE bot.
80 //
81 ///////////////////////////////////////////////////////////////////////
82 
83 #ifdef HAVE_CONFIG_H
84 #include "config.h"
85 #endif
86 
87 #include "game/g_local.h"
88 #include "game/m_player.h"
89 #include "acebot.h"
90 
91 #define MAX_BOTTMPFILE_COUNT 64 // arbitrary limit for bot .tmp files
92 	// mostly just for checking validity of the count field in the file.
93 
94 static size_t szr; // just for unused result warnings
95 
96 /*
97  * Bot File Functions
98  *
99  * for loading bots from custom<n>.cfg, team.cfg, and <mapname>.cfg
100  */
101 static struct loadbots_file_s
102 {
103 	FILE *pfile;
104 	int record_count;
105 } loadbots_file;
106 
loadbots_openfile(void)107 static void loadbots_openfile( void )
108 {
109 	char bot_filename[MAX_OSPATH];
110 	char stem[MAX_QPATH];
111 	int tmpcount;
112 	FILE *tmppfile;
113 	size_t result;
114 
115 	loadbots_file.pfile = NULL;
116 	loadbots_file.record_count = 0;
117 
118 	// custom<n>.cfg has priority over team.cfg
119 	if ( sv_custombots && sv_custombots->integer )
120 	{
121 		sprintf( stem, BOT_GAMEDATA"/custom%i.tmp", sv_custombots->integer );
122 	}
123 	else if ( TEAM_GAME )
124 	{
125 		strcpy(stem, BOT_GAMEDATA"/team.tmp");
126 	}
127 	else
128 	{
129 		sprintf(stem, BOT_GAMEDATA"/%s.tmp", level.mapname);
130 	}
131 
132 	if ( !gi.FullPath( bot_filename, sizeof(bot_filename), stem ) )
133 	{
134 		gi.dprintf("ACESP_LoadBots: not found: %s\n", stem);
135 	}
136 	else if ( (tmppfile = fopen(bot_filename, "rb")) == NULL )
137 	{
138 		gi.dprintf("ACESP_LoadBots: failed fopen for read: %s\n", bot_filename);
139 	}
140 	else
141 	{ // read the count
142 		result = fread( &tmpcount, sizeof(int), 1, tmppfile );
143 		if (result != 1 || tmpcount < 0 || tmpcount > MAX_BOTTMPFILE_COUNT)
144 		{
145 			gi.dprintf("ACESP_LoadBots: failed fread or invalid count in %s\n", bot_filename);
146 			fclose( tmppfile );
147 		}
148 		else if ( tmpcount == 0 )
149 		{
150 			gi.dprintf("ACESP_LoadBots: %s is empty\n", bot_filename);
151 			fclose( tmppfile );
152 		}
153 		else
154 		{
155 			loadbots_file.pfile = tmppfile;
156 			loadbots_file.record_count = tmpcount;
157 		}
158 	}
159 }
160 
loadbots_closefile(void)161 static void loadbots_closefile( void )
162 {
163 	if ( loadbots_file.pfile != NULL )
164 	{
165 		fclose( loadbots_file.pfile );
166 		loadbots_file.record_count = 0;
167 	}
168 }
169 
loadbots_readnext(char * p_userinfo_bfr)170 static size_t loadbots_readnext( char *p_userinfo_bfr )
171 {
172 	size_t result;
173 	char botname[PLAYERNAME_SIZE];
174 
175 	if ( loadbots_file.pfile == NULL || loadbots_file.record_count == 0 )
176 	{
177 		result = 0 ;
178 	}
179 	else
180 	{
181 		result = fread( p_userinfo_bfr, sizeof(char) * MAX_INFO_STRING, 1, loadbots_file.pfile );
182 		if ( result )
183 		{ // make sure name from file is valid
184 			Q_strncpyz2( botname, Info_ValueForKey( p_userinfo_bfr, "name" ), sizeof(botname) );
185 			ValidatePlayerName( botname, sizeof(botname) );
186 			Info_SetValueForKey( p_userinfo_bfr, "name", botname );
187 		}
188 	}
189 
190 	return result;  // 1 if ok, 0 if not
191 }
192 
193 
194 /*
195  * client_botupdate
196  *
197  * count bots and update bot information in clients
198  *
199  * see:
200  *  p_hud.c::G_UpdateStats()
201  *  sv_main.c::SV_StatusString()
202  *
203  */
client_botupdate(void)204 static int client_botupdate( void )
205 {
206 	int botnum;
207 	int botidx;
208 	char *botname;
209 	edict_t *pbot;
210 	int bots;
211 	edict_t *pclient;
212 	int clients;
213 	qboolean firstclient;
214 
215 	// count bots
216 	botnum = 0;
217 	clients = game.maxclients;
218 	for ( pbot=&g_edicts[clients]; clients--; --pbot )
219 	{
220 		if ( pbot->inuse && pbot->is_bot )
221 		{
222 			++botnum;
223 		}
224 	}
225 
226 	// update clients
227 	if ( botnum == 0 )
228 	{ // clear bot count
229 		for ( pclient = &g_edicts[1], clients=game.maxclients ; clients--; ++pclient )
230 		{ // in every active client
231 			if ( pclient->inuse )
232 			{
233 				pclient->client->ps.botnum = pclient->client->resp.botnum = 0;
234 			}
235 		}
236 		return 0;
237 	}
238 
239 	botidx = 0;
240 	bots = botnum;
241 	for ( pbot = &g_edicts[game.maxclients]; bots ; --pbot )
242 	{ // process each bot
243 		if ( pbot->inuse && pbot->is_bot )
244 		{
245 			botname = Info_ValueForKey( pbot->client->pers.userinfo, "name" );
246 			firstclient = true;
247 			for ( pclient = &g_edicts[1], clients=game.maxclients ; clients--; ++pclient )
248 			{ // for every active client, plus always update first client for server
249 				if ( pclient->inuse || firstclient )
250 				{
251 					pclient->client->ps.botnum = pclient->client->resp.botnum = botnum;
252 					strcpy( pclient->client->resp.bots[botidx].name, botname );
253 					strcpy( pclient->client->ps.bots[botidx].name, botname );
254 					pclient->client->resp.bots[botidx].score = pbot->client->resp.score;
255 					pclient->client->ps.bots[botidx].score   = pbot->client->resp.score;
256 					firstclient = false;
257 				}
258 			}
259 			++botidx;
260 			--bots;
261 		}
262 	}
263 
264 	return botnum;
265 }
266 
267 /**
268  * @brief Update bot info in client records
269  *
270  * @detail  Intended to be called once per frame, in RunFrame. First client
271  *          record should always have current bot info, so that server status
272  *          shows bots even when in intermission.
273  *          In ACE debug mode, reports when bot count changes. ACE debug mode
274  *          is controlled with "sv acedebug on","sv acedebug off"
275  *
276  */
ACESP_UpdateBots(void)277 void ACESP_UpdateBots( void )
278 {
279 	static int prev_count = 0;
280 	int count;
281 
282 	/* count bots, and, in debug mode, output changes. */
283 	count = client_botupdate();
284 	if ( debug_mode )
285 	{
286 		if ( count != prev_count )
287 			debug_printf("Bot count %i to %i\n", prev_count, count );
288 	}
289 	prev_count = count;
290 
291 }
292 
293 /*
294 ======
295  ACESP_SaveBots
296 
297  Save current bots to bots.tmp file, which is used for
298  creating custom<n>.tmp, team.tmp and <mapname>.tmp files
299 
300  also update bot information in client records
301 
302 ======
303  */
ACESP_SaveBots(void)304 void ACESP_SaveBots( void )
305 {
306     edict_t *bot;
307     FILE *pOut;
308 	int i,count;
309 	char full_path[MAX_OSPATH];
310 
311     count = client_botupdate(); // count bots and update clients
312 
313 	gi.FullWritePath( full_path, sizeof(full_path), BOT_GAMEDATA"/bots.tmp" );
314 	if ( ( pOut = fopen( full_path, "wb" )) == NULL )
315 	{
316 		gi.dprintf("ACESP_SaveBots: fopen for write failed: %s\n", full_path );
317 		return;
318 	}
319 
320 	if ( count > MAX_BOTTMPFILE_COUNT )
321 	{ // record count limit mostly just for file error checking
322 		count = MAX_BOTTMPFILE_COUNT;
323 		gi.dprintf("ACESP_SaveBots: count limited to (%i)\n, ", count );
324 	}
325 
326 	szr = fwrite(&count,sizeof (int),1,pOut); // Write number of bots
327 
328 	for (i = game.maxclients; i > 0 && count ; i--)
329 	{ // write all current bots to bots.tmp
330 		bot = &g_edicts[i];
331 		if (bot->inuse && bot->is_bot)
332 		{
333 			szr = fwrite(bot->client->pers.userinfo,sizeof (char) * MAX_INFO_STRING,1,pOut);
334 			--count;
335 		}
336 	}
337 
338     fclose(pOut);
339 
340 }
341 
342 /*
343 ======
344  ACESP_FindBot
345 
346  find bot by name
347 ======
348 */
ACESP_FindBot(const char * name)349 edict_t *ACESP_FindBot( const char *name )
350 {
351 	edict_t *pbot;
352 	edict_t *found_bot = NULL;
353 	int clients = game.maxclients;
354 
355 	for ( pbot=&g_edicts[clients]; clients-- ; --pbot )
356 	{
357 		if ( pbot->inuse && pbot->is_bot
358 			&& !strcmp( pbot->client->pers.netname, name ) )
359 		{
360 			found_bot = pbot;
361 			break;
362 		}
363 	}
364 
365 	return found_bot;
366 }
367 
368 /*
369  * game_census
370  *
371  * simple census, for non team.
372  * team games use p_client.c::TeamCensus()
373  */
374 typedef struct gamecensus_s
375 {
376 	int real;
377 	int bots;
378 } gamecensus_t;
379 
game_census(gamecensus_t * gamecensus)380 static void game_census( gamecensus_t *gamecensus )
381 {
382 	edict_t *pentity;
383 	int clients;
384 	int real = 0;
385 	int bots = 0;
386 
387 	clients = game.maxclients;
388 	for ( pentity = &g_edicts[1]; clients--; ++pentity )
389 	{
390 		if ( pentity->inuse )
391 		{
392 			if ( pentity->is_bot )
393 			{
394 				++bots;
395 			}
396 			else
397 			{
398 				if ( g_duel->integer )
399 				{ // in duel, spectators count as being ingame
400 					++real;
401 				}
402 				else if ( pentity->client->pers.spectator == 0 )
403 				{
404 					++real;
405 				}
406 			}
407 
408 		}
409 	}
410 	gamecensus->real = real;
411 	gamecensus->bots = bots;
412 }
413 
414 /*
415 ======
416  ACESP_LoadBots
417 
418  Called for a client on connect or disconnect
419  or for every client on ResetLevel()
420 
421  Also "unloads" bots for auto bot kick
422 
423 ======
424 */
425 
426 /*
427  * LoadBots sub-functions
428  *
429  * Team and Non-Team, With and Without Auto Bot Kick
430  *
431  * On entry:
432  *  '*.tmp' file has been opened, caller will close file
433  *  record count has been read and is known to be >0
434  *  file read position is at first bot record
435  */
436 
loadbots_team_botkick(edict_t * ent)437 static void loadbots_team_botkick( edict_t *ent )
438 {
439 	char userinfo[MAX_INFO_STRING];
440 	char *name;
441 	char *skin;
442 	int rec_count;
443 	teamcensus_t teamcensus;
444 	int spawnkicknum;
445 	int ingame_players;
446 	int ingame_bots;
447 	qboolean bot_spawned;
448 	qboolean replace_bot;
449 	int botreplace_team;
450 	edict_t *pbot;
451 	int result;
452 
453 	// count the ingame players and bots
454 	TeamCensus( &teamcensus );
455 
456 	if ( ent->client->pers.spectator && teamcensus.bots > 0 )
457 	{ // "spectator" is player who has not yet joined a team
458 		// nothing to do, unless bots have not been loaded.
459 		return;
460 	}
461 
462 	ingame_players = teamcensus.real;
463 	ingame_bots = teamcensus.bots;
464 
465 	spawnkicknum = sv_botkickthreshold->integer;
466 	if ( ingame_players >= spawnkicknum )
467 	{ // do not need any bots, kick any that are on server
468 		for ( pbot=&g_edicts[game.maxclients]; ingame_bots ; --pbot )
469 		{
470 			if ( pbot->inuse && pbot->is_bot )
471 			{
472 				ACESP_KickBot( pbot );
473 				--ingame_bots;
474 			}
475 		}
476 		return;
477 	}
478 
479 	replace_bot = false;
480 	botreplace_team = NO_TEAM; // default, either team.
481 	if ( teamcensus.total > spawnkicknum && ingame_bots > 0 )
482 	{  // a bot will be replaced
483 		replace_bot = true;
484 		if ( teamcensus.bots_blue > 0 && teamcensus.bots_red > 0 )
485 		{ // bots on both teams,
486 			botreplace_team = ent->dmteam; // replace same team, unless...
487 			if ( ent->dmteam == RED_TEAM && teamcensus.red < teamcensus.blue )
488 			{
489 				botreplace_team = BLUE_TEAM;
490 			}
491 			else if ( ent->dmteam == BLUE_TEAM && teamcensus.blue < teamcensus.red )
492 			{
493 				botreplace_team = RED_TEAM;
494 			}
495 		}
496 	}
497 
498 	// bot team assignment is done in ACESP_SetName(), called from ACESP_SpawnBot()
499 
500 	rec_count = loadbots_file.record_count;
501 	while ( rec_count-- )
502 	{
503 		result = loadbots_readnext( userinfo );
504 		if ( result == 0 )
505 		{ // file error
506 			break;
507 		}
508 
509 		name = Info_ValueForKey( userinfo, "name" );
510 		pbot = ACESP_FindBot( name );
511 		if ( pbot == NULL )
512 		{ // not on server
513 			if ( !replace_bot )
514 			{ // not benching a bot, so ok to spawn if limit allows
515 				if ( (ingame_players + ingame_bots) < spawnkicknum )
516 				{ // spawn a team bot
517 					skin = Info_ValueForKey( userinfo, "skin" );
518 					bot_spawned = ACESP_SpawnBot( name, skin, NULL);
519 					if ( bot_spawned )
520 					{
521 						++ingame_bots;
522 					}
523 				}
524 			}
525 		}
526 		else
527 		{ // already on server
528 			if ( replace_bot )
529 			{
530 				if ( botreplace_team == NO_TEAM || botreplace_team == pbot->dmteam )
531 				{ // on either team (NO_TEAM) or on specified team only
532 					ACESP_KickBot( pbot );
533 					--ingame_bots;
534 					replace_bot = false; // one time only
535 				}
536 			}
537 			else if ( (ingame_players + ingame_bots) > spawnkicknum )
538 			{ // apply bot kick threshold
539 				ACESP_KickBot( pbot );
540 				--ingame_bots;
541 			}
542 		}
543 	}
544 }
545 
loadbots_team(void)546 static void loadbots_team( void )
547 {
548 	char userinfo[MAX_INFO_STRING];
549 	char *name;
550 	char *skin;
551 	int rec_count;
552 	qboolean bot_spawned;
553 	edict_t *bot;
554 	int result;
555 
556 	// bot team assignment is done in ACESP_SetName(), called from ACESP_SpawnBot()
557 
558 	rec_count = loadbots_file.record_count;
559 	while ( rec_count-- )
560 	{ // load all bots in the file
561 		result = loadbots_readnext( userinfo );
562 		if (!result)
563 		{ // file read error
564 			break;
565 		}
566 
567 		name = Info_ValueForKey(userinfo, "name");
568 		bot = ACESP_FindBot( name );
569 		if (bot == NULL)
570 		{ // not on server, spawn a team bot
571 			skin = Info_ValueForKey(userinfo, "skin");
572 			bot_spawned = ACESP_SpawnBot( name, skin, NULL );
573 		}
574 	}
575 }
576 
loadbots_nonteam_botkick(void)577 static void loadbots_nonteam_botkick( void )
578 {
579 	char userinfo[MAX_INFO_STRING];
580 	char *name;
581 	int rec_count;
582 	int spawnkicknum;
583 	int ingame_players;
584 	int ingame_bots;
585 	qboolean bot_spawned;
586 	edict_t *pbot;
587 	int result;
588 	gamecensus_t gamecensus;
589 
590 	game_census( &gamecensus );
591 
592 	ingame_players = gamecensus.real;
593 	ingame_bots = gamecensus.bots;
594 
595 	if ( g_duel->integer )
596 	{ // duel mode can have 1 and only 1 bot
597 		if ( ingame_players > 0 )
598 		{ // 1 or 0 bots and 1 or more real players
599 			spawnkicknum = 2;
600 		}
601 		else
602 		{ // 1 bot or 0 bots, 0 real players
603 			spawnkicknum = 1;
604 		}
605 	}
606 	else
607 	{
608 		spawnkicknum = sv_botkickthreshold->integer;
609 	}
610 
611 	if ( ingame_players >= spawnkicknum )
612 	{ // do not need any bots, kick any that are on server
613 		for ( pbot=&g_edicts[game.maxclients]; ingame_bots ; pbot-- )
614 		{
615 			if ( pbot->inuse && pbot->is_bot )
616 			{
617 				ACESP_KickBot( pbot );
618 				--ingame_bots;
619 			}
620 		}
621 		return;
622 	}
623 
624 	rec_count = loadbots_file.record_count;
625 	while ( rec_count-- )
626 	{
627 		result = loadbots_readnext( userinfo );
628 		if ( result == 0 )
629 		{ // file error
630 			break;
631 		}
632 
633 		name = Info_ValueForKey( userinfo, "name" );
634 		pbot = ACESP_FindBot( name );
635 		if ( pbot == NULL )
636 		{ // not on server, spawn a bot if bot kick threshold allows
637 			if ( (ingame_players + ingame_bots) < spawnkicknum  )
638 			{ // spawn a non-team bot
639 				bot_spawned = ACESP_SpawnBot (NULL, NULL, userinfo);
640 				if ( bot_spawned )
641 				{
642 					++ingame_bots;
643 				}
644 			}
645 		}
646 		else
647 		{ // already on server
648 			if ( (ingame_players + ingame_bots) > spawnkicknum )
649 			{ // bot kick threshold
650 				ACESP_KickBot( pbot );
651 				--ingame_bots;
652 			}
653 		}
654 	}
655 }
656 
loadbots_nonteam(void)657 static void loadbots_nonteam( void )
658 {
659 	char userinfo[MAX_INFO_STRING];
660 	char *name;
661 	int rec_count;
662 	qboolean bot_spawned;
663 	edict_t *pbot;
664 	int result;
665 
666 	rec_count = loadbots_file.record_count;
667 	while ( rec_count-- )
668 	{
669 		result = loadbots_readnext( userinfo );
670 		if ( !result )
671 		{ // file read error
672 			break;
673 		}
674 		name = Info_ValueForKey( userinfo, "name" );
675 		pbot = ACESP_FindBot( name );
676 		if ( pbot == NULL )
677 		{ // not found on server, spawn a non-team bot
678 			bot_spawned = ACESP_SpawnBot (NULL, NULL, userinfo);
679 		}
680 	}
681 }
682 
ACESP_LoadBots(edict_t * ent)683 void ACESP_LoadBots( edict_t *ent )
684 {
685 
686 	if ( dmflags->integer & DF_BOTS )
687 	{ // bots disabled.
688 		// result of setting the dmflag to disable bots when there are
689 		// bots in the game is undefined.
690 		return;
691 	}
692 
693 	loadbots_openfile();
694 	if ( loadbots_file.record_count == 0 )
695 	{ // no bots to load
696 		loadbots_closefile();
697 		return;
698 	}
699 
700 	if ( (sv_botkickthreshold && sv_botkickthreshold->integer) || g_duel->integer )
701 	{ // auto botkick, duel mode allows only 1 bot
702 		if ( TEAM_GAME )
703 		{
704 			loadbots_team_botkick( ent );
705 		}
706 		else
707 		{
708 			loadbots_nonteam_botkick();
709 		}
710 	}
711 	else
712 	{ // no auto botkick
713 		/*
714 		 * TODO: see if it is possible to disable bot loading here
715 		 *  if there have been any callvote bot kicks in a game.
716 		 *  Because, if a new player enters, kicked bots will be reloaded.
717 		 */
718 		if ( TEAM_GAME )
719 		{
720 			loadbots_team();
721 		}
722 		else
723 		{
724 			loadbots_nonteam();
725 		}
726 	}
727 
728 	loadbots_closefile();
729 
730 }
731 
732 /*
733 ======
734  ACESP_FindBotNum
735 
736  called by server to determine bot kick threshold
737 ======
738  */
ACESP_FindBotNum(void)739 int ACESP_FindBotNum(void)
740 {
741 	int count;
742 
743 	if ( dmflags->integer & DF_BOTS )
744 	{ // bots disabled by dmflag bit
745 		return 0;
746 	}
747 
748 	loadbots_openfile();
749 	count = loadbots_file.record_count;
750 	loadbots_closefile();
751 
752 	return count;
753 }
754 
755 ///////////////////////////////////////////////////////////////////////
756 // Called by PutClient in Server to actually release the bot into the game
757 // Keep from killin' each other when all spawned at once
758 ///////////////////////////////////////////////////////////////////////
ACESP_HoldSpawn(edict_t * self)759 void ACESP_HoldSpawn(edict_t *self)
760 {
761 
762 	if ( !self->inuse || !self->is_bot )
763 	{
764 		gi.dprintf("ACEAI_HoldSpawn: bad call program error\n");
765 		return;
766 	}
767 
768 	if (!KillBox (self))
769 	{	// could't spawn in?
770 	}
771 
772 	gi.linkentity (self);
773 
774 	self->think = ACEAI_Think;
775 	self->nextthink = level.time + FRAMETIME;
776 
777 	// send effect
778 	gi.WriteByte (svc_muzzleflash);
779 	gi.WriteShort (self-g_edicts);
780 	gi.WriteByte (MZ_LOGIN);
781 	gi.multicast (self->s.origin, MULTICAST_PVS);
782 
783 	safe_bprintf (PRINT_MEDIUM, "%s entered the game\n", self->client->pers.netname);
784 }
785 
786 /*===
787   ACECO_ReadConfig()
788 
789   System-independent bot configuration file reader.
790   2010-08: Replaces function in acebot_config.cpp for Windows
791 
792   To be called with relative path to the .cfg file.
793 
794 ===*/
ACECO_ReadConfig(char * config_file)795 void ACECO_ReadConfig( char *config_file )
796 {
797 	char full_path[ MAX_OSPATH];
798 	FILE *fp;
799 	int k;
800 	size_t length, result;
801 	char *buffer;
802 	char *s;
803 	const char *delim = "\r\n";
804 	float tmpf;
805 
806 	//set bot defaults(in case no bot config file is present for that bot)
807 	botvals.skill = 1; //medium
808 	strcpy(botvals.faveweap, "None");
809 	for ( k = 1; k < 10; k++ )
810 		botvals.weapacc[k] = 0.75;
811 	botvals.awareness = 0.7; // 0.7 is 145 degree FOV
812 
813 	strcpy( botvals.chatmsg1, "%s: You are a real jerk %s!"    );
814 	strcpy( botvals.chatmsg2, "%s: Wait till next time %s."    );
815 	strcpy( botvals.chatmsg3, "%s: Life was better alive, %s!" );
816 	strcpy( botvals.chatmsg4, "%s: You will pay for this %s!"  );
817 	strcpy( botvals.chatmsg5, "%s: You're using a bot %s!"     );
818 	strcpy( botvals.chatmsg6, "%s: I will be hunting you %s!"  );
819 	strcpy( botvals.chatmsg7, "%s: It hurts %s...it hurts..."  );
820 	strcpy( botvals.chatmsg8, "%s: Just a lucky shot %s!"      );
821 
822 	if ( !gi.FullPath( full_path, sizeof(full_path), config_file ) )
823 	{ // bot not configured, use defaults
824 		return;
825 	}
826 	if ( (fp = fopen( full_path, "rb" )) == NULL )
827 	{
828 		gi.dprintf("ACECO_ReadConfig: failed open for read: %s\n", full_path );
829 		return;
830 	}
831 	if ( fseek(fp, 0, SEEK_END) )
832 	{ // seek error
833 		fclose( fp );
834 		return;
835 	}
836 	if ( (length = ftell(fp)) == (size_t)-1L )
837 	{ // tell error
838 		fclose( fp );
839 		return;
840 	}
841 	if ( fseek(fp, 0, SEEK_SET) )
842 	{ // seek error
843 		fclose( fp );
844 		return;
845 	}
846 	buffer = malloc( length + 1);
847 	if ( buffer == NULL )
848 	{ // memory allocation error
849 		fclose( fp );
850 		return;
851 	}
852 	result = fread( buffer, 1, length, fp );
853 	fclose( fp );
854 	if ( result != length )
855 	{ // read error
856 		free( buffer );
857 		return;
858 	}
859 	buffer[length] = 0;
860 
861 	// note: malloc'd buffer is modified by strtok
862 	if ( (s = strtok( buffer, delim )) != NULL )
863 		botvals.skill = atoi( s );
864 	if ( botvals.skill < 0 )
865 		botvals.skill = 0;
866 
867 	if ( s &&  ((s = strtok( NULL, delim )) != NULL) )
868 		strncpy( botvals.faveweap, s, sizeof(botvals.faveweap)-1 );
869 
870 	for(k = 1; k < 10; k++)
871 	{
872 		if ( s && ((s = strtok( NULL, delim )) != NULL ) )
873 		{
874 			tmpf = atof( s );
875 			if ( tmpf < 0.5f )
876 				tmpf = 0.5f;
877 			else if (tmpf > 1.0f )
878 				tmpf = 1.0f;
879 			botvals.weapacc[k] = tmpf;
880 		}
881 	}
882 
883 	if ( s && ((s = strtok( NULL, delim)) != NULL) )
884 	{
885 		tmpf = atof( s );
886 		if ( tmpf < 0.1f )
887 			tmpf = 0.1f;
888 		else if ( tmpf > 1.0f )
889 			tmpf = 1.0f;
890 		botvals.awareness = tmpf;
891 	}
892 
893 	if ( s && ((s = strtok( NULL, delim)) != NULL) )
894 		strncpy( botvals.chatmsg1, s, sizeof(botvals.chatmsg1)-1 );
895 	if ( s && ((s = strtok( NULL, delim)) != NULL) )
896 		strncpy( botvals.chatmsg2, s, sizeof(botvals.chatmsg2)-1 );
897 	if ( s && ((s = strtok( NULL, delim)) != NULL) )
898 		strncpy( botvals.chatmsg3, s, sizeof(botvals.chatmsg3)-1 );
899 	if ( s && ((s = strtok( NULL, delim)) != NULL) )
900 		strncpy( botvals.chatmsg4, s, sizeof(botvals.chatmsg4)-1 );
901 	if ( s && ((s = strtok( NULL, delim)) != NULL) )
902 		strncpy( botvals.chatmsg5, s, sizeof(botvals.chatmsg5)-1 );
903 	if ( s && ((s = strtok( NULL, delim)) != NULL) )
904 		strncpy( botvals.chatmsg6, s, sizeof(botvals.chatmsg6)-1 );
905 	if ( s && ((s = strtok( NULL, delim)) != NULL) )
906 		strncpy( botvals.chatmsg7, s, sizeof(botvals.chatmsg7)-1 );
907 	if ( s && ((s = strtok( NULL, delim)) != NULL) )
908 		strncpy( botvals.chatmsg8, s, sizeof(botvals.chatmsg8)-1 );
909 
910 	free( buffer );
911 
912 }
913 
914 /*
915 ======
916  ACESP_PutClientInServer
917 
918  Bot version of p_client.c:PutClientInServer()
919  Also see: ClientUserinfoChanged() and ClientChangeSkin()
920 
921 ======
922  */
ACESP_PutClientInServer(edict_t * bot,qboolean respawn)923 void ACESP_PutClientInServer (edict_t *bot, qboolean respawn )
924 {
925 	vec3_t	mins = {-16, -16, -24};
926 	vec3_t	maxs = {16, 16, 32};
927 	int		index, armor_index;
928 	vec3_t	spawn_origin, spawn_angles;
929 	gclient_t	*client;
930 	gitem_t		*item;
931 	int		i, k, done;
932 	client_persistant_t	saved;
933 	client_respawn_t	resp;
934 	char    *info;
935 	char playermodel[MAX_OSPATH] = " ";
936 	char modelpath[MAX_OSPATH] = " ";
937 	FILE *file;
938 	char userinfo[MAX_INFO_STRING];
939 	char bot_configfilename[MAX_OSPATH];
940 
941 	// find a spawn point
942 	// do it before setting health back up, so farthest
943 	// ranging doesn't count this client
944 	if(!g_tactical->integer)
945 		SelectSpawnPoint (bot, spawn_origin, spawn_angles);
946 
947 	index = bot - g_edicts - 1;
948 	client = bot->client;
949 	resp = bot->client->resp;
950 
951 	// init pers.* variables, save and restore userinfo variables (name, skin)
952 	memcpy(userinfo, client->pers.userinfo, MAX_INFO_STRING );
953 	InitClientPersistant (client);
954 	memcpy(client->pers.userinfo, userinfo, MAX_INFO_STRING );
955 
956 	// set netname from userinfo
957 	strncpy( bot->client->pers.netname,
958 			 (Info_ValueForKey( client->pers.userinfo, "name")),
959 			 sizeof(bot->client->pers.netname)-1);
960 
961 	// combine name and skin into a configstring
962 	gi.configstring( CS_PLAYERSKINS+index, va("%s\\%s",
963 			bot->client->pers.netname,
964 			(Info_ValueForKey( client->pers.userinfo, "skin"))));
965 
966 	// clear everything but the persistant data ( pers.* and resp.*)
967 	saved = client->pers;
968 	memset (client, 0, sizeof(*client));
969 	client->pers = saved;
970 	client->resp = resp;
971 
972 	// match p_client.c., all do not necessarily apply to bots
973 	client->is_bot = 1;  // this is a bot
974 	client->kill_streak = 0;
975 	client->homing_shots = 0;
976 	client->mapvote = 0;
977 	client->lasttaunttime = 0;
978 	client->rayImmunity = false;
979 
980 	// copy some data from the client to the entity
981 	FetchClientEntData (bot);
982 
983 	// clear entity values
984 	bot->groundentity = NULL;
985 	bot->client = &game.clients[index];
986 	if(g_spawnprotect->value)
987 		bot->client->spawnprotected = true;
988 	bot->takedamage = DAMAGE_AIM;
989 	bot->movetype = MOVETYPE_WALK;
990 	bot->viewheight = 24;
991 	bot->classname = "bot";
992 	bot->mass = 200;
993 	bot->solid = SOLID_BBOX;
994 	bot->deadflag = DEAD_NO;
995 	bot->air_finished = level.time + 12;
996 	bot->clipmask = MASK_PLAYERSOLID;
997 	bot->model = "players/martianenforcer/tris.md2";
998 	bot->pain = player_pain;
999 	bot->die = player_die;
1000 	bot->waterlevel = 0;
1001 	bot->watertype = 0;
1002 	bot->flags &= ~FL_NO_KNOCKBACK;
1003 	bot->svflags &= ~SVF_DEADMONSTER;
1004 	bot->is_jumping = false;
1005 
1006 	//vehicles
1007 	bot->in_vehicle = false;
1008 
1009 	//deathball
1010 	bot->in_deathball = false;
1011 
1012 	VectorCopy (mins, bot->mins);
1013 	VectorCopy (maxs, bot->maxs);
1014 	VectorClear (bot->velocity);
1015 
1016 	// clear playerstate values
1017 	memset (&bot->client->ps, 0, sizeof(client->ps));
1018 
1019 //ZOID
1020 	client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
1021 //ZOID
1022 
1023 	client->ps.fov = 90;
1024 
1025 	client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model);
1026 
1027 	// clear entity state values
1028 	bot->s.effects = 0;
1029 	bot->s.skinnum = bot - g_edicts - 1;
1030 	bot->s.modelindex = 255;		// will use the skin specified model
1031 	bot->s.modelindex2 = 255;		// custom gun model
1032 
1033 	info = Info_ValueForKey (bot->client->pers.userinfo, "skin");
1034 	i = 0;
1035 	done = false;
1036 	strcpy(playermodel, " ");
1037 	while(!done)
1038 	{
1039 		if((info[i] == '/') || (info[i] == '\\'))
1040 			done = true;
1041 		playermodel[i] = info[i];
1042 		if(i > 62)
1043 			done = true;
1044 		i++;
1045 	}
1046 	playermodel[i-1] = 0;
1047 
1048 	sprintf(modelpath, "players/%s/helmet.md2", playermodel);
1049 	Q2_FindFile (modelpath, &file); //does a helmet exist?
1050 	if(file) {
1051 		sprintf(modelpath, "players/%s/helmet.md2", playermodel);
1052 		bot->s.modelindex3 = gi.modelindex(modelpath);
1053 		fclose(file);
1054 	}
1055 	else
1056 		bot->s.modelindex3 = 0;
1057 
1058 	bot->s.modelindex4 = 0;
1059 
1060 	//check for gib file
1061 	bot->usegibs = 0; //alien is default
1062 	sprintf(modelpath, "players/%s/usegibs", playermodel);
1063 	Q2_FindFile (modelpath, &file);
1064 	if(file) { //use model specific gibs
1065 		bot->usegibs = 1;
1066 		sprintf(bot->head, "players/%s/head.md2", playermodel);
1067 		sprintf(bot->body, "players/%s/body.md2", playermodel);
1068 		sprintf(bot->leg, "players/%s/leg.md2", playermodel);
1069 		sprintf(bot->arm, "players/%s/arm.md2", playermodel);
1070 		fclose(file);
1071 	}
1072 
1073 	//check for class file
1074 	bot->ctype = 0;
1075 	sprintf(modelpath, "players/%s/human", playermodel);
1076 	Q2_FindFile (modelpath, &file);
1077 	if(file)
1078 	{
1079 		fclose(file);
1080 
1081 		//human
1082 		bot->ctype = 1;
1083 		if(g_tactical->integer || (classbased->value && !(rocket_arena->integer || instagib->integer || insta_rockets->value || excessive->value)))
1084 		{
1085 			if(g_tactical->integer)
1086 			{
1087 				//read class file(tactical only)
1088 				//example:
1089 				//100-150 (health)
1090 				//0-3 (armor type)
1091 				//0-1 (has bomb)
1092 				//0-1 (has detonator)
1093 				//0-1 (has mind eraser)
1094 				//0-1 (has vaporizor)
1095 
1096 				ParseClassFile(modelpath, bot);
1097 				if(bot->has_bomb)
1098 				{
1099 					bot->client->pers.inventory[ITEM_INDEX(FindItem("Human Bomb"))] = 1;
1100 					bot->client->pers.inventory[ITEM_INDEX(FindItem("bombs"))] = 1; //tactical note - humans will use same ammo, etc, just different weapons
1101 				}
1102 				item = FindItem("Blaster");
1103 			}
1104 			else
1105 			{
1106 				bot->health = bot->max_health = client->pers.max_health = client->pers.health = 100;
1107 				armor_index = ITEM_INDEX(FindItem("Jacket Armor"));
1108 				client->pers.inventory[armor_index] += 30;
1109 
1110 				client->pers.inventory[ITEM_INDEX(FindItem("Rocket Launcher"))] = 1;
1111 				client->pers.inventory[ITEM_INDEX(FindItem("rockets"))] = 10;
1112 				item = FindItem("Rocket Launcher");
1113 			}
1114 			client->pers.selected_item = ITEM_INDEX(item);
1115 			client->pers.inventory[client->pers.selected_item] = 1;
1116 			client->pers.weapon = item;
1117 		}
1118 	}
1119 	else
1120 	{
1121 		sprintf(modelpath, "players/%s/robot", playermodel);
1122 		Q2_FindFile (modelpath, &file);
1123 		if(file && !g_tactical->integer)
1124 		{
1125 			//robot - not used in tactical
1126 			bot->ctype = 2;
1127 			if(classbased->value && !(rocket_arena->integer || instagib->integer || insta_rockets->value || excessive->value))
1128 			{
1129 				bot->health = bot->max_health = client->pers.max_health = client->pers.health = 85;
1130 				armor_index = ITEM_INDEX(FindItem("Jacket Armor"));
1131 				client->pers.inventory[armor_index] += 175;
1132 			}
1133 			fclose(file);
1134 		}
1135 		else
1136 		{
1137 			//alien
1138 			bot->ctype = 0;
1139 			if(g_tactical->integer || (classbased->value && !(rocket_arena->integer || instagib->integer || insta_rockets->value || excessive->value)))
1140 			{
1141 				bot->health = bot->max_health = client->pers.max_health = client->pers.health = 150;
1142 				if(g_tactical->integer)
1143 				{
1144 					sprintf(modelpath, "players/%s/alien", playermodel);
1145 					Q2_FindFile (modelpath, &file);
1146 					if(file)
1147 					{
1148 						ParseClassFile(modelpath, bot);
1149 						if(bot->has_bomb)
1150 						{
1151 							bot->client->pers.inventory[ITEM_INDEX(FindItem("Alien Bomb"))] = 1;
1152 							bot->client->pers.inventory[ITEM_INDEX(FindItem("bombs"))] = 1; //tactical note - humans will use same ammo, etc, just different weapons
1153 						}
1154 					}
1155 					item = FindItem("Blaster");
1156 					client->pers.selected_item = ITEM_INDEX(item);
1157 					client->pers.inventory[client->pers.selected_item] = 0;
1158 
1159 					item = FindItem("Alien Blaster");
1160 				}
1161 				else
1162 				{
1163 					client->pers.inventory[ITEM_INDEX(FindItem("Alien Disruptor"))] = 1;
1164 					client->pers.inventory[ITEM_INDEX(FindItem("cells"))] = 100;
1165 					item = FindItem("Alien Disruptor");
1166 				}
1167 				client->pers.selected_item = ITEM_INDEX(item);
1168 				client->pers.inventory[client->pers.selected_item] = 1;
1169 				client->pers.weapon = item;
1170 			}
1171 		}
1172 	}
1173 
1174 	//has to be done after determining the class/team - note - we don't care about spawn distances in tactical
1175 	if(g_tactical->integer)
1176 		SelectSpawnPoint (bot, spawn_origin, spawn_angles);
1177 
1178 	client->ps.pmove.origin[0] = spawn_origin[0]*8;
1179 	client->ps.pmove.origin[1] = spawn_origin[1]*8;
1180 	client->ps.pmove.origin[2] = spawn_origin[2]*8;
1181 
1182 	bot->s.frame = 0;
1183 	VectorCopy (spawn_origin, bot->s.origin);
1184 	bot->s.origin[2] += 1;	// make sure off ground
1185 
1186 	// set the delta angle
1187 	for (i=0 ; i<3 ; i++)
1188 		client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - client->resp.cmd_angles[i]);
1189 
1190 	bot->s.angles[PITCH] = 0;
1191 	bot->s.angles[YAW] = spawn_angles[YAW];
1192 	bot->s.angles[ROLL] = 0;
1193 	VectorCopy (bot->s.angles, client->ps.viewangles);
1194 	VectorCopy (bot->s.angles, client->v_angle);
1195 
1196 	// force the current weapon up
1197 	client->newweapon = client->pers.weapon;
1198 	ChangeWeapon (bot);
1199 
1200 	bot->enemy = NULL;
1201 	bot->movetarget = NULL;
1202 	bot->yaw_speed = 37; // bot turning speed. angle in degrees
1203 	bot->state = STATE_MOVE;
1204 
1205 	// Set the current node
1206 	bot->current_node = ACEND_FindClosestReachableNode(bot,NODE_DENSITY, NODE_ALL);
1207 	bot->goal_node = bot->current_node;
1208 	bot->next_node = bot->current_node;
1209 	bot->next_move_time = level.time;
1210 	bot->suicide_timeout = level.time + 15.0;
1211 
1212 	if ( !respawn )
1213 	{
1214 		/*
1215 		 * on initial spawn, load bot configuration
1216 		 * from botinfo/<botname>.cfg file
1217 		 * ReadConfig sets defaults if there is no such file.
1218 		 */
1219 		info = Info_ValueForKey (bot->client->pers.userinfo, "name");
1220 		sprintf( bot_configfilename, BOT_GAMEDATA"/%s.cfg", info );
1221 		ACECO_ReadConfig(bot_configfilename);
1222 
1223 		//set config items
1224 		bot->skill = botvals.skill;
1225 		strcpy(bot->faveweap, botvals.faveweap);
1226 		for(k = 1; k < 10; k++)
1227 			bot->weapacc[k] = botvals.weapacc[k];
1228 		bot->accuracy = 0.75; //start with this(changes when bot selects a weapon
1229 		bot->awareness = botvals.awareness;
1230 		strcpy(bot->chatmsg1, botvals.chatmsg1);
1231 		strcpy(bot->chatmsg2, botvals.chatmsg2);
1232 		strcpy(bot->chatmsg3, botvals.chatmsg3);
1233 		strcpy(bot->chatmsg4, botvals.chatmsg4);
1234 		strcpy(bot->chatmsg5, botvals.chatmsg5);
1235 		strcpy(bot->chatmsg6, botvals.chatmsg6);
1236 		strcpy(bot->chatmsg7, botvals.chatmsg7);
1237 		strcpy(bot->chatmsg8, botvals.chatmsg8);
1238 
1239 		/*
1240 		 * adjust skill according to cvar. Single Player menu selections
1241 		 *  force the cvar.
1242 		 *   0 : forces all to 0 skill (single player easy)
1243 		 *   1 : skill is cfg setting  (single player medium)
1244 		 *   2 : skill is cfg setting setting plus 1 (single player hard)
1245 		 *   3 : forces all to skill 3 (single player ultra)
1246 		 */
1247 		if( skill->integer == 0 )
1248 		{
1249 			bot->skill = 0; //dumb as a box of rocks
1250 		}
1251 		else if ( skill->integer == 2 )
1252 		{
1253 			bot->skill += 1;
1254 			if(bot->skill > 3)
1255 				bot->skill = 3;
1256 		}
1257 		else if ( skill->integer >= 3 )
1258 		{
1259 			bot->skill = 3;
1260 		}
1261 
1262 		/*
1263 		 * clear the weapon accuracy statistics.
1264 		 * for testing aim related settings.
1265 		 */
1266 		for(i = 0; i < 9; i++)
1267 		{
1268 			client->resp.weapon_shots[i] = 0;
1269 			client->resp.weapon_hits[i] = 0;
1270 		}
1271 	}
1272 
1273 	// If we are not respawning hold off for up to three seconds before releasing into game
1274     if(!respawn)
1275 	{
1276 		bot->think = ACESP_HoldSpawn;
1277 		bot->nextthink = level.time + random()*3.0; // up to three seconds
1278 	}
1279 	else
1280 	{
1281 		if (!KillBox (bot))
1282 		{	// could't spawn in?
1283 		}
1284 
1285 		bot->s.event = EV_OTHER_TELEPORT; //fix "player flash" bug
1286 		gi.linkentity (bot);
1287 
1288 		bot->think = ACEAI_Think;
1289 		bot->nextthink = level.time + FRAMETIME;
1290 
1291 			// send effect
1292 		gi.WriteByte (svc_muzzleflash);
1293 		gi.WriteShort (bot-g_edicts);
1294 		gi.WriteByte (MZ_LOGIN);
1295 		gi.multicast (bot->s.origin, MULTICAST_PVS);
1296 	}
1297 	client->spawnprotecttime = level.time;
1298 
1299 	//unlagged
1300 	if ( g_antilag->integer) {
1301 		G_ResetHistory( bot );
1302 		// and this is as good a time as any to clear the saved state
1303 		bot->client->saved.leveltime = 0;
1304 	}
1305 
1306 }
1307 
1308 ///////////////////////////////////////////////////////////////////////
1309 // Respawn the bot
1310 ///////////////////////////////////////////////////////////////////////
ACESP_Respawn(edict_t * self)1311 void ACESP_Respawn (edict_t *self)
1312 {
1313 	CopyToBodyQue (self);
1314 
1315 	ACESP_PutClientInServer( self, true ); // respawn
1316 
1317 	// add a teleportation effect
1318 	self->s.event = EV_PLAYER_TELEPORT;
1319 
1320 		// hold in place briefly
1321 	self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
1322 	self->client->ps.pmove.pm_time = 14;
1323 
1324 	self->client->respawn_time = level.time;
1325 
1326 }
1327 
1328 ///////////////////////////////////////////////////////////////////////
1329 // Find a free client spot
1330 ///////////////////////////////////////////////////////////////////////
ACESP_FindFreeClient(void)1331 edict_t *ACESP_FindFreeClient (void)
1332 {
1333 	int clients;
1334 	edict_t *pbot = NULL;
1335 	edict_t *found_bot = NULL;
1336 	int maxcount = 0;
1337 
1338 	// find current maximum count field, used for bot naming
1339 	clients = game.maxclients;
1340 	for ( pbot=&g_edicts[clients]; clients--; --pbot )
1341 	{
1342 		if ( pbot->is_bot && (pbot->count > maxcount) )
1343 		{
1344 			maxcount = pbot->count;
1345 		}
1346 	}
1347 	// find a free slot for new bot
1348 	clients = game.maxclients;
1349 	for ( pbot=&g_edicts[clients]; clients--; --pbot )
1350 	{ // reverse search, bots are assigned from end of list
1351 		if ( !pbot->inuse )
1352 		{
1353 			pbot->count = maxcount + 1; // for new bot's generic name
1354 			found_bot = pbot;
1355 			break;
1356 		}
1357 	}
1358 
1359 	return found_bot;
1360 }
1361 
1362 /*
1363  * ACESP_SetName()
1364  *
1365  * determine bot name and skin
1366  * for team games, select the team
1367  */
ACESP_SetName(edict_t * bot,char * name,char * skin,char * userinfo)1368 void ACESP_SetName(edict_t *bot, char *name, char *skin, char *userinfo )
1369 {
1370 	char bot_skin[MAX_INFO_STRING];
1371 	char bot_name[MAX_INFO_STRING];
1372 	char playerskin[MAX_INFO_STRING];
1373 	char playermodel[MAX_INFO_STRING];
1374 	int i, j, k, copychar;
1375 	teamcensus_t teamcensus;
1376 	char *skin2;
1377 
1378 	if(strlen(name) == 0)
1379 	{ // generate generic name, set model/skin to default
1380 		sprintf(bot_name,"ACEBot_%d",bot->count);
1381 		sprintf(bot_skin,"martianenforcer/default");
1382 		skin2 = bot_skin;
1383 	}
1384 	else
1385 	{ // name and skin from args
1386 		strcpy(bot_name,name);
1387 		skin2 = skin;
1388 	}
1389 
1390 	bot->dmteam = NO_TEAM;
1391 	bot->teamset = false;
1392 	if ( TEAM_GAME )
1393 	{
1394 		// extract model from <model/skin> info record
1395 		copychar = false;
1396 		strcpy(playerskin, " ");
1397 		strcpy(playermodel, " ");
1398 		j = k = 0;
1399 		for(i = 0; i <= (int)strlen(skin2) && i < MAX_INFO_STRING; i++)
1400 		{
1401 			if(copychar){
1402 				playerskin[k] = skin2[i];
1403 				k++;
1404 			}
1405 			else {
1406 				playermodel[j] = skin2[i];
1407 				j++;
1408 			}
1409 			if(skin2[i] == '/')
1410 				copychar = true;
1411 		}
1412 		playermodel[j] = 0;
1413 
1414 		// assign bot to a team and set skin
1415 		TeamCensus( &teamcensus ); // apply team balancing rules
1416 		bot->dmteam = teamcensus.team_for_bot;
1417 		if ( bot->dmteam == BLUE_TEAM )
1418 		{
1419 			strcpy(playerskin, "blue");
1420 			bot->teamset = true;
1421 		}
1422 		else if ( bot->dmteam == RED_TEAM )
1423 		{
1424 			strcpy(playerskin, "red");
1425 			bot->teamset = true;
1426 		}
1427 		else
1428 		{ // not expected. probably a program error.
1429 			gi.dprintf("ACESP_SetName: bot team skin program error.\n");
1430 			strcpy( playerskin, "default" );
1431 		}
1432 		// concatenate model and new skin
1433 		strcpy(bot_skin, playermodel);
1434 		strcat(bot_skin, playerskin);
1435 	}
1436 	else
1437 	{ // non-team
1438 		if ( strlen(skin2) == 0 )
1439 		{ // if no skin yet, choose model/skin randomly
1440 			if ( rand() & 1 )
1441 				sprintf(bot_skin,"martianenforcer/red");
1442 			else
1443 				sprintf(bot_skin,"martianenforcer/blue");
1444 		}
1445 		else
1446 			strcpy(bot_skin,skin2);
1447 	}
1448 
1449 	// initialise userinfo
1450 	memset( userinfo, 0, MAX_INFO_STRING );
1451 
1452 	// add bot's name\model/skin\hand to userinfo
1453 	Info_SetValueForKey (userinfo, "name", bot_name);
1454 	Info_SetValueForKey (userinfo, "skin", bot_skin);
1455 	Info_SetValueForKey (userinfo, "hand", "2"); // bot is center handed for now!
1456 
1457 }
1458 
1459 /*
1460  * ACESP_ClientConnect
1461  *
1462  * bot version of p_client.c::ClientConnect()
1463  *
1464  */
ACESP_ClientConnect(edict_t * pbot)1465 qboolean ACESP_ClientConnect( edict_t *pbot )
1466 {
1467 	edict_t *pclient;
1468 	int clients;
1469 	char *name = pbot->client->pers.netname;
1470 
1471 	// check for collison with a player name
1472 	clients = game.maxclients;
1473 	for ( pclient = &g_edicts[1]; clients--; pclient++ )
1474 	{
1475 		if ( pclient->inuse && pclient != pbot &&
1476 				!strcmp( pclient->client->pers.netname, name  ) )
1477 		{ //
1478 			return false;
1479 		}
1480 	}
1481 
1482 
1483 	return true;
1484 }
1485 
1486 /*
1487  * ACESP_SpawnBot
1488  *
1489  * initial spawn from LoadBots() or server command
1490  *
1491  */
ACESP_SpawnBot(char * name,char * skin,char * userinfo)1492 qboolean ACESP_SpawnBot (char *name, char *skin, char *userinfo)
1493 {
1494 	char new_userinfo[MAX_INFO_STRING];
1495 	edict_t *pbot;
1496 	qboolean connect_allowed = false;
1497 
1498 
1499 	pbot = ACESP_FindFreeClient ();
1500 	if (!pbot)
1501 	{ // no free client record
1502 		gi.dprintf("Server is full, maxclients is %d\n", game.maxclients);
1503 		return false;
1504 	}
1505 	// there is a free client record, use it
1506 	pbot->inuse = true;
1507 	pbot->is_bot = true;
1508 
1509 	if ( userinfo == NULL )
1510 	{ // team bot or "sv addbot", generate userinfo.
1511 		// for team games, select team
1512 		ACESP_SetName( pbot, name, skin, new_userinfo );
1513 	}
1514 	else
1515 	{ // non-team bot , userinfo from *.tmp file
1516 		// copy to local userinfo record
1517 		memcpy( new_userinfo, userinfo, MAX_INFO_STRING );
1518 	}
1519 	// store userinfo in client
1520 	memcpy( pbot->client->pers.userinfo, new_userinfo, MAX_INFO_STRING );
1521 	// set name from userinfo
1522 	memcpy( pbot->client->pers.netname, Info_ValueForKey( new_userinfo, "name"),
1523 				sizeof( pbot->client->pers.netname ) );
1524 
1525 	// attempt connection to server
1526 	connect_allowed = ACESP_ClientConnect( pbot );
1527 	if( !connect_allowed )
1528 	{
1529 		/* Tony: Sometimes bots are refused entry to servers - give up gracefully */
1530 		// safe_bprintf (PRINT_MEDIUM, "Bot was refused entry to server.\n");
1531 		gi.dprintf("%s (bot) failed connection to server\n", pbot->client->pers.netname );
1532 		// release client record and exit
1533 		pbot->inuse = false;
1534 		pbot->is_bot = false;
1535 		return false;
1536 	}
1537 	pbot->client->pers.connected = true;
1538 	gi.dprintf("%s (bot) connected\n", pbot->client->pers.netname );
1539 
1540 	// some basic initial value setting
1541 	G_InitEdict( pbot );
1542 	InitClientResp( pbot->client);
1543 
1544 	// initial spawn
1545 	ACESP_PutClientInServer( pbot, false );
1546 
1547 	ACESP_SaveBots(); // update bots.tmp and clients bot information
1548 
1549 	if ( g_duel->integer )
1550 	{
1551 		ClientPlaceInQueue( pbot );
1552 		ClientCheckQueue( pbot );
1553 	}
1554 
1555 	// make sure all view stuff is valid
1556 	ClientEndServerFrame( pbot );
1557 
1558 	ACEAI_PickLongRangeGoal( pbot ); // pick a new goal
1559 
1560 	return true;
1561 }
1562 
1563 ///////////////////////////////////////////////////////////////////////
1564 // Remove/Kick Bots
1565 ///////////////////////////////////////////////////////////////////////
1566 
1567 /*===
1568  remove_bot()
1569 
1570  common routine for removal or kick of a bot
1571  adapted from player_die(), and previous ACESP_RemoveBot(), ACESP_KickBot()
1572 
1573 === */
remove_bot(edict_t * bot)1574 static void remove_bot( edict_t *bot )
1575 {
1576 
1577 	VectorClear( bot->avelocity );
1578 
1579 	if ( bot->in_vehicle )
1580 	{
1581 		VehicleDeadDrop( bot );
1582 	}
1583 	if(ctf->value)
1584 	{
1585 		CTFDeadDropFlag(bot, NULL);
1586 	}
1587 	if ( bot->in_deathball )
1588 	{
1589 		DeadDropDeathball(bot);
1590 	}
1591 
1592 	if ( g_duel->integer )
1593 	{// duel mode, we need to bump people down the queue if its the player in game leaving
1594 		MoveClientsDownQueue(bot);
1595 		if( !bot->client->resp.spectator )
1596 		{ // bot was in duel
1597 			int j;
1598 			for ( j = 1; j <= game.maxclients; j++)
1599 			{ // clear scores of other players
1600 				if ( g_edicts[j].inuse && g_edicts[j].client )
1601 					g_edicts[j].client->resp.score = 0;
1602 			}
1603 		}
1604 	}
1605 
1606 	bot->inuse = false;
1607 	bot->solid = SOLID_NOT;
1608 	bot->classname = "disconnected";
1609 
1610 	bot->s.modelindex = 0;
1611 	bot->s.modelindex2= 0;
1612 	bot->s.modelindex3 = 0;
1613 	bot->s.modelindex4 = 0;
1614 	bot->s.angles[0] = 0;  // ?
1615 	bot->s.angles[2] = 0;  // ?
1616 	bot->s.sound = 0;
1617 	bot->client->weapon_sound = 0;
1618 	bot->s.effects = 0;
1619 
1620 	// remove powerups
1621 	bot->client->quad_framenum = 0;
1622 	bot->client->invincible_framenum = 0;
1623 	bot->client->haste_framenum = 0;
1624 	bot->client->sproing_framenum = 0;
1625 	bot->client->invis_framenum = 0;
1626 
1627 	// clear inventory
1628 	memset( bot->client->pers.inventory, 0, sizeof(bot->client->pers.inventory));
1629 
1630 	bot->client->pers.connected = false;
1631 
1632 	// particle effect for exit from game
1633 	gi.WriteByte (svc_muzzleflash);
1634 	gi.WriteShort (bot-g_edicts);
1635 	gi.WriteByte (MZ_LOGOUT);
1636 	gi.multicast (bot->s.origin, MULTICAST_PVS);
1637 
1638 	gi.configstring( CS_PLAYERSKINS + ( ((ptrdiff_t)(bot - g_edicts))-1 ), "");
1639 
1640 	// unlink from world
1641 	gi.unlinkentity (bot);
1642 
1643 }
1644 
1645 /*
1646 ======
1647  ACESP_RemoveBot
1648 
1649  remove by server command "sv removebot <name>|all"
1650 ======
1651 */
ACESP_RemoveBot(char * name)1652 void ACESP_RemoveBot(char *name)
1653 {
1654 	edict_t *pbot;
1655 	int clients;
1656 	qboolean allbots;
1657 	qboolean freed = false;
1658 
1659 	allbots = strcmp( name, "all" ) == 0;
1660 
1661 	clients = game.maxclients;
1662 	for ( pbot = &g_edicts[1]; clients--; pbot++ )
1663 	{
1664 		if ( pbot->inuse && pbot->is_bot
1665 				&& (allbots || G_NameMatch(	pbot->client->pers.netname, name )))
1666 		{
1667 			remove_bot( pbot );
1668 			safe_cprintf( NULL, PRINT_HIGH, "%s removed\n", pbot->client->pers.netname );
1669 			freed = true;
1670 		}
1671 	}
1672 	if ( !allbots && !freed )
1673 	{
1674 		safe_cprintf( NULL, PRINT_HIGH, "%s not found, not removed\n", name );
1675 	}
1676 
1677 	// update bots.tmp and client bot information
1678 	ACESP_SaveBots();
1679 }
1680 
1681 /*
1682 ======
1683  ACESP_KickBot
1684 
1685  remove by auto bot kick (cvar:sv_botkickthreshold)
1686  or by "callvote kick <botname>"
1687 
1688 ======
1689  */
ACESP_KickBot(edict_t * bot)1690 void ACESP_KickBot( edict_t *bot )
1691 {
1692 
1693 	remove_bot( bot );
1694 	safe_bprintf (PRINT_MEDIUM, "%s (bot) kicked\n", bot->client->pers.netname);
1695 
1696 	// update bots.tmp and client bot information
1697 	ACESP_SaveBots();
1698 }
1699 
1700