
Quake DeveLS - Map Rotation
Author: Mark
"Grey" Davies
Difficulty: Easy
Overview:
This tutorial just explains how to add my map rotation code to your mod. My map rotation code takes into account the number of players currently connected and attempts to select an appropriate map.
The code uses the following cvars.
The format of the map file is an based upon id's
maps.lst file. Optional information can be added to specify the minimum and
maximum players that can play on a map. If a map entry does not specify any
values they are assumed to be 0. A map with a maximum set to 0 is ignored.
An example of a modified maps.lst file
starctf2 "starctf2" 00 00
3haddm2 "3haddm2" 00 00
crdm1 "Jailhouse Frag" 00 06
ikdm1 "ikdm1" 02 04
komit "komit" 02 05
dndm1 "Circle of Death" 02 05
kombat03 "The Forgotten Fort" 02 05
pinhole "pinhole" 02 06
kick "Kick the P.A." 02 08
mpq1 "Fear in your Eyes" 03 05
ikdm3 "ikdm3" 03 06
ikdm2 "ikdm2" 03 06
jabbar "Gom Jabbar" 03 07
sqldm1 "SQL Deathmatch Level 1" 03 08
q2dm2 "Tokay's Towers" 03 10
q2dm1 "The Edge" 03 10
broken1 "Third Degree" 03 20
ikdm4 "ikdm4" 04 00
pirahna "pirahna" 04 06
inhabdm1 "inhabdm1" 04 07
ranbase "Ranger Base 2" 04 08
colour "Colours of War" 04 08
ztn2dm2 "The Killing Machine" 04 10
q2dm6 "Lava Tomb" 04 10
q2dm5 "The Pits" 04 15
q2dm3 "The Frag Pipe" 04 15
q2dm4 "Lost Hallways" 04 15
trdm02 "Shadows and Imaginings" 04 15
q2dm7 "The Slimy Place" 04 15
ecdm1 "All That I Bleed" 05 10
fatal2 "fatality 2" 05 20
abandon "Abandon All Hope" 05 32
mutated "Mutated!" 05 32
q2dm8 "WareHouse" 06 20
arma5 "Armageddon 5" 08 20
city64 "Courts of War" 15 64
sewer64 "The Sewers of Stroggos" 20 64
base64 "The Strogg Base" 20 64
Instructions:
Create a file called s_map.c and add the following lines
#include "g_local.h"
typedef struct
{char aFile[MAX_QPATH];
char aName[MAX_QPATH];
int min;
int max;
int fVisited;
} MAP_ENTRY;
static MAP_ENTRY *mdsoft_map = NULL;
static unsigned int mdsoft_map_size = 0;static unsigned int mdsoft_map_last = 0;static int mdsoft_read_map_entry( FILE *fpFile,
char *pFile,
char *pName,
int *pMin,
int *pMax );
edict_t *mdsoft_NextMap( void )
{edict_t *ent = NULL;
int count = 0;
int fFound = 0;
int nTimes = 0;
cvar_t *map_c = gi.cvar( "md_map_change", "1", CVAR_SERVERINFO ); cvar_t *map_r = gi.cvar( "md_map_random", "0", CVAR_SERVERINFO ); cvar_t *map_o = gi.cvar( "md_map_once", "0", CVAR_SERVERINFO ); cvar_t *map_d = gi.cvar( "md_map_debug", "0", CVAR_SERVERINFO ); if( (int)map_c->value == 0 ) return NULL; /* Load Maps File */ if( NULL == mdsoft_map ) {FILE *fpFile = NULL;
cvar_t *game = gi.cvar( "gamedir", "mod-1", CVAR_SERVERINFO );
cvar_t *base = gi.cvar( "basedir", ".", CVAR_SERVERINFO );
cvar_t *map_f = gi.cvar( "md_map_file", "maps.lst", CVAR_SERVERINFO );
/* Load maps.lst file */ if( game && base ) {char mapfile[MAX_QPATH] = {0};
char *pFileName = &mapfile[0]; strcat( mapfile, base->string ); strcat( mapfile, "\\" ); strcat( mapfile, game->string ); if( NULL != map_f ) { strcat( mapfile, "\\" ); strcat( mapfile, map_f->string ); } else { strcat( mapfile, "\\maps.lst" ); } fpFile = fopen( pFileName, "r" );if( fpFile )
{MAP_ENTRY temp;
int element;
do {temp.min = 0;
temp.max = 0;
temp.fVisited = 0; element = mdsoft_read_map_entry( fpFile, &temp.aFile[0], &temp.aName[0], &temp.min, &temp.max ); if( 2 <= element ){
MAP_ENTRY *newone; newone = realloc( mdsoft_map, (mdsoft_map_size+1) * sizeof(*newone) ); if( newone ){
mdsoft_map = newone; memcpy( &mdsoft_map[mdsoft_map_size], &temp, sizeof(temp) ); mdsoft_map_size++; } }}while( 2 <= element );
#if 0
/* The following code will overflow the clients (players) */ /* And kick them off the server */ { int i;for( i=0; i< mdsoft_map_size; i++ )
{ if( 0 == strcmp( level.mapname, &mdsoft_map[i].aFile[0] ) ) { mdsoft_map_last = i; break; } } }#endif
if( mdsoft_map_size ) { do {/* Find random map to search from */
if( (NULL != map_r) && ((int)map_r->value > 0 ) ) { mdsoft_map_last = random() * (mdsoft_map_size-1); if( mdsoft_map_last < 0 )mdsoft_map_last = 0-mdsoft_map_last;
if( (NULL != map_d) && ((int)map_d->value > 0 ) ) gi.bprintf( PRINT_HIGH, "Random Map %d %s\n", mdsoft_map_last,
mdsoft_map[mdsoft_map_last].aFile); } /* Choose map */ { int i; int point = (mdsoft_map_last+1) % mdsoft_map_size;count = 0;
for (i = 0; i < maxclients->value; i++) { if (game.clients[i].pers.connected) count++; } /*gi.bprintf (PRINT_HIGH, "MAP CHANGE: Count = %d \n", count );*/ do { if( (0 != mdsoft_map[point].max) && (0 == mdsoft_map[point].fVisited) ) { if( (mdsoft_map[point].min <= count) && (mdsoft_map[point].max >= count) ) { mdsoft_map_last = point; point = -1; fFound = 1;if( (NULL != map_d) &&
((int)map_d->value > 0 ) ) gi.bprintf( PRINT_HIGH, "Map Found %s [fVisited = %d]\n",mdsoft_map[mdsoft_map_last].aFile,
mdsoft_map[mdsoft_map_last].fVisited); } else { point = (point+1) % mdsoft_map_size; } } else { point = (point+1) % mdsoft_map_size; } }while( (point != -1) && (point != mdsoft_map_last) ); /* Could not select an appropriate map */ if(point == mdsoft_map_last) { if( (NULL != map_d) && ((int)map_d->value > 0 ) )gi.bprintf(PRINT_HIGH, "Map could not be found\n" );
/* Clear visited flags */ if( (NULL != map_o) && ((int)map_o->value > 0 ) ) { int i; if( (NULL != map_d) && ((int)map_d->value > 0 ) ) gi.bprintf(PRINT_HIGH, "Clearing Visited flags\n" ); for( i=0; i < mdsoft_map_size; i++ )mdsoft_map[i].fVisited = 0;
} /* Use next map in list */ mdsoft_map_last = (mdsoft_map_last+1) % mdsoft_map_size; } } nTimes++;} while( !fFound && (nTimes < 2) );
} else { mdsoft_map_last = 0; } if( fFound && !ent ) { /* Set map as visited */ if( (NULL != map_o) && ((int)map_o->value > 0 ) ) { mdsoft_map[mdsoft_map_last].fVisited = 1; } /* Set next map */ ent = G_Spawn (); if( ent ) { ent->classname = "target_changelevel"; ent->map = &mdsoft_map[mdsoft_map_last].aFile[0];if( (NULL != map_d) &&
((int)map_d->value > 0 ) ) { gi.bprintf (PRINT_HIGH, "MAP CHANGE: %d ", mdsoft_map_last ); gi.bprintf (PRINT_HIGH, &mdsoft_map[mdsoft_map_last].aFile[0] );gi.bprintf (PRINT_HIGH,
" [min = %d,max = %d, players = %d]\n", mdsoft_map[mdsoft_map_last].min, mdsoft_map[mdsoft_map_last].max, count );}
} } return ent;} /* end of mdsoft_NextMap() */
static int mdsoft_read_map_entry( FILE *fpFile,
char *pFile,
char *pName,
int *pMin,
int *pMax )
{char buffer[MAX_QPATH] = {0};
int c;
int i = 0;
int fInQuotes = 0;
int element = 0;
do { c = fgetc( fpFile ); /* Use buffer */ if( (i > 0) && ( (((' ' == c) || ('\t' == c)) && !fInQuotes) || (EOF == c) || ('\n' == c) ) ) { buffer[i] = '\0';switch( element )
{ case 0: { strncpy( pFile, buffer, MAX_QPATH ); break; } case 1: { strncpy( pName, buffer, MAX_QPATH ); break; } case 2: { *pMin = atoi( buffer ); break; } case 3: { *pMax = atoi( buffer ); break; } } i = 0; element++; } else { switch( c ) { case '\"':{
fInQuotes = 1 - fInQuotes; break; } case '\t': case ' ': { if( !fInQuotes ) break; } /* fallthrough */ default: { if( i < (MAX_QPATH-1) ) { buffer[i] = c; i++; } break;}
} } } while( (c != EOF) && (c != '\n') ); return element;} /* end of mdsoft_read_map_entry() */
Create a file called s_map.h and add the following lines
extern edict_t *mdsoft_NextMap( void );
Open the file called g_main.c and replace
#include "g_local.h"
with
#include "g_local.h"
#include "s_map.h"
Find the function EndDMLevel(), it should be located around line 181 and replace it with
void EndDMLevel (void)
{edict_t *ent = NULL; /* Added '= NULL' - mdavies */
// stay on same level flag if ((int)dmflags->value & DF_SAME_LEVEL) { ent = G_Spawn (); ent->classname = "target_changelevel"; ent->map = level.mapname; } /* New code - START - mdavies */ if( !ent ) { ent = mdsoft_NextMap(); } /* New code - END - mdavies */if( !ent ) /* added line - mdavies */
{ /* added line - mdavies */
if (level.nextmap[0]) /* changed else if to if - mdavies */
{ // go to a specific map
ent = G_Spawn (); ent->classname = "target_changelevel"; ent->map = level.nextmap; } else{ // search for a changeleve
ent = G_Find (NULL, FOFS(classname), "target_changelevel"); if (!ent){ // the map designer didn't include a changelevel,
// so create a fake ent that goes back to the same level ent = G_Spawn ();ent->classname = "target_changelevel";
ent->map = level.mapname; } }} /* Added line - mdavies */
BeginIntermission (ent);}
Ensure that the file s_map.c is compiled with the rest of your source and that it is linked into your mod. Compile, link and run.
Notes:
The format of the file can be changed if needed. The function mdsoft_read_map_entry() is used to parse a line in the map file. By modifying this function you can control the format of the input file.
Tips:
If your server runs with a time limit then you can get the system to default to a specific map when all players have disconnected. To do this all you need to do is set a map to have a valid maximum and a minimum of 0. For example, if you want your server to default to the map "Jailhouse Frag" then add the following line to your maps.lst file.
crdm1 "Jailhouse Frag" 00 06
If you add more than one map of this type to your maps.lst file then one will be selected. Which one depends on your settings.
This code can be used freely. If you use this code then please email Mark "Grey" Davies and credit him.