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.