You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1871 lines
48 KiB
1871 lines
48 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
// |
|
//=============================================================================// |
|
/* |
|
Entity Data Descriptions |
|
|
|
Each entity has an array which defines it's data in way that is useful for |
|
entity communication, parsing initial values from the map, and save/restore. |
|
|
|
each entity has to have the following line in it's class declaration: |
|
|
|
DECLARE_DATADESC(); |
|
|
|
this line defines that it has an m_DataDesc[] array, and declares functions through |
|
which various subsystems can iterate the data. |
|
|
|
In it's implementation, each entity has to have: |
|
|
|
typedescription_t CBaseEntity::m_DataDesc[] = { ... } |
|
|
|
in which all it's data is defined (see below), followed by: |
|
|
|
|
|
which implements the functions necessary for iterating through an entities data desc. |
|
|
|
There are several types of data: |
|
|
|
FIELD : this is a variable which gets saved & loaded from disk |
|
KEY : this variable can be read in from the map file |
|
GLOBAL : a global field is actually local; it is saved/restored, but is actually |
|
unique to the entity on each level. |
|
CUSTOM : the save/restore parsing functions are described by the user. |
|
ARRAY : an array of values |
|
OUTPUT : a variable or event that can be connected to other entities (see below) |
|
INPUTFUNC : maps a string input to a function pointer. Outputs connected to this input |
|
will call the notify function when fired. |
|
INPUT : maps a string input to a member variable. Outputs connected to this input |
|
will update the input data value when fired. |
|
INPUTNOTIFY : maps a string input to a member variable/function pointer combo. Outputs |
|
connected to this input will update the data value and call the notify |
|
function when fired. |
|
|
|
some of these can overlap. all the data descriptions usable are: |
|
|
|
DEFINE_FIELD( name, fieldtype ) |
|
DEFINE_KEYFIELD( name, fieldtype, mapname ) |
|
DEFINE_KEYFIELD_NOTSAVED( name, fieldtype, mapname ) |
|
DEFINE_ARRAY( name, fieldtype, count ) |
|
DEFINE_GLOBAL_FIELD(name, fieldtype ) |
|
DEFINE_CUSTOM_FIELD(name, datafuncs, mapname ) |
|
DEFINE_GLOBAL_KEYFIELD(name, fieldtype, mapname ) |
|
|
|
where: |
|
type is the name of the class (eg. CBaseEntity) |
|
name is the name of the variable in the class (eg. m_iHealth) |
|
fieldtype is the type of data (FIELD_STRING, FIELD_INTEGER, etc) |
|
mapname is the string by which this variable is associated with map file data |
|
count is the number of items in the array |
|
datafuncs is a struct containing function pointers for a custom-defined save/restore/parse |
|
|
|
OUTPUTS: |
|
|
|
DEFINE_OUTPUT( outputvar, outputname ) |
|
|
|
This maps the string 'outputname' to the COutput-derived member variable outputvar. In the VMF |
|
file these outputs can be hooked up to inputs (see above). Whenever the internal state |
|
of an entity changes it will often fire off outputs so that map makers can hook up behaviors. |
|
e.g. A door entity would have OnDoorOpen, OnDoorClose, OnTouched, etc outputs. |
|
*/ |
|
|
|
|
|
#include "cbase.h" |
|
#include "entitylist.h" |
|
#include "mapentities_shared.h" |
|
#include "isaverestore.h" |
|
#include "eventqueue.h" |
|
#include "entityinput.h" |
|
#include "entityoutput.h" |
|
#include "mempool.h" |
|
#include "tier1/strtools.h" |
|
#include "datacache/imdlcache.h" |
|
#include "env_debughistory.h" |
|
|
|
#include "tier0/vprof.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
extern ISaveRestoreOps *variantFuncs; // function pointer set for save/restoring variants |
|
|
|
BEGIN_SIMPLE_DATADESC( CEventAction ) |
|
DEFINE_FIELD( m_iTarget, FIELD_STRING ), |
|
DEFINE_FIELD( m_iTargetInput, FIELD_STRING ), |
|
DEFINE_FIELD( m_iParameter, FIELD_STRING ), |
|
DEFINE_FIELD( m_flDelay, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_nTimesToFire, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_iIDStamp, FIELD_INTEGER ), |
|
|
|
// This is dealt with by the Restore method |
|
// DEFINE_FIELD( m_pNext, CEventAction ), |
|
END_DATADESC() |
|
|
|
|
|
// ID Stamp used to uniquely identify every output |
|
int CEventAction::s_iNextIDStamp = 0; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Creates an event action and assigns it an unique ID stamp. |
|
// Input : ActionData - the map file data block descibing the event action. |
|
//----------------------------------------------------------------------------- |
|
CEventAction::CEventAction( const char *ActionData ) |
|
{ |
|
m_pNext = NULL; |
|
m_iIDStamp = ++s_iNextIDStamp; |
|
|
|
m_flDelay = 0; |
|
m_iTarget = NULL_STRING; |
|
m_iParameter = NULL_STRING; |
|
m_iTargetInput = NULL_STRING; |
|
m_nTimesToFire = EVENT_FIRE_ALWAYS; |
|
|
|
if (ActionData == NULL) |
|
return; |
|
|
|
char szToken[256]; |
|
|
|
// |
|
// Parse the target name. |
|
// |
|
const char *psz = nexttoken(szToken, ActionData, ','); |
|
if (szToken[0] != '\0') |
|
{ |
|
m_iTarget = AllocPooledString(szToken); |
|
} |
|
|
|
// |
|
// Parse the input name. |
|
// |
|
psz = nexttoken(szToken, psz, ','); |
|
if (szToken[0] != '\0') |
|
{ |
|
m_iTargetInput = AllocPooledString(szToken); |
|
} |
|
else |
|
{ |
|
m_iTargetInput = AllocPooledString("Use"); |
|
} |
|
|
|
// |
|
// Parse the parameter override. |
|
// |
|
psz = nexttoken(szToken, psz, ','); |
|
if (szToken[0] != '\0') |
|
{ |
|
m_iParameter = AllocPooledString(szToken); |
|
} |
|
|
|
// |
|
// Parse the delay. |
|
// |
|
psz = nexttoken(szToken, psz, ','); |
|
if (szToken[0] != '\0') |
|
{ |
|
m_flDelay = atof(szToken); |
|
} |
|
|
|
// |
|
// Parse the number of times to fire. |
|
// |
|
nexttoken(szToken, psz, ','); |
|
if (szToken[0] != '\0') |
|
{ |
|
m_nTimesToFire = atoi(szToken); |
|
if (m_nTimesToFire == 0) |
|
{ |
|
m_nTimesToFire = EVENT_FIRE_ALWAYS; |
|
} |
|
} |
|
} |
|
|
|
|
|
// this memory pool stores blocks around the size of CEventAction/inputitem_t structs |
|
// can be used for other blocks; will error if to big a block is tried to be allocated |
|
CUtlMemoryPool g_EntityListPool( MAX(sizeof(CEventAction),sizeof(CMultiInputVar::inputitem_t)), 512, CUtlMemoryPool::GROW_FAST, "g_EntityListPool" ); |
|
|
|
#include "tier0/memdbgoff.h" |
|
|
|
void *CEventAction::operator new( size_t stAllocateBlock ) |
|
{ |
|
return g_EntityListPool.Alloc( stAllocateBlock ); |
|
} |
|
|
|
void *CEventAction::operator new( size_t stAllocateBlock, int nBlockUse, const char *pFileName, int nLine ) |
|
{ |
|
return g_EntityListPool.Alloc( stAllocateBlock ); |
|
} |
|
|
|
void CEventAction::operator delete( void *pMem ) |
|
{ |
|
g_EntityListPool.Free( pMem ); |
|
} |
|
|
|
#include "tier0/memdbgon.h" |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns the highest-valued delay in our list of event actions. |
|
//----------------------------------------------------------------------------- |
|
float CBaseEntityOutput::GetMaxDelay(void) |
|
{ |
|
float flMaxDelay = 0; |
|
CEventAction *ev = m_ActionList; |
|
|
|
while (ev != NULL) |
|
{ |
|
if (ev->m_flDelay > flMaxDelay) |
|
{ |
|
flMaxDelay = ev->m_flDelay; |
|
} |
|
ev = ev->m_pNext; |
|
} |
|
|
|
return(flMaxDelay); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Destructor. |
|
//----------------------------------------------------------------------------- |
|
CBaseEntityOutput::~CBaseEntityOutput() |
|
{ |
|
CEventAction *ev = m_ActionList; |
|
while (ev != NULL) |
|
{ |
|
CEventAction *pNext = ev->m_pNext; |
|
delete ev; |
|
ev = pNext; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Fires the event, causing a sequence of action to occur in other ents. |
|
// Input : pActivator - Entity that initiated this sequence of actions. |
|
// pCaller - Entity that is actually causing the event. |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntityOutput::FireOutput(variant_t Value, CBaseEntity *pActivator, CBaseEntity *pCaller, float fDelay) |
|
{ |
|
// |
|
// Iterate through all eventactions and fire them off. |
|
// |
|
CEventAction *ev = m_ActionList; |
|
CEventAction *prev = NULL; |
|
|
|
while (ev != NULL) |
|
{ |
|
if (ev->m_iParameter == NULL_STRING) |
|
{ |
|
// |
|
// Post the event with the default parameter. |
|
// |
|
g_EventQueue.AddEvent( STRING(ev->m_iTarget), STRING(ev->m_iTargetInput), Value, ev->m_flDelay + fDelay, pActivator, pCaller, ev->m_iIDStamp ); |
|
} |
|
else |
|
{ |
|
// |
|
// Post the event with a parameter override. |
|
// |
|
variant_t ValueOverride; |
|
ValueOverride.SetString( ev->m_iParameter ); |
|
g_EventQueue.AddEvent( STRING(ev->m_iTarget), STRING(ev->m_iTargetInput), ValueOverride, ev->m_flDelay, pActivator, pCaller, ev->m_iIDStamp ); |
|
} |
|
|
|
if ( ev->m_flDelay ) |
|
{ |
|
char szBuffer[256]; |
|
Q_snprintf( szBuffer, |
|
sizeof(szBuffer), |
|
"(%0.2f) output: (%s,%s) -> (%s,%s,%.1f)(%s)\n", |
|
#ifdef TF_DLL |
|
engine->GetServerTime(), |
|
#else |
|
gpGlobals->curtime, |
|
#endif |
|
pCaller ? STRING(pCaller->m_iClassname) : "NULL", |
|
pCaller ? STRING(pCaller->GetEntityName()) : "NULL", |
|
STRING(ev->m_iTarget), |
|
STRING(ev->m_iTargetInput), |
|
ev->m_flDelay, |
|
STRING(ev->m_iParameter) ); |
|
|
|
DevMsg( 2, "%s", szBuffer ); |
|
ADD_DEBUG_HISTORY( HISTORY_ENTITY_IO, szBuffer ); |
|
} |
|
else |
|
{ |
|
char szBuffer[256]; |
|
Q_snprintf( szBuffer, |
|
sizeof(szBuffer), |
|
"(%0.2f) output: (%s,%s) -> (%s,%s)(%s)\n", |
|
#ifdef TF_DLL |
|
engine->GetServerTime(), |
|
#else |
|
gpGlobals->curtime, |
|
#endif |
|
pCaller ? STRING(pCaller->m_iClassname) : "NULL", |
|
pCaller ? STRING(pCaller->GetEntityName()) : "NULL", STRING(ev->m_iTarget), |
|
STRING(ev->m_iTargetInput), |
|
STRING(ev->m_iParameter) ); |
|
|
|
DevMsg( 2, "%s", szBuffer ); |
|
ADD_DEBUG_HISTORY( HISTORY_ENTITY_IO, szBuffer ); |
|
} |
|
|
|
if ( pCaller && pCaller->m_debugOverlays & OVERLAY_MESSAGE_BIT) |
|
{ |
|
pCaller->DrawOutputOverlay(ev); |
|
} |
|
|
|
// |
|
// Remove the event action from the list if it was set to be fired a finite |
|
// number of times (and has been). |
|
// |
|
bool bRemove = false; |
|
if (ev->m_nTimesToFire != EVENT_FIRE_ALWAYS) |
|
{ |
|
ev->m_nTimesToFire--; |
|
if (ev->m_nTimesToFire == 0) |
|
{ |
|
char szBuffer[256]; |
|
Q_snprintf( szBuffer, sizeof(szBuffer), "Removing from action list: (%s,%s) -> (%s,%s)\n", pCaller ? STRING(pCaller->m_iClassname) : "NULL", pCaller ? STRING(pCaller->GetEntityName()) : "NULL", STRING(ev->m_iTarget), STRING(ev->m_iTargetInput)); |
|
DevMsg( 2, "%s", szBuffer ); |
|
ADD_DEBUG_HISTORY( HISTORY_ENTITY_IO, szBuffer ); |
|
bRemove = true; |
|
} |
|
} |
|
|
|
if (!bRemove) |
|
{ |
|
prev = ev; |
|
ev = ev->m_pNext; |
|
} |
|
else |
|
{ |
|
if (prev != NULL) |
|
{ |
|
prev->m_pNext = ev->m_pNext; |
|
} |
|
else |
|
{ |
|
m_ActionList = ev->m_pNext; |
|
} |
|
|
|
CEventAction *next = ev->m_pNext; |
|
delete ev; |
|
ev = next; |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Parameterless firing of an event |
|
// Input : pActivator - |
|
// pCaller - |
|
//----------------------------------------------------------------------------- |
|
void COutputEvent::FireOutput(CBaseEntity *pActivator, CBaseEntity *pCaller, float fDelay) |
|
{ |
|
variant_t Val; |
|
Val.Set( FIELD_VOID, NULL ); |
|
CBaseEntityOutput::FireOutput(Val, pActivator, pCaller, fDelay); |
|
} |
|
|
|
|
|
void CBaseEntityOutput::ParseEventAction( const char *EventData ) |
|
{ |
|
AddEventAction( new CEventAction( EventData ) ); |
|
} |
|
|
|
void CBaseEntityOutput::AddEventAction( CEventAction *pEventAction ) |
|
{ |
|
pEventAction->m_pNext = m_ActionList; |
|
m_ActionList = pEventAction; |
|
} |
|
|
|
|
|
// save data description for the event queue |
|
BEGIN_SIMPLE_DATADESC( CBaseEntityOutput ) |
|
|
|
DEFINE_CUSTOM_FIELD( m_Value, variantFuncs ), |
|
|
|
// This is saved manually by CBaseEntityOutput::Save |
|
// DEFINE_FIELD( m_ActionList, CEventAction ), |
|
END_DATADESC() |
|
|
|
|
|
int CBaseEntityOutput::Save( ISave &save ) |
|
{ |
|
// save that value out to disk, so we know how many to restore |
|
if ( !save.WriteFields( "Value", this, NULL, m_DataMap.dataDesc, m_DataMap.dataNumFields ) ) |
|
return 0; |
|
|
|
for ( CEventAction *ev = m_ActionList; ev != NULL; ev = ev->m_pNext ) |
|
{ |
|
if ( !save.WriteFields( "EntityOutput", ev, NULL, ev->m_DataMap.dataDesc, ev->m_DataMap.dataNumFields ) ) |
|
return 0; |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
int CBaseEntityOutput::Restore( IRestore &restore, int elementCount ) |
|
{ |
|
// load the number of items saved |
|
if ( !restore.ReadFields( "Value", this, NULL, m_DataMap.dataDesc, m_DataMap.dataNumFields ) ) |
|
return 0; |
|
|
|
m_ActionList = NULL; |
|
|
|
// read in all the fields |
|
CEventAction *lastEv = NULL; |
|
for ( int i = 0; i < elementCount; i++ ) |
|
{ |
|
CEventAction *ev = new CEventAction(NULL); |
|
|
|
if ( !restore.ReadFields( "EntityOutput", ev, NULL, ev->m_DataMap.dataDesc, ev->m_DataMap.dataNumFields ) ) |
|
return 0; |
|
|
|
// add it to the list in the same order it was saved in |
|
if ( lastEv ) |
|
{ |
|
lastEv->m_pNext = ev; |
|
} |
|
else |
|
{ |
|
m_ActionList = ev; |
|
} |
|
ev->m_pNext = NULL; |
|
lastEv = ev; |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
int CBaseEntityOutput::NumberOfElements( void ) |
|
{ |
|
int count = 0; |
|
for ( CEventAction *ev = m_ActionList; ev != NULL; ev = ev->m_pNext ) |
|
{ |
|
count++; |
|
} |
|
return count; |
|
} |
|
|
|
/// Delete every single action in the action list. |
|
void CBaseEntityOutput::DeleteAllElements( void ) |
|
{ |
|
// walk front to back, deleting as we go. We needn't fix up pointers because |
|
// EVERYTHING will die. |
|
|
|
CEventAction *pNext = m_ActionList; |
|
// wipe out the head |
|
m_ActionList = NULL; |
|
while (pNext) |
|
{ |
|
register CEventAction *strikeThis = pNext; |
|
pNext = pNext->m_pNext; |
|
delete strikeThis; |
|
} |
|
|
|
} |
|
|
|
/// EVENTS save/restore parsing wrapper |
|
|
|
class CEventsSaveDataOps : public ISaveRestoreOps |
|
{ |
|
virtual void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave ) |
|
{ |
|
AssertMsg( fieldInfo.pTypeDesc->fieldSize == 1, "CEventsSaveDataOps does not support arrays"); |
|
|
|
CBaseEntityOutput *ev = (CBaseEntityOutput*)fieldInfo.pField; |
|
const int fieldSize = fieldInfo.pTypeDesc->fieldSize; |
|
for ( int i = 0; i < fieldSize; i++, ev++ ) |
|
{ |
|
// save out the number of fields |
|
int numElements = ev->NumberOfElements(); |
|
pSave->WriteInt( &numElements, 1 ); |
|
|
|
// save the event data |
|
ev->Save( *pSave ); |
|
} |
|
} |
|
|
|
virtual void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore ) |
|
{ |
|
AssertMsg( fieldInfo.pTypeDesc->fieldSize == 1, "CEventsSaveDataOps does not support arrays"); |
|
|
|
CBaseEntityOutput *ev = (CBaseEntityOutput*)fieldInfo.pField; |
|
const int fieldSize = fieldInfo.pTypeDesc->fieldSize; |
|
for ( int i = 0; i < fieldSize; i++, ev++ ) |
|
{ |
|
int nElements = pRestore->ReadInt(); |
|
|
|
Assert( nElements < 100 ); |
|
|
|
ev->Restore( *pRestore, nElements ); |
|
} |
|
} |
|
|
|
virtual bool IsEmpty( const SaveRestoreFieldInfo_t &fieldInfo ) |
|
{ |
|
AssertMsg( fieldInfo.pTypeDesc->fieldSize == 1, "CEventsSaveDataOps does not support arrays"); |
|
|
|
// check all the elements of the array (usually only 1) |
|
CBaseEntityOutput *ev = (CBaseEntityOutput*)fieldInfo.pField; |
|
const int fieldSize = fieldInfo.pTypeDesc->fieldSize; |
|
for ( int i = 0; i < fieldSize; i++, ev++ ) |
|
{ |
|
// It's not empty if it has events or if it has a non-void variant value |
|
if (( ev->NumberOfElements() != 0 ) || ( ev->ValueFieldType() != FIELD_VOID )) |
|
return 0; |
|
} |
|
|
|
// variant has no data |
|
return 1; |
|
} |
|
|
|
virtual void MakeEmpty( const SaveRestoreFieldInfo_t &fieldInfo ) |
|
{ |
|
// Don't no how to. This is okay, since objects of this type |
|
// are always born clean before restore, and not reused |
|
} |
|
|
|
virtual bool Parse( const SaveRestoreFieldInfo_t &fieldInfo, char const* szValue ) |
|
{ |
|
CBaseEntityOutput *ev = (CBaseEntityOutput*)fieldInfo.pField; |
|
ev->ParseEventAction( szValue ); |
|
return true; |
|
} |
|
}; |
|
|
|
CEventsSaveDataOps g_EventsSaveDataOps; |
|
ISaveRestoreOps *eventFuncs = &g_EventsSaveDataOps; |
|
|
|
//----------------------------------------------------------------------------- |
|
// CMultiInputVar implementation |
|
// |
|
// Purpose: holds a list of inputs and their ID tags |
|
// used for entities that hold inputs from a set of other entities |
|
//----------------------------------------------------------------------------- |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: destructor, frees the data list |
|
//----------------------------------------------------------------------------- |
|
CMultiInputVar::~CMultiInputVar() |
|
{ |
|
if ( m_InputList ) |
|
{ |
|
while ( m_InputList->next != NULL ) |
|
{ |
|
inputitem_t *input = m_InputList->next; |
|
m_InputList->next = input->next; |
|
delete input; |
|
} |
|
delete m_InputList; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Updates the data set with a new value |
|
// Input : newVal - the new value to add to or update in the list |
|
// outputID - the source of the value |
|
//----------------------------------------------------------------------------- |
|
void CMultiInputVar::AddValue( variant_t newVal, int outputID ) |
|
{ |
|
// see if it's already in the list |
|
inputitem_t *inp; |
|
for ( inp = m_InputList; inp != NULL; inp = inp->next ) |
|
{ |
|
// already in list, so just update this link |
|
if ( inp->outputID == outputID ) |
|
{ |
|
inp->value = newVal; |
|
return; |
|
} |
|
} |
|
|
|
// add to start of list |
|
inp = new inputitem_t; |
|
inp->value = newVal; |
|
inp->outputID = outputID; |
|
if ( !m_InputList ) |
|
{ |
|
m_InputList = inp; |
|
inp->next = NULL; |
|
} |
|
else |
|
{ |
|
inp->next = m_InputList; |
|
m_InputList = inp; |
|
} |
|
} |
|
|
|
|
|
#include "tier0/memdbgoff.h" |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: allocates memory from the entitylist pool |
|
//----------------------------------------------------------------------------- |
|
void *CMultiInputVar::inputitem_t::operator new( size_t stAllocateBlock ) |
|
{ |
|
return g_EntityListPool.Alloc( stAllocateBlock ); |
|
} |
|
|
|
void *CMultiInputVar::inputitem_t::operator new( size_t stAllocateBlock, int nBlockUse, const char *pFileName, int nLine ) |
|
{ |
|
return g_EntityListPool.Alloc( stAllocateBlock ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: frees memory from the entitylist pool |
|
//----------------------------------------------------------------------------- |
|
void CMultiInputVar::inputitem_t::operator delete( void *pMem ) |
|
{ |
|
g_EntityListPool.Free( pMem ); |
|
} |
|
|
|
#include "tier0/memdbgon.h" |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// CEventQueue implementation |
|
// |
|
// Purpose: holds and executes a global prioritized queue of entity actions |
|
//----------------------------------------------------------------------------- |
|
DEFINE_FIXEDSIZE_ALLOCATOR( EventQueuePrioritizedEvent_t, 128, CUtlMemoryPool::GROW_SLOW ); |
|
|
|
CEventQueue g_EventQueue; |
|
|
|
CEventQueue::CEventQueue() |
|
{ |
|
m_Events.m_flFireTime = -FLT_MAX; |
|
m_Events.m_pNext = NULL; |
|
|
|
Init(); |
|
} |
|
|
|
CEventQueue::~CEventQueue() |
|
{ |
|
Clear(); |
|
} |
|
|
|
// Robin: Left here for backwards compatability. |
|
class CEventQueueSaveLoadProxy : public CLogicalEntity |
|
{ |
|
DECLARE_CLASS( CEventQueueSaveLoadProxy, CLogicalEntity ); |
|
|
|
int Save( ISave &save ) |
|
{ |
|
if ( !BaseClass::Save(save) ) |
|
return 0; |
|
|
|
// save out the message queue |
|
return g_EventQueue.Save( save ); |
|
} |
|
|
|
|
|
int Restore( IRestore &restore ) |
|
{ |
|
if ( !BaseClass::Restore(restore) ) |
|
return 0; |
|
|
|
// restore the event queue |
|
int iReturn = g_EventQueue.Restore( restore ); |
|
|
|
// Now remove myself, because the CEventQueue_SaveRestoreBlockHandler |
|
// will handle future saves. |
|
UTIL_Remove( this ); |
|
|
|
return iReturn; |
|
} |
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS(event_queue_saveload_proxy, CEventQueueSaveLoadProxy); |
|
|
|
//----------------------------------------------------------------------------- |
|
// EVENT QUEUE SAVE / RESTORE |
|
//----------------------------------------------------------------------------- |
|
static short EVENTQUEUE_SAVE_RESTORE_VERSION = 1; |
|
|
|
class CEventQueue_SaveRestoreBlockHandler : public CDefSaveRestoreBlockHandler |
|
{ |
|
public: |
|
const char *GetBlockName() |
|
{ |
|
return "EventQueue"; |
|
} |
|
|
|
//--------------------------------- |
|
|
|
void Save( ISave *pSave ) |
|
{ |
|
g_EventQueue.Save( *pSave ); |
|
} |
|
|
|
//--------------------------------- |
|
|
|
void WriteSaveHeaders( ISave *pSave ) |
|
{ |
|
pSave->WriteShort( &EVENTQUEUE_SAVE_RESTORE_VERSION ); |
|
} |
|
|
|
//--------------------------------- |
|
|
|
void ReadRestoreHeaders( IRestore *pRestore ) |
|
{ |
|
// No reason why any future version shouldn't try to retain backward compatability. The default here is to not do so. |
|
short version; |
|
pRestore->ReadShort( &version ); |
|
m_fDoLoad = ( version == EVENTQUEUE_SAVE_RESTORE_VERSION ); |
|
} |
|
|
|
//--------------------------------- |
|
|
|
void Restore( IRestore *pRestore, bool createPlayers ) |
|
{ |
|
if ( m_fDoLoad ) |
|
{ |
|
g_EventQueue.Restore( *pRestore ); |
|
} |
|
} |
|
|
|
private: |
|
bool m_fDoLoad; |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
CEventQueue_SaveRestoreBlockHandler g_EventQueue_SaveRestoreBlockHandler; |
|
|
|
//------------------------------------- |
|
|
|
ISaveRestoreBlockHandler *GetEventQueueSaveRestoreBlockHandler() |
|
{ |
|
return &g_EventQueue_SaveRestoreBlockHandler; |
|
} |
|
|
|
|
|
void CEventQueue::Init( void ) |
|
{ |
|
Clear(); |
|
} |
|
|
|
void CEventQueue::Clear( void ) |
|
{ |
|
// delete all the events in the queue |
|
EventQueuePrioritizedEvent_t *pe = m_Events.m_pNext; |
|
|
|
while ( pe != NULL ) |
|
{ |
|
EventQueuePrioritizedEvent_t *next = pe->m_pNext; |
|
delete pe; |
|
pe = next; |
|
} |
|
|
|
m_Events.m_pNext = NULL; |
|
} |
|
|
|
void CEventQueue::Dump( void ) |
|
{ |
|
EventQueuePrioritizedEvent_t *pe = m_Events.m_pNext; |
|
|
|
Msg("Dumping event queue. Current time is: %.2f\n", |
|
#ifdef TF_DLL |
|
engine->GetServerTime() |
|
#else |
|
gpGlobals->curtime |
|
#endif |
|
); |
|
|
|
while ( pe != NULL ) |
|
{ |
|
EventQueuePrioritizedEvent_t *next = pe->m_pNext; |
|
|
|
Msg(" (%.2f) Target: '%s', Input: '%s', Parameter '%s'. Activator: '%s', Caller '%s'. \n", |
|
pe->m_flFireTime, |
|
STRING(pe->m_iTarget), |
|
STRING(pe->m_iTargetInput), |
|
pe->m_VariantValue.String(), |
|
pe->m_pActivator ? pe->m_pActivator->GetDebugName() : "None", |
|
pe->m_pCaller ? pe->m_pCaller->GetDebugName() : "None" ); |
|
|
|
pe = next; |
|
} |
|
|
|
Msg("Finished dump.\n"); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: adds the action into the correct spot in the priority queue, targeting entity via string name |
|
//----------------------------------------------------------------------------- |
|
void CEventQueue::AddEvent( const char *target, const char *targetInput, variant_t Value, float fireDelay, CBaseEntity *pActivator, CBaseEntity *pCaller, int outputID ) |
|
{ |
|
// build the new event |
|
EventQueuePrioritizedEvent_t *newEvent = new EventQueuePrioritizedEvent_t; |
|
#ifdef TF_DLL |
|
newEvent->m_flFireTime = engine->GetServerTime() + fireDelay; // priority key in the priority queue |
|
#else |
|
newEvent->m_flFireTime = gpGlobals->curtime + fireDelay; // priority key in the priority queue |
|
#endif |
|
newEvent->m_iTarget = MAKE_STRING( target ); |
|
newEvent->m_pEntTarget = NULL; |
|
newEvent->m_iTargetInput = MAKE_STRING( targetInput ); |
|
newEvent->m_pActivator = pActivator; |
|
newEvent->m_pCaller = pCaller; |
|
newEvent->m_VariantValue = Value; |
|
newEvent->m_iOutputID = outputID; |
|
|
|
AddEvent( newEvent ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: adds the action into the correct spot in the priority queue, targeting entity via pointer |
|
//----------------------------------------------------------------------------- |
|
void CEventQueue::AddEvent( CBaseEntity *target, const char *targetInput, variant_t Value, float fireDelay, CBaseEntity *pActivator, CBaseEntity *pCaller, int outputID ) |
|
{ |
|
// build the new event |
|
EventQueuePrioritizedEvent_t *newEvent = new EventQueuePrioritizedEvent_t; |
|
#ifdef TF_DLL |
|
newEvent->m_flFireTime = engine->GetServerTime() + fireDelay; // primary priority key in the priority queue |
|
#else |
|
newEvent->m_flFireTime = gpGlobals->curtime + fireDelay; // primary priority key in the priority queue |
|
#endif |
|
newEvent->m_iTarget = NULL_STRING; |
|
newEvent->m_pEntTarget = target; |
|
newEvent->m_iTargetInput = MAKE_STRING( targetInput ); |
|
newEvent->m_pActivator = pActivator; |
|
newEvent->m_pCaller = pCaller; |
|
newEvent->m_VariantValue = Value; |
|
newEvent->m_iOutputID = outputID; |
|
|
|
AddEvent( newEvent ); |
|
} |
|
|
|
void CEventQueue::AddEvent( CBaseEntity *target, const char *action, float fireDelay, CBaseEntity *pActivator, CBaseEntity *pCaller, int outputID ) |
|
{ |
|
variant_t Value; |
|
Value.Set( FIELD_VOID, NULL ); |
|
AddEvent( target, action, Value, fireDelay, pActivator, pCaller, outputID ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: private function, adds an event into the list |
|
// Input : *newEvent - the (already built) event to add |
|
//----------------------------------------------------------------------------- |
|
void CEventQueue::AddEvent( EventQueuePrioritizedEvent_t *newEvent ) |
|
{ |
|
// loop through the actions looking for a place to insert |
|
EventQueuePrioritizedEvent_t *pe; |
|
for ( pe = &m_Events; pe->m_pNext != NULL; pe = pe->m_pNext ) |
|
{ |
|
if ( pe->m_pNext->m_flFireTime > newEvent->m_flFireTime ) |
|
{ |
|
break; |
|
} |
|
} |
|
|
|
Assert( pe ); |
|
|
|
// insert |
|
newEvent->m_pNext = pe->m_pNext; |
|
newEvent->m_pPrev = pe; |
|
pe->m_pNext = newEvent; |
|
if ( newEvent->m_pNext ) |
|
{ |
|
newEvent->m_pNext->m_pPrev = newEvent; |
|
} |
|
} |
|
|
|
void CEventQueue::RemoveEvent( EventQueuePrioritizedEvent_t *pe ) |
|
{ |
|
Assert( pe->m_pPrev ); |
|
pe->m_pPrev->m_pNext = pe->m_pNext; |
|
if ( pe->m_pNext ) |
|
{ |
|
pe->m_pNext->m_pPrev = pe->m_pPrev; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: fires off any events in the queue who's fire time is (or before) the present time |
|
//----------------------------------------------------------------------------- |
|
void CEventQueue::ServiceEvents( void ) |
|
{ |
|
if (!CBaseEntity::Debug_ShouldStep()) |
|
{ |
|
return; |
|
} |
|
|
|
EventQueuePrioritizedEvent_t *pe = m_Events.m_pNext; |
|
|
|
#ifdef TF_DLL |
|
while ( pe != NULL && pe->m_flFireTime <= engine->GetServerTime() ) |
|
#else |
|
while ( pe != NULL && pe->m_flFireTime <= gpGlobals->curtime ) |
|
#endif |
|
{ |
|
MDLCACHE_CRITICAL_SECTION(); |
|
|
|
bool targetFound = false; |
|
|
|
// find the targets |
|
if ( pe->m_iTarget != NULL_STRING ) |
|
{ |
|
// In the context the event, the searching entity is also the caller |
|
CBaseEntity *pSearchingEntity = pe->m_pCaller; |
|
CBaseEntity *target = NULL; |
|
while ( 1 ) |
|
{ |
|
target = gEntList.FindEntityByName( target, pe->m_iTarget, pSearchingEntity, pe->m_pActivator, pe->m_pCaller ); |
|
if ( !target ) |
|
break; |
|
|
|
// pump the action into the target |
|
target->AcceptInput( STRING(pe->m_iTargetInput), pe->m_pActivator, pe->m_pCaller, pe->m_VariantValue, pe->m_iOutputID ); |
|
targetFound = true; |
|
} |
|
} |
|
|
|
// direct pointer |
|
if ( pe->m_pEntTarget != NULL ) |
|
{ |
|
pe->m_pEntTarget->AcceptInput( STRING(pe->m_iTargetInput), pe->m_pActivator, pe->m_pCaller, pe->m_VariantValue, pe->m_iOutputID ); |
|
targetFound = true; |
|
} |
|
|
|
if ( !targetFound ) |
|
{ |
|
// See if we can find a target if we treat the target as a classname |
|
if ( pe->m_iTarget != NULL_STRING ) |
|
{ |
|
CBaseEntity *target = NULL; |
|
while ( 1 ) |
|
{ |
|
target = gEntList.FindEntityByClassname( target, STRING(pe->m_iTarget) ); |
|
if ( !target ) |
|
break; |
|
|
|
// pump the action into the target |
|
target->AcceptInput( STRING(pe->m_iTargetInput), pe->m_pActivator, pe->m_pCaller, pe->m_VariantValue, pe->m_iOutputID ); |
|
targetFound = true; |
|
} |
|
} |
|
} |
|
|
|
if ( !targetFound ) |
|
{ |
|
const char *pClass ="", *pName = ""; |
|
|
|
// might be NULL |
|
if ( pe->m_pCaller ) |
|
{ |
|
pClass = STRING(pe->m_pCaller->m_iClassname); |
|
pName = STRING(pe->m_pCaller->GetEntityName()); |
|
} |
|
|
|
char szBuffer[256]; |
|
Q_snprintf( szBuffer, sizeof(szBuffer), "unhandled input: (%s) -> (%s), from (%s,%s); target entity not found\n", STRING(pe->m_iTargetInput), STRING(pe->m_iTarget), pClass, pName ); |
|
DevMsg( 2, "%s", szBuffer ); |
|
ADD_DEBUG_HISTORY( HISTORY_ENTITY_IO, szBuffer ); |
|
} |
|
|
|
// remove the event from the list (remembering that the queue may have been added to) |
|
RemoveEvent( pe ); |
|
delete pe; |
|
|
|
// |
|
// If we are in debug mode, exit the loop if we have fired the correct number of events. |
|
// |
|
if (CBaseEntity::Debug_IsPaused()) |
|
{ |
|
if (!CBaseEntity::Debug_Step()) |
|
{ |
|
break; |
|
} |
|
} |
|
|
|
// restart the list (to catch any new items have probably been added to the queue) |
|
pe = m_Events.m_pNext; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Dumps the contents of the Entity I/O event queue to the console. |
|
//----------------------------------------------------------------------------- |
|
void CC_DumpEventQueue() |
|
{ |
|
if ( !UTIL_IsCommandIssuedByServerAdmin() ) |
|
return; |
|
|
|
g_EventQueue.Dump(); |
|
} |
|
static ConCommand dumpeventqueue( "dumpeventqueue", CC_DumpEventQueue, "Dump the contents of the Entity I/O event queue to the console." ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Removes all pending events from the I/O queue that were added by the |
|
// given caller. |
|
// |
|
// TODO: This is only as reliable as callers are in passing the correct |
|
// caller pointer when they fire the outputs. Make more foolproof. |
|
//----------------------------------------------------------------------------- |
|
void CEventQueue::CancelEvents( CBaseEntity *pCaller ) |
|
{ |
|
if (!pCaller) |
|
return; |
|
|
|
EventQueuePrioritizedEvent_t *pCur = m_Events.m_pNext; |
|
|
|
while (pCur != NULL) |
|
{ |
|
bool bDelete = false; |
|
if (pCur->m_pCaller == pCaller) |
|
{ |
|
// Pointers match; make sure everything else matches. |
|
if (!stricmp(STRING(pCur->m_pCaller->GetEntityName()), STRING(pCaller->GetEntityName())) && |
|
!stricmp(pCur->m_pCaller->GetClassname(), pCaller->GetClassname())) |
|
{ |
|
// Found a matching event; delete it from the queue. |
|
bDelete = true; |
|
} |
|
} |
|
|
|
EventQueuePrioritizedEvent_t *pCurSave = pCur; |
|
pCur = pCur->m_pNext; |
|
|
|
if (bDelete) |
|
{ |
|
RemoveEvent( pCurSave ); |
|
delete pCurSave; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Removes all pending events of the specified type from the I/O queue of the specified target |
|
// |
|
// TODO: This is only as reliable as callers are in passing the correct |
|
// caller pointer when they fire the outputs. Make more foolproof. |
|
//----------------------------------------------------------------------------- |
|
void CEventQueue::CancelEventOn( CBaseEntity *pTarget, const char *sInputName ) |
|
{ |
|
if (!pTarget) |
|
return; |
|
|
|
EventQueuePrioritizedEvent_t *pCur = m_Events.m_pNext; |
|
|
|
while (pCur != NULL) |
|
{ |
|
bool bDelete = false; |
|
if (pCur->m_pEntTarget == pTarget) |
|
{ |
|
if ( !Q_strncmp( STRING(pCur->m_iTargetInput), sInputName, strlen(sInputName) ) ) |
|
{ |
|
// Found a matching event; delete it from the queue. |
|
bDelete = true; |
|
} |
|
} |
|
|
|
EventQueuePrioritizedEvent_t *pCurSave = pCur; |
|
pCur = pCur->m_pNext; |
|
|
|
if (bDelete) |
|
{ |
|
RemoveEvent( pCurSave ); |
|
delete pCurSave; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Return true if the target has any pending inputs. |
|
// Input : *pTarget - |
|
// *sInputName - NULL for any input, or a specified one |
|
//----------------------------------------------------------------------------- |
|
bool CEventQueue::HasEventPending( CBaseEntity *pTarget, const char *sInputName ) |
|
{ |
|
if (!pTarget) |
|
return false; |
|
|
|
EventQueuePrioritizedEvent_t *pCur = m_Events.m_pNext; |
|
|
|
while (pCur != NULL) |
|
{ |
|
if (pCur->m_pEntTarget == pTarget) |
|
{ |
|
if ( !sInputName ) |
|
return true; |
|
|
|
if ( !Q_strncmp( STRING(pCur->m_iTargetInput), sInputName, strlen(sInputName) ) ) |
|
return true; |
|
} |
|
|
|
pCur = pCur->m_pNext; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
void ServiceEventQueue( void ) |
|
{ |
|
VPROF("ServiceEventQueue()"); |
|
|
|
g_EventQueue.ServiceEvents(); |
|
} |
|
|
|
|
|
|
|
// save data description for the event queue |
|
BEGIN_SIMPLE_DATADESC( CEventQueue ) |
|
// These are saved explicitly in CEventQueue::Save below |
|
// DEFINE_FIELD( m_Events, EventQueuePrioritizedEvent_t ), |
|
|
|
DEFINE_FIELD( m_iListCount, FIELD_INTEGER ), // this value is only used during save/restore |
|
END_DATADESC() |
|
|
|
|
|
// save data for a single event in the queue |
|
BEGIN_SIMPLE_DATADESC( EventQueuePrioritizedEvent_t ) |
|
DEFINE_FIELD( m_flFireTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_iTarget, FIELD_STRING ), |
|
DEFINE_FIELD( m_iTargetInput, FIELD_STRING ), |
|
DEFINE_FIELD( m_pActivator, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_pCaller, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_pEntTarget, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_iOutputID, FIELD_INTEGER ), |
|
DEFINE_CUSTOM_FIELD( m_VariantValue, variantFuncs ), |
|
|
|
// DEFINE_FIELD( m_pNext, FIELD_??? ), |
|
// DEFINE_FIELD( m_pPrev, FIELD_??? ), |
|
END_DATADESC() |
|
|
|
|
|
int CEventQueue::Save( ISave &save ) |
|
{ |
|
// count the number of items in the queue |
|
EventQueuePrioritizedEvent_t *pe; |
|
|
|
m_iListCount = 0; |
|
for ( pe = m_Events.m_pNext; pe != NULL; pe = pe->m_pNext ) |
|
{ |
|
m_iListCount++; |
|
} |
|
|
|
// save that value out to disk, so we know how many to restore |
|
if ( !save.WriteFields( "EventQueue", this, NULL, m_DataMap.dataDesc, m_DataMap.dataNumFields ) ) |
|
return 0; |
|
|
|
// cycle through all the events, saving them all |
|
for ( pe = m_Events.m_pNext; pe != NULL; pe = pe->m_pNext ) |
|
{ |
|
if ( !save.WriteFields( "PEvent", pe, NULL, pe->m_DataMap.dataDesc, pe->m_DataMap.dataNumFields ) ) |
|
return 0; |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
|
|
int CEventQueue::Restore( IRestore &restore ) |
|
{ |
|
// clear the event queue |
|
Clear(); |
|
|
|
// rebuild the event queue by restoring all the queue items |
|
EventQueuePrioritizedEvent_t tmpEvent; |
|
|
|
// load the number of items saved |
|
if ( !restore.ReadFields( "EventQueue", this, NULL, m_DataMap.dataDesc, m_DataMap.dataNumFields ) ) |
|
return 0; |
|
|
|
for ( int i = 0; i < m_iListCount; i++ ) |
|
{ |
|
if ( !restore.ReadFields( "PEvent", &tmpEvent, NULL, tmpEvent.m_DataMap.dataDesc, tmpEvent.m_DataMap.dataNumFields ) ) |
|
return 0; |
|
|
|
// add the restored event into the list |
|
if ( tmpEvent.m_pEntTarget ) |
|
{ |
|
AddEvent( tmpEvent.m_pEntTarget, |
|
STRING(tmpEvent.m_iTargetInput), |
|
tmpEvent.m_VariantValue, |
|
#ifdef TF_DLL |
|
tmpEvent.m_flFireTime - engine->GetServerTime(), |
|
#else |
|
tmpEvent.m_flFireTime - gpGlobals->curtime, |
|
#endif |
|
tmpEvent.m_pActivator, |
|
tmpEvent.m_pCaller, |
|
tmpEvent.m_iOutputID ); |
|
} |
|
else |
|
{ |
|
AddEvent( STRING(tmpEvent.m_iTarget), |
|
STRING(tmpEvent.m_iTargetInput), |
|
tmpEvent.m_VariantValue, |
|
#ifdef TF_DLL |
|
tmpEvent.m_flFireTime - engine->GetServerTime(), |
|
#else |
|
tmpEvent.m_flFireTime - gpGlobals->curtime, |
|
#endif |
|
tmpEvent.m_pActivator, |
|
tmpEvent.m_pCaller, |
|
tmpEvent.m_iOutputID ); |
|
} |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
////////////////////////// variant_t implementation ////////////////////////// |
|
|
|
// BUGBUG: Add support for function pointer save/restore to variants |
|
// BUGBUG: Must pass datamap_t to read/write fields |
|
void variant_t::Set( fieldtype_t ftype, void *data ) |
|
{ |
|
fieldType = ftype; |
|
|
|
switch ( ftype ) |
|
{ |
|
case FIELD_BOOLEAN: bVal = *((bool *)data); break; |
|
case FIELD_CHARACTER: iVal = *((char *)data); break; |
|
case FIELD_SHORT: iVal = *((short *)data); break; |
|
case FIELD_INTEGER: iVal = *((int *)data); break; |
|
case FIELD_STRING: iszVal = *((string_t *)data); break; |
|
case FIELD_FLOAT: flVal = *((float *)data); break; |
|
case FIELD_COLOR32: rgbaVal = *((color32 *)data); break; |
|
|
|
case FIELD_VECTOR: |
|
case FIELD_POSITION_VECTOR: |
|
{ |
|
vecVal[0] = ((float *)data)[0]; |
|
vecVal[1] = ((float *)data)[1]; |
|
vecVal[2] = ((float *)data)[2]; |
|
break; |
|
} |
|
|
|
case FIELD_EHANDLE: eVal = *((EHANDLE *)data); break; |
|
case FIELD_CLASSPTR: eVal = *((CBaseEntity **)data); break; |
|
case FIELD_VOID: |
|
default: |
|
iVal = 0; fieldType = FIELD_VOID; |
|
break; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Copies the value in the variant into a block of memory |
|
// Input : *data - the block to write into |
|
//----------------------------------------------------------------------------- |
|
void variant_t::SetOther( void *data ) |
|
{ |
|
switch ( fieldType ) |
|
{ |
|
case FIELD_BOOLEAN: *((bool *)data) = bVal != 0; break; |
|
case FIELD_CHARACTER: *((char *)data) = iVal; break; |
|
case FIELD_SHORT: *((short *)data) = iVal; break; |
|
case FIELD_INTEGER: *((int *)data) = iVal; break; |
|
case FIELD_STRING: *((string_t *)data) = iszVal; break; |
|
case FIELD_FLOAT: *((float *)data) = flVal; break; |
|
case FIELD_COLOR32: *((color32 *)data) = rgbaVal; break; |
|
|
|
case FIELD_VECTOR: |
|
case FIELD_POSITION_VECTOR: |
|
{ |
|
((float *)data)[0] = vecVal[0]; |
|
((float *)data)[1] = vecVal[1]; |
|
((float *)data)[2] = vecVal[2]; |
|
break; |
|
} |
|
|
|
case FIELD_EHANDLE: *((EHANDLE *)data) = eVal; break; |
|
case FIELD_CLASSPTR: *((CBaseEntity **)data) = eVal; break; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Converts the variant to a new type. This function defines which I/O |
|
// types can be automatically converted between. Connections that require |
|
// an unsupported conversion will cause an error message at runtime. |
|
// Input : newType - the type to convert to |
|
// Output : Returns true on success, false if the conversion is not legal |
|
//----------------------------------------------------------------------------- |
|
bool variant_t::Convert( fieldtype_t newType ) |
|
{ |
|
if ( newType == fieldType ) |
|
{ |
|
return true; |
|
} |
|
|
|
// |
|
// Converting to a null value is easy. |
|
// |
|
if ( newType == FIELD_VOID ) |
|
{ |
|
Set( FIELD_VOID, NULL ); |
|
return true; |
|
} |
|
|
|
// |
|
// FIELD_INPUT accepts the variant type directly. |
|
// |
|
if ( newType == FIELD_INPUT ) |
|
{ |
|
return true; |
|
} |
|
|
|
switch ( fieldType ) |
|
{ |
|
case FIELD_INTEGER: |
|
{ |
|
switch ( newType ) |
|
{ |
|
case FIELD_FLOAT: |
|
{ |
|
SetFloat( (float) iVal ); |
|
return true; |
|
} |
|
|
|
case FIELD_BOOLEAN: |
|
{ |
|
SetBool( iVal != 0 ); |
|
return true; |
|
} |
|
} |
|
break; |
|
} |
|
|
|
case FIELD_FLOAT: |
|
{ |
|
switch ( newType ) |
|
{ |
|
case FIELD_INTEGER: |
|
{ |
|
SetInt( (int) flVal ); |
|
return true; |
|
} |
|
|
|
case FIELD_BOOLEAN: |
|
{ |
|
SetBool( flVal != 0 ); |
|
return true; |
|
} |
|
} |
|
break; |
|
} |
|
|
|
// |
|
// Everyone must convert from FIELD_STRING if possible, since |
|
// parameter overrides are always passed as strings. |
|
// |
|
case FIELD_STRING: |
|
{ |
|
switch ( newType ) |
|
{ |
|
case FIELD_INTEGER: |
|
{ |
|
if (iszVal != NULL_STRING) |
|
{ |
|
SetInt(atoi(STRING(iszVal))); |
|
} |
|
else |
|
{ |
|
SetInt(0); |
|
} |
|
return true; |
|
} |
|
|
|
case FIELD_FLOAT: |
|
{ |
|
if (iszVal != NULL_STRING) |
|
{ |
|
SetFloat(atof(STRING(iszVal))); |
|
} |
|
else |
|
{ |
|
SetFloat(0); |
|
} |
|
return true; |
|
} |
|
|
|
case FIELD_BOOLEAN: |
|
{ |
|
if (iszVal != NULL_STRING) |
|
{ |
|
SetBool( atoi(STRING(iszVal)) != 0 ); |
|
} |
|
else |
|
{ |
|
SetBool(false); |
|
} |
|
return true; |
|
} |
|
|
|
case FIELD_VECTOR: |
|
{ |
|
Vector tmpVec = vec3_origin; |
|
if (sscanf(STRING(iszVal), "[%f %f %f]", &tmpVec[0], &tmpVec[1], &tmpVec[2]) == 0) |
|
{ |
|
// Try sucking out 3 floats with no []s |
|
sscanf(STRING(iszVal), "%f %f %f", &tmpVec[0], &tmpVec[1], &tmpVec[2]); |
|
} |
|
SetVector3D( tmpVec ); |
|
return true; |
|
} |
|
|
|
case FIELD_COLOR32: |
|
{ |
|
int nRed = 0; |
|
int nGreen = 0; |
|
int nBlue = 0; |
|
int nAlpha = 255; |
|
|
|
sscanf(STRING(iszVal), "%d %d %d %d", &nRed, &nGreen, &nBlue, &nAlpha); |
|
SetColor32( nRed, nGreen, nBlue, nAlpha ); |
|
return true; |
|
} |
|
|
|
case FIELD_EHANDLE: |
|
{ |
|
// convert the string to an entity by locating it by classname |
|
CBaseEntity *ent = NULL; |
|
if ( iszVal != NULL_STRING ) |
|
{ |
|
// FIXME: do we need to pass an activator in here? |
|
ent = gEntList.FindEntityByName( NULL, iszVal ); |
|
} |
|
SetEntity( ent ); |
|
return true; |
|
} |
|
} |
|
|
|
break; |
|
} |
|
|
|
case FIELD_EHANDLE: |
|
{ |
|
switch ( newType ) |
|
{ |
|
case FIELD_STRING: |
|
{ |
|
// take the entities targetname as the string |
|
string_t iszStr = NULL_STRING; |
|
if ( eVal != NULL ) |
|
{ |
|
SetString( eVal->GetEntityName() ); |
|
} |
|
return true; |
|
} |
|
} |
|
break; |
|
} |
|
} |
|
|
|
// invalid conversion |
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: All types must be able to display as strings for debugging purposes. |
|
// Output : Returns a pointer to the string that represents this value. |
|
// |
|
// NOTE: The returned pointer should not be stored by the caller as |
|
// subsequent calls to this function will overwrite the contents |
|
// of the buffer! |
|
//----------------------------------------------------------------------------- |
|
const char *variant_t::ToString( void ) const |
|
{ |
|
COMPILE_TIME_ASSERT( sizeof(string_t) == sizeof(intp) ); |
|
|
|
static char szBuf[512]; |
|
|
|
switch (fieldType) |
|
{ |
|
case FIELD_STRING: |
|
{ |
|
return(STRING(iszVal)); |
|
} |
|
|
|
case FIELD_BOOLEAN: |
|
{ |
|
if (bVal == 0) |
|
{ |
|
Q_strncpy(szBuf, "false",sizeof(szBuf)); |
|
} |
|
else |
|
{ |
|
Q_strncpy(szBuf, "true",sizeof(szBuf)); |
|
} |
|
return(szBuf); |
|
} |
|
|
|
case FIELD_INTEGER: |
|
{ |
|
Q_snprintf( szBuf, sizeof( szBuf ), "%i", iVal ); |
|
return(szBuf); |
|
} |
|
|
|
case FIELD_FLOAT: |
|
{ |
|
Q_snprintf(szBuf,sizeof(szBuf), "%g", flVal); |
|
return(szBuf); |
|
} |
|
|
|
case FIELD_COLOR32: |
|
{ |
|
Q_snprintf(szBuf,sizeof(szBuf), "%d %d %d %d", (int)rgbaVal.r, (int)rgbaVal.g, (int)rgbaVal.b, (int)rgbaVal.a); |
|
return(szBuf); |
|
} |
|
|
|
case FIELD_VECTOR: |
|
{ |
|
Q_snprintf(szBuf,sizeof(szBuf), "[%g %g %g]", (double)vecVal[0], (double)vecVal[1], (double)vecVal[2]); |
|
return(szBuf); |
|
} |
|
|
|
case FIELD_VOID: |
|
{ |
|
szBuf[0] = '\0'; |
|
return(szBuf); |
|
} |
|
|
|
case FIELD_EHANDLE: |
|
{ |
|
const char *pszName = (Entity()) ? STRING(Entity()->GetEntityName()) : "<<null entity>>"; |
|
Q_strncpy( szBuf, pszName, 512 ); |
|
return (szBuf); |
|
} |
|
} |
|
|
|
return("No conversion to string"); |
|
} |
|
|
|
#define classNameTypedef variant_t // to satisfy DEFINE... macros |
|
|
|
typedescription_t variant_t::m_SaveBool[] = |
|
{ |
|
DEFINE_FIELD( bVal, FIELD_BOOLEAN ), |
|
}; |
|
typedescription_t variant_t::m_SaveInt[] = |
|
{ |
|
DEFINE_FIELD( iVal, FIELD_INTEGER ), |
|
}; |
|
typedescription_t variant_t::m_SaveFloat[] = |
|
{ |
|
DEFINE_FIELD( flVal, FIELD_FLOAT ), |
|
}; |
|
typedescription_t variant_t::m_SaveEHandle[] = |
|
{ |
|
DEFINE_FIELD( eVal, FIELD_EHANDLE ), |
|
}; |
|
typedescription_t variant_t::m_SaveString[] = |
|
{ |
|
DEFINE_FIELD( iszVal, FIELD_STRING ), |
|
}; |
|
typedescription_t variant_t::m_SaveColor[] = |
|
{ |
|
DEFINE_FIELD( rgbaVal, FIELD_COLOR32 ), |
|
}; |
|
|
|
#undef classNameTypedef |
|
|
|
// |
|
// Struct for saving and restoring vector variants, since they are |
|
// stored as float[3] and we want to take advantage of position vector |
|
// fixup across level transitions. |
|
// |
|
#define classNameTypedef variant_savevector_t // to satisfy DEFINE... macros |
|
|
|
struct variant_savevector_t |
|
{ |
|
Vector vecSave; |
|
}; |
|
typedescription_t variant_t::m_SaveVector[] = |
|
{ |
|
// Just here to shut up ClassCheck |
|
// DEFINE_ARRAY( vecVal, FIELD_FLOAT, 3 ), |
|
|
|
DEFINE_FIELD( vecSave, FIELD_VECTOR ), |
|
}; |
|
typedescription_t variant_t::m_SavePositionVector[] = |
|
{ |
|
DEFINE_FIELD( vecSave, FIELD_POSITION_VECTOR ), |
|
}; |
|
#undef classNameTypedef |
|
|
|
#define classNameTypedef variant_savevmatrix_t // to satisfy DEFINE... macros |
|
struct variant_savevmatrix_t |
|
{ |
|
VMatrix matSave; |
|
}; |
|
typedescription_t variant_t::m_SaveVMatrix[] = |
|
{ |
|
DEFINE_FIELD( matSave, FIELD_VMATRIX ), |
|
}; |
|
typedescription_t variant_t::m_SaveVMatrixWorldspace[] = |
|
{ |
|
DEFINE_FIELD( matSave, FIELD_VMATRIX_WORLDSPACE ), |
|
}; |
|
#undef classNameTypedef |
|
|
|
#define classNameTypedef variant_savevmatrix3x4_t // to satisfy DEFINE... macros |
|
struct variant_savevmatrix3x4_t |
|
{ |
|
matrix3x4_t matSave; |
|
}; |
|
typedescription_t variant_t::m_SaveMatrix3x4Worldspace[] = |
|
{ |
|
DEFINE_FIELD( matSave, FIELD_MATRIX3X4_WORLDSPACE ), |
|
}; |
|
#undef classNameTypedef |
|
|
|
class CVariantSaveDataOps : public CDefSaveRestoreOps |
|
{ |
|
// saves the entire array of variables |
|
virtual void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave ) |
|
{ |
|
variant_t *var = (variant_t*)fieldInfo.pField; |
|
|
|
int type = var->FieldType(); |
|
pSave->WriteInt( &type, 1 ); |
|
|
|
switch ( var->FieldType() ) |
|
{ |
|
case FIELD_VOID: |
|
break; |
|
case FIELD_BOOLEAN: |
|
pSave->WriteFields( fieldInfo.pTypeDesc->fieldName, var, NULL, variant_t::m_SaveBool, 1 ); |
|
break; |
|
case FIELD_INTEGER: |
|
pSave->WriteFields( fieldInfo.pTypeDesc->fieldName, var, NULL, variant_t::m_SaveInt, 1 ); |
|
break; |
|
case FIELD_FLOAT: |
|
pSave->WriteFields( fieldInfo.pTypeDesc->fieldName, var, NULL, variant_t::m_SaveFloat, 1 ); |
|
break; |
|
case FIELD_EHANDLE: |
|
pSave->WriteFields( fieldInfo.pTypeDesc->fieldName, var, NULL, variant_t::m_SaveEHandle, 1 ); |
|
break; |
|
case FIELD_STRING: |
|
pSave->WriteFields( fieldInfo.pTypeDesc->fieldName, var, NULL, variant_t::m_SaveString, 1 ); |
|
break; |
|
case FIELD_COLOR32: |
|
pSave->WriteFields( fieldInfo.pTypeDesc->fieldName, var, NULL, variant_t::m_SaveColor, 1 ); |
|
break; |
|
case FIELD_VECTOR: |
|
{ |
|
variant_savevector_t Temp; |
|
var->Vector3D(Temp.vecSave); |
|
pSave->WriteFields( fieldInfo.pTypeDesc->fieldName, &Temp, NULL, variant_t::m_SaveVector, 1 ); |
|
break; |
|
} |
|
|
|
case FIELD_POSITION_VECTOR: |
|
{ |
|
variant_savevector_t Temp; |
|
var->Vector3D(Temp.vecSave); |
|
pSave->WriteFields( fieldInfo.pTypeDesc->fieldName, &Temp, NULL, variant_t::m_SavePositionVector, 1 ); |
|
break; |
|
} |
|
|
|
default: |
|
Warning( "Bad type %d in saved variant_t\n", var->FieldType() ); |
|
Assert(0); |
|
} |
|
} |
|
|
|
// restores a single instance of the variable |
|
virtual void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore ) |
|
{ |
|
variant_t *var = (variant_t*)fieldInfo.pField; |
|
|
|
*var = variant_t(); |
|
|
|
var->fieldType = (_fieldtypes)pRestore->ReadInt(); |
|
|
|
switch ( var->fieldType ) |
|
{ |
|
case FIELD_VOID: |
|
break; |
|
case FIELD_BOOLEAN: |
|
pRestore->ReadFields( fieldInfo.pTypeDesc->fieldName, var, NULL, variant_t::m_SaveBool, 1 ); |
|
break; |
|
case FIELD_INTEGER: |
|
pRestore->ReadFields( fieldInfo.pTypeDesc->fieldName, var, NULL, variant_t::m_SaveInt, 1 ); |
|
break; |
|
case FIELD_FLOAT: |
|
pRestore->ReadFields( fieldInfo.pTypeDesc->fieldName, var, NULL, variant_t::m_SaveFloat, 1 ); |
|
break; |
|
case FIELD_EHANDLE: |
|
pRestore->ReadFields( fieldInfo.pTypeDesc->fieldName, var, NULL, variant_t::m_SaveEHandle, 1 ); |
|
break; |
|
case FIELD_STRING: |
|
pRestore->ReadFields( fieldInfo.pTypeDesc->fieldName, var, NULL, variant_t::m_SaveString, 1 ); |
|
break; |
|
case FIELD_COLOR32: |
|
pRestore->ReadFields( fieldInfo.pTypeDesc->fieldName, var, NULL, variant_t::m_SaveColor, 1 ); |
|
break; |
|
case FIELD_VECTOR: |
|
{ |
|
variant_savevector_t Temp; |
|
pRestore->ReadFields( fieldInfo.pTypeDesc->fieldName, &Temp, NULL, variant_t::m_SaveVector, 1 ); |
|
var->SetVector3D(Temp.vecSave); |
|
break; |
|
} |
|
case FIELD_POSITION_VECTOR: |
|
{ |
|
variant_savevector_t Temp; |
|
pRestore->ReadFields( fieldInfo.pTypeDesc->fieldName, &Temp, NULL, variant_t::m_SavePositionVector, 1 ); |
|
var->SetPositionVector3D(Temp.vecSave); |
|
break; |
|
} |
|
default: |
|
Warning( "Bad type %d in saved variant_t\n", var->FieldType() ); |
|
Assert(0); |
|
break; |
|
} |
|
} |
|
|
|
|
|
virtual bool IsEmpty( const SaveRestoreFieldInfo_t &fieldInfo ) |
|
{ |
|
// check all the elements of the array (usually only 1) |
|
variant_t *var = (variant_t*)fieldInfo.pField; |
|
for ( int i = 0; i < fieldInfo.pTypeDesc->fieldSize; i++, var++ ) |
|
{ |
|
if ( var->FieldType() != FIELD_VOID ) |
|
return 0; |
|
} |
|
|
|
// variant has no data |
|
return 1; |
|
} |
|
|
|
virtual void MakeEmpty( const SaveRestoreFieldInfo_t &fieldInfo ) |
|
{ |
|
// Don't no how to. This is okay, since objects of this type |
|
// are always born clean before restore, and not reused |
|
} |
|
}; |
|
|
|
CVariantSaveDataOps g_VariantSaveDataOps; |
|
ISaveRestoreOps *variantFuncs = &g_VariantSaveDataOps; |
|
|
|
/////////////////////// entitylist ///////////////////// |
|
|
|
CUtlMemoryPool g_EntListMemPool( sizeof(entitem_t), 256, CUtlMemoryPool::GROW_NONE, "g_EntListMemPool" ); |
|
|
|
#include "tier0/memdbgoff.h" |
|
|
|
void *entitem_t::operator new( size_t stAllocateBlock ) |
|
{ |
|
return g_EntListMemPool.Alloc( stAllocateBlock ); |
|
} |
|
|
|
void *entitem_t::operator new( size_t stAllocateBlock, int nBlockUse, const char *pFileName, int nLine ) |
|
{ |
|
return g_EntListMemPool.Alloc( stAllocateBlock ); |
|
} |
|
|
|
void entitem_t::operator delete( void *pMem ) |
|
{ |
|
g_EntListMemPool.Free( pMem ); |
|
} |
|
|
|
#include "tier0/memdbgon.h" |
|
|
|
|
|
CEntityList::CEntityList() |
|
{ |
|
m_pItemList = NULL; |
|
m_iNumItems = 0; |
|
} |
|
|
|
CEntityList::~CEntityList() |
|
{ |
|
// remove all items from the list |
|
entitem_t *next, *e = m_pItemList; |
|
while ( e != NULL ) |
|
{ |
|
next = e->pNext; |
|
delete e; |
|
e = next; |
|
} |
|
m_pItemList = NULL; |
|
} |
|
|
|
void CEntityList::AddEntity( CBaseEntity *pEnt ) |
|
{ |
|
// check if it's already in the list; if not, add it |
|
entitem_t *e = m_pItemList; |
|
while ( e != NULL ) |
|
{ |
|
if ( e->hEnt == pEnt ) |
|
{ |
|
// it's already in the list |
|
return; |
|
} |
|
|
|
if ( e->pNext == NULL ) |
|
{ |
|
// we've hit the end of the list, so tack it on |
|
e->pNext = new entitem_t; |
|
e->pNext->hEnt = pEnt; |
|
e->pNext->pNext = NULL; |
|
m_iNumItems++; |
|
return; |
|
} |
|
|
|
e = e->pNext; |
|
} |
|
|
|
// empty list |
|
m_pItemList = new entitem_t; |
|
m_pItemList->hEnt = pEnt; |
|
m_pItemList->pNext = NULL; |
|
m_iNumItems = 1; |
|
} |
|
|
|
void CEntityList::DeleteEntity( CBaseEntity *pEnt ) |
|
{ |
|
// find the entry in the list and delete it |
|
entitem_t *prev = NULL, *e = m_pItemList; |
|
while ( e != NULL ) |
|
{ |
|
// delete the link if it's the matching entity OR if the link is NULL |
|
if ( e->hEnt == pEnt || e->hEnt == NULL ) |
|
{ |
|
if ( prev ) |
|
{ |
|
prev->pNext = e->pNext; |
|
} |
|
else |
|
{ |
|
m_pItemList = e->pNext; |
|
} |
|
|
|
delete e; |
|
m_iNumItems--; |
|
|
|
// REVISIT: Is this correct? Is this just here to clean out dead EHANDLEs? |
|
// restart the loop |
|
e = m_pItemList; |
|
prev = NULL; |
|
continue; |
|
} |
|
|
|
prev = e; |
|
e = e->pNext; |
|
} |
|
} |
|
|
|
|