// Copyright (C) 1997 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// DESCRIPTION:
// Base class for all enities that are controlled by Sin.  If you have any
// object that should be called on a periodic basis and it is not an entity,
// then you have to have an dummy entity that calls it.
//
// An entity in Sin is any object that is not part of the world.  Any non-world
// object that is visible in Sin is an entity, although it is not required that
// all entities be visible to the player.  Some objects are basically just virtual
// constructs that act as an instigator of certain actions, for example, some
// triggers are invisible and cannot be touched, but when activated by other
// objects can cause things to happen.
//
// All entities are capable of receiving messages from Sin or from other entities.
// Messages received by an entity may be ignored, passed on to their superclass,
// or acted upon by the entity itself.  The programmer must decide on the proper
// action for the entity to take to any message.  There will be many messages
// that are completely irrelevant to an entity and should be ignored.  Some messages
// may require certain states to exist and if they are received by an entity when
// it these states don't exist may indicate a logic error on the part of the
// programmer or map designer and should be reported as warnings (if the problem is
// not severe enough for the game to be halted) or as errors (if the problem should
// not be ignored at any cost).
//

#include "entity.h"
#include "worldspawn.h"
#include "scriptmaster.h"
#include "sentient.h"
#include "misc.h"
#include "specialfx.h"
#include "object.h"
#include "player.h"

CLASS_DECLARATION( Listener, Entity, NULL );

// Player events
Event EV_ClientConnect( "client_connect" );
Event EV_ClientDisconnect( "client_disconnect" );
Event EV_ClientKill( "client_kill" );
Event EV_ClientMove( "client_move" );
Event EV_ClientEndFrame( "client_endframe" );

// Generic entity events
Event EV_GetEntName( "getentname" );
Event EV_Classname( "classname" );
Event EV_Activate( "doActivate" );
Event EV_Use( "doUse" );
Event EV_PreciseUse( "doPreciseUse" ); //### precise use
//Event EV_Footstep( "footstep" );
Event EV_FadeOut( "fadeout" );
Event EV_Fade( "fade" );
Event EV_Killed( "killed" );
Event EV_GotKill( "gotkill" );
Event EV_Pain( "pain" );
Event EV_Damage( "damage" );
Event EV_Kill( "kill", EV_CONSOLE );
Event EV_Gib( "gib" );
Event EV_Hurt( "hurt" );

Event EV_CourseAngles( "courseangles" );
Event EV_SmoothAngles( "smoothangles" );
Event EV_TakeDamage( "takedamage" );
Event EV_NoDamage( "nodamage" );

// Physics events
Event EV_MoveDone( "movedone" );
Event EV_Touch( "doTouch" );
Event EV_Blocked( "doBlocked" );
Event EV_UseBoundingBox( "usebbox" );

// Animation events
Event EV_NewAnim( "animChanged" );
Event EV_LastFrame( "lastFrame" );
Event EV_NextAnim( "nextanim" );
Event EV_NextFrame( "nextframe" );
Event EV_PrevFrame( "prevframe" );
Event EV_SetFrame( "setframe" );
Event EV_StopAnim( "stopanim" );
Event EV_EndAnim( "endanim" );
Event EV_ProcessInitCommands( "processinit" );
Event EV_Attach( "attach" );
Event EV_AttachModel( "attachmodel" );
Event EV_Detach( "detach" );

// script stuff
Event EV_Model( "model" );
Event EV_Hide( "hide" );
Event EV_Show( "show" );
Event EV_BecomeSolid( "solid" );
Event EV_BecomeNonSolid( "notsolid" );
Event EV_Ghost( "ghost" );
Event EV_PlaySound( "playsound" );
Event EV_PHSSound( "phssound" );
Event EV_StopSound( "stopsound" );
Event EV_GravityAxis( "gravityaxis", EV_CHEAT );
Event EV_Bind( "bind" );
Event EV_Unbind( "unbind" );
Event EV_JoinTeam( "joinTeam" );
Event EV_QuitTeam( "quitTeam" );
Event EV_SetHealth( "health", EV_CHEAT );
Event EV_SetScale( "scale" );
Event EV_SetSize( "setsize" );
Event EV_SetAlpha( "alpha" );
Event EV_SetGroupAlpha( "groupalpha" ); //###
Event EV_SetOrigin( "origin" );
Event EV_SetTargetName( "targetname" );
Event EV_SetTarget( "target" );
Event EV_SetKillTarget( "killtarget" );
Event EV_SetAngles( "angles" );
Event EV_RegisterAlias( "alias" );
Event EV_RegisterAliasAndCache( "aliascache" );
Event EV_RandomSound( "randomsound" );
Event EV_RandomPHSSound( "randomphssound" );
Event EV_Tesselate( "shatter" );
Event EV_SetMass( "mass" );
//### extended targeting stuff
Event EV_SetTargetName2( "targetname2" );
Event EV_SetTarget2( "target2" );
Event EV_SetTarget3( "target3" );
Event EV_SetTarget4( "target4" );
Event EV_SetKillTarget2( "killtarget2" );
//###

//HACK HACK
Event EV_EntitySound( "ambientsound" );
Event EV_RandomGlobalEntitySound( "randomglobalambientsound" );
Event EV_RandomEntitySound( "randomambientsound" );
Event EV_StopEntitySound( "stopambientsound" );
Event EV_Anim( "anim" );
Event EV_StartAnimating( "animate" );
Event EV_GroupModelEvent( "group" );
Event EV_DialogEvent( "dialog" );
Event EV_SetSkin( "skin" );

// AI sound events
Event EV_WeaponSound( "weaponsound" );
Event EV_MovementSound( "movementsound" );
Event EV_PainSound( "painsound" );
Event EV_DeathSound( "deathsound" );
Event EV_BreakingSound( "breakingsound" );
Event EV_DoorSound( "doorsound" );
Event EV_MutantSound( "mutantsound" );
Event EV_VoiceSound( "voicesound" );
Event EV_MachineSound( "machinesound" );
Event EV_RadioSound( "radiosound" );

Event EV_HeardWeapon( "heardweapon" );
Event EV_HeardMovement( "heardmovement" );
Event EV_HeardPain( "heardpain" );
Event EV_HeardDeath( "hearddeath" );
Event EV_HeardBreaking( "heardbreaking" );
Event EV_HeardDoor( "hearddoor" );
Event EV_HeardMutant( "heardmutant" );
Event EV_HeardVoice( "heardvoice" );
Event EV_HeardMachine( "heardmachine" );
Event EV_HeardRadio( "heardradio" );

// Conditionals
Event EV_IfSkill( "ifskill" );

// Lighting
Event EV_SetLight( "light" );
Event EV_LightOn( "lightOn" );
Event EV_LightOff( "lightOff" );
Event EV_LightRed( "lightRed" );
Event EV_LightGreen( "lightGreen" );
Event EV_LightBlue( "lightBlue" );
Event EV_LightRadius( "lightRadius" );

Event EV_Lightoffset( "lightoffset" );
Event EV_Minlight( "minlight" );
Event EV_Gravity( "gravity" );

// Entity flag specific
Event EV_EntityFlags( "flags" );
Event EV_EntityRenderEffects( "rendereffects" );
Event EV_EntityEffects( "effects" );

// Special Effects
Event EV_SpawnParticles( "sparks" );

// Tesselation setup events
Event EV_Shatter_MinSize( "shatter_minsize" );
Event EV_Shatter_MaxSize( "shatter_maxsize" );
Event EV_Shatter_Thickness( "shatter_thickness" );
Event EV_Shatter_Percentage( "shatter_percentage" );

Event EV_Mutate( "mutate", EV_CHEAT );
Event EV_Censor( "censor" );

ResponseDef Entity::Responses[] =
{
	{ &EV_Damage,               ( Response )Entity::DamageEvent },
	{ &EV_Kill,               ( Response )Entity::Kill },
	{ &EV_FadeOut,               ( Response )Entity::FadeOut },
	{ &EV_Fade,                 ( Response )Entity::Fade },
	{ &EV_Hide,                  ( Response )Entity::EventHideModel },
	{ &EV_Show,                  ( Response )Entity::EventShowModel },
	{ &EV_BecomeSolid,       ( Response )Entity::BecomeSolid },
	{ &EV_BecomeNonSolid,    ( Response )Entity::BecomeNonSolid },
	{ &EV_Ghost,          ( Response )Entity::Ghost },
	{ &EV_PlaySound,         ( Response )Entity::PlaySound },
	{ &EV_StopSound,         ( Response )Entity::StopSound },
	{ &EV_GravityAxis,      ( Response )Entity::GravityAxisEvent },
	{ &EV_Bind,                  ( Response )Entity::BindEvent },
	{ &EV_Unbind,                ( Response )Entity::EventUnbind },
	{ &EV_JoinTeam,          ( Response )Entity::JoinTeam },
	{ &EV_QuitTeam,          ( Response )Entity::EventQuitTeam },
	{ &EV_SetHealth,         ( Response )Entity::SetHealth },
	{ &EV_SetSize,               ( Response )Entity::SetSize },
	{ &EV_SetScale,         ( Response )Entity::SetScale },
	{ &EV_SetAlpha,          ( Response )Entity::SetAlpha },
	{ &EV_SetGroupAlpha,     ( Response )Entity::SetGroupAlpha },    //###
	{ &EV_SetOrigin,         ( Response )Entity::SetOrigin },
	{ &EV_SetTargetName,     ( Response )Entity::SetTargetName },
	{ &EV_SetTarget,         ( Response )Entity::SetTarget },
	{ &EV_SetKillTarget,     ( Response )Entity::SetKillTarget },
	//### extended targeting stuff
	{ &EV_SetTargetName2,    ( Response )Entity::SetTargetName2 },
	{ &EV_SetTarget2,        ( Response )Entity::SetTarget2 },
	{ &EV_SetTarget3,        ( Response )Entity::SetTarget3 },
	{ &EV_SetTarget4,        ( Response )Entity::SetTarget4 },
	{ &EV_SetKillTarget2,    ( Response )Entity::SetKillTarget2 },
	//###
	{ &EV_SetAngles,         ( Response )Entity::SetAngles },
	{ &EV_SetMass,          ( Response )Entity::SetMassEvent },

	{ &EV_CourseAngles,     ( Response )Entity::CourseAnglesEvent },
	{ &EV_SmoothAngles,     ( Response )Entity::SmoothAnglesEvent },

	{ &EV_RegisterAlias,    ( Response )Entity::RegisterAlias },
	{ &EV_RegisterAliasAndCache, ( Response )Entity::RegisterAliasAndCache },
	{ &EV_RandomSound,      ( Response )Entity::RandomSound },
	{ &EV_EntitySound,      ( Response )Entity::EntitySound },
	{ &EV_RandomEntitySound,( Response )Entity::RandomEntitySound },
	{ &EV_RandomGlobalEntitySound, ( Response )Entity::RandomGlobalEntitySoundEvent },
	{ &EV_StopEntitySound,   ( Response )Entity::StopEntitySound },
	{ &EV_Anim,             ( Response )Entity::AnimEvent },
	{ &EV_StartAnimating,    ( Response )Entity::StartAnimatingEvent },
	{ &EV_NextAnim,         ( Response )Entity::NextAnimEvent },
	{ &EV_NextFrame,            ( Response )Entity::NextFrameEvent },
	{ &EV_PrevFrame,            ( Response )Entity::PrevFrameEvent },
	{ &EV_SetFrame,         ( Response )Entity::SetFrameEvent },
	{ &EV_StopAnim,         ( Response )Entity::StopAnimatingEvent },
	{ &EV_EndAnim,              ( Response )Entity::EndAnimEvent },
	{ &EV_Model,                ( Response )Entity::SetModelEvent },
	{ &EV_SetLight,           ( Response )Entity::SetLight },
	{ &EV_LightOn,                ( Response )Entity::LightOn },
	{ &EV_LightOff,          ( Response )Entity::LightOff },
	{ &EV_LightRed,          ( Response )Entity::LightRed },
	{ &EV_LightGreen,        ( Response )Entity::LightGreen },
	{ &EV_LightBlue,         ( Response )Entity::LightBlue },
	{ &EV_LightRadius,       ( Response )Entity::LightRadius },
	{ &EV_Tesselate,        ( Response )Entity::Tesselate },
	{ &EV_EntityFlags,       ( Response )Entity::Flags },
	{ &EV_EntityEffects,     ( Response )Entity::Effects },
	{ &EV_EntityRenderEffects,       ( Response )Entity::RenderEffects },
	{ &EV_RandomPHSSound,    ( Response )Entity::RandomPHSSound },
	{ &EV_PHSSound,        ( Response )Entity::PHSSound },

	{ &EV_WeaponSound,      ( Response )Entity::WeaponSound },
	{ &EV_MovementSound,        ( Response )Entity::MovementSound },
	{ &EV_PainSound,            ( Response )Entity::PainSound },
	{ &EV_DeathSound,           ( Response )Entity::DeathSound },
	{ &EV_BreakingSound,        ( Response )Entity::BreakingSound },
	{ &EV_DoorSound,            ( Response )Entity::DoorSound },
	{ &EV_MutantSound,      ( Response )Entity::MutantSound },
	{ &EV_VoiceSound,           ( Response )Entity::VoiceSound },
	{ &EV_MachineSound,     ( Response )Entity::MachineSound },
	{ &EV_RadioSound,           ( Response )Entity::RadioSound },
	{ &EV_SpawnParticles,    ( Response )Entity::SpawnParticles },
	{ &EV_GroupModelEvent,   ( Response )Entity::GroupModelEvent },
	{ &EV_DialogEvent,       ( Response )Entity::DialogEvent },
	{ &EV_ProcessInitCommands,( Response )Entity::ProcessInitCommandsEvent },
	{ &EV_Attach,           ( Response )Entity::AttachEvent },
	{ &EV_AttachModel,      ( Response )Entity::AttachModelEvent },
	{ &EV_Detach,           ( Response )Entity::DetachEvent },
	{ &EV_TakeDamage,       ( Response )Entity::TakeDamageEvent },
	{ &EV_NoDamage,         ( Response )Entity::NoDamageEvent },
	{ &EV_SetSkin,          ( Response )Entity::SetSkinEvent },
	{ &EV_Lightoffset,      ( Response )Entity::Lightoffset },
	{ &EV_Minlight,         ( Response )Entity::Minlight },
	{ &EV_Gravity,          ( Response )Entity::Gravity },

	{ &EV_Shatter_MinSize,   ( Response )Entity::SetShatterMinSize },
	{ &EV_Shatter_MaxSize,   ( Response )Entity::SetShatterMaxSize },
	{ &EV_Shatter_Thickness,( Response )Entity::SetShatterThickness },
	{ &EV_Shatter_Percentage,( Response )Entity::SetShatterPercentage },

	{ &EV_UseBoundingBox,   ( Response )Entity::UseBoundingBoxEvent },
	{ &EV_Hurt,             ( Response )Entity::HurtEvent },
	{ &EV_IfSkill,          ( Response )Entity::IfSkillEvent },

	{ &EV_GetEntName,       ( Response )Entity::GetEntName },
	{ &EV_Censor,           ( Response )Entity::Censor },

	{ NULL, NULL }
};

