/*
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"

typedef struct ctfstats_s
{
	int			caps;
	int			basedef;
	int			carrierdef;
	int			triggerpushed;
	float		ctf_lasthurtcarrier;
	float		ctf_lastreturnedflag;
	float		ctf_flagsince;
	float		ctf_lastfraggedcarrier;

}ctfstats_t;

typedef struct
{
	float	last_flag_capture;
	int		last_capture_team;

	ctfstats_t	playerstats[MAX_CLIENTS];

	float flagStolenTime[GS_MAX_TEAMS];
	unsigned int flagResetTimer[GS_MAX_TEAMS];
	qboolean flagResetTimerTouched[GS_MAX_TEAMS];
	qboolean flagResetTimerTouchedByFlagCarrier[GS_MAX_TEAMS];
} ctfgame_t;

ctfgame_t ctfgame;

//==========================================================
//
//		MATCH MANAGER
//
//==========================================================


void G_Gametype_GENERIC_SetUpMatch( void );
void G_Gametype_GENERIC_SetUpEndMatch( void );

//=================
//G_Gametype_CTF_SetUpMatch
//=================
void G_Gametype_CTF_SetUpMatch( void )
{
	G_Gametype_GENERIC_SetUpMatch();
	G_Gametype_CTF_ResetFlags();

	//clear ctf teams & stats
	memset( &ctfgame.playerstats, 0, sizeof(ctfstats_t) * MAX_CLIENTS );
	memset( &ctfgame, 0, sizeof(ctfgame) );
}

//=================
//G_Gametype_CTF_SetUpEndMatch
//=================
void G_Gametype_CTF_SetUpEndMatch( void ) {
	G_Gametype_GENERIC_SetUpEndMatch();
	if( match.state == MATCH_STATE_PLAYTIME ) // overtime
		return;

	G_Gametype_CTF_ResetFlags();
}

// changed the ctf scores, in an attempt to make CTF scores more meaningfull
#define CTF_CAPTURE_BONUS		5	// what you get for capture
#define CTF_TEAM_BONUS			1	// what your team gets for capture
#define CTF_RECOVERY_BONUS		1	// what you get for recovery
#define CTF_FRAG_CARRIER_BONUS	2	// what you get for fragging enemy flag carrier
#define CTF_FLAG_RETURN_TIME	30	// seconds until auto return

#define CTF_CARRIER_DANGER_PROTECT_BONUS	2	// bonus for fraggin someone who has recently hurt your flag carrier
#define CTF_CARRIER_PROTECT_BONUS			1	// bonus for fraggin someone while either you or your target are near your flag carrier
#define CTF_FLAG_DEFENSE_BONUS				1	// bonus for fraggin someone while either you or your target are near your flag
#define CTF_RETURN_FLAG_ASSIST_BONUS		1	// awarded for returning a flag that causes a capture to happen almost immediately
#define CTF_FRAG_CARRIER_ASSIST_BONUS		2	// award for fragging a flag carrier if a capture happens almost immediately
#define CTF_EXTEND_RESET_ASSIST_BONUS		3	// award for touching the enemy base trigger so the flag auto reset countdown is extended

#define CTF_TARGET_PROTECT_RADIUS			500	// the radius around an object being defended where a target will be worth extra frags
#define CTF_ATTACKER_PROTECT_RADIUS			500	// the radius around an object being defended where an attacker will get extra frags when making kills

#define CTF_CARRIER_DANGER_PROTECT_TIMEOUT	8
#define CTF_FRAG_CARRIER_ASSIST_TIMEOUT		10
#define CTF_RETURN_FLAG_ASSIST_TIMEOUT		10

#define CTF_AUTO_FLAG_RETURN_TIMEOUT		30	// number of seconds before dropped flag auto-returns

#define CTF_AUTO_FLAG_RESET_COUNTDOWN		10



//QUAKED SP_team_CTF_redspawn (1 0 0) (-16 -16 -24) (16 16 32)
//potential team1 spawning position for ctf games

void SP_team_CTF_redspawn( edict_t *self )
{
	G_DropSpawnpointToFloor( self );
}

//QUAKED SP_team_CTF_bluespawn (0 0 1) (-16 -16 -24) (16 16 32)
//potential team2 spawning position for ctf games

void SP_team_CTF_bluespawn( edict_t *self )
{
	G_DropSpawnpointToFloor( self );
}

//QUAKED SP_team_CTF_redplayer (1 0 0) (-16 -16 -24) (16 16 32)
//potential team1 spawning position for ctf games

void SP_team_CTF_redplayer( edict_t *self )
{
	G_DropSpawnpointToFloor( self );
}

//QUAKED SP_team_CTF_blueplayer (0 0 1) (-16 -16 -24) (16 16 32)
//potential team2 spawning position for ctf games

void SP_team_CTF_blueplayer( edict_t *self )
{
	G_DropSpawnpointToFloor( self );
}

//================
//SelectCTFSpawnPoint

//go to a ctf point, but NOT the two points closest
//to other players
//================
float PlayersRangeFromSpot( edict_t *spot, int ignore_team );
edict_t *G_Gametype_CTF_SelectSpawnPoint( edict_t *ent )
{
	edict_t	*spot, *spot1, *spot2;
	int		count = 0;
	int		selection;
	float	range, range1, range2;
	char	*cname = NULL;

	if( ent->r.client && !ent->r.client->resp.respawnCount ) {
		switch( ent->s.team ) {
		case TEAM_RED:
			cname = "team_CTF_redplayer";
			break;
		case TEAM_BLUE:
			cname = "team_CTF_blueplayer";
			break;
		default:
			break;
		}
	}
	if( !cname ) {
		switch( ent->s.team ) {
		case TEAM_RED:
			cname = "team_CTF_redspawn";
			break;
		case TEAM_BLUE:
			cname = "team_CTF_bluespawn";
			break;
		default:
			break;
		}
	}

	if( !cname )
		return SelectDeathmatchSpawnPoint(ent);

	spot = NULL;
	range1 = range2 = 99999;
	spot1 = spot2 = NULL;

	while( (spot = G_Find (spot, FOFS(classname), cname)) != NULL )
	{
		count++;
		range = PlayersRangeFromSpot( spot, ent->s.team );
		if( range < range1 )
		{
			range1 = range;
			spot1 = spot;
		}
		else if( range < range2 )
		{
			range2 = range;
			spot2 = spot;
		}
	}

	if( !count )
		return SelectDeathmatchSpawnPoint(ent);

	if( count <= 2 ) {
		spot1 = spot2 = NULL;
	}
	else
		count -= 2;

	selection = rand() % count;

	spot = NULL;
	do
	{
		spot = G_Find( spot, FOFS(classname), cname );
		if( spot == spot1 || spot == spot2 )
			selection++;
	}while( selection-- );

	return spot;
}


//==========================================================
//
//		FLAGS
//
//==========================================================

gitem_t	*flagItems[GS_MAX_TEAMS];

//picts precache (changeme)
int imageindex_i_ctf1;
int imageindex_i_ctf2;
int imageindex_i_ctf1d;
int imageindex_i_ctf2d;
int imageindex_i_ctf1t;
int imageindex_i_ctf2t;
int imageindex_i_ctfj;
int imageindex_sbfctf1;
int imageindex_sbfctf2;
int imageindex_ctfsb1;
int imageindex_ctfsb2;


void target_reset_flag_countdown_use( edict_t *self, edict_t *other, edict_t *activator )
{
	edict_t *flag;
	int			flag_team;

	if( !activator->r.client )
		return;
	if( activator->deadflag )
		return;

	if( self->s.team && self->s.team != activator->s.team )
		return;

	flag_team = (activator->s.team == TEAM_RED)?TEAM_BLUE:TEAM_RED;

	// flag carriers can't reset the countdown in external spots.
	if( activator->r.client->inventory[flagItems[flag_team]->tag] )
		return;

	// reset counter at enemy base
	flag = NULL;
	while( (flag = G_Find(flag, FOFS(classname), flagItems[flag_team]->classname)) != NULL ) {
		if( flag->s.type == ET_FLAG_BASE ) {
			if( !(flag->spawnflags & DROPPED_ITEM) && !(flag->s.effects & EF_ENEMY_FLAG) )
				ctfgame.flagResetTimer[flag_team] = level.timemsec + (flag->wait * 1000);
		}
	}
}

//QUAKED SP_target_reset_flag_countdown (1 0 0) (-16 -16 -24) (16 16 32)
//resets flag countdown (as when touching the base)
void SP_target_reset_flag_countdown( edict_t *self )
{
	if( !g_tctf->integer ) {
		G_FreeEdict(self);
		return;
	}
	self->r.svflags |= SVF_NOCLIENT;
	self->use = target_reset_flag_countdown_use;
	if( st.gameteam ) {
		if( st.gameteam >= TEAM_RED && st.gameteam < GS_MAX_TEAMS )
			self->s.team = st.gameteam;
	}
}

void target_freeze_flag_countdown_use( edict_t *self, edict_t *other, edict_t *activator )
{
	edict_t *flag;
	int			flag_team;

	if( !activator->r.client )
		return;
	if( activator->deadflag )
		return;

	if( self->s.team && self->s.team != activator->s.team )
		return;

	flag_team = (activator->s.team == TEAM_RED)?TEAM_BLUE:TEAM_RED;

	// only flag carriers can freeze the countdown in external spots.
	if( !activator->r.client->inventory[flagItems[flag_team]->tag] )
		return;

	// reset counter at enemy base
	flag = NULL;
	while( (flag = G_Find(flag, FOFS(classname), flagItems[flag_team]->classname)) != NULL ) {
		if( flag->s.type == ET_FLAG_BASE ) {
			if( !(flag->spawnflags & DROPPED_ITEM) && !(flag->s.effects & EF_ENEMY_FLAG) ) {
				ctfgame.flagResetTimer[flag_team] += game.framemsec;
			}
		}
	}
}

//QUAKED SP_target_freeze_flag_countdown (1 0 0) (-16 -16 -24) (16 16 32)
//freezes flag countdown
void SP_target_freeze_flag_countdown( edict_t *self )
{
	if( !g_tctf->integer ) {
		G_FreeEdict(self);
		return;
	}

	self->r.svflags |= SVF_NOCLIENT;
	self->use = target_freeze_flag_countdown_use;
	if( st.gameteam ) {
		if( st.gameteam >= TEAM_RED && st.gameteam < GS_MAX_TEAMS )
			self->s.team = st.gameteam;
	}
}


//==================
//G_Gametype_CTF_CleanUpPlayerStats
//==================
void G_Gametype_CTF_CleanUpPlayerStats( edict_t *ent )
{
	memset( &ctfgame.playerstats[PLAYERNUM(ent)], 0, sizeof(ctfstats_t) );
}

//==================
//G_Gametype_CTF_NewMap
//==================
void G_Gametype_CTF_NewMap( void ) 
{
	//clear teams & stats
	memset( &ctfgame.playerstats, 0, sizeof(ctfstats_t) * MAX_CLIENTS );
	memset( &ctfgame, 0, sizeof(ctfgame) );

	//set up the local flag items
	flagItems[TEAM_SPECTATOR] = NULL;
	flagItems[TEAM_PLAYERS] = NULL;
	flagItems[TEAM_RED] = GS_FindItemByClassname("team_CTF_redflag");
	flagItems[TEAM_BLUE] = GS_FindItemByClassname("team_CTF_blueflag");
	flagItems[TEAM_GREEN] = GS_FindItemByClassname("team_CTF_greenflag");
	flagItems[TEAM_YELLOW] = GS_FindItemByClassname("team_CTF_yellowflag");

	imageindex_i_ctf1 =   trap_ImageIndex("pics/i_ctf1"); 
	imageindex_i_ctf2 =   trap_ImageIndex("pics/i_ctf2"); 
	imageindex_i_ctf1d =  trap_ImageIndex("pics/i_ctf1d");
	imageindex_i_ctf2d =  trap_ImageIndex("pics/i_ctf2d");
	imageindex_i_ctf1t =  trap_ImageIndex("pics/i_ctf1t");
	imageindex_i_ctf2t =  trap_ImageIndex("pics/i_ctf2t");
	imageindex_i_ctfj =   trap_ImageIndex("pics/i_ctfj"); 
	imageindex_sbfctf1 =  trap_ImageIndex("pics/sbfctf1");
	imageindex_sbfctf2 =  trap_ImageIndex("pics/sbfctf2");
	imageindex_ctfsb1 =   trap_ImageIndex("pics/ctfsb1");
	imageindex_ctfsb2 =   trap_ImageIndex("pics/ctfsb2");
}

//==================
//G_Gametype_CTF_Init
//==================
void G_Gametype_CTF_Init( void )
{
	if( g_tctf->integer < 0 ) // validate
		trap_Cvar_SetValue( "g_tctf", 0 );
}

//==================
//G_Gametype_CTF_HasFlag
//==================
qboolean G_Gametype_CTF_HasFlag( edict_t *ent, int team )
{
	if( !ent->r.client ) return qfalse;
	if( !flagItems[team] ) return qfalse;

	return ent->r.client->inventory[flagItems[team]->tag];
}

//==================
//G_Gametype_CTF_FlagItem
//==================
gitem_t *G_Gametype_CTF_FlagItem( int team )
{
	return flagItems[team];
}

//==================
//G_Gametype_CTF_CapturedFlagTimer
//==================
int G_Gametype_CTF_CapturedFlagTimer( int team ) {
	int flagteam;
	if( team < TEAM_RED || team >= TEAM_RED + g_maxteams->integer )
		return 0;

	//fixme? : we only support 2 teams in ctf
	flagteam = (team == TEAM_RED)?TEAM_BLUE:TEAM_RED;
	if( ctfgame.flagResetTimer[flagteam] )
		return ctfgame.flagResetTimer[flagteam] - level.timemsec;

	return 0;
}

//==================
//G_Gametype_CTF_FlagStatus
//==================
int G_Gametype_CTF_FlagStatus( int team ) {
	int			i;
	edict_t		*ent;

	if( team < TEAM_RED || team >= TEAM_RED + g_maxteams->integer )
		return 0;

	for (i = 0; i < game.maxclients; i++) {
		ent = game.edicts + i + 1;
		if (ent->r.inuse && ent->r.client->inventory[flagItems[team]->tag])
			return FLAG_STATUS_STOLEN;
	}

	ent = NULL;
	while( (ent = G_Find (ent, FOFS(classname), flagItems[team]->classname)) != NULL ) {
		if( ent->spawnflags & DROPPED_ITEM )
			return FLAG_STATUS_DROPPED;
	}

	return FLAG_STATUS_SAFE;
}

//==================
//G_Gametype_CTF_CheckHurtCarrier
//
//carrier bonus is only for enemys carrying YOUR flag.
//==================
void G_Gametype_CTF_CheckHurtCarrier( edict_t *targ, edict_t *attacker )
{
	if (!targ->r.client)
		return;

	if( !targ->s.team || !attacker->s.team || 
		targ->s.team == attacker->s.team )
		return;

	if( !flagItems[attacker->s.team] || PLAYERNUM(attacker) > MAX_CLIENTS ) 
		return;
	
	if( targ->r.client->inventory[flagItems[attacker->s.team]->tag] )
		ctfgame.playerstats[PLAYERNUM(attacker)].ctf_lasthurtcarrier = level.time;
}

static qboolean loc_CanSee( edict_t *targ, edict_t *inflictor )
{
	trace_t	trace;
	vec3_t	targpoints[8];
	int i;
	vec3_t viewpoint;

// bmodels need special checking because their origin is 0,0,0
	if (targ->movetype == MOVETYPE_PUSH)
		return qfalse; // bmodels not supported

	BuildBoxPoints(targpoints, targ->s.origin, targ->r.mins, targ->r.maxs);
	
	VectorCopy(inflictor->s.origin, viewpoint);
	viewpoint[2] += inflictor->viewheight;

	for (i = 0; i < 8; i++) {
		trap_Trace (&trace, viewpoint, vec3_origin, vec3_origin, targpoints[i], inflictor, MASK_SOLID);
		if (trace.fraction == 1.0)
			return qtrue;
	}

	return qfalse;
}


//
//Calculate the bonuses for flag defense, flag carrier defense, etc.
//Note that bonuses are not cumaltive.  You get one, they are in importance
//order.
//
//==================
//G_Gametype_CTF_FragBonuses
//==================
void G_Gametype_CTF_FragBonuses( edict_t *targ, edict_t *inflictor, edict_t *attacker, int mod )
{
	int i;
	edict_t *ent;
	edict_t *flag;
	edict_t *carrier = NULL;
	vec3_t v1, v2;
	int		attackerflag, targflag;

	if( targ->s.team < TEAM_RED || targ->s.team >= GS_MAX_TEAMS )
		return; // whoever died isn't on a team

	// add frags 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--;
		}
		return;
	}

	//selffrag or teamfrag
	if( targ->s.team == attacker->s.team ) { 
		match.scores[PLAYERNUM(attacker)].score--;
		teamlist[attacker->s.team].teamplayerscores--;
		if( targ == attacker )
			match.scores[PLAYERNUM(attacker)].suicides++;
		else
			match.scores[PLAYERNUM(attacker)].teamfrags++;
	} else {
		match.scores[PLAYERNUM(attacker)].score++;
		teamlist[attacker->s.team].teamplayerscores++;
		match.scores[PLAYERNUM(attacker)].kills++;
	}

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

	//targ->r.client->resp.deaths++;
	match.scores[PLAYERNUM(targ)].deaths++;

	// no bonus for fragging yourself
	if (targ == attacker)
		return;

	if( !flagItems[targ->s.team] || !flagItems[attacker->s.team] )
		return; //one of them is not on a valid ctf team

	attackerflag = flagItems[attacker->s.team]->tag;
	targflag = flagItems[targ->s.team]->tag;

	// did the attacker frag the flag carrier (carrying attacker's team flag)?
	if( targ->r.client->inventory[attackerflag] ) 
	{
		ctfgame.playerstats[PLAYERNUM(attacker)].ctf_lastfraggedcarrier = level.time;
		match.scores[PLAYERNUM(attacker)].score += CTF_FRAG_CARRIER_BONUS;
		teamlist[attacker->s.team].teamplayerscores += CTF_FRAG_CARRIER_BONUS;
		G_PrintMsg( attacker, "%sBONUS: %d points for fragging enemy flag carrier.\n",
			S_COLOR_GREY, CTF_FRAG_CARRIER_BONUS );

		// the target had the flag, clear the hurt carrier
		// field on the other team
		for (i = 0; i < game.maxclients; i++) {
			ent = game.edicts + i + 1;
			if( !ent->r.inuse || !ent->s.team )
				continue;

			if (ent->s.team == targ->s.team)
				ctfgame.playerstats[PLAYERNUM(ent)].ctf_lasthurtcarrier = 0;
		}
		return;
	}

	if( ctfgame.playerstats[PLAYERNUM(targ)].ctf_lasthurtcarrier &&
		level.time - ctfgame.playerstats[PLAYERNUM(targ)].ctf_lasthurtcarrier < CTF_CARRIER_DANGER_PROTECT_TIMEOUT &&
		!(attacker->s.effects & EF_ENEMY_FLAG) ) {
		// attacker is on the same team as the flag carrier and
		// fragged a guy who hurt our flag carrier
		// (and he is not the flag carrier)
		match.scores[PLAYERNUM(attacker)].score += CTF_CARRIER_DANGER_PROTECT_BONUS;
		teamlist[attacker->s.team].teamplayerscores += CTF_CARRIER_DANGER_PROTECT_BONUS;
		G_PrintMsg( NULL, "%s%s defends %s's flag carrier against an agressive enemy\n",
			attacker->r.client->pers.netname, S_COLOR_GREY, 
			GS_TeamName(attacker->s.team) );

		ctfgame.playerstats[PLAYERNUM(attacker)].carrierdef++;
		return;
	}

	// flag and flag carrier area defense bonuses

	// we have to find the flag and carrier entities
	flag = NULL;
	while ((flag = G_Find (flag, FOFS(classname), flagItems[attacker->s.team]->classname)) != NULL) {
		if (!(flag->spawnflags & DROPPED_ITEM))
			break;
	}

	if (!flag)
		return; // can't find attacker's flag

	// find attacker's team's flag carrier
	for (i = 0; i < game.maxclients; i++) {
		carrier = game.edicts + i + 1;
		if( carrier->r.inuse && 
			carrier->r.client->inventory[targflag] )
			break;
	}
	if( !carrier->r.client->inventory[targflag] )
		carrier = NULL;

	// ok we have the attackers flag and a pointer to the carrier

	// check to see if we are defending the base's flag
	VectorSubtract(targ->s.origin, flag->s.origin, v1);
	VectorSubtract(attacker->s.origin, flag->s.origin, v2);

	if ((VectorLengthFast(v1) < CTF_TARGET_PROTECT_RADIUS ||
		VectorLengthFast(v2) < CTF_TARGET_PROTECT_RADIUS ||
		loc_CanSee(flag, targ) || loc_CanSee(flag, attacker)) &&
		attacker->s.team != targ->s.team) 
	{
		// we defended the base flag
		match.scores[PLAYERNUM(attacker)].score += CTF_FLAG_DEFENSE_BONUS;
		teamlist[attacker->s.team].teamplayerscores += CTF_FLAG_DEFENSE_BONUS;
		if( !(flag->s.effects & EF_ENEMY_FLAG) )
			G_PrintMsg( NULL, "%s%s defends the %s base.\n",
				attacker->r.client->pers.netname, S_COLOR_GREY, 
				GS_TeamName(attacker->s.team) );
		else
			G_PrintMsg( NULL, "%s%s defends the %s flag.\n",
				attacker->r.client->pers.netname, S_COLOR_GREY, 
				GS_TeamName(attacker->s.team) );

		ctfgame.playerstats[PLAYERNUM(attacker)].basedef++;
		return;
	}

	if( carrier && attacker && carrier != attacker && carrier->s.team == attacker->s.team ) {
		VectorSubtract( targ->s.origin, carrier->s.origin, v1 );
		VectorSubtract( attacker->s.origin, carrier->s.origin, v2 );

		if( VectorLengthFast(v1) < CTF_ATTACKER_PROTECT_RADIUS ||
			VectorLengthFast(v2) < CTF_ATTACKER_PROTECT_RADIUS ||
			loc_CanSee(carrier, targ) || loc_CanSee(carrier, attacker) ) {
			match.scores[PLAYERNUM(attacker)].score += CTF_CARRIER_PROTECT_BONUS;
			teamlist[attacker->s.team].teamplayerscores += CTF_CARRIER_PROTECT_BONUS;
			G_PrintMsg( NULL, "%s%s defends the %s's flag carrier.\n",
				attacker->r.client->pers.netname, S_COLOR_GREY, 
				GS_TeamName(attacker->s.team) );

			ctfgame.playerstats[PLAYERNUM(attacker)].carrierdef++;
			return;
		}
	}
}


//==================
//G_Gametype_CTF_SetPowerUpEffect	//jalfixme: THIS ISN'T CTF
//==================
void G_Gametype_CTF_SetPowerUpEffect( edict_t *ent )
{
	ent->s.effects |= EF_QUAD;
}


//==================
//G_Gametype_CTF_Effects
//==================
void G_Gametype_CTF_Effects( edict_t *player )
{
	// to do
	if( game.gametype != GAMETYPE_CTF )
		return;

	// jalfixme: This code asumes there are only 2 ctf teams
	if( player->s.team == TEAM_BLUE && player->r.client->inventory[flagItems[TEAM_RED]->tag] )
		player->s.effects |= EF_ENEMY_FLAG;
	else if( player->s.team == TEAM_RED && player->r.client->inventory[flagItems[TEAM_BLUE]->tag] )
		player->s.effects |= EF_ENEMY_FLAG;
}

//==================
//G_Gametype_CTF_ResetFlag
//==================
void G_Gametype_CTF_ResetFlag( int team )
{
	edict_t *ent, *self;
	int		i;

	if( !flagItems[team] )
		return;

	ent = NULL;
	while( (ent = G_Find (ent, FOFS(classname), flagItems[team]->classname)) != NULL ) {
		if( ent->spawnflags & DROPPED_ITEM )
			G_FreeEdict(ent);
		else {
			ent->r.svflags &= ~SVF_NOCLIENT;
			ent->r.solid = SOLID_TRIGGER;

			// wsw : jal : we add the modelindex2 again, so it shows the flag on the pedal
			//ent->s.modelindex2 = trap_ModelIndex( ent->item->world_model[1] );
			ent->s.effects |= EF_ENEMY_FLAG; // flag is at base
			if( g_tctf->integer )
				ctfgame.flagResetTimer[ent->s.team] = 0;

			trap_LinkEntity( ent );
			G_AddEvent( ent, EV_ITEM_RESPAWN, 0, qtrue );
		}

		if( g_tctf->integer ) {
			// reset the timer trigger bonus points from all team members
			for( i = 0; teamlist[team].playerIndices[i] != -1; i++ ) {
				self = game.edicts + teamlist[team].playerIndices[i];
				ctfgame.playerstats[PLAYERNUM(self)].triggerpushed = 0;
			}
		}
	}
}

//==================
//G_Gametype_CTF_ResetFlags
//==================
void G_Gametype_CTF_ResetFlags( void )
{
	int i;
	for( i = TEAM_RED; i < GS_MAX_TEAMS; i++ )
		G_Gametype_CTF_ResetFlag(i);
}

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

	for( i = TEAM_RED; i < TEAM_RED + g_maxteams->integer; i++ ) {
		if( flagItems[i] == NULL )
			continue;

		if( ent->r.client->inventory[flagItems[i]->tag] ) {
			G_Gametype_CTF_ResetFlag(i);
			ent->r.client->inventory[flagItems[i]->tag] = 0;
			ent->s.effects &= ~EF_ENEMY_FLAG;
		}
	}
}

//==================
//G_Gametype_CTF_Pickup_Flag
//==================
qboolean G_Gametype_CTF_Pickup_Flag( edict_t *ent, edict_t *other )
{
	int ctf_team;
	int i;
	edict_t *player, *sendto, *compareto;
	gitem_t *flag_item, *enemy_flag_item;

	// figure out what team this flag is
	//flag team is simply ent->s.team (we set flag's team into it)
	ctf_team = ent->s.team;

	// same team, if the flag at base, check to he has the enemy flag
	if( ent->s.team == TEAM_RED ) {
		flag_item = flagItems[TEAM_RED];
		enemy_flag_item = flagItems[TEAM_BLUE];
	} else {
		flag_item = flagItems[TEAM_BLUE];
		enemy_flag_item = flagItems[TEAM_RED];
	}

	// it is our flag
	if( ent->s.team == other->s.team ) 
	{
		// it is not a dropped flag
		if( !(ent->spawnflags & DROPPED_ITEM) ) 
		{
			// player is a flag carrier
			if( other->r.client->inventory[enemy_flag_item->tag] ) 
			{
				 // and our flag is at base
				if( ent->s.effects & EF_ENEMY_FLAG ) 
				{
					for( sendto = game.edicts + 1; PLAYERNUM(sendto) < game.maxclients; sendto++ ) {
						if( !sendto->r.client || trap_GetClientState(PLAYERNUM(sendto)) < CS_SPAWNED )
							continue;

						if( sendto->r.client->chase.active )
							compareto = &game.edicts[sendto->r.client->chase.target];
						else
							compareto = sendto;

						if( compareto == other ) {
							G_AnnouncerSound( sendto, trap_SoundIndex(va(S_ANNOUNCER_CTF_SCORE_1_to_2, (rand()&1)+1)),
								GS_MAX_TEAMS, qtrue );
						} else if( compareto->s.team == other->s.team ) {
							G_AnnouncerSound( sendto, trap_SoundIndex(va(S_ANNOUNCER_CTF_SCORE_TEAM_1_to_2, (rand()&1)+1)),
								GS_MAX_TEAMS, qtrue );
						} else if( compareto->s.team != TEAM_SPECTATOR ) {
							G_AnnouncerSound( sendto, trap_SoundIndex(va(S_ANNOUNCER_CTF_SCORE_ENEMY_1_to_2, (rand()&1)+1)),
								GS_MAX_TEAMS, qtrue );
						}
						// fixme: no sound for floating spectator
					}

					//jalfixme: capture sounds for all teams
					if( other->s.team == TEAM_RED ) {
						//G_AnnouncerSound( NULL, trap_SoundIndex("sounds/teamplay/flagcap_red.wav"), GS_MAX_TEAMS, qtrue );
						G_PrintMsg( NULL, "%s%s captured the %s flag!\n",
							other->r.client->pers.netname, S_COLOR_RED, GS_TeamName(TEAM_BLUE) );
						G_CenterPrintMsg( other, "YOU CAPTURED THE FLAG! (%.2f)\n",
							(level.time - ctfgame.flagStolenTime[TEAM_BLUE]) );
					} else {
						//G_AnnouncerSound( NULL, trap_SoundIndex("sounds/teamplay/flagcap_blu.wav"), GS_MAX_TEAMS, qtrue );
						G_PrintMsg( NULL, "%s%s captured the %s flag!\n",
							other->r.client->pers.netname, S_COLOR_BLUE, GS_TeamName(TEAM_RED) );
						G_CenterPrintMsg( other, "YOU CAPTURED THE FLAG! (%.2f)\n",
							(level.time - ctfgame.flagStolenTime[TEAM_RED]) );
					}

					other->r.client->inventory[enemy_flag_item->tag] = 0;
					ctfgame.last_flag_capture = level.time;
					ctfgame.last_capture_team = other->s.team;
					teamlist[other->s.team].teamscore++;

					// other gets another 10 frag bonus
					match.scores[PLAYERNUM(other)].score += CTF_CAPTURE_BONUS;
					teamlist[other->s.team].teamplayerscores += CTF_CAPTURE_BONUS;
					ctfgame.playerstats[PLAYERNUM(other)].caps++;

					// Ok, let's do the player loop, hand out the bonuses
					for( i = 1; i <= game.maxclients; i++ ) {
						player = &game.edicts[i];
						if( !player->r.inuse )
							continue;

						if( player->s.team != other->s.team )
							ctfgame.playerstats[PLAYERNUM(player)].ctf_lasthurtcarrier = -5;

						else if( player->s.team == other->s.team ) {
							if( player != other ) {
								match.scores[PLAYERNUM(player)].score += CTF_TEAM_BONUS;
								teamlist[player->s.team].teamplayerscores += CTF_TEAM_BONUS;
							}
							// award extra points for capture assists
							if( ctfgame.playerstats[PLAYERNUM(player)].ctf_lastreturnedflag + CTF_RETURN_FLAG_ASSIST_TIMEOUT > level.time ) {
								G_PrintMsg( NULL, "%s%s gets an assist for returning the flag!\n", player->r.client->pers.netname, S_COLOR_GREY );
								match.scores[PLAYERNUM(player)].score += CTF_RETURN_FLAG_ASSIST_BONUS;
								teamlist[player->s.team].teamplayerscores += CTF_RETURN_FLAG_ASSIST_BONUS;
							}
							if( ctfgame.playerstats[PLAYERNUM(player)].ctf_lastfraggedcarrier + CTF_FRAG_CARRIER_ASSIST_TIMEOUT > level.time ) {
								G_PrintMsg( NULL, "%s%s gets an assist for fragging the flag carrier!\n", player->r.client->pers.netname, S_COLOR_GREY );
								match.scores[PLAYERNUM(player)].score += CTF_FRAG_CARRIER_ASSIST_BONUS;
								teamlist[player->s.team].teamplayerscores += CTF_FRAG_CARRIER_ASSIST_BONUS;
							}
							if( g_tctf->integer ) {
								if( ctfgame.playerstats[PLAYERNUM(player)].triggerpushed ) {
									G_PrintMsg( NULL, "%s%s gets an assist for extending the flag reset!\n", player->r.client->pers.netname, S_COLOR_GREY );
									match.scores[PLAYERNUM(player)].score += CTF_EXTEND_RESET_ASSIST_BONUS;
									teamlist[player->s.team].teamplayerscores += CTF_EXTEND_RESET_ASSIST_BONUS;
								}
							}
						}
					}

					G_Gametype_CTF_ResetFlags();
					return qfalse;
				} else { // it is a flag carrier, but our flag isn't at base (extend enemy timer)
					if( g_tctf->integer ) {
						int enemyteam = (ent->s.team==TEAM_RED)?TEAM_BLUE:TEAM_RED;
						ctfgame.flagResetTimerTouched[enemyteam] = qtrue;
					}

					return qfalse;
				}
			}
			return qfalse;
		}

		// hey, its not home.  return it by teleporting it back
		match.scores[PLAYERNUM(other)].score += CTF_RECOVERY_BONUS;
		teamlist[other->s.team].teamplayerscores += CTF_RECOVERY_BONUS;
		ctfgame.playerstats[PLAYERNUM(other)].ctf_lastreturnedflag = level.time;

		// different sound for recoverer/teammates/enemy
		
		for( sendto = game.edicts + 1; PLAYERNUM(sendto) < game.maxclients; sendto++ ) {
			if( !sendto->r.client || trap_GetClientState(PLAYERNUM(sendto)) < CS_SPAWNED )
				continue;

			if( sendto->r.client->chase.active )
				compareto = &game.edicts[sendto->r.client->chase.target];
			else
				compareto = sendto;

			if( compareto == other ) {
				G_AnnouncerSound( sendto, trap_SoundIndex(va(S_ANNOUNCER_CTF_RECOVERY_1_to_2, (rand()&1)+1)),
					GS_MAX_TEAMS, qtrue );
			} else if( compareto->s.team == ent->s.team ) {
				G_AnnouncerSound( sendto, trap_SoundIndex(S_ANNOUNCER_CTF_RECOVERY_TEAM), GS_MAX_TEAMS, qtrue );
			} else if( compareto->s.team != TEAM_SPECTATOR ) {
				// fixme: only play this sound if YOUR team had the enemy flag that was returned
				G_AnnouncerSound( sendto, trap_SoundIndex(S_ANNOUNCER_CTF_RECOVERY_ENEMY), GS_MAX_TEAMS, qtrue );
			}
			// fixme: no sound for floating spectator
		}

		//JALFIXME!!: need sounds for all 4 teams
		if( ent->s.team == TEAM_RED ) {
			//G_AddAnnouncerEvent( NULL, trap_SoundIndex("sounds/teamplay/flagret_red.wav"), GS_MAX_TEAMS );
			G_PrintMsg( NULL, "%s%s returned the %s flag!\n", 
				other->r.client->pers.netname, S_COLOR_RED, GS_TeamName(ent->s.team) );
		} else {
			//G_AddAnnouncerEvent( NULL, trap_SoundIndex("sounds/teamplay/flagret_blu.wav"), GS_MAX_TEAMS );
			G_PrintMsg( NULL, "%s%s returned the %s flag!\n", 
				other->r.client->pers.netname, S_COLOR_BLUE, GS_TeamName(ent->s.team) );
		}

		//ResetFlag will remove this entity!  We must return false
		G_Gametype_CTF_ResetFlag( ent->s.team );
		return qfalse;
	}

	// hey, its not our flag, pick it up

	// hey, it's not even a flag, but an empty base
	if( g_tctf->integer ) {
		// reset flag auto-return timer
		if( !(ent->s.effects & EF_ENEMY_FLAG) ) { // we are touching a empty flag base
			ctfgame.flagResetTimerTouched[ent->s.team] = qtrue;
			if( !other->r.client->inventory[flag_item->tag] ) { // bonus only if you are not the carrier
				ctfgame.playerstats[PLAYERNUM(other)].triggerpushed += game.framemsec;
			} else {
				ctfgame.flagStolenTime[ent->s.team] = level.time;
				ctfgame.flagResetTimerTouchedByFlagCarrier[ent->s.team] = qtrue; // flag carrier resets it right away
			}

			return qfalse;
		}
	}

	// pick it up

	other->r.client->inventory[flag_item->tag] = 1;
	ctfgame.playerstats[PLAYERNUM(other)].ctf_flagsince = level.time;
	ctfgame.playerstats[PLAYERNUM(other)].triggerpushed = 0;

	for( sendto = game.edicts + 1; PLAYERNUM(sendto) < game.maxclients; sendto++ ) {
		if( !sendto->r.client || trap_GetClientState(PLAYERNUM(sendto)) < CS_SPAWNED )
			continue;

		if( sendto->r.client->chase.active )
			compareto = &game.edicts[sendto->r.client->chase.target];
		else
			compareto = sendto;

		if( compareto == other ) {
			G_AnnouncerSound( sendto, trap_SoundIndex(S_ANNOUNCER_CTF_FLAG_TAKEN), GS_MAX_TEAMS, qtrue );
		} else if( compareto->s.team == other->s.team ) {
			G_AnnouncerSound( sendto, trap_SoundIndex(va(S_ANNOUNCER_CTF_FLAG_TAKEN_TEAM_1_to_2, (rand()&1)+1)),
				GS_MAX_TEAMS, qtrue );
		} else if( compareto->s.team == ent->s.team ) {
			G_AnnouncerSound( sendto, trap_SoundIndex(va(S_ANNOUNCER_CTF_FLAG_TAKEN_ENEMY_1_to_2, (rand()&1)+1)),
				GS_MAX_TEAMS, qtrue );
		}
		// fixme: no sound for floating spectator
	}

	//JALFIXME: more sounds needed
	if( ent->s.team == TEAM_RED ) {
		//G_AddAnnouncerEvent( NULL, trap_SoundIndex("sounds/teamplay/flagtk_red.wav"), GS_MAX_TEAMS );
		G_PrintMsg( NULL, "%s%s got the %s flag!\n",
			other->r.client->pers.netname, S_COLOR_BLUE, GS_TeamName(ent->s.team) );
	} else {
		//G_AddAnnouncerEvent( NULL, trap_SoundIndex("sounds/teamplay/flagtk_blu.wav"), GS_MAX_TEAMS );
		G_PrintMsg( NULL, "%s%s got the %s flag!\n",
			other->r.client->pers.netname, S_COLOR_RED, GS_TeamName(ent->s.team) );
	}

	// pick up the flag
	// if it's not a dropped flag, we just make it disappear
	// if it's dropped, it will be removed by the pickup caller
	if( !(ent->spawnflags & DROPPED_ITEM) ) {
		if( g_tctf->integer ) { // first time set the full timer
			ctfgame.flagResetTimer[ent->s.team] = level.timemsec + (ent->wait * 1000);
			ctfgame.flagStolenTime[ent->s.team] = level.time;
		} else {  // in timed ctf style the base remains touchable
			ent->flags |= FL_RESPAWN;
			ent->r.solid = SOLID_NOT;
		}
		ent->s.effects &= ~EF_ENEMY_FLAG; // flag is not at base anymore
	}

	return qtrue;
}

//==================
//G_Gametype_CTF_DropFlagTouch
//==================
static void G_Gametype_CTF_DropFlagTouch( edict_t *ent, edict_t *other, cplane_t *plane, int surfFlags )
{
	//owner (who dropped us) can't touch for two secs
	if (other == ent->r.owner && 
		ent->nextthink - level.timemsec > (CTF_AUTO_FLAG_RETURN_TIMEOUT-2)*1000)
		return;

	Touch_Item( ent, other, plane, surfFlags );
}

//==================
//G_Gametype_CTF_DropFlagThink
//==================
static void G_Gametype_CTF_DropFlagThink( edict_t *ent )
{
	int	team = ent->s.team;
	
	G_Gametype_CTF_ResetFlag(team);
	G_PrintMsg( NULL, "%sThe %s flag has returned!\n",
		S_COLOR_GREY, GS_TeamName(team) );
}

//==================
//G_Gametype_CTF_Drop_Flag
//==================
void G_Gametype_CTF_Drop_Flag( edict_t *ent, gitem_t *item )
{
	edict_t *dropped = NULL;
	int		team;

	if( !item || !(item->type & IT_FLAG) )
		return;

	// find the team
	for( team = TEAM_RED; team < TEAM_RED + g_maxteams->integer; team++ )
	{
		if( flagItems[team] == NULL )
			continue;

		if( flagItems[team] == item && ent->r.client->inventory[item->tag] ) 
		{
			dropped = Drop_Item( ent, item );
			if( dropped ) {
				// dropped flags don't have any model, just the "add flag model" bitflag
				dropped->s.effects |= EF_ENEMY_FLAG;
				dropped->s.modelindex = 0;
				dropped->s.modelindex2 = 0;

				dropped->s.team = team;
				dropped->think = G_Gametype_CTF_DropFlagThink;
				dropped->nextthink = level.timemsec + 1000 * CTF_AUTO_FLAG_RETURN_TIMEOUT;
				dropped->touch = G_Gametype_CTF_DropFlagTouch;

				ent->r.client->inventory[item->tag] = 0;
				ent->s.effects &= ~EF_ENEMY_FLAG;
			}
		}
	}
}


//==================
//G_Gametype_CTF_DeadDropFlag
//==================
// Called from PlayerDie, to drop the flag from a dying player
void G_Gametype_CTF_DeadDropFlag( edict_t *ent )
{
	int		team;

	for( team = TEAM_RED; team < TEAM_RED + g_maxteams->integer; team++ )
	{
		if( flagItems[team] != NULL && ent->r.client->inventory[flagItems[team]->tag] ) 
		{
			G_Gametype_CTF_Drop_Flag( ent, flagItems[team] );
			if( !ent->r.client->inventory[flagItems[team]->tag] ) {
				G_PrintMsg( NULL, "%s%s lost the %s flag!\n",
					ent->r.client->pers.netname, S_COLOR_GREY, GS_TeamName(team) );
			}
		}
	}
}

//==================
//G_Gametype_CTF_UpdatedFlagBases
// Each frame we must check for autoreset countdowns, and add their effects if needed
//==================
void G_Gametype_CTF_UpdatedFlagBases( void )
{
	int team, countdown;
	edict_t	*flag, *ent, *sendto, *compareto;

	if( !g_tctf->integer )
		return;

	for( team = TEAM_RED; team < TEAM_RED + g_maxteams->integer; team++ ) 
	{
		flag = NULL;
		while( (flag = G_Find(flag, FOFS(classname), flagItems[team]->classname)) != NULL )
		{
			// flag base without a flag
			if( flag->s.type == ET_FLAG_BASE && !(flag->s.effects & EF_ENEMY_FLAG) )
			{
				// check for timer being modified
				if( ctfgame.flagResetTimerTouched[team] ) 
				{
					if( ctfgame.flagResetTimerTouchedByFlagCarrier[team] ) { // reset
						ctfgame.flagResetTimer[team] = level.timemsec + (flag->wait * 1000);
					} else { // count up
						if( ctfgame.flagResetTimer[team] < level.timemsec + 2000 )
							ctfgame.flagResetTimer[team] = level.timemsec + 2000 - game.framemsec;
						// add extra time
						ctfgame.flagResetTimer[team] += game.framemsec*(1+3); // 1 for the time spent, 3 for the extra time we add
						//clamp to full
						if( ctfgame.flagResetTimer[team] > level.timemsec + (flag->wait * 1000) )
							ctfgame.flagResetTimer[team] = level.timemsec + (flag->wait * 1000);
					}
				}

				// check for flag auto-returns. When countdown is finished, send flag back to base
				if( ctfgame.flagResetTimer[team] && ctfgame.flagResetTimer[team] <= level.timemsec ) {
					// remove it from the players
					for( ent = game.edicts + 1; PLAYERNUM(ent) < game.maxclients; ent++ ) {
						if( !ent->r.inuse || !ent->r.client )
							continue;
						if( ent->r.client->inventory[flagItems[team]->tag] ) {
							ent->r.client->inventory[flagItems[team]->tag] = 0;
							ent->s.effects &= ~EF_ENEMY_FLAG;
							break;
						}
					}
					// put it at base
					G_Gametype_CTF_ResetFlag( team );

					for( sendto = game.edicts + 1; PLAYERNUM(sendto) < game.maxclients; sendto++ ) {
						if( !sendto->r.client || trap_GetClientState(PLAYERNUM(sendto)) < CS_SPAWNED )
							continue;

						if( sendto->r.client->chase.active )
							compareto = &game.edicts[sendto->r.client->chase.target];
						else
							compareto = sendto;

						if( compareto->s.team == team ) {
							G_AnnouncerSound( sendto, trap_SoundIndex(S_ANNOUNCER_CTF_RECOVERY_TEAM), GS_MAX_TEAMS,
								qtrue );
						} else if( compareto->s.team != TEAM_SPECTATOR ) {
							// fixme: only play this if your team had the enemy flag?
							G_AnnouncerSound( sendto, trap_SoundIndex(S_ANNOUNCER_CTF_RECOVERY_ENEMY), GS_MAX_TEAMS,
								qtrue );
						}
						// fixme: no sound for floating spectator
					}

					// fixme: need prints for all 4 teams
					if( team == TEAM_RED ) {
						G_PrintMsg( NULL, "%s%s flag returned!\n", S_COLOR_RED, GS_TeamName(team) );
					} else {
						G_PrintMsg( NULL, "%s%s flag returned!\n", S_COLOR_BLUE, GS_TeamName(team) );
					}

					/*if( team == TEAM_RED ) {
						G_AddAnnouncerEvent( NULL, trap_SoundIndex("sounds/teamplay/flagret_red.wav"), GS_MAX_TEAMS );
					} else {
						G_AddAnnouncerEvent( NULL, trap_SoundIndex("sounds/teamplay/flagret_blu.wav"), GS_MAX_TEAMS );
					}*/
				}
				// countdown is active // Add effects on flag_base entities
				else if( ctfgame.flagResetTimer[team] > level.timemsec ) {
					countdown = (int)( (ctfgame.flagResetTimer[team] - level.timemsec) * 0.001f );
					clamp( countdown, 0, 98 );
					flag->s.modelindex2 = countdown + 1;
				}
			}
		}

		// reset touched state for this team
		ctfgame.flagResetTimerTouched[team] = qfalse;
		ctfgame.flagResetTimerTouchedByFlagCarrier[team] = qfalse;
	}
}

