furrycat furrycat

Modifying the Bryar pistol

The question was posted on the Lucas forums of whether it would be possible to make a weapon that could only be fired when the firer was crouched and not moving, and whose ammo had a different speed depending on the length of time fire was held: 10% of maximum for a quick tap, 100% for a two-second press and a sliding scale inbetween.

I thought I'd have a crack at this problem. Although the original poster wanted to create an entirely new weapon, I've simply modified the Bryar pistol for the purposes of this exercise. Also, since the Bryar is already set up for a charging alternate fire, I've kept that instead of having the player hold primary attack to charge the weapon.

There are two problems we need to solve.

Firstly and most obviously we want the server to disallow a player from firing unless he is crouched and stationary. The second problem isn't immediately apparent. When you press the fire button, the client code handily draws the muzzle flash and plays the firing sound for you and then waits for the server to actually tell it where to draw the weapon's projectile. We need to stop this from happening.

Server hacking

The server-side code goes in bg_pmove.c. The function we want to modify is PM_Weapon().

Somewhere near the top of the function there's a block which checks whether the player is in an ATST. After that block we can add the following code:

/* can't fire Bryar unless ducked and stationary */
if (pm->ps->weapon == WP_BRYAR_PISTOL) {
  if (pm->cmd.forwardmove || pm->cmd.rightmove || pm->cmd.upmove >= 0) {
    /* cancel any previous charge we had (can't charge, move and then fire) */
    pm->cmd.buttons &= ~BUTTON_ALT_ATTACK;
    /* set weapon to ready state; no longer charging */
    pm->ps->weaponstate = WEAPON_READY;

The firing function will return without doing anything if we have the Bryar and we aren't crouched and still. The "cancel any previous charge" thing makes the game forget we pressed alt fire and the weapon was ever charging. This is because otherwise you could crouch long enough to start charging, move about and then stop and crouch, release fire and let off a powerful shot. The code forces you to be stopped and crouched the whole time you are charging up your shot.

Next thing is to modify the function that actually fires the weapon: WP_FireBryarPistol():

static void WP_FireBryarPistol(gentity_t *ent, qboolean altFire) {
  int damage = BRYAR_PISTOL_DAMAGE;
  float mul;
  gentity_t *missile;

  if (altFire) {
    /* calculate a speed multiplier; 100% for a 2s charge, min 10% */
    mul = (level.time - ent->client->ps.weaponChargeTime) / 2000.0f;
    if (mul < 0.1) mul = 0.1;
    else if (mul > 1.0) mul = 1.0;
  else {
    /* primary fire has the base 10% */
    mul = 0.1;

  missile = CreateMissile(muzzle, forward, BRYAR_PISTOL_VEL * mul, 10000, ent, altFire);

  /* rest of function */

For the Bryar pistol example we also need to comment out the whole "if (altFire)" block which follows and then keep the rest of the function intact.

Client hacking

To solve the client-side problem we open up cg_weapons.c and find CG_FireWeapon(). This is the function which is called when the server says that a player has fired. Theoretically (untested by me!) this function won't be called if a remote player tries to fire because the server code we fixed above won't get as far as sending a "player fired" event. However, the function is called locally on the assumption that the server will later confirm a shot.

The code to insert comes just after the initialisation of weapon (weap = &cg_weapons[ ent->weapon ];):

if (ent->weapon == WP_BRYAR_PISTOL) {
  /* is it us? */
  if (cg.predictedPlayerState.clientNum == cent->currentState.number) {
    if (cg_pmove.cmd.forwardmove) return;
    if (cg_pmove.cmd.rightmove) return;
    if (cg_pmove.cmd.upmove >= 0) return;

This is practically the same as the server code. We check the command which was issued (cg_pmove.cmd) and return from the function without drawing any graphics or playing any sounds if the player is not stationary or if he isn't crouched. forwardmove will be >0 if he's moving forward, <0 if he's moving backwards and so we check for 0. Similarly for rightmove. upmove will be <0 when crouched so we return if it's non-negative.

For this to compile, we also need to declare cg_pmove at the top of the file:

extern pmove_t cg_pmove

The main problem which is not solved is that charging the pistol uses energy, which is lost if it isn't actually fired. You could get round this easily enough if you wanted to by including another test for the Bryar later on in PM_Weapon(). Then again maybe you want the player to waste energy. Depends how your weapon will work.


You can grab the modified source files here: bryarhacking.zip


Send any comments to jk2@furrycat.net.

Please note I have configured ICQ to ignore messages from individuals not on my contact list.