The basics of creating Entities

 

Or objects if you want to use a different name. I saw a bunch of people talking on the warzone bbs about what they want from tutorials and something along these lines was suggested; So here it is.


The Real Basics

The first thing you will need if you want to create an object you can interact with (a bomb, a bullet , something along those lines) is a model. You could use one of the ones that comes with quake 2 or you could create your own. If you don't want to create your own (or can't) then get something like Qped (Qped homepage) to open up the pak0.pak file that comes with quake 2, to find the pathname of the model you want to use. If you can create your own model, then note down the pathname (I'd suggest putting the model somewhere like yourmoddir/models/mymodel.md2)

After you have the model, then you will need to have a set of coordinates to create the model at. Quite commonly people will use the coords of the player model. Thats what I will be doing in the example at the bottom.

With the information to hand, you can now add the object to the quake 2 environment. It won't do anything, but sit there and look pretty, but I'll get back to that.


If you have looked through the code you will have noticed that all definitions of edict_t 's(edict_t = object it's just the name of the structure that holds all the information) have had a star in front of the variable name. This is because you never actually create an edict_t yourself. All you do is create a variable that will store the location of one that the engine has created for you. This means there is a limit of 1024 edict_t's. While this may seem like a limitation, it was done to make it easier to code. without the list that exists it would be hard to keep track of all the edict_t's there are in the game. To assign a real edict_t to your pointer, you will need to call the function G_Spawn this will return the location of a free edict_t.

