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 |