Quake Style - Quake 2 Tutorials
Tech Power Up Mayhem - Part 3
Defense Laser Rune

Defense Laser Rune:

Main credits for this one go to Yaya, whose original defense laser tutorial can be found here at Qdevels. We only need very few done to get it rolling, some of that are even points Yaya back then set on his to-do list. When finished, you'll have a hand-grenade you can stick to the wall, spawns a laser beam to the next (any distance) wall which kills everybody and lasts for some time (naturally, you can modify lifespan, damage and other parameters in the code if you want different settings than the ones I used).

Requirements:

The stuff we did in the previous parts of "Tech Power Up Mayhem". This series of tutorials builds up step-by-step and you must have done any previous pages before adding this one!

Add in the blue code and take out the pink code. Regular yellow code is Zoid's original stuff.

Let's make the big ones first: Here's Yaya's tut again, with all the needed modifications; put this in a new file called g_laser1.c, and make sure you include it in the makefile:

#include "g_local.h"
#include "g_laser1.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                             
	};
    
	if ((!ent->client) || (ent->health<=0))    
		return;    
    
	// modify this if-statement and the message
	// if you want a different number of max defense
	// laser which are possible at a certain time
	if (ent->client->rune_count > 5)
	{
		gi.cprintf(ent, PRINT_HIGH, "No more than 5 defense lasers at one time\n");        
		return;    
	}
	ent->client->rune_count += 1;

	// 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 away 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, "Placed laser.\n");

	// -------------
	// Setup laser
	// -------------
	self = G_Spawn();
	self->movetype	= MOVETYPE_NONE;    
	self->activator	= ent;
	self->solid		= SOLID_NOT;
	self->s.renderfx	= RF_BEAM|RF_TRANSLUCENT;
	self->s.modelindex	= 1;
	self->s.sound	= gi.soundindex ("world/laser.wav");
	self->classname	= "laser1";
	self->s.frame	= 2;
	self->owner	= self;
	self->s.skinnum	= laser_colour[((int) (random() * 1000))  5];
	self->dmg		= LASER_DAMAGE-10;
	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;

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

void pre_target_laser_think (edict_t *self)
{
	target_laser_on (self);

	self->think = laserbarriere_think;
}

void laserbarriere_think (edict_t *self)
{
	edict_t	*ignore;
	edict_t	*ent;
	vec3_t	start;
	vec3_t	end;
	trace_t	tr;
	vec3_t	point;
	vec3_t	last_movedir;
	int	count;

	// Defense Laser Rune
	if (strcmp(self->classname,"laser1") == 0)
		if (level.time > self->delay)         
		{             
			// bit of damage
			T_RadiusDamage (self, self->activator, LASER_MOUNT_DAMAGE_RADIUS, NULL, LASER_MOUNT_DAMAGE, MOD_LASER1);

			// 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
			ent = self->activator;
			ent->client->rune_count=ent->client->rune_count-1;
			G_FreeEdict (self);
			return;
		}

	if (self->spawnflags & 0x80000000)
		count = 8;
	else
		count = 4;

	if (self->enemy)
	{
		VectorCopy (self->movedir, last_movedir);
		VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point);
		VectorSubtract (point, self->s.origin, self->movedir);
		VectorNormalize (self->movedir);
		if (!VectorCompare(self->movedir, last_movedir))
			self->spawnflags |= 0x80000000;
	}

	ignore = self;

	VectorCopy (self->s.origin, start);
	VectorMA (start, 2048, self->movedir, end);
	while(1)
	{
		tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER);

		if (!tr.ent)
			break;
        
		// hurt it if we can
		if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER))
			T_Damage (tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_LASER1);

		// if we hit something that's not a monster or player or is immune to lasers, we're done
		if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
		{
			if (self->spawnflags & 0x80000000)
			{
				self->spawnflags &= ~0x80000000;
				gi.WriteByte (svc_temp_entity);
				gi.WriteByte (TE_LASER_SPARKS);
				gi.WriteByte (count);
				gi.WritePosition (tr.endpos);
				gi.WriteDir (tr.plane.normal);
				gi.WriteByte (self->s.skinnum);
				gi.multicast (tr.endpos, MULTICAST_PVS);
			}
			break;
		}

		ignore = tr.ent;
		VectorCopy (tr.endpos, start);
	}

	VectorCopy (tr.endpos, self->s.old_origin);

	self->nextthink = level.time + FRAMETIME;
}


There are a handful of comments; the most interesting may be that through the if-statement re rune_count, you can control how many defense lasers are possible at a give time; note that after the laser "dies" (see seetings in g_laser.h), rune_count is set back by one and you can place another one. The second new file we need is g_laser1.h - insert the following:

/*
LASER DEFENSE RUNE
*/

// my functions    
void PlaceLaser (edict_t *ent);
void pre_target_laser_think (edict_t *self);    
void deathpak_think(edict_t *deathpak);

// controlling parameters
#define	LASER_TIME		30
#define	LASER_DAMAGE		80
#define	LASER_MOUNT_DAMAGE	40
#define	LASER_MOUNT_DAMAGE_RADIUS	64

// In-built Quake2 routines

void	target_laser_use (edict_t *self, edict_t *other, edict_t *activator);
void	laserbarriere_think (edict_t *self);
void	target_laser_on (edict_t *self);     
void	target_laser_off (edict_t *self);

// Laser Beam Color Codes

#define Laser_Red		0xf2f2f0f0 // bright red

#define Laser_Green		0xd0d1d2d3 // bright green

