QuakeZone
MOD Coding Tutorials
Quake II Coding Tutorials!

Introduction
Code
Notes
Exercises
Quake II Coding Tutorials

09 Lightning Rockets

Introduction Introduction

These shocking beauts will fire thunderbolts at all opponents in the vicinity while in flight. On impact with a wall, an electric vortex is created that continues to let loose thunderbolts on anyone foolish enough to venture clode. Eventually the vortex explodes in a huge fireball.

This tutorial is built on the lightning weapon tutorial on the Quakestyle site (see Links page) and credit is hereby given to the author. This Lightning Rockets tutorial requires that the Quakestyle tutorial has already been coded.

This tutorial assumes you have already run through the Nuclear Rockets tutorial for adding new rocket types and the Status Bar tutorial for updating the player's HUD.

If you have already coded the positron rockets tutorial you will notice a striking resemblence with this tutorial.

The vortex/fireball model used in this tutorial is the same as that used for Nuke rockets and is supplied from Lox - see the Nuke Rocket tutorial for more details. The other models and sounds required for the thunderbolt effect will have been provided in the Quakestyle Lightning Gun tutorial.


Code Code


g_weapon.c

Add the following 2 procedures just after procedure fire_bfg:

// PJT - lightning rockets - fireball explosion

void lightning_explode2 (edict_t *self)
{
	edict_t	*ent;
	float	points;
	vec3_t	v;
	float	dist;

	// comment out damage - done in touch function
	// if (self->s.frame == 0) - do for all frames
	{
		// PJT - still electricute people
		rocket_think_lightning(self);

		// the positron effect
		ent = NULL;
		while ((ent = findradius(ent, self->s.origin, self->dmg_radius)) != NULL)
		{
			if (!ent->takedamage)
				continue;
			// let owner take damage
			//if (ent == self->owner)
			//	continue;
			if (!CanDamage (ent, self))
				continue;
			if (!CanDamage (ent, self->owner))
				continue;

			VectorAdd (ent->mins, ent->maxs, v);
			VectorMA (ent->s.origin, 0.5, v, v);
			VectorSubtract (self->s.origin, v, v);
			dist = VectorLength(v);
			points = self->radius_dmg * (1.0 - sqrt(dist/self->dmg_radius));
			if (ent == self->owner)
				points = points * 0.5;

			gi.WriteByte (svc_temp_entity);
			gi.WriteByte (TE_ROCKET_EXPLOSION);
			gi.WritePosition (ent->s.origin);
			gi.multicast (ent->s.origin, MULTICAST_PHS);
			T_Damage (ent, self, self->owner, self->velocity, ent->s.origin, vec3_origin, (int)points, 0, 0, MOD_TBOLT); // last 0 was DAMAGE_ENERGY
		}
	}
	//*/

	self->nextthink = level.time + FRAMETIME;
	self->s.frame++;
	self->dmg_radius = self->dmg_radius * 1.25;
	self->s.renderfx = RF_SHELL_GREEN + RF_SHELL_BLUE + RF_SHELL_RED; 	// PJT - make lightning explosion white.
  	self->s.effects |= EF_COLOR_SHELL;  //
	if (self->s.frame == 10)			// 16 for full positron
		self->think = G_FreeEdict;
}

// PJT


// PJT - lightning rockets - initial explosion

void lightning_explode (edict_t *self)
{
	// if (other == self->owner)   : allow impact on rocket firer!
	//	return;

	// if (surf && (surf->flags & SURF_SKY))
	// {
	//	G_FreeEdict (self);
	//	return;
	// }

	if (self->owner->client)
		PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);

	// core explosion - prevents firing it into the wall/floor
	// if (other->takedamage)
	//	T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, 200, 0, 0, MOD_BFG_BLAST);
	T_RadiusDamage(self, self->owner, 200, self, 100, MOD_TBOLT);

	gi.sound (self, CHAN_VOICE, gi.soundindex ("weapons/bfg__x1b.wav"), 1, ATTN_NORM, 0);
	self->solid = SOLID_BBOX;  // SOLID_NOT;
	self->touch = NULL;
	VectorMA (self->s.origin, -1 * FRAMETIME, self->velocity, self->s.origin);
	VectorClear (self->velocity);
	self->s.modelindex = gi.modelindex ("models/positron/tris.md2");
	self->s.frame = 0;
	self->s.sound = 0;
	self->s.effects &= ~EF_ANIM_ALLFAST;
	self->s.renderfx = RF_SHELL_GREEN + RF_SHELL_BLUE + RF_SHELL_RED; 	// PJT - make lightning explosion white.
  	self->s.effects |= EF_COLOR_SHELL;  //
	self->think = lightning_explode2;
	self->nextthink = level.time + FRAMETIME;
	self->enemy = NULL;		// PJT - was other;
	self->radius_dmg = 100;
	self->dmg_radius = 10;
	// self->touch = positron_touch;

	gi.WriteByte (svc_temp_entity);
	gi.WriteByte (TE_ROCKET_EXPLOSION);
	gi.WritePosition (self->s.origin);
	gi.multicast (self->s.origin, MULTICAST_PVS);
}

