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.

Geza Beladi.

This site, and all content and graphics displayed on it,
are ©opyrighted to the Quake DeveLS team. All rights received.
Got a suggestion? Comment? Question? Hate mail? Send it to us!
Oh yeah, this site is best viewed in 16 Bit or higher, with the resolution on 800*600.
Thanks to Planet Quake for there great help and support with hosting.
Best viewed with Netscape 4