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)


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

	// 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)


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,

	vec3_t		forward,

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

	// 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");

	// Setup "little look" to close wall

	// Cast along view angle
	AngleVectors (ent->client->v_angle, forward, NULL, NULL);

	// Setup end point

	// 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");

	// Hit sky ?
	if (tr.surface)
		if (tr.surface->flags & SURF_SKY)

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

	// 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).


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)


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

			// 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);

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