#define Laser_Blue		0xf3f3f1f1 // bright blue

#define Laser_Yellow		0xdcdddedf // bright yellow

#define Laser_YellowS		0xe0e1e2e3 // yellow strobe

#define Laser_DkPurple	0x80818283 // dark purple

#define Laser_LtBlue		0x70717273 // light blue

#define Laser_Green2		0x90919293 // different green

#define Laser_Purple		0xb0b1b2b3 // purple

#define Laser_Red2		0x40414243 // different red

#define Laser_Orange		0xe2e5e3e6 // orange

#define Laser_Mix		0xd0f1d3f3 // mixture

#define Laser_RedBlue	0xf2f3f0f1 // inner = red, outer = blue

#define Laser_BlueRed	0xf3f2f1f0 // inner = blue, outer = red

#define Laser_GreenY		0xdad0dcd2 // inner = green, outer = yellow

#define Laser_YellowG		0xd0dad2dc // inner = yellow, outer = green


The parameters should be self-explanatory; toy around with 'em until you're satisfied. As we want a specific MOD (means of death), we have to add one to g_local.h:
#define MOD_TARGET_LASER	30
#define MOD_TRIGGER_HURT	31
#define MOD_HIT		32
#define MOD_TARGET_BLASTER	33
#define MOD_GRAPPLE	34
#define MOD_LASER1		35

Then we gotta take care of the messages, which is done in p_client.c - look for the ClientObituary() function. There we have to add (as with all deaths) three messages. The first one will look a little differently than you're used to to make sure everything is credited properly. In the "if (deathmatch->value || coop->value)"-statement add the following:
		case MOD_TRIGGER_HURT:
			message = "was in the wrong place";
			break;
		case MOD_LASER1:
			//have to prevent this from checking for "MOD_LASER1"
			//since that assumes that it was a self inflicted death
			if(strcmp(inflictor->classname, "laser1") && (attacker != inflictor->activator))
				message = "got a defense laser up his butt";
			break;


Second, in the "if (attacker == self)"-statement, add:
			case MOD_BFG_BLAST:
				message = "should have used a smaller gun";
				break;
			case MOD_LASER1:
				message = "was killed by the own defense laser";
				break;
			default:
				if (IsFemale(self))
					message = "killed herself";
				else
					message = "killed himself";
				break;

Third, in the "if (attacker && attacker->client)"-statement, add this:
//ZOID
			case MOD_GRAPPLE:
				message = "was caught by";
				message2 = "'s grapple";
				break;
//ZOID
			case MOD_LASER1:
				message="got";
				message2="'s defense laser up his butt";
				break;
			}
			if (message)
			{
				gi.bprintf (PRINT_MEDIUM,"s s ss\n", self->client->pers.netname, message, attacker->client->pers.netname, message2);

Did we forget the item itself? Yep, so we should open g_items.c and add it at the very end of the itemlist[]:

/* Defense Laser */
	{
		"item_tech5",
		CTFPickup_Tech,
		NULL,
		CTFDrop_Tech, //Should this be null if we don't want players to drop it manually?
		NULL,
		"items/pkup.wav",
		"models/ctf/regeneration/tris.md2", EF_ROTATE,
		NULL,
/* icon */		"tech4",
/* pickup */	"Defense Laser [U]",
/* width */		2,
		0,
		NULL,
		IT_TECH,
		// 0, // NOTE: activate/deactivate this line to set up for different source bases (CTF or 3.20)
		NULL,
		0,
/* precache */	"ctf/tech4.wav"
	},

	// end of list marker
	{NULL}
};

Important note: Depending on which source base you use, you may need to include the line above ("NOTE: activate/deactivate...") to make your compilation work without warnings. If you use the CTF source as used in our tut, you can leave it like above; if you added the CTF functionality to the 3.20 source yourself, you need to include this line in your source. (This is the Viewable Weapons ID line!)

Note: In case you change the pickup name, you also must change every other occurence of it (like in the Rune_Use() function).
Feel free to modify sounds and item models (see also my comment at the bottom of Part Two).

Last step: Open g_ctf.c and replace this:

static char *tnames[] = {
	"item_tech1", "item_tech2", "item_tech3", "item_tech4",
	NULL
};


with this:

static char *tnames[] = {
	"item_tech1", "item_tech2", "item_tech3", "item_tech4", "item_tech5",
	NULL
};


And before you compile, you need to extend the Rune_Use() function:
void Rune_Use(edict_t *ent)
{
	int	index;
	gitem_t	*it;

	char	*rune_msg;

	// say "no rune or automatically activated rune" if there is no
	// special usage (=pressing a button) needed or if player
	// doesn't own a rune
	// otherwise just call the using function for that specific rune
	rune_msg="No rune or automatically activated rune";

	it = FindItem ("Defense Laser [U]"); 
	index = ITEM_INDEX (it);
	if (ent->client->pers.inventory[index])
	{
		PlaceLaser (ent);
		return;
	}

	gi.centerprintf(ent, "s\n",rune_msg);
}

Woops, done! Compile, try! Be sure to bind a key to "drop tech" (to throw away a rune if you want pick up a new one) and to "userune" (to use a rune like the one we just added).

Nick back to the tutorials page and have a look at the next tutorial in the sequence.

-- Credits:
   Tutorial by peter
   Return to QS Tutorials

-- Important:
   If you do use something from QuakeStyle in your mod, please give us credit.
   Our code is copyrighted, but we give permission to everyone to use it in any way they see fit, as long as we are recognized.