
#include "p_arms.h"

#include "ObjManager.h"
#include "ai/weapons/whimstar.h"
#include "autogen/sprites.h"
#include "caret.h"
#include "common/misc.h"
#include "console.h"
#include "game.h"
#include "input.h"
#include "nx.h"
#include "player.h"
#include "playerstats.h"
#include "sound/SoundManager.h"
#include "statusbar.h"

static int empty_timer = 0;

struct BulletInfo
{
  int sprite;            // sprite to use
  int level;             // specify what level weapon is at when it fires this shot type
  int frame;             // specify which frame within sprite
  uint8_t makes_star;    // 1=make star effect, 2=make star but add x inertia to position
  int timetolive;        // shot range
  int damage;            // damage dealt per tick of contact with enemy
  int speed;             // speed of shot
  uint8_t manualsetup;   // 1= no auto setup at all, 2= don't use separate vert sprite
  NXE::Sound::SFX sound; // specify firing sound
};

BulletInfo bullet_table[] = {
    //		sprite			  lvl  frm st ttl dmg spd  manset      sound
    {SPR_SHOT_POLARSTAR, 0, 0, 1, 8, 1, 0x1000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L1_2},   // polarstar l1
    {SPR_SHOT_POLARSTAR, 1, 1, 1, 12, 2, 0x1000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L1_2},  // polarstar l2
    {SPR_SHOT_POLARSTAR_L3, 2, 0, 1, 16, 4, 0x1000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L3}, // polarstar l3

    {SPR_SHOT_MGUN_L1, 0, 0, 1, 20, 2, 0x1000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L1_2}, // mgun l1

    {SPR_SHOT_MGUN_L2, 1, 0, 1, 20, 4, 0x1000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L1_2}, // mgun l2, white piece
    {SPR_SHOT_MGUN_L2, 1, 1, 0, 21, 0, 0x1000, 0, NXE::Sound::SFX::SND_NULL},            // mgun l2, blue piece
    {SPR_SHOT_MGUN_L2, 1, 2, 0, 22, 0, 0x1000, 0, NXE::Sound::SFX::SND_NULL},            // mgun l2, dark piece

    {SPR_SHOT_MGUN_L3LEAD, 2, 0, 1, 20, 6, 0x1000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L3}, // mgun l3
    {SPR_SHOT_MGUN_L3TAIL, 2, 0, 0, 21, 0, 0x1000, 0, NXE::Sound::SFX::SND_NULL},          // the very long...
    {SPR_SHOT_MGUN_L3TAIL, 2, 1, 0, 22, 0, 0x1000, 0, NXE::Sound::SFX::SND_NULL},          // ...4 piece trail...
    {SPR_SHOT_MGUN_L3TAIL, 2, 2, 0, 23, 0, 0x1000, 0, NXE::Sound::SFX::SND_NULL},          // ...of the level 3...
    {SPR_SHOT_MGUN_L3TAIL, 2, 3, 0, 24, 0, 0x1000, 0, NXE::Sound::SFX::SND_NULL},          // ...machine gun

    // damage for missiles is set inside missile.cpp
    {SPR_SHOT_MISSILE1, 0, 0, 1, 50, 0, 0x0000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L1_2}, // missile level 1
    {SPR_SHOT_MISSILE2, 1, 0, 1, 65, 0, 0x0000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L1_2}, // missile level 2
    {SPR_SHOT_MISSILE3, 2, 0, 1, 90, 0, 0x0000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L1_2}, // missile level 3

    {SPR_SHOT_SUPERMISSILE13, 0, 0, 1, 30, 0, 0x0000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L1_2}, // supermissile l1
    {SPR_SHOT_SUPERMISSILE2, 1, 0, 1, 40, 0, 0x0000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L1_2},  // supermissile l2
    {SPR_SHOT_SUPERMISSILE13, 2, 0, 1, 40, 0, 0x0000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L1_2}, // supermissile l3

    // damages are doubled because fireball can hit twice before dissipating
    {SPR_SHOT_FIREBALL1, 0, 0, 1, 100, 2, 0x0000, 1, NXE::Sound::SFX::SND_FIREBALL},  // fireball l1
    {SPR_SHOT_FIREBALL23, 1, 0, 1, 100, 3, 0x0000, 1, NXE::Sound::SFX::SND_FIREBALL}, // fireball l2
    {SPR_SHOT_FIREBALL23, 2, 0, 1, 100, 3, 0x0000, 1, NXE::Sound::SFX::SND_FIREBALL}, // fireball l3

    {SPR_SHOT_BLADE_L1, 0, 0, 0, 29, 15, 0x800, 0, NXE::Sound::SFX::SND_FIREBALL}, // Blade L1
    {SPR_SHOT_BLADE_L2, 1, 0, 0, 17, 6, 0x800, 0, NXE::Sound::SFX::SND_FIREBALL},  // Blade L2
    {SPR_SHOT_BLADE_L3, 2, 0, 0, 30, 1, 0x800, 0, NXE::Sound::SFX::SND_FIREBALL},  // Blade L3

    {SPR_SHOT_SNAKE_L1, 0, 0, 1, 20, 4, 0x600, 2, NXE::Sound::SFX::SND_SNAKE_FIRE},   // Snake L1
    {SPR_SHOT_FIREBALL23, 1, 0, 1, 23, 6, 0x200, 2, NXE::Sound::SFX::SND_SNAKE_FIRE}, // Snake L2
    {SPR_SHOT_FIREBALL23, 2, 0, 1, 30, 8, 0x200, 2, NXE::Sound::SFX::SND_SNAKE_FIRE}, // Snake L3

    {SPR_SHOT_NEMESIS_L1, 0, 0, 2, 20, 4, 0x1000, 0, NXE::Sound::SFX::SND_NEMESIS_FIRE},
    {SPR_SHOT_NEMESIS_L2, 1, 0, 2, 20, 4, 0x1000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L3},
    {SPR_SHOT_NEMESIS_L3, 2, 0, 2, 20, 1, 0x555, 0, NXE::Sound::SFX::SND_SPUR_CHARGE_2}, // 1/3 speed

    {SPR_SHOT_BUBBLER_L1, 0, 0, 1, 40, 1, 0x600, 2, NXE::Sound::SFX::SND_BUBBLER_FIRE},
    {SPR_SHOT_BUBBLER_L2, 1, 0, 1, 60, 2, 0x600, 2, NXE::Sound::SFX::SND_BUBBLER_FIRE},
    {SPR_SHOT_BUBBLER_L3, 2, 0, 1, 100, 2, 0x600, 2, NXE::Sound::SFX::SND_BUBBLER_FIRE},

    // Spur also messes with it's damage at runtime; see spur.cpp for details.
    {SPR_SHOT_POLARSTAR, 0, 0, 1, 30, 4, 0x1000, 0, NXE::Sound::SFX::SND_SPUR_FIRE_1},
    {SPR_SHOT_POLARSTAR, 1, 1, 1, 30, 8, 0x1000, 0, NXE::Sound::SFX::SND_SPUR_FIRE_2},
    {SPR_SHOT_POLARSTAR_L3, 2, 0, 0, 30, 12, 0x1000, 0, NXE::Sound::SFX::SND_SPUR_FIRE_3},

    // Curly's Nemesis from Hell (OBJ_CURLY_CARRIED_SHOOTING)
    {SPR_SHOT_NEMESIS_L1, 0, 0, 1, 20, 4, 0x1000, 0, NXE::Sound::SFX::SND_NEMESIS_FIRE},

    {0, 0, 0, 0, 0, 0, 0, 0, NXE::Sound::SFX::SND_NULL}};

