css_enhanced_waf/game/client/css_enhanced/c_triggers_base.cpp

647 lines
18 KiB
C++

#include "cbase.h"
#include "c_triggers.h"
#include "const.h"
#include "datamap.h"
#include "dt_utlvector_recv.h"
#include "in_buttons.h"
#include "collisionutils.h"
#include "platform.h"
#include "prediction.h"
#include "mapentities_shared.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "predictioncopy.h"
#include "tier0/memdbgon.h"
bool IsTriggerClass( CBaseEntity *pEntity );
CON_COMMAND(report_triggerinfo, "")
{
for (int i = 0; i < cl_entitylist->GetHighestEntityIndex(); i++)
{
C_BaseEntity* pEntity = (C_BaseEntity*) cl_entitylist->GetClientEntity( i );
if (!IsTriggerClass(pEntity))
{
continue;
}
C_BaseTrigger* pTrigger = (C_BaseTrigger*) ( pEntity );
ConMsg(
"------ Trigger Information ------\n"
" >> Trigger Index : %i\n"
" >> Name : %s\n"
" >> Classname : %s\n"
" >> Target Name : %s\n"
" >> Filter Name : %s\n"
" >> Origin : %.2f, %.2f, %.2f\n"
"---------------------------------\n\n",
pTrigger->entindex(),
pTrigger->GetEntityName(), pTrigger->GetClassname(), pTrigger->m_target, pTrigger->m_iFilterName,
pTrigger->GetAbsOrigin().x, pTrigger->GetAbsOrigin().y, pTrigger->GetAbsOrigin().z);
}
}
// Command to dynamically toggle trigger visibility
void Cmd_ShowtriggersToggle_f( const CCommand &args )
{
for (int i = 0; i < cl_entitylist->GetHighestEntityIndex(); i++)
{
C_BaseEntity* pEntity = (C_BaseEntity*) cl_entitylist->GetClientEntity( i );
if ( IsTriggerClass(pEntity) )
{
if ( pEntity->IsEffectActive( EF_NODRAW ) )
{
pEntity->RemoveEffects( EF_NODRAW );
}
else
{
pEntity->AddEffects( EF_NODRAW );
}
}
}
}
static ConCommand showtriggers_toggle( "cl_showtriggers_toggle", Cmd_ShowtriggersToggle_f, "Toggle show triggers" );
// Global Savedata for base trigger
BEGIN_DATADESC( C_BaseTrigger )
// Inputs
DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
DEFINE_INPUTFUNC( FIELD_VOID, "TouchTest", InputTouchTest ),
DEFINE_INPUTFUNC( FIELD_VOID, "StartTouch", InputStartTouch ),
DEFINE_INPUTFUNC( FIELD_VOID, "EndTouch", InputEndTouch ),
// Outputs
DEFINE_OUTPUT( m_OnStartTouch, "OnStartTouch"),
DEFINE_OUTPUT( m_OnStartTouchAll, "OnStartTouchAll"),
DEFINE_OUTPUT( m_OnEndTouch, "OnEndTouch"),
DEFINE_OUTPUT( m_OnEndTouchAll, "OnEndTouchAll"),
DEFINE_OUTPUT( m_OnTouching, "OnTouching" ),
DEFINE_OUTPUT( m_OnNotTouching, "OnNotTouching" ),
END_DATADESC()
// TODO_ENHANCED: this should be predicted, but since we don't have yet proper spawn, m_target would be an empty string all the time.
BEGIN_PREDICTION_DATA(C_BaseTrigger)
// DEFINE_PRED_FIELD(m_bDisabled, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE),
// DEFINE_PRED_FIELD(m_target, FIELD_STRING, FTYPEDESC_INSENDTABLE),
// DEFINE_PRED_FIELD(m_iFilterName, FIELD_STRING, FTYPEDESC_INSENDTABLE),
// DEFINE_PRED_ARRAY(m_hPredictedTouchingEntities, FIELD_EHANDLE, MAX_EDICTS, FTYPEDESC_PRIVATE),
// DEFINE_PRED_FIELD(m_iCountPredictedTouchingEntities, FIELD_INTEGER, FTYPEDESC_PRIVATE)
END_PREDICTION_DATA();
// Incase server decides to change the filter name
void RecvProxy_FilterName(const CRecvProxyData *pData, void *pStruct, void *pOut)
{
C_BaseTrigger *entity = (C_BaseTrigger *) pStruct;
Q_strncpy( entity->m_iFilterName, pData->m_Value.m_pString, MAX_PATH );
// Update the Filter
entity->m_hFilter = static_cast<C_BaseFilter *>(UTIL_FindEntityByName(entity->m_iFilterName));
}
void RecvProxy_Target(const CRecvProxyData *pData, void *pStruct, void *pOut)
{
C_BaseTrigger *entity = (C_BaseTrigger *) pStruct;
Q_strncpy( entity->m_target, pData->m_Value.m_pString, MAX_PATH );
}
// Incase server decides to change m_bDisabled
void RecvProxy_Disabled(const CRecvProxyData *pData, void *pStruct, void *pOut)
{
C_BaseTrigger *entity = (C_BaseTrigger *) pStruct;
entity->m_bDisabled = pData->m_Value.m_Int;
entity->m_bDisabled == 1 ? entity->Disable() : entity->Enable();
}
#define RECVINFO_OUTPUT(outputName) #outputName ".m_ActionList", offsetof(currentRecvDTClass, outputName)
IMPLEMENT_CLIENTCLASS_DT(C_BaseTrigger, DT_BaseTrigger, CBaseTrigger)
RecvPropInt(RECVINFO(m_bDisabled), NULL, RecvProxy_Disabled),
RecvPropString(RECVINFO(m_target), NULL, RecvProxy_Target),
RecvPropString(RECVINFO(m_iFilterName), NULL, RecvProxy_FilterName),
END_RECV_TABLE();
LINK_ENTITY_TO_CLASS( trigger, C_BaseTrigger );
const char *ParseEntity(CBaseEntity *&pEntity, const char *pEntData)
{
CEntityMapData entData( (char*)pEntData );
char model[MAPKEY_MAXLENGTH];
if (!entData.ExtractValue("model", model))
{
return entData.CurrentBufferPosition();
}
int i = atoi(&model[0] + 1);
if ((pEntity->GetModelIndex() - 1) == i)
{
pEntity->ParseMapData(&entData);
}
return entData.CurrentBufferPosition();
}
void ParseAllEntities(CBaseEntity *pEntity, const char *pMapData)
{
char szTokenBuffer[MAPKEY_MAXLENGTH];
for ( ; true; pMapData = MapEntity_SkipToNextEntity(pMapData, szTokenBuffer) )
{
char token[MAPKEY_MAXLENGTH];
pMapData = MapEntity_ParseToken( pMapData, token );
if (!pMapData)
break;
if (token[0] != '{')
{
Error( "ParseAllEntities: found %s when expecting {", token);
continue;
}
const char *pCurMapData = pMapData;
pMapData = ParseEntity(pEntity, pMapData);
}
}
C_BaseTrigger::C_BaseTrigger()
{
SetPredictionEligible( true );
AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID );
Q_memset(m_iFilterName, 0, sizeof(m_iFilterName));
Q_memset(m_target, 0, sizeof(m_target));
// m_iCountPredictedTouchingEntities = 0;
// Q_memset(m_hPredictedTouchingEntities, 0, sizeof(m_hPredictedTouchingEntities));
}
void C_BaseTrigger::Spawn()
{
m_hFilter = static_cast<C_BaseFilter *>(UTIL_FindEntityByName( m_iFilterName ));
SetSolid(SOLID_BSP);
AddSolidFlags(FSOLID_TRIGGER);
SetMoveType(MOVETYPE_NONE);
// why doens't NotifyShouldTransmit call this
UpdatePartitionListEntry();
}
void C_BaseTrigger::PostDataUpdate( DataUpdateType_t updateType )
{
if (updateType == DATA_UPDATE_CREATED)
{
ParseAllEntities( this, engine->GetMapEntitiesString() );
}
BaseClass::PostDataUpdate( updateType );
}
//////////////////////////////////////////////////////////////////////////////
/// This needs to be restored on each simulations times during prediction. ///
/// By defaut data is rested only once but in our situation, ///
/// we need to restore everytime a new command processes. ///
//////////////////////////////////////////////////////////////////////////////
void C_BaseTrigger::RestoreTouchEntitiesTo( int current_command )
{
RestoreData( "RestoreTouchEntitiesForTriggers", current_command, PC_EVERYTHING );
}
bool C_BaseTrigger::ShouldPredict(void)
{
#if !defined( NO_ENTITY_PREDICTION )
return true;
#else
return false;
#endif
}
int C_BaseTrigger::SaveData(const char* context, int slot, int type)
{
// m_iCountPredictedTouchingEntities = m_hTouchingEntities.Count();
// if (m_iCountPredictedTouchingEntities >= MAX_EDICTS)
// {
// Error("C_BaseTrigger::SaveData: Should never reach this!");
// }
// // NOTE:
// // Since UtlVector can't be used for prediction, we use arrays.
// for (int i = 0; i < m_iCountPredictedTouchingEntities; i++)
// {
// m_hPredictedTouchingEntities[i] = m_hTouchingEntities[i];
// }
return BaseClass::SaveData(context, slot, type);
}
int C_BaseTrigger::RestoreData(const char* context, int slot, int type)
{
int ret = BaseClass::RestoreData(context, slot, type);
// m_hTouchingEntities.RemoveAll();
// for (int i = 0; i < m_iCountPredictedTouchingEntities; i++)
// {
// m_hTouchingEntities.AddToTail( m_hPredictedTouchingEntities[i] );
// }
return ret;
}
void C_BaseTrigger::UpdatePartitionListEntry(void)
{
::partition->RemoveAndInsert(
PARTITION_CLIENT_SOLID_EDICTS | PARTITION_CLIENT_RESPONSIVE_EDICTS | PARTITION_CLIENT_NON_STATIC_EDICTS, // remove
PARTITION_CLIENT_TRIGGER_ENTITIES, // add
CollisionProp()->GetPartitionHandle() );
}
void C_BaseTrigger::UpdateFilter(void)
{
// We do this since, since we dont know what order the entities are sent in, so a trigger might be sent
// before the client know about the filter entity
if (prediction->IsFirstTimePredicted() && m_hFilter.Get() == nullptr)
{
m_hFilter = static_cast<C_BaseFilter *>(UTIL_FindEntityByName(m_iFilterName));
}
}
//------------------------------------------------------------------------------
// Purpose: Input handler to turn on this trigger.
//------------------------------------------------------------------------------
void C_BaseTrigger::InputEnable( inputdata_t &inputdata )
{
Enable();
}
//------------------------------------------------------------------------------
// Purpose: Input handler to turn off this trigger.
//------------------------------------------------------------------------------
void C_BaseTrigger::InputDisable( inputdata_t &inputdata )
{
Disable();
}
void C_BaseTrigger::InputTouchTest( inputdata_t &inputdata )
{
TouchTest();
}
//------------------------------------------------------------------------------
// Cleanup
//------------------------------------------------------------------------------
void C_BaseTrigger::UpdateOnRemove( void )
{
if ( VPhysicsGetObject())
{
VPhysicsGetObject()->RemoveTrigger();
}
BaseClass::UpdateOnRemove();
}
//------------------------------------------------------------------------------
// Purpose: Turns on this trigger.
//------------------------------------------------------------------------------
void C_BaseTrigger::Enable( void )
{
m_bDisabled = false;
if ( VPhysicsGetObject())
{
VPhysicsGetObject()->EnableCollisions( true );
}
if (!IsSolidFlagSet( FSOLID_TRIGGER ))
{
AddSolidFlags( FSOLID_TRIGGER );
PhysicsTouchTriggers();
}
}
//------------------------------------------------------------------------------
// Purpose: Turns off this trigger.
//------------------------------------------------------------------------------
void C_BaseTrigger::Disable( void )
{
m_bDisabled = true;
if ( VPhysicsGetObject())
{
VPhysicsGetObject()->EnableCollisions( false );
}
if (IsSolidFlagSet(FSOLID_TRIGGER))
{
RemoveSolidFlags( FSOLID_TRIGGER );
PhysicsTouchTriggers();
}
}
//------------------------------------------------------------------------------
// Purpose: Tests to see if anything is touching this trigger.
//------------------------------------------------------------------------------
void C_BaseTrigger::TouchTest( void )
{
// If the trigger is disabled don't test to see if anything is touching it.
if ( !m_bDisabled )
{
if ( m_hTouchingEntities.Count() !=0 )
{
m_OnTouching.FireOutput( this, this );
}
else
{
m_OnNotTouching.FireOutput( this, this );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Return true if the specified point is within this zone
//-----------------------------------------------------------------------------
bool C_BaseTrigger::PointIsWithin( const Vector &vecPoint )
{
Ray_t ray;
trace_t tr;
ICollideable *pCollide = CollisionProp();
ray.Init( vecPoint, vecPoint );
enginetrace->ClipRayToCollideable( ray, MASK_ALL, pCollide, &tr );
return ( tr.startsolid );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_BaseTrigger::InitTrigger( )
{
SetSolid(SOLID_VPHYSICS);
AddSolidFlags( FSOLID_NOT_SOLID );
if (m_bDisabled)
{
RemoveSolidFlags( FSOLID_TRIGGER );
}
else
{
AddSolidFlags( FSOLID_TRIGGER );
}
SetMoveType( MOVETYPE_NONE );
m_hTouchingEntities.Purge();
if ( HasSpawnFlags( SF_TRIG_TOUCH_DEBRIS ) )
{
CollisionProp()->AddSolidFlags( FSOLID_TRIGGER_TOUCH_DEBRIS );
}
}
//-----------------------------------------------------------------------------
// Purpose: Returns true if this entity passes the filter criteria, false if not.
// Input : pOther - The entity to be filtered.
//-----------------------------------------------------------------------------
bool C_BaseTrigger::PassesTriggerFilters(CBaseEntity *pOther)
{
if (m_bDisabled)
{
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))
{
if (pOther->GetFlags() & FL_NPC)
{
if (HasSpawnFlags(SF_TRIGGER_ONLY_PLAYER_ALLY_NPCS))
{
return false;
}
if (HasSpawnFlags(SF_TRIGGER_ONLY_NPCS_IN_VEHICLES))
{
return false;
}
}
bool bOtherIsPlayer = pOther->IsPlayer();
if (bOtherIsPlayer)
{
CBasePlayer *pPlayer = (CBasePlayer*)pOther;
if (!pPlayer->IsAlive())
return false;
if (HasSpawnFlags(SF_TRIGGER_ONLY_CLIENTS_IN_VEHICLES))
{
if (!pPlayer->IsInAVehicle())
return false;
// Make sure we're also not exiting the vehicle at the moment
IClientVehicle *pVehicle = pPlayer->GetVehicle();
if (pVehicle == NULL)
return false;
//XYZ_TODO: Check if exit anim is on
//if (pVehicle->IsPassengerExiting())
return false;
}
if (HasSpawnFlags(SF_TRIGGER_ONLY_CLIENTS_OUT_OF_VEHICLES))
{
if (pPlayer->IsInAVehicle())
return false;
}
if (HasSpawnFlags(SF_TRIGGER_DISALLOW_BOTS))
{
return false;
}
}
UpdateFilter();
C_BaseFilter *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 C_BaseTrigger::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 C_BaseTrigger::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 C_BaseTrigger::StartTouch(CBaseEntity *pOther)
{
if (PassesTriggerFilters(pOther))
{
EHANDLE hOther;
hOther = pOther;
bool bAdded = false;
if ( m_hTouchingEntities.Find( hOther ) == m_hTouchingEntities.InvalidIndex() )
{
m_hTouchingEntities.AddToTail( hOther );
bAdded = true;
}
m_OnStartTouch.FireOutput(pOther, this);
if ( bAdded && ( m_hTouchingEntities.Count() == 1 ) )
{
// First entity to touch us that passes our filters
m_OnStartTouchAll.FireOutput( pOther, this );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Called when an entity stops touching us.
// Input : pOther - The entity that was touching us.
//-----------------------------------------------------------------------------
void C_BaseTrigger::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 if ( hOther->IsPlayer() && !hOther->IsAlive() )
{
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 C_BaseTrigger::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
//-----------------------------------------------------------------------------
C_BaseEntity *C_BaseTrigger::GetTouchedEntityOfType( const char *sClassName )
{
int iCount = m_hTouchingEntities.Count();
for ( int i = 0; i < iCount; i++ )
{
C_BaseEntity *pEntity = m_hTouchingEntities[i];
if ( FClassnameIs( pEntity, sClassName ) )
return pEntity;
}
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose: Toggles this trigger between enabled and disabled.
//-----------------------------------------------------------------------------
void C_BaseTrigger::InputToggle( inputdata_t &inputdata )
{
if (IsSolidFlagSet( FSOLID_TRIGGER ))
{
RemoveSolidFlags(FSOLID_TRIGGER);
}
else
{
AddSolidFlags(FSOLID_TRIGGER);
}
PhysicsTouchTriggers();
}
bool IsTriggerClass( CBaseEntity *pEntity )
{
if ( pEntity->IsTrigger() )
return true;
//if ( NULL != dynamic_cast<C_TriggerVPhysicsMotion *>(pEntity) )
// return true;
//if ( NULL != dynamic_cast<C_TriggerVolume *>(pEntity) )
// return true;
return false;
}