Entity::Entity()
{
	const char *m;
	Event *ev;
	int minlight;

	classname = this->getClassname();

	if ( game.force_entnum )
	{
		game.force_entnum = false;
		edict = &g_edicts[ game.spawn_entnum ];
		LL_Remove( edict, next, prev );
		G_InitEdict( edict );
		LL_Add( &active_edicts, edict, next, prev );
	}
	else
	{
		edict = G_Spawn ();
	}

	client = edict->client;
	edict->entity = this;
	entnum = edict->s.number;

	m = G_GetSpawnArg( "classname" );
	if ( m )
	{
		strncpy( edict->entname, m, sizeof( edict->entname ) - 1 );
	}

	// spawning variables
	spawnflags = G_GetIntArg( "spawnflags" );
	if ( spawnflags & SPAWNFLAG_DETAIL )
	{
		edict->s.renderfx |= RF_DETAIL;
	}

	// rendering variables
	setAlpha( G_GetFloatArg( "alpha", 1.0f ) );
	setScale( G_GetFloatArg( "scale", 1.0f ) );

	minlight = G_GetIntArg( "minlight", 0 );
	if ( minlight )
		edict->s.renderfx |= RF_MINLIGHT;

	edict->s.lightofs = G_GetFloatArg( "lightoffset", 0 );
	if ( edict->s.lightofs )
		edict->s.renderfx |= RF_LIGHTOFFSET;

	viewheight   = 0;
	light_level = 0;

	// Animation variables
	next_anim      = -1;
	next_frame     = -1;
	frame_delta    = "0 0 0";
	next_anim_delta = "0 0 0";
	next_anim_time = 0;
	total_delta    = "0 0 0";
	animDoneEvent   = NULL;
	animating       = false;
	last_frame_in_anim = 0;
	last_animation_time = -1;
	num_frames_in_gun_anim = 0;

	// team variables
	teamchain   = NULL;
	teammaster  = NULL;
	m           = G_GetSpawnArg( "team" );
	if ( m )
	{
		moveteam = str( m );
	}

	// physics variables
	contents                        = 0;
	mass                            = 0;
	gravity                     = 1.0;
	groundentity                = NULL;
	groundsurface               = NULL;
	groundentity_linkcount  = 0;
	bindmaster                  = NULL;
	velocity                        = vec_zero;
	avelocity                   = vec_zero;

	SetGravityAxis( G_GetIntArg( "gravityaxis", 0 ) );

	// model binding variables
	numchildren             = 0;
	memset( &children, 0, sizeof( children ) );

	setOrigin( G_GetSpawnArg( "origin" ) );
	worldorigin.copyTo( edict->s.old_origin );

	setMoveType( MOVETYPE_NONE );
	setSolidType( SOLID_NOT );

	// targeting variables
	SetTargetName( G_GetSpawnArg( "targetname" ) );
	SetTarget( G_GetSpawnArg( "target" ) );

	//### extended targeting stuff
	SetTargetName2( G_GetSpawnArg( "targetname2" ) );
	SetTarget2( G_GetSpawnArg( "target2" ) );
	SetTarget3( G_GetSpawnArg( "target3" ) );
	SetTarget4( G_GetSpawnArg( "target4" ) );
	//###

	// Character state
	health      = 0;
	max_health  = 0;
	deadflag        = DEAD_NO;
	flags           = 0;

	// underwater variables
	watertype       = 0;
	waterlevel      = 0;

	// Pain and damage variables
	takedamage              = DAMAGE_NO;
	enemy                       = NULL;
	pain_finished           = 0;
	damage_debounce_time = 0;

	m = G_GetSpawnArg( "model" );
	if ( m )
	{
		setModel( m );
	}

	//
	// see if we have a mins and maxs set for this model
	//
	if ( gi.IsModel( edict->s.modelindex ) && !mins.length() && !maxs.length())
	{
		vec3_t tempmins, tempmaxs;
		gi.CalculateBounds( edict->s.modelindex, edict->s.scale, tempmins, tempmaxs );
		setSize( tempmins, tempmaxs );
	}

	//
	// get the number of groups for this model
	//
	edict->s.numgroups = gi.NumGroups( edict->s.modelindex );

	m = G_GetSpawnArg( "bind" );
	if ( m )
	{
		str name;

		ev = new Event( EV_Bind );

		// construct an object name
		name = "$";
		name += m;
		ev->AddString( name );

		// Wait until all entities are spawned.
		PostEvent( ev, 0 );
	}

	//
	// initialize tesselation variables
	//
	tess_max_size = size.length() / 4;
	tess_min_size = tess_max_size / 3;

	if ( tess_min_size < 8 )
	{
		tess_min_size = 8;
	}

	if ( tess_max_size <= tess_min_size )
	{
		tess_max_size = tess_min_size * 2;
	}

	tess_thickness = tess_min_size;
	tess_percentage = TESS_DEFAULT_PERCENT;
}

Entity::~Entity()
{
	Container<Entity *> bindlist;
	Entity *ent;
	int num;
	int i;

	// unbind any entities that are bound to me
	// can't unbind within this loop, so make an array
	// and unbind them outside of it.
	num = 0;
	for( ent = teamchain; ent; ent = ent->teamchain )
	{
		if ( ent->bindmaster == this )
		{
			bindlist.AddObject( ent );
		}
	}

	num = bindlist.NumObjects();
	for( i = 1; i <= num; i++ )
	{
		bindlist.ObjectAt( i )->unbind();
	}

	bindlist.FreeObjectList();

	unbind();
	quitTeam();
	detach();

	//
	// go through and set our children
	//
	num = numchildren;
	for( i = 0; ( i < MAX_MODEL_CHILDREN ) && num; i++ )
	{
		if ( !children[ i ] )
		{
			continue;
		}
		ent = ( Entity * )G_GetEntity( children[ i ] );
		ent->PostEvent( EV_Remove, 0 );
		num--;
	}

	if ( targetname.length() && world )
	{
		world->RemoveTargetEntity( targetname, this );
	}
	//### extended targeting stuff
	if ( targetname2.length() && world )
	{
		world->RemoveTargetEntity( targetname2, this );
	}
	//###

	this->CancelPendingEvents();
	G_FreeEdict( edict );
}

EXPORT_FROM_DLL void Entity::SetEntNum( int num )
{
	if ( edict )
	{
		G_FreeEdict( edict );
	}

	edict = &g_edicts[ num ];
	LL_Remove( edict, next, prev );
	G_InitEdict( edict );
	LL_Add( &active_edicts, edict, next, prev );

	client = edict->client;
	edict->entity = this;
	entnum = num;
}

EXPORT_FROM_DLL void Entity::GetEntName( Event *ev )
{
	strncpy( edict->entname, getClassname(), sizeof( edict->entname ) - 1 );
}

EXPORT_FROM_DLL void Entity::SetTarget( const char *text )
{
	if ( text )
	{
		target = text;
	}
	else
	{
		target = "";
	}
}

EXPORT_FROM_DLL void Entity::SetTargetName( const char *text )
{
	if ( targetname.length() && world )
	{
		world->RemoveTargetEntity( targetname, this );
	}

	if ( text )
	{
		if ( text[ 0 ] == '$' )
			text++;
		targetname = text;
	}
	else
	{
		targetname = "";
	}

	if ( targetname.length() && world )
	{
		world->AddTargetEntity( targetname, this );
	}
}

EXPORT_FROM_DLL void Entity::SetKillTarget( const char *text )
{
	if ( text )
	{
		killtarget = text;
	}
	else
	{
		killtarget = "";
	}
}

EXPORT_FROM_DLL int Entity::modelIndex( const char *mdl )
{
	str name;

	assert( mdl );

	if ( !mdl )
	{
		return 0;
	}

	// Prepend 'models/' to make things easier
	if ( !strchr( mdl, '*' ) && !strchr( mdl, '\\' ) && !strchr( mdl, '/' ) )
	{
		name = "models/";
	}

	name += mdl;

	return gi.modelindex( name.c_str() );
}

EXPORT_FROM_DLL void Entity::setModel( const char *mdl )
{
	str temp;

	if ( !mdl )
	{
		mdl = "";
	}

	// Prepend 'models/' to make things easier
	temp = "";
	if ( !strchr( mdl, '*' ) && !strchr( mdl, '\\' ) && !strchr( mdl, '/' ) )
	{
		temp = "models/";
	}
	temp += mdl;

	// we use a temp string so that if model was passed into here, we don't
	// accidentally free up the string that we're using in the process.
	model = temp;

	gi.setmodel( edict, model.c_str() );

	if ( gi.IsModel( edict->s.modelindex ) )
	{
		Event *ev;

		edict->s.numgroups = gi.NumGroups( edict->s.modelindex );

		if ( !LoadingSavegame )
		{
			CancelEventsOfType( EV_ProcessInitCommands );

			ev = new Event( EV_ProcessInitCommands );
			ev->AddInteger( edict->s.modelindex );
			PostEvent( ev, 0 );
		}
	}

	// Sanity check to see if we're expecting a B-Model
	assert( !( ( edict->solid == SOLID_BSP ) && !edict->s.modelindex ) );
	if ( ( edict->solid == SOLID_BSP ) && !edict->s.modelindex )
	{
		const char *name;

		name = getClassID();
		if ( !name )
		{
			name = getClassname();
		}
		gi.dprintf( "%s with SOLID_BSP and no model - '%s'(%d)\n", name, targetname.c_str(), entnum );

		// Make it non-solid so that the collision code doesn't kick us out.
		setSolidType( SOLID_NOT );
	}

	mins = edict->mins;
	maxs = edict->maxs;
	size = edict->size;
}


EXPORT_FROM_DLL void Entity::ProcessInitCommands( int index )
{
	sinmdl_cmd_t *cmds;

	if ( LoadingSavegame )
	{
		// Don't process init commands when loading a savegame since
		// it will cause items to be added to inventories unnecessarily.
		// All variables affected by the init commands will be set
		// by the unarchive functions.
		return;
	}

	cmds = gi.InitCommands( index );
	if (cmds)
	{
		int i, j;
		Event       *event;

		for (i=0; i<cmds->num_cmds; i++)
		{
			event = new Event( cmds->cmds[i].args[0] );
			for(j=1; j<cmds->cmds[i].num_args; j++)
			{
				event->AddToken( cmds->cmds[i].args[j] );
			}
			ProcessEvent( event );
		}
	}
}

EXPORT_FROM_DLL void Entity::ProcessInitCommandsEvent( Event *ev )
{
	int index;

	index = ev->GetInteger( 1 );
	ProcessInitCommands( index );
}

EXPORT_FROM_DLL void Entity::EventHideModel( Event *ev )
{
	hideModel();
}

EXPORT_FROM_DLL void Entity::EventShowModel( Event *ev )
{
	showModel();
}

EXPORT_FROM_DLL void Entity::setAlpha( float alpha )
{
	if ( alpha > 1.0f )
	{
		alpha = 1.0f;
	}
	if ( alpha < 0 )
	{
		alpha = 0;
	}
	translucence = 1.0f - alpha;
	edict->s.alpha = alpha;
	edict->s.renderfx &= ~RF_TRANSLUCENT;

	if ( ( translucence > 0 ) && ( translucence <= 1.0 ) )
	{
		edict->s.renderfx |= RF_TRANSLUCENT;
	}
}

EXPORT_FROM_DLL void Entity::setScale( float scale )
{
	if ( scale > 255.0f )
	{
		scale = 255.0f;
	}
	if ( scale < 0.004f )
	{
		scale = 0.004f;
	}
	edict->s.scale = scale;
}

