1
0
mirror of https://github.com/alliedmodders/hl2sdk.git synced 2025-01-05 17:13:36 +08:00
hl2sdk/dlls/triggers.cpp

4714 lines
132 KiB
C++

//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Spawn and use functions for editor-placed triggers.
//
//=============================================================================//
#include "cbase.h"
#include "ai_basenpc.h"
#include "player.h"
#include "saverestore.h"
#include "gamerules.h"
#include "entityapi.h"
#include "entitylist.h"
#include "ndebugoverlay.h"
#include "globalstate.h"
#include "filters.h"
#include "vstdlib/random.h"
#include "triggers.h"
#include "saverestoretypes.h"
#include "hierarchy.h"
#include "bspfile.h"
#include "saverestore_utlvector.h"
#include "physics_saverestore.h"
#include "te_effect_dispatch.h"
#include "ammodef.h"
#include "iservervehicle.h"
#include "movevars_shared.h"
#include "physics_prop_ragdoll.h"
#include "props.h"
#include "RagdollBoogie.h"
#include "EntityParticleTrail.h"
#include "in_buttons.h"
#include "ai_behavior_follow.h"
#include "ai_behavior_lead.h"
#include "gameinterface.h"
#ifdef HL2_DLL
#include "hl2_player.h"
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define DEBUG_TRANSITIONS_VERBOSE 2
ConVar g_debug_transitions( "g_debug_transitions", "0", FCVAR_NONE, "Set to 1 and restart the map to be warned if the map has no trigger_transition volumes. Set to 2 to see a dump of all entities & associated results during a transition." );
// Global list of triggers that care about weapon fire
// Doesn't need saving, the triggers re-add themselves on restore.
CUtlVector< CHandle<CTriggerMultiple> > g_hWeaponFireTriggers;
extern CServerGameDLL g_ServerGameDLL;
extern bool g_fGameOver;
ConVar showtriggers( "showtriggers", "0", FCVAR_CHEAT, "Shows trigger brushes" );
bool IsTriggerClass( CBaseEntity *pEntity );
// Command to dynamically toggle trigger visibility
void Cmd_ShowtriggersToggle_f( void )
{
// Loop through the entities in the game and make visible anything derived from CBaseTrigger
CBaseEntity *pEntity = gEntList.FirstEnt();
while ( pEntity )
{
if ( IsTriggerClass(pEntity) )
{
// If a classname is specified, only show triggles of that type
if ( engine->Cmd_Argc() > 1 )
{
const char *sClassname = engine->Cmd_Argv(1);
if ( sClassname && sClassname[0] )
{
if ( !FClassnameIs( pEntity, sClassname ) )
{
pEntity = gEntList.NextEnt( pEntity );
continue;
}
}
}
if ( pEntity->IsEffectActive( EF_NODRAW ) )
{
pEntity->RemoveEffects( EF_NODRAW );
}
else
{
pEntity->AddEffects( EF_NODRAW );
}
}
pEntity = gEntList.NextEnt( pEntity );
}
}
static ConCommand showtriggers_toggle( "showtriggers_toggle", Cmd_ShowtriggersToggle_f, "Toggle show triggers", FCVAR_CHEAT );
// Global Savedata for base trigger
BEGIN_DATADESC( CBaseTrigger )
// Keyfields
DEFINE_KEYFIELD( m_iFilterName, FIELD_STRING, "filtername" ),
DEFINE_FIELD( m_hFilter, FIELD_EHANDLE ),
DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ),
DEFINE_UTLVECTOR( m_hTouchingEntities, FIELD_EHANDLE ),
// Inputs
DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
DEFINE_INPUTFUNC( FIELD_VOID, "StartTouch", InputStartTouch ),
DEFINE_INPUTFUNC( FIELD_VOID, "EndTouch", InputEndTouch ),
// Outputs
DEFINE_OUTPUT( m_OnStartTouch, "OnStartTouch"),
DEFINE_OUTPUT( m_OnEndTouch, "OnEndTouch"),
DEFINE_OUTPUT( m_OnEndTouchAll, "OnEndTouchAll"),
END_DATADESC()
LINK_ENTITY_TO_CLASS( trigger, CBaseTrigger );
CBaseTrigger::CBaseTrigger()
{
AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID );
}
//------------------------------------------------------------------------------
// Purpose: Input handler to turn on this trigger.
//------------------------------------------------------------------------------
void CBaseTrigger::InputEnable( inputdata_t &inputdata )
{
Enable();
}
//------------------------------------------------------------------------------
// Purpose: Input handler to turn off this trigger.
//------------------------------------------------------------------------------
void CBaseTrigger::InputDisable( inputdata_t &inputdata )
{
Disable();
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void CBaseTrigger::Spawn()
{
if( HasSpawnFlags( SF_TRIGGER_ONLY_PLAYER_ALLY_NPCS ) )
{
// Automatically set this trigger to work with NPC's.
AddSpawnFlags( SF_TRIGGER_ALLOW_NPCS );
}
if ( HasSpawnFlags( SF_TRIGGER_ONLY_CLIENTS_IN_VEHICLES ) )
{
AddSpawnFlags( SF_TRIGGER_ALLOW_CLIENTS );
}
if ( HasSpawnFlags( SF_TRIGGER_ONLY_CLIENTS_OUT_OF_VEHICLES ) )
{
AddSpawnFlags( SF_TRIGGER_ALLOW_CLIENTS );
}
BaseClass::Spawn();
}
//------------------------------------------------------------------------------
// Create VPhysics
//------------------------------------------------------------------------------
bool CBaseTrigger::CreateVPhysics( void )
{
if ( !HasSpawnFlags( SF_TRIG_TOUCH_DEBRIS ) )
return false;
IPhysicsObject *pPhysics;
pPhysics = VPhysicsInitShadow( false, false );
if ( pPhysics )
{
pPhysics->BecomeTrigger();
}
return true;
}
//------------------------------------------------------------------------------
// Cleanup
//------------------------------------------------------------------------------
void CBaseTrigger::UpdateOnRemove( void )
{
if ( VPhysicsGetObject())
{
VPhysicsGetObject()->RemoveTrigger();
}
BaseClass::UpdateOnRemove();
}
//------------------------------------------------------------------------------
// Purpose: Turns on this trigger.
//------------------------------------------------------------------------------
void CBaseTrigger::Enable( void )
{
m_bDisabled = false;
if ( VPhysicsGetObject())
{
VPhysicsGetObject()->EnableCollisions( true );
}
if (!IsSolidFlagSet( FSOLID_TRIGGER ))
{
AddSolidFlags( FSOLID_TRIGGER );
PhysicsTouchTriggers();
}
}
//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
void CBaseTrigger::Activate( void )
{
// Get a handle to my filter entity if there is one
if (m_iFilterName != NULL_STRING)
{
m_hFilter = dynamic_cast<CBaseFilter *>(gEntList.FindEntityByName( NULL, m_iFilterName ));
}
BaseClass::Activate();
}
//-----------------------------------------------------------------------------
// Purpose: Called after player becomes active in the game
//-----------------------------------------------------------------------------
void CBaseTrigger::PostClientActive( void )
{
BaseClass::PostClientActive();
if ( !m_bDisabled )
{
PhysicsTouchTriggers();
}
}
//------------------------------------------------------------------------------
// Purpose: Turns off this trigger.
//------------------------------------------------------------------------------
void CBaseTrigger::Disable( void )
{
m_bDisabled = true;
if ( VPhysicsGetObject())
{
VPhysicsGetObject()->EnableCollisions( false );
}
if (IsSolidFlagSet(FSOLID_TRIGGER))
{
RemoveSolidFlags( FSOLID_TRIGGER );
PhysicsTouchTriggers();
}
}
//-----------------------------------------------------------------------------
// Purpose: Draw any debug text overlays
// Output : Current text offset from the top
//-----------------------------------------------------------------------------
int CBaseTrigger::DrawDebugTextOverlays(void)
{
int text_offset = BaseClass::DrawDebugTextOverlays();
if (m_debugOverlays & OVERLAY_TEXT_BIT)
{
// --------------
// Print Target
// --------------
char tempstr[255];
if (IsSolidFlagSet(FSOLID_TRIGGER))
{
Q_strncpy(tempstr,"State: Enabled",sizeof(tempstr));
}
else
{
Q_strncpy(tempstr,"State: Disabled",sizeof(tempstr));
}
EntityText(text_offset,tempstr,0);
text_offset++;
}
return text_offset;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBaseTrigger::InitTrigger( )
{
SetSolid( GetParent() ? SOLID_VPHYSICS : SOLID_BSP );
AddSolidFlags( FSOLID_NOT_SOLID );
if (m_bDisabled)
{
RemoveSolidFlags( FSOLID_TRIGGER );
}
else
{
AddSolidFlags( FSOLID_TRIGGER );
}
SetMoveType( MOVETYPE_NONE );
SetModel( STRING( GetModelName() ) ); // set size and link into world
if ( showtriggers.GetInt() == 0 )
{
AddEffects( EF_NODRAW );
}
m_hTouchingEntities.Purge();
if ( HasSpawnFlags( SF_TRIG_TOUCH_DEBRIS ) )
{
CreateVPhysics();
}
}
//-----------------------------------------------------------------------------
// Purpose: Returns true if this entity passes the filter criterea, false if not.
// Input : pOther - The entity to be filtered.
//-----------------------------------------------------------------------------
bool CBaseTrigger::PassesTriggerFilters(CBaseEntity *pOther)
{
// First test spawn flag filters
if ( HasSpawnFlags(SF_TRIGGER_ALLOW_ALL) ||
(HasSpawnFlags(SF_TRIGGER_ALLOW_CLIENTS) && (pOther->GetFlags() & FL_CLIENT)) ||
(HasSpawnFlags(SF_TRIGGER_ALLOW_NPCS) && (pOther->GetFlags() & FL_NPC)) ||
(HasSpawnFlags(SF_TRIGGER_ALLOW_PUSHABLES) && FClassnameIs(pOther, "func_pushable")) ||
(HasSpawnFlags(SF_TRIGGER_ALLOW_PHYSICS) && pOther->GetMoveType() == MOVETYPE_VPHYSICS))
{
bool bOtherIsPlayer = pOther->IsPlayer();
if( HasSpawnFlags(SF_TRIGGER_ONLY_PLAYER_ALLY_NPCS) && !bOtherIsPlayer )
{
CAI_BaseNPC *pNPC = pOther->MyNPCPointer();
if( !pNPC || !pNPC->IsPlayerAlly() )
{
return false;
}
}
if ( HasSpawnFlags(SF_TRIGGER_ONLY_CLIENTS_IN_VEHICLES) && bOtherIsPlayer )
{
if ( !((CBasePlayer*)pOther)->IsInAVehicle() )
return false;
}
if ( HasSpawnFlags(SF_TRIGGER_ONLY_CLIENTS_OUT_OF_VEHICLES) && bOtherIsPlayer )
{
if ( ((CBasePlayer*)pOther)->IsInAVehicle() )
return false;
}
CBaseFilter *pFilter = m_hFilter.Get();
return (!pFilter) ? true : pFilter->PassesFilter( this, pOther );
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Called to simulate what happens when an entity touches the trigger.
// Input : pOther - The entity that is touching us.
//-----------------------------------------------------------------------------
void CBaseTrigger::InputStartTouch( inputdata_t &inputdata )
{
//Pretend we just touched the trigger.
StartTouch( inputdata.pCaller );
}
//-----------------------------------------------------------------------------
// Purpose: Called to simulate what happens when an entity leaves the trigger.
// Input : pOther - The entity that is touching us.
//-----------------------------------------------------------------------------
void CBaseTrigger::InputEndTouch( inputdata_t &inputdata )
{
//And... pretend we left the trigger.
EndTouch( inputdata.pCaller );
}
//-----------------------------------------------------------------------------
// Purpose: Called when an entity starts touching us.
// Input : pOther - The entity that is touching us.
//-----------------------------------------------------------------------------
void CBaseTrigger::StartTouch(CBaseEntity *pOther)
{
if ( HasSpawnFlags( SF_TRIG_TOUCH_DEBRIS ) )
{
triggerevent_t event;
if ( PhysGetTriggerEvent( &event, this ) )
{
// We've been called due a vphysics touch.
// If we're not debris, abort. The normal game code will call touch for us.
if ( pOther->GetCollisionGroup() != COLLISION_GROUP_DEBRIS )
return;
}
}
if (PassesTriggerFilters(pOther) )
{
EHANDLE hOther;
hOther = pOther;
m_hTouchingEntities.AddToTail( hOther );
m_OnStartTouch.FireOutput(pOther, this);
}
}
//-----------------------------------------------------------------------------
// Purpose: Called when an entity stops touching us.
// Input : pOther - The entity that was touching us.
//-----------------------------------------------------------------------------
void CBaseTrigger::EndTouch(CBaseEntity *pOther)
{
if ( IsTouching( pOther ) )
{
EHANDLE hOther;
hOther = pOther;
m_hTouchingEntities.FindAndRemove( hOther );
//FIXME: Without this, triggers fire their EndTouch outputs when they are disabled!
//if ( !m_bDisabled )
//{
m_OnEndTouch.FireOutput(pOther, this);
//}
// If there are no more entities touching this trigger, fire the lost all touches
// Loop through the touching entities backwards. Clean out old ones, and look for existing
bool bFoundOtherTouchee = false;
int iSize = m_hTouchingEntities.Count();
for ( int i = iSize-1; i >= 0; i-- )
{
EHANDLE hOther;
hOther = m_hTouchingEntities[i];
if ( !hOther )
{
m_hTouchingEntities.Remove( i );
}
else
{
bFoundOtherTouchee = true;
}
}
//FIXME: Without this, triggers fire their EndTouch outputs when they are disabled!
// Didn't find one?
if ( !bFoundOtherTouchee /*&& !m_bDisabled*/ )
{
m_OnEndTouchAll.FireOutput(pOther, this);
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Return true if the specified entity is touching us
//-----------------------------------------------------------------------------
bool CBaseTrigger::IsTouching( CBaseEntity *pOther )
{
EHANDLE hOther;
hOther = pOther;
return ( m_hTouchingEntities.Find( hOther ) != m_hTouchingEntities.InvalidIndex() );
}
//-----------------------------------------------------------------------------
// Purpose: Return a pointer to the first entity of the specified type being touched by this trigger
//-----------------------------------------------------------------------------
CBaseEntity *CBaseTrigger::GetTouchedEntityOfType( const char *sClassName )
{
int iCount = m_hTouchingEntities.Count();
for ( int i = 0; i < iCount; i++ )
{
CBaseEntity *pEntity = m_hTouchingEntities[i];
if ( FClassnameIs( pEntity, sClassName ) )
return pEntity;
}
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose: Toggles this trigger between enabled and disabled.
//-----------------------------------------------------------------------------
void CBaseTrigger::InputToggle( inputdata_t &inputdata )
{
if (IsSolidFlagSet( FSOLID_TRIGGER ))
{
RemoveSolidFlags(FSOLID_TRIGGER);
}
else
{
AddSolidFlags(FSOLID_TRIGGER);
}
PhysicsTouchTriggers();
}
//-----------------------------------------------------------------------------
// Purpose: Removes anything that touches it. If the trigger has a targetname,
// firing it will toggle state.
//-----------------------------------------------------------------------------
class CTriggerRemove : public CBaseTrigger
{
public:
DECLARE_CLASS( CTriggerRemove, CBaseTrigger );
void Spawn( void );
void Touch( CBaseEntity *pOther );
DECLARE_DATADESC();
// Outputs
COutputEvent m_OnRemove;
};
BEGIN_DATADESC( CTriggerRemove )
// Outputs
DEFINE_OUTPUT( m_OnRemove, "OnRemove" ),
END_DATADESC()
LINK_ENTITY_TO_CLASS( trigger_remove, CTriggerRemove );
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTriggerRemove::Spawn( void )
{
BaseClass::Spawn();
InitTrigger();
}
//-----------------------------------------------------------------------------
// Purpose: 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 CTriggerRemove::Touch( CBaseEntity *pOther )
{
if (!PassesTriggerFilters(pOther))
return;
UTIL_Remove( pOther );
}
//-----------------------------------------------------------------------------
// Purpose: Hurts anything that touches it. If the trigger has a targetname,
// firing it will toggle state.
//-----------------------------------------------------------------------------
class CTriggerHurt : public CBaseTrigger
{
public:
CTriggerHurt()
{
// This field came along after levels were built so the field defaults to 20 here in the constructor.
m_flDamageCap = 20.0f;
}
DECLARE_CLASS( CTriggerHurt, CBaseTrigger );
void Spawn( void );
void RadiationThink( void );
void HurtThink( void );
void Touch( CBaseEntity *pOther );
void EndTouch( CBaseEntity *pOther );
bool HurtEntity( CBaseEntity *pOther, float damage );
int HurtAllTouchers( float dt );
DECLARE_DATADESC();
float m_flOriginalDamage; // Damage as specified by the level designer.
float m_flDamage; // Damage per second.
float m_flDamageCap; // Maximum damage per second.
float m_flLastDmgTime; // Time that we last applied damage.
float m_flDmgResetTime; // For forgiveness, the time to reset the counter that accumulates damage.
int m_bitsDamageInflict; // DMG_ damage type that the door or tigger does
int m_damageModel;
enum
{
DAMAGEMODEL_NORMAL = 0,
DAMAGEMODEL_DOUBLE_FORGIVENESS,
};
// Outputs
COutputEvent m_OnHurt;
COutputEvent m_OnHurtPlayer;
CUtlVector<EHANDLE> m_hurtEntities;
};
BEGIN_DATADESC( CTriggerHurt )
// Function Pointers
DEFINE_FUNCTION( RadiationThink ),
DEFINE_FUNCTION( HurtThink ),
// Fields
DEFINE_FIELD( m_flOriginalDamage, FIELD_FLOAT ),
DEFINE_KEYFIELD( m_flDamage, FIELD_FLOAT, "damage" ),
DEFINE_KEYFIELD( m_flDamageCap, FIELD_FLOAT, "damagecap" ),
DEFINE_KEYFIELD( m_bitsDamageInflict, FIELD_INTEGER, "damagetype" ),
DEFINE_KEYFIELD( m_damageModel, FIELD_INTEGER, "damagemodel" ),
DEFINE_FIELD( m_flLastDmgTime, FIELD_TIME ),
DEFINE_FIELD( m_flDmgResetTime, FIELD_TIME ),
DEFINE_UTLVECTOR( m_hurtEntities, FIELD_EHANDLE ),
// Inputs
DEFINE_INPUT( m_flDamage, FIELD_FLOAT, "SetDamage" ),
// Outputs
DEFINE_OUTPUT( m_OnHurt, "OnHurt" ),
DEFINE_OUTPUT( m_OnHurtPlayer, "OnHurtPlayer" ),
END_DATADESC()
LINK_ENTITY_TO_CLASS( trigger_hurt, CTriggerHurt );
//-----------------------------------------------------------------------------
// Purpose: Called when spawning, after keyvalues have been handled.
//-----------------------------------------------------------------------------
void CTriggerHurt::Spawn( void )
{
BaseClass::Spawn();
InitTrigger();
m_flOriginalDamage = m_flDamage;
SetNextThink( TICK_NEVER_THINK );
SetThink( NULL );
if (m_bitsDamageInflict & DMG_RADIATION)
{
SetThink ( &CTriggerHurt::RadiationThink );
SetNextThink( gpGlobals->curtime + random->RandomFloat(0.0, 0.5) );
}
}
//-----------------------------------------------------------------------------
// Purpose: 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 )
{
// check to see if a player is in pvs
// if not, continue
Vector vecSurroundMins, vecSurroundMaxs;
CollisionProp()->WorldSpaceSurroundingBounds( &vecSurroundMins, &vecSurroundMaxs );
CBasePlayer *pPlayer = static_cast<CBasePlayer *>(UTIL_FindClientInPVS( vecSurroundMins, vecSurroundMaxs ));
if (pPlayer)
{
// get range to player;
float flRange = CollisionProp()->CalcDistanceFromPoint( pPlayer->WorldSpaceCenter() );
flRange *= 3.0f;
pPlayer->NotifyNearbyRadiationSource(flRange);
}
float dt = gpGlobals->curtime - m_flLastDmgTime;
if ( dt >= 0.5 )
{
HurtAllTouchers( dt );
}
SetNextThink( gpGlobals->curtime + 0.25 );
}
//-----------------------------------------------------------------------------
// Purpose: When touched, a hurt trigger does m_flDamage points of damage each half-second.
// Input : pOther - The entity that is touching us.
//-----------------------------------------------------------------------------
bool CTriggerHurt::HurtEntity( CBaseEntity *pOther, float damage )
{
if ( !pOther->m_takedamage || !PassesTriggerFilters(pOther) )
return false;
if ( damage < 0 )
{
pOther->TakeHealth( -damage, m_bitsDamageInflict );
}
else
{
// The damage position is the nearest point on the damaged entity
// to the trigger's center. Not perfect, but better than nothing.
Vector vecCenter = CollisionProp()->WorldSpaceCenter();
Vector vecDamagePos;
pOther->CollisionProp()->CalcNearestPoint( vecCenter, &vecDamagePos );
CTakeDamageInfo info( this, this, damage, m_bitsDamageInflict );
info.SetDamagePosition( vecDamagePos );
GuessDamageForce( &info, ( vecDamagePos - vecCenter ), vecDamagePos );
pOther->TakeDamage( info );
}
if (pOther->IsPlayer())
{
m_OnHurtPlayer.FireOutput(pOther, this);
}
else
{
m_OnHurt.FireOutput(pOther, this);
}
m_hurtEntities.AddToTail( EHANDLE(pOther) );
//NDebugOverlay::Box( pOther->GetAbsOrigin(), pOther->WorldAlignMins(), pOther->WorldAlignMaxs(), 255,0,0,0,0.5 );
return true;
}
void CTriggerHurt::HurtThink()
{
// if I hurt anyone, think again
if ( HurtAllTouchers( 0.5 ) <= 0 )
{
SetThink(NULL);
}
else
{
SetNextThink( gpGlobals->curtime + 0.5f );
}
}
void CTriggerHurt::EndTouch( CBaseEntity *pOther )
{
if (PassesTriggerFilters(pOther))
{
EHANDLE hOther;
hOther = pOther;
// if this guy has never taken damage, hurt him now
if ( !m_hurtEntities.HasElement( hOther ) )
{
HurtEntity( pOther, m_flDamage * 0.5 );
}
}
BaseClass::EndTouch( pOther );
}
//-----------------------------------------------------------------------------
// Purpose: called from RadiationThink() as well as HurtThink()
// This function applies damage to any entities currently touching the
// trigger
// Input : dt - time since last call
// Output : int - number of entities actually hurt
//-----------------------------------------------------------------------------
#define TRIGGER_HURT_FORGIVE_TIME 3.0f // time in seconds
int CTriggerHurt::HurtAllTouchers( float dt )
{
int hurtCount = 0;
// half second worth of damage
float fldmg = m_flDamage * dt;
m_flLastDmgTime = gpGlobals->curtime;
m_hurtEntities.RemoveAll();
touchlink_t *root = ( touchlink_t * )GetDataObject( TOUCHLINK );
if ( root )
{
for ( touchlink_t *link = root->nextLink; link != root; link = link->nextLink )
{
CBaseEntity *pTouch = link->entityTouched;
if ( pTouch )
{
if ( HurtEntity( pTouch, fldmg ) )
{
hurtCount++;
}
}
}
}
if( m_damageModel == DAMAGEMODEL_DOUBLE_FORGIVENESS )
{
if( hurtCount == 0 )
{
if( gpGlobals->curtime > m_flDmgResetTime )
{
// Didn't hurt anyone. Reset the damage if it's time. (hence, the forgiveness)
m_flDamage = m_flOriginalDamage;
}
}
else
{
// Hurt someone! double the damage
m_flDamage *= 2.0f;
if( m_flDamage > m_flDamageCap )
{
// Clamp
m_flDamage = m_flDamageCap;
}
// Now, put the damage reset time into the future. The forgive time is how long the trigger
// must go without harming anyone in order that its accumulated damage be reset to the amount
// set by the level designer. This is a stop-gap for an exploit where players could hop through
// slime and barely take any damage because the trigger would reset damage anytime there was no
// one in the trigger when this function was called. (sjb)
m_flDmgResetTime = gpGlobals->curtime + TRIGGER_HURT_FORGIVE_TIME;
}
}
return hurtCount;
}
void CTriggerHurt::Touch( CBaseEntity *pOther )
{
if ( m_pfnThink == NULL )
{
SetThink( &CTriggerHurt::HurtThink );
SetNextThink( gpGlobals->curtime );
}
}
// ##################################################################################
// >> TriggerMultiple
// ##################################################################################
LINK_ENTITY_TO_CLASS( trigger_multiple, CTriggerMultiple );
BEGIN_DATADESC( CTriggerMultiple )
// Function Pointers
DEFINE_FUNCTION(MultiTouch),
DEFINE_FUNCTION(MultiWaitOver ),
// Outputs
DEFINE_OUTPUT(m_OnTrigger, "OnTrigger")
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose: Called when spawning, after keyvalues have been handled.
//-----------------------------------------------------------------------------
void CTriggerMultiple::Spawn( void )
{
BaseClass::Spawn();
InitTrigger();
if (m_flWait == 0)
{
m_flWait = 0.2;
}
ASSERTSZ(m_iHealth == 0, "trigger_multiple with health");
SetTouch( &CTriggerMultiple::MultiTouch );
}
//-----------------------------------------------------------------------------
// Purpose: Touch function. Activates the trigger.
// Input : pOther - The thing that touched us.
//-----------------------------------------------------------------------------
void CTriggerMultiple::MultiTouch(CBaseEntity *pOther)
{
if (PassesTriggerFilters(pOther))
{
ActivateMultiTrigger( pOther );
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : pActivator -
//-----------------------------------------------------------------------------
void CTriggerMultiple::ActivateMultiTrigger(CBaseEntity *pActivator)
{
if (GetNextThink() > gpGlobals->curtime)
return; // still waiting for reset time
m_hActivator = pActivator;
m_OnTrigger.FireOutput(m_hActivator, this);
if (m_flWait > 0)
{
SetThink( &CTriggerMultiple::MultiWaitOver );
SetNextThink( gpGlobals->curtime + 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 );
SetNextThink( gpGlobals->curtime + 0.1f );
SetThink( &CTriggerMultiple::SUB_Remove );
}
}
//-----------------------------------------------------------------------------
// Purpose: The wait time has passed, so set back up for another activation
//-----------------------------------------------------------------------------
void CTriggerMultiple::MultiWaitOver( void )
{
SetThink( NULL );
}
// ##################################################################################
// >> TriggerOnce
// ##################################################################################
class CTriggerOnce : public CTriggerMultiple
{
DECLARE_CLASS( CTriggerOnce, CTriggerMultiple );
public:
void Spawn( void );
};
LINK_ENTITY_TO_CLASS( trigger_once, CTriggerOnce );
void CTriggerOnce::Spawn( void )
{
BaseClass::Spawn();
m_flWait = -1;
}
// ##################################################################################
// >> TriggerLook
//
// Triggers once when player is looking at m_target
//
// ##################################################################################
#define SF_TRIGGERLOOK_FIREONCE 128
#define SF_TRIGGERLOOK_USEVELOCITY 256
class CTriggerLook : public CTriggerOnce
{
DECLARE_CLASS( CTriggerLook, CTriggerOnce );
public:
EHANDLE m_hLookTarget;
float m_flFieldOfView;
float m_flLookTime; // How long must I look for
float m_flLookTimeTotal; // How long have I looked
float m_flLookTimeLast; // When did I last look
float m_flTimeoutDuration; // Number of seconds after start touch to fire anyway
bool m_bTimeoutFired; // True if the OnTimeout output fired since the last StartTouch.
EHANDLE m_hActivator; // The entity that triggered us.
void Spawn( void );
void Touch( CBaseEntity *pOther );
void StartTouch(CBaseEntity *pOther);
void EndTouch( CBaseEntity *pOther );
int DrawDebugTextOverlays(void);
DECLARE_DATADESC();
private:
void Trigger(CBaseEntity *pActivator, bool bTimeout);
void TimeoutThink();
COutputEvent m_OnTimeout;
};
LINK_ENTITY_TO_CLASS( trigger_look, CTriggerLook );
BEGIN_DATADESC( CTriggerLook )
DEFINE_FIELD( m_hLookTarget, FIELD_EHANDLE ),
DEFINE_FIELD( m_flLookTimeTotal, FIELD_FLOAT ),
DEFINE_FIELD( m_flLookTimeLast, FIELD_TIME ),
DEFINE_KEYFIELD( m_flTimeoutDuration, FIELD_FLOAT, "timeout" ),
DEFINE_FIELD( m_bTimeoutFired, FIELD_BOOLEAN ),
DEFINE_FIELD( m_hActivator, FIELD_EHANDLE ),
DEFINE_OUTPUT( m_OnTimeout, "OnTimeout" ),
DEFINE_FUNCTION( TimeoutThink ),
// Inputs
DEFINE_INPUT( m_flFieldOfView, FIELD_FLOAT, "FieldOfView" ),
DEFINE_INPUT( m_flLookTime, FIELD_FLOAT, "LookTime" ),
END_DATADESC()
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerLook::Spawn( void )
{
m_hLookTarget = NULL;
m_flLookTimeTotal = -1;
m_bTimeoutFired = false;
BaseClass::Spawn();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : pOther -
//-----------------------------------------------------------------------------
void CTriggerLook::StartTouch(CBaseEntity *pOther)
{
BaseClass::StartTouch(pOther);
if (pOther->IsPlayer() && m_flTimeoutDuration)
{
m_bTimeoutFired = false;
m_hActivator = pOther;
SetThink(&CTriggerLook::TimeoutThink);
SetNextThink(gpGlobals->curtime + m_flTimeoutDuration);
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTriggerLook::TimeoutThink(void)
{
Trigger(m_hActivator, true);
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerLook::EndTouch(CBaseEntity *pOther)
{
BaseClass::EndTouch(pOther);
if (pOther->IsPlayer())
{
SetThink(NULL);
SetNextThink(0);
m_flLookTimeTotal = -1;
}
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerLook::Touch(CBaseEntity *pOther)
{
// Don't fire the OnTrigger if we've already fired the OnTimeout. This will be
// reset in OnEndTouch.
if (m_bTimeoutFired)
return;
// --------------------------------
// Make sure we have a look target
// --------------------------------
if (m_hLookTarget == NULL)
{
m_hLookTarget = GetNextTarget();
if (m_hLookTarget == NULL)
{
return;
}
}
// This is designed for single player only
// so we'll always have the same player
if (pOther->IsPlayer())
{
// ----------------------------------------
// Check that toucher is facing the target
// ----------------------------------------
Vector vLookDir;
if ( HasSpawnFlags( SF_TRIGGERLOOK_USEVELOCITY ) )
{
vLookDir = pOther->GetAbsVelocity();
if ( vLookDir == vec3_origin )
{
// See if they're in a vehicle
CBasePlayer *pPlayer = (CBasePlayer *)pOther;
if ( pPlayer->IsInAVehicle() )
{
vLookDir = pPlayer->GetVehicle()->GetVehicleEnt()->GetSmoothedVelocity();
}
}
VectorNormalize( vLookDir );
}
else
{
vLookDir = ((CBaseCombatCharacter*)pOther)->EyeDirection3D( );
}
Vector vTargetDir = m_hLookTarget->GetAbsOrigin() - pOther->EyePosition();
VectorNormalize(vTargetDir);
float fDotPr = DotProduct(vLookDir,vTargetDir);
if (fDotPr > m_flFieldOfView)
{
// Is it the first time I'm looking?
if (m_flLookTimeTotal == -1)
{
m_flLookTimeLast = gpGlobals->curtime;
m_flLookTimeTotal = 0;
}
else
{
m_flLookTimeTotal += gpGlobals->curtime - m_flLookTimeLast;
m_flLookTimeLast = gpGlobals->curtime;
}
if (m_flLookTimeTotal >= m_flLookTime)
{
Trigger(pOther, false);
}
}
else
{
m_flLookTimeTotal = -1;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Called when the trigger is fired by look logic or timeout.
//-----------------------------------------------------------------------------
void CTriggerLook::Trigger(CBaseEntity *pActivator, bool bTimeout)
{
if (bTimeout)
{
// Fired due to timeout (player never looked at the target).
m_OnTimeout.FireOutput(pActivator, this);
// Don't fire the OnTrigger for this toucher.
m_bTimeoutFired = true;
}
else
{
// Fire because the player looked at the target.
m_OnTrigger.FireOutput(pActivator, this);
m_flLookTimeTotal = -1;
// Cancel the timeout think.
SetThink(NULL);
SetNextThink(0);
}
if (HasSpawnFlags(SF_TRIGGERLOOK_FIREONCE))
{
SetThink(&CTriggerLook::SUB_Remove);
SetNextThink(gpGlobals->curtime);
}
}
//-----------------------------------------------------------------------------
// Purpose: Draw any debug text overlays
// Output : Current text offset from the top
//-----------------------------------------------------------------------------
int CTriggerLook::DrawDebugTextOverlays(void)
{
int text_offset = BaseClass::DrawDebugTextOverlays();
if (m_debugOverlays & OVERLAY_TEXT_BIT)
{
// ----------------
// Print Look time
// ----------------
char tempstr[255];
Q_snprintf(tempstr,sizeof(tempstr),"Time: %3.2f",m_flLookTime - MAX(0,m_flLookTimeTotal));
EntityText(text_offset,tempstr,0);
text_offset++;
}
return text_offset;
}
// ##################################################################################
// >> TriggerVolume
// ##################################################################################
class CTriggerVolume : public CPointEntity // Derive from point entity so this doesn't move across levels
{
public:
DECLARE_CLASS( CTriggerVolume, CPointEntity );
void Spawn( void );
};
LINK_ENTITY_TO_CLASS( trigger_transition, CTriggerVolume );
// Define space that travels across a level transition
void CTriggerVolume::Spawn( void )
{
SetSolid( SOLID_BSP );
AddSolidFlags( FSOLID_NOT_SOLID );
SetMoveType( MOVETYPE_NONE );
SetModel( STRING( GetModelName() ) ); // set size and link into world
if ( showtriggers.GetInt() == 0 )
{
AddEffects( EF_NODRAW );
}
}
#define SF_CHANGELEVEL_NOTOUCH 0x0002
#define SF_CHANGELEVEL_CHAPTER 0x0004
#define cchMapNameMost 32
enum
{
TRANSITION_VOLUME_SCREENED_OUT = 0,
TRANSITION_VOLUME_NOT_FOUND = 1,
TRANSITION_VOLUME_PASSED = 2,
};
//------------------------------------------------------------------------------
// Reesponsible for changing levels when the player touches it
//------------------------------------------------------------------------------
class CChangeLevel : public CBaseTrigger
{
DECLARE_DATADESC();
public:
DECLARE_CLASS( CChangeLevel, CBaseTrigger );
void Spawn( void );
void Activate( void );
bool KeyValue( const char *szKeyName, const char *szValue );
static int ChangeList( levellist_t *pLevelList, int maxList );
private:
void TouchChangeLevel( CBaseEntity *pOther );
void ChangeLevelNow( CBaseEntity *pActivator );
void InputChangeLevel( inputdata_t &inputdata );
bool IsEntityInTransition( CBaseEntity *pEntity );
void NotifyEntitiesOutOfTransition();
void WarnAboutActiveLead( void );
static CBaseEntity *FindLandmark( const char *pLandmarkName );
static int AddTransitionToList( levellist_t *pLevelList, int listCount, const char *pMapName, const char *pLandmarkName, edict_t *pentLandmark );
static int InTransitionVolume( CBaseEntity *pEntity, const char *pVolumeName );
// Builds the list of entities to save when moving across a transition
static int BuildChangeLevelList( levellist_t *pLevelList, int maxList );
// Builds the list of entities to bring across a particular transition
static int BuildEntityTransitionList( CBaseEntity *pLandmarkEntity, const char *pLandmarkName, CBaseEntity **ppEntList, int *pEntityFlags, int nMaxList );
// Adds a single entity to the transition list, if appropriate. Returns the new count
static int AddEntityToTransitionList( CBaseEntity *pEntity, int flags, int nCount, CBaseEntity **ppEntList, int *pEntityFlags );
// Adds in all entities depended on by entities near the transition
static int AddDependentEntities( int nCount, CBaseEntity **ppEntList, int *pEntityFlags, int nMaxList );
// Figures out save flags for the entity
static int ComputeEntitySaveFlags( CBaseEntity *pEntity );
private:
char m_szMapName[cchMapNameMost]; // trigger_changelevel only: next map
char m_szLandmarkName[cchMapNameMost]; // trigger_changelevel only: landmark on next map
bool m_bTouched;
// Outputs
COutputEvent m_OnChangeLevel;
};
LINK_ENTITY_TO_CLASS( trigger_changelevel, CChangeLevel );
// Global Savedata for changelevel trigger
BEGIN_DATADESC( CChangeLevel )
DEFINE_AUTO_ARRAY( m_szMapName, FIELD_CHARACTER ),
DEFINE_AUTO_ARRAY( m_szLandmarkName, FIELD_CHARACTER ),
// DEFINE_FIELD( m_touchTime, FIELD_TIME ), // don't save
// DEFINE_FIELD( m_bTouched, FIELD_BOOLEAN ),
// Function Pointers
DEFINE_FUNCTION( TouchChangeLevel ),
DEFINE_INPUTFUNC( FIELD_VOID, "ChangeLevel", InputChangeLevel ),
// Outputs
DEFINE_OUTPUT( m_OnChangeLevel, "OnChangeLevel"),
END_DATADESC()
//
// Cache user-entity-field values until spawn is called.
//
bool CChangeLevel::KeyValue( const char *szKeyName, const char *szValue )
{
if (FStrEq(szKeyName, "map"))
{
if (strlen(szValue) >= cchMapNameMost)
{
Warning( "Map name '%s' too long (32 chars)\n", szValue );
Assert(0);
}
Q_strncpy(m_szMapName, szValue, sizeof(m_szMapName));
}
else if (FStrEq(szKeyName, "landmark"))
{
if (strlen(szValue) >= cchMapNameMost)
{
Warning( "Landmark name '%s' too long (32 chars)\n", szValue );
Assert(0);
}
Q_strncpy(m_szLandmarkName, szValue, sizeof( m_szLandmarkName ));
}
else
return BaseClass::KeyValue( szKeyName, szValue );
return true;
}
void CChangeLevel::Spawn( void )
{
if ( FStrEq( m_szMapName, "" ) )
{
Msg( "a trigger_changelevel doesn't have a map" );
}
if ( FStrEq( m_szLandmarkName, "" ) )
{
Msg( "trigger_changelevel to %s doesn't have a landmark", m_szMapName );
}
InitTrigger();
if ( !HasSpawnFlags(SF_CHANGELEVEL_NOTOUCH) )
{
SetTouch( &CChangeLevel::TouchChangeLevel );
}
// Msg( "TRANSITION: %s (%s)\n", m_szMapName, m_szLandmarkName );
}
void CChangeLevel::Activate( void )
{
BaseClass::Activate();
if ( gpGlobals->eLoadType == MapLoad_NewGame )
{
if ( HasSpawnFlags( SF_CHANGELEVEL_CHAPTER ) )
{
VPhysicsInitStatic();
RemoveSolidFlags( FSOLID_NOT_SOLID | FSOLID_TRIGGER );
SetTouch( NULL );
return;
}
}
// Level transitions will bust if they are in solid
CBaseEntity *pLandmark = FindLandmark( m_szLandmarkName );
if ( pLandmark )
{
int clusterIndex = engine->GetClusterForOrigin( pLandmark->GetAbsOrigin() );
if ( clusterIndex < 0 )
{
Warning( "trigger_changelevel to map %s has a landmark embedded in solid!\n"
"This will break level transitions!\n", m_szMapName );
}
if ( g_debug_transitions.GetInt() )
{
if ( !gEntList.FindEntityByClassname( NULL, "trigger_transition" ) )
{
Warning( "Map has no trigger_transition volumes for landmark %s\n", m_szLandmarkName );
}
}
}
m_bTouched = false;
}
static char st_szNextMap[cchMapNameMost];
static char st_szNextSpot[cchMapNameMost];
// Used to show debug for only the transition volume we're currently in
static int g_iDebuggingTransition = 0;
CBaseEntity *CChangeLevel::FindLandmark( const char *pLandmarkName )
{
CBaseEntity *pentLandmark;
pentLandmark = gEntList.FindEntityByName( NULL, pLandmarkName );
while ( pentLandmark )
{
// Found the landmark
if ( FClassnameIs( pentLandmark, "info_landmark" ) )
return pentLandmark;
else
pentLandmark = gEntList.FindEntityByName( pentLandmark, pLandmarkName );
}
Warning( "Can't find landmark %s\n", pLandmarkName );
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose: Allows level transitions to be triggered by buttons, etc.
//-----------------------------------------------------------------------------
void CChangeLevel::InputChangeLevel( inputdata_t &inputdata )
{
// Ignore changelevel transitions if the player's dead
if ( gpGlobals->maxClients == 1 )
{
CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
if ( pPlayer && !pPlayer->IsAlive() )
return;
}
ChangeLevelNow( inputdata.pActivator );
}
//-----------------------------------------------------------------------------
// Purpose: Performs the level change and fires targets.
// Input : pActivator -
//-----------------------------------------------------------------------------
bool CChangeLevel::IsEntityInTransition( CBaseEntity *pEntity )
{
int transitionState = InTransitionVolume(pEntity, m_szLandmarkName);
if ( transitionState == TRANSITION_VOLUME_SCREENED_OUT )
{
return false;
}
// look for a landmark entity
CBaseEntity *pLandmark = FindLandmark( m_szLandmarkName );
if ( !pLandmark )
return false;
// Check to make sure it's also in the PVS of landmark
byte pvs[MAX_MAP_CLUSTERS/8];
int clusterIndex = engine->GetClusterForOrigin( pLandmark->GetAbsOrigin() );
engine->GetPVSForCluster( clusterIndex, sizeof(pvs), pvs );
Vector vecSurroundMins, vecSurroundMaxs;
pEntity->CollisionProp()->WorldSpaceSurroundingBounds( &vecSurroundMins, &vecSurroundMaxs );
return engine->CheckBoxInPVS( vecSurroundMins, vecSurroundMaxs, pvs, sizeof( pvs ) );
}
void CChangeLevel::NotifyEntitiesOutOfTransition()
{
CBaseEntity *pEnt = gEntList.FirstEnt();
while ( pEnt )
{
// Found the landmark
if ( pEnt->ObjectCaps() & FCAP_NOTIFY_ON_TRANSITION )
{
variant_t emptyVariant;
if ( !(pEnt->ObjectCaps() & (FCAP_ACROSS_TRANSITION|FCAP_FORCE_TRANSITION)) || !IsEntityInTransition( pEnt ) )
{
pEnt->AcceptInput( "OutsideTransition", this, this, emptyVariant, 0 );
}
else
{
pEnt->AcceptInput( "InsideTransition", this, this, emptyVariant, 0 );
}
}
pEnt = gEntList.NextEnt( pEnt );
}
}
//------------------------------------------------------------------------------
// Purpose : Checks all spawned AIs and prints a warning if any are actively leading
// Input :
// Output :
//------------------------------------------------------------------------------
void CChangeLevel::WarnAboutActiveLead( void )
{
int i;
CAI_BaseNPC * ai;
CAI_BehaviorBase * behavior;
for ( i = 0; i < g_AI_Manager.NumAIs(); i++ )
{
ai = g_AI_Manager.AccessAIs()[i];
behavior = ai->GetRunningBehavior();
if ( behavior )
{
if ( dynamic_cast<CAI_LeadBehavior *>( behavior ) )
{
Warning( "Entity '%s' is still actively leading\n", STRING( ai->GetEntityName() ) );
}
}
}
}
void CChangeLevel::ChangeLevelNow( CBaseEntity *pActivator )
{
CBaseEntity *pLandmark;
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 ( m_bTouched )
return;
m_bTouched = true;
CBaseEntity *pPlayer = (pActivator && pActivator->IsPlayer()) ? pActivator : UTIL_GetLocalPlayer();
int transitionState = InTransitionVolume(pPlayer, m_szLandmarkName);
if ( transitionState == TRANSITION_VOLUME_SCREENED_OUT )
{
DevMsg( 2, "Player isn't in the transition volume %s, aborting\n", m_szLandmarkName );
return;
}
// look for a landmark entity
pLandmark = FindLandmark( m_szLandmarkName );
if ( !pLandmark )
return;
// no transition volumes, check PVS of landmark
if ( transitionState == TRANSITION_VOLUME_NOT_FOUND )
{
byte pvs[MAX_MAP_CLUSTERS/8];
int clusterIndex = engine->GetClusterForOrigin( pLandmark->GetAbsOrigin() );
engine->GetPVSForCluster( clusterIndex, sizeof(pvs), pvs );
if ( pPlayer )
{
Vector vecSurroundMins, vecSurroundMaxs;
pPlayer->CollisionProp()->WorldSpaceSurroundingBounds( &vecSurroundMins, &vecSurroundMaxs );
bool playerInPVS = engine->CheckBoxInPVS( vecSurroundMins, vecSurroundMaxs, pvs, sizeof( pvs ) );
//Assert( playerInPVS );
if ( !playerInPVS )
{
Warning( "Player isn't in the landmark's (%s) PVS, aborting\n", m_szLandmarkName );
#ifndef HL1_DLL
// HL1 works even with these errors!
return;
#endif
}
}
}
WarnAboutActiveLead();
g_iDebuggingTransition = 0;
st_szNextSpot[0] = 0; // Init landmark to NULL
Q_strncpy(st_szNextSpot, m_szLandmarkName,sizeof(st_szNextSpot));
// This object will get removed in the call to engine->ChangeLevel, copy the params into "safe" memory
Q_strncpy(st_szNextMap, m_szMapName, sizeof(st_szNextMap));
m_hActivator = pActivator;
m_OnChangeLevel.FireOutput(pActivator, this);
NotifyEntitiesOutOfTransition();
//// Msg( "Level touches %d levels\n", ChangeList( levels, 16 ) );
if ( g_debug_transitions.GetInt() )
{
Msg( "CHANGE LEVEL: %s %s\n", st_szNextMap, st_szNextSpot );
}
// If we're debugging, don't actually change level
if ( g_debug_transitions.GetInt() == 0 )
{
engine->ChangeLevel( st_szNextMap, st_szNextSpot );
}
else
{
// Build a change list so we can see what would be transitioning
CSaveRestoreData *pSaveData = SaveInit( 0 );
if ( pSaveData )
{
g_pGameSaveRestoreBlockSet->PreSave( pSaveData );
pSaveData->levelInfo.connectionCount = BuildChangeList( pSaveData->levelInfo.levelList, MAX_LEVEL_CONNECTIONS );
g_pGameSaveRestoreBlockSet->PostSave();
}
SetTouch( NULL );
}
}
//
// GLOBALS ASSUMED SET: st_szNextMap
//
void CChangeLevel::TouchChangeLevel( CBaseEntity *pOther )
{
CBasePlayer *pPlayer = ToBasePlayer(pOther);
if ( !pPlayer )
return;
if( pPlayer->IsSinglePlayerGameEnding() )
{
// Some semblance of deceleration, but allow player to fall normally.
// Also, disable controls.
Vector vecVelocity = pPlayer->GetAbsVelocity();
vecVelocity.x *= 0.5f;
vecVelocity.y *= 0.5f;
pPlayer->SetAbsVelocity( vecVelocity );
pPlayer->AddFlag( FL_FROZEN );
return;
}
if ( !pPlayer->IsInAVehicle() && pPlayer->GetMoveType() == MOVETYPE_NOCLIP )
{
DevMsg("In level transition: %s %s\n", st_szNextMap, st_szNextSpot );
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_t *pLevelList, int listCount, const char *pMapName, const char *pLandmarkName, edict_t *pentLandmark )
{
int i;
if ( !pLevelList || !pMapName || !pLandmarkName || !pentLandmark )
return 0;
// Ignore changelevels to the level we're ready in. Mapmakers love to do this!
if ( stricmp( pMapName, STRING(gpGlobals->mapname) ) == 0 )
return 0;
for ( i = 0; i < listCount; i++ )
{
if ( pLevelList[i].pentLandmark == pentLandmark && stricmp( pLevelList[i].mapName, pMapName ) == 0 )
return 0;
}
Q_strncpy( pLevelList[listCount].mapName, pMapName, sizeof(pLevelList[listCount].mapName) );
Q_strncpy( pLevelList[listCount].landmarkName, pLandmarkName, sizeof(pLevelList[listCount].landmarkName) );
pLevelList[listCount].pentLandmark = pentLandmark;
CBaseEntity *ent = CBaseEntity::Instance( pentLandmark );
Assert( ent );
pLevelList[listCount].vecLandmarkOrigin = ent->GetAbsOrigin();
return 1;
}
int BuildChangeList( levellist_t *pLevelList, int maxList )
{
return CChangeLevel::ChangeList( pLevelList, maxList );
}
struct collidelist_t
{
const CPhysCollide *pCollide;
Vector origin;
QAngle angles;
};
// NOTE: This routine is relatively slow. If you need to use it for per-frame work, consider that fact.
// UNDONE: Expand this to the full matrix of solid types on each side and move into enginetrace
static bool TestEntityTriggerIntersection_Accurate( CBaseEntity *pTrigger, CBaseEntity *pEntity )
{
Assert( pTrigger->GetSolid() == SOLID_BSP );
if ( pTrigger->Intersects( pEntity ) ) // It touches one, it's in the volume
{
switch ( pEntity->GetSolid() )
{
case SOLID_BBOX:
{
ICollideable *pCollide = pTrigger->CollisionProp();
Ray_t ray;
trace_t tr;
ray.Init( pEntity->GetAbsOrigin(), pEntity->GetAbsOrigin(), pEntity->WorldAlignMins(), pEntity->WorldAlignMaxs() );
enginetrace->ClipRayToCollideable( ray, MASK_ALL, pCollide, &tr );
if ( tr.startsolid )
return true;
}
break;
case SOLID_BSP:
case SOLID_VPHYSICS:
{
CPhysCollide *pTriggerCollide = modelinfo->GetVCollide( pTrigger->GetModelIndex() )->solids[0];
Assert( pTriggerCollide );
CUtlVector<collidelist_t> collideList;
IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
int physicsCount = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
if ( physicsCount )
{
for ( int i = 0; i < physicsCount; i++ )
{
const CPhysCollide *pCollide = pList[i]->GetCollide();
if ( pCollide )
{
collidelist_t element;
element.pCollide = pCollide;
pList[i]->GetPosition( &element.origin, &element.angles );
collideList.AddToTail( element );
}
}
}
else
{
vcollide_t *pVCollide = modelinfo->GetVCollide( pEntity->GetModelIndex() );
if ( pVCollide && pVCollide->solidCount )
{
collidelist_t element;
element.pCollide = pVCollide->solids[0];
element.origin = pEntity->GetAbsOrigin();
element.angles = pEntity->GetAbsAngles();
collideList.AddToTail( element );
}
}
for ( int i = collideList.Count()-1; i >= 0; --i )
{
const collidelist_t &element = collideList[i];
trace_t tr;
physcollision->TraceCollide( element.origin, element.origin, element.pCollide, element.angles, pTriggerCollide, pTrigger->GetAbsOrigin(), pTrigger->GetAbsAngles(), &tr );
if ( tr.startsolid )
return true;
}
}
break;
default:
return true;
}
}
return false;
}
int CChangeLevel::InTransitionVolume( CBaseEntity *pEntity, const char *pVolumeName )
{
CBaseEntity *pVolume;
if ( pEntity->ObjectCaps() & FCAP_FORCE_TRANSITION )
return TRANSITION_VOLUME_PASSED;
// If you're following another entity, follow it through the transition (weapons follow the player)
pEntity = pEntity->GetRootMoveParent();
int inVolume = TRANSITION_VOLUME_NOT_FOUND; // Unless we find a trigger_transition, everything is in the volume
pVolume = gEntList.FindEntityByName( NULL, pVolumeName );
while ( pVolume )
{
if ( pVolume && FClassnameIs( pVolume, "trigger_transition" ) )
{
if ( TestEntityTriggerIntersection_Accurate(pVolume, pEntity ) ) // It touches one, it's in the volume
return TRANSITION_VOLUME_PASSED;
inVolume = TRANSITION_VOLUME_SCREENED_OUT; // Found a trigger_transition, but I don't intersect it -- if I don't find another, don't go!
}
pVolume = gEntList.FindEntityByName( pVolume, pVolumeName );
}
return inVolume;
}
//------------------------------------------------------------------------------
// Builds the list of entities to save when moving across a transition
//------------------------------------------------------------------------------
int CChangeLevel::BuildChangeLevelList( levellist_t *pLevelList, int maxList )
{
int nCount = 0;
CBaseEntity *pentChangelevel = gEntList.FindEntityByClassname( NULL, "trigger_changelevel" );
while ( pentChangelevel )
{
CChangeLevel *pTrigger = dynamic_cast<CChangeLevel *>(pentChangelevel);
if ( pTrigger )
{
// Find the corresponding landmark
CBaseEntity *pentLandmark = FindLandmark( pTrigger->m_szLandmarkName );
if ( pentLandmark )
{
// Build a list of unique transitions
if ( AddTransitionToList( pLevelList, nCount, pTrigger->m_szMapName, pTrigger->m_szLandmarkName, pentLandmark->edict() ) )
{
++nCount;
if ( nCount >= maxList ) // FULL!!
break;
}
}
}
pentChangelevel = gEntList.FindEntityByClassname( pentChangelevel, "trigger_changelevel" );
}
return nCount;
}
//------------------------------------------------------------------------------
// Adds a single entity to the transition list, if appropriate. Returns the new count
//------------------------------------------------------------------------------
int CChangeLevel::ComputeEntitySaveFlags( CBaseEntity *pEntity )
{
if ( g_iDebuggingTransition == DEBUG_TRANSITIONS_VERBOSE )
{
Msg( "Trying %s (%s): ", pEntity->GetClassname(), pEntity->GetDebugName() );
}
int caps = pEntity->ObjectCaps();
if ( caps & FCAP_DONT_SAVE )
{
if ( g_iDebuggingTransition == DEBUG_TRANSITIONS_VERBOSE )
{
Msg( "IGNORED due to being marked \"Don't save\".\n" );
}
return 0;
}
// If this entity can be moved or is global, mark it
int flags = 0;
if ( caps & FCAP_ACROSS_TRANSITION )
{
flags |= FENTTABLE_MOVEABLE;
}
if ( pEntity->m_iGlobalname != NULL_STRING && !pEntity->IsDormant() )
{
flags |= FENTTABLE_GLOBAL;
}
if ( g_iDebuggingTransition == DEBUG_TRANSITIONS_VERBOSE && !flags )
{
Msg( "IGNORED, no across_transition flag & no globalname\n" );
}
return flags;
}
//------------------------------------------------------------------------------
// Adds a single entity to the transition list, if appropriate. Returns the new count
//------------------------------------------------------------------------------
inline int CChangeLevel::AddEntityToTransitionList( CBaseEntity *pEntity, int flags, int nCount, CBaseEntity **ppEntList, int *pEntityFlags )
{
ppEntList[ nCount ] = pEntity;
pEntityFlags[ nCount ] = flags;
++nCount;
// If we're debugging, make it visible
if ( g_iDebuggingTransition )
{
if ( g_iDebuggingTransition == DEBUG_TRANSITIONS_VERBOSE )
{
// In verbose mode we've already printed out what the entity is
Msg("ADDED.\n");
}
else
{
// In non-verbose mode, we just print this line
Msg( "ADDED %s (%s) to transition.\n", pEntity->GetClassname(), pEntity->GetDebugName() );
}
pEntity->m_debugOverlays |= (OVERLAY_BBOX_BIT | OVERLAY_NAME_BIT);
}
return nCount;
}
//------------------------------------------------------------------------------
// Builds the list of entities to bring across a particular transition
//------------------------------------------------------------------------------
int CChangeLevel::BuildEntityTransitionList( CBaseEntity *pLandmarkEntity, const char *pLandmarkName,
CBaseEntity **ppEntList, int *pEntityFlags, int nMaxList )
{
int iEntity = 0;
// Only show debug for the transition to the level we're going to
if ( g_debug_transitions.GetInt() && pLandmarkEntity->NameMatches(st_szNextSpot) )
{
g_iDebuggingTransition = g_debug_transitions.GetInt();
// Show us where the landmark entity is
pLandmarkEntity->m_debugOverlays |= (OVERLAY_PIVOT_BIT | OVERLAY_BBOX_BIT | OVERLAY_NAME_BIT);
}
else
{
g_iDebuggingTransition = 0;
}
// Follow the linked list of entities in the PVS of the transition landmark
CBaseEntity *pEntity = NULL;
while ( (pEntity = UTIL_EntitiesInPVS( pLandmarkEntity, pEntity)) != NULL )
{
int flags = ComputeEntitySaveFlags( pEntity );
if ( !flags )
continue;
// Check to make sure the entity isn't screened out by a trigger_transition
if ( !InTransitionVolume( pEntity, pLandmarkName ) )
{
if ( g_iDebuggingTransition == DEBUG_TRANSITIONS_VERBOSE )
{
Msg( "IGNORED, outside transition volume.\n" );
}
continue;
}
if ( iEntity >= nMaxList )
{
Warning( "Too many entities across a transition!\n" );
Assert( 0 );
return iEntity;
}
iEntity = AddEntityToTransitionList( pEntity, flags, iEntity, ppEntList, pEntityFlags );
}
return iEntity;
}
//------------------------------------------------------------------------------
// Tests bits in a bitfield
//------------------------------------------------------------------------------
static inline bool IsBitSet( char *pBuf, int nBit )
{
return (pBuf[ nBit >> 3 ] & ( 1 << (nBit & 0x7) )) != 0;
}
static inline void SetBit( char *pBuf, int nBit )
{
pBuf[ nBit >> 3 ] |= 1 << (nBit & 0x7);
}
//------------------------------------------------------------------------------
// Adds in all entities depended on by entities near the transition
//------------------------------------------------------------------------------
#define MAX_ENTITY_BYTE_COUNT (NUM_ENT_ENTRIES >> 3)
int CChangeLevel::AddDependentEntities( int nCount, CBaseEntity **ppEntList, int *pEntityFlags, int nMaxList )
{
char pEntitiesSaved[MAX_ENTITY_BYTE_COUNT];
memset( pEntitiesSaved, 0, MAX_ENTITY_BYTE_COUNT * sizeof(char) );
// Populate the initial bitfield
int i;
for ( i = 0; i < nCount; ++i )
{
// NOTE: Must use GetEntryIndex because we're saving non-networked entities
int nEntIndex = ppEntList[i]->GetRefEHandle().GetEntryIndex();
// We shouldn't already have this entity in the list!
Assert( !IsBitSet( pEntitiesSaved, nEntIndex ) );
// Mark the entity as being in the list
SetBit( pEntitiesSaved, nEntIndex );
}
IEntitySaveUtils *pSaveUtils = GetEntitySaveUtils();
// Iterate over entities whose dependencies we've not yet processed
// NOTE: nCount will change value during this loop in AddEntityToTransitionList
for ( i = 0; i < nCount; ++i )
{
CBaseEntity *pEntity = ppEntList[i];
// Find dependencies in the hash.
int nDepCount = pSaveUtils->GetEntityDependencyCount( pEntity );
if ( !nDepCount )
continue;
CBaseEntity **ppDependentEntities = (CBaseEntity**)stackalloc( nDepCount * sizeof(CBaseEntity*) );
pSaveUtils->GetEntityDependencies( pEntity, nDepCount, ppDependentEntities );
for ( int j = 0; j < nDepCount; ++j )
{
CBaseEntity *pDependent = ppDependentEntities[j];
if ( !pDependent )
continue;
// NOTE: Must use GetEntryIndex because we're saving non-networked entities
int nEntIndex = pDependent->GetRefEHandle().GetEntryIndex();
// Don't re-add it if it's already in the list
if ( IsBitSet( pEntitiesSaved, nEntIndex ) )
continue;
// Mark the entity as being in the list
SetBit( pEntitiesSaved, nEntIndex );
int flags = ComputeEntitySaveFlags( pEntity );
if ( flags )
{
if ( nCount >= nMaxList )
{
Warning( "Too many entities across a transition!\n" );
Assert( 0 );
return false;
}
if ( g_debug_transitions.GetInt() )
{
Msg( "ADDED DEPENDANCY: %s (%s)\n", pEntity->GetClassname(), pEntity->GetDebugName() );
}
nCount = AddEntityToTransitionList( pEntity, flags, nCount, ppEntList, pEntityFlags );
}
else
{
Warning("Warning!! Save dependency is linked to an entity that doesn't want to be saved!\n");
}
}
}
return nCount;
}
//------------------------------------------------------------------------------
// This builds the list of all transitions on this level and which entities
// are in their PVS's and can / should be moved across.
//------------------------------------------------------------------------------
// We can only ever move 512 entities across a transition
#define MAX_ENTITY 512
// FIXME: This has grown into a complicated beast. Can we make this more elegant?
int CChangeLevel::ChangeList( levellist_t *pLevelList, int maxList )
{
// Find all of the possible level changes on this BSP
int count = BuildChangeLevelList( pLevelList, maxList );
if ( !gpGlobals->pSaveData || ( static_cast<CSaveRestoreData *>(gpGlobals->pSaveData)->NumEntities() == 0 ) )
return count;
CSave saveHelper( static_cast<CSaveRestoreData *>(gpGlobals->pSaveData) );
// For each level change, find nearby entities and save them
int i;
for ( i = 0; i < count; i++ )
{
CBaseEntity *pEntList[ MAX_ENTITY ];
int entityFlags[ MAX_ENTITY ];
// First, figure out which entities are near the transition
CBaseEntity *pLandmarkEntity = CBaseEntity::Instance( pLevelList[i].pentLandmark );
int iEntity = BuildEntityTransitionList( pLandmarkEntity, pLevelList[i].landmarkName, pEntList, entityFlags, MAX_ENTITY );
// FIXME: Activate if we have a dependency problem on level transition
// Next, add in all entities depended on by entities near the transition
// iEntity = AddDependentEntities( iEntity, pEntList, entityFlags, MAX_ENTITY );
int j;
for ( j = 0; j < iEntity; j++ )
{
// 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) );
}
}
return count;
}
//-----------------------------------------------------------------------------
// Purpose: A trigger that pushes the player, NPCs, or objects.
//-----------------------------------------------------------------------------
class CTriggerPush : public CBaseTrigger
{
public:
DECLARE_CLASS( CTriggerPush, CBaseTrigger );
void Spawn( void );
void Touch( CBaseEntity *pOther );
void Untouch( CBaseEntity *pOther );
Vector m_vecPushDir;
DECLARE_DATADESC();
};
BEGIN_DATADESC( CTriggerPush )
DEFINE_KEYFIELD( m_vecPushDir, FIELD_VECTOR, "pushdir" ),
END_DATADESC()
LINK_ENTITY_TO_CLASS( trigger_push, CTriggerPush );
//-----------------------------------------------------------------------------
// Purpose: Called when spawning, after keyvalues have been handled.
//-----------------------------------------------------------------------------
void CTriggerPush::Spawn( )
{
// Convert pushdir from angles to a vector
Vector vecAbsDir;
QAngle angPushDir = QAngle(m_vecPushDir.x, m_vecPushDir.y, m_vecPushDir.z);
AngleVectors(angPushDir, &vecAbsDir);
// Transform the vector into entity space
VectorIRotate( vecAbsDir, EntityToWorldTransform(), m_vecPushDir );
BaseClass::Spawn();
InitTrigger();
if (m_flSpeed == 0)
{
m_flSpeed = 100;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pOther -
//-----------------------------------------------------------------------------
void CTriggerPush::Touch( CBaseEntity *pOther )
{
if ( !pOther->IsSolid() || (pOther->GetMoveType() == MOVETYPE_PUSH || pOther->GetMoveType() == MOVETYPE_NONE ) )
return;
if (!PassesTriggerFilters(pOther))
return;
// FIXME: If something is hierarchically attached, should we try to push the parent?
if (pOther->GetMoveParent())
return;
// Transform the push dir into global space
Vector vecAbsDir;
VectorRotate( m_vecPushDir, EntityToWorldTransform(), vecAbsDir );
// Instant trigger, just transfer velocity and remove
if (HasSpawnFlags(SF_TRIG_PUSH_ONCE))
{
pOther->ApplyAbsVelocityImpulse( m_flSpeed * vecAbsDir );
if ( vecAbsDir.z > 0 )
{
pOther->SetGroundEntity( NULL );
}
UTIL_Remove( this );
return;
}
switch( pOther->GetMoveType() )
{
case MOVETYPE_NONE:
case MOVETYPE_PUSH:
case MOVETYPE_NOCLIP:
break;
case MOVETYPE_VPHYSICS:
{
IPhysicsObject *pPhys = pOther->VPhysicsGetObject();
if ( pPhys )
{
// UNDONE: Assume the velocity is for a 100kg object, scale with mass
pPhys->ApplyForceCenter( m_flSpeed * vecAbsDir * 100.0f * gpGlobals->frametime );
return;
}
}
break;
default:
{
#if defined( HL2_DLL )
// HACK HACK HL2 players on ladders will only be disengaged if the sf is set, otherwise no push occurs.
if ( pOther->IsPlayer() &&
pOther->GetMoveType() == MOVETYPE_LADDER )
{
if ( !HasSpawnFlags(SF_TRIG_PUSH_AFFECT_PLAYER_ON_LADDER) )
{
// Ignore the push
return;
}
}
#endif
Vector vecPush = (m_flSpeed * vecAbsDir);
if ( pOther->GetFlags() & FL_BASEVELOCITY )
{
vecPush = vecPush + pOther->GetBaseVelocity();
}
if ( vecPush.z > 0 && (pOther->GetFlags() & FL_ONGROUND) )
{
pOther->SetGroundEntity( NULL );
Vector origin = pOther->GetAbsOrigin();
origin.z += 1.0f;
pOther->SetAbsOrigin( origin );
}
#ifdef HL1_DLL
// Apply the z velocity as a force so it counteracts gravity properly
Vector vecImpulse( 0, 0, vecPush.z * 0.025 );//magic hack number
pOther->ApplyAbsVelocityImpulse( vecImpulse );
// apply x, y as a base velocity so we travel at constant speed on conveyors
vecPush.z = 0;
#endif
pOther->SetBaseVelocity( vecPush );
pOther->AddFlag( FL_BASEVELOCITY );
}
break;
}
}
//-----------------------------------------------------------------------------
// Teleport trigger
//-----------------------------------------------------------------------------
const int SF_TELEPORT_PRESERVE_ANGLES = 0x20; // Preserve angles even when a local landmark is not specified
class CTriggerTeleport : public CBaseTrigger
{
public:
DECLARE_CLASS( CTriggerTeleport, CBaseTrigger );
void Spawn( void );
void Touch( CBaseEntity *pOther );
string_t m_iLandmark;
DECLARE_DATADESC();
};
LINK_ENTITY_TO_CLASS( trigger_teleport, CTriggerTeleport );
BEGIN_DATADESC( CTriggerTeleport )
DEFINE_KEYFIELD( m_iLandmark, FIELD_STRING, "landmark" ),
END_DATADESC()
void CTriggerTeleport::Spawn( void )
{
InitTrigger();
}
//-----------------------------------------------------------------------------
// Purpose: Teleports the entity that touched us to the location of our target,
// setting the toucher's angles to our target's angles if they are a
// player.
//
// If a landmark was specified, the toucher is offset from the target
// by their initial offset from the landmark and their angles are
// left alone.
//
// Input : pOther - The entity that touched us.
//-----------------------------------------------------------------------------
void CTriggerTeleport::Touch( CBaseEntity *pOther )
{
CBaseEntity *pentTarget = NULL;
if (!PassesTriggerFilters(pOther))
{
return;
}
// The activator and caller are the same
pentTarget = gEntList.FindEntityByName( pentTarget, m_target, NULL, pOther, pOther );
if (!pentTarget)
{
return;
}
//
// If a landmark was specified, offset the player relative to the landmark.
//
CBaseEntity *pentLandmark = NULL;
Vector vecLandmarkOffset(0, 0, 0);
if (m_iLandmark != NULL_STRING)
{
// The activator and caller are the same
pentLandmark = gEntList.FindEntityByName(pentLandmark, m_iLandmark, NULL, pOther, pOther );
if (pentLandmark)
{
vecLandmarkOffset = pOther->GetAbsOrigin() - pentLandmark->GetAbsOrigin();
}
}
pOther->SetGroundEntity( NULL );
Vector tmp = pentTarget->GetAbsOrigin();
if (!pentLandmark && pOther->IsPlayer())
{
// make origin adjustments in case the teleportee is a player. (origin in center, not at feet)
tmp.z -= pOther->WorldAlignMins().z;
}
//
// Only modify the toucher's angles and zero their velocity if no landmark was specified.
//
const QAngle *pAngles = NULL;
Vector *pVelocity = NULL;
#ifdef HL1_DLL
Vector vecZero(0,0,0);
#endif
if (!pentLandmark && !HasSpawnFlags(SF_TELEPORT_PRESERVE_ANGLES) )
{
pAngles = &pentTarget->GetAbsAngles();
#ifdef HL1_DLL
pVelocity = &vecZero;
#else
pVelocity = NULL; //BUGBUG - This does not set the player's velocity to zero!!!
#endif
}
tmp += vecLandmarkOffset;
pOther->Teleport( &tmp, pAngles, pVelocity );
}
LINK_ENTITY_TO_CLASS( info_teleport_destination, CPointEntity );
//-----------------------------------------------------------------------------
// Purpose: Saves the game when the player touches the trigger. Can be enabled or disabled
//-----------------------------------------------------------------------------
class CTriggerToggleSave : public CBaseTrigger
{
public:
DECLARE_CLASS( CTriggerToggleSave, CBaseTrigger );
void Spawn( void );
void Touch( CBaseEntity *pOther );
void InputEnable( inputdata_t &inputdata )
{
m_bDisabled = false;
}
void InputDisable( inputdata_t &inputdata )
{
m_bDisabled = true;
}
bool m_bDisabled; // Initial state
DECLARE_DATADESC();
};
BEGIN_DATADESC( CTriggerToggleSave )
DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ),
DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
END_DATADESC()
LINK_ENTITY_TO_CLASS( trigger_togglesave, CTriggerToggleSave );
//-----------------------------------------------------------------------------
// Purpose: Called when spawning, after keyvalues have been set.
//-----------------------------------------------------------------------------
void CTriggerToggleSave::Spawn( void )
{
if ( g_pGameRules->IsDeathmatch() )
{
UTIL_Remove( this );
return;
}
InitTrigger();
}
//-----------------------------------------------------------------------------
// Purpose: Performs the autosave when the player touches us.
// Input : pOther -
//-----------------------------------------------------------------------------
void CTriggerToggleSave::Touch( CBaseEntity *pOther )
{
if( m_bDisabled )
return;
// Only save on clients
if ( !pOther->IsPlayer() )
return;
// Can be re-enabled
m_bDisabled = true;
engine->ServerCommand( "autosave\n" );
}
//-----------------------------------------------------------------------------
// Purpose: Saves the game when the player touches the trigger.
//-----------------------------------------------------------------------------
class CTriggerSave : public CBaseTrigger
{
public:
DECLARE_CLASS( CTriggerSave, CBaseTrigger );
void Spawn( void );
void Touch( CBaseEntity *pOther );
DECLARE_DATADESC();
bool m_bForceNewLevelUnit;
float m_fDangerousTimer;
};
BEGIN_DATADESC( CTriggerSave )
DEFINE_KEYFIELD( m_bForceNewLevelUnit, FIELD_BOOLEAN, "NewLevelUnit" ),
DEFINE_KEYFIELD( m_fDangerousTimer, FIELD_FLOAT, "DangerousTimer" ),
END_DATADESC()
LINK_ENTITY_TO_CLASS( trigger_autosave, CTriggerSave );
//-----------------------------------------------------------------------------
// Purpose: Called when spawning, after keyvalues have been set.
//-----------------------------------------------------------------------------
void CTriggerSave::Spawn( void )
{
if ( g_pGameRules->IsDeathmatch() )
{
UTIL_Remove( this );
return;
}
InitTrigger();
}
//-----------------------------------------------------------------------------
// Purpose: Performs the autosave when the player touches us.
// Input : pOther -
//-----------------------------------------------------------------------------
void CTriggerSave::Touch( CBaseEntity *pOther )
{
// Only save on clients
if ( !pOther->IsPlayer() )
return;
if ( m_fDangerousTimer != 0.0f )
{
if ( g_ServerGameDLL.m_fAutoSaveDangerousTime != 0.0f && g_ServerGameDLL.m_fAutoSaveDangerousTime >= gpGlobals->curtime )
{
// A previous dangerous auto save was waiting to become safe
CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
if ( pPlayer->GetDeathTime() == 0.0f || pPlayer->GetDeathTime() > gpGlobals->curtime )
{
// The player isn't dead, so make the dangerous auto save safe
engine->ServerCommand( "autosavedangerousissafe\n" );
}
}
}
// this is a one-way transition - there is no way to return to the previous map.
if ( m_bForceNewLevelUnit )
{
engine->ClearSaveDir();
}
UTIL_Remove( this );
if ( m_fDangerousTimer != 0.0f )
{
// There's a dangerous timer
engine->ServerCommand( "autosavedangerous\n" );
g_ServerGameDLL.m_fAutoSaveDangerousTime = gpGlobals->curtime + m_fDangerousTimer;
}
else
{
engine->ServerCommand( "autosave\n" );
}
}
class CTriggerGravity : public CBaseTrigger
{
public:
DECLARE_CLASS( CTriggerGravity, CBaseTrigger );
DECLARE_DATADESC();
void Spawn( void );
void GravityTouch( CBaseEntity *pOther );
};
LINK_ENTITY_TO_CLASS( trigger_gravity, CTriggerGravity );
BEGIN_DATADESC( CTriggerGravity )
// Function Pointers
DEFINE_FUNCTION(GravityTouch),
END_DATADESC()
void CTriggerGravity::Spawn( void )
{
BaseClass::Spawn();
InitTrigger();
SetTouch( &CTriggerGravity::GravityTouch );
}
void CTriggerGravity::GravityTouch( CBaseEntity *pOther )
{
// Only save on clients
if ( !pOther->IsPlayer() )
return;
pOther->SetGravity( GetGravity() );
}
// this is a really bad idea.
class CAI_ChangeTarget : public CBaseEntity
{
public:
DECLARE_CLASS( CAI_ChangeTarget, CBaseEntity );
// Input handlers.
void InputActivate( inputdata_t &inputdata );
int ObjectCaps( void ) { return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
DECLARE_DATADESC();
private:
string_t m_iszNewTarget;
};
LINK_ENTITY_TO_CLASS( ai_changetarget, CAI_ChangeTarget );
BEGIN_DATADESC( CAI_ChangeTarget )
DEFINE_KEYFIELD( m_iszNewTarget, FIELD_STRING, "m_iszNewTarget" ),
// Inputs
DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ),
END_DATADESC()
void CAI_ChangeTarget::InputActivate( inputdata_t &inputdata )
{
CBaseEntity *pTarget = NULL;
while ((pTarget = gEntList.FindEntityByName( pTarget, m_target, NULL, inputdata.pActivator, inputdata.pCaller )) != NULL)
{
pTarget->m_target = m_iszNewTarget;
CAI_BaseNPC *pNPC = pTarget->MyNPCPointer( );
if (pNPC)
{
pNPC->SetGoalEnt( NULL );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Change an NPC's hint group to something new
//-----------------------------------------------------------------------------
class CAI_ChangeHintGroup : public CBaseEntity
{
public:
DECLARE_CLASS( CAI_ChangeHintGroup, CBaseEntity );
int ObjectCaps( void ) { return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
// Input handlers.
void InputActivate( inputdata_t &inputdata );
DECLARE_DATADESC();
private:
CAI_BaseNPC *FindQualifiedNPC( CAI_BaseNPC *pPrev );
int m_iSearchType;
string_t m_strSearchName;
string_t m_strNewHintGroup;
float m_flRadius;
bool m_bHintGroupNavLimiting;
};
LINK_ENTITY_TO_CLASS( ai_changehintgroup, CAI_ChangeHintGroup );
BEGIN_DATADESC( CAI_ChangeHintGroup )
DEFINE_KEYFIELD( m_iSearchType, FIELD_INTEGER, "SearchType" ),
DEFINE_KEYFIELD( m_strSearchName, FIELD_STRING, "SearchName" ),
DEFINE_KEYFIELD( m_strNewHintGroup, FIELD_STRING, "NewHintGroup" ),
DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "Radius" ),
DEFINE_KEYFIELD( m_bHintGroupNavLimiting, FIELD_BOOLEAN, "hintlimiting" ),
DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ),
END_DATADESC()
CAI_BaseNPC *CAI_ChangeHintGroup::FindQualifiedNPC( CAI_BaseNPC *pPrev )
{
CBaseEntity *pEntity = pPrev;
CAI_BaseNPC *pResult = NULL;
const char *pszSearchName = STRING(m_strSearchName);
while ( !pResult )
{
// Find a candidate
switch ( m_iSearchType )
{
case 0:
{
pEntity = gEntList.FindEntityByNameWithin( pEntity, pszSearchName, GetLocalOrigin(), m_flRadius );
break;
}
case 1:
{
pEntity = gEntList.FindEntityByClassnameWithin( pEntity, pszSearchName, GetLocalOrigin(), m_flRadius );
break;
}
case 2:
{
pEntity = gEntList.FindEntityInSphere( pEntity, GetLocalOrigin(), ( m_flRadius != 0.0 ) ? m_flRadius : FLT_MAX );
break;
}
}
if ( !pEntity )
return NULL;
// Qualify
pResult = pEntity->MyNPCPointer();
if ( pResult && m_iSearchType == 2 && (!FStrEq( STRING(pResult->GetHintGroup()), pszSearchName ) ) )
{
pResult = NULL;
}
}
return pResult;
}
void CAI_ChangeHintGroup::InputActivate( inputdata_t &inputdata )
{
CAI_BaseNPC *pTarget = NULL;
while((pTarget = FindQualifiedNPC( pTarget )) != NULL)
{
pTarget->SetHintGroup( m_strNewHintGroup, m_bHintGroupNavLimiting );
}
}
#define SF_CAMERA_PLAYER_POSITION 1
#define SF_CAMERA_PLAYER_TARGET 2
#define SF_CAMERA_PLAYER_TAKECONTROL 4
#define SF_CAMERA_PLAYER_INFINITE_WAIT 8
#define SF_CAMERA_PLAYER_SNAP_TO 16
#define SF_CAMERA_PLAYER_NOT_SOLID 32
#define SF_CAMERA_PLAYER_INTERRUPT 64
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CTriggerCamera : public CBaseEntity
{
public:
DECLARE_CLASS( CTriggerCamera, CBaseEntity );
void Spawn( void );
bool KeyValue( const char *szKeyName, const char *szValue );
void Enable( void );
void Disable( void );
void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
void FollowTarget( void );
void Move(void);
// Always transmit to clients so they know where to move the view to
virtual int UpdateTransmitState();
DECLARE_DATADESC();
// Input handlers
void InputEnable( inputdata_t &inputdata );
void InputDisable( inputdata_t &inputdata );
private:
EHANDLE m_hPlayer;
EHANDLE m_hTarget;
CBaseEntity *m_pPath;
string_t 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;
Vector m_vecMoveDir;
string_t m_iszTargetAttachment;
int m_iAttachmentIndex;
bool m_bSnapToGoal;
int m_nPlayerButtons;
private:
COutputEvent m_OnEndFollow;
};
LINK_ENTITY_TO_CLASS( point_viewcontrol, CTriggerCamera );
BEGIN_DATADESC( CTriggerCamera )
DEFINE_FIELD( m_hPlayer, FIELD_EHANDLE ),
DEFINE_FIELD( m_hTarget, FIELD_EHANDLE ),
DEFINE_FIELD( m_pPath, FIELD_CLASSPTR ),
DEFINE_FIELD( m_sPath, FIELD_STRING ),
DEFINE_FIELD( m_flWait, FIELD_FLOAT ),
DEFINE_FIELD( m_flReturnTime, FIELD_TIME ),
DEFINE_FIELD( m_flStopTime, FIELD_TIME ),
DEFINE_FIELD( m_moveDistance, FIELD_FLOAT ),
DEFINE_FIELD( m_targetSpeed, FIELD_FLOAT ),
DEFINE_FIELD( m_initialSpeed, FIELD_FLOAT ),
DEFINE_FIELD( m_acceleration, FIELD_FLOAT ),
DEFINE_FIELD( m_deceleration, FIELD_FLOAT ),
DEFINE_FIELD( m_state, FIELD_INTEGER ),
DEFINE_FIELD( m_vecMoveDir, FIELD_VECTOR ),
DEFINE_KEYFIELD( m_iszTargetAttachment, FIELD_STRING, "targetattachment" ),
DEFINE_FIELD( m_iAttachmentIndex, FIELD_INTEGER ),
DEFINE_FIELD( m_bSnapToGoal, FIELD_BOOLEAN ),
DEFINE_FIELD( m_nPlayerButtons, FIELD_INTEGER ),
// Inputs
DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
// Function Pointers
DEFINE_FUNCTION( FollowTarget ),
DEFINE_OUTPUT( m_OnEndFollow, "OnEndFollow" ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTriggerCamera::Spawn( void )
{
BaseClass::Spawn();
SetMoveType( MOVETYPE_NOCLIP );
SetSolid( SOLID_NONE ); // Remove model & collisions
SetRenderColorA( 0 ); // The engine won't draw this model if this is set to 0 and blending is on
m_nRenderMode = kRenderTransTexture;
m_state = USE_OFF;
m_initialSpeed = m_flSpeed;
if ( m_acceleration == 0 )
m_acceleration = 500;
if ( m_deceleration == 0 )
m_deceleration = 500;
DispatchUpdateTransmitState();
}
int CTriggerCamera::UpdateTransmitState()
{
// always tranmit if currently used by a monitor
if ( m_state == USE_ON )
{
return SetTransmitState( FL_EDICT_ALWAYS );
}
else
{
return SetTransmitState( FL_EDICT_DONTSEND );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTriggerCamera::KeyValue( const char *szKeyName, const char *szValue )
{
if (FStrEq(szKeyName, "wait"))
{
m_flWait = atof(szValue);
}
else if (FStrEq(szKeyName, "moveto"))
{
m_sPath = AllocPooledString( szValue );
}
else if (FStrEq(szKeyName, "acceleration"))
{
m_acceleration = atof( szValue );
}
else if (FStrEq(szKeyName, "deceleration"))
{
m_deceleration = atof( szValue );
}
else
return BaseClass::KeyValue( szKeyName, szValue );
return true;
}
//------------------------------------------------------------------------------
// Purpose: Input handler to turn on this trigger.
//------------------------------------------------------------------------------
void CTriggerCamera::InputEnable( inputdata_t &inputdata )
{
m_hPlayer = inputdata.pActivator;
Enable();
}
//------------------------------------------------------------------------------
// Purpose: Input handler to turn off this trigger.
//------------------------------------------------------------------------------
void CTriggerCamera::InputDisable( inputdata_t &inputdata )
{
Disable();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTriggerCamera::Enable( void )
{
m_state = USE_ON;
if ( !m_hPlayer || !m_hPlayer->IsPlayer() )
{
m_hPlayer = UTIL_GetLocalPlayer();
}
if ( !m_hPlayer )
{
DispatchUpdateTransmitState();
return;
}
if ( m_hPlayer->IsPlayer() )
{
m_nPlayerButtons = ((CBasePlayer*)m_hPlayer.Get())->m_nButtons;
}
if ( HasSpawnFlags( SF_CAMERA_PLAYER_NOT_SOLID ) )
{
m_hPlayer->AddSolidFlags( FSOLID_NOT_SOLID );
}
m_flReturnTime = gpGlobals->curtime + m_flWait;
m_flSpeed = m_initialSpeed;
m_targetSpeed = m_initialSpeed;
if ( HasSpawnFlags( SF_CAMERA_PLAYER_SNAP_TO ) )
{
m_bSnapToGoal = true;
}
if ( HasSpawnFlags(SF_CAMERA_PLAYER_TARGET ) )
{
m_hTarget = m_hPlayer;
}
else
{
m_hTarget = GetNextTarget();
}
// If we don't have a target, ignore the attachment / etc
if ( m_hTarget )
{
m_iAttachmentIndex = 0;
if ( m_iszTargetAttachment != NULL_STRING )
{
if ( !m_hTarget->GetBaseAnimating() )
{
Warning("%s tried to target an attachment (%s) on target %s, which has no model.\n", GetClassname(), STRING(m_iszTargetAttachment), STRING(m_hTarget->GetEntityName()) );
}
else
{
m_iAttachmentIndex = m_hTarget->GetBaseAnimating()->LookupAttachment( STRING(m_iszTargetAttachment) );
if ( !m_iAttachmentIndex )
{
Warning("%s could not find attachment %s on target %s.\n", GetClassname(), STRING(m_iszTargetAttachment), STRING(m_hTarget->GetEntityName()) );
}
}
}
}
if (HasSpawnFlags(SF_CAMERA_PLAYER_TAKECONTROL ) )
{
((CBasePlayer*)m_hPlayer.Get())->EnableControl(FALSE);
}
if ( m_sPath != NULL_STRING )
{
m_pPath = gEntList.FindEntityByName( NULL, m_sPath, NULL, m_hPlayer );
}
else
{
m_pPath = NULL;
}
m_flStopTime = gpGlobals->curtime;
if ( m_pPath )
{
if ( m_pPath->m_flSpeed != 0 )
m_targetSpeed = m_pPath->m_flSpeed;
m_flStopTime += m_pPath->GetDelay();
}
// copy over player information
if (HasSpawnFlags(SF_CAMERA_PLAYER_POSITION ) )
{
UTIL_SetOrigin( this, m_hPlayer->EyePosition() );
SetLocalAngles( QAngle( m_hPlayer->GetLocalAngles().x, m_hPlayer->GetLocalAngles().y, 0 ) );
SetAbsVelocity( m_hPlayer->GetAbsVelocity() );
}
else
{
SetAbsVelocity( vec3_origin );
}
((CBasePlayer*)m_hPlayer.Get())->SetViewEntity( this );
// Hide the player's viewmodel
if ( ((CBasePlayer*)m_hPlayer.Get())->GetActiveWeapon() )
{
((CBasePlayer*)m_hPlayer.Get())->GetActiveWeapon()->AddEffects( EF_NODRAW );
}
// Only track if we have a target
if ( m_hTarget )
{
// follow the player down
SetThink( &CTriggerCamera::FollowTarget );
SetNextThink( gpGlobals->curtime );
}
m_moveDistance = 0;
Move();
DispatchUpdateTransmitState();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTriggerCamera::Disable( void )
{
if ( m_hPlayer && m_hPlayer->IsAlive() )
{
if ( HasSpawnFlags( SF_CAMERA_PLAYER_NOT_SOLID ) )
{
m_hPlayer->RemoveSolidFlags( FSOLID_NOT_SOLID );
}
((CBasePlayer*)m_hPlayer.Get())->SetViewEntity( m_hPlayer );
((CBasePlayer*)m_hPlayer.Get())->EnableControl(TRUE);
// Restore the player's viewmodel
if ( ((CBasePlayer*)m_hPlayer.Get())->GetActiveWeapon() )
{
((CBasePlayer*)m_hPlayer.Get())->GetActiveWeapon()->RemoveEffects( EF_NODRAW );
}
}
m_state = USE_OFF;
m_flReturnTime = gpGlobals->curtime;
SetThink( NULL );
m_OnEndFollow.FireOutput(this, this); // dvsents2: what is the best name for this output?
SetLocalAngularVelocity( vec3_angle );
DispatchUpdateTransmitState();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTriggerCamera::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
if ( !ShouldToggle( useType, m_state ) )
return;
// Toggle state
if ( m_state != USE_OFF )
{
Disable();
}
else
{
m_hPlayer = pActivator;
Enable();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTriggerCamera::FollowTarget( )
{
if (m_hPlayer == NULL)
return;
if ( m_hTarget == NULL )
{
Disable();
return;
}
if ( !HasSpawnFlags(SF_CAMERA_PLAYER_INFINITE_WAIT) && (!m_hTarget || m_flReturnTime < gpGlobals->curtime) )
{
Disable();
return;
}
QAngle vecGoal;
if ( m_iAttachmentIndex )
{
Vector vecOrigin;
m_hTarget->GetBaseAnimating()->GetAttachment( m_iAttachmentIndex, vecOrigin );
VectorAngles( vecOrigin - GetAbsOrigin(), vecGoal );
}
else
{
if ( m_hTarget )
{
VectorAngles( m_hTarget->GetAbsOrigin() - GetAbsOrigin(), vecGoal );
}
else
{
// Use the viewcontroller's angles
vecGoal = GetAbsAngles();
}
}
// Should we just snap to the goal angles?
if ( m_bSnapToGoal )
{
SetAbsAngles( vecGoal );
m_bSnapToGoal = false;
}
else
{
// UNDONE: Can't we just use UTIL_AngleDiff here?
QAngle angles = GetLocalAngles();
if (angles.y > 360)
angles.y -= 360;
if (angles.y < 0)
angles.y += 360;
SetLocalAngles( angles );
float dx = vecGoal.x - GetLocalAngles().x;
float dy = vecGoal.y - GetLocalAngles().y;
if (dx < -180)
dx += 360;
if (dx > 180)
dx = dx - 360;
if (dy < -180)
dy += 360;
if (dy > 180)
dy = dy - 360;
QAngle vecAngVel;
vecAngVel.Init( dx * 40 * gpGlobals->frametime, dy * 40 * gpGlobals->frametime, GetLocalAngularVelocity().z );
SetLocalAngularVelocity(vecAngVel);
}
if (!HasSpawnFlags(SF_CAMERA_PLAYER_TAKECONTROL))
{
SetAbsVelocity( GetAbsVelocity() * 0.8 );
if (GetAbsVelocity().Length( ) < 10.0)
{
SetAbsVelocity( vec3_origin );
}
}
SetNextThink( gpGlobals->curtime );
Move();
}
void CTriggerCamera::Move()
{
if ( HasSpawnFlags( SF_CAMERA_PLAYER_INTERRUPT ) )
{
if ( m_hPlayer )
{
CBasePlayer *pPlayer = ToBasePlayer( m_hPlayer );
if ( pPlayer )
{
int buttonsChanged = m_nPlayerButtons ^ pPlayer->m_nButtons;
if ( buttonsChanged && pPlayer->m_nButtons )
{
Disable();
return;
}
m_nPlayerButtons = pPlayer->m_nButtons;
}
}
}
// Not moving on a path, return
if (!m_pPath)
return;
// Subtract movement from the previous frame
m_moveDistance -= m_flSpeed * gpGlobals->frametime;
// Have we moved enough to reach the target?
if ( m_moveDistance <= 0 )
{
variant_t emptyVariant;
m_pPath->AcceptInput( "InPass", this, this, emptyVariant, 0 );
// Time to go to the next target
m_pPath = m_pPath->GetNextTarget();
// Set up next corner
if ( !m_pPath )
{
SetAbsVelocity( vec3_origin );
}
else
{
if ( m_pPath->m_flSpeed != 0 )
m_targetSpeed = m_pPath->m_flSpeed;
m_vecMoveDir = m_pPath->GetLocalOrigin() - GetLocalOrigin();
m_moveDistance = VectorNormalize( m_vecMoveDir );
m_flStopTime = gpGlobals->curtime + m_pPath->GetDelay();
}
}
if ( m_flStopTime > gpGlobals->curtime )
m_flSpeed = UTIL_Approach( 0, m_flSpeed, m_deceleration * gpGlobals->frametime );
else
m_flSpeed = UTIL_Approach( m_targetSpeed, m_flSpeed, m_acceleration * gpGlobals->frametime );
float fraction = 2 * gpGlobals->frametime;
SetAbsVelocity( ((m_vecMoveDir * m_flSpeed) * fraction) + (GetAbsVelocity() * (1-fraction)) );
}
//-----------------------------------------------------------------------------
// Purpose: Starts/stops cd audio tracks
//-----------------------------------------------------------------------------
class CTriggerCDAudio : public CBaseTrigger
{
public:
DECLARE_CLASS( CTriggerCDAudio, CBaseTrigger );
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 );
//-----------------------------------------------------------------------------
// Purpose: Changes tracks or stops CD when player touches
// Input : pOther - The entity that touched us.
//-----------------------------------------------------------------------------
void CTriggerCDAudio::Touch ( CBaseEntity *pOther )
{
if ( !pOther->IsPlayer() )
{
return;
}
PlayTrack();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTriggerCDAudio::Spawn( void )
{
BaseClass::Spawn();
InitTrigger();
}
void CTriggerCDAudio::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
PlayTrack();
}
//-----------------------------------------------------------------------------
// Purpose: Issues a client command to play a given CD track. Called from
// trigger_cdaudio and target_cdaudio.
// Input : iTrack - Track number to play.
//-----------------------------------------------------------------------------
static void PlayCDTrack( int iTrack )
{
edict_t *pClient;
// manually find the single player.
pClient = engine->PEntityOfEntIndex( 1 );
Assert(gpGlobals->maxClients == 1);
// Can't play if the client is not connected!
if ( !pClient )
return;
// UNDONE: Move this to engine sound
if ( iTrack < -1 || iTrack > 30 )
{
Warning( "TriggerCDAudio - Track %d out of range\n" );
return;
}
if ( iTrack == -1 )
{
engine->ClientCommand ( pClient, "cd pause\n");
}
else
{
char string [ 64 ];
Q_snprintf( string,sizeof(string), "cd play %3d\n", iTrack );
engine->ClientCommand ( pClient, string);
}
}
// only plays for ONE client, so only use in single play!
void CTriggerCDAudio::PlayTrack( void )
{
PlayCDTrack( (int)m_iHealth );
SetTouch( NULL );
UTIL_Remove( this );
}
//-----------------------------------------------------------------------------
// Purpose: Measures the proximity to a specified entity of any entities within
// the trigger, provided they are within a given radius of the specified
// entity. The nearest entity distance is output as a number from [0 - 1].
//-----------------------------------------------------------------------------
class CTriggerProximity : public CBaseTrigger
{
public:
DECLARE_CLASS( CTriggerProximity, CBaseTrigger );
virtual void Spawn(void);
virtual void Activate(void);
virtual void StartTouch(CBaseEntity *pOther);
virtual void EndTouch(CBaseEntity *pOther);
void MeasureThink(void);
protected:
EHANDLE m_hMeasureTarget;
string_t m_iszMeasureTarget; // The entity from which we measure proximities.
float m_fRadius; // The radius around the measure target that we measure within.
int m_nTouchers; // Number of entities touching us.
// Outputs
COutputFloat m_NearestEntityDistance;
DECLARE_DATADESC();
};
BEGIN_DATADESC( CTriggerProximity )
// Functions
DEFINE_FUNCTION(MeasureThink),
// Keys
DEFINE_KEYFIELD(m_iszMeasureTarget, FIELD_STRING, "measuretarget"),
DEFINE_FIELD( m_hMeasureTarget, FIELD_EHANDLE ),
DEFINE_KEYFIELD(m_fRadius, FIELD_FLOAT, "radius"),
DEFINE_FIELD( m_nTouchers, FIELD_INTEGER ),
// Outputs
DEFINE_OUTPUT(m_NearestEntityDistance, "NearestEntityDistance"),
END_DATADESC()
LINK_ENTITY_TO_CLASS(trigger_proximity, CTriggerProximity);
LINK_ENTITY_TO_CLASS(logic_proximity, CPointEntity);
//-----------------------------------------------------------------------------
// Purpose: Called when spawning, after keyvalues have been handled.
//-----------------------------------------------------------------------------
void CTriggerProximity::Spawn(void)
{
// Avoid divide by zero in MeasureThink!
if (m_fRadius == 0)
{
m_fRadius = 32;
}
InitTrigger();
}
//-----------------------------------------------------------------------------
// Purpose: Called after all entities have spawned and after a load game.
// Finds the reference point from which to measure.
//-----------------------------------------------------------------------------
void CTriggerProximity::Activate(void)
{
BaseClass::Activate();
m_hMeasureTarget = gEntList.FindEntityByName(NULL, m_iszMeasureTarget );
//
// Disable our Touch function if we were given a bad measure target.
//
if ((m_hMeasureTarget == NULL) || (m_hMeasureTarget->edict() == NULL))
{
Warning( "TriggerProximity - Missing measure target or measure target with no origin!\n");
}
}
//-----------------------------------------------------------------------------
// Purpose: Decrements the touch count and cancels the think if the count reaches
// zero.
// Input : pOther -
//-----------------------------------------------------------------------------
void CTriggerProximity::StartTouch(CBaseEntity *pOther)
{
BaseClass::StartTouch( pOther );
if ( PassesTriggerFilters( pOther ) )
{
m_nTouchers++;
SetThink( &CTriggerProximity::MeasureThink );
SetNextThink( gpGlobals->curtime );
}
}
//-----------------------------------------------------------------------------
// Purpose: Decrements the touch count and cancels the think if the count reaches
// zero.
// Input : pOther -
//-----------------------------------------------------------------------------
void CTriggerProximity::EndTouch(CBaseEntity *pOther)
{
BaseClass::EndTouch( pOther );
if ( PassesTriggerFilters( pOther ) )
{
m_nTouchers--;
if ( m_nTouchers == 0 )
{
SetThink( NULL );
SetNextThink( TICK_NEVER_THINK );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Think function called every frame as long as we have entities touching
// us that we care about. Finds the closest entity to the measure
// target and outputs the distance as a normalized value from [0..1].
//-----------------------------------------------------------------------------
void CTriggerProximity::MeasureThink( void )
{
if ( ( m_hMeasureTarget == NULL ) || ( m_hMeasureTarget->edict() == NULL ) )
{
SetThink(NULL);
SetNextThink( TICK_NEVER_THINK );
return;
}
//
// Traverse our list of touchers and find the entity that is closest to the
// measure target.
//
float fMinDistance = m_fRadius + 100;
CBaseEntity *pNearestEntity = NULL;
touchlink_t *root = ( touchlink_t * )GetDataObject( TOUCHLINK );
if ( root )
{
touchlink_t *pLink = root->nextLink;
while ( pLink != root )
{
CBaseEntity *pEntity = pLink->entityTouched;
// If this is an entity that we care about, check its distance.
if ( ( pEntity != NULL ) && PassesTriggerFilters( pEntity ) )
{
float flDistance = (pEntity->GetLocalOrigin() - m_hMeasureTarget->GetLocalOrigin()).Length();
if (flDistance < fMinDistance)
{
fMinDistance = flDistance;
pNearestEntity = pEntity;
}
}
pLink = pLink->nextLink;
}
}
// Update our output with the nearest entity distance, normalized to [0..1].
if ( fMinDistance <= m_fRadius )
{
fMinDistance /= m_fRadius;
if ( fMinDistance != m_NearestEntityDistance.Get() )
{
m_NearestEntityDistance.Set( fMinDistance, pNearestEntity, this );
}
}
SetNextThink( gpGlobals->curtime );
}
// ##################################################################################
// >> TriggerWind
//
// Blows physics objects in the trigger
//
// ##################################################################################
#define MAX_WIND_CHANGE 5.0f
//------------------------------------------------------------------------------
// Purpose :
// Input :
// Output :
//------------------------------------------------------------------------------
class CPhysicsWind : public IMotionEvent
{
DECLARE_SIMPLE_DATADESC();
public:
simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
{
// If we have no windspeed, we're not doing anything
if ( !m_flWindSpeed )
return IMotionEvent::SIM_NOTHING;
// Get a cosine modulated noise between 5 and 20 that is object specific
int nNoiseMod = 5+(int)pObject%15; //
// Turn wind yaw direction into a vector and add noise
QAngle vWindAngle = vec3_angle;
vWindAngle[1] = m_nWindYaw+(30*cos(nNoiseMod * gpGlobals->curtime + nNoiseMod));
Vector vWind;
AngleVectors(vWindAngle,&vWind);
// Add lift with noise
vWind.z = 1.1 + (1.0 * sin(nNoiseMod * gpGlobals->curtime + nNoiseMod));
linear = 3*vWind*m_flWindSpeed;
angular = vec3_origin;
return IMotionEvent::SIM_GLOBAL_FORCE;
}
int m_nWindYaw;
float m_flWindSpeed;
};
BEGIN_SIMPLE_DATADESC( CPhysicsWind )
DEFINE_FIELD( m_nWindYaw, FIELD_INTEGER ),
DEFINE_FIELD( m_flWindSpeed, FIELD_FLOAT ),
END_DATADESC()
extern short g_sModelIndexSmoke;
extern float GetFloorZ(const Vector &origin);
#define WIND_THINK_CONTEXT "WindThinkContext"
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CTriggerWind : public CBaseVPhysicsTrigger
{
DECLARE_CLASS( CTriggerWind, CBaseVPhysicsTrigger );
public:
DECLARE_DATADESC();
void Spawn( void );
bool KeyValue( const char *szKeyName, const char *szValue );
void OnRestore();
void UpdateOnRemove();
bool CreateVPhysics();
void StartTouch( CBaseEntity *pOther );
void EndTouch( CBaseEntity *pOther );
void WindThink( void );
int DrawDebugTextOverlays( void );
// Input handlers
void InputEnable( inputdata_t &inputdata );
void InputSetSpeed( inputdata_t &inputdata );
private:
int m_nSpeedBase; // base line for how hard the wind blows
int m_nSpeedNoise; // noise added to wind speed +/-
int m_nSpeedCurrent;// current wind speed
int m_nSpeedTarget; // wind speed I'm approaching
int m_nDirBase; // base line for direction the wind blows (yaw)
int m_nDirNoise; // noise added to wind direction
int m_nDirCurrent; // the current wind direction
int m_nDirTarget; // wind direction I'm approaching
int m_nHoldBase; // base line for how long to wait before changing wind
int m_nHoldNoise; // noise added to how long to wait before changing wind
bool m_bSwitch; // when does wind change
IPhysicsMotionController* m_pWindController;
CPhysicsWind m_WindCallback;
};
LINK_ENTITY_TO_CLASS( trigger_wind, CTriggerWind );
BEGIN_DATADESC( CTriggerWind )
DEFINE_FIELD( m_nSpeedCurrent, FIELD_INTEGER),
DEFINE_FIELD( m_nSpeedTarget, FIELD_INTEGER),
DEFINE_FIELD( m_nDirBase, FIELD_INTEGER),
DEFINE_FIELD( m_nDirCurrent, FIELD_INTEGER),
DEFINE_FIELD( m_nDirTarget, FIELD_INTEGER),
DEFINE_FIELD( m_bSwitch, FIELD_BOOLEAN),
DEFINE_FIELD( m_nSpeedBase, FIELD_INTEGER ),
DEFINE_KEYFIELD( m_nSpeedNoise, FIELD_INTEGER, "SpeedNoise"),
DEFINE_KEYFIELD( m_nDirNoise, FIELD_INTEGER, "DirectionNoise"),
DEFINE_KEYFIELD( m_nHoldBase, FIELD_INTEGER, "HoldTime"),
DEFINE_KEYFIELD( m_nHoldNoise, FIELD_INTEGER, "HoldNoise"),
DEFINE_PHYSPTR( m_pWindController ),
DEFINE_EMBEDDED( m_WindCallback ),
DEFINE_FUNCTION( WindThink ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSpeed", InputSetSpeed ),
END_DATADESC()
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerWind::Spawn( void )
{
m_bSwitch = true;
m_nDirBase = static_cast<int>(GetLocalAngles().y);
BaseClass::Spawn();
m_nSpeedCurrent = m_nSpeedBase;
m_nDirCurrent = m_nDirBase;
SetContextThink( &CTriggerWind::WindThink, gpGlobals->curtime, WIND_THINK_CONTEXT );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTriggerWind::KeyValue( const char *szKeyName, const char *szValue )
{
// Done here to avoid collision with CBaseEntity's speed key
if ( FStrEq(szKeyName, "Speed") )
{
m_nSpeedBase = atoi( szValue );
}
else
return BaseClass::KeyValue( szKeyName, szValue );
return true;
}
//------------------------------------------------------------------------------
// Create VPhysics
//------------------------------------------------------------------------------
bool CTriggerWind::CreateVPhysics()
{
BaseClass::CreateVPhysics();
m_pWindController = physenv->CreateMotionController( &m_WindCallback );
return true;
}
//------------------------------------------------------------------------------
// Cleanup
//------------------------------------------------------------------------------
void CTriggerWind::UpdateOnRemove()
{
if ( m_pWindController )
{
physenv->DestroyMotionController( m_pWindController );
m_pWindController = NULL;
}
BaseClass::UpdateOnRemove();
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerWind::OnRestore()
{
BaseClass::OnRestore();
if ( m_pWindController )
{
m_pWindController->SetEventHandler( &m_WindCallback );
}
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerWind::StartTouch(CBaseEntity *pOther)
{
if ( !PassesTriggerFilters(pOther) )
return;
if ( pOther->IsPlayer() )
return;
IPhysicsObject *pPhys = pOther->VPhysicsGetObject();
if ( pPhys)
{
m_pWindController->AttachObject( pPhys, false );
pPhys->Wake();
}
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerWind::EndTouch(CBaseEntity *pOther)
{
if ( !PassesTriggerFilters(pOther) )
return;
if ( pOther->IsPlayer() )
return;
IPhysicsObject *pPhys = pOther->VPhysicsGetObject();
if ( pPhys && m_pWindController )
{
m_pWindController->DetachObject( pPhys );
}
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerWind::InputEnable( inputdata_t &inputdata )
{
BaseClass::InputEnable( inputdata );
SetContextThink( &CTriggerWind::WindThink, gpGlobals->curtime + 0.1f, WIND_THINK_CONTEXT );
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerWind::WindThink( void )
{
// By default...
SetContextThink( &CTriggerWind::WindThink, gpGlobals->curtime + 0.1, WIND_THINK_CONTEXT );
// Is it time to change the wind?
if (m_bSwitch)
{
m_bSwitch = false;
// Set new target direction and speed
m_nSpeedTarget = m_nSpeedBase + random->RandomInt( -m_nSpeedNoise, m_nSpeedNoise );
m_nDirTarget = static_cast<int>(UTIL_AngleMod( m_nDirBase + random->RandomInt(-m_nDirNoise, m_nDirNoise) ));
}
else
{
bool bDone = true;
// either ramp up, or sleep till change
if (abs(m_nSpeedTarget - m_nSpeedCurrent) > MAX_WIND_CHANGE)
{
m_nSpeedCurrent += (m_nSpeedTarget > m_nSpeedCurrent) ? static_cast<int>(MAX_WIND_CHANGE) : static_cast<int>(-MAX_WIND_CHANGE);
bDone = false;
}
if (abs(m_nDirTarget - m_nDirCurrent) > MAX_WIND_CHANGE)
{
m_nDirCurrent = static_cast<int>(UTIL_ApproachAngle( m_nDirTarget, m_nDirCurrent, MAX_WIND_CHANGE ));
bDone = false;
}
if (bDone)
{
m_nSpeedCurrent = m_nSpeedTarget;
SetContextThink( &CTriggerWind::WindThink, m_nHoldBase + random->RandomFloat(-m_nHoldNoise,m_nHoldNoise), WIND_THINK_CONTEXT );
m_bSwitch = true;
}
}
// If we're starting to blow, where we weren't before, wake up all our objects
if ( m_nSpeedCurrent )
{
m_pWindController->WakeObjects();
}
// store the wind data in the controller callback
m_WindCallback.m_nWindYaw = m_nDirCurrent;
if ( m_bDisabled )
{
m_WindCallback.m_flWindSpeed = 0;
}
else
{
m_WindCallback.m_flWindSpeed = m_nSpeedCurrent;
}
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerWind::InputSetSpeed( inputdata_t &inputdata )
{
// Set new speed and mark to switch
m_nSpeedBase = inputdata.value.Int();
m_bSwitch = true;
}
//-----------------------------------------------------------------------------
// Purpose: Draw any debug text overlays
// Output : Current text offset from the top
//-----------------------------------------------------------------------------
int CTriggerWind::DrawDebugTextOverlays(void)
{
int text_offset = BaseClass::DrawDebugTextOverlays();
if (m_debugOverlays & OVERLAY_TEXT_BIT)
{
// --------------
// Print Target
// --------------
char tempstr[255];
Q_snprintf(tempstr,sizeof(tempstr),"Dir: %i (%i)",m_nDirCurrent,m_nDirTarget);
EntityText(text_offset,tempstr,0);
text_offset++;
Q_snprintf(tempstr,sizeof(tempstr),"Speed: %i (%i)",m_nSpeedCurrent,m_nSpeedTarget);
EntityText(text_offset,tempstr,0);
text_offset++;
}
return text_offset;
}
// ##################################################################################
// >> TriggerImpact
//
// Blows physics objects in the trigger
//
// ##################################################################################
#define TRIGGERIMPACT_VIEWKICK_SCALE 0.1
class CTriggerImpact : public CTriggerMultiple
{
DECLARE_CLASS( CTriggerImpact, CTriggerMultiple );
public:
DECLARE_DATADESC();
float m_flMagnitude;
float m_flNoise;
float m_flViewkick;
void Spawn( void );
void StartTouch( CBaseEntity *pOther );
// Inputs
void InputSetMagnitude( inputdata_t &inputdata );
void InputImpact( inputdata_t &inputdata );
// Outputs
COutputVector m_pOutputForce; // Output force in case anyone else wants to use it
// Debug
int DrawDebugTextOverlays(void);
};
LINK_ENTITY_TO_CLASS( trigger_impact, CTriggerImpact );
BEGIN_DATADESC( CTriggerImpact )
DEFINE_KEYFIELD( m_flMagnitude, FIELD_FLOAT, "Magnitude"),
DEFINE_KEYFIELD( m_flNoise, FIELD_FLOAT, "Noise"),
DEFINE_KEYFIELD( m_flViewkick, FIELD_FLOAT, "Viewkick"),
// Inputs
DEFINE_INPUTFUNC( FIELD_VOID, "Impact", InputImpact ),
DEFINE_INPUTFUNC( FIELD_FLOAT, "SetMagnitude", InputSetMagnitude ),
// Outputs
DEFINE_OUTPUT(m_pOutputForce, "ImpactForce"),
// Function Pointers
DEFINE_FUNCTION( Disable ),
END_DATADESC()
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerImpact::Spawn( void )
{
// Clamp date in case user made an error
m_flNoise = clamp(m_flNoise,0,1);
m_flViewkick = clamp(m_flViewkick,0,1);
// Always start disabled
m_bDisabled = true;
BaseClass::Spawn();
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerImpact::InputImpact( inputdata_t &inputdata )
{
// Output the force vector in case anyone else wants to use it
Vector vDir;
AngleVectors( GetLocalAngles(),&vDir );
m_pOutputForce.Set( m_flMagnitude * vDir, inputdata.pActivator, inputdata.pCaller);
// Enable long enough to throw objects inside me
Enable();
SetNextThink( gpGlobals->curtime + 0.1f );
SetThink(&CTriggerImpact::Disable);
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerImpact::StartTouch(CBaseEntity *pOther)
{
//If the entity is valid and has physics, hit it
if ( ( pOther != NULL ) && ( pOther->VPhysicsGetObject() != NULL ) )
{
Vector vDir;
AngleVectors( GetLocalAngles(),&vDir );
vDir += RandomVector(-m_flNoise,m_flNoise);
pOther->VPhysicsGetObject()->ApplyForceCenter( m_flMagnitude * vDir );
}
// If the player, so a view kick
if (pOther->IsPlayer() && fabs(m_flMagnitude)>0 )
{
Vector vDir;
AngleVectors( GetLocalAngles(),&vDir );
float flPunch = -m_flViewkick*m_flMagnitude*TRIGGERIMPACT_VIEWKICK_SCALE;
pOther->ViewPunch( QAngle( vDir.y * flPunch, 0, vDir.x * flPunch ) );
}
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerImpact::InputSetMagnitude( inputdata_t &inputdata )
{
m_flMagnitude = inputdata.value.Float();
}
//-----------------------------------------------------------------------------
// Purpose: Draw any debug text overlays
// Output : Current text offset from the top
//-----------------------------------------------------------------------------
int CTriggerImpact::DrawDebugTextOverlays(void)
{
int text_offset = BaseClass::DrawDebugTextOverlays();
if (m_debugOverlays & OVERLAY_TEXT_BIT)
{
char tempstr[255];
Q_snprintf(tempstr,sizeof(tempstr),"Magnitude: %3.2f",m_flMagnitude);
EntityText(text_offset,tempstr,0);
text_offset++;
}
return text_offset;
}
//-----------------------------------------------------------------------------
// Purpose: Disables auto movement on players that touch it
//-----------------------------------------------------------------------------
const int SF_TRIGGER_MOVE_AUTODISABLE = 0x80; // disable auto movement
class CTriggerPlayerMovement : public CBaseTrigger
{
DECLARE_CLASS( CTriggerPlayerMovement, CBaseTrigger );
public:
void Spawn( void );
void StartTouch( CBaseEntity *pOther );
void EndTouch( CBaseEntity *pOther );
DECLARE_DATADESC();
};
BEGIN_DATADESC( CTriggerPlayerMovement )
END_DATADESC()
LINK_ENTITY_TO_CLASS( trigger_playermovement, CTriggerPlayerMovement );
//-----------------------------------------------------------------------------
// Purpose: Called when spawning, after keyvalues have been handled.
//-----------------------------------------------------------------------------
void CTriggerPlayerMovement::Spawn( void )
{
if( HasSpawnFlags( SF_TRIGGER_ONLY_PLAYER_ALLY_NPCS ) )
{
// @Note (toml 01-07-04): fix up spawn flag collision coding error. Remove at some point once all maps fixed up please!
DevMsg("*** trigger_playermovement using obsolete spawnflag. Remove and reset with new value for \"Disable auto player movement\"\n" );
RemoveSpawnFlags(SF_TRIGGER_ONLY_PLAYER_ALLY_NPCS);
AddSpawnFlags(SF_TRIGGER_MOVE_AUTODISABLE);
}
BaseClass::Spawn();
InitTrigger();
}
// UNDONE: This will not support a player touching more than one of these
// UNDONE: Do we care? If so, ref count automovement in the player?
void CTriggerPlayerMovement::StartTouch( CBaseEntity *pOther )
{
if (!PassesTriggerFilters(pOther))
return;
CBasePlayer *pPlayer = ToBasePlayer( pOther );
if ( !pPlayer )
return;
// UNDONE: Currently this is the only operation this trigger can do
if ( HasSpawnFlags(SF_TRIGGER_MOVE_AUTODISABLE) )
{
pPlayer->m_Local.m_bAllowAutoMovement = false;
}
}
void CTriggerPlayerMovement::EndTouch( CBaseEntity *pOther )
{
if (!PassesTriggerFilters(pOther))
return;
CBasePlayer *pPlayer = ToBasePlayer( pOther );
if ( !pPlayer )
return;
if ( HasSpawnFlags(SF_TRIGGER_MOVE_AUTODISABLE) )
{
pPlayer->m_Local.m_bAllowAutoMovement = true;
}
}
//------------------------------------------------------------------------------
// Base VPhysics trigger implementation
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// Save/load
//------------------------------------------------------------------------------
BEGIN_DATADESC( CBaseVPhysicsTrigger )
DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ),
DEFINE_KEYFIELD( m_iFilterName, FIELD_STRING, "filtername" ),
DEFINE_FIELD( m_hFilter, FIELD_EHANDLE ),
DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
END_DATADESC()
//------------------------------------------------------------------------------
// Spawn
//------------------------------------------------------------------------------
void CBaseVPhysicsTrigger::Spawn()
{
Precache();
SetSolid( SOLID_VPHYSICS );
AddSolidFlags( FSOLID_NOT_SOLID );
// NOTE: Don't make yourself FSOLID_TRIGGER here or you'll get game
// collisions AND vphysics collisions. You don't want any game collisions
// so just use FSOLID_NOT_SOLID
SetMoveType( MOVETYPE_NONE );
SetModel( STRING( GetModelName() ) ); // set size and link into world
if ( showtriggers.GetInt() == 0 )
{
AddEffects( EF_NODRAW );
}
CreateVPhysics();
}
//------------------------------------------------------------------------------
// Create VPhysics
//------------------------------------------------------------------------------
bool CBaseVPhysicsTrigger::CreateVPhysics()
{
IPhysicsObject *pPhysics;
if ( !HasSpawnFlags( SF_VPHYSICS_MOTION_MOVEABLE ) )
{
pPhysics = VPhysicsInitStatic();
}
else
{
pPhysics = VPhysicsInitShadow( false, false );
}
pPhysics->BecomeTrigger();
return true;
}
//------------------------------------------------------------------------------
// Cleanup
//------------------------------------------------------------------------------
void CBaseVPhysicsTrigger::UpdateOnRemove()
{
if ( VPhysicsGetObject())
{
VPhysicsGetObject()->RemoveTrigger();
}
BaseClass::UpdateOnRemove();
}
//------------------------------------------------------------------------------
// Activate
//------------------------------------------------------------------------------
void CBaseVPhysicsTrigger::Activate( void )
{
// Get a handle to my filter entity if there is one
if (m_iFilterName != NULL_STRING)
{
m_hFilter = dynamic_cast<CBaseFilter *>(gEntList.FindEntityByName( NULL, m_iFilterName ));
}
BaseClass::Activate();
}
//------------------------------------------------------------------------------
// Inputs
//------------------------------------------------------------------------------
void CBaseVPhysicsTrigger::InputToggle( inputdata_t &inputdata )
{
if ( m_bDisabled )
{
InputEnable( inputdata );
}
else
{
InputDisable( inputdata );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBaseVPhysicsTrigger::InputEnable( inputdata_t &inputdata )
{
if ( m_bDisabled )
{
m_bDisabled = false;
if ( VPhysicsGetObject())
{
VPhysicsGetObject()->EnableCollisions( true );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBaseVPhysicsTrigger::InputDisable( inputdata_t &inputdata )
{
if ( !m_bDisabled )
{
m_bDisabled = true;
if ( VPhysicsGetObject())
{
VPhysicsGetObject()->EnableCollisions( false );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBaseVPhysicsTrigger::StartTouch( CBaseEntity *pOther )
{
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBaseVPhysicsTrigger::EndTouch( CBaseEntity *pOther )
{
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CBaseVPhysicsTrigger::PassesTriggerFilters( CBaseEntity *pOther )
{
if ( pOther->GetMoveType() != MOVETYPE_VPHYSICS && !pOther->IsPlayer() )
return false;
// First test spawn flag filters
if ( HasSpawnFlags(SF_TRIGGER_ALLOW_ALL) ||
(HasSpawnFlags(SF_TRIGGER_ALLOW_CLIENTS) && (pOther->GetFlags() & FL_CLIENT)) ||
(HasSpawnFlags(SF_TRIGGER_ALLOW_NPCS) && (pOther->GetFlags() & FL_NPC)) ||
(HasSpawnFlags(SF_TRIGGER_ALLOW_PUSHABLES) && FClassnameIs(pOther, "func_pushable")) ||
(HasSpawnFlags(SF_TRIGGER_ALLOW_PHYSICS) && pOther->GetMoveType() == MOVETYPE_VPHYSICS))
{
bool bOtherIsPlayer = pOther->IsPlayer();
if( HasSpawnFlags(SF_TRIGGER_ONLY_PLAYER_ALLY_NPCS) && !bOtherIsPlayer )
{
CAI_BaseNPC *pNPC = pOther->MyNPCPointer();
if( !pNPC || !pNPC->IsPlayerAlly() )
{
return false;
}
}
if ( HasSpawnFlags(SF_TRIGGER_ONLY_CLIENTS_IN_VEHICLES) && bOtherIsPlayer )
{
if ( !((CBasePlayer*)pOther)->IsInAVehicle() )
return false;
}
if ( HasSpawnFlags(SF_TRIGGER_ONLY_CLIENTS_OUT_OF_VEHICLES) && bOtherIsPlayer )
{
if ( ((CBasePlayer*)pOther)->IsInAVehicle() )
return false;
}
CBaseFilter *pFilter = m_hFilter.Get();
return (!pFilter) ? true : pFilter->PassesFilter( this, pOther );
}
return false;
}
//=====================================================================================================================
//-----------------------------------------------------------------------------
// Purpose: VPhysics trigger that changes the motion of vphysics objects that touch it
//-----------------------------------------------------------------------------
class CTriggerVPhysicsMotion : public CBaseVPhysicsTrigger, public IMotionEvent
{
DECLARE_CLASS( CTriggerVPhysicsMotion, CBaseVPhysicsTrigger );
public:
void Spawn();
void Precache();
virtual void UpdateOnRemove();
bool CreateVPhysics();
void OnRestore();
// UNDONE: Pass trigger event in or change Start/EndTouch. Add ITriggerVPhysics perhaps?
// BUGBUG: If a player touches two of these, his movement will screw up.
// BUGBUG: If a player uses crouch/uncrouch it will generate touch events and clear the motioncontroller flag
void StartTouch( CBaseEntity *pOther );
void EndTouch( CBaseEntity *pOther );
void InputSetVelocityLimitTime( inputdata_t &inputdata );
float LinearLimit();
inline bool HasGravityScale() { return m_gravityScale != 1.0 ? true : false; }
inline bool HasAirDensity() { return m_addAirDensity != 0 ? true : false; }
inline bool HasLinearLimit() { return LinearLimit() != 0.0f; }
inline bool HasLinearScale() { return m_linearScale != 1.0 ? true : false; }
inline bool HasAngularLimit() { return m_angularLimit != 0 ? true : false; }
inline bool HasAngularScale() { return m_angularScale != 1.0 ? true : false; }
inline bool HasLinearForce() { return m_linearForce != 0.0 ? true : false; }
DECLARE_DATADESC();
virtual simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular );
private:
IPhysicsMotionController *m_pController;
#ifndef _XBOX
EntityParticleTrailInfo_t m_ParticleTrail;
#endif //!_XBOX
float m_gravityScale;
float m_addAirDensity;
float m_linearLimit;
float m_linearLimitDelta;
float m_linearLimitTime;
float m_linearLimitStart;
float m_linearLimitStartTime;
float m_linearScale;
float m_angularLimit;
float m_angularScale;
float m_linearForce;
QAngle m_linearForceAngles;
};
//------------------------------------------------------------------------------
// Save/load
//------------------------------------------------------------------------------
BEGIN_DATADESC( CTriggerVPhysicsMotion )
DEFINE_PHYSPTR( m_pController ),
#ifndef _XBOX
DEFINE_EMBEDDED( m_ParticleTrail ),
#endif //!_XBOX
DEFINE_INPUT( m_gravityScale, FIELD_FLOAT, "SetGravityScale" ),
DEFINE_INPUT( m_addAirDensity, FIELD_FLOAT, "SetAdditionalAirDensity" ),
DEFINE_INPUT( m_linearLimit, FIELD_FLOAT, "SetVelocityLimit" ),
DEFINE_INPUT( m_linearLimitDelta, FIELD_FLOAT, "SetVelocityLimitDelta" ),
DEFINE_FIELD( m_linearLimitTime, FIELD_FLOAT ),
DEFINE_FIELD( m_linearLimitStart, FIELD_TIME ),
DEFINE_FIELD( m_linearLimitStartTime, FIELD_TIME ),
DEFINE_INPUT( m_linearScale, FIELD_FLOAT, "SetVelocityScale" ),
DEFINE_INPUT( m_angularLimit, FIELD_FLOAT, "SetAngVelocityLimit" ),
DEFINE_INPUT( m_angularScale, FIELD_FLOAT, "SetAngVelocityScale" ),
DEFINE_INPUT( m_linearForce, FIELD_FLOAT, "SetLinearForce" ),
DEFINE_INPUT( m_linearForceAngles, FIELD_VECTOR, "SetLinearForceAngles" ),
DEFINE_INPUTFUNC( FIELD_STRING, "SetVelocityLimitTime", InputSetVelocityLimitTime ),
END_DATADESC()
LINK_ENTITY_TO_CLASS( trigger_vphysics_motion, CTriggerVPhysicsMotion );
//------------------------------------------------------------------------------
// Spawn
//------------------------------------------------------------------------------
void CTriggerVPhysicsMotion::Spawn()
{
Precache();
BaseClass::Spawn();
}
//------------------------------------------------------------------------------
// Precache
//------------------------------------------------------------------------------
void CTriggerVPhysicsMotion::Precache()
{
#ifndef _XBOX
if ( m_ParticleTrail.m_strMaterialName != NULL_STRING )
{
PrecacheMaterial( STRING(m_ParticleTrail.m_strMaterialName) );
}
#endif //!_XBOX
}
//------------------------------------------------------------------------------
// Create VPhysics
//------------------------------------------------------------------------------
float CTriggerVPhysicsMotion::LinearLimit()
{
if ( m_linearLimitTime == 0.0f )
return m_linearLimit;
float dt = gpGlobals->curtime - m_linearLimitStartTime;
if ( dt >= m_linearLimitTime )
{
m_linearLimitTime = 0.0;
return m_linearLimit;
}
dt /= m_linearLimitTime;
float flLimit = RemapVal( dt, 0.0f, 1.0f, m_linearLimitStart, m_linearLimit );
return flLimit;
}
//------------------------------------------------------------------------------
// Create VPhysics
//------------------------------------------------------------------------------
bool CTriggerVPhysicsMotion::CreateVPhysics()
{
m_pController = physenv->CreateMotionController( this );
BaseClass::CreateVPhysics();
return true;
}
//------------------------------------------------------------------------------
// Cleanup
//------------------------------------------------------------------------------
void CTriggerVPhysicsMotion::UpdateOnRemove()
{
if ( m_pController )
{
physenv->DestroyMotionController( m_pController );
m_pController = NULL;
}
BaseClass::UpdateOnRemove();
}
//------------------------------------------------------------------------------
// Restore
//------------------------------------------------------------------------------
void CTriggerVPhysicsMotion::OnRestore()
{
BaseClass::OnRestore();
if ( m_pController )
{
m_pController->SetEventHandler( this );
}
}
//------------------------------------------------------------------------------
// Start/End Touch
//------------------------------------------------------------------------------
// UNDONE: Pass trigger event in or change Start/EndTouch. Add ITriggerVPhysics perhaps?
// BUGBUG: If a player touches two of these, his movement will screw up.
// BUGBUG: If a player uses crouch/uncrouch it will generate touch events and clear the motioncontroller flag
void CTriggerVPhysicsMotion::StartTouch( CBaseEntity *pOther )
{
BaseClass::StartTouch( pOther );
if ( !PassesTriggerFilters(pOther) )
return;
CBasePlayer *pPlayer = ToBasePlayer( pOther );
if ( pPlayer )
{
pPlayer->SetPhysicsFlag( PFLAG_VPHYSICS_MOTIONCONTROLLER, true );
pPlayer->m_Local.m_bSlowMovement = true;
}
triggerevent_t event;
PhysGetTriggerEvent( &event, this );
if ( event.pObject )
{
// these all get done again on save/load, so check
m_pController->AttachObject( event.pObject, true );
}
// Don't show these particles on the XBox
#ifndef _XBOX
if ( m_ParticleTrail.m_strMaterialName != NULL_STRING )
{
CEntityParticleTrail::Create( pOther, m_ParticleTrail, this );
}
#endif
if ( pOther->GetBaseAnimating() && pOther->GetBaseAnimating()->IsRagdoll() )
{
CRagdollBoogie::IncrementSuppressionCount( pOther );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTriggerVPhysicsMotion::EndTouch( CBaseEntity *pOther )
{
BaseClass::EndTouch( pOther );
if ( !PassesTriggerFilters(pOther) )
return;
CBasePlayer *pPlayer = ToBasePlayer( pOther );
if ( pPlayer )
{
pPlayer->SetPhysicsFlag( PFLAG_VPHYSICS_MOTIONCONTROLLER, false );
pPlayer->m_Local.m_bSlowMovement = false;
}
triggerevent_t event;
PhysGetTriggerEvent( &event, this );
if ( event.pObject && m_pController )
{
m_pController->DetachObject( event.pObject );
}
#ifndef _XBOX
if ( m_ParticleTrail.m_strMaterialName != NULL_STRING )
{
CEntityParticleTrail::Destroy( pOther, m_ParticleTrail );
}
#endif //!_XBOX
if ( pOther->GetBaseAnimating() && pOther->GetBaseAnimating()->IsRagdoll() )
{
CRagdollBoogie::DecrementSuppressionCount( pOther );
}
}
//------------------------------------------------------------------------------
// Inputs
//------------------------------------------------------------------------------
void CTriggerVPhysicsMotion::InputSetVelocityLimitTime( inputdata_t &inputdata )
{
m_linearLimitStart = LinearLimit();
m_linearLimitStartTime = gpGlobals->curtime;
float args[2];
UTIL_StringToFloatArray( args, 2, inputdata.value.String() );
m_linearLimit = args[0];
m_linearLimitTime = args[1];
}
//------------------------------------------------------------------------------
// Apply the forces to the entity
//------------------------------------------------------------------------------
IMotionEvent::simresult_e CTriggerVPhysicsMotion::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
{
if ( m_bDisabled )
return SIM_NOTHING;
linear.Init();
angular.Init();
if ( HasGravityScale() )
{
// assume object already has 1.0 gravities applied to it, so apply the additional amount
linear.z -= (m_gravityScale-1) * sv_gravity.GetFloat();
}
if ( HasLinearForce() )
{
Vector vecForceDir;
AngleVectors( m_linearForceAngles, &vecForceDir );
VectorMA( linear, m_linearForce, vecForceDir, linear );
}
if ( HasAirDensity() || HasLinearLimit() || HasLinearScale() || HasAngularLimit() || HasAngularScale() )
{
Vector vel;
AngularImpulse angVel;
pObject->GetVelocity( &vel, &angVel );
vel += linear * deltaTime; // account for gravity scale
Vector unitVel = vel;
Vector unitAngVel = angVel;
float speed = VectorNormalize( unitVel );
float angSpeed = VectorNormalize( unitAngVel );
float speedScale = 0.0;
float angSpeedScale = 0.0;
if ( HasAirDensity() )
{
float linearDrag = -0.5 * m_addAirDensity * pObject->CalculateLinearDrag( unitVel ) * deltaTime;
if ( linearDrag < -1 )
{
linearDrag = -1;
}
speedScale += linearDrag / deltaTime;
float angDrag = -0.5 * m_addAirDensity * pObject->CalculateAngularDrag( unitAngVel ) * deltaTime;
if ( angDrag < -1 )
{
angDrag = -1;
}
angSpeedScale += angDrag / deltaTime;
}
if ( HasLinearLimit() && speed > m_linearLimit )
{
float flDeltaVel = (LinearLimit() - speed) / deltaTime;
if ( m_linearLimitDelta != 0.0f )
{
float flMaxDeltaVel = -m_linearLimitDelta / deltaTime;
if ( flDeltaVel < flMaxDeltaVel )
{
flDeltaVel = flMaxDeltaVel;
}
}
VectorMA( linear, flDeltaVel, unitVel, linear );
}
if ( HasAngularLimit() && angSpeed > m_angularLimit )
{
angular += ((m_angularLimit - angSpeed)/deltaTime) * unitAngVel;
}
if ( HasLinearScale() )
{
speedScale = ( (speedScale+1) * m_linearScale ) - 1;
}
if ( HasAngularScale() )
{
angSpeedScale = ( (angSpeedScale+1) * m_angularScale ) - 1;
}
linear += vel * speedScale;
angular += angVel * angSpeedScale;
}
return SIM_GLOBAL_ACCELERATION;
}
class CServerRagdollTrigger : public CBaseTrigger
{
DECLARE_CLASS( CServerRagdollTrigger, CBaseTrigger );
public:
virtual void StartTouch( CBaseEntity *pOther );
virtual void EndTouch( CBaseEntity *pOther );
virtual void Spawn( void );
};
LINK_ENTITY_TO_CLASS( trigger_serverragdoll, CServerRagdollTrigger );
void CServerRagdollTrigger::Spawn( void )
{
BaseClass::Spawn();
InitTrigger();
}
void CServerRagdollTrigger::StartTouch(CBaseEntity *pOther)
{
BaseClass::StartTouch( pOther );
if ( pOther->IsPlayer() )
return;
CBaseCombatCharacter *pCombatChar = pOther->MyCombatCharacterPointer();
if ( pCombatChar )
{
pCombatChar->m_bForceServerRagdoll = true;
}
}
void CServerRagdollTrigger::EndTouch(CBaseEntity *pOther)
{
BaseClass::EndTouch( pOther );
if ( pOther->IsPlayer() )
return;
CBaseCombatCharacter *pCombatChar = pOther->MyCombatCharacterPointer();
if ( pCombatChar )
{
pCombatChar->m_bForceServerRagdoll = false;
}
}
#ifdef HL1_DLL
//----------------------------------------------------------------------------------
// func_friction
//----------------------------------------------------------------------------------
class CFrictionModifier : public CBaseTrigger
{
DECLARE_CLASS( CFrictionModifier, CBaseTrigger );
public:
void Spawn( void );
bool KeyValue( const char *szKeyName, const char *szValue );
virtual void StartTouch(CBaseEntity *pOther);
virtual void EndTouch(CBaseEntity *pOther);
virtual int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
float m_frictionFraction;
DECLARE_DATADESC();
};
LINK_ENTITY_TO_CLASS( func_friction, CFrictionModifier );
BEGIN_DATADESC( CFrictionModifier )
DEFINE_FIELD( m_frictionFraction, FIELD_FLOAT ),
END_DATADESC()
// Modify an entity's friction
void CFrictionModifier::Spawn( void )
{
BaseClass::Spawn();
InitTrigger();
}
// Sets toucher's friction to m_frictionFraction (1.0 = normal friction)
bool CFrictionModifier::KeyValue( const char *szKeyName, const char *szValue )
{
if (FStrEq(szKeyName, "modifier"))
{
m_frictionFraction = atof(szValue) / 100.0;
}
else
{
BaseClass::KeyValue( szKeyName, szValue );
}
return true;
}
void CFrictionModifier::StartTouch( CBaseEntity *pOther )
{
if ( !pOther->IsPlayer() ) // ignore player
{
pOther->SetFriction( m_frictionFraction );
}
}
void CFrictionModifier::EndTouch( CBaseEntity *pOther )
{
if ( !pOther->IsPlayer() ) // ignore player
{
pOther->SetFriction( 1.0f );
}
}
#endif //HL1_DLL
bool IsTriggerClass( CBaseEntity *pEntity )
{
if ( NULL != dynamic_cast<CBaseTrigger *>(pEntity) )
return true;
if ( NULL != dynamic_cast<CTriggerVPhysicsMotion *>(pEntity) )
return true;
if ( NULL != dynamic_cast<CTriggerVolume *>(pEntity) )
return true;
return false;
}