|
QuakeZone MOD Coding Tutorials
|
|
|
Introduction Code Notes Exercises |
|
| 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 |
|
|
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 |
|
|
glocal.h
p_weapon.c
g_weapon.c
g_cmds.c
|
| Exercises |
|
|