1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4 Copyright (C) 2000-2006 Tim Angus
5
6 This file is part of Tremulous.
7
8 Tremulous is free software; you can redistribute it
9 and/or modify it under the terms of the GNU General Public License as
10 published by the Free Software Foundation; either version 2 of the License,
11 or (at your option) any later version.
12
13 Tremulous is distributed in the hope that it will be
14 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with Tremulous; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 ===========================================================================
22 */
23
24 // g_maprotation.c -- the map rotation system
25
26 #include "g_local.h"
27
28 static mapRotations_t mapRotations;
29
30 /*
31 ===============
32 G_ParseCommandSection
33
34 Parse a map rotation command section
35 ===============
36 */
G_ParseMapCommandSection(mapRotationEntry_t * mre,char ** text_p)37 static qboolean G_ParseMapCommandSection( mapRotationEntry_t *mre, char **text_p )
38 {
39 char *token;
40
41 // read optional parameters
42 while( 1 )
43 {
44 token = COM_Parse( text_p );
45
46 if( !token )
47 break;
48
49 if( !Q_stricmp( token, "" ) )
50 return qfalse;
51
52 if( !Q_stricmp( token, "}" ) )
53 return qtrue; //reached the end of this command section
54
55 Q_strncpyz( mre->postCmds[ mre->numCmds ], token, sizeof( mre->postCmds[ 0 ] ) );
56 Q_strcat( mre->postCmds[ mre->numCmds ], sizeof( mre->postCmds[ 0 ] ), " " );
57
58 token = COM_ParseExt( text_p, qfalse );
59
60 while( token && token[ 0 ] != 0 )
61 {
62 Q_strcat( mre->postCmds[ mre->numCmds ], sizeof( mre->postCmds[ 0 ] ), token );
63 Q_strcat( mre->postCmds[ mre->numCmds ], sizeof( mre->postCmds[ 0 ] ), " " );
64 token = COM_ParseExt( text_p, qfalse );
65 }
66
67 if( mre->numCmds == MAX_MAP_COMMANDS )
68 {
69 G_Printf( S_COLOR_RED "ERROR: maximum number of map commands (%d) reached\n",
70 MAX_MAP_COMMANDS );
71 return qfalse;
72 }
73 else
74 mre->numCmds++;
75 }
76
77 return qfalse;
78 }
79
80 /*
81 ===============
82 G_ParseMapRotation
83
84 Parse a map rotation section
85 ===============
86 */
G_ParseMapRotation(mapRotation_t * mr,char ** text_p)87 static qboolean G_ParseMapRotation( mapRotation_t *mr, char **text_p )
88 {
89 char *token;
90 qboolean mnSet = qfalse;
91 mapRotationEntry_t *mre = NULL;
92 mapRotationCondition_t *mrc;
93
94 // read optional parameters
95 while( 1 )
96 {
97 token = COM_Parse( text_p );
98
99 if( !token )
100 break;
101
102 if( !Q_stricmp( token, "" ) )
103 return qfalse;
104
105 if( !Q_stricmp( token, "{" ) )
106 {
107 if( !mnSet )
108 {
109 G_Printf( S_COLOR_RED "ERROR: map settings section with no name\n" );
110 return qfalse;
111 }
112
113 if( !G_ParseMapCommandSection( mre, text_p ) )
114 {
115 G_Printf( S_COLOR_RED "ERROR: failed to parse map command section\n" );
116 return qfalse;
117 }
118
119 mnSet = qfalse;
120 continue;
121 }
122 else if( !Q_stricmp( token, "goto" ) )
123 {
124 token = COM_Parse( text_p );
125
126 if( !token )
127 break;
128
129 mrc = &mre->conditions[ mre->numConditions ];
130 mrc->unconditional = qtrue;
131 Q_strncpyz( mrc->dest, token, sizeof( mrc->dest ) );
132
133 if( mre->numConditions == MAX_MAP_ROTATION_CONDS )
134 {
135 G_Printf( S_COLOR_RED "ERROR: maximum number of conditions for one map (%d) reached\n",
136 MAX_MAP_ROTATION_CONDS );
137 return qfalse;
138 }
139 else
140 mre->numConditions++;
141
142 continue;
143 }
144 else if( !Q_stricmp( token, "if" ) )
145 {
146 token = COM_Parse( text_p );
147
148 if( !token )
149 break;
150
151 mrc = &mre->conditions[ mre->numConditions ];
152
153 if( !Q_stricmp( token, "numClients" ) )
154 {
155 mrc->lhs = MCV_NUMCLIENTS;
156
157 token = COM_Parse( text_p );
158
159 if( !token )
160 break;
161
162 if( !Q_stricmp( token, "<" ) )
163 mrc->op = MCO_LT;
164 else if( !Q_stricmp( token, ">" ) )
165 mrc->op = MCO_GT;
166 else if( !Q_stricmp( token, "=" ) )
167 mrc->op = MCO_EQ;
168 else
169 {
170 G_Printf( S_COLOR_RED "ERROR: invalid operator in expression: %s\n", token );
171 return qfalse;
172 }
173
174 token = COM_Parse( text_p );
175
176 if( !token )
177 break;
178
179 mrc->numClients = atoi( token );
180 }
181 else if( !Q_stricmp( token, "lastWin" ) )
182 {
183 mrc->lhs = MCV_LASTWIN;
184
185 token = COM_Parse( text_p );
186
187 if( !token )
188 break;
189
190 if( !Q_stricmp( token, "aliens" ) )
191 mrc->lastWin = PTE_ALIENS;
192 else if( !Q_stricmp( token, "humans" ) )
193 mrc->lastWin = PTE_HUMANS;
194 else
195 {
196 G_Printf( S_COLOR_RED "ERROR: invalid right hand side in expression: %s\n", token );
197 return qfalse;
198 }
199 }
200 else if( !Q_stricmp( token, "random" ) )
201 mrc->lhs = MCV_RANDOM;
202 else
203 {
204 G_Printf( S_COLOR_RED "ERROR: invalid left hand side in expression: %s\n", token );
205 return qfalse;
206 }
207
208 token = COM_Parse( text_p );
209
210 if( !token )
211 break;
212
213 mrc->unconditional = qfalse;
214 Q_strncpyz( mrc->dest, token, sizeof( mrc->dest ) );
215
216 if( mre->numConditions == MAX_MAP_ROTATION_CONDS )
217 {
218 G_Printf( S_COLOR_RED "ERROR: maximum number of conditions for one map (%d) reached\n",
219 MAX_MAP_ROTATION_CONDS );
220 return qfalse;
221 }
222 else
223 mre->numConditions++;
224
225 continue;
226 }
227 else if( !Q_stricmp( token, "}" ) )
228 return qtrue; //reached the end of this map rotation
229
230 mre = &mr->maps[ mr->numMaps ];
231
232 if( mr->numMaps == MAX_MAP_ROTATION_MAPS )
233 {
234 G_Printf( S_COLOR_RED "ERROR: maximum number of maps in one rotation (%d) reached\n",
235 MAX_MAP_ROTATION_MAPS );
236 return qfalse;
237 }
238 else
239 mr->numMaps++;
240
241 Q_strncpyz( mre->name, token, sizeof( mre->name ) );
242 mnSet = qtrue;
243 }
244
245 return qfalse;
246 }
247
248 /*
249 ===============
250 G_ParseMapRotationFile
251
252 Load the map rotations from a map rotation file
253 ===============
254 */
G_ParseMapRotationFile(const char * fileName)255 static qboolean G_ParseMapRotationFile( const char *fileName )
256 {
257 char *text_p;
258 int i;
259 int len;
260 char *token;
261 char text[ 20000 ];
262 char mrName[ MAX_QPATH ];
263 qboolean mrNameSet = qfalse;
264 fileHandle_t f;
265
266 // load the file
267 len = trap_FS_FOpenFile( fileName, &f, FS_READ );
268 if( len <= 0 )
269 return qfalse;
270
271 if( len >= sizeof( text ) - 1 )
272 {
273 G_Printf( S_COLOR_RED "ERROR: map rotation file %s too long\n", fileName );
274 return qfalse;
275 }
276
277 trap_FS_Read( text, len, f );
278 text[ len ] = 0;
279 trap_FS_FCloseFile( f );
280
281 // parse the text
282 text_p = text;
283
284 // read optional parameters
285 while( 1 )
286 {
287 token = COM_Parse( &text_p );
288
289 if( !token )
290 break;
291
292 if( !Q_stricmp( token, "" ) )
293 break;
294
295 if( !Q_stricmp( token, "{" ) )
296 {
297 if( mrNameSet )
298 {
299 //check for name space clashes
300 for( i = 0; i < mapRotations.numRotations; i++ )
301 {
302 if( !Q_stricmp( mapRotations.rotations[ i ].name, mrName ) )
303 {
304 G_Printf( S_COLOR_RED "ERROR: a map rotation is already named %s\n", mrName );
305 return qfalse;
306 }
307 }
308
309 Q_strncpyz( mapRotations.rotations[ mapRotations.numRotations ].name, mrName, MAX_QPATH );
310
311 if( !G_ParseMapRotation( &mapRotations.rotations[ mapRotations.numRotations ], &text_p ) )
312 {
313 G_Printf( S_COLOR_RED "ERROR: %s: failed to parse map rotation %s\n", fileName, mrName );
314 return qfalse;
315 }
316
317 //start parsing particle systems again
318 mrNameSet = qfalse;
319
320 if( mapRotations.numRotations == MAX_MAP_ROTATIONS )
321 {
322 G_Printf( S_COLOR_RED "ERROR: maximum number of map rotations (%d) reached\n",
323 MAX_MAP_ROTATIONS );
324 return qfalse;
325 }
326 else
327 mapRotations.numRotations++;
328
329 continue;
330 }
331 else
332 {
333 G_Printf( S_COLOR_RED "ERROR: unamed map rotation\n" );
334 return qfalse;
335 }
336 }
337
338 if( !mrNameSet )
339 {
340 Q_strncpyz( mrName, token, sizeof( mrName ) );
341 mrNameSet = qtrue;
342 }
343 else
344 {
345 G_Printf( S_COLOR_RED "ERROR: map rotation already named\n" );
346 return qfalse;
347 }
348 }
349
350 return qtrue;
351 }
352
353 /*
354 ===============
355 G_PrintRotations
356
357 Print the parsed map rotations
358 ===============
359 */
G_PrintRotations(void)360 void G_PrintRotations( void )
361 {
362 int i, j, k;
363
364 G_Printf( "Map rotations as parsed:\n\n" );
365
366 for( i = 0; i < mapRotations.numRotations; i++ )
367 {
368 G_Printf( "rotation: %s\n{\n", mapRotations.rotations[ i ].name );
369
370 for( j = 0; j < mapRotations.rotations[ i ].numMaps; j++ )
371 {
372 G_Printf( " map: %s\n {\n", mapRotations.rotations[ i ].maps[ j ].name );
373
374 for( k = 0; k < mapRotations.rotations[ i ].maps[ j ].numCmds; k++ )
375 {
376 G_Printf( " command: %s\n",
377 mapRotations.rotations[ i ].maps[ j ].postCmds[ k ] );
378 }
379
380 G_Printf( " }\n" );
381
382 for( k = 0; k < mapRotations.rotations[ i ].maps[ j ].numConditions; k++ )
383 {
384 G_Printf( " conditional: %s\n",
385 mapRotations.rotations[ i ].maps[ j ].conditions[ k ].dest );
386 }
387
388 }
389
390 G_Printf( "}\n" );
391 }
392
393 G_Printf( "Total memory used: %d bytes\n", sizeof( mapRotations ) );
394 }
395
396 /*
397 ===============
398 G_GetCurrentMapArray
399
400 Fill a static array with the current map of each rotation
401 ===============
402 */
G_GetCurrentMapArray(void)403 static int *G_GetCurrentMapArray( void )
404 {
405 static int currentMap[ MAX_MAP_ROTATIONS ];
406 int i = 0;
407 char text[ MAX_MAP_ROTATIONS * 2 ];
408 char *text_p, *token;
409
410 Q_strncpyz( text, g_currentMap.string, sizeof( text ) );
411
412 text_p = text;
413
414 while( 1 )
415 {
416 token = COM_Parse( &text_p );
417
418 if( !token )
419 break;
420
421 if( !Q_stricmp( token, "" ) )
422 break;
423
424 currentMap[ i++ ] = atoi( token );
425 }
426
427 return currentMap;
428 }
429
430 /*
431 ===============
432 G_SetCurrentMap
433
434 Set the current map in some rotation
435 ===============
436 */
G_SetCurrentMap(int currentMap,int rotation)437 static void G_SetCurrentMap( int currentMap, int rotation )
438 {
439 char text[ MAX_MAP_ROTATIONS * 2 ] = { 0 };
440 int *p = G_GetCurrentMapArray( );
441 int i;
442
443 p[ rotation ] = currentMap;
444
445 for( i = 0; i < mapRotations.numRotations; i++ )
446 Q_strcat( text, sizeof( text ), va( "%d ", p[ i ] ) );
447
448 trap_Cvar_Set( "g_currentMap", text );
449 trap_Cvar_Update( &g_currentMap );
450 }
451
452 /*
453 ===============
454 G_GetCurrentMap
455
456 Return the current map in some rotation
457 ===============
458 */
G_GetCurrentMap(int rotation)459 static int G_GetCurrentMap( int rotation )
460 {
461 int *p = G_GetCurrentMapArray( );
462
463 return p[ rotation ];
464 }
465
466 /*
467 ===============
468 G_IssueMapChange
469
470 Send commands to the server to actually change the map
471 ===============
472 */
G_IssueMapChange(int rotation)473 static void G_IssueMapChange( int rotation )
474 {
475 int i;
476 int map = G_GetCurrentMap( rotation );
477 char cmd[ MAX_TOKEN_CHARS ];
478
479 trap_SendConsoleCommand( EXEC_APPEND, va( "map %s\n",
480 mapRotations.rotations[ rotation ].maps[ map ].name ) );
481
482 for( i = 0; i < mapRotations.rotations[ rotation ].maps[ map ].numCmds; i++ )
483 {
484 Q_strncpyz( cmd, mapRotations.rotations[ rotation ].maps[ map ].postCmds[ i ],
485 sizeof( cmd ) );
486 Q_strcat( cmd, sizeof( cmd ), "\n" );
487 trap_SendConsoleCommand( EXEC_APPEND, cmd );
488 }
489 }
490
491 /*
492 ===============
493 G_ResolveConditionDestination
494
495 Resolve the destination of some condition
496 ===============
497 */
G_ResolveConditionDestination(int * n,char * name)498 static mapConditionType_t G_ResolveConditionDestination( int *n, char *name )
499 {
500 int i;
501
502 //search the current rotation first...
503 for( i = 0; i < mapRotations.rotations[ g_currentMapRotation.integer ].numMaps; i++ )
504 {
505 if( !Q_stricmp( mapRotations.rotations[ g_currentMapRotation.integer ].maps[ i ].name, name ) )
506 {
507 *n = i;
508 return MCT_MAP;
509 }
510 }
511
512 //...then search the rotation names
513 for( i = 0; i < mapRotations.numRotations; i++ )
514 {
515 if( !Q_stricmp( mapRotations.rotations[ i ].name, name ) )
516 {
517 *n = i;
518 return MCT_ROTATION;
519 }
520 }
521
522 //this should probably be prevented by a 2nd pass at compile time
523 //but i'm lazy (FIXME)
524 return MCT_ERR;
525 }
526
527 /*
528 ===============
529 G_EvaluateMapCondition
530
531 Evaluate a map condition
532 ===============
533 */
G_EvaluateMapCondition(mapRotationCondition_t * mrc)534 static qboolean G_EvaluateMapCondition( mapRotationCondition_t *mrc )
535 {
536 switch( mrc->lhs )
537 {
538 case MCV_RANDOM:
539 return rand( ) & 1;
540 break;
541
542 case MCV_NUMCLIENTS:
543 switch( mrc->op )
544 {
545 case MCO_LT:
546 return level.numConnectedClients < mrc->numClients;
547 break;
548
549 case MCO_GT:
550 return level.numConnectedClients > mrc->numClients;
551 break;
552
553 case MCO_EQ:
554 return level.numConnectedClients == mrc->numClients;
555 break;
556 }
557 break;
558
559 case MCV_LASTWIN:
560 return level.lastWin == mrc->lastWin;
561 break;
562
563 default:
564 case MCV_ERR:
565 G_Printf( S_COLOR_RED "ERROR: malformed map switch condition\n" );
566 break;
567 }
568
569 return qfalse;
570 }
571
572 /*
573 ===============
574 G_AdvanceMapRotation
575
576 Increment the current map rotation
577 ===============
578 */
G_AdvanceMapRotation(void)579 qboolean G_AdvanceMapRotation( void )
580 {
581 mapRotation_t *mr;
582 mapRotationEntry_t *mre;
583 mapRotationCondition_t *mrc;
584 int currentRotation, currentMap, nextMap;
585 int i, n;
586 mapConditionType_t mct;
587
588 if( ( currentRotation = g_currentMapRotation.integer ) == NOT_ROTATING )
589 return qfalse;
590
591 currentMap = G_GetCurrentMap( currentRotation );
592
593 mr = &mapRotations.rotations[ currentRotation ];
594 mre = &mr->maps[ currentMap ];
595 nextMap = ( currentMap + 1 ) % mr->numMaps;
596
597 for( i = 0; i < mre->numConditions; i++ )
598 {
599 mrc = &mre->conditions[ i ];
600
601 if( mrc->unconditional || G_EvaluateMapCondition( mrc ) )
602 {
603 mct = G_ResolveConditionDestination( &n, mrc->dest );
604
605 switch( mct )
606 {
607 case MCT_MAP:
608 nextMap = n;
609 break;
610
611 case MCT_ROTATION:
612 G_StartMapRotation( mrc->dest, qtrue );
613 return qtrue;
614 break;
615
616 default:
617 case MCT_ERR:
618 G_Printf( S_COLOR_YELLOW "WARNING: map switch destination could not be resolved: %s\n",
619 mrc->dest );
620 break;
621 }
622 }
623 }
624
625 G_SetCurrentMap( nextMap, currentRotation );
626 G_IssueMapChange( currentRotation );
627
628 return qtrue;
629 }
630
631 /*
632 ===============
633 G_StartMapRotation
634
635 Switch to a new map rotation
636 ===============
637 */
G_StartMapRotation(char * name,qboolean changeMap)638 qboolean G_StartMapRotation( char *name, qboolean changeMap )
639 {
640 int i;
641
642 for( i = 0; i < mapRotations.numRotations; i++ )
643 {
644 if( !Q_stricmp( mapRotations.rotations[ i ].name, name ) )
645 {
646 trap_Cvar_Set( "g_currentMapRotation", va( "%d", i ) );
647 trap_Cvar_Update( &g_currentMapRotation );
648
649 if( changeMap )
650 G_IssueMapChange( i );
651 break;
652 }
653 }
654
655 if( i == mapRotations.numRotations )
656 return qfalse;
657 else
658 return qtrue;
659 }
660
661 /*
662 ===============
663 G_StopMapRotation
664
665 Stop the current map rotation
666 ===============
667 */
G_StopMapRotation(void)668 void G_StopMapRotation( void )
669 {
670 trap_Cvar_Set( "g_currentMapRotation", va( "%d", NOT_ROTATING ) );
671 trap_Cvar_Update( &g_currentMapRotation );
672 }
673
674 /*
675 ===============
676 G_MapRotationActive
677
678 Test if any map rotation is currently active
679 ===============
680 */
G_MapRotationActive(void)681 qboolean G_MapRotationActive( void )
682 {
683 return ( g_currentMapRotation.integer != NOT_ROTATING );
684 }
685
686 /*
687 ===============
688 G_InitMapRotations
689
690 Load and intialise the map rotations
691 ===============
692 */
G_InitMapRotations(void)693 void G_InitMapRotations( void )
694 {
695 const char *fileName = "maprotation.cfg";
696 fileHandle_t f;
697
698 //load the file if it exists
699 if( trap_FS_FOpenFile( fileName, &f, FS_READ ) > 0 )
700 {
701 trap_FS_FCloseFile( f );
702
703 if( !G_ParseMapRotationFile( fileName ) )
704 G_Printf( S_COLOR_RED "ERROR: failed to parse %s file\n", fileName );
705 }
706 else
707 G_Printf( "%s file not found.\n", fileName );
708
709 if( g_currentMapRotation.integer == NOT_ROTATING )
710 {
711 if( g_initialMapRotation.string[ 0 ] != 0 )
712 {
713 G_StartMapRotation( g_initialMapRotation.string, qfalse );
714
715 trap_Cvar_Set( "g_initialMapRotation", "" );
716 trap_Cvar_Update( &g_initialMapRotation );
717 }
718 }
719 }
720