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

*/
// g_utils.c -- misc utility functions for game module

#include "g_local.h"
#include "g_gametypes.h"

void G_ProjectSource( vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result )
{
	result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1];
	result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1];
	result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + distance[2];
}

/*
=============
G_Find

Searches all active entities for the next one that holds
the matching string at fieldofs (use the FOFS() macro) in the structure.

Searches beginning at the edict after from, or the beginning if NULL
NULL will be returned if the end of the list is reached.

=============
*/
edict_t *G_Find( edict_t *from, size_t fieldofs, char *match )
{
	char	*s;

	if( !from )
		from = world;
	else
		from++;

	for( ; from < &game.edicts[game.numentities]; from++ )
	{
		if( !from->r.inuse )
			continue;
		s = *(char **) ((qbyte *)from + fieldofs);
		if( !s )
			continue;
		if( !Q_stricmp (s, match) )
			return from;
	}

	return NULL;
}


/*
=================
findradius

Returns entities that have origins within a spherical area

findradius (origin, radius)
=================
*/
edict_t *findradius( edict_t *from, vec3_t org, float rad )
{
	vec3_t	eorg;
	int		j;

	if( !from )
		from = world;
	else
		from++;

	for( ; from < &game.edicts[game.numentities]; from++ )
	{
		if( !from->r.inuse )
			continue;
		if( from->r.solid == SOLID_NOT )
			continue;
		for( j=0 ; j<3 ; j++ )
			eorg[j] = org[j] - (from->s.origin[j] + (from->r.mins[j] + from->r.maxs[j])*0.5);
		if( VectorLengthFast(eorg) > rad )
			continue;
		return from;
	}

	return NULL;
}

//=================
//G_FindBoxInRadius
//Returns entities that have their boxes within a spherical area
//=================
edict_t *G_FindBoxInRadius( edict_t *from, vec3_t org, float rad )
{
	int		j;
	vec3_t  mins, maxs;

	if( !from )
		from = world;
	else
		from++;

	for( ; from < &game.edicts[game.numentities]; from++ )
	{
		if( !from->r.inuse )
			continue;
		if( from->r.solid == SOLID_NOT )
			continue;
		// make absolute mins and maxs
		for( j = 0; j < 3; j++ ) {
			mins[j] = from->s.origin[j] + from->r.mins[j];
			maxs[j] = from->s.origin[j] + from->r.maxs[j];
		}
		if( !BoundsAndSphereIntersect(mins,  maxs, org, rad) )
			continue;

		return from;
	}

	return NULL;
}

/*
=============
G_PickTarget

Searches all active entities for the next one that holds
the matching string at fieldofs (use the FOFS() macro) in the structure.

Searches beginning at the edict after from, or the beginning if NULL
NULL will be returned if the end of the list is reached.

=============
*/
#define MAXCHOICES	8

edict_t *G_PickTarget( char *targetname )
{
	edict_t	*ent = NULL;
	int		num_choices = 0;
	edict_t	*choice[MAXCHOICES];

	if( !targetname )
	{
		G_Printf( "G_PickTarget called with NULL targetname\n" );
		return NULL;
	}

	while( 1 )
	{
		ent = G_Find( ent, FOFS(targetname), targetname );
		if( !ent )
			break;
		choice[num_choices++] = ent;
		if( num_choices == MAXCHOICES )
			break;
	}

	if( !num_choices )
	{
		G_Printf( "G_PickTarget: target %s not found\n", targetname );
		return NULL;
	}

	return choice[rand() % num_choices];
}



void Think_Delay( edict_t *ent )
{
	G_UseTargets( ent, ent->activator );
	G_FreeEdict( ent );
}

