QuakeZone
MOD Coding Tutorials
Quake II Coding Tutorials!

Introduction
Code
Notes
Exercises
Quake II Coding Tutorials

Nuclear Rockets

Introduction Introduction

This tutorial explains how to add nuclear rockets to Quake II. The player can toggle a key to activate the nuclear missiles. When a nuclear missiles hits its target, it inflicts damage on the target, damage on any objects within a certain radius, a blsat waves shoves away any players close to the impact, and BFG-style blasts are sent out to simulate blast waves in all directions.

This tutorial also explains how different rocket types may also be added, switched to by toggling one key.

Code Code


g_local.h

Add the following line to the end of the client_persistant_t structure:

	qboolean    nuke_state;			// PJT - nuke rocket state

p_weapon.c

Add the following function just before Weapon_RocketLauncher_Fire:


// PJT - count number of rockets required

qboolean Rocket_Count (edict_t *ent)
{
	int		count;

	// default to OK if not client firing (no inventory)
	if (!ent->client) return true;

	// rockets for particular type
	count = 1;
	switch (ent->client->pers.nuke_state)
	{
		case 0:		// standard
			count = 1;
			break;
		case 1:		// nukes
			count = 20;
			break;
		// add further case statements for different rocket types
	}

	// check amount
	if (ent->client->pers.inventory[ent->client->ammo_index] < count)
	{
		gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
		gi.centerprintf (ent, "Not enough rockets [%i required]\n", count);
		return false;
	}

	// deduct rockets fired
	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
		ent->client->pers.inventory[ent->client->ammo_index] -= count;
	// always have minimum 1 rocket (comment out this code if not required
	if (ent->client->pers.inventory[ent->client->ammo_index] < 1)
		ent->client->pers.inventory[ent->client->ammo_index] = 1;
	return true;

}


Then, at the start of Weapon_RocketLauncher_Fire just after the variable definitions add the following:
	// PJT - don't continue if not enough rockets
	if (Rocket_Count(ent) == false)
	{
	   ent->client->ps.gunframe++;
	   return;
	}
	// PJT

g_weapon.c

Add the following functions at the top of the file, just after the includes:

// PJT - nuclear missiles

//======================================================
// True if Ent is valid, has client, and edict_t inuse.
//======================================================
qboolean G_EntExists(edict_t *ent) {
  return ((ent) && (ent->client) && (ent->inuse));
}

//======================================================
// True if ent is not DEAD or DEAD or DEAD (and BURIED!)
//======================================================
qboolean G_ClientNotDead(edict_t *ent) {
qboolean b1=ent->client->ps.pmove.pm_type!=PM_DEAD;
qboolean b2=ent->deadflag != DEAD_DEAD;
qboolean b3=ent->health > 0;
  return (b3 || b2 || b1);
}

//======================================================
// True if ent is not DEAD and not just did a Respawn.
//======================================================
qboolean G_ClientInGame(edict_t *ent) {
  if (!G_EntExists(ent)) return false;
  if (!G_ClientNotDead(ent)) return false;
  return (ent->client->respawn_time + 5.0 < level.time);
}

//======================================================
// True if start and end are within radius distance.
//======================================================
qboolean G_Within_Radius(vec3_t start, vec3_t end, float rad) {
vec3_t eorg={0,0,0};
int j;
    for (j=0; j<3; j++)
      eorg[j]=abs(start[j]-end[j]);
    return  (VectorLength(eorg) < rad);
}

// PJT

Then, just before the procedure rocket_think, insert the following new procedure:


