~ubuntu-branches/ubuntu/precise/openarena/precise

« back to all changes in this revision

Viewing changes to code/game/g_bot.c

  • Committer: Bazaar Package Importer
  • Author(s): Bruno "Fuddl" Kleinert
  • Date: 2007-01-20 12:28:09 UTC
  • Revision ID: james.westby@ubuntu.com-20070120122809-2yza5ojt7nqiyiam
Tags: upstream-0.6.0
ImportĀ upstreamĀ versionĀ 0.6.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
===========================================================================
 
3
Copyright (C) 1999-2005 Id Software, Inc.
 
4
 
 
5
This file is part of Quake III Arena source code.
 
6
 
 
7
Quake III Arena source code is free software; you can redistribute it
 
8
and/or modify it under the terms of the GNU General Public License as
 
9
published by the Free Software Foundation; either version 2 of the License,
 
10
or (at your option) any later version.
 
11
 
 
12
Quake III Arena source code is distributed in the hope that it will be
 
13
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
15
GNU General Public License for more details.
 
16
 
 
17
You should have received a copy of the GNU General Public License
 
18
along with Quake III Arena source code; if not, write to the Free Software
 
19
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
20
===========================================================================
 
21
*/
 
22
//
 
23
// g_bot.c
 
24
 
 
25
#include "g_local.h"
 
26
 
 
27
 
 
28
static int              g_numBots;
 
29
static char             *g_botInfos[MAX_BOTS];
 
30
 
 
31
 
 
32
int                             g_numArenas;
 
33
static char             *g_arenaInfos[MAX_ARENAS];
 
34
 
 
35
 
 
36
#define BOT_BEGIN_DELAY_BASE            2000
 
37
#define BOT_BEGIN_DELAY_INCREMENT       1500
 
38
 
 
39
#define BOT_SPAWN_QUEUE_DEPTH   16
 
40
 
 
41
typedef struct {
 
42
        int             clientNum;
 
43
        int             spawnTime;
 
44
} botSpawnQueue_t;
 
45
 
 
46
//static int                    botBeginDelay = 0;  // bk001206 - unused, init
 
47
static botSpawnQueue_t  botSpawnQueue[BOT_SPAWN_QUEUE_DEPTH];
 
48
 
 
49
vmCvar_t bot_minplayers;
 
50
 
 
51
extern gentity_t        *podium1;
 
52
extern gentity_t        *podium2;
 
53
extern gentity_t        *podium3;
 
54
 
 
55
float trap_Cvar_VariableValue( const char *var_name ) {
 
56
        char buf[128];
 
57
 
 
58
        trap_Cvar_VariableStringBuffer(var_name, buf, sizeof(buf));
 
59
        return atof(buf);
 
60
}
 
61
 
 
62
 
 
63
 
 
64
/*
 
65
===============
 
66
G_ParseInfos
 
67
===============
 
68
*/
 
69
int G_ParseInfos( char *buf, int max, char *infos[] ) {
 
70
        char    *token;
 
71
        int             count;
 
72
        char    key[MAX_TOKEN_CHARS];
 
73
        char    info[MAX_INFO_STRING];
 
74
 
 
75
        count = 0;
 
76
 
 
77
        while ( 1 ) {
 
78
                token = COM_Parse( &buf );
 
79
                if ( !token[0] ) {
 
80
                        break;
 
81
                }
 
82
                if ( strcmp( token, "{" ) ) {
 
83
                        Com_Printf( "Missing { in info file\n" );
 
84
                        break;
 
85
                }
 
86
 
 
87
                if ( count == max ) {
 
88
                        Com_Printf( "Max infos exceeded\n" );
 
89
                        break;
 
90
                }
 
91
 
 
92
                info[0] = '\0';
 
93
                while ( 1 ) {
 
94
                        token = COM_ParseExt( &buf, qtrue );
 
95
                        if ( !token[0] ) {
 
96
                                Com_Printf( "Unexpected end of info file\n" );
 
97
                                break;
 
98
                        }
 
99
                        if ( !strcmp( token, "}" ) ) {
 
100
                                break;
 
101
                        }
 
102
                        Q_strncpyz( key, token, sizeof( key ) );
 
103
 
 
104
                        token = COM_ParseExt( &buf, qfalse );
 
105
                        if ( !token[0] ) {
 
106
                                strcpy( token, "<NULL>" );
 
107
                        }
 
108
                        Info_SetValueForKey( info, key, token );
 
109
                }
 
110
                //NOTE: extra space for arena number
 
111
                infos[count] = G_Alloc(strlen(info) + strlen("\\num\\") + strlen(va("%d", MAX_ARENAS)) + 1);
 
112
                if (infos[count]) {
 
113
                        strcpy(infos[count], info);
 
114
                        count++;
 
115
                }
 
116
        }
 
117
        return count;
 
118
}
 
119
 
 
120
/*
 
121
===============
 
122
G_LoadArenasFromFile
 
123
===============
 
124
*/
 