/*
==============================
G_UseTargets

the global "activator" should be set to the entity that initiated the firing.

If self.delay is set, a DelayedUse entity will be created that will actually
do the SUB_UseTargets after that many seconds have passed.

Centerprints any self.message to the activator.

Search for (string)targetname in all entities that
match (string)self.target and call their .use function

==============================
*/
void G_UseTargets( edict_t *ent, edict_t *activator )
{
	edict_t		*t;

//
// check for a delay
//
	if( ent->delay )
	{
	// create a temp object to fire at a later time
		t = G_Spawn();
		t->classname = "DelayedUse";
		t->nextthink = level.timemsec + 1000 * ent->delay;
		t->think = Think_Delay;
		t->activator = activator;
		if( !activator )
			G_Printf( "Think_Delay with no activator\n" );
		t->message = ent->message;
		t->target = ent->target;
		t->killtarget = ent->killtarget;
		return;
	}
	
	
//
// print the message
//
	if( (ent->message) && !(activator->r.svflags & SVF_MONSTER) )
	{
		G_CenterPrintMsg( activator, "%s", ent->message );
		if( ent->noise_index )
			G_Sound( activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM );
		else
			G_Sound( activator, CHAN_AUTO, trap_SoundIndex( S_WORLD_MESSAGE ), 1, ATTN_NORM );
	}

//
// kill killtargets
//
	if( ent->killtarget )
	{
		t = NULL;
		while( ( t = G_Find(t, FOFS(targetname), ent->killtarget) ) )
		{
			G_FreeEdict( t );
			if( !ent->r.inuse )
			{
				G_Printf( "entity was removed while using killtargets\n" );
				return;
			}
		}
	}

//	G_Printf ("TARGET: activating %s\n", ent->target);

//
// fire targets
//
	if( ent->target )
	{
		t = NULL;
		while( ( t = G_Find(t, FOFS(targetname), ent->target) ) )
		{
			if( t == ent )
			{
				G_Printf( "WARNING: Entity used itself.\n" );
			}
			else
			{
				if( t->use )
					t->use( t, ent, activator );
			}
			if( !ent->r.inuse )
			{
				G_Printf( "entity was removed while using targets\n" );
				return;
			}
		}
	}
}


vec3_t VEC_UP		= {0, -1, 0};
vec3_t MOVEDIR_UP	= {0, 0, 1};
vec3_t VEC_DOWN		= {0, -2, 0};
vec3_t MOVEDIR_DOWN	= {0, 0, -1};

void G_SetMovedir( vec3_t angles, vec3_t movedir )
{
	if( VectorCompare(angles, VEC_UP) )
	{
		VectorCopy( MOVEDIR_UP, movedir );
	}
	else if( VectorCompare(angles, VEC_DOWN) )
	{
		VectorCopy( MOVEDIR_DOWN, movedir );
	}
	else
	{
		AngleVectors( angles, movedir, NULL, NULL );
	}

	VectorClear( angles );
}


float vectoyaw( vec3_t vec )
{
	float	yaw;
	
	if( /* vec[YAW] == 0 && */ vec[PITCH] == 0 ) 
	{
		yaw = 0;
		if( vec[YAW] > 0 )
			yaw = 90;
		else if( vec[YAW] < 0 )
			yaw = -90;
	}
	else
	{
		yaw = RAD2DEG( atan2(vec[YAW], vec[PITCH]) );
		if (yaw < 0)
			yaw += 360;
	}

	return yaw;
}


char *G_CopyString( char *in )
{
	char	*out;
	
	out = G_Malloc( strlen(in)+1 );
	strcpy( out, in );
	return out;
}


void G_InitEdict( edict_t *e )
{
	e->r.inuse = qtrue;
	e->classname = "noclass";
	e->gravity = 1.0;
	e->s.number = e - game.edicts;

	//wsw clean up the backpack counts
	memset( e->invpak, 0, sizeof(e->invpak) );
}

/*
=================
G_Spawn

Either finds a free edict, or allocates a new one.
Try to avoid reusing an entity that was recently freed, because it
can cause the client to think the entity morphed into something else
instead of being removed and recreated, which can cause interpolated
angles and bad trails.
=================
*/
edict_t *G_Spawn( void )
{
	int			i;
	edict_t		*e;

	e = &game.edicts[game.maxclients+1];
	for( i=game.maxclients+1 ; i<game.numentities ; i++, e++ )
	{
		// the first couple seconds of server time can involve a lot of
		// freeing and allocating, so relax the replacement policy
		if( !e->r.inuse && ( e->freetime < 2 || level.time - e->freetime > 0.5 ) )
		{
			G_InitEdict( e );
			return e;
		}
	}

	if( i == game.maxentities )
		G_Error( "G_Spawn: no free edicts" );

	game.numentities++;

	trap_LocateEntities( game.edicts, sizeof(game.edicts[0]), game.numentities, game.maxentities );

	G_InitEdict( e );

	return e;
}

