8999 lines
272 KiB
C++
8999 lines
272 KiB
C++
//======= Copyright (c) 1996-2009, Valve Corporation, All rights reserved. ======
|
|
//
|
|
// Purpose: The base class from which all game entities are derived.
|
|
//
|
|
//===============================================================================
|
|
|
|
#include "cbase.h"
|
|
#include "globalstate.h"
|
|
#include "isaverestore.h"
|
|
#include "client.h"
|
|
#include "decals.h"
|
|
#include "gamerules.h"
|
|
#include "entityapi.h"
|
|
#include "entitylist.h"
|
|
#include "eventqueue.h"
|
|
#include "hierarchy.h"
|
|
#include "basecombatweapon.h"
|
|
#include "const.h"
|
|
#include "player.h" // For debug draw sending
|
|
#include "ndebugoverlay.h"
|
|
#include "physics.h"
|
|
#include "model_types.h"
|
|
#include "team.h"
|
|
#include "sendproxy.h"
|
|
#include "IEffects.h"
|
|
#include "vstdlib/random.h"
|
|
#include "baseentity.h"
|
|
#include "collisionutils.h"
|
|
#include "coordsize.h"
|
|
#include "animation.h"
|
|
#include "tier1/strtools.h"
|
|
#include "engine/IEngineSound.h"
|
|
#include "physics_saverestore.h"
|
|
#include "saverestore_utlvector.h"
|
|
#include "bone_setup.h"
|
|
#include "vcollide_parse.h"
|
|
#include "filters.h"
|
|
#include "te_effect_dispatch.h"
|
|
#include "ai_criteria.h"
|
|
#include "ai_responsesystem.h"
|
|
#include "world.h"
|
|
#include "globals.h"
|
|
#include "saverestoretypes.h"
|
|
#include "SkyCamera.h"
|
|
#include "sceneentity.h"
|
|
#include "game.h"
|
|
#include "tier0/vprof.h"
|
|
#include "ai_basenpc.h"
|
|
#include "iservervehicle.h"
|
|
#include "eventlist.h"
|
|
#include "scriptevent.h"
|
|
#include "SoundEmitterSystem/isoundemittersystembase.h"
|
|
#include "UtlCachedFileData.h"
|
|
#include "utlbuffer.h"
|
|
#include "positionwatcher.h"
|
|
#include "movetype_push.h"
|
|
#include "tier0/icommandline.h"
|
|
#include "vphysics/friction.h"
|
|
#include <ctype.h>
|
|
#include "datacache/imdlcache.h"
|
|
#include "ModelSoundsCache.h"
|
|
#include "env_debughistory.h"
|
|
#include "tier1/utlstring.h"
|
|
#include "vscript_server.h"
|
|
#include "toolframework/itoolframework.h"
|
|
#include "videocfg/videocfg.h"
|
|
#include "vstdlib/ikeyvaluessystem.h"
|
|
#include "ilagcompensationmanager.h"
|
|
#include "bittools.h"
|
|
#include "cellcoord.h"
|
|
#include "sendprop_priorities.h"
|
|
#include "videocfg/videocfg.h"
|
|
#include "tier0/stackstats.h"
|
|
|
|
#if defined ( PORTAL2 )
|
|
#include "PortalSimulation.h"
|
|
#endif
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
extern bool g_bTestMoveTypeStepSimulation;
|
|
extern ConVar sv_vehicle_autoaim_scale;
|
|
|
|
// Init static class variables
|
|
bool CBaseEntity::m_bInDebugSelect = false; // Used for selection in debug overlays
|
|
int CBaseEntity::m_nDebugPlayer = -1; // Player doing the selection
|
|
|
|
// This can be set before creating an entity to force it to use a particular edict.
|
|
edict_t *g_pForceAttachEdict = NULL;
|
|
|
|
bool CBaseEntity::m_bDebugPause = false; // Whether entity i/o is paused.
|
|
int CBaseEntity::m_nDebugSteps = 1; // Number of entity outputs to fire before pausing again.
|
|
bool CBaseEntity::sm_bDisableTouchFuncs = false; // Disables PhysicsTouch and PhysicsStartTouch function calls
|
|
|
|
int CBaseEntity::m_nPredictionRandomSeed = -1;
|
|
int CBaseEntity::m_nPredictionRandomSeedServer = -1;
|
|
CBasePlayer *CBaseEntity::m_pPredictionPlayer = NULL;
|
|
|
|
// Used to make sure nobody calls UpdateTransmitState directly.
|
|
int g_nInsideDispatchUpdateTransmitState = 0;
|
|
|
|
// When this is false, throw an assert in debug when GetAbsAnything is called. Used when hierachy is incomplete/invalid.
|
|
bool CBaseEntity::s_bAbsQueriesValid = true;
|
|
|
|
ConVar sv_netvisdist( "sv_netvisdist", "10000", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Test networking visibility distance" );
|
|
ConVar ent_show_contexts( "ent_show_contexts", "0", 0, "Show entity contexts in ent_text display" );
|
|
|
|
ConVar sv_script_think_interval("sv_script_think_interval", "0.1");
|
|
|
|
// This table encodes edict data.
|
|
void SendProxy_AnimTime( const SendProp *pProp, const void *pStruct, const void *pVarData, DVariant *pOut, int iElement, int objectID )
|
|
{
|
|
CBaseEntity *pEntity = (CBaseEntity *)pStruct;
|
|
|
|
#if defined( _DEBUG )
|
|
CBaseAnimating *pAnimating = pEntity->GetBaseAnimating();
|
|
Assert( pAnimating );
|
|
|
|
if ( pAnimating )
|
|
{
|
|
Assert( !pAnimating->IsUsingClientSideAnimation() );
|
|
}
|
|
#endif
|
|
|
|
int ticknumber = TIME_TO_TICKS( pEntity->m_flAnimTime );
|
|
// Tickbase is current tick rounded down to closes 100 ticks
|
|
int tickbase = gpGlobals->GetNetworkBase( gpGlobals->tickcount, pEntity->entindex() );
|
|
int addt = 0;
|
|
// If it's within the last tick interval through the current one, then we can encode it
|
|
if ( ticknumber >= ( tickbase - 100 ) )
|
|
{
|
|
addt = ( ticknumber - tickbase ) & 0xFF;
|
|
}
|
|
|
|
pOut->m_Int = addt;
|
|
}
|
|
|
|
// This table encodes edict data.
|
|
void SendProxy_SimulationTime( const SendProp *pProp, const void *pStruct, const void *pVarData, DVariant *pOut, int iElement, int objectID )
|
|
{
|
|
CBaseEntity *pEntity = (CBaseEntity *)pStruct;
|
|
|
|
int ticknumber = TIME_TO_TICKS( pEntity->m_flSimulationTime );
|
|
// tickbase is current tick rounded down to closest 100 ticks
|
|
int tickbase = gpGlobals->GetNetworkBase( gpGlobals->tickcount, pEntity->entindex() );
|
|
int addt = 0;
|
|
if ( ticknumber >= tickbase )
|
|
{
|
|
addt = ( ticknumber - tickbase ) & 0xff;
|
|
}
|
|
|
|
pOut->m_Int = addt;
|
|
}
|
|
|
|
void* SendProxy_ClientSideAnimation( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID )
|
|
{
|
|
CBaseEntity *pEntity = (CBaseEntity *)pStruct;
|
|
CBaseAnimating *pAnimating = pEntity->GetBaseAnimating();
|
|
|
|
if ( pAnimating && !pAnimating->IsUsingClientSideAnimation() )
|
|
return (void*)pVarData;
|
|
else
|
|
return NULL; // Don't send animtime unless the client needs it.
|
|
}
|
|
REGISTER_SEND_PROXY_NON_MODIFIED_POINTER( SendProxy_ClientSideAnimation );
|
|
|
|
|
|
BEGIN_SEND_TABLE_NOBASE( CBaseEntity, DT_AnimTimeMustBeFirst )
|
|
// NOTE: Animtime must be sent before origin and angles ( from pev ) because it has a
|
|
// proxy on the client that stores off the old values before writing in the new values and
|
|
// if it is sent after the new values, then it will only have the new origin and studio model, etc.
|
|
// interpolation will be busted
|
|
SendPropInt (SENDINFO(m_flAnimTime), 8, SPROP_UNSIGNED|SPROP_CHANGES_OFTEN|SPROP_ENCODED_AGAINST_TICKCOUNT, SendProxy_AnimTime),
|
|
END_SEND_TABLE()
|
|
|
|
#if !defined( NO_ENTITY_PREDICTION ) && defined( USE_PREDICTABLEID )
|
|
BEGIN_SEND_TABLE_NOBASE( CBaseEntity, DT_PredictableId )
|
|
SendPropPredictableId( SENDINFO( m_PredictableID ) ),
|
|
SendPropInt( SENDINFO( m_bIsPlayerSimulated ), 1, SPROP_UNSIGNED ),
|
|
END_SEND_TABLE()
|
|
#endif
|
|
|
|
void BuildMergedPlayerIndexListForSplitUser( int nPlayerIndex, CUtlVector< int > &list )
|
|
{
|
|
CBasePlayer *pl = static_cast< CBasePlayer * >( CBaseEntity::Instance( nPlayerIndex ) );
|
|
Assert( pl );
|
|
if ( !pl )
|
|
return;
|
|
|
|
int nRootPlayer = nPlayerIndex;
|
|
|
|
bool bIsSplit = engine->IsSplitScreenPlayer( nPlayerIndex );
|
|
if ( bIsSplit )
|
|
{
|
|
edict_t *ed = engine->GetSplitScreenPlayerAttachToEdict( nPlayerIndex );
|
|
if ( ed )
|
|
{
|
|
nRootPlayer = ENTINDEX( ed );
|
|
}
|
|
}
|
|
|
|
// Now use root player to build list
|
|
list.AddToTail( nRootPlayer );
|
|
for ( int i = 1; i < MAX_SPLITSCREEN_PLAYERS; ++i )
|
|
{
|
|
edict_t *ed = engine->GetSplitScreenPlayerForEdict( nRootPlayer, i ) ;
|
|
if ( ed )
|
|
{
|
|
list.AddToTail( ENTINDEX( ed ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
#if !defined( NO_ENTITY_PREDICTION ) && defined( USE_PREDICTABLEID )
|
|
|
|
static void* SendProxy_SendPredictableId( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID )
|
|
{
|
|
CBaseEntity *pEntity = (CBaseEntity *)pStruct;
|
|
if ( !pEntity || !pEntity->m_PredictableID->IsActive() )
|
|
return NULL;
|
|
|
|
int id_player_index = pEntity->m_PredictableID->GetPlayer();
|
|
|
|
CUtlVector< int > rList;
|
|
BuildMergedPlayerIndexListForSplitUser( id_player_index, rList );
|
|
pRecipients->ClearAllRecipients();
|
|
FOR_EACH_VEC( rList, i )
|
|
{
|
|
pRecipients->SetRecipient( rList[ i ] );
|
|
}
|
|
return ( void * )pVarData;
|
|
}
|
|
REGISTER_SEND_PROXY_NON_MODIFIED_POINTER( SendProxy_SendPredictableId );
|
|
#endif
|
|
|
|
/*
|
|
//--------------------------------------------------------------------------------------------------------
|
|
// Origin debugging
|
|
//--------------------------------------------------------------------------------------------------------
|
|
#if (defined(_WIN32) && (!defined(_GAMECONSOLE) ) )
|
|
#include "filesystem.h"
|
|
|
|
struct SOriginDebugFP
|
|
{
|
|
SOriginDebugFP()
|
|
{
|
|
m_originDebugFP = NULL;
|
|
}
|
|
|
|
~SOriginDebugFP()
|
|
{
|
|
if ( m_originDebugFP )
|
|
{
|
|
filesystem->Close( m_originDebugFP );
|
|
m_originDebugFP = NULL;
|
|
m_count = 0;
|
|
}
|
|
}
|
|
|
|
FileHandle_t m_originDebugFP;
|
|
int m_count;
|
|
};
|
|
|
|
static SOriginDebugFP sOriginDebugFP;
|
|
|
|
ConVar sv_lognetorigindeltas( "sv_lognetorigindeltas", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Log entity origins" );
|
|
|
|
static void LogNetOriginDeltas( CBaseEntity *entity, Vector const *v )
|
|
{
|
|
if ( !sv_lognetorigindeltas.GetBool() )
|
|
{
|
|
if ( sOriginDebugFP.m_originDebugFP )
|
|
{
|
|
filesystem->Close( sOriginDebugFP.m_originDebugFP );
|
|
sOriginDebugFP.m_originDebugFP = NULL;
|
|
sOriginDebugFP.m_count = 0;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if ( !sOriginDebugFP.m_originDebugFP )
|
|
{
|
|
char logFileName[MAX_PATH];
|
|
V_snprintf( logFileName, ARRAYSIZE( logFileName ), "origin_server_%s.csv",
|
|
gpGlobals->mapname.ToCStr() );
|
|
|
|
sOriginDebugFP.m_originDebugFP = filesystem->Open( logFileName, "wt" );
|
|
|
|
if ( sOriginDebugFP.m_originDebugFP )
|
|
{
|
|
filesystem->FPrintf( sOriginDebugFP.m_originDebugFP,
|
|
"Count"
|
|
",Class"
|
|
",Index"
|
|
",X"
|
|
",Y"
|
|
",Z"
|
|
"\n" );
|
|
}
|
|
|
|
}
|
|
|
|
if ( sOriginDebugFP.m_originDebugFP )
|
|
{
|
|
filesystem->FPrintf( sOriginDebugFP.m_originDebugFP,
|
|
"%d,%s,%d,%6.4f,%6.4f,%6.4f\n",
|
|
sOriginDebugFP.m_count++,
|
|
entity->GetClassname(),
|
|
entity->entindex(),
|
|
v->x,
|
|
v->y,
|
|
v->z );
|
|
}
|
|
}
|
|
|
|
#endif // (defined(_WIN32) && (!defined(_GAMECONSOLE) ) )
|
|
*/
|
|
|
|
//--------------------------------------------------------------------------------------------------------
|
|
void SendProxy_Origin( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
|
|
{
|
|
CBaseEntity *entity = (CBaseEntity*)pStruct;
|
|
Assert( entity );
|
|
|
|
const Vector *v;
|
|
|
|
if ( !entity->UseStepSimulationNetworkOrigin( &v ) )
|
|
{
|
|
v = &entity->GetLocalOrigin();
|
|
}
|
|
|
|
pOut->m_Vector[ 0 ] = v->x;
|
|
pOut->m_Vector[ 1 ] = v->y;
|
|
pOut->m_Vector[ 2 ] = v->z;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------
|
|
// Used when breaking up origin, note we still have to deal with StepSimulation
|
|
//--------------------------------------------------------------------------------------------------------
|
|
void SendProxy_OriginXY( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
|
|
{
|
|
CBaseEntity *entity = (CBaseEntity*)pStruct;
|
|
Assert( entity );
|
|
|
|
const Vector *v;
|
|
|
|
if ( !entity->UseStepSimulationNetworkOrigin( &v ) )
|
|
{
|
|
v = &entity->GetLocalOrigin();
|
|
}
|
|
|
|
pOut->m_Vector[ 0 ] = v->x;
|
|
pOut->m_Vector[ 1 ] = v->y;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------
|
|
// Used when breaking up origin, note we still have to deal with StepSimulation
|
|
//--------------------------------------------------------------------------------------------------------
|
|
void SendProxy_OriginZ( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
|
|
{
|
|
CBaseEntity *entity = (CBaseEntity*)pStruct;
|
|
Assert( entity );
|
|
|
|
const Vector *v;
|
|
|
|
if ( !entity->UseStepSimulationNetworkOrigin( &v ) )
|
|
{
|
|
v = &entity->GetLocalOrigin();
|
|
}
|
|
|
|
pOut->m_Float = v->z;
|
|
}
|
|
|
|
static float const cellEpsilon = 0.001f;
|
|
|
|
void CBaseEntity::SendProxy_CellX( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID)
|
|
{
|
|
CBaseEntity *entity = (CBaseEntity*)pStruct;
|
|
Assert( entity );
|
|
|
|
const Vector *v;
|
|
int cell[3];
|
|
|
|
if ( entity->UseStepSimulationNetworkOrigin( &v, cell ) )
|
|
{
|
|
pOut->m_Int = cell[0];
|
|
}
|
|
else
|
|
{
|
|
pOut->m_Int = *((int*)pData);
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::SendProxy_CellY( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID)
|
|
{
|
|
CBaseEntity *entity = (CBaseEntity*)pStruct;
|
|
Assert( entity );
|
|
|
|
const Vector *v;
|
|
int cell[3];
|
|
|
|
if ( entity->UseStepSimulationNetworkOrigin( &v, cell ) )
|
|
{
|
|
pOut->m_Int = cell[1];
|
|
}
|
|
else
|
|
{
|
|
pOut->m_Int = *((int*)pData);
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::SendProxy_CellZ( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID)
|
|
{
|
|
CBaseEntity *entity = (CBaseEntity*)pStruct;
|
|
Assert( entity );
|
|
|
|
const Vector *v;
|
|
int cell[3];
|
|
|
|
if ( entity->UseStepSimulationNetworkOrigin( &v, cell ) )
|
|
{
|
|
pOut->m_Int = cell[2];
|
|
}
|
|
else
|
|
{
|
|
pOut->m_Int = *((int*)pData);
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------
|
|
// The origin is adjusted to be relative to current cell
|
|
//--------------------------------------------------------------------------------------------------------
|
|
void CBaseEntity::SendProxy_CellOrigin( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
|
|
{
|
|
CBaseEntity *entity = (CBaseEntity*)pStruct;
|
|
Assert( entity );
|
|
|
|
const Vector *v;
|
|
int cell[3];
|
|
|
|
if ( !entity->UseStepSimulationNetworkOrigin( &v, cell ) )
|
|
{
|
|
v = &entity->GetLocalOrigin();
|
|
cell[0] = entity->m_cellX;
|
|
cell[1] = entity->m_cellY;
|
|
cell[2] = entity->m_cellZ;
|
|
}
|
|
|
|
register int const cellwidth = entity->m_cellwidth; // Load it into a register
|
|
|
|
Assert( cell[0] == CellFromCoord( cellwidth, v->x ) );
|
|
Assert( cell[1] == CellFromCoord( cellwidth, v->y ) );
|
|
Assert( cell[2] == CellFromCoord( cellwidth, v->z ) );
|
|
|
|
// Adjust based on cell size
|
|
pOut->m_Vector[ 0 ] = CellInCoord( cellwidth, cell[0], v->x );
|
|
pOut->m_Vector[ 1 ] = CellInCoord( cellwidth, cell[1], v->y );
|
|
pOut->m_Vector[ 2 ] = CellInCoord( cellwidth, cell[2], v->z );
|
|
|
|
Assert( pOut->m_Vector[ 0 ] >= 0.0f );
|
|
Assert( pOut->m_Vector[ 1 ] >= 0.0f );
|
|
Assert( pOut->m_Vector[ 2 ] >= 0.0f );
|
|
Assert( fabs( CoordFromCell( cellwidth, cell[0], pOut->m_Vector[ 0 ] ) - v->x ) < cellEpsilon );
|
|
Assert( fabs( CoordFromCell( cellwidth, cell[1], pOut->m_Vector[ 1 ] ) - v->y ) < cellEpsilon );
|
|
Assert( fabs( CoordFromCell( cellwidth, cell[2], pOut->m_Vector[ 2 ] ) - v->z ) < cellEpsilon );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------
|
|
// The origin is adjusted to be relative to current cell
|
|
//--------------------------------------------------------------------------------------------------------
|
|
void CBaseEntity::SendProxy_CellOriginXY( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
|
|
{
|
|
CBaseEntity *entity = (CBaseEntity*)pStruct;
|
|
Assert( entity );
|
|
|
|
const Vector *v;
|
|
int cell[3];
|
|
|
|
if ( !entity->UseStepSimulationNetworkOrigin( &v, cell ) )
|
|
{
|
|
v = &entity->GetLocalOrigin();
|
|
cell[0] = entity->m_cellX;
|
|
cell[1] = entity->m_cellY;
|
|
}
|
|
|
|
register int const cellwidth = entity->m_cellwidth; // Load it into a register
|
|
|
|
Assert( cell[0] == CellFromCoord( cellwidth, v->x ) );
|
|
Assert( cell[1] == CellFromCoord( cellwidth, v->y ) );
|
|
|
|
pOut->m_Vector[ 0 ] = CellInCoord( cellwidth, cell[0], v->x );
|
|
pOut->m_Vector[ 1 ] = CellInCoord( cellwidth, cell[1], v->y );
|
|
|
|
Assert( pOut->m_Vector[ 0 ] >= 0.0f );
|
|
Assert( pOut->m_Vector[ 1 ] >= 0.0f );
|
|
Assert( fabs( CoordFromCell( cellwidth, cell[0], pOut->m_Vector[ 0 ] ) - v->x ) < cellEpsilon );
|
|
Assert( fabs( CoordFromCell( cellwidth, cell[1], pOut->m_Vector[ 1 ] ) - v->y ) < cellEpsilon );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------
|
|
// The origin is adjusted to be relative to current cell
|
|
//--------------------------------------------------------------------------------------------------------
|
|
void CBaseEntity::SendProxy_CellOriginZ( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
|
|
{
|
|
CBaseEntity *entity = (CBaseEntity*)pStruct;
|
|
Assert( entity );
|
|
|
|
const Vector *v;
|
|
int cell[3];
|
|
|
|
if ( !entity->UseStepSimulationNetworkOrigin( &v, cell ) )
|
|
{
|
|
v = &entity->GetLocalOrigin();
|
|
cell[2] = entity->m_cellZ;
|
|
}
|
|
|
|
register int const cellwidth = entity->m_cellwidth; // Load it into a register
|
|
|
|
Assert( cell[2] == CellFromCoord( cellwidth, v->z ) );
|
|
|
|
pOut->m_Float = CellInCoord( cellwidth, cell[2], v->z );
|
|
|
|
Assert( pOut->m_Float >= 0.0f );
|
|
Assert( fabs( CoordFromCell( cellwidth, cell[2], pOut->m_Float ) - v->z ) < cellEpsilon );
|
|
}
|
|
|
|
void SendProxy_Angles( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
|
|
{
|
|
CBaseEntity *entity = (CBaseEntity*)pStruct;
|
|
Assert( entity );
|
|
|
|
const QAngle *a;
|
|
|
|
if ( !entity->UseStepSimulationNetworkAngles( &a ) )
|
|
{
|
|
a = &entity->GetLocalAngles();
|
|
}
|
|
|
|
pOut->m_Vector[ 0 ] = anglemod( a->x );
|
|
pOut->m_Vector[ 1 ] = anglemod( a->y );
|
|
pOut->m_Vector[ 2 ] = anglemod( a->z );
|
|
}
|
|
|
|
#if PREDICTION_ERROR_CHECK_LEVEL > 1
|
|
const int SENDPROP_ANGROTATION_DEFAULT_BITS = -1;
|
|
const int SENDPROP_VECORIGIN_FLAGS = SPROP_NOSCALE|SPROP_CHANGES_OFTEN;
|
|
#else
|
|
const int SENDPROP_ANGROTATION_DEFAULT_BITS = 13;
|
|
const int SENDPROP_VECORIGIN_FLAGS = SPROP_CELL_COORD|SPROP_CHANGES_OFTEN;
|
|
#endif
|
|
|
|
// This table encodes the CBaseEntity data.
|
|
IMPLEMENT_SERVERCLASS_ST_NOBASE( CBaseEntity, DT_BaseEntity )
|
|
SendPropDataTable( "AnimTimeMustBeFirst", 0, &REFERENCE_SEND_TABLE(DT_AnimTimeMustBeFirst), SendProxy_ClientSideAnimation ),
|
|
SendPropInt (SENDINFO(m_flSimulationTime), SIMULATION_TIME_WINDOW_BITS, SPROP_UNSIGNED|SPROP_CHANGES_OFTEN|SPROP_ENCODED_AGAINST_TICKCOUNT, SendProxy_SimulationTime, SENDPROP_SIMULATION_TIME_PRIORITY ),
|
|
#if defined(ENABLE_CREATE_TIME)
|
|
SendPropFloat (SENDINFO( m_flCreateTime ) ),
|
|
#endif
|
|
|
|
SendPropInt (SENDINFO(m_cellbits), MINIMUM_BITS_NEEDED( 32 ), SPROP_UNSIGNED, 0, SENDPROP_CELL_INFO_PRIORITY ),
|
|
// SendPropArray (SendPropInt(SENDINFO_ARRAY(m_cellXY), CELL_COUNT_BITS( CELL_BASEENTITY_ORIGIN_CELL_BITS ), SPROP_UNSIGNED|SPROP_CHANGES_OFTEN ), m_cellXY),
|
|
SendPropInt (SENDINFO(m_cellX), CELL_COUNT_BITS( CELL_BASEENTITY_ORIGIN_CELL_BITS ), SPROP_UNSIGNED, CBaseEntity::SendProxy_CellX, SENDPROP_CELL_INFO_PRIORITY ), // 32 priority in the send table
|
|
SendPropInt (SENDINFO(m_cellY), CELL_COUNT_BITS( CELL_BASEENTITY_ORIGIN_CELL_BITS ), SPROP_UNSIGNED, CBaseEntity::SendProxy_CellY, SENDPROP_CELL_INFO_PRIORITY ),
|
|
SendPropInt (SENDINFO(m_cellZ), CELL_COUNT_BITS( CELL_BASEENTITY_ORIGIN_CELL_BITS ), SPROP_UNSIGNED, CBaseEntity::SendProxy_CellZ, SENDPROP_CELL_INFO_PRIORITY ),
|
|
SendPropVector (SENDINFO(m_vecOrigin), CELL_BASEENTITY_ORIGIN_CELL_BITS, SENDPROP_VECORIGIN_FLAGS, 0.0f, HIGH_DEFAULT, CBaseEntity::SendProxy_CellOrigin ),
|
|
|
|
SendPropModelIndex(SENDINFO(m_nModelIndex)),
|
|
SendPropDataTable( SENDINFO_DT( m_Collision ), &REFERENCE_SEND_TABLE(DT_CollisionProperty) ),
|
|
SendPropInt (SENDINFO(m_nRenderFX), 8, SPROP_UNSIGNED ),
|
|
SendPropInt (SENDINFO(m_nRenderMode), 8, SPROP_UNSIGNED ),
|
|
SendPropInt (SENDINFO(m_fEffects), EF_MAX_BITS, SPROP_UNSIGNED),
|
|
SendPropInt (SENDINFO(m_clrRender), 32, SPROP_UNSIGNED, SendProxy_Color32ToInt32 ),
|
|
SendPropInt (SENDINFO(m_iTeamNum), TEAMNUM_NUM_BITS, 0),
|
|
SendPropInt (SENDINFO(m_iPendingTeamNum), TEAMNUM_NUM_BITS, 0),
|
|
#ifdef INFESTED_DLL
|
|
SendPropInt (SENDINFO(m_CollisionGroup), 6, SPROP_UNSIGNED),
|
|
#else
|
|
SendPropInt (SENDINFO(m_CollisionGroup), 5, SPROP_UNSIGNED),
|
|
#endif
|
|
SendPropFloat (SENDINFO(m_flElasticity), 0, SPROP_COORD),
|
|
SendPropFloat (SENDINFO(m_flShadowCastDistance), 12, SPROP_UNSIGNED ),
|
|
SendPropEHandle (SENDINFO(m_hOwnerEntity)),
|
|
SendPropEHandle (SENDINFO(m_hEffectEntity)),
|
|
SendPropEHandle (SENDINFO_NAME(m_hMoveParent, moveparent)),
|
|
SendPropInt (SENDINFO(m_iParentAttachment), NUM_PARENTATTACHMENT_BITS, SPROP_UNSIGNED),
|
|
|
|
SendPropStringT( SENDINFO( m_iName ) ),
|
|
|
|
#ifdef PORTAL2
|
|
SendPropStringT( SENDINFO( m_iSignifierName ) ),
|
|
#endif // PORTAL2
|
|
|
|
SendPropInt (SENDINFO_NAME( m_MoveType, movetype ), MOVETYPE_MAX_BITS, SPROP_UNSIGNED ),
|
|
SendPropInt (SENDINFO_NAME( m_MoveCollide, movecollide ), MOVECOLLIDE_MAX_BITS, SPROP_UNSIGNED ),
|
|
#if PREDICTION_ERROR_CHECK_LEVEL > 1
|
|
SendPropVector (SENDINFO(m_angRotation), SENDPROP_ANGROTATION_DEFAULT_BITS, SPROP_NOSCALE|SPROP_CHANGES_OFTEN, 0, HIGH_DEFAULT, SendProxy_Angles ),
|
|
#else
|
|
SendPropQAngles (SENDINFO(m_angRotation), SENDPROP_ANGROTATION_DEFAULT_BITS, SPROP_CHANGES_OFTEN, SendProxy_Angles ),
|
|
#endif
|
|
|
|
SendPropInt ( SENDINFO( m_iTextureFrameIndex ), 8, SPROP_UNSIGNED ),
|
|
|
|
#if defined ( PORTAL2 )
|
|
SendPropInt ( SENDINFO( m_iObjectCapsCache ), 6, SPROP_UNSIGNED ),
|
|
#endif
|
|
|
|
#if !defined( NO_ENTITY_PREDICTION ) && defined( USE_PREDICTABLEID )
|
|
SendPropEHandle (SENDINFO(m_hPlayerSimulationOwner)),
|
|
SendPropDataTable( "predictable_id", 0, &REFERENCE_SEND_TABLE( DT_PredictableId ), SendProxy_SendPredictableId ),
|
|
#endif
|
|
|
|
// FIXME: Collapse into another flag field?
|
|
SendPropInt( SENDINFO(m_bSimulatedEveryTick), 1, SPROP_UNSIGNED ),
|
|
SendPropInt( SENDINFO(m_bAnimatedEveryTick), 1, SPROP_UNSIGNED ),
|
|
SendPropBool( SENDINFO( m_bAlternateSorting )),
|
|
SendPropBool( SENDINFO( m_bSpotted )),
|
|
SendPropArray3( SENDINFO_ARRAY3(m_bSpottedByMask), SendPropInt( SENDINFO_ARRAY( m_bSpottedByMask ), -1, SPROP_UNSIGNED ) ),
|
|
|
|
SendPropBool( SENDINFO( m_bIsAutoaimTarget )),
|
|
|
|
// Fading
|
|
SendPropFloat( SENDINFO( m_fadeMinDist ), 0, SPROP_NOSCALE ),
|
|
SendPropFloat( SENDINFO( m_fadeMaxDist ), 0, SPROP_NOSCALE ),
|
|
SendPropFloat( SENDINFO( m_flFadeScale ), 0, SPROP_NOSCALE ),
|
|
|
|
#if 1
|
|
// #ifndef _GAMECONSOLE -- X360 client and Win32 XLSP dedicated server need equivalent SendTables
|
|
SendPropInt( SENDINFO(m_nMinCPULevel), CPU_LEVEL_BIT_COUNT, SPROP_UNSIGNED ),
|
|
SendPropInt( SENDINFO(m_nMaxCPULevel), CPU_LEVEL_BIT_COUNT, SPROP_UNSIGNED ),
|
|
SendPropInt( SENDINFO(m_nMinGPULevel), GPU_LEVEL_BIT_COUNT, SPROP_UNSIGNED ),
|
|
SendPropInt( SENDINFO(m_nMaxGPULevel), GPU_LEVEL_BIT_COUNT, SPROP_UNSIGNED ),
|
|
#endif
|
|
|
|
SendPropFloat(SENDINFO( m_flUseLookAtAngle ) ),
|
|
|
|
SendPropFloat( SENDINFO( m_flLastMadeNoiseTime ) ),
|
|
|
|
END_SEND_TABLE()
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool EntityNamesMatchCStrings( const char *pszQuery, const char *pszNameToMatch )
|
|
{
|
|
while ( *pszNameToMatch && *pszQuery )
|
|
{
|
|
char cName = *pszNameToMatch;
|
|
char cQuery = *pszQuery;
|
|
if ( cName != cQuery && tolower(cName) != tolower(cQuery) ) // people almost always use lowercase, so assume that first
|
|
break;
|
|
++pszNameToMatch;
|
|
++pszQuery;
|
|
}
|
|
|
|
if ( *pszQuery == 0 && *pszNameToMatch == 0 )
|
|
return true;
|
|
|
|
// @TODO (toml 03-18-03): Perhaps support real wildcards. Right now, only thing supported is trailing *
|
|
if ( *pszQuery == '*' )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CBaseEntity::CBaseEntity( bool bServerOnly )
|
|
{
|
|
COMPILE_TIME_ASSERT( MOVETYPE_LAST < (1 << MOVETYPE_MAX_BITS) );
|
|
COMPILE_TIME_ASSERT( MOVECOLLIDE_COUNT < (1 << MOVECOLLIDE_MAX_BITS) );
|
|
|
|
// Fix CPU_LEVEL_BIT_COUNT/GPU_LEVEL_BIT_COUNT here if necessary
|
|
COMPILE_TIME_ASSERT( CPU_LEVEL_PC_COUNT+1 <= (1 << CPU_LEVEL_BIT_COUNT) );
|
|
COMPILE_TIME_ASSERT( GPU_LEVEL_PC_COUNT+1 <= (1 << GPU_LEVEL_BIT_COUNT) );
|
|
|
|
#ifdef _DEBUG
|
|
// necessary since in debug, we initialize vectors to NAN for debugging
|
|
m_vecAngVelocity.Init();
|
|
m_vecAbsOrigin.Init();
|
|
m_angAbsRotation.Init();
|
|
// m_vecAbsAngVelocity.Init();
|
|
m_vecViewOffset.Init();
|
|
m_vecBaseVelocity.GetForModify().Init();
|
|
m_vecVelocity.Init();
|
|
m_vecAbsVelocity.Init();
|
|
#endif
|
|
|
|
SetCellBits();
|
|
UpdateCell();
|
|
|
|
m_bAlternateSorting = false;
|
|
m_CollisionGroup = COLLISION_GROUP_NONE;
|
|
m_iParentAttachment = 0;
|
|
CollisionProp()->Init( this );
|
|
NetworkProp()->Init( this );
|
|
m_bForcePurgeFixedupStrings = false;
|
|
|
|
m_fadeMinDist = 0;
|
|
m_fadeMaxDist = 0;
|
|
m_flFadeScale = 0.0f;
|
|
|
|
// NOTE: THIS MUST APPEAR BEFORE ANY SetMoveType() or SetNextThink() calls
|
|
AddEFlags( EFL_NO_THINK_FUNCTION | EFL_NO_GAME_PHYSICS_SIMULATION | EFL_USE_PARTITION_WHEN_NOT_SOLID );
|
|
|
|
// clear debug overlays
|
|
m_debugOverlays = 0;
|
|
m_pTimedOverlay = NULL;
|
|
m_pPhysicsObject = NULL;
|
|
m_flElasticity = 1.0f;
|
|
m_flShadowCastDistance = m_flDesiredShadowCastDistance = 0;
|
|
SetRenderColor( 255, 255, 255 );
|
|
SetRenderAlpha( 255 );
|
|
m_iPendingTeamNum = m_iTeamNum = m_iInitialTeamNum = TEAM_UNASSIGNED;
|
|
m_nLastThinkTick = gpGlobals->tickcount;
|
|
m_nSimulationTick = -1;
|
|
SetIdentityMatrix( m_rgflCoordinateFrame );
|
|
m_pBlocker = NULL;
|
|
#if _DEBUG
|
|
m_iCurrentThinkContext = NO_THINK_CONTEXT;
|
|
#endif
|
|
m_nWaterTouch = m_nSlimeTouch = 0;
|
|
|
|
SetSolid( SOLID_NONE );
|
|
ClearSolidFlags();
|
|
|
|
SetMoveType( MOVETYPE_NONE );
|
|
SetOwnerEntity( NULL );
|
|
SetCheckUntouch( false );
|
|
SetModelIndex( 0 );
|
|
SetModelName( NULL_STRING );
|
|
m_nTransmitStateOwnedCounter = 0;
|
|
|
|
SetCollisionBounds( vec3_origin, vec3_origin );
|
|
ClearFlags();
|
|
|
|
SetFriction( 1.0f );
|
|
|
|
if ( bServerOnly )
|
|
{
|
|
AddEFlags( EFL_SERVER_ONLY );
|
|
}
|
|
NetworkProp()->MarkPVSInformationDirty();
|
|
|
|
AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID );
|
|
|
|
m_pPrevByClass = m_pNextByClass = NULL;
|
|
m_ListByClass = (UtlHashHandle_t)~0;
|
|
SetNetworkQuantizeOriginAngAngles( false );
|
|
|
|
#if defined(ENABLE_CREATE_TIME)
|
|
m_flCreateTime = 0.0f;
|
|
#endif
|
|
m_pEvent = NULL;
|
|
|
|
#ifdef PORTAL2
|
|
m_iSignifierName = NULL_STRING;
|
|
#endif // PORTAL2
|
|
|
|
m_bSpotted = false;
|
|
ClearSpottedBy();
|
|
|
|
m_nSpotRules = 0;
|
|
m_fLastSpotCheck = 0.0f;
|
|
|
|
m_bIsAutoaimTarget = false;
|
|
|
|
m_flUseLookAtAngle = DEFAULT_LOOK_AT_USE_ANGLE;
|
|
|
|
m_flLastMadeNoiseTime = 0.0f;
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Scale up our physics hull and test against the new one
|
|
// Input : *pNewCollide - New collision hull
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SetScaledPhysics( IPhysicsObject *pNewObject )
|
|
{
|
|
if ( pNewObject )
|
|
{
|
|
AddSolidFlags( FSOLID_CUSTOMBOXTEST | FSOLID_CUSTOMRAYTEST );
|
|
}
|
|
else
|
|
{
|
|
RemoveSolidFlags( FSOLID_CUSTOMBOXTEST | FSOLID_CUSTOMRAYTEST );
|
|
}
|
|
}
|
|
|
|
extern bool g_bDisableEhandleAccess;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: See note below
|
|
//-----------------------------------------------------------------------------
|
|
CBaseEntity::~CBaseEntity( )
|
|
{
|
|
// FIXME: This can't be called from UpdateOnRemove! There's at least one
|
|
// case where friction sounds are added between the call to UpdateOnRemove + ~CBaseEntity
|
|
PhysCleanupFrictionSounds( this );
|
|
|
|
// In debug make sure that we don't call delete on an entity without setting
|
|
// the disable flag first!
|
|
// EHANDLE accessors will check, in debug, for access to entities during destruction of
|
|
// another entity.
|
|
// That kind of operation should only occur in UpdateOnRemove calls
|
|
// Deletion should only occur via UTIL_Remove(Immediate) calls, not via naked delete calls
|
|
Assert( g_bDisableEhandleAccess );
|
|
|
|
VPhysicsDestroyObject();
|
|
|
|
// Need to remove references to this entity before EHANDLES go null
|
|
{
|
|
g_bDisableEhandleAccess = false;
|
|
CBaseEntity::PhysicsRemoveTouchedList( this );
|
|
CBaseEntity::PhysicsRemoveGroundList( this );
|
|
DestroyAllDataObjects();
|
|
g_bDisableEhandleAccess = true;
|
|
|
|
// Remove this entity from the ent list (NOTE: This Makes EHANDLES go NULL)
|
|
gEntList.RemoveEntity( GetRefEHandle() );
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::PostConstructor( const char *szClassname )
|
|
{
|
|
NetworkProp()->CacheServerClass();
|
|
if ( szClassname )
|
|
{
|
|
SetClassname(szClassname);
|
|
}
|
|
|
|
Assert( m_iClassname != NULL_STRING && STRING(m_iClassname) != NULL );
|
|
|
|
// Possibly get an edict, and add self to global list of entites.
|
|
if ( IsEFlagSet( EFL_SERVER_ONLY ) )
|
|
{
|
|
gEntList.AddNonNetworkableEntity( this );
|
|
}
|
|
else
|
|
{
|
|
// Certain entities set up their edicts in the constructor
|
|
if ( !IsEFlagSet( EFL_NO_AUTO_EDICT_ATTACH ) )
|
|
{
|
|
NetworkProp()->AttachEdict( g_pForceAttachEdict );
|
|
g_pForceAttachEdict = NULL;
|
|
}
|
|
|
|
// Some ents like the player override the AttachEdict function and do it at a different time.
|
|
// While precaching, they don't ever have an edict, so we don't need to add them to
|
|
// the entity list in that case.
|
|
if ( edict() )
|
|
{
|
|
gEntList.AddNetworkableEntity( this, entindex() );
|
|
|
|
// Cache our IServerNetworkable pointer for the engine for fast access.
|
|
if ( edict() )
|
|
edict()->m_pNetworkable = NetworkProp();
|
|
}
|
|
|
|
InitSharedVars();
|
|
}
|
|
|
|
CheckHasThinkFunction( false );
|
|
CheckHasGamePhysicsSimulation();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Called after player becomes active in the game
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::PostClientActive( void )
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Verifies that this entity's data description is valid in debug builds.
|
|
//-----------------------------------------------------------------------------
|
|
#ifdef _DEBUG
|
|
typedef CUtlVector< const char * > KeyValueNameList_t;
|
|
|
|
static void AddDataMapFieldNamesToList( KeyValueNameList_t &list, datamap_t *pDataMap )
|
|
{
|
|
while (pDataMap != NULL)
|
|
{
|
|
for (int i = 0; i < pDataMap->dataNumFields; i++)
|
|
{
|
|
typedescription_t *pField = &pDataMap->dataDesc[i];
|
|
|
|
if (pField->fieldType == FIELD_EMBEDDED)
|
|
{
|
|
AddDataMapFieldNamesToList( list, pField->td );
|
|
continue;
|
|
}
|
|
|
|
if (pField->flags & FTYPEDESC_KEY)
|
|
{
|
|
list.AddToTail( pField->externalName );
|
|
}
|
|
}
|
|
|
|
pDataMap = pDataMap->baseMap;
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::ValidateDataDescription(void)
|
|
{
|
|
// Multiple key fields that have the same name are not allowed - it creates an
|
|
// ambiguity when trying to parse keyvalues and outputs.
|
|
datamap_t *pDataMap = GetDataDescMap();
|
|
if ((pDataMap == NULL) || pDataMap->bValidityChecked)
|
|
return;
|
|
|
|
pDataMap->bValidityChecked = true;
|
|
|
|
// Let's generate a list of all keyvalue strings in the entire hierarchy...
|
|
KeyValueNameList_t names(128);
|
|
AddDataMapFieldNamesToList( names, pDataMap );
|
|
|
|
for (int i = names.Count(); --i > 0; )
|
|
{
|
|
for (int j = i - 1; --j >= 0; )
|
|
{
|
|
if (!Q_stricmp(names[i], names[j]))
|
|
{
|
|
DevMsg( "%s has multiple data description entries for \"%s\"\n", STRING(m_iClassname), names[i]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // _DEBUG
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Sets the collision bounds + the size
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SetCollisionBounds( const Vector& mins, const Vector &maxs )
|
|
{
|
|
m_Collision.SetCollisionBounds( mins, maxs );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Vscript: Gets the min collision bounds, centered on object
|
|
//-----------------------------------------------------------------------------
|
|
const Vector& CBaseEntity::ScriptGetBoundingMins( void )
|
|
{
|
|
return m_Collision.OBBMins();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Vscript: Gets the max collision bounds, centered on object
|
|
//-----------------------------------------------------------------------------
|
|
const Vector& CBaseEntity::ScriptGetBoundingMaxs( void )
|
|
{
|
|
return m_Collision.OBBMaxs();
|
|
}
|
|
|
|
void CBaseEntity::StopFollowingEntity( )
|
|
{
|
|
if( !IsFollowingEntity() )
|
|
{
|
|
Assert( IsEffectActive( EF_BONEMERGE ) == 0 );
|
|
return;
|
|
}
|
|
|
|
SetParent( NULL );
|
|
RemoveEffects( EF_BONEMERGE );
|
|
RemoveSolidFlags( FSOLID_NOT_SOLID );
|
|
SetMoveType( MOVETYPE_NONE );
|
|
CollisionRulesChanged();
|
|
}
|
|
|
|
bool CBaseEntity::IsFollowingEntity()
|
|
{
|
|
return IsEffectActive( EF_BONEMERGE ) && (GetMoveType() == MOVETYPE_NONE) && GetMoveParent();
|
|
}
|
|
|
|
CBaseEntity *CBaseEntity::GetFollowedEntity()
|
|
{
|
|
if (!IsFollowingEntity())
|
|
return NULL;
|
|
return GetMoveParent();
|
|
}
|
|
|
|
void CBaseEntity::SetClassname( const char *className )
|
|
{
|
|
m_iClassname = AllocPooledString( className );
|
|
|
|
#ifdef PORTAL2
|
|
m_iSignifierName = m_iClassname;
|
|
#endif // PORTAL2
|
|
}
|
|
|
|
// position to shoot at
|
|
Vector CBaseEntity::BodyTarget( const Vector &posSrc, bool bNoisy)
|
|
{
|
|
return WorldSpaceCenter( );
|
|
}
|
|
|
|
// return the position of my head. someone's trying to attack it.
|
|
Vector CBaseEntity::HeadTarget( const Vector &posSrc )
|
|
{
|
|
return EyePosition();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Methods related to fade distance
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SetFadeDistance( float minFadeDist, float maxFadeDist )
|
|
{
|
|
m_fadeMinDist = minFadeDist;
|
|
m_fadeMaxDist = maxFadeDist;
|
|
}
|
|
|
|
void CBaseEntity::SetGlobalFadeScale( float flFadeScale )
|
|
{
|
|
m_flFadeScale = flFadeScale;
|
|
}
|
|
|
|
float CBaseEntity::GetGlobalFadeScale() const
|
|
{
|
|
return m_flFadeScale;
|
|
}
|
|
|
|
struct TimedOverlay_t
|
|
{
|
|
char *msg;
|
|
int msgEndTime;
|
|
int msgStartTime;
|
|
TimedOverlay_t *pNextTimedOverlay;
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Display an error message on the entity
|
|
// Input :
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::AddTimedOverlay( const char *msg, int endTime )
|
|
{
|
|
TimedOverlay_t *pNewTO = new TimedOverlay_t;
|
|
int len = strlen(msg);
|
|
pNewTO->msg = new char[len + 1];
|
|
Q_strncpy(pNewTO->msg,msg, len+1);
|
|
pNewTO->msgEndTime = gpGlobals->curtime + endTime;
|
|
pNewTO->msgStartTime = gpGlobals->curtime;
|
|
pNewTO->pNextTimedOverlay = m_pTimedOverlay;
|
|
m_pTimedOverlay = pNewTO;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Send debug overlay box to the client
|
|
// Input :
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::DrawBBoxOverlay( float flDuration )
|
|
{
|
|
if (edict())
|
|
{
|
|
NDebugOverlay::EntityBounds(this, 255, 100, 0, 0, flDuration );
|
|
|
|
if ( CollisionProp()->IsSolidFlagSet( FSOLID_USE_TRIGGER_BOUNDS ) )
|
|
{
|
|
Vector vecTriggerMins, vecTriggerMaxs;
|
|
CollisionProp()->WorldSpaceTriggerBounds( &vecTriggerMins, &vecTriggerMaxs );
|
|
Vector center = 0.5f * (vecTriggerMins + vecTriggerMaxs);
|
|
Vector extents = vecTriggerMaxs - center;
|
|
NDebugOverlay::Box(center, -extents, extents, 0, 255, 255, 0, flDuration );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CBaseEntity::DrawAbsBoxOverlay()
|
|
{
|
|
int red = 0;
|
|
int green = 200;
|
|
|
|
if ( VPhysicsGetObject() && VPhysicsGetObject()->IsAsleep() )
|
|
{
|
|
red = 90;
|
|
green = 120;
|
|
}
|
|
|
|
if (edict())
|
|
{
|
|
// Surrounding boxes are axially aligned, so ignore angles
|
|
Vector vecSurroundMins, vecSurroundMaxs;
|
|
CollisionProp()->WorldSpaceSurroundingBounds( &vecSurroundMins, &vecSurroundMaxs );
|
|
Vector center = 0.5f * (vecSurroundMins + vecSurroundMaxs);
|
|
Vector extents = vecSurroundMaxs - center;
|
|
NDebugOverlay::Box(center, -extents, extents, red, green, 0, 0 ,0);
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::DrawRBoxOverlay()
|
|
{
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Draws an axis overlay at the origin and angles of the entity
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SendDebugPivotOverlay( void )
|
|
{
|
|
if ( edict() )
|
|
{
|
|
NDebugOverlay::Axis( GetAbsOrigin(), GetAbsAngles(), 20, true, 0 );
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : Add new entity positioned overlay text
|
|
// Input : How many lines to offset text from origin
|
|
// The text to print
|
|
// How long to display text
|
|
// The color of the text
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::EntityText( int text_offset, const char *text, float duration, int r, int g, int b, int a )
|
|
{
|
|
Vector origin;
|
|
Vector vecLocalCenter;
|
|
|
|
VectorAdd( m_Collision.OBBMins(), m_Collision.OBBMaxs(), vecLocalCenter );
|
|
vecLocalCenter *= 0.5f;
|
|
|
|
if ( ( m_Collision.GetCollisionAngles() == vec3_angle ) || ( vecLocalCenter == vec3_origin ) )
|
|
{
|
|
VectorAdd( vecLocalCenter, m_Collision.GetCollisionOrigin(), origin );
|
|
}
|
|
else
|
|
{
|
|
VectorTransform( vecLocalCenter, m_Collision.CollisionToWorldTransform(), origin );
|
|
}
|
|
|
|
NDebugOverlay::EntityTextAtPosition( origin, text_offset, text, duration, r, g, b, a );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::DrawTimedOverlays(void)
|
|
{
|
|
// Draw name first if I have an overlay or am in message mode
|
|
if ((m_debugOverlays & OVERLAY_MESSAGE_BIT))
|
|
{
|
|
char tempstr[512];
|
|
Q_snprintf( tempstr, sizeof( tempstr ), "[%s]", GetDebugName() );
|
|
EntityText(0,tempstr, 0);
|
|
}
|
|
|
|
// Now draw overlays
|
|
TimedOverlay_t* pTO = m_pTimedOverlay;
|
|
TimedOverlay_t* pNextTO = NULL;
|
|
TimedOverlay_t* pLastTO = NULL;
|
|
int nCount = 1; // Offset by one
|
|
while (pTO)
|
|
{
|
|
pNextTO = pTO->pNextTimedOverlay;
|
|
|
|
// Remove old messages unless messages are paused
|
|
if ((!CBaseEntity::Debug_IsPaused() && gpGlobals->curtime > pTO->msgEndTime) ||
|
|
(nCount > 10))
|
|
{
|
|
if (pLastTO)
|
|
{
|
|
pLastTO->pNextTimedOverlay = pNextTO;
|
|
}
|
|
else
|
|
{
|
|
m_pTimedOverlay = pNextTO;
|
|
}
|
|
|
|
delete pTO->msg;
|
|
delete pTO;
|
|
}
|
|
else
|
|
{
|
|
int nAlpha = 0;
|
|
|
|
// If messages aren't paused fade out
|
|
if (!CBaseEntity::Debug_IsPaused())
|
|
{
|
|
nAlpha = 255*((gpGlobals->curtime - pTO->msgStartTime)/(pTO->msgEndTime - pTO->msgStartTime));
|
|
}
|
|
int r = 185;
|
|
int g = 145;
|
|
int b = 145;
|
|
|
|
// Brighter when new message
|
|
if (nAlpha < 50)
|
|
{
|
|
r = 255;
|
|
g = 205;
|
|
b = 205;
|
|
}
|
|
if (nAlpha < 0) nAlpha = 0;
|
|
EntityText(nCount,pTO->msg, 0.0, r, g, b, 255-nAlpha);
|
|
nCount++;
|
|
|
|
pLastTO = pTO;
|
|
}
|
|
pTO = pNextTO;
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::DrawVPhysicsObjectCenterAndContactPoints(IPhysicsObject *obj)
|
|
{
|
|
if ( obj == NULL ) return;
|
|
|
|
Vector massCenter = obj->GetMassCenterLocalSpace();
|
|
Vector worldPos;
|
|
obj->LocalToWorld( &worldPos, massCenter );
|
|
NDebugOverlay::Cross3D( worldPos, 12, 255, 0, 0, false, 0 );
|
|
DebugDrawContactPoints(obj);
|
|
if ( GetMoveType() != MOVETYPE_VPHYSICS )
|
|
{
|
|
Vector pos;
|
|
QAngle angles;
|
|
obj->GetPosition( &pos, &angles );
|
|
float dist = (pos - GetAbsOrigin()).Length();
|
|
|
|
Vector axis;
|
|
float deltaAngle;
|
|
RotationDeltaAxisAngle( angles, GetAbsAngles(), axis, deltaAngle );
|
|
if ( dist > 2 || fabsf(deltaAngle) > 2 )
|
|
{
|
|
Vector mins, maxs;
|
|
|
|
if ( obj->GetCollide() )
|
|
{
|
|
physcollision->CollideGetAABB( &mins, &maxs, obj->GetCollide(), vec3_origin, vec3_angle );
|
|
}
|
|
else
|
|
{
|
|
mins = WorldAlignMins();
|
|
maxs = WorldAlignMins();
|
|
}
|
|
NDebugOverlay::BoxAngles( pos, mins, maxs, angles, 255, 255, 0, 16, 0 );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Draw all overlays (should be implemented by subclass to add
|
|
// any additional non-text overlays)
|
|
// Input :
|
|
// Output : Current text offset from the top
|
|
//-----------------------------------------------------------------------------
|
|
ConVar autoaim_viewing_client("autoaim_viewing_client", "1", FCVAR_DEVELOPMENTONLY );
|
|
void CBaseEntity::DrawDebugGeometryOverlays(void)
|
|
{
|
|
DrawTimedOverlays();
|
|
DrawDebugTextOverlays();
|
|
|
|
if (m_debugOverlays & OVERLAY_NAME_BIT)
|
|
{
|
|
EntityText(0,GetDebugName(), 0);
|
|
}
|
|
if (m_debugOverlays & OVERLAY_BBOX_BIT)
|
|
{
|
|
DrawBBoxOverlay();
|
|
}
|
|
if (m_debugOverlays & OVERLAY_ABSBOX_BIT )
|
|
{
|
|
DrawAbsBoxOverlay();
|
|
}
|
|
if (m_debugOverlays & OVERLAY_PIVOT_BIT)
|
|
{
|
|
SendDebugPivotOverlay();
|
|
}
|
|
if( m_debugOverlays & OVERLAY_RBOX_BIT )
|
|
{
|
|
DrawRBoxOverlay();
|
|
}
|
|
if ( m_debugOverlays & (OVERLAY_BBOX_BIT|OVERLAY_PIVOT_BIT) )
|
|
{
|
|
// draw mass center and contact points
|
|
DrawVPhysicsObjectCenterAndContactPoints(VPhysicsGetObject());
|
|
|
|
IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
|
|
int count = VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
DrawVPhysicsObjectCenterAndContactPoints(pList[i]);
|
|
}
|
|
}
|
|
if ( m_debugOverlays & OVERLAY_SHOW_BLOCKSLOS )
|
|
{
|
|
if ( BlocksLOS() )
|
|
{
|
|
NDebugOverlay::EntityBounds(this, 255, 255, 255, 0, 0 );
|
|
}
|
|
}
|
|
if ( ( m_debugOverlays & OVERLAY_AUTOAIM_BIT ) && (GetFlags()&FL_AIMTARGET) && AI_GetSinglePlayer() != NULL )
|
|
{
|
|
// Crude, but it gets the point across.
|
|
Vector vecCenter = GetAutoAimCenter();
|
|
Vector vecRight, vecUp, vecDiag;
|
|
|
|
CBasePlayer *pPlayer = AI_GetSinglePlayer();
|
|
|
|
float radius = GetAutoAimRadius();
|
|
|
|
QAngle angles = pPlayer->EyeAngles();
|
|
AngleVectors( angles, NULL, &vecRight, &vecUp );
|
|
|
|
int r,g,b;
|
|
|
|
if( ((int)gpGlobals->curtime) % 2 == 1 )
|
|
{
|
|
r = 255;
|
|
g = 255;
|
|
b = 255;
|
|
|
|
if( pPlayer->GetActiveWeapon() != NULL )
|
|
radius *= pPlayer->GetActiveWeapon()->WeaponAutoAimScale();
|
|
|
|
}
|
|
else
|
|
{
|
|
r = 255;g=0;b=0;
|
|
|
|
if( !ShouldAttractAutoAim(pPlayer) )
|
|
{
|
|
g = 255;
|
|
}
|
|
}
|
|
|
|
if( pPlayer->IsInAVehicle() )
|
|
{
|
|
radius *= sv_vehicle_autoaim_scale.GetFloat();
|
|
}
|
|
|
|
NDebugOverlay::Line( vecCenter, vecCenter + vecRight * radius, r, g, b, true, 0.1 );
|
|
NDebugOverlay::Line( vecCenter, vecCenter - vecRight * radius, r, g, b, true, 0.1 );
|
|
NDebugOverlay::Line( vecCenter, vecCenter + vecUp * radius, r, g, b, true, 0.1 );
|
|
NDebugOverlay::Line( vecCenter, vecCenter - vecUp * radius, r, g, b, true, 0.1 );
|
|
|
|
vecDiag = vecRight + vecUp;
|
|
VectorNormalize( vecDiag );
|
|
NDebugOverlay::Line( vecCenter - vecDiag * radius, vecCenter + vecDiag * radius, r, g, b, true, 0.1 );
|
|
|
|
vecDiag = vecRight - vecUp;
|
|
VectorNormalize( vecDiag );
|
|
NDebugOverlay::Line( vecCenter - vecDiag * radius, vecCenter + vecDiag * radius, r, g, b, true, 0.1 );
|
|
}
|
|
}
|
|
|
|
static ConVar debug_overlay_fullposition( "debug_overlay_fullposition",
|
|
#if defined( _DEBUG )
|
|
"1"
|
|
#else
|
|
"0"
|
|
#endif
|
|
);
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Draw any text overlays (override in subclass to add additional text)
|
|
// Output : Current text offset from the top
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::DrawDebugTextOverlays(void)
|
|
{
|
|
int offset = 1;
|
|
if (m_debugOverlays & OVERLAY_TEXT_BIT)
|
|
{
|
|
int r = 0;
|
|
int g = 255;
|
|
int b = 0;
|
|
|
|
char tempstr[512];
|
|
Q_snprintf( tempstr, sizeof(tempstr), "(%d) Name: %s (%s)", entindex(), GetDebugName(), GetClassname() );
|
|
EntityText(offset,tempstr, 0,r,g,b);
|
|
offset++;
|
|
|
|
if( m_iGlobalname != NULL_STRING )
|
|
{
|
|
Q_snprintf( tempstr, sizeof(tempstr), "GLOBALNAME: %s", STRING( m_iGlobalname ) );
|
|
EntityText(offset,tempstr, 0);
|
|
offset++;
|
|
}
|
|
|
|
if (debug_overlay_fullposition.GetBool() )
|
|
{
|
|
Vector vecOrigin = GetAbsOrigin();
|
|
Q_snprintf( tempstr, sizeof(tempstr), "pos: (%f, %f, %f)\n", vecOrigin.x, vecOrigin.y, vecOrigin.z );
|
|
EntityText( offset, tempstr, 0 );
|
|
offset++;
|
|
|
|
QAngle angOrientation = GetAbsAngles();
|
|
Q_snprintf( tempstr, sizeof(tempstr), "ang: (%f, %f, %f)\n", angOrientation.x, angOrientation.y, angOrientation.z );
|
|
EntityText( offset, tempstr, 0 );
|
|
offset++;
|
|
|
|
Q_snprintf( tempstr, sizeof(tempstr), "cell: (%d, %d, %d)\n", m_cellX.Get(), m_cellY.Get(), m_cellZ.Get() );
|
|
EntityText( offset, tempstr, 0 );
|
|
offset++;
|
|
|
|
register int const cellwidth = m_cellwidth; // Load it into a register
|
|
Vector cellOrigin;
|
|
cellOrigin.x = CellInCoord( cellwidth, m_cellX, m_vecOrigin->x );
|
|
cellOrigin.y = CellInCoord( cellwidth, m_cellY, m_vecOrigin->y );
|
|
cellOrigin.z = CellInCoord( cellwidth, m_cellZ, m_vecOrigin->z );
|
|
|
|
Q_snprintf( tempstr, sizeof(tempstr), "celloffset: (%f, %f, %f)\n", cellOrigin.x, cellOrigin.y, cellOrigin.z );
|
|
EntityText( offset, tempstr, 0 );
|
|
offset++;
|
|
}
|
|
else
|
|
{
|
|
Vector vecOrigin = GetAbsOrigin();
|
|
Q_snprintf( tempstr, sizeof(tempstr), "Position: %0.3f, %0.3f, %0.3f\n", vecOrigin.x, vecOrigin.y, vecOrigin.z );
|
|
EntityText( offset, tempstr, 0 );
|
|
offset++;
|
|
}
|
|
|
|
if ( !BlocksLOS() )
|
|
{
|
|
EntityText(offset,"Doesn't block LOS",0);
|
|
offset++;
|
|
}
|
|
|
|
if ( GetModelName() != NULL_STRING || GetBaseAnimating() )
|
|
{
|
|
Q_snprintf(tempstr, sizeof(tempstr), "Model:%s", STRING(GetModelName()) );
|
|
EntityText(offset,tempstr,0,255,255,0);
|
|
offset++;
|
|
}
|
|
|
|
if( m_hDamageFilter.Get() != NULL )
|
|
{
|
|
Q_snprintf( tempstr, sizeof(tempstr), "DAMAGE FILTER:%s", m_hDamageFilter->GetDebugName() );
|
|
EntityText( offset,tempstr,0,r,g,b);
|
|
offset++;
|
|
}
|
|
|
|
if ( ent_show_contexts.GetBool() )
|
|
{
|
|
int count = GetContextCount();
|
|
if ( count )
|
|
{
|
|
for ( int i = 0; i < count; ++i )
|
|
{
|
|
Q_snprintf(tempstr, sizeof(tempstr),"Context: %s:%s", GetContextName( i ), GetContextValue( i ) );
|
|
EntityText( offset, tempstr, 0,r,g,b);
|
|
offset++;
|
|
}
|
|
}
|
|
}
|
|
|
|
Q_snprintf(tempstr, sizeof(tempstr), "Flags :%d", GetFlags() );
|
|
EntityText(offset,tempstr,0);
|
|
offset++;
|
|
|
|
Q_snprintf(tempstr, sizeof(tempstr), "Effects :%d (EF_NODRAW=%d)", GetEffects(), GetEffects() & EF_NODRAW );
|
|
EntityText(offset,tempstr,0);
|
|
offset++;
|
|
|
|
#if defined ( PORTAL2 )
|
|
Q_snprintf(tempstr, sizeof(tempstr), "In Portal Environment: %s", (CPortalSimulator::GetSimulatorThatOwnsEntity(this))?("yes"):("no") );
|
|
EntityText(offset,tempstr,0);
|
|
offset++;
|
|
#endif
|
|
|
|
}
|
|
|
|
if (m_debugOverlays & OVERLAY_VIEWOFFSET)
|
|
{
|
|
NDebugOverlay::Cross3D( EyePosition(), 16, 255, 0, 0, true, 0.05f );
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
|
|
void CBaseEntity::SetParent( string_t newParent, CBaseEntity *pActivator, int iAttachment )
|
|
{
|
|
// find and notify the new parent
|
|
CBaseEntity *pParent = gEntList.FindEntityByName( NULL, newParent, NULL, pActivator );
|
|
|
|
// debug check
|
|
if ( newParent != NULL_STRING && pParent == NULL )
|
|
{
|
|
Msg( "Entity %s(%s) has bad parent %s\n", STRING(m_iClassname), GetDebugName(), STRING(newParent) );
|
|
}
|
|
else
|
|
{
|
|
// make sure there isn't any ambiguity
|
|
if ( gEntList.FindEntityByName( pParent, newParent, NULL, pActivator ) )
|
|
{
|
|
Msg( "Entity %s(%s) is ambiguously parented to %s, because there is more than one entity by that name.\n", STRING(m_iClassname), GetDebugName(), STRING(newParent) );
|
|
}
|
|
SetParent( pParent, iAttachment );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Move our points from parent to worldspace
|
|
// Input : *pParent - Parent to use as reference
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::TransformStepData_ParentToWorld( CBaseEntity *pParent )
|
|
{
|
|
// Fix up our step simulation points to be in the proper local space
|
|
StepSimulationData *step = (StepSimulationData *) GetDataObject( STEPSIMULATION );
|
|
if ( step != NULL )
|
|
{
|
|
// Convert our positions
|
|
UTIL_ParentToWorldSpace( pParent, step->m_Previous2.vecOrigin, step->m_Previous2.qRotation );
|
|
UTIL_ParentToWorldSpace( pParent, step->m_Previous.vecOrigin, step->m_Previous.qRotation );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Move step data between two parent-spaces
|
|
// Input : *pOldParent - parent we were attached to
|
|
// *pNewParent - parent we're now attached to
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::TransformStepData_ParentToParent( CBaseEntity *pOldParent, CBaseEntity *pNewParent )
|
|
{
|
|
// Fix up our step simulation points to be in the proper local space
|
|
StepSimulationData *step = (StepSimulationData *) GetDataObject( STEPSIMULATION );
|
|
if ( step != NULL )
|
|
{
|
|
// Convert our positions
|
|
UTIL_ParentToWorldSpace( pOldParent, step->m_Previous2.vecOrigin, step->m_Previous2.qRotation );
|
|
UTIL_WorldToParentSpace( pNewParent, step->m_Previous2.vecOrigin, step->m_Previous2.qRotation );
|
|
|
|
UTIL_ParentToWorldSpace( pOldParent, step->m_Previous.vecOrigin, step->m_Previous.qRotation );
|
|
UTIL_WorldToParentSpace( pNewParent, step->m_Previous.vecOrigin, step->m_Previous.qRotation );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: After parenting to an object, we need to also correctly translate our
|
|
// step stimulation positions and angles into that parent space. Otherwise
|
|
// we end up splining between two different world spaces.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::TransformStepData_WorldToParent( CBaseEntity *pParent )
|
|
{
|
|
// Fix up our step simulation points to be in the proper local space
|
|
StepSimulationData *step = (StepSimulationData *) GetDataObject( STEPSIMULATION );
|
|
if ( step != NULL )
|
|
{
|
|
// Convert our positions
|
|
UTIL_WorldToParentSpace( pParent, step->m_Previous2.vecOrigin, step->m_Previous2.qRotation );
|
|
UTIL_WorldToParentSpace( pParent, step->m_Previous.vecOrigin, step->m_Previous.qRotation );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets the movement parent of this entity. This entity will be moved
|
|
// to a local coordinate calculated from its current absolute offset
|
|
// from the parent entity and will then follow the parent entity.
|
|
// Input : pParentEntity - This entity's new parent in the movement hierarchy.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SetParent( CBaseEntity *pParentEntity, int iAttachment )
|
|
{
|
|
// If they didn't specify an attachment, use our current
|
|
if ( iAttachment == -1 )
|
|
{
|
|
iAttachment = m_iParentAttachment;
|
|
}
|
|
|
|
bool bWasNotParented = ( GetParent() == NULL );
|
|
CBaseEntity *pOldParent = m_pParent;
|
|
|
|
// notify the old parent of the loss
|
|
UnlinkFromParent( this );
|
|
|
|
// set the new name
|
|
m_pParent = pParentEntity;
|
|
|
|
if ( m_pParent == this )
|
|
{
|
|
// should never set parent to 'this' - makes no sense
|
|
Assert(0);
|
|
m_pParent = NULL;
|
|
}
|
|
|
|
if ( m_pParent == NULL )
|
|
{
|
|
m_iParent = NULL_STRING;
|
|
|
|
// Transform step data from parent to worldspace
|
|
TransformStepData_ParentToWorld( pOldParent );
|
|
UpdatePhysicsShadowToCurrentPosition(0);
|
|
return;
|
|
}
|
|
|
|
m_iParent = m_pParent->m_iName;
|
|
|
|
RemoveSolidFlags( FSOLID_ROOT_PARENT_ALIGNED );
|
|
if ( pParentEntity )
|
|
{
|
|
if ( const_cast<CBaseEntity *>(pParentEntity)->GetRootMoveParent()->GetSolid() == SOLID_BSP )
|
|
{
|
|
AddSolidFlags( FSOLID_ROOT_PARENT_ALIGNED );
|
|
}
|
|
else
|
|
{
|
|
if ( GetSolid() == SOLID_BSP )
|
|
{
|
|
// Must be SOLID_VPHYSICS because parent might rotate
|
|
SetSolid( SOLID_VPHYSICS );
|
|
}
|
|
}
|
|
}
|
|
// set the move parent if we have one
|
|
if ( edict() )
|
|
{
|
|
// add ourselves to the list
|
|
LinkChild( m_pParent, this );
|
|
|
|
m_iParentAttachment = (char)iAttachment;
|
|
|
|
EntityMatrix matrix, childMatrix;
|
|
matrix.InitFromEntity( const_cast<CBaseEntity *>(pParentEntity), m_iParentAttachment ); // parent->world
|
|
childMatrix.InitFromEntityLocal( this ); // child->world
|
|
Vector localOrigin = matrix.WorldToLocal( GetLocalOrigin() );
|
|
|
|
// I have the axes of local space in world space. (childMatrix)
|
|
// I want to compute those world space axes in the parent's local space
|
|
// and set that transform (as angles) on the child's object so the net
|
|
// result is that the child is now in parent space, but still oriented the same way
|
|
VMatrix tmp = matrix.Transpose(); // world->parent
|
|
tmp.MatrixMul( childMatrix, matrix ); // child->parent
|
|
QAngle angles;
|
|
MatrixToAngles( matrix, angles );
|
|
SetLocalAngles( angles );
|
|
UTIL_SetOrigin( this, localOrigin );
|
|
|
|
// Move our step data into the correct space
|
|
if ( bWasNotParented )
|
|
{
|
|
// Transform step data from world to parent-space
|
|
TransformStepData_WorldToParent( this );
|
|
}
|
|
else
|
|
{
|
|
// Transform step data between parent-spaces
|
|
TransformStepData_ParentToParent( pOldParent, this );
|
|
}
|
|
}
|
|
if ( VPhysicsGetObject() )
|
|
{
|
|
if ( VPhysicsGetObject()->IsStatic())
|
|
{
|
|
if ( VPhysicsGetObject()->IsAttachedToConstraint(false) )
|
|
{
|
|
Warning("SetParent on static object, all constraints attached to %s (%s)will now be broken!\n", GetDebugName(), GetClassname() );
|
|
}
|
|
VPhysicsDestroyObject();
|
|
VPhysicsInitShadow(false, false);
|
|
}
|
|
else
|
|
{
|
|
UpdatePhysicsShadowToCurrentPosition(0);
|
|
}
|
|
}
|
|
CollisionRulesChanged();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::ValidateEntityConnections()
|
|
{
|
|
if ( m_target == NULL_STRING )
|
|
return;
|
|
|
|
if ( ClassMatches( "scripted_*" ) ||
|
|
ClassMatches( "trigger_relay" ) ||
|
|
ClassMatches( "trigger_auto" ) ||
|
|
ClassMatches( "path_*" ) ||
|
|
ClassMatches( "monster_*" ) ||
|
|
ClassMatches( "trigger_teleport" ) ||
|
|
ClassMatches( "func_train" ) ||
|
|
ClassMatches( "func_tracktrain" ) ||
|
|
ClassMatches( "func_plat*" ) ||
|
|
ClassMatches( "npc_*" ) ||
|
|
ClassMatches( "info_big*" ) ||
|
|
ClassMatches( "env_texturetoggle" ) ||
|
|
ClassMatches( "env_render" ) ||
|
|
ClassMatches( "func_areaportalwindow") ||
|
|
ClassMatches( "point_view*") ||
|
|
ClassMatches( "func_traincontrols" ) ||
|
|
ClassMatches( "multisource" ) ||
|
|
ClassMatches( "xen_plant*" ) )
|
|
return;
|
|
|
|
datamap_t *dmap = GetDataDescMap();
|
|
while ( dmap )
|
|
{
|
|
int fields = dmap->dataNumFields;
|
|
for ( int i = 0; i < fields; i++ )
|
|
{
|
|
typedescription_t *dataDesc = &dmap->dataDesc[i];
|
|
if ( ( dataDesc->fieldType == FIELD_CUSTOM ) && ( dataDesc->flags & FTYPEDESC_OUTPUT ) )
|
|
{
|
|
CBaseEntityOutput *pOutput = (CBaseEntityOutput *)((intp)this + (int)dataDesc->fieldOffset);
|
|
if ( pOutput->NumberOfElements() )
|
|
return;
|
|
}
|
|
}
|
|
|
|
dmap = dmap->baseMap;
|
|
}
|
|
|
|
Vector vecLoc = WorldSpaceCenter();
|
|
Warning("---------------------------------\n");
|
|
Warning( "Entity %s - (%s) has a target and NO OUTPUTS\n", GetDebugName(), GetClassname() );
|
|
Warning( "Location %f %f %f\n", vecLoc.x, vecLoc.y, vecLoc.z );
|
|
Warning("---------------------------------\n");
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::FireNamedOutput( const char *pszOutput, variant_t variant, CBaseEntity *pActivator, CBaseEntity *pCaller, float flDelay )
|
|
{
|
|
if ( pszOutput == NULL )
|
|
return;
|
|
|
|
CBaseEntityOutput *pOutput = FindNamedOutput( pszOutput );
|
|
if ( pOutput )
|
|
{
|
|
pOutput->FireOutput( variant, pActivator, pCaller, flDelay );
|
|
return;
|
|
}
|
|
}
|
|
|
|
CBaseEntityOutput *CBaseEntity::FindNamedOutput( const char *pszOutput )
|
|
{
|
|
if ( pszOutput == NULL )
|
|
return NULL;
|
|
|
|
datamap_t *dmap = GetDataDescMap();
|
|
while ( dmap )
|
|
{
|
|
int fields = dmap->dataNumFields;
|
|
for ( int i = 0; i < fields; i++ )
|
|
{
|
|
typedescription_t *dataDesc = &dmap->dataDesc[i];
|
|
if ( ( dataDesc->fieldType == FIELD_CUSTOM ) && ( dataDesc->flags & FTYPEDESC_OUTPUT ) )
|
|
{
|
|
CBaseEntityOutput *pOutput = ( CBaseEntityOutput * )( ( intp )this + ( int )dataDesc->fieldOffset );
|
|
if ( !Q_stricmp( dataDesc->externalName, pszOutput ) )
|
|
{
|
|
return pOutput;
|
|
}
|
|
}
|
|
}
|
|
dmap = dmap->baseMap;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void CBaseEntity::Activate( void )
|
|
{
|
|
#ifdef DEBUG
|
|
extern bool g_bCheckForChainedActivate;
|
|
extern bool g_bReceivedChainedActivate;
|
|
|
|
if ( g_bCheckForChainedActivate && g_bReceivedChainedActivate )
|
|
{
|
|
AssertMsg2( false, "Multiple calls to base class Activate() by %s (a %s)!\n", GetDebugName(), GetClassname() );
|
|
}
|
|
g_bReceivedChainedActivate = true;
|
|
#endif
|
|
|
|
// NOTE: This forces a team change so that stuff in the level
|
|
// that starts out on a team correctly changes team
|
|
if (m_iInitialTeamNum)
|
|
{
|
|
ChangeTeam( m_iInitialTeamNum );
|
|
}
|
|
|
|
// Get a handle to my damage filter entity if there is one.
|
|
if ( m_iszDamageFilterName != NULL_STRING )
|
|
{
|
|
m_hDamageFilter = gEntList.FindEntityByName( NULL, m_iszDamageFilterName );
|
|
}
|
|
|
|
// Add any non-null context strings to our context vector
|
|
if ( m_iszResponseContext != NULL_STRING )
|
|
{
|
|
AddContext( m_iszResponseContext.ToCStr() );
|
|
}
|
|
|
|
#if defined ( PORTAL2 )
|
|
UpdateObjectCapsCache();
|
|
#endif
|
|
}
|
|
|
|
//////////////////////////// old CBaseEntity stuff ///////////////////////////////////
|
|
|
|
|
|
// give health.
|
|
// Returns the amount of health actually taken.
|
|
int CBaseEntity::TakeHealth( float flHealth, int bitsDamageType )
|
|
{
|
|
if ( !edict() || m_takedamage < DAMAGE_YES )
|
|
return 0;
|
|
|
|
int iMax = GetMaxHealth();
|
|
|
|
// heal
|
|
if ( m_iHealth >= iMax )
|
|
return 0;
|
|
|
|
const int oldHealth = m_iHealth;
|
|
|
|
m_iHealth += flHealth;
|
|
|
|
if (m_iHealth > iMax)
|
|
m_iHealth = iMax;
|
|
|
|
return m_iHealth - oldHealth;
|
|
}
|
|
|
|
// inflict damage on this entity. bitsDamageType indicates type of damage inflicted, ie: DMG_CRUSH
|
|
|
|
int CBaseEntity::OnTakeDamage( const CTakeDamageInfo &info )
|
|
{
|
|
Vector vecTemp;
|
|
|
|
if ( !edict() || !m_takedamage )
|
|
return 0;
|
|
|
|
if ( info.GetInflictor() )
|
|
{
|
|
vecTemp = info.GetInflictor()->WorldSpaceCenter() - ( WorldSpaceCenter() );
|
|
}
|
|
else
|
|
{
|
|
vecTemp.Init( 1, 0, 0 );
|
|
}
|
|
|
|
// this global is still used for glass and other non-NPC killables, along with decals.
|
|
g_vecAttackDir = vecTemp;
|
|
VectorNormalize(g_vecAttackDir);
|
|
|
|
// save damage based on the target's armor level
|
|
|
|
// figure momentum add (don't let hurt brushes or other triggers move player)
|
|
|
|
// physics objects have their own calcs for this: (don't let fire move things around!)
|
|
if ( !IsEFlagSet( EFL_NO_DAMAGE_FORCES ) )
|
|
{
|
|
if ( ( GetMoveType() == MOVETYPE_VPHYSICS ) )
|
|
{
|
|
VPhysicsTakeDamage( info );
|
|
}
|
|
else
|
|
{
|
|
if ( info.GetInflictor() && (GetMoveType() == MOVETYPE_WALK || GetMoveType() == MOVETYPE_STEP) &&
|
|
!info.GetAttacker()->IsSolidFlagSet(FSOLID_TRIGGER) )
|
|
{
|
|
Vector vecDir, vecInflictorCentroid;
|
|
vecDir = WorldSpaceCenter( );
|
|
vecInflictorCentroid = info.GetInflictor()->WorldSpaceCenter( );
|
|
vecDir -= vecInflictorCentroid;
|
|
VectorNormalize( vecDir );
|
|
|
|
float flForce = info.GetDamage() * ((32 * 32 * 72.0) / (WorldAlignSize().x * WorldAlignSize().y * WorldAlignSize().z)) * 5;
|
|
|
|
if (flForce > 1000.0)
|
|
flForce = 1000.0;
|
|
ApplyAbsVelocityImpulse( vecDir * flForce );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( m_takedamage != DAMAGE_EVENTS_ONLY )
|
|
{
|
|
// do the damage
|
|
m_iHealth -= info.GetDamage();
|
|
if (m_iHealth <= 0)
|
|
{
|
|
Event_Killed( info );
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Scale damage done and call OnTakeDamage
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::TakeDamage( const CTakeDamageInfo &inputInfo )
|
|
{
|
|
if ( !g_pGameRules )
|
|
return;
|
|
|
|
bool bHasPhysicsForceDamage = !g_pGameRules->Damage_NoPhysicsForce( inputInfo.GetDamageType() );
|
|
if ( bHasPhysicsForceDamage && inputInfo.GetDamageType() != DMG_GENERIC )
|
|
{
|
|
// If you hit this assert, you've called TakeDamage with a damage type that requires a physics damage
|
|
// force & position without specifying one or both of them. Decide whether your damage that's causing
|
|
// this is something you believe should impart physics force on the receiver. If it is, you need to
|
|
// setup the damage force & position inside the CTakeDamageInfo (Utility functions for this are in
|
|
// takedamageinfo.cpp. If you think the damage shouldn't cause force (unlikely!) then you can set the
|
|
// damage type to DMG_GENERIC, or | DMG_CRUSH if you need to preserve the damage type for purposes of HUD display.
|
|
|
|
if ( inputInfo.GetDamageForce() == vec3_origin || inputInfo.GetDamagePosition() == vec3_origin )
|
|
{
|
|
static int warningCount = 0;
|
|
if ( ++warningCount < 10 )
|
|
{
|
|
if ( inputInfo.GetDamageForce() == vec3_origin )
|
|
{
|
|
DevWarning( "CBaseEntity::TakeDamage: with inputInfo.GetDamageForce() == vec3_origin\n" );
|
|
}
|
|
if ( inputInfo.GetDamagePosition() == vec3_origin )
|
|
{
|
|
DevWarning( "CBaseEntity::TakeDamage: with inputInfo.GetDamagePosition() == vec3_origin\n" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make sure our damage filter allows the damage.
|
|
if ( !PassesDamageFilter( inputInfo ))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( !g_pGameRules->AllowDamage(this, inputInfo) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( PhysIsInCallback() )
|
|
{
|
|
PhysCallbackDamage( this, inputInfo );
|
|
}
|
|
else
|
|
{
|
|
CTakeDamageInfo info = inputInfo;
|
|
|
|
// Scale the damage by the attacker's modifier.
|
|
if ( info.GetAttacker() )
|
|
{
|
|
info.ScaleDamage( info.GetAttacker()->GetAttackDamageScale( this ) );
|
|
}
|
|
|
|
// Scale the damage by my own modifiers
|
|
info.ScaleDamage( GetReceivedDamageScale( info.GetAttacker() ) );
|
|
|
|
//Msg("%s took %.2f Damage, at %.2f\n", GetClassname(), info.GetDamage(), gpGlobals->curtime );
|
|
|
|
OnTakeDamage( info );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns a value that scales all damage done by this entity.
|
|
//-----------------------------------------------------------------------------
|
|
float CBaseEntity::GetAttackDamageScale( CBaseEntity *pVictim )
|
|
{
|
|
float flScale = 1;
|
|
#if ENABLE_DAMAGE_MODIFIERS
|
|
FOR_EACH_LL( m_DamageModifiers, i )
|
|
{
|
|
if ( !m_DamageModifiers[i]->IsDamageDoneToMe() )
|
|
{
|
|
flScale *= m_DamageModifiers[i]->GetModifier();
|
|
}
|
|
}
|
|
#endif
|
|
return flScale;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns a value that scales all damage done to this entity
|
|
//-----------------------------------------------------------------------------
|
|
float CBaseEntity::GetReceivedDamageScale( CBaseEntity *pAttacker )
|
|
{
|
|
float flScale = 1;
|
|
#if ENABLE_DAMAGE_MODIFIERS
|
|
FOR_EACH_LL( m_DamageModifiers, i )
|
|
{
|
|
if ( m_DamageModifiers[i]->IsDamageDoneToMe() )
|
|
{
|
|
flScale *= m_DamageModifiers[i]->GetModifier();
|
|
}
|
|
}
|
|
#endif
|
|
return flScale;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Applies forces to our physics object in response to damage.
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::VPhysicsTakeDamage( const CTakeDamageInfo &info )
|
|
{
|
|
// don't let physics impacts or fire cause objects to move (again)
|
|
bool bNoPhysicsForceDamage = g_pGameRules->Damage_NoPhysicsForce( info.GetDamageType() );
|
|
if ( bNoPhysicsForceDamage || info.GetDamageType() == DMG_GENERIC )
|
|
return 1;
|
|
|
|
// Bash/Shooting wants to set 0 force to not shove heavy physics props at all
|
|
if ( (info.GetDamageType() & (DMG_BULLET | DMG_CLUB)) && info.GetDamageForce().IsZero() )
|
|
return 1;
|
|
|
|
Assert(VPhysicsGetObject() != NULL);
|
|
if ( VPhysicsGetObject() )
|
|
{
|
|
Vector force = info.GetDamageForce();
|
|
Vector offset = info.GetDamagePosition();
|
|
|
|
// If you hit this assert, you've called TakeDamage with a damage type that requires a physics damage
|
|
// force & position without specifying one or both of them. Decide whether your damage that's causing
|
|
// this is something you believe should impart physics force on the receiver. If it is, you need to
|
|
// setup the damage force & position inside the CTakeDamageInfo (Utility functions for this are in
|
|
// takedamageinfo.cpp. If you think the damage shouldn't cause force (unlikely!) then you can set the
|
|
// damage type to DMG_GENERIC, or | DMG_CRUSH if you need to preserve the damage type for purposes of HUD display.
|
|
Assert( force != vec3_origin && offset != vec3_origin );
|
|
|
|
unsigned short gameFlags = VPhysicsGetObject()->GetGameFlags();
|
|
if ( gameFlags & FVPHYSICS_PLAYER_HELD )
|
|
{
|
|
// if the player is holding the object, use it's real mass (player holding reduced the mass)
|
|
CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
|
|
if ( pPlayer )
|
|
{
|
|
float mass = pPlayer->GetHeldObjectMass( VPhysicsGetObject() );
|
|
if ( mass != 0.0f )
|
|
{
|
|
float ratio = VPhysicsGetObject()->GetMass() / mass;
|
|
force *= ratio;
|
|
}
|
|
}
|
|
}
|
|
else if ( (gameFlags & FVPHYSICS_PART_OF_RAGDOLL) && (gameFlags & FVPHYSICS_CONSTRAINT_STATIC) )
|
|
{
|
|
IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
|
|
int count = VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
if ( !(pList[i]->GetGameFlags() & FVPHYSICS_CONSTRAINT_STATIC) )
|
|
{
|
|
pList[i]->ApplyForceOffset( force, offset );
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
}
|
|
VPhysicsGetObject()->ApplyForceOffset( force, offset );
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
// Character killed (only fired once)
|
|
void CBaseEntity::Event_Killed( const CTakeDamageInfo &info )
|
|
{
|
|
if( info.GetAttacker() )
|
|
{
|
|
info.GetAttacker()->Event_KilledOther(this, info);
|
|
}
|
|
|
|
m_takedamage = DAMAGE_NO;
|
|
m_lifeState = LIFE_DEAD;
|
|
UTIL_Remove( this );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: helper method to send a game event when this entity is killed. Note:
|
|
// gets called specifically for particular entities (mostly NPC), this
|
|
// does not get called for every entity
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SendOnKilledGameEvent( const CTakeDamageInfo &info )
|
|
{
|
|
IGameEvent *event = gameeventmanager->CreateEvent( "entity_killed" );
|
|
if ( event )
|
|
{
|
|
event->SetInt( "entindex_killed", entindex() );
|
|
if ( info.GetAttacker())
|
|
{
|
|
event->SetInt( "entindex_attacker", info.GetAttacker()->entindex() );
|
|
}
|
|
if ( info.GetInflictor())
|
|
{
|
|
event->SetInt( "entindex_inflictor", info.GetInflictor()->entindex() );
|
|
}
|
|
event->SetInt( "damagebits", info.GetDamageType() );
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
}
|
|
|
|
|
|
bool CBaseEntity::HasTarget( string_t targetname )
|
|
{
|
|
if( targetname != NULL_STRING && m_target != NULL_STRING )
|
|
return FStrEq(STRING(targetname), STRING(m_target) );
|
|
else
|
|
return false;
|
|
}
|
|
|
|
|
|
CBaseEntity *CBaseEntity::GetNextTarget( void )
|
|
{
|
|
if ( !m_target )
|
|
return NULL;
|
|
return gEntList.FindEntityByName( NULL, m_target );
|
|
}
|
|
|
|
class CThinkContextsSaveDataOps : public CDefSaveRestoreOps
|
|
{
|
|
virtual void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave )
|
|
{
|
|
AssertMsg( fieldInfo.pTypeDesc->fieldSize == 1, "CThinkContextsSaveDataOps does not support arrays");
|
|
|
|
// Write out the vector
|
|
CUtlVector< thinkfunc_t > *pUtlVector = (CUtlVector< thinkfunc_t > *)fieldInfo.pField;
|
|
SaveUtlVector( pSave, pUtlVector, FIELD_EMBEDDED );
|
|
|
|
// Get our owner
|
|
CBaseEntity *pOwner = (CBaseEntity*)fieldInfo.pOwner;
|
|
|
|
pSave->StartBlock();
|
|
// Now write out all the functions
|
|
for ( int i = 0; i < pUtlVector->Count(); i++ )
|
|
{
|
|
inputfunc_t *ppV = (inputfunc_t *)&((*pUtlVector)[i].m_pfnThink);
|
|
bool bHasFunc = (*ppV != NULL);
|
|
pSave->WriteBool( &bHasFunc, 1 );
|
|
if ( bHasFunc )
|
|
{
|
|
pSave->WriteFunction( pOwner->GetDataDescMap(), "m_pfnThink", ppV, 1 );
|
|
}
|
|
}
|
|
pSave->EndBlock();
|
|
}
|
|
|
|
virtual void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore )
|
|
{
|
|
AssertMsg( fieldInfo.pTypeDesc->fieldSize == 1, "CThinkContextsSaveDataOps does not support arrays");
|
|
|
|
// Read in the vector
|
|
CUtlVector< thinkfunc_t > *pUtlVector = (CUtlVector< thinkfunc_t > *)fieldInfo.pField;
|
|
RestoreUtlVector( pRestore, pUtlVector, FIELD_EMBEDDED );
|
|
|
|
// Get our owner
|
|
CBaseEntity *pOwner = (CBaseEntity*)fieldInfo.pOwner;
|
|
|
|
pRestore->StartBlock();
|
|
// Now read in all the functions
|
|
for ( int i = 0; i < pUtlVector->Count(); i++ )
|
|
{
|
|
bool bHasFunc;
|
|
pRestore->ReadBool( &bHasFunc, 1 );
|
|
inputfunc_t *ppV = (inputfunc_t *)&((*pUtlVector)[i].m_pfnThink);
|
|
if ( bHasFunc )
|
|
{
|
|
SaveRestoreRecordHeader_t header;
|
|
pRestore->ReadHeader( &header );
|
|
pRestore->ReadFunction( pOwner->GetDataDescMap(), ppV, 1, header.size );
|
|
}
|
|
else
|
|
{
|
|
*ppV = NULL;
|
|
}
|
|
}
|
|
pRestore->EndBlock();
|
|
}
|
|
|
|
virtual bool IsEmpty( const SaveRestoreFieldInfo_t &fieldInfo )
|
|
{
|
|
CUtlVector< thinkfunc_t > *pUtlVector = (CUtlVector< thinkfunc_t > *)fieldInfo.pField;
|
|
return ( pUtlVector->Count() == 0 );
|
|
}
|
|
|
|
virtual void MakeEmpty( const SaveRestoreFieldInfo_t &fieldInfo )
|
|
{
|
|
BASEPTR pFunc = *((BASEPTR*)fieldInfo.pField);
|
|
pFunc = NULL;
|
|
}
|
|
};
|
|
CThinkContextsSaveDataOps g_ThinkContextsSaveDataOps;
|
|
ISaveRestoreOps *thinkcontextFuncs = &g_ThinkContextsSaveDataOps;
|
|
|
|
BEGIN_SIMPLE_DATADESC( thinkfunc_t )
|
|
|
|
DEFINE_FIELD( m_iszContext, FIELD_STRING ),
|
|
// DEFINE_FIELD( m_pfnThink, FIELD_FUNCTION ), // Manually written
|
|
DEFINE_FIELD( m_nNextThinkTick, FIELD_TICK ),
|
|
DEFINE_FIELD( m_nLastThinkTick, FIELD_TICK ),
|
|
|
|
END_DATADESC()
|
|
|
|
BEGIN_SIMPLE_DATADESC( ResponseContext_t )
|
|
|
|
DEFINE_FIELD( m_iszName, FIELD_STRING ),
|
|
DEFINE_FIELD( m_iszValue, FIELD_STRING ),
|
|
DEFINE_FIELD( m_fExpirationTime, FIELD_TIME ),
|
|
|
|
END_DATADESC()
|
|
|
|
BEGIN_DATADESC_NO_BASE( CBaseEntity )
|
|
|
|
DEFINE_KEYFIELD( m_iClassname, FIELD_STRING, "classname" ),
|
|
DEFINE_GLOBAL_KEYFIELD( m_iGlobalname, FIELD_STRING, "globalname" ),
|
|
DEFINE_KEYFIELD( m_iParent, FIELD_STRING, "parentname" ),
|
|
|
|
DEFINE_KEYFIELD( m_nMinCPULevel, FIELD_CHARACTER, "mincpulevel" ),
|
|
DEFINE_KEYFIELD( m_nMaxCPULevel, FIELD_CHARACTER, "maxcpulevel" ),
|
|
DEFINE_KEYFIELD( m_nMinGPULevel, FIELD_CHARACTER, "mingpulevel" ),
|
|
DEFINE_KEYFIELD( m_nMaxGPULevel, FIELD_CHARACTER, "maxgpulevel" ),
|
|
|
|
DEFINE_KEYFIELD( m_iHammerID, FIELD_INTEGER, "hammerid" ), // save ID numbers so that entities can be tracked between save/restore and vmf
|
|
|
|
DEFINE_KEYFIELD( m_flSpeed, FIELD_FLOAT, "speed" ),
|
|
DEFINE_KEYFIELD( m_nRenderFX, FIELD_CHARACTER, "renderfx" ),
|
|
DEFINE_KEYFIELD( m_nRenderMode, FIELD_CHARACTER, "rendermode" ),
|
|
|
|
// Consider moving to CBaseAnimating?
|
|
DEFINE_FIELD( m_flPrevAnimTime, FIELD_TIME ),
|
|
DEFINE_FIELD( m_flAnimTime, FIELD_TIME ),
|
|
DEFINE_FIELD( m_flSimulationTime, FIELD_TIME ),
|
|
#if defined(ENABLE_CREATE_TIME)
|
|
DEFINE_FIELD( m_flCreateTime, FIELD_TIME ),
|
|
#endif
|
|
DEFINE_FIELD( m_nLastThinkTick, FIELD_TICK ),
|
|
|
|
DEFINE_FIELD( m_iszScriptId, FIELD_STRING ),
|
|
// m_ScriptScope;
|
|
// m_hScriptInstance;
|
|
|
|
DEFINE_KEYFIELD( m_iszVScripts, FIELD_STRING, "vscripts" ),
|
|
DEFINE_KEYFIELD( m_iszScriptThinkFunction, FIELD_STRING, "thinkfunction" ),
|
|
DEFINE_KEYFIELD( m_nNextThinkTick, FIELD_TICK, "nextthink" ),
|
|
DEFINE_KEYFIELD( m_fEffects, FIELD_INTEGER, "effects" ),
|
|
DEFINE_KEYFIELD( m_clrRender, FIELD_COLOR32, "rendercolor" ),
|
|
DEFINE_GLOBAL_KEYFIELD( m_nModelIndex, FIELD_SHORT, "modelindex" ),
|
|
#if !defined( NO_ENTITY_PREDICTION ) && defined( USE_PREDICTABLEID )
|
|
// DEFINE_FIELD( m_PredictableID, CPredictableId ),
|
|
#endif
|
|
DEFINE_FIELD( touchStamp, FIELD_INTEGER ),
|
|
DEFINE_CUSTOM_FIELD( m_aThinkFunctions, thinkcontextFuncs ),
|
|
// m_iCurrentThinkContext (not saved, debug field only, and think transient to boot)
|
|
|
|
DEFINE_UTLVECTOR(m_ResponseContexts, FIELD_EMBEDDED),
|
|
DEFINE_KEYFIELD( m_iszResponseContext, FIELD_STRING, "ResponseContext" ),
|
|
|
|
DEFINE_FIELD( m_pfnThink, FIELD_FUNCTION ),
|
|
DEFINE_FIELD( m_pfnTouch, FIELD_FUNCTION ),
|
|
DEFINE_FIELD( m_pfnUse, FIELD_FUNCTION ),
|
|
DEFINE_FIELD( m_pfnBlocked, FIELD_FUNCTION ),
|
|
DEFINE_FIELD( m_pfnMoveDone, FIELD_FUNCTION ),
|
|
|
|
DEFINE_FIELD( m_lifeState, FIELD_CHARACTER ),
|
|
DEFINE_FIELD( m_takedamage, FIELD_CHARACTER ),
|
|
DEFINE_KEYFIELD( m_iMaxHealth, FIELD_INTEGER, "max_health" ),
|
|
DEFINE_KEYFIELD( m_iHealth, FIELD_INTEGER, "health" ),
|
|
// DEFINE_FIELD( m_pLink, FIELD_CLASSPTR ),
|
|
DEFINE_KEYFIELD( m_bIsAutoaimTarget, FIELD_BOOLEAN, "is_autoaim_target" ),
|
|
DEFINE_KEYFIELD( m_target, FIELD_STRING, "target" ),
|
|
|
|
DEFINE_KEYFIELD( m_iszDamageFilterName, FIELD_STRING, "damagefilter" ),
|
|
DEFINE_FIELD( m_hDamageFilter, FIELD_EHANDLE ),
|
|
|
|
DEFINE_FIELD( m_debugOverlays, FIELD_INTEGER ),
|
|
|
|
DEFINE_GLOBAL_FIELD( m_pParent, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_iParentAttachment, FIELD_CHARACTER ),
|
|
DEFINE_GLOBAL_FIELD( m_hMoveParent, FIELD_EHANDLE ),
|
|
DEFINE_GLOBAL_FIELD( m_hMoveChild, FIELD_EHANDLE ),
|
|
DEFINE_GLOBAL_FIELD( m_hMovePeer, FIELD_EHANDLE ),
|
|
|
|
DEFINE_FIELD( m_iEFlags, FIELD_INTEGER ),
|
|
|
|
DEFINE_FIELD( m_iName, FIELD_STRING ),
|
|
|
|
#ifdef PORTAL2
|
|
DEFINE_FIELD( m_iSignifierName, FIELD_STRING ),
|
|
#endif // PORTAL2
|
|
|
|
DEFINE_EMBEDDED( m_Collision ),
|
|
DEFINE_EMBEDDED( m_Network ),
|
|
|
|
DEFINE_KEYFIELD( m_MoveType, FIELD_CHARACTER, "MoveType" ),
|
|
DEFINE_FIELD( m_MoveCollide, FIELD_CHARACTER ),
|
|
DEFINE_FIELD( m_hOwnerEntity, FIELD_EHANDLE ),
|
|
DEFINE_KEYFIELD( m_CollisionGroup, FIELD_INTEGER, "CollisionGroup" ),
|
|
DEFINE_PHYSPTR( m_pPhysicsObject),
|
|
DEFINE_FIELD( m_flElasticity, FIELD_FLOAT ),
|
|
DEFINE_KEYFIELD( m_flShadowCastDistance, FIELD_FLOAT, "shadowcastdist" ),
|
|
DEFINE_FIELD( m_flDesiredShadowCastDistance, FIELD_FLOAT ),
|
|
|
|
DEFINE_INPUT( m_iInitialTeamNum, FIELD_INTEGER, "TeamNum" ),
|
|
DEFINE_KEYFIELD( m_iTeamNum, FIELD_INTEGER, "teamnumber" ),
|
|
DEFINE_KEYFIELD( m_iPendingTeamNum, FIELD_INTEGER, "pendingteamnumber" ),
|
|
|
|
// DEFINE_FIELD( m_bSentLastFrame, FIELD_INTEGER ),
|
|
|
|
#if defined ( PORTAL2 )
|
|
DEFINE_FIELD( m_iObjectCapsCache, FIELD_INTEGER ),
|
|
#endif
|
|
|
|
DEFINE_FIELD( m_hGroundEntity, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_flGroundChangeTime, FIELD_TIME ),
|
|
DEFINE_GLOBAL_KEYFIELD( m_ModelName, FIELD_MODELNAME, "model" ),
|
|
|
|
DEFINE_KEYFIELD( m_AIAddOn, FIELD_STRING, "addon" ),
|
|
|
|
DEFINE_KEYFIELD( m_vecBaseVelocity, FIELD_VECTOR, "basevelocity" ),
|
|
DEFINE_FIELD( m_vecAbsVelocity, FIELD_VECTOR ),
|
|
DEFINE_KEYFIELD( m_vecAngVelocity, FIELD_VECTOR, "avelocity" ),
|
|
// DEFINE_FIELD( m_vecAbsAngVelocity, FIELD_VECTOR ),
|
|
DEFINE_ARRAY( m_rgflCoordinateFrame, FIELD_FLOAT, 12 ), // NOTE: MUST BE IN LOCAL SPACE, NOT POSITION_VECTOR!!! (see CBaseEntity::Restore)
|
|
|
|
DEFINE_KEYFIELD( m_nWaterLevel, FIELD_CHARACTER, "waterlevel" ),
|
|
DEFINE_FIELD( m_nWaterType, FIELD_CHARACTER ),
|
|
DEFINE_FIELD( m_pBlocker, FIELD_EHANDLE ),
|
|
|
|
DEFINE_KEYFIELD( m_flGravity, FIELD_FLOAT, "gravity" ),
|
|
DEFINE_KEYFIELD( m_flFriction, FIELD_FLOAT, "friction" ),
|
|
|
|
// Local time is local to each object. It doesn't need to be re-based if the clock
|
|
// changes. Therefore it is saved as a FIELD_FLOAT, not a FIELD_TIME
|
|
DEFINE_KEYFIELD( m_flLocalTime, FIELD_FLOAT, "ltime" ),
|
|
DEFINE_FIELD( m_flVPhysicsUpdateLocalTime, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_flMoveDoneTime, FIELD_FLOAT ),
|
|
|
|
// DEFINE_FIELD( m_nPushEnumCount, FIELD_INTEGER ),
|
|
|
|
DEFINE_FIELD( m_vecAbsOrigin, FIELD_POSITION_VECTOR ),
|
|
DEFINE_KEYFIELD( m_vecVelocity, FIELD_VECTOR, "velocity" ),
|
|
DEFINE_KEYFIELD( m_iTextureFrameIndex, FIELD_CHARACTER, "texframeindex" ),
|
|
DEFINE_FIELD( m_bSimulatedEveryTick, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_bAnimatedEveryTick, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_bAlternateSorting, FIELD_BOOLEAN ),
|
|
//DEFINE_FIELD( m_bSpotted, FIELD_BOOLEAN ),
|
|
DEFINE_KEYFIELD( m_spawnflags, FIELD_INTEGER, "spawnflags" ),
|
|
DEFINE_FIELD( m_nTransmitStateOwnedCounter, FIELD_CHARACTER ),
|
|
DEFINE_FIELD( m_angAbsRotation, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_vecOrigin, FIELD_VECTOR ), // NOTE: MUST BE IN LOCAL SPACE, NOT POSITION_VECTOR!!! (see CBaseEntity::Restore)
|
|
DEFINE_FIELD( m_angRotation, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_bClientSideRagdoll, FIELD_BOOLEAN ),
|
|
|
|
DEFINE_KEYFIELD( m_vecViewOffset, FIELD_VECTOR, "view_ofs" ),
|
|
|
|
DEFINE_FIELD( m_fFlags, FIELD_INTEGER ),
|
|
#if !defined( NO_ENTITY_PREDICTION )
|
|
// DEFINE_FIELD( m_bIsPlayerSimulated, FIELD_INTEGER ),
|
|
// DEFINE_FIELD( m_hPlayerSimulationOwner, FIELD_EHANDLE ),
|
|
#endif
|
|
// DEFINE_FIELD( m_pTimedOverlay, TimedOverlay_t* ),
|
|
DEFINE_FIELD( m_nSimulationTick, FIELD_TICK ),
|
|
// DEFINE_FIELD( m_RefEHandle, CBaseHandle ),
|
|
|
|
// DEFINE_FIELD( m_nWaterTouch, FIELD_INTEGER ),
|
|
// DEFINE_FIELD( m_nSlimeTouch, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_flNavIgnoreUntilTime, FIELD_TIME ),
|
|
|
|
// DEFINE_FIELD( m_bToolRecording, FIELD_BOOLEAN ),
|
|
// DEFINE_FIELD( m_ToolHandle, FIELD_INTEGER ),
|
|
|
|
// NOTE: This is tricky. TeamNum must be saved, but we can't directly
|
|
// read it in, because we can only set it after the team entity has been read in,
|
|
// which may or may not actually occur before the entity is parsed.
|
|
// Therefore, we set the TeamNum from the InitialTeamNum in Activate
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetTeam", InputSetTeam ),
|
|
|
|
DEFINE_INPUT( m_fadeMinDist, FIELD_FLOAT, "fademindist" ),
|
|
DEFINE_INPUT( m_fadeMaxDist, FIELD_FLOAT, "fademaxdist" ),
|
|
DEFINE_KEYFIELD( m_flFadeScale, FIELD_FLOAT, "fadescale" ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "Kill", InputKill ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "KillHierarchy", InputKillHierarchy ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "Use", InputUse ),
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "Alpha", InputAlpha ),
|
|
DEFINE_INPUTFUNC( FIELD_BOOLEAN, "AlternativeSorting", InputAlternativeSorting ),
|
|
DEFINE_INPUTFUNC( FIELD_COLOR32, "Color", InputColor ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetParent", InputSetParent ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetParentAttachment", InputSetParentAttachment ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetParentAttachmentMaintainOffset", InputSetParentAttachmentMaintainOffset ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "ClearParent", InputClearParent ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetLocalOrigin", InputSetLocalOrigin ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetLocalAngles", InputSetLocalAngles ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetDamageFilter", InputSetDamageFilter ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "EnableDamageForces", InputEnableDamageForces ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "DisableDamageForces", InputDisableDamageForces ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "DispatchResponse", InputDispatchResponse ),
|
|
|
|
// Entity I/O methods to alter context
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "AddContext", InputAddContext ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "RemoveContext", InputRemoveContext ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "ClearContext", InputClearContext ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "DisableShadow", InputDisableShadow ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "EnableShadow", InputEnableShadow ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "DisableDraw", InputDisableDraw ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "EnableDraw", InputEnableDraw ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "DisableReceivingFlashlight", InputDisableReceivingFlashlight ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "EnableReceivingFlashlight", InputEnableReceivingFlashlight ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "DisableDrawInFastReflection", InputDisableDrawInFastReflection ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "EnableDrawInFastReflection", InputEnableDrawInFastReflection ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "AddOutput", InputAddOutput ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "FireUser1", InputFireUser1 ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "FireUser2", InputFireUser2 ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "FireUser3", InputFireUser3 ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "FireUser4", InputFireUser4 ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "RunScriptFile", InputRunScriptFile ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "RunScriptCode", InputRunScript ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "CallScriptFunction", InputCallScriptFunction ),
|
|
|
|
#ifdef PORTAL2
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "RemovePaint", InputRemovePaint ),
|
|
#endif
|
|
|
|
DEFINE_OUTPUT( m_OnUser1, "OnUser1" ),
|
|
DEFINE_OUTPUT( m_OnUser2, "OnUser2" ),
|
|
DEFINE_OUTPUT( m_OnUser3, "OnUser3" ),
|
|
DEFINE_OUTPUT( m_OnUser4, "OnUser4" ),
|
|
|
|
// Function Pointers
|
|
DEFINE_FUNCTION( SUB_Remove ),
|
|
DEFINE_FUNCTION( SUB_DoNothing ),
|
|
DEFINE_FUNCTION( SUB_StartFadeOut ),
|
|
DEFINE_FUNCTION( SUB_StartFadeOutInstant ),
|
|
DEFINE_FUNCTION( SUB_FadeOut ),
|
|
DEFINE_FUNCTION( SUB_Vanish ),
|
|
DEFINE_FUNCTION( SUB_CallUseToggle ),
|
|
DEFINE_THINKFUNC( ShadowCastDistThink ),
|
|
#if defined(ENABLE_FRICTION_OVERRIDE)
|
|
DEFINE_THINKFUNC( FrictionRevertThink ),
|
|
#endif
|
|
DEFINE_THINKFUNC( ScriptThink ),
|
|
|
|
DEFINE_FIELD( m_hEffectEntity, FIELD_EHANDLE ),
|
|
|
|
//DEFINE_FIELD( m_DamageModifiers, FIELD_?? ), // can't save?
|
|
// DEFINE_FIELD( m_fDataObjectTypes, FIELD_INTEGER ),
|
|
|
|
DEFINE_KEYFIELD( m_bLagCompensate, FIELD_BOOLEAN, "LagCompensate" ),
|
|
DEFINE_FIELD( m_bForcePurgeFixedupStrings, FIELD_BOOLEAN ),
|
|
|
|
DEFINE_FIELD( m_flUseLookAtAngle, FIELD_FLOAT ),
|
|
END_DATADESC()
|
|
|
|
DEFINE_SCRIPT_INSTANCE_HELPER( CBaseEntity, &g_BaseEntityScriptInstanceHelper )
|
|
|
|
BEGIN_ENT_SCRIPTDESC_ROOT( CBaseEntity, "Root class of all server-side entities" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ConnectOutputToScript, "ConnectOutput", "Adds an I/O connection that will call the named function when the specified output fires" )
|
|
DEFINE_SCRIPTFUNC_NAMED( DisconnectOutputFromScript, "DisconnectOutput", "Removes a connected script function from an I/O event." )
|
|
|
|
DEFINE_SCRIPTFUNC( GetHealth, "" )
|
|
DEFINE_SCRIPTFUNC( SetHealth, "" )
|
|
DEFINE_SCRIPTFUNC( GetMaxHealth, "" )
|
|
DEFINE_SCRIPTFUNC( SetMaxHealth, "" )
|
|
|
|
DEFINE_SCRIPTFUNC( SetModel, "" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetModelName, "GetModelName", "Returns the name of the model" )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptEmitSound, "EmitSound", "Plays a sound from this entity." )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptStopSound, "StopSound", "Stops a sound on this entity." )
|
|
DEFINE_SCRIPTFUNC_NAMED( VScriptPrecacheScriptSound, "PrecacheSoundScript", "Precache a sound for later playing." )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSoundDuration, "GetSoundDuration", "Returns float duration of the sound. Takes soundname and optional actormodelname.")
|
|
|
|
|
|
DEFINE_SCRIPTFUNC( GetClassname, "" )
|
|
DEFINE_SCRIPTFUNC_NAMED( GetEntityNameAsCStr, "GetName", "" )
|
|
DEFINE_SCRIPTFUNC( GetPreTemplateName, "Get the entity name stripped of template unique decoration" )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( GetAbsOrigin, "GetOrigin", "" )
|
|
DEFINE_SCRIPTFUNC( SetAbsOrigin, "SetAbsOrigin" )
|
|
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetOrigin, "SetOrigin", "" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetForward, "GetForwardVector", "Get the forward vector of the entity" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetLeft, "GetLeftVector", "Get the left vector of the entity" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetUp, "GetUpVector", "Get the up vector of the entity" )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetForward, "SetForwardVector", "Set the orientation of the entity to have this forward vector" )
|
|
DEFINE_SCRIPTFUNC_NAMED( GetAbsVelocity, "GetVelocity", "" )
|
|
DEFINE_SCRIPTFUNC_NAMED( SetAbsVelocity, "SetVelocity", "" )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetLocalAngularVelocity, "SetAngularVelocity", "Set the local angular velocity - takes float pitch,yaw,roll velocities" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetLocalAngularVelocity, "GetAngularVelocity", "Get the local angular velocity - returns a vector of pitch,yaw,roll" )
|
|
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( WorldSpaceCenter, "GetCenter", "Get vector to center of object - absolute coords")
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptEyePosition, "EyePosition", "Get vector to eye position - absolute coords")
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetAngles, "SetAngles", "Set entity pitch, yaw, roll")
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetAngles, "GetAngles", "Get entity pitch, yaw, roll as a vector")
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetSize, "SetSize", "" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetBoundingMins, "GetBoundingMins", "Get a vector containing min bounds, centered on object")
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetBoundingMaxs, "GetBoundingMaxs", "Get a vector containing max bounds, centered on object")
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptUtilRemove, "Destroy", "" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptSetOwner, "SetOwner", "" )
|
|
DEFINE_SCRIPTFUNC_NAMED( GetTeamNumber, "GetTeam", "" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ChangeTeam, "SetTeam", "" )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetMoveParent, "GetMoveParent", "If in hierarchy, retrieves the entity's parent" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetRootMoveParent, "GetRootMoveParent", "If in hierarchy, walks up the hierarchy to find the root parent" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptFirstMoveChild, "FirstMoveChild", "" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptNextMovePeer, "NextMovePeer", "" )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( KeyValueFromString, "__KeyValueFromString", SCRIPT_HIDE )
|
|
DEFINE_SCRIPTFUNC_NAMED( KeyValueFromFloat, "__KeyValueFromFloat", SCRIPT_HIDE )
|
|
DEFINE_SCRIPTFUNC_NAMED( KeyValueFromInt, "__KeyValueFromInt", SCRIPT_HIDE )
|
|
DEFINE_SCRIPTFUNC_NAMED( KeyValueFromVector, "__KeyValueFromVector", SCRIPT_HIDE )
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptGetModelKeyValues, "GetModelKeyValues", "Get a KeyValue class instance on this entity's model")
|
|
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptPrecacheModel, "PrecacheModel", "" )
|
|
DEFINE_SCRIPTFUNC_NAMED( ScriptPrecacheScriptSound, "PrecacheScriptSound", "" )
|
|
|
|
DEFINE_SCRIPTFUNC( ValidateScriptScope, "Ensure that an entity's script scope has been created" )
|
|
DEFINE_SCRIPTFUNC( GetScriptScope, "Retrieve the script-side data associated with an entity" )
|
|
DEFINE_SCRIPTFUNC( GetScriptId, "Retrieve the unique identifier used to refer to the entity within the scripting system" )
|
|
DEFINE_SCRIPTFUNC_NAMED( GetScriptOwnerEntity, "GetOwner", "Gets this entity's owner" )
|
|
DEFINE_SCRIPTFUNC_NAMED( SetScriptOwnerEntity, "SetOwner", "Sets this entity's owner" )
|
|
DEFINE_SCRIPTFUNC( entindex, "" )
|
|
END_SCRIPTDESC();
|
|
|
|
|
|
// For code error checking
|
|
extern bool g_bReceivedChainedUpdateOnRemove;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Called just prior to object destruction
|
|
// Entities that need to unlink themselves from other entities should do the unlinking
|
|
// here rather than in their destructor. The reason why is that when the global entity list
|
|
// is told to Clear(), it first takes a pass through all active entities and calls UTIL_Remove
|
|
// on each such entity. Then it calls the delete function on each deleted entity in the list.
|
|
// In the old code, the objects were simply destroyed in order and there was no guarantee that the
|
|
// destructor of one object would not try to access another object that might already have been
|
|
// destructed (especially since the entity list order is more or less random!).
|
|
// NOTE: You should never call delete directly on an entity (there's an assert now), see note
|
|
// at CBaseEntity::~CBaseEntity for more information.
|
|
//
|
|
// NOTE: You should chain to BaseClass::UpdateOnRemove after doing your own cleanup code, e.g.:
|
|
//
|
|
// void CDerived::UpdateOnRemove( void )
|
|
// {
|
|
// ... cleanup code
|
|
// ...
|
|
//
|
|
// BaseClass::UpdateOnRemove();
|
|
// }
|
|
//
|
|
// In general, this function updates global tables that need to know about entities being removed
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::UpdateOnRemove( void )
|
|
{
|
|
g_bReceivedChainedUpdateOnRemove = true;
|
|
|
|
// Virtual call to shut down any looping sounds.
|
|
StopLoopingSounds();
|
|
|
|
// Remove from lag compensation 'extra' list
|
|
if ( ShouldLagCompensate() )
|
|
{
|
|
lagcompensation->RemoveAdditionalEntity( this );
|
|
}
|
|
|
|
// Notifies entity listeners, etc
|
|
gEntList.NotifyRemoveEntity( this );
|
|
|
|
if ( edict() )
|
|
{
|
|
AddFlag( FL_KILLME );
|
|
/* <<TODO>>
|
|
if ( GetFlags() & FL_GRAPHED )
|
|
{
|
|
// this entity was a LinkEnt in the world node graph, so we must remove it from
|
|
// the graph since we are removing it from the world.
|
|
for ( int i = 0 ; i < WorldGraph.m_cLinks ; i++ )
|
|
{
|
|
if ( WorldGraph.m_pLinkPool [ i ].m_pLinkEnt == pev )
|
|
{
|
|
// if this link has a link ent which is the same ent that is removing itself, remove it!
|
|
WorldGraph.m_pLinkPool [ i ].m_pLinkEnt = NULL;
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|
|
if ( m_iGlobalname != NULL_STRING )
|
|
{
|
|
// NOTE: During level shutdown the global list will suppress this
|
|
// it assumes your changing levels or the game will end
|
|
// causing the whole list to be flushed
|
|
GlobalEntity_SetState( m_iGlobalname, GLOBAL_DEAD );
|
|
}
|
|
|
|
// Remove the fixed up name from this entity
|
|
// we need to do this now since we will set the name to nothing later
|
|
if( m_bForcePurgeFixedupStrings )
|
|
{
|
|
if( m_iName.Get() != NULL_STRING )
|
|
{
|
|
RemovePooledString( STRING( m_iName.Get() ) );
|
|
}
|
|
if( m_iszScriptId != NULL_STRING )
|
|
{
|
|
RemovePooledString( STRING( m_iszScriptId ) );
|
|
}
|
|
}
|
|
|
|
VPhysicsDestroyObject();
|
|
|
|
// This is only here to allow the MOVETYPE_NONE to be set without the
|
|
// assertion triggering. Why do we bother setting the MOVETYPE to none here?
|
|
RemoveEffects( EF_BONEMERGE );
|
|
SetMoveType(MOVETYPE_NONE);
|
|
|
|
// If we have a parent, unlink from it.
|
|
UnlinkFromParent( this );
|
|
|
|
// Any children still connected are orphans, mark all for delete
|
|
CUtlVector<CBaseEntity *> childrenList;
|
|
GetAllChildren( this, childrenList );
|
|
if ( childrenList.Count() )
|
|
{
|
|
DevMsg( 2, "Warning: Deleting orphaned children of %s\n", GetClassname() );
|
|
for ( int i = childrenList.Count()-1; i >= 0; --i )
|
|
{
|
|
UTIL_Remove( childrenList[i] );
|
|
}
|
|
}
|
|
|
|
SetGroundEntity( NULL );
|
|
|
|
if ( m_hScriptInstance )
|
|
{
|
|
g_pScriptVM->RemoveInstance( m_hScriptInstance );
|
|
m_hScriptInstance = NULL;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// capabilities
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::ObjectCaps( void )
|
|
{
|
|
#if 1
|
|
model_t *pModel = GetModel();
|
|
bool bIsBrush = ( pModel && modelinfo->GetModelType( pModel ) == mod_brush );
|
|
|
|
// We inherit our parent's use capabilities so that we can forward use commands
|
|
// to our parent.
|
|
CBaseEntity *pParent = GetParent();
|
|
if ( pParent )
|
|
{
|
|
int caps = pParent->ObjectCaps();
|
|
|
|
if ( !bIsBrush )
|
|
caps &= ( FCAP_ACROSS_TRANSITION | FCAP_IMPULSE_USE | FCAP_CONTINUOUS_USE | FCAP_ONOFF_USE | FCAP_DIRECTIONAL_USE );
|
|
else
|
|
caps &= ( FCAP_IMPULSE_USE | FCAP_CONTINUOUS_USE | FCAP_ONOFF_USE | FCAP_DIRECTIONAL_USE );
|
|
|
|
if ( pParent->IsPlayer() )
|
|
caps |= FCAP_ACROSS_TRANSITION;
|
|
|
|
return caps;
|
|
}
|
|
else if ( !bIsBrush )
|
|
{
|
|
return FCAP_ACROSS_TRANSITION;
|
|
}
|
|
|
|
return 0;
|
|
#else
|
|
// We inherit our parent's use capabilities so that we can forward use commands
|
|
// to our parent.
|
|
int parentCaps = 0;
|
|
if (GetParent())
|
|
{
|
|
parentCaps = GetParent()->ObjectCaps();
|
|
parentCaps &= ( FCAP_IMPULSE_USE | FCAP_CONTINUOUS_USE | FCAP_ONOFF_USE | FCAP_DIRECTIONAL_USE );
|
|
}
|
|
|
|
model_t *pModel = GetModel();
|
|
if ( pModel && modelinfo->GetModelType( pModel ) == mod_brush )
|
|
return parentCaps;
|
|
|
|
return FCAP_ACROSS_TRANSITION | parentCaps;
|
|
#endif
|
|
}
|
|
|
|
#if defined ( PORTAL2 )
|
|
void CBaseEntity::UpdateObjectCapsCache( void )
|
|
{
|
|
// Send the first six bits of the object caps to the client
|
|
// those should be the +use logic capabilities
|
|
m_iObjectCapsCache = 0x0000003f & ObjectCaps();
|
|
}
|
|
#endif
|
|
|
|
void CBaseEntity::StartTouch( CBaseEntity *pOther )
|
|
{
|
|
// notify parent
|
|
if ( m_pParent != NULL )
|
|
m_pParent->StartTouch( pOther );
|
|
}
|
|
|
|
void CBaseEntity::Touch( CBaseEntity *pOther )
|
|
{
|
|
if ( m_pfnTouch )
|
|
(this->*m_pfnTouch)( pOther );
|
|
|
|
// notify parent of touch
|
|
if ( m_pParent != NULL )
|
|
m_pParent->Touch( pOther );
|
|
}
|
|
|
|
void CBaseEntity::EndTouch( CBaseEntity *pOther )
|
|
{
|
|
// notify parent
|
|
if ( m_pParent != NULL )
|
|
{
|
|
m_pParent->EndTouch( pOther );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Dispatches blocked events to this entity's blocked handler, set via SetBlocked.
|
|
// Input : pOther - The entity that is blocking us.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::Blocked( CBaseEntity *pOther )
|
|
{
|
|
if ( m_pfnBlocked )
|
|
{
|
|
(this->*m_pfnBlocked)( pOther );
|
|
}
|
|
|
|
//
|
|
// Forward the blocked event to our parent, if any.
|
|
//
|
|
if ( m_pParent != NULL )
|
|
{
|
|
m_pParent->Blocked( pOther );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Dispatches use events to this entity's use handler, set via SetUse.
|
|
// Input : pActivator -
|
|
// pCaller -
|
|
// useType -
|
|
// value -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
|
|
{
|
|
if ( m_pfnUse != NULL )
|
|
{
|
|
(this->*m_pfnUse)( pActivator, pCaller, useType, value );
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We don't handle use events. Forward to our parent, if any.
|
|
//
|
|
if ( m_pParent != NULL )
|
|
{
|
|
m_pParent->Use( pActivator, pCaller, useType, value );
|
|
}
|
|
}
|
|
}
|
|
|
|
static CBaseEntity *FindPhysicsBlocker( IPhysicsObject *pPhysics, physicspushlist_t &list, const Vector &pushVel )
|
|
{
|
|
IPhysicsFrictionSnapshot *pSnapshot = pPhysics->CreateFrictionSnapshot();
|
|
CBaseEntity *pBlocker = NULL;
|
|
float maxForce = 0;
|
|
while ( pSnapshot->IsValid() )
|
|
{
|
|
IPhysicsObject *pOther = pSnapshot->GetObject(1);
|
|
CBaseEntity *pOtherEntity = static_cast<CBaseEntity *>(pOther->GetGameData());
|
|
bool inList = false;
|
|
for ( int i = 0; i < list.pushedCount; i++ )
|
|
{
|
|
if ( pOtherEntity == list.pushedEnts[i] )
|
|
{
|
|
inList = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Vector normal;
|
|
pSnapshot->GetSurfaceNormal(normal);
|
|
float dot = DotProduct( pushVel, pSnapshot->GetNormalForce() * normal );
|
|
if ( !pBlocker || (!inList && dot > maxForce) )
|
|
{
|
|
pBlocker = pOtherEntity;
|
|
if ( !inList )
|
|
{
|
|
maxForce = dot;
|
|
}
|
|
}
|
|
|
|
pSnapshot->NextFrictionData();
|
|
}
|
|
pPhysics->DestroyFrictionSnapshot( pSnapshot );
|
|
|
|
return pBlocker;
|
|
}
|
|
|
|
|
|
struct pushblock_t
|
|
{
|
|
physicspushlist_t *pList;
|
|
CBaseEntity *pRootParent;
|
|
CBaseEntity *pBlockedEntity;
|
|
float moveBackFraction;
|
|
float movetime;
|
|
};
|
|
|
|
static void ComputePushStartMatrix( matrix3x4_t &start, CBaseEntity *pEntity, const pushblock_t ¶ms )
|
|
{
|
|
Vector localOrigin;
|
|
QAngle localAngles;
|
|
if ( params.pList )
|
|
{
|
|
localOrigin = params.pList->localOrigin;
|
|
localAngles = params.pList->localAngles;
|
|
}
|
|
else
|
|
{
|
|
localOrigin = params.pRootParent->GetAbsOrigin() - params.pRootParent->GetAbsVelocity() * params.movetime;
|
|
localAngles = params.pRootParent->GetAbsAngles() - params.pRootParent->GetLocalAngularVelocity() * params.movetime;
|
|
}
|
|
matrix3x4_t xform, delta;
|
|
AngleMatrix( localAngles, localOrigin, xform );
|
|
|
|
matrix3x4_t srcInv;
|
|
// xform = src(-1) * dest
|
|
MatrixInvert( params.pRootParent->EntityToWorldTransform(), srcInv );
|
|
ConcatTransforms( xform, srcInv, delta );
|
|
ConcatTransforms( delta, pEntity->EntityToWorldTransform(), start );
|
|
}
|
|
|
|
#define DEBUG_PUSH_MESSAGES 0
|
|
static void CheckPushedEntity( CBaseEntity *pEntity, pushblock_t ¶ms )
|
|
{
|
|
IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject();
|
|
if ( !pPhysics )
|
|
return;
|
|
// somehow we've got a static or motion disabled physics object in hierarchy!
|
|
// This is not allowed! Don't test blocking in that case.
|
|
Assert(pPhysics->IsMoveable());
|
|
if ( !pPhysics->IsMoveable() || !pPhysics->GetShadowController() )
|
|
{
|
|
#if DEBUG_PUSH_MESSAGES
|
|
Msg("Blocking %s, not moveable!\n", pEntity->GetClassname());
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
bool checkrot = true;
|
|
bool checkmove = true;
|
|
Vector origin;
|
|
QAngle angles;
|
|
pPhysics->GetShadowPosition( &origin, &angles );
|
|
float fraction = -1.0f;
|
|
|
|
matrix3x4_t parentDelta;
|
|
if ( pEntity == params.pRootParent )
|
|
{
|
|
if ( pEntity->GetLocalAngularVelocity() == vec3_angle )
|
|
checkrot = false;
|
|
if ( pEntity->GetLocalVelocity() == vec3_origin)
|
|
checkmove = false;
|
|
}
|
|
else
|
|
{
|
|
#if DEBUG_PUSH_MESSAGES
|
|
if ( pPhysics->IsAttachedToConstraint(false))
|
|
{
|
|
Msg("Warning, hierarchical entity is attached to a constraint %s\n", pEntity->GetClassname());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if ( checkmove )
|
|
{
|
|
// project error onto the axis of movement
|
|
Vector dir = pEntity->GetAbsVelocity();
|
|
float speed = VectorNormalize(dir);
|
|
Vector targetPos;
|
|
pPhysics->GetShadowController()->GetTargetPosition( &targetPos, NULL );
|
|
float targetAmount = DotProduct(targetPos, dir);
|
|
float currentAmount = DotProduct(origin, dir);
|
|
float entityAmount = DotProduct(pEntity->GetAbsOrigin(), dir);
|
|
|
|
// if target and entity origin are not in sync, then the position of the entity was updated
|
|
// by something outside of push physics
|
|
if ( (targetAmount - entityAmount) > 1 )
|
|
{
|
|
pEntity->UpdatePhysicsShadowToCurrentPosition(0);
|
|
#if DEBUG_PUSH_MESSAGES
|
|
Warning("Someone slammed the position of a %s\n", pEntity->GetClassname() );
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
float dist = targetAmount - currentAmount;
|
|
if ( dist > 1 )
|
|
{
|
|
#if DEBUG_PUSH_MESSAGES
|
|
const char *pName = pEntity->GetClassname();
|
|
Msg( "%s blocked by %.2f units\n", pName, dist );
|
|
#endif
|
|
float movementAmount = targetAmount - (speed * params.movetime);
|
|
if ( pEntity == params.pRootParent )
|
|
{
|
|
if ( params.pList )
|
|
{
|
|
Vector localVel = pEntity->GetLocalVelocity();
|
|
VectorNormalize(localVel);
|
|
float localTargetAmt = DotProduct(pEntity->GetLocalOrigin(), localVel);
|
|
movementAmount = targetAmount + DotProduct(params.pList->localOrigin, localVel) - localTargetAmt;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
matrix3x4_t start;
|
|
ComputePushStartMatrix( start, pEntity, params );
|
|
Vector startPos;
|
|
MatrixPosition( start, startPos );
|
|
movementAmount = DotProduct(startPos, dir);
|
|
}
|
|
float expectedDist = targetAmount - movementAmount;
|
|
// compute the fraction to move back the AI to match the physics
|
|
if ( expectedDist <= 0 )
|
|
{
|
|
fraction = 1;
|
|
}
|
|
else
|
|
{
|
|
fraction = dist / expectedDist;
|
|
fraction = clamp(fraction, 0, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( checkrot )
|
|
{
|
|
Vector axis;
|
|
float deltaAngle;
|
|
RotationDeltaAxisAngle( angles, pEntity->GetAbsAngles(), axis, deltaAngle );
|
|
if ( fabsf(deltaAngle) > 0.5f )
|
|
{
|
|
Vector targetAxis;
|
|
QAngle targetRot;
|
|
float deltaTargetAngle;
|
|
pPhysics->GetShadowController()->GetTargetPosition( NULL, &targetRot );
|
|
RotationDeltaAxisAngle( angles, targetRot, targetAxis, deltaTargetAngle );
|
|
if ( fabsf(deltaTargetAngle) > 0.01f )
|
|
{
|
|
float expectedDist = deltaAngle;
|
|
#if DEBUG_PUSH_MESSAGES
|
|
const char *pName = pEntity->GetClassname();
|
|
Msg( "%s blocked by %.2f degrees\n", pName, deltaAngle );
|
|
if ( pPhysics->IsAsleep() )
|
|
{
|
|
Msg("Asleep while blocked?\n");
|
|
}
|
|
if ( pPhysics->GetGameFlags() & FVPHYSICS_PENETRATING )
|
|
{
|
|
Msg("Blocking for penetration!\n");
|
|
}
|
|
#endif
|
|
if ( pEntity == params.pRootParent )
|
|
{
|
|
expectedDist = pEntity->GetLocalAngularVelocity().Length() * params.movetime;
|
|
}
|
|
else
|
|
{
|
|
matrix3x4_t start;
|
|
ComputePushStartMatrix( start, pEntity, params );
|
|
Vector startAxis;
|
|
float startAngle;
|
|
Vector startPos;
|
|
QAngle startAngles;
|
|
MatrixAngles( start, startAngles, startPos );
|
|
RotationDeltaAxisAngle( startAngles, pEntity->GetAbsAngles(), startAxis, startAngle );
|
|
expectedDist = startAngle * DotProduct( startAxis, axis );
|
|
}
|
|
|
|
float t = expectedDist != 0.0f ? fabsf(deltaAngle / expectedDist) : 1.0f;
|
|
t = clamp(t,0,1);
|
|
fraction = MAX(fraction, t);
|
|
}
|
|
else
|
|
{
|
|
pEntity->UpdatePhysicsShadowToCurrentPosition(0);
|
|
#if DEBUG_PUSH_MESSAGES
|
|
Warning("Someone slammed the position of a %s\n", pEntity->GetClassname() );
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
if ( fraction >= params.moveBackFraction && sv_phys_props_block_movers.GetBool() )
|
|
{
|
|
params.moveBackFraction = fraction;
|
|
params.pBlockedEntity = pEntity;
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::VPhysicsUpdatePusher( IPhysicsObject *pPhysics )
|
|
{
|
|
float movetime = m_flLocalTime - m_flVPhysicsUpdateLocalTime;
|
|
if (movetime <= 0 )
|
|
return;
|
|
|
|
// only reconcile pushers on the final vphysics tick
|
|
if ( !PhysIsFinalTick() )
|
|
return;
|
|
|
|
Vector origin;
|
|
QAngle angles;
|
|
|
|
// physics updated the shadow, so check to see if I got blocked
|
|
// NOTE: SOLID_BSP cannont compute consistent collisions wrt vphysics, so
|
|
// don't allow vphysics to block. Assume game physics has handled it.
|
|
if ( GetSolid() != SOLID_BSP && pPhysics->GetShadowPosition( &origin, &angles ) )
|
|
{
|
|
CUtlVector<CBaseEntity *> list;
|
|
GetAllInHierarchy( this, list );
|
|
//NDebugOverlay::BoxAngles( origin, CollisionProp()->OBBMins(), CollisionProp()->OBBMaxs(), angles, 255,0,0,0, gpGlobals->frametime);
|
|
|
|
physicspushlist_t *pList = NULL;
|
|
if ( HasDataObjectType(PHYSICSPUSHLIST) )
|
|
{
|
|
pList = (physicspushlist_t *)GetDataObject( PHYSICSPUSHLIST );
|
|
Assert(pList);
|
|
}
|
|
bool checkrot = (GetLocalAngularVelocity() != vec3_angle) ? true : false;
|
|
bool checkmove = (GetLocalVelocity() != vec3_origin) ? true : false;
|
|
|
|
pushblock_t params;
|
|
params.pRootParent = this;
|
|
params.pList = pList;
|
|
params.pBlockedEntity = NULL;
|
|
params.moveBackFraction = 0.0f;
|
|
params.movetime = movetime;
|
|
for ( int i = 0; i < list.Count(); i++ )
|
|
{
|
|
if ( list[i]->IsSolid() )
|
|
{
|
|
CheckPushedEntity( list[i], params );
|
|
}
|
|
}
|
|
|
|
float physLocalTime = m_flLocalTime;
|
|
if ( params.pBlockedEntity )
|
|
{
|
|
float moveback = movetime * params.moveBackFraction;
|
|
if ( moveback > 0 )
|
|
{
|
|
physLocalTime = m_flLocalTime - moveback;
|
|
// add 1% noise for bouncing in collision.
|
|
if ( physLocalTime <= (m_flVPhysicsUpdateLocalTime + movetime * 0.99f) )
|
|
{
|
|
CBaseEntity *pBlocked = NULL;
|
|
IPhysicsObject *pOther;
|
|
if ( params.pBlockedEntity->VPhysicsGetObject()->GetContactPoint( NULL, &pOther ) )
|
|
{
|
|
pBlocked = static_cast<CBaseEntity *>(pOther->GetGameData());
|
|
}
|
|
// UNDONE: Need to traverse hierarchy here? Shouldn't.
|
|
if ( pList )
|
|
{
|
|
SetLocalOrigin( pList->localOrigin );
|
|
SetLocalAngles( pList->localAngles );
|
|
physLocalTime = pList->localMoveTime;
|
|
for ( int i = 0; i < pList->pushedCount; i++ )
|
|
{
|
|
CBaseEntity *pEntity = pList->pushedEnts[i];
|
|
if ( !pEntity )
|
|
continue;
|
|
|
|
pEntity->SetAbsOrigin( pEntity->GetAbsOrigin() - pList->pushVec[i] );
|
|
}
|
|
CBaseEntity *pPhysicsBlocker = FindPhysicsBlocker( VPhysicsGetObject(), *pList, pList->pushVec[0] );
|
|
if ( pPhysicsBlocker )
|
|
{
|
|
pBlocked = pPhysicsBlocker;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Vector origin = GetLocalOrigin();
|
|
QAngle angles = GetLocalAngles();
|
|
|
|
if ( checkmove )
|
|
{
|
|
origin -= GetLocalVelocity() * moveback;
|
|
}
|
|
if ( checkrot )
|
|
{
|
|
// BUGBUG: This is pretty hack-tastic!
|
|
angles -= GetLocalAngularVelocity() * moveback;
|
|
}
|
|
|
|
SetLocalOrigin( origin );
|
|
SetLocalAngles( angles );
|
|
}
|
|
|
|
if ( pBlocked )
|
|
{
|
|
Blocked( pBlocked );
|
|
}
|
|
m_flLocalTime = physLocalTime;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// this data is no longer useful, free the memory
|
|
if ( HasDataObjectType(PHYSICSPUSHLIST) )
|
|
{
|
|
DestroyDataObject( PHYSICSPUSHLIST );
|
|
}
|
|
|
|
m_flVPhysicsUpdateLocalTime = m_flLocalTime;
|
|
if ( m_flMoveDoneTime <= m_flLocalTime && m_flMoveDoneTime > 0 )
|
|
{
|
|
SetMoveDoneTime( -1 );
|
|
MoveDone();
|
|
}
|
|
}
|
|
|
|
|
|
void CBaseEntity::SetMoveDoneTime( float flDelay )
|
|
{
|
|
if (flDelay >= 0)
|
|
{
|
|
m_flMoveDoneTime = GetLocalTime() + flDelay;
|
|
}
|
|
else
|
|
{
|
|
m_flMoveDoneTime = -1;
|
|
}
|
|
CheckHasGamePhysicsSimulation();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Relinks all of a parents children into the collision tree
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::PhysicsRelinkChildren( float dt )
|
|
{
|
|
CBaseEntity *child;
|
|
|
|
// iterate through all children
|
|
for ( child = FirstMoveChild(); child != NULL; child = child->NextMovePeer() )
|
|
{
|
|
if ( child->IsSolid() || child->IsSolidFlagSet(FSOLID_TRIGGER) )
|
|
{
|
|
child->PhysicsTouchTriggers();
|
|
}
|
|
|
|
//
|
|
// Update their physics shadows. We should never have any children of
|
|
// movetype VPHYSICS.
|
|
//
|
|
if ( child->GetMoveType() != MOVETYPE_VPHYSICS )
|
|
{
|
|
child->UpdatePhysicsShadowToCurrentPosition( dt );
|
|
}
|
|
else if ( child->GetOwnerEntity() != this )
|
|
{
|
|
// the only case where this is valid is if this entity is an attached ragdoll.
|
|
// So assert here to catch the non-ragdoll case.
|
|
Assert( 0 );
|
|
}
|
|
|
|
if ( child->FirstMoveChild() )
|
|
{
|
|
child->PhysicsRelinkChildren(dt);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::VPhysicsShadowCollision( int index, gamevcollisionevent_t *pEvent )
|
|
{
|
|
}
|
|
|
|
|
|
|
|
void CBaseEntity::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
|
|
{
|
|
// filter out ragdoll props hitting other parts of itself too often
|
|
// UNDONE: Store a sound time for this entity (not just this pair of objects)
|
|
// and filter repeats on that?
|
|
int otherIndex = !index;
|
|
CBaseEntity *pHitEntity = pEvent->pEntities[otherIndex];
|
|
|
|
// Don't make sounds / effects if neither entity is MOVETYPE_VPHYSICS. The game
|
|
// physics should have done so.
|
|
if ( GetMoveType() != MOVETYPE_VPHYSICS && pHitEntity->GetMoveType() != MOVETYPE_VPHYSICS )
|
|
return;
|
|
|
|
if ( pEvent->deltaCollisionTime < 0.5 && (pHitEntity == this) )
|
|
return;
|
|
|
|
// don't make noise for hidden/invisible/sky materials
|
|
surfacedata_t *phit = physprops->GetSurfaceData( pEvent->surfaceProps[otherIndex] );
|
|
const surfacedata_t *pprops = physprops->GetSurfaceData( pEvent->surfaceProps[index] );
|
|
if ( phit->game.material == 'X' || pprops->game.material == 'X' )
|
|
return;
|
|
|
|
if ( pHitEntity == this )
|
|
{
|
|
PhysCollisionSound( this, pEvent->pObjects[index], CHAN_BODY, pEvent->surfaceProps[index], pEvent->surfaceProps[otherIndex], pEvent->deltaCollisionTime, pEvent->collisionSpeed );
|
|
}
|
|
else
|
|
{
|
|
PhysCollisionSound( this, pEvent->pObjects[index], CHAN_STATIC, pEvent->surfaceProps[index], pEvent->surfaceProps[otherIndex], pEvent->deltaCollisionTime, pEvent->collisionSpeed );
|
|
}
|
|
PhysCollisionScreenShake( pEvent, index );
|
|
|
|
#if HL2_EPISODIC
|
|
// episodic does something different for when advisor shields are struck
|
|
if ( phit->game.material == 'Z' || pprops->game.material == 'Z')
|
|
{
|
|
PhysCollisionWarpEffect( pEvent, phit );
|
|
}
|
|
else
|
|
{
|
|
PhysCollisionDust( pEvent, phit );
|
|
}
|
|
#else
|
|
PhysCollisionDust( pEvent, phit );
|
|
#endif
|
|
}
|
|
|
|
void CBaseEntity::VPhysicsFriction( IPhysicsObject *pObject, float energy, int surfaceProps, int surfacePropsHit )
|
|
{
|
|
PhysFrictionSound( this, pObject, energy, surfaceProps, surfacePropsHit );
|
|
}
|
|
|
|
|
|
// Tells the physics shadow to update it's target to the current position
|
|
void CBaseEntity::UpdatePhysicsShadowToCurrentPosition( float deltaTime )
|
|
{
|
|
if ( GetMoveType() != MOVETYPE_VPHYSICS )
|
|
{
|
|
IPhysicsObject *pPhys = VPhysicsGetObject();
|
|
if ( pPhys )
|
|
{
|
|
pPhys->UpdateShadow( GetAbsOrigin(), GetAbsAngles(), false, deltaTime );
|
|
}
|
|
}
|
|
}
|
|
|
|
int CBaseEntity::VPhysicsGetObjectList( IPhysicsObject **pList, int listMax )
|
|
{
|
|
IPhysicsObject *pPhys = VPhysicsGetObject();
|
|
if ( pPhys )
|
|
{
|
|
// multi-object entities must implement this function
|
|
Assert( !(pPhys->GetGameFlags() & FVPHYSICS_MULTIOBJECT_ENTITY) );
|
|
if ( listMax > 0 )
|
|
{
|
|
pList[0] = pPhys;
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::VPhysicsIsFlesh( void )
|
|
{
|
|
IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
|
|
int count = VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
int material = pList[i]->GetMaterialIndex();
|
|
const surfacedata_t *pSurfaceData = physprops->GetSurfaceData( material );
|
|
// Is flesh ?, don't allow pickup
|
|
if ( pSurfaceData->game.material == CHAR_TEX_ANTLION || pSurfaceData->game.material == CHAR_TEX_FLESH || pSurfaceData->game.material == CHAR_TEX_BLOODYFLESH || pSurfaceData->game.material == CHAR_TEX_ALIENFLESH )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CBaseEntity::Intersects( CBaseEntity *pOther )
|
|
{
|
|
if ( !edict() || !pOther->edict() )
|
|
return false;
|
|
|
|
CCollisionProperty *pMyProp = CollisionProp();
|
|
CCollisionProperty *pOtherProp = pOther->CollisionProp();
|
|
|
|
return IsOBBIntersectingOBB(
|
|
pMyProp->GetCollisionOrigin(), pMyProp->GetCollisionAngles(), pMyProp->OBBMins(), pMyProp->OBBMaxs(),
|
|
pOtherProp->GetCollisionOrigin(), pOtherProp->GetCollisionAngles(), pOtherProp->OBBMins(), pOtherProp->OBBMaxs() );
|
|
}
|
|
|
|
extern ConVar ai_LOS_mode;
|
|
|
|
// With RUNTIME STACK TRANSLATION enabled, you get a few additional cvars which let you debug exactly where
|
|
// FVisible calls are coming from.
|
|
// To use,
|
|
// Uncomment the #define ENABLE_RUNTIME_STACK_TRANSLATION line in stacktools.h
|
|
// vpc +game /allgames /nofpo to disable frame pointer omission in *ALL COMPILED PROJECTS*
|
|
// rebuild everything
|
|
#ifdef ENABLE_STACK_STATS_GATHERING
|
|
//#define FVIS_STACK_TRACKING
|
|
#endif
|
|
|
|
#if defined( FVIS_STACK_TRACKING )
|
|
struct Count_t ///< simple wrapper so I get a default constructor
|
|
{
|
|
DECLARE_CALLSTACKSTATSTRUCT();
|
|
|
|
Count_t() : i(0) {};
|
|
Count_t(int a) : i(a) {};
|
|
|
|
unsigned int i;
|
|
};
|
|
BEGIN_STATSTRUCTDESCRIPTION( Count_t )
|
|
END_STATSTRUCTDESCRIPTION()
|
|
|
|
static CCallStackStatsGatherer<Count_t, 16> s_FVisCallStackInfo;
|
|
|
|
|
|
void CC_FVis_Stack_Reset( const CCommand& args )
|
|
{
|
|
s_FVisCallStackInfo.Reset();
|
|
}
|
|
static ConCommand perf_fvis_stacks_reset("perf_fvis_stacks_reset", CC_FVis_Stack_Reset, "Resets CBaseEntity::FVisible stacktrace counts", FCVAR_CHEAT);
|
|
|
|
void CC_FVis_Stack_Dump( const CCommand& args )
|
|
{
|
|
int count = s_FVisCallStackInfo.NumEntries();
|
|
for (int i = 0 ; i < count ; ++i)
|
|
{
|
|
Msg( "\t%d calls:\n", s_FVisCallStackInfo.GetEntry(i) );
|
|
|
|
// print call stack
|
|
// TranslateStackInfo( const void * const *pCallStack, int iCallStackCount, tchar *szOutput, int iOutBufferSize, const tchar *szEntrySeparator, TranslateStackInfo_StyleFlags_t style = TSISTYLEFLAG_DEFAULT );
|
|
char outBuf[1024];
|
|
TranslateStackInfo( s_FVisCallStackInfo.GetCallStackForIndex(i), s_FVisCallStackInfo.CapturedCallStackLength, outBuf, 1023, "\n", (TranslateStackInfo_StyleFlags_t)(TSISTYLEFLAG_MODULENAME | TSISTYLEFLAG_SYMBOLNAME | TSISTYLEFLAG_SHORTPATH ) );
|
|
Msg( outBuf );
|
|
Msg( "\n\n" );
|
|
}
|
|
}
|
|
static ConCommand perf_fvis_stacks_dump("perf_fvis_stacks_dump", CC_FVis_Stack_Dump, "Dump info on all CBaseEntity::FVisible stack traces to console", FCVAR_CHEAT);
|
|
|
|
ConVar perf_fvis_stacks_trace( "perf_fvis_stacks_trace", "0", FCVAR_CHEAT, "Enable to get detailed perf on fvis" );
|
|
|
|
#endif
|
|
|
|
//=========================================================
|
|
// FVisible - returns true if a line can be traced from
|
|
// the caller's eyes to the target
|
|
//=========================================================
|
|
bool CBaseEntity::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
|
|
{
|
|
VPROF( "CBaseEntity::FVisible" );
|
|
|
|
#if defined( FVIS_STACK_TRACKING )
|
|
if ( perf_fvis_stacks_trace.GetBool() )
|
|
{
|
|
CCallStackStatsGatherer_StructAccessor_AutoLock<Count_t> accessor = s_FVisCallStackInfo.GetEntry(); //( CCallStackStorage( s_FVisCallStackInfo.StackFunction, 1 ) );
|
|
accessor->i += 1;
|
|
}
|
|
#endif
|
|
|
|
if ( pEntity->GetFlags() & FL_NOTARGET )
|
|
return false;
|
|
Vector vecLookerOrigin = EyePosition();//look through the caller's 'eyes'
|
|
Vector vecTargetOrigin = pEntity->EyePosition();
|
|
|
|
trace_t tr;
|
|
if ( ai_LOS_mode.GetBool() )
|
|
{
|
|
UTIL_TraceLine(vecLookerOrigin, vecTargetOrigin, traceMask, this, COLLISION_GROUP_NONE, &tr);
|
|
}
|
|
else
|
|
{
|
|
// If we're doing an LOS search, include NPCs.
|
|
if ( traceMask == MASK_BLOCKLOS )
|
|
{
|
|
traceMask = MASK_BLOCKLOS_AND_NPCS;
|
|
}
|
|
|
|
// Player sees through nodraw
|
|
if ( IsPlayer() )
|
|
{
|
|
traceMask &= ~CONTENTS_BLOCKLOS;
|
|
}
|
|
|
|
// Use the custom LOS trace filter
|
|
CTraceFilterLOS traceFilter( this, COLLISION_GROUP_NONE, pEntity );
|
|
UTIL_TraceLine( vecLookerOrigin, vecTargetOrigin, traceMask, &traceFilter, &tr );
|
|
}
|
|
|
|
if (tr.fraction != 1.0 || tr.startsolid )
|
|
{
|
|
// If we hit the entity we're looking for, it's visible
|
|
if ( tr.m_pEnt == pEntity )
|
|
return true;
|
|
|
|
// Got line of sight on the vehicle the player is driving!
|
|
if ( pEntity && pEntity->IsPlayer() )
|
|
{
|
|
CBasePlayer *pPlayer = assert_cast<CBasePlayer*>( pEntity );
|
|
if ( tr.m_pEnt == pPlayer->GetVehicleEntity() )
|
|
return true;
|
|
}
|
|
|
|
if (ppBlocker)
|
|
{
|
|
*ppBlocker = tr.m_pEnt;
|
|
}
|
|
|
|
return false;// Line of sight is not established
|
|
}
|
|
|
|
return true;// line of sight is valid.
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
// FVisible - returns true if a line can be traced from
|
|
// the caller's eyes to the wished position.
|
|
//=========================================================
|
|
bool CBaseEntity::FVisible( const Vector &vecTarget, int traceMask, CBaseEntity **ppBlocker )
|
|
{
|
|
VPROF( "CBaseEntity::FVisible" );
|
|
|
|
#if defined( FVIS_STACK_TRACKING )
|
|
if ( perf_fvis_stacks_trace.GetBool() )
|
|
{
|
|
CCallStackStatsGatherer_StructAccessor_AutoLock<Count_t> accessor = s_FVisCallStackInfo.GetEntry(); //( CCallStackStorage( s_FVisCallStackInfo.StackFunction, 1 ) );
|
|
accessor->i += 1;
|
|
}
|
|
#endif
|
|
|
|
trace_t tr;
|
|
Vector vecLookerOrigin = EyePosition();// look through the caller's 'eyes'
|
|
|
|
if ( ai_LOS_mode.GetBool() )
|
|
{
|
|
UTIL_TraceLine( vecLookerOrigin, vecTarget, traceMask, this, COLLISION_GROUP_NONE, &tr);
|
|
}
|
|
else
|
|
{
|
|
// If we're doing an LOS search, include NPCs.
|
|
if ( traceMask == MASK_BLOCKLOS )
|
|
{
|
|
traceMask = MASK_BLOCKLOS_AND_NPCS;
|
|
}
|
|
|
|
// Player sees through nodraw and blocklos
|
|
if ( IsPlayer() )
|
|
{
|
|
traceMask |= CONTENTS_IGNORE_NODRAW_OPAQUE;
|
|
traceMask &= ~CONTENTS_BLOCKLOS;
|
|
}
|
|
|
|
// Use the custom LOS trace filter
|
|
CTraceFilterLOS traceFilter( this, COLLISION_GROUP_NONE );
|
|
UTIL_TraceLine( vecLookerOrigin, vecTarget, traceMask, &traceFilter, &tr );
|
|
}
|
|
|
|
if (tr.fraction != 1.0)
|
|
{
|
|
if (ppBlocker)
|
|
{
|
|
*ppBlocker = tr.m_pEnt;
|
|
}
|
|
return false;// Line of sight is not established
|
|
}
|
|
|
|
return true;// line of sight is valid.
|
|
}
|
|
|
|
extern ConVar ai_debug_los;
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Turn on prop LOS debugging mode
|
|
//-----------------------------------------------------------------------------
|
|
void CC_AI_LOS_Debug( IConVar *var, const char *pOldString, float flOldValue )
|
|
{
|
|
int iLOSMode = ai_debug_los.GetInt();
|
|
for ( CBaseEntity *pEntity = gEntList.FirstEnt(); pEntity != NULL; pEntity = gEntList.NextEnt(pEntity) )
|
|
{
|
|
if ( iLOSMode == 1 && pEntity->IsSolid() )
|
|
{
|
|
pEntity->m_debugOverlays |= OVERLAY_SHOW_BLOCKSLOS;
|
|
}
|
|
else if ( iLOSMode == 2 )
|
|
{
|
|
pEntity->m_debugOverlays |= OVERLAY_SHOW_BLOCKSLOS;
|
|
}
|
|
else
|
|
{
|
|
pEntity->m_debugOverlays &= ~OVERLAY_SHOW_BLOCKSLOS;
|
|
}
|
|
}
|
|
}
|
|
ConVar ai_debug_los("ai_debug_los", "0", FCVAR_CHEAT, "NPC Line-Of-Sight debug mode. If 1, solid entities that block NPC LOC will be highlighted with white bounding boxes. If 2, it'll show non-solid entities that would do it if they were solid.", CC_AI_LOS_Debug );
|
|
|
|
|
|
Class_T CBaseEntity::Classify ( void )
|
|
{
|
|
return CLASS_NONE;
|
|
}
|
|
|
|
float CBaseEntity::GetAutoAimRadius()
|
|
{
|
|
if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE )
|
|
return 48.0f;
|
|
else
|
|
return 24.0f;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Changes the shadow cast distance over time
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::ShadowCastDistThink( )
|
|
{
|
|
SetShadowCastDistance( m_flDesiredShadowCastDistance );
|
|
SetContextThink( NULL, gpGlobals->curtime, "ShadowCastDistThink" );
|
|
}
|
|
|
|
void CBaseEntity::SetShadowCastDistance( float flDesiredDistance, float flDelay )
|
|
{
|
|
m_flDesiredShadowCastDistance = flDesiredDistance;
|
|
if ( m_flDesiredShadowCastDistance != m_flShadowCastDistance )
|
|
{
|
|
SetContextThink( &CBaseEntity::ShadowCastDistThink, gpGlobals->curtime + flDelay, "ShadowCastDistThink" );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
TraceAttack
|
|
================
|
|
*/
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns whether a damage info can damage this entity.
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::PassesDamageFilter( const CTakeDamageInfo &info )
|
|
{
|
|
if (m_hDamageFilter)
|
|
{
|
|
CBaseFilter *pFilter = (CBaseFilter *)(m_hDamageFilter.Get());
|
|
return pFilter->PassesDamageFilter(info);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CBaseEntity::NameMatchesComplex( const char *pszNameOrWildcard )
|
|
{
|
|
if ( !Q_stricmp( "!player", pszNameOrWildcard) )
|
|
return IsPlayer();
|
|
|
|
return EntityNamesMatch( pszNameOrWildcard, m_iName );
|
|
}
|
|
|
|
bool CBaseEntity::ClassMatchesComplex( const char *pszClassOrWildcard )
|
|
{
|
|
return EntityNamesMatch( pszClassOrWildcard, m_iClassname );
|
|
}
|
|
|
|
void CBaseEntity::MakeDormant( void )
|
|
{
|
|
AddEFlags( EFL_DORMANT );
|
|
|
|
// disable thinking for dormant entities
|
|
SetThink( NULL );
|
|
|
|
if ( !edict() )
|
|
return;
|
|
|
|
SETBITS( m_iEFlags, EFL_DORMANT );
|
|
|
|
// Don't touch
|
|
AddSolidFlags( FSOLID_NOT_SOLID );
|
|
// Don't move
|
|
SetMoveType( MOVETYPE_NONE );
|
|
// Don't draw
|
|
AddEffects( EF_NODRAW );
|
|
// Don't think
|
|
SetNextThink( TICK_NEVER_THINK );
|
|
}
|
|
|
|
int CBaseEntity::IsDormant( void )
|
|
{
|
|
return IsEFlagSet( EFL_DORMANT );
|
|
}
|
|
|
|
bool CBaseEntity::IsInWorld( void ) const
|
|
{
|
|
if ( !edict() )
|
|
return true;
|
|
|
|
// position
|
|
if (GetAbsOrigin().x >= MAX_COORD_INTEGER) return false;
|
|
if (GetAbsOrigin().y >= MAX_COORD_INTEGER) return false;
|
|
if (GetAbsOrigin().z >= MAX_COORD_INTEGER) return false;
|
|
if (GetAbsOrigin().x <= MIN_COORD_INTEGER) return false;
|
|
if (GetAbsOrigin().y <= MIN_COORD_INTEGER) return false;
|
|
if (GetAbsOrigin().z <= MIN_COORD_INTEGER) return false;
|
|
// speed
|
|
if (GetAbsVelocity().x >= 2000) return false;
|
|
if (GetAbsVelocity().y >= 2000) return false;
|
|
if (GetAbsVelocity().z >= 2000) return false;
|
|
if (GetAbsVelocity().x <= -2000) return false;
|
|
if (GetAbsVelocity().y <= -2000) return false;
|
|
if (GetAbsVelocity().z <= -2000) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool CBaseEntity::IsViewable( void )
|
|
{
|
|
if ( IsEffectActive( EF_NODRAW ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (IsBSPModel())
|
|
{
|
|
if (GetMoveType() != MOVETYPE_NONE)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else if (GetModelIndex() != 0)
|
|
{
|
|
// check for total transparency???
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
int CBaseEntity::ShouldToggle( USE_TYPE useType, int currentState )
|
|
{
|
|
if ( useType != USE_TOGGLE && useType != USE_SET )
|
|
{
|
|
if ( (currentState && useType == USE_ON) || (!currentState && useType == USE_OFF) )
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
// NOTE: szName must be a pointer to constant memory, e.g. "NPC_class" because the entity
|
|
// will keep a pointer to it after this call.
|
|
CBaseEntity *CBaseEntity::Create( const char *szName, const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity *pOwner )
|
|
{
|
|
CBaseEntity *pEntity = CreateNoSpawn( szName, vecOrigin, vecAngles, pOwner );
|
|
|
|
DispatchSpawn( pEntity );
|
|
return pEntity;
|
|
}
|
|
|
|
|
|
|
|
// NOTE: szName must be a pointer to constant memory, e.g. "NPC_class" because the entity
|
|
// will keep a pointer to it after this call.
|
|
CBaseEntity * CBaseEntity::CreateNoSpawn( const char *szName, const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity *pOwner )
|
|
{
|
|
CBaseEntity *pEntity = CreateEntityByName( szName, -1, false );
|
|
if ( !pEntity )
|
|
{
|
|
Assert( !"CreateNoSpawn: only works for CBaseEntities" );
|
|
return NULL;
|
|
}
|
|
|
|
pEntity->SetLocalOrigin( vecOrigin );
|
|
pEntity->SetLocalAngles( vecAngles );
|
|
pEntity->SetOwnerEntity( pOwner );
|
|
|
|
gEntList.NotifyCreateEntity( pEntity );
|
|
|
|
return pEntity;
|
|
}
|
|
|
|
Vector CBaseEntity::GetSoundEmissionOrigin() const
|
|
{
|
|
return WorldSpaceCenter();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Saves the current object out to disk, by iterating through the objects
|
|
// data description hierarchy
|
|
// Input : &save - save buffer which the class data is written to
|
|
// Output : int - 0 if the save failed, 1 on success
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::Save( ISave &save )
|
|
{
|
|
// loop through the data description list, saving each data desc block
|
|
int status = SaveDataDescBlock( save, GetDataDescMap() );
|
|
|
|
return status;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Recursively saves all the classes in an object, in reverse order (top down)
|
|
// Output : int 0 on failure, 1 on success
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::SaveDataDescBlock( ISave &save, datamap_t *dmap )
|
|
{
|
|
return save.WriteAll( this, dmap );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Restores the current object from disk, by iterating through the objects
|
|
// data description hierarchy
|
|
// Input : &restore - restore buffer which the class data is read from
|
|
// Output : int - 0 if the restore failed, 1 on success
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::Restore( IRestore &restore )
|
|
{
|
|
// This is essential to getting the spatial partition info correct
|
|
CollisionProp()->DestroyPartitionHandle();
|
|
|
|
// loops through the data description list, restoring each data desc block in order
|
|
int status = RestoreDataDescBlock( restore, GetDataDescMap() );
|
|
|
|
// ---------------------------------------------------------------
|
|
// HACKHACK: We don't know the space of these vectors until now
|
|
// if they are worldspace, fix them up.
|
|
// ---------------------------------------------------------------
|
|
{
|
|
CGameSaveRestoreInfo *pGameInfo = restore.GetGameSaveRestoreInfo();
|
|
Vector parentSpaceOffset = pGameInfo->modelSpaceOffset;
|
|
if ( !GetParent() )
|
|
{
|
|
// parent is the world, so parent space is worldspace
|
|
// so update with the worldspace leveltransition transform
|
|
parentSpaceOffset += pGameInfo->GetLandmark();
|
|
}
|
|
|
|
// NOTE: Do *not* use GetAbsOrigin() here because it will
|
|
// try to recompute m_rgflCoordinateFrame!
|
|
MatrixSetColumn( m_vecAbsOrigin, 3, m_rgflCoordinateFrame );
|
|
|
|
m_vecOrigin += parentSpaceOffset;
|
|
}
|
|
|
|
// Gotta do this after the coordframe is set up as it depends on it.
|
|
|
|
// By definition, the surrounding bounds are dirty
|
|
// Also, twiddling with the flags here ensures it gets added to the KD tree dirty list
|
|
// (We don't want to use the saved version of this flag)
|
|
RemoveEFlags( EFL_DIRTY_SPATIAL_PARTITION );
|
|
CollisionProp()->MarkSurroundingBoundsDirty();
|
|
|
|
if ( edict() && GetModelIndex() != 0 && GetModelName() != NULL_STRING && restore.GetPrecacheMode() )
|
|
{
|
|
PrecacheModel( STRING( GetModelName() ) );
|
|
|
|
//Adrian: We should only need to do this after we precache. No point in setting the model again.
|
|
SetModelIndex( modelinfo->GetModelIndex( STRING(GetModelName() ) ) );
|
|
}
|
|
|
|
// Restablish ground entity
|
|
if ( m_hGroundEntity != NULL )
|
|
{
|
|
m_hGroundEntity->AddEntityToGroundList( this );
|
|
}
|
|
|
|
// Tracker 22129
|
|
// This is a hack to make sure that the entity is added to the AddPostClientMessageEntity
|
|
// list so that EF_NOINTERP can be cleared at the end of the frame. Otherwise, a restored entity
|
|
// with this flag will not interpolate until the next time the flag is set. ywb
|
|
if ( IsEffectActive( EF_NOINTERP ) )
|
|
{
|
|
AddEffects( EF_NOINTERP );
|
|
}
|
|
|
|
// Ensure our cell is current
|
|
UpdateCell();
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// handler to do stuff before you are saved
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::OnSave( IEntitySaveUtils *pUtils )
|
|
{
|
|
// Here, we must force recomputation of all abs data so it gets saved correctly
|
|
// We can't leave the dirty bits set because the loader can't cope with it.
|
|
CalcAbsolutePosition();
|
|
CalcAbsoluteVelocity();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// handler to do stuff after you are restored
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::OnRestore()
|
|
{
|
|
gEntList.UpdateName( this );
|
|
|
|
SimThink_EntityChanged( this );
|
|
|
|
// touchlinks get recomputed
|
|
if ( IsEFlagSet( EFL_CHECK_UNTOUCH ) )
|
|
{
|
|
RemoveEFlags( EFL_CHECK_UNTOUCH );
|
|
SetCheckUntouch( true );
|
|
}
|
|
|
|
// disable touch functions while we recreate the touch links between entities
|
|
// NOTE: We don't do this on transitions, because we'd miss the OnStartTouch call!
|
|
#if !defined(HL2_DLL) || ( defined(HL2_DLL) && defined(HL2_EPISODIC) )
|
|
CBaseEntity::sm_bDisableTouchFuncs = ( gpGlobals->eLoadType != MapLoad_Transition );
|
|
PhysicsTouchTriggers();
|
|
CBaseEntity::sm_bDisableTouchFuncs = false;
|
|
#endif // HL2_EPISODIC
|
|
|
|
//Adrian: If I'm restoring with these fields it means I've become a client side ragdoll.
|
|
//Don't create another one, just wait until is my time of being removed.
|
|
if ( GetFlags() & FL_TRANSRAGDOLL )
|
|
{
|
|
m_nRenderFX = kRenderFxNone;
|
|
AddEffects( EF_NODRAW );
|
|
RemoveFlag( FL_DISSOLVING | FL_ONFIRE );
|
|
}
|
|
|
|
if ( m_pParent )
|
|
{
|
|
CBaseEntity *pChild = m_pParent->FirstMoveChild();
|
|
while ( pChild )
|
|
{
|
|
if ( pChild == this )
|
|
break;
|
|
pChild = pChild->NextMovePeer();
|
|
}
|
|
if ( pChild != this )
|
|
{
|
|
#if _DEBUG
|
|
// generally this means you've got something marked FCAP_DONT_SAVE
|
|
// in a hierarchy. That's probably ok given this fixup, but the hierarhcy
|
|
// linked list is just saved/loaded in-place
|
|
Warning("Fixing up parent on %s\n", GetClassname() );
|
|
#endif
|
|
// We only need to be back in the parent's list because we're already in the right place and with the right data
|
|
LinkChild( m_pParent, this );
|
|
}
|
|
}
|
|
|
|
// We're not save/loading the PVS dirty state. Assume everything is dirty after a restore
|
|
NetworkProp()->MarkPVSInformationDirty();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Recursively restores all the classes in an object, in reverse order (top down)
|
|
// Output : int 0 on failure, 1 on success
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::RestoreDataDescBlock( IRestore &restore, datamap_t *dmap )
|
|
{
|
|
return restore.ReadAll( this, dmap );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool CBaseEntity::ShouldSavePhysics()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "tier0/memdbgoff.h"
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// CBaseEntity new/delete
|
|
// allocates and frees memory for itself from the engine->
|
|
// All fields in the object are all initialized to 0.
|
|
//-----------------------------------------------------------------------------
|
|
void *CBaseEntity::operator new( size_t stAllocateBlock )
|
|
{
|
|
// call into engine to get memory
|
|
Assert( stAllocateBlock != 0 );
|
|
return calloc( 1, stAllocateBlock );
|
|
};
|
|
|
|
void *CBaseEntity::operator new( size_t stAllocateBlock, int nBlockUse, const char *pFileName, int nLine )
|
|
{
|
|
// call into engine to get memory
|
|
Assert( stAllocateBlock != 0 );
|
|
return MemAlloc_InlineCallocMemset( MemAlloc_Alloc(stAllocateBlock, pFileName, nLine), 1, stAllocateBlock );
|
|
}
|
|
|
|
void CBaseEntity::operator delete( void *pMem )
|
|
{
|
|
// get the engine to free the memory
|
|
free( pMem );
|
|
}
|
|
|
|
#include "tier0/memdbgon.h"
|
|
|
|
|
|
#ifdef _DEBUG
|
|
void CBaseEntity::FunctionCheck( inputfunc_t pFunction, const char *name )
|
|
{
|
|
#ifdef USES_SAVERESTORE
|
|
// Note, if you crash here and your class is using multiple inheritance, it is
|
|
// probably the case that CBaseEntity (or a descendant) is not the first
|
|
// class in your list of ancestors, which it must be.
|
|
if ( pFunction && !UTIL_FunctionToName( GetDataDescMap(), pFunction ) )
|
|
{
|
|
Warning( "FUNCTION NOT IN TABLE!: %s:%s (%08lx)\n", STRING(m_iClassname), name, *reinterpret_cast<uint32*>(&pFunction) );
|
|
Assert(0);
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
|
|
bool CBaseEntity::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Perform hitbox test, returns true *if hitboxes were tested at all*!!
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
|
|
void CBaseEntity::SetOwnerEntity( CBaseEntity* pOwner )
|
|
{
|
|
if ( m_hOwnerEntity.Get() != pOwner )
|
|
{
|
|
m_hOwnerEntity = pOwner;
|
|
|
|
CollisionRulesChanged();
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::SetMoveType( MoveType_t val, MoveCollide_t moveCollide )
|
|
{
|
|
#ifdef _DEBUG
|
|
// Make sure the move type + move collide are compatible...
|
|
if ((val != MOVETYPE_FLY) && (val != MOVETYPE_FLYGRAVITY))
|
|
{
|
|
Assert( moveCollide == MOVECOLLIDE_DEFAULT );
|
|
}
|
|
|
|
if ( m_MoveType == MOVETYPE_VPHYSICS && val != m_MoveType )
|
|
{
|
|
if ( VPhysicsGetObject() && val != MOVETYPE_NONE )
|
|
{
|
|
// What am I supposed to do with the physics object if
|
|
// you're changing away from MOVETYPE_VPHYSICS without making the object
|
|
// shadow? This isn't likely to work, assert.
|
|
// You probably meant to call VPhysicsInitShadow() instead of VPhysicsInitNormal()!
|
|
Assert( VPhysicsGetObject()->GetShadowController() );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if ( m_MoveType == val )
|
|
{
|
|
m_MoveCollide = moveCollide;
|
|
return;
|
|
}
|
|
|
|
if ( m_MoveType == MOVETYPE_NOCLIP && val != m_MoveType )
|
|
{
|
|
RemoveEFlags( EFL_NOCLIP_ACTIVE );
|
|
}
|
|
|
|
// This is needed to the removal of MOVETYPE_FOLLOW:
|
|
// We can't transition from follow to a different movetype directly
|
|
// or the leaf code will break.
|
|
Assert( !IsEffectActive( EF_BONEMERGE ) );
|
|
if ( IsPushableMoveType(val) != IsPushableMoveType(m_MoveType) )
|
|
{
|
|
CollisionProp()->MarkPartitionHandleDirty();
|
|
}
|
|
|
|
m_MoveType = val;
|
|
m_MoveCollide = moveCollide;
|
|
|
|
CollisionRulesChanged();
|
|
|
|
switch( m_MoveType )
|
|
{
|
|
case MOVETYPE_WALK:
|
|
{
|
|
SetSimulatedEveryTick( true );
|
|
SetAnimatedEveryTick( true );
|
|
}
|
|
break;
|
|
case MOVETYPE_STEP:
|
|
{
|
|
// This will probably go away once I remove the cvar that controls the test code
|
|
SetSimulatedEveryTick( g_bTestMoveTypeStepSimulation ? true : false );
|
|
SetAnimatedEveryTick( false );
|
|
}
|
|
break;
|
|
case MOVETYPE_FLY:
|
|
case MOVETYPE_FLYGRAVITY:
|
|
{
|
|
// Initialize our water state, because these movetypes care about transitions in/out of water
|
|
UpdateWaterState();
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
SetSimulatedEveryTick( true );
|
|
SetAnimatedEveryTick( false );
|
|
}
|
|
}
|
|
|
|
// This will probably go away or be handled in a better way once I remove the cvar that controls the test code
|
|
CheckStepSimulationChanged();
|
|
CheckHasGamePhysicsSimulation();
|
|
}
|
|
|
|
void CBaseEntity::Spawn( void )
|
|
{
|
|
}
|
|
|
|
// Post KeyValues/Map data parsing hook
|
|
void CBaseEntity::OnParseMapDataFinished()
|
|
{
|
|
// Add to lag compensation list
|
|
if ( ShouldLagCompensate() )
|
|
{
|
|
lagcompensation->AddAdditionalEntity( this );
|
|
}
|
|
}
|
|
|
|
CBaseEntity* CBaseEntity::Instance( const CBaseHandle &hEnt )
|
|
{
|
|
return gEntList.GetBaseEntity( hEnt );
|
|
}
|
|
|
|
int CBaseEntity::GetTransmitState( void )
|
|
{
|
|
edict_t *ed = edict();
|
|
|
|
if ( !ed )
|
|
return 0;
|
|
|
|
return ed->m_fStateFlags;
|
|
}
|
|
|
|
int CBaseEntity::SetTransmitState( int nFlag)
|
|
{
|
|
edict_t *ed = edict();
|
|
|
|
if ( !ed )
|
|
return 0;
|
|
|
|
// clear current flags = check ShouldTransmit()
|
|
ed->ClearTransmitState();
|
|
|
|
int oldFlags = ed->m_fStateFlags;
|
|
ed->m_fStateFlags |= nFlag;
|
|
|
|
// Tell the engine (used for a network backdoor optimization).
|
|
if ( (oldFlags & FL_EDICT_DONTSEND) != (ed->m_fStateFlags & FL_EDICT_DONTSEND) )
|
|
engine->NotifyEdictFlagsChange( entindex() );
|
|
|
|
return ed->m_fStateFlags;
|
|
}
|
|
|
|
int CBaseEntity::UpdateTransmitState()
|
|
{
|
|
// If you get this assert, you should be calling DispatchUpdateTransmitState
|
|
// instead of UpdateTransmitState.
|
|
Assert( g_nInsideDispatchUpdateTransmitState > 0 );
|
|
|
|
#ifdef DOTA_DLL
|
|
return SetTransmitState( FL_EDICT_FULLCHECK );
|
|
#endif
|
|
|
|
// If an object is the moveparent of something else, don't skip it just because it's marked EF_NODRAW or else
|
|
// the client won't have a proper origin for the child since the hierarchy won't be correctly transmitted down
|
|
if ( IsEffectActive( EF_NODRAW ) &&
|
|
!m_hMoveChild.Get() )
|
|
{
|
|
return SetTransmitState( FL_EDICT_DONTSEND );
|
|
}
|
|
|
|
if ( !IsEFlagSet( EFL_FORCE_CHECK_TRANSMIT ) )
|
|
{
|
|
if ( !GetModelIndex() || !GetModelName() )
|
|
{
|
|
return SetTransmitState( FL_EDICT_DONTSEND );
|
|
}
|
|
}
|
|
|
|
// Always send the world
|
|
if ( GetModelIndex() == 1 )
|
|
{
|
|
return SetTransmitState( FL_EDICT_ALWAYS );
|
|
}
|
|
|
|
if ( IsEFlagSet( EFL_IN_SKYBOX ) )
|
|
{
|
|
return SetTransmitState( FL_EDICT_ALWAYS );
|
|
}
|
|
|
|
// by default cull against PVS
|
|
return SetTransmitState( FL_EDICT_PVSCHECK );
|
|
}
|
|
|
|
int CBaseEntity::DispatchUpdateTransmitState()
|
|
{
|
|
edict_t *ed = edict();
|
|
if ( m_nTransmitStateOwnedCounter != 0 )
|
|
return ed ? ed->m_fStateFlags : 0;
|
|
|
|
g_nInsideDispatchUpdateTransmitState++;
|
|
int ret = UpdateTransmitState();
|
|
g_nInsideDispatchUpdateTransmitState--;
|
|
|
|
return ret;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Note, an entity can override the send table ( e.g., to send less data or to send minimal data for
|
|
// objects ( prob. players ) that are not in the pvs.
|
|
// Input : **ppSendTable -
|
|
// *recipient -
|
|
// *pvs -
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::ShouldTransmit( const CCheckTransmitInfo *pInfo )
|
|
{
|
|
int fFlags = DispatchUpdateTransmitState();
|
|
|
|
if ( fFlags & FL_EDICT_PVSCHECK )
|
|
{
|
|
return FL_EDICT_PVSCHECK;
|
|
}
|
|
else if ( fFlags & FL_EDICT_ALWAYS )
|
|
{
|
|
return FL_EDICT_ALWAYS;
|
|
}
|
|
else if ( fFlags & FL_EDICT_DONTSEND )
|
|
{
|
|
return FL_EDICT_DONTSEND;
|
|
}
|
|
|
|
// if ( IsToolRecording() )
|
|
// {
|
|
// return FL_EDICT_ALWAYS;
|
|
// }
|
|
|
|
CBaseEntity *pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt );
|
|
|
|
Assert( pRecipientEntity->IsPlayer() );
|
|
|
|
CBasePlayer *pRecipientPlayer = static_cast<CBasePlayer*>( pRecipientEntity );
|
|
|
|
|
|
// FIXME: Refactor once notion of "team" is moved into HL2 code
|
|
// Team rules may tell us that we should
|
|
if ( pRecipientPlayer->GetTeam() )
|
|
{
|
|
int iRet = pRecipientPlayer->GetTeam()->ShouldTransmitToPlayer( pRecipientPlayer, this );
|
|
|
|
if ( iRet )
|
|
return iRet;
|
|
}
|
|
|
|
// by default do a PVS check
|
|
return FL_EDICT_PVSCHECK;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Rules about which entities need to transmit along with me
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways )
|
|
{
|
|
int index = entindex();
|
|
|
|
// Are we already marked for transmission?
|
|
if ( pInfo->m_pTransmitEdict->Get( index ) )
|
|
return;
|
|
|
|
CServerNetworkProperty *pNetworkParent = NetworkProp()->GetNetworkParent();
|
|
|
|
pInfo->m_pTransmitEdict->Set( index );
|
|
|
|
// HLTV/Replay need to know if this entity is culled by PVS limits
|
|
if ( pInfo->m_pTransmitAlways )
|
|
{
|
|
// in HLTV/Replay mode always transmit entitys with move-parents
|
|
// HLTV/Replay can't resolve the mode-parents relationships
|
|
if ( bAlways || pNetworkParent )
|
|
{
|
|
// tell HLTV/Replay that this entity is always transmitted
|
|
pInfo->m_pTransmitAlways->Set( index );
|
|
}
|
|
else
|
|
{
|
|
// HLTV/Replay will PVS cull this entity, so update the
|
|
// node/cluster infos if necessary
|
|
m_Network.RecomputePVSInformation();
|
|
}
|
|
}
|
|
|
|
// Force our aiment and move parent to be sent.
|
|
if ( pNetworkParent )
|
|
{
|
|
CBaseEntity *pMoveParent = pNetworkParent->GetBaseEntity();
|
|
pMoveParent->SetTransmit( pInfo, bAlways );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Returns which skybox the entity is in
|
|
//-----------------------------------------------------------------------------
|
|
CSkyCamera *CBaseEntity::GetEntitySkybox()
|
|
{
|
|
int area = engine->GetArea( WorldSpaceCenter() );
|
|
|
|
CSkyCamera *pCur = GetSkyCameraList();
|
|
while ( pCur )
|
|
{
|
|
if ( engine->CheckAreasConnected( area, pCur->m_skyboxData.area ) )
|
|
return pCur;
|
|
|
|
pCur = pCur->m_pNext;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
bool CBaseEntity::DetectInSkybox()
|
|
{
|
|
if ( GetEntitySkybox() != NULL )
|
|
{
|
|
AddEFlags( EFL_IN_SKYBOX );
|
|
return true;
|
|
}
|
|
|
|
RemoveEFlags( EFL_IN_SKYBOX );
|
|
return false;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Computes a world-aligned bounding box that surrounds everything in the entity
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::ComputeWorldSpaceSurroundingBox( Vector *pMins, Vector *pMaxs )
|
|
{
|
|
// Should never get here.. only use USE_GAME_CODE with bounding boxes
|
|
// if you have an implementation for this method
|
|
Assert( 0 );
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : If name exists returns name, otherwise returns classname
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
const char *CBaseEntity::GetDebugName(void)
|
|
{
|
|
if ( this == NULL )
|
|
return "<<null>>";
|
|
|
|
if ( m_iName.Get() != NULL_STRING )
|
|
{
|
|
return STRING(m_iName.Get());
|
|
}
|
|
else
|
|
{
|
|
if ( ToBasePlayer( this ) )
|
|
{
|
|
return ToBasePlayer( this )->GetPlayerName();
|
|
}
|
|
|
|
return STRING(m_iClassname);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::DrawInputOverlay(const char *szInputName, CBaseEntity *pCaller, variant_t Value)
|
|
{
|
|
char bigstring[1024];
|
|
if ( Value.FieldType() == FIELD_INTEGER )
|
|
{
|
|
Q_snprintf( bigstring,sizeof(bigstring), "%3.1f (%s,%d) <-- (%s)\n", gpGlobals->curtime, szInputName, Value.Int(), pCaller ? pCaller->GetDebugName() : NULL);
|
|
}
|
|
else if ( Value.FieldType() == FIELD_STRING )
|
|
{
|
|
Q_snprintf( bigstring,sizeof(bigstring), "%3.1f (%s,%s) <-- (%s)\n", gpGlobals->curtime, szInputName, Value.String(), pCaller ? pCaller->GetDebugName() : NULL);
|
|
}
|
|
else
|
|
{
|
|
Q_snprintf( bigstring,sizeof(bigstring), "%3.1f (%s) <-- (%s)\n", gpGlobals->curtime, szInputName, pCaller ? pCaller->GetDebugName() : NULL);
|
|
}
|
|
AddTimedOverlay(bigstring, 10.0);
|
|
|
|
if ( Value.FieldType() == FIELD_INTEGER )
|
|
{
|
|
DevMsg( 2, "input: (%s,%d) -> (%s,%s), from (%s)\n", szInputName, Value.Int(), STRING(m_iClassname), GetDebugName(), pCaller ? pCaller->GetDebugName() : NULL);
|
|
}
|
|
else if ( Value.FieldType() == FIELD_STRING )
|
|
{
|
|
DevMsg( 2, "input: (%s,%s) -> (%s,%s), from (%s)\n", szInputName, Value.String(), STRING(m_iClassname), GetDebugName(), pCaller ? pCaller->GetDebugName() : NULL);
|
|
}
|
|
else
|
|
DevMsg( 2, "input: (%s) -> (%s,%s), from (%s)\n", szInputName, STRING(m_iClassname), GetDebugName(), pCaller ? pCaller->GetDebugName() : NULL);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::DrawOutputOverlay(CEventAction *ev)
|
|
{
|
|
// Print to entity
|
|
char bigstring[1024];
|
|
if ( ev->m_flDelay )
|
|
{
|
|
Q_snprintf( bigstring,sizeof(bigstring), "%3.1f (%s) --> (%s),%.1f) \n", gpGlobals->curtime, STRING(ev->m_iTargetInput), STRING(ev->m_iTarget), ev->m_flDelay);
|
|
}
|
|
else
|
|
{
|
|
Q_snprintf( bigstring,sizeof(bigstring), "%3.1f (%s) --> (%s)\n", gpGlobals->curtime, STRING(ev->m_iTargetInput), STRING(ev->m_iTarget));
|
|
}
|
|
AddTimedOverlay(bigstring, 10.0);
|
|
|
|
// Now print to the console
|
|
if ( ev->m_flDelay )
|
|
{
|
|
DevMsg( 2, "output: (%s,%s) -> (%s,%s,%.1f)\n", STRING(m_iClassname), GetDebugName(), STRING(ev->m_iTarget), STRING(ev->m_iTargetInput), ev->m_flDelay );
|
|
}
|
|
else
|
|
{
|
|
DevMsg( 2, "output: (%s,%s) -> (%s,%s)\n", STRING(m_iClassname), GetDebugName(), STRING(ev->m_iTarget), STRING(ev->m_iTargetInput) );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Entity events... these are events targetted to a particular entity
|
|
// Each event defines its own well-defined event data structure
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::OnEntityEvent( EntityEvent_t event, void *pEventData )
|
|
{
|
|
switch( event )
|
|
{
|
|
case ENTITY_EVENT_WATER_TOUCH:
|
|
{
|
|
int nContents = size_cast< int >( (intp)pEventData );
|
|
if ( !nContents || (nContents & CONTENTS_WATER) )
|
|
{
|
|
++m_nWaterTouch;
|
|
}
|
|
if ( nContents & CONTENTS_SLIME )
|
|
{
|
|
++m_nSlimeTouch;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ENTITY_EVENT_WATER_UNTOUCH:
|
|
{
|
|
int nContents = size_cast< int >( (intp)pEventData );
|
|
if ( !nContents || (nContents & CONTENTS_WATER) )
|
|
{
|
|
--m_nWaterTouch;
|
|
}
|
|
if ( nContents & CONTENTS_SLIME )
|
|
{
|
|
--m_nSlimeTouch;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
// Only do this for vphysics objects
|
|
if ( GetMoveType() != MOVETYPE_VPHYSICS )
|
|
return;
|
|
|
|
int nNewContents = 0;
|
|
if ( m_nWaterTouch > 0 )
|
|
{
|
|
nNewContents |= CONTENTS_WATER;
|
|
}
|
|
|
|
if ( m_nSlimeTouch > 0 )
|
|
{
|
|
nNewContents |= CONTENTS_SLIME;
|
|
}
|
|
|
|
if (( nNewContents & MASK_WATER ) == 0)
|
|
{
|
|
SetWaterLevel( 0 );
|
|
SetWaterType( CONTENTS_EMPTY );
|
|
return;
|
|
}
|
|
|
|
SetWaterLevel( 1 );
|
|
SetWaterType( nNewContents );
|
|
}
|
|
|
|
|
|
ConVar ent_messages_draw( "ent_messages_draw", "0", FCVAR_CHEAT, "Visualizes all entity input/output activity." );
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: calls the appropriate message mapped function in the entity according
|
|
// to the fired action.
|
|
// Input : char *szInputName - input destination
|
|
// *pActivator - entity which initiated this sequence of actions
|
|
// *pCaller - entity from which this event is sent
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::AcceptInput( const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t Value, int outputID )
|
|
{
|
|
if ( ent_messages_draw.GetBool() )
|
|
{
|
|
if ( pCaller != NULL )
|
|
{
|
|
NDebugOverlay::Line( pCaller->GetAbsOrigin(), GetAbsOrigin(), 255, 255, 255, false, 3 );
|
|
NDebugOverlay::Box( pCaller->GetAbsOrigin(), Vector(-4, -4, -4), Vector(4, 4, 4), 255, 0, 0, 0, 3 );
|
|
}
|
|
|
|
NDebugOverlay::Text( GetAbsOrigin(), szInputName, false, 3 );
|
|
NDebugOverlay::Box( GetAbsOrigin(), Vector(-4, -4, -4), Vector(4, 4, 4), 0, 255, 0, 0, 3 );
|
|
}
|
|
|
|
// loop through the data description list, restoring each data desc block
|
|
for ( datamap_t *dmap = GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap )
|
|
{
|
|
// search through all the actions in the data description, looking for a match
|
|
for ( int i = 0; i < dmap->dataNumFields; i++ )
|
|
{
|
|
if ( dmap->dataDesc[i].flags & FTYPEDESC_INPUT )
|
|
{
|
|
if ( !Q_stricmp(dmap->dataDesc[i].externalName, szInputName) )
|
|
{
|
|
// found a match
|
|
|
|
if ( developer.GetBool() )
|
|
{
|
|
char szBuffer[256];
|
|
// mapper debug message
|
|
if (pCaller != NULL)
|
|
{
|
|
Q_snprintf( szBuffer, sizeof(szBuffer), "(%0.2f) input %s: %s.%s(%s)\n", gpGlobals->curtime, STRING(pCaller->m_iName.Get()), GetDebugName(), szInputName, Value.String() );
|
|
}
|
|
else
|
|
{
|
|
Q_snprintf( szBuffer, sizeof(szBuffer), "(%0.2f) input <NULL>: %s.%s(%s)\n", gpGlobals->curtime, GetDebugName(), szInputName, Value.String() );
|
|
}
|
|
DevMsg( 2, "%s", szBuffer );
|
|
ADD_DEBUG_HISTORY( HISTORY_ENTITY_IO, szBuffer );
|
|
|
|
if (m_debugOverlays & OVERLAY_MESSAGE_BIT)
|
|
{
|
|
DrawInputOverlay(szInputName,pCaller,Value);
|
|
}
|
|
}
|
|
|
|
// convert the value if necessary
|
|
if ( Value.FieldType() != dmap->dataDesc[i].fieldType )
|
|
{
|
|
if ( !(Value.FieldType() == FIELD_VOID && dmap->dataDesc[i].fieldType == FIELD_STRING) ) // allow empty strings
|
|
{
|
|
if ( !Value.Convert( (fieldtype_t)dmap->dataDesc[i].fieldType ) )
|
|
{
|
|
// bad conversion
|
|
Warning( "!! ERROR: bad input/output link:\n!! %s(%s,%s) doesn't match type from %s(%s)\n",
|
|
STRING(m_iClassname), GetDebugName(), szInputName,
|
|
( pCaller != NULL ) ? STRING(pCaller->m_iClassname) : "<null>",
|
|
( pCaller != NULL ) ? STRING(pCaller->m_iName.Get()) : "<null>" );
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// call the input handler, or if there is none just set the value
|
|
inputfunc_t pfnInput = dmap->dataDesc[i].inputFunc;
|
|
|
|
if ( pfnInput )
|
|
{
|
|
// Package the data into a struct for passing to the input handler.
|
|
inputdata_t data;
|
|
data.pActivator = pActivator;
|
|
data.pCaller = pCaller;
|
|
data.value = Value;
|
|
data.nOutputID = outputID;
|
|
|
|
// Now, see if there's a function named Input<Name of Input> in this entity's script file.
|
|
// If so, execute it and let it decide whether to allow the default behavior to also execute.
|
|
bool bCallInputFunc = true; // Always assume default behavior (do call the input function)
|
|
ScriptVariant_t functionReturn;
|
|
|
|
if ( m_ScriptScope.IsInitialized() )
|
|
{
|
|
char szScriptFunctionName[255];
|
|
Q_strcpy( szScriptFunctionName, "Input" );
|
|
Q_strcat( szScriptFunctionName, szInputName, 255 );
|
|
|
|
g_pScriptVM->SetValue( "activator", ( pActivator ) ? ScriptVariant_t( pActivator->GetScriptInstance() ) : SCRIPT_VARIANT_NULL );
|
|
g_pScriptVM->SetValue( "caller", ( pCaller ) ? ScriptVariant_t( pCaller->GetScriptInstance() ) : SCRIPT_VARIANT_NULL );
|
|
|
|
if( CallScriptFunction( szScriptFunctionName, &functionReturn ) )
|
|
{
|
|
bCallInputFunc = functionReturn.m_bool;
|
|
}
|
|
}
|
|
|
|
if( bCallInputFunc )
|
|
{
|
|
(this->*pfnInput)( data );
|
|
}
|
|
|
|
if ( m_ScriptScope.IsInitialized() )
|
|
{
|
|
g_pScriptVM->ClearValue( "activator" );
|
|
g_pScriptVM->ClearValue( "caller" );
|
|
}
|
|
}
|
|
else if ( dmap->dataDesc[i].flags & FTYPEDESC_KEY )
|
|
{
|
|
// set the value directly
|
|
Value.SetOther( ((char*)this) + dmap->dataDesc[i].fieldOffset);
|
|
|
|
// TODO: if this becomes evil and causes too many full entity updates, then we should make
|
|
// a macro like this:
|
|
//
|
|
// define MAKE_INPUTVAR(x) void Note##x##Modified() { x.GetForModify(); }
|
|
//
|
|
// Then the datadesc points at that function and we call it here. The only pain is to add
|
|
// that function for all the DEFINE_INPUT calls.
|
|
NetworkStateChanged();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DevMsg( 2, "unhandled input: (%s) -> (%s,%s)\n", szInputName, STRING(m_iClassname), GetDebugName()/*,", from (%s,%s)" STRING(pCaller->m_iClassname), STRING(pCaller->m_iName)*/ );
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Input handler for the entity alpha.
|
|
// Input : nAlpha - Alpha value (0 - 255).
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputAlpha( inputdata_t &inputdata )
|
|
{
|
|
SetRenderAlpha( clamp( inputdata.value.Int(), 0, 255 ) );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Activate alternative sorting
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputAlternativeSorting( inputdata_t &inputdata )
|
|
{
|
|
m_bAlternateSorting = inputdata.value.Bool();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Input handler for the entity color. Ignores alpha since that is handled
|
|
// by a separate input handler.
|
|
// Input : Color32 new value for color (alpha is ignored).
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputColor( inputdata_t &inputdata )
|
|
{
|
|
color32 clr = inputdata.value.Color32();
|
|
|
|
SetRenderColor( clr.r, clr.g, clr.b );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Called whenever the entity is 'Used'. This can be when a player hits
|
|
// use, or when an entity targets it without an output name (legacy entities)
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputUse( inputdata_t &inputdata )
|
|
{
|
|
Use( inputdata.pActivator, inputdata.pCaller, (USE_TYPE)inputdata.nOutputID, 0 );
|
|
|
|
IGameEvent *event = gameeventmanager->CreateEvent( "player_use" );
|
|
if ( event )
|
|
{
|
|
event->SetInt( "userid", inputdata.pActivator && inputdata.pActivator->IsPlayer() ? static_cast< CBasePlayer* >( inputdata.pActivator )->GetUserID() : 0 );
|
|
event->SetInt( "entity", entindex() );
|
|
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Reads an output variable, by string name, from an entity
|
|
// Input : char *varName - the string name of the variable
|
|
// variant_t *var - the value is stored here
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::ReadKeyField( const char *varName, variant_t *var )
|
|
{
|
|
if ( !varName )
|
|
return false;
|
|
|
|
// loop through the data description list, restoring each data desc block
|
|
for ( datamap_t *dmap = GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap )
|
|
{
|
|
// search through all the readable fields in the data description, looking for a match
|
|
for ( int i = 0; i < dmap->dataNumFields; i++ )
|
|
{
|
|
if ( dmap->dataDesc[i].flags & (FTYPEDESC_OUTPUT | FTYPEDESC_KEY) )
|
|
{
|
|
if ( !Q_stricmp(dmap->dataDesc[i].externalName, varName) )
|
|
{
|
|
var->Set( dmap->dataDesc[i].fieldType, ((char*)this) + dmap->dataDesc[i].fieldOffset);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets the damage filter on the object
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputEnableDamageForces( inputdata_t &inputdata )
|
|
{
|
|
RemoveEFlags( EFL_NO_DAMAGE_FORCES );
|
|
}
|
|
|
|
void CBaseEntity::InputDisableDamageForces( inputdata_t &inputdata )
|
|
{
|
|
AddEFlags( EFL_NO_DAMAGE_FORCES );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets the damage filter on the object
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetDamageFilter( inputdata_t &inputdata )
|
|
{
|
|
// Get a handle to my damage filter entity if there is one.
|
|
m_iszDamageFilterName = inputdata.value.StringID();
|
|
if ( m_iszDamageFilterName != NULL_STRING )
|
|
{
|
|
m_hDamageFilter = gEntList.FindEntityByName( NULL, m_iszDamageFilterName );
|
|
}
|
|
else
|
|
{
|
|
m_hDamageFilter = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Marks the entity for deletion
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputKill( inputdata_t &inputdata )
|
|
{
|
|
// tell owner ( if any ) that we're dead.This is mostly for NPCMaker functionality.
|
|
CBaseEntity *pOwner = GetOwnerEntity();
|
|
if ( pOwner )
|
|
{
|
|
pOwner->DeathNotice( this );
|
|
SetOwnerEntity( NULL );
|
|
}
|
|
|
|
if( IsPlayer() )
|
|
{
|
|
//never just delete players
|
|
engine->ServerCommand( UTIL_VarArgs( "kickid %d CBaseEntity::InputKill()\n", engine->GetPlayerUserId( edict() ) ) );
|
|
}
|
|
else
|
|
{
|
|
UTIL_Remove( this );
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::InputKillHierarchy( inputdata_t &inputdata )
|
|
{
|
|
CBaseEntity *pChild, *pNext;
|
|
for ( pChild = FirstMoveChild(); pChild; pChild = pNext )
|
|
{
|
|
pNext = pChild->NextMovePeer();
|
|
pChild->InputKillHierarchy( inputdata );
|
|
}
|
|
|
|
// tell owner ( if any ) that we're dead. This is mostly for NPCMaker functionality.
|
|
CBaseEntity *pOwner = GetOwnerEntity();
|
|
if ( pOwner )
|
|
{
|
|
pOwner->DeathNotice( this );
|
|
SetOwnerEntity( NULL );
|
|
}
|
|
|
|
UTIL_Remove( this );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Input handler for changing this entity's movement parent.
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetParent( inputdata_t &inputdata )
|
|
{
|
|
// If we had a parent attachment, clear it, because it's no longer valid.
|
|
if ( m_iParentAttachment )
|
|
{
|
|
m_iParentAttachment = 0;
|
|
}
|
|
|
|
SetParent( inputdata.value.StringID(), inputdata.pActivator );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose:
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::SetParentAttachment( const char *szInputName, const char *szAttachment, bool bMaintainOffset )
|
|
{
|
|
// Must have a parent
|
|
if ( !m_pParent )
|
|
{
|
|
Warning("ERROR: Tried to %s for entity %s (%s), but it has no parent.\n", szInputName, GetClassname(), GetDebugName() );
|
|
return;
|
|
}
|
|
|
|
// Valid only on CBaseAnimating
|
|
CBaseAnimating *pAnimating = m_pParent->GetBaseAnimating();
|
|
if ( !pAnimating )
|
|
{
|
|
Warning("ERROR: Tried to %s for entity %s (%s), but its parent has no model.\n", szInputName, GetClassname(), GetDebugName() );
|
|
return;
|
|
}
|
|
|
|
// Lookup the attachment
|
|
int iAttachment = pAnimating->LookupAttachment( szAttachment );
|
|
if ( !iAttachment )
|
|
{
|
|
Warning("ERROR: Tried to %s for entity %s (%s), but it has no attachment named %s.\n", szInputName, GetClassname(), GetDebugName(), szAttachment );
|
|
return;
|
|
}
|
|
|
|
m_iParentAttachment = iAttachment;
|
|
SetParent( m_pParent, m_iParentAttachment );
|
|
|
|
// Now move myself directly onto the attachment point
|
|
SetMoveType( MOVETYPE_NONE );
|
|
|
|
if ( !bMaintainOffset )
|
|
{
|
|
SetLocalOrigin( vec3_origin );
|
|
SetLocalAngles( vec3_angle );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Input handler for changing this entity's movement parent's attachment point
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetParentAttachment( inputdata_t &inputdata )
|
|
{
|
|
SetParentAttachment( "SetParentAttachment", inputdata.value.String(), false );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Input handler for changing this entity's movement parent's attachment point
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetParentAttachmentMaintainOffset( inputdata_t &inputdata )
|
|
{
|
|
SetParentAttachment( "SetParentAttachmentMaintainOffset", inputdata.value.String(), true );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Input handler for clearing this entity's movement parent.
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::InputClearParent( inputdata_t &inputdata )
|
|
{
|
|
SetParent( NULL );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Input handler for setting the entities local origin
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetLocalOrigin( inputdata_t &inputdata )
|
|
{
|
|
Vector tmpVec;
|
|
|
|
if (sscanf(inputdata.value.String(), "[%f %f %f]", &tmpVec[0], &tmpVec[1], &tmpVec[2]) == 0)
|
|
{
|
|
// Try sucking out 3 floats with no []s
|
|
sscanf(inputdata.value.String(), "%f %f %f", &tmpVec[0], &tmpVec[1], &tmpVec[2]);
|
|
}
|
|
|
|
// DevMsg("%s : InputSetLocalOrigin( %f %f %f )\n", GetDebugName(), tmpVec.x, tmpVec.y, tmpVec.z );
|
|
SetLocalOrigin( tmpVec );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Input handler for setting the entities local angles
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::InputSetLocalAngles( inputdata_t &inputdata )
|
|
{
|
|
Vector tmpVec;
|
|
|
|
if (sscanf(inputdata.value.String(), "[%f %f %f]", &tmpVec[0], &tmpVec[1], &tmpVec[2]) == 0)
|
|
{
|
|
// Try sucking out 3 floats with no []s
|
|
sscanf(inputdata.value.String(), "%f %f %f", &tmpVec[0], &tmpVec[1], &tmpVec[2]);
|
|
}
|
|
|
|
// DevMsg("%s : InputSetLocalAngles( %f %f %f )\n", GetDebugName(), tmpVec.x, tmpVec.y, tmpVec.z );
|
|
QAngle angles( tmpVec.x, tmpVec.y, tmpVec.z );
|
|
SetLocalAngles( angles );
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : Returns velcocity of base entity. If physically simulated gets
|
|
// velocity from physics object
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::GetVelocity(Vector *vVelocity, AngularImpulse *vAngVelocity)
|
|
{
|
|
if (GetMoveType()==MOVETYPE_VPHYSICS && m_pPhysicsObject)
|
|
{
|
|
m_pPhysicsObject->GetVelocity(vVelocity,vAngVelocity);
|
|
}
|
|
else
|
|
{
|
|
if (vVelocity != NULL)
|
|
{
|
|
*vVelocity = GetAbsVelocity();
|
|
}
|
|
if (vAngVelocity != NULL)
|
|
{
|
|
QAngle tmp = GetLocalAngularVelocity();
|
|
QAngleToAngularImpulse( tmp, *vAngVelocity );
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CBaseEntity::IsMoving()
|
|
{
|
|
Vector velocity;
|
|
GetVelocity( &velocity, NULL );
|
|
return velocity != vec3_origin;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Retrieves the coordinate frame for this entity.
|
|
// Input : forward - Receives the entity's forward vector.
|
|
// right - Receives the entity's right vector.
|
|
// up - Receives the entity's up vector.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::GetVectors(Vector* pForward, Vector* pRight, Vector* pUp) const
|
|
{
|
|
// This call is necessary to cause m_rgflCoordinateFrame to be recomputed
|
|
const matrix3x4_t &entityToWorld = EntityToWorldTransform();
|
|
|
|
if (pForward != NULL)
|
|
{
|
|
MatrixGetColumn( entityToWorld, 0, *pForward );
|
|
}
|
|
|
|
if (pRight != NULL)
|
|
{
|
|
MatrixGetColumn( entityToWorld, 1, *pRight );
|
|
*pRight *= -1.0f;
|
|
}
|
|
|
|
if (pUp != NULL)
|
|
{
|
|
MatrixGetColumn( entityToWorld, 2, *pUp );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets the model, validates that it's of the appropriate type
|
|
// Input : *szModelName -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SetModel( const char *szModelName )
|
|
{
|
|
int modelIndex = modelinfo->GetModelIndex( szModelName );
|
|
const model_t *model = modelinfo->GetModel( modelIndex );
|
|
if ( model && modelinfo->GetModelType( model ) != mod_brush )
|
|
{
|
|
Msg( "Setting CBaseEntity to non-brush model %s\n", szModelName );
|
|
}
|
|
UTIL_SetModel( this, szModelName );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
CStudioHdr *CBaseEntity::OnNewModel()
|
|
{
|
|
// Do nothing.
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Called once per frame after the server frame loop has finished and after all messages being
|
|
// sent to clients have been sent. NOTE: Only called if scheduled via AddPostClientMessageEntity() !
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::PostClientMessagesSent( void )
|
|
{
|
|
// Remove nointerp flags from entity after every frame
|
|
if ( IsEffectActive( EF_NOINTERP ) )
|
|
{
|
|
RemoveEffects( EF_NOINTERP );
|
|
}
|
|
}
|
|
|
|
//================================================================================
|
|
// TEAM HANDLING
|
|
//================================================================================
|
|
void CBaseEntity::InputSetTeam( inputdata_t &inputdata )
|
|
{
|
|
if ( IsPlayer() )
|
|
return;
|
|
|
|
ChangeTeam( inputdata.value.Int() );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Put the entity in the specified team
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::ChangeTeam( int iTeamNum )
|
|
{
|
|
m_iTeamNum = iTeamNum;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Get the Team this entity is on
|
|
//-----------------------------------------------------------------------------
|
|
CTeam *CBaseEntity::GetTeam( void ) const
|
|
{
|
|
return GetGlobalTeam( m_iTeamNum );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns true if these players are both in at least one team together
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::InSameTeam( CBaseEntity *pEntity ) const
|
|
{
|
|
if ( !pEntity )
|
|
return false;
|
|
|
|
return ( pEntity->GetTeam() == GetTeam() );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns the string name of the players team
|
|
//-----------------------------------------------------------------------------
|
|
const char *CBaseEntity::TeamID( void ) const
|
|
{
|
|
if ( GetTeam() == NULL )
|
|
return "";
|
|
|
|
return GetTeam()->GetName();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns true if the player is on the same team
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::IsInTeam( CTeam *pTeam ) const
|
|
{
|
|
return ( GetTeam() == pTeam );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::GetTeamNumber( void ) const
|
|
{
|
|
return m_iTeamNum;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::GetPendingTeamNumber( void ) const
|
|
{
|
|
return m_iPendingTeamNum;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::IsInAnyTeam( void ) const
|
|
{
|
|
return ( GetTeam() != NULL );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns the type of damage that this entity inflicts.
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::GetDamageType() const
|
|
{
|
|
return DMG_GENERIC;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// process notification
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CBaseEntity::NotifySystemEvent( CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t ¶ms )
|
|
{
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Holds an entity's previous abs origin and angles at the time of
|
|
// teleportation. Used for child & constrained entity fixup to prevent
|
|
// lazy updates of abs origins and angles from messing things up.
|
|
//-----------------------------------------------------------------------------
|
|
struct TeleportListEntry_t
|
|
{
|
|
CBaseEntity *pEntity;
|
|
Vector prevAbsOrigin;
|
|
QAngle prevAbsAngles;
|
|
};
|
|
|
|
static void TeleportEntity( CBaseEntity *pSourceEntity, TeleportListEntry_t &entry, const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity, bool bUseSlowHighAccuracyContacts )
|
|
{
|
|
CBaseEntity *pTeleport = entry.pEntity;
|
|
Vector prevOrigin = entry.prevAbsOrigin;
|
|
QAngle prevAngles = entry.prevAbsAngles;
|
|
|
|
int nSolidFlags = pTeleport->GetSolidFlags();
|
|
IPhysicsObject *pPhys = pTeleport->VPhysicsGetObject();
|
|
bool bEnablePhysCollision = false;
|
|
// by default we use a slow method that keeps contacts accurate.
|
|
// when desired it is possible to use a quicker method that trades off accuracy to save CPU
|
|
if ( pPhys && bUseSlowHighAccuracyContacts )
|
|
{
|
|
bEnablePhysCollision = pPhys->IsCollisionEnabled();
|
|
if ( bEnablePhysCollision )
|
|
{
|
|
pPhys->EnableCollisions( false );
|
|
}
|
|
pTeleport->AddSolidFlags( FSOLID_NOT_SOLID );
|
|
}
|
|
|
|
// I'm teleporting myself
|
|
if ( pSourceEntity == pTeleport )
|
|
{
|
|
if ( newAngles )
|
|
{
|
|
pTeleport->SetLocalAngles( *newAngles );
|
|
if ( pTeleport->IsPlayer() )
|
|
{
|
|
CBasePlayer *pPlayer = (CBasePlayer *)pTeleport;
|
|
pPlayer->SnapEyeAngles( *newAngles );
|
|
}
|
|
}
|
|
|
|
if ( newVelocity )
|
|
{
|
|
if ( !pPhys || pTeleport->GetMoveType() != MOVETYPE_VPHYSICS )
|
|
{
|
|
pTeleport->SetAbsVelocity( *newVelocity );
|
|
}
|
|
pTeleport->SetBaseVelocity( vec3_origin );
|
|
}
|
|
|
|
if ( newPosition )
|
|
{
|
|
pTeleport->AddEffects( EF_NOINTERP );
|
|
UTIL_SetOrigin( pTeleport, *newPosition );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// My parent is teleporting, just update my position & physics
|
|
pTeleport->CalcAbsolutePosition();
|
|
}
|
|
|
|
// handle physics objects / shadows
|
|
if ( pPhys )
|
|
{
|
|
if ( newVelocity )
|
|
{
|
|
pPhys->SetVelocity( newVelocity, NULL );
|
|
}
|
|
const QAngle *rotAngles = &pTeleport->GetAbsAngles();
|
|
// don't rotate physics on players or bbox entities
|
|
if (pTeleport->IsPlayer() || pTeleport->GetSolid() == SOLID_BBOX )
|
|
{
|
|
if ( newAngles )
|
|
{
|
|
rotAngles = newAngles;
|
|
}
|
|
else
|
|
{
|
|
rotAngles = &vec3_angle;
|
|
}
|
|
}
|
|
|
|
pPhys->SetPosition( pTeleport->GetAbsOrigin(), *rotAngles, bUseSlowHighAccuracyContacts );
|
|
}
|
|
|
|
g_pNotify->ReportTeleportEvent( pTeleport, prevOrigin, prevAngles, true );
|
|
|
|
if ( pPhys && bUseSlowHighAccuracyContacts )
|
|
{
|
|
pTeleport->SetSolidFlags( nSolidFlags );
|
|
if ( bEnablePhysCollision )
|
|
{
|
|
pPhys->EnableCollisions( true );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Recurses an entity hierarchy and fills out a list of all entities
|
|
// in the hierarchy with their current origins and angles.
|
|
//
|
|
// This list is necessary to keep lazy updates of abs origins and angles
|
|
// from messing up our child/constrained entity fixup.
|
|
//-----------------------------------------------------------------------------
|
|
static void BuildTeleportList_r( CBaseEntity *pTeleport, CUtlVector<TeleportListEntry_t> &teleportList )
|
|
{
|
|
TeleportListEntry_t entry;
|
|
|
|
entry.pEntity = pTeleport;
|
|
entry.prevAbsOrigin = pTeleport->GetAbsOrigin();
|
|
entry.prevAbsAngles = pTeleport->GetAbsAngles();
|
|
|
|
teleportList.AddToTail( entry );
|
|
|
|
CBaseEntity *pList = pTeleport->FirstMoveChild();
|
|
while ( pList )
|
|
{
|
|
BuildTeleportList_r( pList, teleportList );
|
|
pList = pList->NextMovePeer();
|
|
}
|
|
}
|
|
|
|
|
|
static CUtlVector<CBaseEntity *> g_TeleportStack;
|
|
void CBaseEntity::Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity, bool bUseSlowHighAccuracyContacts )
|
|
{
|
|
if ( g_TeleportStack.Find( this ) >= 0 )
|
|
return;
|
|
int index = g_TeleportStack.AddToTail( this );
|
|
|
|
CUtlVector<TeleportListEntry_t> teleportList;
|
|
BuildTeleportList_r( this, teleportList );
|
|
|
|
int i;
|
|
for ( i = 0; i < teleportList.Count(); i++)
|
|
{
|
|
TeleportEntity( this, teleportList[i], newPosition, newAngles, newVelocity, bUseSlowHighAccuracyContacts );
|
|
}
|
|
|
|
Assert( g_TeleportStack[index] == this );
|
|
g_TeleportStack.FastRemove( index );
|
|
|
|
// FIXME: add an initializer function to StepSimulationData
|
|
StepSimulationData *step = ( StepSimulationData * )GetDataObject( STEPSIMULATION );
|
|
if (step)
|
|
{
|
|
Q_memset( step, 0, sizeof( *step ) );
|
|
}
|
|
}
|
|
|
|
// Stuff implemented for weapon prediction code
|
|
void CBaseEntity::SetSize( const Vector &vecMin, const Vector &vecMax )
|
|
{
|
|
UTIL_SetSize( this, vecMin, vecMax );
|
|
}
|
|
|
|
CStudioHdr *ModelSoundsCache_LoadModel( const char *filename )
|
|
{
|
|
// Load the file
|
|
int idx = engine->PrecacheModel( filename, true );
|
|
if ( idx != -1 )
|
|
{
|
|
model_t *mdl = (model_t *)modelinfo->GetModel( idx );
|
|
if ( mdl )
|
|
{
|
|
CStudioHdr *studioHdr = new CStudioHdr( modelinfo->GetStudiomodel( mdl ), mdlcache );
|
|
if ( studioHdr->IsValid() )
|
|
{
|
|
return studioHdr;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void ModelSoundsCache_FinishModel( CStudioHdr *hdr )
|
|
{
|
|
Assert( hdr );
|
|
delete hdr;
|
|
}
|
|
|
|
void ModelSoundsCache_PrecacheScriptSound( const char *soundname )
|
|
{
|
|
CBaseEntity::PrecacheScriptSound( soundname );
|
|
}
|
|
|
|
static CUtlCachedFileData< CModelSoundsCache > g_ModelSoundsCache( "modelsounds.cache", MODELSOUNDSCACHE_VERSION, 0, UTL_CACHED_FILE_USE_FILESIZE, false );
|
|
|
|
void ClearModelSoundsCache()
|
|
{
|
|
if ( IsGameConsole() || engine->IsCreatingXboxReslist() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
g_ModelSoundsCache.Reload();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool ModelSoundsCacheInit()
|
|
{
|
|
if ( IsGameConsole() || engine->IsCreatingXboxReslist() )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return g_ModelSoundsCache.Init();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void ModelSoundsCacheShutdown()
|
|
{
|
|
if ( IsGameConsole() || engine->IsCreatingXboxReslist() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
g_ModelSoundsCache.Shutdown();
|
|
}
|
|
|
|
static CUtlSymbolTable g_ModelSoundsSymbolHelper( 0, 32, true );
|
|
class CModelSoundsCacheSaver: public CAutoGameSystem
|
|
{
|
|
public:
|
|
CModelSoundsCacheSaver( const char *name ) : CAutoGameSystem( name )
|
|
{
|
|
}
|
|
virtual void LevelInitPostEntity()
|
|
{
|
|
if ( IsGameConsole() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( g_ModelSoundsCache.IsDirty() )
|
|
{
|
|
g_ModelSoundsCache.Save();
|
|
}
|
|
}
|
|
virtual void LevelShutdownPostEntity()
|
|
{
|
|
if ( IsGameConsole() )
|
|
{
|
|
// Unforunate that this table must persist through duration of level.
|
|
// It is the common case that PrecacheModel() still gets called (and needs this table),
|
|
// after LevelInitPostEntity, as PrecacheModel() redundantly precaches.
|
|
g_ModelSoundsSymbolHelper.RemoveAll();
|
|
return;
|
|
}
|
|
|
|
if ( g_ModelSoundsCache.IsDirty() )
|
|
{
|
|
g_ModelSoundsCache.Save();
|
|
}
|
|
}
|
|
};
|
|
|
|
static CModelSoundsCacheSaver g_ModelSoundsCacheSaver( "CModelSoundsCacheSaver" );
|
|
|
|
//#define WATCHACCESS
|
|
#if defined( WATCHACCESS )
|
|
|
|
static bool g_bWatching = true;
|
|
|
|
void ModelLogFunc( const char *fileName, const char *accessType )
|
|
{
|
|
if ( g_bWatching && !CBaseEntity::IsPrecacheAllowed() )
|
|
{
|
|
if ( Q_stristr( fileName, ".vcd" ) )
|
|
{
|
|
Msg( "%s\n", fileName );
|
|
}
|
|
}
|
|
}
|
|
|
|
class CWatchForModelAccess: public CAutoGameSystem
|
|
{
|
|
public:
|
|
virtual bool Init()
|
|
{
|
|
filesystem->AddLoggingFunc(&ModelLogFunc);
|
|
return true;
|
|
}
|
|
|
|
virtual void Shutdown()
|
|
{
|
|
filesystem->RemoveLoggingFunc(&ModelLogFunc);
|
|
}
|
|
|
|
};
|
|
|
|
static CWatchForModelAccess g_WatchForModels;
|
|
|
|
#endif
|
|
|
|
// HACK: This must match the #define in cl_animevent.h in the client .dll code!!!
|
|
#define CL_EVENT_SOUND 5004
|
|
#define CL_EVENT_FOOTSTEP_LEFT 6004
|
|
#define CL_EVENT_FOOTSTEP_RIGHT 6005
|
|
#define CL_EVENT_MFOOTSTEP_LEFT 6006
|
|
#define CL_EVENT_MFOOTSTEP_RIGHT 6007
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Precache model sound. Requires a local symbol table to prevent
|
|
// a very expensive call to PrecacheScriptSound().
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::PrecacheSoundHelper( const char *pName )
|
|
{
|
|
if ( !IsGameConsole() )
|
|
{
|
|
// 360 only
|
|
Assert( 0 );
|
|
return;
|
|
}
|
|
|
|
if ( !pName || !pName[0] )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( UTL_INVAL_SYMBOL == g_ModelSoundsSymbolHelper.Find( pName ) )
|
|
{
|
|
g_ModelSoundsSymbolHelper.AddString( pName );
|
|
|
|
// very expensive, only call when required
|
|
PrecacheScriptSound( pName );
|
|
}
|
|
}
|
|
|
|
class CModelPrecacheSystem : public CAutoGameSystem
|
|
{
|
|
public:
|
|
CModelPrecacheSystem() : CAutoGameSystem( "CModelPrecacheSystem" ), m_RepeatCounts( 0, 0, DefLessFunc( int ) )
|
|
{
|
|
}
|
|
|
|
// Level init, shutdown
|
|
virtual void LevelShutdownPreEntity()
|
|
{
|
|
m_RepeatCounts.Purge();
|
|
}
|
|
|
|
bool ShouldPrecache( int nModelIndex )
|
|
{
|
|
int slot = m_RepeatCounts.Find( nModelIndex );
|
|
if ( slot != m_RepeatCounts.InvalidIndex() )
|
|
{
|
|
m_RepeatCounts[ slot ]++;
|
|
return false;
|
|
}
|
|
|
|
m_RepeatCounts.Insert( nModelIndex, 0 );
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
|
|
CUtlMap< int, int > m_RepeatCounts;
|
|
};
|
|
|
|
static CModelPrecacheSystem g_ModelPrecacheSystem;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Precache model components
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::PrecacheModelComponents( int nModelIndex )
|
|
{
|
|
if ( !g_ModelPrecacheSystem.ShouldPrecache( nModelIndex ) )
|
|
return;
|
|
|
|
model_t *pModel = (model_t *)modelinfo->GetModel( nModelIndex );
|
|
if ( !pModel || modelinfo->GetModelType( pModel ) != mod_studio )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// sounds
|
|
if ( IsPC() )
|
|
{
|
|
const char *name = modelinfo->GetModelName( pModel );
|
|
if ( !g_ModelSoundsCache.EntryExists( name ) )
|
|
{
|
|
char extension[ 8 ];
|
|
Q_ExtractFileExtension( name, extension, sizeof( extension ) );
|
|
|
|
if ( Q_stristr( extension, "mdl" ) )
|
|
{
|
|
DevMsg( 2, "Late precache of %s, need to rebuild modelsounds.cache\n", name );
|
|
}
|
|
else
|
|
{
|
|
if ( !extension[ 0 ] )
|
|
{
|
|
Warning( "Precache of %s ambigious (no extension specified)\n", name );
|
|
}
|
|
else
|
|
{
|
|
Warning( "Late precache of %s (file missing?)\n", name );
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
CModelSoundsCache *entry = g_ModelSoundsCache.Get( name );
|
|
Assert( entry );
|
|
if ( entry )
|
|
{
|
|
entry->PrecacheSoundList();
|
|
}
|
|
}
|
|
|
|
// particles
|
|
{
|
|
// Check keyvalues for auto-emitting particles
|
|
KeyValues *pModelKeyValues = new KeyValues("");
|
|
KeyValues::AutoDelete autodelete_pModelKeyValues( pModelKeyValues );
|
|
if ( pModelKeyValues->LoadFromBuffer( modelinfo->GetModelName( pModel ), modelinfo->GetModelKeyValueText( pModel ) ) )
|
|
{
|
|
static int keyParticles = KeyValuesSystem()->GetSymbolForString( "Particles" );
|
|
static int keyName = KeyValuesSystem()->GetSymbolForString( "name" );
|
|
|
|
KeyValues *pParticleEffects = pModelKeyValues->FindKey(keyParticles);
|
|
if ( pParticleEffects )
|
|
{
|
|
// Start grabbing the sounds and slotting them in
|
|
for ( KeyValues *pSingleEffect = pParticleEffects->GetFirstSubKey(); pSingleEffect; pSingleEffect = pSingleEffect->GetNextKey() )
|
|
{
|
|
const char *pParticleEffectName = pSingleEffect->GetString( keyName, "" );
|
|
PrecacheParticleSystem( pParticleEffectName );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// model anim event owned components
|
|
{
|
|
// Check animevents for particle events
|
|
CStudioHdr studioHdr( modelinfo->GetStudiomodel( pModel ), mdlcache );
|
|
if ( studioHdr.IsValid() )
|
|
{
|
|
// force animation event resolution!!!
|
|
VerifySequenceIndex( &studioHdr );
|
|
|
|
int nSeqCount = studioHdr.GetNumSeq();
|
|
for ( int i = 0; i < nSeqCount; ++i )
|
|
{
|
|
mstudioseqdesc_t &seq = studioHdr.pSeqdesc( i );
|
|
int nEventCount = seq.numevents;
|
|
for ( int j = 0; j < nEventCount; ++j )
|
|
{
|
|
mstudioevent_t *pEvent = (mstudioevent_for_client_server_t*)seq.pEvent( j );
|
|
|
|
int nEvent = pEvent->Event();
|
|
|
|
if ( !( pEvent->type & AE_TYPE_NEWEVENTSYSTEM ) || ( pEvent->type & AE_TYPE_CLIENT ) )
|
|
{
|
|
if ( nEvent == AE_CL_CREATE_PARTICLE_EFFECT )
|
|
{
|
|
char token[256];
|
|
const char *pOptions = pEvent->pszOptions();
|
|
nexttoken( token, pOptions, ' ' );
|
|
if ( token )
|
|
{
|
|
PrecacheParticleSystem( token );
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// 360 precaches the model sounds now at init time, the cost is now ~250 msecs worst case.
|
|
// The disk based solution was not needed. Now at runtime partly due to already crawling the sequences
|
|
// for the particles and the expensive part was redundant PrecacheScriptSound(), which is now prevented
|
|
// by a local symbol table.
|
|
if ( IsGameConsole() )
|
|
{
|
|
switch ( nEvent )
|
|
{
|
|
default:
|
|
{
|
|
if ( ( pEvent->type & AE_TYPE_NEWEVENTSYSTEM ) && ( nEvent == AE_SV_PLAYSOUND ) )
|
|
{
|
|
PrecacheSoundHelper( pEvent->pszOptions() );
|
|
}
|
|
}
|
|
break;
|
|
case CL_EVENT_FOOTSTEP_LEFT:
|
|
case CL_EVENT_FOOTSTEP_RIGHT:
|
|
{
|
|
char soundname[256];
|
|
char const *options = pEvent->pszOptions();
|
|
if ( !options || !options[0] )
|
|
{
|
|
options = "NPC_CombineS";
|
|
}
|
|
|
|
Q_snprintf( soundname, sizeof( soundname ), "%s.RunFootstepLeft", options );
|
|
PrecacheSoundHelper( soundname );
|
|
Q_snprintf( soundname, sizeof( soundname ), "%s.RunFootstepRight", options );
|
|
PrecacheSoundHelper( soundname );
|
|
Q_snprintf( soundname, sizeof( soundname ), "%s.FootstepLeft", options );
|
|
PrecacheSoundHelper( soundname );
|
|
Q_snprintf( soundname, sizeof( soundname ), "%s.FootstepRight", options );
|
|
PrecacheSoundHelper( soundname );
|
|
}
|
|
break;
|
|
case AE_CL_PLAYSOUND:
|
|
{
|
|
if ( !( pEvent->type & AE_TYPE_CLIENT ) )
|
|
break;
|
|
|
|
if ( pEvent->pszOptions()[0] )
|
|
{
|
|
PrecacheSoundHelper( pEvent->pszOptions() );
|
|
}
|
|
else
|
|
{
|
|
Warning( "-- Error --: empty soundname, .qc error on AE_CL_PLAYSOUND in model %s, sequence %s, animevent # %i\n",
|
|
studioHdr.GetRenderHdr()->pszName(), seq.pszLabel(), j+1 );
|
|
}
|
|
}
|
|
break;
|
|
case CL_EVENT_SOUND:
|
|
case SCRIPT_EVENT_SOUND:
|
|
case SCRIPT_EVENT_SOUND_VOICE:
|
|
{
|
|
PrecacheSoundHelper( pEvent->pszOptions() );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Add model to level precache list
|
|
// Input : *name - model name
|
|
// Output : int -- model index for model
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::PrecacheModel( const char *name, bool bPreload )
|
|
{
|
|
if ( !name || !*name )
|
|
{
|
|
Msg( "Attempting to precache model, but model name is NULL\n");
|
|
return -1;
|
|
}
|
|
|
|
// Warn on out of order precache
|
|
if ( !CBaseEntity::IsPrecacheAllowed() )
|
|
{
|
|
if ( !engine->IsModelPrecached( name ) )
|
|
{
|
|
Assert( !"CBaseEntity::PrecacheModel: too late" );
|
|
Warning( "Late precache of %s\n", name );
|
|
}
|
|
}
|
|
#if defined( WATCHACCESS )
|
|
else
|
|
{
|
|
g_bWatching = false;
|
|
}
|
|
#endif
|
|
|
|
int idx = engine->PrecacheModel( name, bPreload );
|
|
if ( idx != -1 )
|
|
{
|
|
PrecacheModelComponents( idx );
|
|
}
|
|
|
|
#if defined( WATCHACCESS )
|
|
g_bWatching = true;
|
|
#endif
|
|
|
|
return idx;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::Remove( )
|
|
{
|
|
UTIL_Remove( this );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// VScript access to model's key values
|
|
// for iteration and value access, use:
|
|
// ScriptFindKey, ScriptGetFirstSubKey, ScriptGetString,
|
|
// ScriptGetInt, ScriptGetFloat, ScriptGetNextKey
|
|
//-----------------------------------------------------------------------------
|
|
HSCRIPT CBaseEntity::ScriptGetModelKeyValues( void )
|
|
{
|
|
KeyValues *pModelKeyValues = new KeyValues("");
|
|
HSCRIPT hScript = NULL;
|
|
const char *pszModelName = modelinfo->GetModelName( GetModel() );
|
|
const char *pBuffer = modelinfo->GetModelKeyValueText( GetModel() ) ;
|
|
|
|
if ( pModelKeyValues->LoadFromBuffer( pszModelName, pBuffer ) )
|
|
{
|
|
// UNDONE: how does destructor get called on this
|
|
m_pScriptModelKeyValues = new CScriptKeyValues( pModelKeyValues );
|
|
|
|
// UNDONE: who calls ReleaseInstance on this??? Does name need to be unique???
|
|
|
|
hScript = g_pScriptVM->RegisterInstance( m_pScriptModelKeyValues );
|
|
|
|
/*
|
|
KeyValues *pParticleEffects = pModelKeyValues->FindKey("Particles");
|
|
if ( pParticleEffects )
|
|
{
|
|
// Start grabbing the sounds and slotting them in
|
|
for ( KeyValues *pSingleEffect = pParticleEffects->GetFirstSubKey(); pSingleEffect; pSingleEffect = pSingleEffect->GetNextKey() )
|
|
{
|
|
const char *pParticleEffectName = pSingleEffect->GetString( "name", "" );
|
|
PrecacheParticleSystem( pParticleEffectName );
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|
|
|
|
|
|
return hScript;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::ScriptPrecacheModel( const char *name )
|
|
{
|
|
PrecacheModel( name );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::ScriptPrecacheScriptSound( const char *name )
|
|
{
|
|
PrecacheScriptSound( name );
|
|
}
|
|
|
|
// Entity degugging console commands
|
|
extern void SetDebugBits( CBasePlayer* pPlayer, const char *name, int bit );
|
|
extern CBaseEntity *GetNextCommandEntity( CBasePlayer *pPlayer, const char *name, CBaseEntity *ent );
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void ConsoleFireTargets( CBasePlayer *pPlayer, const char *name)
|
|
{
|
|
// If no name was given use the picker
|
|
if (FStrEq(name,""))
|
|
{
|
|
CBaseEntity *pEntity = pPlayer ? pPlayer->FindPickerEntity() : NULL;
|
|
if ( pEntity && !pEntity->IsMarkedForDeletion())
|
|
{
|
|
Msg( "[%03d] Found: %s, firing\n", gpGlobals->tickcount%1000, pEntity->GetDebugName());
|
|
pEntity->Use( pPlayer, pPlayer, USE_TOGGLE, 0 );
|
|
return;
|
|
}
|
|
}
|
|
// Otherwise use name or classname
|
|
FireTargets( name, pPlayer, pPlayer, USE_TOGGLE, 0 );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
//------------------------------------------------------------------------------
|
|
void DumpScriptScope( CBasePlayer* pPlayer, const char *name)
|
|
{
|
|
CBaseEntity *pEntity = NULL;
|
|
while ( (pEntity = GetNextCommandEntity( pPlayer, name, pEntity )) != NULL )
|
|
{
|
|
if( pEntity->m_ScriptScope.IsInitialized() )
|
|
{
|
|
Msg("----Script Dump for entity %s\n", pEntity->GetDebugName() );
|
|
HSCRIPT hDumpScopeFunc = g_pScriptVM->LookupFunction( "__DumpScope" );
|
|
g_pScriptVM->Call( hDumpScopeFunc, NULL, true, NULL, 1,(HSCRIPT)pEntity->m_ScriptScope );
|
|
Msg("----End Script Dump\n" );
|
|
}
|
|
else
|
|
{
|
|
DevWarning( "ent_script_dump: Entity %s has no script scope!\n", pEntity->GetDebugName() );
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Name( const CCommand& args )
|
|
{
|
|
SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_NAME_BIT);
|
|
}
|
|
static ConCommand ent_name("ent_name", CC_Ent_Name, 0, FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Text( const CCommand& args )
|
|
{
|
|
SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_TEXT_BIT);
|
|
}
|
|
static ConCommand ent_text("ent_text", CC_Ent_Text, "Displays text debugging information about the given entity(ies) on top of the entity (See Overlay Text)\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Script_Dump( const CCommand& args )
|
|
{
|
|
DumpScriptScope(UTIL_GetCommandClient(),args[1]);
|
|
}
|
|
static ConCommand ent_script_dump("ent_script_dump", CC_Ent_Script_Dump, "Dumps the names and values of this entity's script scope to the console\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_BBox( const CCommand& args )
|
|
{
|
|
SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_BBOX_BIT);
|
|
}
|
|
static ConCommand ent_bbox("ent_bbox", CC_Ent_BBox, "Displays the movement bounding box for the given entity(ies) in orange. Some entites will also display entity specific overlays.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_AbsBox( const CCommand& args )
|
|
{
|
|
SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_ABSBOX_BIT);
|
|
}
|
|
static ConCommand ent_absbox("ent_absbox", CC_Ent_AbsBox, "Displays the total bounding box for the given entity(s) in green. Some entites will also display entity specific overlays.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_RBox( const CCommand& args )
|
|
{
|
|
SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_RBOX_BIT);
|
|
}
|
|
static ConCommand ent_rbox("ent_rbox", CC_Ent_RBox, "Displays the total bounding box for the given entity(s) in green. Some entites will also display entity specific overlays.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_AttachmentPoints( const CCommand& args )
|
|
{
|
|
SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_ATTACHMENTS_BIT);
|
|
}
|
|
static ConCommand ent_attachments("ent_attachments", CC_Ent_AttachmentPoints, "Displays the attachment points on an entity.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_ViewOffset( const CCommand& args )
|
|
{
|
|
SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_VIEWOFFSET);
|
|
}
|
|
static ConCommand ent_viewoffset("ent_viewoffset", CC_Ent_ViewOffset, "Displays the eye position for the given entity(ies) in red.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Remove( const CCommand& args )
|
|
{
|
|
CBaseEntity *pEntity = NULL;
|
|
|
|
// If no name was given set bits based on the picked
|
|
if ( FStrEq( args[1],"") )
|
|
{
|
|
pEntity = UTIL_GetCommandClient() ? UTIL_GetCommandClient()->FindPickerEntity() : NULL;
|
|
}
|
|
else
|
|
{
|
|
int index = atoi( args[1] );
|
|
if ( index )
|
|
{
|
|
pEntity = CBaseEntity::Instance( index );
|
|
}
|
|
else
|
|
{
|
|
// Otherwise set bits based on name or classname
|
|
CBaseEntity *ent = NULL;
|
|
while ( (ent = gEntList.NextEnt(ent)) != NULL )
|
|
{
|
|
if ( (ent->GetEntityName() != NULL_STRING && FStrEq(args[1], STRING(ent->GetEntityName()))) ||
|
|
(ent->m_iClassname != NULL_STRING && FStrEq(args[1], STRING(ent->m_iClassname))) ||
|
|
(ent->GetClassname()!=NULL && FStrEq(args[1], ent->GetClassname())))
|
|
{
|
|
pEntity = ent;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Found one?
|
|
if ( pEntity )
|
|
{
|
|
Msg( "Removed %s(%s)\n", STRING(pEntity->m_iClassname), pEntity->GetDebugName() );
|
|
UTIL_Remove( pEntity );
|
|
}
|
|
}
|
|
static ConCommand ent_remove("ent_remove", CC_Ent_Remove, "Removes the given entity(s)\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_RemoveAll( const CCommand& args )
|
|
{
|
|
// If no name was given remove based on the picked
|
|
if ( args.ArgC() < 2 )
|
|
{
|
|
Msg( "Removes all entities of the specified type\n\tArguments: {entity_name} / {class_name}\n" );
|
|
}
|
|
else
|
|
{
|
|
// Otherwise remove based on name or classname
|
|
int iCount = 0;
|
|
CBaseEntity *ent = NULL;
|
|
while ( (ent = gEntList.NextEnt(ent)) != NULL )
|
|
{
|
|
if ( (ent->GetEntityName() != NULL_STRING && FStrEq(args[1], STRING(ent->GetEntityName()))) ||
|
|
(ent->m_iClassname != NULL_STRING && FStrEq(args[1], STRING(ent->m_iClassname))) ||
|
|
(ent->GetClassname()!=NULL && FStrEq(args[1], ent->GetClassname())))
|
|
{
|
|
UTIL_Remove( ent );
|
|
iCount++;
|
|
}
|
|
}
|
|
|
|
if ( iCount )
|
|
{
|
|
Msg( "Removed %d %s's\n", iCount, args[1] );
|
|
}
|
|
else
|
|
{
|
|
Msg( "No %s found.\n", args[1] );
|
|
}
|
|
}
|
|
}
|
|
static ConCommand ent_remove_all("ent_remove_all", CC_Ent_RemoveAll, "Removes all entities of the specified type\n\tArguments: {entity_name} / {class_name} ", FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_SetName( const CCommand& args )
|
|
{
|
|
CBaseEntity *pEntity = NULL;
|
|
|
|
if ( args.ArgC() < 1 )
|
|
{
|
|
CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() );
|
|
if (!pPlayer)
|
|
return;
|
|
|
|
ClientPrint( pPlayer, HUD_PRINTCONSOLE, "Usage:\n ent_setname <new name> <entity name>\n" );
|
|
}
|
|
else
|
|
{
|
|
// If no name was given set bits based on the picked
|
|
if ( FStrEq( args[2],"") )
|
|
{
|
|
pEntity = UTIL_GetCommandClient() ? UTIL_GetCommandClient()->FindPickerEntity() : NULL;
|
|
}
|
|
else
|
|
{
|
|
// Otherwise set bits based on name or classname
|
|
CBaseEntity *ent = NULL;
|
|
while ( (ent = gEntList.NextEnt(ent)) != NULL )
|
|
{
|
|
if ( (ent->GetEntityName() != NULL_STRING && FStrEq(args[1], STRING(ent->GetEntityName()))) ||
|
|
(ent->m_iClassname != NULL_STRING && FStrEq(args[1], STRING(ent->m_iClassname))) ||
|
|
(ent->GetClassname()!=NULL && FStrEq(args[1], ent->GetClassname())))
|
|
{
|
|
pEntity = ent;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Found one?
|
|
if ( pEntity )
|
|
{
|
|
Msg( "Set the name of %s to %s\n", STRING(pEntity->m_iClassname), args[1] );
|
|
pEntity->SetName( AllocPooledString( args[1] ) );
|
|
}
|
|
}
|
|
}
|
|
static ConCommand ent_setname("ent_setname", CC_Ent_SetName, "Sets the targetname of the given entity(s)\n\tArguments: {new entity name} {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Find_Ent( const CCommand& args )
|
|
{
|
|
if ( args.ArgC() < 2 )
|
|
{
|
|
Msg( "Format: find_ent <substring>\n" );
|
|
return;
|
|
}
|
|
|
|
int iCount = 0;
|
|
const char *pszSubString = args[1];
|
|
Msg("Searching for entities with class/target name containing substring: '%s'\n", pszSubString );
|
|
|
|
CBaseEntity *ent = NULL;
|
|
while ( (ent = gEntList.NextEnt(ent)) != NULL )
|
|
{
|
|
const char *pszClassname = ent->GetClassname();
|
|
const char *pszTargetname = STRING(ent->GetEntityName());
|
|
|
|
bool bMatches = false;
|
|
if ( pszClassname && pszClassname[0] )
|
|
{
|
|
if ( Q_stristr( pszClassname, pszSubString ) )
|
|
{
|
|
bMatches = true;
|
|
}
|
|
}
|
|
|
|
if ( !bMatches && pszTargetname && pszTargetname[0] )
|
|
{
|
|
if ( Q_stristr( pszTargetname, pszSubString ) )
|
|
{
|
|
bMatches = true;
|
|
}
|
|
}
|
|
|
|
if ( bMatches )
|
|
{
|
|
iCount++;
|
|
Msg(" '%s' : '%s' (entindex %d) \n", ent->GetClassname(), ent->GetEntityName().ToCStr(), ent->entindex() );
|
|
}
|
|
}
|
|
|
|
Msg("Found %d matches.\n", iCount);
|
|
}
|
|
static ConCommand find_ent("find_ent", CC_Find_Ent, "Find and list all entities with classnames or targetnames that contain the specified substring.\nFormat: find_ent <substring>\n", FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Find_Ent_Index( const CCommand& args )
|
|
{
|
|
if ( args.ArgC() < 2 )
|
|
{
|
|
Msg( "Format: find_ent_index <index>\n" );
|
|
return;
|
|
}
|
|
|
|
int iIndex = atoi(args[1]);
|
|
CBaseEntity *pEnt = UTIL_EntityByIndex( iIndex );
|
|
if ( pEnt )
|
|
{
|
|
Msg(" '%s' : '%s' (entindex %d) \n", pEnt->GetClassname(), pEnt->GetEntityName().ToCStr(), iIndex );
|
|
}
|
|
else
|
|
{
|
|
Msg("Found no entity at %d.\n", iIndex);
|
|
}
|
|
}
|
|
static ConCommand find_ent_index("find_ent_index", CC_Find_Ent_Index, "Display data for entity matching specified index.\nFormat: find_ent_index <index>\n", FCVAR_CHEAT);
|
|
|
|
void DumpEntity( CBaseEntity *ent )
|
|
{
|
|
for ( datamap_t *dmap = ent->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap )
|
|
{
|
|
// search through all the actions in the data description, printing out details
|
|
for ( int i = 0; i < dmap->dataNumFields; i++ )
|
|
{
|
|
variant_t var;
|
|
if ( ent->ReadKeyField( dmap->dataDesc[i].externalName, &var) )
|
|
{
|
|
char buf[256];
|
|
buf[0] = 0;
|
|
switch( var.FieldType() )
|
|
{
|
|
case FIELD_STRING:
|
|
Q_strncpy( buf, var.String() ,sizeof(buf));
|
|
break;
|
|
case FIELD_INTEGER:
|
|
if ( var.Int() )
|
|
Q_snprintf( buf,sizeof(buf), "%d", var.Int() );
|
|
break;
|
|
case FIELD_FLOAT:
|
|
if ( var.Float() )
|
|
Q_snprintf( buf,sizeof(buf), "%.2f", var.Float() );
|
|
break;
|
|
case FIELD_EHANDLE:
|
|
{
|
|
// get the entities name
|
|
if ( var.Entity() )
|
|
{
|
|
Q_snprintf( buf,sizeof(buf), "%s", STRING(var.Entity()->GetEntityName()) );
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
// don't print out the duplicate keys
|
|
if ( !Q_stricmp("parentname",dmap->dataDesc[i].externalName) || !Q_stricmp("targetname",dmap->dataDesc[i].externalName) )
|
|
continue;
|
|
|
|
// don't print out empty keys
|
|
if ( buf[0] )
|
|
{
|
|
Msg( " %s: %s\n", dmap->dataDesc[i].externalName, buf );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Purpose :
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Dump( const CCommand& args )
|
|
{
|
|
CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() );
|
|
if (!pPlayer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( args.ArgC() < 2 )
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTCONSOLE, "Usage:\n ent_dump <entity name/index/class>\n" );
|
|
}
|
|
else
|
|
{
|
|
bool bFound = false;
|
|
CBaseEntity *pEntity = NULL;
|
|
while ( (pEntity = GetNextCommandEntity( pPlayer, args[1], pEntity )) != NULL )
|
|
{
|
|
bFound = true;
|
|
|
|
DumpEntity( pEntity );
|
|
}
|
|
if ( !bFound )
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTCONSOLE, "ent_dump: no such entity" );
|
|
}
|
|
}
|
|
}
|
|
static ConCommand ent_dump("ent_dump", CC_Ent_Dump, "Usage:\n ent_dump <entity name>\n", FCVAR_CHEAT);
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_FireTarget( const CCommand& args )
|
|
{
|
|
ConsoleFireTargets(UTIL_GetCommandClient(),args[1]);
|
|
}
|
|
static ConCommand firetarget("firetarget", CC_Ent_FireTarget, 0, FCVAR_CHEAT);
|
|
|
|
static bool UtlStringLessFunc( const CUtlString &lhs, const CUtlString &rhs )
|
|
{
|
|
return Q_stricmp( lhs.String(), rhs.String() ) < 0;
|
|
}
|
|
|
|
class CEntFireAutoCompletionFunctor : public ICommandCallback, public ICommandCompletionCallback
|
|
{
|
|
public:
|
|
virtual void CommandCallback( const CCommand &command )
|
|
{
|
|
CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() );
|
|
if (!pPlayer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// fires a command from the console
|
|
if ( command.ArgC() < 2 )
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTCONSOLE, "Usage:\n ent_fire <target> [action] [value] [delay]\n" );
|
|
}
|
|
else
|
|
{
|
|
const char *target = "", *action = "Use";
|
|
variant_t value;
|
|
int delay = 0;
|
|
|
|
target = STRING( AllocPooledString(command.Arg( 1 ) ) );
|
|
|
|
// Don't allow them to run anything on a point_servercommand unless they're the host player. Otherwise they can ent_fire
|
|
// and run any command on the server. Admittedly, they can only do the ent_fire if sv_cheats is on, but
|
|
// people complained about users resetting the rcon password if the server briefly turned on cheats like this:
|
|
// give point_servercommand
|
|
// ent_fire point_servercommand command "rcon_password mynewpassword"
|
|
if ( gpGlobals->maxClients > 1 && V_stricmp( target, "point_servercommand" ) == 0 )
|
|
{
|
|
if ( engine->IsDedicatedServer() )
|
|
return;
|
|
|
|
CBasePlayer *pHostPlayer = UTIL_GetListenServerHost();
|
|
if ( pPlayer != pHostPlayer )
|
|
return;
|
|
}
|
|
|
|
if ( command.ArgC() >= 3 )
|
|
{
|
|
action = STRING( AllocPooledString(command.Arg( 2 )) );
|
|
}
|
|
if ( command.ArgC() >= 4 )
|
|
{
|
|
value.SetString( AllocPooledString(command.Arg( 3 )) );
|
|
}
|
|
if ( command.ArgC() >= 5 )
|
|
{
|
|
delay = atoi( command.Arg( 4 ) );
|
|
}
|
|
|
|
g_EventQueue.AddEvent( target, action, value, delay, pPlayer, pPlayer );
|
|
}
|
|
}
|
|
|
|
virtual int CommandCompletionCallback( const char *partial, CUtlVector< CUtlString > &commands )
|
|
{
|
|
if ( !g_pGameRules )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
const char *cmdname = "ent_fire";
|
|
|
|
char *substring = (char *)partial;
|
|
if ( Q_strstr( partial, cmdname ) )
|
|
{
|
|
substring = (char *)partial + strlen( cmdname ) + 1;
|
|
}
|
|
|
|
int checklen = 0;
|
|
char *space = Q_strstr( substring, " " );
|
|
if ( space )
|
|
{
|
|
return EntFire_AutoCompleteInput( partial, commands );;
|
|
}
|
|
else
|
|
{
|
|
checklen = Q_strlen( substring );
|
|
}
|
|
|
|
CUtlRBTree< CUtlString > symbols( 0, 0, UtlStringLessFunc );
|
|
|
|
CBaseEntity *pos = NULL;
|
|
while ( ( pos = gEntList.NextEnt( pos ) ) != NULL )
|
|
{
|
|
// Check target name against partial string
|
|
if ( pos->GetEntityName() == NULL_STRING )
|
|
continue;
|
|
|
|
if ( Q_strnicmp( STRING( pos->GetEntityName() ), substring, checklen ) )
|
|
continue;
|
|
|
|
CUtlString sym = STRING( pos->GetEntityName() );
|
|
int idx = symbols.Find( sym );
|
|
if ( idx == symbols.InvalidIndex() )
|
|
{
|
|
symbols.Insert( sym );
|
|
}
|
|
|
|
// Too many
|
|
if ( symbols.Count() >= COMMAND_COMPLETION_MAXITEMS )
|
|
break;
|
|
}
|
|
|
|
// Now fill in the results
|
|
for ( int i = symbols.FirstInorder(); i != symbols.InvalidIndex(); i = symbols.NextInorder( i ) )
|
|
{
|
|
const char *name = symbols[ i ].String();
|
|
|
|
char buf[ 512 ];
|
|
Q_strncpy( buf, name, sizeof( buf ) );
|
|
Q_strlower( buf );
|
|
|
|
CUtlString command;
|
|
command = CFmtStr( "%s %s", cmdname, buf );
|
|
commands.AddToTail( command );
|
|
}
|
|
|
|
return symbols.Count();
|
|
}
|
|
private:
|
|
int EntFire_AutoCompleteInput( const char *partial, CUtlVector< CUtlString > &commands )
|
|
{
|
|
const char *cmdname = "ent_fire";
|
|
|
|
char *substring = (char *)partial;
|
|
if ( Q_strstr( partial, cmdname ) )
|
|
{
|
|
substring = (char *)partial + strlen( cmdname ) + 1;
|
|
}
|
|
|
|
int checklen = 0;
|
|
char *space = Q_strstr( substring, " " );
|
|
if ( !space )
|
|
{
|
|
Assert( !"CC_EntFireAutoCompleteInputFunc is broken\n" );
|
|
return 0;
|
|
}
|
|
|
|
checklen = Q_strlen( substring );
|
|
|
|
char targetEntity[ 256 ];
|
|
targetEntity[0] = 0;
|
|
int nEntityNameLength = (space-substring);
|
|
Q_strncat( targetEntity, substring, sizeof( targetEntity ), nEntityNameLength );
|
|
|
|
// Find the target entity by name
|
|
CBaseEntity *target = gEntList.FindEntityByName( NULL, targetEntity );
|
|
if ( target == NULL )
|
|
return 0;
|
|
|
|
CUtlRBTree< CUtlString > symbols( 0, 0, UtlStringLessFunc );
|
|
|
|
// Find the next portion of the text chain, if any (removing space)
|
|
int nInputNameLength = (checklen-nEntityNameLength-1);
|
|
|
|
// Starting past the last space, this is the remainder of the string
|
|
char *inputPartial = ( checklen > nEntityNameLength ) ? (space+1) : NULL;
|
|
|
|
for ( datamap_t *dmap = target->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap )
|
|
{
|
|
// Make sure we don't keep adding things in if the satisfied the limit
|
|
if ( symbols.Count() >= COMMAND_COMPLETION_MAXITEMS )
|
|
break;
|
|
|
|
int c = dmap->dataNumFields;
|
|
for ( int i = 0; i < c; i++ )
|
|
{
|
|
typedescription_t *field = &dmap->dataDesc[ i ];
|
|
|
|
// Only want inputs
|
|
if ( !( field->flags & FTYPEDESC_INPUT ) )
|
|
continue;
|
|
|
|
// Only want input functions
|
|
if ( field->flags & FTYPEDESC_SAVE )
|
|
continue;
|
|
|
|
// See if we've got a partial string for the input name already
|
|
if ( inputPartial != NULL )
|
|
{
|
|
if ( Q_strnicmp( inputPartial, field->externalName, nInputNameLength ) )
|
|
continue;
|
|
}
|
|
|
|
CUtlString sym = field->externalName;
|
|
|
|
int idx = symbols.Find( sym );
|
|
if ( idx == symbols.InvalidIndex() )
|
|
{
|
|
symbols.Insert( sym );
|
|
}
|
|
|
|
// Too many items have been added
|
|
if ( symbols.Count() >= COMMAND_COMPLETION_MAXITEMS )
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Now fill in the results
|
|
for ( int i = symbols.FirstInorder(); i != symbols.InvalidIndex(); i = symbols.NextInorder( i ) )
|
|
{
|
|
const char *name = symbols[ i ].String();
|
|
|
|
char buf[ 512 ];
|
|
Q_strncpy( buf, name, sizeof( buf ) );
|
|
Q_strlower( buf );
|
|
|
|
CUtlString command;
|
|
command = CFmtStr( "%s %s %s", cmdname, targetEntity, buf );
|
|
commands.AddToTail( command );
|
|
}
|
|
|
|
return symbols.Count();
|
|
}
|
|
};
|
|
|
|
static CEntFireAutoCompletionFunctor g_EntFireAutoComplete;
|
|
static ConCommand ent_fire("ent_fire", &g_EntFireAutoComplete, "Usage:\n ent_fire <target> [action] [value] [delay]\n", FCVAR_CHEAT, &g_EntFireAutoComplete );
|
|
|
|
void CC_Ent_CancelPendingEntFires( const CCommand& args )
|
|
{
|
|
if ( !UTIL_IsCommandIssuedByServerAdmin() )
|
|
return;
|
|
|
|
CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() );
|
|
if (!pPlayer)
|
|
return;
|
|
|
|
g_EventQueue.CancelEvents( pPlayer );
|
|
}
|
|
static ConCommand ent_cancelpendingentfires("ent_cancelpendingentfires", CC_Ent_CancelPendingEntFires, "Cancels all ent_fire created outputs that are currently waiting for their delay to expire." );
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Info( const CCommand& args )
|
|
{
|
|
CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() );
|
|
if (!pPlayer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( args.ArgC() < 2 )
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTCONSOLE, "Usage:\n ent_info <class name>\n" );
|
|
}
|
|
else
|
|
{
|
|
// iterate through all the ents printing out their details
|
|
CBaseEntity *ent = CreateEntityByName( args[1] );
|
|
|
|
if ( ent )
|
|
{
|
|
datamap_t *dmap;
|
|
for ( dmap = ent->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap )
|
|
{
|
|
// search through all the actions in the data description, printing out details
|
|
for ( int i = 0; i < dmap->dataNumFields; i++ )
|
|
{
|
|
if ( dmap->dataDesc[i].flags & FTYPEDESC_OUTPUT )
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTCONSOLE, UTIL_VarArgs(" output: %s\n", dmap->dataDesc[i].externalName) );
|
|
}
|
|
}
|
|
}
|
|
|
|
for ( dmap = ent->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap )
|
|
{
|
|
// search through all the actions in the data description, printing out details
|
|
for ( int i = 0; i < dmap->dataNumFields; i++ )
|
|
{
|
|
if ( dmap->dataDesc[i].flags & FTYPEDESC_INPUT )
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTCONSOLE, UTIL_VarArgs(" input: %s\n", dmap->dataDesc[i].externalName) );
|
|
}
|
|
}
|
|
}
|
|
|
|
delete ent;
|
|
}
|
|
else
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTCONSOLE, UTIL_VarArgs("no such entity %s\n", args[1]) );
|
|
}
|
|
}
|
|
}
|
|
static ConCommand ent_info("ent_info", CC_Ent_Info, "Usage:\n ent_info <class name>\n", FCVAR_CHEAT);
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Messages( const CCommand& args )
|
|
{
|
|
SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_MESSAGE_BIT);
|
|
}
|
|
static ConCommand ent_messages("ent_messages", CC_Ent_Messages ,"Toggles input/output message display for the selected entity(ies). The name of the entity will be displayed as well as any messages that it sends or receives.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at", FCVAR_CHEAT);
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Pause( void )
|
|
{
|
|
if (CBaseEntity::Debug_IsPaused())
|
|
{
|
|
Msg( "Resuming entity I/O events\n" );
|
|
CBaseEntity::Debug_Pause(false);
|
|
}
|
|
else
|
|
{
|
|
Msg( "Pausing entity I/O events\n" );
|
|
CBaseEntity::Debug_Pause(true);
|
|
}
|
|
}
|
|
static ConCommand ent_pause("ent_pause", CC_Ent_Pause, "Toggles pausing of input/output message processing for entities. When turned on processing of all message will stop. Any messages displayed with 'ent_messages' will stop fading and be displayed indefinitely. To step through the messages one by one use 'ent_step'.", FCVAR_CHEAT);
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : Enables the entity picker, revelaing debug information about the
|
|
// entity under the crosshair.
|
|
// Input : an optional command line argument "full" enables all debug info.
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Picker( void )
|
|
{
|
|
CBaseEntity::m_bInDebugSelect = CBaseEntity::m_bInDebugSelect ? false : true;
|
|
|
|
// Remember the player that's making this request
|
|
CBaseEntity::m_nDebugPlayer = UTIL_GetCommandClientIndex();
|
|
}
|
|
static ConCommand picker("picker", CC_Ent_Picker, "Toggles 'picker' mode. When picker is on, the bounding box, pivot and debugging text is displayed for whatever entity the player is looking at.\n\tArguments: full - enables all debug information", FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Pivot( const CCommand& args )
|
|
{
|
|
SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_PIVOT_BIT);
|
|
}
|
|
static ConCommand ent_pivot("ent_pivot", CC_Ent_Pivot, "Displays the pivot for the given entity(ies).\n\t(y=up=green, z=forward=blue, x=left=red). \n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Step( const CCommand& args )
|
|
{
|
|
int nSteps = atoi(args[1]);
|
|
if (nSteps <= 0)
|
|
{
|
|
nSteps = 1;
|
|
}
|
|
CBaseEntity::Debug_SetSteps(nSteps);
|
|
}
|
|
static ConCommand ent_step("ent_step", CC_Ent_Step, "When 'ent_pause' is set this will step through one waiting input / output message at a time.", FCVAR_CHEAT);
|
|
|
|
void CBaseEntity::SetCheckUntouch( bool check )
|
|
{
|
|
// Invalidate touchstamp
|
|
if ( check )
|
|
{
|
|
touchStamp++;
|
|
if ( !IsEFlagSet( EFL_CHECK_UNTOUCH ) )
|
|
{
|
|
AddEFlags( EFL_CHECK_UNTOUCH );
|
|
EntityTouch_Add( this );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RemoveEFlags( EFL_CHECK_UNTOUCH );
|
|
EntityTouch_Remove( this );
|
|
}
|
|
}
|
|
|
|
model_t *CBaseEntity::GetModel( void )
|
|
{
|
|
return (model_t *)modelinfo->GetModel( GetModelIndex() );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Calculates the absolute position of an edict in the world
|
|
// assumes the parent's absolute origin has already been calculated
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::CalcAbsolutePosition( void )
|
|
{
|
|
if (!IsEFlagSet( EFL_DIRTY_ABSTRANSFORM ))
|
|
return;
|
|
|
|
RemoveEFlags( EFL_DIRTY_ABSTRANSFORM );
|
|
|
|
// Plop the entity->parent matrix into m_rgflCoordinateFrame
|
|
AngleMatrix( m_angRotation, m_vecOrigin, m_rgflCoordinateFrame );
|
|
|
|
CBaseEntity *pMoveParent = GetMoveParent();
|
|
if ( !pMoveParent )
|
|
{
|
|
// no move parent, so just copy existing values
|
|
m_vecAbsOrigin = m_vecOrigin;
|
|
m_angAbsRotation = m_angRotation;
|
|
if ( HasDataObjectType( POSITIONWATCHER ) )
|
|
{
|
|
ReportPositionChanged( this );
|
|
}
|
|
return;
|
|
}
|
|
|
|
// concatenate with our parent's transform
|
|
matrix3x4a_t tmpMatrix;
|
|
matrix3x4a_t scratchSpace;
|
|
|
|
ConcatTransforms( GetParentToWorldTransform( scratchSpace ), m_rgflCoordinateFrame, tmpMatrix );
|
|
MatrixCopy( tmpMatrix, m_rgflCoordinateFrame );
|
|
|
|
// pull our absolute position out of the matrix
|
|
MatrixGetColumn( m_rgflCoordinateFrame, 3, m_vecAbsOrigin );
|
|
|
|
// if we have any angles, we have to extract our absolute angles from our matrix
|
|
if (( m_angRotation == vec3_angle ) && ( m_iParentAttachment == 0 ))
|
|
{
|
|
// just copy our parent's absolute angles
|
|
VectorCopy( pMoveParent->GetAbsAngles(), m_angAbsRotation );
|
|
}
|
|
else
|
|
{
|
|
MatrixAngles( m_rgflCoordinateFrame, m_angAbsRotation );
|
|
}
|
|
if ( HasDataObjectType( POSITIONWATCHER ) )
|
|
{
|
|
ReportPositionChanged( this );
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::CalcAbsoluteVelocity()
|
|
{
|
|
if (!IsEFlagSet( EFL_DIRTY_ABSVELOCITY ))
|
|
return;
|
|
|
|
RemoveEFlags( EFL_DIRTY_ABSVELOCITY );
|
|
|
|
CBaseEntity *pMoveParent = GetMoveParent();
|
|
if ( !pMoveParent )
|
|
{
|
|
m_vecAbsVelocity = m_vecVelocity;
|
|
return;
|
|
}
|
|
|
|
// This transforms the local velocity into world space
|
|
VectorRotate( m_vecVelocity, pMoveParent->EntityToWorldTransform(), m_vecAbsVelocity );
|
|
|
|
// Now add in the parent abs velocity
|
|
m_vecAbsVelocity += pMoveParent->GetAbsVelocity();
|
|
}
|
|
|
|
// FIXME: While we're using (dPitch, dYaw, dRoll) as our local angular velocity
|
|
// representation, we can't actually solve this problem
|
|
/*
|
|
void CBaseEntity::CalcAbsoluteAngularVelocity()
|
|
{
|
|
if (!IsEFlagSet( EFL_DIRTY_ABSANGVELOCITY ))
|
|
return;
|
|
|
|
RemoveEFlags( EFL_DIRTY_ABSANGVELOCITY );
|
|
|
|
CBaseEntity *pMoveParent = GetMoveParent();
|
|
if ( !pMoveParent )
|
|
{
|
|
m_vecAbsAngVelocity = m_vecAngVelocity;
|
|
return;
|
|
}
|
|
|
|
// This transforms the local ang velocity into world space
|
|
matrix3x4_t angVelToParent, angVelToWorld;
|
|
AngleMatrix( m_vecAngVelocity, angVelToParent );
|
|
ConcatTransforms( pMoveParent->EntityToWorldTransform(), angVelToParent, angVelToWorld );
|
|
MatrixAngles( angVelToWorld, m_vecAbsAngVelocity );
|
|
}
|
|
*/
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Computes the abs position of a point specified in local space
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::ComputeAbsPosition( const Vector &vecLocalPosition, Vector *pAbsPosition )
|
|
{
|
|
CBaseEntity *pMoveParent = GetMoveParent();
|
|
if ( !pMoveParent )
|
|
{
|
|
*pAbsPosition = vecLocalPosition;
|
|
}
|
|
else
|
|
{
|
|
VectorTransform( vecLocalPosition, pMoveParent->EntityToWorldTransform(), *pAbsPosition );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Computes the abs position of a point specified in local space
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::ComputeAbsDirection( const Vector &vecLocalDirection, Vector *pAbsDirection )
|
|
{
|
|
CBaseEntity *pMoveParent = GetMoveParent();
|
|
if ( !pMoveParent )
|
|
{
|
|
*pAbsDirection = vecLocalDirection;
|
|
}
|
|
else
|
|
{
|
|
VectorRotate( vecLocalDirection, pMoveParent->EntityToWorldTransform(), *pAbsDirection );
|
|
}
|
|
}
|
|
|
|
|
|
matrix3x4_t& CBaseEntity::GetParentToWorldTransform( matrix3x4_t &tempMatrix )
|
|
{
|
|
CBaseEntity *pMoveParent = GetMoveParent();
|
|
if ( !pMoveParent )
|
|
{
|
|
Assert( false );
|
|
SetIdentityMatrix( tempMatrix );
|
|
return tempMatrix;
|
|
}
|
|
|
|
if ( m_iParentAttachment != 0 )
|
|
{
|
|
MDLCACHE_CRITICAL_SECTION();
|
|
|
|
CBaseAnimating *pAnimating = pMoveParent->GetBaseAnimating();
|
|
if ( pAnimating && pAnimating->GetAttachment( m_iParentAttachment, tempMatrix ) )
|
|
{
|
|
return tempMatrix;
|
|
}
|
|
}
|
|
|
|
// If we fall through to here, then just use the move parent's abs origin and angles.
|
|
return pMoveParent->EntityToWorldTransform();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// These methods recompute local versions as well as set abs versions
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SetAbsOrigin( const Vector& absOrigin )
|
|
{
|
|
AssertMsg( absOrigin.IsValid(), "Invalid origin set" );
|
|
|
|
// This is necessary to get the other fields of m_rgflCoordinateFrame ok
|
|
CalcAbsolutePosition();
|
|
|
|
if ( m_vecAbsOrigin == absOrigin )
|
|
return;
|
|
|
|
// All children are invalid, but we are not
|
|
InvalidatePhysicsRecursive( POSITION_CHANGED );
|
|
RemoveEFlags( EFL_DIRTY_ABSTRANSFORM );
|
|
|
|
m_vecAbsOrigin = absOrigin;
|
|
|
|
MatrixSetColumn( absOrigin, 3, m_rgflCoordinateFrame );
|
|
|
|
Vector vecNewOrigin;
|
|
CBaseEntity *pMoveParent = GetMoveParent();
|
|
if (!pMoveParent)
|
|
{
|
|
vecNewOrigin = absOrigin;
|
|
}
|
|
else
|
|
{
|
|
matrix3x4_t tempMat;
|
|
matrix3x4_t &parentTransform = GetParentToWorldTransform( tempMat );
|
|
|
|
// Moveparent case: transform the abs position into local space
|
|
VectorITransform( absOrigin, parentTransform, vecNewOrigin );
|
|
}
|
|
|
|
if (m_vecOrigin != vecNewOrigin)
|
|
{
|
|
m_vecOrigin = vecNewOrigin;
|
|
SetSimulationTime( gpGlobals->curtime );
|
|
UpdateCell();
|
|
}
|
|
|
|
if ( HasDataObjectType( POSITIONWATCHER ) )
|
|
{
|
|
ReportPositionChanged( this );
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::SetAbsAngles( const QAngle& absAngles )
|
|
{
|
|
// This is necessary to get the other fields of m_rgflCoordinateFrame ok
|
|
CalcAbsolutePosition();
|
|
|
|
// FIXME: The normalize caused problems in server code like momentary_rot_button that isn't
|
|
// handling things like +/-180 degrees properly. This should be revisited.
|
|
//QAngle angleNormalize( AngleNormalize( absAngles.x ), AngleNormalize( absAngles.y ), AngleNormalize( absAngles.z ) );
|
|
|
|
if ( m_angAbsRotation == absAngles )
|
|
return;
|
|
|
|
// All children are invalid, but we are not
|
|
InvalidatePhysicsRecursive( ANGLES_CHANGED );
|
|
RemoveEFlags( EFL_DIRTY_ABSTRANSFORM );
|
|
|
|
m_angAbsRotation = absAngles;
|
|
AngleMatrix( absAngles, m_rgflCoordinateFrame );
|
|
MatrixSetColumn( m_vecAbsOrigin, 3, m_rgflCoordinateFrame );
|
|
|
|
QAngle angNewRotation;
|
|
CBaseEntity *pMoveParent = GetMoveParent();
|
|
if (!pMoveParent)
|
|
{
|
|
angNewRotation = absAngles;
|
|
}
|
|
else
|
|
{
|
|
if ( m_angAbsRotation == pMoveParent->GetAbsAngles() )
|
|
{
|
|
angNewRotation.Init( );
|
|
}
|
|
else
|
|
{
|
|
// Moveparent case: transform the abs transform into local space
|
|
matrix3x4_t worldToParent, localMatrix;
|
|
MatrixInvert( pMoveParent->EntityToWorldTransform(), worldToParent );
|
|
ConcatTransforms( worldToParent, m_rgflCoordinateFrame, localMatrix );
|
|
MatrixAngles( localMatrix, angNewRotation );
|
|
}
|
|
}
|
|
|
|
if (m_angRotation != angNewRotation)
|
|
{
|
|
m_angRotation = angNewRotation;
|
|
SetSimulationTime( gpGlobals->curtime );
|
|
|
|
// PRB TODO : HACK FIX
|
|
// This call fixes the fact that this network var does not get set for some derived classes (eg chickens)
|
|
// Since we are so close to CERT this will have to do for now, but needs re-visiting.
|
|
|
|
m_angRotation.GetForModify();
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::SetAbsVelocity( const Vector &vecAbsVelocity )
|
|
{
|
|
if ( m_vecAbsVelocity == vecAbsVelocity )
|
|
return;
|
|
|
|
// The abs velocity won't be dirty since we're setting it here
|
|
// All children are invalid, but we are not
|
|
InvalidatePhysicsRecursive( VELOCITY_CHANGED );
|
|
RemoveEFlags( EFL_DIRTY_ABSVELOCITY );
|
|
|
|
Assert( vecAbsVelocity.IsValid() );
|
|
m_vecAbsVelocity = vecAbsVelocity;
|
|
|
|
// NOTE: Do *not* do a network state change in this case.
|
|
// m_vecVelocity is only networked for the player, which is not manual mode
|
|
CBaseEntity *pMoveParent = GetMoveParent();
|
|
if (!pMoveParent)
|
|
{
|
|
m_vecVelocity = vecAbsVelocity;
|
|
return;
|
|
}
|
|
|
|
// First subtract out the parent's abs velocity to get a relative
|
|
// velocity measured in world space
|
|
Vector relVelocity;
|
|
VectorSubtract( vecAbsVelocity, pMoveParent->GetAbsVelocity(), relVelocity );
|
|
|
|
// Transform relative velocity into parent space
|
|
Vector vNew;
|
|
VectorIRotate( relVelocity, pMoveParent->EntityToWorldTransform(), vNew );
|
|
m_vecVelocity = vNew;
|
|
}
|
|
|
|
// FIXME: While we're using (dPitch, dYaw, dRoll) as our local angular velocity
|
|
// representation, we can't actually solve this problem
|
|
/*
|
|
void CBaseEntity::SetAbsAngularVelocity( const QAngle &vecAbsAngVelocity )
|
|
{
|
|
// The abs velocity won't be dirty since we're setting it here
|
|
// All children are invalid, but we are not
|
|
InvalidatePhysicsRecursive( EFL_DIRTY_ABSANGVELOCITY );
|
|
RemoveEFlags( EFL_DIRTY_ABSANGVELOCITY );
|
|
|
|
m_vecAbsAngVelocity = vecAbsAngVelocity;
|
|
|
|
CBaseEntity *pMoveParent = GetMoveParent();
|
|
if (!pMoveParent)
|
|
{
|
|
m_vecAngVelocity = vecAbsAngVelocity;
|
|
return;
|
|
}
|
|
|
|
// NOTE: We *can't* subtract out parent ang velocity, it's nonsensical
|
|
matrix3x4_t entityToWorld;
|
|
AngleMatrix( vecAbsAngVelocity, entityToWorld );
|
|
|
|
// Moveparent case: transform the abs relative angular vel into local space
|
|
matrix3x4_t worldToParent, localMatrix;
|
|
MatrixInvert( pMoveParent->EntityToWorldTransform(), worldToParent );
|
|
ConcatTransforms( worldToParent, entityToWorld, localMatrix );
|
|
MatrixAngles( localMatrix, m_vecAngVelocity );
|
|
}
|
|
*/
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Methods that modify local physics state, and let us know to compute abs state later
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SetLocalOrigin( const Vector& origin )
|
|
{
|
|
// Safety check against NaN or really huge numbers
|
|
if ( !IsEntityPositionReasonable( origin ) )
|
|
{
|
|
if ( CheckEmitReasonablePhysicsSpew() )
|
|
{
|
|
Warning( "Bad SetLocalOrigin(%f,%f,%f) on %s\n", origin.x, origin.y, origin.z, GetDebugName() );
|
|
}
|
|
Assert( false );
|
|
return;
|
|
}
|
|
|
|
if (m_vecOrigin != origin)
|
|
{
|
|
// Sanity check to make sure the origin is valid.
|
|
#ifdef _DEBUG
|
|
float largeVal = 1024 * 128;
|
|
Assert( origin.x >= -largeVal && origin.x <= largeVal );
|
|
Assert( origin.y >= -largeVal && origin.y <= largeVal );
|
|
Assert( origin.z >= -largeVal && origin.z <= largeVal );
|
|
#endif
|
|
|
|
InvalidatePhysicsRecursive( POSITION_CHANGED );
|
|
|
|
// Call set direct which flags it for change immediately, no need to check
|
|
// for change we already did!
|
|
m_vecOrigin.SetDirect( origin );
|
|
SetSimulationTime( gpGlobals->curtime );
|
|
UpdateCell();
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::SetLocalAngles( const QAngle& angles )
|
|
{
|
|
// NOTE: The angle normalize is a little expensive, but we can save
|
|
// a bunch of time in interpolation if we don't have to invalidate everything
|
|
// and sometimes it's off by a normalization amount
|
|
|
|
// FIXME: The normalize caused problems in server code like momentary_rot_button that isn't
|
|
// handling things like +/-180 degrees properly. This should be revisited.
|
|
//QAngle angleNormalize( AngleNormalize( angles.x ), AngleNormalize( angles.y ), AngleNormalize( angles.z ) );
|
|
|
|
// Safety check against NaN or really huge numbers
|
|
if ( !IsEntityQAngleReasonable( angles ) )
|
|
{
|
|
Warning( "Bad SetLocalAngles(%f,%f,%f) on %s\n", angles.x, angles.y, angles.z, GetDebugName() );
|
|
Assert( false );
|
|
return;
|
|
}
|
|
|
|
if (m_angRotation != angles)
|
|
{
|
|
InvalidatePhysicsRecursive( ANGLES_CHANGED );
|
|
m_angRotation.SetDirect( angles );
|
|
SetSimulationTime( gpGlobals->curtime );
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::SetLocalVelocity( const Vector &inVecVelocity )
|
|
{
|
|
Vector vecVelocity = inVecVelocity;
|
|
|
|
// Safety check against NaN's or really huge numbers, which can explode physics
|
|
switch ( CheckEntityVelocity( vecVelocity ) )
|
|
{
|
|
case -1:
|
|
Warning( "Discarding SetLocalVelocity(%f,%f,%f) on %s\n", vecVelocity.x, vecVelocity.y, vecVelocity.z, GetDebugName() );
|
|
Assert( false );
|
|
return;
|
|
case 0:
|
|
if ( CheckEmitReasonablePhysicsSpew() )
|
|
{
|
|
Warning( "Clamping SetLocalVelocity(%f,%f,%f) on %s\n", vecVelocity.x, vecVelocity.y, vecVelocity.z, GetDebugName() );
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (m_vecVelocity != vecVelocity)
|
|
{
|
|
InvalidatePhysicsRecursive( VELOCITY_CHANGED );
|
|
m_vecVelocity.SetDirect( vecVelocity );
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::SetLocalAngularVelocity( const QAngle &vecAngVelocity )
|
|
{
|
|
// Safety check against NaN's or really huge numbers
|
|
if ( !IsEntityQAngleVelReasonable( vecAngVelocity ) )
|
|
{
|
|
if ( CheckEmitReasonablePhysicsSpew() )
|
|
{
|
|
Warning( "Bad SetLocalAngularVelocity(%f,%f,%f) on %s\n", vecAngVelocity.x, vecAngVelocity.y, vecAngVelocity.z, GetDebugName() );
|
|
}
|
|
Assert( false );
|
|
return;
|
|
}
|
|
|
|
if (m_vecAngVelocity != vecAngVelocity)
|
|
{
|
|
// InvalidatePhysicsRecursive( EFL_DIRTY_ABSANGVELOCITY );
|
|
m_vecAngVelocity = vecAngVelocity;
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::ScriptSetLocalAngularVelocity( float pitchVel, float yawVel, float rollVel )
|
|
{
|
|
QAngle qa;
|
|
qa.Init(pitchVel,yawVel,rollVel);
|
|
SetLocalAngularVelocity(qa);
|
|
}
|
|
|
|
const Vector &CBaseEntity::ScriptGetLocalAngularVelocity( void )
|
|
{
|
|
QAngle qa = GetLocalAngularVelocity();
|
|
static Vector v;
|
|
v.x = qa.x;
|
|
v.y = qa.y;
|
|
v.z = qa.z;
|
|
return v;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Sets the local position from a transform
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SetLocalTransform( const matrix3x4_t &localTransform )
|
|
{
|
|
// FIXME: Should angles go away? Should we just use transforms?
|
|
Vector vecLocalOrigin;
|
|
QAngle vecLocalAngles;
|
|
MatrixGetColumn( localTransform, 3, vecLocalOrigin );
|
|
MatrixAngles( localTransform, vecLocalAngles );
|
|
SetLocalOrigin( vecLocalOrigin );
|
|
SetLocalAngles( vecLocalAngles );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Adjust the number of cell bits
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SetCellBits( int cellbits )
|
|
{
|
|
m_cellbits = cellbits;
|
|
m_cellwidth = ( 1 << cellbits );
|
|
UpdateCell();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Called when the origin changes and recomputes cell
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::UpdateCell()
|
|
{
|
|
register int const cellwidth = m_cellwidth; // Load it into a register
|
|
|
|
m_cellX = CellFromCoord( cellwidth, m_vecOrigin.GetX() );
|
|
m_cellY = CellFromCoord( cellwidth, m_vecOrigin.GetY() );
|
|
m_cellZ = CellFromCoord( cellwidth, m_vecOrigin.GetZ() );
|
|
|
|
|
|
// PRB TODO : HACK fix for hostage warping.
|
|
// Somehow the above code does not flag the network vars as modified, but below does...
|
|
// Needs revisiting, but at this stage it fixes our bug
|
|
|
|
m_cellX.GetForModify();
|
|
m_cellY.GetForModify();
|
|
m_cellZ.GetForModify();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Is the entity floating?
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::IsFloating()
|
|
{
|
|
if ( !IsEFlagSet(EFL_TOUCHING_FLUID) )
|
|
return false;
|
|
|
|
IPhysicsObject *pObject = VPhysicsGetObject();
|
|
if ( !pObject )
|
|
return false;
|
|
|
|
int nMaterialIndex = pObject->GetMaterialIndex();
|
|
|
|
float flDensity;
|
|
float flThickness;
|
|
float flFriction;
|
|
float flElasticity;
|
|
physprops->GetPhysicsProperties( nMaterialIndex, &flDensity,
|
|
&flThickness, &flFriction, &flElasticity );
|
|
|
|
// FIXME: This really only works for water at the moment..
|
|
// Owing the check for density == 1000
|
|
return (flDensity < 1000.0f);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Created predictable and sets up Id. Note that persist is ignored on the server.
|
|
// Input : *classname -
|
|
// *module -
|
|
// line -
|
|
// persist -
|
|
// Output : CBaseEntity
|
|
//-----------------------------------------------------------------------------
|
|
CBaseEntity *CBaseEntity::CreatePredictedEntityByName( const char *classname, const char *module, int line, bool persist /* = false */ )
|
|
{
|
|
#if !defined( NO_ENTITY_PREDICTION ) && defined( USE_PREDICTABLEID )
|
|
CBasePlayer *player = CBaseEntity::GetPredictionPlayer();
|
|
Assert( player );
|
|
|
|
CBaseEntity *ent = NULL;
|
|
|
|
int command_number = player->CurrentCommandNumber();
|
|
int player_index = player->entindex() - 1;
|
|
|
|
CPredictableId testId;
|
|
testId.Init( player_index, command_number, classname, module, line );
|
|
|
|
ent = CreateEntityByName( classname );
|
|
// No factory???
|
|
if ( !ent )
|
|
return NULL;
|
|
|
|
ent->SetPredictionEligible( true );
|
|
|
|
// Set up "shared" id number
|
|
ent->m_PredictableID.GetForModify().SetRaw( testId.GetRaw() );
|
|
|
|
return ent;
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
|
|
}
|
|
|
|
void CBaseEntity::SetPredictionEligible( bool canpredict )
|
|
{
|
|
// Nothing in game code m_bPredictionEligible = canpredict;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// These could be virtual, but only the player is overriding them
|
|
// NOTE: If you make any of these virtual, remove this implementation!!!
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::AddPoints( int score, bool bAllowNegativeScore )
|
|
{
|
|
CBasePlayer *pPlayer = ToBasePlayer(this);
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->CBasePlayer::AddPoints( score, bAllowNegativeScore );
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::AddPointsToTeam( int score, bool bAllowNegativeScore )
|
|
{
|
|
CBasePlayer *pPlayer = ToBasePlayer(this);
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->CBasePlayer::AddPointsToTeam( score, bAllowNegativeScore );
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::ViewPunch( const QAngle &angleOffset )
|
|
{
|
|
CBasePlayer *pPlayer = ToBasePlayer(this);
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->CBasePlayer::ViewPunch( angleOffset );
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::VelocityPunch( const Vector &vecForce )
|
|
{
|
|
CBasePlayer *pPlayer = ToBasePlayer(this);
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->CBasePlayer::VelocityPunch( vecForce );
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Tell clients to remove all decals from this entity
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::RemoveAllDecals( void )
|
|
{
|
|
EntityMessageBegin( this );
|
|
WRITE_BYTE( BASEENTITY_MSG_REMOVE_DECALS );
|
|
MessageEnd();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : set -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::ModifyOrAppendCriteria( AI_CriteriaSet& set )
|
|
{
|
|
// TODO
|
|
// Append chapter/day?
|
|
|
|
set.AppendCriteria( "randomnum", UTIL_VarArgs("%d", RandomInt(0,100)) );
|
|
// Append map name
|
|
set.AppendCriteria( "map", gpGlobals->mapname.ToCStr() );
|
|
// Append our classname and game name
|
|
set.AppendCriteria( "classname", GetClassname() );
|
|
set.AppendCriteria( "name", GetEntityName().ToCStr() );
|
|
|
|
// Append our health
|
|
set.AppendCriteria( "health", UTIL_VarArgs( "%i", GetHealth() ) );
|
|
|
|
float healthfrac = 0.0f;
|
|
if ( GetMaxHealth() > 0 )
|
|
{
|
|
healthfrac = (float)GetHealth() / (float)GetMaxHealth();
|
|
}
|
|
|
|
set.AppendCriteria( "healthfrac", UTIL_VarArgs( "%.3f", healthfrac ) );
|
|
|
|
// Go through all the global states and append them
|
|
|
|
for ( int i = 0; i < GlobalEntity_GetNumGlobals(); i++ )
|
|
{
|
|
const char *szGlobalName = GlobalEntity_GetName(i);
|
|
int iGlobalState = (int)GlobalEntity_GetStateByIndex(i);
|
|
set.AppendCriteria( szGlobalName, UTIL_VarArgs( "%i", iGlobalState ) );
|
|
}
|
|
|
|
// Append anything from I/O or keyvalues pairs
|
|
AppendContextToCriteria( set );
|
|
|
|
if( hl2_episodic.GetBool() )
|
|
{
|
|
set.AppendCriteria( "episodic", "1" );
|
|
}
|
|
|
|
// Append anything from world I/O/keyvalues with "world" as prefix
|
|
CWorld *world = assert_cast< CWorld * >( CBaseEntity::Instance( INDEXENT( 0 ) ) );
|
|
if ( world )
|
|
{
|
|
world->AppendContextToCriteria( set, "world" );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : set -
|
|
// "" -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::AppendContextToCriteria( AI_CriteriaSet& set, const char *prefix /*= ""*/ )
|
|
{
|
|
RemoveExpiredConcepts();
|
|
|
|
int c = GetContextCount();
|
|
int i;
|
|
|
|
char sz[ 128 ];
|
|
for ( i = 0; i < c; i++ )
|
|
{
|
|
const char *name = GetContextName( i );
|
|
const char *value = GetContextValue( i );
|
|
|
|
Q_snprintf( sz, sizeof( sz ), "%s%s", prefix, name );
|
|
|
|
set.AppendCriteria( sz, value );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Removes expired concepts from list
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::RemoveExpiredConcepts( void )
|
|
{
|
|
int c = GetContextCount();
|
|
int i;
|
|
|
|
for ( i = 0; i < c; i++ )
|
|
{
|
|
if ( ContextExpired( i ) )
|
|
{
|
|
m_ResponseContexts.Remove( i );
|
|
c--;
|
|
i--;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Get current context count
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::GetContextCount() const
|
|
{
|
|
return m_ResponseContexts.Count();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : index -
|
|
// Output : const char
|
|
//-----------------------------------------------------------------------------
|
|
const char *CBaseEntity::GetContextName( int index ) const
|
|
{
|
|
if ( index < 0 || index >= m_ResponseContexts.Count() )
|
|
{
|
|
Assert( 0 );
|
|
return "";
|
|
}
|
|
|
|
return m_ResponseContexts[ index ].m_iszName.ToCStr();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : index -
|
|
// Output : const char
|
|
//-----------------------------------------------------------------------------
|
|
const char *CBaseEntity::GetContextValue( int index ) const
|
|
{
|
|
if ( index < 0 || index >= m_ResponseContexts.Count() )
|
|
{
|
|
Assert( 0 );
|
|
return "";
|
|
}
|
|
|
|
return m_ResponseContexts[ index ].m_iszValue.ToCStr();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: remove all contexts set on this entity
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::ClearAllContexts( )
|
|
{
|
|
m_ResponseContexts.RemoveAll();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Check if context has expired
|
|
// Input : index -
|
|
// Output : bool
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::ContextExpired( int index ) const
|
|
{
|
|
if ( index < 0 || index >= m_ResponseContexts.Count() )
|
|
{
|
|
Assert( 0 );
|
|
return true;
|
|
}
|
|
|
|
if ( !m_ResponseContexts[ index ].m_fExpirationTime )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return ( m_ResponseContexts[ index ].m_fExpirationTime <= gpGlobals->curtime );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Search for index of named context string
|
|
// Input : *name -
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseEntity::FindContextByName( const char *name ) const
|
|
{
|
|
int c = m_ResponseContexts.Count();
|
|
for ( int i = 0; i < c; i++ )
|
|
{
|
|
if ( FStrEq( name, GetContextName( i ) ) )
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : inputdata -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputAddContext( inputdata_t& inputdata )
|
|
{
|
|
const char *contextName = inputdata.value.String();
|
|
AddContext( contextName );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: User inputs. These fire the corresponding user outputs, and are
|
|
// a means of forwarding messages through !activator to a target known
|
|
// known by !activator but not by the targetting entity.
|
|
//
|
|
// For example, say you have three identical trains, following the same
|
|
// path. Each train has a sprite in hierarchy with it that needs to
|
|
// toggle on/off as it passes each path_track. You would hook each train's
|
|
// OnUser1 output to it's sprite's Toggle input, then connect each path_track's
|
|
// OnPass output to !activator's FireUser1 input.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputFireUser1( inputdata_t& inputdata )
|
|
{
|
|
m_OnUser1.FireOutput( inputdata.pActivator, this );
|
|
}
|
|
|
|
|
|
void CBaseEntity::InputFireUser2( inputdata_t& inputdata )
|
|
{
|
|
m_OnUser2.FireOutput( inputdata.pActivator, this );
|
|
}
|
|
|
|
|
|
void CBaseEntity::InputFireUser3( inputdata_t& inputdata )
|
|
{
|
|
m_OnUser3.FireOutput( inputdata.pActivator, this );
|
|
}
|
|
|
|
|
|
void CBaseEntity::InputFireUser4( inputdata_t& inputdata )
|
|
{
|
|
m_OnUser4.FireOutput( inputdata.pActivator, this );
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// Use the string as the filename of a script file
|
|
// that should be loaded from disk, compiled, and run.
|
|
//---------------------------------------------------------
|
|
void CBaseEntity::InputRunScriptFile( inputdata_t& inputdata )
|
|
{
|
|
RunScriptFile( inputdata.value.String() );
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// Send the string to the VM as source code and execute it
|
|
//---------------------------------------------------------
|
|
void CBaseEntity::InputRunScript( inputdata_t& inputdata )
|
|
{
|
|
RunScript( inputdata.value.String(), "InputRunScript" );
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// Make an explicit function call.
|
|
//---------------------------------------------------------
|
|
void CBaseEntity::InputCallScriptFunction( inputdata_t& inputdata )
|
|
{
|
|
CallScriptFunction( inputdata.value.String(), NULL );
|
|
}
|
|
|
|
|
|
#ifdef PORTAL2
|
|
|
|
//---------------------------------------------------------
|
|
// Remove paint from entity with BSP model.
|
|
//---------------------------------------------------------
|
|
void CBaseEntity::InputRemovePaint( inputdata_t &inputdata )
|
|
{
|
|
if ( engine->HasPaintmap() && IsBSPModel() )
|
|
{
|
|
engine->RemovePaint( GetModel() );
|
|
|
|
CBroadcastRecipientFilter filter;
|
|
filter.MakeReliable();
|
|
UserMessageBegin( filter, "RemovePaint" );
|
|
WRITE_EHANDLE( this );
|
|
MessageEnd();
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
// #define VMPROFILE // define to profile vscript calls
|
|
|
|
#ifdef VMPROFILE
|
|
float g_debugCumulativeTime = 0.0;
|
|
float g_debugCounter = 0;
|
|
|
|
#define START_VMPROFILE float debugStartTime = Plat_FloatTime();
|
|
#define UPDATE_VMPROFILE \
|
|
g_debugCumulativeTime += Plat_FloatTime() - debugStartTime; \
|
|
g_debugCounter++; \
|
|
if ( g_debugCounter >= 500 ) \
|
|
{ \
|
|
DevMsg("***VSCRIPT PROFILE***: %s %s: %6.4f milliseconds\n", "500 vscript function calls", "", g_debugCumulativeTime*1000.0 ); \
|
|
g_debugCounter = 0; \
|
|
g_debugCumulativeTime = 0.0; \
|
|
} \
|
|
|
|
#else
|
|
|
|
#define START_VMPROFILE
|
|
#define UPDATE_VMPROFILE
|
|
|
|
#endif // VMPROFILE
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Returns true if the function was located and called. false otherwise.
|
|
// NOTE: Assumes the function takes no parameters at the moment.
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::CallScriptFunction( const char *pFunctionName, ScriptVariant_t *pFunctionReturn )
|
|
{
|
|
START_VMPROFILE
|
|
|
|
if( !ValidateScriptScope() )
|
|
{
|
|
DevMsg("\n***\nFAILED to create private ScriptScope. ABORTING script\n***\n");
|
|
return false;
|
|
}
|
|
|
|
|
|
HSCRIPT hFunc = m_ScriptScope.LookupFunction( pFunctionName );
|
|
|
|
if( hFunc )
|
|
{
|
|
// Kind of a hack to make glados.nut easier to work with...
|
|
// When a script function is called by connecting the function to an entity output,
|
|
// the entity who is connected to the output and who has this function in their scope
|
|
// will be set to 'owninginstance'. In this situation, it can be a different instance than 'self'.
|
|
g_pScriptVM->SetValue( "owninginstance", ScriptVariant_t( GetScriptInstance() ) );
|
|
m_ScriptScope.Call( hFunc, pFunctionReturn );
|
|
m_ScriptScope.ReleaseFunction( hFunc );
|
|
g_pScriptVM->ClearValue( "owninginstance" );
|
|
|
|
UPDATE_VMPROFILE
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::ConnectOutputToScript( const char *pszOutput, const char *pszScriptFunc )
|
|
{
|
|
CBaseEntityOutput *pOutput = FindNamedOutput( pszOutput );
|
|
if ( !pOutput )
|
|
{
|
|
DevMsg( 2, "Script failed to find output \"%s\"\n", pszOutput );
|
|
return;
|
|
}
|
|
|
|
string_t iszSelf = AllocPooledString( "!self" ); // @TODO: cache this [4/25/2008 tom]
|
|
CEventAction *pAction = pOutput->GetFirstAction();
|
|
while ( pAction )
|
|
{
|
|
if ( pAction->m_iTarget == iszSelf &&
|
|
pAction->m_flDelay == 0 &&
|
|
pAction->m_nTimesToFire == EVENT_FIRE_ALWAYS &&
|
|
V_strcmp( STRING(pAction->m_iTargetInput), "CallScriptFunction" ) == 0 &&
|
|
V_strcmp( STRING(pAction->m_iParameter), pszScriptFunc ) == 0 )
|
|
{
|
|
return;
|
|
}
|
|
pAction = pAction->m_pNext;
|
|
}
|
|
|
|
pAction = new CEventAction( NULL );
|
|
pAction->m_iTarget = iszSelf;
|
|
pAction->m_iTargetInput = AllocPooledString( "CallScriptFunction" );
|
|
pAction->m_iParameter = AllocPooledString( pszScriptFunc );
|
|
pOutput->AddEventAction( pAction );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::DisconnectOutputFromScript( const char *pszOutput, const char *pszScriptFunc )
|
|
{
|
|
CBaseEntityOutput *pOutput = FindNamedOutput( pszOutput );
|
|
if ( !pOutput )
|
|
{
|
|
DevMsg( 2, "Script failed to find output \"%s\"\n", pszOutput );
|
|
return;
|
|
}
|
|
|
|
string_t iszSelf = AllocPooledString( "!self" ); // @TODO: cache this [4/25/2008 tom]
|
|
CEventAction *pAction = pOutput->GetFirstAction();
|
|
while ( pAction )
|
|
{
|
|
if ( pAction->m_iTarget == iszSelf &&
|
|
pAction->m_flDelay == 0 &&
|
|
pAction->m_nTimesToFire == EVENT_FIRE_ALWAYS &&
|
|
V_strcmp( STRING(pAction->m_iTargetInput), "CallScriptFunction" ) == 0 &&
|
|
V_strcmp( STRING(pAction->m_iParameter), pszScriptFunc ) == 0 )
|
|
{
|
|
pOutput->RemoveEventAction( pAction );
|
|
delete pAction;
|
|
return;
|
|
}
|
|
pAction = pAction->m_pNext;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::ScriptThink( void )
|
|
{
|
|
ScriptVariant_t varThinkRetVal;
|
|
if( CallScriptFunction( m_iszScriptThinkFunction.ToCStr(), &varThinkRetVal ) )
|
|
{
|
|
float flThinkFrequency = 0.0f;
|
|
if ( !varThinkRetVal.AssignTo( &flThinkFrequency ) )
|
|
{
|
|
// use default think interval if script think function doesn't provide one
|
|
flThinkFrequency = sv_script_think_interval.GetFloat();
|
|
}
|
|
SetContextThink( &CBaseEntity::ScriptThink,
|
|
gpGlobals->curtime + flThinkFrequency, "ScriptThink" );
|
|
}
|
|
else
|
|
{
|
|
DevWarning("%s FAILED to call script think function %s!\n", GetDebugName(), STRING(m_iszScriptThinkFunction) );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
const char *CBaseEntity::GetScriptId()
|
|
{
|
|
return STRING( m_iszScriptThinkFunction );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
HSCRIPT CBaseEntity::GetScriptScope()
|
|
{
|
|
return m_ScriptScope;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
HSCRIPT CBaseEntity::ScriptGetMoveParent( void )
|
|
{
|
|
return ToHScript( GetMoveParent() );
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
HSCRIPT CBaseEntity::ScriptGetRootMoveParent()
|
|
{
|
|
return ToHScript( GetRootMoveParent() );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
HSCRIPT CBaseEntity::ScriptFirstMoveChild( void )
|
|
{
|
|
return ToHScript( FirstMoveChild() );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
HSCRIPT CBaseEntity::ScriptNextMovePeer( void )
|
|
{
|
|
return ToHScript( NextMovePeer() );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Load, compile, and run a script file from disk.
|
|
// Input : *pScriptFile - The filename of the script file.
|
|
// bUseRootScope - If true, runs this script in the root scope, not
|
|
// in this entity's private scope.
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::RunScriptFile( const char *pScriptFile, bool bUseRootScope )
|
|
{
|
|
if( !ValidateScriptScope() )
|
|
{
|
|
DevMsg("\n***\nFAILED to create private ScriptScope. ABORTING script\n***\n");
|
|
return false;
|
|
}
|
|
|
|
if( bUseRootScope )
|
|
{
|
|
return VScriptRunScript( pScriptFile );
|
|
}
|
|
else
|
|
{
|
|
return VScriptRunScript( pScriptFile, m_ScriptScope, true );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Compile and execute a discrete string of script source code
|
|
// Input : *pScriptText - A string containing script code to compile and run
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::RunScript( const char *pScriptText, const char *pDebugFilename )
|
|
{
|
|
if( !ValidateScriptScope() )
|
|
{
|
|
DevMsg("\n***\nFAILED to create private ScriptScope. ABORTING script\n***\n");
|
|
return false;
|
|
}
|
|
|
|
if( m_ScriptScope.Run( pScriptText, pDebugFilename ) == SCRIPT_ERROR )
|
|
{
|
|
DevWarning(" Entity %s encountered an error in RunScript()\n", GetDebugName() );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: given a string context1:value1,context2:value2,... tokenize and
|
|
// add its data to this object.
|
|
// Input : *contextName -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::AddContext( const char *contextName )
|
|
{
|
|
char key[ 128 ];
|
|
char value[ 128 ];
|
|
float duration;
|
|
#ifdef TERROR // from changelist 729204 . Ifdef'd out because not tested outside L4D yet.
|
|
CWorld * const world = assert_cast< CWorld * >( CBaseEntity::Instance( INDEXENT( 0 ) ) );
|
|
#endif
|
|
|
|
const char *p = contextName;
|
|
while ( p )
|
|
{
|
|
duration = 0.0f;
|
|
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), &duration, contextName );
|
|
if ( duration )
|
|
{
|
|
duration += gpGlobals->curtime;
|
|
}
|
|
|
|
#ifdef TERROR
|
|
// Egregious last-minute hack. If a specific context is prefixed with a '$', then
|
|
// apply it to the World instead of to this character. The proper way to fix this
|
|
// would be to do away with this insane mechanism of writing contexts out into a
|
|
// string and then parsing it back apart again after calling a member function
|
|
// on the receiving character; but that's way too big to deal with at this stage
|
|
// of L4D2. ( this hack dated 9/4/09 )
|
|
if ( key[0] == AI_CriteriaSet::kAPPLYTOWORLDPREFIX && world && world != this )
|
|
{
|
|
world->AddContext( key+1, value, duration );
|
|
}
|
|
else
|
|
{
|
|
AddContext( key, value, duration );
|
|
}
|
|
#else
|
|
AddContext( key, value, duration );
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#include "ai_speech.h"
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: add exactly one context key,value pair to this object
|
|
// Input : inputdata -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::AddContext( const char *pKey, const char *pValue, float duration )
|
|
{
|
|
int iIndex = FindContextByName( pKey );
|
|
if ( iIndex != -1 )
|
|
{
|
|
// Set the existing context to the new value
|
|
char buf[64];
|
|
if ( RR::CApplyContextOperator::FindOperator( pValue )->Apply(
|
|
m_ResponseContexts[iIndex].m_iszValue.ToCStr(), pValue, buf, sizeof(buf) ) )
|
|
{
|
|
m_ResponseContexts[iIndex].m_iszValue = AllocPooledString( buf );
|
|
}
|
|
else
|
|
{
|
|
Warning( "RR: could not apply operator %s to prior value %s\n",
|
|
pValue, m_ResponseContexts[iIndex].m_iszValue.ToCStr() );
|
|
m_ResponseContexts[iIndex].m_iszValue = AllocPooledString( pValue );
|
|
}
|
|
|
|
m_ResponseContexts[iIndex].m_fExpirationTime = duration;
|
|
}
|
|
else
|
|
{
|
|
ResponseContext_t newContext;
|
|
newContext.m_iszName = AllocPooledString( pKey );
|
|
|
|
// Create a new context with the appropriate value ( some operators assume 0 on nonexistent prior )
|
|
char buf[64];
|
|
if ( RR::CApplyContextOperator::FindOperator( pValue )->Apply(
|
|
NULL, pValue, buf, sizeof(buf) ) )
|
|
{
|
|
newContext.m_iszValue = AllocPooledString( buf );
|
|
}
|
|
else
|
|
{
|
|
newContext.m_iszValue = AllocPooledString( pValue );
|
|
}
|
|
newContext.m_fExpirationTime = duration;
|
|
|
|
m_ResponseContexts.AddToTail( newContext );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : inputdata -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputRemoveContext( inputdata_t& inputdata )
|
|
{
|
|
const char *contextName = inputdata.value.String();
|
|
int idx = FindContextByName( contextName );
|
|
if ( idx == -1 )
|
|
return;
|
|
|
|
m_ResponseContexts.Remove( idx );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : inputdata -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputClearContext( inputdata_t& inputdata )
|
|
{
|
|
m_ResponseContexts.RemoveAll();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : IResponseSystem
|
|
//-----------------------------------------------------------------------------
|
|
IResponseSystem *CBaseEntity::GetResponseSystem()
|
|
{
|
|
#ifndef INFESTED_DLL
|
|
return NULL;
|
|
#else
|
|
extern IResponseSystem *g_pResponseSystem;
|
|
return g_pResponseSystem;
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : inputdata -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputDispatchResponse( inputdata_t& inputdata )
|
|
{
|
|
DispatchResponse( inputdata.value.String() );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputDisableShadow( inputdata_t &inputdata )
|
|
{
|
|
AddEffects( EF_NOSHADOW );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputEnableShadow( inputdata_t &inputdata )
|
|
{
|
|
RemoveEffects( EF_NOSHADOW );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputDisableReceivingFlashlight( inputdata_t &inputdata )
|
|
{
|
|
AddEffects( EF_NOFLASHLIGHT );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputEnableReceivingFlashlight( inputdata_t &inputdata )
|
|
{
|
|
RemoveEffects( EF_NOFLASHLIGHT );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputDisableDrawInFastReflection( inputdata_t &inputdata )
|
|
{
|
|
RemoveEffects( EF_MARKED_FOR_FAST_REFLECTION );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputEnableDrawInFastReflection( inputdata_t &inputdata )
|
|
{
|
|
AddEffects( EF_MARKED_FOR_FAST_REFLECTION );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputDisableDraw( inputdata_t &inputdata )
|
|
{
|
|
AddEffects( EF_NODRAW );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputEnableDraw( inputdata_t &inputdata )
|
|
{
|
|
RemoveEffects( EF_NODRAW );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: An input to add a new connection from this entity
|
|
// Input : &inputdata -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::InputAddOutput( inputdata_t &inputdata )
|
|
{
|
|
char sOutputName[MAX_PATH];
|
|
Q_strncpy( sOutputName, inputdata.value.String(), sizeof(sOutputName) );
|
|
char *sChar = strchr( sOutputName, ' ' );
|
|
if ( sChar )
|
|
{
|
|
*sChar = '\0';
|
|
// Now replace all the :'s in the string with ,'s.
|
|
// Has to be done this way because Hammer doesn't allow ,'s inside parameters.
|
|
char *sColon = strchr( sChar+1, ':' );
|
|
while ( sColon )
|
|
{
|
|
*sColon = ',';
|
|
sColon = strchr( sChar+1, ':' );
|
|
}
|
|
KeyValue( sOutputName, sChar+1 );
|
|
}
|
|
else
|
|
{
|
|
Warning("AddOutput input fired with bad string. Format: <output name> <targetname>,<inputname>,<parameter>,<delay>,<max times to fire (-1 == infinite)>\n");
|
|
}
|
|
}
|
|
|
|
#include "ai_speech.h"
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *conceptName -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::DispatchResponse( const char *conceptName )
|
|
{
|
|
IResponseSystem *rs = GetResponseSystem();
|
|
if ( !rs )
|
|
return;
|
|
|
|
AI_CriteriaSet set;
|
|
// Always include the concept name
|
|
set.AppendCriteria( "concept", conceptName, CONCEPT_WEIGHT );
|
|
// Let NPC fill in most match criteria
|
|
ModifyOrAppendCriteria( set );
|
|
|
|
// Append local player criteria to set,too
|
|
CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
|
|
if( pPlayer )
|
|
pPlayer->ModifyOrAppendPlayerCriteria( set );
|
|
|
|
// Now that we have a criteria set, ask for a suitable response
|
|
AI_Response result;
|
|
bool found = rs->FindBestResponse( set, result );
|
|
if ( !found )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Handle the response here...
|
|
char response[ 256 ];
|
|
result.GetResponse( response, sizeof( response ) );
|
|
switch ( result.GetType() )
|
|
{
|
|
case ResponseRules::RESPONSE_SPEAK:
|
|
{
|
|
EmitSound( response );
|
|
}
|
|
break;
|
|
case ResponseRules::RESPONSE_SENTENCE:
|
|
{
|
|
int sentenceIndex = SENTENCEG_Lookup( response );
|
|
if( sentenceIndex == -1 )
|
|
{
|
|
// sentence not found
|
|
break;
|
|
}
|
|
|
|
// FIXME: Get pitch from npc?
|
|
CPASAttenuationFilter filter( this );
|
|
CBaseEntity::EmitSentenceByIndex( filter, entindex(), CHAN_VOICE, sentenceIndex, 1, result.GetSoundLevel(), 0, PITCH_NORM );
|
|
}
|
|
break;
|
|
case ResponseRules::RESPONSE_SCENE:
|
|
{
|
|
// Try to fire scene w/o an actor
|
|
InstancedScriptedScene( NULL, response );
|
|
}
|
|
break;
|
|
case ResponseRules::RESPONSE_PRINT:
|
|
{
|
|
|
|
}
|
|
break;
|
|
case ResponseRules::RESPONSE_ENTITYIO:
|
|
{
|
|
CAI_Expresser::FireEntIOFromResponse( response, this );
|
|
break;
|
|
}
|
|
default:
|
|
// Don't know how to handle .vcds!!!
|
|
break;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::DumpResponseCriteria( void )
|
|
{
|
|
Msg("----------------------------------------------\n");
|
|
Msg("RESPONSE CRITERIA FOR: %s (%s)\n", GetClassname(), GetDebugName() );
|
|
|
|
AI_CriteriaSet set;
|
|
// Let NPC fill in most match criteria
|
|
ModifyOrAppendCriteria( set );
|
|
|
|
// Append local player criteria to set,too
|
|
CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->ModifyOrAppendPlayerCriteria( set );
|
|
}
|
|
|
|
// Now dump it all to console
|
|
set.Describe();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Show_Response_Criteria( const CCommand& args )
|
|
{
|
|
CBaseEntity *pEntity = NULL;
|
|
while ( (pEntity = GetNextCommandEntity( UTIL_GetCommandClient(), args[1], pEntity )) != NULL )
|
|
{
|
|
pEntity->DumpResponseCriteria();
|
|
}
|
|
}
|
|
static ConCommand ent_show_response_criteria("ent_show_response_criteria", CC_Ent_Show_Response_Criteria, "Print, to the console, an entity's current criteria set used to select responses.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Show an entity's autoaim radius
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Autoaim( const CCommand& args )
|
|
{
|
|
SetDebugBits( UTIL_GetCommandClient(),args[1], OVERLAY_AUTOAIM_BIT );
|
|
}
|
|
static ConCommand ent_autoaim("ent_autoaim", CC_Ent_Autoaim, "Displays the entity's autoaim radius.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at", FCVAR_CHEAT );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CAI_BaseNPC *CBaseEntity::MyNPCPointer( void )
|
|
{
|
|
if ( IsNPC() )
|
|
return assert_cast<CAI_BaseNPC *>(this);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
ConVar step_spline( "step_spline", "0" );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Run one tick's worth of faked simulation
|
|
// Input : *step -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::ComputeStepSimulationNetwork( StepSimulationData *step )
|
|
{
|
|
if ( !step )
|
|
{
|
|
Assert( !"ComputeStepSimulationNetworkOriginAndAngles with NULL step\n" );
|
|
return;
|
|
}
|
|
|
|
// Don't run again if we've already calculated this tick
|
|
if ( step->m_nLastProcessTickCount == gpGlobals->tickcount )
|
|
{
|
|
return;
|
|
}
|
|
|
|
step->m_nLastProcessTickCount = gpGlobals->tickcount;
|
|
|
|
// Origin
|
|
// It's inactive
|
|
if ( step->m_bOriginActive )
|
|
{
|
|
// First see if any external code moved the entity
|
|
if ( GetStepOrigin() != step->m_Next.vecOrigin )
|
|
{
|
|
step->m_bOriginActive = false;
|
|
}
|
|
else
|
|
{
|
|
// Compute interpolated info based on tick interval
|
|
float frac = 1.0f;
|
|
int tickdelta = step->m_Next.nTickCount - step->m_Previous.nTickCount;
|
|
if ( tickdelta > 0 )
|
|
{
|
|
frac = (float)( gpGlobals->tickcount - step->m_Previous.nTickCount ) / (float) tickdelta;
|
|
frac = clamp( frac, 0.0f, 1.0f );
|
|
}
|
|
|
|
if (step->m_Previous2.nTickCount == 0 || step->m_Previous2.nTickCount >= step->m_Previous.nTickCount)
|
|
{
|
|
Vector delta = step->m_Next.vecOrigin - step->m_Previous.vecOrigin;
|
|
VectorMA( step->m_Previous.vecOrigin, frac, delta, step->m_vecNetworkOrigin );
|
|
// m_vecNetworkOrigin get network in place of m_vecOrigin in the SendProxy_Origin so it needs to mark the entities state as changed.
|
|
NetworkStateChanged();
|
|
}
|
|
else if (!step_spline.GetBool())
|
|
{
|
|
StepSimulationStep *pOlder = &step->m_Previous;
|
|
StepSimulationStep *pNewer = &step->m_Next;
|
|
|
|
if (step->m_Discontinuity.nTickCount > step->m_Previous.nTickCount)
|
|
{
|
|
if (gpGlobals->tickcount > step->m_Discontinuity.nTickCount)
|
|
{
|
|
pOlder = &step->m_Discontinuity;
|
|
}
|
|
else
|
|
{
|
|
pNewer = &step->m_Discontinuity;
|
|
}
|
|
|
|
tickdelta = pNewer->nTickCount - pOlder->nTickCount;
|
|
if ( tickdelta > 0 )
|
|
{
|
|
frac = (float)( gpGlobals->tickcount - pOlder->nTickCount ) / (float) tickdelta;
|
|
frac = clamp( frac, 0.0f, 1.0f );
|
|
}
|
|
}
|
|
|
|
Vector delta = pNewer->vecOrigin - pOlder->vecOrigin;
|
|
VectorMA( pOlder->vecOrigin, frac, delta, step->m_vecNetworkOrigin );
|
|
// m_vecNetworkOrigin get network in place of m_vecOrigin in the SendProxy_Origin so it needs to mark the entities state as changed.
|
|
NetworkStateChanged();
|
|
}
|
|
else
|
|
{
|
|
Hermite_Spline( step->m_Previous2.vecOrigin, step->m_Previous.vecOrigin, step->m_Next.vecOrigin, frac, step->m_vecNetworkOrigin );
|
|
// m_vecNetworkOrigin get network in place of m_vecOrigin in the SendProxy_Origin so it needs to mark the entities state as changed.
|
|
NetworkStateChanged();
|
|
}
|
|
|
|
// Calculate the cell of this origin
|
|
register int const cellwidth = m_cellwidth; // Load it into a register
|
|
step->m_networkCell[0] = CellFromCoord( cellwidth, step->m_vecNetworkOrigin[0] );
|
|
step->m_networkCell[1] = CellFromCoord( cellwidth, step->m_vecNetworkOrigin[1] );
|
|
step->m_networkCell[2] = CellFromCoord( cellwidth, step->m_vecNetworkOrigin[2] );
|
|
}
|
|
}
|
|
|
|
// Angles
|
|
if ( step->m_bAnglesActive )
|
|
{
|
|
// See if external code changed the orientation of the entity
|
|
if ( GetStepAngles() != step->m_angNextRotation )
|
|
{
|
|
step->m_bAnglesActive = false;
|
|
}
|
|
else
|
|
{
|
|
// Compute interpolated info based on tick interval
|
|
float frac = 1.0f;
|
|
int tickdelta = step->m_Next.nTickCount - step->m_Previous.nTickCount;
|
|
if ( tickdelta > 0 )
|
|
{
|
|
frac = (float)( gpGlobals->tickcount - step->m_Previous.nTickCount ) / (float) tickdelta;
|
|
frac = clamp( frac, 0.0f, 1.0f );
|
|
}
|
|
|
|
if (step->m_Previous2.nTickCount == 0 || step->m_Previous2.nTickCount >= step->m_Previous.nTickCount)
|
|
{
|
|
// Pure blend between start/end orientations
|
|
Quaternion outangles;
|
|
QuaternionBlend( step->m_Previous.qRotation, step->m_Next.qRotation, frac, outangles );
|
|
QuaternionAngles( outangles, step->m_angNetworkAngles );
|
|
}
|
|
else if (!step_spline.GetBool())
|
|
{
|
|
StepSimulationStep *pOlder = &step->m_Previous;
|
|
StepSimulationStep *pNewer = &step->m_Next;
|
|
|
|
if (step->m_Discontinuity.nTickCount > step->m_Previous.nTickCount)
|
|
{
|
|
if (gpGlobals->tickcount > step->m_Discontinuity.nTickCount)
|
|
{
|
|
pOlder = &step->m_Discontinuity;
|
|
}
|
|
else
|
|
{
|
|
pNewer = &step->m_Discontinuity;
|
|
}
|
|
|
|
tickdelta = pNewer->nTickCount - pOlder->nTickCount;
|
|
if ( tickdelta > 0 )
|
|
{
|
|
frac = (float)( gpGlobals->tickcount - pOlder->nTickCount ) / (float) tickdelta;
|
|
frac = clamp( frac, 0.0f, 1.0f );
|
|
}
|
|
}
|
|
|
|
// Pure blend between start/end orientations
|
|
Quaternion outangles;
|
|
QuaternionBlend( pOlder->qRotation, pNewer->qRotation, frac, outangles );
|
|
QuaternionAngles( outangles, step->m_angNetworkAngles );
|
|
}
|
|
else
|
|
{
|
|
// FIXME: enable spline interpolation when turning is debounced.
|
|
Quaternion outangles;
|
|
Hermite_Spline( step->m_Previous2.qRotation, step->m_Previous.qRotation, step->m_Next.qRotation, frac, outangles );
|
|
QuaternionAngles( outangles, step->m_angNetworkAngles );
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::UseStepSimulationNetworkOrigin( const Vector **out_v, int cell[3] )
|
|
{
|
|
Assert( out_v );
|
|
|
|
|
|
if ( g_bTestMoveTypeStepSimulation &&
|
|
GetMoveType() == MOVETYPE_STEP &&
|
|
HasDataObjectType( STEPSIMULATION ) )
|
|
{
|
|
StepSimulationData *step = ( StepSimulationData * )GetDataObject( STEPSIMULATION );
|
|
ComputeStepSimulationNetwork( step );
|
|
*out_v = &step->m_vecNetworkOrigin;
|
|
if ( cell )
|
|
{
|
|
cell[0] = step->m_networkCell[0];
|
|
cell[1] = step->m_networkCell[1];
|
|
cell[2] = step->m_networkCell[2];
|
|
}
|
|
|
|
return step->m_bOriginActive;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::UseStepSimulationNetworkAngles( const QAngle **out_a )
|
|
{
|
|
Assert( out_a );
|
|
|
|
if ( g_bTestMoveTypeStepSimulation &&
|
|
GetMoveType() == MOVETYPE_STEP &&
|
|
HasDataObjectType( STEPSIMULATION ) )
|
|
{
|
|
StepSimulationData *step = ( StepSimulationData * )GetDataObject( STEPSIMULATION );
|
|
ComputeStepSimulationNetwork( step );
|
|
*out_a = &step->m_angNetworkAngles;
|
|
return step->m_bAnglesActive;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool CBaseEntity::AddStepDiscontinuity( float flTime, const Vector &vecOrigin, const QAngle &vecAngles )
|
|
{
|
|
if ((GetMoveType() != MOVETYPE_STEP ) || !HasDataObjectType( STEPSIMULATION ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
StepSimulationData *step = ( StepSimulationData * )GetDataObject( STEPSIMULATION );
|
|
|
|
if (!step)
|
|
{
|
|
Assert( 0 );
|
|
return false;
|
|
}
|
|
|
|
step->m_Discontinuity.nTickCount = TIME_TO_TICKS( flTime );
|
|
step->m_Discontinuity.vecOrigin = vecOrigin;
|
|
AngleQuaternion( vecAngles, step->m_Discontinuity.qRotation );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
Vector CBaseEntity::GetStepOrigin( void ) const
|
|
{
|
|
return GetLocalOrigin();
|
|
}
|
|
|
|
QAngle CBaseEntity::GetStepAngles( void ) const
|
|
{
|
|
return GetLocalAngles();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: For each client who appears to be a valid recipient, checks the client has disabled CC and if so, removes them from
|
|
// the recipient list.
|
|
// Input : filter -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::RemoveRecipientsIfNotCloseCaptioning( CRecipientFilter& filter )
|
|
{
|
|
int c = filter.GetRecipientCount();
|
|
for ( int i = c - 1; i >= 0; --i )
|
|
{
|
|
int playerIndex = filter.GetRecipientIndex( i );
|
|
|
|
CBasePlayer *player = static_cast< CBasePlayer * >( CBaseEntity::Instance( playerIndex ) );
|
|
if ( !player )
|
|
continue;
|
|
|
|
const char *cvarvalue = engine->GetClientConVarValue( playerIndex, "closecaption" );
|
|
Assert( cvarvalue );
|
|
if ( !cvarvalue[ 0 ] )
|
|
continue;
|
|
|
|
int value = atoi( cvarvalue );
|
|
|
|
// No close captions?
|
|
if ( value == 0 )
|
|
{
|
|
filter.RemoveRecipient( player );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Wrapper to emit a sentence and also a close caption token for the sentence as appropriate.
|
|
// Input : filter -
|
|
// iEntIndex -
|
|
// iChannel -
|
|
// iSentenceIndex -
|
|
// flVolume -
|
|
// iSoundlevel -
|
|
// iFlags -
|
|
// iPitch -
|
|
// bUpdatePositions -
|
|
// soundtime -
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::EmitSentenceByIndex( IRecipientFilter& filter, int iEntIndex, int iChannel, int iSentenceIndex,
|
|
float flVolume, soundlevel_t iSoundlevel, int iFlags /*= 0*/, int iPitch /*=PITCH_NORM*/,
|
|
const Vector *pOrigin /*=NULL*/, const Vector *pDirection /*=NULL*/,
|
|
bool bUpdatePositions /*=true*/, float soundtime /*=0.0f*/ )
|
|
{
|
|
CUtlVector< Vector > dummy;
|
|
enginesound->EmitSentenceByIndex( filter, iEntIndex, iChannel, iSentenceIndex,
|
|
flVolume, iSoundlevel, 0, iFlags, iPitch, pOrigin, pDirection, &dummy, bUpdatePositions, soundtime );
|
|
}
|
|
|
|
|
|
void CBaseEntity::SetRefEHandle( const CBaseHandle &handle )
|
|
{
|
|
m_RefEHandle = handle;
|
|
if ( edict() )
|
|
{
|
|
edict()->m_NetworkSerialNumber = (m_RefEHandle.GetSerialNumber() & (1 << NUM_NETWORKED_EHANDLE_SERIAL_NUMBER_BITS) - 1);
|
|
}
|
|
}
|
|
|
|
|
|
bool CPointEntity::KeyValue( const char *szKeyName, const char *szValue )
|
|
{
|
|
if ( FStrEq( szKeyName, "mins" ) || FStrEq( szKeyName, "maxs" ) )
|
|
{
|
|
Warning("Warning! Can't specify mins/maxs for point entities! (%s)\n", GetClassname() );
|
|
return true;
|
|
}
|
|
|
|
return BaseClass::KeyValue( szKeyName, szValue );
|
|
}
|
|
|
|
bool CServerOnlyPointEntity::KeyValue( const char *szKeyName, const char *szValue )
|
|
{
|
|
if ( FStrEq( szKeyName, "mins" ) || FStrEq( szKeyName, "maxs" ) )
|
|
{
|
|
Warning("Warning! Can't specify mins/maxs for point entities! (%s)\n", GetClassname() );
|
|
return true;
|
|
}
|
|
|
|
return BaseClass::KeyValue( szKeyName, szValue );
|
|
}
|
|
|
|
bool CLogicalEntity::KeyValue( const char *szKeyName, const char *szValue )
|
|
{
|
|
if ( FStrEq( szKeyName, "mins" ) || FStrEq( szKeyName, "maxs" ) )
|
|
{
|
|
Warning("Warning! Can't specify mins/maxs for point entities! (%s)\n", GetClassname() );
|
|
return true;
|
|
}
|
|
|
|
return BaseClass::KeyValue( szKeyName, szValue );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets the entity invisible, and makes it remove itself on the next frame
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::RemoveDeferred( void )
|
|
{
|
|
// Set our next think to remove us
|
|
SetThink( &CBaseEntity::SUB_Remove );
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
|
|
// Hide us completely
|
|
AddEffects( EF_NODRAW );
|
|
AddSolidFlags( FSOLID_NOT_SOLID );
|
|
SetMoveType( MOVETYPE_NONE );
|
|
}
|
|
|
|
#define MIN_CORPSE_FADE_TIME 10.0
|
|
#define MIN_CORPSE_FADE_DIST 256.0
|
|
#define MAX_CORPSE_FADE_DIST 1500.0
|
|
|
|
//
|
|
// fade out - slowly fades a entity out, then removes it.
|
|
//
|
|
// DON'T USE ME FOR GIBS AND STUFF IN MULTIPLAYER!
|
|
// SET A FUTURE THINK AND A RENDERMODE!!
|
|
void CBaseEntity::SUB_StartFadeOut( float delay, bool notSolid )
|
|
{
|
|
SetThink( &CBaseEntity::SUB_FadeOut );
|
|
SetNextThink( gpGlobals->curtime + delay );
|
|
SetRenderAlpha( 255 );
|
|
m_nRenderMode = kRenderNormal;
|
|
|
|
if ( notSolid )
|
|
{
|
|
AddSolidFlags( FSOLID_NOT_SOLID );
|
|
SetLocalAngularVelocity( vec3_angle );
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::SUB_StartFadeOutInstant()
|
|
{
|
|
SUB_StartFadeOut( 0, true );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Vanish when players aren't looking
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SUB_Vanish( void )
|
|
{
|
|
//Always think again next frame
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
|
|
CBasePlayer *pPlayer;
|
|
|
|
//Get all players
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
//Get the next client
|
|
if ( ( pPlayer = UTIL_PlayerByIndex( i ) ) != NULL )
|
|
{
|
|
Vector corpseDir = (GetAbsOrigin() - pPlayer->WorldSpaceCenter() );
|
|
|
|
float flDistSqr = corpseDir.LengthSqr();
|
|
//If the player is close enough, don't fade out
|
|
if ( flDistSqr < (MIN_CORPSE_FADE_DIST*MIN_CORPSE_FADE_DIST) )
|
|
return;
|
|
|
|
// If the player's far enough away, we don't care about looking at it
|
|
if ( flDistSqr < (MAX_CORPSE_FADE_DIST*MAX_CORPSE_FADE_DIST) )
|
|
{
|
|
VectorNormalize( corpseDir );
|
|
|
|
Vector plForward;
|
|
pPlayer->EyeVectors( &plForward );
|
|
|
|
float dot = plForward.Dot( corpseDir );
|
|
|
|
if ( dot > 0.0f )
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//If we're here, then we can vanish safely
|
|
m_iHealth = 0;
|
|
SetThink( &CBaseEntity::SUB_Remove );
|
|
}
|
|
|
|
void CBaseEntity::SUB_PerformFadeOut( void )
|
|
{
|
|
float dt = gpGlobals->frametime;
|
|
if ( dt > 0.1f )
|
|
{
|
|
dt = 0.1f;
|
|
}
|
|
m_nRenderMode = kRenderTransTexture;
|
|
int speed = MAX(1,256*dt); // fade out over 1 second
|
|
SetRenderAlpha( UTIL_Approach( 0, m_clrRender->a, speed ) );
|
|
}
|
|
|
|
bool CBaseEntity::SUB_AllowedToFade( void )
|
|
{
|
|
if( VPhysicsGetObject() )
|
|
{
|
|
if( VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD || GetEFlags() & EFL_IS_BEING_LIFTED_BY_BARNACLE )
|
|
return false;
|
|
}
|
|
|
|
// only keep fading things active on the high end
|
|
#if !defined( PORTAL2 )
|
|
if ( !IsGameConsole() )
|
|
{
|
|
CBasePlayer *pPlayer = ( AI_IsSinglePlayer() ) ? UTIL_GetLocalPlayer() : NULL;
|
|
|
|
if ( pPlayer && pPlayer->FInViewCone( this ) )
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Fade out slowly
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::SUB_FadeOut( void )
|
|
{
|
|
if ( SUB_AllowedToFade() == false )
|
|
{
|
|
SetNextThink( gpGlobals->curtime + 1 );
|
|
SetRenderAlpha( 255 );
|
|
return;
|
|
}
|
|
|
|
SUB_PerformFadeOut();
|
|
|
|
if ( m_clrRender->a == 0 )
|
|
{
|
|
UTIL_Remove(this);
|
|
}
|
|
else
|
|
{
|
|
SetNextThink( gpGlobals->curtime );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
HSCRIPT CBaseEntity::GetScriptInstance()
|
|
{
|
|
if ( !m_hScriptInstance )
|
|
{
|
|
if ( m_iszScriptId == NULL_STRING )
|
|
{
|
|
char *szName = (char *)stackalloc( 1024 );
|
|
g_pScriptVM->GenerateUniqueKey( ( m_iName.Get() != NULL_STRING ) ? STRING(GetEntityName()) : GetClassname(), szName, 1024 );
|
|
m_iszScriptId = AllocPooledString( szName );
|
|
}
|
|
|
|
m_hScriptInstance = g_pScriptVM->RegisterInstance( GetScriptDesc(), this );
|
|
g_pScriptVM->SetInstanceUniqeId( m_hScriptInstance, STRING(m_iszScriptId) );
|
|
}
|
|
return m_hScriptInstance;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Using my edict, cook up a unique VScript scope that's private to me, and
|
|
// persistent.
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseEntity::ValidateScriptScope()
|
|
{
|
|
if ( !m_ScriptScope.IsInitialized() )
|
|
{
|
|
if( scriptmanager == NULL )
|
|
{
|
|
ExecuteOnce( DevMsg( "Cannot execute script because scripting is disabled (-scripting)\n" ) );
|
|
return false;
|
|
}
|
|
|
|
if( g_pScriptVM == NULL )
|
|
{
|
|
ExecuteOnce( DevMsg(" Cannot execute script because there is no available VM\n" ) );
|
|
return false;
|
|
}
|
|
|
|
// Force instance creation
|
|
GetScriptInstance();
|
|
|
|
EHANDLE hThis;
|
|
hThis.Set( this );
|
|
|
|
bool bResult = m_ScriptScope.Init( STRING(m_iszScriptId) );
|
|
|
|
if( !bResult )
|
|
{
|
|
DevMsg("%s couldn't create ScriptScope!\n", GetDebugName() );
|
|
return false;
|
|
}
|
|
g_pScriptVM->SetValue( m_ScriptScope, "self", GetScriptInstance() );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Run all of the vscript files that are set in this entity's VSCRIPTS
|
|
// field in Hammer. The list is space-delimited.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseEntity::RunVScripts()
|
|
{
|
|
if( m_iszVScripts == NULL_STRING )
|
|
{
|
|
return;
|
|
}
|
|
|
|
ValidateScriptScope();
|
|
|
|
// All functions we want to have call chained instead of overwritten
|
|
// by other scripts in this entities list.
|
|
static const char * sCallChainFunctions[] =
|
|
{
|
|
"OnPostSpawn",
|
|
"Precache"
|
|
};
|
|
|
|
ScriptLanguage_t language = g_pScriptVM->GetLanguage();
|
|
|
|
// Make a call chainer for each in this entities scope
|
|
for ( int j = 0; j < ARRAYSIZE( sCallChainFunctions ); ++j )
|
|
{
|
|
|
|
if ( language == SL_PYTHON )
|
|
{
|
|
// UNDONE - handle call chaining in python
|
|
;
|
|
}
|
|
else if ( language == SL_SQUIRREL )
|
|
{
|
|
//TODO: For perf, this should be precompiled and the %s should be passed as a parameter
|
|
HSCRIPT hCreateChainScript = g_pScriptVM->CompileScript( CFmtStr( "%sCallChain <- CSimpleCallChainer(\"%s\", self.GetScriptScope(), true)", sCallChainFunctions[j], sCallChainFunctions[j] ) );
|
|
g_pScriptVM->Run( hCreateChainScript, (HSCRIPT)m_ScriptScope );
|
|
g_pScriptVM->ReleaseScript( hCreateChainScript );
|
|
}
|
|
}
|
|
|
|
char szScriptsList[255];
|
|
V_strcpy_safe( szScriptsList, STRING(m_iszVScripts) );
|
|
CUtlStringList szScripts;
|
|
|
|
V_SplitString( szScriptsList, " ", szScripts);
|
|
|
|
for( int i = 0 ; i < szScripts.Count() ; i++ )
|
|
{
|
|
Log_Msg( LOG_VScript, "%s executing script: %s\n", GetDebugName(), szScripts[i] );
|
|
|
|
RunScriptFile( szScripts[i], IsWorld() );
|
|
|
|
for ( int j = 0; j < ARRAYSIZE( sCallChainFunctions ); ++j )
|
|
{
|
|
if ( language == SL_PYTHON )
|
|
{
|
|
// UNDONE - handle call chaining in python
|
|
;
|
|
}
|
|
else if ( language == SL_SQUIRREL )
|
|
{
|
|
//TODO: For perf, this should be precompiled and the %s should be passed as a parameter.
|
|
HSCRIPT hRunPostScriptExecute = g_pScriptVM->CompileScript( CFmtStr( "%sCallChain.PostScriptExecute()", sCallChainFunctions[j] ) );
|
|
g_pScriptVM->Run( hRunPostScriptExecute, (HSCRIPT)m_ScriptScope );
|
|
g_pScriptVM->ReleaseScript( hRunPostScriptExecute );
|
|
}
|
|
}
|
|
}
|
|
|
|
if( m_iszScriptThinkFunction != NULL_STRING )
|
|
{
|
|
SetContextThink( &CBaseEntity::ScriptThink, gpGlobals->curtime + sv_script_think_interval.GetFloat(), "ScriptThink" );
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// This is called during entity spawning and after restore to allow scripts to precache any
|
|
// resources they need.
|
|
//--------------------------------------------------------------------------------------------------
|
|
void CBaseEntity::RunPrecacheScripts( void )
|
|
{
|
|
if( m_iszVScripts == NULL_STRING )
|
|
{
|
|
return;
|
|
}
|
|
|
|
HSCRIPT hScriptPrecache = m_ScriptScope.LookupFunction( "DispatchPrecache" );
|
|
if ( hScriptPrecache )
|
|
{
|
|
g_pScriptVM->Call( hScriptPrecache, m_ScriptScope );
|
|
m_ScriptScope.ReleaseFunction( hScriptPrecache );
|
|
}
|
|
}
|
|
|
|
void CBaseEntity::RunOnPostSpawnScripts( void )
|
|
{
|
|
if( m_iszVScripts == NULL_STRING )
|
|
{
|
|
return;
|
|
}
|
|
|
|
HSCRIPT hFuncConnect = g_pScriptVM->LookupFunction("ConnectOutputs");
|
|
if ( hFuncConnect )
|
|
{
|
|
g_pScriptVM->Call( hFuncConnect, NULL, true, NULL, (HSCRIPT)m_ScriptScope );
|
|
g_pScriptVM->ReleaseFunction( hFuncConnect );
|
|
}
|
|
|
|
HSCRIPT hFuncDisp = m_ScriptScope.LookupFunction( "DispatchOnPostSpawn" );
|
|
if ( hFuncDisp )
|
|
{
|
|
variant_t variant;
|
|
variant.SetString( MAKE_STRING("DispatchOnPostSpawn") );
|
|
g_EventQueue.AddEvent( this, "CallScriptFunction", variant, 0, this, this );
|
|
m_ScriptScope.ReleaseFunction( hFuncDisp );
|
|
|
|
}
|
|
}
|
|
|
|
HSCRIPT CBaseEntity::GetScriptOwnerEntity()
|
|
{
|
|
return ToHScript( GetOwnerEntity() );
|
|
}
|
|
|
|
void CBaseEntity::SetScriptOwnerEntity( HSCRIPT pOwner )
|
|
{
|
|
SetOwnerEntity( ToEnt( pOwner ) );
|
|
}
|
|
|
|
inline bool AnyPlayersInHierarchy_R( CBaseEntity *pEnt )
|
|
{
|
|
if ( pEnt->IsPlayer() )
|
|
return true;
|
|
|
|
for ( CBaseEntity *pCur = pEnt->FirstMoveChild(); pCur; pCur=pCur->NextMovePeer() )
|
|
{
|
|
if ( AnyPlayersInHierarchy_R( pCur ) )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void CBaseEntity::RecalcHasPlayerChildBit()
|
|
{
|
|
if ( AnyPlayersInHierarchy_R( this ) )
|
|
AddEFlags( EFL_HAS_PLAYER_CHILD );
|
|
else
|
|
RemoveEFlags( EFL_HAS_PLAYER_CHILD );
|
|
}
|
|
|
|
|
|
bool CBaseEntity::DoesHavePlayerChild()
|
|
{
|
|
return IsEFlagSet( EFL_HAS_PLAYER_CHILD );
|
|
}
|
|
|
|
void CBaseEntity::SetFriction( float flFriction )
|
|
{
|
|
m_flFriction = flFriction;
|
|
#if defined(ENABLE_FRICTION_OVERRIDE)
|
|
if ( GetIndexForThinkContext( "FrictionRevertThink" ) != NO_THINK_CONTEXT )
|
|
{
|
|
SetContextThink( NULL, TICK_NEVER_THINK, "FrictionRevertThink" );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if defined(ENABLE_FRICTION_OVERRIDE)
|
|
void CBaseEntity::FrictionRevertThink( void )
|
|
{
|
|
SetFriction( m_flOverriddenFriction );
|
|
}
|
|
|
|
|
|
void CBaseEntity::OverrideFriction( float duration, float friction )
|
|
{
|
|
if ( GetIndexForThinkContext( "FrictionRevertThink" ) == NO_THINK_CONTEXT || GetNextThinkTick( "FrictionRevertThink" ) == TICK_NEVER_THINK )
|
|
{
|
|
// not already overriding friction. this is what we'll restore to.
|
|
m_flOverriddenFriction = m_flFriction;
|
|
}
|
|
m_flFriction = friction;
|
|
SetContextThink( &CBaseEntity::FrictionRevertThink, gpGlobals->curtime + duration, "FrictionRevertThink" );
|
|
}
|
|
#endif
|
|
|
|
void CBaseEntity::SetNetworkQuantizeOriginAngAngles( bool bQuantize )
|
|
{
|
|
m_bNetworkQuantizeOriginAndAngles = bQuantize;
|
|
}
|
|
|
|
// NOTE: This only quantizes to the default entity precision currently used by CBaseEntity!!!
|
|
void CBaseEntity::NetworkQuantize( Vector &org, QAngle &angles )
|
|
{
|
|
#if PREDICTION_ERROR_CHECK_LEVEL < 2
|
|
// Angles are sent with 13 (SENDPROP_ANGROTATION_DEFAULT_BITS)bits to represent 0 -> 359.??? (SPROP_ROUNDDOWN)
|
|
const float QUANTIZE_MIN_ANGLE = 0.0f;
|
|
const float QUANTIZE_MAX_ANGLE = 360.0f - 360.0f / (float)( 1 << SENDPROP_ANGROTATION_DEFAULT_BITS );
|
|
const unsigned long QUANTIZE_HIGH_VALUE = ((1 << (unsigned long)SENDPROP_ANGROTATION_DEFAULT_BITS) - 1);
|
|
const double QUANTIZE_RANGE = QUANTIZE_MAX_ANGLE - QUANTIZE_MIN_ANGLE;
|
|
const float QUANTIZE_HIGHLOWMULTIPLIER = QUANTIZE_HIGH_VALUE / QUANTIZE_RANGE;
|
|
|
|
if ( !m_bNetworkQuantizeOriginAndAngles )
|
|
return;
|
|
|
|
if ( !( SENDPROP_VECORIGIN_FLAGS & SPROP_NOSCALE ) )
|
|
{
|
|
COMPILE_TIME_ASSERT( SENDPROP_VECORIGIN_FLAGS & ( SPROP_COORD | SPROP_CELL_COORD ) );
|
|
COMPILE_TIME_ASSERT( !(SENDPROP_VECORIGIN_FLAGS & ( SPROP_COORD_MP_LOWPRECISION | SPROP_COORD_MP_INTEGRAL ) ) );
|
|
|
|
for ( int i = 0 ; i < 3; ++i )
|
|
{
|
|
// Crop to exact bit precision
|
|
int tmp = RoundFloatToInt( org[ i ] * COORD_DENOMINATOR );
|
|
org[ i ] = (float)tmp * COORD_RESOLUTION;
|
|
}
|
|
}
|
|
|
|
if ( SENDPROP_ANGROTATION_DEFAULT_BITS != -1 )
|
|
{
|
|
for ( int i = 0 ; i < 3; ++i )
|
|
{
|
|
float flAngNormalized = anglemod( angles[ i ] );
|
|
float flAngle = ( flAngNormalized - QUANTIZE_MIN_ANGLE ) * QUANTIZE_HIGHLOWMULTIPLIER;
|
|
unsigned int uiAngle = RoundFloatToUnsignedLong( flAngle );
|
|
angles[ i ] = QUANTIZE_MIN_ANGLE + (float)uiAngle / QUANTIZE_HIGHLOWMULTIPLIER;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
bool CBaseEntity::ShouldLagCompensate() const
|
|
{
|
|
return m_bLagCompensate;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CBaseEntity::SetPendingTeamNum( int nTeamID )
|
|
{
|
|
m_iPendingTeamNum = nTeamID;
|
|
}
|
|
|
|
|
|
|
|
void CBaseEntity::ClearSpotRule( int bRuleFlags )
|
|
{
|
|
m_nSpotRules = m_nSpotRules & ~bRuleFlags;
|
|
}
|
|
|
|
void CBaseEntity::SetSpotRules( int nRuleFlags )
|
|
{
|
|
m_nSpotRules |= nRuleFlags;
|
|
}
|
|
|
|
bool CBaseEntity::IsSpotted()
|
|
{
|
|
return m_bSpotted;
|
|
}
|
|
|
|
void CBaseEntity::SetIsSpotted( bool bSpotted )
|
|
{
|
|
m_bSpotted = bSpotted;
|
|
}
|
|
|
|
void CBaseEntity::ClearSpottedBy( void )
|
|
{
|
|
for ( int i = 0; i < kNumSpottedByMask; i++ )
|
|
m_bSpottedByMask.Set(i,0);
|
|
}
|
|
|
|
void CBaseEntity::SetIsSpottedBy( int nPlayerEntIndex )
|
|
{
|
|
Assert( nPlayerEntIndex > 0 && nPlayerEntIndex <= MAX_PLAYERS );
|
|
if ( nPlayerEntIndex > 0 && nPlayerEntIndex <= MAX_PLAYERS )
|
|
{
|
|
int maskBitIndex = nPlayerEntIndex - 1; // player ents start at 1
|
|
int maskIndex = BitVec_Int( maskBitIndex );
|
|
m_bSpottedByMask.Set( maskIndex, m_bSpottedByMask.Get( maskIndex ) | BitVec_Bit( maskBitIndex ) );
|
|
}
|
|
}
|
|
|
|
int CBaseEntity::GetSpotRules( void )
|
|
{
|
|
return m_nSpotRules;
|
|
}
|
|
|
|
bool CBaseEntity::CanBeSpotted( void )
|
|
{
|
|
return ( m_nSpotRules != 0 );
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Create an NPC of the given type
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Create( const CCommand& args )
|
|
{
|
|
MDLCACHE_CRITICAL_SECTION();
|
|
|
|
bool allowPrecache = CBaseEntity::IsPrecacheAllowed();
|
|
CBaseEntity::SetAllowPrecache( true );
|
|
|
|
// Try to create entity
|
|
CBaseEntity *entity = dynamic_cast< CBaseEntity * >( CreateEntityByName(args[1]) );
|
|
if (entity)
|
|
{
|
|
if ( entity->IsPlayer() )
|
|
{
|
|
AssertMsg( false, "Cannot ent_create players!" );
|
|
Warning( "Cannot ent_create players!\n" );
|
|
UTIL_Remove( entity );
|
|
}
|
|
else
|
|
{
|
|
entity->Precache();
|
|
DispatchSpawn(entity);
|
|
// Now attempt to drop into the world
|
|
CBasePlayer* pPlayer = UTIL_GetCommandClient();
|
|
trace_t tr;
|
|
Vector forward;
|
|
pPlayer->EyeVectors( &forward );
|
|
UTIL_TraceLine(pPlayer->EyePosition(),
|
|
pPlayer->EyePosition() + forward * MAX_TRACE_LENGTH,MASK_SOLID,
|
|
pPlayer, COLLISION_GROUP_NONE, &tr );
|
|
if ( tr.fraction != 1.0 )
|
|
{
|
|
// Raise the end position a little up off the floor, place the npc and drop him down
|
|
tr.endpos.z += 12;
|
|
|
|
QAngle vecOppositePlayerAngles = pPlayer->GetAbsAngles();
|
|
vecOppositePlayerAngles[YAW] += 180;
|
|
|
|
entity->Teleport( &tr.endpos, &vecOppositePlayerAngles, NULL );
|
|
UTIL_DropToFloor( entity, MASK_SOLID );
|
|
}
|
|
}
|
|
}
|
|
CBaseEntity::SetAllowPrecache( allowPrecache );
|
|
}
|
|
static ConCommand ent_create("ent_create", CC_Ent_Create, "Creates an entity of the given type where the player is looking.", FCVAR_GAMEDLL | FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Teleport a specified entity to where the player is looking
|
|
//------------------------------------------------------------------------------
|
|
bool CC_GetCommandEnt( const CCommand& args, CBaseEntity **ent, Vector *vecTargetPoint, QAngle *vecPlayerAngle )
|
|
{
|
|
// Find the entity
|
|
*ent = NULL;
|
|
// First try using it as an entindex
|
|
int iEntIndex = atoi( args[1] );
|
|
if ( iEntIndex )
|
|
{
|
|
*ent = CBaseEntity::Instance( iEntIndex );
|
|
}
|
|
else
|
|
{
|
|
// Try finding it by name
|
|
*ent = gEntList.FindEntityByName( NULL, args[1] );
|
|
|
|
if ( !*ent )
|
|
{
|
|
// Finally, try finding it by classname
|
|
*ent = gEntList.FindEntityByClassname( NULL, args[1] );
|
|
}
|
|
}
|
|
|
|
if ( !*ent )
|
|
{
|
|
Msg( "Couldn't find any entity named '%s'\n", args[1] );
|
|
return false;
|
|
}
|
|
|
|
CBasePlayer *pPlayer = UTIL_GetCommandClient();
|
|
if ( vecTargetPoint )
|
|
{
|
|
trace_t tr;
|
|
Vector forward;
|
|
pPlayer->EyeVectors( &forward );
|
|
UTIL_TraceLine(pPlayer->EyePosition(),
|
|
pPlayer->EyePosition() + forward * MAX_TRACE_LENGTH,MASK_NPCSOLID,
|
|
pPlayer, COLLISION_GROUP_NONE, &tr );
|
|
|
|
if ( tr.fraction != 1.0 )
|
|
{
|
|
*vecTargetPoint = tr.endpos;
|
|
}
|
|
}
|
|
|
|
if ( vecPlayerAngle )
|
|
{
|
|
*vecPlayerAngle = pPlayer->EyeAngles();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Teleport a specified entity to where the player is looking
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Teleport( const CCommand& args )
|
|
{
|
|
if ( args.ArgC() < 2 )
|
|
{
|
|
Msg( "Format: ent_teleport <entity name>\n" );
|
|
return;
|
|
}
|
|
|
|
CBaseEntity *pEnt;
|
|
Vector vecTargetPoint;
|
|
if ( CC_GetCommandEnt( args, &pEnt, &vecTargetPoint, NULL ) )
|
|
{
|
|
pEnt->Teleport( &vecTargetPoint, NULL, NULL );
|
|
}
|
|
}
|
|
|
|
static ConCommand ent_teleport("ent_teleport", CC_Ent_Teleport, "Teleport the specified entity to where the player is looking.\n\tFormat: ent_teleport <entity name>", FCVAR_CHEAT);
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Orient a specified entity to match the player's angles
|
|
//------------------------------------------------------------------------------
|
|
void CC_Ent_Orient( const CCommand& args )
|
|
{
|
|
if ( args.ArgC() < 2 )
|
|
{
|
|
Msg( "Format: ent_orient <entity name> <optional: allangles>\n" );
|
|
return;
|
|
}
|
|
|
|
CBaseEntity *pEnt;
|
|
QAngle vecPlayerAngles;
|
|
if ( CC_GetCommandEnt( args, &pEnt, NULL, &vecPlayerAngles ) )
|
|
{
|
|
QAngle vecEntAngles = pEnt->GetAbsAngles();
|
|
if ( args.ArgC() == 3 && StringHasPrefixCaseSensitive( args[2], "allangles" ) )
|
|
{
|
|
vecEntAngles = vecPlayerAngles;
|
|
}
|
|
else
|
|
{
|
|
vecEntAngles[YAW] = vecPlayerAngles[YAW];
|
|
}
|
|
|
|
pEnt->SetAbsAngles( vecEntAngles );
|
|
}
|
|
}
|
|
|
|
static ConCommand ent_orient("ent_orient", CC_Ent_Orient, "Orient the specified entity to match the player's angles. By default, only orients target entity's YAW. Use the 'allangles' option to orient on all axis.\n\tFormat: ent_orient <entity name> <optional: allangles>", FCVAR_CHEAT);
|