125
static void G_LoadArenasFromFile( char *filename ) {
 
126
        int                             len;
 
127
        fileHandle_t    f;
 
128
        char                    buf[MAX_ARENAS_TEXT];
 
129
 
 
130
        len = trap_FS_FOpenFile( filename, &f, FS_READ );
 
131
        if ( !f ) {
 
132
                trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) );
 
133
                return;
 
134
        }
 
135
        if ( len >= MAX_ARENAS_TEXT ) {
 
136
                trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT ) );
 
137
                trap_FS_FCloseFile( f );
 
138
                return;
 
139
        }
 
140
 
 
141
        trap_FS_Read( buf, len, f );
 
142
        buf[len] = 0;
 
143
        trap_FS_FCloseFile( f );
 
144
 
 
145
        g_numArenas += G_ParseInfos( buf, MAX_ARENAS - g_numArenas, &g_arenaInfos[g_numArenas] );
 
146
}
 
147
 
 
148
/*
 
149
===============
 
150
G_LoadArenas
 
151
===============
 
152
*/
 
153
static void G_LoadArenas( void ) {
 
154
        int                     numdirs;
 
155
        vmCvar_t        arenasFile;
 
156
        char            filename[128];
 
157
        char            dirlist[1024];
 
158
        char*           dirptr;
 
159
        int                     i, n;
 
160
        int                     dirlen;
 
161
 
 
162
        g_numArenas = 0;
 
163
 
 
164
        trap_Cvar_Register( &arenasFile, "g_arenasFile", "", CVAR_INIT|CVAR_ROM );
 
165
        if( *arenasFile.string ) {
 
166
                G_LoadArenasFromFile(arenasFile.string);
 
167
        }
 
168
        else {
 
169
                G_LoadArenasFromFile("scripts/arenas.txt");
 
170
        }
 
171
 
 
172
        // get all arenas from .arena files
 
173
        numdirs = trap_FS_GetFileList("scripts", ".arena", dirlist, 1024 );
 
174
        dirptr  = dirlist;
 
175
        for (i = 0; i < numdirs; i++, dirptr += dirlen+1) {
 
176
                dirlen = strlen(dirptr);
 
177
                strcpy(filename, "scripts/");
 
178
                strcat(filename, dirptr);
 
179
                G_LoadArenasFromFile(filename);
 
180
        }
 
181
        trap_Printf( va( "%i arenas parsed\n", g_numArenas ) );
 
182
        
 
183
        for( n = 0; n < g_numArenas; n++ ) {
 
184
                Info_SetValueForKey( g_arenaInfos[n], "num", va( "%i", n ) );
 
185
        }
 
186
}
 
187
 
 
188
 
 
189
/*
 
190
===============
 
191
G_GetArenaInfoByNumber
 
192
===============
 
193
*/
 
194
const char *G_GetArenaInfoByMap( const char *map ) {
 
195
        int                     n;
 
196
 
 
197
        for( n = 0; n < g_numArenas; n++ ) {
 
198
                if( Q_stricmp( Info_ValueForKey( g_arenaInfos[n], "map" ), map ) == 0 ) {
 
199
                        return g_arenaInfos[n];
 
200
                }
 
201
        }
 
202
 
 
203
        return NULL;
 
204
}
 
205
 
 
206
 
 
207
/*
 
208
=================
 
209
PlayerIntroSound
 
210
=================
 
211
*/
 
212
static void PlayerIntroSound( const char *modelAndSkin ) {
 
213
        char    model[MAX_QPATH];
 
214
        char    *skin;
 
215
 
 
216
        Q_strncpyz( model, modelAndSkin, sizeof(model) );
 
217
        skin = Q_strrchr( model, '/' );
 
218
        if ( skin ) {
 
219
                *skin++ = '\0';
 
220
        }
 
221
        else {
 
222
                skin = model;
 
223
        }
 
224
 
 
225
        if( Q_stricmp( skin, "default" ) == 0 ) {
 
226
                skin = model;
 
227
        }
 
228
 
 
229
        trap_SendConsoleCommand( EXEC_APPEND, va( "play sound/player/announce/%s.wav\n", skin ) );
 
230
}
 
231
 
 
232
/*
 
233
===============
 
234
G_AddRandomBot
 
235
===============
 
236
*/
 
237
void G_AddRandomBot( int team ) {
 
238
        int             i, n, num;
 
239
        float   skill;
 
240
        char    *value, netname[36], *teamstr;
 
241
        gclient_t       *cl;
 
242
 
 
243
        num = 0;
 
244
        for ( n = 0; n < g_numBots ; n++ ) {
 
245
                value = Info_ValueForKey( g_botInfos[n], "name" );
 
246
                //
 
247
                for ( i=0 ; i< g_maxclients.integer ; i++ ) {
 
248
                        cl = level.clients + i;
 
249
                        if ( cl->pers.connected != CON_CONNECTED ) {
 
250
                                continue;
 
251
                        }
 
252
                        if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) {
 
253
                                continue;
 
254
                        }
 
255
                        if ( team >= 0 && cl->sess.sessionTeam != team ) {
 
256
                                continue;
 
257
                        }
 
258
                        if ( !Q_stricmp( value, cl->pers.netname ) ) {
 
259
                                break;
 
260
                        }
 
261
                }
 
262
                if (i >= g_maxclients.integer) {
 
263
                        num++;
 
264
                }
 
265
        }
 
