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

typedef struct
{
	ca_playerclass_t playerclass[MAX_CLIENTS];
	edict_t *spawnpoint[GS_MAX_TEAMS];
} cagame_t;

cagame_t cagame;

char *ca_playerclass_names[] =
{
	"Grunt",
	"Camper",
	"Spammer",
	0
};

static void G_Gametype_CA_SetBotClass( edict_t *ent );
static qboolean G_Match_CA_CountDownAnnounces( void );
static ca_weaponflag_t G_Gametype_CA_WeaponFlag( qboolean classmode, ca_playerclass_t playerclass );
static char *G_Gametype_CA_ClassName( ca_playerclass_t playerclass );
static char *G_Gametype_CA_WeaponName( ca_weaponflag_t weaponflag, char *result, size_t resultsize );
static void G_Gametype_CA_SetUpSpawnPoint( void );

//==================
//G_Gametype_CA_NewMap
//==================
void G_Gametype_CA_NewMap( void )
{
	// clear player's class
	memset( &cagame, 0, sizeof( cagame ) );
}

//=================
//G_Gametype_CA_SetUpCountdown
//=================
// copy from g_gametypes.c: G_Gametype_GENERIC_SetUpCountdown
void G_Gametype_CA_SetUpCountdown( void )
{
	match.roundstate = MATCH_STATE_COUNTDOWN;
	match.roundstarttime = level.time;
	match.roundendtime = level.time + (int)( fabs( g_countdown_time->value )*1000 );
	G_Gametype_CA_SetUpSpawnPoint();
	G_Match_RespawnAllClients(); // move from SetUpMatch
	G_Gametype_GENERIC_SetUpCountdown();
}

//=================
//G_Gametype_CA_SetUpMatch
//=================
// copy from g_gametypes.c: G_Gametype_GENERIC_SetUpMatch
void G_Gametype_CA_SetUpMatch( void )
{
	int i;

	match.roundstate = MATCH_STATE_PLAYTIME;
	match.roundstarttime = level.time;
	match.roundendtime = match.endtime;

	//clear stats and scores
	memset( match.scores, 0, sizeof( client_scores_t ) * MAX_CLIENTS );
	for( i = TEAM_PLAYERS; i < GS_MAX_TEAMS; i++ )
	{
		teamlist[i].teamplayerscores = 0;
		teamlist[i].teamscore = 0;
	}

	G_Match_SetUpDelayedItems();
	// G_Match_RespawnAllClients(); // move to SetUpCountdown
	G_Match_FreeBodyQueue();
	match.pickableItemsMask =
	        GS_Gametype_SpawnableItemMask( game.gametype )|GS_Gametype_DropableItemMask( game.gametype );
	if( g_instagib->integer )
	{
		match.pickableItemsMask &= ~G_INSTAGIB_NEGATE_ITEMMASK;
	}

	G_AnnouncerSound( NULL, trap_SoundIndex( va( S_ANNOUNCER_COUNTDOWN_FIGHT_1_to_2, ( rand()&1 )+1 ) ), GS_MAX_TEAMS,
	                  qtrue );
	G_CenterPrintMsg( NULL, "FIGHT!\n" );
}

static void G_Gametype_CA_SetBotClass( edict_t *ent )
{
	int playerclass;

	if( !ent->r.inuse || trap_GetClientState( PLAYERNUM( ent ) ) < CS_SPAWNED )
		return;
	if( ent->s.team <= TEAM_SPECTATOR || ent->s.team >= GS_MAX_TEAMS )
		return;
	if( game.gametype != GAMETYPE_CA )
		return;
#ifndef WSW_RELEASE
	if( !g_ca_classmode->integer )
		return;
#endif
	if( g_instagib->integer )
		return;

	if( ent->r.svflags & SVF_FAKECLIENT && ent->ai.type == AI_ISBOT )
	{
		playerclass = ( CA_PLAYERCLASS_GRUNT + (int)( random() * CA_PLAYERCLASS_TOTAL ) ) % CA_PLAYERCLASS_TOTAL;
		// FIXME: a bit annoying?
		// if( playerclass != cagame.playerclass[PLAYERNUM(ent)] )
		//	G_PrintMsg( NULL, "%s selects class %s\n", ent->r.client->pers.netname, G_Gametype_CA_ClassName(cagame.playerclass[PLAYERNUM(ent)]));
		cagame.playerclass[PLAYERNUM( ent )] = playerclass;
	}
}