EXPORT_FROM_DLL void Entity::setSolidType( solid_t type )
{
	if (
		( !LoadingSavegame ) &&
		( type == SOLID_BSP ) &&
		( this != world ) &&
		(
			!model.length() ||
			(
				( model[ 0 ] != '*' ) &&
				( !strstr( model.c_str(), ".bsp" ) )
			)
		)
		)
	{
		error( "setSolidType", "SOLID_BSP entity at x%.2f y%.2f z%.2f with no BSP model", worldorigin[ 0 ], worldorigin[ 1 ], worldorigin[ 2 ] );
	}
	edict->solid = type;
	link();

	edict->svflags &= ~SVF_NOCLIENT;
	if ( hidden() )
	{
		edict->svflags |= SVF_NOCLIENT;
	}
}

EXPORT_FROM_DLL void Entity::setSize( Vector min,Vector max )
{
	Vector delta;

	if ( flags & FL_ROTATEDBOUNDS )
	{
		vec3_t tempmins, tempmaxs;
		float mat[3][3];

		//
		// rotate the mins and maxs for the model
		//
		min.copyTo( tempmins );
		max.copyTo( tempmaxs );

		VectorCopy( orientation[ 0 ], mat[ 0 ] );
		VectorNegate( orientation[ 1 ], mat[ 1 ] );
		VectorCopy( orientation[ 2 ], mat[ 2 ] );

		CalculateRotatedBounds2( mat, tempmins, tempmaxs );

		mins = Vector( tempmins );
		maxs = Vector( tempmaxs );
		size = max - min;

		mins.copyTo( edict->mins );
		maxs.copyTo( edict->maxs );
		size.copyTo( edict->size );
		mins.copyTo( edict->fullmins );
		maxs.copyTo( edict->fullmaxs );
		edict->fullradius = size.length() * 0.5;
	}
	else
	{
		if ( ( min == edict->mins ) && ( max == edict->maxs ) )
		{
			return;
		}

		mins = min;
		maxs = max;
		size = max - min;

		mins.copyTo( edict->mins );
		maxs.copyTo( edict->maxs );
		size.copyTo( edict->size );

		//
		// get the full mins and maxs for this model
		//
		if ( gi.IsModel( edict->s.modelindex ) )
		{
			Vector delta;
			vec3_t fmins;
			vec3_t fmaxs;
			const gravityaxis_t &grav = gravity_axis[ gravaxis ];

			gi.CalculateBounds( edict->s.modelindex, edict->s.scale, fmins, fmaxs );
			edict->fullmins[ 0 ] = fmins[ grav.x ];
			edict->fullmaxs[ 0 ] = fmaxs[ grav.x ];

			if ( grav.sign > 0 )
			{
				edict->fullmins[ 1 ] = fmins[ grav.y ];
				edict->fullmins[ 2 ] = fmins[ grav.z ];
				edict->fullmaxs[ 1 ] = fmaxs[ grav.y ];
				edict->fullmaxs[ 2 ] = fmaxs[ grav.z ];
			}
			else
			{
				edict->fullmins[ 1 ] = -fmaxs[ grav.y ];
				edict->fullmins[ 2 ] = -fmaxs[ grav.z ];
				edict->fullmaxs[ 1 ] = -fmins[ grav.y ];
				edict->fullmaxs[ 2 ] = -fmins[ grav.z ];
			}

			delta = Vector( edict->fullmaxs ) - Vector( edict->fullmins );
			edict->fullradius = delta.length() * 0.5f;
		}
		else
		{
			mins.copyTo( edict->fullmins );
			maxs.copyTo( edict->fullmaxs );
			edict->fullradius = size.length() * 0.5;
		}
	}

	link();
}

EXPORT_FROM_DLL Vector Entity::getLocalVector( Vector vec )
{
	Vector pos;

	pos[ 0 ] = vec * orientation[ 0 ];
	pos[ 1 ] = vec * orientation[ 1 ];
	pos[ 2 ] = vec * orientation[ 2 ];

	return pos;
}

EXPORT_FROM_DLL Vector Entity::getParentVector( Vector vec )
{
	Vector pos;

	if ( !bindmaster )
	{
		return vec;
	}

	pos[ 0 ] = vec * bindmaster->orientation[ 0 ];
	pos[ 1 ] = vec * bindmaster->orientation[ 1 ];
	pos[ 2 ] = vec * bindmaster->orientation[ 2 ];

	return pos;
}

EXPORT_FROM_DLL void Entity::link( void )
{
	gi.linkentity( edict );
	absmin = edict->absmin;
	absmax = edict->absmax;
	centroid = ( absmin + absmax ) * 0.5;
	centroid.copyTo( edict->centroid );

	// If this has a parent, then set the areanum the same
	// as the parent's
	if ( edict->s.parent )
	{
		edict->areanum = g_edicts[ edict->s.parent ].areanum;
	}
}

EXPORT_FROM_DLL void Entity::setOrigin( Vector org )
{
	Entity * ent;
	int i, num;

	origin = org;
	if ( bindmaster )
	{
		MatrixTransformVector( origin.vec3(), bindmaster->orientation, worldorigin.vec3() );
		worldorigin += bindmaster->worldorigin;
		worldorigin.copyTo( edict->s.vieworigin );
	}
	else if ( edict->s.parent )
	{
		org.copyTo( edict->s.vieworigin );
		ent = ( Entity * )G_GetEntity( edict->s.parent );
		worldorigin = ent->centroid;

	}
	else
	{
		worldorigin = origin;
		worldorigin.copyTo( edict->s.vieworigin );
	}

	worldorigin.copyTo( edict->s.origin );
	link();

	//
	// go through and set our children
	//
	num = numchildren;
	for( i = 0; ( i < MAX_MODEL_CHILDREN ) && num; i++ )
	{
		if ( !children[ i ] )
		{
			continue;
		}
		ent = ( Entity * )G_GetEntity( children[ i ] );
		ent->setOrigin( ent->origin );
		num--;
	}
}

EXPORT_FROM_DLL qboolean Entity::GetBone( const char *name,Vector *pos,Vector *forward,Vector *right,Vector *up )
{
	vec3_t trans[ 3 ];
	vec3_t p1, p2;
	vec3_t orient;
	int groupindex;
	int tri_num;

	// get the bone information
	if ( !gi.GetBoneInfo( edict->s.modelindex, name, &groupindex, &tri_num, orient ) )
	{
		return false;
	}
	if ( !gi.GetBoneTransform( edict->s.modelindex, groupindex, tri_num, orient, edict->s.anim, edict->s.frame,
	                           edict->s.scale, trans, p1 ) )
	{
		return false;
	}

	if ( forward || right || up )
	{
		R_ConcatRotations( trans, orientation, trans );
	}

	if ( pos )
	{
		MatrixTransformVector( p1, orientation, p2 );
		*pos = Vector( p2 );
	}
	if ( forward )
	{
		*forward = Vector( trans[ 0 ] );
	}
	if ( right )
	{
		*right = Vector( trans[ 1 ] );
	}
	if ( up )
	{
		*up = Vector( trans[ 2 ] );
	}

	return true;
}

EXPORT_FROM_DLL void Entity::setAngles( Vector ang )
{
	Entity * ent;
	float mat[3][3];
	int num,i;

	angles[ 0 ] = angmod( ang[ 0 ] );
	angles[ 1 ] = angmod( ang[ 1 ] );
	angles[ 2 ] = angmod( ang[ 2 ] );

	if ( bindmaster )
	{
		AnglesToMat( angles.vec3(), mat );
		R_ConcatRotations( mat, bindmaster->orientation, orientation );
		MatrixToEulerAngles( orientation, worldangles.vec3() );
		worldangles.copyTo( edict->s.viewangles );
	}
	else if (edict->s.parent)
	{
		float trans[3][3];
		float local_trans[3][3];
		vec3_t p1;

		ent = ( Entity * )G_GetEntity( edict->s.parent );
		ang.copyTo( edict->s.viewangles );

//### added fake "origin" bone that acts like modelindexes in Quake2
		if(edict->s.bone.tri_num == BONE_ORIGIN)
		{
//gi.dprintf("fake 'origin' bone: setAngles\n");
//			angles = ent->angles;
			AnglesToMat( angles.vec3(), mat );
			R_ConcatRotations( mat, ent->orientation, orientation );
			MatrixToEulerAngles( orientation, worldangles.vec3());
//			worldangles = angles;
//			AnglesToMat(worldangles.vec3(), orientation);
//			worldangles.copyTo(edict->s.viewangles);
		}
//      if ( gi.GetBoneTransform( ent->edict->s.modelindex, edict->s.bone.group_num, edict->s.bone.tri_num, edict->s.bone.orientation,
//         ent->edict->s.anim, ent->edict->s.frame, ent->edict->s.scale, trans, p1 ) )
		else if ( gi.GetBoneTransform( ent->edict->s.modelindex, edict->s.bone.group_num, edict->s.bone.tri_num, edict->s.bone.orientation,
		                               ent->edict->s.anim, ent->edict->s.frame, ent->edict->s.scale, trans, p1 ) )
//###
		{
			AnglesToMat( angles.vec3(), mat );
			R_ConcatRotations( mat, trans, local_trans );
			R_ConcatRotations( local_trans, ent->orientation, orientation );
			MatrixToEulerAngles( orientation, worldangles.vec3() );
		}
	}
	else
	{
		worldangles = angles;
		AnglesToMat( worldangles.vec3(), orientation );
		worldangles.copyTo( edict->s.viewangles );
	}

	worldangles.copyTo( edict->s.angles );

	// Fill the edicts matrix
	VectorCopy( orientation[ 0 ], edict->s.mat[ 0 ] );
	VectorCopy( orientation[ 1 ], edict->s.mat[ 1 ] );
	VectorCopy( orientation[ 2 ], edict->s.mat[ 2 ] );

	//
	// go through and set our children
	//
	num = numchildren;
	for (i=0; (i < MAX_MODEL_CHILDREN) && num; i++)
	{
		if (!children[i])
			continue;
		ent = ( Entity * )G_GetEntity( children[i] );
		ent->setAngles( ent->angles );
		num--;
	}
}

EXPORT_FROM_DLL qboolean Entity::droptofloor( float maxfall )
{
	trace_t trace;
	Vector end;

	end = origin;
	end[ 2 ]-= maxfall;
	origin += "0 0 1";

	trace = G_Trace( origin, mins, maxs, end, this, MASK_SOLID, "Entity::droptofloor" );
	if ( trace.fraction == 1 || trace.allsolid )
	{
		groundentity = NULL;
		return false;
	}

	setOrigin( trace.endpos );

	groundentity                = trace.ent;
	groundentity_linkcount  = trace.ent->linkcount;

	return true;
}

void Entity::Damage( Entity *inflictor,Entity *attacker,int damage,Vector position,Vector direction,Vector normal,int knockback,int dflags,int meansofdeath,int groupnum,int trinum,float damage_multiplier )
{
	Event   *ev;

	if ( !attacker )
	{
		attacker = world;
	}
	if ( !inflictor )
	{
		inflictor = world;
	}

	ev = new Event( EV_Damage );
	ev->AddInteger( damage );
	ev->AddEntity ( inflictor );
	ev->AddEntity ( attacker );
	ev->AddVector ( position );
	ev->AddVector ( direction );
	ev->AddVector ( normal );
	ev->AddInteger( knockback );
	ev->AddInteger( dflags );
	ev->AddInteger( meansofdeath );
	ev->AddInteger( groupnum );
	ev->AddInteger( trinum );
	ev->AddFloat  ( damage_multiplier );
	ProcessEvent  ( ev );
}