/*
=================
G_FreeEdict

Marks the edict as free
=================
*/
void G_FreeEdict( edict_t *ed )
{
	trap_UnlinkEntity( ed );		// unlink from world

	if( (ed - game.edicts) <= (game.maxclients + BODY_QUEUE_SIZE) )
	{
//		G_Printf ("tried to free special edict\n");
		return;
	}

	G_FreeAI( ed ); //mbotgame
	memset( ed, 0, sizeof(*ed) );
	ed->classname = "freed";
	ed->freetime = level.time;
	ed->r.inuse = qfalse;
}

/*
============
G_AddEvent

============
*/
void G_AddEvent( edict_t *ent, int event, int parm, qboolean highPriority )
{
	if( !ent || ent == world || !ent->r.inuse ) {
		return;
	}
	if( !event ) {
		return;
	}
	// replace the most outdated low-priority event
	if( !highPriority ) {
		int oldEventNum = -1;

		if( !ent->eventPriority[0] && !ent->eventPriority[1] ) {
			oldEventNum = (ent->numEvents + 1) & 2;
		} else if( !ent->eventPriority[0] ) {
			oldEventNum = 0;
		} else if( !ent->eventPriority[1] ) {
			oldEventNum = 1;
		}

		// no luck
		if( oldEventNum == -1 ) {
			return;
		}

		ent->s.events[oldEventNum] = event;
		ent->s.eventParms[oldEventNum] = parm;
		ent->eventPriority[oldEventNum] = qfalse;
		return;
	}

	ent->s.events[ent->numEvents & 1] = event;
	ent->s.eventParms[ent->numEvents & 1] = parm;
	ent->eventPriority[ent->numEvents & 1] = highPriority;
	ent->numEvents++;
}

/*
============
G_SpawnEvent

============
*/
edict_t *G_SpawnEvent( int event, int parm, vec3_t origin )
{
	edict_t *ent;

	ent = G_Spawn();
	ent->s.type = ET_EVENT;
	ent->freeAfterEvent = qtrue;
	if( origin != NULL )
		VectorCopy( origin, ent->s.origin );
	G_AddEvent( ent, event, parm, qtrue );

	trap_LinkEntity( ent );

	return ent;
}

/*
============
G_TurnEntityIntoEvent

============
*/
void G_TurnEntityIntoEvent( edict_t *ent, int event, int parm )
{
	ent->s.type = ET_EVENT;
	ent->r.solid = SOLID_NOT;
	ent->freeAfterEvent = qtrue;
	G_AddEvent( ent, event, parm, qtrue );

	trap_LinkEntity( ent );
}

#ifdef COLLISION4D
/*
============
G_TouchTriggers

============
*/
void G_TouchTriggers( edict_t *ent )
{
	int			i, num;
	edict_t		*hit;
	int			touch[MAX_EDICTS];
	vec3_t		mins, maxs;

	// dead things don't activate triggers!
	if( (ent->r.client || (ent->r.svflags & SVF_MONSTER)) && G_IsDead(ent) )
		return;

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

	// FIXME: should be s.origin + mins and s.origin + maxs because of absmin and absmax padding?
	num = trap_BoxEdicts( ent->r.absmin, ent->r.absmax, touch, MAX_EDICTS, AREA_TRIGGERS );

	// be careful, it is possible to have an entity in this
	// list removed before we get to it (killtriggered)
	for( i = 0; i < num; i++ ) {
		hit = &game.edicts[touch[i]];
		if( !hit->r.inuse )
			continue;
		if( !hit->touch )
			continue;
		if( !hit->item && !trap_EntityContact( mins, maxs, hit ) )
			continue;
		hit->touch (hit, ent, NULL, 0);
	}
}