//==================
//G_Gametype_CTF_FlagSetup
//==================
void G_Gametype_CTF_FlagSetup( edict_t *ent )
{
	trace_t		tr;
	vec3_t		dest;
	int			i;

	VectorCopy( item_box_mins, ent->r.mins );
	VectorCopy( item_box_maxs, ent->r.maxs );

	//if( ent->model ) {
	//	ent->s.modelindex = trap_ModelIndex( ent->model );
	//} else {
		ent->s.modelindex = trap_ModelIndex( ent->item->world_model[0] );
		ent->s.modelindex2 = trap_ModelIndex( ent->item->world_model[1] );
	//}

	// this is the flag base, they use special drawing
	ent->s.type = ET_FLAG_BASE;
	ent->s.effects |= EF_ENEMY_FLAG; // wsw : flag is at base

	ent->r.solid = SOLID_TRIGGER;
	ent->movetype = MOVETYPE_TOSS;  
	ent->touch = Touch_Item;

	if( g_tctf->integer ) {
		if( !ent->wait ) {
			// temp hack for wctf1
			if( !Q_stricmp( "wctf1", level.mapname ) )
				ent->wait = 8;
			else
				ent->wait = CTF_AUTO_FLAG_RESET_COUNTDOWN;
		}
	}

	ent->s.origin[2] += 1;
	VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 128 );
	trap_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent, MASK_SOLID );
	if( tr.startsolid )
	{
		G_Printf( "CTFFlagSetup: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin) );
		G_FreeEdict(ent);
		return;
	}

	VectorCopy( tr.endpos, ent->s.origin );

	//set up teamgame info
	for( i = TEAM_RED; i < GS_MAX_TEAMS; i++ ) {
		if( flagItems[i] == NULL )
			continue;

		if( flagItems[i] == ent->item )
			ent->s.team = i;
	}
	//G_Printf( "%sSpawned a flag for %s team\n", S_COLOR_RED, GS_TeamName(ent->s.team) );

	if( !ent->s.team )
		G_Error( "G_Gametype_CTF_FlagSetup found a flag without a team\n" );

	trap_LinkEntity(ent);
}

