#include "g_local.h"

void SP_misc_teleporter_dest (edict_t *ent);
void button_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
void button_use (edict_t *self, edict_t *other, edict_t *activator);
void button_fire (edict_t *self);
void button_wait (edict_t *self);
void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*));
float	PlayersRangeFromSpot (edict_t *spot);

#define	STATE_TOP			0
#define	STATE_BOTTOM		1
#define STATE_UP			2
#define STATE_DOWN			3


//
// Base Defence Map ents
//

/*QUAKED info_player_base (1 0 1) (-16 -16 -24) (16 16 32)
potential defence spawning position for Q2BD games
*/
void SP_info_player_base(edict_t *self)
{
	if (!deathmatch->value)
	{
		G_FreeEdict (self);
		return;
	}
	gi.linkentity (self);

	//SP_misc_teleporter_dest (self);
}

/*QUAKED info_player_attack (1 0 1) (-16 -16 -24) (16 16 32)
potential attack spawning position for Q2BD games
*/
void SP_info_player_attack(edict_t *self)
{
	if (!deathmatch->value)
	{
		G_FreeEdict (self);
		return;
	}
	gi.linkentity (self);

	//SP_misc_teleporter_dest (self);
}


void teleport_to_base (edict_t *other)
{
	edict_t		*dest;
	int			i;
	
	float	bestdistance, bestplayerdistance;
	edict_t	*spot;


	if (!other->client)
		return;
	
	//Give the player a URD since he is about to become a defender
	//item = FindItem("URD");
	//other->pers.selected_item = ITEM_INDEX(item);
	//other->pers.inventory[client->pers.selected_item] = 1;

	spot = NULL;
	dest = NULL;
	bestdistance = 0;
	while ((spot = G_Find (spot, FOFS(classname), "info_player_base")) != NULL)	//Lab Rat
	{
		bestplayerdistance = PlayersRangeFromSpot (spot);

		if (bestplayerdistance > bestdistance)
		{
			dest = spot;
			bestdistance = bestplayerdistance;
		}
	}

	// if there is a player just spawned on each and every start spot
	// we have no choice to turn one into a telefrag meltdown
	if (!dest)
		dest = G_Find (NULL, FOFS(classname), "info_player_base");		//Lab Rat


	// unlink to make sure it can't possibly interfere with KillBox
	gi.unlinkentity (other);

	VectorCopy (dest->s.origin, other->s.origin);
	VectorCopy (dest->s.origin, other->s.old_origin);
	other->s.origin[2] += 10;

	// clear the velocity and hold them in place briefly
	VectorClear (other->velocity);
	other->client->ps.pmove.pm_time = 160>>3;		// hold time
	other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT;

	// draw the teleport splash at source and on the player
	//self->owner->s.event = EV_PLAYER_TELEPORT;
	other->s.event = EV_PLAYER_TELEPORT;

	// set angles
	for (i=0 ; i<3 ; i++)
		other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]);

	VectorClear (other->s.angles);
	VectorClear (other->client->ps.viewangles);
	VectorClear (other->client->v_angle);

	// kill anything at the destination
	KillBox (other);

	gi.linkentity (other);
}

