Quake DeveLS - Defense Laser

Author: Yaya (-*-)
Difficulty:
Medium


Just TRY getting through these doors without searing flesh burns.

This dead easy tutorial is going to show you how to create a really handy Quake 2 defensive weapon, as apposed to the current batch of all-out mass destruction guns ! :)

The idea is to use the normal laser's found in Quake 2 and modify them for our own purposes. Thus, we'll be able to place them where we want, slicing out enemies into pieces ! Yeah! To make it "real", we're going to make it cost energy cells to use (and so that we don't end up with thousands of lasers, casting lots of luvly lights)

On with the show ...

Command Handler

First off, lets just get the command handler into the system, to call our laser code. Stick the following in g_cmds.c

 
  else if (Q_stricmp (cmd, "gameversion") == 0)
  {
         gi.cprintf (ent, PRINT_HIGH, "%s : %s\n", GAMEVERSION, __DATE__);
  }
 
  // yaya
         else if (Q_stricmp (cmd, "laser") == 0)
         PlaceLaser (ent);

You can then bind the command "laser" to your desired key (very handy on right mouse)

laser.h

Now to the code. Create a file called "laser.h" in your Quake 2 project folder and put the following into it :

 
  // my functions
  void           PlaceLaser (edict_t *ent);
  void           pre_target_laser_think (edict_t *self);
 
  // controlling parameters
  #define        LASER_TIME                                                             30
  #define        CELLS_FOR_LASER                                                20
  #define        LASER_DAMAGE                                                   100
  #define        LASER_MOUNT_DAMAGE                                             50
  #define        LASER_MOUNT_DAMAGE_RADIUS                              64
 
  // In-built Quake2 routines
  void           target_laser_use (edict_t *self, edict_t *other, edict_t *activator);
  void           target_laser_think (edict_t *self);
  void           target_laser_on (edict_t *self);
  void           target_laser_off (edict_t *self);

These will be the controlling parameters for how the laser functions and also the in-built Quake2 code that the laser's will need to call. (you'll need to include it in g_cmds.c and laser.c below)

laser.c

Now we need "laser.c" - so make another file and start adding the following :

 
#include "g_local.h"
#include "laser.h"
 
void     PlaceLaser (edict_t *ent)
{
  edict_t        *self,
                         *grenade;
 
  vec3_t         forward,
                         wallp;
 
  trace_t        tr;
 
 
  int            laser_colour[] = {
                                                 0xf2f2f0f0,            // red
                                                 0xd0d1d2d3,            // green
                                                 0xf3f3f1f1,            // blue
                                                 0xdcdddedf,            // yellow
                                                  0xe0e1e2e3             // bitty yellow strobe
                                               };
 
 
  // valid ent ?
         if ((!ent->client) || (ent->health<=0))
     return;
 
  // cells for laser ?
  if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] < CELLS_FOR_LASER)
  {
         gi.cprintf(ent, PRINT_HIGH, "Not enough cells for laser.\n");
         return;
  }
 
  // Setup "little look" to close wall
  VectorCopy(ent->s.origin,wallp);         
 
  // Cast along view angle
  AngleVectors (ent->client->v_angle, forward, NULL, NULL);
 
  // Setup end point
  wallp[0]=ent->s.origin[0]+forward[0]*50;
  wallp[1]=ent->s.origin[1]+forward[1]*50;
  wallp[2]=ent->s.origin[2]+forward[2]*50;  
 
  // trace
  tr = gi.trace (ent->s.origin, NULL, NULL, wallp, ent, MASK_SOLID);
 
  // Line complete ? (ie. no collision)
  if (tr.fraction == 1.0)
  {
          gi.cprintf (ent, PRINT_HIGH, "Too far from wall.\n");
         return;
  }
 
  // Hit sky ?
  if (tr.surface)
         if (tr.surface->flags & SURF_SKY)
                 return;
 
  // Ok, lets stick one on then ...
  gi.cprintf (ent, PRINT_HIGH, "Laser attached.\n");
 
  ent->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] -= CELLS_FOR_LASER;

Explanation : This chunk of code first checks to see if the player has enough cells for the laser. Then we need to find out the players position relative to the closest wall. We do this using the players point of origin (ent -> s.origin) and projecting a small distance along the view angle. Using AngleVectors translates the players view angle into a forward looking vector that we can scale (all the * 50). Next we use the gi.trace command to find out the collision on this short path. Using the MASK_SOLID flag, we specify only solid wall collisions should be returned. If the complete line is traced (tr.fraction == 1) then we are too far from the wall.

Creating the Laser