//
//==================
//G_Gametype_CTF_ScoreboardMessage
//==================
//
char *G_Gametype_CTF_ScoreboardMessage( edict_t *ent, edict_t *killer )
{
	char			entry[MAX_TOKEN_CHARS];
	unsigned int	len;
	int				i, team;
	edict_t			*e;

	//fixed layout scheme id
	Q_snprintfz(scoreboardString, sizeof(scoreboardString), "scb \"&ctfs " ); 
	len = strlen(scoreboardString);

	//the players are in score order in the team lists
	for( team = TEAM_RED; team < TEAM_RED + g_maxteams->integer; team++ )
	{
		//team tab entry
		*entry = 0;
		Q_snprintfz(entry, sizeof(entry), "&t %i %i %i ",
			team,
			teamlist[team].teamplayerscores,
			teamlist[team].teamscore);
		
		if( SCOREBOARD_MSG_MAXSIZE - len > strlen(entry) ) {
			Q_strncatz(scoreboardString, entry, sizeof(scoreboardString));
			len = strlen(scoreboardString);
		}
		
		//players
		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 ",
				PLAYERNUM(e),
				match.scores[PLAYERNUM(e)].score, 
				e->r.client->r.ping > 999 ? 999 : e->r.client->r.ping,
				match.ready[PLAYERNUM(e)]
				);
			
			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_CTF_ClientBegin
// client just finished connecting to the server
//=================
void G_Gametype_CTF_ClientBegin( edict_t *ent )
{
	ClientBeginMultiplayerGame(ent);
}

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

	if( game.gametype != GAMETYPE_CTF )
		return;

	G_GameType_ClientHealthRule();
#ifdef ARMOR_SYSTEM_OF_THE_DAY
	G_GameType_ClientArmorDecayRule();
#endif
	G_Teams_UpdateTeamTabMessages();

	G_Gametype_CTF_UpdatedFlagBases();

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