266
        num = random() * num;
 
267
        for ( n = 0; n < g_numBots ; n++ ) {
 
268
                value = Info_ValueForKey( g_botInfos[n], "name" );
 
269
                //
 
270
                for ( i=0 ; i< g_maxclients.integer ; i++ ) {
 
271
                        cl = level.clients + i;
 
272
                        if ( cl->pers.connected != CON_CONNECTED ) {
 
273
                                continue;
 
274
                        }
 
275
                        if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) {
 
276
                                continue;
 
277
                        }
 
278
                        if ( team >= 0 && cl->sess.sessionTeam != team ) {
 
279
                                continue;
 
280
                        }
 
281
                        if ( !Q_stricmp( value, cl->pers.netname ) ) {
 
282
                                break;
 
283
                        }
 
284
                }
 
285
                if (i >= g_maxclients.integer) {
 
286
                        num--;
 
287
                        if (num <= 0) {
 
288
                                skill = trap_Cvar_VariableValue( "g_spSkill" );
 
289
                                if (team == TEAM_RED) teamstr = "red";
 
290
                                else if (team == TEAM_BLUE) teamstr = "blue";
 
291
                                else teamstr = "";
 
292
                                strncpy(netname, value, sizeof(netname)-1);
 
293
                                netname[sizeof(netname)-1] = '\0';
 
294
                                Q_CleanStr(netname);
 
295
                                trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s %f %s %i\n", netname, skill, teamstr, 0) );
 
296
                                return;
 
297
                        }
 
298
                }
 
299
        }
 
300
}
 
301
 
 
302
/*
 
303
===============
 
304
G_RemoveRandomBot
 
305
===============
 
306
*/
 
307
int G_RemoveRandomBot( int team ) {
 
308
        int i;
 
309
        char netname[36];
 
310
        gclient_t       *cl;
 
311
 
 
312
        for ( i=0 ; i< g_maxclients.integer ; i++ ) {
 
313
                cl = level.clients + i;
 
314
                if ( cl->pers.connected != CON_CONNECTED ) {
 
315
                        continue;
 
316
                }
 
317
                if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) {
 
318
                        continue;
 
319
                }
 
320
                if ( team >= 0 && cl->sess.sessionTeam != team ) {
 
321
                        continue;
 
322
                }
 
323
                strcpy(netname, cl->pers.netname);
 
324
                Q_CleanStr(netname);
 
325
                trap_SendConsoleCommand( EXEC_INSERT, va("kick %s\n", netname) );
 
326
                return qtrue;
 
327
        }
 
328
        return qfalse;
 
329
}
 
330
 
 
331
/*
 
332
===============
 
333
G_CountHumanPlayers
 
334
===============
 
335
*/
 
336
int G_CountHumanPlayers( int team ) {
 
337
        int i, num;
 
338
        gclient_t       *cl;
 
339
 
 
340
        num = 0;
 
341
        for ( i=0 ; i< g_maxclients.integer ; i++ ) {
 
342
                cl = level.clients + i;
 
343
                if ( cl->pers.connected != CON_CONNECTED ) {
 
344
                        continue;
 
345
                }
 
346
                if ( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) {
 
347
                        continue;
 
348
                }
 
349
                if ( team >= 0 && cl->sess.sessionTeam != team ) {
 
350
                        continue;
 
351
                }
 
352
                num++;
 
353
        }
 
354
        return num;
 
355
}
 
356
 
 
357
/*
 
358
===============
 
359
G_CountBotPlayers
 
360
===============
 
361
*/
 
362
int G_CountBotPlayers( int team ) {
 
363
        int i, n, num;
 
364
        gclient_t       *cl;
 
365
 
 
366
        num = 0;
 
367
        for ( i=0 ; i< g_maxclients.integer ; i++ ) {
 
368
                cl = level.clients + i;
 
369
                if ( cl->pers.connected != CON_CONNECTED ) {
 
370
                        continue;
 
371
                }
 
372
                if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) {
 
373
                        continue;
 
374
                }
 
375
                if ( team >= 0 && cl->sess.sessionTeam != team ) {
 
376
                        continue;
 
377
                }
 
378
                num++;
 
379
        }
 
380
        for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) {
 
381
                if( !botSpawnQueue[n].spawnTime ) {
 
382
                        continue;
 
383
                }
 
384
                if ( botSpawnQueue[n].spawnTime > level.time ) {
 
385
                        continue;
 
386
                }
 
387
                num++;
 
388
        }
 
389
        return num;
 
390
}
 
391
 
 
392
/*
 
393
===============
 
394
G_CheckMinimumPlayers
 
395
===============
 
396
*/
 