void BaseChangeOver (edict_t *self)
{
	int i, lastbase, kills;
	edict_t *other;
	
	//save off the team that was defending
	lastbase = TeamBase;

	//Change the base team, for all other updates
	TeamBase = self->client->pers.team;

	
	//Increase since last base
	for (i=0; i <= BD_MAX_TEAM; i++) 
	{
		if (!teams[i])
			continue;
		if (i == TeamBase)
			teams[i]->since_last_base = 0;
		else 
			++(teams[i]->since_last_base);
	}

	kills = 0;
	for (i=0; i<game.maxclients; i++)
	{
		if (!(g_edicts[i+1].inuse))
			continue;

		if (!(teams[g_edicts[i+1].client->pers.team]))
			continue;

		other = &g_edicts[i+1];

		//Gib players on the leaving team
		if (other->client->pers.team == lastbase) 
		{
			T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_CHANGEOVER);
			++kills;
			continue;
		}

		//Reward players on the incoming team
		if (other->client->pers.team == TeamBase) 
		{
			other->client->resp.score += bonus[CHANGEOVER_BONUS];
			teams[other->client->pers.team]->total_score += bonus[CHANGEOVER_BONUS];
			//And teleport them to the base, unless they are the person who pressed the button
			if (other != self)
				teleport_to_base (other);
		}
	}

	//Reward the person who did it, and send a message
	gi.bprintf (PRINT_MEDIUM, "%s takes over %s team's base and claims %i lives.\n", self->client->pers.netname, teams[lastbase]->name, kills);
	self->client->resp.score += kills;
	teams[self->client->pers.team]->total_score += kills;

	//Reset the alarm time, incoming players may have trigged a sensor, but they 
	//do not want to see the warning message upon the changeover.
	level.AlarmTime = 0;

	//Respawn any explosive brushes (basewalls)
	other = NULL;
	while ((other = G_Find (other, FOFS(classname), "func_explosive")) != NULL)	
	{
		func_explosive_respawn (other);
	}

	//Change lasers
	other = NULL;
	while ((other = G_Find (other, FOFS(classname), "target_laser")) != NULL)	
	{
		if (other->spawnflags & 128)
			other->s.skinnum = teams[TeamBase]->laser;
	}

	//Change banners
	other = NULL;
	while ((other = G_Find (other, FOFS(classname), "misc_banner")) != NULL)	
	{
			//gi.unlinkentity (other);

			other->s.modelindex = gi.modelindex (teams[TeamBase]->banner);	
			other->s.skinnum = teams[TeamBase]->banner_skin;

			gi.linkentity (other);

	}
}
 



/*QUAKED func_base_button (0 .5 .8) ?
When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again.

Seemed easyest to just created a whole new func_ than to use spawnflags to dictate properties.

"angle"		determines the opening direction
"target"	all entities with a matching targetname will be used
"speed"		override the default 40 speed
"wait"		override the default 1 second wait (-1 = never return)
"lip"		override the default 4 pixel lip remaining at end of move
"health"	if set, the button must be killed instead of touched
"sounds"
1) silent
2) steam metal
3) wooden clunk
4) metallic click
5) in-out
*/

void base_button_fire (edict_t *self)
{
	if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP)
		return;

	self->moveinfo.state = STATE_UP;
	if (self->moveinfo.sound_start && !(self->flags & FL_TEAMSLAVE))
		gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
	Move_Calc (self, self->moveinfo.end_origin, button_wait);
	BaseChangeOver(self->activator);
}

void base_button_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
	if (!other->client)
		return;

	if (other->health <= 0)
		return;
	
	//Lab Rat, don't want peeps pressing their own base button.
	if (other->client->pers.team == TeamBase)
		return;

	self->activator = other;
	base_button_fire (self);
}


void SP_func_base_button (edict_t *ent)
{
	vec3_t	abs_movedir;
	float	dist;

	G_SetMovedir (ent->s.angles, ent->movedir);
	ent->movetype = MOVETYPE_STOP;
	ent->solid = SOLID_BSP;
	gi.setmodel (ent, ent->model);

	if (ent->sounds != 1)
		ent->moveinfo.sound_start = gi.soundindex ("switches/butn2.wav");
	
	if (!ent->speed)
		ent->speed = 40;
	if (!ent->accel)
		ent->accel = ent->speed;
	if (!ent->decel)
		ent->decel = ent->speed;

	if (!ent->wait)
		ent->wait = 3;
	if (!st.lip)
		st.lip = 4;

	VectorCopy (ent->s.origin, ent->pos1);
	abs_movedir[0] = fabs(ent->movedir[0]);
	abs_movedir[1] = fabs(ent->movedir[1]);
	abs_movedir[2] = fabs(ent->movedir[2]);
	dist = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip;
	VectorMA (ent->pos1, dist, ent->movedir, ent->pos2);

	ent->use = button_use;
	ent->s.effects |= EF_ANIM01;

	if (ent->health)
	{
		ent->max_health = ent->health;
		ent->die = button_killed;
		ent->takedamage = DAMAGE_YES;
	}
	else if (! ent->targetname)
		ent->touch = base_button_touch;

	ent->moveinfo.state = STATE_BOTTOM;

	ent->moveinfo.speed = ent->speed;
	ent->moveinfo.accel = ent->accel;
	ent->moveinfo.decel = ent->decel;
	ent->moveinfo.wait = ent->wait;
	VectorCopy (ent->pos1, ent->moveinfo.start_origin);
	VectorCopy (ent->s.angles, ent->moveinfo.start_angles);
	VectorCopy (ent->pos2, ent->moveinfo.end_origin);
	VectorCopy (ent->s.angles, ent->moveinfo.end_angles);

	gi.linkentity (ent);
}

