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

static qbyte *levelpool = NULL;          // Vic: I'm tempted to rename this to liverpool...
static size_t levelpool_size = 0;
static size_t levelpool_pointer = 0;

//=============
//G_LevelInitPool
//=============
void G_LevelInitPool( size_t size )
{
	if( levelpool )
	{
		G_Free( levelpool );
		levelpool = NULL;
	}

	if( !size )
		size = levelpool_size;
	assert( size );

	levelpool = ( qbyte * )G_Malloc( size );
	memset( levelpool, 0, size );

	levelpool_size = size;
	levelpool_pointer = 0;
}

//=============
//G_LevelMalloc
// Note that we never release allocated memory
//=============
void *_G_LevelMalloc( size_t size, const char *filename, int fileline )
{
	qbyte *pointer;

	if( levelpool_pointer + size > levelpool_size )
	{
		G_Error( "G_LevelMalloc: out of memory (alloc %i bytes at %s:%i)\n", size, filename, fileline );
		return NULL;
	}

	pointer = levelpool + levelpool_pointer;
	levelpool_pointer += size;

	return ( void * )pointer;
}

//=============
//G_LevelFree
//=============
void _G_LevelFree( void *data, const char *filename, int fileline )
{
}

//=============
//G_LevelCopyString
//=============
char *_G_LevelCopyString( const char *in, const char *filename, int fileline )
{
	char *out;

	out = _G_LevelMalloc( strlen( in ) + 1, filename, fileline );
	strcpy( out, in );
	return out;
}

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];
}



static 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 = "delayed_use";
		t->nextthink = level.time + 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[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( const char *in, const char *filename, int fileline )
{
	char *out;

	out = trap_MemAlloc( strlen( in )+1, filename, fileline );
	strcpy( out, in );
	return out;
}

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

	// set for not being transmitted
	ed->r.svflags = SVF_NOCLIENT;

	G_FreeAI( ed ); //mbotgame

	memset( ed, 0, sizeof( *ed ) );
	ed->freetime = game.realtime;
	ed->r.inuse = qfalse;
	ed->s.number = ENTNUM( ed );
}

