text: /***
  *
  *	Copyright (c) 1996-2002, Valve LLC. All rights reserved.
  *	
  *	This product contains software technology licensed from Id 
  *	Software, Inc. ("Id Technology").  Id Technology (c) 1996 Id Software, Inc. 
  *	All Rights Reserved.
  *
  *   Use, distribution, and modification of this source code and/or resulting
  *   object code is restricted to non-commercial enhancements to products from
  *   Valve LLC.  All other use, distribution, or modification is prohibited
  *   without written permission from Valve LLC.
  *
  ****/
  /*
  
  ===== triggers.cpp ========================================================
  
    spawn and use functions for editor-placed triggers              
  
  */
  
  #include "extdll.h"
  #include "util.h"
  #include "cbase.h"
  #include "player.h"
  #include "saverestore.h"
  #include "trains.h"			// trigger_camera has train functionality
  #include "gamerules.h"
  
  #define	SF_TRIGGER_PUSH_START_OFF	2//spawnflag that makes trigger_push spawn turned OFF
  #define SF_TRIGGER_HURT_TARGETONCE	1// Only fire hurt target once
  #define	SF_TRIGGER_HURT_START_OFF	2//spawnflag that makes trigger_push spawn turned OFF
  #define	SF_TRIGGER_HURT_NO_CLIENTS	8//spawnflag that makes trigger_push spawn turned OFF
  #define SF_TRIGGER_HURT_CLIENTONLYFIRE	16// trigger hurt will only fire its target if it is hurting a client
  #define SF_TRIGGER_HURT_CLIENTONLYTOUCH 32// only clients may touch this trigger.
  
  extern DLL_GLOBAL BOOL		g_fGameOver;
  
  extern Vector g_vecTeleMins[ MAX_TELES ];
  extern Vector g_vecTeleMaxs[ MAX_TELES ];
  extern int g_iTeleNum;
  
  extern void SetMovedir(entvars_t* pev);
  extern Vector VecBModelOrigin( entvars_t* pevBModel );
  
  extern unsigned short g_sTeleport;
  
  class CFrictionModifier : public CBaseEntity
  {
  public:
  	void		Spawn( void );
  	void		KeyValue( KeyValueData *pkvd );
  	void EXPORT	ChangeFriction( CBaseEntity *pOther );
  	virtual int		Save( CSave &save );
  	virtual int		Restore( CRestore &restore );
  
  	virtual int	ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
  
  	static	TYPEDESCRIPTION m_SaveData[];
  
  	float		m_frictionFraction;		// Sorry, couldn't resist this name :)
  };
  
  LINK_ENTITY_TO_CLASS( func_friction, CFrictionModifier );
  
  // Global Savedata for changelevel friction modifier
  TYPEDESCRIPTION	CFrictionModifier::m_SaveData[] = 
  {
  	DEFINE_FIELD( CFrictionModifier, m_frictionFraction, FIELD_FLOAT ),
  };
  
  IMPLEMENT_SAVERESTORE(CFrictionModifier,CBaseEntity);
  
  
  // Modify an entity's friction
  void CFrictionModifier :: Spawn( void )
  {
  	pev->solid = SOLID_TRIGGER;
  	SET_MODEL(ENT(pev), STRING(pev->model));    // set size and link into world
  	pev->movetype = MOVETYPE_NONE;
  	SetTouch( ChangeFriction );
  }
  
  
  // Sets toucher's friction to m_frictionFraction (1.0 = normal friction)
  void CFrictionModifier :: ChangeFriction( CBaseEntity *pOther )
  {
  	if ( pOther->pev->movetype != MOVETYPE_BOUNCEMISSILE && pOther->pev->movetype != MOVETYPE_BOUNCE )
  		pOther->pev->friction = m_frictionFraction;
  }
  
  
  
  // Sets toucher's friction to m_frictionFraction (1.0 = normal friction)
  void CFrictionModifier :: KeyValue( KeyValueData *pkvd )
  {
  	if (FStrEq(pkvd->szKeyName, "modifier"))
  	{
  		m_frictionFraction = atof(pkvd->szValue) / 100.0;
  		pkvd->fHandled = TRUE;
  	}
  	else
  		CBaseEntity::KeyValue( pkvd );
  }
  
  
  // This trigger will fire when the level spawns (or respawns if not fire once)
  // It will check a global state before firing.  It supports delay and killtargets
  
  #define SF_AUTO_FIREONCE		0x0001
  
  class CAutoTrigger : public CBaseDelay
  {
  public:
  	void KeyValue( KeyValueData *pkvd );
  	void Spawn( void );
  	void Precache( void );
  	void Think( void );
  
  	int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
  	virtual int		Save( CSave &save );
  	virtual int		Restore( CRestore &restore );
  
  	static	TYPEDESCRIPTION m_SaveData[];
  
  private:
  	int			m_globalstate;
  	USE_TYPE	triggerType;
  };
  LINK_ENTITY_TO_CLASS( trigger_auto, CAutoTrigger );
  
  TYPEDESCRIPTION	CAutoTrigger::m_SaveData[] = 
  {
  	DEFINE_FIELD( CAutoTrigger, m_globalstate, FIELD_STRING ),
  	DEFINE_FIELD( CAutoTrigger, triggerType, FIELD_INTEGER ),
  };
  
  IMPLEMENT_SAVERESTORE(CAutoTrigger,CBaseDelay);
  
  void CAutoTrigger::KeyValue( KeyValueData *pkvd )
  {
  	if (FStrEq(pkvd->szKeyName, "globalstate"))
  	{
  		m_globalstate = ALLOC_STRING( pkvd->szValue );
  		pkvd->fHandled = TRUE;
  	}
  	else if (FStrEq(pkvd->szKeyName, "triggerstate"))
  	{
  		int type = atoi( pkvd->szValue );
  		switch( type )
  		{
  		case 0:
  			triggerType = USE_OFF;
  			break;
  		case 2:
  			triggerType = USE_TOGGLE;
  			break;
  		default:
  			triggerType = USE_ON;
  			break;
  		}
  		pkvd->fHandled = TRUE;
  	}
  	else
  		CBaseDelay::KeyValue( pkvd );
  }
  
  
  void CAutoTrigger::Spawn( void )
  {
  	Precache();
  }
  
  
  void CAutoTrigger::Precache( void )
  {
  	pev->nextthink = gpGlobals->time + 0.1;
  }
  
  
  void CAutoTrigger::Think( void )
  {
  	if ( !m_globalstate || gGlobalState.EntityGetState( m_globalstate ) == GLOBAL_ON )
  	{
  		SUB_UseTargets( this, triggerType, 0 );
  		if ( pev->spawnflags & SF_AUTO_FIREONCE )
  			UTIL_Remove( this );
  	}
  }
  
  
  
  #define SF_RELAY_FIREONCE		0x0001
  
  class CTriggerRelay : public CBaseDelay
  {
  public:
  	void KeyValue( KeyValueData *pkvd );
  	void Spawn( void );
  	void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
  
  	int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
  	virtual int		Save( CSave &save );
  	virtual int		Restore( CRestore &restore );
  
  	static	TYPEDESCRIPTION m_SaveData[];
  
  private:
  	USE_TYPE	triggerType;
  };
  LINK_ENTITY_TO_CLASS( trigger_relay, CTriggerRelay );
  
  TYPEDESCRIPTION	CTriggerRelay::m_SaveData[] = 
  {
  	DEFINE_FIELD( CTriggerRelay, triggerType, FIELD_INTEGER ),
  };
  
  IMPLEMENT_SAVERESTORE(CTriggerRelay,CBaseDelay);
  
  void CTriggerRelay::KeyValue( KeyValueData *pkvd )
  {
  	if (FStrEq(pkvd->szKeyName, "triggerstate"))
  	{
  		int type = atoi( pkvd->szValue );
  		switch( type )
  		{
  		case 0:
  			triggerType = USE_OFF;
  			break;
  		case 2:
  			triggerType = USE_TOGGLE;
  			break;
  		default:
  			triggerType = USE_ON;
  			break;
  		}
  		pkvd->fHandled = TRUE;
  	}
  	else
  		CBaseDelay::KeyValue( pkvd );
  }
  
  
  void CTriggerRelay::Spawn( void )
  {
  }
  
  
  
  
  void CTriggerRelay::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  {
  	SUB_UseTargets( this, triggerType, 0 );
  	if ( pev->spawnflags & SF_RELAY_FIREONCE )
  		UTIL_Remove( this );
  }
  
  
  //**********************************************************
  // The Multimanager Entity - when fired, will fire up to 16 targets 
  // at specified times.
  // FLAG:		THREAD (create clones when triggered)
  // FLAG:		CLONE (this is a clone for a threaded execution)
  
  #define SF_MULTIMAN_CLONE		0x80000000
  #define SF_MULTIMAN_THREAD		0x00000001
  
  class CMultiManager : public CBaseToggle
  {
  public:
  	void KeyValue( KeyValueData *pkvd );
  	void Spawn ( void );
  	void EXPORT ManagerThink ( void );
  	void EXPORT ManagerUse   ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
  
  #if _DEBUG
  	void EXPORT ManagerReport( void );
  #endif
  
  	BOOL		HasTarget( string_t targetname );
  	
  	int ObjectCaps( void ) { return CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
  
  	virtual int		Save( CSave &save );
  	virtual int		Restore( CRestore &restore );
  
  	static	TYPEDESCRIPTION m_SaveData[];
  
  	int		m_cTargets;	// the total number of targets in this manager's fire list.
  	int		m_index;	// Current target
  	float	m_startTime;// Time we started firing
  	int		m_iTargetName	[ MAX_MULTI_TARGETS ];// list if indexes into global string array
  	float	m_flTargetDelay [ MAX_MULTI_TARGETS ];// delay (in seconds) from time of manager fire to target fire
  private:
  	inline BOOL IsClone( void ) { return (pev->spawnflags & SF_MULTIMAN_CLONE) ? TRUE : FALSE; }
  	inline BOOL ShouldClone( void ) 
  	{ 
  		if ( IsClone() )
  			return FALSE;
  
  		return (pev->spawnflags & SF_MULTIMAN_THREAD) ? TRUE : FALSE; 
  	}
  
  	CMultiManager *Clone( void );
  };
  LINK_ENTITY_TO_CLASS( multi_manager, CMultiManager );
  
  // Global Savedata for multi_manager
  TYPEDESCRIPTION	CMultiManager::m_SaveData[] = 
  {
  	DEFINE_FIELD( CMultiManager, m_cTargets, FIELD_INTEGER ),
  	DEFINE_FIELD( CMultiManager, m_index, FIELD_INTEGER ),
  	DEFINE_FIELD( CMultiManager, m_startTime, FIELD_TIME ),
  	DEFINE_ARRAY( CMultiManager, m_iTargetName, FIELD_STRING, MAX_MULTI_TARGETS ),
  	DEFINE_ARRAY( CMultiManager, m_flTargetDelay, FIELD_FLOAT, MAX_MULTI_TARGETS ),
  };
  
  IMPLEMENT_SAVERESTORE(CMultiManager,CBaseToggle);
  
  void CMultiManager :: KeyValue( KeyValueData *pkvd )
  {
  	// UNDONE: Maybe this should do something like this:
  	//CBaseToggle::KeyValue( pkvd );
  	// if ( !pkvd->fHandled )
  	// ... etc.
  
  	if (FStrEq(pkvd->szKeyName, "wait"))
  	{
  		m_flWait = atof(pkvd->szValue);
  		pkvd->fHandled = TRUE;
  	}
  	else // add this field to the target list
  	{
  		// this assumes that additional fields are targetnames and their values are delay values.
  		if ( m_cTargets < MAX_MULTI_TARGETS )
  		{
  			char tmp[128];
  
  			UTIL_StripToken( pkvd->szKeyName, tmp );
  			m_iTargetName [ m_cTargets ] = ALLOC_STRING( tmp );
  			m_flTargetDelay [ m_cTargets ] = atof (pkvd->szValue);
  			m_cTargets++;
  			pkvd->fHandled = TRUE;
  		}
  	}
  }
  
  
  void CMultiManager :: Spawn( void )
  {
  	pev->solid = SOLID_NOT;
  	SetUse ( ManagerUse );
  	SetThink ( ManagerThink);
  
  	// Sort targets
  	// Quick and dirty bubble sort
  	int swapped = 1;
  
  	while ( swapped )
  	{
  		swapped = 0;
  		for ( int i = 1; i < m_cTargets; i++ )
  		{
  			if ( m_flTargetDelay[i] < m_flTargetDelay[i-1] )
  			{
  				// Swap out of order elements
  				int name = m_iTargetName[i];
  				float delay = m_flTargetDelay[i];
  				m_iTargetName[i] = m_iTargetName[i-1];
  				m_flTargetDelay[i] = m_flTargetDelay[i-1];
  				m_iTargetName[i-1] = name;
  				m_flTargetDelay[i-1] = delay;
  				swapped = 1;
  			}
  		}
  	}
  }
  
  
  BOOL CMultiManager::HasTarget( string_t targetname )
  { 
  	for ( int i = 0; i < m_cTargets; i++ )
  		if ( FStrEq(STRING(targetname), STRING(m_iTargetName[i])) )
  			return TRUE;
  	
  	return FALSE;
  }
  
  
  // Designers were using this to fire targets that may or may not exist -- 
  // so I changed it to use the standard target fire code, made it a little simpler.
  void CMultiManager :: ManagerThink ( void )
  {
  	float	time;
  
  	time = gpGlobals->time - m_startTime;
  	while ( m_index < m_cTargets && m_flTargetDelay[ m_index ] <= time )
  	{
  		FireTargets( STRING( m_iTargetName[ m_index ] ), m_hActivator, this, USE_TOGGLE, 0 );
  		m_index++;
  	}
  
  	if ( m_index >= m_cTargets )// have we fired all targets?
  	{
  		SetThink( NULL );
  		if ( IsClone() )
  		{
  			UTIL_Remove( this );
  			return;
  		}
  		SetUse ( ManagerUse );// allow manager re-use 
  	}
  	else
  		pev->nextthink = m_startTime + m_flTargetDelay[ m_index ];
  }
  
  CMultiManager *CMultiManager::Clone( void )
  {
  	CMultiManager *pMulti = GetClassPtr( (CMultiManager *)NULL );
  
  	edict_t *pEdict = pMulti->pev->pContainingEntity;
  	memcpy( pMulti->pev, pev, sizeof(*pev) );
  	pMulti->pev->pContainingEntity = pEdict;
  
  	pMulti->pev->spawnflags |= SF_MULTIMAN_CLONE;
  	pMulti->m_cTargets = m_cTargets;
  	memcpy( pMulti->m_iTargetName, m_iTargetName, sizeof( m_iTargetName ) );
  	memcpy( pMulti->m_flTargetDelay, m_flTargetDelay, sizeof( m_flTargetDelay ) );
  
  	return pMulti;
  }
  
  
  // The USE function builds the time table and starts the entity thinking.
  void CMultiManager :: ManagerUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  {
  	// In multiplayer games, clone the MM and execute in the clone (like a thread)
  	// to allow multiple players to trigger the same multimanager
  	if ( ShouldClone() )
  	{
  		CMultiManager *pClone = Clone();
  		pClone->ManagerUse( pActivator, pCaller, useType, value );
  		return;
  	}
  
  	m_hActivator = pActivator;
  	m_index = 0;
  	m_startTime = gpGlobals->time;
  
  	SetUse( NULL );// disable use until all targets have fired
  
  	SetThink ( ManagerThink );
  	pev->nextthink = gpGlobals->time;
  }
  
  #if _DEBUG
  void CMultiManager :: ManagerReport ( void )
  {
  	int	cIndex;
  
  	for ( cIndex = 0 ; cIndex < m_cTargets ; cIndex++ )
  	{
  		ALERT ( at_console, "%s %f\n", STRING(m_iTargetName[cIndex]), m_flTargetDelay[cIndex] );
  	}
  }
  #endif
  
  //***********************************************************
  
  
  //
  // Render parameters trigger
  //
  // This entity will copy its render parameters (renderfx, rendermode, rendercolor, renderamt)
  // to its targets when triggered.
  //
  
  
  // Flags to indicate masking off various render parameters that are normally copied to the targets
  #define SF_RENDER_MASKFX	(1<<0)
  #define SF_RENDER_MASKAMT	(1<<1)
  #define SF_RENDER_MASKMODE	(1<<2)
  #define SF_RENDER_MASKCOLOR	(1<<3)
  
  class CRenderFxManager : public CBaseEntity
  {
  public:
  	void Spawn( void );
  	void Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
  };
  
  LINK_ENTITY_TO_CLASS( env_render, CRenderFxManager );
  
  
  void CRenderFxManager :: Spawn ( void )
  {
  	pev->solid = SOLID_NOT;
  }
  
  void CRenderFxManager :: Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  {
  	if (!FStringNull(pev->target))
  	{
  		edict_t* pentTarget	= NULL;
  		while ( 1 )
  		{
  			pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->target));
  			if (FNullEnt(pentTarget))
  				break;
  
  			entvars_t *pevTarget = VARS( pentTarget );
  			if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKFX ) )
  				pevTarget->renderfx = pev->renderfx;
  			if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKAMT ) )
  				pevTarget->renderamt = pev->renderamt;
  			if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKMODE ) )
  				pevTarget->rendermode = pev->rendermode;
  			if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKCOLOR ) )
  				pevTarget->rendercolor = pev->rendercolor;
  		}
  	}
  }
  
  
  
  class CBaseTrigger : public CBaseToggle
  {
  public:
  	void EXPORT TeleportTouch ( CBaseEntity *pOther );
  	void KeyValue( KeyValueData *pkvd );
  	void EXPORT MultiTouch( CBaseEntity *pOther );
  	void EXPORT HurtTouch ( CBaseEntity *pOther );
  	void EXPORT EnvTouch ( CBaseEntity *pOther );
  	void EXPORT CDAudioTouch ( CBaseEntity *pOther );
  	void ActivateMultiTrigger( CBaseEntity *pActivator );
  	void EXPORT MultiWaitOver( void );
  	void EXPORT CounterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
  	void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
  	void InitTrigger( void );
  
  	virtual int	ObjectCaps( void ) { return CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
  };
  
  LINK_ENTITY_TO_CLASS( trigger, CBaseTrigger );
  
  /*
  ================
  InitTrigger
  ================
  */
  void CBaseTrigger::InitTrigger( )
  {
  	// trigger angles are used for one-way touches.  An angle of 0 is assumed
  	// to mean no restrictions, so use a yaw of 360 instead.
  	if (pev->angles != g_vecZero)
  		SetMovedir(pev);
  	pev->solid = SOLID_TRIGGER;
  	pev->movetype = MOVETYPE_NONE;
  	SET_MODEL(ENT(pev), STRING(pev->model));    // set size and link into world
  //	if ( CVAR_GET_FLOAT("showtriggers") == 0 )
  		SetBits( pev->effects, EF_NODRAW );
  }
  
  
  //
  // Cache user-entity-field values until spawn is called.
  //
  
  void CBaseTrigger :: KeyValue( KeyValueData *pkvd )
  {
  	if (FStrEq(pkvd->szKeyName, "damage"))
  	{
  		pev->dmg = atof(pkvd->szValue);
  		pkvd->fHandled = TRUE;
  	}
  	else if (FStrEq(pkvd->szKeyName, "count"))
  	{
  		m_cTriggersLeft = (int) atof(pkvd->szValue);
  		pkvd->fHandled = TRUE;
  	}
  	else if (FStrEq(pkvd->szKeyName, "damagetype"))
  	{
  		m_bitsDamageInflict = atoi(pkvd->szValue);
  		pkvd->fHandled = TRUE;
  	}
  	else
  		CBaseToggle::KeyValue( pkvd );
  }
  
  class CTriggerHurt : public CBaseTrigger
  {
  public:
  	void Spawn( void );
  	void EXPORT RadiationThink( void );
  };
  
  LINK_ENTITY_TO_CLASS( trigger_hurt, CTriggerHurt );
  
  class CTriggerEnvHurt : public CBaseTrigger
  {
  public:
  	void Spawn( void );
  };
  
  LINK_ENTITY_TO_CLASS( trigger_env_hurt, CTriggerEnvHurt );
  
  
  //
  // trigger_monsterjump
  //
  class CTriggerMonsterJump : public CBaseTrigger
  {
  public:
  	void Spawn( void );
  	void Touch( CBaseEntity *pOther );
  	void Think( void );
  };
  
  LINK_ENTITY_TO_CLASS( trigger_monsterjump, CTriggerMonsterJump );
  
  
  void CTriggerMonsterJump :: Spawn ( void )
  {
  	SetMovedir ( pev );
  	
  	InitTrigger ();
  
  	pev->nextthink = 0;
  	pev->speed = 200;
  	m_flHeight = 150;
  
  	if ( !FStringNull ( pev->targetname ) )
  	{// if targetted, spawn turned off
  		pev->solid = SOLID_NOT;
  		UTIL_SetOrigin( pev, pev->origin ); // Unlink from trigger list
  		SetUse( ToggleUse );
  	}
  }
  
  
  void CTriggerMonsterJump :: Think( void )
  {
  	pev->solid = SOLID_NOT;// kill the trigger for now !!!UNDONE
  	UTIL_SetOrigin( pev, pev->origin ); // Unlink from trigger list
  	SetThink( NULL );
  }
  
  void CTriggerMonsterJump :: Touch( CBaseEntity *pOther )
  {
  	entvars_t *pevOther = pOther->pev;
  
  	if ( !FBitSet ( pevOther->flags , FL_MONSTER ) ) 
  	{// touched by a non-monster.
  		return;
  	}
  
  	pevOther->origin.z += 1;
  	
  	if ( FBitSet ( pevOther->flags, FL_ONGROUND ) ) 
  	{// clear the onground so physics don't bitch
  		pevOther->flags &= ~FL_ONGROUND;
  	}
  
  	// toss the monster!
  	pevOther->velocity = pev->movedir * pev->speed;
  	pevOther->velocity.z += m_flHeight;
  	pev->nextthink = gpGlobals->time;
  }
  
  
  //=====================================
  //
  // trigger_cdaudio - starts/stops cd audio tracks
  //
  class CTriggerCDAudio : public CBaseTrigger
  {
  public:
  	void Spawn( void );
  
  	virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
  	void PlayTrack( void );
  	void Touch ( CBaseEntity *pOther );
  };
  
  LINK_ENTITY_TO_CLASS( trigger_cdaudio, CTriggerCDAudio );
  
  //
  // Changes tracks or stops CD when player touches
  //
  // !!!HACK - overloaded HEALTH to avoid adding new field
  void CTriggerCDAudio :: Touch ( CBaseEntity *pOther )
  {
  	if ( !pOther->IsPlayer() )
  	{// only clients may trigger these events
  		return;
  	}
  
  	PlayTrack();
  }
  
  void CTriggerCDAudio :: Spawn( void )
  {
  	InitTrigger();
  }
  
  void CTriggerCDAudio::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  {
  	PlayTrack();
  }
  
  void PlayCDTrack( int iTrack )
  {
  	edict_t *pClient;
  	
  	// manually find the single player. 
  	pClient = g_engfuncs.pfnPEntityOfEntIndex( 1 );
  	
  	// Can't play if the client is not connected!
  	if ( !pClient )
  		return;
  
  	if ( iTrack < -1 || iTrack > 30 )
  	{
  		ALERT ( at_console, "TriggerCDAudio - Track %d out of range\n" );
  		return;
  	}
  
  	if ( iTrack == -1 )
  	{
  		CLIENT_COMMAND ( pClient, "cd pause\n");
  	}
  	else
  	{
  		char string [ 64 ];
  
  		sprintf( string, "cd play %3d\n", iTrack );
  		CLIENT_COMMAND ( pClient, string);
  	}
  }
  
  
  // only plays for ONE client, so only use in single play!
  void CTriggerCDAudio :: PlayTrack( void )
  {
  	//PlayCDTrack( (int)pev->health );
  	
  	SetTouch( NULL );
  	UTIL_Remove( this );
  }
  
  
  // This plays a CD track when fired or when the player enters it's radius
  class CTargetCDAudio : public CPointEntity
  {
  public:
  	void			Spawn( void );
  	void			KeyValue( KeyValueData *pkvd );
  
  	virtual void	Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
  	void			Think( void );
  	void			Play( void );
  };
  
  LINK_ENTITY_TO_CLASS( target_cdaudio, CTargetCDAudio );
  
  void CTargetCDAudio :: KeyValue( KeyValueData *pkvd )
  {
  	if (FStrEq(pkvd->szKeyName, "radius"))
  	{
  		pev->scale = atof(pkvd->szValue);
  		pkvd->fHandled = TRUE;
  	}
  	else
  		CPointEntity::KeyValue( pkvd );
  }
  
  void CTargetCDAudio :: Spawn( void )
  {
  	pev->solid = SOLID_NOT;
  	pev->movetype = MOVETYPE_NONE;
  
  	if ( pev->scale > 0 )
  		pev->nextthink = gpGlobals->time + 1.0;
  }
  
  void CTargetCDAudio::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  {
  	Play();
  }
  
  // only plays for ONE client, so only use in single play!
  void CTargetCDAudio::Think( void )
  {
  	edict_t *pClient;
  	
  	// manually find the single player. 
  	pClient = g_engfuncs.pfnPEntityOfEntIndex( 1 );
  	
  	// Can't play if the client is not connected!
  	if ( !pClient )
  		return;
  	
  	pev->nextthink = gpGlobals->time + 0.5;
  
  	if ( (pClient->v.origin - pev->origin).Length() <= pev->scale )
  		Play();
  
  }
  
  void CTargetCDAudio::Play( void ) 
  { 
  	//PlayCDTrack( (int)pev->health );
  	UTIL_Remove(this); 
  }
  
  //=====================================
  
  /*
  ==========================
  CTriggerEnvHurt
  Used to represent Slime or Lava
  ==========================
  */
  
  void CTriggerEnvHurt :: Spawn( void )
  {
  	InitTrigger();
  	SetTouch ( EnvTouch );
  
  	if ( FBitSet (pev->spawnflags, SF_TRIGGER_HURT_START_OFF) )// if flagged to Start Turned Off, make trigger nonsolid.
  		pev->solid = SOLID_NOT;
  
  	UTIL_SetOrigin( pev, pev->origin );		// Link into the list
  }
  
  // When touched, a hurt trigger does DMG points of damage each half-second
  void CBaseTrigger :: EnvTouch ( CBaseEntity *pOther )
  {
  	float fldmg;
  
  	if ( !pOther->pev->takedamage )
  		return;
  
  	if ( (pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYTOUCH) && !pOther->IsPlayer() )
  	{
  		// this trigger is only allowed to touch clients, and this ain't a client.
  		return;
  	}
  
  	if ( (pev->spawnflags & SF_TRIGGER_HURT_NO_CLIENTS) && pOther->IsPlayer() )
  		return;
  
  	// HACKHACK -- In multiplayer, players touch this based on packet receipt.
  	// So the players who send packets later aren't always hurt.  Keep track of
  	// how much time has passed and whether or not you've touched that player
  	if ( g_pGameRules->IsMultiplayer() )
  	{
  		if ( pev->dmgtime > gpGlobals->time )
  		{
  			if ( gpGlobals->time != pev->pain_finished )
  			{// too early to hurt again, and not same frame with a different entity
  				if ( pOther->IsPlayer() )
  				{
  					int playerMask = 1 << (pOther->entindex() - 1);
  
  					// If I've already touched this player (this time), then bail out
  					if ( pev->impulse & playerMask )
  						return;
  
  					// Mark this player as touched
  					// BUGBUG - There can be only 32 players!
  					pev->impulse |= playerMask;
  				}
  				else
  				{
  					return;
  				}
  			}
  		}
  		else
  		{
  			// New clock, "un-touch" all players
  			pev->impulse = 0;
  			if ( pOther->IsPlayer() )
  			{
  				int playerMask = 1 << (pOther->entindex() - 1);
  
  				// Mark this player as touched
  				// BUGBUG - There can be only 32 players!
  				pev->impulse |= playerMask;
  			}
  		}
  	}
  	else	// Original code -- single player
  	{
  		if ( pev->dmgtime > gpGlobals->time && gpGlobals->time != pev->pain_finished )
  		{// too early to hurt again, and not same frame with a different entity
  			return;
  		}
  	}
  
  
  	//We are in slime
  	if ( m_bitsDamageInflict & DMG_ACID )
  	{
  		pev->dmg = 4; //Default damage of 4
  		fldmg = (float)( pev->dmg * pOther->pev->waterlevel ); // pev->damage plus our current waterlevel
  
  		pev->dmgtime = gpGlobals->time + 1.0; //Next damage in 1 second
  	}
  	//We are in lava
  	else if ( m_bitsDamageInflict & DMG_BURN )
  	{
  		pev->dmg = 10; //Default damage of 10
  		fldmg = (float)( pev->dmg * pOther->pev->waterlevel );	// pev->damage plus our current waterlevel
  		
  		if ( pOther->IsPlayer() )
  		{
  			if (  ( (CBasePlayer *)pOther )->m_iQuakeItems & IT_SUIT) // Wearing the suit slows down the next damage time
  				pev->dmgtime = gpGlobals->time + 1.0;
  			else
  				pev->dmgtime = gpGlobals->time + 0.2;
  		}
  		else
  			pev->dmgtime = gpGlobals->time + 0.2;
  	}
  
  	if ( fldmg < 0 )
  		pOther->TakeHealth( -fldmg, m_bitsDamageInflict );
  	else
  		pOther->TakeDamage( pev, pev, fldmg, m_bitsDamageInflict );
  
  	// Store pain time so we can get all of the other entities on this frame
  	pev->pain_finished = gpGlobals->time;
    
  	
  	if ( pev->target )
  	{
  		// trigger has a target it wants to fire. 
  		if ( pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYFIRE )
  		{
  			// if the toucher isn't a client, don't fire the target!
  			if ( !pOther->IsPlayer() )
  			{
  				return;
  			}
  		}
  
  		SUB_UseTargets( pOther, USE_TOGGLE, 0 );
  		if ( pev->spawnflags & SF_TRIGGER_HURT_TARGETONCE )
  			pev->target = 0;
  	}
  }
  
  // trigger_hurt - hurts anything that touches it. if the trigger has a targetname, firing it will toggle state
  //
  //int gfToggleState = 0; // used to determine when all radiation trigger hurts have called 'RadiationThink'
  void CTriggerHurt :: Spawn( void )
  {
  	InitTrigger();
  	SetTouch ( HurtTouch );
  
  	if ( !FStringNull ( pev->targetname ) )
  	{
  		SetUse ( ToggleUse );
  	}
  	else
  	{
  		SetUse ( NULL );
  	}
  
  	if (m_bitsDamageInflict & DMG_RADIATION)
  	{
  		SetThink ( RadiationThink );
  		pev->nextthink = gpGlobals->time + RANDOM_FLOAT(0.0, 0.5); 
  	}
  
  	if ( FBitSet (pev->spawnflags, SF_TRIGGER_HURT_START_OFF) )// if flagged to Start Turned Off, make trigger nonsolid.
  		pev->solid = SOLID_NOT;
  
  	UTIL_SetOrigin( pev, pev->origin );		// Link into the list
  }
  
  // trigger hurt that causes radiation will do a radius
  // check and set the player's geiger counter level
  // according to distance from center of trigger
  
  void CTriggerHurt :: RadiationThink( void )
  {
  
  	edict_t *pentPlayer;
  	CBasePlayer *pPlayer = NULL;
  	float flRange;
  	entvars_t *pevTarget;
  	Vector vecSpot1;
  	Vector vecSpot2;
  	Vector vecRange;
  	Vector origin;
  	Vector view_ofs;
  
  	// check to see if a player is in pvs
  	// if not, continue	
  
  	// set origin to center of trigger so that this check works
  	origin = pev->origin;
  	view_ofs = pev->view_ofs;
  
  	pev->origin = (pev->absmin + pev->absmax) * 0.5;
  	pev->view_ofs = pev->view_ofs * 0.0;
  
  	pentPlayer = FIND_CLIENT_IN_PVS(edict());
  
  	pev->origin = origin;
  	pev->view_ofs = view_ofs;
  
  	// reset origin
  
  	if (!FNullEnt(pentPlayer))
  	{
   
  		pPlayer = GetClassPtr( (CBasePlayer *)VARS(pentPlayer));
  
  		pevTarget = VARS(pentPlayer);
  
  		// get range to player;
  
  		vecSpot1 = (pev->absmin + pev->absmax) * 0.5;
  		vecSpot2 = (pevTarget->absmin + pevTarget->absmax) * 0.5;
  		
  		vecRange = vecSpot1 - vecSpot2;
  		flRange = vecRange.Length();
  
  		// if player's current geiger counter range is larger
  		// than range to this trigger hurt, reset player's
  		// geiger counter range 
  
  		if (pPlayer->m_flgeigerRange >= flRange)
  			pPlayer->m_flgeigerRange = flRange;
  	}
  
  	pev->nextthink = gpGlobals->time + 0.25;
  }
  
  //
  // ToggleUse - If this is the USE function for a trigger, its state will toggle every time it's fired
  //
  void CBaseTrigger :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  {
  	if (pev->solid == SOLID_NOT)
  	{// if the trigger is off, turn it on
  		pev->solid = SOLID_TRIGGER;
  		
  		// Force retouch
  		gpGlobals->force_retouch++;
  	}
  	else
  	{// turn the trigger off
  		pev->solid = SOLID_NOT;
  	}
  	UTIL_SetOrigin( pev, pev->origin );
  }
  
  // When touched, a hurt trigger does DMG points of damage each half-second
  void CBaseTrigger :: HurtTouch ( CBaseEntity *pOther )
  {
  	float fldmg;
  
  	if ( !pOther->pev->takedamage )
  		return;
  
  	if ( (pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYTOUCH) && !pOther->IsPlayer() )
  	{
  		// this trigger is only allowed to touch clients, and this ain't a client.
  		return;
  	}
  
  	if ( (pev->spawnflags & SF_TRIGGER_HURT_NO_CLIENTS) && pOther->IsPlayer() )
  		return;
  
  	// HACKHACK -- In multiplayer, players touch this based on packet receipt.
  	// So the players who send packets later aren't always hurt.  Keep track of
  	// how much time has passed and whether or not you've touched that player
  	if ( g_pGameRules->IsMultiplayer() )
  	{
  		if ( pev->dmgtime > gpGlobals->time )
  		{
  			if ( gpGlobals->time != pev->pain_finished )
  			{// too early to hurt again, and not same frame with a different entity
  				if ( pOther->IsPlayer() )
  				{
  					int playerMask = 1 << (pOther->entindex() - 1);
  
  					// If I've already touched this player (this time), then bail out
  					if ( pev->impulse & playerMask )
  						return;
  
  					// Mark this player as touched
  					// BUGBUG - There can be only 32 players!
  					pev->impulse |= playerMask;
  				}
  				else
  				{
  					return;
  				}
  			}
  		}
  		else
  		{
  			// New clock, "un-touch" all players
  			pev->impulse = 0;
  			if ( pOther->IsPlayer() )
  			{
  				int playerMask = 1 << (pOther->entindex() - 1);
  
  				// Mark this player as touched
  				// BUGBUG - There can be only 32 players!
  				pev->impulse |= playerMask;
  			}
  		}
  	}
  	else	// Original code -- single player
  	{
  		if ( pev->dmgtime > gpGlobals->time && gpGlobals->time != pev->pain_finished )
  		{// too early to hurt again, and not same frame with a different entity
  			return;
  		}
  	}
  
  
  
  	// If this is time_based damage (poison, radiation), override the pev->dmg with a 
  	// default for the given damage type.  Monsters only take time-based damage
  	// while touching the trigger.  Player continues taking damage for a while after
  	// leaving the trigger
  
  	fldmg = pev->dmg * 0.5;	// 0.5 seconds worth of damage, pev->dmg is damage/second
  
  
  	// JAY: Cut this because it wasn't fully realized.  Damage is simpler now.
  #if 0
  	switch (m_bitsDamageInflict)
  	{
  	default: break;
  	case DMG_POISON:		fldmg = POISON_DAMAGE/4; break;
  	case DMG_NERVEGAS:		fldmg = NERVEGAS_DAMAGE/4; break;
  	case DMG_RADIATION:		fldmg = RADIATION_DAMAGE/4; break;
  	case DMG_PARALYZE:		fldmg = PARALYZE_DAMAGE/4; break; // UNDONE: cut this? should slow movement to 50%
  	case DMG_ACID:			fldmg = ACID_DAMAGE/4; break;
  	case DMG_SLOWBURN:		fldmg = SLOWBURN_DAMAGE/4; break;
  	case DMG_SLOWFREEZE:	fldmg = SLOWFREEZE_DAMAGE/4; break;
  	}
  #endif
  
  	if ( fldmg < 0 )
  		pOther->TakeHealth( -fldmg, m_bitsDamageInflict );
  	else
  		pOther->TakeDamage( pev, pev, fldmg, m_bitsDamageInflict );
  
  	// Store pain time so we can get all of the other entities on this frame
  	pev->pain_finished = gpGlobals->time;
  
  	// Apply damage every half second
  	pev->dmgtime = gpGlobals->time + 0.5;// half second delay until this trigger can hurt toucher again
  
    
  	
  	if ( pev->target )
  	{
  		// trigger has a target it wants to fire. 
  		if ( pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYFIRE )
  		{
  			// if the toucher isn't a client, don't fire the target!
  			if ( !pOther->IsPlayer() )
  			{
  				return;
  			}
  		}
  
  		SUB_UseTargets( pOther, USE_TOGGLE, 0 );
  		if ( pev->spawnflags & SF_TRIGGER_HURT_TARGETONCE )
  			pev->target = 0;
  	}
  }
  
  
  /*QUAKED trigger_multiple (.5 .5 .5) ? notouch
  Variable sized repeatable trigger.  Must be targeted at one or more entities.
  If "health" is set, the trigger must be killed to activate each time.
  If "delay" is set, the trigger waits some time after activating before firing.
  "wait" : Seconds between triggerings. (.2 default)
  If notouch is set, the trigger is only fired by other entities, not by touching.
  NOTOUCH has been obsoleted by trigger_relay!
  sounds
  1)      secret
  2)      beep beep
  3)      large switch
  4)
  NEW
  if a trigger has a NETNAME, that NETNAME will become the TARGET of the triggered object.
  */
  class CTriggerMultiple : public CBaseTrigger
  {
  public:
  	void Spawn( void );
  };
  
  LINK_ENTITY_TO_CLASS( trigger_multiple, CTriggerMultiple );
  
  
  void CTriggerMultiple :: Spawn( void )
  {
  	if (m_flWait == 0)
  		m_flWait = 0.2;
  
  	InitTrigger();
  
  	ASSERTSZ(pev->health == 0, "trigger_multiple with health");
  //	UTIL_SetOrigin(pev, pev->origin);
  //	SET_MODEL( ENT(pev), STRING(pev->model) );
  //	if (pev->health > 0)
  //		{
  //		if (FBitSet(pev->spawnflags, SPAWNFLAG_NOTOUCH))
  //			ALERT(at_error, "trigger_multiple spawn: health and notouch don't make sense");
  //		pev->max_health = pev->health;
  //UNDONE: where to get pfnDie from?
  //		pev->pfnDie = multi_killed;
  //		pev->takedamage = DAMAGE_YES;
  //		pev->solid = SOLID_BBOX;
  //		UTIL_SetOrigin(pev, pev->origin);  // make sure it links into the world
  //		}
  //	else
  		{
  			SetTouch( MultiTouch );
  		}
  	}
  
  
  /*QUAKED trigger_once (.5 .5 .5) ? notouch
  Variable sized trigger. Triggers once, then removes itself.  You must set the key "target" to the name of another object in the level that has a matching
  "targetname".  If "health" is set, the trigger must be killed to activate.
  If notouch is set, the trigger is only fired by other entities, not by touching.
  if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
  if "angle" is set, the trigger will only fire when someone is facing the direction of the angle.  Use "360" for an angle of 0.
  sounds
  1)      secret
  2)      beep beep
  3)      large switch
  4)
  */
  class CTriggerOnce : public CTriggerMultiple
  {
  public:
  	void Spawn( void );
  };
  
  LINK_ENTITY_TO_CLASS( trigger_once, CTriggerOnce );
  void CTriggerOnce::Spawn( void )
  {
  	m_flWait = -1;
  	
  	CTriggerMultiple :: Spawn();
  }
  
  
  
  void CBaseTrigger :: MultiTouch( CBaseEntity *pOther )
  {
  	entvars_t	*pevToucher;
  
  	pevToucher = pOther->pev;
  
  	// Only touch clients, monsters, or pushables (depending on flags)
  	if ( ((pevToucher->flags & FL_CLIENT) && !(pev->spawnflags & SF_TRIGGER_NOCLIENTS)) ||
  		 ((pevToucher->flags & FL_MONSTER) && (pev->spawnflags & SF_TRIGGER_ALLOWMONSTERS)) ||
  		 (pev->spawnflags & SF_TRIGGER_PUSHABLES) && FClassnameIs(pevToucher,"func_pushable") )
  	{
  
  #if 0
  		// if the trigger has an angles field, check player's facing direction
  		if (pev->movedir != g_vecZero)
  		{
  			UTIL_MakeVectors( pevToucher->angles );
  			if ( DotProduct( gpGlobals->v_forward, pev->movedir ) < 0 )
  				return;         // not facing the right way
  		}
  #endif
  		
  		ActivateMultiTrigger( pOther );
  	}
  }
  
  
  //
  // the trigger was just touched/killed/used
  // self.enemy should be set to the activator so it can be held through a delay
  // so wait for the delay time before firing
  //
  void CBaseTrigger :: ActivateMultiTrigger( CBaseEntity *pActivator )
  {
  	if (pev->nextthink > gpGlobals->time)
  		return;         // still waiting for reset time
  
  	if (!UTIL_IsMasterTriggered(m_sMaster,pActivator))
  		return;
  
  	if (FClassnameIs(pev, "trigger_secret"))
  	{
  		if ( pev->enemy == NULL || !FClassnameIs(pev->enemy, "player"))
  			return;
  		gpGlobals->found_secrets++;
  	}
  
  	if (!FStringNull(pev->noise))
  		EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM);
  
  // don't trigger again until reset
  // pev->takedamage = DAMAGE_NO;
  
  	m_hActivator = pActivator;
  	SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 );
  
  	if ( pev->message && pActivator->IsPlayer() )
  	{
  		UTIL_ShowMessage( STRING(pev->message), pActivator );
  //		CLIENT_PRINTF( ENT( pActivator->pev ), print_center, STRING(pev->message) );
  	}
  
  	if (m_flWait > 0)
  	{
  		SetThink( MultiWaitOver );
  		pev->nextthink = gpGlobals->time + m_flWait;
  	}
  	else
  	{
  		// we can't just remove (self) here, because this is a touch function
  		// called while C code is looping through area links...
  		SetTouch( NULL );
  		pev->nextthink = gpGlobals->time + 0.1;
  		SetThink(  SUB_Remove );
  	}
  }
  
  
  // the wait time has passed, so set back up for another activation
  void CBaseTrigger :: MultiWaitOver( void )
  {
  //	if (pev->max_health)
  //		{
  //		pev->health		= pev->max_health;
  //		pev->takedamage	= DAMAGE_YES;
  //		pev->solid		= SOLID_BBOX;
  //		}
  	SetThink( NULL );
  }
  
  
  // ========================= COUNTING TRIGGER =====================================
  
  //
  // GLOBALS ASSUMED SET:  g_eoActivator
  //
  void CBaseTrigger::CounterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  {
  	m_cTriggersLeft--;
  	m_hActivator = pActivator;
  
  	if (m_cTriggersLeft < 0)
  		return;
  	
  	BOOL fTellActivator =
  		(m_hActivator != 0) &&
  		FClassnameIs(m_hActivator->pev, "player") &&
  		!FBitSet(pev->spawnflags, SPAWNFLAG_NOMESSAGE);
  	if (m_cTriggersLeft != 0)
  	{
  		if (fTellActivator)
  		{
  			// UNDONE: I don't think we want these Quakesque messages
  			switch (m_cTriggersLeft)
  			{
  			case 1:		ALERT(at_console, "Only 1 more to go...");		break;
  			case 2:		ALERT(at_console, "Only 2 more to go...");		break;
  			case 3:		ALERT(at_console, "Only 3 more to go...");		break;
  			default:	ALERT(at_console, "There are more to go...");	break;
  			}
  		}
  		return;
  	}
  
  	// !!!UNDONE: I don't think we want these Quakesque messages
  	if (fTellActivator)
  		ALERT(at_console, "Sequence completed!");
  	
  	ActivateMultiTrigger( m_hActivator );
  }
  
  
  /*QUAKED trigger_counter (.5 .5 .5) ? nomessage
  Acts as an intermediary for an action that takes multiple inputs.
  If nomessage is not set, it will print "1 more.. " etc when triggered and
  "sequence complete" when finished.  After the counter has been triggered "cTriggersLeft"
  times (default 2), it will fire all of it's targets and remove itself.
  */
  class CTriggerCounter : public CBaseTrigger
  {
  public:
  	void Spawn( void );
  };
  LINK_ENTITY_TO_CLASS( trigger_counter, CTriggerCounter );
  
  void CTriggerCounter :: Spawn( void )
  {
  	// By making the flWait be -1, this counter-trigger will disappear after it's activated
  	// (but of course it needs cTriggersLeft "uses" before that happens).
  	m_flWait = -1;
  
  	if (m_cTriggersLeft == 0)
  		m_cTriggersLeft = 2;
  	SetUse( CounterUse );
  }
  
  // ====================== TRIGGER_CHANGELEVEL ================================
  
  class CTriggerVolume : public CPointEntity	// Derive from point entity so this doesn't move across levels
  {
  public:
  	void		Spawn( void );
  };
  
  LINK_ENTITY_TO_CLASS( trigger_transition, CTriggerVolume );
  
  // Define space that travels across a level transition
  void CTriggerVolume :: Spawn( void )
  {
  	pev->solid = SOLID_NOT;
  	pev->movetype = MOVETYPE_NONE;
  	SET_MODEL(ENT(pev), STRING(pev->model));    // set size and link into world
  	pev->model = NULL;
  	pev->modelindex = 0;
  }
  
  
  // Fires a target after level transition and then dies
  class CFireAndDie : public CBaseDelay
  {
  public:
  	void Spawn( void );
  	void Precache( void );
  	void Think( void );
  	int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() | FCAP_FORCE_TRANSITION; }	// Always go across transitions
  };
  LINK_ENTITY_TO_CLASS( fireanddie, CFireAndDie );
  
  void CFireAndDie::Spawn( void )
  {
  	pev->classname = MAKE_STRING("fireanddie");
  	// Don't call Precache() - it should be called on restore
  }
  
  
  void CFireAndDie::Precache( void )
  {
  	// This gets called on restore
  	pev->nextthink = gpGlobals->time + m_flDelay;
  }
  
  
  void CFireAndDie::Think( void )
  {
  	SUB_UseTargets( this, USE_TOGGLE, 0 );
  	UTIL_Remove( this );
  }
  
  
  #define SF_CHANGELEVEL_USEONLY		0x0002
  class CChangeLevel : public CBaseTrigger
  {
  public:
  	void Spawn( void );
  	void KeyValue( KeyValueData *pkvd );
  	void EXPORT UseChangeLevel ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
  	void EXPORT TriggerChangeLevel( void );
  	void EXPORT ExecuteChangeLevel( void );
  	void EXPORT TouchChangeLevel( CBaseEntity *pOther );
  	void ChangeLevelNow( CBaseEntity *pActivator );
  
  	static edict_t *FindLandmark( const char *pLandmarkName );
  	static int ChangeList( LEVELLIST *pLevelList, int maxList );
  	static int AddTransitionToList( LEVELLIST *pLevelList, int listCount, const char *pMapName, const char *pLandmarkName, edict_t *pentLandmark );
  	static int InTransitionVolume( CBaseEntity *pEntity, char *pVolumeName );
  
  	virtual int		Save( CSave &save );
  	virtual int		Restore( CRestore &restore );
  
  	static	TYPEDESCRIPTION m_SaveData[];
  
  	char m_szMapName[cchMapNameMost];		// trigger_changelevel only:  next map
  	char m_szLandmarkName[cchMapNameMost];		// trigger_changelevel only:  landmark on next map
  	int		m_changeTarget;
  	float	m_changeTargetDelay;
  };
  LINK_ENTITY_TO_CLASS( trigger_changelevel, CChangeLevel );
  
  // Global Savedata for changelevel trigger
  TYPEDESCRIPTION	CChangeLevel::m_SaveData[] = 
  {
  	DEFINE_ARRAY( CChangeLevel, m_szMapName, FIELD_CHARACTER, cchMapNameMost ),
  	DEFINE_ARRAY( CChangeLevel, m_szLandmarkName, FIELD_CHARACTER, cchMapNameMost ),
  	DEFINE_FIELD( CChangeLevel, m_changeTarget, FIELD_STRING ),
  	DEFINE_FIELD( CChangeLevel, m_changeTargetDelay, FIELD_FLOAT ),
  };
  
  IMPLEMENT_SAVERESTORE(CChangeLevel,CBaseTrigger);
  
  //
  // Cache user-entity-field values until spawn is called.
  //
  
  void CChangeLevel :: KeyValue( KeyValueData *pkvd )
  {
  	if (FStrEq(pkvd->szKeyName, "map"))
  	{
  		if (strlen(pkvd->szValue) >= cchMapNameMost)
  			ALERT( at_error, "Map name '%s' too long (32 chars)\n", pkvd->szValue );
  		strcpy(m_szMapName, pkvd->szValue);
  		pkvd->fHandled = TRUE;
  	}
  	else if (FStrEq(pkvd->szKeyName, "landmark"))
  	{
  		if (strlen(pkvd->szValue) >= cchMapNameMost)
  			ALERT( at_error, "Landmark name '%s' too long (32 chars)\n", pkvd->szValue );
  		strcpy(m_szLandmarkName, pkvd->szValue);
  		pkvd->fHandled = TRUE;
  	}
  	else if (FStrEq(pkvd->szKeyName, "changetarget"))
  	{
  		m_changeTarget = ALLOC_STRING( pkvd->szValue );
  		pkvd->fHandled = TRUE;
  	}
  	else if (FStrEq(pkvd->szKeyName, "changedelay"))
  	{
  		m_changeTargetDelay = atof( pkvd->szValue );
  		pkvd->fHandled = TRUE;
  	}
  	else
  		CBaseTrigger::KeyValue( pkvd );
  }
  
  
  /*QUAKED trigger_changelevel (0.5 0.5 0.5) ? NO_INTERMISSION
  When the player touches this, he gets sent to the map listed in the "map" variable.  Unless the NO_INTERMISSION flag is set, the view will go to the info_intermission spot and display stats.
  */
  
  void CChangeLevel :: Spawn( void )
  {
  	if ( FStrEq( m_szMapName, "" ) )
  		ALERT( at_console, "a trigger_changelevel doesn't have a map" );
  
  	if ( FStrEq( m_szLandmarkName, "" ) )
  		ALERT( at_console, "trigger_changelevel to %s doesn't have a landmark", m_szMapName );
  
  	if (!FStringNull ( pev->targetname ) )
  	{
  		SetUse ( UseChangeLevel );
  	}
  	InitTrigger();
  	if ( !(pev->spawnflags & SF_CHANGELEVEL_USEONLY) )
  		SetTouch( TouchChangeLevel );
  //	ALERT( at_console, "TRANSITION: %s (%s)\n", m_szMapName, m_szLandmarkName );
  }
  
  
  void CChangeLevel :: ExecuteChangeLevel( void )
  {
  	MESSAGE_BEGIN( MSG_ALL, SVC_CDTRACK );
  		WRITE_BYTE( 3 );
  		WRITE_BYTE( 3 );
  	MESSAGE_END();
  
  	MESSAGE_BEGIN(MSG_ALL, SVC_INTERMISSION);
  	MESSAGE_END();
  }
  
  
  FILE_GLOBAL char st_szNextMap[cchMapNameMost];
  FILE_GLOBAL char st_szNextSpot[cchMapNameMost];
  
  edict_t *CChangeLevel :: FindLandmark( const char *pLandmarkName )
  {
  	edict_t	*pentLandmark;
  
  	pentLandmark = FIND_ENTITY_BY_STRING( NULL, "targetname", pLandmarkName );
  	while ( !FNullEnt( pentLandmark ) )
  	{
  		// Found the landmark
  		if ( FClassnameIs( pentLandmark, "info_landmark" ) )
  			return pentLandmark;
  		else
  			pentLandmark = FIND_ENTITY_BY_STRING( pentLandmark, "targetname", pLandmarkName );
  	}
  	ALERT( at_error, "Can't find landmark %s\n", pLandmarkName );
  	return NULL;
  }
  
  
  //=========================================================
  // CChangeLevel :: Use - allows level transitions to be 
  // triggered by buttons, etc.
  //
  //=========================================================
  void CChangeLevel :: UseChangeLevel ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  {
  	ChangeLevelNow( pActivator );
  }
  
  void CChangeLevel :: ChangeLevelNow( CBaseEntity *pActivator )
  {
  	edict_t	*pentLandmark;
  	LEVELLIST	levels[16];
  
  	ASSERT(!FStrEq(m_szMapName, ""));
  
  	// Don't work in deathmatch
  	if ( g_pGameRules->IsDeathmatch() )
  		return;
  
  	// Some people are firing these multiple times in a frame, disable
  	if ( gpGlobals->time == pev->dmgtime )
  		return;
  
  	pev->dmgtime = gpGlobals->time;
  
  
  	CBaseEntity *pPlayer = CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) );
  	if ( !InTransitionVolume( pPlayer, m_szLandmarkName ) )
  	{
  		ALERT( at_aiconsole, "Player isn't in the transition volume %s, aborting\n", m_szLandmarkName );
  		return;
  	}
  
  	// Create an entity to fire the changetarget
  	if ( m_changeTarget )
  	{
  		CFireAndDie *pFireAndDie = GetClassPtr( (CFireAndDie *)NULL );
  		if ( pFireAndDie )
  		{
  			// Set target and delay
  			pFireAndDie->pev->target = m_changeTarget;
  			pFireAndDie->m_flDelay = m_changeTargetDelay;
  			pFireAndDie->pev->origin = pPlayer->pev->origin;
  			// Call spawn
  			DispatchSpawn( pFireAndDie->edict() );
  		}
  	}
  	// This object will get removed in the call to CHANGE_LEVEL, copy the params into "safe" memory
  	strcpy(st_szNextMap, m_szMapName);
  
  	m_hActivator = pActivator;
  	SUB_UseTargets( pActivator, USE_TOGGLE, 0 );
  	st_szNextSpot[0] = 0;	// Init landmark to NULL
  
  	// look for a landmark entity		
  	pentLandmark = FindLandmark( m_szLandmarkName );
  	if ( !FNullEnt( pentLandmark ) )
  	{
  		strcpy(st_szNextSpot, m_szLandmarkName);
  		gpGlobals->vecLandmarkOffset = VARS(pentLandmark)->origin;
  	}
  //	ALERT( at_console, "Level touches %d levels\n", ChangeList( levels, 16 ) );
  	ALERT( at_console, "CHANGE LEVEL: %s %s\n", st_szNextMap, st_szNextSpot );
  	CHANGE_LEVEL( st_szNextMap, st_szNextSpot );
  }
  
  //
  // GLOBALS ASSUMED SET:  st_szNextMap
  //
  void CChangeLevel :: TouchChangeLevel( CBaseEntity *pOther )
  {
  	if (!FClassnameIs(pOther->pev, "player"))
  		return;
  
  	ChangeLevelNow( pOther );
  }
  
  
  // Add a transition to the list, but ignore duplicates 
  // (a designer may have placed multiple trigger_changelevels with the same landmark)
  int CChangeLevel::AddTransitionToList( LEVELLIST *pLevelList, int listCount, const char *pMapName, const char *pLandmarkName, edict_t *pentLandmark )
  {
  	int i;
  
  	if ( !pLevelList || !pMapName || !pLandmarkName || !pentLandmark )
  		return 0;
  
  	for ( i = 0; i < listCount; i++ )
  	{
  		if ( pLevelList[i].pentLandmark == pentLandmark && strcmp( pLevelList[i].mapName, pMapName ) == 0 )
  			return 0;
  	}
  	strcpy( pLevelList[listCount].mapName, pMapName );
  	strcpy( pLevelList[listCount].landmarkName, pLandmarkName );
  	pLevelList[listCount].pentLandmark = pentLandmark;
  	pLevelList[listCount].vecLandmarkOrigin = VARS(pentLandmark)->origin;
  
  	return 1;
  }
  
  int BuildChangeList( LEVELLIST *pLevelList, int maxList )
  {
  	return CChangeLevel::ChangeList( pLevelList, maxList );
  }
  
  
  int CChangeLevel::InTransitionVolume( CBaseEntity *pEntity, char *pVolumeName )
  {
  	edict_t	*pentVolume;
  
  
  	if ( pEntity->ObjectCaps() & FCAP_FORCE_TRANSITION )
  		return 1;
  
  	// If you're following another entity, follow it through the transition (weapons follow the player)
  	if ( pEntity->pev->movetype == MOVETYPE_FOLLOW )
  	{
  		if ( pEntity->pev->aiment != NULL )
  			pEntity = CBaseEntity::Instance( pEntity->pev->aiment );
  	}
  
  	int inVolume = 1;	// Unless we find a trigger_transition, everything is in the volume
  
  	pentVolume = FIND_ENTITY_BY_TARGETNAME( NULL, pVolumeName );
  	while ( !FNullEnt( pentVolume ) )
  	{
  		CBaseEntity *pVolume = CBaseEntity::Instance( pentVolume );
  		
  		if ( pVolume && FClassnameIs( pVolume->pev, "trigger_transition" ) )
  		{
  			if ( pVolume->Intersects( pEntity ) )	// It touches one, it's in the volume
  				return 1;
  			else
  				inVolume = 0;	// Found a trigger_transition, but I don't intersect it -- if I don't find another, don't go!
  		}
  		pentVolume = FIND_ENTITY_BY_TARGETNAME( pentVolume, pVolumeName );
  	}
  
  	return inVolume;
  }
  
  
  // We can only ever move 512 entities across a transition
  #define MAX_ENTITY 512
  
  // This has grown into a complicated beast
  // Can we make this more elegant?
  // This builds the list of all transitions on this level and which entities are in their PVS's and can / should
  // be moved across.
  int CChangeLevel::ChangeList( LEVELLIST *pLevelList, int maxList )
  {
  	edict_t	*pentChangelevel, *pentLandmark;
  	int			i, count;
  
  	count = 0;
  
  	// Find all of the possible level changes on this BSP
  	pentChangelevel = FIND_ENTITY_BY_STRING( NULL, "classname", "trigger_changelevel" );
  	if ( FNullEnt( pentChangelevel ) )
  		return 0;
  	while ( !FNullEnt( pentChangelevel ) )
  	{
  		CChangeLevel *pTrigger;
  		
  		pTrigger = GetClassPtr((CChangeLevel *)VARS(pentChangelevel));
  		if ( pTrigger )
  		{
  			// Find the corresponding landmark
  			pentLandmark = FindLandmark( pTrigger->m_szLandmarkName );
  			if ( pentLandmark )
  			{
  				// Build a list of unique transitions
  				if ( AddTransitionToList( pLevelList, count, pTrigger->m_szMapName, pTrigger->m_szLandmarkName, pentLandmark ) )
  				{
  					count++;
  					if ( count >= maxList )		// FULL!!
  						break;
  				}
  			}
  		}
  		pentChangelevel = FIND_ENTITY_BY_STRING( pentChangelevel, "classname", "trigger_changelevel" );
  	}
  
  	if ( gpGlobals->pSaveData && ((SAVERESTOREDATA *)gpGlobals->pSaveData)->pTable )
  	{
  		CSave saveHelper( (SAVERESTOREDATA *)gpGlobals->pSaveData );
  
  		for ( i = 0; i < count; i++ )
  		{
  			int j, entityCount = 0;
  			CBaseEntity *pEntList[ MAX_ENTITY ];
  			int			 entityFlags[ MAX_ENTITY ];
  
  			// Follow the linked list of entities in the PVS of the transition landmark
  			edict_t *pent = UTIL_EntitiesInPVS( pLevelList[i].pentLandmark );
  
  			// Build a list of valid entities in this linked list (we're going to use pent->v.chain again)
  			while ( !FNullEnt( pent ) )
  			{
  				CBaseEntity *pEntity = CBaseEntity::Instance(pent);
  				if ( pEntity )
  				{
  //					ALERT( at_console, "Trying %s\n", STRING(pEntity->pev->classname) );
  					int caps = pEntity->ObjectCaps();
  					if ( !(caps & FCAP_DONT_SAVE) )
  					{
  						int flags = 0;
  
  						// If this entity can be moved or is global, mark it
  						if ( caps & FCAP_ACROSS_TRANSITION )
  							flags |= FENTTABLE_MOVEABLE;
  						if ( pEntity->pev->globalname && !pEntity->IsDormant() )
  							flags |= FENTTABLE_GLOBAL;
  						if ( flags )
  						{
  							pEntList[ entityCount ] = pEntity;
  							entityFlags[ entityCount ] = flags;
  							entityCount++;
  							if ( entityCount > MAX_ENTITY )
  								ALERT( at_error, "Too many entities across a transition!" );
  						}
  //						else
  //							ALERT( at_console, "Failed %s\n", STRING(pEntity->pev->classname) );
  					}
  //					else
  //						ALERT( at_console, "DON'T SAVE %s\n", STRING(pEntity->pev->classname) );
  				}
  				pent = pent->v.chain;
  			}
  
  			for ( j = 0; j < entityCount; j++ )
  			{
  				// Check to make sure the entity isn't screened out by a trigger_transition
  				if ( entityFlags[j] && InTransitionVolume( pEntList[j], pLevelList[i].landmarkName ) )
  				{
  					// Mark entity table with 1<<i
  					int index = saveHelper.EntityIndex( pEntList[j] );
  					// Flag it with the level number
  					saveHelper.EntityFlagsSet( index, entityFlags[j] | (1<<i) );
  				}
  //				else
  //					ALERT( at_console, "Screened out %s\n", STRING(pEntList[j]->pev->classname) );
  
  			}
  		}
  	}
  
  	return count;
  }
  
  /*
  go to the next level for deathmatch
  only called if a time or frag limit has expired
  */
  void NextLevel( void )
  {
  	edict_t* pent;
  	CChangeLevel *pChange;
  	
  	// find a trigger_changelevel
  	pent = FIND_ENTITY_BY_CLASSNAME(NULL, "trigger_changelevel");
  	
  	// go back to start if no trigger_changelevel
  	if (FNullEnt(pent))
  	{
  		gpGlobals->mapname = ALLOC_STRING("start");
  		pChange = GetClassPtr( (CChangeLevel *)NULL );
  		strcpy(pChange->m_szMapName, "start");
  	}
  	else
  		pChange = GetClassPtr( (CChangeLevel *)VARS(pent));
  	
  	strcpy(st_szNextMap, pChange->m_szMapName);
  	g_fGameOver = TRUE;
  	
  	if (pChange->pev->nextthink < gpGlobals->time)
  	{
  		pChange->SetThink( CChangeLevel::ExecuteChangeLevel );
  		pChange->pev->nextthink = gpGlobals->time + 0.1;
  	}
  }
  
  
  // ============================== LADDER =======================================
  
  class CLadder : public CBaseTrigger
  {
  public:
  	void KeyValue( KeyValueData *pkvd );
  	void Spawn( void );
  	void Precache( void );
  };
  LINK_ENTITY_TO_CLASS( func_ladder, CLadder );
  
  
  void CLadder :: KeyValue( KeyValueData *pkvd )
  {
  	CBaseTrigger::KeyValue( pkvd );
  }
  
  
  //=========================================================
  // func_ladder - makes an area vertically negotiable
  //=========================================================
  void CLadder :: Precache( void )
  {
  	// Do all of this in here because we need to 'convert' old saved games
  	pev->solid = SOLID_NOT;
  	pev->skin = CONTENTS_LADDER;
  	if ( CVAR_GET_FLOAT("showtriggers") == 0 )
  	{
  		pev->rendermode = kRenderTransTexture;
  		pev->renderamt = 0;
  	}
  	pev->effects &= ~EF_NODRAW;
  }
  
  
  void CLadder :: Spawn( void )
  {
  	Precache();
  
  	SET_MODEL(ENT(pev), STRING(pev->model));    // set size and link into world
  	pev->movetype = MOVETYPE_PUSH;
  }
  
  
  // ========================== A TRIGGER THAT PUSHES YOU ===============================
  
  class CTriggerPush : public CBaseTrigger
  {
  public:
  	void Spawn( void );
  	void KeyValue( KeyValueData *pkvd );
  	void Touch( CBaseEntity *pOther );
  };
  LINK_ENTITY_TO_CLASS( trigger_push, CTriggerPush );
  
  
  void CTriggerPush :: KeyValue( KeyValueData *pkvd )
  {
  	CBaseTrigger::KeyValue( pkvd );
  }
  
  
  /*QUAKED trigger_push (.5 .5 .5) ? TRIG_PUSH_ONCE
  Pushes the player
  */
  
  void CTriggerPush :: Spawn( )
  {
  	if ( pev->angles == g_vecZero )
  		pev->angles.y = 360;
  	InitTrigger();
  
  	if (pev->speed == 0)
  		pev->speed = 100;
  
  	if ( FBitSet (pev->spawnflags, SF_TRIGGER_PUSH_START_OFF) )// if flagged to Start Turned Off, make trigger nonsolid.
  		pev->solid = SOLID_NOT;
  
  	SetUse( ToggleUse );
  
  	UTIL_SetOrigin( pev, pev->origin );		// Link into the list
  }
  
  
  void CTriggerPush :: Touch( CBaseEntity *pOther )
  {
  	entvars_t* pevToucher = pOther->pev;
  
  	// UNDONE: Is there a better way than health to detect things that have physics? (clients/monsters)
  	switch( pevToucher->movetype )
  	{
  	case MOVETYPE_NONE:
  	case MOVETYPE_PUSH:
  	case MOVETYPE_NOCLIP:
  	case MOVETYPE_FOLLOW:
  		return;
  	}
  
  	//Only players
  	if ( !pOther->IsPlayer() )
  		return;
  
  	if ( pevToucher->solid != SOLID_NOT && pevToucher->solid != SOLID_BSP )
  	{
  		// Instant trigger, just transfer velocity and remove
  		if (FBitSet(pev->spawnflags, SF_TRIG_PUSH_ONCE))
  		{
  			pevToucher->velocity = pevToucher->velocity + (pev->speed * pev->movedir);
  			if ( pevToucher->velocity.z > 0 )
  				pevToucher->flags &= ~FL_ONGROUND;
  			UTIL_Remove( this );
  		}
  		else
  		{	// Push field, transfer to base velocity
  			Vector vecPush = (pev->speed * pev->movedir);
  			if ( pevToucher->flags & FL_BASEVELOCITY )
  				vecPush = vecPush +  pevToucher->basevelocity;
  
  			pevToucher->basevelocity = vecPush;
  
  			pevToucher->flags |= FL_BASEVELOCITY;
  //			ALERT( at_console, "Vel %f, base %f\n", pevToucher->velocity.z, pevToucher->basevelocity.z );
  		}
  	}
  }
  
  //========================================================================================
  // TELEPORT TRIGGERS
  //========================================================================================
  
  //-----------------------------------------------------------------------------
  // Purpose: Teleport Death entity. Kills anything it touches
  //-----------------------------------------------------------------------------
  class CTeleDeath : public CBaseTrigger
  {
  public:
  	void Spawn( void );
  	void EXPORT DeathTouch( CBaseEntity *pOther );
  };
  LINK_ENTITY_TO_CLASS( teledeath, CTeleDeath );
  
  void CTeleDeath::Spawn( void )
  {
  	pev->movetype = MOVETYPE_NONE;
  	pev->solid = SOLID_TRIGGER;
  	pev->angles = g_vecZero;
  
  	// Owner is the player who's spawned this
  	CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner);
  	if (!pOwner)
  		return;
  
  	UTIL_SetSize( pev, pOwner->pev->mins - Vector(1,1,1), pOwner->pev->maxs + Vector(1,1,1) );
  	UTIL_SetOrigin( pev, pev->origin );
  
  	SetTouch( DeathTouch );
  	pev->nextthink = gpGlobals->time + 0.2;
  	SetThink( SUB_Remove );
  	
  	// Touch still players
  	gpGlobals->force_retouch = 2;
  }
  
  void CTeleDeath::DeathTouch( CBaseEntity *pOther )
  {
  	CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner);
  
  	if ( pOther == pOwner )
  		return;
  
  	// frag anyone who teleports in on top of an invincible player
  	if ( pOther->IsPlayer() )
  	{
  		if ( ((CBasePlayer*)pOther)->m_flInvincibleFinished > gpGlobals->time )
  		{
  			// Note: This did not work in Quake. Fixed in QUAKECLASSIC.
  			if ( pOwner->IsPlayer() )
  			{
  				g_szDeathType = "teledeath2";
  				pOwner->TakeDamage( pOwner->pev, pOwner->pev, 1000, DMG_GENERIC | DMG_ALWAYSGIB);
  				return;
  			}
  		}
  	}
  
  	if ( pOther->pev->health )
  	{
  		g_szDeathType = "teledeath";
  		pOther->TakeDamage( pOther->pev, pOwner->pev, 1000, DMG_GENERIC | DMG_ALWAYSGIB);
  	}
  }
  
  //======================================
  // teleport trigger
  //
  // QUAKECLASSIC: Different bitflags
  #define	TELE_PLAYER_ONLY	1
  #define	TELE_SILENT			2
  
  //-----------------------------------------------------------------------------
  // Purpose: Spawn a teleport fog at the vector specified
  //-----------------------------------------------------------------------------
  void CBaseEntity::Spawn_Telefog( Vector vecOrg, CBaseEntity *pOther )
  {
  	//Moved to the client
  	PLAYBACK_EVENT_FULL ( FEV_GLOBAL | FEV_NOTHOST, pOther->edict(), g_sTeleport, 0.0, (float *)&vecOrg, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0);
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Kill anything at the teleport destination
  //-----------------------------------------------------------------------------
  void CBaseTrigger :: TeleportTouch( CBaseEntity *pOther )
  {
  	// no clients allowed?
  	if ( ( pev->spawnflags & TELE_PLAYER_ONLY ) )
  	{
  		if ( pOther->IsPlayer() )
  			return;
  	}
  
  	// only teleport living creatures
  	if (pOther->pev->health <= 0 || pOther->pev->solid != SOLID_SLIDEBOX)
  		return;
  
  	SUB_UseTargets( this, USE_TOGGLE, 0 );
  
  	// put a tfog where the player was
  	Spawn_Telefog(pOther->pev->origin, pOther );
  
  	edict_t	*pentTarget = NULL;
  	pentTarget = FIND_ENTITY_BY_TARGETNAME( pentTarget, STRING(pev->target) );
  	if (FNullEnt(pentTarget))
  	   return;	
  
  	// spawn a tfog flash in front of the destination
  	CBaseEntity *pTarget = CBaseEntity::Instance( pentTarget );
  	UTIL_MakeVectors(pTarget->m_vecTeleAngles);
  	Vector vecNewOrg = pTarget->pev->origin + (gpGlobals->v_forward * 32);
  	pTarget->Spawn_Telefog( vecNewOrg, pOther );
  
  	// Spawn teledeath at destination
  	CTeleDeath *pDeath = GetClassPtr( (CTeleDeath *)NULL );
  	pDeath->pev->owner = pOther->edict();
  	pDeath->pev->origin = pTarget->pev->origin;
  	pDeath->Spawn();
  
  	// May be killed by teleporting onto an invulnerable player
  	if (!pOther->pev->health)
  	{
  		pOther->pev->origin = pTarget->pev->origin;
  		pOther->pev->velocity = (gpGlobals->v_forward * pOther->pev->velocity.x) + (gpGlobals->v_forward * pOther->pev->velocity.y);
  		return;
  	}
  
  	// Move the player
  	UTIL_SetOrigin( pOther->pev, pTarget->pev->origin );
  	pOther->pev->angles = pTarget->m_vecTeleAngles;
  
  	if (pOther->IsPlayer())
  	{
  	//	pOther->pev->fixangle = 1;		// turn this way immediately
  		
  		//Err, why is this here?
  		pOther->pev->fuser4 = gpGlobals->time + 0.7;
  		pOther->pev->velocity = gpGlobals->v_forward * 300;
  	}
  	pOther->pev->flags &= ~FL_ONGROUND;
  }
  
  
  class CTriggerTeleport : public CBaseTrigger
  {
  public:
  	void Spawn( void );
  };
  LINK_ENTITY_TO_CLASS( trigger_teleport, CTriggerTeleport );
  
  void CTriggerTeleport :: Spawn( void )
  {
  	InitTrigger();
  
  	SetTouch( TeleportTouch );
  
  	g_vecTeleMins[ g_iTeleNum ] = pev->absmin; 
  	g_vecTeleMaxs[ g_iTeleNum ] = pev->absmax;
  	
  	g_iTeleNum++;
  
  	if (!(pev->spawnflags & TELE_SILENT))
  	{
  		PRECACHE_SOUND("ambience/hum1.wav");
  		UTIL_EmitAmbientSound(ENT(pev), (pev->mins + pev->maxs) * 0.5, "ambience/hum1.wav", 0.5, ATTN_STATIC, 0, 100);
  	}
  }
  
  
  class CTriggerTeleportDest : public CBaseTrigger
  {
  public:
  	void Spawn( void );
  };
  
  void CTriggerTeleportDest :: Spawn( void )
  {
  	m_vecTeleAngles = pev->angles;
  	pev->angles = g_vecZero;
  	pev->origin.z += 27;
  	UTIL_SetOrigin( pev, pev->origin );
  }
  
  LINK_ENTITY_TO_CLASS( info_teleport_destination, CTriggerTeleportDest );
  
  //==========================================================================
  class CTriggerSave : public CBaseTrigger
  {
  public:
  	void Spawn( void );
  	void EXPORT SaveTouch( CBaseEntity *pOther );
  };
  LINK_ENTITY_TO_CLASS( trigger_autosave, CTriggerSave );
  
  void CTriggerSave::Spawn( void )
  {
  	if ( g_pGameRules->IsDeathmatch() )
  	{
  		REMOVE_ENTITY( ENT(pev) );
  		return;
  	}
  
  	InitTrigger();
  	SetTouch( SaveTouch );
  }
  
  void CTriggerSave::SaveTouch( CBaseEntity *pOther )
  {
  	if ( !UTIL_IsMasterTriggered( m_sMaster, pOther ) )
  		return;
  
  	// Only save on clients
  	if ( !pOther->IsPlayer() )
  		return;
      
  	SetTouch( NULL );
  	UTIL_Remove( this );
  	SERVER_COMMAND( "autosave\n" );
  }
  
  #define SF_ENDSECTION_USEONLY		0x0001
  
  class CTriggerEndSection : public CBaseTrigger
  {
  public:
  	void Spawn( void );
  	void EXPORT EndSectionTouch( CBaseEntity *pOther );
  	void KeyValue( KeyValueData *pkvd );
  	void EXPORT EndSectionUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
  };
  LINK_ENTITY_TO_CLASS( trigger_endsection, CTriggerEndSection );
  
  
  void CTriggerEndSection::EndSectionUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  {
  	// Only save on clients
  	if ( !pActivator->IsNetClient() )
  		return;
      
  	SetUse( NULL );
  
  	if ( pev->message )
  	{
  		g_engfuncs.pfnEndSection(STRING(pev->message));
  	}
  	UTIL_Remove( this );
  }
  
  void CTriggerEndSection::Spawn( void )
  {
  	if ( g_pGameRules->IsDeathmatch() )
  	{
  		REMOVE_ENTITY( ENT(pev) );
  		return;
  	}
  
  	InitTrigger();
  
  	SetUse ( EndSectionUse );
  	// If it is a "use only" trigger, then don't set the touch function.
  	if ( ! (pev->spawnflags & SF_ENDSECTION_USEONLY) )
  		SetTouch( EndSectionTouch );
  }
  
  void CTriggerEndSection::EndSectionTouch( CBaseEntity *pOther )
  {
  	// Only save on clients
  	if ( !pOther->IsNetClient() )
  		return;
      
  	SetTouch( NULL );
  
  	if (pev->message)
  	{
  		g_engfuncs.pfnEndSection(STRING(pev->message));
  	}
  	UTIL_Remove( this );
  }
  
  void CTriggerEndSection :: KeyValue( KeyValueData *pkvd )
  {
  	if (FStrEq(pkvd->szKeyName, "section"))
  	{
  //		m_iszSectionName = ALLOC_STRING( pkvd->szValue );
  		// Store this in message so we don't have to write save/restore for this ent
  		pev->message = ALLOC_STRING( pkvd->szValue );
  		pkvd->fHandled = TRUE;
  	}
  	else
  		CBaseTrigger::KeyValue( pkvd );
  }
  
  
  class CTriggerGravity : public CBaseTrigger
  {
  public:
  	void Spawn( void );
  	void EXPORT GravityTouch( CBaseEntity *pOther );
  };
  LINK_ENTITY_TO_CLASS( trigger_gravity, CTriggerGravity );
  
  void CTriggerGravity::Spawn( void )
  {
  	InitTrigger();
  	SetTouch( GravityTouch );
  }
  
  void CTriggerGravity::GravityTouch( CBaseEntity *pOther )
  {
  	// Only save on clients
  	if ( !pOther->IsPlayer() )
  		return;
  
  	pOther->pev->gravity = pev->gravity;
  }
  
  
  
  
  
  
  
  // this is a really bad idea.
  class CTriggerChangeTarget : public CBaseDelay
  {
  public:
  	void KeyValue( KeyValueData *pkvd );
  	void Spawn( void );
  	void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
  
  	int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
  	virtual int		Save( CSave &save );
  	virtual int		Restore( CRestore &restore );
  
  	static	TYPEDESCRIPTION m_SaveData[];
  
  private:
  	int		m_iszNewTarget;
  };
  LINK_ENTITY_TO_CLASS( trigger_changetarget, CTriggerChangeTarget );
  
  TYPEDESCRIPTION	CTriggerChangeTarget::m_SaveData[] = 
  {
  	DEFINE_FIELD( CTriggerChangeTarget, m_iszNewTarget, FIELD_STRING ),
  };
  
  IMPLEMENT_SAVERESTORE(CTriggerChangeTarget,CBaseDelay);
  
  void CTriggerChangeTarget::KeyValue( KeyValueData *pkvd )
  {
  	if (FStrEq(pkvd->szKeyName, "m_iszNewTarget"))
  	{
  		m_iszNewTarget = ALLOC_STRING( pkvd->szValue );
  		pkvd->fHandled = TRUE;
  	}
  	else
  		CBaseDelay::KeyValue( pkvd );
  }
  
  void CTriggerChangeTarget::Spawn( void )
  {
  }
  
  
  void CTriggerChangeTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  {
  	CBaseEntity *pTarget = UTIL_FindEntityByString( NULL, "targetname", STRING( pev->target ) );
  
  	if (pTarget)
  	{
  		pTarget->pev->target = m_iszNewTarget;
  		CBaseMonster *pMonster = pTarget->MyMonsterPointer( );
  		if (pMonster)
  		{
  			pMonster->m_pGoalEnt = NULL;
  		}
  	}
  }
  
  
  
  
  #define SF_CAMERA_PLAYER_POSITION	1
  #define SF_CAMERA_PLAYER_TARGET		2
  #define SF_CAMERA_PLAYER_TAKECONTROL 4
  
  class CTriggerCamera : public CBaseDelay
  {
  public:
  	void Spawn( void );
  	void KeyValue( KeyValueData *pkvd );
  	void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
  	void EXPORT FollowTarget( void );
  	void Move(void);
  
  	virtual int		Save( CSave &save );
  	virtual int		Restore( CRestore &restore );
  	virtual int	ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
  	static	TYPEDESCRIPTION m_SaveData[];
  
  	EHANDLE m_hPlayer;
  	EHANDLE m_hTarget;
  	CBaseEntity *m_pentPath;
  	int	  m_sPath;
  	float m_flWait;
  	float m_flReturnTime;
  	float m_flStopTime;
  	float m_moveDistance;
  	float m_targetSpeed;
  	float m_initialSpeed;
  	float m_acceleration;
  	float m_deceleration;
  	int	  m_state;
  	
  };
  LINK_ENTITY_TO_CLASS( trigger_camera, CTriggerCamera );
  
  // Global Savedata for changelevel friction modifier
  TYPEDESCRIPTION	CTriggerCamera::m_SaveData[] = 
  {
  	DEFINE_FIELD( CTriggerCamera, m_hPlayer, FIELD_EHANDLE ),
  	DEFINE_FIELD( CTriggerCamera, m_hTarget, FIELD_EHANDLE ),
  	DEFINE_FIELD( CTriggerCamera, m_pentPath, FIELD_CLASSPTR ),
  	DEFINE_FIELD( CTriggerCamera, m_sPath, FIELD_STRING ),
  	DEFINE_FIELD( CTriggerCamera, m_flWait, FIELD_FLOAT ),
  	DEFINE_FIELD( CTriggerCamera, m_flReturnTime, FIELD_TIME ),
  	DEFINE_FIELD( CTriggerCamera, m_flStopTime, FIELD_TIME ),
  	DEFINE_FIELD( CTriggerCamera, m_moveDistance, FIELD_FLOAT ),
  	DEFINE_FIELD( CTriggerCamera, m_targetSpeed, FIELD_FLOAT ),
  	DEFINE_FIELD( CTriggerCamera, m_initialSpeed, FIELD_FLOAT ),
  	DEFINE_FIELD( CTriggerCamera, m_acceleration, FIELD_FLOAT ),
  	DEFINE_FIELD( CTriggerCamera, m_deceleration, FIELD_FLOAT ),
  	DEFINE_FIELD( CTriggerCamera, m_state, FIELD_INTEGER ),
  };
  
  IMPLEMENT_SAVERESTORE(CTriggerCamera,CBaseDelay);
  
  void CTriggerCamera::Spawn( void )
  {
  	pev->movetype = MOVETYPE_NOCLIP;
  	pev->solid = SOLID_NOT;							// Remove model & collisions
  	pev->renderamt = 0;								// The engine won't draw this model if this is set to 0 and blending is on
  	pev->rendermode = kRenderTransTexture;
  
  	m_initialSpeed = pev->speed;
  	if ( m_acceleration == 0 )
  		m_acceleration = 500;
  	if ( m_deceleration == 0 )
  		m_deceleration = 500;
  }
  
  
  void CTriggerCamera :: KeyValue( KeyValueData *pkvd )
  {
  	if (FStrEq(pkvd->szKeyName, "wait"))
  	{
  		m_flWait = atof(pkvd->szValue);
  		pkvd->fHandled = TRUE;
  	}
  	else if (FStrEq(pkvd->szKeyName, "moveto"))
  	{
  		m_sPath = ALLOC_STRING( pkvd->szValue );
  		pkvd->fHandled = TRUE;
  	}
  	else if (FStrEq(pkvd->szKeyName, "acceleration"))
  	{
  		m_acceleration = atof( pkvd->szValue );
  		pkvd->fHandled = TRUE;
  	}
  	else if (FStrEq(pkvd->szKeyName, "deceleration"))
  	{
  		m_deceleration = atof( pkvd->szValue );
  		pkvd->fHandled = TRUE;
  	}
  	else
  		CBaseDelay::KeyValue( pkvd );
  }
  
  
  
  void CTriggerCamera::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  {
  	if ( !ShouldToggle( useType, m_state ) )
  		return;
  
  	// Toggle state
  	m_state = !m_state;
  	if (m_state == 0)
  	{
  		m_flReturnTime = gpGlobals->time;
  		return;
  	}
  	if ( !pActivator || !pActivator->IsPlayer() )
  	{
  		pActivator = CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex( 1 ));
  	}
  		
  	m_hPlayer = pActivator;
  
  	m_flReturnTime = gpGlobals->time + m_flWait;
  	pev->speed = m_initialSpeed;
  	m_targetSpeed = m_initialSpeed;
  
  	if (FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_TARGET ) )
  	{
  		m_hTarget = m_hPlayer;
  	}
  	else
  	{
  		m_hTarget = GetNextTarget();
  	}
  
  	// Nothing to look at!
  	if ( m_hTarget == NULL )
  	{
  		return;
  	}
  
  
  	if (FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_TAKECONTROL ) )
  	{
  		((CBasePlayer *)pActivator)->EnableControl(FALSE);
  	}
  
  	if ( m_sPath )
  	{
  		m_pentPath = Instance( FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(m_sPath)) );
  	}
  	else
  	{
  		m_pentPath = NULL;
  	}
  
  	m_flStopTime = gpGlobals->time;
  	if ( m_pentPath )
  	{
  		if ( m_pentPath->pev->speed != 0 )
  			m_targetSpeed = m_pentPath->pev->speed;
  		
  		m_flStopTime += m_pentPath->GetDelay();
  	}
  
  	// copy over player information
  	if (FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_POSITION ) )
  	{
  		UTIL_SetOrigin( pev, pActivator->pev->origin + pActivator->pev->view_ofs );
  		pev->angles.x = -pActivator->pev->angles.x;
  		pev->angles.y = pActivator->pev->angles.y;
  		pev->angles.z = 0;
  		pev->velocity = pActivator->pev->velocity;
  	}
  	else
  	{
  		pev->velocity = Vector( 0, 0, 0 );
  	}
  
  	SET_VIEW( pActivator->edict(), edict() );
  
  	SET_MODEL(ENT(pev), STRING(pActivator->pev->model) );
  
  	// follow the player down
  	SetThink( FollowTarget );
  	pev->nextthink = gpGlobals->time;
  
  	m_moveDistance = 0;
  	Move();
  }
  
  
  void CTriggerCamera::FollowTarget( )
  {
  	if (m_hPlayer == NULL)
  		return;
  
  	if (m_hTarget == NULL || m_flReturnTime < gpGlobals->time)
  	{
  		if (m_hPlayer->IsAlive( ))
  		{
  			SET_VIEW( m_hPlayer->edict(), m_hPlayer->edict() );
  			((CBasePlayer *)((CBaseEntity *)m_hPlayer))->EnableControl(TRUE);
  		}
  		SUB_UseTargets( this, USE_TOGGLE, 0 );
  		pev->avelocity = Vector( 0, 0, 0 );
  		m_state = 0;
  		return;
  	}
  
  	Vector vecGoal = UTIL_VecToAngles( m_hTarget->pev->origin - pev->origin );
  	vecGoal.x = -vecGoal.x;
  
  	if (pev->angles.y > 360)
  		pev->angles.y -= 360;
  
  	if (pev->angles.y < 0)
  		pev->angles.y += 360;
  
  	float dx = vecGoal.x - pev->angles.x;
  	float dy = vecGoal.y - pev->angles.y;
  
  	if (dx < -180) 
  		dx += 360;
  	if (dx > 180) 
  		dx = dx - 360;
  	
  	if (dy < -180) 
  		dy += 360;
  	if (dy > 180) 
  		dy = dy - 360;
  
  	pev->avelocity.x = dx * 40 * gpGlobals->frametime;
  	pev->avelocity.y = dy * 40 * gpGlobals->frametime;
  
  
  	if (!(FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_TAKECONTROL)))	
  	{
  		pev->velocity = pev->velocity * 0.8;
  		if (pev->velocity.Length( ) < 10.0)
  			pev->velocity = g_vecZero;
  	}
  
  	pev->nextthink = gpGlobals->time;
  
  	Move();
  }
  
  void CTriggerCamera::Move()
  {
  	// Not moving on a path, return
  	if (!m_pentPath)
  		return;
  
  	// Subtract movement from the previous frame
  	m_moveDistance -= pev->speed * gpGlobals->frametime;
  
  	// Have we moved enough to reach the target?
  	if ( m_moveDistance <= 0 )
  	{
  		// Fire the passtarget if there is one
  		if ( m_pentPath->pev->message )
  		{
  			FireTargets( STRING(m_pentPath->pev->message), this, this, USE_TOGGLE, 0 );
  			if ( FBitSet( m_pentPath->pev->spawnflags, SF_CORNER_FIREONCE ) )
  				m_pentPath->pev->message = 0;
  		}
  		// Time to go to the next target
  		m_pentPath = m_pentPath->GetNextTarget();
  
  		// Set up next corner
  		if ( !m_pentPath )
  		{
  			pev->velocity = g_vecZero;
  		}
  		else 
  		{
  			if ( m_pentPath->pev->speed != 0 )
  				m_targetSpeed = m_pentPath->pev->speed;
  
  			Vector delta = m_pentPath->pev->origin - pev->origin;
  			m_moveDistance = delta.Length();
  			pev->movedir = delta.Normalize();
  			m_flStopTime = gpGlobals->time + m_pentPath->GetDelay();
  		}
  	}
  
  	if ( m_flStopTime > gpGlobals->time )
  		pev->speed = UTIL_Approach( 0, pev->speed, m_deceleration * gpGlobals->frametime );
  	else
  		pev->speed = UTIL_Approach( m_targetSpeed, pev->speed, m_acceleration * gpGlobals->frametime );
  
  	float fraction = 2 * gpGlobals->frametime;
  	pev->velocity = ((pev->movedir * pev->speed) * fraction) + (pev->velocity * (1-fraction));
  }
  
  void CClientFog :: KeyValue( KeyValueData *pkvd )
  {
  	if (FStrEq(pkvd->szKeyName, "startdist"))
  	{
  		m_iStartDist = atoi(pkvd->szValue);
  		pkvd->fHandled = TRUE;
  	}
  	else if (FStrEq(pkvd->szKeyName, "enddist"))
  	{
  		m_iEndDist = atoi(pkvd->szValue);
  		pkvd->fHandled = TRUE;
  	}
  	else
  		CBaseEntity::KeyValue( pkvd );
  }
  
  void CClientFog :: Spawn ( void )
  {
  	pev->movetype = MOVETYPE_NOCLIP;
  	pev->solid = SOLID_NOT;							// Remove model & collisions
  	pev->renderamt = 0;								// The engine won't draw this model if this is set to 0 and blending is on
  	pev->rendermode = kRenderTransTexture;
  }
  
  LINK_ENTITY_TO_CLASS( env_fog, CClientFog );