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

#define CTF_TEAM_COND( team, alpha, beta, other ) ( ( team == TEAM_ALPHA ) ? alpha : ( ( team == TEAM_BETA ) ? beta : other ) )
#define CTF_ENEMYTEAM( team )	  CTF_TEAM_COND( team, TEAM_BETA, TEAM_ALPHA, TEAM_SPECTATOR )
#define CTF_COLOR_ALPHA		S_COLOR_RED
#define CTF_COLOR_BETA		S_COLOR_BLUE
#define CTF_TEAMCOLOR( team )	  CTF_TEAM_COND( team, CTF_COLOR_ALPHA, CTF_COLOR_BETA, "" )
#define CTF_ENEMYTEAMCOLOR( team )    CTF_TEAM_COND( team, CTF_COLOR_BETA, CTF_COLOR_ALPHA, "" )
//#define CTF_ENEMYTEAM(team)	( ( team == TEAM_ALPHA ) ? TEAM_BETA   : ( (team == TEAM_BETA) ? TEAM_ALPHA   : TEAM_SPECTATOR ) )
//#define CTF_TEAMCOLOR(team)	( ( team == TEAM_ALPHA ) ? S_COLOR_RED : ( (team == TEAM_BETA) ? S_COLOR_BLUE : ""             ) )
#define FLAG_EXTRA_BOXSIZE 16   // Extra size for the flag model

#define SPAWNFLAG_ONLY_CARRIER 1
#define SPAWNFLAG_ONLY_NOT_CARRIER 2

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

} ctfstats_t;
typedef struct ctf_player_timers_s
{
	unsigned int newtouch; // Set to the current level.time when the flag is touched
	unsigned int lasttouch; // Keeps the previous level.time when the flag was touched
	unsigned int inittouch; // Set to the initial time the flag was touched
	int team;           // team to which this player belongs
} ctf_player_timers_t;

typedef struct
{
	int last_capture_team;

	ctfstats_t playerstats[MAX_CLIENTS];

	unsigned int flagStolenTime[GS_MAX_TEAMS];
	unsigned int flagResetTimer[GS_MAX_TEAMS];
	unsigned int flagFrozenTimer[GS_MAX_TEAMS];

	// KoFFiE: Added these for the new CTF gametypes
	ctf_player_timers_t ut[MAX_CLIENTS]; // Unlock timers for each gametype
	ctf_player_timers_t ft[GS_MAX_TEAMS]; // Flag carrier times

	qboolean flagResetTimerTouched[GS_MAX_TEAMS];
	qboolean flagResetTimerTouchedByFlagCarrier[GS_MAX_TEAMS];
} ctfgame_t;


static edict_t *flagEnts[GS_MAX_TEAMS];
static gitem_t *flagItems[GS_MAX_TEAMS];
static ctfgame_t ctfgame;

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

//=================
//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		3   // 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	2   // 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	    8

/* SPAWNS */

//QUAKED SP_team_CTF_alphaspawn (1 0 0) (-16 -16 -24) (16 16 32)
//potential team1 spawning position for ctf games
void SP_team_CTF_alphaspawn( edict_t *self )
{
	G_DropSpawnpointToFloor( self );
}

//QUAKED SP_team_CTF_betaspawn (0 0 1) (-16 -16 -24) (16 16 32)
//potential team2 spawning position for ctf games
void SP_team_CTF_betaspawn( edict_t *self )
{
	G_DropSpawnpointToFloor( self );
}

//QUAKED SP_team_CTF_gammaspawn (0 0 1) (-16 -16 -24) (16 16 32)
//potential team3 spawning position for ctf games
void SP_team_CTF_gammaspawn( edict_t *self )
{
	G_DropSpawnpointToFloor( self );
}

//QUAKED SP_team_CTF_deltaspawn (1 0 0) (-16 -16 -24) (16 16 32)
//potential team4 spawning position for ctf games
void SP_team_CTF_deltaspawn( edict_t *self )
{
	G_DropSpawnpointToFloor( self );
}

//QUAKED SP_team_CTF_alphaplayer (1 0 0) (-16 -16 -24) (16 16 32)
//potential team1 spawning position for ctf games
void SP_team_CTF_alphaplayer( edict_t *self )
{
	G_DropSpawnpointToFloor( self );
}

//QUAKED SP_team_CTF_betaplayer (0 0 1) (-16 -16 -24) (16 16 32)
//potential team2 spawning position for ctf games
void SP_team_CTF_betaplayer( edict_t *self )
{
	G_DropSpawnpointToFloor( self );
}

