/*****************************************************************************/
/* D2K: A Doom Source Port for the 21st Century                              */
/*                                                                           */
/* Copyright (C) 2014: See COPYRIGHT file                                    */
/*                                                                           */
/* This file is part of D2K.                                                 */
/*                                                                           */
/* D2K is free software: you can redistribute it and/or modify it under the  */
/* terms of the GNU General Public License as published by the Free Software */
/* Foundation, either version 2 of the License, or (at your option) any      */
/* later version.                                                            */
/*                                                                           */
/* D2K is distributed in the hope that it will be useful, but WITHOUT ANY    */
/* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS */
/* FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more    */
/* details.                                                                  */
/*                                                                           */
/* You should have received a copy of the GNU General Public License along   */
/* with D2K.  If not, see <http://www.gnu.org/licenses/>.                    */
/*                                                                           */
/*****************************************************************************/


#include "z_zone.h"

#include "d_items.h"
#include "e6y.h"
#include "m_random.h"
#include "n_main.h"
#include "p_user.h"
#include "p_enemy.h"
#include "p_inter.h"
#include "p_map.h"
#include "p_tick.h"
#include "w_wad.h"
#include "r_demo.h"
#include "r_main.h"
#include "s_sound.h"
#include "sounds.h"
#include "sv_main.h"
#include "g_game.h"
#include "p_setup.h"
#include "p_mobj.h"

#define LOWERSPEED   (FRACUNIT*6)
#define RAISESPEED   (FRACUNIT*6)
#define WEAPONBOTTOM (FRACUNIT*128)
#define WEAPONTOP    (FRACUNIT*32)

#define BFGCELLS bfgcells        /* Ty 03/09/98 externalized in p_inter.c */

// Checking correctness of input parameters for weapon codepointers
// for avoiding crashes when they are used with player/monster states.
#ifdef DEBUG_WEAPON_CODEPOINTERS
  #define CHECK_WEAPON_CODEPOINTER(codepointer, player)\
    if (!player->mo->player) {\
      I_Error("%s: Weapon codepointers cannot be used with player/monster states (incorrect DEH).", codepointer);\
      return;\
    }
#else
  #define CHECK_WEAPON_CODEPOINTER(codepointer, player)
#endif

extern void PL_Thrust(player_t *player, angle_t angle, fixed_t move);

// The following array holds the recoil values         // phares

static const int recoil_values[] = {    // phares
  10,  // wp_fist
  10,  // wp_pistol
  30,  // wp_shotgun
  10,  // wp_chaingun
  100, // wp_missile
  20,  // wp_plasma
  100, // wp_bfg
  0,   // wp_chainsaw
  80   // wp_supershotgun
};

int autoaim = 0;  // killough 7/19/98: autoaiming was not in original beta

//e6y static 
fixed_t bulletslope;

//
// PL_SetPsprite
//

static void PL_SetPsprite(player_t *player, int position, statenum_t stnum) {
  pspdef_t *psp = &player->psprites[position];

  do {
    state_t *state;

    if (!stnum) {
      // object removed itself
      psp->state = NULL;
      break;
    }

    state = &states[stnum];
    psp->state = state;
    psp->tics = state->tics;        // could be 0

    if (state->misc1) {
      // coordinate set
      psp->sx = state->misc1 << FRACBITS;
      psp->sy = state->misc2 << FRACBITS;
    }

    // Call action routine.
    // Modified handling.
    if (state->action) {
      state->action(player, psp);

      if (!psp->state) {
        break;
      }
    }

    stnum = psp->state->nextstate;
  } while (!psp->tics); // an initial state of 0 could cycle through
}

//
// PL_BringUpWeapon
// Starts bringing the pending weapon up
// from the bottom of the screen.
// Uses player
//

