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