Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

767 lines (612 sloc) 19.615 kB
#include "nx.h"
#include "p_arms.fdh"
static Object *FireSimpleBullet(int otype, int btype, int xoff=0, int yoff=0);
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
uint8_t 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, SND_POLAR_STAR_L1_2, // polarstar l1
SPR_SHOT_POLARSTAR, 1, 1, 1, 12, 2, 0x1000, 0, SND_POLAR_STAR_L1_2, // polarstar l2
SPR_SHOT_POLARSTAR_L3, 2, 0, 1, 16, 4, 0x1000, 0, SND_POLAR_STAR_L3, // polarstar l3
SPR_SHOT_MGUN_L1, 0, 0, 1, 20, 2, 0x1000, 0, SND_POLAR_STAR_L1_2, // mgun l1
SPR_SHOT_MGUN_L2, 1, 0, 1, 20, 4, 0x1000, 0, SND_POLAR_STAR_L1_2, // mgun l2, white piece
SPR_SHOT_MGUN_L2, 1, 1, 0, 21, 0, 0x1000, 0, 0, // mgun l2, blue piece
SPR_SHOT_MGUN_L2, 1, 2, 0, 22, 0, 0x1000, 0, 0, // mgun l2, dark piece
SPR_SHOT_MGUN_L3LEAD, 2, 0, 1, 20, 6, 0x1000, 0, SND_POLAR_STAR_L3, // mgun l3
SPR_SHOT_MGUN_L3TAIL, 2, 0, 0, 21, 0, 0x1000, 0, 0, // the very long...
SPR_SHOT_MGUN_L3TAIL, 2, 1, 0, 22, 0, 0x1000, 0, 0, // ...4 piece trail...
SPR_SHOT_MGUN_L3TAIL, 2, 2, 0, 23, 0, 0x1000, 0, 0, // ...of the level 3...
SPR_SHOT_MGUN_L3TAIL, 2, 3, 0, 24, 0, 0x1000, 0, 0, // ...machine gun
// damage for missiles is set inside missile.cpp
SPR_SHOT_MISSILE1, 0, 0, 1, 50, 0, 0x0000, 0, SND_POLAR_STAR_L1_2, // missile level 1
SPR_SHOT_MISSILE2, 1, 0, 1, 65, 0, 0x0000, 0, SND_POLAR_STAR_L1_2, // missile level 2
SPR_SHOT_MISSILE3, 2, 0, 1, 90, 0, 0x0000, 0, SND_POLAR_STAR_L1_2, // missile level 3
SPR_SHOT_SUPERMISSILE13,0, 0, 1, 30, 0, 0x0000, 0, SND_POLAR_STAR_L1_2, // supermissile l1
SPR_SHOT_SUPERMISSILE2, 1, 0, 1, 40, 0, 0x0000, 0, SND_POLAR_STAR_L1_2, // supermissile l2
SPR_SHOT_SUPERMISSILE13,2, 0, 1, 40, 0, 0x0000, 0, 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, SND_FIREBALL, // fireball l1
SPR_SHOT_FIREBALL23, 1, 0, 1, 100, 3, 0x0000, 1, SND_FIREBALL, // fireball l2
SPR_SHOT_FIREBALL23, 2, 0, 1, 100, 3, 0x0000, 1, SND_FIREBALL, // fireball l3
SPR_SHOT_BLADE_L1, 0, 0, 0, 29, 15, 0x800, 0, SND_FIREBALL, // Blade L1
SPR_SHOT_BLADE_L2, 1, 0, 0, 17, 6, 0x800, 0, SND_FIREBALL, // Blade L2
SPR_SHOT_BLADE_L3, 2, 0, 0, 30, 1, 0x800, 0, SND_FIREBALL, // Blade L3
SPR_SHOT_SNAKE_L1, 0, 0, 1, 20, 4, 0x600, 2, SND_SNAKE_FIRE, // Snake L1
SPR_SHOT_FIREBALL23, 1, 0, 1, 23, 6, 0x200, 2, SND_SNAKE_FIRE, // Snake L2
SPR_SHOT_FIREBALL23, 2, 0, 1, 30, 8, 0x200, 2, SND_SNAKE_FIRE, // Snake L3
SPR_SHOT_NEMESIS_L1, 0, 0, 2, 20, 12, 0x1000, 0, SND_NEMESIS_FIRE,
SPR_SHOT_NEMESIS_L2, 1, 0, 2, 20, 6, 0x1000, 0, SND_POLAR_STAR_L3,
SPR_SHOT_NEMESIS_L3, 2, 0, 2, 20, 1, 0x555, 0, 0, // 1/3 speed
SPR_SHOT_BUBBLER_L1, 0, 0, 1, 40, 1, 0x600, 2, SND_BUBBLER_FIRE,
SPR_SHOT_BUBBLER_L2, 1, 0, 1, 60, 2, 0x600, 2, SND_BUBBLER_FIRE,
SPR_SHOT_BUBBLER_L3, 2, 0, 1, 100,2, 0x600, 2, 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, SND_SPUR_FIRE_1,
SPR_SHOT_POLARSTAR, 1, 1, 1, 30, 8, 0x1000, 0, SND_SPUR_FIRE_2,
SPR_SHOT_POLARSTAR_L3, 2, 0, 0, 30, 12, 0x1000, 0, SND_SPUR_FIRE_3,
// Curly's Nemesis from Hell (OBJ_CURLY_CARRIED_SHOOTING)
SPR_SHOT_NEMESIS_L1, 0, 0, 1, 20, 12, 0x1000, 0, SND_NEMESIS_FIRE,
0, 0, 0, 0, 0, 0, 0
};
// 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);
}
void PDoWeapons(void)
{
// switching weapons. have to check for inputs_frozen since justpushed
// reads inputs[] directly, not pinputs[].
if (!player->inputs_locked)
{
if (justpushed(PREVWPNKEY)) stat_PrevWeapon();
if (justpushed(NEXTWPNKEY)) stat_NextWeapon();
}
// firing weapon
if (pinputs[FIREKEY])
{
FireWeapon();
RunWeapon(true);
}
else
{
RunWeapon(false);
}
PHandleSpur();
run_whimstar(&player->whimstar);
if (empty_timer)
empty_timer--;
}
/*
void c------------------------------() {}
*/
// 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
// decremented in RunWeapon()
if (curweapon->firetimer)
{
return;
}
else
{
curweapon->firetimer = curweapon->firerate[level];
}
}
else
{ // else must push key for each shot
if (lastpinputs[FIREKEY])
return;
}
// check if we have enough ammo
if (curweapon->maxammo > 0 && curweapon->ammo <= 0)
{
sound(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);
sound(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;
// 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)
sound(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);
}
// fire a basic, single bullet
static Object *FireSimpleBullet(int otype, int btype, int xoff, int yoff)
{
int x, y, dir;
// get location to fire from
GetPlayerShootPoint(&x, &y);
x += xoff;
y += yoff;
// create the shot
Object *shot = CreateObject(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 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;
}
/*
void c------------------------------() {}
*/
static void PFirePolarStar(int level)
{
// at level 3 only two shots per screen permitted
if (level < 2 || CountObjectsOfType(OBJ_POLAR_SHOT) < 2)
{
int xoff;
if (level == 2) xoff = -5<<CSF; else xoff = -4<<CSF;
FireSimpleBulletOffset(OBJ_POLAR_SHOT, B_PSTAR_L1+level, xoff, 0);
}
}
/*
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);
}
else
{
// drop an OBJ_MGUN_SHOOTER object to fire the layers (trail) of the MGun blast.
GetPlayerShootPoint(&x, &y);
FireLevel23MGun(x, y, level, dir);
}
// do machine-gun flying
if (player->look==DOWN && level==2)
{
PMgunFly();
}
}
// 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 uchar 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(void)
{
if (player->yinertia > 0)
{
player->yinertia >>= 1;
}
if (player->yinertia > -0x400)
{
player->yinertia -= 0x200;
if (player->yinertia < -0x400) player->yinertia = -0x400;
}
}
/*
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)
{
Object *o;
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
FireSimpleBulletOffset(object_type, bullet_type, -4<<CSF, 0);
// level 3 fires three missiles, they wave, and are "offset",
// so if it's level 3 fire two more missiles.
if (level == 2)
{
// norm super
static const int recoil_upper[] = { 0x500, 0xd00 };
static const int recoil_lower[] = { 0x700, 0x600 };
if (player->look==DOWN || player->look==UP) { xoff = (4<<CSF); yoff = 0; }
else { yoff = (4<<CSF); xoff = 0; }
// this one is higher
o = FireSimpleBullet(object_type, bullet_type, -xoff, -yoff);
if (o->shot.dir==LEFT) o->xinertia = recoil_upper[is_super];
else if (o->shot.dir==RIGHT) o->xinertia = -recoil_upper[is_super];
else if (o->shot.dir==UP) o->yinertia = recoil_upper[is_super];
else o->yinertia = -recoil_upper[is_super];
// this one is lower
o = FireSimpleBullet(object_type, bullet_type, xoff, yoff);
if (o->shot.dir==LEFT) o->xinertia = recoil_lower[is_super];
else if (o->shot.dir==RIGHT) o->xinertia = -recoil_lower[is_super];
else if (o->shot.dir==UP) o->yinertia = recoil_lower[is_super];
else o->yinertia = -recoil_lower[is_super];
}
}
/*
void c------------------------------() {}
*/
static void PFireFireball(int level)
{
static const int object_types[] = { OBJ_FIREBALL1, OBJ_FIREBALL23, OBJ_FIREBALL23 };
static uchar 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, -8<<CSF, 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 << CSF);
x += (dir == LEFT) ? (3 << CSF) : -(3 << CSF);
}
}
else
{
switch(dir)
{
case RIGHT: x -= (6 << CSF); y -= (3 << CSF); break;
case LEFT: x += (6 << CSF); y -= (3 << CSF); break;
case UP: y += (6 << CSF); break;
case DOWN: y -= (6 << CSF); 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<<CSF, 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])
return;
int objtype = (level != 2) ? OBJ_BUBBLER12_SHOT : OBJ_BUBBLER3_SHOT;
FireSimpleBulletOffset(objtype, B_BUBBLER_L1+level, -4<<CSF, 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<<CSF, 0);
}
// 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())
{
sound(SND_SPUR_MAXED);
}
else
{
spur->chargetimer++;
if (spur->chargetimer & 2)
{
sound(SND_SPUR_CHARGE_1 + spur->level);
}
}
}
else
{ // keep flashing even once at max
statusbar.xpflashcount = FLASH_TIME;
if (player->equipmask & EQUIP_WHIMSTAR)
add_whimstar(&player->whimstar);
}
}
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<<CSF, 0);
}
spur->chargetimer = 0;
}
spur->level = 0;
spur->xp = 0;
}
if (statusbar.xpflashcount > FLASH_TIME)
statusbar.xpflashcount = FLASH_TIME;
}
static bool can_fire_spur(void)
{
if (CountObjectsOfType(OBJ_SPUR_SHOT))
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]);
}
Jump to Line
Something went wrong with that request. Please try again.