static void PL_BringUpWeapon(player_t *player) {
  statenum_t newstate;

  if (player->pendingweapon == wp_nochange) {
    player->pendingweapon = player->readyweapon;
  }

  if (player->pendingweapon == wp_chainsaw) {
    S_StartSound(player->mo, sfx_sawup);
  }

  if (player->pendingweapon >= NUMWEAPONS) {
    D_Msg(MSG_WARN, "PL_BringUpWeapon: weaponinfo overrun has occured.\n");
  }

  newstate = weaponinfo[player->pendingweapon].upstate;

  player->pendingweapon = wp_nochange;

  // killough 12/98: prevent pistol from starting visibly at bottom of screen:
  player->psprites[ps_weapon].sy = mbf_features ?
                                   WEAPONBOTTOM + FRACUNIT * 2 :
                                   WEAPONBOTTOM;

  PL_SetPsprite(player, ps_weapon, newstate);
}

// The first set is where the weapon preferences from             // killough,
// default.cfg are stored. These values represent the keys used   // phares
// in DOOM2 to bring up the weapon, i.e. 6 = plasma gun. These    //    |
// are NOT the wp_* constants.                                    //    V

int weapon_preferences[2][NUMWEAPONS + 1] = {
  {6, 9, 4, 3, 2, 8, 5, 7, 1, 0},  // !compatibility preferences
  {6, 9, 4, 3, 2, 8, 5, 7, 1, 0},  //  compatibility preferences
};

// PL_SwitchWeapon checks current ammo levels and gives you the
// most preferred weapon with ammo. It will not pick the currently
// raised weapon. When called from PL_CheckAmmo this won't matter,
// because the raised weapon has no ammo anyway. When called from
// G_BuildTiccmd you want to toggle to a different weapon regardless.

int PL_SwitchWeapon(player_t *player) {
  int *prefer = weapon_preferences[demo_compatibility != 0]; // killough 3/22/98
  int currentweapon = player->readyweapon;
  int newweapon = currentweapon;
  int i = NUMWEAPONS+1;   // killough 5/2/98

  // killough 2/8/98: follow preferences and fix BFG/SSG bugs

  do {
    switch (*prefer++) {
      case 1:
        if (!player->powers[pw_strength]) {    // allow chainsaw override
          break;
        }
      case 0:
        newweapon = wp_fist;
      break;
      case 2:
        if (player->ammo[am_clip]) {
          newweapon = wp_pistol;
        }
      break;
      case 3:
        if (player->weaponowned[wp_shotgun] && player->ammo[am_shell]) {
          newweapon = wp_shotgun;
        }
      break;
      case 4:
        if (player->weaponowned[wp_chaingun] && player->ammo[am_clip]) {
          newweapon = wp_chaingun;
        }
      break;
      case 5:
        if (player->weaponowned[wp_missile] && player->ammo[am_misl]) {
          newweapon = wp_missile;
        }
      break;
      case 6:
        if (player->weaponowned[wp_plasma] && player->ammo[am_cell] &&
                                              gamemode != shareware) {
          newweapon = wp_plasma;
        }
      break;
      case 7:
        if (player->weaponowned[wp_bfg] &&
            gamemode != shareware &&
            player->ammo[am_cell] >= (demo_compatibility ? 41 : 40)) {
          newweapon = wp_bfg;
        }
      break;
      case 8:
        if (player->weaponowned[wp_chainsaw])
          newweapon = wp_chainsaw;
      break;
      case 9:
        if (player->weaponowned[wp_supershotgun] &&
            gamemode == commercial &&
            player->ammo[am_shell] >= (demo_compatibility ? 3 : 2)) {
          newweapon = wp_supershotgun;
        }
      break;
    }
  } while (newweapon == currentweapon && --i); // killough 5/2/98

  return newweapon;
}

