You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
753 lines
14 KiB
753 lines
14 KiB
//++ BulliT |
|
#include "extdll.h" |
|
#include "util.h" |
|
#include "cbase.h" |
|
#include "gamerules.h" |
|
#include "player.h" |
|
#include "game.h" |
|
#include "agglobal.h" |
|
#include "agsettings.h" |
|
#ifdef AGSTATS |
|
#include "agstats.h" |
|
#endif |
|
////////////////////////////////////////////////////////////////////// |
|
// Construction/Destruction |
|
////////////////////////////////////////////////////////////////////// |
|
|
|
DLL_GLOBAL bool g_bMapchange = false; |
|
DLL_GLOBAL AgString g_sNextMap; |
|
DLL_GLOBAL AgString g_sNextRules; |
|
|
|
extern cvar_t timeleft, fragsleft; |
|
extern int gmsgNextmap; |
|
|
|
|
|
AgSettings::AgSettings() |
|
{ |
|
m_bChangeNextLevel = false; |
|
g_bMapchange = false; |
|
|
|
m_bCheckNextMap = true; |
|
m_bCalcNextMap = true; |
|
|
|
m_fNextCheck = gpGlobals->time + 10.0; |
|
} |
|
|
|
AgSettings::~AgSettings() |
|
{ |
|
|
|
} |
|
|
|
bool AgSettings::Think() |
|
{ |
|
if (!g_pGameRules) |
|
return false; |
|
|
|
if (g_bMapchange) |
|
return false; |
|
|
|
if (m_bChangeNextLevel) |
|
{ |
|
m_bChangeNextLevel = false; |
|
g_bMapchange = true; |
|
//Change the map. |
|
AgChangelevel(g_sNextMap); |
|
|
|
if (g_sNextRules.size()) |
|
{ |
|
SERVER_COMMAND((char*)g_sNextRules.c_str()); |
|
g_sNextRules = ""; |
|
} |
|
return false; |
|
} |
|
|
|
if (g_fGameOver) |
|
return true; |
|
|
|
//No need to do rest of this every frame. |
|
if (m_fNextCheck > gpGlobals->time) |
|
return true; |
|
|
|
if (m_bCalcNextMap) |
|
CalcNextMap(); |
|
|
|
m_fNextCheck = gpGlobals->time + 5.0; //Every 5 seconds. |
|
|
|
//Check if to display next map. |
|
if (m_bCheckNextMap && timelimit.value || m_bCheckNextMap && fraglimit.value) |
|
{ |
|
if (timeleft.value && 60 > timeleft.value || fraglimit.value && 2 > fragsleft.value) |
|
{ |
|
#ifdef AG_NO_CLIENT_DLL |
|
AgSay(NULL,UTIL_VarArgs("Next map is %s\n",GetNextLevel().c_str()),NULL,30,0.03,0.05,2); |
|
#else |
|
MESSAGE_BEGIN(MSG_BROADCAST,gmsgNextmap); |
|
WRITE_STRING( GetNextLevel().c_str() ); |
|
MESSAGE_END(); |
|
#endif |
|
|
|
m_bCheckNextMap = false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
bool AgSettings::AdminSetting(const AgString& sSetting, const AgString& sValue) |
|
{ |
|
if (0 == strnicmp(sSetting.c_str(),"ag_",3) |
|
||0 == strnicmp(sSetting.c_str(),"mp_timelimit",12) |
|
||0 == strnicmp(sSetting.c_str(),"mp_fraglimit",12) |
|
) |
|
{ |
|
CVAR_SET_STRING(sSetting.c_str(),sValue.c_str()); |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
|
|
void AgSettings::Changelevel(const AgString& sMap) |
|
{ |
|
if (32 < sMap.size() || 0 == sMap.size()) |
|
return; |
|
|
|
char szTemp[64]; |
|
strcpy(szTemp,sMap.c_str()); |
|
|
|
//Check if it exists. |
|
if (IS_MAP_VALID(szTemp)) |
|
{ |
|
g_sNextMap = sMap; |
|
g_sNextRules = ""; |
|
g_pGameRules->GoToIntermission(); |
|
|
|
#ifdef AGSTATS |
|
Stats.OnChangeLevel(); |
|
#endif |
|
} |
|
} |
|
|
|
|
|
void AgSettings::SetNextLevel(const AgString& sMap) |
|
{ |
|
if (32 < sMap.size() || 0 == sMap.size()) |
|
return; |
|
|
|
char szTemp[64]; |
|
strcpy(szTemp,sMap.c_str()); |
|
|
|
//Check if it exists. |
|
if (IS_MAP_VALID(szTemp)) |
|
g_sNextMap = sMap; |
|
|
|
if (g_sNextMap.size()) |
|
{ |
|
#ifdef AG_NO_CLIENT_DLL |
|
char szNextMap[128]; |
|
sprintf(szNextMap, "Next map is %s", g_sNextMap.c_str()); |
|
AgSay(NULL,szNextMap,NULL,5,0.5,0.2); |
|
#else |
|
MESSAGE_BEGIN(MSG_BROADCAST,gmsgNextmap); |
|
WRITE_STRING( g_sNextMap.c_str() ); |
|
MESSAGE_END(); |
|
#endif |
|
} |
|
} |
|
|
|
AgString AgSettings::GetNextLevel() |
|
{ |
|
return g_sNextMap; |
|
} |
|
|
|
void AgSettings::ChangeNextLevel() |
|
{ |
|
if (32 < g_sNextMap.size() || 0 == g_sNextMap.size()) |
|
return; |
|
|
|
m_bChangeNextLevel = true; |
|
} |
|
|
|
/* |
|
void AgSettings::CalcNextMap() |
|
{ |
|
//Calc next map, wont work with maps that are in more than one place in mapcycle file. |
|
typedef list<AgString> AgMapList; |
|
AgMapList lstMaps; |
|
|
|
char *pszMapFile = (char*) CVAR_GET_STRING( "mapcyclefile" ); |
|
ASSERT( pszMapFile != NULL ); |
|
|
|
// Load the file |
|
int nLength = 0; |
|
char* pFileList = (char *)LOAD_FILE_FOR_ME(pszMapFile,&nLength); |
|
|
|
if (pFileList && nLength) |
|
{ |
|
// Loop while there are lines |
|
char *pFileCur = pFileList; |
|
while (nLength > 0) |
|
{ |
|
// Get the next line |
|
char szLine [256]; |
|
char *pszLine = szLine; |
|
while (nLength > 0 && *pFileCur != '\n') |
|
{ |
|
char c = *pFileCur++; |
|
if (c > ' ' && c < 127) *pszLine++ = c; |
|
nLength--; |
|
} |
|
|
|
// Remove the LF |
|
if (nLength > 0) |
|
{ |
|
nLength--; |
|
pFileCur++; |
|
} |
|
|
|
// Terminate the line |
|
*pszLine++ = 0; |
|
|
|
// If there is anything in the line, add to map list |
|
if (szLine[0] && IS_MAP_VALID(szLine)) |
|
lstMaps.push_back(szLine); |
|
} |
|
|
|
// Free the file |
|
FREE_FILE(pFileList); |
|
} |
|
|
|
//Find the next map. Ain't there a find function in stl? weird.. |
|
AgMapList::iterator itrMaps = lstMaps.begin(); |
|
for ( ;itrMaps != lstMaps.end() && *itrMaps != g_sNextMap; ++itrMaps) |
|
{ |
|
} |
|
|
|
if (itrMaps == lstMaps.end()) |
|
{ |
|
if (0 == lstMaps.size()) |
|
{ |
|
//No maps in list. Set the current. |
|
g_sNextMap = STRING(gpGlobals->mapname); |
|
} |
|
else |
|
{ |
|
//Map aint in list. Do default to first map. |
|
g_sNextMap = *lstMaps.begin(); |
|
} |
|
} |
|
else |
|
{ |
|
++itrMaps; |
|
if (itrMaps == lstMaps.end()) |
|
{ |
|
//End of list, use first map in list. |
|
g_sNextMap = *lstMaps.begin(); |
|
} |
|
else |
|
{ |
|
//Set next map. |
|
g_sNextMap = *itrMaps; |
|
} |
|
|
|
} |
|
|
|
//Still empty? Should not be so... - this is VERY defenisive programming :) |
|
if (0 == g_sNextMap.size()) |
|
g_sNextMap = STRING(gpGlobals->mapname); |
|
|
|
//No need to calc more. |
|
m_bCalcNextMap = false; |
|
lstMaps.clear(); |
|
} |
|
*/ |
|
|
|
|
|
#define MAX_RULE_BUFFER 1024 |
|
|
|
typedef struct mapcycle_item_s |
|
{ |
|
struct mapcycle_item_s *next; |
|
|
|
char mapname[ 32 ]; |
|
int minplayers, maxplayers; |
|
char rulebuffer[ MAX_RULE_BUFFER ]; |
|
} mapcycle_item_t; |
|
|
|
typedef struct mapcycle_s |
|
{ |
|
struct mapcycle_item_s *items; |
|
struct mapcycle_item_s *next_item; |
|
} mapcycle_t; |
|
|
|
/* |
|
============== |
|
DestroyMapCycle |
|
|
|
Clean up memory used by mapcycle when switching it |
|
============== |
|
*/ |
|
void AgDestroyMapCycle( mapcycle_t *cycle ) |
|
{ |
|
mapcycle_item_t *p, *n, *start; |
|
p = cycle->items; |
|
if ( p ) |
|
{ |
|
start = p; |
|
p = p->next; |
|
while ( p != start ) |
|
{ |
|
n = p->next; |
|
delete p; |
|
p = n; |
|
} |
|
|
|
delete cycle->items; |
|
} |
|
cycle->items = NULL; |
|
cycle->next_item = NULL; |
|
} |
|
|
|
static char com_token[ 1500 ]; |
|
|
|
/* |
|
============== |
|
COM_Parse |
|
|
|
Parse a token out of a string |
|
============== |
|
*/ |
|
char *AgCOM_Parse (char *data) |
|
{ |
|
int c; |
|
int len; |
|
|
|
len = 0; |
|
com_token[0] = 0; |
|
|
|
if (!data) |
|
return NULL; |
|
|
|
// skip whitespace |
|
skipwhite: |
|
while ( (c = *data) <= ' ') |
|
{ |
|
if (c == 0) |
|
return NULL; // end of file; |
|
data++; |
|
} |
|
|
|
// skip // comments |
|
if (c=='/' && data[1] == '/') |
|
{ |
|
while (*data && *data != '\n') |
|
data++; |
|
goto skipwhite; |
|
} |
|
|
|
|
|
// handle quoted strings specially |
|
if (c == '\"') |
|
{ |
|
data++; |
|
while (1) |
|
{ |
|
c = *data++; |
|
if (c=='\"' || !c) |
|
{ |
|
com_token[len] = 0; |
|
return data; |
|
} |
|
com_token[len] = c; |
|
len++; |
|
} |
|
} |
|
|
|
// parse single characters |
|
if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c == ',' ) |
|
{ |
|
com_token[len] = c; |
|
len++; |
|
com_token[len] = 0; |
|
return data+1; |
|
} |
|
|
|
// parse a regular word |
|
do |
|
{ |
|
com_token[len] = c; |
|
data++; |
|
len++; |
|
c = *data; |
|
if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c == ',' ) |
|
break; |
|
} while (c>32); |
|
|
|
com_token[len] = 0; |
|
return data; |
|
} |
|
|
|
/* |
|
============== |
|
COM_TokenWaiting |
|
|
|
Returns 1 if additional data is waiting to be processed on this line |
|
============== |
|
*/ |
|
int AgCOM_TokenWaiting( char *buffer ) |
|
{ |
|
char *p; |
|
|
|
p = buffer; |
|
while ( *p && *p!='\n') |
|
{ |
|
if ( !isspace( *p ) || isalnum( *p ) ) |
|
return 1; |
|
|
|
p++; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
|
|
|
|
/* |
|
============== |
|
ReloadMapCycleFile |
|
|
|
|
|
Parses mapcycle.txt file into mapcycle_t structure |
|
============== |
|
*/ |
|
int AgReloadMapCycleFile( char *filename, mapcycle_t *cycle ) |
|
{ |
|
char szBuffer[ MAX_RULE_BUFFER ]; |
|
char szMap[ 32 ]; |
|
int length; |
|
char *pFileList; |
|
char *aFileList = pFileList = (char*)LOAD_FILE_FOR_ME( filename, &length ); |
|
int hasbuffer; |
|
mapcycle_item_s *item, *newlist = NULL, *next; |
|
|
|
if ( pFileList && length ) |
|
{ |
|
// the first map name in the file becomes the default |
|
while ( 1 ) |
|
{ |
|
hasbuffer = 0; |
|
memset( szBuffer, 0, MAX_RULE_BUFFER ); |
|
|
|
pFileList = AgCOM_Parse( pFileList ); |
|
if ( strlen( com_token ) <= 0 ) |
|
break; |
|
|
|
strcpy( szMap, com_token ); |
|
|
|
// Any more tokens on this line? |
|
if ( AgCOM_TokenWaiting( pFileList ) ) |
|
{ |
|
pFileList = AgCOM_Parse( pFileList ); |
|
if ( strlen( com_token ) > 0 ) |
|
{ |
|
hasbuffer = 1; |
|
strcpy( szBuffer, com_token ); |
|
} |
|
} |
|
|
|
// Check map |
|
if ( IS_MAP_VALID( szMap ) ) |
|
{ |
|
// Create entry |
|
char *s; |
|
|
|
item = new mapcycle_item_s; |
|
|
|
strcpy( item->mapname, szMap ); |
|
|
|
item->minplayers = 0; |
|
item->maxplayers = 0; |
|
|
|
memset( item->rulebuffer, 0, MAX_RULE_BUFFER ); |
|
|
|
if ( hasbuffer ) |
|
{ |
|
s = g_engfuncs.pfnInfoKeyValue( szBuffer, "minplayers" ); |
|
if ( s && s[0] ) |
|
{ |
|
item->minplayers = atoi( s ); |
|
item->minplayers = max( item->minplayers, 0 ); |
|
item->minplayers = min( item->minplayers, gpGlobals->maxClients ); |
|
} |
|
s = g_engfuncs.pfnInfoKeyValue( szBuffer, "maxplayers" ); |
|
if ( s && s[0] ) |
|
{ |
|
item->maxplayers = atoi( s ); |
|
item->maxplayers = max( item->maxplayers, 0 ); |
|
item->maxplayers = min( item->maxplayers, gpGlobals->maxClients ); |
|
} |
|
|
|
// Remove keys |
|
// |
|
g_engfuncs.pfnInfo_RemoveKey( szBuffer, "minplayers" ); |
|
g_engfuncs.pfnInfo_RemoveKey( szBuffer, "maxplayers" ); |
|
|
|
strcpy( item->rulebuffer, szBuffer ); |
|
} |
|
|
|
item->next = cycle->items; |
|
cycle->items = item; |
|
} |
|
else |
|
{ |
|
ALERT( at_console, "Skipping %s from mapcycle, not a valid map\n", szMap ); |
|
} |
|
|
|
} |
|
|
|
FREE_FILE( aFileList ); |
|
} |
|
|
|
// Fixup circular list pointer |
|
item = cycle->items; |
|
|
|
// Reverse it to get original order |
|
while ( item ) |
|
{ |
|
next = item->next; |
|
item->next = newlist; |
|
newlist = item; |
|
item = next; |
|
} |
|
cycle->items = newlist; |
|
item = cycle->items; |
|
|
|
// Didn't parse anything |
|
if ( !item ) |
|
{ |
|
return 0; |
|
} |
|
|
|
while ( item->next ) |
|
{ |
|
item = item->next; |
|
} |
|
item->next = cycle->items; |
|
|
|
cycle->next_item = item->next; |
|
|
|
return 1; |
|
} |
|
|
|
/* |
|
============== |
|
CountPlayers |
|
|
|
Determine the current # of active players on the server for map cycling logic |
|
============== |
|
*/ |
|
int AgCountPlayers( void ) |
|
{ |
|
int num = 0; |
|
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ ) |
|
{ |
|
CBaseEntity *pEnt = UTIL_PlayerByIndex( i ); |
|
|
|
if ( pEnt ) |
|
{ |
|
num = num + 1; |
|
} |
|
} |
|
|
|
return num; |
|
} |
|
|
|
/* |
|
============== |
|
ExtractCommandString |
|
|
|
Parse commands/key value pairs to issue right after map xxx command is issued on server |
|
level transition |
|
============== |
|
*/ |
|
void AgExtractCommandString( char *s, char *szCommand ) |
|
{ |
|
// Now make rules happen |
|
char pkey[512]; |
|
char value[512]; // use two buffers so compares |
|
// work without stomping on each other |
|
char *o; |
|
|
|
if ( *s == '\\' ) |
|
s++; |
|
|
|
while (1) |
|
{ |
|
o = pkey; |
|
while ( *s != '\\' ) |
|
{ |
|
if ( !*s ) |
|
return; |
|
*o++ = *s++; |
|
} |
|
*o = 0; |
|
s++; |
|
|
|
o = value; |
|
|
|
while (*s != '\\' && *s) |
|
{ |
|
if (!*s) |
|
return; |
|
*o++ = *s++; |
|
} |
|
*o = 0; |
|
|
|
strcat( szCommand, pkey ); |
|
if ( strlen( value ) > 0 ) |
|
{ |
|
strcat( szCommand, " " ); |
|
strcat( szCommand, value ); |
|
} |
|
strcat( szCommand, "\n" ); |
|
|
|
if (!*s) |
|
return; |
|
s++; |
|
} |
|
} |
|
|
|
/* |
|
============== |
|
ChangeLevel |
|
|
|
Server is changing to a new level, check mapcycle.txt for map name and setup info |
|
============== |
|
*/ |
|
void AgSettings::CalcNextMap() |
|
{ |
|
static char szPreviousMapCycleFile[ 256 ]; |
|
static mapcycle_t mapcycle; |
|
|
|
char szNextMap[32]; |
|
char szFirstMapInList[32]; |
|
char szCommands[ 1500 ]; |
|
char szRules[ 1500 ]; |
|
int minplayers = 0, maxplayers = 0; |
|
strcpy( szFirstMapInList, "boot_camp" ); // the absolute default level is hldm1 |
|
|
|
int curplayers; |
|
BOOL do_cycle = TRUE; |
|
|
|
// find the map to change to |
|
char *mapcfile = (char*)CVAR_GET_STRING( "mapcyclefile" ); |
|
ASSERT( mapcfile != NULL ); |
|
|
|
szCommands[ 0 ] = '\0'; |
|
szRules[ 0 ] = '\0'; |
|
|
|
curplayers = AgCountPlayers(); |
|
|
|
// Has the map cycle filename changed? |
|
if ( stricmp( mapcfile, szPreviousMapCycleFile ) ) |
|
{ |
|
strcpy( szPreviousMapCycleFile, mapcfile ); |
|
|
|
AgDestroyMapCycle( &mapcycle ); |
|
|
|
if ( !AgReloadMapCycleFile( mapcfile, &mapcycle ) || ( !mapcycle.items ) ) |
|
{ |
|
ALERT( at_console, "Unable to load map cycle file %s\n", mapcfile ); |
|
do_cycle = FALSE; |
|
} |
|
} |
|
|
|
if ( do_cycle && mapcycle.items ) |
|
{ |
|
BOOL keeplooking = FALSE; |
|
BOOL found = FALSE; |
|
mapcycle_item_s *item; |
|
|
|
// Assume current map |
|
strcpy( szNextMap, STRING(gpGlobals->mapname) ); |
|
strcpy( szFirstMapInList, STRING(gpGlobals->mapname) ); |
|
|
|
// Traverse list |
|
for ( item = mapcycle.next_item; item->next != mapcycle.next_item; item = item->next ) |
|
{ |
|
keeplooking = FALSE; |
|
|
|
ASSERT( item != NULL ); |
|
|
|
if ( item->minplayers != 0 ) |
|
{ |
|
if ( curplayers >= item->minplayers ) |
|
{ |
|
found = TRUE; |
|
minplayers = item->minplayers; |
|
} |
|
else |
|
{ |
|
keeplooking = TRUE; |
|
} |
|
} |
|
|
|
if ( item->maxplayers != 0 ) |
|
{ |
|
if ( curplayers <= item->maxplayers ) |
|
{ |
|
found = TRUE; |
|
maxplayers = item->maxplayers; |
|
} |
|
else |
|
{ |
|
keeplooking = TRUE; |
|
} |
|
} |
|
|
|
if ( keeplooking ) |
|
continue; |
|
|
|
found = TRUE; |
|
break; |
|
} |
|
|
|
if ( !found ) |
|
{ |
|
item = mapcycle.next_item; |
|
} |
|
|
|
// Increment next item pointer |
|
mapcycle.next_item = item->next; |
|
|
|
// Perform logic on current item |
|
strcpy( szNextMap, item->mapname ); |
|
|
|
AgExtractCommandString( item->rulebuffer, szCommands ); |
|
strcpy( szRules, item->rulebuffer ); |
|
} |
|
|
|
if ( !IS_MAP_VALID(szNextMap) ) |
|
{ |
|
strcpy( szNextMap, szFirstMapInList ); |
|
} |
|
|
|
ALERT( at_console, "CHANGE LEVEL: %s\n", szNextMap ); |
|
if ( minplayers || maxplayers ) |
|
{ |
|
ALERT( at_console, "PLAYER COUNT: min %i max %i current %i\n", minplayers, maxplayers, curplayers ); |
|
} |
|
if ( strlen( szRules ) > 0 ) |
|
{ |
|
ALERT( at_console, "RULES: %s\n", szRules ); |
|
} |
|
|
|
g_sNextMap = szNextMap; |
|
g_sNextRules = szCommands; |
|
//No need to calc more. |
|
m_bCalcNextMap = false; |
|
} |
|
|
|
|
|
//-- Martin Webrant
|
|
|