//=================
//G_InitEdict
//=================
void G_InitEdict( edict_t *e )
{
	e->r.inuse = qtrue;
	e->classname = NULL;
	e->gravity = 1.0;
	e->s.number = ENTNUM( e );
	e->timeDelta = 0;

	e->s.teleported = qfalse;
	e->fall_fatal = qfalse;
	e->timeStamp = 0;
	e->s.linearProjectile = qfalse;

	//mark all entities to not be sent by default
	if( e->r.svflags & SVF_FAKECLIENT )
		e->r.svflags = SVF_NOCLIENT|SVF_FAKECLIENT;
	else
		e->r.svflags = SVF_NOCLIENT;

	// clear the old state data
	memset( &e->olds, 0, sizeof( e->olds ) );
	memset( &e->snap, 0, sizeof( e->snap ) );

	//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 < level.spawnedTimeStamp + 2000 || game.realtime > e->freetime + 500 ) )
		{
			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_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->r.solid = SOLID_NOT;
	ent->r.svflags &= ~SVF_NOCLIENT;
	if( origin != NULL )
		VectorCopy( origin, ent->s.origin );
	G_AddEvent( ent, event, parm, qtrue );

	GClip_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->r.svflags &= ~SVF_PROJECTILE; // FIXME: Medar: should be remove all or remove this one elsewhere?
	ent->s.linearProjectile = qfalse;
	G_AddEvent( ent, event, parm, qtrue );

	GClip_LinkEntity( ent );
}

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

	GClip_SetBrushModel( ent, ent->model );
	G_PureModel( ent->model );

	if( ent->model2 )
	{
		ent->s.modelindex2 = trap_ModelIndex( ent->model2 );
		G_PureModel( 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, const char *format, ... )
{
	char msg[MAX_STRING_CHARS];
	va_list	argptr;
	char *s, *p;

	va_start( argptr, format );
	Q_vsnprintfz( msg, sizeof( msg ), format, argptr );
	va_end( argptr );

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

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

	if( !ent )
	{
		// mirror at server console
		if( dedicated->integer )
			G_Printf( "%s", msg );
		trap_GameCmd( NULL, s );
	}
	else
	{
		if( ent->r.inuse && ent->r.client )
			trap_GameCmd( ent, s );
	}
}

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

	va_start( argptr, format );
	Q_vsnprintfz( msg, sizeof( msg ), format, argptr );
	va_end( argptr );

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

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

	if( !ent )
	{
		// mirror at server console
		if( dedicated->integer )
			G_Printf( "%s", msg );
		trap_GameCmd( NULL, s );
	}
	else
	{
		if( ent->r.inuse && ent->r.client && ent->r.client->pers.connected )
			trap_GameCmd( ent, s );
	}
}

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

	va_start( argptr, format );
	Q_vsnprintfz( msg, sizeof( msg ), format, argptr );
	va_end( argptr );

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

	trap_GameCmd( 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_GameCmd( NULL, va( "obry %i %i %i", victim - game.edicts, attacker - game.edicts, mod ) );
}

//============
//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 )
{
	matchmessage_t newmm = ent->r.client->teamchange.matchmessage;

	if( match.state < MATCH_STATE_WARMUP || match.state > MATCH_STATE_PLAYTIME )
		newmm = MATCHMESSAGE_NONE;
	else if( ent->s.team == TEAM_SPECTATOR )
	{
		if( G_Gametype_hasChallengersQueue( game.gametype ) )	// He is in the queue
			newmm = ( ent->r.client->pers.queueTimeStamp && ent->s.team == TEAM_SPECTATOR ? MATCHMESSAGE_CHALLENGERS_QUEUE
				: MATCHMESSAGE_ENTER_CHALLENGERS_QUEUE );
		else
			newmm = ( ent->r.client->chase.active ? MATCHMESSAGE_NONE : MATCHMESSAGE_SPECTATOR_MODES );
	}
	else
	{
		if( match.state == MATCH_STATE_WARMUP )
			newmm = ( !match.ready[PLAYERNUM( ent )] ? MATCHMESSAGE_GET_READY : MATCHMESSAGE_NONE );
		else
			newmm = MATCHMESSAGE_NONE;
	}

	if( newmm != ent->r.client->teamchange.matchmessage )
	{
		ent->r.client->teamchange.matchmessage = newmm;
		trap_GameCmd( ent, va( "mm %i", newmm ) );
	}
}

//============
//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;
	qboolean telefragged = qfalse;

	while( 1 )
	{
		G_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, ent->s.origin, world, MASK_PLAYERSOLID );
		if( ( tr.fraction == 1.0f && !tr.startsolid ) || tr.ent < 0 )
			return telefragged;

		if( tr.ent == ENTNUM( world ) )
			return telefragged; // found the world (but a player could be in there too). suicide?

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

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

	return telefragged; // all clear
}

//==================
//LookAtKillerYAW
// returns the YAW angle to look at our killer
//==================
float LookAtKillerYAW( edict_t *self, edict_t *inflictor, edict_t *attacker )
{
	vec3_t dir;
	float killer_yaw;

	if( attacker && attacker != world && attacker != self )
	{
		VectorSubtract( attacker->s.origin, self->s.origin, dir );
	}
	else if( inflictor && inflictor != world && inflictor != self )
	{
		VectorSubtract( inflictor->s.origin, self->s.origin, dir );
	}
	else
	{
		killer_yaw = self->s.angles[YAW];
		return killer_yaw;
	}

	if( dir[0] )
	{
		killer_yaw = RAD2DEG( atan2( dir[1], dir[0] ) );
	}
	else
	{
		killer_yaw = 0;
		if( dir[1] > 0 )
			killer_yaw = 90;
		else if( dir[1] < 0 )
			killer_yaw = -90;
	}
	if( killer_yaw < 0 )
		killer_yaw += 360;

	return killer_yaw;
}