/*QUAKED func_base (0 .5 .8) ? 
Defines the inside of the base so that bonuses can be correctly issued.
*/

void func_base_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
	if (!other->client)
		return;

	if (other->health <= 0)
		return;
	
	other->client->base_time = level.time + .5;
}

void SP_func_base (edict_t *self)
{
	self->touch = func_base_touch;

	gi.setmodel (self, self->model);
	self->solid = SOLID_TRIGGER;
	gi.linkentity (self);
}

/*QUAKED base_sensor (0 1 0) (-8 -8 -8) (8 8 8) START_OFF
Placed in maps to create an alarm system.
message			printed to all defending clients when the alarm is set off (does not work at the moment)
dmg_radius		radius where the sensor is acitve.

Use START_OFF to create extra points where the alarm sound is made without creating more sensors
*/
void base_sensor_think (edict_t *self)
{
	edict_t *other, *alarm;
	int i, j, dist;
	vec3_t	v;

	self->nextthink = level.time + (rand() % 10);
	
	//find any close by clients.
	for (i=0; i<game.maxclients; i++)
	{
		//No client, no good
		if (!(g_edicts[i+1].inuse))
			continue;

		//No team, no good
		if (!(teams[g_edicts[i+1].client->pers.team]))
			continue;

		//dead, no good
		if (g_edicts[i+1].health <= 0)
			continue;

		//other = &g_edicts[i+1];

		//Defending, no good
		if (g_edicts[i+1].client->pers.team == TeamBase)
			continue;

		VectorSubtract (self->s.origin, g_edicts[i+1].s.origin, v);
		dist = VectorLength(v);
		
		//Too far away, no good
		
		if (dist > self->dmg_radius)
			continue;
		else 
		{
			level.AlarmTime = level.time + BD_ALARM_TIME;
			
			alarm = NULL;
			while ((alarm = G_Find (alarm, FOFS(classname), "base_sensor")) != NULL)
			{
				gi.sound(alarm, CHAN_VOICE, gi.soundindex("world/x_alarm.wav"), 1, ATTN_NORM, 0);
			}
		
			for (j = 1; j <= game.maxclients; j++)
			{
				other = &g_edicts[j];
				if (!other->inuse)
					continue;
				if (!other->client)
					continue;
				if (other->client->pers.team != TeamBase)
					continue;
				//if (alarm->message)
				//	gi.cprintf(other, PRINT_HIGH, "%s\n", alarm->message);
				//else
					gi.cprintf(other, PRINT_HIGH, "Intruder alert.\n");
			}
		}
	}
}

void SP_base_sensor (edict_t *ent)
{
	ent->movetype = MOVETYPE_NONE;
	ent->solid = SOLID_NOT;
	if (!ent->spawnflags & 1)
	{
		ent->think = base_sensor_think;
		ent->nextthink = level.time + (rand() % 10);
		if (!ent->dmg_radius)
			ent->dmg_radius = BD_SENSOR_RANGE;
	}

	gi.linkentity (ent);
}

/*
HasPowerUp, just a quick utility function to neaten up the award bonuses code
*/