/*
============
G_TouchSolids

Call after linking a new trigger in during gameplay
to force all entities it covers to immediately touch it
============
*/
void G_TouchSolids( edict_t *ent )
{
	int			i, num;
	edict_t		*hit;
	int			touch[MAX_EDICTS];
	vec3_t		mins, maxs;

	// FIXME: should be s.origin + mins and s.origin + maxs because of absmin and absmax padding?
	num = trap_BoxEdicts( ent->r.absmin, ent->r.absmax, touch, MAX_EDICTS, AREA_SOLID);

	// be careful, it is possible to have an entity in this
	// list removed before we get to it (killtriggered)
	for( i = 0; i < num; i++ ) {
		hit = &game.edicts[touch[i]];
		if( !hit->r.inuse )
			continue;

		VectorAdd( hit->s.origin, hit->r.mins, mins );
		VectorAdd( hit->s.origin, hit->r.maxs, maxs );
		if( !ent->item && !trap_EntityContact( mins, maxs, ent ) )
			continue;

		if( ent->touch )
			ent->touch( hit, ent, NULL, 0 );
		if( !ent->r.inuse )
			break;
	}
}
#else
/*
============
G_TouchTriggers

============
*/
void G_TouchTriggers (edict_t *ent)
{
	int			i, num;
	edict_t		*touch[MAX_EDICTS], *hit;
	vec3_t		mins, maxs;

	// dead things don't activate triggers!
	if( (ent->r.client || (ent->r.svflags & SVF_MONSTER)) && G_IsDead(ent) )
		return;

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

	// FIXME: should be s.origin + mins and s.origin + maxs because of absmin and absmax padding?
	num = trap_BoxEdicts( ent->r.absmin, ent->r.absmax, touch, MAX_EDICTS, AREA_TRIGGERS );

	// be careful, it is possible to have an entity in this
	// list removed before we get to it (killtriggered)
	for( i = 0; i < num; i++ ) {
		hit = touch[i];
		if( !hit->r.inuse )
			continue;
		if( !hit->touch )
			continue;
		if( !hit->item && !trap_EntityContact( mins, maxs, hit ) )
			continue;
		hit->touch (hit, ent, NULL, 0);
	}
}

/*
============
G_TouchSolids

Call after linking a new trigger in during gameplay
to force all entities it covers to immediately touch it
============
*/
void	G_TouchSolids (edict_t *ent)
{
	int			i, num;
	edict_t		*touch[MAX_EDICTS], *hit;
	vec3_t		mins, maxs;

	// FIXME: should be s.origin + mins and s.origin + maxs because of absmin and absmax padding?
	num = trap_BoxEdicts( ent->r.absmin, ent->r.absmax, touch, MAX_EDICTS, AREA_SOLID);

	// be careful, it is possible to have an entity in this
	// list removed before we get to it (killtriggered)
	for( i = 0; i < num; i++ ) {
		hit = touch[i];
		if( !hit->r.inuse )
			continue;

		VectorAdd( hit->s.origin, hit->r.mins, mins );
		VectorAdd( hit->s.origin, hit->r.maxs, maxs );
		if( !ent->item && !trap_EntityContact( mins, maxs, ent ) )
			continue;

		if( ent->touch )
			ent->touch( hit, ent, NULL, 0 );
		if( !ent->r.inuse )
			break;
	}
}
#endif

/*
============
G_InitMover

============
*/
void G_InitMover( edict_t *ent )
{
	ent->r.solid = SOLID_BSP;
	ent->movetype = MOVETYPE_PUSH;

	trap_SetBrushModel( ent, ent->model );

	if( ent->model2 ) {
		ent->s.modelindex2 = trap_ModelIndex( ent->model2 );
	}

	if( ent->light || !VectorCompare(ent->color, vec3_origin) )
	{
		int r, g, b, i;

		if( !ent->light )
			i = 100;
		else 
			i = ent->light;

		i /= 4;
		i = min (i, 255);

		r = ent->color[0];
		if( r <= 1.0 ) {
			r *= 255;
		}
		clamp (r, 0, 255);

		g = ent->color[1];
		if( g <= 1.0 ) {
			g *= 255;
		}
		clamp (g, 0, 255);

		b = ent->color[2];
		if( b <= 1.0 ) {
			b *= 255;
		}
		clamp (b, 0, 255);

		ent->s.light = COLOR_RGBA( r, g, b, i );
	}
}

/*
============
G_PlayerGender
server doesn't know the model gender, so all are neutrals in console prints.
============
*/
int G_PlayerGender( edict_t *player )
{
	return GENDER_NEUTRAL;
}

/*
============
G_PrintMsg

NULL sends to all the message to all clients 
============
*/
void G_PrintMsg( edict_t *ent, char *fmt, ... )
{
	char		msg[1024];
	va_list		argptr;
	char		*s, *p;

	va_start( argptr, fmt );
	vsnprintf( msg, sizeof(msg) - 1, fmt, argptr );
	va_end( argptr );
	msg[sizeof(msg)-1] = 0;

	// double quotes are bad
	while( (p = strchr(msg, '\"')) != NULL )
		*p = '\'';

	s = va( "pr \"%s\"", msg );

	if( !ent ) {
		int i;	

		for( i = 0; i < game.maxclients; i++ )
		{
			ent = game.edicts + 1 + i;
			if( !ent->r.inuse )
				continue;
			if( !ent->r.client )
				continue;
			trap_ServerCmd( ent, s );
		}

		// mirror at server console
		if( dedicated->integer )
			G_Printf( "%s", msg );
		return;
	}

	if( ent->r.inuse && ent->r.client )
		trap_ServerCmd( ent, s );
}