397
void G_CheckMinimumPlayers( void ) {
 
398
        int minplayers;
 
399
        int humanplayers, botplayers;
 
400
        static int checkminimumplayers_time;
 
401
 
 
402
        if (level.intermissiontime) return;
 
403
        //only check once each 10 seconds
 
404
        if (checkminimumplayers_time > level.time - 10000) {
 
405
                return;
 
406
        }
 
407
        checkminimumplayers_time = level.time;
 
408
        trap_Cvar_Update(&bot_minplayers);
 
409
        minplayers = bot_minplayers.integer;
 
410
        if (minplayers <= 0) return;
 
411
 
 
412
        if (g_gametype.integer >= GT_TEAM) {
 
413
                if (minplayers >= g_maxclients.integer / 2) {
 
414
                        minplayers = (g_maxclients.integer / 2) -1;
 
415
                }
 
416
 
 
417
                humanplayers = G_CountHumanPlayers( TEAM_RED );
 
418
                botplayers = G_CountBotPlayers( TEAM_RED );
 
419
                //
 
420
                if (humanplayers + botplayers < minplayers) {
 
421
                        G_AddRandomBot( TEAM_RED );
 
422
                } else if (humanplayers + botplayers > minplayers && botplayers) {
 
423
                        G_RemoveRandomBot( TEAM_RED );
 
424
                }
 
425
                //
 
426
                humanplayers = G_CountHumanPlayers( TEAM_BLUE );
 
427
                botplayers = G_CountBotPlayers( TEAM_BLUE );
 
428
                //
 
429
                if (humanplayers + botplayers < minplayers) {
 
430
                        G_AddRandomBot( TEAM_BLUE );
 
431
                } else if (humanplayers + botplayers > minplayers && botplayers) {
 
432
                        G_RemoveRandomBot( TEAM_BLUE );
 
433
                }
 
434
        }
 
435
        else if (g_gametype.integer == GT_TOURNAMENT ) {
 
436
                if (minplayers >= g_maxclients.integer) {
 
437
                        minplayers = g_maxclients.integer-1;
 
438
                }
 
439
                humanplayers = G_CountHumanPlayers( -1 );
 
440
                botplayers = G_CountBotPlayers( -1 );
 
441
                //
 
442
                if (humanplayers + botplayers < minplayers) {
 
443
                        G_AddRandomBot( TEAM_FREE );
 
444
                } else if (humanplayers + botplayers > minplayers && botplayers) {
 
445
                        // try to remove spectators first
 
446
                        if (!G_RemoveRandomBot( TEAM_SPECTATOR )) {
 
447
                                // just remove the bot that is playing
 
448
                                G_RemoveRandomBot( -1 );
 
449
                        }
 
450
                }
 
451
        }
 
452
        else if (g_gametype.integer == GT_FFA) {
 
453
                if (minplayers >= g_maxclients.integer) {
 
454
                        minplayers = g_maxclients.integer-1;
 
455
                }
 
456
                humanplayers = G_CountHumanPlayers( TEAM_FREE );
 
457
                botplayers = G_CountBotPlayers( TEAM_FREE );
 
458
                //
 
459
                if (humanplayers + botplayers < minplayers) {
 
460
                        G_AddRandomBot( TEAM_FREE );
 
461
                } else if (humanplayers + botplayers > minplayers && botplayers) {
 
462
                        G_RemoveRandomBot( TEAM_FREE );
 
463
                }
 
464
        }
 
465
}
 
466
 
 
467
/*
 
468
===============
 
469
G_CheckBotSpawn
 
470
===============
 
471
*/
 
472
void G_CheckBotSpawn( void ) {
 
473
        int             n;
 
474
        char    userinfo[MAX_INFO_VALUE];
 
475
 
 
476
        G_CheckMinimumPlayers();
 
477
 
 
478
        for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) {
 
479
                if( !botSpawnQueue[n].spawnTime ) {
 
480
                        continue;
 
481
                }
 
482
                if ( botSpawnQueue[n].spawnTime > level.time ) {
 
483
                        continue;
 
484
                }
 
485
                ClientBegin( botSpawnQueue[n].clientNum );
 
486
                botSpawnQueue[n].spawnTime = 0;
 
487
 
 
488
                if( g_gametype.integer == GT_SINGLE_PLAYER ) {
 
489
                        trap_GetUserinfo( botSpawnQueue[n].clientNum, userinfo, sizeof(userinfo) );
 
490
                        PlayerIntroSound( Info_ValueForKey (userinfo, "model") );
 
491
                }
 
492
        }
 
493
}
 
494
 
 
495
 
 
496
/*
 
497
===============
 
498
AddBotToSpawnQueue
 
499
===============
 
500
*/
 
501
static void AddBotToSpawnQueue( int clientNum, int delay ) {
 
502
        int             n;
 
503
 
 
504
        for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) {
 
505
                if( !botSpawnQueue[n].spawnTime ) {
 
506
                        botSpawnQueue[n].spawnTime = level.time + delay;
 
507
                        botSpawnQueue[n].clientNum = clientNum;
 
508
                        return;
 
509
                }
 
510
        }
 