qboolean HasPowerup (edict_t *ent)
{

	if (!ent->client)
		return false;

	if (ent->client->quad_framenum > level.framenum)
		return true;

	if (ent->client->invincible_framenum > level.framenum)
		return true;

	if (ent->client->breather_framenum > level.framenum)
		return true;

	if (ent->client->enviro_framenum > level.framenum)
		return true;

	if (ent->client->pers.inventory[ITEM_INDEX(FindItem ("Power Shield"))])
		return true;

	if ((int)(dmflags->value) & DF_INSTANT_ITEMS)
		return false;

	
	if (ent->client->pers.inventory[ITEM_INDEX(FindItem ("Quad Damage"))])
		return true;

	if (ent->client->pers.inventory[ITEM_INDEX(FindItem ("Invulnerability"))])
		return true;

	if (ent->client->pers.inventory[ITEM_INDEX(FindItem ("Silencer"))])
		return true;

	if (ent->client->pers.inventory[ITEM_INDEX(FindItem ("Rebreather"))])
		return true;

	if (ent->client->pers.inventory[ITEM_INDEX(FindItem ("Environment Suit"))])
		return true;

	return false;
}

/*
HasGuns, just a quick utility function to neaten up the award bonuses code
*/
int HasGuns (edict_t *ent)
{
	int guns = 0;
	if (!ent->client)
		return 0;

	if (ent->client->pers.inventory[ITEM_INDEX(FindItem ("Shotgun"))])
		guns += 1;
	if (ent->client->pers.inventory[ITEM_INDEX(FindItem ("Super Shotgun"))])
		guns += 1;
	if (ent->client->pers.inventory[ITEM_INDEX(FindItem ("Machinegun"))])
		guns += 1;
	if (ent->client->pers.inventory[ITEM_INDEX(FindItem ("Chaingun"))])
		guns += 2;
	if (ent->client->pers.inventory[ITEM_INDEX(FindItem ("Grenade Launcher"))])
		guns += 1;
	if (ent->client->pers.inventory[ITEM_INDEX(FindItem ("Rocket Launcher"))])
		guns += 2;
	if (ent->client->pers.inventory[ITEM_INDEX(FindItem ("HyperBlaster"))])
		guns += 2;
	if (ent->client->pers.inventory[ITEM_INDEX(FindItem ("Railgun"))])
		guns += 2;
	if (ent->client->pers.inventory[ITEM_INDEX(FindItem ("BFG10K"))])
		guns += 3;

	return guns;
}