// killough 5/2/98: whether consoleplayer prefers weapon w1 over weapon w2.
int PL_WeaponPreferred(int w1, int w2) {
  return
    (weapon_preferences[0][0] != ++w2 && (weapon_preferences[0][0] == ++w1 ||
    (weapon_preferences[0][1] !=   w2 && (weapon_preferences[0][1] ==   w1 ||
    (weapon_preferences[0][2] !=   w2 && (weapon_preferences[0][2] ==   w1 ||
    (weapon_preferences[0][3] !=   w2 && (weapon_preferences[0][3] ==   w1 ||
    (weapon_preferences[0][4] !=   w2 && (weapon_preferences[0][4] ==   w1 ||
    (weapon_preferences[0][5] !=   w2 && (weapon_preferences[0][5] ==   w1 ||
    (weapon_preferences[0][6] !=   w2 && (weapon_preferences[0][6] ==   w1 ||
    (weapon_preferences[0][7] !=   w2 && (weapon_preferences[0][7] ==   w1
   ))))))))))))))));
}

//
// PL_CheckAmmo
// Returns true if there is enough ammo to shoot.
// If not, selects the next weapon to use.
// (only in demo_compatibility mode -- killough 3/22/98)
//

bool PL_CheckAmmo(player_t *player) {
  ammotype_t ammo  = weaponinfo[player->readyweapon].ammo;
  int        count = 1;  // Regular

  if (player->readyweapon == wp_bfg) { // Minimal amount for one shot varies.
    count = BFGCELLS;
  }
  else if (player->readyweapon == wp_supershotgun) { // Double barrel.
    count = 2;
  }

  // Some do not need ammunition anyway.
  // Return if current ammunition sufficient.

  if (ammo == am_noammo || player->ammo[ammo] >= count) {
    return true;
  }

  // Out of ammo, pick a weapon to change to.
  //
  // killough 3/22/98: for old demos we do the switch here and now;
  // for Boom games we cannot do this, and have different player
  // preferences across demos or networks, so we have to use the
  // G_BuildTiccmd() interface instead of making the switch here.

  if (demo_compatibility) {
    player->pendingweapon = PL_SwitchWeapon(player); // phares
    // Now set appropriate weapon overlay.
    PL_SetPsprite(player,ps_weapon,weaponinfo[player->readyweapon].downstate);
  }

  return false;
}

//
// PL_FireWeapon.
//

static void PL_FireWeapon(player_t *player) {
  bool unlagged_activated = false;
  statenum_t newstate;

  if (!PL_CheckAmmo(player)) {
    return;
  }

  if (SERVER) {
    unlagged_activated = SV_UnlagStart();
  }

  PL_SetMobjState(player->mo, S_PLAY_ATK1);

  if (unlagged_activated) {
    SV_UnlagEnd();
  }

  newstate = weaponinfo[player->readyweapon].atkstate;
  PL_SetPsprite(player, ps_weapon, newstate);
  P_NoiseAlert(player->mo, player->mo);
}

//
// PL_DropWeapon
// Player died, so put the weapon away.
//

void PL_DropWeapon(player_t *player) {
  PL_SetPsprite(player, ps_weapon, weaponinfo[player->readyweapon].downstate);
}

//
// A_WeaponReady
// The player can fire the weapon
// or change to another weapon at this time.
// Follows after getting weapon up,
// or after previous attack/fire sequence.
//