void Entity::DamageEvent( Event *ev )
{
	Entity  *inflictor;
	Entity  *attacker;
	int damage;
	Vector dir;
	Vector momentum;
	Event       *event;
	float m;

	if ( ( takedamage == DAMAGE_NO ) || ( movetype == MOVETYPE_NOCLIP ) )
	{
		return;
	}

	damage      = ev->GetInteger( 1 );
	inflictor   = ev->GetEntity( 2 );
	attacker        = ev->GetEntity( 3 );

	// figure momentum add
	if ( ( inflictor != world ) &&
	     ( movetype != MOVETYPE_NONE ) &&
	     ( movetype != MOVETYPE_BOUNCE ) &&
	     ( movetype != MOVETYPE_PUSH ) &&
	     ( movetype != MOVETYPE_STOP ) )
	{
		dir = worldorigin - ( inflictor->worldorigin + ( inflictor->mins + inflictor->maxs ) * 0.5 );
		dir.normalize();

		if ( mass < 50)
		{
			m = 50;
		}
		else
		{
			m = mass;
		}

		momentum = dir * damage * ( 1700.0 / m );
		velocity += momentum;
	}

	// check for godmode or invincibility
	if ( flags & FL_GODMODE )
	{
		return;
	}

	// Forcefields make objects invulnerable
	if ( flags & FL_FORCEFIELD )
	{
		float alpha;
		float radius;
		Entity   *forcefield;
		//
		// spawn forcefield
		//
		forcefield = new Entity;

		radius = ( centroid - worldorigin ).length();
		forcefield->setModel( "sphere2.def" );
		forcefield->setOrigin( centroid );
		forcefield->worldorigin.copyTo( forcefield->edict->s.old_origin );
		forcefield->setMoveType( MOVETYPE_NONE );
		forcefield->setSolidType( SOLID_NOT );
		forcefield->edict->s.scale = radius / 16;
		alpha = damage / 100;
		if ( alpha > 1 )
			alpha = 1;
		if ( alpha < 0.15f )
			alpha = 0.15f;
		forcefield->edict->s.alpha = alpha;
		forcefield->edict->s.renderfx |= RF_TRANSLUCENT;
		forcefield->PostEvent( EV_Remove, 0.1f );
		return;
	}

	// team play damage avoidance
	//if ( ( global->teamplay == 1 ) && ( edict->team > 0 ) && ( edict->team == attacker->edict->team ) )
	//	{
	//	return;
	//	}

	if ( !deathmatch->value && isSubclassOf( Player ) )
	{
		damage *= 0.15;
	}

	if ( deadflag )
	{
		// Check for gib.
		if ( inflictor->isSubclassOf( Projectile ) )
		{
			Event *gibEv;

			health -= damage;

			gibEv = new Event( EV_Gib );
			gibEv->AddEntity( this );
			gibEv->AddFloat( health );
			ProcessEvent( gibEv );
		}
		return;
	}

	// do the damage
	health -= damage;
	if ( health <= 0 )
	{
		if ( attacker )
		{
			event = new Event( EV_GotKill );
			event->AddEntity( this );
			event->AddInteger( damage );
			event->AddEntity( inflictor );
			// location based damage
			event->AddString( ev->GetString( 4 ) );
			event->AddInteger( ev->GetInteger( 9 ) );
			event->AddInteger( 0 );
			attacker->ProcessEvent( event );
		}

		event = new Event( EV_Killed );
		event->AddEntity( attacker );
		event->AddInteger( damage );
		event->AddEntity( inflictor );
		// location based damage
		event->AddString( ev->GetString( 4 ) );
		event->AddInteger( ev->GetInteger( 9 ) ); //### needed for the Hoverbike
		ProcessEvent( event );
		return;
	}

	if (flags & FL_TESSELATE)
	{
		TesselateModel
		(
			this,
			tess_min_size,
			tess_max_size,
			dir,
			damage,
			tess_percentage*0.5f,
			tess_thickness,
			ev->GetVector( 5 )
		);
	}

	if (flags & FL_DARKEN)
	{
		edict->s.renderfx |= RF_LIGHTOFFSET;
		if ( max_health )
		{
			edict->s.lightofs = -( 40.0f * ( (float)(max_health - health) / (float)max_health ) );
		}
		else
		{
			edict->s.lightofs -= damage;
		}
		if ( edict->s.lightofs < -127 )
			edict->s.lightofs = -127;
		if ( edict->s.lightofs > 127 )
			edict->s.lightofs = 127;
	}

	event = new Event( EV_Pain );
	event->AddFloat( damage );
	event->AddEntity( attacker );
	// location based damage
	event->AddString( ev->GetString( 4 ) );
	ProcessEvent( event );
}

/*
   ============
   CanDamage

   Returns true if the inflictor can directly damage the target.  Used for
   explosions and melee attacks.
   ============
 */
qboolean Entity::CanDamage( Entity *target )
{
	trace_t trace;
	Vector pos;

	// bmodels need special checking because their origin is 0,0,0
	if ( target->getMoveType() == MOVETYPE_PUSH )
	{
		pos = ( target->absmin + target->absmax ) * 0.5;
		trace = G_Trace( origin, vec_origin, vec_origin, pos, this, MASK_SHOT, "Entity::CanDamage 1" );
		if ( trace.fraction == 1 || trace.ent == target->edict )
		{
			return true;
		}
		return false;
	}

	trace = G_Trace( origin, vec_origin, vec_origin, target->centroid, this, MASK_SHOT, "Entity::CanDamage 2" );
	if ( trace.fraction == 1 || trace.ent == target->edict )
	{
		return true;
	}
	pos = target->centroid + Vector( 15, 15, 0 );
	trace = G_Trace( origin, vec_origin, vec_origin, pos, this, MASK_SHOT, "Entity::CanDamage 3" );
	if ( trace.fraction == 1 || trace.ent == target->edict )
	{
		return true;
	}
	pos = target->centroid + Vector( -15, 15, 0 );
	trace = G_Trace( origin, vec_zero, vec_zero, pos, this, MASK_SHOT, "Entity::CanDamage 4" );
	if ( trace.fraction == 1 || trace.ent == target->edict )
	{
		return true;
	}
	pos = target->centroid + Vector( 15, -15, 0 );
	trace = G_Trace( origin, vec_zero, vec_zero, pos, this, MASK_SHOT, "Entity::CanDamage 5" );
	if ( trace.fraction == 1 || trace.ent == target->edict )
	{
		return true;
	}
	pos = target->centroid + Vector( -15, -15, 0 );
	trace = G_Trace( origin, vec_zero, vec_zero, pos, this, MASK_SHOT, "Entity::CanDamage 6" );
	if ( trace.fraction == 1 || trace.ent == target->edict )
	{
		return true;
	}

	return false;
}

EXPORT_FROM_DLL qboolean Entity::IsTouching( Entity *e1 )
{
	if ( e1->absmin.x > absmax.x )
	{
		return false;
	}
	if ( e1->absmin.y > absmax.y )
	{
		return false;
	}
	if ( e1->absmin.z > absmax.z )
	{
		return false;
	}
	if ( e1->absmax.x < absmin.x )
	{
		return false;
	}
	if ( e1->absmax.y < absmin.y )
	{
		return false;
	}
	if ( e1->absmax.z < absmin.z )
	{
		return false;
	}

	return true;
}

void Entity::StopAnimating( void )
{
	// Cancel all animating events
	last_animation_time = -1;
	animating = false;
	total_delta = vec_zero;
	if ( animDoneEvent )
	{
		CancelEventsOfType( animDoneEvent );
		delete animDoneEvent;
		animDoneEvent = NULL;
	}
}

void Entity::StartAnimating( void )
{
	// start animating
	AnimateFrame();
	animating = true;
}

void Entity::NextAnim( int animnum )
{
	if ( ( animnum >= 0 ) && ( animnum < gi.NumAnims( edict->s.modelindex ) ) )
	{
		next_anim = animnum;
	}
	else
	{
		// bad value
		return;
	}

	// get the next anim delta
	gi.Anim_Delta( edict->s.modelindex, next_anim, next_anim_delta.vec3() );
	next_anim_delta *= edict->s.scale;

	// get the next anim time
	next_anim_time = gi.Anim_Time( edict->s.modelindex, next_anim );
	NextFrame( 0 );
}

void Entity::NextFrame( int framenum )
{
	if ( ( framenum >= 0 ) && ( framenum <= last_frame_in_anim ) )
	{
		next_frame = framenum;
	}
	else
	{
		// bad value
		return;
	}
}

void Entity::AnimateFrame( void )
{
	float delta;
	sinmdl_cmd_t * cmds;
	Event *ev;
	int i;
	int j;

	//
	// see if we have already animated this frame
	//
	if (
		( level.time == last_animation_time ) &&
		( next_anim < 0 ) &&
		( next_frame == edict->s.frame + 1 )
		)
	{
		return;
	}

	// see if we have an anim change pending
	if (next_anim >= 0)
	{
		edict->s.anim = next_anim;
		last_frame_in_anim = gi.Anim_NumFrames( edict->s.modelindex, edict->s.anim ) - 1;
		next_anim = -1;
		if ( edict->s.gunmodelindex )
		{
			const char * animname;
			animname = gi.Anim_NameForNum( edict->s.modelindex, edict->s.anim );
			//
			// see if the anim exists in the world model
			//
			edict->s.gunanim        = gi.Anim_Random( edict->s.gunmodelindex, animname );
			if ( edict->s.gunanim < 0 )
			{
				//
				// see if at least we have an idle
				//
				edict->s.gunanim        = gi.Anim_Random( edict->s.gunmodelindex, "idle" );
			}
			if ( edict->s.gunanim >= 0 )
			{
				num_frames_in_gun_anim = gi.Anim_NumFrames( edict->s.gunmodelindex, edict->s.gunanim );
			}
			else
			{
				edict->s.gunanim = 0;
				num_frames_in_gun_anim = 0;
			}
		}
	}

	// see if we have a frame change pending
	if (next_frame >= 0)
	{
		edict->s.frame = next_frame;
		next_frame = -1;
	}
#if 0
	{
		const char * animname;
		animname = gi.Anim_NameForNum( edict->s.modelindex, edict->s.anim );
		warning( "aframe", "%d anim %s frame %d", entnum, animname, edict->s.frame );
	}
#endif

	delta = gi.Frame_Time( edict->s.modelindex, edict->s.anim, edict->s.frame );
	if ( !delta )
	{
		delta = FRAMETIME;
	}
	next_frame = edict->s.frame + ( int )( ( float )FRAMETIME / delta );

	// should never be greater...but just in case
	if ( ( edict->s.frame >= last_frame_in_anim ) || ( next_frame > last_frame_in_anim ) )
	{
		PostEvent( EV_LastFrame, 0 );
		if ( animDoneEvent )
		{
			PostEvent( animDoneEvent, 0 );
			animDoneEvent = NULL;
		}
		next_frame -= last_frame_in_anim+1;
	}

	// get the current frame delta
	gi.Frame_Delta( edict->s.modelindex, edict->s.anim, edict->s.frame, frame_delta.vec3() );
	total_delta += frame_delta * edict->s.scale;
	cmds = gi.Frame_Commands( edict->s.modelindex, edict->s.anim, edict->s.frame );
	if ( cmds )
	{
		for( i = 0; i < cmds->num_cmds; i++ )
		{
			ev = new Event( cmds->cmds[ i ].args[ 0 ] );
			for( j = 1; j < cmds->cmds[ i ].num_args; j++ )
			{
				ev->AddToken( cmds->cmds[ i ].args[ j ] );
			}
			ProcessEvent( ev );
		}
	}

	last_animation_time = level.time;
	//
	// check to see if we have a secondary animation system going on here
	//
	if ( edict->s.gunmodelindex )
	{
		if ( num_frames_in_gun_anim > 1 )
		{
			edict->s.gunframe = ( edict->s.frame * num_frames_in_gun_anim ) / ( last_frame_in_anim + 1 );
		}
		else
		{
			edict->s.gunframe = 0;
		}
	}
}

void Entity::RandomAnimate( const char *animname,Event *endevent )
{
	int num;

	num = gi.Anim_Random( edict->s.modelindex, animname );

	//
	// if we have an event that hasn't been processed, kill the current one
	//
	if ( animDoneEvent )
	{
		CancelEventsOfType( animDoneEvent );
		delete animDoneEvent;
		animDoneEvent = NULL;
	}
	//
	// see if we even have a valid animation at all
	//
	if ( num == -1 )
	{
		if ( endevent )
		{
			PostEvent( endevent, FRAMETIME );
		}

		animDoneEvent = NULL;
		return;
	}

	NextAnim( num );

	animDoneEvent = endevent;
	if ( !animating )
	{
		StartAnimating();
	}
	last_animation_time = -1;
}

EXPORT_FROM_DLL void Entity::joinTeam( Entity *teammember )
{
	Entity *ent;
	Entity *master;
	Entity *prev;
	Entity *next;

	if ( teammaster && ( teammaster != this ) )
	{
		quitTeam();
	}

	assert( teammember );
	if ( !teammember )
	{
		warning( "joinTeam", "Null entity" );
		return;
	}

	master = teammember->teammaster;
	if ( !master )
	{
		master = teammember;
		teammember->teammaster = teammember;
		teammember->teamchain = this;

		// make anyone who's bound to me part of the new team
		for( ent = teamchain; ent != NULL; ent = ent->teamchain )
		{
			ent->teammaster = master;
		}
	}
	else
	{
		// skip past the chain members bound to the entity we're teaming up with
		prev = teammember;
		next = teammember->teamchain;
		if ( bindmaster )
		{
			// if we have a bindmaster, joing after any entities bound to the entity
			// we're joining
			while( next && next->isBoundTo( teammember ) )
			{
				prev = next;
				next = next->teamchain;
			}
		}
		else
		{
			// if we're not bound to someone, then put us at the end of the team
			while( next )
			{
				prev = next;
				next = next->teamchain;
			}
		}

		// make anyone who's bound to me part of the new team and
		// also find the last member of my team
		for( ent = this; ent->teamchain != NULL; ent = ent->teamchain )
		{
			ent->teamchain->teammaster = master;
		}

		prev->teamchain = this;
		ent->teamchain = next;
	}

	teammaster = master;
	flags |= FL_TEAMSLAVE;
}

EXPORT_FROM_DLL void Entity::quitTeam( void )
{
	Entity *ent;

	if ( !teammaster )
	{
		return;
	}

	if ( teammaster == this )
	{
		if ( !teamchain->teamchain )
		{
			teamchain->teammaster = NULL;
		}
		else
		{
			// make next teammate the teammaster
			for( ent = teamchain; ent; ent = ent->teamchain )
			{
				ent->teammaster = teamchain;
			}
		}

		teamchain->flags &= ~FL_TEAMSLAVE;
	}
	else
	{
		assert( flags & FL_TEAMSLAVE );
		assert( teammaster->teamchain );

		ent = teammaster;
		while( ent->teamchain != this )
		{
			// this should never happen
			assert( ent->teamchain );

			ent = ent->teamchain;
		}

		ent->teamchain = teamchain;

		if ( !teammaster->teamchain )
		{
			teammaster->teammaster = NULL;
		}
	}

	teammaster = NULL;
	teamchain = NULL;
	flags &= ~FL_TEAMSLAVE;
}