/*
AwardBonuses, sort out whether or not we should give a player any base defence bonuses
and give them where required.
*/
qboolean AwardBonuses (edict_t *targ, edict_t *inflictor, edict_t *attacker)
{
	qboolean message = false;
	int given = 0, nearplayers = 0;
	edict_t *ent = NULL;

	if (!targ->client)
		return false;
	if (!attacker->client)
		return false;

	//Possible defender bonuses

	if ( (attacker->client->pers.team == TeamBase) && 
		   (!OnSameTeam (attacker, targ) ) )
	{

		ent = NULL;
		//Check out all the suff that is around the dead player	
		while (ent = findradius (ent, targ->s.origin, BD_BONUS_RADII))
		{
			// Button protect, cumulative with respect to all bonuses.
			// When a defender kills an attacker while either are near to the base button.
			// Not cumulative if both attacker and target are near to the button.
			if (strcmp(ent->classname, "func_base_button") == 0) 
			{
				attacker->client->resp.score += bonus[BD_BUTTON_PROTECT];
				teams[attacker->client->pers.team]->total_score += bonus[BD_BUTTON_PROTECT];
				gi.bprintf (PRINT_MEDIUM,"%s prevents %s from taking %s team's base\n", attacker->client->pers.netname, targ->client->pers.netname, teams[TeamBase]->name);
				message = true;
				given |= 1;
			}

			
			// Priority Targets, given to a defender for killing an attacker
			// with (dealt with later in the code) or near one of the main power ups.  
			// Envirosuit is included for completeness only.  
			// But maybe someone will do a map where slime gives
			// attacker a back way in.    ?:)
			// Cumulative with respect to multiple items, both wearing
			// and close to the target.
			if ( (strcmp(ent->classname, "item_quad") == 0) ||
				  (strcmp(ent->classname, "item_invulnerability") == 0) ||
				   (strcmp(ent->classname, "item_silencer") == 0) ||
				    (strcmp(ent->classname, "item_breather") == 0) ||
					 (strcmp(ent->classname, "item_enviro") == 0) ||
					  (strcmp(ent->classname, "item_power_shield") == 0) )

			{
				attacker->client->resp.score += bonus[BD_PRIORITY_BONUS];
				teams[attacker->client->pers.team]->total_score += bonus[BD_PRIORITY_BONUS];
				gi.bprintf (PRINT_MEDIUM,"%s was prioritised by %s\n", targ->client->pers.netname, attacker->client->pers.netname);
				message = true;
			}
			

			
			if (ent->client)
			{
				// Raid Prevention, when a defender takes out an attacker inside
				// of the base who was part of a large group of attackers	
				if(OnSameTeam (ent, targ) && (ent->client->base_time > level.time) && (ent->client->pers.health > 0))
					++nearplayers;	// count up attacking player on the inside.

				// Healer protect, when a defender kills an attacker and either of them are 
				// near to someone who has just healed the wall.  Should be cumulative with 
				// respect to other bonuses and multiple healers but not to 
				// both the attacker and target near to the healer.
				if ((ent->client->base_flag&BD_WALL_HEAL) && (ent->client->base_flag_time > level.time + BD_WALL_PROTECT_TIMEOUT))
				{
					attacker->client->resp.score += bonus[BD_HEALER_PROTECT_BONUS];
					teams[attacker->client->pers.team]->total_score += bonus[BD_HEALER_PROTECT_BONUS];
					gi.bprintf (PRINT_MEDIUM,"%s covers %s from %s's fire\n", attacker->client->pers.netname, ent->client->pers.netname, targ->client->pers.netname);
					message = true;
					given |= 2;
				}

			}	
			
			
		}
		
		
		if (nearplayers)
		{
			attacker->client->resp.score += nearplayers;
			teams[attacker->client->pers.team]->total_score += nearplayers;
			gi.bprintf (PRINT_MEDIUM,"%s eliminates raid member %s\n", attacker->client->pers.netname, targ->client->pers.netname);
			message = true;
		}

		
		ent = NULL;
		//Check out all the suff that is around the attacking player	
		while (ent = findradius (ent, attacker->s.origin, BD_BONUS_RADII))
		{
			// Not cumulative if both attacker and target are neer to the button.
			if ((strcmp(ent->classname, "func_base_button") == 0) && (!given&1))
			{
				attacker->client->resp.score += bonus[BD_BUTTON_PROTECT];
				teams[attacker->client->pers.team]->total_score += bonus[BD_BUTTON_PROTECT];
				gi.bprintf (PRINT_MEDIUM,"%s prevents %s from taking %s team's base\n", attacker->client->pers.netname, targ->client->pers.netname, teams[TeamBase]->name);
				message = true;
			}
				
			if (ent->client)
			{
				if ((ent->client->base_flag&BD_WALL_HEAL) && (ent->client->base_flag_time > level.time + BD_WALL_PROTECT_TIMEOUT) && (!given&2))
				{
					attacker->client->resp.score += bonus[BD_HEALER_PROTECT_BONUS];
					teams[attacker->client->pers.team]->total_score += bonus[BD_HEALER_PROTECT_BONUS];
					gi.bprintf (PRINT_MEDIUM,"%s covers %s from %s's fire\n", attacker->client->pers.netname, ent->client->pers.netname, targ->client->pers.netname);
					message = true;
				}
			}

		}

				
		if ( HasPowerup (targ))
		{
			attacker->client->resp.score += bonus[BD_PRIORITY_BONUS];
			teams[attacker->client->pers.team]->total_score += bonus[BD_PRIORITY_BONUS];
			gi.bprintf (PRINT_MEDIUM,"%s was prioritised by %s\n", targ->client->pers.netname, attacker->client->pers.netname);
			message = true;
		}

		// Wall protect, awarded for killing someone who has damaged the wall,
		// before the timeout passes
		if ((targ->client->base_flag & BD_WALL_HURT) && (targ->client->base_flag_time + BD_WALL_PROTECT_TIMEOUT > level.time ))
		{
			attacker->client->resp.score += bonus[BD_WALL_PROTECT];
			teams[attacker->client->pers.team]->total_score += bonus[BD_WALL_PROTECT];
			gi.bprintf (PRINT_MEDIUM,"%s stops %s from damaging %s teams's defenses\n", attacker->client->pers.netname, targ->client->pers.netname, teams[TeamBase]->name);
			message = true;
		}
			
		

		// Hardcore kill, awarded for killing an attacker who is heavily armed
		// i.e. has more than a threshold number of weapons.
		if (HasGuns (targ) >= BD_HARDCORE_THRESHOLD)
		{
			attacker->client->resp.score += bonus[BD_HARDCORE_BONUS];
			teams[attacker->client->pers.team]->total_score += bonus[BD_HARDCORE_BONUS];
			gi.bprintf (PRINT_MEDIUM,"%s's hardcore kit was no match for %s\n", targ->client->pers.netname, attacker->client->pers.netname);
			message = true;
		}
	}//end of defender bonuses.


	
	//Possible attacker bonuses
	if ((attacker->client->pers.team != TeamBase) && 
		(!OnSameTeam (attacker, targ) ) )
	{
		// Fairly few bonuses for attacking team, to make
		// having the base an advantage.

		// Healer kill, bonus for an attacker taking out some
		// one who has been healing the wall.
		if ((targ->client->base_flag&BD_WALL_HEAL) && (targ->client->base_flag_time + BD_WALL_PROTECT_TIMEOUT > level.time))
		{
			attacker->client->resp.score += bonus[BD_HEALER_KILL];
			teams[attacker->client->pers.team]->total_score += bonus[BD_HEALER_KILL];
			gi.bprintf (PRINT_MEDIUM,"%s stops %s from repairing %s teams's defenses\n", attacker->client->pers.netname, targ->client->pers.netname, teams[TeamBase]->name);
			message = true;
		}
	}
	

	return message;
}