// resets anything like charging states etc on player re-init (Player::Init)
void PResetWeapons()
{
  Weapon *spur      = &player->weapons[WPN_SPUR];
  spur->chargetimer = 0;
  spur->level       = 0;
  spur->xp          = 0;

  init_whimstar(&player->whimstar);
}

static bool can_fire_spur(void)
{
  if (CountObjectsOfType(OBJ_SPUR_TRAIL))
    return false;

  return true;
}

// returns true if the current weapon has full xp at level 3 (is showing "Max")
static bool IsWeaponMaxed(void)
{
  Weapon *wpn = &player->weapons[player->curWeapon];
  return (wpn->level == 2) && (wpn->xp == wpn->max_xp[2]);
}

// fire a basic, single bullet
static Object *FireSimpleBullet(int otype, int btype, int xoff = 0, int yoff = 0)
{
  int x, y, dir;

  // get location to fire from
  GetPlayerShootPoint(&x, &y);
  x += xoff;
  y += yoff;

  // create the shot
  Object *shot = CreateBullet(0, 0, otype);

  // set up the shot
  if (player->look)
    dir = player->look;
  else
    dir = player->dir;

  SetupBullet(shot, x, y, btype, dir);
  return shot;
}

// fires a missile type bullet at an offset from the exact center of the player
static Object *FireMissileBullet(int otype, int btype, int xoff = 0, int yoff = 0, int accel = 0, bool wiggle = false)
{
  int x, y, dir;

  // create the shot
  Object *shot = CreateBullet(0, 0, otype);

  // set up the shot
  if (player->look)
    dir = player->look;
  else
    dir = player->dir;

  // for shot star effect
  GetPlayerShootPoint(&x, &y);
  SetupBullet(shot, x, y, btype, dir);

  x = player->CenterX();
  y = player->CenterY(); // + 4*CSFI;

  shot->SetCenterX(x + xoff);
  shot->SetCenterY(y + yoff);

  if (player->look)
  {
    shot->yinertia = random(-512, 512);
    if (wiggle)
      shot->xinertia = (shot->x <= player->x) ? -256 : 256;
  }
  else
  {
    shot->xinertia = random(-512, 512);
    if (wiggle)
      shot->yinertia = (shot->y <= player->y) ? -256 : 256;
  }
  shot->shot.accel = accel;
  return shot;
}