EXPORT_FROM_DLL void Entity::EventQuitTeam( Event *ev )
{
	quitTeam();
}

qboolean Entity::isBoundTo( Entity *master )
{
	Entity *ent;

	for( ent = bindmaster; ent != NULL; ent = ent->bindmaster )
	{
		if ( ent == master )
		{
			return true;
		}
	}

	return false;
}

EXPORT_FROM_DLL void Entity::bind( Entity *master )
{
	float mat[ 3 ][ 3 ];
	float local[ 3 ][ 3 ];
	Vector ang;

	assert( master );
	if ( !master )
	{
		warning( "bind", "Null master entity" );
		return;
	}

	if ( master == this )
	{
		warning( "bind", "Trying to bind to oneself." );
		return;
	}

	// unbind myself from my master
	unbind();

	bindmaster = master;

	// We are now separated from our previous team and are either
	// an individual, or have a team of our own.  Now we can join
	// the new bindmaster's team.  Bindmaster must be set before
	// joining the team, or we will be placed in the wrong position
	// on the team.
	joinTeam( master );

	// calculate local angles
	TransposeMatrix( bindmaster->orientation, mat );
	R_ConcatRotations( mat, orientation, local );
	MatrixToEulerAngles( local, ang.vec3() );
	setAngles( ang );

	setOrigin( getParentVector( origin - bindmaster->worldorigin ) );

	return;
}

EXPORT_FROM_DLL void Entity::unbind( void )
{
	Entity *prev;
	Entity *next;
	Entity *last;
	Entity *ent;

	if ( !bindmaster )
	{
		return;
	}

	//bindmaster = NULL;

	origin = Vector( edict->s.origin );
	angles = Vector( edict->s.angles );

	if ( !teammaster )
	{
		bindmaster = NULL;
		//Teammaster already has been freed
		return;
	}

	// We're still part of a team, so that means I have to extricate myself
	// and any entities that are bound to me from the old team.
	// Find the node previous to me in the team
	prev = teammaster;

	for( ent = teammaster->teamchain; ent && ( ent != this ); ent = ent->teamchain )
	{
		prev = ent;
	}

	// If ent is not pointing to me, then something is very wrong.
	assert( ent );
	if ( !ent )
	{
		error( "unbind", "corrupt team chain\n" );
	}

	// Find the last node in my team that is bound to me.
	// Also find the first node not bound to me, if one exists.
	last = this;
	for( next = teamchain; next != NULL; next = next->teamchain )
	{
		if ( !next->isBoundTo( this ) )
		{
			break;
		}

		// Tell them I'm now the teammaster
		next->teammaster = this;
		last = next;
	}

	// disconnect the last member of our team from the old team
	last->teamchain = NULL;

	// connect up the previous member of the old team to the node that
	// follow the last node bound to me (if one exists).
	if ( teammaster != this )
	{
		prev->teamchain = next;
		if ( !next && ( teammaster == prev ) )
		{
			prev->teammaster = NULL;
		}
	}
	else if ( next )
	{
		// If we were the teammaster, then the nodes that were not bound to me are now
		// a disconnected chain.  Make them into their own team.
		for( ent = next; ent->teamchain != NULL; ent = ent->teamchain )
		{
			ent->teammaster = next;
		}
		next->teammaster = next;
		next->flags &= ~FL_TEAMSLAVE;
	}

	// If we don't have anyone on our team, then clear the team variables.
	if ( teamchain )
	{
		// make myself my own team
		teammaster = this;
	}
	else
	{
		// no longer a team
		teammaster = NULL;
	}

	flags &= ~FL_TEAMSLAVE;
	bindmaster = NULL;
}

EXPORT_FROM_DLL void Entity::EventUnbind( Event *ev )
{
	unbind();
}

EXPORT_FROM_DLL void Entity::FadeOut( Event *ev )
{
	PostEvent( EV_FadeOut, 0.1f );

	edict->s.renderfx |= RF_TRANSLUCENT;
	translucence += 0.03f;
	if ( translucence >= 0.96f )
	{
		PostEvent( EV_Remove, 0 );
	}

	setAlpha( 1.0f - translucence );
}

EXPORT_FROM_DLL void Entity::Fade( Event *ev )
{
	float rate = ev->GetFloat( 1 );

	edict->s.renderfx |= RF_TRANSLUCENT;
	translucence += rate;
	setAlpha( 1.0f - translucence );

	if ( translucence <= 1 )
		PostEvent( EV_FadeOut, 0.1f );
}

EXPORT_FROM_DLL void Entity::SetMassEvent( Event *ev )
{
	mass = ev->GetFloat( 1 );
}

void Entity::CheckGround( void )
{
	Vector point;
	trace_t trace;
	const gravityaxis_t &grav = gravity_axis[ gravaxis ];

	if ( flags & ( FL_SWIM | FL_FLY ) )
	{
		return;
	}

	if ( velocity[ grav.z ] > 100 )
	{
		groundentity = NULL;
		return;
	}

	// if the hull point one-quarter unit down is solid the entity is on ground
	point = worldorigin;
	point[ grav.z ] -= 0.25 * grav.sign;
	trace = G_Trace( worldorigin, mins, maxs, point, this, MASK_MONSTERSOLID, "Entity::CheckGround" );

	// check steepness
	if ( ( ( trace.plane.normal[ grav.z ] * grav.sign ) <= 0.7 ) && !trace.startsolid )
	{
		groundentity = NULL;
		return;
	}

	groundentity = trace.ent;
	groundentity_linkcount = trace.ent->linkcount;
	groundplane = trace.plane;
	groundsurface = trace.surface;
	groundcontents = trace.contents;

	if ( !trace.startsolid && !trace.allsolid )
	{
		setOrigin( trace.endpos );
		velocity[ grav.z ] = 0;
	}
}

//###
// NOTE: Didn't bother to make it work with gravaxis
void Entity::CheckCeilingGround( void )
{
	Vector point;
	trace_t trace;
//	const gravityaxis_t &grav = gravity_axis[ gravaxis ];

	if ( flags & ( FL_SWIM | FL_FLY ) )
	{
		return;
	}

//	if ( velocity[ grav.z ] < -100 )
	if ( velocity.z < -100 )
	{
		groundentity = NULL;
		return;
	}

	// if the hull point one-quarter unit down is solid the entity is on ground
	point = worldorigin;
//	point[ grav.z ] -= 0.25 * grav.sign;
	point.z += 0.25;
	trace = G_Trace( worldorigin, mins, maxs, point, this, MASK_MONSTERSOLID, "Entity::CheckCeilingGround" );

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

	groundentity = trace.ent;
	groundentity_linkcount = trace.ent->linkcount;
	groundplane = trace.plane;
	groundsurface = trace.surface;
	groundcontents = trace.contents;

	if ( !trace.startsolid && !trace.allsolid )
	{
		setOrigin( trace.endpos );
//		velocity[ grav.z ] = 0;
		velocity.z = 0;
	}
}
//###

EXPORT_FROM_DLL void Entity::BecomeSolid( Event *ev )
{
	if ( ( model.length() ) && ( ( model[ 0 ] == '*' ) || ( strstr( model.c_str(), ".bsp" ) ) ) )
	{
		setSolidType( SOLID_BSP );
	}
	else
	{
		setSolidType( SOLID_BBOX );
	}
}

EXPORT_FROM_DLL void Entity::BecomeNonSolid( Event *ev )
{
	setSolidType( SOLID_NOT );
}

EXPORT_FROM_DLL void Entity::Ghost( Event *ev )
{
	// Make not solid, but send still send over whether it is hidden or not
	setSolidType( SOLID_NOT );
	edict->svflags &= ~SVF_NOCLIENT;
}

EXPORT_FROM_DLL void Entity::PlaySound( Event *ev )
{
	char name[ 128 ];
	float volume;
	int channel;
	float attenuation;
	float pitch;
	float timeofs;
	float fadetime;
	int flags;
	int i;

	//
	// set defaults
	//
	name[0] = 0;
	volume = 1.0f;
	channel = CHAN_BODY;
	attenuation = ATTN_NORM;
	pitch = 1.0f;
	timeofs = 0;
	fadetime = 0;
	flags = SOUND_SYNCH;
	for ( i = 1; i <= ev->NumArgs(); i++ )
	{
		switch (i-1)
		{
		case 0:
			strcpy( name, ev->GetString( i ) );
			break;
		case 1:
			volume = ev->GetFloat( i );
			break;
		case 2:
			channel = ev->GetInteger( i );
			break;
		case 3:
			attenuation = ev->GetFloat( i );
			break;
		case 4:
			pitch = ev->GetFloat( i );
			break;
		case 5:
			timeofs = ev->GetFloat( i );
			break;
		case 6:
			fadetime = ev->GetFloat( i );
			break;
		case 7:
			flags = ev->GetInteger( i );
			break;
		default:
			break;
		}
	}
	channel |= CHAN_NO_PHS_ADD;
	sound( name, volume, channel, attenuation, pitch, timeofs, fadetime, flags );
}

EXPORT_FROM_DLL void Entity::StopSound( Event *ev )
{
	if (ev->NumArgs() < 1)
		stopsound( CHAN_BODY );
	else
		stopsound( ev->GetInteger( 1 ) );
}

EXPORT_FROM_DLL void Entity::SetGravityAxis( int axis )
{
	Vector min;
	Vector max;

	if ( ( axis < 0 ) || ( axis > 5 ) )
	{
		axis = 0;
	}

	// don't do anything if the axis has been already set
	if ( axis == gravaxis )
		return;

	edict->s.effects &= ~( EF_GRAVITY_AXIS_0 | EF_GRAVITY_AXIS_1 | EF_GRAVITY_AXIS_2 );
	edict->s.effects |= GRAVITYAXIS_TO_EFFECTS( axis );
	gravaxis = EFFECTS_TO_GRAVITYAXIS( edict->s.effects );
	groundentity = NULL;

	const gravityaxis_t &grav = gravity_axis[ gravaxis ];

	min[ grav.x ] = mins[ 0 ];
	min[ grav.y ] = mins[ 1 ] * grav.sign;
	min[ grav.z ] = mins[ 2 ] * grav.sign;
	max[ grav.x ] = maxs[ 0 ];
	max[ grav.y ] = maxs[ 1 ] * grav.sign;
	max[ grav.z ] = maxs[ 2 ] * grav.sign;

	setSize( min, max );
}

EXPORT_FROM_DLL void Entity::GravityAxisEvent( Event *ev )
{
	SetGravityAxis( ev->GetInteger( 1 ) );
}

EXPORT_FROM_DLL void Entity::BindEvent( Event *ev )
{
	Entity *ent;

	ent = ev->GetEntity( 1 );
	if ( ent )
	{
		bind( ent );
	}
}


EXPORT_FROM_DLL void Entity::JoinTeam( Event *ev )
{
	Entity *ent;

	ent = ev->GetEntity( 1 );
	if ( ent )
	{
		joinTeam( ent );
	}
}

EXPORT_FROM_DLL void Entity::SetLight( Event *ev )
{
	edict->s.renderfx |= RF_DLIGHT;
	edict->s.color_r = ev->GetFloat( 1 );
	edict->s.color_g = ev->GetFloat( 2 );
	edict->s.color_b = ev->GetFloat( 3 );
	edict->s.radius  = ev->GetFloat( 4 );
}

EXPORT_FROM_DLL void Entity::LightOn( Event *ev )
{
	edict->s.renderfx |= RF_DLIGHT;
}

EXPORT_FROM_DLL void Entity::LightOff( Event *ev )
{
	edict->s.renderfx &= ~RF_DLIGHT;
}

EXPORT_FROM_DLL void Entity::LightRed( Event *ev )
{
	edict->s.renderfx |= RF_DLIGHT;
	edict->s.color_r = ev->GetFloat( 1 );
}

EXPORT_FROM_DLL void Entity::LightGreen( Event *ev )
{
	edict->s.renderfx |= RF_DLIGHT;
	edict->s.color_g = ev->GetFloat( 1 );
}

EXPORT_FROM_DLL void Entity::LightBlue( Event *ev )
{
	edict->s.renderfx |= RF_DLIGHT;
	edict->s.color_b = ev->GetFloat( 1 );
}

EXPORT_FROM_DLL void Entity::LightRadius( Event *ev )
{
	edict->s.renderfx |= RF_DLIGHT;
	edict->s.radius = ev->GetFloat( 1 );
}

EXPORT_FROM_DLL void Entity::SetHealth( Event *ev )
{
	health = ev->GetFloat( 1 );
	max_health = health;
}

EXPORT_FROM_DLL void Entity::SetSize( Event *ev )
{
	Vector min, max;

	min = ev->GetVector( 1 );
	max = ev->GetVector( 2 );
	setSize( min, max );
}

EXPORT_FROM_DLL void Entity::SetScale( Event *ev )
{
	setScale( ev->GetFloat( 1 ) );
}

EXPORT_FROM_DLL void Entity::SetAlpha( Event *ev )
{
	setAlpha( ev->GetFloat( 1 ) );
}

