mirror of
https://github.com/shawns-valve/halflife.git
synced 2024-11-22 04:21:30 +00:00
2430 lines
61 KiB
C++
2430 lines
61 KiB
C++
/***
|
|
*
|
|
* Copyright (c) 1996-2001, 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 void SetMovedir(entvars_t* pev);
|
|
extern Vector VecBModelOrigin( entvars_t* pevBModel );
|
|
|
|
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( &CFrictionModifier::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 ( &CMultiManager::ManagerUse );
|
|
SetThink ( &CMultiManager::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 ( &CMultiManager::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 ( &CMultiManager::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 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 );
|
|
|
|
//
|
|
// 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( &CTriggerMonsterJump::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 stop\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);
|
|
}
|
|
|
|
//=====================================
|
|
|
|
//
|
|
// 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 ( &CTriggerHurt::HurtTouch );
|
|
|
|
if ( !FStringNull ( pev->targetname ) )
|
|
{
|
|
SetUse ( &CTriggerHurt::ToggleUse );
|
|
}
|
|
else
|
|
{
|
|
SetUse ( NULL );
|
|
}
|
|
|
|
if (m_bitsDamageInflict & DMG_RADIATION)
|
|
{
|
|
SetThink ( &CTriggerHurt::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( &CTriggerMultiple::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( &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.1;
|
|
SetThink( &CBaseTrigger::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( &CTriggerCounter::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 ( &CChangeLevel::UseChangeLevel );
|
|
}
|
|
InitTrigger();
|
|
if ( !(pev->spawnflags & SF_CHANGELEVEL_USEONLY) )
|
|
SetTouch( &CChangeLevel::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( &CTriggerPush::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;
|
|
}
|
|
|
|
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 trigger
|
|
//
|
|
//
|
|
|
|
void CBaseTrigger :: TeleportTouch( CBaseEntity *pOther )
|
|
{
|
|
entvars_t* pevToucher = pOther->pev;
|
|
edict_t *pentTarget = NULL;
|
|
|
|
// Only teleport monsters or clients
|
|
if ( !FBitSet( pevToucher->flags, FL_CLIENT|FL_MONSTER ) )
|
|
return;
|
|
|
|
if (!UTIL_IsMasterTriggered(m_sMaster, pOther))
|
|
return;
|
|
|
|
if ( !( pev->spawnflags & SF_TRIGGER_ALLOWMONSTERS ) )
|
|
{// no monsters allowed!
|
|
if ( FBitSet( pevToucher->flags, FL_MONSTER ) )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( ( pev->spawnflags & SF_TRIGGER_NOCLIENTS ) )
|
|
{// no clients allowed
|
|
if ( pOther->IsPlayer() )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
pentTarget = FIND_ENTITY_BY_TARGETNAME( pentTarget, STRING(pev->target) );
|
|
if (FNullEnt(pentTarget))
|
|
return;
|
|
|
|
Vector tmp = VARS( pentTarget )->origin;
|
|
|
|
if ( pOther->IsPlayer() )
|
|
{
|
|
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;
|
|
|
|
UTIL_SetOrigin( pevToucher, tmp );
|
|
|
|
pevToucher->angles = pentTarget->v.angles;
|
|
|
|
if ( pOther->IsPlayer() )
|
|
{
|
|
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 );
|
|
|
|
void CTriggerTeleport :: Spawn( void )
|
|
{
|
|
InitTrigger();
|
|
|
|
SetTouch( &CTriggerTeleport::TeleportTouch );
|
|
}
|
|
|
|
|
|
LINK_ENTITY_TO_CLASS( info_teleport_destination, CPointEntity );
|
|
|
|
|
|
|
|
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( &CTriggerSave::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 && !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 ( &CTriggerEndSection::EndSectionUse );
|
|
// If it is a "use only" trigger, then don't set the touch function.
|
|
if ( ! (pev->spawnflags & SF_ENDSECTION_USEONLY) )
|
|
SetTouch( &CTriggerEndSection::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( &CTriggerGravity::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( &CTriggerCamera::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));
|
|
}
|