// fires a bullet at an offset from the exact center of the player's shoot point.
// FireSimpleBullet can do this too-- but it's xoff/yoff is absolute. This function
// takes a parameter for when you are shooting right and extrapolates out the other
// directions from that. ALSO, xoff/yoff on FireSimpleBullet moves the star;
// this function does not.
static Object *FireSimpleBulletOffset(int otype, int btype, int xoff, int yoff)
{
  int dir;

  if (player->look)
    dir = player->look;
  else
    dir = player->dir;

  switch (dir)
  {
    case RIGHT:
      break; // already in format for RIGHT frame
    case LEFT:
      xoff = -xoff;
      break;
    case UP:
      SWAP(xoff, yoff);
      yoff = -yoff;
      break;
    case DOWN:
      SWAP(xoff, yoff);
      break;
  }

  Object *shot = FireSimpleBullet(otype, btype);
  shot->x += xoff;
  shot->y += yoff;

  return shot;
}

// fires and handles charged shots
static void PHandleSpur(void)
{
  static const int FLASH_TIME = 10;
  Weapon *spur                = &player->weapons[WPN_SPUR];

  if (player->curWeapon != WPN_SPUR)
  {
    spur->level = 0;
    spur->xp    = 0;
    return;
  }

  if (pinputs[FIREKEY])
  {
    if (!IsWeaponMaxed())
    {
      int amt = (player->equipmask & EQUIP_TURBOCHARGE) ? 3 : 2;
      AddXP(amt, true);

      if (IsWeaponMaxed())
      {
        NXE::Sound::SoundManager::getInstance()->playSfx(NXE::Sound::SFX::SND_SPUR_MAXED);
      }
      else
      {
        spur->chargetimer++;
        if (spur->chargetimer / 2 & 1)
        {
          NXE::Sound::SoundManager::getInstance()->playSfx(
              (NXE::Sound::SFX)((int)NXE::Sound::SFX::SND_SPUR_CHARGE_1 + spur->level));
        }
      }
    }
    else
    {
      // keep flashing even once at max
      int amt = (player->equipmask & EQUIP_TURBOCHARGE) ? 3 : 2;
      AddXP(amt, true);
    }
  }
  else
  {
    if (spur->chargetimer)
    {
      if (spur->level > 0 && can_fire_spur())
      {
        int level = IsWeaponMaxed() ? 2 : (spur->level - 1);
        FireSimpleBulletOffset(OBJ_SPUR_SHOT, B_SPUR_L1 + level, -4 * CSFI, 0);
      }

      spur->chargetimer = 0;
    }

    spur->level = 0;
    spur->xp    = 0;
  }

  if (statusbar.xpflashcount > FLASH_TIME)
    statusbar.xpflashcount = FLASH_TIME;
}