//PJT nuke missiles!!!!
/*
=================
rocket_touch_nuke
=================
*/
void rocket_touch_nuke (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
{
	vec3_t		origin;
	int			n, i;
	vec3_t 		forward, backward, right, left, up,
				for_right, for_left, back_right, back_left;  // PJT
	edict_t 	*player=NULL;

// PJT - nuclear missile
// 1.  BFG explosion at impact
	bfg_touch (ent, other, plane, surf);

// 2.  fire BDG blasts at 8 compass points plus up from impact

  // For some reason, must use VectorSet()..
	VectorSet(forward,0,1,0);
	VectorSet(right,1,0,0);
	VectorSet(backward,0,-1,0);
	VectorSet(left,-1,0,0);
	VectorSet(for_left,-1,1,0);
	VectorSet(for_right,1,1,0);
	VectorSet(back_right,1,-1,0);
	VectorSet(back_left,-1,-1,0);
	VectorSet(up,0,0,1);

  // Fire BFG fireball with dmg=200; speed=400; radius=1000
  // in direction of 8 compass points plus up.
	fire_bfg(ent, ent->s.origin, forward,  		200, 400, 1000);
	fire_bfg(ent, ent->s.origin, right,    		200, 400, 1000);
	fire_bfg(ent, ent->s.origin, backward, 		200, 400, 1000);
	fire_bfg(ent, ent->s.origin, left,     		200, 400, 1000);
	fire_bfg(ent, ent->s.origin, for_left,  	200, 400, 1000);
	fire_bfg(ent, ent->s.origin, for_right,    	200, 400, 1000);
	fire_bfg(ent, ent->s.origin, back_right, 	200, 400, 1000);
	fire_bfg(ent, ent->s.origin, back_left,     200, 400, 1000);
	fire_bfg(ent, ent->s.origin, up,     		200, 400, 1000);

// 3.  blast wave - blow everyone over in radius 1000

  for(i=0;i < game.maxclients;i++) {
    player=g_edicts+i+1;
    if (!G_ClientInGame(player)) continue;
    if (other==player) continue;
    if (!G_Within_Radius(player->s.origin, ent->s.origin, 1000)) continue;
    AngleVectors(player->s.angles, forward, right, up);
    VectorMA(player->velocity, 200, forward, player->velocity);
    VectorMA(player->velocity, 200, up, player->velocity);
    } // end for

}

// PJT

Then, in the procedure rocket_think, insert the following code after the indicated line:

	rocket->owner = self;	// <- insert code after this line

	// PJT - determine rocket type
	if (self->client)
	{
		rocket->touch = rocket_touch;
    	switch(self->client->pers.nuke_state)
		{
		case 0:		// standard
			rocket->touch = rocket_touch;
			break;
		case 1:		// nukes
			rocket->touch = rocket_touch_nuke;
			rocket->s.renderfx = RF_SHELL_GREEN + RF_SHELL_BLUE + RF_SHELL_RED; 	// make nuke rockets white.
  			rocket->s.effects |= EF_COLOR_SHELL;
			break;
		// add case statements here for new rocket types
		}
	}
	else
		rocket->touch = rocket_touch;
	// PJT


g_cmds.c

Add the following code into procedure ClientCommand at the place indicated:

// PJT - multiple rockets
	else if (Q_stricmp (cmd, "rocket_type") == 0)
		Cmd_Nukes_f (ent);
// PJT

 	else if (Q_stricmp (cmd, "gameversion") == 0)
 		gi.cprintf (ent, PRINT_HIGH, "%s : %s\n", GAMEVERSION, __DATE__);

Then, at the end of the file, insert the following procedure:

//PJT nuclear missiles !!!, etc
/*
=================
Cmd_Nukes_f
PJT: whole new function for switching rocket type
add new rocket types in here
=================
*/
void Cmd_Nukes_f (edict_t *ent)
{
	int		i;

	// update pointer
	i = ent->client->pers.nuke_state;
	i++;
	if (i>1) i = 0;		// increment '1' if adding new rocket types
	ent->client->pers.nuke_state = i;

	// any special handling for rocket types
	// user display of selected rocket type now done in p_hud.c
	switch (i)
	{
	case 0:			// standard rockets
	default:
		break;
	case 1:			// nukes
		gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power1.wav"), 1, ATTN_NORM, 0);
		break;
	// add case statements here for new rocket types
	}
}
// PJT

Then, bind a key to "rocket_type" to toggle between the different rocket types.

Notes Notes to code


glocal.h
  • This creates a toggle in the client's data structure for activating nuclear missile status.

p_weapon.c
  • This code determines number of rockets required from 'nuclear' toggle in client structure
  • As indicated i the code, add further case statements to determine the number of rockets required for rocket types you add yourself
  • if client does not hold enough ammo, the 'out of ammo' sound is played, a message is sent to the player's screen, and a value false is returned to indicate not to fire a rocket
  • the number of rockets for this shot is deducted, unless the 'infinite ammo' flag is on. Note that the code always forces the player to have at least 1 rocket - this is a 'Team Rocket' requirement, comment out the appropriate lines if not required.

g_weapon.c
  • The first 4 functions are used by rocket_touch_nuke for determining which players are hit by the blast wave. They replace existing Quake functions and are more efficient.
  • The function rocket_touch_nuke is called when a nuclear rocket impacts something. It will then do the following:
    • generate a BFG explosion at the point of impact
    • fire BFG blasts in 8 compass directions plus upwards to simulate the nuclear blast
    • blow all clients over in a 1000-unit radius
  • The changes in procedure fire_rocket are as follows:
    • if normal rockets are selected, use default impact routine rocket_touch
    • if nuclear rockets are selected, use impact routine rocket_touch_nuke and give the rocket a white glow
    • as indicated in the code, more rocket types can be catered for by adding extra case statements, pointing to different "touch" routines

g_cmds.c
  • This assigns the nuclear rocket toggle function to console command "rocket_type".
  • The client structure flag between standard and nuclear rockets is toggled when this function is called.
  • When nuclear rockets are selected, a 'power up' sound is played
  • as indicated in the code, more rocket types can be catered for by adding more case statements and incrementing the number of values allowed in the client structure nuke_status
Exercises Exercises

  • cause a white flash to be displayed on players in the blast radius
  • parameterise the number of rockets required for each type using server (cvar) variables
  • simulate EMP (electro-magnetic pulse) that knocks out electronic equipment that is in use


Page last updated 22nd October 2000