Wavelength Logo
tl.jpg (2351 bytes) blank.gif (837 bytes) tr.jpg (2446 bytes)
blank.gif (837 bytes)
Coding New Entities for Half-Life by PsychoDude
blank.gif (837 bytes)

Note: This tutorial is meant for people fluent in C++, and who know how to create levels in Worldcraft. You need to have Visual C++, preferably 5.0 or 6.0. You probably can't program HL mods in any other VC++ versions.

This is a simple tutorial to discuss making new entities, and reading their keys in your code, so you can customize them. We'll create an object that writes a text message to the screen, and activates some entity, when it's touched. For this tutorial, the mod we'll be modifying will be called "YourMod", though we'll only cover the code that creates the new entity. You must already know how to create new mods in general. The new entity will be called object_touch and the class will be CObjectTouch.

Creating the FGD file for Worldcraft

First we need to create the FGD game data file for Worldcraft. Make a file called YourMod.fgd and put it in your mod directory, like Half-Life\YourMod. Make sure it's in plain-text format; a good idea is to use Notepad for this. Write this in it:

// Worldcraft Game Data file for YourMod

@PointClass = object_touch : "Object Touch"
[
model (string) : "Model" : "models/filecabinet.mdl"
gravity (integer) : "Gravity" : 0.5
message (string) : "Text Message" : "This is a file cabinet."
touchsnd (string) : "Sound On Touch" : "vox/buzwarn.wav"
touchvol (integer) : "Volume (0.0 - 1.0)" : 1.0
touchpitch (integer) : "Pitch" : 100 =
[
75: "Low"
100: "Normal"
150: "High"
]
target (target_destination) : "Activate Entity"
]

Now create a level in Worldcraft and place the entity in there. Set its properties to whatever you want, or keep them as the default. For this example, also place a door in the level somewhere, and enter its Name (the targetname key). Compile your level.

Creating the Code

