Step 3 Extending the concept a little. Why only explode grenades?? Why don't we
explode nearby ammo boxes, rockets, etc as well! Well why not indeedy! Now
a box of rockets has a classname of "item_rockets", so we need to test the
classname for this as well. But how do we explode the box? Well there are
a number of ways to do this, but there are also a few problems as well. In
deathmatch, you still want the item to respawn after it has been exploded. If
we have a look at items.qc, we can see the way the items pretty much work.
Each item has a different "touch" function. This function checks the "other"
variable, and if it is a player (it could be a rocket/grenade/etc), it
modifies the players health/ammo/weapons/etc, and then dissappears. It is
this disappearance part that we are interested in. Have a look at the end
of the code in the ammo_touch() function near the end of items.qc.
void() ammo_touch={
...
...
// remove it in single player, or setup for respawning in deathmatch
self.model=string_null;
self.solid=SOLID_NOT;
if(deathmatch==1)
self.nextthink=time+30;
self.think=SUB_regen;
activator=other;
SUB_UseTargets(); // fire all targets / killtargets
...
}
The code changes the items model to null (nothing), changes its solid value
to SOLID_NOT, which means it does not interact with any other objects. From
defs,qc, the comment is "no interaction with other objects", which means you
can effectively walk through it without any touch/etc functions being called.
It then checks to see what deathmatch mode it is in, and if respawn is set,
it sets the "think" function to SUB_regen. This simply sets the model back
to the correct one, and sets solid back to SOLID_TRIGGER. If you are
interested, have a look at it at the top of items.qc. The SUB_UseTargets()
call does death resolution, etc, etc. I really don't understand it all that
well, but since we are not doing anything we can safely(?) ignore it. Now
given, all what we know now, we could write the following code,
//WEAPONS.QC
void() GrenadeExplode={
local entity ptr;
T_RadiusDamage(self, self.owner, 120, world);
WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
WriteByte(MSG_BROADCAST, TE_EXPLOSION);
WriteByte(MSG_BROADCAST, self.origin_x);
WriteByte(MSG_BROADCAST, self.origin_y);
WriteByte(MSG_BROADCAST, self.origin_z);
// call "findradius" to return list of entity's found
// (I have chosen to use a radius of 120 because that is what is used by
// the grenades in the T_RadiusDamage() function call above. Feel free
// to alter it to taste)
ptr=findradius(self.origin, 120);
// set this grenades lip value
ptr.lip=1234.0;
// The list is returned to us in a linked-list fashion. Put simply; if there
// are no entities found, ptr is zero (or false). If there is at least one
// entity found, ptr will refer to it.
while(ptr){
// here we check to see if the entity is of class "grenade", and that
// we haven't found a grenade with a set lip value. Notice that we
// don't check for self any more? We don't need to anymore because we
// set our own lip value before entering this section of code.
if((ptr.classname=="grenade") && (ptr.lip!=1234.0)){
// if both these conditions are true, set the nextthink time of the
// entity to the current game time, and make sure the think function
// of the entity is referring to the GrenadeExplode() function.
ptr.nextthink=time;
ptr.think=GrenadeExplode;
// here we check to see if we have found a item_rockets entity. We also
// check the solid value or we would keep finding the same ammo box more
// than once.
}else if((ptr.classname=="item_rockets") && (ptr.solid==SOLID_TRIGGER)){
// this code simply make the box invisible.
ptr.model=string_null;
ptr.solid=SOLID_NOT;
if(deathmatch==1)
ptr.nextthink=time+30;
ptr.think=SUB_regen;
}
// By using the "chain" attribute of the entity we can find the next
// entity in the list of entity's found by the findradius() function.
ptr=ptr.chain;
}
// finally become an explosion, just as before
BecomeExplosion();
};
Ok, so we are part way there, but we still don't get an explosion. The box
simply dissappears. Ok, well hold on to your hats. What we are going to do
is dynamically create a grenade at the same location as the ammo box, and
set it to detonate instantly! This allows us to keep all our altered code
in the same place. Now, if we have a look at the W_FireGrenade() function,
void() W_FireGrenade={
local entity missile, mpuff;
...
missile=spawn ();
missile.owner=self;
missile.movetype=MOVETYPE_BOUNCE;
missile.solid=SOLID_BBOX;
missile.classname="grenade";
...
missile.touch=GrenadeTouch;
// set missile duration
missile.nextthink=time+2.5;
missile.think=GrenadeExplode;
setmodel (missile, "progs/grenade.mdl");
setsize (missile, '0 0 0', '0 0 0');
setorigin (missile, self.origin);
};
So what happens is that a new entity is spawned. We set up its attributes
like owner, movetype, model, etc, set its nexthink time, and think function.
Now, some of this we can ignore. What we are interested is the following,
missile.classname="grenade";
missile.movetype=MOVETYPE_NONE; //straight from defs.qc
missile.solid=SOLID_NOT;
missile.nextthink=time;
missile.think=GrenadeExplode;
setmodel(missile, "progs/grenade.mdl");
setsize(missile, '0 0 0', '0 0 0');
setorigin(missile, self.origin);
This gives us the barest minimum to create a grenade set to explode virtually
straight away at the location of the ammo box. I set movetype to MOVETYPE_NONE
because it won't be moving. It also allows us to ignore initialising the
velocity. Similarly, setting solid to SOLID_NOT guarentees that we don't
have to set the touch function. Now, if we embed this code fragment into our
function we get,
//WEAPONS.QC
void() GrenadeExplode={
local entity ptr, grenade;
T_RadiusDamage(self, self.owner, 120, world);
WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
WriteByte(MSG_BROADCAST, TE_EXPLOSION);
WriteByte(MSG_BROADCAST, self.origin_x);
WriteByte(MSG_BROADCAST, self.origin_y);
WriteByte(MSG_BROADCAST, self.origin_z);
// call "findradius" to return list of entity's found
// (I have chosen to use a radius of 120 because that is what is used by
// the grenades in the T_RadiusDamage() function call above. Feel free
// to alter it to taste)
ptr=findradius(self.origin, 120);
// set this grenades lip value
ptr.lip=1234.0;
// The list is returned to us in a linked-list fashion. Put simply; if there
// are no entities found, ptr is zero (or false). If there is at least one
// entity found, ptr will refer to it.
while(ptr){
// here we check to see if the entity is of class "grenade", and that
// we haven't found a grenade with a set lip value. Notice that we
// don't check for self any more? We don't need to anymore because we
// set our own lip value before entering this section of code.
if((ptr.classname=="grenade") && (ptr.lip!=1234.0)){
// if both these conditions are true, set the nextthink time of the
// entity to the current game time, and make sure the think function
// of the entity is referring to the GrenadeExplode() function.
ptr.nextthink=time;
ptr.think=GrenadeExplode;
// here we check to see if we have found a item_rockets entity. We also
// check the solid value or we would keep finding the same ammo box more
// than once.
}else if((ptr.classname=="item_rockets") && (ptr.solid==SOLID_TRIGGER)){
// this code simply make the box invisible.
ptr.model=string_null;
ptr.solid=SOLID_NOT;
if(deathmatch==1)
ptr.nextthink=time+30;
ptr.think=SUB_regen;
// dynamically create a new entity. Initialise the grenades attributes
grenade=spawn();
grenade.classname="grenade";
grenade.movetype=MOVETYPE_NONE; //static movement
grenade.solid=SOLID_NOT; //don't want it to interact
grenade.nextthink=time; //want it to explode NOW! (almost)
grenade.think=GrenadeExplode;
setmodel(grenade, "progs/grenade.mdl"); //set model
setsize(grenade, '0 0 0', '0 0 0'); //set size (ignored)
setorigin(grenade, ptr.origin); //set location of grenade
}
// By using the "chain" attribute of the entity we can find the next
// entity in the list of entity's found by the findradius() function.
ptr=ptr.chain;
}
// finally become an explosion, just as before
BecomeExplosion();
};
Step 4
So compile this, and you will now be able to explode ammo boxes! Now, onto
rockets! This is where it gets a little messier, but shit happens.
< to be continued >
Tutorial HTMLized by Adrian "Mr. Pink" Finol |
|