void G_ChatMsg( edict_t *ent, char *fmt, ... )
{
	char		msg[1024];
	va_list		argptr;
	char		*s, *p;

	va_start( argptr, fmt );
	vsnprintf( msg, sizeof(msg) - 1, fmt, argptr );
	va_end( argptr );
	msg[sizeof(msg)-1] = 0;

	// double quotes are bad
	while( (p = strchr(msg, '\"')) != NULL )
		*p = '\'';

	s = va( "ch \"%s\"", msg );

	if( !ent ) {
		int i;	
		for( i = 0; i < game.maxclients; i++ )
		{
			ent = game.edicts + 1 + i;
			if( !ent->r.inuse || !ent->r.client || !ent->r.client->pers.connected )
				continue;
			trap_ServerCmd( ent, s );
		}

		// mirror at server console
		if( dedicated->integer )
			G_Printf( "%s", msg );
		return;
	}

	if( ent->r.inuse && ent->r.client && ent->r.client->pers.connected )
		trap_ServerCmd( ent, s );
}

/*
============
G_CenterPrintMsg

NULL sends to all the message to all clients 
============
*/
void G_CenterPrintMsg( edict_t *ent, char *fmt, ... )
{
	char		msg[1024];
	va_list		argptr;
	char		*p;

	va_start( argptr, fmt );
	vsnprintf( msg, sizeof(msg), fmt, argptr );
	va_end( argptr );
	msg[sizeof(msg)-1] = 0;

	// double quotes are bad
	while( (p = strchr(msg, '\"')) != NULL )
		*p = '\'';

	trap_ServerCmd( ent, va ( "cp \"%s\"", msg ) );
}

/*
============
G_Obituary

Prints death message to all clients
============
*/
void G_Obituary( edict_t *victim, edict_t *attacker, int mod )
{
	if( victim && attacker )
		trap_ServerCmd( NULL, va( "obry %i %i %i", victim - game.edicts, attacker - game.edicts, mod ) );
}

/*
============
G_PrintMatchMessage

Sends match message to a client
Only used by the update functions below
============
*/
static void G_PrintMatchMsg( edict_t *ent, char *fmt, ... )
{
	char		msg[1024];
	va_list		argptr;
	char		*p;

	va_start( argptr, fmt );
	vsnprintf( msg, sizeof(msg), fmt, argptr );
	va_end( argptr );
	msg[sizeof(msg)-1] = 0;

	// double quotes are bad
	while( (p = strchr(msg, '\"')) != NULL )
		*p = '\'';

	trap_ServerCmd( ent, va("mm \"%s\"", msg) );
}

/*
============
G_UpdatePlayerMatchMsg

Sends correct match msg to one client
Must be called whenever client's team, ready status or chase mode changes
============
*/
void G_UpdatePlayerMatchMsg( edict_t *ent )
{
	if( match.state < MATCH_STATE_WARMUP || match.state > MATCH_STATE_PLAYTIME )
		G_PrintMatchMsg( ent, "" );

	if( ent->s.team == TEAM_SPECTATOR )
	{
		if( G_Gametype_hasChallengersQueue( game.gametype ) ) {
			if( ent->r.client->pers.queueTimeStamp && ent->s.team == TEAM_SPECTATOR ) { // He is in the queue
				G_PrintMatchMsg( ent, "'ESC' for in-game menu.\nYou are inside the challengers queue waiting for your turn to play.\nUse the in-game menu, or type 'spec' in the console to exit the queue.\n--\nUse the mouse buttons for switching spectator modes." );
			} else {
				G_PrintMatchMsg( ent, "'ESC' for in-game menu.\nUse the in-game menu or type 'join' in the console to enter the challengers queue.\nOnly players in the queue will have a turn to play against the last winner.\n--\nUse the mouse buttons for switching spectator modes." );
			}
		} else {
		if( ent->r.client->chase.active )
			G_PrintMatchMsg( ent, "" );
		else
			G_PrintMatchMsg( ent, "'ESC' for in-game menu.\nMouse buttons for switching spectator modes.\nThis message can be hidden by disabling 'help' in graphic options menu." );
		}
	}
	else
	{
		if( match.state == MATCH_STATE_WARMUP )
		{
			if( !match.ready[PLAYERNUM(ent)] )
				G_PrintMatchMsg( ent, "Set yourself READY to start the match!\n You can use the in-game menu or type 'ready' in the console." );
			else
				G_PrintMatchMsg( ent, "" );
		}
		else
		{
			G_PrintMatchMsg( ent, "" );
		}
	}
}

