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