//==============================================================================
//
//		Warsow: more miscelanea tools
//
//==============================================================================

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

	if( trap_GetClientState( PLAYERNUM( ent ) ) < CS_SPAWNED || ent->r.solid == SOLID_NOT )
		return;

	// add a teleportation effect
	event = G_SpawnEvent( EV_PLAYER_TELEPORT_IN, 0, ent->s.origin );
	event->r.svflags |= SVF_NOORIGIN2;
	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;
	G_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 ) )
	{
		ent->groundentity = NULL;
		ent->groundentity_linkcount = 0;
		return;
	}

	if( ( ent->velocity[2] > 1 && !ent->r.client ) || ( ent->r.client && ent->velocity[2] > 180 ) )
	{
		ent->groundentity = NULL;
		ent->groundentity_linkcount = 0;
		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;

	G_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;
		ent->groundentity_linkcount = 0;
		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 = G_PointContents( point );

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

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

	ent->waterlevel = 2;
	point[2] += 22;
	cont = G_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;

	G_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 );

	GClip_LinkEntity( ent );
	G_CheckGround( ent );
	G_CategorizePosition( ent );
}

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

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

	G_Trace( &trace, start, playerbox_stand_mins, playerbox_stand_maxs, end, ent, MASK_PLAYERSOLID );
	if( trace.startsolid || trace.allsolid )
	{
		G_Printf( "Warning: %s %s spawns inside solid. Inhibited\n", ent->classname, vtos( ent->s.origin ) );
		G_FreeEdict( ent );
		return;
	}

	if( ent->spawnflags & 1 )  //  floating items flag, we test that they are not inside solid too
		return;

	if( trace.fraction < 1.0f )
	{
		VectorMA( trace.endpos, 1.0f, trace.plane.normal, 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( G_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;
	G_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];

			G_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_ReleaseClientPSEvent
//===============
void G_ReleaseClientPSEvent( gclient_t *client )
{
	if( client )
	{
		if( client->eventsCurrent < client->eventsHead )
		{
			client->ps.event = client->events[client->eventsCurrent&MAX_CLIENT_EVENTS_MASK];
			client->eventsCurrent++;
		}
		else
		{
			client->ps.event = PSEV_NONE;
		}
	}
}

//============
//G_AddPlayerStateEvent
//This event is only sent to this client inside its player_state_t.
//============
void G_AddPlayerStateEvent( gclient_t *client, int event, int parm )
{
	int eventdata;
	if( client )
	{
		if( !event || event > PSEV_MAX_EVENTS || parm > 0xFF )
			return;
		if( client )
		{
			eventdata = ( ( event &0xFF )|( parm&0xFF )<<8 );
			client->events[client->eventsHead&MAX_CLIENT_EVENTS_MASK] = eventdata;
			client->eventsHead++;
		}
	}
}

//=================
//G_ClearPlayerStateEvents
//=================
void G_ClearPlayerStateEvents( gclient_t *client )
{
	if( client )
	{
		memset( client->events, PSEV_NONE, sizeof( client->events ) );
		client->eventsCurrent = client->eventsHead = 0;
	}
}

//=================
//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( targ ) // only for a given player
	{
		if( !targ->r.client || trap_GetClientState( PLAYERNUM( targ ) ) < CS_SPAWNED )
			return;

		G_AddPlayerStateEvent( targ->r.client, psev, soundindex );
		return;
	}
	else // 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 );
		}
	}
}

//=================
//G_PureSound
//=================
void G_PureSound( const char *sound )
{
	assert( sound && sound[0] && strlen( sound ) < MAX_CONFIGSTRING_CHARS );

	if( sound[0] == '*' )
	{                  // sexed sounds
		trap_PureSound( sound + 1 );
	}
	else
	{
		trap_PureSound( sound );
	}
}

//=================
//G_PureModel
//=================
void G_PureModel( const char *model )
{
	assert( model && model[0] && strlen( model ) < MAX_CONFIGSTRING_CHARS );

	trap_PureModel( model );
}