/*
============
G_UpdatePlayerMatchMsg

Sends correct match msg to every client
Must be called whenever match state changes
============
*/
void G_UpdatePlayersMatchMsgs( void )
{
	int i;
	edict_t *cl_ent;

	for( i = 0; i < game.maxclients; i++ )
	{
		cl_ent = game.edicts + 1 + i;
		if( !cl_ent->r.inuse )
			continue;
		G_UpdatePlayerMatchMsg( cl_ent );
	}
}

/*
=================
G_Sound
=================
*/
void G_Sound( edict_t *ent, int channel, int soundindex, float volume, float attenuation )
{
	if( ent )
		trap_Sound( NULL, ent, channel, soundindex, volume, attenuation );
}

/*
=================
G_PositionedSound
=================
*/
void G_PositionedSound( vec3_t origin, edict_t *ent, int channel, int soundindex, float volume, float attenuation )
{
	if( origin || ent )
		trap_Sound( origin, ent, channel, soundindex, volume, attenuation );
}

/*
=================
G_GlobalSound
=================
*/
void G_GlobalSound( int channel, int soundindex )
{
	trap_Sound( vec3_origin, game.edicts, channel | CHAN_NO_PHS_ADD | CHAN_RELIABLE, soundindex, 1.0, ATTN_NONE );
}

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

Kill box

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

//=================
//KillBox
//
//Kills all entities that would touch the proposed new positioning
//of ent.  Ent should be unlinked before calling this!
//=================
qboolean KillBox( edict_t *ent )
{
	trace_t		tr;

	while( 1 )
	{
		//trap_Trace (&tr, ent->s.origin, ent->r.mins, ent->r.maxs, ent->s.origin, NULL, MASK_PLAYERSOLID);
		trap_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, ent->s.origin, ent, CONTENTS_BODY );
		if( tr.fraction == 1.0f && !tr.startsolid )
			break;

		if( tr.ent == ENTNUM(world) )
			return qfalse; // found the world

		if( tr.ent < 1 )
			break;

		// nail it
		T_Damage( &game.edicts[tr.ent], ent, ent, vec3_origin, ent->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG );

		// if we didn't kill it, fail
		if( game.edicts[tr.ent].r.solid )
			return qfalse;
	}

	return qtrue;		// all clear
}

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

		Warsow: more miscelanea tools

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

//=================
//G_SpawnTeleportEffect
//=================
void G_SpawnTeleportEffect( edict_t *ent )
{
	edict_t *event;

	// add a teleportation effect
	event = G_SpawnEvent ( EV_PLAYER_TELEPORT_IN, 0, ent->s.origin );
	event->r.svflags = SVF_NOOLDORIGIN;
	event->s.ownerNum = ENTNUM(ent);
}

//=============
//visible
//returns 1 if the entity is visible to self, even if not infront ()
//=============
qboolean G_Visible( edict_t *self, edict_t *other )
{
	vec3_t	spot1;
	vec3_t	spot2;
	trace_t	trace;

	VectorCopy (self->s.origin, spot1);
	spot1[2] += self->viewheight;
	VectorCopy (other->s.origin, spot2);
	spot2[2] += other->viewheight;
	trap_Trace (&trace, spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE);
	//trace = gi.trace( spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE );
	
	if( trace.fraction == 1.0 )
		return qtrue;
	return qfalse;
}

//=============
//infront
//returns 1 if the entity is in front (in sight) of self
//=============
qboolean G_InFront( edict_t *self, edict_t *other )
{
	vec3_t	vec;
	float	dot;
	vec3_t	forward;
	
	AngleVectors( self->s.angles, forward, NULL, NULL );
	VectorSubtract( other->s.origin, self->s.origin, vec );
	VectorNormalize( vec );
	dot = DotProduct( vec, forward );
	
	if( dot > 0.3 )
		return qtrue;
	return qfalse;
}


//=================
//G_SolidMaskForEnt
//=================
int G_SolidMaskForEnt( edict_t *ent )
{
	int solidmask;
	if( ent->r.svflags & SVF_MONSTER )
		solidmask = MASK_MONSTERSOLID;
	else if( ent->r.client )
		solidmask = MASK_PLAYERSOLID;
	else
		solidmask = MASK_SOLID;

	return solidmask;
}