//QUAKED SP_team_CTF_gammaplayer (0 0 1) (-16 -16 -24) (16 16 32)
//potential team3 spawning position for ctf games
void SP_team_CTF_gammaplayer( edict_t *self )
{
	G_DropSpawnpointToFloor( self );
}

//QUAKED SP_team_CTF_deltaplayer (1 0 0) (-16 -16 -24) (16 16 32)
//potential team4 spawning position for ctf games
void SP_team_CTF_deltaplayer( edict_t *self )
{
	G_DropSpawnpointToFloor( self );
}

/* SPAWNS: Q3 and 0.21 compatibility */

//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 )
{
	self->classname = "team_CTF_alphaspawn";
	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 )
{
	self->classname = "team_CTF_betaspawn";
	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 )
{
	self->classname = "team_CTF_alphaplayer";
	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 )
{
	self->classname = "team_CTF_betaplayer";
	G_DropSpawnpointToFloor( self );
}

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

//go to a ctf point, but NOT the two points closest
//to other players
//================
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_ALPHA:
			cname = "team_CTF_alphaplayer";
			break;
		case TEAM_BETA:
			cname = "team_CTF_betaplayer";
			break;
		case TEAM_GAMMA:
			cname = "team_CTF_gammaplayer";
			break;
		case TEAM_DELTA:
			cname = "team_CTF_deltaplayer";
			break;
		default:
			break;
		}
	}
	if( !cname )
	{
		switch( ent->s.team )
		{
		case TEAM_ALPHA:
			cname = "team_CTF_alphaspawn";
			break;
		case TEAM_BETA:
			cname = "team_CTF_betaspawn";
			break;
		case TEAM_GAMMA:
			cname = "team_CTF_gammaspawn";
			break;
		case TEAM_DELTA:
			cname = "team_CTF_deltaspawn";
			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
//
//==========================================================

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

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

	if( activator->s.team < TEAM_ALPHA && activator->s.team > TEAM_BETA )
		return;

	flag_team = ( activator->s.team == TEAM_ALPHA ) ? TEAM_BETA : TEAM_ALPHA;
	if( flag_team != self->s.team )
		return;

	if( !flagItems[flag_team] )
		return;

	// he is the flag carrier?
	if( activator->r.client->inventory[flagItems[flag_team]->tag] )
	{
		if( self->spawnflags & SPAWNFLAG_ONLY_NOT_CARRIER )
			return;
	}
	else if( self->spawnflags & SPAWNFLAG_ONLY_CARRIER )
		return;

	// "other" is the trigger that fired the target, and it contains the time lapse to be refired
	if( other->wait <= 0 )
	{
		G_Printf( "target_freeze_flag_countdown: invalid wait value on activation trigger\n" );
		lapse = 0;
	}
	else
	{
		lapse = ( other->wait * 1000 ) + game.snapFrameTime;
	}

	// 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.time + (flag->wait * 1000);
				// this target entity freezes the countdown and adds up slowly a little bit ammount of time
				ctfgame.flagResetTimerTouched[flag_team] = qtrue;
				ctfgame.flagFrozenTimer[flag_team] = level.time + lapse;
			}
		}
	}
}

//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( game.gametype != GAMETYPE_CTF || !G_CTF_TIMER )
	{
		G_FreeEdict( self );
		return;
	}

	if( !st.gameteam || ( st.gameteam < TEAM_ALPHA && st.gameteam >= GS_MAX_TEAMS ) )
	{
		G_Printf( "SP_target_reset_flag_countdown with a invalid gameteam value\n" );
		G_FreeEdict( self );
		return;
	}

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


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

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

	if( activator->s.team < TEAM_ALPHA && activator->s.team > TEAM_BETA )
		return;

	flag_team = ( activator->s.team == TEAM_ALPHA ) ? TEAM_BETA : TEAM_ALPHA;
	if( flag_team != self->s.team )
		return;

	if( !flagItems[flag_team] )
		return;

	// he is the flag carrier?
	if( activator->r.client->inventory[flagItems[flag_team]->tag] )
	{
		if( self->spawnflags & SPAWNFLAG_ONLY_NOT_CARRIER )
			return;
	}
	else if( self->spawnflags & SPAWNFLAG_ONLY_CARRIER )
		return;

	// "other" is the trigger that fired the target, and it contains the time lapse to be refired
	if( other->wait <= 0 )
	{
		G_Printf( "target_freeze_flag_countdown: invalid wait value on activation trigger\n" );
		lapse = 0;
	}
	else
	{
		lapse = ( other->wait * 1000 ) + game.snapFrameTime;
	}

	// 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.flagFrozenTimer[flag_team] = level.time + lapse;
			}
		}
	}
}

