/*
   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"
#include "g_gametype_ca.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_operator_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;

cvar_t *g_maplist;
cvar_t *g_maprotation;

cvar_t *g_floodprotection_messages;
cvar_t *g_floodprotection_team;
cvar_t *g_floodprotection_seconds;
cvar_t *g_floodprotection_penalty;

//MBotGame [start]
cvar_t *bot_showpath;
cvar_t *bot_showcombat;
cvar_t *bot_showsrgoal;
cvar_t *bot_showlrgoal;
cvar_t *bot_debugmonster;
cvar_t *bot_dummy;
//[end]

cvar_t *g_projectile_touch_owner;
cvar_t *g_projectile_prestep;
cvar_t *g_numbots;
cvar_t *g_maxtimeouts;
cvar_t *g_antilag;
cvar_t *g_antilag_maxtimedelta;
cvar_t *g_antilag_timenudge;
cvar_t *g_autorecord;
cvar_t *g_autorecord_maxdemos;

cvar_t *g_self_knockback;

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_ammo_respawn;
cvar_t *g_weapon_respawn;
cvar_t *g_health_respawn;
cvar_t *g_armor_respawn;

cvar_t *g_ctf_timer;
#ifdef WSW_CTF_CVARS    // KoFFiE: cvars for the CTF capture and unlock timers
cvar_t *g_ctf_unlock_time;
cvar_t *g_ctf_capture_time;
cvar_t *g_ctf_freeze_time;
#endif
cvar_t *g_itdm_capture_time;
cvar_t *g_itdm_points_time;
cvar_t *g_instagib;
cvar_t *g_instajump;

cvar_t *g_ca_health;
cvar_t *g_ca_armor;
cvar_t *g_ca_weapons;
cvar_t *g_ca_weak_ammo;
cvar_t *g_ca_strong_ammo;
cvar_t *g_ca_roundlimit;
cvar_t *g_ca_allow_selfdamage;
cvar_t *g_ca_allow_teamdamage;
cvar_t *g_ca_competitionmode;
#ifndef WSW_RELEASE
cvar_t *g_ca_classmode;
#endif

cvar_t *g_disable_vote_gametype;

cvar_t *g_uploads_demos;

cvar_t *g_allow_spectator_voting;

static char *G_SelectNextMapName( void );

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

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

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

	va_start( argptr, format );
	Q_vsnprintfz( msg, sizeof( msg ), format, argptr );
	va_end( argptr );

	trap_Error( msg );
}

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

	va_start( argptr, format );
	Q_vsnprintfz( msg, sizeof( msg ), format, argptr );
	va_end( argptr );

	trap_Print( msg );
}

//============
//G_GS_Malloc - Used only for gameshared linking
//============
static void *G_GS_Malloc( size_t size )
{
	return G_Malloc( size );
}

//============
//G_GS_Free - Used only for gameshared linking
//============
static void G_GS_Free( void *data )
{
	G_Free( data );
}

//============
//G_GS_Trace - Used only for gameshared linking
//============
static void G_GS_Trace( trace_t *tr, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int ignore, int contentmask )
{
	edict_t *passent = NULL;
	if( ignore >= 0 && ignore < MAX_EDICTS )
		passent = &game.edicts[ignore];

	G_Trace( tr, start, mins, maxs, end, passent, contentmask );
}

#ifndef WSW_RELEASE
static int G_GS_FS_FOpenFile( const char *filename, int *filenum, int mode )
{
	return trap_FS_FOpenFile( filename, filenum, mode );
}

static int G_GS_FS_Read( void *buffer, size_t len, int file )
{
	return trap_FS_Read( buffer, len, file );
}

static int G_GS_FS_Write( const void *buffer, size_t len, int file )
{
	return trap_FS_Write( buffer, len, file );
}

static void G_GS_FS_FCloseFile( int file )
{
	trap_FS_FCloseFile( file );
}
#endif

//============
//G_InitGameShared
//give gameshared access to some utilities
//============
static void G_InitGameShared( void )
{
	gs_module = GS_MODULE_GAME;
	GS_PredictedEvent = G_PredictedEvent;
	GS_Error = G_Error;
	GS_Printf = G_Printf;
	GS_Malloc = G_GS_Malloc;
	GS_Free = G_GS_Free;
	GS_Trace = G_GS_Trace;
	GS_PointContents = G_PointContents;
	GS_PMoveTouchTriggers = G_PMoveTouchTriggers;
#ifndef WSW_RELEASE
	GS_FS_FOpenFile = G_GS_FS_FOpenFile;
	GS_FS_Read = G_GS_FS_Read;
	GS_FS_Write = G_GS_FS_Write;
	GS_FS_FCloseFile = G_GS_FS_FCloseFile;
#endif
}

//============
//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, int protocol )
{
	cvar_t *g_maxentities;
	G_Printf( "==== G_Init ====\n" );

	srand( seed );

	G_InitGameShared();

	SV_ReadIPList ();

	game.snapFrameTime = framemsec;
	game.frametime = game.snapFrameTime;
	game.protocol = protocol;

	g_maxvelocity = trap_Cvar_Get( "g_maxvelocity", "16000", 0 );
	if( g_maxvelocity->value < 20 )
	{
		trap_Cvar_SetValue( "g_maxvelocity", 20 );
	}
#ifdef MOREGRAVITY
	g_gravity = trap_Cvar_Get( "g_gravity", va( "%i", NEWGRAVITY ), 0 );
#else
	g_gravity = trap_Cvar_Get( "g_gravity", "800", 0 );
#endif
	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 );

	// hack in CVAR_SERVERINFO flag
	trap_Cvar_Get( "gamename", trap_Cvar_String( "gamename" ), CVAR_SERVERINFO );
	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 );
	password->modified = qtrue; // force an update of g_needpass in G_UpdateServerInfo
	g_operator_password = trap_Cvar_Get( "g_operator_password", "", CVAR_ARCHIVE );
	filterban = trap_Cvar_Get( "filterban", "1", 0 );

	g_ammo_respawn = trap_Cvar_Get( "g_ammo_respawn", "0", CVAR_ARCHIVE );
	g_weapon_respawn = trap_Cvar_Get( "g_weapon_respawn", "0", CVAR_ARCHIVE );
	g_health_respawn = trap_Cvar_Get( "g_health_respawn", "0", CVAR_ARCHIVE );
	g_armor_respawn = trap_Cvar_Get( "g_armor_respawn", "0", CVAR_ARCHIVE );
	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", "24", CVAR_DEVELOPER );
	g_self_knockback = trap_Cvar_Get( "g_self_knockback", "1.25", 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 );
	g_antilag = trap_Cvar_Get( "g_antilag", "1", CVAR_SERVERINFO|CVAR_ARCHIVE|CVAR_LATCH );
	g_antilag_maxtimedelta = trap_Cvar_Get( "g_antilag_maxtimedelta", "200", CVAR_ARCHIVE );
	g_antilag_maxtimedelta->modified = qtrue;
	g_antilag_timenudge = trap_Cvar_Get( "g_antilag_timenudge", "0", CVAR_ARCHIVE );
	g_antilag_timenudge->modified = qtrue;

	g_allow_spectator_voting = trap_Cvar_Get( "g_allow_spectator_voting", "0", CVAR_ARCHIVE );

	if( dedicated->integer )
	{
		g_autorecord = trap_Cvar_Get( "g_autorecord", "1", CVAR_ARCHIVE );
		g_autorecord_maxdemos = trap_Cvar_Get( "g_autorecord_maxdemos", "20", CVAR_ARCHIVE );
	}
	else
	{
		g_autorecord = trap_Cvar_Get( "g_autorecord", "0", CVAR_ARCHIVE );
		g_autorecord_maxdemos = trap_Cvar_Get( "g_autorecord_maxdemos", "0", CVAR_ARCHIVE );
	}

	// flood control
	g_floodprotection_messages = trap_Cvar_Get( "g_floodprotection_messages", "4", 0 );
	g_floodprotection_messages->modified = qtrue;
	g_floodprotection_team = trap_Cvar_Get( "g_floodprotection_team", "0", 0 );
	g_floodprotection_team->modified = qtrue;
	g_floodprotection_seconds = trap_Cvar_Get( "g_floodprotection_seconds", "4", 0 );
	g_floodprotection_seconds->modified = qtrue;
	g_floodprotection_penalty = trap_Cvar_Get( "g_floodprotection_delay", "10", 0 );
	g_floodprotection_penalty->modified = qtrue;

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

	//game switches
	g_ctf_timer = trap_Cvar_Get( "g_ctf_timer", ( ( WSW_CTF_TIMER_DEFAULT == 0.0 ) ? "0" : "1" ), CVAR_ARCHIVE );
#ifdef WSW_CTF_CVARS    // KoFFiE: cvars for the CTF capture and unlock timers
	g_ctf_unlock_time  = trap_Cvar_Get( "g_ctf_unlock_time", va( "%.2f", WSW_CTF_UNLOCK_TIME_DEFAULT ), CVAR_ARCHIVE );
	g_ctf_capture_time = trap_Cvar_Get( "g_ctf_capture_time", va( "%.2f", WSW_CTF_CAPTURE_TIME_DEFAULT ), CVAR_ARCHIVE );
	g_ctf_freeze_time  = trap_Cvar_Get( "g_ctf_freeze_time", va("%d", WSW_CTF_FREEZE_TIME_DEFAULT), CVAR_ARCHIVE );
#endif
	g_itdm_capture_time = trap_Cvar_Get( "g_itdm_capture_time", "2", CVAR_ARCHIVE );
	g_itdm_points_time = trap_Cvar_Get( "g_itdm_points_time", "5", CVAR_ARCHIVE );

	g_instagib = trap_Cvar_Get( "g_instagib", "0", CVAR_SERVERINFO|CVAR_ARCHIVE|CVAR_LATCH );
	g_instajump = trap_Cvar_Get( "g_instajump", "1", CVAR_ARCHIVE );

	// CA
	g_ca_health = trap_Cvar_Get( "g_ca_health", "100", CVAR_ARCHIVE );
	g_ca_armor = trap_Cvar_Get( "g_ca_armor", "150", CVAR_ARCHIVE );
	g_ca_weapons = trap_Cvar_Get( "g_ca_weapons", va( "%i %i %i %i", CA_WEAPONFLAG_ALL, ( CA_WEAPONFLAG_WEAK_ALL | CA_WEAPONFLAG_STRONG_GB | CA_WEAPONFLAG_STRONG_RL | CA_WEAPONFLAG_STRONG_RG ), ( CA_WEAPONFLAG_WEAK_ALL | CA_WEAPONFLAG_STRONG_GB | CA_WEAPONFLAG_STRONG_EB | CA_WEAPONFLAG_STRONG_GL ), ( CA_WEAPONFLAG_WEAK_ALL | CA_WEAPONFLAG_STRONG_GB | CA_WEAPONFLAG_STRONG_LG | CA_WEAPONFLAG_STRONG_PG ) ), CVAR_ARCHIVE );
	g_ca_weak_ammo = trap_Cvar_Get( "g_ca_weak_ammo", "0 15 15 50 75 120 10", CVAR_ARCHIVE );
	g_ca_strong_ammo = trap_Cvar_Get( "g_ca_strong_ammo", "10 15 20 25 75 100 10", CVAR_ARCHIVE );
	g_ca_roundlimit = trap_Cvar_Get( "g_ca_roundlimit", "0", CVAR_ARCHIVE );
	g_ca_allow_selfdamage = trap_Cvar_Get( "g_ca_allow_selfdamage", "0", CVAR_ARCHIVE );
	g_ca_allow_teamdamage = trap_Cvar_Get( "g_ca_allow_teamdamage", "0", CVAR_ARCHIVE );
	g_ca_competitionmode = trap_Cvar_Get( "g_ca_competitionmode", "0", CVAR_ARCHIVE );
#ifndef WSW_RELEASE
	g_ca_classmode = trap_Cvar_Get( "g_ca_classmode", "1", CVAR_ARCHIVE );
#endif

	// 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 );

	// uploads
	g_uploads_demos = trap_Cvar_Get( "g_uploads_demos", "1", CVAR_ARCHIVE );

	// nextmap
	trap_Cvar_ForceSet( "nextmap", "match \"advance\"" );


	// commands
	G_AddCommands();

	// 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_Malloc( game.maxentities * sizeof( game.edicts[0] ) );

	// initialize all clients for this game
	game.maxclients = maxclients;
	game.clients = G_Malloc( 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 )
{
	int i;

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

	SV_WriteIPList ();

	trap_Cvar_ForceSet( "nextmap", va( "map \"%s\"", G_SelectNextMapName() ) );

	BOT_RemoveBot( "all" );

	G_RemoveCommands();

	for( i = 0; i < game.numentities; i++ )
	{
		if( game.edicts[i].r.inuse )
			G_FreeEdict( &game.edicts[i] );
	}

	for( i = 0; i < level.numLocations; i++ )
		G_LevelFree( level.locationNames[i] );

	if( game.map_entities )
		G_LevelFree( game.map_entities );

	if( game.map_parsed_ents )
		G_LevelFree( game.map_parsed_ents );

	G_Free( game.edicts );
	G_Free( game.clients );
}

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

//=================
//G_AllowDownload
//=================
qboolean G_AllowDownload( edict_t *ent, const char *requestname, const char *uploadname )
{
	// allow uploading of demos
	if( g_uploads_demos->integer )
	{
		if( !Q_stricmp( COM_FileExtension( uploadname ), va( ".wd%i", game.protocol ) ) )
		{
			const char *p;

			p = strchr( uploadname, '/' );
			if( p && !Q_strnicmp( p + 1, "demos/server/", strlen( "demos/server/" ) ) )
				return qtrue;
		}
	}

	return qfalse;
}


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

//=================
//CreateTargetChangeLevel
//
//Returns the created target changelevel
//=================
static 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
//=================
static 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
//=================
static 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;
	unsigned int timeLimit;
	const unsigned int wrappingPoint = 0x70000000;

	level.exitNow = qfalse;

	nextmapname = G_SelectNextMapName();
	timeLimit = g_timelimit->integer > 0 ? max( g_timelimit->integer, 60 ) : 60;
	timeLimit *= 60 * 1000;

	// if it's the same map see if we can restart without loading
	if( !level.hardReset && !Q_stricmp( nextmapname, level.mapname ) )
	{
		if( ( (signed)level.time < (signed)( wrappingPoint-timeLimit ) ) && 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 );
	}

	G_SnapClients();

	// 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;
		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;
		}
	}
}

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

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

	va_start( argptr, format );
	Q_vsnprintfz( msg, sizeof( msg ), format, argptr );
	va_end( argptr );

	G_Error( "%s", msg );
}

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

	va_start( argptr, format );
	Q_vsnprintfz( msg, sizeof( msg ), format, argptr );
	va_end( argptr );

	G_Printf( "%s", msg );
}
#endif
