/*
Copyright (C) 1997-2001 Id Software, Inc.

This program 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.

This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include "g_local.h"
#include "g_callvotes.h"
#include "g_gametypes.h"

game_locals_t	game;
level_locals_t	level;
spawn_temp_t	st;
timeout_t		gtimeout;

struct mempool_s *gamepool;
struct mempool_s *levelpool;

int meansOfDeath;

cvar_t	*dmflags;

cvar_t	*password;
cvar_t	*g_select_empty;
cvar_t	*dedicated;
cvar_t	*developer;

cvar_t	*filterban;

cvar_t	*g_maxvelocity;
cvar_t	*g_gravity;

cvar_t	*sv_cheats;
#ifdef BATTLEYE
cvar_t	*sv_battleye;
#endif

cvar_t	*g_maplist;
cvar_t	*g_maprotation;

cvar_t	*flood_msgs;
cvar_t	*flood_persecond;
cvar_t	*flood_waitdelay;

//MBotGame [start]
cvar_t	*bot_showpath;
cvar_t	*bot_showcombat;
cvar_t	*bot_showsrgoal;
cvar_t	*bot_showlrgoal;
cvar_t	*bot_debugmonster;
#ifndef WSW_RELEASE
cvar_t	*bot_dummy;
#endif
//[end]

cvar_t	*g_projectile_touch_owner;
cvar_t	*g_projectile_prestep;
cvar_t	*g_numbots;
cvar_t	*g_maxtimeouts;

cvar_t	*g_respawn_delay_min;
cvar_t	*g_respawn_delay_max;
cvar_t	*g_deadbody_filter;
cvar_t	*g_deadbody_followkiller;
cvar_t	*g_challengers_queue;

cvar_t	*g_tctf;
cvar_t	*g_instagib;


cvar_t	*g_disable_vote_gametype;

//===================================================================

//=================
//G_API
//=================
int G_API (void) {
	return GAME_API_VERSION;
}

//============
//G_Error
//
//Abort the server with a game error
//============
void G_Error ( char *fmt, ... )
{
	char		msg[1024];
	va_list		argptr;

	va_start( argptr, fmt );
	vsnprintf( msg, sizeof(msg), fmt, argptr );
	va_end( argptr );
	msg[sizeof(msg)-1] = 0;

	trap_Error ( msg );
}

//============
//G_Printf
//
//Debug print to server console
//============
void G_Printf ( char *fmt, ... )
{
	char		msg[1024];
	va_list		argptr;

	va_start( argptr, fmt );
	vsnprintf( msg, sizeof(msg), fmt, argptr );
	va_end( argptr );
	msg[sizeof(msg)-1] = 0;

	trap_Print ( msg );
}

//============
//G_Init
//
//This will be called when the dll is first loaded, which
//only happens when a new game is started or a save game is loaded.
//============
void G_Init( unsigned int seed, unsigned int maxclients, unsigned int framemsec )
{
	cvar_t	*g_maxentities;

	G_Printf( "==== G_Init ====\n" );

	srand( seed );

	gamepool = G_MemAllocPool( "Game" );
	levelpool = G_MemAllocPool( "Level" );

	game.framemsec = framemsec;
	game.frametime = game.framemsec * 0.001;
	G_Printf( "Server running at %i pps\n", (int)(1000 / game.framemsec) );

	g_maxvelocity = trap_Cvar_Get( "g_maxvelocity", "10000", 0 );
	g_gravity = trap_Cvar_Get( "g_gravity", "800", 0 );

	developer = trap_Cvar_Get( "developer", "0", 0 );

	// noset vars
	dedicated = trap_Cvar_Get("dedicated", "0", CVAR_NOSET);

	// latched vars
	sv_cheats = trap_Cvar_Get("sv_cheats", "0", CVAR_SERVERINFO|CVAR_LATCH );

#ifdef BATTLEYE
	if( dedicated->integer )
		sv_battleye =			trap_Cvar_Get( "sv_battleye", "1", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_LATCH );
	else
		sv_battleye =			trap_Cvar_Get( "sv_battleye", "0", CVAR_SERVERINFO | CVAR_READONLY );
#endif

	trap_Cvar_Get( "gamename", GAMENAME , CVAR_SERVERINFO | CVAR_LATCH );
	trap_Cvar_Get( "gamedate", __DATE__ , CVAR_SERVERINFO | CVAR_LATCH );

	// change anytime vars
	dmflags = trap_Cvar_Get( "dmflags", va("%i", DF_INSTANT_ITEMS), CVAR_SERVERINFO );

 	password = trap_Cvar_Get( "password", "", CVAR_USERINFO );
	filterban = trap_Cvar_Get( "filterban", "1", 0 );

	// wsw
	g_select_empty = trap_Cvar_Get( "g_select_empty", "0", CVAR_DEVELOPER );
	g_projectile_touch_owner = trap_Cvar_Get( "g_projectile_touch_owner", "0", CVAR_DEVELOPER );
	g_projectile_prestep = trap_Cvar_Get( "g_projectile_prestep", "32", CVAR_DEVELOPER );
	g_respawn_delay_min = trap_Cvar_Get( "g_respawn_delay_min", "600", CVAR_DEVELOPER );
	g_respawn_delay_max = trap_Cvar_Get( "g_respawn_delay_max", "6000", CVAR_DEVELOPER );
	g_numbots = trap_Cvar_Get( "g_numbots", "0", CVAR_ARCHIVE );
	g_deadbody_followkiller = trap_Cvar_Get( "g_deadbody_followkiller", "1", CVAR_DEVELOPER );
	g_deadbody_filter = trap_Cvar_Get( "g_deadbody_filter", "1", CVAR_DEVELOPER );
	g_challengers_queue = trap_Cvar_Get( "g_challengers_queue", "1", CVAR_ARCHIVE );
	g_maxtimeouts = trap_Cvar_Get( "g_maxtimeouts", "2", CVAR_ARCHIVE );

	// flood control
	flood_msgs = trap_Cvar_Get( "flood_msgs", "4", 0 );
	flood_persecond = trap_Cvar_Get( "flood_persecond", "4", 0 );
	flood_waitdelay = trap_Cvar_Get( "flood_waitdelay", "10", 0 );

	// map list
	g_maplist = trap_Cvar_Get( "g_maplist", "", CVAR_ARCHIVE );
	g_maprotation = trap_Cvar_Get( "g_maprotation", "1", CVAR_ARCHIVE );

	//game switches
	g_tctf = trap_Cvar_Get( "g_tctf", "10", CVAR_SERVERINFO|CVAR_ARCHIVE );
	g_instagib = trap_Cvar_Get( "g_instagib", "0", CVAR_SERVERINFO|CVAR_ARCHIVE|CVAR_LATCH );

	// helper cvars to show current status in serverinfo reply
	trap_Cvar_Get( "g_match_time", "", CVAR_SERVERINFO|CVAR_READONLY );
	trap_Cvar_Get( "g_match_score", "", CVAR_SERVERINFO|CVAR_READONLY );
	trap_Cvar_Get( "g_needpass", "", CVAR_SERVERINFO|CVAR_READONLY );
	trap_Cvar_Get( "g_gametypes_available", "", CVAR_SERVERINFO|CVAR_READONLY );

	// define this one here so we can see when it's modified
	g_disable_vote_gametype = trap_Cvar_Get( "g_disable_vote_gametype", "0", CVAR_ARCHIVE );


	// items
	InitItems ();

	// initialize all entities for this game
	g_maxentities = trap_Cvar_Get( "sv_maxentities", "1024", CVAR_LATCH );
	game.maxentities = g_maxentities->integer;
	game.edicts = G_GameMalloc( game.maxentities * sizeof(game.edicts[0]) );
	
	// initialize all clients for this game
	game.maxclients = maxclients;
	game.clients = G_GameMalloc( game.maxclients * sizeof(game.clients[0]) );

	game.numentities = game.maxclients + 1;

	trap_LocateEntities( game.edicts, sizeof(game.edicts[0]), game.numentities, game.maxentities );

	G_Gametype_Init();//newgametypes
	G_CallVotes_Init();
	AI_Init();//MbotGame
}

//=================
//G_Shutdown
//=================
void G_Shutdown( void )
{
	BOT_RemoveBot( "all" );
	G_Printf( "==== G_Shutdown ====\n" );

	G_MemFreePool( &gamepool );
	G_MemFreePool( &levelpool );
}

//======================================================================

//================
//G_SetEntityBits
//
//Set misc bits EF_CORPSE, etc
//================
void G_SetEntityBits( edict_t *ent )
{
	// inverse entity type if it can take damage
	ent->s.takedamage = (ent->takedamage != 0);

	// set EF_CORPSE for client side prediction
	if( ent->r.svflags & SVF_CORPSE )
		ent->s.effects |= EF_CORPSE;
}

//=================
//ClientEndServerFrames
//=================
void ClientEndServerFrames( void )
{
	int		i;
	edict_t	*ent;

	// calc the player views now that all pushing
	// and damage has been added
	for( i = 0; i < game.maxclients; i++ )
	{
		ent = game.edicts + 1 + i;
		if( !ent->r.inuse || !ent->r.client )
			continue;

		ClientEndServerFrame(ent);

		G_SetEntityBits(ent);
	}

	ClientEndServerFrames_UpdateScoreBoardMessages(); //newgametypes
	ClientEndServerFrames_UpdateWeaponListMessages(); //newgametypes
	G_EndServerFrames_UpdateChaseCam();	//splitmodels
}

//=================
//CreateTargetChangeLevel
//
//Returns the created target changelevel
//=================
edict_t *CreateTargetChangeLevel(char *map)
{
	edict_t *ent;

	ent = G_Spawn ();
	ent->classname = "target_changelevel";
	Q_strncpyz (level.nextmap, map, sizeof(level.nextmap));
	ent->map = level.nextmap;
	return ent;
}

//=================
//G_ChooseNextMap
//=================
edict_t *G_ChooseNextMap( void )
{
	edict_t		*ent;
	char *s, *t, *f;
	static const char *seps = " ,\n\r";

	if( *level.forcemap ) {
		return CreateTargetChangeLevel( level.forcemap );
	}

	if( !(*g_maplist->string) || strlen(g_maplist->string) == 0 || g_maprotation->integer == 0 )
	{
		// same map again
		return CreateTargetChangeLevel( level.mapname );
	}
	else if (g_maprotation->integer == 1)
	{
		// next map in list
		s = G_CopyString( g_maplist->string );
		f = NULL;
		t = strtok( s, seps );

		while( t != NULL ) {
			if( !Q_stricmp(t, level.mapname) ) {
				// it's in the list, go to the next one
				t = strtok( NULL, seps );
				if( t == NULL ) { // end of list, go to first one
					if( f == NULL ) // there isn't a first one, same level
						ent = CreateTargetChangeLevel( level.mapname );
					else
						ent = CreateTargetChangeLevel(f);
				} else
					ent = CreateTargetChangeLevel(t);
				G_Free(s);
				return ent;
			}
			if( !f )
				f = t;
			t = strtok( NULL, seps );
		}

		// not in the list, we go for the first one
		ent = CreateTargetChangeLevel(f);
		G_Free(s);
		return ent;
	}
	else
	{
		// random from the list, but not the same
		int count = 0;
		s = G_CopyString( g_maplist->string );

		t = strtok( s, seps );
		while( t != NULL ) {
			if( Q_stricmp(t, level.mapname) )
				count++;
			t = strtok( NULL, seps );
		}

		G_Free(s);
		s = G_CopyString( g_maplist->string );

		if( count < 1 ) {
			// no other maps found, restart
			ent = CreateTargetChangeLevel( level.mapname );
		} else {
			int seed = game.realtime;
			count -= (int)Q_brandom(&seed, 0, count); // this should give random integer from 0 to count-1
			ent = NULL; // shutup compiler warning;

			t = strtok( s, seps );
			while( t != NULL ) {
				if( Q_stricmp(t, level.mapname) ) {
					count--;
					if( count == 0 ) {
						ent = CreateTargetChangeLevel(t);
						break;
					}
				}
				t = strtok( NULL, seps );
			}
		}

		G_Free(s);
		return ent;
	}

#if 0 // wsw : mdr : this never gets called now, but would be usefull for SP
	if( level.nextmap[0] ) // go to a specific map
		return CreateTargetChangeLevel( level.nextmap );
	
	// search for a changelevel
	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
		return CreateTargetChangeLevel( level.mapname );
	}
	return ent;
#endif
}

//=================
//G_SelectNextMapName
//=================
char *G_SelectNextMapName( void )
{
	edict_t *changelevel;

	changelevel = G_ChooseNextMap();
	return changelevel->map;
}

//=============
//G_ExitLevel
//=============
void G_ExitLevel( void )
{
	int		i;
	edict_t	*ent;
	char	command [256];
	char	*nextmapname;
	qboolean	loadmap = qtrue;

	level.exitnow = 0;
	game.autosaved = qfalse;

	nextmapname = G_SelectNextMapName();

	// if it's the same map see if we can restart without loading
	if( !Q_stricmp(nextmapname, level.mapname) ) {
		if( G_Match_RestartLevel() ) {
			loadmap = qfalse;
		}
	}
	
	if( loadmap ) {
		BOT_RemoveBot( "all" ); // MbotGame (Disconnect all bots before changing map)
		Q_snprintfz( command, sizeof(command), "gamemap \"%s\"\n", nextmapname );
		trap_AddCommandString( command );
	}

	ClientEndServerFrames();

	level.changemap = NULL;

	// clear some things before going to next level
	for( i = 0; i < game.maxclients; i++ )
	{
		ent = game.edicts + 1 + i;
		if( !ent->r.inuse )
			continue;
		
		ent->r.client->showscores = qfalse;
		ent->r.client->showinventory = qfalse;
		if( ent->health > ent->max_health )
			ent->health = ent->max_health;

		// some things are only cleared when there's a new map load
		if( loadmap ) {
			ent->r.client->pers.connecting = qtrue; // set all connected players as "reconnecting"
			ent->s.team = TEAM_SPECTATOR;
		}
	}
}

//=============
// G_Edicts_EndServerFrames
// finish entities, add effects based on accumulated info along the server frame
//=============
void G_Edicts_EndServerFrames( void )
{
#define JALFIXME_DAMAGESAVE
	edict_t *ent;
	int		i;
	vec3_t	dir, origin;

	for( i = 0, ent = &game.edicts[0]; i < game.numentities; i++, ent++ )
	{
		if( !ent->r.inuse )
			continue;

		// types which can have accumulated damage effects
		if( (ent->s.type == ET_GENERIC || ent->s.type == ET_PLAYER)
			&& ent->movetype != MOVETYPE_PUSH ) // doors don't bleed
		{
#ifdef JALFIXME_DAMAGESAVE
			// Until we get a proper damage saved effect, we accumulate both into the blood fx
			// so, at least, we don't send 2 entities where we can send one
			ent->frame_damage_taken += ent->frame_damage_saved;
			ent->frame_damage_saved = 0;
#endif
			//spawn accumulated damage
			if( ent->frame_damage_taken ) {
				edict_t *event;
				float		damage = ent->frame_damage_taken;
				if( damage > 120 )
					damage = 120;
				//VectorSubtract( ent->frame_damage_from, ent->s.origin, dir );
				VectorCopy( ent->frame_damage_dir, dir );
				VectorNormalize( dir );
				VectorAdd( ent->s.origin, ent->frame_damage_at, origin );
				event = G_SpawnEvent( EV_BLOOD2, DirToByte(dir), origin );
				event->s.damage = HEALTH_TO_INT(damage);
				event->r.svflags = SVF_NOOLDORIGIN;
				event->s.ownerNum=i; // set owner
			}
			ent->frame_damage_taken = 0;
#ifndef JALFIXME_DAMAGESAVE
			// This should not be a blood effect, but a armor sparks one
			// or maybe we should just not send any damage saved effect
			//----------------------------------------------------------
			//spawn accumulated save
			if( ent->frame_damage_saved ) {
				edict_t *event;
				float	save = ent->frame_damage_saved;
				if( save > 120 )
					save = 120;
				VectorCopy( ent->frame_damage_dir, dir );
				VectorNormalize( dir );
				VectorAdd( ent->s.origin, ent->frame_damage_at, origin );
				event = G_SpawnEvent( EV_BLOOD_SAVED, DirToByte(dir), origin );
				event->s.damage = HEALTH_TO_INT(save);
				event->r.svflags = SVF_NOOLDORIGIN;
			}
			ent->frame_damage_saved = 0;
#endif
		}

		G_SetEntityBits( ent );
	}
}

//================
//G_Timeout_Reset
//================
void G_Timeout_Reset( void )
{
	int i;

	gtimeout.active = qfalse;
	gtimeout.time = 0;
	gtimeout.endtime = 0;
	gtimeout.caller = 0;
	for( i = 0; i < MAX_CLIENTS; i++ )
		gtimeout.used[i] = 0;
}

//================
//G_Timeout_Update
//
//Updates the timeout struct and informs clients about the status of the pause
//================
static void G_Timeout_Update( unsigned int msec )
{
	static int timeout_printtime = 0;
	static int timeout_last_endtime = 0;
	static int countdown_set = 1;

	if( !gtimeout.active )
		return;

	if( timeout_last_endtime != gtimeout.endtime ) // force print when endtime is changed
	{
		timeout_printtime = 0;
		timeout_last_endtime = gtimeout.endtime;
	}

	gtimeout.time += msec;
	if( gtimeout.endtime && gtimeout.time >= gtimeout.endtime )
	{
		gtimeout.time = 0;
		gtimeout.caller = -1;
		gtimeout.active = qfalse;

		timeout_printtime = 0;
		timeout_last_endtime = -1;

		G_AnnouncerSound( NULL, trap_SoundIndex(va(S_ANNOUNCER_TIMEOUT_MATCH_RESUMED_1_to_2, (rand()&1)+1)),
			GS_MAX_TEAMS, qtrue );
		G_CenterPrintMsg( NULL, "Match resumed" );
		G_PrintMsg( NULL, "Match resumed\n" );
	}
	else if( timeout_printtime == 0 || gtimeout.time - timeout_printtime >= 1000 )
	{
		if( gtimeout.endtime )
		{
			int seconds_left = (int)((gtimeout.endtime - gtimeout.time) / 1000.0 + 0.5);

			if( seconds_left == (TIMEIN_TIME * 2) / 1000 ) {
				G_AnnouncerSound( NULL, trap_SoundIndex(va(S_ANNOUNCER_COUNTDOWN_READY_1_to_2, (rand()&1)+1)),
					GS_MAX_TEAMS, qfalse );
				countdown_set = (rand()&1)+1;
			} else if( seconds_left >= 1 && seconds_left <= 3 ) {
				G_AnnouncerSound( NULL, trap_SoundIndex(va(S_ANNOUNCER_COUNTDOWN_COUNT_1_to_3_SET_1_to_2, seconds_left,
					countdown_set)), GS_MAX_TEAMS, qfalse );
			}

			if( seconds_left > 1 )
				G_CenterPrintMsg( NULL, "Match will resume in %i seconds", seconds_left );
			else
				G_CenterPrintMsg( NULL, "Match will resume in 1 second" );
		}
		else
		{
			G_CenterPrintMsg( NULL, "Match paused" );
		}

		timeout_printtime = gtimeout.time;
	}
}

void G_UpdateServerInfo( void )
{
	// g_match_time
	if( match.state <= MATCH_STATE_WARMUP )
	{
		trap_Cvar_ForceSet( "g_match_time", "Warmup" );
	}
	else if( match.state == MATCH_STATE_COUNTDOWN )
	{
		trap_Cvar_ForceSet( "g_match_time", "Countdown" );
	}
	else if( match.state == MATCH_STATE_PLAYTIME )
	{
		// partly from G_GetMatchState
		char	extra[MAX_INFO_STRING];
		int		clocktime, timelimit, mins, secs;

		if( match.endtime )
			timelimit = (match.endtime - match.starttime) / 60;
		else
			timelimit = 0;

		clocktime = level.time - match.starttime;

		if( clocktime <= 0 ) {
			mins = 0;
			secs = 0;
		} else {
			mins = clocktime / 60;
			secs = clocktime - mins * 60;
		}

		extra[0] = 0;
		if( match.extendedTime > 0 ) {
			if( timelimit )
				Q_strncatz( extra, " overtime", sizeof(extra) );
			else
				Q_strncatz( extra, " suddendeath", sizeof(extra) );
		}
		if( gtimeout.active )
			Q_strncatz( extra, " (in timeout)", sizeof(extra) );

		if( timelimit )
			trap_Cvar_ForceSet( "g_match_time", va("%02i:%02i / %02i:00%s", mins, secs, timelimit, extra) );
		else
			trap_Cvar_ForceSet( "g_match_time", va("%02i:%02i%s", mins, secs, extra) );
	}
	else
	{
		trap_Cvar_ForceSet( "g_match_time", "Finished" );
	}

	// g_match_score
	if( match.state >= MATCH_STATE_PLAYTIME && GS_Gametype_IsTeamBased( game.gametype ) )
	{
		char	score[MAX_INFO_STRING];

		score[0] = 0;
		Q_strncatz( score, va("Red: %i", teamlist[TEAM_RED].teamscore), sizeof(score) );
		if( TEAM_BLUE < TEAM_RED + g_maxteams->integer )
			Q_strncatz( score, va(" Blue: %i", teamlist[TEAM_BLUE].teamscore), sizeof(score) );
		if( TEAM_GREEN < TEAM_RED + g_maxteams->integer )
			Q_strncatz( score, va(" Green: %i", teamlist[TEAM_GREEN].teamscore), sizeof(score) );
		if( TEAM_YELLOW < TEAM_RED + g_maxteams->integer )
			Q_strncatz( score, va(" Yellow: %i", teamlist[TEAM_YELLOW].teamscore), sizeof(score) );

		trap_Cvar_ForceSet( "g_match_score", score );
	}
	else
	{
		trap_Cvar_ForceSet( "g_match_score", "" );
	}

	// g_needpass
	if( password->modified )
	{
		if( password->string && strlen(password->string) ) {
			trap_Cvar_ForceSet( "g_needpass", "1" );
		} else {
			trap_Cvar_ForceSet( "g_needpass", "0" );
		}
		password->modified = qfalse;
	}

	// g_gametypes_available
	if( g_votable_gametypes->modified || g_disable_vote_gametype->modified )
	{
		if( g_disable_vote_gametype->integer || !g_votable_gametypes->string || !strlen(g_votable_gametypes->string) ) {
			trap_Cvar_ForceSet( "g_gametypes_available", "" );
		} else {
			int type;
			char votable[MAX_INFO_VALUE];

			votable[0] = 0;
			for( type = GAMETYPE_DM; type < GAMETYPE_TOTAL; type++ ) {
				if( G_Gametype_IsVotable(type) ) {
					Q_strncatz( votable, GS_Gametype_ShortName(type), sizeof(votable) );
					Q_strncatz( votable, " ", sizeof(votable) );
				}
			}
			votable[strlen(votable)-2] = 0; // remove the last space

			trap_Cvar_ForceSet( "g_gametypes_available", votable );
		}

		g_votable_gametypes->modified = qfalse;
		g_disable_vote_gametype->modified = qfalse;
	}
}

void G_CheckCvars( void )
{
	if( g_teams_lock->modified ) {
		// if we are inside a match, update the teams state
		if( match.state > MATCH_STATE_WARMUP && match.state <= MATCH_STATE_PLAYTIME ) {
			int	team;

			if( g_teams_lock->integer ) {
				for( team = 0; team < GS_MAX_TEAMS; team++ )
					G_Teams_LockTeam( team );
				G_PrintMsg( NULL, "Teams locked.\n");
			} else {
				for( team = 0; team < GS_MAX_TEAMS; team++ )
					G_Teams_UnLockTeam( team );
				G_PrintMsg( NULL, "Teams unlocked.\n");
			}
		}
		g_teams_lock->modified = qfalse;
	}

	if( g_warmup_enabled->modified ) {
		// if we are inside warmup period, finish it
		if( !g_warmup_enabled->integer && (match.state == MATCH_STATE_WARMUP || match.state == MATCH_STATE_COUNTDOWN) )
			G_Match_SetUpNextState();
		g_warmup_enabled->modified = qfalse;
	}

	if( g_warmup_timelimit->modified ) {
		// if we are inside timelimit period, update the endtime
		if( match.state == MATCH_STATE_WARMUP ) {
			if( g_warmup_timelimit->integer )
				match.endtime = match.starttime + fabs(60 * g_warmup_timelimit->integer);
			else
				match.endtime = 0;
		}
		g_warmup_timelimit->modified = qfalse;
	}

	if( g_timelimit->modified ) {
		// if we are inside timelimit period, update the endtime
		if( match.state == MATCH_STATE_PLAYTIME && match.extendedTime == 0 && game.gametype != GAMETYPE_RACE ) {
			if( g_timelimit->value  )
				match.endtime = match.starttime + fabs(60 * g_timelimit->value);
			else
				match.endtime = 0;
		}
		g_timelimit->modified = qfalse;
	}

	if( g_match_extendedtime->modified ) {
		// if we are inside extended_time period, update the endtime
		if( match.state == MATCH_STATE_PLAYTIME && match.extendedTime > 0 ) {
			if( g_match_extendedtime->integer ) {
				// wsw: Medar: this is little bit stupid
				float oldendtime = match.endtime;
				match.endtime = match.starttime + fabs(60 * g_timelimit->value);
				while( match.endtime < oldendtime || match.endtime < level.time )
					match.endtime += fabs(g_match_extendedtime->integer * 60);
			} else {
				match.endtime = 0;
				G_Match_SetUpNextState();
			}
		}
		g_match_extendedtime->modified = qfalse;
	}
}


//================
//G_RunFrame
//
//Advances the world
//================
void G_RunFrame( unsigned int msec )
{
	int		i;
	edict_t	*ent;

	G_Timeout_Update( msec );

	if( !gtimeout.active ) {
		level.framenum++;
		level.maptime += game.framemsec;
	}
	game.framemsec = msec;
	game.frametime = (float)msec * 0.001;
	level.timemsec = level.framenum * game.framemsec;
	level.time = (float)level.timemsec * 0.001;
	
	game.realtime = trap_Milliseconds(); // level.time etc. might not be real time

	//
	// clear all events to free entity spots
	//
	ent = &game.edicts[0];
	for( i = 0; i < game.numentities; i++, ent++ )
	{
		if( !ent->r.inuse )
			continue;

		// free if no events
		if( !ent->s.events[0] ) {
			ent->numEvents = 0;
			ent->eventPriority[0] = ent->eventPriority[1] = qfalse;
			if( ent->freeAfterEvent )
				G_FreeEdict(ent);
		}

		// wsw : jal : clear player state events 
		if( ent->r.client )
			ent->r.client->ps.event = PSEV_NONE;
	}

	// exit intermissions

	if( level.exitnow )
	{
		G_ExitLevel();
		return;
	}

	//
	// treat each object in turn
	// even the world gets a chance to think
	//
	ent = &game.edicts[0];
	for( i = 0; i < game.numentities; i++, ent++ )
	{
		if( !ent->r.inuse )
			continue;
		if( ent->freeAfterEvent )
			continue;		// events do not think

		ent->s.effects &= ~EF_CORPSE;	// remove EF_CORPSE bit

		level.current_entity = ent;

		if( !gtimeout.active )
		{
			if( !(ent->r.svflags & SVF_FORCEOLDORIGIN) )
				VectorCopy( ent->s.origin, ent->s.old_origin );

			// if the ground entity moved, make sure we are still on it
			if( (ent->groundentity) && (ent->groundentity->r.linkcount != ent->groundentity_linkcount) )
			{
				ent->groundentity = NULL;
				if( !(ent->flags & (FL_SWIM|FL_FLY)) && (ent->r.svflags & SVF_MONSTER) )
					G_CheckGround( ent );
			}
		}

		if( i > 0 && i <= game.maxclients )
		{
			ClientBeginServerFrame(ent);
			//MbotGame[start]
			if( !ent->ai.type )
			//[end]
				continue;
		}

		if( !gtimeout.active )
			G_RunEntity( ent );
	}

	G_GametypeCheckRules();//newgametypes

	// finish entities, add effects based on accumulated info along the frame
	G_Edicts_EndServerFrames();

	// build the playerstate_t structures for all players
	ClientEndServerFrames();

	//MbotGame[start]
	AITools_Frame();	//give think time to AI debug tools
	//[end]

	G_CheckCvars();
	G_UpdateServerInfo();
}

//======================================================================

#ifndef GAME_HARD_LINKED
// this is only here so the functions in q_shared.c and q_math.c can link
void Sys_Error(char *error, ...)
{
	va_list		argptr;
	char		text[1024];

	va_start( argptr, error );
	vsnprintf( text, sizeof(text), error, argptr );
	va_end( argptr );
	text[sizeof(text)-1] = 0;

	G_Error( "%s", text );
}

void Com_Printf( char *msg, ... )
{
	va_list		argptr;
	char		text[1024];

	va_start( argptr, msg );
	vsnprintf( text, sizeof(text), msg, argptr );
	va_end( argptr );
	text[sizeof(text)-1] = 0;

	G_Printf( "%s", text );
}

#endif