void PDoWeapons(void)
{
  run_whimstar(&player->whimstar);

  if (player->inputs_locked)
    return; // should prevent from firing in cutscenes

  if (justpushed(PREVWPNKEY))
    stat_PrevWeapon();
  if (justpushed(NEXTWPNKEY))
    stat_NextWeapon();

  // firing weapon
  if (pinputs[FIREKEY])
  {
    FireWeapon();
    RunWeapon(true);
  }
  else
  {
    player->auto_fire_limit = 6;
    RunWeapon(false);
  }

  PHandleSpur();

  if (empty_timer)
    empty_timer--;
}

/*
void c------------------------------() {}
*/

// fire the missile launcher.
// level: 0 - 2: weapon level from 1 - 3
// is_super: bool: true if the player is firing the Super Missile Launcher
static void PFireMissile(int level, bool is_super)
{
  int xoff, yoff;

  int object_type = (!is_super) ? OBJ_MISSILE_SHOT : OBJ_SUPERMISSILE_SHOT;

  // can only fire one missile at once on L1,
  // two missiles on L2, and two sets of three missiles on L3.
  static const uint8_t max_missiles_at_once[] = {1, 2, 6};
  if (CountObjectsOfType(object_type) >= max_missiles_at_once[level])
  {
    // give back the previously-decremented ammo so they don't lose it (hack)
    player->weapons[player->curWeapon].ammo++;
    return;
  }

  int bullet_type = (!is_super) ? B_MISSILE_L1 : B_SUPER_MISSILE_L1;
  bullet_type += level;

  // level 1 & 2 fires just one missile
  yoff = 1;
  xoff = (player->dir == RIGHT) ? 1 : -1;
  if (player->look)
  {
    yoff = (player->look == UP) ? -1 : 1;
    FireMissileBullet(object_type, bullet_type, CSFI * xoff, 8 * CSFI * yoff, (is_super) ? 512 : 128, (level == 2));
  }
  else
  {
    FireMissileBullet(object_type, bullet_type, 6 * CSFI * xoff, (level == 2) ? CSFI : 0, (is_super) ? 512 : 128,
                      (level == 2));
  }
  // lv3 fires 3 missiles that wiggle
  if (level == 2)
  {
    if (player->look)
    {
      yoff = (player->look == UP) ? -1 : 1;
      FireMissileBullet(object_type, bullet_type, 3 * CSFI * xoff, 0, (is_super) ? 256 : 64, true);
      FireMissileBullet(object_type, bullet_type, -3 * CSFI * xoff, 0, (is_super) ? 170 : 51, true);
    }
    else
    {
      FireMissileBullet(object_type, bullet_type, 0, -8 * CSFI, (is_super) ? 256 : 64, true);
      FireMissileBullet(object_type, bullet_type, -4 * CSFI * xoff, -CSFI, (is_super) ? 170 : 51, true);
    }
  }
}

/*
void c------------------------------() {}
*/

static void PFireFireball(int level)
{
  static const int object_types[] = {OBJ_FIREBALL1, OBJ_FIREBALL23, OBJ_FIREBALL23};
  static uint8_t max_fireballs[]  = {2, 3, 4};
  int count;

  count = (CountObjectsOfType(OBJ_FIREBALL1) + CountObjectsOfType(OBJ_FIREBALL23));
  if (count >= max_fireballs[level])
    return;

  // the 8px offset fires the shot just a tiny bit behind the player--
  // you can't see the difference but it makes the shot correctly bounce if
  // you shoot while flat up against a wall, instead of embedding the fireball
  // in the wall.
  Object *fb = FireSimpleBulletOffset(object_types[level], B_FIREBALL1 + level, -6 * CSFI, 0);
  fb->dir    = player->dir;
  fb->nxflags &= ~NXFLAG_NO_RESET_YINERTIA;

  switch (fb->shot.dir)
  {
    case LEFT:
      fb->xinertia = -0x400;
      break;
    case RIGHT:
      fb->xinertia = 0x400;
      break;

    case UP:
      fb->xinertia = player->xinertia + ((player->dir == RIGHT) ? 128 : -128);
      if (player->xinertia)
        fb->dir = (player->xinertia > 0) ? RIGHT : LEFT;
      fb->yinertia = -0x5ff;
      break;

    case DOWN:
      fb->xinertia = player->xinertia;
      if (player->xinertia)
        fb->dir = (player->xinertia > 0) ? RIGHT : LEFT;
      fb->yinertia = 0x5ff;
      break;
  }
}

