Portable Half-Life SDK. GoldSource and Xash3D. Crossplatform.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2474 lines
63 KiB

9 years ago
/***
*
* 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;
4 years ago
extern BOOL g_fIsXash3D;
9 years ago
extern void SetMovedir(entvars_t* pev);
extern Vector VecBModelOrigin( entvars_t* pevBModel );
class CFrictionModifier : public CBaseEntity
{
public:
8 years ago
void Spawn( void );
void KeyValue( KeyValueData *pkvd );
void EXPORT ChangeFriction( CBaseEntity *pOther );
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
9 years ago
8 years ago
virtual int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
9 years ago
8 years ago
static TYPEDESCRIPTION m_SaveData[];
9 years ago
8 years ago
float m_frictionFraction; // Sorry, couldn't resist this name :)
9 years ago
};
LINK_ENTITY_TO_CLASS( func_friction, CFrictionModifier )
9 years ago
// Global Savedata for changelevel friction modifier
TYPEDESCRIPTION CFrictionModifier::m_SaveData[] =
9 years ago
{
DEFINE_FIELD( CFrictionModifier, m_frictionFraction, FIELD_FLOAT ),
};
IMPLEMENT_SAVERESTORE( CFrictionModifier, CBaseEntity )
9 years ago
// Modify an entity's friction
8 years ago
void CFrictionModifier::Spawn( void )
9 years ago
{
pev->solid = SOLID_TRIGGER;
8 years ago
SET_MODEL( ENT( pev ), STRING( pev->model ) ); // set size and link into world
9 years ago
pev->movetype = MOVETYPE_NONE;
SetTouch( &CFrictionModifier::ChangeFriction );
}
// Sets toucher's friction to m_frictionFraction (1.0 = normal friction)
8 years ago
void CFrictionModifier::ChangeFriction( CBaseEntity *pOther )
9 years ago
{
8 years ago
if( pOther->pev->movetype != MOVETYPE_BOUNCEMISSILE && pOther->pev->movetype != MOVETYPE_BOUNCE )
9 years ago
pOther->pev->friction = m_frictionFraction;
}
// Sets toucher's friction to m_frictionFraction (1.0 = normal friction)
8 years ago
void CFrictionModifier::KeyValue( KeyValueData *pkvd )
9 years ago
{
8 years ago
if( FStrEq(pkvd->szKeyName, "modifier" ) )
9 years ago
{
8 years ago
m_frictionFraction = atof( pkvd->szValue ) / 100.0;
9 years ago
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; }
8 years ago
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
9 years ago
8 years ago
static TYPEDESCRIPTION m_SaveData[];
9 years ago
private:
string_t m_globalstate;
8 years ago
USE_TYPE triggerType;
9 years ago
};
LINK_ENTITY_TO_CLASS( trigger_auto, CAutoTrigger )
TYPEDESCRIPTION CAutoTrigger::m_SaveData[] =
9 years ago
{
DEFINE_FIELD( CAutoTrigger, m_globalstate, FIELD_STRING ),
DEFINE_FIELD( CAutoTrigger, triggerType, FIELD_INTEGER ),
};
IMPLEMENT_SAVERESTORE( CAutoTrigger, CBaseDelay )
9 years ago
void CAutoTrigger::KeyValue( KeyValueData *pkvd )
{
8 years ago
if( FStrEq( pkvd->szKeyName, "globalstate" ) )
9 years ago
{
m_globalstate = ALLOC_STRING( pkvd->szValue );
pkvd->fHandled = TRUE;
}
8 years ago
else if( FStrEq( pkvd->szKeyName, "triggerstate" ) )
9 years ago
{
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.1f;
9 years ago
}
void CAutoTrigger::Think( void )
{
8 years ago
if( !m_globalstate || gGlobalState.EntityGetState( m_globalstate ) == GLOBAL_ON )
9 years ago
{
SUB_UseTargets( this, triggerType, 0 );
8 years ago
if( pev->spawnflags & SF_AUTO_FIREONCE )
9 years ago
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; }
8 years ago
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
9 years ago
8 years ago
static TYPEDESCRIPTION m_SaveData[];
9 years ago
private:
8 years ago
USE_TYPE triggerType;
9 years ago
};
LINK_ENTITY_TO_CLASS( trigger_relay, CTriggerRelay )
TYPEDESCRIPTION CTriggerRelay::m_SaveData[] =
9 years ago
{
DEFINE_FIELD( CTriggerRelay, triggerType, FIELD_INTEGER ),
};
IMPLEMENT_SAVERESTORE( CTriggerRelay, CBaseDelay )
9 years ago
void CTriggerRelay::KeyValue( KeyValueData *pkvd )
{
8 years ago
if( FStrEq( pkvd->szKeyName, "triggerstate" ) )
9 years ago
{
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 );
8 years ago
if( pev->spawnflags & SF_RELAY_FIREONCE )
9 years ago
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 );
8 years ago
void Spawn( void );
void EXPORT ManagerThink( void );
void EXPORT ManagerUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
9 years ago
#if _DEBUG
void EXPORT ManagerReport( void );
#endif
8 years ago
BOOL HasTarget( string_t targetname );
9 years ago
int ObjectCaps( void ) { return CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
8 years ago
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
9 years ago
8 years ago
static TYPEDESCRIPTION m_SaveData[];
9 years ago
int m_cTargets; // the total number of targets in this manager's fire list.
8 years ago
int m_index; // Current target
float m_startTime;// Time we started firing
string_t m_iTargetName[MAX_MULTI_TARGETS];// list if indexes into global string array
8 years ago
float m_flTargetDelay[MAX_MULTI_TARGETS];// delay (in seconds) from time of manager fire to target fire
9 years ago
private:
8 years ago
inline BOOL IsClone( void ) { return ( pev->spawnflags & SF_MULTIMAN_CLONE ) ? TRUE : FALSE; }
inline BOOL ShouldClone( void )
9 years ago
{
8 years ago
if( IsClone() )
9 years ago
return FALSE;
8 years ago
return ( pev->spawnflags & SF_MULTIMAN_THREAD ) ? TRUE : FALSE;
9 years ago
}
CMultiManager *Clone( void );
};
LINK_ENTITY_TO_CLASS( multi_manager, CMultiManager )
9 years ago
// Global Savedata for multi_manager
TYPEDESCRIPTION CMultiManager::m_SaveData[] =
9 years ago
{
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 ),
};
8 years ago
IMPLEMENT_SAVERESTORE( CMultiManager, CBaseToggle )
9 years ago
8 years ago
void CMultiManager::KeyValue( KeyValueData *pkvd )
9 years ago
{
// UNDONE: Maybe this should do something like this:
//CBaseToggle::KeyValue( pkvd );
8 years ago
// if( !pkvd->fHandled )
9 years ago
// ... etc.
8 years ago
if( FStrEq( pkvd->szKeyName, "wait" ) )
9 years ago
{
8 years ago
m_flWait = atof( pkvd->szValue );
9 years ago
pkvd->fHandled = TRUE;
}
else // add this field to the target list
{
// this assumes that additional fields are targetnames and their values are delay values.
8 years ago
if( m_cTargets < MAX_MULTI_TARGETS )
9 years ago
{
char tmp[128];
UTIL_StripToken( pkvd->szKeyName, tmp );
8 years ago
m_iTargetName[m_cTargets] = ALLOC_STRING( tmp );
m_flTargetDelay[m_cTargets] = atof( pkvd->szValue );
9 years ago
m_cTargets++;
pkvd->fHandled = TRUE;
}
}
}
8 years ago
void CMultiManager::Spawn( void )
9 years ago
{
pev->solid = SOLID_NOT;
SetUse( &CMultiManager::ManagerUse );
SetThink( &CMultiManager::ManagerThink );
// Sort targets
// Quick and dirty bubble sort
int swapped = 1;
8 years ago
while( swapped )
9 years ago
{
swapped = 0;
8 years ago
for( int i = 1; i < m_cTargets; i++ )
9 years ago
{
8 years ago
if( m_flTargetDelay[i] < m_flTargetDelay[i - 1] )
9 years ago
{
// Swap out of order elements
int name = m_iTargetName[i];
float delay = m_flTargetDelay[i];
8 years ago
m_iTargetName[i] = m_iTargetName[i - 1];
m_flTargetDelay[i] = m_flTargetDelay[i - 1];
m_iTargetName[i - 1] = name;
m_flTargetDelay[i - 1] = delay;
9 years ago
swapped = 1;
}
}
}
}
BOOL CMultiManager::HasTarget( string_t targetname )
{
8 years ago
for( int i = 0; i < m_cTargets; i++ )
if( FStrEq( STRING( targetname ), STRING( m_iTargetName[i] ) ) )
9 years ago
return TRUE;
9 years ago
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.
8 years ago
void CMultiManager::ManagerThink( void )
9 years ago
{
8 years ago
float time;
9 years ago
time = gpGlobals->time - m_startTime;
8 years ago
while( m_index < m_cTargets && m_flTargetDelay[m_index] <= time )
9 years ago
{
8 years ago
FireTargets( STRING( m_iTargetName[m_index] ), m_hActivator, this, USE_TOGGLE, 0 );
9 years ago
m_index++;
}
8 years ago
if( m_index >= m_cTargets )// have we fired all targets?
9 years ago
{
SetThink( NULL );
8 years ago
if( IsClone() )
9 years ago
{
UTIL_Remove( this );
return;
}
SetUse( &CMultiManager::ManagerUse );// allow manager re-use
}
else
8 years ago
pev->nextthink = m_startTime + m_flTargetDelay[m_index];
9 years ago
}
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.
8 years ago
void CMultiManager::ManagerUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
9 years ago
{
// In multiplayer games, clone the MM and execute in the clone (like a thread)
// to allow multiple players to trigger the same multimanager
8 years ago
if( ShouldClone() )
9 years ago
{
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( &CMultiManager::ManagerThink );
pev->nextthink = gpGlobals->time;
}
#if _DEBUG
8 years ago
void CMultiManager::ManagerReport( void )
9 years ago
{
8 years ago
int cIndex;
9 years ago
8 years ago
for( cIndex = 0; cIndex < m_cTargets; cIndex++ )
9 years ago
{
8 years ago
ALERT( at_console, "%s %f\n", STRING( m_iTargetName[cIndex] ), m_flTargetDelay[cIndex] );
9 years ago
}
}
#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
8 years ago
#define SF_RENDER_MASKFX ( 1 << 0 )
#define SF_RENDER_MASKAMT ( 1 << 1 )
#define SF_RENDER_MASKMODE ( 1 << 2 )
#define SF_RENDER_MASKCOLOR ( 1 << 3 )
9 years ago
class CRenderFxManager : public CBaseEntity
{
public:
void Spawn( void );
8 years ago
void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
9 years ago
};
LINK_ENTITY_TO_CLASS( env_render, CRenderFxManager )
9 years ago
8 years ago
void CRenderFxManager::Spawn( void )
9 years ago
{
pev->solid = SOLID_NOT;
}
8 years ago
void CRenderFxManager::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
9 years ago
{
8 years ago
if( !FStringNull( pev->target ) )
9 years ago
{
8 years ago
edict_t *pentTarget = NULL;
while( 1 )
9 years ago
{
8 years ago
pentTarget = FIND_ENTITY_BY_TARGETNAME( pentTarget, STRING( pev->target ) );
if( FNullEnt( pentTarget ) )
9 years ago
break;
entvars_t *pevTarget = VARS( pentTarget );
8 years ago
if( !FBitSet( pev->spawnflags, SF_RENDER_MASKFX ) )
9 years ago
pevTarget->renderfx = pev->renderfx;
8 years ago
if( !FBitSet( pev->spawnflags, SF_RENDER_MASKAMT ) )
9 years ago
pevTarget->renderamt = pev->renderamt;
8 years ago
if( !FBitSet( pev->spawnflags, SF_RENDER_MASKMODE ) )
9 years ago
pevTarget->rendermode = pev->rendermode;
8 years ago
if( !FBitSet( pev->spawnflags, SF_RENDER_MASKCOLOR ) )
9 years ago
pevTarget->rendercolor = pev->rendercolor;
}
}
}
class CBaseTrigger : public CBaseToggle
{
public:
8 years ago
void EXPORT TeleportTouch( CBaseEntity *pOther );
9 years ago
void KeyValue( KeyValueData *pkvd );
void EXPORT MultiTouch( CBaseEntity *pOther );
8 years ago
void EXPORT HurtTouch( CBaseEntity *pOther );
void EXPORT CDAudioTouch( CBaseEntity *pOther );
9 years ago
void ActivateMultiTrigger( CBaseEntity *pActivator );
void EXPORT MultiWaitOver( void );
void EXPORT CounterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
8 years ago
void EXPORT ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
9 years ago
void InitTrigger( void );
8 years ago
virtual int ObjectCaps( void ) { return CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
9 years ago
};
LINK_ENTITY_TO_CLASS( trigger, CBaseTrigger )
9 years ago
/*
================
InitTrigger
================
*/
8 years ago
void CBaseTrigger::InitTrigger()
9 years ago
{
// 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.
8 years ago
if( pev->angles != g_vecZero )
SetMovedir( pev );
9 years ago
pev->solid = SOLID_TRIGGER;
pev->movetype = MOVETYPE_NONE;
SET_MODEL( ENT( pev ), STRING( pev->model ) ); // set size and link into world
8 years ago
if( CVAR_GET_FLOAT( "showtriggers" ) == 0 )
9 years ago
SetBits( pev->effects, EF_NODRAW );
}
//
// Cache user-entity-field values until spawn is called.
//
8 years ago
void CBaseTrigger::KeyValue( KeyValueData *pkvd )
9 years ago
{
8 years ago
if( FStrEq( pkvd->szKeyName, "damage" ) )
9 years ago
{
8 years ago
pev->dmg = atof( pkvd->szValue );
9 years ago
pkvd->fHandled = TRUE;
}
8 years ago
else if( FStrEq( pkvd->szKeyName, "count" ) )
9 years ago
{
8 years ago
m_cTriggersLeft = (int)atof( pkvd->szValue );
9 years ago
pkvd->fHandled = TRUE;
}
8 years ago
else if( FStrEq( pkvd->szKeyName, "damagetype" ) )
9 years ago
{
8 years ago
m_bitsDamageInflict = atoi( pkvd->szValue );
9 years ago
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 )
9 years ago
//
// trigger_monsterjump
//
class CTriggerMonsterJump : public CBaseTrigger
{
public:
void Spawn( void );
void Touch( CBaseEntity *pOther );
void Think( void );
};
LINK_ENTITY_TO_CLASS( trigger_monsterjump, CTriggerMonsterJump )
9 years ago
8 years ago
void CTriggerMonsterJump::Spawn( void )
9 years ago
{
8 years ago
SetMovedir( pev );
8 years ago
InitTrigger();
9 years ago
pev->nextthink = 0;
pev->speed = 200;
m_flHeight = 150;
8 years ago
if( !FStringNull( pev->targetname ) )
{
// if targetted, spawn turned off
9 years ago
pev->solid = SOLID_NOT;
UTIL_SetOrigin( pev, pev->origin ); // Unlink from trigger list
SetUse( &CBaseTrigger::ToggleUse );
}
}
8 years ago
void CTriggerMonsterJump::Think( void )
9 years ago
{
pev->solid = SOLID_NOT;// kill the trigger for now !!!UNDONE
UTIL_SetOrigin( pev, pev->origin ); // Unlink from trigger list
SetThink( NULL );
}
8 years ago
void CTriggerMonsterJump::Touch( CBaseEntity *pOther )
9 years ago
{
entvars_t *pevOther = pOther->pev;
8 years ago
if( !FBitSet( pevOther->flags, FL_MONSTER ) )
{
// touched by a non-monster.
9 years ago
return;
}
pevOther->origin.z += 1.0f;
8 years ago
if( FBitSet( pevOther->flags, FL_ONGROUND ) )
{
// clear the onground so physics don't bitch
9 years ago
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 );
8 years ago
void Touch( CBaseEntity *pOther );
9 years ago
};
LINK_ENTITY_TO_CLASS( trigger_cdaudio, CTriggerCDAudio )
9 years ago
//
// Changes tracks or stops CD when player touches
//
// !!!HACK - overloaded HEALTH to avoid adding new field
8 years ago
void CTriggerCDAudio::Touch( CBaseEntity *pOther )
9 years ago
{
8 years ago
if( !pOther->IsPlayer() )
{
// only clients may trigger these events
9 years ago
return;
}
PlayTrack();
}
8 years ago
void CTriggerCDAudio::Spawn( void )
9 years ago
{
InitTrigger();
}
void CTriggerCDAudio::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
PlayTrack();
}
void PlayCDTrack( int iTrack )
{
edict_t *pClient;
9 years ago
// manually find the single player.
pClient = g_engfuncs.pfnPEntityOfEntIndex( 1 );
9 years ago
// Can't play if the client is not connected!
8 years ago
if( !pClient )
9 years ago
return;
8 years ago
if( iTrack < -1 || iTrack > 30 )
9 years ago
{
8 years ago
ALERT( at_console, "TriggerCDAudio - Track %d out of range\n" );
9 years ago
return;
}
8 years ago
if( iTrack == -1 )
9 years ago
{
CLIENT_COMMAND( pClient, "cd stop\n" );
9 years ago
}
else
{
8 years ago
char string[64];
9 years ago
sprintf( string, "cd play %3d\n", iTrack );
8 years ago
CLIENT_COMMAND( pClient, string );
9 years ago
}
}
// only plays for ONE client, so only use in single play!
8 years ago
void CTriggerCDAudio::PlayTrack( void )
9 years ago
{
PlayCDTrack( (int)pev->health );
9 years ago
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:
8 years ago
void Spawn( void );
void KeyValue( KeyValueData *pkvd );
9 years ago
8 years ago
virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
void Think( void );
void Play( void );
9 years ago
};
LINK_ENTITY_TO_CLASS( target_cdaudio, CTargetCDAudio )
9 years ago
8 years ago
void CTargetCDAudio::KeyValue( KeyValueData *pkvd )
9 years ago
{
8 years ago
if( FStrEq( pkvd->szKeyName, "radius" ) )
9 years ago
{
8 years ago
pev->scale = atof( pkvd->szValue );
9 years ago
pkvd->fHandled = TRUE;
}
else
CPointEntity::KeyValue( pkvd );
}
8 years ago
void CTargetCDAudio::Spawn( void )
9 years ago
{
pev->solid = SOLID_NOT;
pev->movetype = MOVETYPE_NONE;
8 years ago
if( pev->scale > 0 )
pev->nextthink = gpGlobals->time + 1.0f;
9 years ago
}
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;
9 years ago
// manually find the single player.
pClient = g_engfuncs.pfnPEntityOfEntIndex( 1 );
9 years ago
// Can't play if the client is not connected!
8 years ago
if( !pClient )
9 years ago
return;
pev->nextthink = gpGlobals->time + 0.5f;
9 years ago
8 years ago
if( ( pClient->v.origin - pev->origin ).Length() <= pev->scale )
9 years ago
Play();
}
void CTargetCDAudio::Play( void )
8 years ago
{
9 years ago
PlayCDTrack( (int)pev->health );
8 years ago
UTIL_Remove( this );
9 years ago
}
//=====================================
//
// 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'
8 years ago
void CTriggerHurt::Spawn( void )
9 years ago
{
InitTrigger();
SetTouch( &CBaseTrigger::HurtTouch );
8 years ago
if( !FStringNull( pev->targetname ) )
9 years ago
{
SetUse( &CBaseTrigger::ToggleUse );
}
else
{
SetUse( NULL );
}
8 years ago
if( m_bitsDamageInflict & DMG_RADIATION )
9 years ago
{
SetThink( &CTriggerHurt::RadiationThink );
8 years ago
pev->nextthink = gpGlobals->time + RANDOM_FLOAT( 0.0, 0.5 );
9 years ago
}
8 years ago
if( FBitSet( pev->spawnflags, SF_TRIGGER_HURT_START_OFF ) )// if flagged to Start Turned Off, make trigger nonsolid.
9 years ago
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
8 years ago
void CTriggerHurt::RadiationThink( void )
9 years ago
{
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.5f;
pev->view_ofs = pev->view_ofs * 0.0f;
9 years ago
8 years ago
pentPlayer = FIND_CLIENT_IN_PVS( edict() );
9 years ago
pev->origin = origin;
pev->view_ofs = view_ofs;
// reset origin
8 years ago
if( !FNullEnt( pentPlayer ) )
9 years ago
{
8 years ago
pPlayer = GetClassPtr( (CBasePlayer *)VARS( pentPlayer ) );
9 years ago
8 years ago
pevTarget = VARS( pentPlayer );
9 years ago
// get range to player;
vecSpot1 = ( pev->absmin + pev->absmax ) * 0.5f;
vecSpot2 = ( pevTarget->absmin + pevTarget->absmax ) * 0.5f;
9 years ago
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
8 years ago
if( pPlayer->m_flgeigerRange >= flRange )
9 years ago
pPlayer->m_flgeigerRange = flRange;
}
pev->nextthink = gpGlobals->time + 0.25f;
9 years ago
}
//
// ToggleUse - If this is the USE function for a trigger, its state will toggle every time it's fired
//
8 years ago
void CBaseTrigger::ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
9 years ago
{
8 years ago
if( pev->solid == SOLID_NOT )
{
// if the trigger is off, turn it on
9 years ago
pev->solid = SOLID_TRIGGER;
9 years ago
// Force retouch
gpGlobals->force_retouch++;
}
else
{
// turn the trigger off
9 years ago
pev->solid = SOLID_NOT;
}
UTIL_SetOrigin( pev, pev->origin );
}
// When touched, a hurt trigger does DMG points of damage each half-second
8 years ago
void CBaseTrigger::HurtTouch( CBaseEntity *pOther )
9 years ago
{
float fldmg;
8 years ago
if( !pOther->pev->takedamage )
9 years ago
return;
8 years ago
if( ( pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYTOUCH ) && !pOther->IsPlayer() )
9 years ago
{
// this trigger is only allowed to touch clients, and this ain't a client.
return;
}
8 years ago
if( ( pev->spawnflags & SF_TRIGGER_HURT_NO_CLIENTS ) && pOther->IsPlayer() )
9 years ago
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
8 years ago
if( g_pGameRules->IsMultiplayer() )
9 years ago
{
8 years ago
if( pev->dmgtime > gpGlobals->time )
9 years ago
{
8 years ago
if( gpGlobals->time != pev->pain_finished )
{
// too early to hurt again, and not same frame with a different entity
8 years ago
if( pOther->IsPlayer() )
9 years ago
{
8 years ago
int playerMask = 1 << ( pOther->entindex() - 1 );
9 years ago
// If I've already touched this player (this time), then bail out
8 years ago
if( pev->impulse & playerMask )
9 years ago
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;
8 years ago
if( pOther->IsPlayer() )
9 years ago
{
8 years ago
int playerMask = 1 << ( pOther->entindex() - 1 );
9 years ago
// Mark this player as touched
// BUGBUG - There can be only 32 players!
pev->impulse |= playerMask;
}
}
}
else // Original code -- single player
{
8 years ago
if( pev->dmgtime > gpGlobals->time && gpGlobals->time != pev->pain_finished )
{
// too early to hurt again, and not same frame with a different entity
9 years ago
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.5f; // 0.5 seconds worth of damage, pev->dmg is damage/second
9 years ago
// JAY: Cut this because it wasn't fully realized. Damage is simpler now.
#if 0
8 years ago
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: // UNDONE: cut this? should slow movement to 50%
fldmg = PARALYZE_DAMAGE / 4;
break;
case DMG_ACID:
fldmg = ACID_DAMAGE / 4;
break;
case DMG_SLOWBURN:
fldmg = SLOWBURN_DAMAGE / 4;
break;
case DMG_SLOWFREEZE:
fldmg = SLOWFREEZE_DAMAGE / 4;
break;
9 years ago
}
#endif
8 years ago
if( fldmg < 0 )
9 years ago
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.5f;// half second delay until this trigger can hurt toucher again
9 years ago
8 years ago
if( pev->target )
9 years ago
{
// trigger has a target it wants to fire.
8 years ago
if( pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYFIRE )
9 years ago
{
// if the toucher isn't a client, don't fire the target!
8 years ago
if( !pOther->IsPlayer() )
9 years ago
{
return;
}
}
SUB_UseTargets( pOther, USE_TOGGLE, 0 );
8 years ago
if( pev->spawnflags & SF_TRIGGER_HURT_TARGETONCE )
9 years ago
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 )
9 years ago
8 years ago
void CTriggerMultiple::Spawn( void )
9 years ago
{
8 years ago
if( m_flWait == 0 )
m_flWait = 0.2f;
9 years ago
InitTrigger();
8 years ago
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*/
9 years ago
{
SetTouch( &CBaseTrigger::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 )
9 years ago
void CTriggerOnce::Spawn( void )
{
m_flWait = -1;
8 years ago
CTriggerMultiple::Spawn();
9 years ago
}
8 years ago
void CBaseTrigger::MultiTouch( CBaseEntity *pOther )
9 years ago
{
8 years ago
entvars_t *pevToucher;
9 years ago
pevToucher = pOther->pev;
// Only touch clients, monsters, or pushables (depending on flags)
8 years ago
if( ( ( pevToucher->flags & FL_CLIENT ) && !( pev->spawnflags & SF_TRIGGER_NOCLIENTS ) ) ||
( ( pevToucher->flags & FL_MONSTER ) && (pev->spawnflags & SF_TRIGGER_ALLOWMONSTERS ) ) ||
9 years ago
( ( pev->spawnflags & SF_TRIGGER_PUSHABLES ) && FClassnameIs( pevToucher,"func_pushable" ) ) )
{
#if 0
// if the trigger has an angles field, check player's facing direction
8 years ago
if( pev->movedir != g_vecZero )
9 years ago
{
UTIL_MakeVectors( pevToucher->angles );
8 years ago
if( DotProduct( gpGlobals->v_forward, pev->movedir ) < 0 )
9 years ago
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
//
8 years ago
void CBaseTrigger::ActivateMultiTrigger( CBaseEntity *pActivator )
9 years ago
{
8 years ago
if( pev->nextthink > gpGlobals->time )
9 years ago
return; // still waiting for reset time
if( !UTIL_IsMasterTriggered( m_sMaster, pActivator ) )
9 years ago
return;
8 years ago
if( FClassnameIs( pev, "trigger_secret" ) )
9 years ago
{
8 years ago
if( pev->enemy == NULL || !FClassnameIs( pev->enemy, "player" ) )
9 years ago
return;
gpGlobals->found_secrets++;
}
8 years ago
if( !FStringNull( pev->noise ) )
EMIT_SOUND( ENT( pev ), CHAN_VOICE, STRING( pev->noise ), 1, ATTN_NORM );
9 years ago
// don't trigger again until reset
// pev->takedamage = DAMAGE_NO;
9 years ago
m_hActivator = pActivator;
SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 );
8 years ago
if( pev->message && pActivator->IsPlayer() )
9 years ago
{
8 years ago
UTIL_ShowMessage( STRING( pev->message ), pActivator );
//CLIENT_PRINTF( ENT( pActivator->pev ), print_center, STRING( pev->message ) );
9 years ago
}
8 years ago
if( m_flWait > 0 )
9 years ago
{
SetThink( &CBaseTrigger::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.1f;
9 years ago
SetThink( &CBaseEntity::SUB_Remove );
}
}
// the wait time has passed, so set back up for another activation
8 years ago
void CBaseTrigger::MultiWaitOver( void )
9 years ago
{
8 years ago
/*if( pev->max_health )
{
8 years ago
pev->health = pev->max_health;
pev->takedamage = DAMAGE_YES;
8 years ago
pev->solid = SOLID_BBOX;
}*/
9 years ago
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;
8 years ago
if( m_cTriggersLeft < 0 )
9 years ago
return;
9 years ago
BOOL fTellActivator =
8 years ago
( m_hActivator != 0 ) &&
m_hActivator->IsPlayer() &&
8 years ago
!FBitSet( pev->spawnflags, SPAWNFLAG_NOMESSAGE );
if( m_cTriggersLeft != 0 )
9 years ago
{
8 years ago
if( fTellActivator )
9 years ago
{
// UNDONE: I don't think we want these Quakesque messages
8 years ago
switch( m_cTriggersLeft )
9 years ago
{
8 years ago
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;
9 years ago
}
}
return;
}
// !!!UNDONE: I don't think we want these Quakesque messages
8 years ago
if( fTellActivator )
ALERT( at_console, "Sequence completed!" );
9 years ago
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 )
9 years ago
8 years ago
void CTriggerCounter::Spawn( void )
9 years ago
{
// 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;
8 years ago
if( m_cTriggersLeft == 0 )
9 years ago
m_cTriggersLeft = 2;
SetUse( &CBaseTrigger::CounterUse );
}
// ====================== TRIGGER_CHANGELEVEL ================================
class CTriggerVolume : public CPointEntity // Derive from point entity so this doesn't move across levels
{
public:
8 years ago
void Spawn( void );
9 years ago
};
LINK_ENTITY_TO_CLASS( trigger_transition, CTriggerVolume )
9 years ago
// Define space that travels across a level transition
8 years ago
void CTriggerVolume::Spawn( void )
9 years ago
{
pev->solid = SOLID_NOT;
pev->movetype = MOVETYPE_NONE;
SET_MODEL( ENT( pev ), STRING( pev->model ) ); // set size and link into world
pev->model = 0;
9 years ago
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 )
9 years ago
void CFireAndDie::Spawn( void )
{
8 years ago
pev->classname = MAKE_STRING( "fireanddie" );
9 years ago
// 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 );
8 years ago
void EXPORT UseChangeLevel( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
9 years ago
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 );
8 years ago
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
9 years ago
8 years ago
static TYPEDESCRIPTION m_SaveData[];
9 years ago
char m_szMapName[cchMapNameMost]; // trigger_changelevel only: next map
char m_szLandmarkName[cchMapNameMost]; // trigger_changelevel only: landmark on next map
string_t m_changeTarget;
8 years ago
float m_changeTargetDelay;
9 years ago
};
LINK_ENTITY_TO_CLASS( trigger_changelevel, CChangeLevel )
9 years ago
// Global Savedata for changelevel trigger
TYPEDESCRIPTION CChangeLevel::m_SaveData[] =
9 years ago
{
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 )
9 years ago
//
// Cache user-entity-field values until spawn is called.
//
8 years ago
void CChangeLevel::KeyValue( KeyValueData *pkvd )
9 years ago
{
8 years ago
if( FStrEq( pkvd->szKeyName, "map" ) )
9 years ago
{
8 years ago
if( strlen( pkvd->szValue ) >= cchMapNameMost )
9 years ago
ALERT( at_error, "Map name '%s' too long (32 chars)\n", pkvd->szValue );
8 years ago
strcpy( m_szMapName, pkvd->szValue );
9 years ago
pkvd->fHandled = TRUE;
}
8 years ago
else if( FStrEq( pkvd->szKeyName, "landmark" ) )
9 years ago
{
8 years ago
if( strlen( pkvd->szValue ) >= cchMapNameMost )
9 years ago
ALERT( at_error, "Landmark name '%s' too long (32 chars)\n", pkvd->szValue );
8 years ago
strcpy( m_szLandmarkName, pkvd->szValue );
9 years ago
pkvd->fHandled = TRUE;
}
8 years ago
else if( FStrEq( pkvd->szKeyName, "changetarget" ) )
9 years ago
{
m_changeTarget = ALLOC_STRING( pkvd->szValue );
pkvd->fHandled = TRUE;
}
8 years ago
else if( FStrEq( pkvd->szKeyName, "changedelay" ) )
9 years ago
{
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.
*/
8 years ago
void CChangeLevel::Spawn( void )
9 years ago
{
8 years ago
if( FStrEq( m_szMapName, "" ) )
9 years ago
ALERT( at_console, "a trigger_changelevel doesn't have a map" );
8 years ago
if( FStrEq( m_szLandmarkName, "" ) )
9 years ago
ALERT( at_console, "trigger_changelevel to %s doesn't have a landmark\n", m_szMapName );
8 years ago
if( !FStringNull( pev->targetname ) )
9 years ago
{
SetUse( &CChangeLevel::UseChangeLevel );
}
InitTrigger();
8 years ago
if( !( pev->spawnflags & SF_CHANGELEVEL_USEONLY ) )
9 years ago
SetTouch( &CChangeLevel::TouchChangeLevel );
//ALERT( at_console, "TRANSITION: %s (%s)\n", m_szMapName, m_szLandmarkName );
9 years ago
}
8 years ago
void CChangeLevel::ExecuteChangeLevel( void )
9 years ago
{
MESSAGE_BEGIN( MSG_ALL, SVC_CDTRACK );
WRITE_BYTE( 3 );
WRITE_BYTE( 3 );
MESSAGE_END();
8 years ago
MESSAGE_BEGIN( MSG_ALL, SVC_INTERMISSION );
9 years ago
MESSAGE_END();
}
FILE_GLOBAL char st_szNextMap[cchMapNameMost];
FILE_GLOBAL char st_szNextSpot[cchMapNameMost];
8 years ago
edict_t *CChangeLevel::FindLandmark( const char *pLandmarkName )
9 years ago
{
edict_t *pentLandmark;
pentLandmark = FIND_ENTITY_BY_STRING( NULL, "targetname", pLandmarkName );
8 years ago
while( !FNullEnt( pentLandmark ) )
9 years ago
{
// Found the landmark
8 years ago
if( FClassnameIs( pentLandmark, "info_landmark" ) )
9 years ago
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.
//
//=========================================================
8 years ago
void CChangeLevel::UseChangeLevel( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
9 years ago
{
ChangeLevelNow( pActivator );
}
8 years ago
void CChangeLevel::ChangeLevelNow( CBaseEntity *pActivator )
9 years ago
{
edict_t *pentLandmark;
//LEVELLIST levels[16];
9 years ago
8 years ago
ASSERT( !FStrEq( m_szMapName, "" ) );
9 years ago
// Don't work in deathmatch
8 years ago
if( g_pGameRules->IsDeathmatch() )
9 years ago
return;
// Some people are firing these multiple times in a frame, disable
8 years ago
if( gpGlobals->time == pev->dmgtime )
9 years ago
return;
pev->dmgtime = gpGlobals->time;
CBaseEntity *pPlayer = CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) );
8 years ago
if( !InTransitionVolume( pPlayer, m_szLandmarkName ) )
9 years ago
{
ALERT( at_aiconsole, "Player isn't in the transition volume %s, aborting\n", m_szLandmarkName );
return;
}
// Create an entity to fire the changetarget
8 years ago
if( m_changeTarget )
9 years ago
{
CFireAndDie *pFireAndDie = GetClassPtr( (CFireAndDie *)NULL );
8 years ago
if( pFireAndDie )
9 years ago
{
// Set target and delay
pFireAndDie->pev->target = m_changeTarget;
pFireAndDie->m_flDelay = m_changeTargetDelay;
pFireAndDie->pev->origin = pPlayer->pev->origin;
9 years ago
// Call spawn
DispatchSpawn( pFireAndDie->edict() );
}
}
9 years ago
// This object will get removed in the call to CHANGE_LEVEL, copy the params into "safe" memory
8 years ago
strcpy( st_szNextMap, m_szMapName );
9 years ago
m_hActivator = pActivator;
SUB_UseTargets( pActivator, USE_TOGGLE, 0 );
st_szNextSpot[0] = 0; // Init landmark to NULL
8 years ago
// look for a landmark entity
9 years ago
pentLandmark = FindLandmark( m_szLandmarkName );
8 years ago
if( !FNullEnt( pentLandmark ) )
9 years ago
{
8 years ago
strcpy( st_szNextSpot, m_szLandmarkName );
gpGlobals->vecLandmarkOffset = VARS( pentLandmark )->origin;
9 years ago
}
//ALERT( at_console, "Level touches %d levels\n", ChangeList( levels, 16 ) );
9 years ago
ALERT( at_console, "CHANGE LEVEL: %s %s\n", st_szNextMap, st_szNextSpot );
CHANGE_LEVEL( st_szNextMap, st_szNextSpot );
}
//
// GLOBALS ASSUMED SET: st_szNextMap
//
8 years ago
void CChangeLevel::TouchChangeLevel( CBaseEntity *pOther )
9 years ago
{
if( !pOther->IsPlayer() )
9 years ago
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;
8 years ago
if( !pLevelList || !pMapName || !pLandmarkName || !pentLandmark )
9 years ago
return 0;
8 years ago
for( i = 0; i < listCount; i++ )
9 years ago
{
8 years ago
if( pLevelList[i].pentLandmark == pentLandmark && strcmp( pLevelList[i].mapName, pMapName ) == 0 )
9 years ago
return 0;
}
strcpy( pLevelList[listCount].mapName, pMapName );
strcpy( pLevelList[listCount].landmarkName, pLandmarkName );
pLevelList[listCount].pentLandmark = pentLandmark;
8 years ago
pLevelList[listCount].vecLandmarkOrigin = VARS( pentLandmark )->origin;
9 years ago
return 1;
}
int BuildChangeList( LEVELLIST *pLevelList, int maxList )
{
return CChangeLevel::ChangeList( pLevelList, maxList );
}
int CChangeLevel::InTransitionVolume( CBaseEntity *pEntity, char *pVolumeName )
{
edict_t *pentVolume;
8 years ago
if( pEntity->ObjectCaps() & FCAP_FORCE_TRANSITION )
9 years ago
return 1;
// If you're following another entity, follow it through the transition (weapons follow the player)
8 years ago
if( pEntity->pev->movetype == MOVETYPE_FOLLOW )
9 years ago
{
8 years ago
if( pEntity->pev->aiment != NULL )
9 years ago
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 );
8 years ago
while( !FNullEnt( pentVolume ) )
9 years ago
{
CBaseEntity *pVolume = CBaseEntity::Instance( pentVolume );
8 years ago
if( pVolume && FClassnameIs( pVolume->pev, "trigger_transition" ) )
9 years ago
{
8 years ago
if( pVolume->Intersects( pEntity ) ) // It touches one, it's in the volume
9 years ago
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;
8 years ago
int i, count;
9 years ago
count = 0;
// Find all of the possible level changes on this BSP
pentChangelevel = FIND_ENTITY_BY_STRING( NULL, "classname", "trigger_changelevel" );
8 years ago
if( FNullEnt( pentChangelevel ) )
9 years ago
return 0;
8 years ago
while( !FNullEnt( pentChangelevel ) )
9 years ago
{
CChangeLevel *pTrigger;
9 years ago
pTrigger = GetClassPtr((CChangeLevel *)VARS(pentChangelevel));
8 years ago
if( pTrigger )
9 years ago
{
// Find the corresponding landmark
pentLandmark = FindLandmark( pTrigger->m_szLandmarkName );
8 years ago
if( pentLandmark )
9 years ago
{
// Build a list of unique transitions
8 years ago
if( AddTransitionToList( pLevelList, count, pTrigger->m_szMapName, pTrigger->m_szLandmarkName, pentLandmark ) )
9 years ago
{
count++;
8 years ago
if( count >= maxList ) // FULL!!
9 years ago
break;
}
}
}
pentChangelevel = FIND_ENTITY_BY_STRING( pentChangelevel, "classname", "trigger_changelevel" );
}
8 years ago
if( gpGlobals->pSaveData && ( (SAVERESTOREDATA *)gpGlobals->pSaveData)->pTable )
9 years ago
{
CSave saveHelper( (SAVERESTOREDATA *)gpGlobals->pSaveData );
8 years ago
for( i = 0; i < count; i++ )
9 years ago
{
int j, entityCount = 0;
8 years ago
CBaseEntity *pEntList[MAX_ENTITY];
int entityFlags[MAX_ENTITY];
9 years ago
// 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)
8 years ago
while( !FNullEnt( pent ) )
9 years ago
{
8 years ago
CBaseEntity *pEntity = CBaseEntity::Instance( pent );
if( pEntity )
9 years ago
{
8 years ago
//ALERT( at_console, "Trying %s\n", STRING( pEntity->pev->classname ) );
9 years ago
int caps = pEntity->ObjectCaps();
8 years ago
if( !(caps & FCAP_DONT_SAVE ) )
9 years ago
{
int flags = 0;
// If this entity can be moved or is global, mark it
8 years ago
if( caps & FCAP_ACROSS_TRANSITION )
9 years ago
flags |= FENTTABLE_MOVEABLE;
8 years ago
if( pEntity->pev->globalname && !pEntity->IsDormant() )
9 years ago
flags |= FENTTABLE_GLOBAL;
8 years ago
if( flags )
9 years ago
{
8 years ago
pEntList[entityCount] = pEntity;
entityFlags[entityCount] = flags;
9 years ago
entityCount++;
8 years ago
if( entityCount > MAX_ENTITY )
9 years ago
ALERT( at_error, "Too many entities across a transition!" );
}
//else
8 years ago
// ALERT( at_console, "Failed %s\n", STRING( pEntity->pev->classname ) );
9 years ago
}
//else
8 years ago
// ALERT( at_console, "DON'T SAVE %s\n", STRING( pEntity->pev->classname ) );
9 years ago
}
pent = pent->v.chain;
}
8 years ago
for( j = 0; j < entityCount; j++ )
9 years ago
{
// Check to make sure the entity isn't screened out by a trigger_transition
8 years ago
if( entityFlags[j] && InTransitionVolume( pEntList[j], pLevelList[i].landmarkName ) )
9 years ago
{
// Mark entity table with 1<<i
int index = saveHelper.EntityIndex( pEntList[j] );
9 years ago
// Flag it with the level number
8 years ago
saveHelper.EntityFlagsSet( index, entityFlags[j] | ( 1 << i ) );
9 years ago
}
//else
8 years ago
// ALERT( at_console, "Screened out %s\n", STRING( pEntList[j]->pev->classname ) );
9 years ago
}
}
}
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;
9 years ago
// find a trigger_changelevel
8 years ago
pent = FIND_ENTITY_BY_CLASSNAME( NULL, "trigger_changelevel" );
9 years ago
// go back to start if no trigger_changelevel
8 years ago
if( FNullEnt( pent ) )
9 years ago
{
gpGlobals->mapname = MAKE_STRING( "start" );
9 years ago
pChange = GetClassPtr( (CChangeLevel *)NULL );
8 years ago
strcpy( pChange->m_szMapName, "start" );
9 years ago
}
else
8 years ago
pChange = GetClassPtr( (CChangeLevel *)VARS( pent ) );
8 years ago
strcpy( st_szNextMap, pChange->m_szMapName );
9 years ago
g_fGameOver = TRUE;
8 years ago
if( pChange->pev->nextthink < gpGlobals->time )
9 years ago
{
pChange->SetThink( &CChangeLevel::ExecuteChangeLevel );
pChange->pev->nextthink = gpGlobals->time + 0.1f;
9 years ago
}
}
// ============================== LADDER =======================================
class CLadder : public CBaseTrigger
{
public:
void KeyValue( KeyValueData *pkvd );
void Spawn( void );
void Precache( void );
};
LINK_ENTITY_TO_CLASS( func_ladder, CLadder )
9 years ago
8 years ago
void CLadder::KeyValue( KeyValueData *pkvd )
9 years ago
{
CBaseTrigger::KeyValue( pkvd );
}
//=========================================================
// func_ladder - makes an area vertically negotiable
//=========================================================
8 years ago
void CLadder::Precache( void )
9 years ago
{
// Do all of this in here because we need to 'convert' old saved games
pev->solid = SOLID_NOT;
pev->skin = CONTENTS_LADDER;
8 years ago
if( CVAR_GET_FLOAT( "showtriggers" ) == 0 )
9 years ago
{
pev->rendermode = kRenderTransTexture;
pev->renderamt = 0;
}
pev->effects &= ~EF_NODRAW;
}
8 years ago
void CLadder::Spawn( void )
9 years ago
{
Precache();
8 years ago
SET_MODEL( ENT( pev ), STRING( pev->model ) ); // set size and link into world
9 years ago
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 )
9 years ago
8 years ago
void CTriggerPush::KeyValue( KeyValueData *pkvd )
9 years ago
{
CBaseTrigger::KeyValue( pkvd );
}
/*QUAKED trigger_push (.5 .5 .5) ? TRIG_PUSH_ONCE
Pushes the player
*/
8 years ago
void CTriggerPush::Spawn()
9 years ago
{
8 years ago
if( pev->angles == g_vecZero )
9 years ago
pev->angles.y = 360;
InitTrigger();
8 years ago
if( pev->speed == 0 )
9 years ago
pev->speed = 100;
// this flag was changed and flying barrels on c2a5 stay broken
8 years ago
if( FStrEq( STRING( gpGlobals->mapname ), "c2a5" ) && pev->spawnflags & 4 )
9 years ago
pev->spawnflags |= SF_TRIG_PUSH_ONCE;
8 years ago
if( FBitSet( pev->spawnflags, SF_TRIGGER_PUSH_START_OFF ) )// if flagged to Start Turned Off, make trigger nonsolid.
9 years ago
pev->solid = SOLID_NOT;
SetUse( &CBaseTrigger::ToggleUse );
UTIL_SetOrigin( pev, pev->origin ); // Link into the list
}
8 years ago
void CTriggerPush::Touch( CBaseEntity *pOther )
9 years ago
{
8 years ago
entvars_t *pevToucher = pOther->pev;
9 years ago
// 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;
}
8 years ago
if( pevToucher->solid != SOLID_NOT && pevToucher->solid != SOLID_BSP )
9 years ago
{
// Instant trigger, just transfer velocity and remove
8 years ago
if( FBitSet( pev->spawnflags, SF_TRIG_PUSH_ONCE ) )
9 years ago
{
8 years ago
pevToucher->velocity = pevToucher->velocity + ( pev->speed * pev->movedir );
if( pevToucher->velocity.z > 0 )
9 years ago
pevToucher->flags &= ~FL_ONGROUND;
UTIL_Remove( this );
}
else
{
// Push field, transfer to base velocity
8 years ago
Vector vecPush = pev->speed * pev->movedir;
if( pevToucher->flags & FL_BASEVELOCITY )
vecPush = vecPush + pevToucher->basevelocity;
9 years ago
pevToucher->basevelocity = vecPush;
pevToucher->flags |= FL_BASEVELOCITY;
//ALERT( at_console, "Vel %f, base %f\n", pevToucher->velocity.z, pevToucher->basevelocity.z );
9 years ago
}
}
}
//======================================
// teleport trigger
//
//
8 years ago
void CBaseTrigger::TeleportTouch( CBaseEntity *pOther )
9 years ago
{
8 years ago
entvars_t *pevToucher = pOther->pev;
9 years ago
edict_t *pentTarget = NULL;
// Only teleport monsters or clients
8 years ago
if( !FBitSet( pevToucher->flags, FL_CLIENT | FL_MONSTER ) )
9 years ago
return;
8 years ago
if( !UTIL_IsMasterTriggered( m_sMaster, pOther ) )
9 years ago
return;
8 years ago
if( !( pev->spawnflags & SF_TRIGGER_ALLOWMONSTERS ) )
{
// no monsters allowed!
8 years ago
if( FBitSet( pevToucher->flags, FL_MONSTER ) )
9 years ago
{
return;
}
}
8 years ago
if( ( pev->spawnflags & SF_TRIGGER_NOCLIENTS ) )
{
// no clients allowed
8 years ago
if( pOther->IsPlayer() )
9 years ago
{
return;
}
}
8 years ago
pentTarget = FIND_ENTITY_BY_TARGETNAME( pentTarget, STRING( pev->target ) );
if( FNullEnt( pentTarget ) )
9 years ago
return;
9 years ago
Vector tmp = VARS( pentTarget )->origin;
8 years ago
if( pOther->IsPlayer() )
9 years ago
{
tmp.z -= pOther->pev->mins.z;// make origin adjustments in case the teleportee is a player. (origin in center, not at feet)
}
tmp.z++;
pevToucher->flags &= ~FL_ONGROUND;
9 years ago
UTIL_SetOrigin( pevToucher, tmp );
pevToucher->angles = pentTarget->v.angles;
8 years ago
if( pOther->IsPlayer() )
9 years ago
{
pevToucher->v_angle = pentTarget->v.angles;
}
pevToucher->fixangle = TRUE;
pevToucher->velocity = pevToucher->basevelocity = g_vecZero;
}
class CTriggerTeleport : public CBaseTrigger
{
public:
void Spawn( void );
};
LINK_ENTITY_TO_CLASS( trigger_teleport, CTriggerTeleport )
9 years ago
8 years ago
void CTriggerTeleport::Spawn( void )
9 years ago
{
InitTrigger();
SetTouch( &CBaseTrigger::TeleportTouch );
}
LINK_ENTITY_TO_CLASS( info_teleport_destination, CPointEntity )
9 years ago
class CTriggerSave : public CBaseTrigger
{
public:
void Spawn( void );
void EXPORT SaveTouch( CBaseEntity *pOther );
};
LINK_ENTITY_TO_CLASS( trigger_autosave, CTriggerSave )
9 years ago
void CTriggerSave::Spawn( void )
{
8 years ago
if( g_pGameRules->IsDeathmatch() )
9 years ago
{
8 years ago
REMOVE_ENTITY( ENT( pev ) );
9 years ago
return;
}
InitTrigger();
SetTouch( &CTriggerSave::SaveTouch );
}
void CTriggerSave::SaveTouch( CBaseEntity *pOther )
{
8 years ago
if( !UTIL_IsMasterTriggered( m_sMaster, pOther ) )
9 years ago
return;
// Only save on clients
8 years ago
if( !pOther->IsPlayer() )
9 years ago
return;
9 years ago
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 )
9 years ago
void CTriggerEndSection::EndSectionUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
// Only save on clients
8 years ago
if( pActivator && !pActivator->IsNetClient() )
9 years ago
return;
9 years ago
SetUse( NULL );
8 years ago
if( pev->message )
9 years ago
{
8 years ago
g_engfuncs.pfnEndSection( STRING( pev->message ) );
9 years ago
}
UTIL_Remove( this );
}
void CTriggerEndSection::Spawn( void )
{
8 years ago
if( g_pGameRules->IsDeathmatch() )
9 years ago
{
8 years ago
REMOVE_ENTITY( ENT( pev ) );
9 years ago
return;
}
InitTrigger();
SetUse( &CTriggerEndSection::EndSectionUse );
9 years ago
// If it is a "use only" trigger, then don't set the touch function.
8 years ago
if( !( pev->spawnflags & SF_ENDSECTION_USEONLY ) )
9 years ago
SetTouch( &CTriggerEndSection::EndSectionTouch );
}
void CTriggerEndSection::EndSectionTouch( CBaseEntity *pOther )
{
// Only save on clients
8 years ago
if( !pOther->IsNetClient() )
9 years ago
return;
9 years ago
SetTouch( NULL );
8 years ago
if( pev->message )
9 years ago
{
8 years ago
g_engfuncs.pfnEndSection( STRING( pev->message ) );
9 years ago
}
UTIL_Remove( this );
}
8 years ago
void CTriggerEndSection::KeyValue( KeyValueData *pkvd )
9 years ago
{
8 years ago
if( FStrEq( pkvd->szKeyName, "section" ) )
9 years ago
{
//m_iszSectionName = ALLOC_STRING( pkvd->szValue );
9 years ago
// 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 )
9 years ago
void CTriggerGravity::Spawn( void )
{
InitTrigger();
SetTouch( &CTriggerGravity::GravityTouch );
}
void CTriggerGravity::GravityTouch( CBaseEntity *pOther )
{
// Only save on clients
8 years ago
if( !pOther->IsPlayer() )
9 years ago
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; }
8 years ago
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
9 years ago
8 years ago
static TYPEDESCRIPTION m_SaveData[];
9 years ago
private:
string_t m_iszNewTarget;
9 years ago
};
LINK_ENTITY_TO_CLASS( trigger_changetarget, CTriggerChangeTarget )
TYPEDESCRIPTION CTriggerChangeTarget::m_SaveData[] =
9 years ago
{
DEFINE_FIELD( CTriggerChangeTarget, m_iszNewTarget, FIELD_STRING ),
};
8 years ago
IMPLEMENT_SAVERESTORE( CTriggerChangeTarget,CBaseDelay )
9 years ago
void CTriggerChangeTarget::KeyValue( KeyValueData *pkvd )
{
8 years ago
if( FStrEq( pkvd->szKeyName, "m_iszNewTarget" ) )
9 years ago
{
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 ) );
8 years ago
if( pTarget )
9 years ago
{
pTarget->pev->target = m_iszNewTarget;
8 years ago
CBaseMonster *pMonster = pTarget->MyMonsterPointer();
if( pMonster )
9 years ago
{
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 );
8 years ago
void Move( void );
9 years ago
8 years ago
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
static TYPEDESCRIPTION m_SaveData[];
9 years ago
EHANDLE m_hPlayer;
EHANDLE m_hTarget;
CBaseEntity *m_pentPath;
string_t m_sPath;
9 years ago
float m_flWait;
float m_flReturnTime;
float m_flStopTime;
float m_moveDistance;
float m_targetSpeed;
float m_initialSpeed;
float m_acceleration;
float m_deceleration;
8 years ago
int m_state;
BOOL m_bPlaying;
9 years ago
};
LINK_ENTITY_TO_CLASS( trigger_camera, CTriggerCamera )
9 years ago
// Global Savedata for changelevel friction modifier
8 years ago
TYPEDESCRIPTION CTriggerCamera::m_SaveData[] =
9 years ago
{
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 ),
DEFINE_FIELD( CTriggerCamera, m_bPlaying, FIELD_BOOLEAN ),
9 years ago
};
8 years ago
IMPLEMENT_SAVERESTORE( CTriggerCamera, CBaseDelay )
9 years ago
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;
8 years ago
if( m_acceleration == 0 )
9 years ago
m_acceleration = 500;
8 years ago
if( m_deceleration == 0 )
9 years ago
m_deceleration = 500;
m_bPlaying = FALSE;
9 years ago
}
8 years ago
void CTriggerCamera::KeyValue( KeyValueData *pkvd )
9 years ago
{
8 years ago
if( FStrEq( pkvd->szKeyName, "wait" ) )
9 years ago
{
8 years ago
m_flWait = atof( pkvd->szValue );
9 years ago
pkvd->fHandled = TRUE;
}
8 years ago
else if( FStrEq(pkvd->szKeyName, "moveto" ) )
9 years ago
{
m_sPath = ALLOC_STRING( pkvd->szValue );
pkvd->fHandled = TRUE;
}
8 years ago
else if( FStrEq( pkvd->szKeyName, "acceleration" ) )
9 years ago
{
m_acceleration = atof( pkvd->szValue );
pkvd->fHandled = TRUE;
}
8 years ago
else if( FStrEq(pkvd->szKeyName, "deceleration" ) )
9 years ago
{
m_deceleration = atof( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else
CBaseDelay::KeyValue( pkvd );
}
void CTriggerCamera::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
8 years ago
if( !ShouldToggle( useType, m_state ) )
9 years ago
return;
// Toggle state
m_state = !m_state;
8 years ago
if( m_state == 0 )
9 years ago
{
m_flReturnTime = gpGlobals->time;
return;
}
8 years ago
if( !pActivator || !pActivator->IsPlayer() )
9 years ago
{
8 years ago
pActivator = CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) );
9 years ago
}
8 years ago
9 years ago
m_hPlayer = pActivator;
m_flReturnTime = gpGlobals->time + m_flWait;
pev->speed = m_initialSpeed;
m_targetSpeed = m_initialSpeed;
8 years ago
if( FBitSet( pev->spawnflags, SF_CAMERA_PLAYER_TARGET ) )
9 years ago
{
m_hTarget = m_hPlayer;
}
else
{
m_hTarget = GetNextTarget();
}
// Nothing to look at!
if( m_hTarget == 0 )
9 years ago
{
return;
}
8 years ago
if( FBitSet( pev->spawnflags, SF_CAMERA_PLAYER_TAKECONTROL ) )
9 years ago
{
8 years ago
( (CBasePlayer *)pActivator )->EnableControl( FALSE );
9 years ago
}
8 years ago
if( m_sPath )
9 years ago
{
8 years ago
m_pentPath = Instance( FIND_ENTITY_BY_TARGETNAME( NULL, STRING( m_sPath ) ) );
9 years ago
}
else
{
m_pentPath = NULL;
}
m_flStopTime = gpGlobals->time;
8 years ago
if( m_pentPath )
9 years ago
{
8 years ago
if( m_pentPath->pev->speed != 0 )
9 years ago
m_targetSpeed = m_pentPath->pev->speed;
m_flStopTime += m_pentPath->GetDelay();
}
// copy over player information
8 years ago
if( FBitSet(pev->spawnflags, SF_CAMERA_PLAYER_POSITION ) )
9 years ago
{
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() );
8 years ago
SET_MODEL( ENT( pev ), STRING( pActivator->pev->model ) );
9 years ago
// follow the player down
SetThink( &CTriggerCamera::FollowTarget );
pev->nextthink = gpGlobals->time;
if( FStrEq( STRING( pev->targetname ), "cam1" )
&& FStrEq( STRING( gpGlobals->mapname ), "ops_intro" )
4 years ago
&& !g_fIsXash3D
&& CVAR_GET_POINTER( "gl_overbright" )
&& !m_bPlaying ) // if we're not playing, start playing!
{
m_bPlaying = TRUE;
CLIENT_COMMAND( ENT( pActivator->pev ), "playaudio Half-Life03.mp3\n" );
}
9 years ago
m_moveDistance = 0;
Move();
}
8 years ago
void CTriggerCamera::FollowTarget()
9 years ago
{
if( m_hPlayer == 0 )
9 years ago
return;
if( m_hTarget == 0 || m_flReturnTime < gpGlobals->time )
9 years ago
{
8 years ago
if( m_hPlayer->IsAlive() )
9 years ago
{
SET_VIEW( m_hPlayer->edict(), m_hPlayer->edict() );
8 years ago
( (CBasePlayer *)( (CBaseEntity *)m_hPlayer ) )->EnableControl( TRUE );
9 years ago
}
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;
8 years ago
if( pev->angles.y > 360 )
9 years ago
pev->angles.y -= 360;
8 years ago
if( pev->angles.y < 0 )
9 years ago
pev->angles.y += 360;
float dx = vecGoal.x - pev->angles.x;
float dy = vecGoal.y - pev->angles.y;
8 years ago
if( dx < -180 )
9 years ago
dx += 360;
8 years ago
if( dx > 180 )
9 years ago
dx = dx - 360;
8 years ago
if( dy < -180 )
9 years ago
dy += 360;
8 years ago
if( dy > 180 )
9 years ago
dy = dy - 360;
pev->avelocity.x = dx * 40 * 0.01f;
pev->avelocity.y = dy * 40 * 0.01f;
9 years ago
8 years ago
if( !( FBitSet( pev->spawnflags, SF_CAMERA_PLAYER_TAKECONTROL ) ) )
9 years ago
{
pev->velocity = pev->velocity * 0.8f;
if( pev->velocity.Length() < 10.0f )
9 years ago
pev->velocity = g_vecZero;
}
pev->nextthink = gpGlobals->time;
Move();
}
void CTriggerCamera::Move()
{
// Not moving on a path, return
8 years ago
if( !m_pentPath )
9 years ago
return;
// Subtract movement from the previous frame
m_moveDistance -= pev->speed * gpGlobals->frametime;
// Have we moved enough to reach the target?
8 years ago
if( m_moveDistance <= 0 )
9 years ago
{
// Fire the passtarget if there is one
8 years ago
if( m_pentPath->pev->message )
9 years ago
{
8 years ago
FireTargets( STRING( m_pentPath->pev->message ), this, this, USE_TOGGLE, 0 );
if( FBitSet( m_pentPath->pev->spawnflags, SF_CORNER_FIREONCE ) )
9 years ago
m_pentPath->pev->message = 0;
}
9 years ago
// Time to go to the next target
m_pentPath = m_pentPath->GetNextTarget();
// Set up next corner
8 years ago
if( !m_pentPath )
9 years ago
{
pev->velocity = g_vecZero;
}
else
{
8 years ago
if( m_pentPath->pev->speed != 0 )
9 years ago
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();
}
}
8 years ago
if( m_flStopTime > gpGlobals->time )
9 years ago
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;
8 years ago
pev->velocity = ( ( pev->movedir * pev->speed ) * fraction ) + ( pev->velocity * ( 1 - fraction ) );
9 years ago
}
//
// Adapted from TWHL - Using mp3s in Steam
//
#define SF_REMOVE_ON_FIRE 1
class CTargetMP3Audio : public CBaseTrigger
{
public:
int Save( CSave &save );
int Restore( CRestore &restore );
static TYPEDESCRIPTION m_SaveData[];
void Spawn( void );
void KeyValue( KeyValueData *pkvd );
void Touch( CBaseEntity *pOther );
string_t m_iszTrack;
BOOL m_bPlaying;
};
LINK_ENTITY_TO_CLASS( trigger_mp3audio, CTargetMP3Audio );
TYPEDESCRIPTION CTargetMP3Audio::m_SaveData[] =
{
DEFINE_FIELD( CTargetMP3Audio, m_iszTrack, FIELD_STRING ),
DEFINE_FIELD( CTargetMP3Audio, m_bPlaying, FIELD_BOOLEAN ),
};
IMPLEMENT_SAVERESTORE( CTargetMP3Audio, CBaseTrigger );
void CTargetMP3Audio::KeyValue( KeyValueData *pkvd )
{
if( FStrEq( pkvd->szKeyName, "track" ) )
{
if( FStrEq( pkvd->szValue, "hl28.mp3" ) )
m_iszTrack = MAKE_STRING( "Suspense07.mp3" );
else
m_iszTrack = ALLOC_STRING( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else
CBaseTrigger::KeyValue( pkvd );
}
void CTargetMP3Audio::Spawn( void )
{
InitTrigger();
m_bPlaying = FALSE; // start out not playing
}
void CTargetMP3Audio::Touch( CBaseEntity *pOther )
{
char command[64];
if( !pOther || !pOther->IsPlayer() )
return;
if( !m_bPlaying ) // if we're not playing, start playing!
{
m_bPlaying = TRUE;
// issue the play/loop command
sprintf( command, "playaudio %s\n", STRING( m_iszTrack ) );
CLIENT_COMMAND( pOther->edict(), command );
}
/*else
{
// if we're already playing, stop the mp3
m_bPlaying = FALSE;
CLIENT_COMMAND( pOther->edict(), "stopaudio\n" );
return;
}*/
if( pev->spawnflags & SF_REMOVE_ON_FIRE )
UTIL_Remove( this );
}