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