//=================
//G_CheckEntGround
//=================
void G_CheckGround( edict_t *ent )
{
	vec3_t		point;
	trace_t		trace;

	if( ent->flags & (FL_SWIM|FL_FLY) )
		return;

	if( ent->velocity[2] > 100 )
	{
		ent->groundentity = NULL;
		return;
	}

// if the hull point one-quarter unit down is solid the entity is on ground
	point[0] = ent->s.origin[0];
	point[1] = ent->s.origin[1];
	point[2] = ent->s.origin[2] - 0.25;

	//jalfixme: should use different masks (MASK_MONSTERSOLID)
	trap_Trace( &trace, ent->s.origin, ent->r.mins, ent->r.maxs, point, ent, G_SolidMaskForEnt(ent) );

	// check steepness
	if( trace.plane.normal[2] < 0.7 && !trace.startsolid )
	{
		ent->groundentity = NULL;
		return;
	}

	if( !trace.startsolid && !trace.allsolid )
	{
		VectorCopy( trace.endpos, ent->s.origin );
		ent->groundentity = &game.edicts[trace.ent];
		ent->groundentity_linkcount = ent->groundentity->r.linkcount;
		ent->velocity[2] = 0;
	}
}

//=================
//G_CategorizePosition
//=================
void G_CategorizePosition( edict_t *ent )
{
	vec3_t		point;
	int			cont;

//
// get waterlevel
//
	point[0] = ent->s.origin[0];
	point[1] = ent->s.origin[1];
	point[2] = ent->s.origin[2] + ent->r.mins[2] + 1;	
	cont = trap_PointContents (point);

	if( !(cont & MASK_WATER) )
	{
		ent->waterlevel = 0;
		ent->watertype = 0;
		return;
	}

	ent->watertype = cont;
	ent->waterlevel = 1;
	point[2] += 26;
	cont = trap_PointContents( point );
	if( !(cont & MASK_WATER) )
		return;

	ent->waterlevel = 2;
	point[2] += 22;
	cont = trap_PointContents( point );
	if( cont & MASK_WATER )
		ent->waterlevel = 3;
}

//=================
//G_DropToFloor
//=================
void G_DropToFloor( edict_t *ent )
{
	vec3_t		end;
	trace_t		trace;

	ent->s.origin[2] += 1;
	VectorCopy( ent->s.origin, end );
	end[2] -= 256;

	trap_Trace( &trace, ent->s.origin, ent->r.mins, ent->r.maxs, end, ent, G_SolidMaskForEnt(ent) );

	if( trace.fraction == 1 || trace.allsolid )
		return;

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

	trap_LinkEntity(ent);
	G_CheckGround(ent);
	G_CategorizePosition(ent);
}

//=============
//G_DropSpawnpointToFloor
//=============
void G_DropSpawnpointToFloor( edict_t *ent ) {
	vec3_t		start, end;
	trace_t		trace;

	if( ent->spawnflags & 1 ) //  floating items flag
		return;

	VectorCopy( ent->s.origin, start );
	start[2] += 1;
	VectorCopy( ent->s.origin, end );
	end[2] -= 16000;

	trap_Trace( &trace, start, playerbox_stand_mins, playerbox_stand_maxs, end, 0, MASK_PLAYERSOLID );
	if( !trace.startsolid && trace.fraction < 1.0f ) {
		VectorCopy( trace.endpos, ent->s.origin );
	}
}

