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 //
24 // gameinfo.c
25 //
26
27 #include "ui_local.h"
28
29
30 //
31 // arena and bot info
32 //
33
34 #define POOLSIZE 128 * 1024
35
36 int ui_numBots;
37 static char *ui_botInfos[MAX_BOTS];
38
39 static int ui_numArenas;
40 static char *ui_arenaInfos[MAX_ARENAS];
41
42 static int ui_numSinglePlayerArenas;
43 static int ui_numSpecialSinglePlayerArenas;
44
45 static char memoryPool[POOLSIZE];
46 static int allocPoint, outOfMemory;
47
48
49 /*
50 ===============
51 UI_Alloc
52 ===============
53 */
UI_Alloc(int size)54 void *UI_Alloc( int size ) {
55 char *p;
56
57 if ( allocPoint + size > POOLSIZE ) {
58 outOfMemory = qtrue;
59 return NULL;
60 }
61
62 p = &memoryPool[allocPoint];
63
64 allocPoint += ( size + 31 ) & ~31;
65
66 return p;
67 }
68
69 /*
70 ===============
71 UI_InitMemory
72 ===============
73 */
UI_InitMemory(void)74 void UI_InitMemory( void ) {
75 allocPoint = 0;
76 outOfMemory = qfalse;
77 }
78
79 /*
80 ===============
81 UI_ParseInfos
82 ===============
83 */
UI_ParseInfos(char * buf,int max,char * infos[])84 int UI_ParseInfos( char *buf, int max, char *infos[] ) {
85 char *token;
86 int count;
87 char key[MAX_TOKEN_CHARS];
88 char info[MAX_INFO_STRING];
89
90 count = 0;
91
92 while ( 1 ) {
93 token = Com_Parse( &buf );
94 if ( !token[0] ) {
95 break;
96 }
97 if ( strcmp( token, "{" ) ) {
98 Com_Printf( "Missing { in info file\n" );
99 break;
100 }
101
102 if ( count == max ) {
103 Com_Printf( "Max infos exceeded\n" );
104 break;
105 }
106
107 info[0] = '\0';
108 while ( 1 ) {
109 token = Com_ParseExt( &buf, qtrue );
110 if ( !token[0] ) {
111 Com_Printf( "Unexpected end of info file\n" );
112 break;
113 }
114 if ( !strcmp( token, "}" ) ) {
115 break;
116 }
117 Q_strncpyz( key, token, sizeof( key ) );
118
119 token = Com_ParseExt( &buf, qfalse );
120 if ( !token[0] ) {
121 strcpy( token, "<NULL>" );
122 }
123 Info_SetValueForKey( info, key, token );
124 }
125 //NOTE: extra space for arena number
126 infos[count] = UI_Alloc(strlen(info) + strlen("\\num\\") + strlen(va("%d", MAX_ARENAS)) + 1);
127 if (infos[count]) {
128 strcpy(infos[count], info);
129 count++;
130 }
131 }
132 return count;
133 }
134
135 /*
136 ===============
137 UI_LoadArenasFromFile
138 ===============
139 */
UI_LoadArenasFromFile(char * filename)140 static void UI_LoadArenasFromFile( char *filename ) {
141 int len;
142 fileHandle_t f;
143 char buf[MAX_ARENAS_TEXT];
144
145 len = trap_FS_FOpenFile( filename, &f, FS_READ );
146 if ( !f ) {
147 trap_Print( va( S_COLOR_RED "file not found: %s\n", filename ) );
148 return;
149 }
150 if ( len >= MAX_ARENAS_TEXT ) {
151 trap_Print( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT ) );
152 trap_FS_FCloseFile( f );
153 return;
154 }
155
156 trap_FS_Read( buf, len, f );
157 buf[len] = 0;
158 trap_FS_FCloseFile( f );
159
160 ui_numArenas += UI_ParseInfos( buf, MAX_ARENAS - ui_numArenas, &ui_arenaInfos[ui_numArenas] );
161 }
162
163 /*
164 ===============
165 UI_LoadArenas
166 ===============
167 */
UI_LoadArenas(void)168 static void UI_LoadArenas( void ) {
169 int numdirs;
170 vmCvar_t arenasFile;
171 char filename[128];
172 char dirlist[1024];
173 char* dirptr;
174 int i, n;
175 int dirlen;
176 char *type;
177 char *tag;
178 int singlePlayerNum, specialNum, otherNum;
179
180 ui_numArenas = 0;
181
182 trap_Cvar_Register( &arenasFile, "g_arenasFile", "", CVAR_INIT|CVAR_ROM );
183 if( *arenasFile.string ) {
184 UI_LoadArenasFromFile(arenasFile.string);
185 }
186 else {
187 UI_LoadArenasFromFile("scripts/arenas.txt");
188 }
189
190 // get all arenas from .arena files
191 numdirs = trap_FS_GetFileList("scripts", ".arena", dirlist, 1024 );
192 dirptr = dirlist;
193 for (i = 0; i < numdirs; i++, dirptr += dirlen+1) {
194 dirlen = strlen(dirptr);
195 strcpy(filename, "scripts/");
196 strcat(filename, dirptr);
197 UI_LoadArenasFromFile(filename);
198 }
199 trap_Print( va( "%i arenas parsed\n", ui_numArenas ) );
200 if (outOfMemory) trap_Print(S_COLOR_YELLOW"WARNING: not anough memory in pool to load all arenas\n");
201
202 // set initial numbers
203 for( n = 0; n < ui_numArenas; n++ ) {
204 Info_SetValueForKey( ui_arenaInfos[n], "num", va( "%i", n ) );
205 }
206
207 // go through and count single players levels
208 ui_numSinglePlayerArenas = 0;
209 ui_numSpecialSinglePlayerArenas = 0;
210 for( n = 0; n < ui_numArenas; n++ ) {
211 // determine type
212 type = Info_ValueForKey( ui_arenaInfos[n], "type" );
213
214 // if no type specified, it will be treated as "ffa"
215 if( !*type ) {
216 continue;
217 }
218
219 if( strstr( type, "single" ) ) {
220 // check for special single player arenas (training, final)
221 tag = Info_ValueForKey( ui_arenaInfos[n], "special" );
222 if( *tag ) {
223 ui_numSpecialSinglePlayerArenas++;
224 continue;
225 }
226
227 ui_numSinglePlayerArenas++;
228 }
229 }
230
231 n = ui_numSinglePlayerArenas % ARENAS_PER_TIER;
232 if( n != 0 ) {
233 ui_numSinglePlayerArenas -= n;
234 trap_Print( va( "%i arenas ignored to make count divisible by %i\n", n, ARENAS_PER_TIER ) );
235 }
236
237 // go through once more and assign number to the levels
238 singlePlayerNum = 0;
239 specialNum = singlePlayerNum + ui_numSinglePlayerArenas;
240 otherNum = specialNum + ui_numSpecialSinglePlayerArenas;
241 for( n = 0; n < ui_numArenas; n++ ) {
242 // determine type
243 type = Info_ValueForKey( ui_arenaInfos[n], "type" );
244
245 // if no type specified, it will be treated as "ffa"
246 if( *type ) {
247 if( strstr( type, "single" ) ) {
248 // check for special single player arenas (training, final)
249 tag = Info_ValueForKey( ui_arenaInfos[n], "special" );
250 if( *tag ) {
251 Info_SetValueForKey( ui_arenaInfos[n], "num", va( "%i", specialNum++ ) );
252 continue;
253 }
254
255 Info_SetValueForKey( ui_arenaInfos[n], "num", va( "%i", singlePlayerNum++ ) );
256 continue;
257 }
258 }
259
260 Info_SetValueForKey( ui_arenaInfos[n], "num", va( "%i", otherNum++ ) );
261 }
262 }
263
264 /*
265 ===============
266 UI_GetArenaInfoByNumber
267 ===============
268 */
UI_GetArenaInfoByNumber(int num)269 const char *UI_GetArenaInfoByNumber( int num ) {
270 int n;
271 char *value;
272
273 if( num < 0 || num >= ui_numArenas ) {
274 trap_Print( va( S_COLOR_RED "Invalid arena number: %i\n", num ) );
275 return NULL;
276 }
277
278 for( n = 0; n < ui_numArenas; n++ ) {
279 value = Info_ValueForKey( ui_arenaInfos[n], "num" );
280 if( *value && atoi(value) == num ) {
281 return ui_arenaInfos[n];
282 }
283 }
284
285 return NULL;
286 }
287
288
289 /*
290 ===============
291 UI_GetArenaInfoByNumber
292 ===============
293 */
UI_GetArenaInfoByMap(const char * map)294 const char *UI_GetArenaInfoByMap( const char *map ) {
295 int n;
296
297 for( n = 0; n < ui_numArenas; n++ ) {
298 if( Q_stricmp( Info_ValueForKey( ui_arenaInfos[n], "map" ), map ) == 0 ) {
299 return ui_arenaInfos[n];
300 }
301 }
302
303 return NULL;
304 }
305
306
307 /*
308 ===============
309 UI_GetSpecialArenaInfo
310 ===============
311 */
UI_GetSpecialArenaInfo(const char * tag)312 const char *UI_GetSpecialArenaInfo( const char *tag ) {
313 int n;
314
315 for( n = 0; n < ui_numArenas; n++ ) {
316 if( Q_stricmp( Info_ValueForKey( ui_arenaInfos[n], "special" ), tag ) == 0 ) {
317 return ui_arenaInfos[n];
318 }
319 }
320
321 return NULL;
322 }
323
324 /*
325 ===============
326 UI_LoadBotsFromFile
327 ===============
328 */
UI_LoadBotsFromFile(char * filename)329 static void UI_LoadBotsFromFile( char *filename ) {
330 int len;
331 fileHandle_t f;
332 char buf[MAX_BOTS_TEXT];
333
334 len = trap_FS_FOpenFile( filename, &f, FS_READ );
335 if ( !f ) {
336 trap_Print( va( S_COLOR_RED "file not found: %s\n", filename ) );
337 return;
338 }
339 if ( len >= MAX_BOTS_TEXT ) {
340 trap_Print( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_BOTS_TEXT ) );
341 trap_FS_FCloseFile( f );
342 return;
343 }
344
345 trap_FS_Read( buf, len, f );
346 buf[len] = 0;
347 trap_FS_FCloseFile( f );
348
349 ui_numBots += UI_ParseInfos( buf, MAX_BOTS - ui_numBots, &ui_botInfos[ui_numBots] );
350 if (outOfMemory) trap_Print(S_COLOR_YELLOW"WARNING: not anough memory in pool to load all bots\n");
351 }
352
353 /*
354 ===============
355 UI_LoadBots
356 ===============
357 */
UI_LoadBots(void)358 static void UI_LoadBots( void ) {
359 vmCvar_t botsFile;
360 int numdirs;
361 char filename[128];
362 char dirlist[1024];
363 char* dirptr;
364 int i;
365 int dirlen;
366
367 ui_numBots = 0;
368
369 trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM );
370 if( *botsFile.string ) {
371 UI_LoadBotsFromFile(botsFile.string);
372 }
373 else {
374 UI_LoadBotsFromFile("scripts/bots.txt");
375 }
376
377 // get all bots from .bot files
378 numdirs = trap_FS_GetFileList("scripts", ".bot", dirlist, 1024 );
379 dirptr = dirlist;
380 for (i = 0; i < numdirs; i++, dirptr += dirlen+1) {
381 dirlen = strlen(dirptr);
382 strcpy(filename, "scripts/");
383 strcat(filename, dirptr);
384 UI_LoadBotsFromFile(filename);
385 }
386 trap_Print( va( "%i bots parsed\n", ui_numBots ) );
387 }
388
389
390 /*
391 ===============
392 UI_GetBotInfoByNumber
393 ===============
394 */
UI_GetBotInfoByNumber(int num)395 char *UI_GetBotInfoByNumber( int num ) {
396 if( num < 0 || num >= ui_numBots ) {
397 trap_Print( va( S_COLOR_RED "Invalid bot number: %i\n", num ) );
398 return NULL;
399 }
400 return ui_botInfos[num];
401 }
402
403
404 /*
405 ===============
406 UI_GetBotInfoByName
407 ===============
408 */
UI_GetBotInfoByName(const char * name)409 char *UI_GetBotInfoByName( const char *name ) {
410 int n;
411 char *value;
412
413 for ( n = 0; n < ui_numBots ; n++ ) {
414 value = Info_ValueForKey( ui_botInfos[n], "name" );
415 if ( !Q_stricmp( value, name ) ) {
416 return ui_botInfos[n];
417 }
418 }
419
420 return NULL;
421 }
422
423
424 //
425 // single player game info
426 //
427
428 /*
429 ===============
430 UI_GetBestScore
431
432 Returns the player's best finish on a given level, 0 if the have not played the level
433 ===============
434 */
UI_GetBestScore(int level,int * score,int * skill)435 void UI_GetBestScore( int level, int *score, int *skill ) {
436 int n;
437 int skillScore;
438 int bestScore;
439 int bestScoreSkill;
440 char arenaKey[16];
441 char scores[MAX_INFO_VALUE];
442
443 if( !score || !skill ) {
444 return;
445 }
446
447 if( level < 0 || level > ui_numArenas ) {
448 return;
449 }
450
451 bestScore = 0;
452 bestScoreSkill = 0;
453
454 for( n = 1; n <= 5; n++ ) {
455 trap_Cvar_VariableStringBuffer( va( "g_spScores%i", n ), scores, MAX_INFO_VALUE );
456
457 Com_sprintf( arenaKey, sizeof( arenaKey ), "l%i", level );
458 skillScore = atoi( Info_ValueForKey( scores, arenaKey ) );
459
460 if( skillScore < 1 || skillScore > 8 ) {
461 continue;
462 }
463
464 if( !bestScore || skillScore <= bestScore ) {
465 bestScore = skillScore;
466 bestScoreSkill = n;
467 }
468 }
469
470 *score = bestScore;
471 *skill = bestScoreSkill;
472 }
473
474
475 /*
476 ===============
477 UI_SetBestScore
478
479 Set the player's best finish for a level
480 ===============
481 */
UI_SetBestScore(int level,int score)482 void UI_SetBestScore( int level, int score ) {
483 int skill;
484 int oldScore;
485 char arenaKey[16];
486 char scores[MAX_INFO_VALUE];
487
488 // validate score
489 if( score < 1 || score > 8 ) {
490 return;
491 }
492
493 // validate skill
494 skill = (int)trap_Cvar_VariableValue( "g_spSkill" );
495 if( skill < 1 || skill > 5 ) {
496 return;
497 }
498
499 // get scores
500 trap_Cvar_VariableStringBuffer( va( "g_spScores%i", skill ), scores, MAX_INFO_VALUE );
501
502 // see if this is better
503 Com_sprintf( arenaKey, sizeof( arenaKey ), "l%i", level );
504 oldScore = atoi( Info_ValueForKey( scores, arenaKey ) );
505 if( oldScore && oldScore <= score ) {
506 return;
507 }
508
509 // update scores
510 Info_SetValueForKey( scores, arenaKey, va( "%i", score ) );
511 trap_Cvar_Set( va( "g_spScores%i", skill ), scores );
512 }
513
514
515 /*
516 ===============
517 UI_LogAwardData
518 ===============
519 */
UI_LogAwardData(int award,int data)520 void UI_LogAwardData( int award, int data ) {
521 char key[16];
522 char awardData[MAX_INFO_VALUE];
523 int oldValue;
524
525 if( data == 0 ) {
526 return;
527 }
528
529 if( award > AWARD_PERFECT ) {
530 trap_Print( va( S_COLOR_RED "Bad award %i in UI_LogAwardData\n", award ) );
531 return;
532 }
533
534 trap_Cvar_VariableStringBuffer( "g_spAwards", awardData, sizeof(awardData) );
535
536 Com_sprintf( key, sizeof(key), "a%i", award );
537 oldValue = atoi( Info_ValueForKey( awardData, key ) );
538
539 Info_SetValueForKey( awardData, key, va( "%i", oldValue + data ) );
540 trap_Cvar_Set( "g_spAwards", awardData );
541 }
542
543
544 /*
545 ===============
546 UI_GetAwardLevel
547 ===============
548 */
UI_GetAwardLevel(int award)549 int UI_GetAwardLevel( int award ) {
550 char key[16];
551 char awardData[MAX_INFO_VALUE];
552
553 trap_Cvar_VariableStringBuffer( "g_spAwards", awardData, sizeof(awardData) );
554
555 Com_sprintf( key, sizeof(key), "a%i", award );
556 return atoi( Info_ValueForKey( awardData, key ) );
557 }
558
559
560 /*
561 ===============
562 UI_TierCompleted
563 ===============
564 */
UI_TierCompleted(int levelWon)565 int UI_TierCompleted( int levelWon ) {
566 int level;
567 int n;
568 int tier;
569 int score;
570 int skill;
571 const char *info;
572
573 tier = levelWon / ARENAS_PER_TIER;
574 level = tier * ARENAS_PER_TIER;
575
576 if( tier == UI_GetNumSPTiers() ) {
577 info = UI_GetSpecialArenaInfo( "training" );
578 if( levelWon == atoi( Info_ValueForKey( info, "num" ) ) ) {
579 return 0;
580 }
581 info = UI_GetSpecialArenaInfo( "final" );
582 if( !info || levelWon == atoi( Info_ValueForKey( info, "num" ) ) ) {
583 return tier + 1;
584 }
585 return -1;
586 }
587
588 for( n = 0; n < ARENAS_PER_TIER; n++, level++ ) {
589 UI_GetBestScore( level, &score, &skill );
590 if ( score != 1 ) {
591 return -1;
592 }
593 }
594 return tier + 1;
595 }
596
597
598 /*
599 ===============
600 UI_ShowTierVideo
601 ===============
602 */
UI_ShowTierVideo(int tier)603 qboolean UI_ShowTierVideo( int tier ) {
604 char key[16];
605 char videos[MAX_INFO_VALUE];
606
607 if( tier <= 0 ) {
608 return qfalse;
609 }
610
611 trap_Cvar_VariableStringBuffer( "g_spVideos", videos, sizeof(videos) );
612
613 Com_sprintf( key, sizeof(key), "tier%i", tier );
614 if( atoi( Info_ValueForKey( videos, key ) ) ) {
615 return qfalse;
616 }
617
618 Info_SetValueForKey( videos, key, va( "%i", 1 ) );
619 trap_Cvar_Set( "g_spVideos", videos );
620
621 return qtrue;
622 }
623
624
625 /*
626 ===============
627 UI_CanShowTierVideo
628 ===============
629 */
UI_CanShowTierVideo(int tier)630 qboolean UI_CanShowTierVideo( int tier ) {
631 char key[16];
632 char videos[MAX_INFO_VALUE];
633
634 if( !tier ) {
635 return qfalse;
636 }
637
638 if( uis.demoversion && tier != 8 ) {
639 return qfalse;
640 }
641
642 trap_Cvar_VariableStringBuffer( "g_spVideos", videos, sizeof(videos) );
643
644 Com_sprintf( key, sizeof(key), "tier%i", tier );
645 if( atoi( Info_ValueForKey( videos, key ) ) ) {
646 return qtrue;
647 }
648
649 return qfalse;
650 }
651
652
653 /*
654 ===============
655 UI_GetCurrentGame
656
657 Returns the next level the player has not won
658 ===============
659 */
UI_GetCurrentGame(void)660 int UI_GetCurrentGame( void ) {
661 int level;
662 int rank;
663 int skill;
664 const char *info;
665
666 info = UI_GetSpecialArenaInfo( "training" );
667 if( info ) {
668 level = atoi( Info_ValueForKey( info, "num" ) );
669 UI_GetBestScore( level, &rank, &skill );
670 if ( !rank || rank > 1 ) {
671 return level;
672 }
673 }
674
675 for( level = 0; level < ui_numSinglePlayerArenas; level++ ) {
676 UI_GetBestScore( level, &rank, &skill );
677 if ( !rank || rank > 1 ) {
678 return level;
679 }
680 }
681
682 info = UI_GetSpecialArenaInfo( "final" );
683 if( !info ) {
684 return -1;
685 }
686 return atoi( Info_ValueForKey( info, "num" ) );
687 }
688
689
690 /*
691 ===============
692 UI_NewGame
693
694 Clears the scores and sets the difficutly level
695 ===============
696 */
UI_NewGame(void)697 void UI_NewGame( void ) {
698 trap_Cvar_Set( "g_spScores1", "" );
699 trap_Cvar_Set( "g_spScores2", "" );
700 trap_Cvar_Set( "g_spScores3", "" );
701 trap_Cvar_Set( "g_spScores4", "" );
702 trap_Cvar_Set( "g_spScores5", "" );
703 trap_Cvar_Set( "g_spAwards", "" );
704 trap_Cvar_Set( "g_spVideos", "" );
705 }
706
707
708 /*
709 ===============
710 UI_GetNumArenas
711 ===============
712 */
UI_GetNumArenas(void)713 int UI_GetNumArenas( void ) {
714 return ui_numArenas;
715 }
716
717
718 /*
719 ===============
720 UI_GetNumSPArenas
721 ===============
722 */
UI_GetNumSPArenas(void)723 int UI_GetNumSPArenas( void ) {
724 return ui_numSinglePlayerArenas;
725 }
726
727
728 /*
729 ===============
730 UI_GetNumSPTiers
731 ===============
732 */
UI_GetNumSPTiers(void)733 int UI_GetNumSPTiers( void ) {
734 return ui_numSinglePlayerArenas / ARENAS_PER_TIER;
735 }
736
737
738 /*
739 ===============
740 UI_GetNumBots
741 ===============
742 */
UI_GetNumBots(void)743 int UI_GetNumBots( void ) {
744 return ui_numBots;
745 }
746
747
748 /*
749 ===============
750 UI_SPUnlock_f
751 ===============
752 */
UI_SPUnlock_f(void)753 void UI_SPUnlock_f( void ) {
754 char arenaKey[16];
755 char scores[MAX_INFO_VALUE];
756 int level;
757 int tier;
758
759 // get scores for skill 1
760 trap_Cvar_VariableStringBuffer( "g_spScores1", scores, MAX_INFO_VALUE );
761
762 // update scores
763 for( level = 0; level < ui_numSinglePlayerArenas + ui_numSpecialSinglePlayerArenas; level++ ) {
764 Com_sprintf( arenaKey, sizeof( arenaKey ), "l%i", level );
765 Info_SetValueForKey( scores, arenaKey, "1" );
766 }
767 trap_Cvar_Set( "g_spScores1", scores );
768
769 // unlock cinematics
770 for( tier = 1; tier <= 8; tier++ ) {
771 UI_ShowTierVideo( tier );
772 }
773
774 trap_Print( "All levels unlocked at skill level 1\n" );
775
776 UI_SPLevelMenu_ReInit();
777 }
778
779
780 /*
781 ===============
782 UI_SPUnlockMedals_f
783 ===============
784 */
UI_SPUnlockMedals_f(void)785 void UI_SPUnlockMedals_f( void ) {
786 int n;
787 char key[16];
788 char awardData[MAX_INFO_VALUE];
789
790 trap_Cvar_VariableStringBuffer( "g_spAwards", awardData, MAX_INFO_VALUE );
791
792 for( n = 0; n < 6; n++ ) {
793 Com_sprintf( key, sizeof(key), "a%i", n );
794 Info_SetValueForKey( awardData, key, "100" );
795 }
796
797 trap_Cvar_Set( "g_spAwards", awardData );
798
799 trap_Print( "All levels unlocked at 100\n" );
800 }
801
802
803 /*
804 ===============
805 UI_InitGameinfo
806 ===============
807 */
UI_InitGameinfo(void)808 void UI_InitGameinfo( void ) {
809
810 UI_InitMemory();
811 UI_LoadArenas();
812 UI_LoadBots();
813
814 uis.demoversion = qfalse;
815 }
816