The previous page outlined many features found in
CBaseEntity
that you can override and/or use to write entities. Here, you will learn how to write a standard map entity, as well as how to write entries in the FGD for the map editor.
To get the hang of entity writing, let's start with something that is extremely simple, yet still potentially useful for mappers. The idea is an entity that, when triggered, will print a custom message (set by the mapper) to the console. It can be used to debug some entity setups and sequences, or for subtle Easter eggs in the console.
If you haven't read the Overview page, it'd be a very good idea to do so, so you can familiarise yourself with some important methods that you can override. To be precise, read up on Use
.
target_print - implementation
Assuming that you've already created a new .cpp file for the entity, in the
dlls
folder, we will start off by including the necessary headers:
#include "extdll.h"
#include "util.h"
#include "cbase.h"
They absolutely have to be in this order, otherwise there will be compiler errors.
Next, we declare the entity class itself:
class CTargetPrint : public CBaseEntity
{
};
We will also add
LINK_ENTITY_TO_CLASS
below:
LINK_ENTITY_TO_CLASS( target_print, CTargetPrint );
This will essentially "register" the entity, so that the engine can instantiate it whenever it reads "target_print" in the BSP. Under the hood,
LINK_ENTITY_TO_CLASS
declares a global, exported function, which the engine will look up. It's quite dirty and very insecure, but it worked for Valve back in the 90s.
Now, stop for a moment, and think about what the entity is supposed to do. This entity, when it spawns, will basically do nothing, so it doesn't really need a Spawn method. The only thing that will really happen is when the entity is triggered by another entity, whether it's a button or something else. So, we will override Use.
class CTargetPrint : public CBaseEntity
{
public:
void Use( CBaseEntity* pActivator, CBaseEntity* pOther, USE_TYPE useType, float value ) override;
};
LINK_ENTITY_TO_CLASS( target_print, CTargetPrint );
void CTargetPrint::Use( CBaseEntity* pActivator, CBaseEntity* pOther, USE_TYPE useType, float value )
{
}
From here onward, it's quite simple. We just need to print our message:
ALERT( at_console, "Hello world\n" );
However, a question arises: how do we print a custom message? Surely we have to store it somewhere.
Generally, we can approach this in 2 ways, either:
- use one of the pev variables, or
- declare a member variable and initialise it in KeyValue from a custom keyvalue
For the sake of simplicity, let's go with the first route. All
entvars_t
variables are automatically loaded from entity data in the BSP. By default, they're 0. So it's just a matter of choosing which variable we want to use. Let's use
netname
.
Since
netname
is a
string_t
, we will need to convert it to a C string:
ALERT( at_console, "%s\n", STRING( pev->netname ) );
This part is basically done. You could now build the DLL, go to your map editor, and create this entity by creating another entity, renaming its classname and adding keyvalues manually.
target_print - FGD
Of course, this isn't user-friendly. Mappers would rather want to use the entity tool to easily place the entity into the map, and have all relevant properties already listed to them. This is why we write FGDs.
FGDs actually don't have anything to do with game code. The engine doesn't parse the FGD, the game DLL doesn't parse the FGD, and especially not the client DLL. It is only relevant to the map editor, and the only thing the engine reads is entity data from the BSP.
You could take 2 approaches, yet again, either:
- copy and modify your Half-Life FGD to be your mod's FGD (replacement FGD), or
- create a new one with only this single entity, so you can add it to your map editor's FGD list (additive FGD)
Here, we will take the 1st route again, because you won't have to edit your game configuration, and the HL FGD provides many base classes for its entries.
The FGD format has its own, quite extensive syntax, so covering it whole is out of scope for this page. A dedicated article for FGD editing will be written at some point. In short, you write simple point entities the following way:
@ClassType base( B ) = entity_classname : "Description of the entity"
[
keyvalue(datatype) : "Keyvalue title" : "Keyvalue default value"
]
Where:
ClassType
can be BaseClass
(to define a base), PointClass
(to define point entities), SolidClass
(to define brush entities)B
can be any base, such as Targetname
entity_classname
is the classname that must match the one in LINK_ENTITY_TO_CLASS
datatype
can be string
, integer
and so on
It is best to read entries in your FGD to get an idea for the syntax and other rules.
Our entity really only needs 2 keyvalues:
targetname
, so it can be triggered by other entities, and
netname
, to store the message.
@PointClass base( Targetname ) = target_print
[
netname(string) : "Message text"
]
Conclusion
If everything went right, you should see your brand new entity in the map editor's entity list:
And edit its properties nicely:
Finally, in-game, you can see the message:
Note: you need to turn on developer mode (
developer 1
) in order to actually see it in the console.
This entity still isn't entirely done. You can complete it and enhance it with the following:
- if
pev->netname
is empty, delete the entity and warn the mapper - allow the mapper to choose the type of printing (currently, we're only using
at_console
)
You can find the source code for this entity here:
https://github.com/Admer456/halflife-twhl-tutorials/tree/entprog/writing-new-ents