void A_WeaponReady(player_t *player, pspdef_t *psp) {
  CHECK_WEAPON_CODEPOINTER("A_WeaponReady", player);

  // get out of attack state
  if (player->mo->state == &states[S_PLAY_ATK1] ||
      player->mo->state == &states[S_PLAY_ATK2]) {
    P_SetMobjState(player->mo, S_PLAY);
  }

  if (player->readyweapon == wp_chainsaw && psp->state == &states[S_SAW]) {
    S_StartSound(player->mo, sfx_sawidl);
  }

  // check for change
  //  if player is dead, put the weapon away

  if (player->pendingweapon != wp_nochange || !player->health) {
    // change weapon (pending weapon should already be validated)
    statenum_t newstate = weaponinfo[player->readyweapon].downstate;
    PL_SetPsprite(player, ps_weapon, newstate);

    return;
  }

  // check for fire
  //  the missile launcher and bfg do not auto fire

  if (player->cmd.buttons & BT_ATTACK) {
    D_Msg(MSG_SOUND, "(%d) Player %td firing\n", gametic, player - players);

    if (!player->attackdown || (player->readyweapon != wp_missile &&
                                player->readyweapon != wp_bfg)) {
      player->attackdown = true;
      PL_FireWeapon(player);

      return;
    }
  }
  else {
    player->attackdown = false;
  }

  // bob the weapon based on movement speed
  int angle = (128 * leveltime) & FINEMASK;

  psp->sx = FRACUNIT + FixedMul(player->bob, finecosine[angle]);
  angle &= FINEANGLES / 2 - 1;
  psp->sy = WEAPONTOP + FixedMul(player->bob, finesine[angle]);
}

//
// A_ReFire
// The player can re-fire the weapon
// without lowering it entirely.
//

void A_ReFire(player_t *player, pspdef_t *psp) {
  CHECK_WEAPON_CODEPOINTER("A_ReFire", player);

  // check for fire
  //  (if a weaponchange is pending, let it go through instead)

  if ((player->cmd.buttons & BT_ATTACK) &&
      player->pendingweapon == wp_nochange &&
      player->health) {
    D_Msg(MSG_SOUND, "(%d) Player %td re-firing\n", gametic, player - players);
    player->refire++;
    PL_FireWeapon(player);
  }
  else {
    player->refire = 0;
    PL_CheckAmmo(player);
  }
}

void A_CheckReload(player_t *player, pspdef_t *psp) {
  CHECK_WEAPON_CODEPOINTER("A_CheckReload", player);

  if (!PL_CheckAmmo(player) && compatibility_level >= prboom_4_compatibility) {
    /* cph 2002/08/08 - In old Doom, PL_CheckAmmo would start the weapon
     * lowering immediately. This was lost in Boom when the weapon switching
     * logic was rewritten. But we must tell Doom that we don't need to
     * complete the reload frames for the weapon here. G_BuildTiccmd will set
     * ->pendingweapon for us later on. */
    PL_SetPsprite(
      player,
      ps_weapon,
      weaponinfo[player->readyweapon].downstate
    );
  }
}

//
// A_Lower
// Lowers current weapon,
//  and changes weapon at bottom.
//

void A_Lower(player_t *player, pspdef_t *psp) {
  CHECK_WEAPON_CODEPOINTER("A_Lower", player);

  psp->sy += LOWERSPEED;

  // Is already down.
  if (psp->sy < WEAPONBOTTOM) {
    return;
  }

  // Player is dead.
  if (player->playerstate == PST_DEAD) {
    psp->sy = WEAPONBOTTOM;
    return;      // don't bring weapon back up
  }

  // The old weapon has been lowered off the screen,
  // so change the weapon and start raising it

  if (!player->health) {
    // Player is dead, so keep the weapon off screen.
    PL_SetPsprite(player,  ps_weapon, S_NULL);
    return;
  }

  // haleyjd 03/28/10: do not assume pendingweapon is valid
  // e6y: probably for future complevels
  // if(player->pendingweapon < NUMWEAPONS)
  player->readyweapon = player->pendingweapon;

  PL_BringUpWeapon(player);
}

//
// A_Raise
//

void A_Raise(player_t *player, pspdef_t *psp) {
  statenum_t newstate;

  CHECK_WEAPON_CODEPOINTER("A_Raise", player);

  psp->sy -= RAISESPEED;

  if (psp->sy > WEAPONTOP)
    return;

  psp->sy = WEAPONTOP;

  // The weapon has been raised all the way,
  //  so change to the ready state.

  newstate = weaponinfo[player->readyweapon].readystate;

  PL_SetPsprite(player, ps_weapon, newstate);
}