EXPORT_FROM_DLL void Entity::SetOrigin( Event *ev )
{
	setOrigin( ev->GetVector( 1 ) );
}

EXPORT_FROM_DLL void Entity::SetTargetName( Event *ev )
{
	SetTargetName( ev->GetString( 1 ) );
}

EXPORT_FROM_DLL void Entity::SetTarget( Event *ev )
{
	SetTarget( ev->GetString( 1 ) );
}

EXPORT_FROM_DLL void Entity::SetKillTarget( Event *ev )
{
	SetKillTarget( ev->GetString( 1 ) );
}

EXPORT_FROM_DLL void Entity::SetAngles( Event *ev )
{
	setAngles( ev->GetVector( 1 ) );
}

EXPORT_FROM_DLL void Entity::CourseAnglesEvent( Event *ev )
{
	// Angles will be sent over the net as 8-bit values (default)
	edict->s.effects &= ~EF_SMOOTHANGLES;
}

EXPORT_FROM_DLL void Entity::SmoothAnglesEvent( Event *ev )
{
	// Angles will be sent over the net as 16-bit values for smoother rotation (or slow rotation)
	edict->s.effects |= EF_SMOOTHANGLES;
}

EXPORT_FROM_DLL void Entity::RegisterAlias( Event *ev )
{
	if ( ev->NumArgs() < 3 )
	{
		gi.Alias_Add( edict->s.modelindex, ev->GetString( 1 ), ev->GetString( 2 ),  1 );
	}
	else
	{
		gi.Alias_Add( edict->s.modelindex, ev->GetString( 1 ), ev->GetString( 2 ), ev->GetInteger( 3 ) );
	}
}

EXPORT_FROM_DLL void Entity::RegisterAliasAndCache( Event *ev )
{
	int length;
	str realname;
	const char * ptr;

	realname = ev->GetString( 2 );

	if ( ev->NumArgs() < 3 )
	{
		gi.Alias_Add( edict->s.modelindex, ev->GetString( 1 ), realname.c_str(),  1 );
	}
	else
	{
		gi.Alias_Add( edict->s.modelindex, ev->GetString( 1 ), realname.c_str(), ev->GetInteger( 3 ) );
	}

	length = realname.length();
	ptr = realname.c_str();
	ptr += length - 4;
	if ( ( length > 4 ) && ( !strcmpi( ptr, ".wav" ) ) )
	{
		gi.soundindex( realname.c_str() );
	}
	else if ( ( length > 4 ) && ( !strcmpi( ptr, ".def" ) ) )
	{
		gi.modelindex( realname.c_str() );
	}
}

EXPORT_FROM_DLL void Entity::positioned_sound( Vector origin,str soundname,float volume,int channel,int attenuation,float pitch,float timeofs,float fadetime,int flags )
{
	if ( soundname.length() )
	{
		gi.positioned_sound( worldorigin.vec3(), edict, channel, gi.soundindex( soundname.c_str() ),
		                     volume, attenuation, timeofs, pitch, fadetime, flags );
	}
	else
	{
		warning( "sound", "Null sample pointer" );
	}
}

EXPORT_FROM_DLL void Entity::RandomPositionedSound( Vector origin,str soundname,float volume,int channel,int attenuation,float pitch,float timeofs,float fadetime,int flags )
{
	const char * name;

	name = gi.GlobalAlias_FindRandom( soundname.c_str() );
	if ( name )
	{
		positioned_sound( worldorigin, name, volume, channel, attenuation, pitch, timeofs, fadetime, flags );
	}
	else
	{
		warning( "RandomPositionedSound", "Couldn't find alias for %s", soundname.c_str() );
	}
}

EXPORT_FROM_DLL void Entity::sound( str soundname,float volume,int channel,int attenuation,float pitch,float timeofs,float fadetime,int flags )
{
	if ( soundname.length() )
	{
		gi.sound( edict, channel, gi.soundindex( soundname.c_str() ), volume,
		          attenuation, timeofs, pitch, fadetime, flags );
	}
	else
	{
		warning( "sound", "Null sample pointer" );
	}
}

EXPORT_FROM_DLL void Entity::RandomGlobalSound( str soundname,float volume,int channel,int attenuation,float pitch,float timeofs,float fadetime,int flags )
{
	const char * name;

	name = gi.GlobalAlias_FindRandom( soundname.c_str() );
	if ( name )
	{
		sound( name, volume, channel, attenuation, pitch, timeofs, fadetime, flags );
	}
	else
	{
		warning( "RandomGlobalSound", "Couldn't find alias for %s", soundname.c_str() );
	}
}

EXPORT_FROM_DLL void Entity::RandomSound( str soundname,float volume,int channel,int attenuation,float pitch,float timeofs,float fadetime,int flags )
{
	str realname;

	realname = GetRandomAlias( soundname );
	sound( realname, volume, channel, attenuation, pitch, timeofs, fadetime, flags );
}

EXPORT_FROM_DLL void Entity::RandomSound( Event *ev )
{
	str name;
	float volume;
	int channel;
	float attenuation;
	float pitch;
	float timeofs;
	float fadetime;
	int flags;
	int i;

	//
	// set defaults
	//
	volume = 1.0f;
	channel = CHAN_BODY;
	attenuation = ATTN_NORM;
	pitch = 1.0f;
	timeofs = 0;
	fadetime = 0;
	flags = SOUND_SYNCH;
	for ( i = 1; i <= ev->NumArgs(); i++ )
	{
		switch (i-1)
		{
		case 0:
			name = ev->GetString( i );
			break;
		case 1:
			volume = ev->GetFloat( i );
			break;
		case 2:
			channel = ev->GetInteger( i );
			break;
		case 3:
			attenuation = ev->GetFloat( i );
			break;
		case 4:
			pitch = ev->GetFloat( i );
			break;
		case 5:
			timeofs = ev->GetFloat( i );
			break;
		case 6:
			fadetime = ev->GetFloat( i );
			break;
		case 7:
			flags = ev->GetInteger( i );
			break;
		default:
			break;
		}
	}
	channel |= CHAN_NO_PHS_ADD;
	RandomSound( name, volume, channel, attenuation, pitch, timeofs, fadetime, flags );
}

EXPORT_FROM_DLL void Entity::RandomPHSSound( Event *ev )
{
	str name;
	float volume;
	int channel;
	float attenuation;
	float pitch;
	float timeofs;
	float fadetime;
	int flags;
	int i;

	//
	// set defaults
	//
	volume = 1.0f;
	channel = CHAN_BODY;
	attenuation = ATTN_NORM;
	pitch = 1.0f;
	timeofs = 0;
	fadetime = 0;
	flags = SOUND_SYNCH;
	for ( i = 1; i <= ev->NumArgs(); i++ )
	{
		switch (i-1)
		{
		case 0:
			name = ev->GetString( i );
			break;
		case 1:
			volume = ev->GetFloat( i );
			break;
		case 2:
			channel = ev->GetInteger( i );
			break;
		case 3:
			attenuation = ev->GetFloat( i );
			break;
		case 4:
			pitch = ev->GetFloat( i );
			break;
		case 5:
			timeofs = ev->GetFloat( i );
			break;
		case 6:
			fadetime = ev->GetFloat( i );
			break;
		case 7:
			flags = ev->GetInteger( i );
			break;
		default:
			break;
		}
	}
	RandomSound( name, volume, channel, attenuation, pitch, timeofs, fadetime, flags );
}

EXPORT_FROM_DLL void Entity::PHSSound( Event *ev )
{
	char name[ 128 ];
	float volume;
	int channel;
	float attenuation;
	float pitch;
	float timeofs;
	float fadetime;
	int flags;
	int i;

	//
	// set defaults
	//
	name[0] = 0;
	volume = 1.0f;
	channel = CHAN_BODY;
	attenuation = ATTN_NORM;
	pitch = 1.0f;
	timeofs = 0;
	fadetime = 0;
	flags = SOUND_SYNCH;
	for ( i = 1; i <= ev->NumArgs(); i++ )
	{
		switch (i-1)
		{
		case 0:
			strcpy( name, ev->GetString( i ) );
			break;
		case 1:
			volume = ev->GetFloat( i );
			break;
		case 2:
			channel = ev->GetInteger( i );
			break;
		case 3:
			attenuation = ev->GetFloat( i );
			break;
		case 4:
			pitch = ev->GetFloat( i );
			break;
		case 5:
			timeofs = ev->GetFloat( i );
			break;
		case 6:
			fadetime = ev->GetFloat( i );
			break;
		case 7:
			flags = ev->GetInteger( i );
			break;
		default:
			break;
		}
	}
	sound( name, volume, channel, attenuation, pitch, timeofs, fadetime, flags );
}


EXPORT_FROM_DLL void Entity::EntitySound( Event *ev )
{
	edict->s.sound = gi.soundindex( ev->GetString( 1 ) );
	if ( ev->NumArgs() > 1 )
	{
		int attenuation;
		attenuation = ev->GetInteger( 2 );
		if (attenuation > 3) attenuation = 3;
		if (attenuation < 0) attenuation = 0;
		edict->s.sound |= attenuation<<14;
	}
	else
	{
		edict->s.sound |= ATTN_IDLE<<14;
	}
}

EXPORT_FROM_DLL void Entity::StopEntitySound( Event *ev )
{
	edict->s.sound = 0;
}

EXPORT_FROM_DLL void Entity::RandomEntitySound( Event *ev )
{
	const char * alias;
	const char * soundname;

	alias = ev->GetString( 1 );

	soundname = gi.Alias_FindRandom( edict->s.modelindex, alias );
	edict->s.sound = gi.soundindex( soundname );
	if ( ev->NumArgs() > 1 )
	{
		int attenuation;
		attenuation = ev->GetInteger( 2 );
		if (attenuation > 3) attenuation = 3;
		if (attenuation < 0) attenuation = 0;
		edict->s.sound |= attenuation<<14;
	}
	else
	{
		edict->s.sound |= ATTN_IDLE<<14;
	}
}

EXPORT_FROM_DLL void Entity::RandomGlobalEntitySound( str soundname,int attenuation )
{
	const char * name;

	name = gi.GlobalAlias_FindRandom( soundname.c_str() );
	if ( name )
	{
		edict->s.sound = gi.soundindex( name );

		bound( attenuation, 0, 3 );
		edict->s.sound |= attenuation<<14;
	}
	else
	{
		warning( "RandomGlobalEntitySound", "Couldn't find alias for %s", soundname.c_str() );
	}
}

EXPORT_FROM_DLL void Entity::RandomGlobalEntitySoundEvent( Event *ev )
{
	const char *alias;
	int attenuation;

	alias = ev->GetString( 1 );

	attenuation = ATTN_IDLE;
	if ( ev->NumArgs() > 1 )
	{
		attenuation = ev->GetInteger( 2 );
	}

	RandomGlobalEntitySound( alias, attenuation );
}

EXPORT_FROM_DLL qboolean Entity::attach( int parent_entity_num,int group_num,int tri_num,Vector orient )
{
	int i;
	Entity * parent;

	if ( entnum == parent_entity_num )
	{
		warning( "attach","Trying to attach to oneself." );
		return false;
	}

	if (edict->s.parent)
		detach();
	//
	// get the parent
	//
	parent = ( Entity * )G_GetEntity( parent_entity_num );

	if (parent->numchildren < MAX_MODEL_CHILDREN)
	{
		//
		// find a free spot in the parent
		//
		for ( i=0; i < MAX_MODEL_CHILDREN; i++ )
			if (!parent->children[i])
			{
				break;
			}
		parent->children[i] = entnum;
		parent->numchildren++;
		edict->s.parent = parent_entity_num;
		edict->s.bone.group_num = group_num;
		edict->s.bone.tri_num = tri_num;
		VectorCopy( orient.vec3(), edict->s.bone.orientation );
		setOrigin( origin );
		return true;
	}
	return false;
}

EXPORT_FROM_DLL void Entity::detach( void )
{
	int i;
	int num;
	Entity * parent;

	if (!edict->s.parent)
		return;
	parent = ( Entity * )G_GetEntity( edict->s.parent );
	if (!parent)
		return;
	for ( i=0,num = parent->numchildren; i < MAX_MODEL_CHILDREN; i++ )
	{
		if (!parent->children[i])
		{
			continue;
		}
		if (parent->children[i] == entnum)
		{
			parent->children[i] = 0;
			parent->numchildren--;
			break;
		}
		num--;
		if (!num)
			break;
	}
	edict->s.parent = 0;

	//
	// i don't think we want to do this automatically, we might later, but for right now lets not
	//
	//setOrigin( edict->origin + parent->origin );
}

void Entity::AnimEvent( Event *ev )
{
	int num;

	num = gi.Anim_Random( edict->s.modelindex, ev->GetString( 1 ) );
	NextAnim( num );
	if ( !animating )
	{
		StartAnimating();
	}
}

void Entity::StartAnimatingEvent( Event *ev )
{
	StartAnimating();
}

void Entity::StopAnimatingEvent( Event *ev )
{
	StopAnimating();
}

void Entity::EndAnimEvent( Event *ev )
{
	PostEvent( EV_LastFrame, 0 );
	if ( animDoneEvent )
	{
		PostEvent( animDoneEvent, 0 );
		animDoneEvent = NULL;
	}
	next_frame = 0;
}

