
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, |