511
 
 
512
        G_Printf( S_COLOR_YELLOW "Unable to delay spawn\n" );
 
513
        ClientBegin( clientNum );
 
514
}
 
515
 
 
516
 
 
517
/*
 
518
===============
 
519
G_RemoveQueuedBotBegin
 
520
 
 
521
Called on client disconnect to make sure the delayed spawn
 
522
doesn't happen on a freed index
 
523
===============
 
524
*/
 
525
void G_RemoveQueuedBotBegin( int clientNum ) {
 
526
        int             n;
 
527
 
 
528
        for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) {
 
529
                if( botSpawnQueue[n].clientNum == clientNum ) {
 
530
                        botSpawnQueue[n].spawnTime = 0;
 
531
                        return;
 
532
                }
 
533
        }
 
534
}
 
535
 
 
536
 
 
537
/*
 
538
===============
 
539
G_BotConnect
 
540
===============
 
541
*/
 
542
qboolean G_BotConnect( int clientNum, qboolean restart ) {
 
543
        bot_settings_t  settings;
 
544
        char                    userinfo[MAX_INFO_STRING];
 
545
 
 
546
        trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
 
547
 
 
548
        Q_strncpyz( settings.characterfile, Info_ValueForKey( userinfo, "characterfile" ), sizeof(settings.characterfile) );
 
549
        settings.skill = atof( Info_ValueForKey( userinfo, "skill" ) );
 
550
        Q_strncpyz( settings.team, Info_ValueForKey( userinfo, "team" ), sizeof(settings.team) );
 
551
 
 
552
        if (!BotAISetupClient( clientNum, &settings, restart )) {
 
553
                trap_DropClient( clientNum, "BotAISetupClient failed" );
 
554
                return qfalse;
 
555
        }
 
556
 
 
557
        return qtrue;
 
558
}
 
559
 
 
560
 
 
561
/*
 
562
===============
 
563
G_AddBot
 
564
===============
 
565
*/
 
566
static void G_AddBot( const char *name, float skill, const char *team, int delay, char *altname) {
 
567
        int                             clientNum;
 
568
        char                    *botinfo;
 
569
        gentity_t               *bot;
 
570
        char                    *key;
 
571
        char                    *s;
 
572
        char                    *botname;
 
573
        char                    *model;
 
574
        char                    *headmodel;
 
575
        char                    userinfo[MAX_INFO_STRING];
 
576
 
 
577
        // get the botinfo from bots.txt
 
578
        botinfo = G_GetBotInfoByName( name );
 
579
        if ( !botinfo ) {
 
580
                G_Printf( S_COLOR_RED "Error: Bot '%s' not defined\n", name );
 
581
                return;
 
582
        }
 
583
 
 
584
        // create the bot's userinfo
 
585
        userinfo[0] = '\0';
 
586
 
 
587
        botname = Info_ValueForKey( botinfo, "funname" );
 
588
        if( !botname[0] ) {
 
589
                botname = Info_ValueForKey( botinfo, "name" );
 
590
        }
 
591
        // check for an alternative name
 
592
        if (altname && altname[0]) {
 
593
                botname = altname;
 
594
        }
 
595
        Info_SetValueForKey( userinfo, "name", botname );
 
596
        Info_SetValueForKey( userinfo, "rate", "25000" );
 
597
        Info_SetValueForKey( userinfo, "snaps", "20" );
 
598
        Info_SetValueForKey( userinfo, "skill", va("%1.2f", skill) );
 
599
 
 
600
        if ( skill >= 1 && skill < 2 ) {
 
601
                Info_SetValueForKey( userinfo, "handicap", "50" );
 
602
        }
 
603
        else if ( skill >= 2 && skill < 3 ) {
 
604
                Info_SetValueForKey( userinfo, "handicap", "70" );
 
605
        }
 
606
        else if ( skill >= 3 && skill < 4 ) {
 
607
                Info_SetValueForKey( userinfo, "handicap", "90" );
 
608
        }
 
609
 
 
610
        key = "model";
 
611
        model = Info_ValueForKey( botinfo, key );
 
612
        if ( !*model ) {
 
613
                model = "visor/default";
 
614
        }
 
615
        Info_SetValueForKey( userinfo, key, model );
 
616
        key = "team_model";
 
617
        Info_SetValueForKey( userinfo, key, model );
 
618
 
 
619
        key = "headmodel";
 
620
        headmodel = Info_ValueForKey( botinfo, key );
 
621
        if ( !*headmodel ) {
 
622
                headmodel = model;
 
623
        }
 
624
        Info_SetValueForKey( userinfo, key, headmodel );
 
625
        key = "team_headmodel";
 
626
        Info_SetValueForKey( userinfo, key, headmodel );
 
627
 
 
628
        key = "gender";
 
629
        s = Info_ValueForKey( botinfo, key );
 
630
        if ( !*s ) {
 
631
                s = "male";
 
632
        }
 
633
        Info_SetValueForKey( userinfo, "sex", s );
 
634
 
 
635
        key = "color1";
 
636
        s = Info_ValueForKey( botinfo, key );
 
637
        if ( !*s ) {
 
638
                s = "4";
 
639
        }
 
640
        Info_SetValueForKey( userinfo, key, s );
 
641
 
 
642
        key = "color2";
 
643
        s = Info_ValueForKey( botinfo, key );
 
644
        if ( !*s ) {
 
645
                s = "5";
 
646
        }
 
647
        Info_SetValueForKey( userinfo, key, s );
 
648
 
 
649
        s = Info_ValueForKey(botinfo, "aifile");
 
650
        if (!*s ) {
 
651
                trap_Printf( S_COLOR_RED "Error: bot has no aifile specified\n" );
 
652
                return;
 
653
        }
 
654
 
 
655
        // have the server allocate a client slot
 
656
        clientNum = trap_BotAllocateClient();
 
657
        if ( clientNum == -1 ) {
 
658
                G_Printf( S_COLOR_RED "Unable to add bot.  All player slots are in use.\n" );
 
659
                G_Printf( S_COLOR_RED "Start server with more 'open' slots (or check setting of sv_maxclients cvar).\n" );
 
660
                return;
 
661
        }
 
662
 
 
663
        // initialize the bot settings
 
664
        if( !team || !*team ) {
 
665
                if( g_gametype.integer >= GT_TEAM ) {
 
666
                        if( PickTeam(clientNum) == TEAM_RED) {
 
667
                                team = "red";
 
668
                        }
 
669
                        else {
 
670
                                team = "blue";
 
671
                        }
 
672
                }
 
673
                else {
 
674
                        team = "red";
 
675
                }
 
676
        }
 
677
        Info_SetValueForKey( userinfo, "characterfile", Info_ValueForKey( botinfo, "aifile" ) );
 
678
        Info_SetValueForKey( userinfo, "skill", va( "%5.2f", skill ) );
 
679
        Info_SetValueForKey( userinfo, "team", team );
 
680
 
 
681
        bot = &g_entities[ clientNum ];
 
682
        bot->r.svFlags |= SVF_BOT;
 
683
        bot->inuse = qtrue;
 
684
 
 
685
        // register the userinfo
 
686
        trap_SetUserinfo( clientNum, userinfo );
 
687
 
 
688
        // have it connect to the game as a normal client
 
689
        if ( ClientConnect( clientNum, qtrue, qtrue ) ) {
 
690
                return;
 
691
        }
 
692
 
 
693
        if( delay == 0 ) {
 
694
                ClientBegin( clientNum );
 
695
                return;
 
696
        }
 
697
 
 
698
        AddBotToSpawnQueue( clientNum, delay );
 
699
}
 