static void PFireBlade(int level)
{
  int numblades = CountObjectsOfType(OBJ_BLADE12_SHOT) + CountObjectsOfType(OBJ_BLADE3_SHOT);
  if (numblades >= 1)
    return;

  int dir = (player->look) ? player->look : player->dir;

  int x = player->CenterX();
  int y = player->CenterY();

  if (level == 2)
  {
    if (dir == RIGHT || dir == LEFT)
    {
      y -= (3 * CSFI);
      x += (dir == LEFT) ? (3 * CSFI) : -(3 * CSFI);
    }
  }
  else
  {
    switch (dir)
    {
      case RIGHT:
        x -= (6 * CSFI);
        y -= (3 * CSFI);
        break;
      case LEFT:
        x += (6 * CSFI);
        y -= (3 * CSFI);
        break;
      case UP:
        y += (6 * CSFI);
        break;
      case DOWN:
        y -= (6 * CSFI);
        break;
    }
  }

  Object *shot = CreateObject(x, y, (level != 2) ? OBJ_BLADE12_SHOT : OBJ_BLADE3_SHOT);
  SetupBullet(shot, x, y, B_BLADE_L1 + level, dir);
}

/*
void c------------------------------() {}
*/

static void PFireSnake(int level)
{
  if (level == 2)
  {
    int count = (CountObjectsOfType(OBJ_SNAKE1_SHOT) + CountObjectsOfType(OBJ_SNAKE23_SHOT));

    if (count >= 4)
      return;
  }

  int object_type = (level == 0) ? OBJ_SNAKE1_SHOT : OBJ_SNAKE23_SHOT;
  FireSimpleBulletOffset(object_type, B_SNAKE_L1 + level, -5 * CSFI, 0);
}

static void PFireNemesis(int level)
{
  if (CountObjectsOfType(OBJ_NEMESIS_SHOT) >= 2)
    return;

  FireSimpleBullet(OBJ_NEMESIS_SHOT, B_NEMESIS_L1 + level);
}

static void PFireBubbler(int level)
{
  static const int max_bubbles[] = {4, 16, 16};

  int count = CountObjectsOfType(OBJ_BUBBLER12_SHOT) + CountObjectsOfType(OBJ_BUBBLER3_SHOT);

  if (count >= max_bubbles[level])
  {
    // give back the previously-decremented ammo so they don't lose it (hack)
    player->weapons[player->curWeapon].ammo++;
    return;
  }

  int objtype = (level != 2) ? OBJ_BUBBLER12_SHOT : OBJ_BUBBLER3_SHOT;
  FireSimpleBulletOffset(objtype, B_BUBBLER_L1 + level, -4 * CSFI, 0);
}

/*
void c------------------------------() {}
*/

// Spur fires an initial shot of Polar Star L3, then charges
// as long as key is down. Fires when key released.
// Released at L1: nothing
// Released at L2: thin beam
// Released at L3: dual beam
// Released at Max: thick beam
//
// Initial shot is not fired if key is held on a different weapon
// and then weapon is switched to spur.

// fires the regular Polar Star shot when you first push button
static void PFireSpur(void)
{
  if (can_fire_spur())
    FireSimpleBulletOffset(OBJ_POLAR_SHOT, B_PSTAR_L3, -4 * CSFI, 0);
}

static void PFirePolarStar(int level)
{
  if (CountObjectsOfType(OBJ_POLAR_SHOT) < 2)
  {
    int xoff;
    if (level == 2)
      xoff = -5 * CSFI;
    else
      xoff = -4 * CSFI;

    FireSimpleBulletOffset(OBJ_POLAR_SHOT, B_PSTAR_L1 + level, xoff, 0);
    rumble(0.2, 200);
  }
}