//=================
//G_Gametype_CA_ClientRespawn
//=================
// copy from p_client.c: ClientRespawn
qboolean G_Gametype_CA_ClientRespawn( edict_t *self )
{
	int index, i;
	edict_t *spawnpoint;
	vec3_t spawn_origin, spawn_angles;
	gclient_t *client;
	client_respawn_t resp;
	client_teamchange_t teamchange;
	client_persistant_t pers;
	char userinfo[MAX_INFO_STRING];
	weapon_info_t *weaponinfo;
	int ammocount, weakammocount;

	self->r.svflags &= ~SVF_NOCLIENT;

	//if invalid be spectator
	if( self->s.team < 0 || self->s.team >= GS_MAX_TEAMS )
		self->s.team = TEAM_SPECTATOR;

	GClip_UnlinkEntity( self );

	index = self-game.edicts-1;
	client = self->r.client;

	// deathmatch wipes most client data every spawn
	resp = client->resp;
	teamchange = client->teamchange;
	pers = client->pers;
	memcpy( userinfo, client->pers.userinfo, sizeof( userinfo ) );
	memset( client, 0, sizeof( *client ) );
	client->resp = resp;
	client->teamchange = teamchange;
	client->pers = pers;

	self->deadflag = DEAD_NO;
	ClientUserinfoChanged( self, userinfo );

	// clear entity values
	memset( &self->snap, 0, sizeof( self->snap ) );
	self->groundentity = NULL;
	self->r.client = &game.clients[index];
	self->takedamage = DAMAGE_AIM;
	self->movetype = MOVETYPE_PLAYER;
	self->think = player_think;
	self->pain = player_pain;
	self->die = player_die;
	self->viewheight = playerbox_stand_viewheight;
	self->r.inuse = qtrue;
	self->mass = 200;
	self->r.solid = SOLID_BBOX;
	self->air_finished = level.time + ( 12*1000 );
	self->r.clipmask = MASK_PLAYERSOLID;
	self->waterlevel = 0;
	self->watertype = 0;
	self->flags &= ~FL_NO_KNOCKBACK;
	self->r.svflags &= ~SVF_CORPSE;
	//MbotGame[start]

	self->enemy = NULL;
	self->r.owner = NULL;

	if( self->ai.type == AI_ISBOT )
	{
		self->think = AI_Think;
		self->classname = "bot";
	}
	else if( self->r.svflags & SVF_FAKECLIENT )
	{
		self->classname = "fakeclient"; //[end]
	}
	else
	{
		self->classname = "player";
	}

	VectorCopy( playerbox_stand_mins, self->r.mins );
	VectorCopy( playerbox_stand_maxs, self->r.maxs );
	VectorClear( self->velocity );
	VectorClear( self->avelocity );

	// clear playerstate values
	memset( &self->r.client->ps, 0, sizeof( client->ps ) );

	client->ps.POVnum = ENTNUM( self );

	// clear entity state values
	self->s.type = ET_PLAYER;
	self->s.effects = 0;
	self->s.light = 0;

	// modelindex and skinnum are set at calling to update userinfo
	self->s.frame = 0;
	self->s.weapon = 0;

	self->fall_fatal = qfalse;

	SelectSpawnPoint( self, &spawnpoint, spawn_origin, spawn_angles, 400 );
	VectorCopy( spawn_origin, client->ps.pmove.origin );
	client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
	VectorCopy( spawn_origin, self->s.origin );
	VectorCopy( self->s.origin, self->s.old_origin );

	// set angles
	self->s.angles[PITCH] = 0;
	self->s.angles[YAW] = spawn_angles[YAW];
	self->s.angles[ROLL] = 0;
	VectorCopy( self->s.angles, client->ps.viewangles );

	// set the delta angle
	for( i = 0; i < 3; i++ )
		client->ps.pmove.delta_angles[i] = ANGLE2SHORT( self->s.angles[i] ) - client->pers.cmd_angles[i];

	//if invalid join spectator
	if( self->s.team < 0 || self->s.team >= GS_MAX_TEAMS )
		self->s.team = TEAM_SPECTATOR;

	//don't put spectators in the game
	if( self->s.team == TEAM_SPECTATOR )
	{
		self->movetype = MOVETYPE_NOCLIP;
		self->r.solid = SOLID_NOT;
		self->r.svflags |= SVF_NOCLIENT;
		client->ps.pmove.pm_type = PM_SPECTATOR;
	}
	else
	{
		if( KillBox( self ) )
		{               // telefragged someone?
		}

		// add a teleportation effect
		G_SpawnTeleportEffect( self );
	}

	self->s.teleported = qtrue;

	// hold in place briefly
	client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
	client->ps.pmove.pm_time = 14;

	BOT_Respawn( self ); //MbotGame
	// if it's a bot, and in class based mode, get a class for it
	G_Gametype_CA_SetBotClass( self );

	// wsw: pb set default max health
	self->max_health = 100;
	self->health = self->max_health;

	self->r.client->buttons = 0;

	// resetting award stats
	memset( &self->r.client->awardInfo, 0, sizeof( award_info_t ) );

	//give default items
	memset( &self->r.client->inventory, 0, sizeof( self->r.client->inventory ) );
	if( self->s.team == TEAM_SPECTATOR )
	{
		G_UseTargets( spawnpoint, self );
	}
	else
	{
		if( g_instagib->integer )
		{
			self->r.client->inventory[WEAP_INSTAGUN] = 1;
			self->r.client->inventory[AMMO_INSTAS] = 1;
			self->r.client->inventory[AMMO_WEAK_INSTAS] = 1;
		}
		else
		{
			ca_weaponflag_t	weaponflag;
			int gb, rg, gl, rl, pg, lg, eb;

#ifndef WSW_RELEASE
			if( g_ca_classmode->integer )
			{
#endif
			ca_playerclass_t playerclass = cagame.playerclass[PLAYERNUM( self )];
			char weaponname[MAX_TOKEN_CHARS];
			weaponflag = G_Gametype_CA_WeaponFlag( qtrue, playerclass );
			G_Gametype_CA_WeaponName( weaponflag, weaponname, sizeof( weaponname ) );
			// G_PrintMsg( self, "Your class is %s (%s)\n", G_Gametype_CA_ClassName( playerclass ), weaponname );
#ifndef WSW_RELEASE
		}
		else
		{
			weaponflag = G_Gametype_CA_WeaponFlag( qfalse, CA_PLAYERCLASS_GRUNT ); // GRUNT is dummy
		}
#endif

			gb = rg = gl = rl = pg = lg = eb = 0;
			G_Gametype_CA_SetAmmo( g_ca_weak_ammo->string, &gb, &rg, &gl, &rl, &pg, &lg, &eb );

			if( weaponflag & CA_WEAPONFLAG_WEAK_GB )
			{
				self->r.client->inventory[WEAP_GUNBLADE] = 1;
				self->r.client->inventory[AMMO_WEAK_GUNBLADE] = gb;
			}
			if( weaponflag & CA_WEAPONFLAG_WEAK_RG )
			{
				self->r.client->inventory[WEAP_RIOTGUN] = 1;
				self->r.client->inventory[AMMO_WEAK_SHELLS] = rg;
			}
			if( weaponflag & CA_WEAPONFLAG_WEAK_GL )
			{
				self->r.client->inventory[WEAP_GRENADELAUNCHER] = 1;
				self->r.client->inventory[AMMO_WEAK_GRENADES] = gl;
			}
			if( weaponflag & CA_WEAPONFLAG_WEAK_RL )
			{
				self->r.client->inventory[WEAP_ROCKETLAUNCHER] = 1;
				self->r.client->inventory[AMMO_WEAK_ROCKETS] = rl;
			}
			if( weaponflag & CA_WEAPONFLAG_WEAK_PG )
			{
				self->r.client->inventory[WEAP_PLASMAGUN] = 1;
				self->r.client->inventory[AMMO_WEAK_PLASMA] = pg;
			}
			if( weaponflag & CA_WEAPONFLAG_WEAK_LG )
			{
				self->r.client->inventory[WEAP_LASERGUN] = 1;
				self->r.client->inventory[AMMO_WEAK_LASERS] = lg;
			}
			if( weaponflag & CA_WEAPONFLAG_WEAK_EB )
			{
				self->r.client->inventory[WEAP_ELECTROBOLT] = 1;
				self->r.client->inventory[AMMO_WEAK_BOLTS] = eb;
			}

			gb = rg = gl = rl = pg = lg = eb = 0;
			G_Gametype_CA_SetAmmo( g_ca_strong_ammo->string, &gb, &rg, &gl, &rl, &pg, &lg, &eb );
			if( weaponflag & CA_WEAPONFLAG_STRONG_GB )
			{
				weaponinfo = GS_FiredefForWeapon( WEAP_GUNBLADE );
				if ( weaponinfo && ( gb > weaponinfo->firedef->ammo_max ) )
					gb = weaponinfo->firedef->ammo_max;
				self->r.client->inventory[WEAP_GUNBLADE] = 1;
				self->r.client->inventory[AMMO_CELLS] = gb;
			}
			if( weaponflag & CA_WEAPONFLAG_STRONG_RG )
			{
				self->r.client->inventory[WEAP_RIOTGUN] = 1;
				self->r.client->inventory[AMMO_SHELLS] = rg;
			}
			if( weaponflag & CA_WEAPONFLAG_STRONG_GL )
			{
				self->r.client->inventory[WEAP_GRENADELAUNCHER] = 1;
				self->r.client->inventory[AMMO_GRENADES] = gl;
			}
			if( weaponflag & CA_WEAPONFLAG_STRONG_RL )
			{
				self->r.client->inventory[WEAP_ROCKETLAUNCHER] = 1;
				self->r.client->inventory[AMMO_ROCKETS] = rl;
			}
			if( weaponflag & CA_WEAPONFLAG_STRONG_PG )
			{
				self->r.client->inventory[WEAP_PLASMAGUN] = 1;
				self->r.client->inventory[AMMO_PLASMA] = pg;
			}
			if( weaponflag & CA_WEAPONFLAG_STRONG_LG )
			{
				self->r.client->inventory[WEAP_LASERGUN] = 1;
				self->r.client->inventory[AMMO_LASERS] = lg;
			}
			if( weaponflag & CA_WEAPONFLAG_STRONG_EB )
			{
				self->r.client->inventory[WEAP_ELECTROBOLT] = 1;
				self->r.client->inventory[AMMO_BOLTS] = eb;
			}

			self->max_health = g_ca_health->integer;
			self->health = self->max_health;
			self->r.client->armortag = ARMOR_RA; // or ARMOR_YA?
			self->r.client->armor = g_ca_armor->integer;
		}

		G_UseTargets( spawnpoint, self );

		for( i = WEAP_TOTAL - 1; i >= WEAP_GUNBLADE; i-- )
		{
			if( i == WEAP_SHOCKWAVE )  // FIXME!!!
				continue;

			if( !self->r.client->inventory[i] )
				continue;

			if( g_select_empty->integer )
				break;

			weaponinfo = &gs_weaponInfos[i];

			if( weaponinfo->firedef->usage_count && weaponinfo->firedef->ammo_id )
				ammocount = self->r.client->inventory[weaponinfo->firedef->ammo_id];
			else
				ammocount = weaponinfo->firedef->usage_count;

			if( weaponinfo->firedef_weak->usage_count && weaponinfo->firedef_weak->ammo_id )
				weakammocount = self->r.client->inventory[weaponinfo->firedef_weak->ammo_id];
			else
				weakammocount = weaponinfo->firedef_weak->usage_count;

			if( ammocount >= weaponinfo->firedef->usage_count || weakammocount >= weaponinfo->firedef_weak->usage_count )
				break;
		}
		self->r.client->latched_weapon = i;

		// FIXME: forcing RL to be used if available
		if( self->r.client->inventory[WEAP_ROCKETLAUNCHER] )
		{
			if( g_select_empty->integer )
				self->r.client->latched_weapon = WEAP_ROCKETLAUNCHER;

			weaponinfo = &gs_weaponInfos[WEAP_ROCKETLAUNCHER];

			if( weaponinfo->firedef->usage_count && weaponinfo->firedef->ammo_id )
				ammocount = self->r.client->inventory[weaponinfo->firedef->ammo_id];
			else
				ammocount = weaponinfo->firedef->usage_count;

			if( weaponinfo->firedef_weak->usage_count && weaponinfo->firedef_weak->ammo_id )
				weakammocount = self->r.client->inventory[weaponinfo->firedef_weak->ammo_id];
			else
				weakammocount = weaponinfo->firedef_weak->usage_count;

			if( ammocount >= weaponinfo->firedef->usage_count || weakammocount >= weaponinfo->firedef_weak->usage_count )
				self->r.client->latched_weapon = WEAP_ROCKETLAUNCHER;
		}

		ChangeWeapon( self );
		self->r.client->weaponstate.status = WEAPON_ACTIVATING;
		self->r.client->weaponstate.nexttime = WEAPON_RESPAWN_DELAY;
	}

	GClip_LinkEntity( self );
	return qtrue;
}

