1 
2 #include "p_arms.h"
3 
4 #include "ObjManager.h"
5 #include "ai/weapons/whimstar.h"
6 #include "autogen/sprites.h"
7 #include "caret.h"
8 #include "common/misc.h"
9 #include "console.h"
10 #include "game.h"
11 #include "input.h"
12 #include "nx.h"
13 #include "player.h"
14 #include "playerstats.h"
15 #include "sound/SoundManager.h"
16 #include "statusbar.h"
17 
18 static int empty_timer = 0;
19 
20 struct BulletInfo
21 {
22   int sprite;            // sprite to use
23   int level;             // specify what level weapon is at when it fires this shot type
24   int frame;             // specify which frame within sprite
25   uint8_t makes_star;    // 1=make star effect, 2=make star but add x inertia to position
26   int timetolive;        // shot range
27   int damage;            // damage dealt per tick of contact with enemy
28   int speed;             // speed of shot
29   uint8_t manualsetup;   // 1= no auto setup at all, 2= don't use separate vert sprite
30   NXE::Sound::SFX sound; // specify firing sound
31 };
32 
33 BulletInfo bullet_table[] = {
34     //		sprite			  lvl  frm st ttl dmg spd  manset      sound
35     {SPR_SHOT_POLARSTAR, 0, 0, 1, 8, 1, 0x1000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L1_2},   // polarstar l1
36     {SPR_SHOT_POLARSTAR, 1, 1, 1, 12, 2, 0x1000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L1_2},  // polarstar l2
37     {SPR_SHOT_POLARSTAR_L3, 2, 0, 1, 16, 4, 0x1000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L3}, // polarstar l3
38 
39     {SPR_SHOT_MGUN_L1, 0, 0, 1, 20, 2, 0x1000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L1_2}, // mgun l1
40 
41     {SPR_SHOT_MGUN_L2, 1, 0, 1, 20, 4, 0x1000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L1_2}, // mgun l2, white piece
42     {SPR_SHOT_MGUN_L2, 1, 1, 0, 21, 0, 0x1000, 0, NXE::Sound::SFX::SND_NULL},            // mgun l2, blue piece
43     {SPR_SHOT_MGUN_L2, 1, 2, 0, 22, 0, 0x1000, 0, NXE::Sound::SFX::SND_NULL},            // mgun l2, dark piece
44 
45     {SPR_SHOT_MGUN_L3LEAD, 2, 0, 1, 20, 6, 0x1000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L3}, // mgun l3
46     {SPR_SHOT_MGUN_L3TAIL, 2, 0, 0, 21, 0, 0x1000, 0, NXE::Sound::SFX::SND_NULL},          // the very long...
47     {SPR_SHOT_MGUN_L3TAIL, 2, 1, 0, 22, 0, 0x1000, 0, NXE::Sound::SFX::SND_NULL},          // ...4 piece trail...
48     {SPR_SHOT_MGUN_L3TAIL, 2, 2, 0, 23, 0, 0x1000, 0, NXE::Sound::SFX::SND_NULL},          // ...of the level 3...
49     {SPR_SHOT_MGUN_L3TAIL, 2, 3, 0, 24, 0, 0x1000, 0, NXE::Sound::SFX::SND_NULL},          // ...machine gun
50 
51     // damage for missiles is set inside missile.cpp
52     {SPR_SHOT_MISSILE1, 0, 0, 1, 50, 0, 0x0000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L1_2}, // missile level 1
53     {SPR_SHOT_MISSILE2, 1, 0, 1, 65, 0, 0x0000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L1_2}, // missile level 2
54     {SPR_SHOT_MISSILE3, 2, 0, 1, 90, 0, 0x0000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L1_2}, // missile level 3
55 
56     {SPR_SHOT_SUPERMISSILE13, 0, 0, 1, 30, 0, 0x0000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L1_2}, // supermissile l1
57     {SPR_SHOT_SUPERMISSILE2, 1, 0, 1, 40, 0, 0x0000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L1_2},  // supermissile l2
58     {SPR_SHOT_SUPERMISSILE13, 2, 0, 1, 40, 0, 0x0000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L1_2}, // supermissile l3
59 
60     // damages are doubled because fireball can hit twice before dissipating
61     {SPR_SHOT_FIREBALL1, 0, 0, 1, 100, 2, 0x0000, 1, NXE::Sound::SFX::SND_FIREBALL},  // fireball l1
62     {SPR_SHOT_FIREBALL23, 1, 0, 1, 100, 3, 0x0000, 1, NXE::Sound::SFX::SND_FIREBALL}, // fireball l2
63     {SPR_SHOT_FIREBALL23, 2, 0, 1, 100, 3, 0x0000, 1, NXE::Sound::SFX::SND_FIREBALL}, // fireball l3
64 
65     {SPR_SHOT_BLADE_L1, 0, 0, 0, 29, 15, 0x800, 0, NXE::Sound::SFX::SND_FIREBALL}, // Blade L1
66     {SPR_SHOT_BLADE_L2, 1, 0, 0, 17, 6, 0x800, 0, NXE::Sound::SFX::SND_FIREBALL},  // Blade L2
67     {SPR_SHOT_BLADE_L3, 2, 0, 0, 30, 1, 0x800, 0, NXE::Sound::SFX::SND_FIREBALL},  // Blade L3
68 
69     {SPR_SHOT_SNAKE_L1, 0, 0, 1, 20, 4, 0x600, 2, NXE::Sound::SFX::SND_SNAKE_FIRE},   // Snake L1
70     {SPR_SHOT_FIREBALL23, 1, 0, 1, 23, 6, 0x200, 2, NXE::Sound::SFX::SND_SNAKE_FIRE}, // Snake L2
71     {SPR_SHOT_FIREBALL23, 2, 0, 1, 30, 8, 0x200, 2, NXE::Sound::SFX::SND_SNAKE_FIRE}, // Snake L3
72 
73     {SPR_SHOT_NEMESIS_L1, 0, 0, 2, 20, 4, 0x1000, 0, NXE::Sound::SFX::SND_NEMESIS_FIRE},
74     {SPR_SHOT_NEMESIS_L2, 1, 0, 2, 20, 4, 0x1000, 0, NXE::Sound::SFX::SND_POLAR_STAR_L3},
75     {SPR_SHOT_NEMESIS_L3, 2, 0, 2, 20, 1, 0x555, 0, NXE::Sound::SFX::SND_SPUR_CHARGE_2}, // 1/3 speed
76 
77     {SPR_SHOT_BUBBLER_L1, 0, 0, 1, 40, 1, 0x600, 2, NXE::Sound::SFX::SND_BUBBLER_FIRE},
78     {SPR_SHOT_BUBBLER_L2, 1, 0, 1, 60, 2, 0x600, 2, NXE::Sound::SFX::SND_BUBBLER_FIRE},
79     {SPR_SHOT_BUBBLER_L3, 2, 0, 1, 100, 2, 0x600, 2, NXE::Sound::SFX::SND_BUBBLER_FIRE},
80 
81     // Spur also messes with it's damage at runtime; see spur.cpp for details.
82     {SPR_SHOT_POLARSTAR, 0, 0, 1, 30, 4, 0x1000, 0, NXE::Sound::SFX::SND_SPUR_FIRE_1},
83     {SPR_SHOT_POLARSTAR, 1, 1, 1, 30, 8, 0x1000, 0, NXE::Sound::SFX::SND_SPUR_FIRE_2},
84     {SPR_SHOT_POLARSTAR_L3, 2, 0, 0, 30, 12, 0x1000, 0, NXE::Sound::SFX::SND_SPUR_FIRE_3},
85 
86     // Curly's Nemesis from Hell (OBJ_CURLY_CARRIED_SHOOTING)
87     {SPR_SHOT_NEMESIS_L1, 0, 0, 1, 20, 4, 0x1000, 0, NXE::Sound::SFX::SND_NEMESIS_FIRE},
88 
89     {0, 0, 0, 0, 0, 0, 0, 0, NXE::Sound::SFX::SND_NULL}};
90 
91 // resets anything like charging states etc on player re-init (Player::Init)
PResetWeapons()92 void PResetWeapons()
93 {
94   Weapon *spur      = &player->weapons[WPN_SPUR];
95   spur->chargetimer = 0;
96   spur->level       = 0;
97   spur->xp          = 0;
98 
99   init_whimstar(&player->whimstar);
100 }
101 
can_fire_spur(void)102 static bool can_fire_spur(void)
103 {
104   if (CountObjectsOfType(OBJ_SPUR_TRAIL))
105     return false;
106 
107   return true;
108 }
109 
110 // returns true if the current weapon has full xp at level 3 (is showing "Max")
IsWeaponMaxed(void)111 static bool IsWeaponMaxed(void)
112 {
113   Weapon *wpn = &player->weapons[player->curWeapon];
114   return (wpn->level == 2) && (wpn->xp == wpn->max_xp[2]);
115 }
116 
117 // fire a basic, single bullet
FireSimpleBullet(int otype,int btype,int xoff=0,int yoff=0)118 static Object *FireSimpleBullet(int otype, int btype, int xoff = 0, int yoff = 0)
119 {
120   int x, y, dir;
121 
122   // get location to fire from
123   GetPlayerShootPoint(&x, &y);
124   x += xoff;
125   y += yoff;
126 
127   // create the shot
128   Object *shot = CreateBullet(0, 0, otype);
129 
130   // set up the shot
131   if (player->look)
132     dir = player->look;
133   else
134     dir = player->dir;
135 
136   SetupBullet(shot, x, y, btype, dir);
137   return shot;
138 }
139 
140 // fires a missile type bullet at an offset from the exact center of the player
FireMissileBullet(int otype,int btype,int xoff=0,int yoff=0,int accel=0,bool wiggle=false)141 static Object *FireMissileBullet(int otype, int btype, int xoff = 0, int yoff = 0, int accel = 0, bool wiggle = false)
142 {
143   int x, y, dir;
144 
145   // create the shot
146   Object *shot = CreateBullet(0, 0, otype);
147 
148   // set up the shot
149   if (player->look)
150     dir = player->look;
151   else
152     dir = player->dir;
153 
154   // for shot star effect
155   GetPlayerShootPoint(&x, &y);
156   SetupBullet(shot, x, y, btype, dir);
157 
158   x = player->CenterX();
159   y = player->CenterY(); // + 4*CSFI;
160 
161   shot->SetCenterX(x + xoff);
162   shot->SetCenterY(y + yoff);
163 
164   if (player->look)
165   {
166     shot->yinertia = random(-512, 512);
167     if (wiggle)
168       shot->xinertia = (shot->x <= player->x) ? -256 : 256;
169   }
170   else
171   {
172     shot->xinertia = random(-512, 512);
173     if (wiggle)
174       shot->yinertia = (shot->y <= player->y) ? -256 : 256;
175   }
176   shot->shot.accel = accel;
177   return shot;
178 }
179 
180 // fires a bullet at an offset from the exact center of the player's shoot point.
181 // FireSimpleBullet can do this too-- but it's xoff/yoff is absolute. This function
182 // takes a parameter for when you are shooting right and extrapolates out the other
183 // directions from that. ALSO, xoff/yoff on FireSimpleBullet moves the star;
184 // this function does not.
FireSimpleBulletOffset(int otype,int btype,int xoff,int yoff)185 static Object *FireSimpleBulletOffset(int otype, int btype, int xoff, int yoff)
186 {
187   int dir;
188 
189   if (player->look)
190     dir = player->look;
191   else
192     dir = player->dir;
193 
194   switch (dir)
195   {
196     case RIGHT:
197       break; // already in format for RIGHT frame
198     case LEFT:
199       xoff = -xoff;
200       break;
201     case UP:
202       SWAP(xoff, yoff);
203       yoff = -yoff;
204       break;
205     case DOWN:
206       SWAP(xoff, yoff);
207       break;
208   }
209 
210   Object *shot = FireSimpleBullet(otype, btype);
211   shot->x += xoff;
212   shot->y += yoff;
213 
214   return shot;
215 }
216 
217 // fires and handles charged shots
PHandleSpur(void)218 static void PHandleSpur(void)
219 {
220   static const int FLASH_TIME = 10;
221   Weapon *spur                = &player->weapons[WPN_SPUR];
222 
223   if (player->curWeapon != WPN_SPUR)
224   {
225     spur->level = 0;
226     spur->xp    = 0;
227     return;
228   }
229 
230   if (pinputs[FIREKEY])
231   {
232     if (!IsWeaponMaxed())
233     {
234       int amt = (player->equipmask & EQUIP_TURBOCHARGE) ? 3 : 2;
235       AddXP(amt, true);
236 
237       if (IsWeaponMaxed())
238       {
239         NXE::Sound::SoundManager::getInstance()->playSfx(NXE::Sound::SFX::SND_SPUR_MAXED);
240       }
241       else
242       {
243         spur->chargetimer++;
244         if (spur->chargetimer / 2 & 1)
245         {
246           NXE::Sound::SoundManager::getInstance()->playSfx(
247               (NXE::Sound::SFX)((int)NXE::Sound::SFX::SND_SPUR_CHARGE_1 + spur->level));
248         }
249       }
250     }
251     else
252     {
253       // keep flashing even once at max
254       int amt = (player->equipmask & EQUIP_TURBOCHARGE) ? 3 : 2;
255       AddXP(amt, true);
256     }
257   }
258   else
259   {
260     if (spur->chargetimer)
261     {
262       if (spur->level > 0 && can_fire_spur())
263       {
264         int level = IsWeaponMaxed() ? 2 : (spur->level - 1);
265         FireSimpleBulletOffset(OBJ_SPUR_SHOT, B_SPUR_L1 + level, -4 * CSFI, 0);
266       }
267 
268       spur->chargetimer = 0;
269     }
270 
271     spur->level = 0;
272     spur->xp    = 0;
273   }
274 
275   if (statusbar.xpflashcount > FLASH_TIME)
276     statusbar.xpflashcount = FLASH_TIME;
277 }
278 
PDoWeapons(void)279 void PDoWeapons(void)
280 {
281   run_whimstar(&player->whimstar);
282 
283   if (player->inputs_locked)
284     return; // should prevent from firing in cutscenes
285 
286   if (justpushed(PREVWPNKEY))
287     stat_PrevWeapon();
288   if (justpushed(NEXTWPNKEY))
289     stat_NextWeapon();
290 
291   // firing weapon
292   if (pinputs[FIREKEY])
293   {
294     FireWeapon();
295     RunWeapon(true);
296   }
297   else
298   {
299     player->auto_fire_limit = 6;
300     RunWeapon(false);
301   }
302 
303   PHandleSpur();
304 
305   if (empty_timer)
306     empty_timer--;
307 }
308 
309 /*
310 void c------------------------------() {}
311 */
312 
313 // fire the missile launcher.
314 // level: 0 - 2: weapon level from 1 - 3
315 // is_super: bool: true if the player is firing the Super Missile Launcher
PFireMissile(int level,bool is_super)316 static void PFireMissile(int level, bool is_super)
317 {
318   int xoff, yoff;
319 
320   int object_type = (!is_super) ? OBJ_MISSILE_SHOT : OBJ_SUPERMISSILE_SHOT;
321 
322   // can only fire one missile at once on L1,
323   // two missiles on L2, and two sets of three missiles on L3.
324   static const uint8_t max_missiles_at_once[] = {1, 2, 6};
325   if (CountObjectsOfType(object_type) >= max_missiles_at_once[level])
326   {
327     // give back the previously-decremented ammo so they don't lose it (hack)
328     player->weapons[player->curWeapon].ammo++;
329     return;
330   }
331 
332   int bullet_type = (!is_super) ? B_MISSILE_L1 : B_SUPER_MISSILE_L1;
333   bullet_type += level;
334 
335   // level 1 & 2 fires just one missile
336   yoff = 1;
337   xoff = (player->dir == RIGHT) ? 1 : -1;
338   if (player->look)
339   {
340     yoff = (player->look == UP) ? -1 : 1;
341     FireMissileBullet(object_type, bullet_type, CSFI * xoff, 8 * CSFI * yoff, (is_super) ? 512 : 128, (level == 2));
342   }
343   else
344   {
345     FireMissileBullet(object_type, bullet_type, 6 * CSFI * xoff, (level == 2) ? CSFI : 0, (is_super) ? 512 : 128,
346                       (level == 2));
347   }
348   // lv3 fires 3 missiles that wiggle
349   if (level == 2)
350   {
351     if (player->look)
352     {
353       yoff = (player->look == UP) ? -1 : 1;
354       FireMissileBullet(object_type, bullet_type, 3 * CSFI * xoff, 0, (is_super) ? 256 : 64, true);
355       FireMissileBullet(object_type, bullet_type, -3 * CSFI * xoff, 0, (is_super) ? 170 : 51, true);
356     }
357     else
358     {
359       FireMissileBullet(object_type, bullet_type, 0, -8 * CSFI, (is_super) ? 256 : 64, true);
360       FireMissileBullet(object_type, bullet_type, -4 * CSFI * xoff, -CSFI, (is_super) ? 170 : 51, true);
361     }
362   }
363 }
364 
365 /*
366 void c------------------------------() {}
367 */
368 
PFireFireball(int level)369 static void PFireFireball(int level)
370 {
371   static const int object_types[] = {OBJ_FIREBALL1, OBJ_FIREBALL23, OBJ_FIREBALL23};
372   static uint8_t max_fireballs[]  = {2, 3, 4};
373   int count;
374 
375   count = (CountObjectsOfType(OBJ_FIREBALL1) + CountObjectsOfType(OBJ_FIREBALL23));
376   if (count >= max_fireballs[level])
377     return;
378 
379   // the 8px offset fires the shot just a tiny bit behind the player--
380   // you can't see the difference but it makes the shot correctly bounce if
381   // you shoot while flat up against a wall, instead of embedding the fireball
382   // in the wall.
383   Object *fb = FireSimpleBulletOffset(object_types[level], B_FIREBALL1 + level, -6 * CSFI, 0);
384   fb->dir    = player->dir;
385   fb->nxflags &= ~NXFLAG_NO_RESET_YINERTIA;
386 
387   switch (fb->shot.dir)
388   {
389     case LEFT:
390       fb->xinertia = -0x400;
391       break;
392     case RIGHT:
393       fb->xinertia = 0x400;
394       break;
395 
396     case UP:
397       fb->xinertia = player->xinertia + ((player->dir == RIGHT) ? 128 : -128);
398       if (player->xinertia)
399         fb->dir = (player->xinertia > 0) ? RIGHT : LEFT;
400       fb->yinertia = -0x5ff;
401       break;
402 
403     case DOWN:
404       fb->xinertia = player->xinertia;
405       if (player->xinertia)
406         fb->dir = (player->xinertia > 0) ? RIGHT : LEFT;
407       fb->yinertia = 0x5ff;
408       break;
409   }
410 }
411 
PFireBlade(int level)412 static void PFireBlade(int level)
413 {
414   int numblades = CountObjectsOfType(OBJ_BLADE12_SHOT) + CountObjectsOfType(OBJ_BLADE3_SHOT);
415   if (numblades >= 1)
416     return;
417 
418   int dir = (player->look) ? player->look : player->dir;
419 
420   int x = player->CenterX();
421   int y = player->CenterY();
422 
423   if (level == 2)
424   {
425     if (dir == RIGHT || dir == LEFT)
426     {
427       y -= (3 * CSFI);
428       x += (dir == LEFT) ? (3 * CSFI) : -(3 * CSFI);
429     }
430   }
431   else
432   {
433     switch (dir)
434     {
435       case RIGHT:
436         x -= (6 * CSFI);
437         y -= (3 * CSFI);
438         break;
439       case LEFT:
440         x += (6 * CSFI);
441         y -= (3 * CSFI);
442         break;
443       case UP:
444         y += (6 * CSFI);
445         break;
446       case DOWN:
447         y -= (6 * CSFI);
448         break;
449     }
450   }
451 
452   Object *shot = CreateObject(x, y, (level != 2) ? OBJ_BLADE12_SHOT : OBJ_BLADE3_SHOT);
453   SetupBullet(shot, x, y, B_BLADE_L1 + level, dir);
454 }
455 
456 /*
457 void c------------------------------() {}
458 */
459 
PFireSnake(int level)460 static void PFireSnake(int level)
461 {
462   if (level == 2)
463   {
464     int count = (CountObjectsOfType(OBJ_SNAKE1_SHOT) + CountObjectsOfType(OBJ_SNAKE23_SHOT));
465 
466     if (count >= 4)
467       return;
468   }
469 
470   int object_type = (level == 0) ? OBJ_SNAKE1_SHOT : OBJ_SNAKE23_SHOT;
471   FireSimpleBulletOffset(object_type, B_SNAKE_L1 + level, -5 * CSFI, 0);
472 }
473 
PFireNemesis(int level)474 static void PFireNemesis(int level)
475 {
476   if (CountObjectsOfType(OBJ_NEMESIS_SHOT) >= 2)
477     return;
478 
479   FireSimpleBullet(OBJ_NEMESIS_SHOT, B_NEMESIS_L1 + level);
480 }
481 
PFireBubbler(int level)482 static void PFireBubbler(int level)
483 {
484   static const int max_bubbles[] = {4, 16, 16};
485 
486   int count = CountObjectsOfType(OBJ_BUBBLER12_SHOT) + CountObjectsOfType(OBJ_BUBBLER3_SHOT);
487 
488   if (count >= max_bubbles[level])
489   {
490     // give back the previously-decremented ammo so they don't lose it (hack)
491     player->weapons[player->curWeapon].ammo++;
492     return;
493   }
494 
495   int objtype = (level != 2) ? OBJ_BUBBLER12_SHOT : OBJ_BUBBLER3_SHOT;
496   FireSimpleBulletOffset(objtype, B_BUBBLER_L1 + level, -4 * CSFI, 0);
497 }
498 
499 /*
500 void c------------------------------() {}
501 */
502 
503 // Spur fires an initial shot of Polar Star L3, then charges
504 // as long as key is down. Fires when key released.
505 // Released at L1: nothing
506 // Released at L2: thin beam
507 // Released at L3: dual beam
508 // Released at Max: thick beam
509 //
510 // Initial shot is not fired if key is held on a different weapon
511 // and then weapon is switched to spur.
512 
513 // fires the regular Polar Star shot when you first push button
PFireSpur(void)514 static void PFireSpur(void)
515 {
516   if (can_fire_spur())
517     FireSimpleBulletOffset(OBJ_POLAR_SHOT, B_PSTAR_L3, -4 * CSFI, 0);
518 }
519 
PFirePolarStar(int level)520 static void PFirePolarStar(int level)
521 {
522   if (CountObjectsOfType(OBJ_POLAR_SHOT) < 2)
523   {
524     int xoff;
525     if (level == 2)
526       xoff = -5 * CSFI;
527     else
528       xoff = -4 * CSFI;
529 
530     FireSimpleBulletOffset(OBJ_POLAR_SHOT, B_PSTAR_L1 + level, xoff, 0);
531     rumble(0.2, 200);
532   }
533 }
534 
535 /*
536 void c------------------------------() {}
537 */
538 
539 // handles firing the Machine Gun
PFireMachineGun(int level)540 static void PFireMachineGun(int level)
541 {
542   Object *shot;
543   int x, y;
544 
545   int dir = (player->look) ? player->look : player->dir;
546 
547   if (level == 0)
548   { // level 1 is real easy! no frickin' layers!!
549     shot      = FireSimpleBullet(OBJ_POLAR_SHOT, B_MGUN_L1, 0, 0);
550     shot->dir = dir;
551 
552     if (player->look)
553       shot->xinertia = random(-0xAA, 0xAA);
554     else
555       shot->yinertia = random(-0xAA, 0xAA);
556     rumble(0.2, 200);
557   }
558   else
559   {
560     // drop an OBJ_MGUN_SHOOTER object to fire the layers (trail) of the MGun blast.
561     GetPlayerShootPoint(&x, &y);
562     FireLevel23MGun(x, y, level, dir);
563     rumble(0.3, 200);
564   }
565 
566   // do machine-gun flying
567   if (level == 2)
568   {
569     if (player->look == DOWN)
570       PMgunFly(true);
571     else if (player->look == UP)
572       PMgunFly(false);
573   }
574 }
575 
576 // called when player is trying to fire the current weapon
577 // i.e. the fire button is down.
FireWeapon(void)578 void FireWeapon(void)
579 {
580 
581   Weapon *curweapon = &player->weapons[player->curWeapon];
582   int level         = curweapon->level;
583 
584   // check if we can fire
585   if (curweapon->firerate[level] != 0)
586   { // rapid/fully-auto fire
587     if (++player->auto_fire_limit > curweapon->firerate[level])
588     {
589       player->auto_fire_limit = 0;
590     }
591     else
592     {
593       return;
594     }
595   }
596   else
597   { // else must push key for each shot
598     if (lastpinputs[FIREKEY])
599       return;
600   }
601 
602   if (player->fire_limit)
603     return;
604 
605   player->fire_limit = 4;
606 
607   // check if we have enough ammo
608   if (curweapon->maxammo > 0 && curweapon->ammo <= 0)
609   {
610     NXE::Sound::SoundManager::getInstance()->playSfx(NXE::Sound::SFX::SND_GUN_CLICK);
611     if (empty_timer <= 0)
612     {
613       effect(player->CenterX(), player->CenterY(), EFFECT_EMPTY);
614       empty_timer = 50;
615     }
616 
617     return;
618   }
619 
620   // subtract ammo
621   if (curweapon->ammo)
622     curweapon->ammo--;
623 
624   // fire!!
625   switch (player->curWeapon)
626   {
627     case WPN_NONE:
628       break;
629 
630     case WPN_POLARSTAR:
631       PFirePolarStar(level);
632       break;
633 
634     case WPN_FIREBALL:
635       PFireFireball(level);
636       break;
637 
638     case WPN_MGUN:
639       PFireMachineGun(level);
640       break;
641 
642     case WPN_MISSILE:
643     case WPN_SUPER_MISSILE:
644       PFireMissile(level, (player->curWeapon == WPN_SUPER_MISSILE));
645       break;
646 
647     case WPN_BLADE:
648       PFireBlade(level);
649       break;
650 
651     case WPN_SNAKE:
652       PFireSnake(level);
653       break;
654 
655     case WPN_NEMESIS:
656       PFireNemesis(level);
657       break;
658 
659     case WPN_BUBBLER:
660       PFireBubbler(level);
661       break;
662 
663     case WPN_SPUR:
664       PFireSpur();
665       break;
666 
667     default:
668       console.Print("FireWeapon: cannot fire unimplemented weapon %d", player->curWeapon);
669       NXE::Sound::SoundManager::getInstance()->playSfx(NXE::Sound::SFX::SND_BONK_HEAD);
670       break;
671   }
672 }
673 
674 // "run" the current weapon.
675 // firing = 1 if fire key is currently down, and 0 if it is not.
RunWeapon(bool firing)676 void RunWeapon(bool firing)
677 {
678   Weapon *curweapon = &player->weapons[player->curWeapon];
679   int level         = curweapon->level;
680 
681   if (player->fire_limit) player->fire_limit--;
682 
683   // bubbler L1 has recharge but not rapid fire,
684   // so it recharges even if the key is held down.
685   if (firing && !curweapon->firerate[level] && lastpinputs[FIREKEY])
686     firing = false;
687 
688   // recharge machine gun when it's not firing or it's not selected
689   if ((curweapon->rechargerate[level]) && (curweapon->ammo < curweapon->maxammo) && !firing)
690   {
691     // start recharging ammo
692     int rate = curweapon->rechargerate[level];
693     if ((player->equipmask & EQUIP_TURBOCHARGE) && player->curWeapon == WPN_MGUN)
694     {
695       rate = 2;
696     }
697 
698     // it's greater than OR EQUAL TO, so that we can have rate=0 be no recharge.
699     // Otherwise there would be no value that recharges every frame.
700     if (++curweapon->rechargetimer >= rate)
701     {
702       curweapon->rechargetimer = 0;
703       curweapon->ammo++;
704     }
705   }
706 
707   for (int i = 0; i < WPN_COUNT; i++)
708   {
709     if (player->weapons[i].firetimer)
710       player->weapons[i].firetimer--;
711 
712     if ((i != player->curWeapon) || (player->weapons[i].ammo >= player->weapons[i].maxammo) || firing)
713     {
714       player->weapons[i].rechargetimer = 0;
715     }
716   }
717 }
718 
719 /*
720 void c------------------------------() {}
721 */
722 
723 // set up the specified bullet to be a shot of type btype
724 // (note: shared by Curly sand-zone boss)
SetupBullet(Object * shot,int x,int y,int btype,int dir)725 void SetupBullet(Object *shot, int x, int y, int btype, int dir)
726 {
727   const BulletInfo *info = &bullet_table[btype];
728 
729   shot->sprite      = info->sprite;
730   shot->frame       = info->frame;
731   shot->shot.ttl    = info->timetolive;
732   shot->shot.damage = info->damage;
733   shot->shot.level  = info->level;
734   shot->shot.btype  = btype;
735   shot->shot.dir    = dir;
736   shot->nxflags |= NXFLAG_NO_RESET_YINERTIA;
737 
738   if (game.debug.infinite_damage)
739     shot->shot.damage = 255;
740 
741   if (info->sound != NXE::Sound::SFX::SND_NULL)
742     NXE::Sound::SoundManager::getInstance()->playSfx(info->sound);
743 
744   if (info->makes_star == 1)
745     effect(x, y, EFFECT_STARPOOF);
746 
747   if (info->manualsetup != 1)
748   {
749     switch (dir)
750     {
751       case LEFT:
752         shot->xinertia = -info->speed;
753         shot->dir      = LEFT;
754         break;
755 
756       case RIGHT:
757         shot->xinertia = info->speed;
758         shot->dir      = RIGHT;
759         break;
760 
761       case UP:
762         shot->yinertia = -info->speed;
763         shot->dir      = RIGHT;
764         if (info->manualsetup != 2)
765         {
766           shot->sprite++;
767         }
768         break;
769 
770       case DOWN:
771         shot->yinertia = info->speed;
772         shot->dir      = LEFT;
773         if (info->manualsetup != 2)
774         {
775           shot->sprite++;
776         }
777         break;
778     }
779 
780     if (info->makes_star == 2)
781       effect(x + shot->xinertia / 2, y, EFFECT_STARPOOF);
782 
783     // have to do this because inertia will get applied later in the tick before the first
784     // time it's drawn so it won't actually appear where we put it if we don't
785     x -= shot->xinertia;
786     y -= shot->yinertia;
787   }
788 
789   // put shot center at [x,y],
790   // this also centers it within starpoof
791   shot->x = x - (shot->Width() / 2);
792   shot->y = y - (shot->Height() / 2);
793 }
794 
795 /*
796 void c------------------------------() {}
797 */
798 
799 // fire a level 2 or level 3 MGun blast from position x,y.
800 // Broken out here into a seperate sub so OBJ_CURLY_AI can use it also.
FireLevel23MGun(int x,int y,int level,int dir)801 void FireLevel23MGun(int x, int y, int level, int dir)
802 {
803   static const uint8_t no_layers[] = {1, 3, 5};
804   static const int bultype_table[] = {0, B_MGUN_L2, B_MGUN_L3};
805   Object *shot;
806 
807   // note: this relies on the player AI running before the entity AI...which it does...
808   // so leave it that way, else he wouldn't actually fire for 1 additional frame
809   shot = CreateObject(x, y, OBJ_MGUN_SPAWNER);
810 
811   shot->dir           = dir;
812   shot->mgun.bultype  = bultype_table[level];
813   shot->mgun.nlayers  = no_layers[level];
814   shot->mgun.wave_amt = random(-0xAA, 0xAA);
815   shot->invisible     = true;
816 }
817 
818 // handles flying when shooting down using Machine Gun at Level 3
PMgunFly(bool up)819 void PMgunFly(bool up)
820 {
821   if (up)
822   {
823     if (player->yinertia > 0)
824     {
825       player->yinertia >>= 1;
826     }
827 
828     if (player->yinertia > -0x400)
829     {
830       player->yinertia -= 0x200;
831       if (player->yinertia < -0x400)
832         player->yinertia = -0x400;
833     }
834   }
835   else
836   {
837     player->yinertia += 0x100;
838   }
839 }
840