/*
void c------------------------------() {}
*/

// handles firing the Machine Gun
static void PFireMachineGun(int level)
{
  Object *shot;
  int x, y;

  int dir = (player->look) ? player->look : player->dir;

  if (level == 0)
  { // level 1 is real easy! no frickin' layers!!
    shot      = FireSimpleBullet(OBJ_POLAR_SHOT, B_MGUN_L1, 0, 0);
    shot->dir = dir;

    if (player->look)
      shot->xinertia = random(-0xAA, 0xAA);
    else
      shot->yinertia = random(-0xAA, 0xAA);
    rumble(0.2, 200);
  }
  else
  {
    // drop an OBJ_MGUN_SHOOTER object to fire the layers (trail) of the MGun blast.
    GetPlayerShootPoint(&x, &y);
    FireLevel23MGun(x, y, level, dir);
    rumble(0.3, 200);
  }

  // do machine-gun flying
  if (level == 2)
  {
    if (player->look == DOWN)
      PMgunFly(true);
    else if (player->look == UP)
      PMgunFly(false);
  }
}

// called when player is trying to fire the current weapon
// i.e. the fire button is down.
void FireWeapon(void)
{

  Weapon *curweapon = &player->weapons[player->curWeapon];
  int level         = curweapon->level;

  // check if we can fire
  if (curweapon->firerate[level] != 0)
  { // rapid/fully-auto fire
    if (++player->auto_fire_limit > curweapon->firerate[level])
    {
      player->auto_fire_limit = 0;
    }
    else
    {
      return;
    }
  }
  else
  { // else must push key for each shot
    if (lastpinputs[FIREKEY])
      return;
  }

  if (player->fire_limit)
    return;

  player->fire_limit = 4;

  // check if we have enough ammo
  if (curweapon->maxammo > 0 && curweapon->ammo <= 0)
  {
    NXE::Sound::SoundManager::getInstance()->playSfx(NXE::Sound::SFX::SND_GUN_CLICK);
    if (empty_timer <= 0)
    {
      effect(player->CenterX(), player->CenterY(), EFFECT_EMPTY);
      empty_timer = 50;
    }

    return;
  }

  // subtract ammo
  if (curweapon->ammo)
    curweapon->ammo--;

  // fire!!
  switch (player->curWeapon)
  {
    case WPN_NONE:
      break;

    case WPN_POLARSTAR:
      PFirePolarStar(level);
      break;

    case WPN_FIREBALL:
      PFireFireball(level);
      break;

    case WPN_MGUN:
      PFireMachineGun(level);
      break;

    case WPN_MISSILE:
    case WPN_SUPER_MISSILE:
      PFireMissile(level, (player->curWeapon == WPN_SUPER_MISSILE));
      break;

    case WPN_BLADE:
      PFireBlade(level);
      break;

    case WPN_SNAKE:
      PFireSnake(level);
      break;

    case WPN_NEMESIS:
      PFireNemesis(level);
      break;

    case WPN_BUBBLER:
      PFireBubbler(level);
      break;

    case WPN_SPUR:
      PFireSpur();
      break;

    default:
      console.Print("FireWeapon: cannot fire unimplemented weapon %d", player->curWeapon);
      NXE::Sound::SoundManager::getInstance()->playSfx(NXE::Sound::SFX::SND_BONK_HEAD);
      break;
  }
}