// Weapons now recoil, amount depending on the weapon.              // phares
//                                                                  //   |
// The PL_SetPsprite call in each of the weapon firing routines     //   V
// was moved here so the recoil could be synched with the
// muzzle flash, rather than the pressing of the trigger.
// The BFG delay caused this to be necessary.

static void A_FireSomething(player_t* player,int adder) {
  PL_SetPsprite(
    player, ps_flash, weaponinfo[player->readyweapon].flashstate + adder
  );

  // killough 3/27/98: prevent recoil in no-clipping mode
  if (!(player->mo->flags & MF_NOCLIP)) {
    if (!compatibility && weapon_recoil) {
      PL_Thrust(
        player,
        ANG180 + player->mo->angle,
        2048 * recoil_values[player->readyweapon]
      );
    }                                                             //   ^
  }                                                               //   |
}                                                                 // phares

//
// A_GunFlash
//

void A_GunFlash(player_t *player, pspdef_t *psp) {
  CHECK_WEAPON_CODEPOINTER("A_GunFlash", player);

  PL_SetMobjState(player->mo, S_PLAY_ATK2);

  A_FireSomething(player,0);                                      // phares
}

//
// WEAPON ATTACKS
//

//
// A_Punch
//

void A_Punch(player_t *player, pspdef_t *psp) {
  angle_t angle;
  int t, slope, damage;

  CHECK_WEAPON_CODEPOINTER("A_Punch", player);

  damage = (P_Random(pr_punch) % 10 + 1) << 1;

  if (player->powers[pw_strength]) {
    damage *= 10;
  }

  angle = player->mo->angle;

  // killough 5/5/98: remove dependence on order of evaluation:
  t = P_Random(pr_punchangle);
  angle += (t - P_Random(pr_punchangle)) << 18;

  /* killough 8/2/98: make autoaiming prefer enemies */
  if (!mbf_features || (slope = P_AimLineAttack(player->mo,
                                                angle,
                                                MELEERANGE,
                                                MF_FRIEND), !linetarget)) {
    slope = P_AimLineAttack(player->mo, angle, MELEERANGE, 0);
  }

  P_LineAttack(player->mo, angle, MELEERANGE, slope, damage);

  if (!linetarget) {
    return;
  }

  S_StartSound(player->mo, sfx_punch);

  // turn to face target
  player->mo->angle = R_PointToAngle2(
    player->mo->x, player->mo->y, linetarget->x, linetarget->y
  );
  R_SmoothPlaying_Reset(player); // e6y
}

//
// A_Saw
//

void A_Saw(player_t *player, pspdef_t *psp) {
  int slope, damage;
  angle_t angle;
  int t;

  CHECK_WEAPON_CODEPOINTER("A_Saw", player);

  damage = 2 * (P_Random(pr_saw) % 10 + 1);
  angle = player->mo->angle;
  // killough 5/5/98: remove dependence on order of evaluation:
  t = P_Random(pr_saw);
  angle += (t - P_Random(pr_saw)) << 18;

  /* Use meleerange + 1 so that the puff doesn't skip the flash
   * killough 8/2/98: make autoaiming prefer enemies */
  if (!mbf_features || (slope = P_AimLineAttack(player->mo,
                                                angle,
                                                MELEERANGE + 1,
                                                MF_FRIEND), !linetarget)) {
    slope = P_AimLineAttack(player->mo, angle, MELEERANGE + 1, 0);
  }

  P_LineAttack(player->mo, angle, MELEERANGE + 1, slope, damage);

  if (!linetarget) {
    S_StartSound(player->mo, sfx_sawful);
    return;
  }

  S_StartSound(player->mo, sfx_sawhit);

  // turn to face target
  angle = R_PointToAngle2(
    player->mo->x, player->mo->y, linetarget->x, linetarget->y
  );

  if (angle - player->mo->angle > ANG180) {
    if (angle - player->mo->angle < -ANG90 / 20) {
      player->mo->angle = angle + ANG90 / 21;
    }
    else {
      player->mo->angle -= ANG90 / 20;
    }
  }
  else {
    if (angle - player->mo->angle > ANG90 / 20) {
      player->mo->angle = angle - ANG90 / 21;
    }
    else {
      player->mo->angle += ANG90 / 20;
    }
  }

  player->mo->flags |= MF_JUSTATTACKED;
  R_SmoothPlaying_Reset(player); // e6y
}