// PJT
		
Add the following procedure before procedure rocket_touch:
// PJT - lightning rockets - timer think

void rocket_think_lightning (edict_t *self)
{
    edict_t 	*ent, *ignore;
    vec3_t 		dir, start, end, point;
	trace_t		tr;
	int			damage;

    ent = NULL;

	// timer expired - blow up
    if (level.time > self->vortex_timer)
    {
        self->think 		= lightning_explode;
        self->nextthink 	= level.time + 0.2;
	self->vortex_timer 	= 9999;		// PJT don't re-trigger explosion again
        return;
    }

	// look for players close by to electrocute
    while ((ent = findradius(ent, self->s.origin, 512)) != NULL)
    {

	   	if (ent == self)				// don't shock self
            continue;

		if(ent->client && (!G_ClientNotDead(ent)))		// don't shock dead clients
			continue;

        if ((!ent->client) && (strcmp(ent->classname, "decoy") != 0)
	   					   && (strcmp(ent->classname, "superdecoy") != 0))		// only shock clients and decoys
            continue;

        if ((ent == self->owner) && (self->vortex_timer == 9999)) 	// can shock firer
            continue;

	if (!visible(self, ent))		// must have clear path to shock
		continue;

        if (!ent->takedamage)			// not entity that cannot take damage
            continue;

        if (!(ent->svflags & SVF_MONSTER) && (!ent->client) && (strcmp(ent->classname, "misc_explobox") != 0))
            continue;

		// shock client
		damage = 10;
		fire_lightning (self, self->s.origin, NULL, ent->s.origin, 1, damage);

    }

	if (!(self->vortex_timer == 9999))
	{
		self->nextthink = level.time + 0.2;
		self->think		= rocket_think_lightning;
	}

}

// PJT
		
Add the following code after procedure rocket_touch:
// PJT - lightning rockets - touch function