700
 
 
701
 
 
702
/*
 
703
===============
 
704
Svcmd_AddBot_f
 
705
===============
 
706
*/
 
707
void Svcmd_AddBot_f( void ) {
 
708
        float                   skill;
 
709
        int                             delay;
 
710
        char                    name[MAX_TOKEN_CHARS];
 
711
        char                    altname[MAX_TOKEN_CHARS];
 
712
        char                    string[MAX_TOKEN_CHARS];
 
713
        char                    team[MAX_TOKEN_CHARS];
 
714
 
 
715
        // are bots enabled?
 
716
        if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) {
 
717
                return;
 
718
        }
 
719
 
 
720
        // name
 
721
        trap_Argv( 1, name, sizeof( name ) );
 
722
        if ( !name[0] ) {
 
723
                trap_Printf( "Usage: Addbot <botname> [skill 1-5] [team] [msec delay] [altname]\n" );
 
724
                return;
 
725
        }
 
726
 
 
727
        // skill
 
728
        trap_Argv( 2, string, sizeof( string ) );
 
729
        if ( !string[0] ) {
 
730
                skill = 4;
 
731
        }
 
732
        else {
 
733
                skill = atof( string );
 
734
        }
 
735
 
 
736
        // team
 
737
        trap_Argv( 3, team, sizeof( team ) );
 
738
 
 
739
        // delay
 
740
        trap_Argv( 4, string, sizeof( string ) );
 
741
        if ( !string[0] ) {
 
742
                delay = 0;
 
743
        }
 
744
        else {
 
745
                delay = atoi( string );
 
746
        }
 
747
 
 
748
        // alternative name
 
749
        trap_Argv( 5, altname, sizeof( altname ) );
 
750
 
 
751
        G_AddBot( name, skill, team, delay, altname );
 
752
 
 
753
        // if this was issued during gameplay and we are playing locally,
 
754
        // go ahead and load the bot's media immediately
 
755
        if ( level.time - level.startTime > 1000 &&
 
756
                trap_Cvar_VariableIntegerValue( "cl_running" ) ) {
 
757
                trap_SendServerCommand( -1, "loaddefered\n" );  // FIXME: spelled wrong, but not changing for demo
 
758
        }
 
