QuakeZone
MOD Coding Tutorials
Quake II Coding Tutorials!

Introduction
Code
Notes
Exercises
Quake II Coding Tutorials

08 Vortex Rockets

Introduction Introduction

These fiendish devils are the opposite of homing missiles - instead of homing in on players, the players home in on these! Any players unlucky enough to be in the vicinity of these while in flight or at impact site will rapidly get sucked in, then wiped out by the resulting explosion that follows in a short delay.

The firer will not sucked in until impact, giving him chance to get out of harm's way. Tip: fire one of these into lava and watch your opponets standing nearby get dragged in.

This tutorial is based on a tutorial for vortex grenades on the Quakestyle page - go to the Links page from the menu at the top of this page. The 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.

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.

Code Code


g_weapon.c

Add the following 2 procedures just after procedure fire_bfg:

// PJT - vortex rockets - fireball explosion

void vortex_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 drag people in while exploding
		rocket_think_vortex(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_VORTEX); // 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_BLUE; 	// PJT - make vortex explosion blue.
  	self->s.effects |= EF_COLOR_SHELL;  //
	if (self->s.frame == 10)			// 16 for full positron
		self->think = G_FreeEdict;
}

// PJT


// PJT - vortex rockets - initial explosion

void vortex_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_VORTEX);

	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_BLUE; 	// PJT - make vortex explosion blue.
  	self->s.effects |= EF_COLOR_SHELL;  //
	self->think = vortex_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 - vortex rockets - timer think

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

    ent = NULL;

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

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

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

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

        if ((ent == self->owner) && (self->vortex_timer == 9999)) 	// can drag in firer
            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;



		// bubble trail between vortex and client
		gi.WriteByte (svc_temp_entity);
		gi.WriteByte (TE_BUBBLETRAIL);			// PJT - was TE_BFG_LASER
		gi.WritePosition (self->s.origin);
		gi.WritePosition (ent->s.origin);		// PJT - was (tr.endpos);
		gi.multicast (self->s.origin, MULTICAST_PHS);

		// drag client towards vortex
        VectorCopy(ent->s.origin, start);
        VectorCopy(self->s.origin, end);
        VectorSubtract(end, start, dir);
        VectorNormalize(dir);
        VectorScale(dir,1000, ent->velocity);
        VectorCopy(dir, ent->movedir);

		// PJT - cause slight damage when caught in vortex
		// if ((ent->takedamage) && (ent != self->owner))
		//	T_Damage (ent, self, self->owner, dir, ent->s.origin, self->s.origin, 5, 1, DAMAGE_ENERGY, MOD_VORTEX);
    }

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

}

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

/*
=================
rocket_touch_vortex
=================
*/
void rocket_touch_vortex (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)
	{
		vortex_explode(ent);
		return;
	}

	// vortex explosion if hits decoy
	if (Q_stricmp(other->classname, "decoy") == 0)
	{
		vortex_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 		= 2;
		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; 	// PJT - make vortex  blue.
		ent->nextthink     	= level.time + 0.2;
		ent->think         	= rocket_think_vortex;
		ent->vortex_timer  	= level.time + 5.0;
		ent->s.sound	   	= 0;
    	return;
	}

	G_FreeEdict (ent);

}

// PJT
In procedure rocket_think, add the following lines as indicated:
	self->think = rocket_think;
	// PJT - vortex rocket
	if (self->touch == rocket_touch_vortex)
	{
		self->vortex_timer = 9999;
		rocket_think_vortex(self);
	}
	// PJT - end of insert
Finally, 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 5:		// PJT - vortex rockets
			rocket->touch = rocket_touch_vortex;
			rocket->s.renderfx = RF_SHELL_BLUE; 	// make vortex rockets blue
  			rocket->s.effects |= EF_COLOR_SHELL;
			break;
		// <-- end of insert
		}
	}
	else
		rocket->touch = rocket_touch;
	// 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 5:		// vortex 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>5) i = 0;	// PJT - match highest case statement below 
