Hmmm, I'd say that weapons in q3 are one of the hardest things to modify (from what I've seen so far). I got set on this by someone asking how to make different weapons use the same kind of ammo. It is doable, but requires a rewrite of the ammo handling code. The problem is the weapon firing code is actually split into a number of different pieces that stick requests into a stack, and these requests get grabbed elsewhere and acted on.
I'll go through this in the order of things that make a weapon fire.
Of the form :
| /*QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16)
suspended */ |
Just a decription. not important to you |
| { | begining of definition |
| "weapon_shotgun", | The name of the object in a map file |
| "sound/misc/w_pkup.wav", | The noise when you pick it up |
| { "models/weapons2/shotgun/shotgun.md3", 0, 0, 0}, |
The model for the weapon, i'm not sure what the coords are for |
| /* icon */ "icons/iconw_shotgun", | The icon that shows up |
| /* pickup */ "Shotgun", | The pickup name for the weapon (what shows up when you pick it up) |
| 10, | Default ammo |
| IT_WEAPON, | Item type |
| WP_SHOTGUN, | Weapon identifier(from bg_public.h) |
| /* precache */ "", | Precaches for weapon |
| /* sounds */ "" | Sound precaches for weapon |
| }, | end of definition |
I've added comments above
| /*QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
*/ | |
| { | begining of definition |
| "ammo_shells", | Map entity name |
| "sound/misc/am_pkup.wav", | Sound when you pick it up |
| { "models/powerups/ammo/shotgunam.md3", 0, 0, 0}, |
Model for ammo |
| /* icon */ "icons/icona_shotgun", | Icon for ammo |
| /* pickup */ "Shells", | Pickup name of ammo |
| 10, | Quantity in box |
| IT_AMMO, | Item type |
| WP_SHOTGUN, | Weapon ammo is for |
| /* precache */ "", | Item precaches |
| /* sounds */ "" | Sound precaches for weapon |
| }, | end of definition |
Not much for you to do here
Ditto
Not much for you here. only bit that might be of interest is
// set the firing flag for continuous beam weapons
if ( !(pm->ps->pm_flags & PMF_RESPAWNED) && pm->ps->pm_type != PM_INTERMISSION
&& ( pm->cmd.buttons & BUTTON_ATTACK ) && pm->ps->ammo[ pm->ps->weapon ] ) {
pm->ps->eFlags |= EF_FIRING;
} else {
pm->ps->eFlags &= ~EF_FIRING;
}
Fair amount here. I've added comments to the code below (marked with (FS))
/*
==============
PM_Weapon
Generates weapon events and modifes the weapon counter
==============
*/
static void PM_Weapon( void ) {
int addTime;
/* everything has been defined as global variables here (global withing this file at least)
so player is defined in pm as a pmove_t . ps is a player state variable in pm (FS)*/
// don't allow attack until all buttons are up
if ( pm->ps->pm_flags & PMF_RESPAWNED ) { // kinda obvious here (FS)
return;
}
// ignore if spectator
if ( pm->ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) {
return;
}
// check for dead player
if ( pm->ps->stats[STAT_HEALTH] <= 0 ) {
pm->ps->weapon = WP_NONE;
return;
}
// check for item using
if ( pm->cmd.buttons & BUTTON_USE_HOLDABLE ) {
if ( ! ( pm->ps->pm_flags & PMF_USE_ITEM_HELD ) ) {
if ( bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag == HI_MEDKIT
&& pm->ps->stats[STAT_HEALTH] >= pm->ps->stats[STAT_MAX_HEALTH] ) {
// don't use medkit if at max health
} else {
pm->ps->pm_flags |= PMF_USE_ITEM_HELD;
PM_AddEvent( EV_USE_ITEM0 + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag );
pm->ps->stats[STAT_HOLDABLE_ITEM] = 0;
}
return;
}
} else {
pm->ps->pm_flags &= ~PMF_USE_ITEM_HELD;
}
// make weapon function
if ( pm->ps->weaponTime > 0 ) {
pm->ps->weaponTime -= pml.msec; // Makes sure the timer between shots is reduced (FS)
}
// check for weapon change
// can't change if weapon is firing, but can change
// again if lowering or raising
if ( pm->ps->weaponTime <= 0 || pm->ps->weaponstate != WEAPON_FIRING ) {
if ( pm->ps->weapon != pm->cmd.weapon ) {
PM_BeginWeaponChange( pm->cmd.weapon );
}
}
if ( pm->ps->weaponTime > 0 ) {
return; // If the timer hasn't elasped, there nothing else you can do here (FS)
}
// change weapon if time
if ( pm->ps->weaponstate == WEAPON_DROPPING ) {
PM_FinishWeaponChange();
return;
}
if ( pm->ps->weaponstate == WEAPON_RAISING ) {
pm->ps->weaponstate = WEAPON_READY;
if ( pm->ps->weapon == WP_GAUNTLET ) {
PM_StartTorsoAnim( TORSO_STAND2 );
} else {
PM_StartTorsoAnim( TORSO_STAND );
}
return;
}
// check for fire
if ( ! (pm->cmd.buttons & 1) ) {
pm->ps->weaponTime = 0; // by now its either 0 or less but you're not firing so finish (FS)
pm->ps->weaponstate = WEAPON_READY;
return;
}
// start the animation even if out of ammo
if ( pm->ps->weapon == WP_GAUNTLET ) {
// the guantlet only "fires" when it actually hits something
if ( !pm->gauntletHit ) {
pm->ps->weaponTime = 0;
pm->ps->weaponstate = WEAPON_READY;
return;
}
PM_StartTorsoAnim( TORSO_ATTACK2 );
} else {
PM_StartTorsoAnim( TORSO_ATTACK );
}
pm->ps->weaponstate = WEAPON_FIRING;
// as by now the weapon is being fired and the timer has elapsed (FS)
// check for out of ammo
if ( ! pm->ps->ammo[ pm->ps->weapon ] ) {
PM_AddEvent( EV_NOAMMO );
pm->ps->weaponTime += 500;
return;
}
// ^^^^^^^^^you'll have to change this bit if you are changing the way the ammo is being done ^^^^^^^^^^(FS)
// take an ammo away if not infinite
if ( pm->ps->ammo[ pm->ps->weapon ] != -1 ) {
pm->ps->ammo[ pm->ps->weapon ]--;
}
// and here ^^^^^^^^^^^^^^^^^^^^^^ (I'm not keen how this is done) (FS)
// fire weapon
PM_AddEvent( EV_FIRE_WEAPON );
// Adds a fire event to the players event stack. this is picked up later (FS)
switch( pm->ps->weapon ) {
default:
case WP_GAUNTLET:
addTime = 400;
break;
case WP_LIGHTNING:
addTime = 50;
break;
case WP_SHOTGUN:
addTime = 1000;
break;
case WP_MACHINEGUN:
addTime = 100;
break;
case WP_GRENADE_LAUNCHER:
addTime = 800;
break;
case WP_ROCKET_LAUNCHER:
addTime = 800;
break;
case WP_PLASMAGUN:
addTime = 100;
break;
case WP_RAILGUN:
addTime = 1500;
break;
case WP_BFG:
// addTime = 100;
addTime = 200;
break;
case WP_GRAPPLING_HOOK:
addTime = 400;
break;
}
if ( pm->ps->powerups[PW_HASTE] ) {
addTime /= 1.3;
}
// addtime controls how long between shots in milliseconds. having a haste powerup reduces the wait by 22% (for some reason. I'd change this). modify to change fire rates (FS)
pm->ps->weaponTime += addTime;
/* sets the counter again. (I'm not sure why they use += as = would do the same thing (as by before that line weapontime is 0 )) bizzare coding practices. (FS) */
}
Not much here, but its the only link between what comes next and what has already been done. Just a for loop to go through all the events and a switch statement to do stuff
I'll add comments below as I feel fit. (again marked with (FS), okay?)
void FireWeapon( gentity_t *ent ) {
if (ent->client->ps.powerups[PW_QUAD] ) {
s_quadFactor = g_quadfactor.value; // s_quadfactor is a global for this file and is used by all the weapon functions (FS)
} else {
s_quadFactor = 1;
}
// track shots taken for accuracy tracking. Grapple is not a weapon and gauntet is just not tracked
if( ent->s.weapon != WP_GRAPPLING_HOOK && ent->s.weapon != WP_GAUNTLET ) {
ent->client->ps.persistant[PERS_ACCURACY_SHOTS]++;
}
// set aiming directions
AngleVectors (ent->client->ps.viewangles, forward, right, up);
CalcMuzzlePoint ( ent, forward, right, up, muzzle );
// forward, right, and up are vec3_t's holding unit vectors. they are global to this file (FS)
// muzzle is a vec3_t holding the location where the weapon fires from. global to this file. (FS)
// fire the specific weapon
switch( ent->s.weapon ) {
case WP_GAUNTLET:
Weapon_Gauntlet( ent );
break;
case WP_LIGHTNING:
Weapon_LightningFire( ent );
break;
case WP_SHOTGUN:
weapon_supershotgun_fire( ent );
break;
case WP_MACHINEGUN:
if ( g_gametype.integer != GT_TEAM ) {
Bullet_Fire( ent, MACHINEGUN_SPREAD, MACHINEGUN_DAMAGE );
} else {
Bullet_Fire( ent, MACHINEGUN_SPREAD, MACHINEGUN_TEAM_DAMAGE );
}
break;
case WP_GRENADE_LAUNCHER:
weapon_grenadelauncher_fire( ent );
break;
case WP_ROCKET_LAUNCHER:
Weapon_RocketLauncher_Fire( ent );
break;
case WP_PLASMAGUN:
Weapon_Plasmagun_Fire( ent );
break;
case WP_RAILGUN:
weapon_railgun_fire( ent );
break;
case WP_BFG:
BFG_Fire( ent );
break;
case WP_GRAPPLING_HOOK:
Weapon_GrapplingHook_Fire( ent );
break;
default:
// FIXME G_Error( "Bad ent->s.weapon" );
break;
// all the above is kinda obvious (FS)
}
}
Just take a look at the functions. they are fairly easy to understand.