Files
neo.yt-tremulous/src/game/g_maprotation.c
T

744 lines
17 KiB
C

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2000-2006 Tim Angus
This file is part of Tremulous.
Tremulous is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
Tremulous is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Tremulous; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// g_maprotation.c -- the map rotation system
#include "g_local.h"
static mapRotations_t mapRotations;
/*
===============
G_ParseCommandSection
Parse a map rotation command section
===============
*/
static qboolean G_ParseMapCommandSection( mapRotationEntry_t *mre, char **text_p )
{
char *token;
// read optional parameters
while( 1 )
{
token = COM_Parse( text_p );
if( !token )
break;
if( !Q_stricmp( token, "" ) )
return qfalse;
if( !Q_stricmp( token, "}" ) )
return qtrue; //reached the end of this command section
if( !Q_stricmp( token, "layouts" ) )
{
token = COM_ParseExt( text_p, qfalse );
mre->layouts[ 0 ] = '\0';
while( token && token[ 0 ] != 0 )
{
Q_strcat( mre->layouts, sizeof( mre->layouts ), token );
Q_strcat( mre->layouts, sizeof( mre->layouts ), " " );
token = COM_ParseExt( text_p, qfalse );
}
continue;
}
Q_strncpyz( mre->postCmds[ mre->numCmds ], token, sizeof( mre->postCmds[ 0 ] ) );
Q_strcat( mre->postCmds[ mre->numCmds ], sizeof( mre->postCmds[ 0 ] ), " " );
token = COM_ParseExt( text_p, qfalse );
while( token && token[ 0 ] != 0 )
{
Q_strcat( mre->postCmds[ mre->numCmds ], sizeof( mre->postCmds[ 0 ] ), token );
Q_strcat( mre->postCmds[ mre->numCmds ], sizeof( mre->postCmds[ 0 ] ), " " );
token = COM_ParseExt( text_p, qfalse );
}
if( mre->numCmds == MAX_MAP_COMMANDS )
{
G_Printf( S_COLOR_RED "ERROR: maximum number of map commands (%d) reached\n",
MAX_MAP_COMMANDS );
return qfalse;
}
else
mre->numCmds++;
}
return qfalse;
}
/*
===============
G_ParseMapRotation
Parse a map rotation section
===============
*/
static qboolean G_ParseMapRotation( mapRotation_t *mr, char **text_p )
{
char *token;
qboolean mnSet = qfalse;
mapRotationEntry_t *mre = NULL;
mapRotationCondition_t *mrc;
// read optional parameters
while( 1 )
{
token = COM_Parse( text_p );
if( !token )
break;
if( !Q_stricmp( token, "" ) )
return qfalse;
if( !Q_stricmp( token, "{" ) )
{
if( !mnSet )
{
G_Printf( S_COLOR_RED "ERROR: map settings section with no name\n" );
return qfalse;
}
if( !G_ParseMapCommandSection( mre, text_p ) )
{
G_Printf( S_COLOR_RED "ERROR: failed to parse map command section\n" );
return qfalse;
}
mnSet = qfalse;
continue;
}
else if( !Q_stricmp( token, "goto" ) )
{
token = COM_Parse( text_p );
if( !token )
break;
mrc = &mre->conditions[ mre->numConditions ];
mrc->unconditional = qtrue;
Q_strncpyz( mrc->dest, token, sizeof( mrc->dest ) );
if( mre->numConditions == MAX_MAP_ROTATION_CONDS )
{
G_Printf( S_COLOR_RED "ERROR: maximum number of conditions for one map (%d) reached\n",
MAX_MAP_ROTATION_CONDS );
return qfalse;
}
else
mre->numConditions++;
continue;
}
else if( !Q_stricmp( token, "if" ) )
{
token = COM_Parse( text_p );
if( !token )
break;
mrc = &mre->conditions[ mre->numConditions ];
if( !Q_stricmp( token, "numClients" ) )
{
mrc->lhs = MCV_NUMCLIENTS;
token = COM_Parse( text_p );
if( !token )
break;
if( !Q_stricmp( token, "<" ) )
mrc->op = MCO_LT;
else if( !Q_stricmp( token, ">" ) )
mrc->op = MCO_GT;
else if( !Q_stricmp( token, "=" ) )
mrc->op = MCO_EQ;
else
{
G_Printf( S_COLOR_RED "ERROR: invalid operator in expression: %s\n", token );
return qfalse;
}
token = COM_Parse( text_p );
if( !token )
break;
mrc->numClients = atoi( token );
}
else if( !Q_stricmp( token, "lastWin" ) )
{
mrc->lhs = MCV_LASTWIN;
token = COM_Parse( text_p );
if( !token )
break;
if( !Q_stricmp( token, "aliens" ) )
mrc->lastWin = PTE_ALIENS;
else if( !Q_stricmp( token, "humans" ) )
mrc->lastWin = PTE_HUMANS;
else
{
G_Printf( S_COLOR_RED "ERROR: invalid right hand side in expression: %s\n", token );
return qfalse;
}
}
else if( !Q_stricmp( token, "random" ) )
mrc->lhs = MCV_RANDOM;
else
{
G_Printf( S_COLOR_RED "ERROR: invalid left hand side in expression: %s\n", token );
return qfalse;
}
token = COM_Parse( text_p );
if( !token )
break;
mrc->unconditional = qfalse;
Q_strncpyz( mrc->dest, token, sizeof( mrc->dest ) );
if( mre->numConditions == MAX_MAP_ROTATION_CONDS )
{
G_Printf( S_COLOR_RED "ERROR: maximum number of conditions for one map (%d) reached\n",
MAX_MAP_ROTATION_CONDS );
return qfalse;
}
else
mre->numConditions++;
continue;
}
else if( !Q_stricmp( token, "}" ) )
return qtrue; //reached the end of this map rotation
mre = &mr->maps[ mr->numMaps ];
if( mr->numMaps == MAX_MAP_ROTATION_MAPS )
{
G_Printf( S_COLOR_RED "ERROR: maximum number of maps in one rotation (%d) reached\n",
MAX_MAP_ROTATION_MAPS );
return qfalse;
}
else
mr->numMaps++;
Q_strncpyz( mre->name, token, sizeof( mre->name ) );
mnSet = qtrue;
}
return qfalse;
}
/*
===============
G_ParseMapRotationFile
Load the map rotations from a map rotation file
===============
*/
static qboolean G_ParseMapRotationFile( const char *fileName )
{
char *text_p;
int i;
int len;
char *token;
char text[ 20000 ];
char mrName[ MAX_QPATH ];
qboolean mrNameSet = qfalse;
fileHandle_t f;
// load the file
len = trap_FS_FOpenFile( fileName, &f, FS_READ );
if( len <= 0 )
return qfalse;
if( len >= sizeof( text ) - 1 )
{
G_Printf( S_COLOR_RED "ERROR: map rotation file %s too long\n", fileName );
return qfalse;
}
trap_FS_Read( text, len, f );
text[ len ] = 0;
trap_FS_FCloseFile( f );
// parse the text
text_p = text;
// read optional parameters
while( 1 )
{
token = COM_Parse( &text_p );
if( !token )
break;
if( !Q_stricmp( token, "" ) )
break;
if( !Q_stricmp( token, "{" ) )
{
if( mrNameSet )
{
//check for name space clashes
for( i = 0; i < mapRotations.numRotations; i++ )
{
if( !Q_stricmp( mapRotations.rotations[ i ].name, mrName ) )
{
G_Printf( S_COLOR_RED "ERROR: a map rotation is already named %s\n", mrName );
return qfalse;
}
}
Q_strncpyz( mapRotations.rotations[ mapRotations.numRotations ].name, mrName, MAX_QPATH );
if( !G_ParseMapRotation( &mapRotations.rotations[ mapRotations.numRotations ], &text_p ) )
{
G_Printf( S_COLOR_RED "ERROR: %s: failed to parse map rotation %s\n", fileName, mrName );
return qfalse;
}
//start parsing particle systems again
mrNameSet = qfalse;
if( mapRotations.numRotations == MAX_MAP_ROTATIONS )
{
G_Printf( S_COLOR_RED "ERROR: maximum number of map rotations (%d) reached\n",
MAX_MAP_ROTATIONS );
return qfalse;
}
else
mapRotations.numRotations++;
continue;
}
else
{
G_Printf( S_COLOR_RED "ERROR: unamed map rotation\n" );
return qfalse;
}
}
if( !mrNameSet )
{
Q_strncpyz( mrName, token, sizeof( mrName ) );
mrNameSet = qtrue;
}
else
{
G_Printf( S_COLOR_RED "ERROR: map rotation already named\n" );
return qfalse;
}
}
return qtrue;
}
/*
===============
G_PrintRotations
Print the parsed map rotations
===============
*/
void G_PrintRotations( void )
{
int i, j, k;
G_Printf( "Map rotations as parsed:\n\n" );
for( i = 0; i < mapRotations.numRotations; i++ )
{
G_Printf( "rotation: %s\n{\n", mapRotations.rotations[ i ].name );
for( j = 0; j < mapRotations.rotations[ i ].numMaps; j++ )
{
G_Printf( " map: %s\n {\n", mapRotations.rotations[ i ].maps[ j ].name );
for( k = 0; k < mapRotations.rotations[ i ].maps[ j ].numCmds; k++ )
{
G_Printf( " command: %s\n",
mapRotations.rotations[ i ].maps[ j ].postCmds[ k ] );
}
G_Printf( " }\n" );
for( k = 0; k < mapRotations.rotations[ i ].maps[ j ].numConditions; k++ )
{
G_Printf( " conditional: %s\n",
mapRotations.rotations[ i ].maps[ j ].conditions[ k ].dest );
}
}
G_Printf( "}\n" );
}
G_Printf( "Total memory used: %d bytes\n", sizeof( mapRotations ) );
}
/*
===============
G_GetCurrentMapArray
Fill a static array with the current map of each rotation
===============
*/
static int *G_GetCurrentMapArray( void )
{
static int currentMap[ MAX_MAP_ROTATIONS ];
int i = 0;
char text[ MAX_MAP_ROTATIONS * 2 ];
char *text_p, *token;
Q_strncpyz( text, g_currentMap.string, sizeof( text ) );
text_p = text;
while( 1 )
{
token = COM_Parse( &text_p );
if( !token )
break;
if( !Q_stricmp( token, "" ) )
break;
currentMap[ i++ ] = atoi( token );
}
return currentMap;
}
/*
===============
G_SetCurrentMap
Set the current map in some rotation
===============
*/
static void G_SetCurrentMap( int currentMap, int rotation )
{
char text[ MAX_MAP_ROTATIONS * 2 ] = { 0 };
int *p = G_GetCurrentMapArray( );
int i;
p[ rotation ] = currentMap;
for( i = 0; i < mapRotations.numRotations; i++ )
Q_strcat( text, sizeof( text ), va( "%d ", p[ i ] ) );
trap_Cvar_Set( "g_currentMap", text );
trap_Cvar_Update( &g_currentMap );
}
/*
===============
G_GetCurrentMap
Return the current map in some rotation
===============
*/
static int G_GetCurrentMap( int rotation )
{
int *p = G_GetCurrentMapArray( );
return p[ rotation ];
}
/*
===============
G_IssueMapChange
Send commands to the server to actually change the map
===============
*/
static void G_IssueMapChange( int rotation )
{
int i;
int map = G_GetCurrentMap( rotation );
char cmd[ MAX_TOKEN_CHARS ];
// allow a manually defined g_layouts setting to override the maprotation
if( !g_layouts.string[ 0 ] &&
mapRotations.rotations[ rotation ].maps[ map ].layouts[ 0 ] )
{
trap_Cvar_Set( "g_layouts",
mapRotations.rotations[ rotation ].maps[ map ].layouts );
}
trap_SendConsoleCommand( EXEC_APPEND, va( "map %s\n",
mapRotations.rotations[ rotation ].maps[ map ].name ) );
// load up map defaults if g_mapConfigs is set
G_MapConfigs( mapRotations.rotations[ rotation ].maps[ map ].name );
for( i = 0; i < mapRotations.rotations[ rotation ].maps[ map ].numCmds; i++ )
{
Q_strncpyz( cmd, mapRotations.rotations[ rotation ].maps[ map ].postCmds[ i ],
sizeof( cmd ) );
Q_strcat( cmd, sizeof( cmd ), "\n" );
trap_SendConsoleCommand( EXEC_APPEND, cmd );
}
}
/*
===============
G_ResolveConditionDestination
Resolve the destination of some condition
===============
*/
static mapConditionType_t G_ResolveConditionDestination( int *n, char *name )
{
int i;
//search the current rotation first...
for( i = 0; i < mapRotations.rotations[ g_currentMapRotation.integer ].numMaps; i++ )
{
if( !Q_stricmp( mapRotations.rotations[ g_currentMapRotation.integer ].maps[ i ].name, name ) )
{
*n = i;
return MCT_MAP;
}
}
//...then search the rotation names
for( i = 0; i < mapRotations.numRotations; i++ )
{
if( !Q_stricmp( mapRotations.rotations[ i ].name, name ) )
{
*n = i;
return MCT_ROTATION;
}
}
//this should probably be prevented by a 2nd pass at compile time
//but i'm lazy (FIXME)
return MCT_ERR;
}
/*
===============
G_EvaluateMapCondition
Evaluate a map condition
===============
*/
static qboolean G_EvaluateMapCondition( mapRotationCondition_t *mrc )
{
switch( mrc->lhs )
{
case MCV_RANDOM:
return rand( ) & 1;
break;
case MCV_NUMCLIENTS:
switch( mrc->op )
{
case MCO_LT:
return level.numConnectedClients < mrc->numClients;
break;
case MCO_GT:
return level.numConnectedClients > mrc->numClients;
break;
case MCO_EQ:
return level.numConnectedClients == mrc->numClients;
break;
}
break;
case MCV_LASTWIN:
return level.lastWin == mrc->lastWin;
break;
default:
case MCV_ERR:
G_Printf( S_COLOR_RED "ERROR: malformed map switch condition\n" );
break;
}
return qfalse;
}
/*
===============
G_AdvanceMapRotation
Increment the current map rotation
===============
*/
qboolean G_AdvanceMapRotation( void )
{
mapRotation_t *mr;
mapRotationEntry_t *mre;
mapRotationCondition_t *mrc;
int currentRotation, currentMap, nextMap;
int i, n;
mapConditionType_t mct;
if( ( currentRotation = g_currentMapRotation.integer ) == NOT_ROTATING )
return qfalse;
currentMap = G_GetCurrentMap( currentRotation );
mr = &mapRotations.rotations[ currentRotation ];
mre = &mr->maps[ currentMap ];
nextMap = ( currentMap + 1 ) % mr->numMaps;
for( i = 0; i < mre->numConditions; i++ )
{
mrc = &mre->conditions[ i ];
if( mrc->unconditional || G_EvaluateMapCondition( mrc ) )
{
mct = G_ResolveConditionDestination( &n, mrc->dest );
switch( mct )
{
case MCT_MAP:
nextMap = n;
break;
case MCT_ROTATION:
G_StartMapRotation( mrc->dest, qtrue );
return qtrue;
break;
default:
case MCT_ERR:
G_Printf( S_COLOR_YELLOW "WARNING: map switch destination could not be resolved: %s\n",
mrc->dest );
break;
}
}
}
G_SetCurrentMap( nextMap, currentRotation );
G_IssueMapChange( currentRotation );
return qtrue;
}
/*
===============
G_StartMapRotation
Switch to a new map rotation
===============
*/
qboolean G_StartMapRotation( char *name, qboolean changeMap )
{
int i;
for( i = 0; i < mapRotations.numRotations; i++ )
{
if( !Q_stricmp( mapRotations.rotations[ i ].name, name ) )
{
trap_Cvar_Set( "g_currentMapRotation", va( "%d", i ) );
trap_Cvar_Update( &g_currentMapRotation );
if( changeMap )
G_IssueMapChange( i );
break;
}
}
if( i == mapRotations.numRotations )
return qfalse;
else
return qtrue;
}
/*
===============
G_StopMapRotation
Stop the current map rotation
===============
*/
void G_StopMapRotation( void )
{
trap_Cvar_Set( "g_currentMapRotation", va( "%d", NOT_ROTATING ) );
trap_Cvar_Update( &g_currentMapRotation );
}
/*
===============
G_MapRotationActive
Test if any map rotation is currently active
===============
*/
qboolean G_MapRotationActive( void )
{
return ( g_currentMapRotation.integer != NOT_ROTATING );
}
/*
===============
G_InitMapRotations
Load and intialise the map rotations
===============
*/
void G_InitMapRotations( void )
{
const char *fileName = "maprotation.cfg";
fileHandle_t f;
//load the file if it exists
if( trap_FS_FOpenFile( fileName, &f, FS_READ ) > 0 )
{
trap_FS_FCloseFile( f );
if( !G_ParseMapRotationFile( fileName ) )
G_Printf( S_COLOR_RED "ERROR: failed to parse %s file\n", fileName );
}
else
G_Printf( "%s file not found.\n", fileName );
if( g_currentMapRotation.integer == NOT_ROTATING )
{
if( g_initialMapRotation.string[ 0 ] != 0 )
{
G_StartMapRotation( g_initialMapRotation.string, qfalse );
trap_Cvar_Set( "g_initialMapRotation", "" );
trap_Cvar_Update( &g_initialMapRotation );
}
}
}