// "run" the current weapon.
// firing = 1 if fire key is currently down, and 0 if it is not.
void RunWeapon(bool firing)
{
  Weapon *curweapon = &player->weapons[player->curWeapon];
  int level         = curweapon->level;

  if (player->fire_limit) player->fire_limit--;

  // bubbler L1 has recharge but not rapid fire,
  // so it recharges even if the key is held down.
  if (firing && !curweapon->firerate[level] && lastpinputs[FIREKEY])
    firing = false;

  // recharge machine gun when it's not firing or it's not selected
  if ((curweapon->rechargerate[level]) && (curweapon->ammo < curweapon->maxammo) && !firing)
  {
    // start recharging ammo
    int rate = curweapon->rechargerate[level];
    if ((player->equipmask & EQUIP_TURBOCHARGE) && player->curWeapon == WPN_MGUN)
    {
      rate = 2;
    }

    // it's greater than OR EQUAL TO, so that we can have rate=0 be no recharge.
    // Otherwise there would be no value that recharges every frame.
    if (++curweapon->rechargetimer >= rate)
    {
      curweapon->rechargetimer = 0;
      curweapon->ammo++;
    }
  }

  for (int i = 0; i < WPN_COUNT; i++)
  {
    if (player->weapons[i].firetimer)
      player->weapons[i].firetimer--;

    if ((i != player->curWeapon) || (player->weapons[i].ammo >= player->weapons[i].maxammo) || firing)
    {
      player->weapons[i].rechargetimer = 0;
    }
  }
}

/*
void c------------------------------() {}
*/

// set up the specified bullet to be a shot of type btype
// (note: shared by Curly sand-zone boss)
void SetupBullet(Object *shot, int x, int y, int btype, int dir)
{
  const BulletInfo *info = &bullet_table[btype];

  shot->sprite      = info->sprite;
  shot->frame       = info->frame;
  shot->shot.ttl    = info->timetolive;
  shot->shot.damage = info->damage;
  shot->shot.level  = info->level;
  shot->shot.btype  = btype;
  shot->shot.dir    = dir;
  shot->nxflags |= NXFLAG_NO_RESET_YINERTIA;

  if (game.debug.infinite_damage)
    shot->shot.damage = 255;

  if (info->sound != NXE::Sound::SFX::SND_NULL)
    NXE::Sound::SoundManager::getInstance()->playSfx(info->sound);

  if (info->makes_star == 1)
    effect(x, y, EFFECT_STARPOOF);

  if (info->manualsetup != 1)
  {
    switch (dir)
    {
      case LEFT:
        shot->xinertia = -info->speed;
        shot->dir      = LEFT;
        break;

      case RIGHT:
        shot->xinertia = info->speed;
        shot->dir      = RIGHT;
        break;

      case UP:
        shot->yinertia = -info->speed;
        shot->dir      = RIGHT;
        if (info->manualsetup != 2)
        {
          shot->sprite++;
        }
        break;

      case DOWN:
        shot->yinertia = info->speed;
        shot->dir      = LEFT;
        if (info->manualsetup != 2)
        {
          shot->sprite++;
        }
        break;
    }

    if (info->makes_star == 2)
      effect(x + shot->xinertia / 2, y, EFFECT_STARPOOF);

    // have to do this because inertia will get applied later in the tick before the first
    // time it's drawn so it won't actually appear where we put it if we don't
    x -= shot->xinertia;
    y -= shot->yinertia;
  }

  // put shot center at [x,y],
  // this also centers it within starpoof
  shot->x = x - (shot->Width() / 2);
  shot->y = y - (shot->Height() / 2);
}

/*
void c------------------------------() {}
*/

// fire a level 2 or level 3 MGun blast from position x,y.
// Broken out here into a seperate sub so OBJ_CURLY_AI can use it also.
void FireLevel23MGun(int x, int y, int level, int dir)
{
  static const uint8_t no_layers[] = {1, 3, 5};
  static const int bultype_table[] = {0, B_MGUN_L2, B_MGUN_L3};
  Object *shot;

  // note: this relies on the player AI running before the entity AI...which it does...
  // so leave it that way, else he wouldn't actually fire for 1 additional frame
  shot = CreateObject(x, y, OBJ_MGUN_SPAWNER);

  shot->dir           = dir;
  shot->mgun.bultype  = bultype_table[level];
  shot->mgun.nlayers  = no_layers[level];
  shot->mgun.wave_amt = random(-0xAA, 0xAA);
  shot->invisible     = true;
}

// handles flying when shooting down using Machine Gun at Level 3
void PMgunFly(bool up)
{
  if (up)
  {
    if (player->yinertia > 0)
    {
      player->yinertia >>= 1;
    }

    if (player->yinertia > -0x400)
    {
      player->yinertia -= 0x200;
      if (player->yinertia < -0x400)
        player->yinertia = -0x400;
    }
  }
  else
  {
    player->yinertia += 0x100;
  }
}