/*
======================================================================

SAY_TEAM

======================================================================
*/
void buildboxpoints(vec3_t p[8], vec3_t org, vec3_t mins, vec3_t maxs)
{
	VectorAdd(org, mins, p[0]);
	VectorCopy(p[0], p[1]);
	p[1][0] -= mins[0];
	VectorCopy(p[0], p[2]);
	p[2][1] -= mins[1];
	VectorCopy(p[0], p[3]);
	p[3][0] -= mins[0];
	p[3][1] -= mins[1];
	VectorAdd(org, maxs, p[4]);
	VectorCopy(p[4], p[5]);
	p[5][0] -= maxs[0];
	VectorCopy(p[0], p[6]);
	p[6][1] -= maxs[1];
	VectorCopy(p[0], p[7]);
	p[7][0] -= maxs[0];
	p[7][1] -= maxs[1];
}


qboolean 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 false; // bmodels not supported

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

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

	return false;
}


// This array is in 'importance order', it indicates what items are
// more important when reporting their names.

item_p loc_names[] = 
{
	//{	"func_explosive",			1 },
	//{	"func_base_button",			1 },
	{	"item_quad",				2 }, 
	{	"item_invulnerability",		2 },
	{	"weapon_bfg",				3 },
	{	"weapon_railgun",			4 },
	{	"weapon_rocketlauncher",	4 },
	{	"weapon_hyperblaster",		4 },
	{	"weapon_chaingun",			4 },
	{	"weapon_grenadelauncher",	4 },
	{	"weapon_machinegun",		4 },
	{	"weapon_supershotgun",		4 },
	{	"weapon_shotgun",			4 },
	{	"item_power_screen",		5 },
	{	"item_power_shield",		5 },
	{	"item_armor_body",			6 },
	{	"item_armor_combat",		6 },
	{	"item_armor_jacket",		6 },
	{	"item_silencer",			7 },
	{	"item_breather",			7 },
	{	"item_enviro",				7 },
	{	"item_adrenaline",			7 },
	{	"item_bandolier",			8 },
	{	"item_pack",				8 },
	{ NULL, 0 }
};