To make a edict_t visible you have to link it to the world. to do this you have to use the game interface function (one that you can't even get at) gi.linkentity.

Here is an example of how you would create an entity and make it visible. I've encapsulated it in a function that looks like a standard player command function. How to make player commands is a different tutorial entirely

void Example_Command(edict_t *ent)   // the edict_t being passed in is the player
{
edict_t *object;   // the pointer to our object (or at least, it will be when I'm finished)
 
object=G_Spawn();     // here we get a free edict_t and assign it's location to object so we can manipulate it
 
VectorCopy (ent->s.origin, object->s.origin);  // here we take the players location and assign it to the new objects location. 
                                              // we have to use VectorCopy as origin is a <a href="glossary.html#vec3_t" traget=glos>Vec3_t</a>
 
object->s.modelindex = gi.modelindex ("models/items/band.md2");   // here we set the model to be used for the object. I picked this one at random.
 
gi.linkentity (object);   // This is the object linked to the game. It will now show up, not doing anything.
 
}

Fairly simple, don't you agree? Thats the real basics, they don't do anything interesting and aren't even solid.


Slightly more advanced

This is where things become slightly more useful. The way Quake 2 handles you touching objects is by setting bounds on the object and when something passes within them, a function is called. the bounds are 2 Vec3_t's mins and maxs. They default to 0 so if you don't set them you have to pass through the center of the object to have anything happen. You set the fields of maxes to positive floats and those of mins to negitive ones, and a box is generated from them. You set object.touch to the name of the touch function you want to use. It has to be of the following format

void touch_function (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)

Change the function name to something meaningful. self refers to the object being touched, other refers to the edict_t touching it. The others are unimportant at this time

Look at the Rocket function in g_weapon.c for an example


Basic Animation

Say the model you want to use for your object has animation (multiple frames), one way to get the animation working would be to use the think function of the object (I'll get back to this shortly), but this isn't a good idea, especailly if you want your object to do something every now and then rather than every frame. Thankfully there is a way round this and that is object->prethink. you set this equal to a function of a set parameter list and every frame it will be executed. below is a simple piece of code to make you object animate. This code will work for a model that has 18 frames and will cycle through it at 10 hertz (1 frame every 0.1 seconds). The code could be a little difficult to understand but I'll explain it

void ItemAnimate (edict_t *ent)
{
  int lastframe = 18;
  ent->s.frame = ent->s.frame + 1 < lastframe ? ent->s.frame + 1 : 0;
}

to use this you set object->prethink=ItemAnimate As for an explanation. lastframe is set to 18 when it is created. The line below that checks the value of (ent->s.frame+1 < lastframe) , this will be a boolean, true or false. If it is true ent->s.frame is set to ent->s.frame+1 and if it is false it is set to 0. This means the model will cycle though all 18 frames ( 0 to 17). Thanks to Warzone for showing me this.


Making Items Solid

This is simple. You have to pick a type of solid you want the object to be. Generally SOLID_BBOX will be what you want. Then you set object->solid to it. The object->maxs and object->mins will define how much space becomes solid. Types of solid

NAME

Definition

SOLID_NOT

no interaction with other objects

SOLID_TRIGGER

only touch when inside, after moving

SOLID_BBOX

touch on edge

SOLID_BSP

bsp clip, touch on edge


Alowing Items to die

As a default you cannot shoot objects to make them go away. To allow an object to die you have to set object->health to a positive value and object->takedamage to DAMAGE_YES

You will also have to set its maxs and mins to some value, unless you want it to be really hard to hit (a point is harder to hit than a box). You now have to create a function to be executed when the object dies. it has to be of the following format

void object_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)

While you could just G_FreeEdict the object, quake 2 doesn't like this, so I would suggest setting the think function to G_FreeEdict and nextthink to level.time+0.1. A sample object is given below.

void Example_Command(edict_t *ent)
{
edict_t *object;
object=G_Spawn();
VectorCopy (ent->s.origin, object->s.origin);
object->s.modelindex = gi.modelindex ("models/items/band.md2");
// all  this was explained above
 
VectorSet (object->mins, -8, -8, -8);    // The size of the object is set here
VectorSet (object->maxs, 8, 8, 8); // it is a cube 16 units each edge centered on the origin
 
object->health=10;    // not very tough
object->takedamage=DAMAGE_YES;     // Can now be damaged
object->die=exampledie; // will execute exampledie when it takes more than 10 damage
 
 
object->solid=SOLID_BBOX;   // solid so it will stop people passing through it.
 
gi.linkentity (object);   // This is the object linked to the game. It will now show up, not doing anything.
 
}

And now for an example die function

void exampledie(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
self->takedamage=DAMAGE_NO;  // So it doesn't take more damge and try to reexecute it's self when the next line happens
T_RadiusDamage(self, NULL, 200, NULL, 300, MOD_HGRENADE); // I wanted it to blow up :)
ent->nextthink=level.time+0.1; // explained later
ent->think=Become_Explosion1;  // this is a util function, that spawns a temporary explosion and gets rid of the edict that spawns it
}

I suppose you could throw some gibs , but thats not what the focus of this tutrorial is


Making Objects Think

This is probably the most useful feature of objects. You set the think field of an edict_t and the nextthink field, and when level.time equals nextthink the function pointed to by think is executed. This is gererally used for some big flashy effect, like a big explosion, or alternitvly it is used to make the object go away by G_FreeEdict ing it. I'll give an example of both

.

Example 1. Makeing an object that explodes after 30 seconds.

void Example_Command(edict_t *ent)
{
edict_t *object;
object=G_Spawn();
VectorCopy (ent->s.origin, object->s.origin);
object->s.modelindex = gi.modelindex ("models/items/band.md2");
VectorSet (object->mins, -8, -8, -8);
VectorSet (object->maxs, 8, 8, 8);
object->takedamage=DAMAGE_NO; // You could set it to yes, but makesure you turn the damage off before it explodes
object->solid=SOLID_BBOX;
// all  this was explained above
object->think=examplethink;
object->nextthink=level.time+30; // examplethink will be executed after 30 seconds
 
gi.linkentity (object);   // This is the object linked to the game. It will now show up, not doing anything.
}
 
void examplethink(edict_t *ent)
{
T_RadiusDamage(ent, NULL, 200, NULL, 300, MOD_HGRENADE); // I wanted it to blow up :)
Become_Explosion1(ent); // creates an explosion and releases the edict_t
}

Example 2. getting rid of the objects after a time

void Example_Command(edict_t *ent)
{
edict_t *object;
object=G_Spawn();
VectorCopy (ent->s.origin, object->s.origin);
object->s.modelindex = gi.modelindex ("models/items/band.md2");
VectorSet (object->mins, -8, -8, -8);
VectorSet (object->maxs, 8, 8, 8);
object->takedamage=DAMAGE_NO;
object->solid=SOLID_BBOX;
// all  this was explained above
object->think=G_FreeEdict; // gets rid of the edict_t 
object->nextthink=level.time+30; // examplethink will be executed after 30 seconds
 
gi.linkentity (object);   // This is the object linked to the game. It will now show up, not doing anything.
}

Thats it for now. This is a work in progress. It will get updated as I have time.


Skunkworks Tutorial