void Entity::NextAnimEvent( Event *ev )
{
	int num;

	num = gi.Anim_Random( edict->s.modelindex, ev->GetString( 1 ) );
	NextAnim( num );
}

void Entity::NextFrameEvent( Event *ev )
{
	NextFrame( ev->GetInteger( 1 ) );
}

void Entity::PrevFrameEvent( Event *ev )
{
	edict->s.prevframe = ev->GetInteger( 1 );
}

void Entity::SetFrameEvent( Event *ev )
{
	int framenum;

	framenum = ev->GetInteger( 1 );
	edict->s.frame = framenum;
	NextFrame( framenum + 1 );
}

void Entity::Tesselate( Event *ev )
{
	Vector origin, dir, temp;
	Entity * ent;
	int i, power, min_size, max_size, thickness;
	float percentage;

	// dir is 1
	// power is 2
	// minsize is 3
	// maxsize is 4
	// percentage is 5
	// thickness 6
	// entity is 7
	// origin 8

	//
	// initialize some variables
	//
	ent = this;
	min_size = TESS_DEFAULT_MIN_SIZE;
	max_size = TESS_DEFAULT_MIN_SIZE;
	thickness = min_size;
	percentage = TESS_DEFAULT_PERCENT;
	VectorCopy( vec3_origin, origin );
	VectorCopy( vec3_origin, dir );

	for ( i = 1; i <= ev->NumArgs(); i++ )
	{
		switch( i )
		{
		case 1:
			temp = ev->GetVector( i );
			temp.AngleVectors( &dir, NULL, NULL );
			break;
		case 2:
			power = ev->GetInteger( i );
			break;
		case 3:
			min_size = ev->GetInteger( i );
			break;
		case 4:
			max_size = ev->GetInteger( i );
			break;
		case 5:
			percentage = ev->GetFloat( i );
			break;
		case 6:
			thickness = ev->GetInteger( i );
			break;
		case 7:
			ent = ev->GetEntity( i );
			break;
		case 8:
			origin = ev->GetVector( i );
			break;
		}
	}
	TesselateModel
	(
		ent,
		min_size,
		max_size,
		dir,
		power,
		percentage,
		thickness,
		origin
	);
}

void Entity::SetShatterMinSize( Event *ev )
{
	tess_min_size = ev->GetInteger( 1 );
}

void Entity::SetShatterMaxSize( Event *ev )
{
	tess_max_size = ev->GetInteger( 1 );
}

void Entity::SetShatterThickness( Event *ev )
{
	tess_thickness = ev->GetInteger( 1 );
}

void Entity::SetShatterPercentage( Event *ev )
{
	tess_percentage = ev->GetFloat( 1 ) / 100.0f;;
}

void Entity::Flags( Event *ev )
{
	const char *flag;
	int mask;
	int action;
	int i;

#define FLAG_IGNORE  0
#define FLAG_SET     1
#define FLAG_CLEAR   2
#define FLAG_ADD     3

	for ( i = 1; i <= ev->NumArgs(); i++ )
	{
		action = FLAG_IGNORE;
		flag = ev->GetString( i );
		switch( flag[0] )
		{
		case '+':
			action = FLAG_ADD;
			flag++;
			break;
		case '-':
			action = FLAG_CLEAR;
			flag++;
			break;
		default:
			action = FLAG_SET;
			break;
		}

		if ( !stricmp( flag, "blood" ) )
			mask = FL_BLOOD;
		else if ( !stricmp( flag, "sparks" ) )
			mask = FL_SPARKS;
		else if ( !stricmp( flag, "shatter" ) )
			mask = FL_TESSELATE;
		else if ( !stricmp( flag, "blast" ) )
			mask = FL_BLASTMARK;
		else if ( !stricmp( flag, "die_shatter" ) )
			mask = FL_DIE_TESSELATE;
		else if ( !stricmp( flag, "explode" ) )
			mask = FL_DIE_EXPLODE;
		else if ( !stricmp( flag, "die_gibs" ) )
			mask = FL_DIE_GIBS;
		else if ( !stricmp( flag, "darken" ) )
			mask = FL_DARKEN;
		else if ( !stricmp( flag, "forcefield" ) )
			mask = FL_FORCEFIELD;
		else if ( !stricmp( flag, "stealth" ) )
			mask = FL_STEALTH;
		else
		{
			action = FLAG_IGNORE;
			ev->Error( "Unknown flag '%s'", flag );
		}
		switch (action)
		{
		case FLAG_SET:
			// preserver non-configurable bits
			flags &= (FL_BLOOD - 1);
		case FLAG_ADD:
			flags |= mask;
			break;
		case FLAG_CLEAR:
			flags &= ~mask;
			break;
		case FLAG_IGNORE:
			break;
		}
	}
	if ( parentmode->value )
	{
		if ( flags & (FL_BLOOD|FL_DIE_GIBS) )
		{
			flags &= ~FL_BLOOD;
			flags &= ~FL_DIE_GIBS;
			edict->s.effects &= ~EF_WARM; //###
		}
	}
}

void Entity::Effects( Event *ev )
{
	const char *flag;
	int mask=0;
	int action;
	int i;

#define FLAG_IGNORE  0
#define FLAG_SET     1
#define FLAG_CLEAR   2
#define FLAG_ADD     3
	for ( i = 1; i <= ev->NumArgs(); i++ )
	{
		action = 0;
		flag = ev->GetString( i );
		switch( flag[0] )
		{
		case '+':
			action = FLAG_ADD;
			flag++;
			break;
		case '-':
			action = FLAG_CLEAR;
			flag++;
			break;
		default:
			action = FLAG_SET;
			break;
		}

		if ( !stricmp( flag, "rotate" ) )
			mask = EF_ROTATE;
		else if ( !stricmp( flag, "rocket" ) )
			mask = EF_ROCKET;
		else if ( !stricmp( flag, "gib" ) )
			mask = EF_GIB;
		else if ( !stricmp( flag, "pulse" ) )
			mask = EF_PULSE;
		else if ( !stricmp( flag, "everyframe" ) )
			mask = EF_EVERYFRAME;
		else if ( !stricmp( flag, "autoanimate" ) )
			mask = EF_AUTO_ANIMATE;
		//###
		else if ( !stricmp( flag, "warm" ) )
			mask = EF_WARM;
		else if ( !stricmp( flag, "stingerrocket" ) )
			mask = EF_ANIMEROCKET;
		else if ( !stricmp( flag, "flames" ) )
			mask = EF_FLAMES;
		else if ( !stricmp( flag, "plasma1" ) )
			mask = EF_PLASMATRAIL1;
		else if ( !stricmp( flag, "plasma2" ) )
			mask = EF_PLASMATRAIL2;
		else if ( !stricmp( flag, "thruster" ) )
			mask = EF_HOVERTHRUST;
		else if ( !stricmp( flag, "turbo" ) )
			mask = EF_HOVERTURBO;
		//###
		else
		{
			action = FLAG_IGNORE;
			ev->Error( "Unknown token %s.", flag );
		}

		switch (action)
		{
		case FLAG_SET:
		case FLAG_ADD:
			edict->s.effects |= mask;
			break;
		case FLAG_CLEAR:
			edict->s.effects &= ~mask;
			break;
		case FLAG_IGNORE:
			break;
		}
	}
}

void Entity::RenderEffects( Event *ev )
{
	const char *flag;
	int mask=0;
	int action;
	int i;

#define FLAG_IGNORE  0
#define FLAG_SET     1
#define FLAG_CLEAR   2
#define FLAG_ADD     3

	for ( i = 1; i <= ev->NumArgs(); i++ )
	{
		action = 0;
		flag = ev->GetString( i );
		switch( flag[0] )
		{
		case '+':
			action = FLAG_ADD;
			flag++;
			break;
		case '-':
			action = FLAG_CLEAR;
			flag++;
			break;
		default:
			action = FLAG_SET;
			break;
		}

		if ( !stricmp( flag, "minlight" ) )
			mask = RF_MINLIGHT;
		else if ( !stricmp( flag, "fullbright" ) )
			mask = RF_FULLBRIGHT;
		else if ( !stricmp( flag, "envmapped" ) )
			mask = RF_ENVMAPPED;
		else if ( !stricmp( flag, "glow" ) )
			mask = RF_GLOW;
		else if ( !stricmp( flag, "dontdraw" ) )
			mask = RF_DONTDRAW;
		else
		{
			action = FLAG_IGNORE;
			ev->Error( "Unknown token %s.", flag );
		}

		switch (action)
		{
		case FLAG_SET:
		case FLAG_ADD:
			edict->s.renderfx |= mask;
			break;
		case FLAG_CLEAR:
			edict->s.renderfx &= ~mask;
			break;
		case FLAG_IGNORE:
			break;
		}
	}
}

void Entity::BroadcastSound( Event *soundevent,int channel,Event &event,float radius )
{
	Sentient *ent;
	Vector delta;
	Event       *ev;
	str name;
	float r2;
	float dist2;
	float volume;
	float attenuation;
	float pitch;
	float timeofs;
	float fadetime;
	int flags;
	int i;
	int n;
#if 0
	int count;

	count = 0;
#endif

	if ( ( ( int )event != ( int )NullEvent ) && !( this->flags & FL_NOTARGET ) )
	{
		r2 = radius * radius;
		n = SentientList.NumObjects();
		for( i = 1; i <= n; i++ )
		{
			ent = SentientList.ObjectAt( i );
			if ( ent->deadflag || ( ent == this ) )
			{
				continue;
			}

			delta = centroid - ent->centroid;

			// dot product returns length squared
			dist2 = delta * delta;
			if (
				( dist2 <= r2 ) &&
				(
					( edict->areanum == ent->edict->areanum ) ||
					( gi.AreasConnected( edict->areanum, ent->edict->areanum ) )
				)
				)

			{
				ev = new Event( event );
				ev->AddEntity( this );
				ev->AddVector( worldorigin );
				ent->PostEvent( ev, 0 );
#if 0
				count++;
#endif
			}
		}
	}

#if 0
	gi.dprintf( "Broadcast sound to %d entities\n", count );
#endif

	if ( !soundevent->NumArgs() )
	{
		return;
	}

	//
	// set defaults
	//
	volume = 1.0f;
	attenuation = ATTN_NORM;
	pitch = 1.0f;
	timeofs = 0;
	fadetime = 0;
	flags = 0;
	for ( i = 1; i <= soundevent->NumArgs(); i++ )
	{
		switch (i-1)
		{
		case 0:
			name = soundevent->GetString( i );
			break;
		case 1:
			volume = soundevent->GetFloat( i );
			break;
		case 2:
			attenuation = soundevent->GetFloat( i );
			break;
		case 3:
			pitch = soundevent->GetFloat( i );
			break;
		case 4:
			timeofs = soundevent->GetFloat( i );
			break;
		case 5:
			fadetime = soundevent->GetFloat( i );
			break;
		case 6:
			flags = soundevent->GetInteger( i );
			break;
		default:
			break;
		}
	}

	RandomSound( name.c_str(), volume, channel, attenuation, pitch, timeofs, fadetime, flags );
}

void Entity::WeaponSound( Event *ev )
{
	BroadcastSound( ev, CHAN_WEAPON, EV_HeardWeapon, SOUND_WEAPON_RADIUS );
}

void Entity::MovementSound( Event *ev )
{
	static int movement_count = 0;
	//
	// movement sounds now happen very infrequently, unless this is a client
	//
	if ( isClient() || ( movement_count++ > 15 ) )
	{
		BroadcastSound( ev, CHAN_BODY, EV_HeardMovement, SOUND_MOVEMENT_RADIUS );
		if ( movement_count > 15 )
			movement_count = 0;
	}
}

void Entity::PainSound( Event *ev )
{
	BroadcastSound( ev, CHAN_VOICE, EV_HeardPain, SOUND_PAIN_RADIUS );
}

void Entity::DeathSound( Event *ev )
{
	BroadcastSound( ev, CHAN_VOICE, EV_HeardDeath, SOUND_DEATH_RADIUS );
}

void Entity::BreakingSound( Event *ev )
{
	BroadcastSound( ev, CHAN_AUTO, EV_HeardBreaking, SOUND_BREAKING_RADIUS );
}

void Entity::DoorSound( Event *ev )
{
	BroadcastSound( ev, CHAN_AUTO, EV_HeardDoor, SOUND_DOOR_RADIUS );
}

void Entity::MutantSound( Event *ev )
{
	BroadcastSound( ev, CHAN_VOICE, EV_HeardMutant, SOUND_MUTANT_RADIUS );
}

void Entity::VoiceSound( Event *ev )
{
	BroadcastSound( ev, CHAN_VOICE, EV_HeardVoice, SOUND_VOICE_RADIUS );
}

void Entity::MachineSound( Event *ev )
{
	BroadcastSound( ev, CHAN_AUTO, EV_HeardMachine, SOUND_MACHINE_RADIUS );
}

void Entity::RadioSound( Event *ev )
{
	BroadcastSound( ev, CHAN_VOICE, EV_HeardRadio, SOUND_RADIO_RADIUS );
}

