Explanation of weapons


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.

  1. Weapon definition in bg_misc.c (game)
  2. Ammo definition in bg_misc.c (game)
  3. Pmove (bg_pmove.c game) is called by client or server
  4. Pmovesingle (bg_pmove.c game) is called by pmove
  5. PM_Weapon (bg_pmove.c game) is called by Pmovesingle
  6. PM_Weapon checks for firing, and removes some ammo before firing the weapon
  7. PM_Weapon adds a weapon firing event to the players stack
  8. PM_Weapon adds some time to a counter preventing the user from firing again for a set time
  9. ClientEvents (g_active.c game) checks the stack and finds a weapon firing event.
  10. FireWeapon (g_weapon.c game) is called by ClientEvents
  11. FireWeapon sets up the aiming angles then calls the relevant weapon function (same file)
  12. The weapons do different things dependant on which they are.

Weapon definition in bg_misc.c (game)

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


Ammo definition in bg_misc.c (game)

/*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

Pmove (bg_pmove.c game) is called by client or server

Not much for you to do here


Pmovesingle (bg_pmove.c game) is called by pmove

Ditto


PM_Weapon (bg_pmove.c game) is called by Pmovesingle

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;
	}

PM_Weapon checks for firing, and removes some ammo before firing the weapon

PM_Weapon adds a weapon firing event to the players stack

PM_Weapon adds some time to a counter preventing the user from firing again for a set time

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) */  

}

ClientEvents (g_active.c game) checks the stack and finds a weapon firing event.

FireWeapon (g_weapon.c game) is called by ClientEvents

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


FireWeapon sets up the aiming angles then calls the relevant weapon function (same file)

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)

	}
}

The weapons do different things dependant on which they are.

Just take a look at the functions. they are fairly easy to understand.


Back to the tutorials
Mail me