//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( game.gametype != GAMETYPE_CTF || !G_CTF_TIMER )
	{
		G_FreeEdict( self );
		return;
	}

	if( !st.gameteam || ( st.gameteam < TEAM_ALPHA && st.gameteam >= GS_MAX_TEAMS ) )
	{
		G_Printf( "SP_target_freeze_flag_countdown with a invalid gameteam value\n" );
		G_FreeEdict( self );
		return;
	}

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

	G_Printf( "SP_target_freeze_flag_countdown\n" );
}


//==================
//G_Gametype_CTF_CleanUpPlayerStats
//==================
void G_Gametype_CTF_CleanUpPlayerStats( edict_t *ent )
{
	memset( &ctfgame.playerstats[PLAYERNUM( ent )], 0, sizeof( ctfstats_t ) );
	// KoFFiE: Reset player unlock timer
	memset( &ctfgame.ut[PLAYERNUM( ent )], 0, sizeof( ctfgame.ut[0] ) );
}

//==================
//G_Gametype_CTF_NewMap
//==================
void G_Gametype_CTF_NewMap( void )
{
	int team;
	//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_ALPHA] = GS_FindItemByClassname( "team_CTF_alphaflag" );
	flagItems[TEAM_BETA] = GS_FindItemByClassname( "team_CTF_betaflag" );
	flagItems[TEAM_GAMMA] = GS_FindItemByClassname( "team_CTF_deltaflag" );
	flagItems[TEAM_DELTA] = GS_FindItemByClassname( "team_CTF_gammaflag" );

	for( team = TEAM_SPECTATOR; team <= TEAM_DELTA; team++ )
	{
		flagEnts[team] = NULL;
		if( flagItems[team] == NULL )
		{
			flagEnts[team] = NULL;
			continue;
		}
		flagEnts[team] = G_Find( flagEnts[team], FOFS( classname ), flagItems[team]->classname );
	}
}

//==================
//G_Gametype_CTF_Init
//==================
void G_Gametype_CTF_Init( void )
{
	if( g_ctf_timer->integer < 0 )  // validate
		trap_Cvar_SetValue( "g_ctf_timer", 0 );
#ifdef WSW_CTF_CVARS    // KoFFiE: cvars for the CTF capture and unlock timers
	if( g_ctf_capture_time->value < 0 )
		trap_Cvar_SetValue( "g_ctf_capture_time", WSW_CTF_CAPTURE_TIME_DEFAULT );
	if( g_ctf_unlock_time->value < 0 )
		trap_Cvar_SetValue( "g_ctf_unlock_time", WSW_CTF_UNLOCK_TIME_DEFAULT );
	if( g_ctf_freeze_time->integer < 0 )
		trap_Cvar_SetValue( "g_ctf_freeze_time", WSW_CTF_FREEZE_TIME_DEFAULT );
#endif
}

//==================
//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_ALPHA || team >= TEAM_ALPHA + g_maxteams->integer )
		return 0;

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

	return 0;
}

