TUTORIAL 36 - Spreadfire powerup
by Hal9000
This nifty new powerup is just the kind of thing you used to find in those retro arcade
games. Which is probably just as well its in the Smash mod,
because that's based on... you guessed it... a retro arcade game.
Run around fragging bot opponents for money, using a fixed third person view. Gameplay is very
simple - yet surprisingly addictive - with a few extra powerups thrown in for good measure.
Great maps complete this mod and really make it fun to play.
1. Recognizing the powerup
To kick off we need to add a new power up type, and then define the entity that appears on
maps. As there's already a powerup system in place, we don't really need to do any more
work to get the item started.
Adding the new type to bg_public.h, in the powerup_t enumeration
(about line 221):
PW_REDFLAG,
PW_BLUEFLAG,
PW_SPREAD, //Hal9000 spreadfire
PW_BALL,
Then the powerup item is added to the list of entities that the client and server
will be aware of. In bg_misc.c in the array bg_itemlist[]:
/*QUAKED team_CTF_blueflag (0 0 1) (-16 -16 -16) (16 16 16)
Only in CTF games
*/
{
"team_CTF_blueflag",
"sound/teamplay/flagtk_blu.wav",
{ "models/flags/b_flag.md3",
0, 0, 0 },
/* icon */ "icons/iconf_blu1",
/* pickup */ "Blue Flag",
0,
IT_TEAM,
PW_BLUEFLAG,
/* precache */ "",
/* sounds */ "sound/teamplay/flagcap_blu.wav sound/teamplay/flagtk_blu.wav sound/teamplay/flagret_blu.wav"
},
//Hal9000 spreadfire powerup
/*QUAKED item_spread (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
*/
{
"item_spread",
"sound/items/spread.wav",
{ "models/powerups/threeway/threeway.md3",
0, 0, 0 },
/* icon */ "icons/spread",
/* pickup */ "Spreadfire",
30,
IT_POWERUP,
PW_SPREAD,
/* precache */ "",
/* sounds */ ""
},
// end of list marker
{NULL}
item_spread is a unique name for the item, used to identify
and connect it with the entity placed in the map.
&qout;sound/items/spread.wav" will be played when the item is picked up.
Because it's a powerup, it'll be played globally for all to hear.
The next set of information in the curly brackets is the list of
models used to construct the item. In our case we only have one,
"models/powerups/threeway/threeway.md3", but powerups like the quad
have a second model for a counter-rotating ring.
If you want to use the model created for the spreadfire by Hal9000, you'll have
to ask for his permission first.
|
The small graphical icon called icons/spread" is drawn on
the splash screen while the level loads, and is also used when the
player has item models turned off. This can (as it does in this case)
also name a shader used to specify how the icon is drawn on screen.
The item info is completed with the name displayed on screen when the item is picked up,
and the identification as a powerup of type PW_SPREAD.
2. When the powerup is activated...
...we need to fire the weapon with two extra shots to give us a fan shape.
This is the code from Smash2 that does the job. It only works for weapons
that produce a "bolt": rocket launcher, grenade, plasma,
and BFG(!), and direct fire or hit-scan weapons like a railgun can't use this code.
This is the new weapon_grenadelauncher_fire() in g_weapon.c,
with the spreadfire code installed. To use this code in other bolt weapons
you'll need to replace the fire_grenade() call with the equivalent bolt
creation function call.
void weapon_grenadelauncher_fire (gentity_t *ent) {
gentity_t *m;
gclient_t *client; //Hal9000
float newforward[] = {0,0,0}; //Hal9000
client = ent->client; //Hal9000
// extra vertical velocity
forward[2] += 0.2;
VectorNormalize( forward );
m = fire_grenade (ent, muzzle, forward);
m->damage *= s_quadFactor;
m->splashDamage *= s_quadFactor;
//Hal9000 spreadfire
if ( client->ps.powerups[PW_SPREAD] ) {
//First shot, slightly to the right
AngleVectors( client->ps.viewangles, forward, right, up );
VectorCopy( forward, newforward );
if ( forward[0] >= 0.5 && forward[0] <= 1 ) {
newforward[1] += .35;
} else if ( forward[0] <= -0.5 && forward[0] >= -1 ) {
newforward[1] += .35;
} else {
newforward[0] += .35;
}
VectorNormalize( newforward );
VectorAdd( newforward, forward, forward );
CalcMuzzlePoint( ent, forward, right, up, muzzle );
m = fire_grenade (ent, muzzle, forward);
m->damage *= s_quadFactor;
m->splashDamage *= s_quadFactor;
//Second shot, slightly to the left
AngleVectors (client->ps.viewangles, forward, right, up);
VectorCopy( forward, newforward );
if ( forward[0] >= 0.5 && forward[0] <= 1 ) {
newforward[1] -= .35;
} else if ( forward[0] <= -0.5 && forward[0] >= -1 ) {
newforward[1] -= .35;
} else {
newforward[0] -= .35;
}
VectorNormalize( newforward );
VectorAdd( newforward, forward, forward );
CalcMuzzlePoint ( ent, forward, right, up, muzzle );
m = fire_grenade (ent, muzzle, forward);
m->damage *= s_quadFactor;
m->splashDamage *= s_quadFactor;
}
// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity,
// m->s.pos.trDelta ); // "real" physics
}
What this code does is construct a vector (newforward) that's
approximately perpendicular to the direction we're facing, and in the horizontal plane.
It then adds it to our forward facing direction, and fires a bolt off. This achieves the
desired effect of a fan shape, though its only an approximate solution.
Unfortunately there's a small problem. The code assumes that the
spreadfire will only be used in the horizontal plane. This is great and works
well in SmashV2 where you can only fire horizontally. If you want to add
this feature to your mod, and you allow a mouse free-look that changes
pitch, then you'll need to use the code from the next section instead.
3. When the powerup is activated (part II)
I'm going to take a slightly different route with this code just to make it
easier to add to any bolt firing weapon. I'll create a generic function that
accepts the bolt firing function as a parameter, so we can re-use it in multiple
places.
This is the new function, place it near the top of g_weapon.c:
typedef gentity_t* (*weaponLaunch)(gentity_t*, vec3_t, vec3_t);
/*
===============
SpreadFire_Powerup
HypoThermia: fan bolts at any view pitch
===============
*/
static void SpreadFire_Powerup(gentity_t* ent, weaponLaunch fireFunc)
{
gentity_t *m;
gclient_t *client;
vec3_t newforward;
vec3_t angles;
client = ent->client;
//First shot, slightly to the right
AngleVectors( client->ps.viewangles, forward, right, 0);
VectorMA(forward, 0.1, right, newforward);
VectorNormalize(newforward);
vectoangles(newforward, angles);
AngleVectors( angles, forward, right, up );
CalcMuzzlePoint( ent, forward, right, up, muzzle );
m = fireFunc (ent, muzzle, forward);
m->damage *= s_quadFactor;
m->splashDamage *= s_quadFactor;
//Second shot, slightly to the left
AngleVectors( client->ps.viewangles, forward, right, 0);
VectorMA(forward, -0.1, right, newforward);
VectorNormalize(newforward);
vectoangles(newforward, angles);
AngleVectors( angles, forward, right, up );
CalcMuzzlePoint( ent, forward, right, up, muzzle );
m = fireFunc(ent, muzzle, forward);
m->damage *= s_quadFactor;
m->splashDamage *= s_quadFactor;
}
It looks pretty similar to the one used in Smash, but uses a feature of AngleVectors
to recover a useful vector.
The client->ps.viewangles contains the YAW (rotate left-right), PITCH
(lean forward/back), and ROLL (rotate left/right) data for the view.
When used, AngleVectors() returns vectors that, given our
orientation in space, will move us forward, to the right edge of the
screen, and up to the top of the screen.
The important thing to understand is that these vectors give us
movement directions in the absolute world that have
"left-right" and "up-down" meaning in our view
of the world.
How does this relate to the spreadfire? Well each extra shot goes
"to the right" and "to the left" in our view of the
world. So we mix some of the right vector into the view forward
direction using VectorMA() and then reconvert back to the new
(YAW, PITCH, ROLL) angles in preparation for calulating the muzzle
point.
Its a long winded way of saying that the spread of bolts is
independent of the direction you fire them in!
Finally you have to enable the spreadfire for each of the bolt weapons, this is
the modified grenade launcher code:
void weapon_grenadelauncher_fire (gentity_t *ent) {
gentity_t *m;
// extra vertical velocity
forward[2] += 0.2;
VectorNormalize( forward );
m = fire_grenade (ent, muzzle, forward);
m->damage *= s_quadFactor;
m->splashDamage *= s_quadFactor;
if ( client->ps.powerups[PW_SPREAD] )
SpreadFire_Powerup(ent, fire_grenade);
// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity,
// m->s.pos.trDelta ); // "real" physics
}
4. Finishing up
Well that's all you need to do for bolt weapons. Because of the
similarity of "fire and forget" for each of these weapons, the code
change is much more straightforward. If you want to extend other weapon
types then you're going to have to look at each one individually, and
extend the "hit-scan" detection code. This is more
"repetitive" than difficult, just make sure that you keep the
weapon spread independent of the angle of view.
Take a look at the rest of the SmashV2 source code in the
ModSource section. This mod
balances code changes with new maps, showing that a fun mod isn't just
driven by code alone.
|