Prereqs:
be able to compile a q3 mod
By Cricel
Difficulty:
Easy
An intro: I subscribe to the "walk you through the
learning process" philosphy. That means I will walk you
through it as if I were learning it -- for the most part
(I usually have to do a LOT more work for these, mostly
because I do some heavy exploring) this is exactly the
way I learned how to do what is described in the
tutorial -- so this will be a bit slow. However, this is
a really easy tutorial so I'm expecting that mostly
newbie programmers will want to use this. If you're more
advanced though, you might still learn something so
don't dismiss it out of hand. With that as a warning,
let's begin the tutorial...
Now it's time to have fun: We're going to blow people
up. In other words, we're going to add a bit to your q3
code such that when a person dies, an explosion goes off
where they are.
So how do you start something like this? I want to
create an explosion on top of a dead body. Well, first
there's the explosion. What am I basing this explosion
thing on? I want it to look like a rocket hit the person
as they die. Ash... Now we can do something: hunt down
the code for a missile hit. So, jump into g_missile.c.
Go to line 285, it says ROCKET... Yeah, this must be
what we want...Look around, what do we see? Nothing,
it's useless... Agh, scroll down out of frustration, in
fact, you're so frustrated you scroll down to LIGHTNING
GUN and low and BEHOLD... There is this strange little
line that says something about EV_MISSILE_HIT in nice
big little letters... Sounds like something useful,
looks like something interesting, [sniff screen here]
smells like something useful...It's in big capital
letters.... (note: do NOT subscribe to this logc in the
future....) Yes dear children, we are on a mission from
God.
Well, now what? It looks neat and it probably does
something but... Search. Always search. If I find
anything that looks even vaguely interesting I start
doing searches (Find In Files...)and follow where the
trails lead. You can find some MIGHTY interesting stuff
that way... I highly recommend it. Click on the first
result from a search on the missile hit thing, it's the
enum for events, special but not right now. Click on the
second one (should look like G_AddEvent( nent,
EV_MISSILE_HIT, DirToByte( trace->plane.normal) ); ),
hm, funky, find out what function this is (always find
out what function you're in when you go searching
otherwise it is no where near as helpful) --
G_MissileImpact, sounds useful.scroll through it a bit,
passing the instance of EV_MISSILE_HIT that got us here
and hey, what's this, there's another one! Let's look at
that one, it looks neat... G_AddEvent eh? Let's do a
search on that, this time find the definition of it (it
will end with an open bracket in the find files list)
and take a look. Intersting but not useful. Hm, no luck
that route, let's go back to the ever faithful
EV_MISSILE_HIT.
Try one of the lower ones on the results list, the
ones that start with G_TempEntity -- go to the very last
one. Ha, we're back in g_weapon.c in the Lightning gun
bit... Well, let's take a look. What does this do?
Search for G_TempEntity, find the definition.
Conveniently located just above the beginning of the
definition is a section all about G_TempEntity and what
do you know? It's all about making a temporary entity
(read: "thingy")in q3 (entities you need to get the hang
of fast, they're the building blocks of the q3 world).
Sounds about right, explosions are as temporary as they
come. So, let's get on with it: go back to the lightning
gun code.
First line: create a temporary entity. Second: get
the entity number... Entity number? What's the right
hand value? Look around, the traceEnt thing sounds
useful, scroll up a bit and look around some and do a
little bit of regular logical figuring stuff out and we
find out that the traceEnt is actually the entity that
was hit by the lightning gun. Ok, so then, this is the
number of the entity we hit. Q3 must keep track of these
numbers somewhere. So be it (i.e. it's a tool to use,
for _now_ we don't need to care about it any more than
that). Third line: eventParam... yeah....whatever. I
think we're going to need some explanation on this one:
search it. In this particular instance though, a hint:
when searching for things like this in q3 where they are
a part of an entity or client state or entity state
(you'll find em eventually, don't worry) and they have
the -> thing (identifying a member of a pointer to a
class) then odds are you should do your search for the
thing starting with the ->. So, search for this
(without the quotation marks): "->s.eventParm"
Scroll through the results, just look them over and
you'll see that this is used for a whole bunch of stuff.
Obviously id needed some random variable for holding any
old value to identify something for an event; it changes
depending on the event. So for right now, we don't need
to worry too much about what it really is, just that it
is some number based of off DirToByte (whatever that
is).
Fourth line: identifies the weapon that was fired, ok
that works for me.
So now we more or less have some way to create an
explosion somewhere. Now we need to find a dead body to
attach it to....Search for death. Oh, wow, back up, no,
too much death thank you very much... Hmm, how can we
find this a bit faster than sorting through all ~200 of
those results?... How about MOD_[weapon]?It showed up a
couple of times if we looked over the results a bit
so... Search for "MOD_". Ack, quite a few still. BUT...
The last one on the list says something about
G_Damage...hm, maybe that is the function that does
damage to people?...Let's find out. Double click the
line about G_Damage in the results.
Oh, we're in the railgun firing function. How
interesting, but not useful. So that was not specific
enough, let's do a search on "G_Damage" itself... blah,
blah, blah, crap and more crap, oh hey, the definition,
let's jump to that (so do it). Allsorts of damage
dealing stuff, nothing that looks like quite what we
want though... Scroll down through it and... ah, wait,
there at the bottom: targ->health <= 0... that
sounds like a dead body waiting to happen to me... and
right below that what do we find?... targ->die, how
convenient. OK, do a search on "->die". Not a very
long list this time, is it? OK, what have we got?
body_die, player_die, body_die and targ->die where we
started. How about...body_die since it comes up twice.
Click on the self->die =body_die line.
It says in a comment right above the line we jumped
to: "bodycan still be gibbed" Agh, body destruction. Not
useful. So...Do a search on player_die. Jump to the
definition. Whoa, look at that, we were IN player_die
with that last body_die thing. Ah well, we're here now.
Must be the place. Scope the place out. Who killed us...
Tell everyone we died and how... scoring, flags, scores,
yep looks about right to me. So now all we need to do is
paste in our bit about an explosion. Drop this in right
above the (soon to be other) TeamEntity line with
EV_OBITUARY: (copy and paste it from g_weapon.c's
lightning gun bit where we first saw this)
tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT
);
tent->s.otherEntityNum =
traceEnt->s.number;
tent->s.eventParm =
DirToByte( tr.plane.normal );
tent->s.weapon =
ent->s.weapon;
Now we're going to have to change a bit of this. We
no longer have a tr (it's a trace from the railgun
trail, but needless to say, we don't need it) so we're
going to have to do something with that. Then we need to
define some ent above to actually assign this to, tent
doesn't work for me, let's make it explode (or whatever
works for you). We're also going to have to figure out
that DirToBytebit looks like...
So first things first: declare a gentity_t pointer
called explode:
gentity_t *explode; // cricel: the explosion when you
die.
then change all the "tent"s from above into
"explode"s.Then since we need a place to start this
explosion and we're intelligent enough to figure out
that tr.endpos must be the finishing point of the
railgun shot so that's where it does it's missile hit
(we'll deal with the fact that the railgun is not a
rocket in a second in case you're wondering about that)
then we need to replace it with the spot where the dead
body is: look right below at the other TempEntity bit
with the orbituary: self->r.currentOrigin...Sounds
about right to me... So:
explode = G_TempEntity( self->r.currentOrigin,
EV_MISSILE_HIT);
is our first line now. But we don't have any traceEnt
either, that's self again though because that's the
person who got hit --self. So:
explode->s.otherEntityNum = self->s.number;
is our second line now. Let's do something about that
DirToByte thing now. Search for it and get the
definition. Blah blah blah, dotproduct, vector crap
(which I assure you I know more than enough about so I'm
not skipping it because I can't do it but because it is
irrelevant) so it says if no vec3_t, we return 0
eh?...Let's make it zero. So:
explode->s.eventParm = 0;
is our third line now. What about this s.weapon? It
doesn't immediately seem like a problem, so if you want,
compile and run and see what happens. But I'll tell you
now if you want to know: it won't quite work. See what's
happening there right now is that it's going to do
whatever "Missile Hit" is associated with that weapon
type: for a railgun it will make the little circle
thing, for a plasma gun or a BFG it will make a bigger
circle thing and for the Rocket launcher it will make
the explosion. So if you want, compile, kill a bot with
a rocket launcher and it will make a SECOND explosion.
We must be doing something right...But kill them with a
machinegun or shotgun or anything else and they just
die. How not interesting. So:
explode->s.weapon = WP_ROCKET_LAUNCHER; // makes
it look like an RL hit
is our fourth line because we have figured out by
doing a search on "->s.weapon" (you did right?...)
that it takes a #define constant of the different
weapons. So we give it the RL number and voila, compile
and run it: everyone who dies gets an explosion over
them when they die. WE DID IT!!!!! Bask in the glorious
majesty of it all....
And then try something more... Let's add a second
explosion. And a third, in fact, let's just make it a
loop, I'll make the example here for three, you can go
overboard if you want. We need to make sure that the
explosions are not all on top of each other, so we're
going to have to move them around a bit and we're going
to have to make explode into an array. So:
gentity_t *explode[3]; // cricel: the explosions when
you die.
vec3_t explodecent; // the center for the
explosions
int loopi; // the loop counter
.....
// Cricel: the for loop for multiple
explosions
for(loopi=0; loopi<3;
loopi++)
{
VectorCopy(self->r.currentOrigin,
explodecent);
// the above give us a center point at
the dead body, if you want, move this above
// the
loop and what will happen is the explosions will be able
to move around a bit more
// this moves the explosions around
// crandom is
a built in q3 function that gives a number between[-1,
1] inclusive
explodecent[0] += crandom() *
50;
explodecent[1] += crandom() *
50;
explodecent[2] += crandom() * 50;
// what we did before but updated to be an
array
explode[loopi] = G_TempEntity( explodecent,
EV_MISSILE_HIT );
explode[loopi]->s.otherEntityNum
= self->s.number;
explode[loopi]->s.eventParm =
0;
explode[loopi]->s.weapon = WP_ROCKET_LAUNCHER;
// makes itlook like an RL hit
}
That's our final code in g_combat.c. Nothing else is
needed. All players that die now will have as many
explosions on them when they die as you want... Possible
extensions: change 3 in the loop initialization to
random()*3 (random() is crandom except that instead of
going from -1 to 1, it goes from 0 to 1) so that it will
have 0 to 3 explosions when a person dies or perhaps
random()*2+1for 1-3 explosions. The only other one that
I really want is for time staggered explosions but due
to the nature of TempEntity, my efforts to get this to
work so far have failed. That and perhaps making it so
that each explosion that popped off of a dead body was
actually linked to a real explosion that did radius
damage....
The way I attempted to add staggered time for those
who would be interested in fixing it/learning from it
(add this right after the ->s.weapon line, right
before finishing the loop):
explode[loopi]->eventTime += random() * 1000; //
explodes within the next second
Have fun
Cricel