Portable Half-Life SDK. GoldSource and Xash3D. Crossplatform.
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.

754 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