/*
   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_gametypes.h"

// number of milliseconds to capture an area
#define CAPTURE_AREA_TIME ( 1000 * g_itdm_capture_time->value )
// time before players get points for holding area (milliseconds)
#define CAPTURE_AREA_POINTS_TIME ( 1000 * g_itdm_points_time->value )
// number of milliseconds before a touch is discarded
#define CAPTURE_PLAYER_TIMEOUT 150

typedef struct
{
	int picked_powerups[GS_MAX_TEAMS];

	// to add:
	//int	helpBonus;
	//int	giveBonus;
} tdm_stats_t;

tdm_stats_t tdmgame;

//====================
// CAPTURE AREAS
//====================

typedef enum
{
	STATUS_FREE,
	STATUS_CAPTURED
} tdm_capture_status;

typedef struct
{
	int team;

	// when capture area is being captured
	int newteam;
	unsigned int startedcapping;

	// last time points were awarded for capture
	unsigned int lastpoints;

	// stores the last time each client "touched" capture area
	unsigned int players[MAX_CLIENTS];

	tdm_capture_status status;

	qboolean hasindicator;
	qboolean inuse;

	vec3_t origin;

} tdm_capture_area;

static tdm_capture_area capture_areas[MAX_CAPTURE_AREAS];

//====================
// CAPTURE POINTS
//====================
#define CAPTURE_POINT_RADIUS 400

char *capture_items[] =
{
	"item_health_mega",
	"item_armor_ra",
	"item_quad",
	"item_warshell",
	NULL
};

typedef struct
{
	//edict_t *ent;
	int captureareano;
	vec3_t origin;

	qboolean inuse;
} tdm_capture_point;
static tdm_capture_point capture_points[MAX_CAPTURE_AREAS]; // same max as areas

static void _SP_capture_area( edict_t *ent, qboolean fake );
//static void _SP_capture_area_indicator( edict_t *ent, qboolean fake );
static void capture_area_touch( edict_t *self, edict_t *other, cplane_t *plane, int surfFlags );
static void _capture_area_touch( edict_t *other, int captureareano );

static void ClearCaptureArea( tdm_capture_area *area )
{
	area->startedcapping = 0;
	area->team = 0;
	area->newteam = 0;
	area->status = STATUS_FREE;
	memset( area->players, 0, sizeof( area->players ) );
}

typedef struct
{
	int count;
	edict_t *ents[MAX_CLIENTS];
} tdm_area_status;
static void G_Gametype_ITDM_CheckRules( void )
{
	vec3_t len;
	edict_t *ent;
	trace_t tr;
	tdm_capture_area *area;
	tdm_capture_point *point;
	int alpha, beta, gamma, delta;
	tdm_area_status teams[GS_MAX_TEAMS];
	int i, j, team;
	static int lastteam;
	qboolean first = qtrue;

	if( !g_instagib->integer )
		return;

	// check capture points to see if any players are near them
	for( ent = game.edicts + 1; PLAYERNUM( ent ) < game.maxclients; ent++ )
	{
		if( !ent->r.inuse )
			continue;

		if( ent->s.team < TEAM_ALPHA || ent->s.team >= TEAM_DELTA )
			continue;

		for( i = 0, point = &capture_points[0]; i < MAX_CAPTURE_AREAS; point = &capture_points[++i] )
		{
			if( !point->inuse )
				continue;

			VectorSubtract( point->origin, ent->s.origin, len );
			if( VectorLengthFast( len ) <= CAPTURE_POINT_RADIUS )
			{
				// does the player have line of sight?
				G_Trace( &tr, ent->s.origin, vec3_origin, vec3_origin, point->origin, NULL, MASK_SOLID );
				if( tr.fraction == 1.0f )
					// fake a capture area touch
					_capture_area_touch( ent, point->captureareano );
			}
		}
	}

	// check the status for each capture area
	// this means checking that only one team is in the capture area
	for( i = 0 ; i < MAX_CAPTURE_AREAS; i++ )
	{
		area = &capture_areas[i];
		if( !area->inuse )
			continue;

		memset( &teams, 0, sizeof( teams ) );

		// find out how many players from each team are in the capture area
		for( j = 0; j < game.maxclients; j++ )
		{
			// has the player "touched" the capture area recently enough
			if( area->players[j] + CAPTURE_PLAYER_TIMEOUT > game.realtime )
			{
				if( !PLAYERENT( j )->deadflag )
				{
					team = PLAYERENT( j )->s.team;
					teams[team].ents[teams[team].count++] = PLAYERENT( j );
				}
			}
		}

		team = area->team;

		alpha = teams[TEAM_ALPHA].count;
		beta = teams[TEAM_BETA].count;
		gamma = teams[TEAM_GAMMA].count;
		delta = teams[TEAM_DELTA].count;

		// only alpha players in the cap zone
		if( alpha && !( beta || gamma || delta ) )
			team = TEAM_ALPHA;

		// only beta players in the cap zone
		if( beta && !( alpha || gamma || delta ) )
			team = TEAM_BETA;

		// only gamma players in the cap zone
		if( gamma && !( alpha || beta || delta ) )
			team = TEAM_GAMMA;

		// only delta players in the cap zone
		if( delta && !( alpha || beta || gamma ) )
			team = TEAM_DELTA;

		if( team == area->team )
		{
			// clear any attempts that may have been made to cap the area
			// which obviously were unsuccessful if we have reached this
			// part of the code
			area->newteam = 0;
			area->startedcapping = 0;

			if( area->lastpoints + CAPTURE_AREA_POINTS_TIME < game.realtime && !gtimeout.active )
			{
				// give players who are in the controlling team some points!
				if( area->team >= TEAM_ALPHA && area->team <= TEAM_DELTA )
					teamlist[area->team].teamscore++;
				area->lastpoints = game.realtime;
			}
		}
		else
		{
			// team have already started capping
			if( area->startedcapping && area->newteam == team )
			{
				// check to see if they have now finished capping
				if( game.realtime - area->startedcapping >= CAPTURE_AREA_TIME )
				{
					//area->indicator->s.team = area->newteam;
					area->team = area->newteam;
					area->newteam = 0;
					area->startedcapping = 0;
					area->status = STATUS_CAPTURED;

					// give award to all players in the cap area
					for( j = 0 ; j < teams[area->team].count ; j++ )
						G_AwardCaptureArea( teams[area->team].ents[j] );
				}
			}
			else
			{
				area->newteam = team;
				area->startedcapping = game.realtime;
			}
		}
	}

	// check if one team has all the points
	team = 0;
	for( i = 0 ; i < MAX_CAPTURE_AREAS ; i++ )
	{
		area = &capture_areas[i];
		if( !area->inuse )
			continue;

		if( team != area->team && !first )
			break;

		team = area->team;
		first = qfalse;
	}

	// we have a team
	if( i == MAX_CAPTURE_AREAS && team != lastteam )
	{
		lastteam = team;
		// give the award to everyone on the team
		for( i = 0 ; i < game.maxclients ; i++ )
		{
			if( PLAYERENT( i )->s.team == team )
				G_AwardAllCaptureAreas( PLAYERENT( i ) );
		}
	}
}

//=================
// G_Gametype_TDM_PreEntSpawn
// Called before map entities are spawned.
//=================
void G_Gametype_TDM_PreEntSpawn( void )
{
	memset( &capture_areas, 0, sizeof( capture_areas ) );
	trap_ModelIndex( PATH_CAPTUREAREA_A_MODEL );
	trap_ModelIndex( PATH_CAPTUREAREA_B_MODEL );
	trap_ModelIndex( PATH_CAPTUREAREA_C_MODEL );
	trap_ModelIndex( PATH_CAPTUREAREA_D_MODEL );
}

static void TDM_CreateCaptureAreaIndicator( vec3_t origin, int index )
{
	edict_t	*capa;

	capa = G_Spawn();
	capa->classname = "capture_area_indicator";

	VectorCopy( origin, capa->s.origin );
	// Into the air. This needs to be cleverer
	capa->s.origin[2] += 60.0f;
	capa->capture_areano = index + 1;

	G_CallSpawn( capa );
}

void G_Gametype_TDM_AssignSpawnPoints( void )
{
	edict_t *ent;
	int i, bestarea, numareas, numspawns, numassignedspawns, released;
	float bestDist, dist, largestDist;
	edict_t *bestSpawn[MAX_CAPTURE_AREAS];

	// count valid areas
	for( i = 0, numareas = 0; i < MAX_CAPTURE_AREAS; i++ )
		numareas += ( capture_areas[i].inuse != 0 );

	// mark spawn points as owned by a capture area
	ent = NULL;
	numspawns = numassignedspawns = 0;
	largestDist = 0;
	while( ( ent = G_Find( ent, FOFS( classname ), "info_player_deathmatch" ) ) != NULL )
	{
		bestDist = max( world->r.maxs[0], -world->r.mins[0] ) + max( world->r.maxs[1], -world->r.mins[1] ) + max( world->r.maxs[2], -world->r.mins[2] );
		bestarea = -1;

		for( i = 0; i < MAX_CAPTURE_AREAS; i++ )
		{
			if( !capture_areas[i].inuse )
				continue;

			dist = DistanceFast( capture_areas[i].origin, ent->s.origin );
			if( dist < bestDist )
			{
				bestDist = dist;
				bestarea = i;
			}

			if( dist > largestDist )
				largestDist = dist;
		}

		ent->capture_areano = bestarea;
		ent->moveinfo.distance = bestDist;
		numassignedspawns++;
		numspawns++;
	}

	

	released = 0;
	if( numspawns <= numareas || numspawns < 4 ) // if too few spawns, just let them all free
	{
		ent = NULL;
		while( ( ent = G_Find( ent, FOFS( classname ), "info_player_deathmatch" ) ) != NULL ) {
			ent->capture_areano = -1;
			numassignedspawns--;
			released++;
		}
	}
	else
	{
		// find closest spawn to each area
		for( i = 0; i < MAX_CAPTURE_AREAS; i++ )
		{
			bestSpawn[i] = NULL;
			if( !capture_areas[i].inuse )
				continue;

			bestDist = largestDist + 1;
			ent = NULL;
			while( ( ent = G_Find( ent, FOFS( classname ), "info_player_deathmatch" ) ) != NULL )
			{
				if( ent->capture_areano != i )
					continue;

				if( ent->moveinfo.distance < bestDist )
				{
					bestDist = ent->moveinfo.distance;
					bestSpawn[i] = ent;
				}
			}

			// we got this area's closest spawn distance, release
			// every spawn linked to this area and further away the closest
			// distance + a 50%

			ent = NULL;
			while( ( ent = G_Find( ent, FOFS( classname ), "info_player_deathmatch" ) ) != NULL )
			{
				if( ent->capture_areano != i )
					continue;

				if( ent->moveinfo.distance > bestDist + ( bestDist * 0.5f ) )
				{
					ent->capture_areano = -1;
					released++;
					numassignedspawns--;
				}
			}
		}
	}

	G_Printf( "Released %i spawns from a total of %i spawns. Left assigned: %i\n", released, numspawns, numassignedspawns );
}

//=================
// G_Gametype_TDM_NewMap
// We need to record the location of certain capture points
//=================
void G_Gametype_TDM_NewMap( void )
{
	char **captureitem;
	edict_t *ent;
	qboolean ignore = qfalse;
	int cpindex = 0, i;

	if( !g_instagib->integer )
		return;

	// if capture areas exist dont use mh, ra, etc.
	if( G_Find( 0, FOFS( classname ), "trigger_capture_area" ) )
		ignore = qtrue;

	memset( &capture_points, 0, sizeof( capture_points ) );

	// if there are ctf flags, use them instead
	for( ent = world ; ent < &game.edicts[game.maxentities]; ent++ )
	{
		if( ent->item && ent->item->type & IT_FLAG )
		{
			if( ignore || cpindex >= MAX_CAPTURE_AREAS )
			{
				G_FreeEdict( ent );
				continue;
			}

			VectorCopy( ent->s.origin, capture_points[cpindex].origin );
			capture_points[cpindex].inuse = qtrue;
			// fake a capture_area spawn
			_SP_capture_area( ent, qtrue );
			TDM_CreateCaptureAreaIndicator( ent->s.origin, cpindex );
			// copy area number across, because the entity will be freed
			capture_points[cpindex].captureareano = ent->capture_areano;

			// now we can clear edict
			G_FreeEdict( ent );

			cpindex++;
		}
	}

	// we found some flags
	if( cpindex )
		ignore = qtrue;

	// we still need to loop even if capture areas do exist
	// in order to free the edict
	for( captureitem = capture_items; *captureitem; captureitem++ )
	{
		while( ( ent = G_Find( 0, FOFS( classname ), *captureitem ) ) )
		{
			if( ignore || cpindex >= MAX_CAPTURE_AREAS )
			{
				G_FreeEdict( ent );
				continue;
			}

			VectorCopy( ent->s.origin, capture_points[cpindex].origin );
			capture_points[cpindex].inuse = qtrue;
			// fake a capture_area spawn
			_SP_capture_area( ent, qtrue );
			TDM_CreateCaptureAreaIndicator( ent->s.origin, cpindex );
			// copy area number across, because the entity will be freed
			capture_points[cpindex].captureareano = ent->capture_areano;

			// now we can clear edict
			G_FreeEdict( ent );

			cpindex++;
		}
	}

	// check that all capture areas have an indicator
	for( i = 0 ; i < MAX_CAPTURE_AREAS ; i++ )
	{
		if( capture_areas[i].inuse && !capture_areas[i].hasindicator )
			G_Printf( "WARNING: Capture area %i has no indicator\n", i );
	}

	G_Gametype_TDM_AssignSpawnPoints();
}

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

	G_Gametype_GENERIC_SetUpMatch();

	//clear team stats
	memset( &tdmgame, 0, sizeof( tdm_stats_t ) );

	// reset capture areas
	for( i = 0; i < MAX_CAPTURE_AREAS; i++ )
	{
		if( capture_areas[i].inuse )
			ClearCaptureArea( &capture_areas[i] );
	}

	G_UpdatePlayersMatchMsgs();
}

//==================
//G_Gametype_TDM_ScoreboardMessage
//==================
char *G_Gametype_TDM_ScoreboardMessage( void )
{
	char entry[MAX_TOKEN_CHARS];
	size_t len;
	int i, team;
	edict_t	*e;

	//fixed layout scheme id
	Q_snprintfz( scoreboardString, sizeof( scoreboardString ), "scb \"&tdms " );
	len = strlen( scoreboardString );
	*entry = 0; // wsw : aiwa : fix unitialized value heisenbug

	//it's sending the players in score order
	for( team = TEAM_ALPHA; team < TEAM_ALPHA + g_maxteams->integer; team++ )
	{
		//team tab entry
		*entry = 0;
		Q_snprintfz( entry, sizeof( entry ), "&t %i %i ",
		             team,
		             teamlist[team].teamscore );

		if( SCOREBOARD_MSG_MAXSIZE - len > strlen( entry ) )
		{
			Q_strncatz( scoreboardString, entry, sizeof( scoreboardString ) );
			len = strlen( scoreboardString );
		}

		for( i = 0; teamlist[team].playerIndices[i] != -1; i++ )
		{
			e = game.edicts + teamlist[team].playerIndices[i];

			//player tab entry
			*entry = 0;
			Q_snprintfz( entry, sizeof( entry ), "&p %i %i %i %i %i %i %i %i %i ",
			             PLAYERNUM( e ),
			             match.scores[PLAYERNUM( e )].score,
			             match.scores[PLAYERNUM( e )].kills,
			             match.scores[PLAYERNUM( e )].deaths,
			             match.scores[PLAYERNUM( e )].suicides,
			             match.scores[PLAYERNUM( e )].teamfrags,
			             e->r.client->r.ping > 999 ? 999 : e->r.client->r.ping,
			             match.ready[PLAYERNUM( e )],
			             e->r.client->is_coach
			);

			if( SCOREBOARD_MSG_MAXSIZE - len > strlen( entry ) )
			{
				Q_strncatz( scoreboardString, entry, sizeof( scoreboardString ) );
				len = strlen( scoreboardString );
			}
		}
	}

	G_ScoreboardMessage_AddSpectators();

	// add player stats (all weapon weak/strong 0..99) to scoreboard message
	//	G_ScoreboardMessage_AddPlayerStats( ent );

	// add closing quote
	if( SCOREBOARD_MSG_MAXSIZE - len > strlen( entry ) )
	{
		Q_strncatz( scoreboardString, "\"", sizeof( scoreboardString ) );
		len = strlen( scoreboardString );
	}
	return scoreboardString;
}


//==================
//G_Gametype_TDM_FragBonuses
//==================
void G_Gametype_TDM_FragBonuses( edict_t *targ, edict_t *inflictor, edict_t *attacker, int mod )
{
	if( targ->s.team < TEAM_ALPHA || targ->s.team >= GS_MAX_TEAMS )
		return; // whoever died isn't on a team

	// add frag to scores

	if( !attacker->r.client ) // killed by the world
	{
		if( attacker == world && targ->r.client )
		{
			if( mod == MOD_FALLING )  //should have cratereds++
				match.scores[PLAYERNUM( targ )].suicides++;

			match.scores[PLAYERNUM( targ )].deaths++;
			match.scores[PLAYERNUM( targ )].score--;
			teamlist[targ->s.team].teamplayerscores--;
			teamlist[targ->s.team].teamscore--;
		}
		return;
	}

	//selffrag or teamfrag
	if( targ->s.team == attacker->s.team )
	{
		match.scores[PLAYERNUM( attacker )].score--;
		teamlist[attacker->s.team].teamplayerscores--;
		teamlist[attacker->s.team].teamscore--;
		if( targ == attacker )
			match.scores[PLAYERNUM( attacker )].suicides++;
		else
			match.scores[PLAYERNUM( attacker )].teamfrags++;
	}
	else
	{
		match.scores[PLAYERNUM( attacker )].score++;
		// teamscores are based on capture areas for insta
		if( !g_instagib->integer )
		{
			teamlist[attacker->s.team].teamplayerscores++;
			teamlist[attacker->s.team].teamscore++;
		}
		match.scores[PLAYERNUM( attacker )].kills++;
	}

	if( !targ->r.client )  //can't count deaths on non-clients
		return;

	match.scores[PLAYERNUM( targ )].deaths++;
}

//=================
//G_Gametype_TDM_CheckRules
//=================
void G_Gametype_TDM_CheckRules( void )
{
	if( match.state >= MATCH_STATE_POSTMATCH )
		return;

	if( game.gametype != GAMETYPE_TDM )
		return;

	G_Gametype_ITDM_CheckRules();

	G_GameType_ClientHealthRule();
	//G_GameType_ClientArmorDecayRule();
	G_Teams_UpdateTeamInfoMessages();


	if( G_Match_GenericCountDownAnnounces() )
	{                                       // true once a second
	}
}

edict_t *G_Gametype_TDM_SelectSpawnPoint( edict_t *ent )
{
	edict_t	*spot, *spots[16];
	int selection;
	tdm_capture_area *area;
	int count = 0, numassignedspawns = 0;
	

	if( !g_instagib->integer )
		return SelectDeathmatchSpawnPoint( ent );

	// find spawnpoints at areas owned by the player team, or
	// spawnpoints that are *not assigned to any area*
	spot = NULL;
	while( ( spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL )
	{
		if( spot->capture_areano >= 0 || spot->capture_areano < MAX_CAPTURE_AREAS )
		{
			area = &capture_areas[spot->capture_areano];
			if( !area->inuse )
				continue;

			numassignedspawns++;

			if( area->team != ent->s.team )
				continue;
		}

		spots[count] = spot;
		count++;
		if( count == 16 )
			break;
	}

	// if no spawn found yet, try to find spawnpoints assigned to areas but not
	// owned by the enemy
	if( !count && numassignedspawns )
	{
		spot = NULL;
		while( ( spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL )
		{
			if( spot->capture_areano < 0 || spot->capture_areano >= MAX_CAPTURE_AREAS )
				continue;

			area = &capture_areas[spot->capture_areano];
			if( area->inuse && ( area->team == ( ent->s.team == TEAM_ALPHA ) ? TEAM_BETA : TEAM_ALPHA ) )
				continue;

			spots[count] = spot;
			count++;
			if( count == 16 )
				break;
		}
	}

	// if no free spawn found, just use any spawn as in normal TDM
	if( !count )
		return SelectDeathmatchSpawnPoint( ent );

	selection = rand() % count;
	return spots[selection];
}

//=================
// ENTITY HANDLER FUNCTIONS
//=================
// called by the actual entity function
static void capture_area_touch( edict_t *self, edict_t *other, cplane_t *plane, int surfFlags )
{
	_capture_area_touch( other, self->capture_areano );
}

// for compatibility with capture area faking
static void _capture_area_touch( edict_t *other, int capture_areano )
{
	// players can only cap in playtime state
	if( match.state != MATCH_STATE_PLAYTIME )
		return;

	if( Q_stricmp( other->classname, "player" ) && Q_stricmp( other->classname, "bot" ) )
		return;

	if( other->s.team < TEAM_ALPHA || other->s.team >= TEAM_DELTA )
		return;

	capture_areas[capture_areano].players[PLAYERNUM( other )] = game.realtime;
}

// called by the actual entity function
void SP_capture_area( edict_t *ent )
{
	_SP_capture_area( ent, qfalse );
}

// for compatibility with capture area faking
static void _SP_capture_area( edict_t *ent, qboolean fake )
{
	int i;
	tdm_capture_area *area;

	if( game.gametype != GAMETYPE_TDM || !g_instagib->integer )
	{
		G_FreeEdict( ent );
		return;
	}

	if( ent->capture_arealetter )
		ent->capture_areano = TDM_LetterToIndex( *ent->capture_arealetter );
	else
	// non programmers cant handle 0 starting indexes :P
		ent->capture_areano--;

	// if its fake, we need to find it an area number
	if( fake )
	{
		for( i = 0; i < MAX_CAPTURE_AREAS; i++ )
		{
			if( !capture_areas[i].inuse )
			{
				ent->capture_areano = i;
				break;
			}
		}
		// no areas available
		if( i == MAX_CAPTURE_AREAS )
			ent->capture_areano = -1;
	}

	if( ent->capture_areano < 0 || ent->capture_areano >= MAX_CAPTURE_AREAS )
	{
		G_Printf( "WARNING: Invalid capture area number: %d\n", ent->capture_areano );
		G_FreeEdict( ent );
		return;
	}

	if( capture_areas[ent->capture_areano].inuse )
	{
		G_Printf( "WARNING: Duplicate capture area number: %d\n", ent->capture_areano );
		G_FreeEdict( ent );
		return;
	}


	area = &capture_areas[ent->capture_areano];
	area->inuse = qtrue;
	ClearCaptureArea( area );
	
	VectorCopy( ent->s.origin, area->origin );

	if( fake )
		return;

	ent->r.solid = SOLID_TRIGGER;
	ent->r.svflags |= SVF_NOCLIENT;
	ent->touch = capture_area_touch;

	GClip_SetBrushModel( ent, ent->model );

	VectorAdd( ent->r.mins, ent->r.maxs, area->origin );
	VectorAdd( area->origin, ent->s.origin, area->origin );
}

// called by the entity function
void SP_capture_area_indicator( edict_t *ent )
{
	if( game.gametype != GAMETYPE_TDM || !g_instagib->integer )
	{
		G_FreeEdict( ent );
		return;
	}

	if( ent->capture_arealetter )
		ent->capture_areano = TDM_LetterToIndex( *ent->capture_arealetter );
	else
	// non programmers cant handle 0 starting indexes :P
		ent->capture_areano--;

	// no area number assigned
	if( ent->capture_areano < 0 )
	{
		G_Printf( "WARNING: Capture area indicator not linked to an area: %d\n", ent->capture_areano );
		G_FreeEdict( ent );
		return;
	}

	if( ent->capture_areano >= MAX_CAPTURE_AREAS )
	{
		G_Printf( "WARNING: Maximum number of capture areas exceeded: %d\n", ent->capture_areano );
		G_FreeEdict( ent );
		return;
	}

	capture_areas[ent->capture_areano].hasindicator = qtrue;

	// setup entity
	ent->s.type = capture_indicators_type[ent->capture_areano];
	ent->model = capture_indicators[ent->capture_areano];
	ent->classname = "capture_area_indicator";
	ent->spawnflags = 0;
	ent->r.solid = SOLID_NOT;
	ent->movetype = MOVETYPE_NONE;
	ent->r.svflags = 0;
	ent->s.effects = 0; // default effects are applied client side
	ent->s.renderfx = 0;
	ent->s.frame = 0;
	ent->s.team = 0;
	ent->s.modelindex = trap_ModelIndex( ent->model );
	// temp
	VectorCopy( item_box_mins, ent->r.mins );
	VectorCopy( item_box_maxs, ent->r.maxs );
	ent->gravity = 0;

	//GClip_SetBrushModel( ent, ent->model );
	GClip_LinkEntity( ent );
}

//===================
// PLAYER STATS FUNCTIONS
//===================

static int TDM_TeamToBit( int team )
{
	switch( team )
	{
	case TEAM_ALPHA: return AREA_TEAM_ALPHA;
	case TEAM_BETA: return AREA_TEAM_BETA;
	case TEAM_GAMMA: return AREA_TEAM_GAMMA;
	case TEAM_DELTA: return AREA_TEAM_DELTA;
	}

	return 0;
}

short G_Gametype_TDM_AreaStatus( void )
{
	int i;
	short final = 0, current = 0;

	if( !g_instagib->integer )
		return 0;

	for( i = 0; i < MAX_CAPTURE_AREAS; i++ )
	{
		if( !capture_areas[i].inuse )
			continue;

		// convert to the equivalent bits for the stats msg.
		current = TDM_TeamToBit( capture_areas[i].team );

		switch( capture_areas[i].status )
		{
		case STATUS_FREE: current |= AREA_STATE_FREE; break;
		case STATUS_CAPTURED: current |= AREA_STATE_CAPTURED; break;
		}

		final |= ( current & AREA_TOTAL_BITMASK ) << ( i * AREA_TOTAL_SIZE );
	}

	return final;
}

short G_Gametype_TDM_AreaCapturing( int stat )
{
	int i, offset, percent;
	short final = 0, current;
	tdm_capture_area *area;

	if( !g_instagib->integer )
		return 0;

	// which stat are we calculating for?
	if( stat == 1 )
		offset = 0;
	else
		offset = 2;

	for( i = 0; i < MAX_CAPTURE_AREAS / 2; i++ )
	{
		area = &capture_areas[i + offset];
		if( !area->inuse )
			continue;

		if( !area->startedcapping )
			continue;

		current = TDM_TeamToBit( area->newteam );
		// 50 because we are updating every 2% (because of lack of space in stat)
		percent = 50 * ( game.realtime - area->startedcapping ) / CAPTURE_AREA_TIME;
		current |= ( percent & AREA_PERCENTAGE_BITMASK ) << AREA_TEAM_SIZE;

		final |= ( current & AREA_CAPTURING_BITMASK ) << ( i * AREA_CAPTURING_SIZE );
	}

	return final;
}