/*
=================
rocket_touch_lightning
=================
*/
void rocket_touch_lightning (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
{

	// can damage owner!!
	if (other == ent->owner)
		return;

	// remove if hits sky
	if (surf && (surf->flags & SURF_SKY))
	{
		G_FreeEdict (ent);
		return;
	}

	// vortex explosion if hits player
	if (other->client)
	{
		lightning_explode(ent);
		return;
	}

	// vortex explosion if hits decoy
	if ((Q_stricmp(other->classname, "decoy") 	   == 0) ||
		(Q_stricmp(other->classname, "superdecoy") == 0))
	{
		lightning_explode(ent);
		return;
	}

 	// Quake Rockets only if activated by real Player..
  	if (G_EntExists(ent->owner))
	{

		VectorClear (ent->velocity);
		ent->s.modelindex 	= gi.modelindex ("models/positron/tris.md2");
		ent->s.frame 		= 1;
		ent->solid 		= SOLID_BBOX;  // SOLID_NOT;
		ent->s.effects 		&= ~EF_ANIM_ALLFAST;
		ent->s.effects 		|= EF_COLOR_SHELL;
		ent->s.effects 		|= EF_ROTATE;
		ent->s.renderfx 	= RF_SHELL_BLUE + RF_SHELL_RED + RF_SHELL_GREEN; 	// PJT - make vortex  white for lightning.
		ent->nextthink     	= level.time + 0.2;
		ent->think         	= rocket_think_lightning;
		ent->vortex_timer  	= level.time + 10.0;	// PJT make last a little longer
		ent->s.sound	   	= 0;
    	return;
	}

	G_FreeEdict (ent);

}

// PJT
		
In procedure rocket_think, add the following lines as indicated:

	// PJT <-- start insert here
	// 4a. lightning rockets
	if (self->touch == rocket_touch_lightning)
	{
		self->vortex_timer = 9999;
		rocket_think_lightning(self);
	}
	// PJT <-- end of insert

	// 5. next think
	self->nextthink = level.time + .1;
	self->think 	= rocket_think;
		
Then, in procedure fire_rocket, add the following code as indicated within the code added for different rocket types (see Nuclear Rockets tutorial):

	// PJT - determine rocket type
	if (self->client)
	{
		rocket->touch = rocket_touch;
    		switch(self->client->pers.nuke_state)
		{
		case 0:		// standard
	
		// <-- start inserting code here
		case 6:		// PJT - lightning rockets
			rocket->touch = rocket_touch_lightning;
			rocket->s.renderfx = RF_SHELL_BLUE + RF_SHELL_GREEN; 	// make vortex rockets blue/green
  			rocket->s.effects |= EF_COLOR_SHELL;
			break;
		// <-- end of insert
		}
	}
	else
		rocket->touch = rocket_touch;
	// PJT
		
Finally, in procedure fire_lightning, which was created by the Quakestyle Lightning Gun tutorial, make the following changes as indicated:

Firstly, alter the defintion of the procedure itself as follows:

void fire_lightning (edict_t *self, vec3_t start, vec3_t aimdir, vec3_t aimend,
					 int dirtype, int damage)	// PJT - aimend and dirtype added
/* PJT
	if dirtype = 0, use aimdir (fire direction) to calculate end point
	if dirtype = 1, use aimend (end point) as end point of lightning
*/
		
Then, after the variable declarations, amend the next 3 lines as follows:
	// PJT - end point calculated from direction of fire
	if (dirtype == 0)
	{
		vectoangles2 (aimdir, dir);
		AngleVectors (dir, forward, right, up);
		VectorMA (start, 8192, forward, end);
	}
	 else	// PJT - end point passed as parameter
		VectorCopy (aimend, end);
	// PJT
		
Finally, locate the code that creates the damage and amend as follows:

		if ((tr.ent != self) && (tr.ent->takedamage))
		{
			// PJT - when source is not a client (eg a lightning rocket!)
			if (self->client)
			 	T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, damage, 0, MOD_TBOLT);
			else
			{
				tmpobj = G_Spawn();
				VectorCopy (tr.endpos, tmpobj->s.origin);
				tmpobj->movetype		= MOVETYPE_NONE;
				tmpobj->solid			= SOLID_NOT;
				tmpobj->classname		= "tmpobject";
				tmpobj->s.modelindex	= 0;
				tmpobj->nextthink		= level.time + 0.01;
				tmpobj->think			= G_FreeEdict;
				gi.linkentity (tmpobj);
				T_RadiusDamage(tmpobj, self, damage*2, NULL, damage*4, MOD_TBOLT_WATER);
			// PJT
			}
		}
		

p_weapon.c

In procedure Rocket_Count, which was added by the Nuclear Rockets tutorial, add the following lines as indicated:

	switch (ent->client->pers.nuke_state)
	{
		case 0:		// standard

		// <-- start inserting code here
		case 6:		// lightning rockets
			count = 15;		// or any value you choose
			break;
		// <-- end of insert
	}	
		

g_cmds.c

In procedure Cmd_Nukes_f, which was added by the Nuclear Rockets tutorial, add the following lines as indicated:

	i++;
	if (i>6) i = 0;	// PJT - match highest case statement below 
		
then...
	switch (i)
	{
	case 0:			// standard rockets

	// <-- start inserting code here
	case 6:			// PJT lightning rockets
		break;	
	// <-- end of insert
	}
		

p_hud.c

In procedure init_hud_rocket_strings, which was added by the Statusbar tutorial, add the following line as indicated:

	gi.configstring (CS_TR_STRINGS +5, "Vortex     ");	// <-- insert line after here
	gi.configstring (CS_TR_STRINGS +6, "Lightning  ");	// PJT - lightning rockets
		

g_local.h

Add the following line to the end of the edict_s structure:
	int			vortex_timer;	// PJT - vortex timer - timer for vortex duration
		


Notes Notes to code


