Quake DeveLS - Weapon Accelerator (New Items)

Author: Borisian[SFD]
Difficulty:
Medium

This is just a tutorial to how I created the Weapon Accelerator mod for Quake, sans the help screens done using the RA2 Menu Mod. This mod does use the stuffcmd() function included in Quake DeveLS's own Q_Devels.c/Q_Devels.h, so make sure that you've installed the commands in those before going on. If you want to download the complete mod, with full source code, head for my site.

First, you need to get your model and skin done. There are enough resources out there that I won't go through this step in the tutorial; check out theQuake2.com Modeling Center and Kray-Zee's Modeling Guide for info on this. Create the model and it's skin with the filenames tris.md2 and skin.pcx in a new subdirectory of models - for the weapon accelerator, I used models/waccel.

Next, create a picture that will used in the lower-right corner (and for the count-down timer if it's a powerup that goes on the statusbar, such as mine) and save this with the filename p_waccel.pcx or your equivalent in the pics directory.

Now, to the coding. This code is for my own item_waccel, but you can probably alter it to your needs if you've managed through all the other tutorials. First, go into the gclient_s struct in g_local.h (line 758)and add these two lines to the bottom:

// RMyers
int waccel_powercount;
int waccel_active;

Go ahead and close g_local.h. Open g_items.c> (this is the important one) and add a routine for what you want your item to do when used. Mine looks like this:

void Use_Accelerator (edict_t *ent, gitem_t *item)
{
ent->client->pers.inventory[ITEM_INDEX(item)]--;
ValidateSelectedItem (ent);
ent->client->waccel_powercount += 50;
ent->client->waccel_active=1;
}

I put this with the other Use functions after the Use_Silencer function, about line 383.

You need to do a similar function, best put just below the last one, for what to do when it's picked up. The Accelerator is simply used immediately after being picked up:

qboolean Pickup_Accelerator (edict_t *ent, edict_t *other)
{
other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
ent->item->use (other, ent->item);
return true;
}

Finally, here's the big one: go to line 1938 of g_items.c - this is the list of all the items and their behaviors and callbacks. You need to add an entry to the list. Here's mine, put right at the end after the large Health pickup:

{
NULL,
Pickup_Health,
NULL,
NULL,
NULL,
"items/pkup.wav",
NULL, 0,
NULL,
/* icon */ "i_health",
/* pickup */ "Health",
/* width */ 3,
60,
NULL,
0,
NULL,
0,
/* precache */ ""
},
// RMyers Weapon Accelerator
{
"item_waccel",
Pickup_Accelerator,
Use_Accelerator,
Drop_General,
NULL,
"items/pkup.wav",
"models/items/waccel/tris.md2", EF_ROTATE,
NULL,
/* icon */ "p_waccel",
/* pickup */ "Shot Accelerator",
/* width */ 2,
60,
NULL,
0,
NULL,
0,
/* precache */ ""
},

// end of list marker
{NULL}

This creates the Shot Accelerator, an item that respawns about the same frequency as the quad damage. When picked up, it calls the Pickup_Accelerator routine, and when used calls the Use_Accelerator routine. It has the general Drop routine that all other items use, although it doesn't really need it... it uses the normal pick-up sound. The next few lines define the model filename, flags, the icon for it on the statusbar, the name flashed at the bottom when picked up, and the number of digits that can be held of this item (i.e. only the first two digits are displayed). The sixty represents the respawn time, and is the same interval as the Quad. The classname to be used when building levels for the Accelerator is item_waccel.

Okay, with that bit of code, the item itself is in. Now, you just need to build the effects of it in. First, let's put some announcement code in to let the players know about our mod, and alias the commands so that they don't have to constantly type "cmd":

gi.centerprintf (ent,"Welcome to:\n\nWeapon Accelerator 1.0\n\n* Created by Borisian[SFD] *\n\n(Type whelp to get info)\n");
ent->client->waccel_powercount=0;
ent->client->waccel_active=0;
stuffcmd(ent,"alias whelp \"cmd whelp\"\n");
stuffcmd(ent,"alias waccelon \"cmd waccelon\"\n");
stuffcmd(ent,"alias wacceloff \"cmd wacceloff\"\n");
stuffcmd(ent,"alias +waccl \"cmd waccelon\"\n");
stuffcmd(ent,"alias -waccl \"cmd wacceloff\"\n");
stuffcmd(ent,"alias waccelpwr \"cmd waccelpwr\"\n");

Stick this at the ends of both ClientBegin and ClientBeginDeathmatch in p_client.c. This uses the stuffcmd function in Q_Devels.c to send commands to the console. It also initializes both variables, an essential in clean programming.

Next, we actually implement the commands in g_cmds.c:

else if (Q_stricmp (cmd, "whelp") == 0)
menuHelpScreen(ent);
else if (Q_stricmp (cmd, "waccelon") == 0) {
if (ent->client->waccel_powercount<=0) {
gi.dprintf("No Weapon Accelerator\n");
} else {
gi.dprintf("Weapon Accelerator Active\n");
ent->client->waccel_active=1;
}
} else if (Q_stricmp (cmd, "wacceloff") == 0) {
if (ent->client->waccel_powercount<=0) {
gi.dprintf("No Weapon Accelerator\n");
} else {
gi.dprintf("Weapon Accelerator Inactive\n");
ent->client->waccel_active=0;
}
} else if (Q_stricmp (cmd, "waccelpwr") == 0)
gi.dprintf ("The Weapon Accelerator has %d shots of power left.\n", ent->client->waccel_powercount);
else
gi.cprintf (ent, PRINT_HIGH, "Bad command: %s\n", cmd);
}