Now add a new file to your YourMod project (or the MP project if you didn't make your own). Save this file as "ObjectTouch.cpp". First, we'll enter the basic skeleton code for the entity:

#include "extdll.h"
#include "decals.h"
#include "util.h"
#include "cbase.h"
#include "monsters.h"
#include "weapons.h"
#include "nodes.h"
#include "player.h"
#include "soundent.h"
#include "shake.h"
#include "gamerules.h"

class CObjectTouch : public CBaseEntity
{
    public:

    void Spawn( );
    void Precache( );
    void EXPORT Touch( CBaseEntity* );
};

LINK_ENTITY_TO_CLASS( object_touch, CObjectTouch );

void CObjectTouch :: Spawn( )
{
    Precache( );

    SET_MODEL( ENT(pev), "models/filecabinet.mdl" );

    UTIL_SetOrigin( pev, pev->origin );
    UTIL_SetSize( pev, Vector(-16,-16,0), Vector(16,16,64) );

    pev->movetype = MOVETYPE_TOSS;
    pev->solid = SOLID_BBOX;

    SetTouch( Touch );
}

void CObjectTouch :: Precache( )
{
    PRECACHE_MODEL( "models/filecabinet.mdl" );
}

void CObjectTouch :: Touch( CBaseEntity* Player )
{
}

I'm not going to explain all that since I detailed the creation of new entities in my other tutorial.

Now we'll start adding the code to make the values we defined in Worldcraft show up in the entity. It's very easy to use keys that are already defined in the entvars_t struction, which is what pev is an instance of. entvars_t is defined in Engine\progdefs.h, so take a look to see them all. Don't modify this file though, because hl.exe was compiled with it, and if your DLL and hl.exe are compiled with different versions of any file in the Engine directory, it will crash HL.

Since one of the keys is 'model' and since 'model' is already in the entvars_t structure, the code changes we need to make are simple. First, understand this: pev->model is an integer, not a string. Specifically, it's an integer that is the index of the string. It's not an actual pointer, but it's similiar. We need to use STRING to find the string it indexes. STRING returns a string of type 'const char*'. So replace the SET_MODEL line in Spawn() with this line:

SET_MODEL( ENT(pev), STRING(pev->model) );

And replace the line in the Precache() function with this:

PRECACHE_MODEL( (char*)STRING(pev->model) );

The reason we needed to add the '(char*)' part to this is that no PRECACHE functions allow const char* strings as parameters; they only allow char* strings.

Now compile the code. When you run it, you'll find an object placed whereever you put it, with whatever model you set it to have in Worldcraft! Open the map in Worldcraft, and change the Model parameter of the entity to whatever model you want.

In a similiar fashion, we're now gonna add the code for the other keys in the entity that are already defined in entvars_t, which are the message, gravity, and target keys.

We don't need to do anything for the gravity, as it's already set automatically since its key is defined. So just add this to the Touch() function:

if( !Player->IsPlayer() ) return;

UTIL_SayText( STRING(pev->message), Player );

Again, same rules apply for converting pev->message, which is an integer index to a string, directly to a string. So compile the code and run it. When you bump into the object, the message text you set in Worldcraft will print to the screen. By default I set it to "This is a file cabinet."

Adding the target field is less simple. To activate an entity, we call its Use function. Because CBaseEntity has a Use function, every entity derived from it (which is every entity, period) will have a Use function, even if it does nothing. So add this code to the Touch function:

CBaseEntity* EntToActivate = UTIL_FindEntityByTargetname( NULL, STRING(pev->target) );
if( EntToActivate )
    EntToActivate->Use( Player, this, USE_TOGGLE, 0 );

OK, let's go over what all that means. The first parameter of Use is the entity that's activating the event. Since it's the player that's doing it (by bumping the object_touch) we set it to 'Player'. But the entity that's actually calling the function is CObjectTouch, so we set the second parameter to 'this'. USE_TOGGLE is the way we use it... I think you'll always want it to be USE_TOGGLE, since multi_managers always call it this way. I have no idea what the last parameter is, but multi_managers always set it to 0, so I will too.

Now compile the code and run it. The door you set the object's target to should open whenever you bump into the object. Cool!

Now we want to add the custom keys, which are touchsnd, touchvol, and touchpitch. touchsnd is a string, so we want to add an integer index to the CObjectTouch class. Since touchvol and touchpitch are just a float and an integer, which are primitives, we don't need to reference them indirectly with indexes or anything. So rewrite the CObjectTouch class so it looks like this (new code is in green):

class CObjectTouch : public CBaseEntity
{
    public:

    void Spawn( );
    void Precache( );
    void EXPORT Touch( CBaseEntity* );

    void KeyValue( KeyValueData* );

    int TouchSound;
    int TouchPitch;
    float TouchVolume;

};

KeyValue is the function that will get the custom keys' values. First, let's just write it so it reads the touch sound:

void CObjectTouch :: KeyValue( KeyValueData* Data )
{
// Optional:
// ALERT( at_console, "See? KeyValue is called several times." );

    if( FStrEq( Data->szKeyName, "touchsnd" ) )
    {
        TouchSound = ALLOC_STRING( Data->szValue );
        Data->fHandled = TRUE;
    }
}

Really pretty simple. KeyValue is as many times as they are keys for the entity. If there's four keys, it's called four times. To see this, un-comment the ALERT line and watch the console as the level loads. It will be printed each time the KeyValue function is called.

First the function checks to see what the data's key name is. If it's "touchsnd", we set the TouchSound to be an index of the key value string, which by default is "vox/buzwarn.wav". Feel free to change this by setting the entity's key values in Worldcraft.

Now let's implement the code to load the values of touchvol and touchpitch. We need to use atoi (which stands for Something TO Integer) and atof (which stands for Something TO Float) to get their values. So add this code to the KeyValue function, after the if block we already wrote:

else
if( FStrEq( Data->szKeyName, "touchvol" ) )
{
    TouchVol = atof( Data->szValue );
    Data->fHandled = TRUE;
}
else
if( FStrEq( Data->szKeyName, "touchpitch" ) )
{
    TouchPitch = atoi( Data->szValue );
    Data->fHandled = TRUE;
}

So now we have all the custom keys loaded. Since that's what you read this to learn about, you're all set. I'm going to implement them now, to play a sound. Playing sounds is covered in my other tutorial, so if you read it, this following code is nothing new to you.

Anyway, let's play the sound in the Touch() function, so add this code to it:

EMIT_SOUND_DYN( ENT(Player->pev),
                CHAN_ITEM,
                STRING(TouchSound),
                TouchVolume,
                ATTN_NORM,
                0,
                TouchPitch );

...and precache the sound in the Precache() function:

PRECACHE_SOUND( (char*)STRING(TouchSound) );

And there you have it! If you have any problems or questions or comments, feel free to e-mail me. Happy coding!

- PsychoDude

blank.gif (837 bytes)

 

blank.gif (837 bytes)
bl.jpg (2471 bytes) blank.gif (837 bytes) br.jpg (2132 bytes)