//
// A_FireMissile
//

void A_FireMissile(player_t *player, pspdef_t *psp) {
  CHECK_WEAPON_CODEPOINTER("A_FireMissile", player);

  player->ammo[weaponinfo[player->readyweapon].ammo]--;
  P_SpawnPlayerMissile(player->mo, MT_ROCKET);
}

//
// A_FireBFG
//

void A_FireBFG(player_t *player, pspdef_t *psp) {
  CHECK_WEAPON_CODEPOINTER("A_FireBFG", player);

  player->ammo[weaponinfo[player->readyweapon].ammo] -= BFGCELLS;
  P_SpawnPlayerMissile(player->mo, MT_BFG);
}

//
// A_FireOldBFG
//
// This function emulates Doom's Pre-Beta BFG
// By Lee Killough 6/6/98, 7/11/98, 7/19/98, 8/20/98
//
// This code may not be used in other mods without appropriate credit given.
// Code leeches will be telefragged.

void A_FireOldBFG(player_t *player, pspdef_t *psp) {
  int type = MT_PLASMA1;

  if (compatibility_level < mbf_compatibility)
    return;

  CHECK_WEAPON_CODEPOINTER("A_FireOldBFG", player);

  if (weapon_recoil && !(player->mo->flags & MF_NOCLIP)) {
    PL_Thrust(
      player,
      ANG180 + player->mo->angle,
      512 * recoil_values[wp_plasma]
    );
  }

  player->ammo[weaponinfo[player->readyweapon].ammo]--;

  player->extralight = 2;

  do {
    mobj_t *th, *mo = player->mo;
    angle_t an = mo->angle;
    angle_t an1 = ((P_Random(pr_bfg) & 127) - 64) * (ANG90 / 768) + an;
    angle_t an2 = ((P_Random(pr_bfg) & 127) - 64) * (ANG90 / 640) + ANG90;
    extern int autoaim;

    if (autoaim/* || !beta_emulation*/) {
      // killough 8/2/98: make autoaiming prefer enemies
      uint64_t mask = mbf_features ? MF_FRIEND : 0;
      fixed_t slope;
      do {
        slope = P_AimLineAttack(mo, an, 16*64*FRACUNIT, mask);
        if (!linetarget) {
          slope = P_AimLineAttack(mo, an += 1<<26, 16*64*FRACUNIT, mask);
        }
        if (!linetarget) {
          slope = P_AimLineAttack(mo, an -= 2<<26, 16*64*FRACUNIT, mask);
        }
        if (!linetarget) {
          slope = 0, an = mo->angle;
        }
      } while (mask && (mask = 0, !linetarget)); // killough 8/2/98
      an1 += an - mo->angle;
      an2 += tantoangle[slope >> DBITS];
    }

    th = P_SpawnMobj(
      mo->x, mo->y, mo->z + 62 * FRACUNIT - player->psprites[ps_weapon].sy, type
    );
    P_SetTarget(&th->target, mo);
    th->angle = an1;
    th->momx = finecosine[an1 >> ANGLETOFINESHIFT] * 25;
    th->momy = finesine[an1 >> ANGLETOFINESHIFT] * 25;
    th->momz = finetangent[an2 >> ANGLETOFINESHIFT] * 25;
    P_CheckMissileSpawn(th);
  } while ((type != MT_PLASMA2) && (type = MT_PLASMA2)); //killough: obfuscated!
}