//=============
//G_CheckBottom
//
//Returns false if any part of the bottom of the entity is off an edge that
//is not a staircase.
//
//=============
int c_yes, c_no;
qboolean G_CheckBottom( edict_t *ent )
{
	vec3_t	mins, maxs, start, stop;
	trace_t	trace;
	int		x, y;
	float	mid, bottom;
	
	VectorAdd( ent->s.origin, ent->r.mins, mins );
	VectorAdd( ent->s.origin, ent->r.maxs, maxs );

// if all of the points under the corners are solid world, don't bother
// with the tougher checks
// the corners must be within 16 of the midpoint
	start[2] = mins[2] - 1;
	for( x = 0; x <= 1; x++ )
		for( y = 0; y <= 1; y++ )
		{
			start[0] = x ? maxs[0] : mins[0];
			start[1] = y ? maxs[1] : mins[1];
			if( trap_PointContents (start) != CONTENTS_SOLID )
				goto realcheck;
		}

	c_yes++;
	return qtrue;		// we got out easy

realcheck:
	c_no++;
//
// check it for real...
//
	start[2] = mins[2];
	
// the midpoint must be within 16 of the bottom
	start[0] = stop[0] = (mins[0] + maxs[0])*0.5;
	start[1] = stop[1] = (mins[1] + maxs[1])*0.5;
	stop[2] = start[2] - 2*STEPSIZE;
	trap_Trace( &trace, start, vec3_origin, vec3_origin, stop, ent, G_SolidMaskForEnt(ent) );

	if( trace.fraction == 1.0 )
		return qfalse;
	mid = bottom = trace.endpos[2];
	
// the corners must be within 16 of the midpoint	
	for( x = 0; x <= 1; x++ )
		for( y = 0; y <= 1; y++ )
		{
			start[0] = stop[0] = x ? maxs[0] : mins[0];
			start[1] = stop[1] = y ? maxs[1] : mins[1];
			
			trap_Trace( &trace, start, vec3_origin, vec3_origin, stop, ent, G_SolidMaskForEnt(ent) );
			
			if( trace.fraction != 1.0 && trace.endpos[2] > bottom )
				bottom = trace.endpos[2];
			if( trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE )
				return qfalse;
		}

	c_yes++;
	return qtrue;
}

/*
============
G_AddPlayerStateEvent
This event is only sent to this client inside its player_state_t.
There is no parms. Just a byte sized event number.
Jal: to do : add a game-side queue to line up playerstate events. Right now
it will send the last one coming up
============
*/
void G_AddPlayerStateEvent( gclient_t *client, int event, int parm )
{
	unsigned int	i, eventdata;
	
	if( !client )
		return;
	
	if( !event || event > PSEV_MAX_EVENTS || parm > 0xFF )
		return;

	eventdata = ((event &0xFF)|(parm&0xFF)<<8);
	
	for( i = 0; i < 16; i++ ) {
		if( client->events[i] == PSEV_NONE || client->events[i] == eventdata ) {
			client->events[i] = eventdata;
			break;
		}
	}
}

void G_ClearPlayerStateEvents( gclient_t *client )
{
	if( !client )
		return;

	memset( client->events, PSEV_NONE, sizeof(client->events) );
}

//=================
//G_PlayerForText
// Returns player matching given text. It can be either number of the player or player's name.
//=================
edict_t *G_PlayerForText( const char *text )
{
	if( !Q_stricmp(text, va("%i", atoi(text))) && atoi(text) < game.maxclients && game.edicts[atoi(text)+1].r.inuse )
	{
		return &game.edicts[atoi(text)+1];
	}
	else
	{
		int i;
		edict_t *e;
		char colorless[MAX_INFO_VALUE];

		Q_strncpyz( colorless, COM_RemoveColorTokens(text), sizeof(colorless) );

		// check if it's a known player name
		for( i = 0, e = game.edicts+1; i < game.maxclients; i++, e++ ) 
		{
			if( !e->r.inuse ) continue;

			if( !Q_stricmp(colorless, COM_RemoveColorTokens(e->r.client->pers.netname)) )
				return e;
		}
		
		// nothing found
		return NULL;
	}
}

//=================
//G_AnnouncerSound - sends inmediatly. queue client side (excepting at player's ps events queue)
//=================
void G_AnnouncerSound( edict_t *targ, int soundindex, int team, qboolean queued ) {
	int psev = queued?PSEV_ANNOUNCER_QUEUED:PSEV_ANNOUNCER;
	// if it's only for a given player
	if( targ ) {
		if( !targ->r.client || trap_GetClientState(PLAYERNUM(targ)) < CS_SPAWNED )
			return;

		G_AddPlayerStateEvent( targ->r.client, psev, soundindex );
		return;
	}

	// add it to all players
	{
		edict_t *ent;
		
		for( ent = game.edicts + 1; PLAYERNUM(ent) < game.maxclients; ent++ ) {
			if( !ent->r.inuse || trap_GetClientState(PLAYERNUM(ent)) < CS_SPAWNED )
				continue;
			// team filter
			if( team >= TEAM_SPECTATOR && team < GS_MAX_TEAMS && ent->s.team != team )
				continue;

			G_AddPlayerStateEvent( ent->r.client, psev, soundindex );
		}
	}
}