759
}
 
760
 
 
761
/*
 
762
===============
 
763
Svcmd_BotList_f
 
764
===============
 
765
*/
 
766
void Svcmd_BotList_f( void ) {
 
767
        int i;
 
768
        char name[MAX_TOKEN_CHARS];
 
769
        char funname[MAX_TOKEN_CHARS];
 
770
        char model[MAX_TOKEN_CHARS];
 
771
        char aifile[MAX_TOKEN_CHARS];
 
772
 
 
773
        trap_Printf("^1name             model            aifile              funname\n");
 
774
        for (i = 0; i < g_numBots; i++) {
 
775
                strcpy(name, Info_ValueForKey( g_botInfos[i], "name" ));
 
776
                if ( !*name ) {
 
777
                        strcpy(name, "UnnamedPlayer");
 
778
                }
 
779
                strcpy(funname, Info_ValueForKey( g_botInfos[i], "funname" ));
 
780
                if ( !*funname ) {
 
781
                        strcpy(funname, "");
 
782
                }
 
783
                strcpy(model, Info_ValueForKey( g_botInfos[i], "model" ));
 
784
                if ( !*model ) {
 
785
                        strcpy(model, "visor/default");
 
786
                }
 
787
                strcpy(aifile, Info_ValueForKey( g_botInfos[i], "aifile"));
 
788
                if (!*aifile ) {
 
789
                        strcpy(aifile, "bots/default_c.c");
 
790
                }
 
791
                trap_Printf(va("%-16s %-16s %-20s %-20s\n", name, model, aifile, funname));
 
792
        }
 
793
}
 
794
 
 
795
 
 
796
/*
 
797
===============
 
798
G_SpawnBots
 
799
===============
 
800
*/
 
801
static void G_SpawnBots( char *botList, int baseDelay ) {
 
802
        char            *bot;
 
803
        char            *p;
 
804
        float           skill;
 
805
        int                     delay;
 
806
        char            bots[MAX_INFO_VALUE];
 
807
 
 
808
        podium1 = NULL;
 
809
        podium2 = NULL;
 
810
        podium3 = NULL;
 
811
 
 
812
        skill = trap_Cvar_VariableValue( "g_spSkill" );
 
813
        if( skill < 1 ) {
 
814
                trap_Cvar_Set( "g_spSkill", "1" );
 
815
                skill = 1;
 
816
        }
 
817
        else if ( skill > 5 ) {
 
818
                trap_Cvar_Set( "g_spSkill", "5" );
 
819
                skill = 5;
 
820
        }
 
821
 
 
822
        Q_strncpyz( bots, botList, sizeof(bots) );
 
823
        p = &bots[0];
 
824
        delay = baseDelay;
 
825
        while( *p ) {
 
826
                //skip spaces
 
827
                while( *p && *p == ' ' ) {
 
828
                        p++;
 
829
                }
 
830
                if( !p ) {
 
831
                        break;
 
832
                }
 
833
 
 
834
                // mark start of bot name
 
835
                bot = p;
 
836
 
 
837
                // skip until space of null
 
838
                while( *p && *p != ' ' ) {
 
839
                        p++;
 
840
                }
 
841
                if( *p ) {
 
842
                        *p++ = 0;
 
843
                }
 
844
 
 
845
                // we must add the bot this way, calling G_AddBot directly at this stage
 
846
                // does "Bad Things"
 
847
                trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s %f free %i\n", bot, skill, delay) );
 
848
 
 
849
                delay += BOT_BEGIN_DELAY_INCREMENT;
 
850
        }
 
851
}
 
852
 
 
853
 
 
854
/*
 
855
===============
 
856
G_LoadBotsFromFile
 
857
===============
 
858
*/
 
859
static void G_LoadBotsFromFile( char *filename ) {
 
860
        int                             len;
 
861
        fileHandle_t    f;
 
862
        char                    buf[MAX_BOTS_TEXT];
 
863
 
 
864
        len = trap_FS_FOpenFile( filename, &f, FS_READ );
 
865
        if ( !f ) {
 
866
                trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) );
 
867
                return;
 
868
        }
 
869
        if ( len >= MAX_BOTS_TEXT ) {
 
870
                trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_BOTS_TEXT ) );
 
871
                trap_FS_FCloseFile( f );
 
872
                return;
 
873
        }
 
874
 
 
875
        trap_FS_Read( buf, len, f );
 
876
        buf[len] = 0;
 
877
        trap_FS_FCloseFile( f );
 
878
 
 
879
        g_numBots += G_ParseInfos( buf, MAX_BOTS - g_numBots, &g_botInfos[g_numBots] );
 
880
}
 
881
 
 
882
/*
 
883
===============
 
884
G_LoadBots
 
885
===============
 
886
*/
 