void Say_Team_Location(edict_t *who, char *buf)
{
	edict_t *what = NULL;
	edict_t *hot = NULL;
	float hotdist = 999999, newdist;
	vec3_t v;
	int hotindex = 999;
	int i;
	gitem_t *item;
	int nearteam = -1;
	//edict_t *flag1, *flag2;
	qboolean hotsee = false;
	qboolean cansee;

	while ((what = findradius(what, who->s.origin, 1024)) != NULL) 
	{
		// find what in loc_classnames
		for (i = 0; loc_names[i].classname; i++)
			if (strcmp(what->classname, loc_names[i].classname) == 0)
				break;
		if (!loc_names[i].classname)
			continue;
		// something we can see get priority over something we can't
		cansee = CanSee(what, who);
		if (cansee && !hotsee) 
		{
			hotsee = true;
			hotindex = loc_names[i].priority;
			hot = what;
			VectorSubtract(what->s.origin, who->s.origin, v);
			hotdist = VectorLength(v);
			continue;
		}
		// if we can't see this, but we have something we can see, skip it
		if (hotsee && !cansee)
			continue;
		if (hotsee && hotindex < loc_names[i].priority)
			continue;
		VectorSubtract(what->s.origin, who->s.origin, v);
		newdist = VectorLength(v);
		if (newdist < hotdist || 
			(cansee && loc_names[i].priority < hotindex)) 
		{
			hot = what;
			hotdist = newdist;
			hotindex = i;
			hotsee = CanSee(hot, who);
		}
	}

	if (!hot) 
	{
		strcpy(buf, "nowhere");
		return;
	}

	// we now have the closest item
	// see if there's more than one in the map, if so
	// we need to determine what team is closest
	/*what = NULL;
	while ((what = G_Find(what, FOFS(classname), hot->classname)) != NULL) 
	{
		if (what == hot)
			continue;
		// if we are here, there is more than one, find out if hot
		// is closer to red flag or blue flag
		if ((flag1 = G_Find(NULL, FOFS(classname), "item_flag_team1")) != NULL &&
			(flag2 = G_Find(NULL, FOFS(classname), "item_flag_team2")) != NULL) 
		{
			VectorSubtract(hot->s.origin, flag1->s.origin, v);
			hotdist = VectorLength(v);
			VectorSubtract(hot->s.origin, flag2->s.origin, v);
			newdist = VectorLength(v);
			if (hotdist < newdist)
				nearteam = CTF_TEAM1;
			else if (hotdist > newdist)
				nearteam = CTF_TEAM2;
		}
		break;
	}*/

	if ((item = FindItemByClassname(hot->classname)) == NULL) {
		strcpy(buf, "nowhere");
		return;
	}

	// in water?
	if (who->waterlevel)
		strcpy(buf, "in the water ");
	else
		*buf = 0;

	// near or above
	VectorSubtract(who->s.origin, hot->s.origin, v);
	if (fabs(v[2]) > fabs(v[0]) && fabs(v[2]) > fabs(v[1]))
		if (v[2] > 0)
			strcat(buf, "above ");
		else
			strcat(buf, "below ");
	else
		strcat(buf, "near ");

	if (who->client->base_time > level.time/*in base*/)
	{
		if (who->client->pers.team == TeamBase/*defender*/)
			strcat(buf, "our ");
		else /*attacker*/
		{
			strcat(buf, "the ");	
			strcat(buf, teams[TeamBase]->name);
			strcat(buf, " team's ");
		}
	}
	else if (who->client->base_time <= level.time/*out of base*/)
	{
		if (who->client->pers.team == TeamBase/*defender*/)
			strcat(buf, "the enemy ");
		else
			strcat(buf, "the ");	
	}
	else 
		strcat(buf, "the ");

	strcat(buf, item->pickup_name);
}

void Say_Team_Armor(edict_t *who, char *buf)
{
	gitem_t		*item;
	int			index, cells;
	int			power_armor_type;

	*buf = 0;

	power_armor_type = PowerArmorType (who);
	if (power_armor_type)
	{
		cells = who->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))];
		if (cells)
			sprintf(buf+strlen(buf), "%s with %i cells ",
				(power_armor_type == POWER_ARMOR_SCREEN) ?
				"Power Screen" : "Power Shield", cells);
	}

	index = ArmorIndex (who);
	if (index)
	{
		item = GetItemByIndex (index);
		if (item) {
			if (*buf)
				strcat(buf, "and ");
			sprintf(buf+strlen(buf), "%i units of %s",
				who->client->pers.inventory[index], item->pickup_name);
		}
	}

	if (!*buf)
		strcpy(buf, "no armor");
}