( The whelp command calls a routine I wrote that handles the online help, utilizing the Rocket Arena 2-style menus. A bit of a pain to get working, but it's wonderful. ) This just lets you turn the accelerator on and off.

Finally, we actually put in the code that makes the accelerator force the weapon to fire faster. We do this by modifying the Weapon_Generic think function in p_weapon.c to drop frames from the fire action, one of SumFuka's ideas. The entire new function is below:

void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent))
{
int n;

// RMyers
if (ent->client->waccel_active==1)
{
FRAME_FIRE_LAST = ((FRAME_FIRE_LAST-FRAME_ACTIVATE_LAST)/2) + FRAME_ACTIVATE_LAST + 1;
}

// Bug fix: the odd "no such frame number" error. I have yet to find where this
// comes from. I just kill it if the gunframe goes beyond the last one.

// Bug fix: by stopping it at the gun fire frame, it now works correctly; but, the
// gun gets stuck. Set the weaponstate to WEAPON_READY.

if ((ent->client->ps.gunframe > FRAME_FIRE_LAST) && (ent->client->weaponstate == WEAPON_FIRING)) {
ent->client->ps.gunframe=FRAME_IDLE_FIRST;
ent->client->weaponstate=WEAPON_READY;
}

if (ent->client->weaponstate == WEAPON_DROPPING)
{
if (ent->client->ps.gunframe == FRAME_DEACTIVATE_LAST)
{
ChangeWeapon (ent);
return;
}

ent->client->ps.gunframe++;
return;
}

if (ent->client->weaponstate == WEAPON_ACTIVATING)
{
if (ent->client->ps.gunframe == FRAME_ACTIVATE_LAST)
{
ent->client->weaponstate = WEAPON_READY;
ent->client->ps.gunframe = FRAME_IDLE_FIRST;
return;
}

ent->client->ps.gunframe++;
return;
}

if ((ent->client->newweapon) && (ent->client->weaponstate != WEAPON_FIRING))
{
ent->client->weaponstate = WEAPON_DROPPING;
ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST;
return;
}

if (ent->client->weaponstate == WEAPON_READY)
{
if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) )
{
ent->client->latched_buttons &= ~BUTTON_ATTACK;
if ((!ent->client->ammo_index) || ( ent->client->pers.inventory[ent->client->ammo_index] >= ent->client->pers.weapon->quantity))
{
ent->client->ps.gunframe = FRAME_FIRE_FIRST;
ent->client->weaponstate = WEAPON_FIRING;

// start the animation
ent->client->anim_priority = ANIM_ATTACK;
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
{
ent->s.frame = FRAME_crattak1-1;
ent->client->anim_end = FRAME_crattak9;
} else {
ent->s.frame = FRAME_attack1-1;
ent->client->anim_end = FRAME_attack8;
}
} else {
if (level.time >= ent->pain_debounce_time)
{
gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
ent->pain_debounce_time = level.time + 1;
}
NoAmmoWeaponChange (ent);
}
} else {
if (ent->client->ps.gunframe == FRAME_IDLE_LAST)
{
ent->client->ps.gunframe = FRAME_IDLE_FIRST;
return;
}

if (pause_frames)
{
for (n = 0; pause_frames[n]; n++)
{
if (ent->client->ps.gunframe == pause_frames[n])
{
if (rand()&15)
return;
}
}
}

ent->client->ps.gunframe++;
return;
}
}

if (ent->client->weaponstate == WEAPON_FIRING)
{
for (n = 0; fire_frames[n]; n++)
{
if (ent->client->ps.gunframe == fire_frames[n])
{
if (ent->client->quad_framenum > level.framenum)
gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);

fire (ent);

// RMyers

if ((ent->client->waccel_powercount<=0) && (ent->client->waccel_active==1))
{
ent->client->waccel_active=0;
ent->client->waccel_powercount=0;
gi.dprintf("Out of Shots - Weapon Accelerator Lost\n");
}
if (ent->client->waccel_active==1)
ent->client->waccel_powercount--;

break;
}
}

if (!fire_frames[n])
ent->client->ps.gunframe++;

if (ent->client->ps.gunframe == FRAME_IDLE_FIRST+1)
ent->client->weaponstate = WEAPON_READY;
}
}

It simply subtracts half the frames from the fire animation. Note the "bugfix" below: in some cases, the weapon would get stuck firing, or would cycle through every frame it had and on past the number of frames (generating a bunch of R_DrawAliasModel errors until it reaches frame 255). So, if it detects that the weaponframe has gone beyond the new last fire frame and the weapon is still firing, it stops the weapon firing and goes back to the first fire frame. It also makes the accelerator turn off if you run out of cells.

Finally, for that little icon on the statusbar, head into p_hud.c, and at line 377 add these lines:

// RMyers

else if (ent->client->waccel_active==1)
{
ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_waccel");
ent->client->ps.stats[STAT_TIMER] = ent->client->waccel_powercount;
} else {
ent->client->ps.stats[STAT_TIMER_ICON] = 0;
ent->client->ps.stats[STAT_TIMER] = 0;
}

(the last one is left for illustration of placement)

Remember to SPEND TIME MAKING YOUR MODEL!! Crappy models, and especially crappy skins, will make your mod look like crap no matter how nice your code is.

This site, and all content and graphics displayed on it,
are Šopyrighted to the
Quake DeveLS team. All rights received.
Got a suggestion? Comment? Question? Hate mail?
Send it to us!
Oh yeah, this site is best viewed in 16 Bit or higher, with the resolution on 800*600.
Thanks to
Planet Quake for there great help and support with hosting.
Best viewed with
Netscape 4 or IE 3