887
static void G_LoadBots( void ) {
 
888
        vmCvar_t        botsFile;
 
889
        int                     numdirs;
 
890
        char            filename[128];
 
891
        char            dirlist[1024];
 
892
        char*           dirptr;
 
893
        int                     i;
 
894
        int                     dirlen;
 
895
 
 
896
        if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) {
 
897
                return;
 
898
        }
 
899
 
 
900
        g_numBots = 0;
 
901
 
 
902
        trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM );
 
903
        if( *botsFile.string ) {
 
904
                G_LoadBotsFromFile(botsFile.string);
 
905
        }
 
906
        else {
 
907
                G_LoadBotsFromFile("scripts/bots.txt");
 
908
        }
 
909
 
 
910
        // get all bots from .bot files
 
911
        numdirs = trap_FS_GetFileList("scripts", ".bot", dirlist, 1024 );
 
912
        dirptr  = dirlist;
 
913
        for (i = 0; i < numdirs; i++, dirptr += dirlen+1) {
 
914
                dirlen = strlen(dirptr);
 
915
                strcpy(filename, "scripts/");
 
916
                strcat(filename, dirptr);
 
917
                G_LoadBotsFromFile(filename);
 
918
        }
 
919
        trap_Printf( va( "%i bots parsed\n", g_numBots ) );
 
920
}
 
921
 
 
922
 
 
923
 
 
924
/*
 
925
===============
 
926
G_GetBotInfoByNumber
 
927
===============
 
928
*/
 
929
char *G_GetBotInfoByNumber( int num ) {
 
930
        if( num < 0 || num >= g_numBots ) {
 
931
                trap_Printf( va( S_COLOR_RED "Invalid bot number: %i\n", num ) );
 
932
                return NULL;
 
933
        }
 
934
        return g_botInfos[num];
 
935
}
 
936
 
 
937
 
 
938
/*
 
939
===============
 
940
G_GetBotInfoByName
 
941
===============
 
942
*/
 
943
char *G_GetBotInfoByName( const char *name ) {
 
944
        int             n;
 
945
        char    *value;
 
946
 
 
947
        for ( n = 0; n < g_numBots ; n++ ) {
 
948
                value = Info_ValueForKey( g_botInfos[n], "name" );
 
949
                if ( !Q_stricmp( value, name ) ) {
 
950
                        return g_botInfos[n];
 
951
                }
 
952
        }
 
953
 
 
954
        return NULL;
 
955
}
 
956
 
 
957
/*
 
958
===============
 
959
G_InitBots
 
960
===============
 
961
*/
 
962
void G_InitBots( qboolean restart ) {
 
963
        int                     fragLimit;
 
964
        int                     timeLimit;
 
965
        const char      *arenainfo;
 
966
        char            *strValue;
 
967
        int                     basedelay;
 
968
        char            map[MAX_QPATH];
 
969
        char            serverinfo[MAX_INFO_STRING];
 
970
 
 
971
        G_LoadBots();
 
972
        G_LoadArenas();
 
973
 
 
974
        trap_Cvar_Register( &bot_minplayers, "bot_minplayers", "0", CVAR_SERVERINFO );
 
975
 
 
976
        if( g_gametype.integer == GT_SINGLE_PLAYER ) {
 
977
                trap_GetServerinfo( serverinfo, sizeof(serverinfo) );
 
978
                Q_strncpyz( map, Info_ValueForKey( serverinfo, "mapname" ), sizeof(map) );
 
979
                arenainfo = G_GetArenaInfoByMap( map );
 
980
                if ( !arenainfo ) {
 
981
                        return;
 
982
                }
 
983
 
 
984
                strValue = Info_ValueForKey( arenainfo, "fraglimit" );
 
985
                fragLimit = atoi( strValue );
 
986
                if ( fragLimit ) {
 
987
                        trap_Cvar_Set( "fraglimit", strValue );
 
988
                }
 
989
                else {
 
990
                        trap_Cvar_Set( "fraglimit", "0" );
 
991
                }
 
992
 
 
993
                strValue = Info_ValueForKey( arenainfo, "timelimit" );
 
994
                timeLimit = atoi( strValue );
 
995
                if ( timeLimit ) {
 
996
                        trap_Cvar_Set( "timelimit", strValue );
 
997
                }
 
998
                else {
 
999
                        trap_Cvar_Set( "timelimit", "0" );
 
1000
                }
 
1001
 
 
1002
                if ( !fragLimit && !timeLimit ) {
 
1003
                        trap_Cvar_Set( "fraglimit", "10" );
 
1004
                        trap_Cvar_Set( "timelimit", "0" );
 
1005
                }
 
1006
 
 
1007
                basedelay = BOT_BEGIN_DELAY_BASE;
 
1008
                strValue = Info_ValueForKey( arenainfo, "special" );
 
1009
                if( Q_stricmp( strValue, "training" ) == 0 ) {
 
1010
                        basedelay += 10000;
 
1011
                }
 
1012
 
 
1013
                if( !restart ) {
 
1014
                        G_SpawnBots( Info_ValueForKey( arenainfo, "bots" ), basedelay );
 
1015
                }
 
1016
        }
 
1017
}