//
// A_FirePlasma
//

void A_FirePlasma(player_t *player, pspdef_t *psp) {
  CHECK_WEAPON_CODEPOINTER("A_FirePlasma", player);

  player->ammo[weaponinfo[player->readyweapon].ammo]--;

  A_FireSomething(player, P_Random(pr_plasma) & 1); // phares
  P_SpawnPlayerMissile(player->mo, MT_PLASMA);
}

//
// P_BulletSlope
// Sets a slope so a near miss is at aproximately
// the height of the intended target
//

static void P_BulletSlope(mobj_t *mo) {
  angle_t an = mo->angle;    // see which target is to be aimed at
  int funit = 16 * 64 * FRACUNIT;

  /* killough 8/2/98: make autoaiming prefer enemies */
  uint64_t mask = mbf_features ? MF_FRIEND : 0;

  do {
    bulletslope = P_AimLineAttack(mo, an, 16 * 64 * FRACUNIT, mask);
    if (!linetarget) {
      bulletslope = P_AimLineAttack(mo, an += 1 << 26, funit, mask);
    }
    if (!linetarget) {
      bulletslope = P_AimLineAttack(mo, an -= 2 << 26, funit, mask);
    }
  } while (mask && (mask = 0, !linetarget)); /* killough 8/2/98 */
}

//
// P_GunShot
//

static void P_GunShot(mobj_t *mo, bool accurate) {
  int damage = 5 * (P_Random(pr_gunshot) % 3 + 1);
  angle_t angle = mo->angle;

  if (!accurate) {  // killough 5/5/98: remove dependence on order of evaluation:
    int t = P_Random(pr_misfire);

    angle += (t - P_Random(pr_misfire)) << 18;
  }

  P_LineAttack(mo, angle, MISSILERANGE, bulletslope, damage);
}

//
// A_FirePistol
//

void A_FirePistol(player_t *player, pspdef_t *psp) {
  CHECK_WEAPON_CODEPOINTER("A_FirePistol", player);

  S_StartSound(player->mo, sfx_pistol);

  P_SetMobjState(player->mo, S_PLAY_ATK2);
  player->ammo[weaponinfo[player->readyweapon].ammo]--;

  A_FireSomething(player,0);                                      // phares
  P_BulletSlope(player->mo);
  P_GunShot(player->mo, !player->refire);
}

//
// A_FireShotgun
//

void A_FireShotgun(player_t *player, pspdef_t *psp) {
  int i;

  CHECK_WEAPON_CODEPOINTER("A_FireShotgun", player);

  S_StartSound(player->mo, sfx_shotgn);
  P_SetMobjState(player->mo, S_PLAY_ATK2);

  player->ammo[weaponinfo[player->readyweapon].ammo]--;

  A_FireSomething(player,0);                                      // phares

  P_BulletSlope(player->mo);

  for (i = 0; i < 7; i++) {
    P_GunShot(player->mo, false);
  }
}

//
// A_FireShotgun2
//

void A_FireShotgun2(player_t *player, pspdef_t *psp) {
  int i;

  CHECK_WEAPON_CODEPOINTER("A_FireShotgun2", player);

  S_StartSound(player->mo, sfx_dshtgn);
  P_SetMobjState(player->mo, S_PLAY_ATK2);
  player->ammo[weaponinfo[player->readyweapon].ammo] -= 2;

  A_FireSomething(player,0);                                      // phares

  P_BulletSlope(player->mo);

  for (i = 0; i < 20; i++) {
    int damage = 5 * (P_Random(pr_shotgun) % 3 + 1);
    angle_t angle = player->mo->angle;
    // killough 5/5/98: remove dependence on order of evaluation:
    int t = P_Random(pr_shotgun);

    angle += (t - P_Random(pr_shotgun)) << 19;

    t = P_Random(pr_shotgun);

    P_LineAttack(
      player->mo,
      angle,
      MISSILERANGE,
      bulletslope + ((t - P_Random(pr_shotgun)) << 5),
      damage
    );
  }
}

