Accessing Other Entities: Difference between revisions
TomEdwards (talk | contribs) m (→Pointers) |
TomEdwards (talk | contribs) (various tweaks) |
||
Line 1: | Line 1: | ||
{{toc-right}} | {{toc-right}} | ||
In [[Hammer]], [[entity|entities]] can only communicate through pre-defined [[inputs and outputs]]. The good news is that in C++ this limitation goes away and you can access anything that | In [[Hammer]], [[entity|entities]] can only communicate through pre-defined [[inputs and outputs]]. The good news is that in C++ this limitation goes away and you can access anything that isn't explicitly "private". The bad news is that you have to work a bit harder to get off the ground. | ||
== An example == | == An example == | ||
<source lang="cpp">CBreakable* pWall = GetFuncBreakable(); // Made-up function! | <source lang="cpp"> | ||
CBreakable* pWall = GetFuncBreakable(); // Made-up function! | |||
if (!pWall) return; | if (!pWall) return; | ||
pWall->Break(this);</source> | pWall->Break(this); | ||
</source> | |||
In the above code we: | In the above code we: | ||
Line 36: | Line 38: | ||
Pointees must be of the same class as the pointer. But what about [[touch function]]s, which provide the other entity involved as <code>CBaseEntity*</code>? There's no such thing as a pure <code>CBaseEntity</code>, right? | Pointees must be of the same class as the pointer. But what about [[touch function]]s, which provide the other entity involved as <code>CBaseEntity*</code>? There's no such thing as a pure <code>CBaseEntity</code>, right? | ||
Indeed there isn't. The pointee isn't <code>CBaseEntity</code>, but the ''pointer'' has been | Indeed there isn't. The pointee isn't <code>CBaseEntity</code>, but the ''pointer'' has been "cast" to act as if it were one. This is possible when one class inherits from another; since everything inherits from <code>CBaseEntity</code> everything can be cast to <code>CBaseEntity*</code>. Essential knowledge if you don't know what entities your code will be dealing with! | ||
A <code>CBaseEntity*</code> pointer is fine if you want to access something defined in <code>CBaseEntity</code>. But if you need to access something defined further | A <code>CBaseEntity*</code> pointer is fine if you want to access something defined in <code>CBaseEntity</code>. But if you need to access something defined further downstream, then the pointer must be cast to a class that has access to it. This is achieved with '''<code>dynamic_cast</code>'''. | ||
<source lang="cpp">CMyEntity::Touch( CBaseEntity* pOther ) | <source lang="cpp"> | ||
CMyEntity::Touch( CBaseEntity* pOther ) | |||
{ | { | ||
CBreakable* pWall = dynamic_cast<CBreakable*>(pOther); | CBreakable* pWall = dynamic_cast<CBreakable*>(pOther); | ||
Line 46: | Line 49: | ||
pWall->Break(this); | pWall->Break(this); | ||
}</source> | } | ||
</source> | |||
We cast <code>pOther</code> to <code>CBreakable*</code>, storing the result in <code>pWall</code>. | We cast <code>pOther</code> to <code>CBreakable*</code>, storing the result in <code>pWall</code>. We can then access the <code>CBreakable</code> function <code>Break()</code>. | ||
It's '''doubly quadruply important''' to check that the pointer is assigned this time because the cast will be attempted whenever the entity touches something. If <code>pOther</code> isn't actually a <code>CBreakable</code> then the operation will fail, leading to a | It's '''doubly quadruply important''' to check that the pointer is assigned this time because the cast will be attempted whenever the entity touches something. If <code>pOther</code> isn't actually a <code>CBreakable</code> then the operation will fail, leading to a dangerous null pointer. | ||
{{note|Remember that casting a pointer does not affect the pointee itself.}} | {{note|Remember that casting a pointer does not affect the pointee itself.}} | ||
{{tip|If the classes of both objects are known, you can perform a | {{tip|If the classes of both objects are known, you can perform a safer "static cast" like this: <code>CBreakable* pWall <nowiki>=</nowiki> (CBreakable*)pOther</code>. The compiler will throw an error if this isn't legal.}} | ||
=== CHandle === | === CHandle === | ||
Line 62: | Line 66: | ||
== Finding entities == | == Finding entities == | ||
There isn't much point in knowing how to create pointers if you don't have an entity to work with in the first place. '''<code>gEntList</code>''' is the place to get one. It's a global object | There isn't much point in knowing how to create pointers if you don't have an entity to work with in the first place. '''<code>gEntList</code>''' is the place to get one. It's a global object available through <code>cbase.h</code>. | ||
<code>gEntList</code> offers various search functions, the names of which all start with <code>Find</code> or <code>Next</code>. Each one will only return one <code>CBaseEntity*</code> at a time however, so if you want to | <code>gEntList</code> offers various search functions, the names of which all start with <code>Find</code> or <code>Next</code>. Each one will only return one <code>CBaseEntity*</code> at a time however, so if you want to iterate through all the results a bit of padding is needed: | ||
<source lang="cpp">CBaseEntity* pResult = gEntList.FindEntityByClassname(NULL,"npc_*"); | <source lang="cpp"> | ||
CBaseEntity* pResult = gEntList.FindEntityByClassname(NULL,"npc_*"); | |||
while (pResult) | while (pResult) | ||
{ | { | ||
Line 74: | Line 79: | ||
pResult = gEntList.FindEntityByClassname(pResult,"npc_*"); | pResult = gEntList.FindEntityByClassname(pResult,"npc_*"); | ||
}</source> | } | ||
</source> | |||
Here we: | Here we: | ||
Line 83: | Line 89: | ||
# Confirm that the cast has been successful. | # Confirm that the cast has been successful. | ||
# Perform our desired operation. | # Perform our desired operation. | ||
# Call <code>FindEntityByClassname()</code> and search for "npc_*" again | # Call <code>FindEntityByClassname()</code> and search for "npc_*" again, this time starting at pResult's position in the list. | ||
# Return to the start of the loop. | # Return to the start of the loop. | ||
Searching the whole entity list all the time is [[expensive]] so try to avoid doing it if you can. For example | Searching the whole entity list all the time is [[expensive]] so try to avoid doing it if you can. For example if your class will need to access the other entity again later, keep it stored away in a member pointer. | ||
== See also == | == See also == |
Revision as of 10:07, 16 August 2009
In Hammer, entities can only communicate through pre-defined inputs and outputs. The good news is that in C++ this limitation goes away and you can access anything that isn't explicitly "private". The bad news is that you have to work a bit harder to get off the ground.
An example
CBreakable* pWall = GetFuncBreakable(); // Made-up function!
if (!pWall) return;
pWall->Break(this);
In the above code we:
- Declare a pointer and assign an entity we want to it (a func_breakable)
- Confirm that the pointer was successfully assigned a value
- Access a member function of the target entity

Pointers
A pointer is the C++ equivalent of a desktop shortcut. The current target of a pointer is called the 'pointee'.
A pointer has five main rules:
- Pointers are declared like a variable, except for an asterisk before the name (e.g.
CBaseEntity* pOther
). Don't use the asterisk anywhere else - it's just for declarations.Note:Valve tend to place the asterisk before the pointer's name (e.g.
CBaseEntity *pOther
). This isn't strictly correct, but the compiler doesn't mind one way or the other. - You must
#include
the header file of any class you want to create a pointer for. - A pointer can only be assigned an object that matches its class. The compiler will throw an error otherwise.
- When accessing members of a pointee, you must use
->
instead of the usual period character. - Any attempt to access an unassigned ('null') pointer will immediately crash your mod. Welcome to C++!
These aside, the syntax surrounding a pointer is the same as any other member of the current class.
Casting
Pointees must be of the same class as the pointer. But what about touch functions, which provide the other entity involved as CBaseEntity*
? There's no such thing as a pure CBaseEntity
, right?
Indeed there isn't. The pointee isn't CBaseEntity
, but the pointer has been "cast" to act as if it were one. This is possible when one class inherits from another; since everything inherits from CBaseEntity
everything can be cast to CBaseEntity*
. Essential knowledge if you don't know what entities your code will be dealing with!
A CBaseEntity*
pointer is fine if you want to access something defined in CBaseEntity
. But if you need to access something defined further downstream, then the pointer must be cast to a class that has access to it. This is achieved with dynamic_cast
.
CMyEntity::Touch( CBaseEntity* pOther )
{
CBreakable* pWall = dynamic_cast<CBreakable*>(pOther);
if (!pWall) return;
pWall->Break(this);
}
We cast pOther
to CBreakable*
, storing the result in pWall
. We can then access the CBreakable
function Break()
.
It's doubly quadruply important to check that the pointer is assigned this time because the cast will be attempted whenever the entity touches something. If pOther
isn't actually a CBreakable
then the operation will fail, leading to a dangerous null pointer.


CBreakable* pWall = (CBreakable*)pOther
. The compiler will throw an error if this isn't legal.CHandle
Pointers represent physical locations in system memory. If you want to transfer a pointer between the client and server or store it in a saved game, you must use a CHandle
(aka EHANDLE
).
Finding entities
There isn't much point in knowing how to create pointers if you don't have an entity to work with in the first place. gEntList
is the place to get one. It's a global object available through cbase.h
.
gEntList
offers various search functions, the names of which all start with Find
or Next
. Each one will only return one CBaseEntity*
at a time however, so if you want to iterate through all the results a bit of padding is needed:
CBaseEntity* pResult = gEntList.FindEntityByClassname(NULL,"npc_*");
while (pResult)
{
CAI_BaseNPC* pNPC = dynamic_cast<CAI_BaseNPC*>(pResult);
if (pNPC)
pNPC->SetState(NPC_STATE_IDLE);
pResult = gEntList.FindEntityByClassname(pResult,"npc_*");
}
Here we:
- Call
gEntList.FindEntityByClassname()
to get the first entity with the Hammer classname "npc_*".Note:The asterisk is a search wildcard in this context.
- Enter a loop that ends when
pResult
becomes valid. - Cast our
CBaseEntity*
,pResult
, toCAI_BaseNPC*
so that we can access its AI-related members. - Confirm that the cast has been successful.
- Perform our desired operation.
- Call
FindEntityByClassname()
and search for "npc_*" again, this time starting at pResult's position in the list. - Return to the start of the loop.
Searching the whole entity list all the time is expensive so try to avoid doing it if you can. For example if your class will need to access the other entity again later, keep it stored away in a member pointer.