
Quake DeveLS - Lasersight
Author: Geza Beladi
Difficulty: Easy/Medium
Hi there !
SumFuka here, I'm writing up a tutorial for the code that Geza sent in to me, thanks
mate ! This one is interesting and quite fun. Have you ever seen predator ? Do
you remember the part when one of the army guys is hunting the predator, and
then he sees the little red dot moving over his face ? Then the predator blows
him away ? Well... here goes.
The first part of this
tutorial will have you pointing your laser sight (actually, it's a meat ball)
around the map. The second part will involve you creating a new laser sight
'model'... essentially a little red dot. So grab a compiler and a model editor
before entering.
Preliminaries
Open up g_local.h and go
to the very bottom of the file. Add these line to the edict_s structure at the
bottom of the file, so it looks like this :
// common data blocks moveinfo_t moveinfo; monsterinfo_t monsterinfo; // STEVE edict_t *lasersight;}; void LaserSightThink (edict_t *self);void SP_LaserSight(edict_t *self);
Now open
up g_cmds.c and edit the very bottom of the file and edit it to look like this
:
else if (Q_stricmp (cmd, "laser") == 0) SP_LaserSight (ent); else gi.cprintf (ent, PRINT_HIGH, "Bad command: %s\n", cmd);}
The big Kahuna
Now let's create a new
file called laser.c. It should look like this :
// laser sight patch, by Geza Beladi #include "g_local.h" /*---------------------------------------- SP_LaserSight Create/remove the laser sight entity-----------------------------------------*/ #define lss self->lasersight void SP_LaserSight(edict_t *self) { vec3_t start,forward,right,end; if ( lss ) { G_FreeEdict(lss); lss = NULL; gi.bprintf (PRINT_HIGH, "lasersight off."); return; } gi.bprintf (PRINT_HIGH, "lasersight on."); AngleVectors (self->client->v_angle, forward, right, NULL); VectorSet(end,100 , 0, 0); G_ProjectSource (self->s.origin, end, forward, right, start); lss = G_Spawn (); lss->owner = self; lss->movetype = MOVETYPE_NOCLIP; lss->solid = SOLID_NOT; lss->classname = "lasersight";// lss->s.modelindex = gi.modelindex ("put/your/own/model/here.md2"); lss->s.modelindex = gi.modelindex ("models/objects/gibs/sm_meat/tris.md2"); lss->s.skinnum = 0; lss->s.renderfx |= RF_FULLBRIGHT; lss->think = LaserSightThink; lss->nextthink = level.time + 0.1;} /*--------------------------------------------- LaserSightThink Updates the sights position, angle, and shape is the lasersight entity ---------------------------------------------*/ void LaserSightThink (edict_t *self){ vec3_t start,end,endp,offset; vec3_t forward,right,up; trace_t tr; AngleVectors (self->owner->client->v_angle, forward, right, up); VectorSet(offset,24 , 6, self->owner->viewheight-7); G_ProjectSource (self->owner->s.origin, offset, forward, right, start); VectorMA(start,8192,forward,end); tr = gi.trace (start,NULL,NULL, end,self->owner,CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); if (tr.fraction != 1) { VectorMA(tr.endpos,-4,forward,endp); VectorCopy(endp,tr.endpos); } if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client)){ if ((tr.ent->takedamage) && (tr.ent != self->owner)) { self->s.skinnum = 1; } } else self->s.skinnum = 0; vectoangles(tr.plane.normal,self->s.angles); VectorCopy(tr.endpos,self->s.origin); gi.linkentity (self); self->nextthink = level.time + 0.1;}
Spawning a lasersight entity
SP_LaserSight is the
function that 'spawns' the lasersight if it is off, or 'kills' it if is on
(i.e. a toggle function). This is a really well written function... however,
notice the #define at the top that allows the shortcut 'lss' to really mean
'self->lasersight'. This line
if ( lss ) {
tests if
the lasersight entity exists. If it does (i.e. lss is not NULL) then
G_FreeEdict is called (which destroys the lasersight model). If it doesn't (lss
is NULL) then a model is created. I have used the sm_meat model (a small meat
gib) so that you can use this patch 'out of the box', but it looks kinda silly.
Simply substitute your own model in this function. The only thing really
non-standard here is that the 'nextthink' function is 0.1 seconds into the
future. This means that the 'lasersight' function is called every 0.1
seconds... probably not very 'net friendly' in a 70 player game, but hey.
Moving the
lasersight around
Now lets look at
LaserSightThink. Remember it gets called every 0.1 seconds, to move the laser
sight around as the player moves. Lets look at how a 'trace' is done. These
lines trace a vector forwards 8192 units in front of the player, from around
chest height (our player is 64 units tall in comparison, so 8192 is quite far
!).
AngleVectors (self->owner->client->v_angle, forward, right, up); VectorSet(offset,24 , 6, self->owner->viewheight-7); G_ProjectSource (self->owner->s.origin, offset, forward, right, start); VectorMA(start,8192,forward,end); tr = gi.trace (start,NULL,NULL, end,self->owner,CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER);
Now, the
gi.trace function returns a 'trace_t' structure. That means, it returns an
array of stuff describing what happened with the trace, including :
If
tr.fraction is 1.0, we didn't find anything directly in front of us within a
range of 8192 units. If tr.fraction is less than 1.0, we did, so we back the
endpos back a little so the lasersight appears just in front of the target (not
IN the target).
if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client)){ if ((tr.ent->takedamage) && (tr.ent != self->owner)) {
These
lines check to see if the target is a monster or a player, and change to a
different skin if so. This is only applicable if you use your own lasersight
model, and give it a second skin for when a target is aquired. The second line
checks that the entity can take damage (i.e. is not dead) and is not ourselves.
vectoangles(tr.plane.normal,self->s.angles); VectorCopy(tr.endpos,self->s.origin);
This lines
up the angles of the model to be 'in line' with the target surface, and finally
moves our lasersight (or piece of meat) to the correct position. The last lines
ensure the lasersight will think again, and soon.
Create a
lasersight model
Go into your favourite
model editor and create a lasersight ! Perhaps a very small dot (cube or
similar 3d shape) or a cross. Give it two skins.... skin 0 is the standard
skin, and make skin 1 special in some way. For example, make the second skin
brighter. Or even make a cross where the middle of the cross is dark in color
on the first skin and bright red on the second skin. That way, as the target
moves onto a target, the center of the lasersight cross will light up.
Note for
non-MSVC users
MSVC actually initializes
the pointers in an allocated structure to NULL. (huh ? I hear you say...). In
other words, the self->lasersight thingy for each player is set to NULL when
they connect. This is non ANSI-standard, and other compilers don't do this. If
you are using another compiler, set lasersight to NULL when the client
connects. Find ClientConnect and add self->lasersight = NULL;
somewhere.
And that's it... Thanks
again to Geza Beladi for the code.
|
This site, and all
content and graphics displayed on it, |