//==================
//G_Gametype_CTF_FlagStatus
//==================
#define RETURN_FLAGSTATUS( state, timer_ms, max_ms )  ( ( state ) | ( ( ( !max_ms ) ? 0 : (int)( ( (double)( timer_ms ) / (double)( max_ms ) ) * 100 ) ) & FLAG_STATE_TIMERMASK ) )
int G_Gametype_CTF_FlagStatus( int team, int playernr )
{
	int i, pl, plb;
	edict_t *ent, *flag;
	unsigned int mint, mintb, plt, tmp, lvt;
	int enemyteam = CTF_ENEMYTEAM( team );
	short ret;
	assert( team >= TEAM_ALPHA && team < TEAM_ALPHA + g_maxteams->integer );
	assert( enemyteam >= TEAM_ALPHA && enemyteam < TEAM_ALPHA + g_maxteams->integer );
	//lvt = (level.time / 50) * 50; // only work in 50 ms
	lvt = level.time;
	// Flag stolen detection -> No timer?
	for( i = 0; i < game.maxclients; i++ )
	{
		ent = PLAYERENT( i );
		if( ( ent->r.inuse ) && ( ent->r.client->inventory[flagItems[team]->tag] ) )
		{
			ret = FLAG_STATE_STOLEN;
			if( ( WSW_CTF_CAPTURETIME ) && ( ctfgame.ft[enemyteam].inittouch ) )
			{
				if( ( VALID_TIMER( ctfgame.ft[enemyteam].lasttouch ) ) &&
				   ( ( ctfgame.ft[enemyteam].inittouch + WSW_CTF_CAPTURETIME ) > lvt ) )
				{
					// Capturing
					ret = RETURN_FLAGSTATUS( FLAG_STATE_CAPTURING, WSW_CTF_CAPTURETIME - ( lvt - ctfgame.ft[enemyteam].inittouch ), WSW_CTF_CAPTURETIME );
				}
				else if( VALID_TIMER( ctfgame.ft[enemyteam].lasttouch + WSW_CTF_FREEZETIME ) )
				{
					// Freeze
					tmp = WSW_CTF_CAPTURETIME - ( ctfgame.ft[enemyteam].lasttouch -  ctfgame.ft[enemyteam].inittouch );
					return ( FLAG_STATE_SELF_MASK | RETURN_FLAGSTATUS( FLAG_STATE_STOLEN, tmp, WSW_CTF_CAPTURETIME ) );
				}
				else if( ( ctfgame.ft[enemyteam].lasttouch + WSW_CTF_FREEZETIME + ( ctfgame.ft[enemyteam].lasttouch - ctfgame.ft[enemyteam].inittouch ) ) >= lvt )
				{
					// Count down
					tmp = WSW_CTF_CAPTURETIME - ( ( ctfgame.ft[enemyteam].lasttouch + WSW_CTF_FREEZETIME + ( ctfgame.ft[enemyteam].lasttouch - ctfgame.ft[enemyteam].inittouch ) ) - lvt );
					return ( FLAG_STATE_SELF_MASK | RETURN_FLAGSTATUS( FLAG_STATE_STOLEN, tmp, WSW_CTF_CAPTURETIME ) );
				}
			}
			else if( ctfgame.flagResetTimer[team] )
			{
				// Reset timer counting down...
				flag = NULL;
				while( ( flag = G_Find( flag, FOFS( classname ), flagItems[team]->classname ) ) == NULL )
				{
				}
				;
				ret = RETURN_FLAGSTATUS( FLAG_STATE_STOLEN_TIMER, ctfgame.flagResetTimer[team] - lvt, ( flag->wait * 1000 ) );
			}
			if( i == playernr )
				ret |= FLAG_STATE_SELF_MASK;

			return ret;
		}
	}

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

	if( WSW_CTF_UNLOCKTIME )
	{
		// Detect unlocking state -> Get client to first unlock the timer
		mint  = lvt + WSW_CTF_UNLOCKTIME + 10000;
		mintb = lvt + WSW_CTF_UNLOCKTIME + WSW_CTF_FREEZETIME + 10000;
		pl  = -1;
		plb = -1;
		plt = 0;
		for( i = 0; i < game.maxclients; i++ )
		{
			if( ctfgame.ut[i].team != team )
			{
				continue;
			}
			if( VALID_TIMER( ctfgame.ut[i].lasttouch ) &&
			   ( ( ctfgame.ut[i].inittouch + WSW_CTF_UNLOCKTIME ) > lvt ) &&
			   ( ctfgame.ut[i].inittouch < mint ) )
			{
				mint = ctfgame.ut[i].inittouch;
				pl = i;
			}
			if( ( ctfgame.ut[i].inittouch ) && ( ctfgame.ut[i].lasttouch ) &&
			   ( ( ctfgame.ut[i].lasttouch + WSW_CTF_FREEZETIME + ( ctfgame.ut[i].lasttouch - ctfgame.ut[i].inittouch ) ) > lvt )
			    )
			{
				mintb = ctfgame.ut[i].inittouch;
				plb = i;
			}
		}
		if( ( pl >= 0 ) && ( ( ctfgame.ut[pl].inittouch + WSW_CTF_UNLOCKTIME ) > lvt ) )
		{
			// Flag being unlocked!
			ret = RETURN_FLAGSTATUS( FLAG_STATE_UNLOCKING, WSW_CTF_UNLOCKTIME - ( ctfgame.ut[pl].lasttouch - ctfgame.ut[pl].inittouch ), WSW_CTF_UNLOCKTIME );
			if( pl == playernr )
				ret |= FLAG_STATE_SELF_MASK;
			return ret;
		}
		else if( plb >= 0 )
		{
			if( ( ctfgame.ut[plb].lasttouch + WSW_CTF_FREEZETIME ) > lvt )
			{
				// Freeze
				tmp = WSW_CTF_UNLOCKTIME - ( ctfgame.ut[plb].lasttouch -  ctfgame.ut[plb].inittouch );
				//G_Printf("WAIT: (%ld + %ld = %ld) > %ld - %ld\n", ctfgame.ut[plb].lasttouch, WSW_CTF_FREEZETIME, ( ctfgame.ut[plb].lasttouch + WSW_CTF_FREEZETIME ), lvt, tmp);
				return ( FLAG_STATE_SELF_MASK | RETURN_FLAGSTATUS( 0, tmp, WSW_CTF_UNLOCKTIME ) );
			}
			else if( ( ctfgame.ut[plb].lasttouch + WSW_CTF_FREEZETIME + ( ctfgame.ut[plb].lasttouch - ctfgame.ut[plb].inittouch ) ) >= lvt )
			{
				// Count down
				tmp = WSW_CTF_UNLOCKTIME - ( ( ctfgame.ut[plb].lasttouch + WSW_CTF_FREEZETIME + ( ctfgame.ut[plb].lasttouch - ctfgame.ut[plb].inittouch ) ) - lvt );
				//G_Printf("LVT : %ld / INIT: %ld / LAST: %ld / DEADLINE: %ld\n", lvt, ctfgame.ut[plb].inittouch,ctfgame.ut[plb].lasttouch  , tmp );
				return ( FLAG_STATE_SELF_MASK | RETURN_FLAGSTATUS( 0, tmp, WSW_CTF_UNLOCKTIME ) );
			}
		}
	}
	return FLAG_STATE_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++ )
	{
		G_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_ALPHA || 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 );
		G_AwardFragFlagCarrier( attacker );

		// the target had the flag, clear the hurt carrier
		// field on the other team
		for( i = 0; i < game.maxclients; i++ )
		{
			ent = PLAYERENT( i );
			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 &&
	   ctfgame.playerstats[PLAYERNUM( targ )].ctf_lasthurtcarrier + CTF_CARRIER_DANGER_PROTECT_TIMEOUT > level.time &&
	   !( 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 ) );
		G_AwardDefendFlagCarrier( attacker );

		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 = PLAYERENT( i );
		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 ) );

		G_AwardDefendFlagBase( attacker );
		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 ) );
			G_AwardDefendFlagCarrier( attacker );

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

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

	player->s.effects &= ~EF_ENEMY_FLAG;
	// jalfixme: This code asumes there are only 2 ctf teams
	if( player->s.team == TEAM_BETA && player->r.client->inventory[flagItems[TEAM_ALPHA]->tag] )
		player->s.effects |= EF_ENEMY_FLAG;
	else if( player->s.team == TEAM_ALPHA && player->r.client->inventory[flagItems[TEAM_BETA]->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_CTF_TIMER )
			{
				ctfgame.flagResetTimer[ent->s.team] = 0;
				ctfgame.flagFrozenTimer[ent->s.team] = 0;
			}

			if( WSW_CTF_CAPTURETIME )
			{
				memset( &ctfgame.ft[team], 0, sizeof( ctfgame.ft[0] ) );
			}
			if( WSW_CTF_UNLOCKTIME )
			{
				for( i = 0; i < game.maxclients; i++ )
				{
					if( ctfgame.ut[i].team == team )
					{
						ctfgame.ut[i].inittouch = 0;
						ctfgame.ut[i].lasttouch = 0;
						ctfgame.ut[i].newtouch = 0;
						ctfgame.ut[i].team = 0;
					}
				}
			}

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

		if( G_CTF_TIMER )
		{
			// 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_ALPHA; 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_ALPHA; i < TEAM_ALPHA + 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_TimerInhitited
//==================
qboolean G_Gametype_CTF_TimerInhitited( edict_t *ent )
{
	edict_t *user = NULL;

	// if it's the target of a freezetimer trigger don't create our own trigger.
	if( ent->targetname && G_CTF_TIMER )
	{
		while( ( user = G_Find( user, FOFS( target ), ent->targetname ) ) )
		{
			if( !Q_stricmp( user->classname, "target_reset_flag_countdown" )
			   || !Q_stricmp( user->classname, "target_freeze_flag_countdown" ) )
			{
				return qtrue;
			}
		}
	}

	return qfalse;
}


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

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

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

	// it is our flag
	if( flag->s.team == player->s.team )
	{
		if( flag->spawnflags & DROPPED_ITEM )
		{
			// hey, its not home.  return it by teleporting it back
			match.scores[PLAYERNUM( player )].score += CTF_RECOVERY_BONUS;
			teamlist[player->s.team].teamplayerscores += CTF_RECOVERY_BONUS;
			ctfgame.playerstats[PLAYERNUM( player )].ctf_lastreturnedflag = level.time;

			// different sound for recoverer/teammates/enemy
			for( i = 0; i < game.maxclients; i++ )
			{
				sendto = PLAYERENT( i );
				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 == player )
				{
					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 == flag->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
			//G_AddAnnouncerEvent( NULL, trap_SoundIndex("sounds/teamplay/flagret_red.wav"), GS_MAX_TEAMS );
			//G_AddAnnouncerEvent( NULL, trap_SoundIndex("sounds/teamplay/flagret_blu.wav"), GS_MAX_TEAMS );
			G_PrintMsg( NULL, "%s%s returned the %s flag!\n",
			           player->r.client->pers.netname, S_COLOR_WHITE, GS_TeamName( flag->s.team ) );
			G_AwardCTFRecovery( player );

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

		// Not a dropped flag -> On our own flag-stand

		// player is not a flag carrier -> Ignore
		if( !( player->r.client->inventory[enemy_flag_item->tag] ) )
			return qfalse;

		if( ( !( flag->s.effects & EF_ENEMY_FLAG ) ) && ( G_CTF_TIMER ) )
		{
			// it is a flag carrier, but our flag isn't at base (extend enemy timer)
			ctfgame.flagResetTimerTouched[CTF_ENEMYTEAM( flag->s.team )] = qtrue;
		}
		// Flag is at our base AND player is flag carrier!

		if( WSW_CTF_CAPTURETIME )
		{
			if( G_CTF_TIMER )
			{
				ctfgame.flagResetTimerTouched[CTF_ENEMYTEAM( flag->s.team )] = qtrue;
			}
			if( ( ( ctfgame.ft[flag->s.team].inittouch ) &&
			     ( VALID_TIMER( ctfgame.ft[flag->s.team].lasttouch ) ) &&
			     ( ctfgame.ft[flag->s.team].inittouch + WSW_CTF_CAPTURETIME ) <= level.time )
			    )
			{
				// Capture!
				// Reset all timers
				ctfgame.ut[PLAYERNUM( player )].newtouch = 0;
				ctfgame.ut[PLAYERNUM( player )].lasttouch = 0;
				ctfgame.ut[PLAYERNUM( player )].inittouch = 0;
				ctfgame.ut[PLAYERNUM( player )].team = 0;
				ctfgame.ft[flag->s.team].newtouch = 0;
				ctfgame.ft[flag->s.team].lasttouch = 0;
				ctfgame.ft[flag->s.team].inittouch = 0;
				ctfgame.ft[flag->s.team].team = flag->s.team;
			}
			else
			{
				ctfgame.ft[flag->s.team].newtouch = level.time;
				ctfgame.ft[flag->s.team].team = flag->s.team;
				return qfalse;
			}
		}
		// Capture!
		for( i = 0; i < game.maxclients; i++ )
		{
			sendto = PLAYERENT( i );
			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 == player )
			{
				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 == player->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
		//G_AnnouncerSound( NULL, trap_SoundIndex("sounds/teamplay/flagcap_red.wav"), GS_MAX_TEAMS, qtrue );
		//G_AnnouncerSound( NULL, trap_SoundIndex("sounds/teamplay/flagcap_blu.wav"), GS_MAX_TEAMS, qtrue );
		G_PrintMsg( NULL, "%s%s captured the %s flag!\n",
		           player->r.client->pers.netname, S_COLOR_GREY, GS_TeamName( CTF_ENEMYTEAM( player->s.team ) ) );
		if( G_CTF_TIMER )
		{
			G_CenterPrintMsg( player, "YOU CAPTURED THE FLAG! (%.2f)\n",
			                  ( level.time - ctfgame.flagStolenTime[CTF_ENEMYTEAM( player->s.team )] )*0.001f );
		}
		else
		{
			G_CenterPrintMsg( player, "YOU CAPTURED THE FLAG!\n" );
		}

		G_AwardCapture( player );
		player->r.client->inventory[enemy_flag_item->tag] = 0;
		ctfgame.last_capture_team = player->s.team;
		teamlist[player->s.team].teamscore++;

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

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

			if( client->s.team != player->s.team )
				ctfgame.playerstats[i].ctf_lasthurtcarrier = 0;

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

		G_Gametype_CTF_ResetFlag( CTF_ENEMYTEAM( flag->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_CTF_TIMER )
	{
		// reset flag auto-return timer
		if( !( flag->s.effects & EF_ENEMY_FLAG ) )
		{                                  // we are touching a empty flag base
			if( G_Gametype_CTF_TimerInhitited( flag ) )
			{                               // touch ability inhibited at spawn
				return qfalse;
			}
			ctfgame.flagResetTimerTouched[flag->s.team] = qtrue;
			if( !player->r.client->inventory[flag_item->tag] )
			{                                        // bonus only if you are not the carrier
				ctfgame.playerstats[PLAYERNUM( player )].triggerpushed += game.snapFrameTime;
			}
			else
			{
				ctfgame.flagStolenTime[flag->s.team] = level.time;
				ctfgame.flagResetTimerTouchedByFlagCarrier[flag->s.team] = qtrue; // flag carrier resets it right away
			}

			return qfalse;
		}
	}


	// KoFFiE: Pickup on flagstand: unlock the flag first!
	if( ( WSW_CTF_UNLOCKTIME ) && !( flag->spawnflags & DROPPED_ITEM ) )
	{
		if( ( ( ctfgame.ut[PLAYERNUM( player )].inittouch ) &&
		     ( VALID_TIMER( ctfgame.ut[PLAYERNUM( player )].lasttouch ) ) &&
		     ( ctfgame.ut[PLAYERNUM( player )].inittouch + WSW_CTF_UNLOCKTIME ) <= level.time )
		    )
		{
			// Flag unlock!
			// Reset all timers
			ctfgame.ut[PLAYERNUM( player )].newtouch = 0;
			ctfgame.ut[PLAYERNUM( player )].lasttouch = 0;
			ctfgame.ut[PLAYERNUM( player )].inittouch = 0;
			ctfgame.ut[PLAYERNUM( player )].team = 0;
			ctfgame.ft[flag->s.team].newtouch = 0;
			ctfgame.ft[flag->s.team].lasttouch = 0;
			ctfgame.ft[flag->s.team].inittouch = 0;
			ctfgame.ft[flag->s.team].team = flag->s.team;
		}
		else
		{
			ctfgame.ut[PLAYERNUM( player )].newtouch = level.time;
			ctfgame.ut[PLAYERNUM( player )].team = flag->s.team;
			return qfalse;
		}
	}
	// pick it up

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

	for( i = 0; i < game.maxclients; i++ )
	{
		sendto = PLAYERENT( i );
		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 == player )
		{
			G_AnnouncerSound( sendto, trap_SoundIndex( S_ANNOUNCER_CTF_FLAG_TAKEN ), GS_MAX_TEAMS, qtrue );
		}
		else if( compareto->s.team == player->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 == flag->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
	//G_AddAnnouncerEvent( NULL, trap_SoundIndex("sounds/teamplay/flagtk_red.wav"), GS_MAX_TEAMS );
	//G_AddAnnouncerEvent( NULL, trap_SoundIndex("sounds/teamplay/flagtk_blu.wav"), GS_MAX_TEAMS );
	G_PrintMsg( NULL, "%s%s got the %s flag!\n",
	           player->r.client->pers.netname, S_COLOR_GREY, GS_TeamName( flag->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( !( flag->spawnflags & DROPPED_ITEM ) )
	{
		if( G_CTF_TIMER )
		{           // first time set the full timer
			ctfgame.flagResetTimer[flag->s.team] = level.time + ( flag->wait * 1000 );
			ctfgame.flagStolenTime[flag->s.team] = level.time;
		}
		else
		{ // in timed ctf style the base remains touchable
			flag->flags |= FL_RESPAWN;
			flag->r.solid = SOLID_NOT;
		}
		flag->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.time ) > ( ( 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_ALPHA; team < TEAM_ALPHA + 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 = 99; // Draw no number

				dropped->s.team = team;
				dropped->think = G_Gametype_CTF_DropFlagThink;
				dropped->nextthink = level.time + 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_ALPHA; team < TEAM_ALPHA + 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_Validate_Timer
//==================
static void G_Gametype_CTF_Validate_Timer( ctf_player_timers_t *timer )
{
	if( !timer->newtouch )
		return;
	if( !timer->lasttouch )
	{
		// Player hasn't touched flag before -> initial touch
		timer->inittouch = timer->newtouch;
	}
	else if( !VALID_TIMER( timer->lasttouch ) )
	{
		// Ugly hack for detecting flag touch gaps!
		// Gap detected or new touch

		if( ( timer->newtouch - timer->lasttouch ) > WSW_CTF_FREEZETIME )
		{
			// Timer restore calculation
			// Set ctfgame.ut[i].inittouch to ctfgame.ut[x].newtouch if no 'count up' has to be done
			//G_Printf("ADJUST: %ld | %ld -> %ld\n", level.time, timer->inittouch, timer->newtouch - ( ( timer->lasttouch - timer->inittouch ) - ( timer->newtouch - (timer->lasttouch + WSW_CTF_FREEZETIME) ) ));
			timer->inittouch = timer->newtouch - ( ( timer->lasttouch - timer->inittouch ) - ( timer->newtouch - (timer->lasttouch + WSW_CTF_FREEZETIME) ) );
		}
		else
		{
			//G_Printf("WAIT: %ld | %ld -> %ld\n", level.time, timer->inittouch, timer->inittouch + ( timer->newtouch - timer->lasttouch ));
			timer->inittouch += ( timer->newtouch - timer->lasttouch );
		}

		if( timer->inittouch > timer->newtouch )
		{
			// G_Printf("RESET: %ld | %ld > %ld\n", level.time, timer->inittouch, timer->newtouch);
			// Last touch was too long time ago -> Reset
			timer->inittouch = timer->newtouch;
		}
	}
	timer->lasttouch = timer->newtouch;
	timer->newtouch = 0;
}

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


	for( team = TEAM_ALPHA; team < TEAM_ALPHA + 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 ) ) )
				continue;
			if( !G_CTF_TIMER )
			{
				flag->s.modelindex2 = 99;
				continue;
			}

			// if inside frozen timer lapse, add up the spent time
			if( ctfgame.flagFrozenTimer[team] >= level.time )
			{
				ctfgame.flagResetTimer[team] += game.snapFrameTime;
			}
			// check for timer being modified
			if( ctfgame.flagResetTimerTouched[team] )
			{
				if( ctfgame.flagResetTimerTouchedByFlagCarrier[team] )
				{                                        // reset
					ctfgame.flagResetTimer[team] = level.time + ( flag->wait * 1000 );
				}
				else
				{ // count up
					if( ctfgame.flagResetTimer[team] < level.time + 1000 )
						ctfgame.flagResetTimer[team] = level.time + 1000 - game.snapFrameTime;

					// add extra time
					ctfgame.flagResetTimer[team] += game.snapFrameTime*3; // 3 for the extra time we add
					// it the timer wasn't frozen already, add the freezing compensation too
					if( ctfgame.flagFrozenTimer[team] < level.time )
					{
						ctfgame.flagResetTimer[team] += game.snapFrameTime;
					}

					//clamp to full
					if( ctfgame.flagResetTimer[team] > level.time + ( flag->wait * 1000 ) )
						ctfgame.flagResetTimer[team] = level.time + ( 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.time )
			{
				// 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
				G_PrintMsg( NULL, "The %s flag returned!\n", GS_TeamName( team ) );
				//if( team == TEAM_ALPHA ) {
				//	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.time )
			{
				countdown = (int)( ( ctfgame.flagResetTimer[team] - level.time ) * 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;

		// KoFFiE: Check the capture and unlock timers
		if( WSW_CTF_UNLOCKTIME )
		{
			//game.clients[0]
			for( i = 0; i < game.maxclients; i++ )
			{
				p_ent = PLAYERENT( i );
				if( !p_ent->r.inuse || !p_ent->s.team )
				{
					// Invalid/unused player
					continue;
				}
				G_Gametype_CTF_Validate_Timer( &ctfgame.ut[i] );
			}
		}
		if( WSW_CTF_CAPTURETIME )
		{
			G_Gametype_CTF_Validate_Timer( &ctfgame.ft[team] );
		}
	}
}

//==================
//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( FLAG_EXTRA_BOXSIZE )
	{
		ent->r.maxs[0] += FLAG_EXTRA_BOXSIZE; // Width
		ent->r.maxs[1] += FLAG_EXTRA_BOXSIZE; // Width
		ent->r.maxs[2] += FLAG_EXTRA_BOXSIZE; // Height
		ent->r.mins[0] -= FLAG_EXTRA_BOXSIZE; // Width
		ent->r.mins[1] -= FLAG_EXTRA_BOXSIZE; // Width
		//ent->r.mins[2] += FLAG_EXTRA_BOXSIZE;	// Height
	}

	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.svflags &= ~SVF_NOCLIENT;

	// if it's the target of a freezetimer trigger don't create our own trigger.
	/*if( ent->targetname && G_CTF_TIMER ) {
	    edict_t *user;
	    while( ( user = G_Find(user, FOFS(target), ent->targetname) ) ) {
	   	if( !Q_stricmp(user->classname, "target_reset_flag_countdown")
	 || !Q_stricmp(user->classname, "target_freeze_flag_countdown") )
	   	{
	   	    ent->spawnflags |=
	   	    break;
	   	}
	    }
	   }*/

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

	if( G_CTF_TIMER )
	{
		if( !ent->wait )
			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 );
	G_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_ALPHA; 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" );

	GClip_LinkEntity( ent );
}

//
//==================
//G_Gametype_CTF_ScoreboardMessage
//==================
//
char *G_Gametype_CTF_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 \"&ctfs " );
	len = strlen( scoreboardString );
	*entry = 0; // wsw : aiwa : fix unitialized value heisenbug

	//the players are in score order in the team lists
	for( team = TEAM_ALPHA; team < TEAM_ALPHA + 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 %i ",
			             PLAYERNUM( e ),
			             match.scores[PLAYERNUM( e )].score,
			             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_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();
	// nip: don't use armor decay with this armor system
	//G_GameType_ClientArmorDecayRule();
	G_Teams_UpdateTeamInfoMessages();

	G_Gametype_CTF_UpdatedFlagBases();

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