then...
	switch (i)
	{
	case 0:			// standard rockets

	// <-- start inserting code here
	case 5:			// PJT vortex 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 +4, "Earthquake ");	// <-- insert line after here
	gi.configstring (CS_TR_STRINGS +5, "Vortex     ");	// PJT - vortex rockets

g_local.h

Add the following line as indicated:


#define MOD_TARGET_BLASTER	33 // <-- insert line after here
#define MOD_VORTEX		39 // PJT - vortex rockets
Add the following line to the end of the edict_s structure:
	int			vortex_timer;	// PJT - vortex timer - timer for vortex duration

p_client.c

Add the following line as indicated in the following places to procedure ClientObituary:

	// PJT - vortex rockets
      	case MOD_VORTEX:
          	message="was sucked into the vortex";
          	break;
	// PJT - end of insert
		case MOD_BOMB:
		case MOD_SPLASH:
		case MOD_TRIGGER_HURT:
			message = "was in the wrong place";
			break;
then...
			// PJT - vortex rockets
			case MOD_VORTEX:
				message = "was sucked into his own private vortex";
				break;
			// PJT - end of insert

			default:
				if (IsNeutral(self))
					message = "killed itself";
				else if (IsFemale(self))
					message = "killed herself";
				else
					message = "killed himself";
				break;
and finally...
			// PJT vortex rockets
			case MOD_VORTEX:
				message = "was sucked into";
				message2 = "'s vortex";
				break;
			// PJT - end of insert
			case MOD_SHOTGUN:
				message = "was gunned down by";
				break;


Notes Notes to code


g_weapon.c
  • vortex_explode2 is a 'think' function that handles the exploding fireball when the vortex finally bursts:
    • based on the exploding fireball routine for positron rockets
    • this procedure calls rocket_think_vortex 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 vortex explosions from positron explosions
    • when frame 10 is reached, the entity is destroyed (smaller explosion than positron)
  • vortex_explode is the procedure that initiates the vortex explosion when the rocket hits a player, or the vortex timer expires:
    • based on the explosion routine for positron rockets
    • velocity is set to zero, otherwise the fireball may still be moving!
    • damage is generated in vicinity for initial impact - MOD_VORTEX specifies what death message will get written to a killed player
    • for a rokcet impacting on a player, the positron model is now selected, and various other attributes for the model set
    • vortex_explode2 is the new 'think' procuedre to create the fireball
    • an explosion is also created in the middle of the vortex
  • rocket_think_vortex is the 'think' function for both the vortex and a vortex 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 vortex firer from being dragged in when the 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 vortex, and push them towards the vortex or rocket, connecting the two with a bubble effect
    • the rocket firer will not be dragged in 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_vortex is the impact function for a vortex rocket:
    • don't impact if his firer, or destroy rocket if hit sky
    • immediately explode vortex if hits player or decoy
    • otherwise, convert the rocket into a vortex, by switching to the positron model, rotating the model and giving it a blue shell effect. Frame 2 determines the size of the vortex
    • rocket_think_vortex is still the 'think' routine, but the timer has now been activated at 5 seconds after impact to create the exploding vortex fireball
    • the rocket in flight sound is turned off (s.sound = 0), and the velocity is cleared to stop the vortex moving
  • rocket_think is the general 'think' routine for a rocket in flight:
    • code added to call rocket_think_vortex for a vortex rocket - this causes players to be sucked in by the rocket in flight, as well as the vortex
    • the 9999 value makes sure the rocket won't explode before impact, and stops the firer being dragged in 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 vortex rockets:
    • rocket_touch_vortex is the impact routine for vortex rockets
    • the rocket is given a blue shell

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 vortex 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 vortex 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 'vortex' added, and the offset in the list of strings matches the case number in g_cmds.c above

g_local.h
  • MOD_DECOY is the unique flag used to specify death messages for vortex rockets (see below)

p_client.c
  • ClientObituary has bene modified to display appropriate detah messages for vortex rockets, using the flag above
Exercises Exercises

  • use server variables (cvar) to specify radius and duration of vortex, etc
  • use a better effect than the bubbles (e.g. lightning) for players sucked into the vortex
  • use sounds for the vortex, and players being sucked into it


Page last updated 25th November 2000