g_weapon.c
  • lightning_explode2 is a 'think' function that handles the exploding fireball when the lightning vortex finally bursts:
    • based on the exploding fireball routine for positron and vortex rockets
    • this procedure calls rocket_think_lightning so that players are still dragged in during the explosion and can't escape in the blast
    • the next loop looks for all players in the vicinity of the exploding fireball and causes damage against them depening on their distance, and also generated a small explosion
    • the next frame of the model is displayed and the damage radius is increased to simulate the expanding fireball
    • the model is kept blue to differentiate lightning explosions from vortex and positron explosions
    • when frame 10 is reached, the entity is destroyed (smaller explosion than positron)
  • lightning_explode is the procedure that initiates the lightning vortex explosion when the rocket hits a player, or the lightning vortex timer expires:
    • based on the explosion routine for positron and vortex rockets
    • velocity is set to zero, otherwise the fireball may still be moving!
    • damage is generated in vicinity for initial impact - MOD_TBOLT specifies what death message will get written to a killed player
    • for a rocket impacting on a player, the positron model is now selected, and various other attributes for the model set
    • lightning_explode2 is the new 'think' procuedre to create the fireball
    • an explosion is also created in the middle of the lightning vortex
  • rocket_think_lightning is the 'think' function for both the lightning vortex and a lightning rocket in flight:
    • firstly, if the times has expired, start the explosion routine, and set the timer to 9999 to stop it triggering again
    • the 9999 value also acts as a flag to stop the lightning rocket firer from being electrocuted when the lightning vortex explodes (see below), giving the firer a chance to escape death!
    • then look for every player (and decoys, if they exist in your mod) within range of the rocket or lightning vortex, and fire a lightning bolt at them
    • the rocket firer will not be shocked if timer is 9999 (see above)
    • if the vortex timer has not yet been reached, or not set to 9999, increment the timer
  • rocket_touch_lightning is the impact function for a lightningrocket:
    • don't impact if his firer, or destroy rocket if hit sky
    • immediately explode lightning vortex if hits player or decoy
    • otherwise, convert the rocket into a lightning vortex, by switching to the positron model, rotating the model and giving it a blue shell effect. Frame 2 determines the size of the lightning vortex
    • rocket_think_lightning is still the 'think' routine, but the timer has now been activated at 5 seconds after impact to create the exploding lightning vortex fireball
    • the rocket in flight sound is turned off (s.sound = 0), and the velocity is cleared to stop the lightning vortex moving
  • rocket_think is the general 'think' routine for a rocket in flight:
    • code added to call rocket_think_lightning for a lightning rocket - this causes players in the vicinity to be thunderbolted by the rocket
    • the 9999 value makes sure the rocket won't explode before impact, and stops the firer being thunderbolted by the rocket
    • if you have other rocket 'think' routines (e.g. homing_think for homing missiles) then this code could be added in those too
  • fire_rocket is amended to cater for lightning rockets:
    • rocket_touch_lightning is the impact routine for lightning rockets
    • the lightning rocket is given a purple shell
  • fire_lightning is the procedure added by the Quakestyle Lightning Gun tutorial to fire lightning bolts:
    • the parameters to the procedure have been changed so that direction can be specified in one of 2 ways: from direction of facing (as in original tutorial) or to orgin of target object (as in lightning rockets)
    • the first lines of the code are changes so that the end point of the thunderbolt is calculated if encessary, otherwise the value is just taken from the input parameters
    • if the originator of the thunderbolt is not a client (i.e. a lightning rocket) a temporary object is created and T_RadiusDamage is called to inflict damage on the target. This is a fudge because T_Damage (see g_combat.c) expects the originator to be a client and crashes if it is not so.

p_weapon.c
  • Rocket_Count is the procedure created in the Nuclear Rockets tutorial to cater for different rocket types:
    • the number of rockets for lightning rockets is specified (amend if you like)

g_cmds.c
  • Cmd_Nukes_f is the procedure created in the Nuclear Rockets tutorial to cater for different rocket types:
    • initially no special actions for lightning rockets when they are selected, but you could put in a message or sound to the player (and warnings to other players!)

p_hud.c
  • init_hud_rocket_strings is the procedure created in the Statusbar tutorial to cater for different rocket types:
    • make sure no gaps in sequence when 'lightning' added, and the offset in the list of strings matches the case number in g_cmds.c above

g_local.h
  • vortex_timer is used to time how long before the lightning vortex explodes
Exercises Exercises

  • use server variables (cvar) to specify radius and duration of lightning vortex, etc
  • amend T_Damage (or one of the procedures it calls) in g_combat.c so that it does not crash when the originator is not a client, as in the case of lightning rockets. Then the code in fire_lightning in g_weapon.c can be corrected


Page last updated 25th November 2000