//
// A_FireCGun
//

void A_FireCGun(player_t *player, pspdef_t *psp) {
  CHECK_WEAPON_CODEPOINTER("A_FireCGun", player);

  if (player->ammo[weaponinfo[player->readyweapon].ammo] || comp[comp_sound])
    S_StartSound(player->mo, sfx_pistol);

  if (!player->ammo[weaponinfo[player->readyweapon].ammo])
    return;

  P_SetMobjState(player->mo, S_PLAY_ATK2);
  player->ammo[weaponinfo[player->readyweapon].ammo]--;

  A_FireSomething(player,psp->state - &states[S_CHAIN1]);           // phares
  P_BulletSlope(player->mo);
  P_GunShot(player->mo, !player->refire);
}

void A_Light0(player_t *player, pspdef_t *psp) {
  CHECK_WEAPON_CODEPOINTER("A_Light0", player);

  player->extralight = 0;
}

void A_Light1 (player_t *player, pspdef_t *psp) {
  CHECK_WEAPON_CODEPOINTER("A_Light1", player);

  player->extralight = 1;
}

void A_Light2 (player_t *player, pspdef_t *psp) {
  CHECK_WEAPON_CODEPOINTER("A_Light2", player);

  player->extralight = 2;
}

//
// A_BFGSpray
// Spawn a BFG explosion on every monster in view
//

void A_BFGSpray(mobj_t *mo) {
  int i;

  for (i = 0 ; i < 40 ; i++) { // offset angles from its attack angle
    int j;
    int damage;
    angle_t an = mo->angle - ANG90 / 2 + ANG90 / 40 * i;

    // mo->target is the originator (player) of the missile

    // killough 8/2/98: make autoaiming prefer enemies
    if (!mbf_features ||
       (P_AimLineAttack(mo->target, an, 16 * 64 * FRACUNIT, MF_FRIEND),
       !linetarget)) {
      P_AimLineAttack(mo->target, an, 16 * 64 * FRACUNIT, 0);
    }

    if (!linetarget) {
      continue;
    }

    P_SpawnMobj(
      linetarget->x,
      linetarget->y,
      linetarget->z + (linetarget->height >> 2),
      MT_EXTRABFG
    );

    for (damage = j = 0; j < 15; j++) {
      damage += (P_Random(pr_bfg) & 7) + 1;
    }

    P_DamageMobj(linetarget, mo->target, mo->target, damage);
  }
}

//
// A_BFGsound
//

void A_BFGsound(player_t *player, pspdef_t *psp) {
  CHECK_WEAPON_CODEPOINTER("A_BFGsound", player);

  S_StartSound(player->mo, sfx_bfg);
}

//
// PL_SetupPsprites
// Called at start of level for each player.
//

void PL_SetupPsprites(player_t *player) {
  // remove all psprites
  for (int i = 0; i < NUMPSPRITES; i++) {
    player->psprites[i].state = NULL;
  }

  // spawn the gun
  player->pendingweapon = player->readyweapon;
  P_BringUpWeapon(player);
}

//
// P_MovePsprites
// Called every tic by player thinking routine.
//

void PL_MovePsprites(player_t *player) {
  pspdef_t *psp = player->psprites;

  // a null state means not active
  // drop tic count and possibly change state
  // a -1 tic count never changes

  for (int i = 0; i < NUMPSPRITES; i++, psp++) {
    if (psp->state && psp->tics != -1 && !--psp->tics) {
      P_SetPsprite(player, i, psp->state->nextstate);
    }
  }

  player->psprites[ps_flash].sx = player->psprites[ps_weapon].sx;
  player->psprites[ps_flash].sy = player->psprites[ps_weapon].sy;
}

/* vi: set et ts=2 sw=2: */