//==================
//G_Gametype_CA_ScoreboardMessage
//==================
// copy from g_gametype_tdm.c: G_Gametype_TDM_ScoreboardMessage
char *G_Gametype_CA_ScoreboardMessage( void )
{
	char entry[MAX_TOKEN_CHARS];
	size_t len;
	int i, team;
	edict_t	*e;
	float damage_scale = 0.01f;
	// float		damage_scale = 1.0f / (g_ca_health->integer + g_ca_armor->integer); // FIXME: is it more better?

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

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

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

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

			//player tab entry
			*entry = 0;
			Q_snprintfz( entry, sizeof( entry ), "&p %i %i %i %i %i %i %i %i ",
			             PLAYERNUM( e ),
			             match.scores[PLAYERNUM( e )].score,
			             (int)( e->r.client->resp.total_damage_given * damage_scale ),
#ifndef WSW_RELEASE
			             ( g_ca_classmode->integer && !g_ca_competitionmode->integer && !g_instagib->integer ) ? ( cagame.playerclass[PLAYERNUM( e )] + 1 ) : 0,
#else
			             ( !g_ca_competitionmode->integer && !g_instagib->integer ) ? ( cagame.playerclass[PLAYERNUM( e )] + 1 ) : 0,
#endif
			             e->r.client->r.ping > 999 ? 999 : e->r.client->r.ping,
			             G_IsDead( e ), // wsw : imp : Added this to parse
			             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_CA_FragBonuses
//==================
// copy from g_gametype_tdm.c: G_Gametype_TDM_FragBonuses
void G_Gametype_CA_FragBonuses( edict_t *targ, edict_t *inflictor, edict_t *attacker, int mod )
{
	int team;
	int alive_players[GS_MAX_TEAMS] = { 0 };
	int alive_players_max = -999999999;
	int alive_players_min =  999999999;
	int team_min = TEAM_ALPHA;
	edict_t	*e;

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

	if( match.roundstate != MATCH_STATE_PLAYTIME )
		return;

	// Displaying a message for the last living teammember
	for( team = TEAM_ALPHA; team < TEAM_ALPHA + g_maxteams->integer; team++ )
	{
		int i;
		if( !teamlist[team].numplayers )
			continue;
		alive_players[team] = 0;
		for( i = 0; teamlist[team].playerIndices[i] != -1; i++ )
		{
			e = game.edicts + teamlist[team].playerIndices[i];
			if( !e->r.inuse )
				continue;
			if( e->r.client->is_coach )  // ignore coachs
				continue;
			if( !G_IsDead( e ) )
				alive_players[team]++;
		}
		if( alive_players_max < alive_players[team] )
			alive_players_max = alive_players[team];
		if( alive_players_min > alive_players[team] )
		{
			alive_players_min = alive_players[team];
			team_min = team;
		}
	}
	if( alive_players_min == 1 && alive_players_max == 1 )
	{
		G_PrintMsg( NULL, "1v1! Good luck!\n" );
		G_CenterPrintMsg( NULL, "1v1! Good luck!\n" );
	}
	else if( alive_players_min == 1 )
	{
		int i;
		for( i = 0; teamlist[team_min].playerIndices[i] != -1; i++ )
		{
			e = game.edicts + teamlist[team_min].playerIndices[i];
			if( !e->r.inuse )
				continue;
			G_PrintMsg( e, "1v%i! You're on your own!\n", alive_players_max );
			G_CenterPrintMsg( e, "1v%i! You're on your own!\n", alive_players_max );
		}
	}

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

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

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

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

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

	if( !g_ca_competitionmode->integer && targ->s.team != attacker->s.team )
	{
		// FIXME: this code seems to need to integrate into cgame/cg_hud.c: CG_SC_Obituary, but it needs to be enabled/disabled by server-side CVAR (not client-side).
		// char message[128];
		// char message2[128];
		// GS_Obituary( targ, GENDER_NEUTRAL, attacker, mod, message, message2 );
		// G_PrintMsg( targ, "%s %s%s %s%s%s (health: %i, armor: %i)\n", targ->r.client->pers.netname, S_COLOR_WHITE, message, attacker->r.client->pers.netname, S_COLOR_WHITE, message2, HEALTH_TO_INT(attacker->health), ARMOR_TO_INT(attacker->r.client->armor) );
		// FIXME: in order to avoid showing duplicate info, it's safe to use simple format without GS_Obituary.
		G_PrintMsg( targ, "You were killed by %s %s(health: %i, armor: %i)\n", attacker->r.client->pers.netname, S_COLOR_WHITE, HEALTH_TO_INT( attacker->health ), ARMOR_TO_INT( attacker->r.client->armor ) );
	}
}

//=================
//G_Gametype_CA_CheckRules
//=================
// copy from g_gametype_tdm.c: G_Gametype_TDM_CheckRules
void G_Gametype_CA_CheckRules( void )
{
	if( match.state >= MATCH_STATE_POSTMATCH )
		return;

	if( game.gametype != GAMETYPE_CA )
		return;

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

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

	if( match.state != MATCH_STATE_PLAYTIME )
		return;

	// rotate match.roundstate. see also G_Match_SetUpNextState in g_gametypes.c
	// MATCH_STATE_NONE -> MATCH_STATE_COUNTDOWN -> MATCH_STATE_PLAYTIME -> MATCH_STATE_POSTMATCH -> MATCH_STATE_COUNTDOWN -> ... (loop till timelimit hit or scorelimit hit)
	if( match.roundstate == MATCH_STATE_PLAYTIME )
	{
		edict_t	*e;
		int team;
		int alive_team = 0;
		int round = 0;
		qboolean is_match_end = qfalse;
		qboolean is_all_dead[GS_MAX_TEAMS] = { qtrue };

		// update is_all_dead[] and alive_team.
		for( team = TEAM_ALPHA; team < TEAM_ALPHA + g_maxteams->integer; team++ )
		{
			int i;
			is_all_dead[team] = qtrue;
			if( !teamlist[team].numplayers )
				continue;
			for( i = 0; teamlist[team].playerIndices[i] != -1; i++ )
			{
				e = game.edicts + teamlist[team].playerIndices[i];
				if( !e->r.inuse )
					continue;
				if( e->r.client->is_coach )  // ignore coachs
					continue;
				if( !G_IsDead( e ) )
					is_all_dead[team] = qfalse;
			}
			if( !is_all_dead[team] )
				alive_team++;
		}

		// increment score if there is only one alive team. (alive_team == 1)
		if( alive_team <= 1 )
		{               // score or draw
			if( alive_team == 1 )
			{            // score
				for( team = TEAM_ALPHA; team < TEAM_ALPHA + g_maxteams->integer; team++ )
				{
					if( !teamlist[team].numplayers )
						continue;
					if( !is_all_dead[team] )
					{
						// increment score
						teamlist[team].teamplayerscores++;
						teamlist[team].teamscore++;
						break;
					}
				}
				for( e = game.edicts + 1; PLAYERNUM( e ) < game.maxclients; e++ )
				{
					if( !e->r.inuse || trap_GetClientState( PLAYERNUM( e ) ) < CS_SPAWNED )
						continue;
					if( e->s.team < TEAM_SPECTATOR || e->s.team >= GS_MAX_TEAMS )
						continue;
					// FIXME: this announce code works correctly only for alive player. not correctly for dead player in chasecam-mode who spec enemy's team.
					if( e->s.team == TEAM_SPECTATOR )
					{
						// FIXME: change sound file?
						G_AnnouncerSound( e, trap_SoundIndex( va( S_ANNOUNCER_CTF_SCORE_1_to_2, ( rand()&1 )+1 ) ), GS_MAX_TEAMS, qtrue );
					}
					else if( !is_all_dead[e->s.team] )
					{                  // win team
						// FIXME: change sound file?
						G_AnnouncerSound( e, trap_SoundIndex( va( S_ANNOUNCER_CTF_SCORE_TEAM_1_to_2, ( rand()&1 )+1 ) ), GS_MAX_TEAMS, qtrue );
					}
					else
					{ // lose teams
						// FIXME: change sound file?
						G_AnnouncerSound( e, trap_SoundIndex( va( S_ANNOUNCER_CTF_SCORE_ENEMY_1_to_2, ( rand()&1 )+1 ) ), GS_MAX_TEAMS, qtrue );
					}
				}
			}
			else
			{ // draw (alive_team == 0)
				// FIXME: need "DRAW" sound file
				//G_AnnouncerSound( NULL, trap_SoundIndex(va(S_ANNOUNCER_MIDAIR_BONGO_1_to_2, (rand()&1)+1)), GS_MAX_TEAMS, qtrue );
			}

			// check scorelimit hit or roundlimit hit
			round = 0;
			for( team = TEAM_ALPHA; team < TEAM_ALPHA + g_maxteams->integer; team++ )
			{
				if( !teamlist[team].numplayers )
					continue;
				if( g_scorelimit->integer && teamlist[team].teamscore >= g_scorelimit->integer )
				{
					is_match_end = qtrue;
					G_PrintMsg( NULL, "Scorelimit hit.\n" );
					break;
				}
				round += teamlist[team].teamscore;
			}
			if( g_ca_roundlimit->integer && round >= g_ca_roundlimit->integer )
			{
				is_match_end = qtrue;
				G_PrintMsg( NULL, "Roundlimit hit.\n" );
			}

			// round will finish after 4 sec
			match.roundstate = MATCH_STATE_POSTMATCH;
			match.roundstarttime = level.time;
			match.roundendtime = level.time + fabs( G_POSTMATCH_TIMELIMIT );
			G_Match_RemoveAllClientLasers();
			// G_Match_RemoveAllProjectiles(); // remove all fires

			if( is_match_end )
			{
				// match will finish after 4 sec
				match.endtime = match.roundendtime;
				match.roundendtime = 0;
			}
		}
	}
	else if( match.roundstate == MATCH_STATE_POSTMATCH )
	{
		if( !match.roundendtime || level.time < match.roundendtime )
		{
			// do nothing
		}
		else
		{
			int team;
			int round = 0;
			match.roundstate = MATCH_STATE_COUNTDOWN;
			match.roundstarttime = level.time;
			match.roundendtime = level.time + (int)( fabs( g_countdown_time->value )*1000 );
			G_Gametype_CA_SetUpSpawnPoint();
			G_Match_RespawnAllClients();
			G_AnnouncerSound( NULL, trap_SoundIndex( va( S_ANNOUNCER_COUNTDOWN_GET_READY_TO_FIGHT_1_to_2, ( rand()&1 )+1 ) ), GS_MAX_TEAMS, qtrue );

			// count round
			round = 0;
			for( team = TEAM_ALPHA; team < TEAM_ALPHA + g_maxteams->integer; team++ )
			{
				if( !teamlist[team].numplayers )
					continue;
				round += teamlist[team].teamscore;
			}
			if( g_ca_roundlimit->integer && round == ( g_ca_roundlimit->integer - 1 ) )
			{
				G_PrintMsg( NULL, "Final round!\n" );
				G_CenterPrintMsg( NULL, "Final round!\n" );
			}
		}
	}
	else if( match.roundstate == MATCH_STATE_COUNTDOWN )
	{
		if( !match.roundendtime || level.time < match.roundendtime )
		{
			// do nothing
		}
		else
		{
			match.roundstate = MATCH_STATE_PLAYTIME;
			match.roundstarttime = level.time;
			match.roundendtime = match.endtime;
			G_Match_RemoveAllClientLasers();
			G_Match_RemoveAllProjectiles();
			G_AnnouncerSound( NULL, trap_SoundIndex( va( S_ANNOUNCER_COUNTDOWN_FIGHT_1_to_2, ( rand()&1 )+1 ) ), GS_MAX_TEAMS, qtrue );
			G_CenterPrintMsg( NULL, "FIGHT!\n" );
		}
	}
	else
	{    // match.roundstate == MATCH_STATE_NONE
		match.roundstate = MATCH_STATE_COUNTDOWN;
		match.roundstarttime = level.time;
		match.roundendtime = level.time + (int)( fabs( g_countdown_time->value )*1000 );
		G_Gametype_CA_SetUpSpawnPoint();
		G_Match_RespawnAllClients();
		G_AnnouncerSound( NULL, trap_SoundIndex( va( S_ANNOUNCER_COUNTDOWN_GET_READY_TO_FIGHT_1_to_2, ( rand()&1 )+1 ) ), GS_MAX_TEAMS, qtrue );
	}
}

// returns true once each second
// copy from g_gametypes.c: G_Match_GenericCountDownAnnounces
static qboolean G_Match_CA_CountDownAnnounces( void )
{
	static int lastsecond;
	static float remainingtime;
	static int remainingseconds;

	if( match.roundstate >= MATCH_STATE_POSTMATCH )
		return qfalse;

	if( !match.roundendtime )
		return qfalse;

	remainingtime = (float)( match.roundendtime - level.time ) * 0.001f;
	remainingseconds = (int)remainingtime;

	//do the next operations only once per second
	if( lastsecond == remainingseconds )
		return qfalse;

	lastsecond = remainingseconds;

	//print some countdowns
	if( match.roundstate == MATCH_STATE_COUNTDOWN )
	{
		if( 1 + remainingseconds <= g_countdown_time->integer )
		{
			//countdown sound
			if( 1 + remainingseconds < 4 )
				G_AnnouncerSound( NULL, trap_SoundIndex( va( S_ANNOUNCER_COUNTDOWN_COUNT_1_to_3_SET_1_to_2,
				                                             1 + remainingseconds, 1 ) ), GS_MAX_TEAMS, qfalse ); // todo: use random countdown set for all three numbers
			G_CenterPrintMsg( NULL, "%i\n", 1 + remainingseconds );
		}
	}
	else if( match.roundstate == MATCH_STATE_PLAYTIME )
	{
		if( ( 1 + remainingseconds <= g_countdown_time->integer ) && g_timelimit->integer )
		{
			G_CenterPrintMsg( NULL, "%i\n", 1 + remainingseconds );
		}
	}

	return qtrue;
}

// "playerclass" command
void G_Gametype_CA_PlayerClass_Cmd( edict_t *ent )
{
	char *argv1;
	ca_playerclass_t playerclass;
	char weaponname[MAX_TOKEN_CHARS];

	if( !ent->r.inuse || trap_GetClientState( PLAYERNUM( ent ) ) < CS_SPAWNED )
	{
		// illegal client state
		return;
	}

	if( ent->s.team < TEAM_SPECTATOR || ent->s.team >= GS_MAX_TEAMS )
	{
		// illegal team state
		return;
	}

	if( game.gametype != GAMETYPE_CA )
	{
		G_PrintMsg( ent, "This feature is only available in CA gametype.\n" );
		return;
	}

#ifndef WSW_RELEASE
	if( !g_ca_classmode->integer )
	{
		G_PrintMsg( ent, "This feature is only available if g_ca_classmode is 1.\n" );
		return;
	}
#endif

	if( g_instagib->integer )
	{
		G_PrintMsg( ent, "This feature is not available in instagib mode.\n" );
		return;
	}

	if( ent->s.team == TEAM_SPECTATOR )
	{
		G_PrintMsg( ent, "Spectators can't select class.\n" );
		return;
	}

	argv1 = trap_Cmd_Argv( 1 );
	if( !argv1 || *argv1 == 0 )
	{                         // no class specified
		int i;
		playerclass = cagame.playerclass[PLAYERNUM( ent )];
		G_Gametype_CA_WeaponName( G_Gametype_CA_WeaponFlag( qtrue, playerclass ), weaponname, sizeof( weaponname ) );
		G_PrintMsg( ent, "Your class is %s (%s)\nUsage: playerclass <number>\nAvailable classes are:\n", G_Gametype_CA_ClassName( cagame.playerclass[PLAYERNUM( ent )] ), weaponname );
		for( i = CA_PLAYERCLASS_GRUNT; i < CA_PLAYERCLASS_TOTAL; i++ )
		{
			G_Gametype_CA_WeaponName( G_Gametype_CA_WeaponFlag( qtrue, i ), weaponname, sizeof( weaponname ) );
			G_PrintMsg( ent, "%i: %s (%s)\n", i, G_Gametype_CA_ClassName( i ), weaponname );
		}
		return;
	}

	playerclass = atoi( argv1 );
	if( playerclass < CA_PLAYERCLASS_GRUNT || playerclass >= CA_PLAYERCLASS_TOTAL )
	{
		G_PrintMsg( ent, "Invalid class: %i\n", playerclass );
		return;
	}

	if( cagame.playerclass[PLAYERNUM( ent )] == playerclass )
	{
		// G_PrintMsg( ent, "You are already in class %s\n", G_Gametype_CA_ClassName(playerclass) );
		return;
	}

	cagame.playerclass[PLAYERNUM( ent )] = playerclass;

	G_Gametype_CA_WeaponName( G_Gametype_CA_WeaponFlag( qtrue, playerclass ), weaponname, sizeof( weaponname ) );
	G_PrintMsg( ent, "Your class is %s (%s)\n", G_Gametype_CA_ClassName( playerclass ), weaponname );

	return;
}

static ca_weaponflag_t G_Gametype_CA_WeaponFlag( qboolean classmode, ca_playerclass_t playerclass )
{
	int normal, grunt, camper, spammer;
	normal = CA_WEAPONFLAG_ALL;
	grunt = CA_WEAPONFLAG_WEAK_ALL | CA_WEAPONFLAG_STRONG_GB | CA_WEAPONFLAG_STRONG_RL | CA_WEAPONFLAG_STRONG_RG; // (all of weak) + GB + RL + RG
	camper = CA_WEAPONFLAG_WEAK_ALL | CA_WEAPONFLAG_STRONG_GB | CA_WEAPONFLAG_STRONG_EB | CA_WEAPONFLAG_STRONG_GL; // (all of weak) + GB + EB + GL
	spammer = CA_WEAPONFLAG_WEAK_ALL | CA_WEAPONFLAG_STRONG_GB | CA_WEAPONFLAG_STRONG_LG | CA_WEAPONFLAG_STRONG_PG; // (all of weak) + GB + LG + PG
	G_Gametype_CA_SetWeaponFlag( g_ca_weapons->string, &normal, &grunt, &camper, &spammer );

	if( classmode )
	{
		if( playerclass == CA_PLAYERCLASS_GRUNT )
		{
			return grunt;
		}
		else if( playerclass == CA_PLAYERCLASS_CAMPER )
		{
			return camper;
		}
		else if( playerclass == CA_PLAYERCLASS_SPAMMER )
		{
			return spammer;
		}
		else
		{
			return grunt; // default is GRUNT
		}
	}
	else
	{
		return normal;
	}
}

static char *G_Gametype_CA_ClassName( ca_playerclass_t playerclass )
{
	if( playerclass < CA_PLAYERCLASS_GRUNT || playerclass >= CA_PLAYERCLASS_TOTAL )
		return ca_playerclass_names[CA_PLAYERCLASS_GRUNT]; // default is GRUNT
	else
		return ca_playerclass_names[playerclass];
}

static char *G_Gametype_CA_WeaponName( ca_weaponflag_t weaponflag, char *result, size_t resultsize )
{
	if( !result || resultsize <= 0 ) return result;

	result[0] = '\0';

	if( weaponflag == CA_WEAPONFLAG_NONE )
	{
		if( result[0] != '\0' ) Q_strncatz( result, " + ", resultsize );
		Q_strncatz( result, "[none]", resultsize );
		return result;
	}

	if( ( weaponflag & CA_WEAPONFLAG_ALL ) == CA_WEAPONFLAG_ALL )
	{
		if( result[0] != '\0' ) Q_strncatz( result, " + ", resultsize );
		Q_strncatz( result, "[all]", resultsize );
		return result;
	}

	if( ( weaponflag & CA_WEAPONFLAG_WEAK_ALL ) == CA_WEAPONFLAG_WEAK_ALL )
	{
		if( result[0] != '\0' ) Q_strncatz( result, " + ", resultsize );
		Q_strncatz( result, "[all of weak]", resultsize );
	}
	else
	{
		int gb, rg, gl, rl, pg, lg, eb;
		gb = rg = gl = rl = pg = lg = eb = 0;
		G_Gametype_CA_SetAmmo( g_ca_weak_ammo->string, &gb, &rg, &gl, &rl, &pg, &lg, &eb );

		if( weaponflag & CA_WEAPONFLAG_WEAK_GB )
		{
			if( result[0] != '\0' ) Q_strncatz( result, " + ", resultsize );
			Q_strncatz( result, va( "gb:%i", gb ), resultsize );
		}
		if( weaponflag & CA_WEAPONFLAG_WEAK_RG )
		{
			if( result[0] != '\0' ) Q_strncatz( result, " + ", resultsize );
			Q_strncatz( result, va( "rg:%i", rg ), resultsize );
		}
		if( weaponflag & CA_WEAPONFLAG_WEAK_GL )
		{
			if( result[0] != '\0' ) Q_strncatz( result, " + ", resultsize );
			Q_strncatz( result, va( "gl:%i", gl ), resultsize );
		}
		if( weaponflag & CA_WEAPONFLAG_WEAK_RL )
		{
			if( result[0] != '\0' ) Q_strncatz( result, " + ", resultsize );
			Q_strncatz( result, va( "rl:%i", rl ), resultsize );
		}
		if( weaponflag & CA_WEAPONFLAG_WEAK_PG )
		{
			if( result[0] != '\0' ) Q_strncatz( result, " + ", resultsize );
			Q_strncatz( result, va( "pg:%i", pg ), resultsize );
		}
		if( weaponflag & CA_WEAPONFLAG_WEAK_LG )
		{
			if( result[0] != '\0' ) Q_strncatz( result, " + ", resultsize );
			Q_strncatz( result, va( "lg:%i", lg ), resultsize );
		}
		if( weaponflag & CA_WEAPONFLAG_WEAK_EB )
		{
			if( result[0] != '\0' ) Q_strncatz( result, " + ", resultsize );
			Q_strncatz( result, va( "eb:%i", eb ), resultsize );
		}
	}

	if( ( weaponflag & CA_WEAPONFLAG_STRONG_ALL ) == CA_WEAPONFLAG_STRONG_ALL )
	{
		if( result[0] != '\0' ) Q_strncatz( result, " + ", resultsize );
		Q_strncatz( result, "[all of strong]", resultsize );
	}
	else
	{
		int gb, rg, gl, rl, pg, lg, eb;
		gb = rg = gl = rl = pg = lg = eb = 0;
		G_Gametype_CA_SetAmmo( g_ca_strong_ammo->string, &gb, &rg, &gl, &rl, &pg, &lg, &eb );

		if( weaponflag & CA_WEAPONFLAG_STRONG_GB )
		{
			if( result[0] != '\0' ) Q_strncatz( result, " + ", resultsize );
			Q_strncatz( result, va( "GB:%i", gb ), resultsize );
		}
		if( weaponflag & CA_WEAPONFLAG_STRONG_RG )
		{
			if( result[0] != '\0' ) Q_strncatz( result, " + ", resultsize );
			Q_strncatz( result, va( "RG:%i", rg ), resultsize );
		}
		if( weaponflag & CA_WEAPONFLAG_STRONG_GL )
		{
			if( result[0] != '\0' ) Q_strncatz( result, " + ", resultsize );
			Q_strncatz( result, va( "GL:%i", gl ), resultsize );
		}
		if( weaponflag & CA_WEAPONFLAG_STRONG_RL )
		{
			if( result[0] != '\0' ) Q_strncatz( result, " + ", resultsize );
			Q_strncatz( result, va( "RL:%i", rl ), resultsize );
		}
		if( weaponflag & CA_WEAPONFLAG_STRONG_PG )
		{
			if( result[0] != '\0' ) Q_strncatz( result, " + ", resultsize );
			Q_strncatz( result, va( "PG:%i", pg ), resultsize );
		}
		if( weaponflag & CA_WEAPONFLAG_STRONG_LG )
		{
			if( result[0] != '\0' ) Q_strncatz( result, " + ", resultsize );
			Q_strncatz( result, va( "LG:%i", lg ), resultsize );
		}
		if( weaponflag & CA_WEAPONFLAG_STRONG_EB )
		{
			if( result[0] != '\0' ) Q_strncatz( result, " + ", resultsize );
			Q_strncatz( result, va( "EB:%i", eb ), resultsize );
		}
	}

	return result;
}

qboolean G_Gametype_CA_SetWeaponFlag( char *weaponflaginfo, int *normal, int *grunt, int *camper, int *spammer )
{
	char *ptr, *tok;
	ptr = weaponflaginfo;

	tok = COM_Parse( &ptr );
	if( !tok[0] )
		return qfalse;
	*normal = atoi( tok );
	if( *normal < CA_WEAPONFLAG_NONE || *normal > CA_WEAPONFLAG_ALL )
		*normal = CA_WEAPONFLAG_ALL; // all weapons

	tok = COM_Parse( &ptr );
	if( !tok[0] )
		return qfalse;
	*grunt = atoi( tok );
	if( *grunt < CA_WEAPONFLAG_NONE || *grunt > CA_WEAPONFLAG_ALL )
		*grunt = CA_WEAPONFLAG_WEAK_ALL | CA_WEAPONFLAG_STRONG_GB | CA_WEAPONFLAG_STRONG_RL | CA_WEAPONFLAG_STRONG_RG; // (all of weak) + GB + RL + RG

	tok = COM_Parse( &ptr );
	if( !tok[0] )
		return qfalse;
	*camper = atoi( tok );
	if( *camper < CA_WEAPONFLAG_NONE || *camper > CA_WEAPONFLAG_ALL )
		*camper = CA_WEAPONFLAG_WEAK_ALL | CA_WEAPONFLAG_STRONG_GB | CA_WEAPONFLAG_STRONG_EB | CA_WEAPONFLAG_STRONG_GL; // (all of weak) + GB + EB + GL

	tok = COM_Parse( &ptr );
	if( !tok[0] )
		return qfalse;
	*spammer = atoi( tok );
	if( *spammer < CA_WEAPONFLAG_NONE || *spammer > CA_WEAPONFLAG_ALL )
		*spammer = CA_WEAPONFLAG_WEAK_ALL | CA_WEAPONFLAG_STRONG_GB | CA_WEAPONFLAG_STRONG_LG | CA_WEAPONFLAG_STRONG_PG; // (all of weak) + GB + LG + PG

	return qtrue;
}

qboolean G_Gametype_CA_SetAmmo( char *ammoinfo, int *gb, int *rg, int *gl, int *rl, int *pg, int *lg, int *eb )
{
	char *ptr, *tok;
	ptr = ammoinfo;

	tok = COM_Parse( &ptr );
	if( !tok[0] )
		return qfalse;
	*gb = atoi( tok );
	if( *gb < 0 )
		*gb = 0;

	tok = COM_Parse( &ptr );
	if( !tok[0] )
		return qfalse;
	*rg = atoi( tok );
	if( *rg < 0 )
		*rg = 0;

	tok = COM_Parse( &ptr );
	if( !tok[0] )
		return qfalse;
	*gl = atoi( tok );
	if( *gl < 0 )
		*gl = 0;

	tok = COM_Parse( &ptr );
	if( !tok[0] )
		return qfalse;
	*rl = atoi( tok );
	if( *rl < 0 )
		*rl = 0;

	tok = COM_Parse( &ptr );
	if( !tok[0] )
		return qfalse;
	*pg = atoi( tok );
	if( *pg < 0 )
		*pg = 0;

	tok = COM_Parse( &ptr );
	if( !tok[0] )
		return qfalse;
	*lg = atoi( tok );
	if( *lg < 0 )
		*lg = 0;

	tok = COM_Parse( &ptr );
	if( !tok[0] )
		return qfalse;
	*eb = atoi( tok );
	if( *eb < 0 )
		*eb = 0;

	return qtrue;
}

edict_t *G_Gametype_CA_SelectSpawnPoint( edict_t *ent )
{
	if( ent && ent->r.inuse && ent->s.team >= TEAM_ALPHA && ent->s.team < TEAM_ALPHA + g_maxteams->integer && !ent->r.client->is_coach && cagame.spawnpoint[ent->s.team] )
		return cagame.spawnpoint[ent->s.team];
	else
		return SelectDeathmatchSpawnPoint( ent );
}

static void G_Gametype_CA_SetUpSpawnPoint( void )
{
	int team;
	edict_t *spot = NULL;
	edict_t *prev_spot = NULL;

	G_Teams_UpdateMembersList(); // update teamlist[team].numplayers

	memset( &cagame.spawnpoint, 0, sizeof( cagame.spawnpoint ) );

	for( team = TEAM_ALPHA; team < TEAM_ALPHA + g_maxteams->integer; team++ )
	{
		int i;
		edict_t *e;
		if( !teamlist[team].numplayers )
			continue;


		for( i = 0; teamlist[team].playerIndices[i] != -1; i++ )
		{
			int count;
			e = game.edicts + teamlist[team].playerIndices[i];
			if( !e->r.inuse )
				continue;
			if( e->r.client->is_coach )  // ignore coachs
				continue;

			for( count = 0; count < 100; count++ )
			{                            // inhibit to have same spawnpoint
				spot = SelectDeathmatchSpawnPoint( e );
				if( !prev_spot )
					break;
				if( DistanceFast( spot->s.origin, prev_spot->s.origin ) > 0.1 )  // FIXME: need to customize by cvar?
					break;
			}
			cagame.spawnpoint[team] = spot;
			prev_spot = spot;
			break;
		}
	}
}