Now to create the laser : (stick it after the above)

 
  // -----------
  // Setup laser
  // -----------
  self = G_Spawn();
 
  self -> movetype               = MOVETYPE_NONE;
  self -> solid                  = SOLID_NOT;
  self -> s.renderfx             = RF_BEAM|RF_TRANSLUCENT;
  self -> s.modelindex   = 1;                   // must be non-zero
  self -> s.sound                = gi.soundindex ("world/laser.wav");
  self -> classname              = "laser_yaya";
  self -> s.frame                = 2;    // beam diameter
         self -> owner                  = self;
  self -> s.skinnum              = laser_colour[((int) (random() * 1000)) % 5];
         self -> dmg                           = LASER_DAMAGE;
  self -> think                  = pre_target_laser_think;
  self -> delay                  = level.time + LASER_TIME;
 
  // Set orgin of laser to point of contact with wall
  VectorCopy(tr.endpos,self->s.origin);
 
  // convert normal at point of contact to laser angles
  vectoangles(tr.plane.normal,self -> s.angles);
 
  // setup laser movedir (projection of laser)
  G_SetMovedir (self->s.angles, self->movedir);
 
  VectorSet (self->mins, -8, -8, -8);
  VectorSet (self->maxs, 8, 8, 8);
 
  // link to world
  gi.linkentity (self);
 
  // start off ...
  target_laser_off (self);
 
  // ... but make automatically come on
  self -> nextthink = level.time + 2;

Explanation : This sets up a Quake 2 laser entity at the point in space where the projected view line intersected a solid. Most of the parameters are self-explantory, but note the ent class name change to "laser_yaya" so we can identify it later in the normal laser think routines. The colour is randomly chosen and a laser hum attached as the sound. The next vector bit simply sets the lasers (x/y/z) at the point of intersection and the laser direction as the normal of the point of intersection on the plane. All this info is returned via the gi.trace command (Thanks John ! :) To give the player a chance, we delay calling the normal laser think routine straight away, but delay it for two seocnds.

Wall Mounting

To complete the picture, we can add a small grenade at the laser origin, for purely asthetic purposes (a wall mounting) :

 
  grenade = G_Spawn();
 
  VectorClear (grenade->mins);
  VectorClear (grenade->maxs);
  VectorCopy (tr.endpos, grenade->s.origin);
  vectoangles(tr.plane.normal,grenade -> s.angles);
 
  grenade -> movetype            = MOVETYPE_NONE;
  grenade -> clipmask            = MASK_SHOT;
  grenade -> solid               = SOLID_NOT;
  grenade -> s.modelindex        = gi.modelindex ("models/objects/grenade2/tris.md2");
  grenade -> owner               = self;
  grenade -> nextthink   = level.time + LASER_TIME;
  grenade -> think               = G_FreeEdict;
 
  gi.linkentity (grenade);
}

Explanation : er, well it's a non-doing-anything grenade ! :) Just orientate it to the normal again and set the first think to simply remove the ent. (in time with the laser blowing up - see later).

pre_target_laser_think

That's it to create our laser. To create the first laser think function (that is called after two seconds are up) add the following chunk to laser.c :

 
void     pre_target_laser_think (edict_t *self)
{
  target_laser_on (self);
 
  self->think = target_laser_think;
}

Explanation : Turn our "off" laser "on", and start "thinking" like a normal laser (ie. damage anything along the direction angle)

targer_laser_think

Finally, to remove our lasers after the lifetime allowed (specified in laser.h) add the following to "target_laser_think" in g_target.c, just after the local variable delcarations :

 
  if (strcmp(self -> classname,"laser_yaya") == 0)
         if (level.time > self -> delay)
         {
                 // bit of damage
                 T_RadiusDamage (self, self, LASER_MOUNT_DAMAGE_RADIUS, NULL, LASER_MOUNT_DAMAGE);
 
                 // BANG !
                 gi.WriteByte (svc_temp_entity);
                 gi.WriteByte (TE_EXPLOSION1);
                 gi.WritePosition(self -> s.origin);
                 gi.multicast (self->s.origin, MULTICAST_PVS);
 
                 // bye bye laser
                 G_FreeEdict (self);
 
                 return;
         }

This checks to see if the laser lifetime is over, and if it is, to blow it up (with suitable explosion) and throw out a little radius damage (if someone happens to be standy close to it)

Lastly, add this line to the top of g_target.c, after #include "g_local.h" :

 
#include "laser.h"

Well, that's it ! Simple but very handy. My favourtie trick is to place a laser on one side of a door and to let it close (being on the other) When someone comes, simply side-step closer to the door, opening it and allowing the laser beam out. WHAM ! :)

Stuff for you (the reader, yes YOU!) to try !

There are a few improvements still to be made :

1) Allow the grenade wall mount to be shot, blowing up the laser.

2) Stop it attaching to moving solids (ie. platforms). If anyone can tell me how to find out this property, let me know (or maybe I could lock them to the entity so it moves with it ?)

3) Credit the death properly. At the moment, any death incurred via laser is classed as a "self - death" (it's still *extremely* satisfying to see "jimmy died." come quitely over the communicator)

Enjoy ! Is should make your games that bit more "cautious" ...

Tutorial by Yaya (-*-) .

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 their great help and support with hosting.
Best viewed with Netscape 4

 

--104BC136-WebSite-Rules-Byte-Range-Data-104BC136--