void Say_Team_Health(edict_t *who, char *buf)
{
	if (who->health <= 0)
		strcpy(buf, "dead");
	else
		sprintf(buf, "%i health", who->health);
}

/*void Say_Team_Status(edict_t *who, char *buf)
{
	if (who->health <= 0)
		strcpy(buf, "0 percent");
	else
		sprintf(buf, "%i percent", who->health);
}*/

/*void Say_Team_Tech(edict_t *who, char *buf)
{
	gitem_t *tech;
	int i;

	// see if the player has a tech powerup
	i = 0;
	while (tnames[i]) {
		if ((tech = FindItemByClassname(tnames[i])) != NULL &&
			who->client->pers.inventory[ITEM_INDEX(tech)]) {
			sprintf(buf, "the %s", tech->pickup_name);
			return;
		}
		i++;
	}
	strcpy(buf, "no powerup");
}*/

void Say_Team_Weapon(edict_t *who, char *buf)
{
	if (who->client->pers.weapon)
		strcpy(buf, who->client->pers.weapon->pickup_name);
	else
		strcpy(buf, "none");
}

void Say_Team_Sight(edict_t *who, char *buf)
{
	int i;
	edict_t *targ;
	int n = 0;
	char s[1024];
	char s2[1024];

	*s = *s2 = 0;
	for (i = 1; i <= maxclients->value; i++) {
		targ = g_edicts + i;
		if (!targ->inuse || 
			targ == who ||
			!CanSee(targ, who))
			continue;
		if (*s2) {
			if (strlen(s) + strlen(s2) + 3 < sizeof(s)) {
				if (n)
					strcat(s, ", ");
				strcat(s, s2);
				*s2 = 0;
			}
			n++;
		}
		strcpy(s2, targ->client->pers.netname);
	}
	if (*s2) {
		if (strlen(s) + strlen(s2) + 6 < sizeof(s)) {
			if (n)
				strcat(s, " and ");
			strcat(s, s2);
		}
		strcpy(buf, s);
	} else
		strcpy(buf, "no one");
}

void Cmd_Say_Team_f(edict_t *who, char *msg)
{
	char outmsg[1024];
	char buf[1024];
	int i;
	char *p;
	edict_t *cl_ent;

	outmsg[0] = 0;

	if (*msg == '\"') {
		msg[strlen(msg) - 1] = 0;
		msg++;
	}

	for (p = outmsg; *msg && (p - outmsg) < sizeof(outmsg) - 1; msg++) {
		if (*msg == '%') {
			switch (*++msg) {
				case 'l' :
				case 'L' :
					Say_Team_Location(who, buf);
					strcpy(p, buf);
					p += strlen(buf);
					break;
				case 'a' :
				case 'A' :
					Say_Team_Armor(who, buf);
					strcpy(p, buf);
					p += strlen(buf);
					break;
				case 'h' :
				case 'H' :
					Say_Team_Health(who, buf);
					strcpy(p, buf);
					p += strlen(buf);
					break;

/*				case 's' :
				case 'S' :
					Say_Team_Status(who, buf);
					strcpy(p, buf);
					p += strlen(buf);
					break;
*/

/*				case 't' :
				case 'T' :
					Say_Team_Tech(who, buf);
					strcpy(p, buf);
					p += strlen(buf);
					break;
*/				case 'w' :
				case 'W' :
					Say_Team_Weapon(who, buf);
					strcpy(p, buf);
					p += strlen(buf);
					break;

				case 'n' :
				case 'N' :
					Say_Team_Sight(who, buf);
					strcpy(p, buf);
					p += strlen(buf);
					break;

				default :
					*p++ = *msg;
			}
		} else
			*p++ = *msg;
	}
	*p = 0;

	for (i = 0; i < maxclients->value; i++) 
	{
		cl_ent = g_edicts + 1 + i;
		if (!cl_ent->inuse)
			continue;
		if (cl_ent->client->pers.team == who->client->pers.team)
			gi.cprintf(cl_ent, PRINT_CHAT, "(%s): %s\n", 
				who->client->pers.netname, outmsg);
	}
}