aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/TemplateEntities.cpp
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/server/TemplateEntities.cpp
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/server/TemplateEntities.cpp')
-rw-r--r--mp/src/game/server/TemplateEntities.cpp510
1 files changed, 510 insertions, 0 deletions
diff --git a/mp/src/game/server/TemplateEntities.cpp b/mp/src/game/server/TemplateEntities.cpp
new file mode 100644
index 00000000..629546d9
--- /dev/null
+++ b/mp/src/game/server/TemplateEntities.cpp
@@ -0,0 +1,510 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Template entities are used by spawners to create copies of entities
+// that were configured by the level designer. This allows us to spawn
+// entities with arbitrary sets of key/value data and entity I/O
+// connections.
+//
+// Template entities are marked with a special spawnflag which causes
+// them not to spawn, but to be saved as a string containing all the
+// map data (keyvalues and I/O connections) from the BSP. Template
+// entities are looked up by name by the spawner, which copies the
+// map data into a local string (that's how the template data is saved
+// and restored). Once all the entities in the map have been activated,
+// the template database is freed.
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "igamesystem.h"
+#include "mapentities_shared.h"
+#include "point_template.h"
+#include "eventqueue.h"
+#include "TemplateEntities.h"
+#include "utldict.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+ConVar template_debug( "template_debug", "0" );
+
+// This is appended to key's values that will need to be unique in template instances
+const char *ENTITYIO_FIXUP_STRING = "&0000";
+
+int MapEntity_GetNumKeysInEntity( const char *pEntData );
+
+struct TemplateEntityData_t
+{
+ const char *pszName;
+ char *pszMapData;
+ string_t iszMapData;
+ int iMapDataLength;
+ bool bNeedsEntityIOFixup; // If true, this template has entity I/O in its mapdata that needs fixup before spawning.
+ char *pszFixedMapData; // A single copy of this template that we used to fix up the Entity I/O whenever someone wants a fixed version of this template
+
+ DECLARE_SIMPLE_DATADESC();
+};
+
+BEGIN_SIMPLE_DATADESC( TemplateEntityData_t )
+ //DEFINE_FIELD( pszName, FIELD_STRING ), // Saved custom, see below
+ //DEFINE_FIELD( pszMapData, FIELD_STRING ), // Saved custom, see below
+ DEFINE_FIELD( iszMapData, FIELD_STRING ),
+ DEFINE_FIELD( iMapDataLength, FIELD_INTEGER ),
+ DEFINE_FIELD( bNeedsEntityIOFixup, FIELD_BOOLEAN ),
+
+ //DEFINE_FIELD( pszFixedMapData, FIELD_STRING ), // Not saved at all
+END_DATADESC()
+
+struct grouptemplate_t
+{
+ CEntityMapData *pMapDataParser;
+ char pszName[MAPKEY_MAXLENGTH];
+ int iIndex;
+ bool bChangeTargetname;
+};
+
+static CUtlVector<TemplateEntityData_t *> g_Templates;
+
+int g_iCurrentTemplateInstance;
+
+//-----------------------------------------------------------------------------
+// Purpose: Saves the given entity's keyvalue data for later use by a spawner.
+// Returns the index into the templates.
+//-----------------------------------------------------------------------------
+int Templates_Add(CBaseEntity *pEntity, const char *pszMapData, int nLen)
+{
+ const char *pszName = STRING(pEntity->GetEntityName());
+ if ((!pszName) || (!strlen(pszName)))
+ {
+ DevWarning(1, "RegisterTemplateEntity: template entity with no name, class %s\n", pEntity->GetClassname());
+ return -1;
+ }
+
+ TemplateEntityData_t *pEntData = (TemplateEntityData_t *)malloc(sizeof(TemplateEntityData_t));
+ pEntData->pszName = strdup( pszName );
+
+ // We may modify the values of the keys in this mapdata chunk later on to fix Entity I/O
+ // connections. For this reason, we need to ensure we have enough memory to do that.
+ int iKeys = MapEntity_GetNumKeysInEntity( pszMapData );
+ int iExtraSpace = (strlen(ENTITYIO_FIXUP_STRING)+1) * iKeys;
+
+ // Extra 1 because the mapdata passed in isn't null terminated
+ pEntData->iMapDataLength = nLen + iExtraSpace + 1;
+ pEntData->pszMapData = (char *)malloc( pEntData->iMapDataLength );
+ memcpy(pEntData->pszMapData, pszMapData, nLen + 1);
+ pEntData->pszMapData[nLen] = '\0';
+
+ // We don't alloc these suckers right now because that gives us no time to
+ // tweak them for Entity I/O purposes.
+ pEntData->iszMapData = NULL_STRING;
+ pEntData->bNeedsEntityIOFixup = false;
+ pEntData->pszFixedMapData = NULL;
+
+ return g_Templates.AddToTail(pEntData);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if the specified index needs to be fixed up to be unique
+// when the template is spawned.
+//-----------------------------------------------------------------------------
+bool Templates_IndexRequiresEntityIOFixup( int iIndex )
+{
+ Assert( iIndex < g_Templates.Count() );
+ return g_Templates[iIndex]->bNeedsEntityIOFixup;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Looks up a template entity by its index in the templates
+// Used by point_templates because they often have multiple templates with the same name
+//-----------------------------------------------------------------------------
+string_t Templates_FindByIndex( int iIndex )
+{
+ Assert( iIndex < g_Templates.Count() );
+
+ // First time through we alloc the mapdata onto the pool.
+ // It's safe to do it now because this isn't called until post Entity I/O cleanup.
+ if ( g_Templates[iIndex]->iszMapData == NULL_STRING )
+ {
+ g_Templates[iIndex]->iszMapData = AllocPooledString( g_Templates[iIndex]->pszMapData );
+ }
+
+ return g_Templates[iIndex]->iszMapData;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int Templates_GetStringSize( int iIndex )
+{
+ Assert( iIndex < g_Templates.Count() );
+ return g_Templates[iIndex]->iMapDataLength;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Looks up a template entity by name, returning the map data blob as
+// a null-terminated string containing key/value pairs.
+// NOTE: This can't handle multiple templates with the same targetname.
+//-----------------------------------------------------------------------------
+string_t Templates_FindByTargetName(const char *pszName)
+{
+ int nCount = g_Templates.Count();
+ for (int i = 0; i < nCount; i++)
+ {
+ TemplateEntityData_t *pTemplate = g_Templates.Element(i);
+ if ( !stricmp(pTemplate->pszName, pszName) )
+ return Templates_FindByIndex( i );
+ }
+
+ return NULL_STRING;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: A CPointTemplate has asked us to reconnect all the entity I/O links
+// inside it's templates. Go through the keys and add look for values
+// that match a name within the group's entity names. Append %d to any
+// found values, which will later be filled out by a unique identifier
+// whenever the template is instanced.
+//-----------------------------------------------------------------------------
+void Templates_ReconnectIOForGroup( CPointTemplate *pGroup )
+{
+ int iCount = pGroup->GetNumTemplates();
+ if ( !iCount )
+ return;
+
+ // First assemble a list of the targetnames of all the templates in the group.
+ // We need to store off the original names here, because we're going to change
+ // them as we go along.
+ CUtlVector< grouptemplate_t > GroupTemplates;
+ int i;
+ for ( i = 0; i < iCount; i++ )
+ {
+ grouptemplate_t newGroupTemplate;
+ newGroupTemplate.iIndex = pGroup->GetTemplateIndexForTemplate(i);
+ newGroupTemplate.pMapDataParser = new CEntityMapData( g_Templates[ newGroupTemplate.iIndex ]->pszMapData, g_Templates[ newGroupTemplate.iIndex ]->iMapDataLength );
+ Assert( newGroupTemplate.pMapDataParser );
+ newGroupTemplate.pMapDataParser->ExtractValue( "targetname", newGroupTemplate.pszName );
+ newGroupTemplate.bChangeTargetname = false;
+
+ GroupTemplates.AddToTail( newGroupTemplate );
+ }
+
+ if (pGroup->AllowNameFixup())
+ {
+ char keyName[MAPKEY_MAXLENGTH];
+ char value[MAPKEY_MAXLENGTH];
+ char valueclipped[MAPKEY_MAXLENGTH];
+
+ // Now go through all the entities in the group and parse their mapdata keyvalues.
+ // We're looking for any values that match targetnames of any of the group entities.
+ for ( i = 0; i < iCount; i++ )
+ {
+ // We need to know what instance of each key we're changing.
+ // Store a table of the count of the keys we've run into.
+ CUtlDict< int, int > KeyInstanceCount;
+ CEntityMapData *mapData = GroupTemplates[i].pMapDataParser;
+
+ // Loop through our keys
+ if ( !mapData->GetFirstKey(keyName, value) )
+ continue;
+
+ do
+ {
+ // Ignore targetnames
+ if ( !stricmp( keyName, "targetname" ) )
+ continue;
+
+ // Add to the count for this
+ int idx = KeyInstanceCount.Find( keyName );
+ if ( idx == KeyInstanceCount.InvalidIndex() )
+ {
+ idx = KeyInstanceCount.Insert( keyName, 0 );
+ }
+ KeyInstanceCount[idx]++;
+
+ // Entity I/O values are stored as "Targetname,<data>", so we need to see if there's a ',' in the string
+ char *sValue = value;
+ // FIXME: This is very brittle. Any key with a , will not be found.
+ char *s = strchr( value, ',' );
+ if ( s )
+ {
+ // Grab just the targetname of the receiver
+ Q_strncpy( valueclipped, value, (s - value+1) );
+ sValue = valueclipped;
+ }
+
+ // Loop through our group templates
+ for ( int iTName = 0; iTName < iCount; iTName++ )
+ {
+ char *pName = GroupTemplates[iTName].pszName;
+ if ( stricmp( pName, sValue ) )
+ continue;
+
+ if ( template_debug.GetInt() )
+ {
+ Msg("Template Connection Found: Key %s (\"%s\") in entity named \"%s\"(%d) matches entity %d's targetname\n", keyName, sValue, GroupTemplates[i].pszName, i, iTName );
+ }
+
+ char newvalue[MAPKEY_MAXLENGTH];
+
+ // Get the current key instance. (-1 because it's this one we're changing)
+ int nKeyInstance = KeyInstanceCount[idx] - 1;
+
+ // Add our IO value to the targetname
+ // We need to append it if this isn't an Entity I/O value, or prepend it to the ',' if it is
+ if ( s )
+ {
+ Q_strncpy( newvalue, valueclipped, MAPKEY_MAXLENGTH );
+ Q_strncat( newvalue, ENTITYIO_FIXUP_STRING, sizeof(newvalue), COPY_ALL_CHARACTERS );
+ Q_strncat( newvalue, s, sizeof(newvalue), COPY_ALL_CHARACTERS );
+ mapData->SetValue( keyName, newvalue, nKeyInstance );
+ }
+ else
+ {
+ Q_strncpy( newvalue, sValue, MAPKEY_MAXLENGTH );
+ Q_strncat( newvalue, ENTITYIO_FIXUP_STRING, sizeof(newvalue), COPY_ALL_CHARACTERS );
+ mapData->SetValue( keyName, newvalue, nKeyInstance );
+ }
+
+ // Remember we changed this targetname
+ GroupTemplates[iTName].bChangeTargetname = true;
+
+ // Set both entity's flags telling them their template needs fixup when it's spawned
+ g_Templates[ GroupTemplates[i].iIndex ]->bNeedsEntityIOFixup = true;
+ g_Templates[ GroupTemplates[iTName].iIndex ]->bNeedsEntityIOFixup = true;
+ }
+ }
+ while ( mapData->GetNextKey(keyName, value) );
+ }
+
+ // Now change targetnames for all entities that need them changed
+ for ( i = 0; i < iCount; i++ )
+ {
+ char value[MAPKEY_MAXLENGTH];
+
+ if ( GroupTemplates[i].bChangeTargetname )
+ {
+ CEntityMapData *mapData = GroupTemplates[i].pMapDataParser;
+ mapData->ExtractValue( "targetname", value );
+ Q_strncat( value, ENTITYIO_FIXUP_STRING, sizeof(value), COPY_ALL_CHARACTERS );
+ mapData->SetValue( "targetname", value );
+ }
+ }
+ }
+
+ // Delete our group parsers
+ for ( i = 0; i < iCount; i++ )
+ {
+ delete GroupTemplates[i].pMapDataParser;
+ }
+ GroupTemplates.Purge();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Someone's about to start instancing a new group of entities.
+// Generate a unique identifier for this group.
+//-----------------------------------------------------------------------------
+void Templates_StartUniqueInstance( void )
+{
+ g_iCurrentTemplateInstance++;
+
+ // Make sure there's enough room to fit it into the string
+ int iMax = pow(10.0f, (int)((strlen(ENTITYIO_FIXUP_STRING) - 1))); // -1 for the &
+ if ( g_iCurrentTemplateInstance >= iMax )
+ {
+ // We won't hit this.
+ Assert(0);
+ // Hopefully there were still be instance number 0 around.
+ g_iCurrentTemplateInstance = 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Someone wants to spawn an instance of a template that requires
+// entity IO fixup. Fill out the pMapData with a copy of the template
+// with unique key/values where the template requires them.
+//-----------------------------------------------------------------------------
+char *Templates_GetEntityIOFixedMapData( int iIndex )
+{
+ Assert( Templates_IndexRequiresEntityIOFixup( iIndex ) );
+
+ // First time through?
+ if ( !g_Templates[iIndex]->pszFixedMapData )
+ {
+ g_Templates[iIndex]->pszFixedMapData = new char[g_Templates[iIndex]->iMapDataLength];
+ Q_strncpy( g_Templates[iIndex]->pszFixedMapData, g_Templates[iIndex]->pszMapData, g_Templates[iIndex]->iMapDataLength );
+ }
+
+ int iFixupSize = strlen(ENTITYIO_FIXUP_STRING); // don't include \0 when copying in the fixup
+ char *sOurFixup = new char[iFixupSize+1]; // do alloc room here for the null terminator
+ Q_snprintf( sOurFixup, iFixupSize+1, "%c%.4d", ENTITYIO_FIXUP_STRING[0], g_iCurrentTemplateInstance );
+
+ // Now rip through the map data string and replace any instances of the fixup string with our unique identifier
+ char *c = g_Templates[iIndex]->pszFixedMapData;
+ do
+ {
+ if ( *c == ENTITYIO_FIXUP_STRING[0] )
+ {
+ // Make sure it's our fixup string
+ bool bValid = true;
+ for ( int i = 1; i < iFixupSize; i++ )
+ {
+ // Look for any number, because we've already used this string
+ if ( !(*(c+i) >= '0' && *(c+i) <= '9') )
+ {
+ // Some other string
+ bValid = false;
+ break;
+ }
+ }
+
+ // Stomp it with our unique string
+ if ( bValid )
+ {
+ memcpy( c, sOurFixup, iFixupSize );
+ c += iFixupSize;
+ }
+ }
+ c++;
+ } while (*c);
+
+ return g_Templates[iIndex]->pszFixedMapData;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Frees all the template data. Called on level shutdown.
+//-----------------------------------------------------------------------------
+void Templates_RemoveAll(void)
+{
+ int nCount = g_Templates.Count();
+ for (int i = 0; i < nCount; i++)
+ {
+ TemplateEntityData_t *pTemplate = g_Templates.Element(i);
+
+ free((void *)pTemplate->pszName);
+ free(pTemplate->pszMapData);
+ if ( pTemplate->pszFixedMapData )
+ {
+ free(pTemplate->pszFixedMapData);
+ }
+
+ free(pTemplate);
+ }
+
+ g_Templates.RemoveAll();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Hooks in the template manager's callbacks.
+//-----------------------------------------------------------------------------
+class CTemplatesHook : public CAutoGameSystem
+{
+public:
+ CTemplatesHook( char const *name ) : CAutoGameSystem( name )
+ {
+ }
+
+ virtual void LevelShutdownPostEntity( void )
+ {
+ Templates_RemoveAll();
+ }
+};
+
+CTemplatesHook g_TemplateEntityHook( "CTemplatesHook" );
+
+
+//-----------------------------------------------------------------------------
+// TEMPLATE SAVE / RESTORE
+//-----------------------------------------------------------------------------
+static short TEMPLATE_SAVE_RESTORE_VERSION = 1;
+
+class CTemplate_SaveRestoreBlockHandler : public CDefSaveRestoreBlockHandler
+{
+public:
+ const char *GetBlockName()
+ {
+ return "Templates";
+ }
+
+ //---------------------------------
+
+ void Save( ISave *pSave )
+ {
+ pSave->WriteInt( &g_iCurrentTemplateInstance );
+
+ short nCount = g_Templates.Count();
+ pSave->WriteShort( &nCount );
+ for ( int i = 0; i < nCount; i++ )
+ {
+ TemplateEntityData_t *pTemplate = g_Templates[i];
+ pSave->WriteAll( pTemplate );
+ pSave->WriteString( pTemplate->pszName );
+ pSave->WriteString( pTemplate->pszMapData );
+ }
+ }
+
+ //---------------------------------
+
+ void WriteSaveHeaders( ISave *pSave )
+ {
+ pSave->WriteShort( &TEMPLATE_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 == TEMPLATE_SAVE_RESTORE_VERSION );
+ }
+
+ //---------------------------------
+
+ void Restore( IRestore *pRestore, bool createPlayers )
+ {
+ if ( m_fDoLoad )
+ {
+ Templates_RemoveAll();
+ g_Templates.Purge();
+ g_iCurrentTemplateInstance = pRestore->ReadInt();
+
+ int iTemplates = pRestore->ReadShort();
+ while ( iTemplates-- )
+ {
+ TemplateEntityData_t *pNewTemplate = (TemplateEntityData_t *)malloc(sizeof(TemplateEntityData_t));
+ pRestore->ReadAll( pNewTemplate );
+
+ int sizeData = 0;//pRestore->SkipHeader();
+ char szName[MAPKEY_MAXLENGTH];
+ pRestore->ReadString( szName, MAPKEY_MAXLENGTH, sizeData );
+ pNewTemplate->pszName = strdup( szName );
+ //sizeData = pRestore->SkipHeader();
+ pNewTemplate->pszMapData = (char *)malloc( pNewTemplate->iMapDataLength );
+ pRestore->ReadString( pNewTemplate->pszMapData, pNewTemplate->iMapDataLength, sizeData );
+
+ // Set this to NULL so it'll be created the first time it gets used
+ pNewTemplate->pszFixedMapData = NULL;
+
+ g_Templates.AddToTail( pNewTemplate );
+ }
+ }
+
+ }
+
+private:
+ bool m_fDoLoad;
+};
+
+//-----------------------------------------------------------------------------
+
+CTemplate_SaveRestoreBlockHandler g_Template_SaveRestoreBlockHandler;
+
+//-------------------------------------
+
+ISaveRestoreBlockHandler *GetTemplateSaveRestoreBlockHandler()
+{
+ return &g_Template_SaveRestoreBlockHandler;
+}