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