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