void Entity::SpawnParticles( Event *ev )
{
	int i;
	Vector norm;
	int count;
	int lightstyle;

	norm = orientation[0];
	count = 4;
	lightstyle = 122;
	for ( i = 1; i <= ev->NumArgs(); i++ )
	{
		switch( i )
		{
		case 1:
			norm = ev->GetVector( 1 );
			break;
		case 2:
			count = ev->GetInteger( 2 );
			break;
		case 3:
			lightstyle = ev->GetInteger( 3 );
			break;
		case 4:
			flags = ev->GetInteger( 4 );
		default:
			break;
		}
	}
	Particles( worldorigin, norm, count, lightstyle, flags );
}

void Entity::Prethink( void )

{}

void Entity::Postthink( void )

{}

void Entity::SetWaterType( void )
{
	qboolean isinwater;

	watertype = gi.pointcontents( worldorigin.vec3() );
	isinwater = watertype & MASK_WATER;

	if ( isinwater )
	{
		waterlevel = 1;
	}
	else
	{
		waterlevel = 0;
	}
}

void Entity::DamageSkin( trace_t * trace,float damage )
{
	int group;

	group = trace->intersect.parentgroup;
	if ( !edict->s.groups[ group ] )
	{
		edict->s.groups[ group ]++;
	}
}

void Entity::Kill( Event *ev )
{
	health = 0;
	Damage( this, this, 10, worldorigin, vec_zero, vec_zero, 0, 0, MOD_SUICIDE, -1, -1, 1.0f );
}


void Entity::GroupModelEvent( Event *ev )
{
	const char * group_name;
	const char * token;
	int i, group_num, argnum, flags;
	int mask;
	int action;
	qboolean do_all;

#define FLAG_IGNORE  0
#define FLAG_SET     1
#define FLAG_CLEAR   2
#define FLAG_ADD     3

	do_all = false;
	// "group" is first
	group_name = ev->GetString( 1 );
	if ( str( group_name ) != str( "all" ) )
	{
		group_num = gi.Group_NameToNum( edict->s.modelindex, group_name );
		if (group_num < 0)
		{
			ev->Error( "group %s not found.", group_name );
		}
	}
	else
	{
		group_num = 0;
		do_all = true;
	}
	flags = 0;
	argnum = 2;
	for ( i = argnum; i <= ev->NumArgs(); i++ )
	{
		token = ev->GetString( i );
		action = 0;
		switch( token[0] )
		{
		case '+':
			action = FLAG_ADD;
			token++;
			break;
		case '-':
			action = FLAG_CLEAR;
			token++;
			break;
		default:
			action = FLAG_SET;
			break;
		}
		if (!strcmpi( token, "skin1" ))
		{
			mask = MDL_GROUP_SKINOFFSET_BIT0;
		}
		else if (!strcmpi ( token, "skin2" ))
		{
			mask = MDL_GROUP_SKINOFFSET_BIT1;
		}
		else if (!strcmpi ( token, "nodraw" ))
		{
			mask = MDL_GROUP_NODRAW;
		}
		else if (!strcmpi ( token, "envmapped" ))
		{
			mask = MDL_GROUP_ENVMAPPED_SILVER;
		}
		else if (!strcmpi ( token, "goldenvmapped" ))
		{
			mask = MDL_GROUP_ENVMAPPED_GOLD;
		}
		else if (!strcmpi ( token, "translucent33" ))
		{
			mask = MDL_GROUP_TRANSLUCENT_33;
		}
		else if (!strcmpi ( token, "translucent66" ))
		{
			mask = MDL_GROUP_TRANSLUCENT_66;
		}
		else if (!strcmpi ( token, "fullbright" ))
		{
			mask = MDL_GROUP_FULLBRIGHT;
		}
		else
		{
			ev->Error( "Unknown token %s.", token );
			action = FLAG_IGNORE;
		}
		for(; group_num < edict->s.numgroups; group_num++ )
		{
			switch (action)
			{
			case FLAG_SET:
				// clear out group
				edict->s.groups[ group_num ] = 0;
			case FLAG_ADD:
				edict->s.groups[ group_num ] |= mask;
				break;
			case FLAG_CLEAR:
				edict->s.groups[ group_num ] &= ~mask;
				break;
			case FLAG_IGNORE:
				break;
			}
			if ( !do_all )
				break;
		}
	}
}

void Entity::DialogEvent( Event * ev )
{
	str name;
	float volume;
	int channel;
	float attenuation;
	float pitch;
	float timeofs;
	float fadetime;
	int flags;
	int i;
	str icon_name;
	str dialog_text;

	if ( ( dialog->value == 1 ) || ( dialog->value == 3 ) )
	{
		icon_name = ev->GetString( 1 );
		dialog_text = ev->GetString( 2 );
		SendDialog( icon_name.c_str(), dialog_text.c_str() );
	}

	if ( ( dialog->value == 0 ) || ( dialog->value == 1 ) )
		return;

	//
	// set defaults
	//
	volume = 1.0f;
	channel = CHAN_DIALOG_SECONDARY | CHAN_NO_PHS_ADD;
	attenuation = ATTN_NORM;
	pitch = 1.0f;
	timeofs = 0;
	fadetime = 0;
	flags = SOUND_SYNCH;
	for ( i = 3; i <= ev->NumArgs(); i++ )
	{
		switch (i-3)
		{
		case 0:
			name = ev->GetString( i );
			break;
		case 1:
			volume = ev->GetFloat( i );
			break;
		case 2:
			channel = ev->GetInteger( i );
			break;
		case 3:
			attenuation = ev->GetFloat( i );
			break;
		case 4:
			pitch = ev->GetFloat( i );
			break;
		case 5:
			timeofs = ev->GetFloat( i );
			break;
		case 6:
			fadetime = ev->GetFloat( i );
			break;
		case 7:
			flags = ev->GetInteger( i );
			break;
		default:
			break;
		}
	}
	sound( name, volume, channel, attenuation, pitch, timeofs, fadetime, flags );
}

void Entity::AttachEvent( Event * ev )
{
	Entity * parent;
	const char * bone;
	int groupindex;
	int tri_num;
	vec3_t orient;

	parent = ev->GetEntity( 1 );
	bone = ev->GetString( 2 );

	if ( !parent )
		return;

	if ( gi.GetBoneInfo( parent->edict->s.modelindex, bone, &groupindex, &tri_num, orient ) )
	{
		attach( parent->entnum, groupindex, tri_num, Vector( orient ) );
	}
	else
	{
		warning( "AttachEvent", "GetBoneInfo failed for bone %s", bone );
	}
}

void Entity::AttachModelEvent( Event * ev )
{
	ThrowObject * tobj;
	const char * bone;
	int groupindex;
	int tri_num;
	vec3_t orient;
	str modelname;

	tobj = new ThrowObject;

	modelname = ev->GetString( 1 );
	bone = ev->GetString( 2 );
	if ( ev->NumArgs() > 2 )
	{
		tobj->SetTargetName( ev->GetString( 3 ) );
	}

	tobj->setModel( modelname );

	if ( gi.GetBoneInfo( edict->s.modelindex, bone, &groupindex, &tri_num, orient ) )
	{
		tobj->attach( this->entnum, groupindex, tri_num, Vector( orient ) );
	}
	else
	{
		warning( "AttachModelEvent", "GetBoneInfo failed for bone %s", bone );
	}
}

void Entity::DetachEvent( Event * ev )
{
	float trans[ 3 ][ 3 ];
	Entity * ent;
	vec3_t p1, p2;

	if ( edict->s.parent <= 0 )
	{
		return;
	}

	ent = ( Entity * )G_GetEntity( edict->s.parent );

	if ( gi.GetBoneTransform( ent->edict->s.modelindex, edict->s.bone.group_num, edict->s.bone.tri_num, edict->s.bone.orientation,
	                          ent->edict->s.anim, ent->edict->s.frame, ent->edict->s.scale, trans, p1 ) )
	{
		VectorAdd( p1, origin.vec3(), p1 );
		MatrixTransformVector( p1, ent->orientation, p2 );
		VectorAdd( p2, ent->worldorigin, p2 );
	}

	detach();

	setOrigin( p2 );
	worldorigin.copyTo( edict->s.old_origin );
}

void Entity::TakeDamageEvent( Event * ev )
{
	takedamage = DAMAGE_YES;
}

void Entity::NoDamageEvent( Event * ev )
{
	takedamage = DAMAGE_NO;
}

void Entity::SetSkinEvent( Event *ev )
{
	int num;

	num = gi.Skin_NumForName( edict->s.modelindex, ev->GetString( 1 ) );
	if ( num >= 0 )
	{
		edict->s.skinnum = num;
	}
	else
	{
		ev->Error( "Could not find %s as a skin name.\n", ev->GetString( 1 ) );
	}
}

void Entity::Lightoffset( Event *ev )
{
	edict->s.lightofs = ev->GetFloat( 1 );

	if ( edict->s.lightofs != 0 )
		edict->s.renderfx |= RF_LIGHTOFFSET;
	else
		edict->s.renderfx &= ~RF_LIGHTOFFSET;
}

void Entity::Gravity( Event *ev )
{
	gravity = ev->GetFloat( 1 );
}

void Entity::Minlight( Event *ev )
{

	if ( ev->GetInteger( 1 ) )
		edict->s.renderfx |= RF_MINLIGHT;
	else
		edict->s.renderfx &= ~RF_MINLIGHT;
}

void Entity::UseBoundingBoxEvent( Event *ev )
{
	edict->svflags |= SVF_USEBBOX;
}

void Entity::HurtEvent( Event *ev )
{
	Vector normal;
	float dmg;

	if ( ev->NumArgs() < 1 )
	{
		dmg = 50;
	}
	else
	{
		dmg = ev->GetFloat( 1 );
	}
	normal = Vector( orientation[ 0 ] );
	Damage( world, world, dmg, worldorigin, vec_zero, normal, dmg, 0, MOD_CRUSH, -1, -1, 1.0f );
}

void Entity::IfSkillEvent( Event *ev )
{
	float skilllevel;

	skilllevel = ev->GetFloat( 1 );

	if ( skill->value == skilllevel )
	{
		int argc;
		int numargs;
		Event       *event;
		int i;

		numargs = ev->NumArgs();
		argc = numargs - 2 + 1;

		event = new Event( ev->GetToken( 2 ) );

		for( i = 1; i < argc; i++ )
		{
			event->AddToken( ev->GetToken( 2 + i ) );
		}
		ProcessEvent( event );
	}
}

void Entity::Censor( Event *ev )
{
	Vector delta;
	float oldsize;
	float newsize;

	if ( !parentmode->value )
		return;

	oldsize = size.length();
	setSolidType( SOLID_NOT );
	setModel( "censored.def" );
	gi.CalculateBounds( edict->s.modelindex, 1, mins.vec3(), maxs.vec3() );
	delta = maxs - mins;
	newsize = delta.length();
	edict->s.scale = oldsize / newsize;
	mins *= edict->s.scale;
	maxs *= edict->s.scale;
	setSize( mins, maxs );
	setOrigin( origin );
}

//===============================================================
//### 2015 added stuff

// extended targeting stuff
EXPORT_FROM_DLL void Entity::SetTarget2( const char *text )
{
	if ( text )
	{
		target2 = text;
	}
	else
	{
		target2 = "";
	}
}

EXPORT_FROM_DLL void Entity::SetTarget3( const char *text )
{
	if ( text )
	{
		target3 = text;
	}
	else
	{
		target3 = "";
	}
}

EXPORT_FROM_DLL void Entity::SetTarget4( const char *text )
{
	if ( text )
	{
		target4 = text;
	}
	else
	{
		target4 = "";
	}
}

EXPORT_FROM_DLL void Entity::SetTargetName2( const char *text )
{
	if ( targetname2.length() && world )
	{
		world->RemoveTargetEntity( targetname2, this );
	}

	if ( text )
	{
		targetname2 = text;
	}
	else
	{
		targetname2 = "";
	}

	if ( targetname2.length() && world )
	{
		world->AddTargetEntity( targetname2, this );
	}
}

EXPORT_FROM_DLL void Entity::SetKillTarget2( const char *text )
{
	if ( text )
	{
		killtarget2 = text;
	}
	else
	{
		killtarget2 = "";
	}
}

EXPORT_FROM_DLL void Entity::SetTargetName2( Event *ev )
{
	SetTargetName2( ev->GetString( 1 ) );
}

EXPORT_FROM_DLL void Entity::SetTarget2( Event *ev )
{
	SetTarget2( ev->GetString( 1 ) );
}

EXPORT_FROM_DLL void Entity::SetTarget3( Event *ev )
{
	SetTarget3( ev->GetString( 1 ) );
}

EXPORT_FROM_DLL void Entity::SetTarget4( Event *ev )
{
	SetTarget4( ev->GetString( 1 ) );
}

EXPORT_FROM_DLL void Entity::SetKillTarget2( Event *ev )
{
	SetKillTarget2( ev->GetString( 1 ) );
}

EXPORT_FROM_DLL void Entity::SetGroupAlpha( Event *ev )
{
	float alpha;

	alpha = ev->GetFloat( 1 );

	if ( alpha > 1.0f )
	{
		alpha = 1.0f;
	}
	if ( alpha < 0 )
	{
		alpha = 0;
	}
	translucence = 1.0f - alpha;
	edict->s.alpha = alpha;
	edict->s.renderfx &= ~RF_TRANSLUCENT;

//   if ( ( translucence > 0 ) && ( translucence <= 1.0 ) )
//	{
//		edict->s.renderfx |= RF_TRANSLUCENT;
//	}
}
