NXEngine v1.0.0.6
[NXEngine.git] / p_arms.cpp
blobfa84f8c1caad2a3ca81b9e64240f67ff10335b12
2 #include "nx.h"
3 #include "p_arms.fdh"
5 static Object *FireSimpleBullet(int otype, int btype, int xoff=0, int yoff=0);
6 static int empty_timer = 0;
8 struct BulletInfo
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
21 BulletInfo bullet_table[] =
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
28 SPR_SHOT_MGUN_L1, 0, 0, 1, 20, 2, 0x1000, 0, SND_POLAR_STAR_L1_2, // mgun l1
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
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
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
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
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
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
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
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
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,
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,
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,
78 0, 0, 0, 0, 0, 0, 0
82 // resets anything like charging states etc on player re-init (Player::Init)
83 void PResetWeapons()
85 Weapon *spur = &player->weapons[WPN_SPUR];
86 spur->chargetimer = 0;
87 spur->level = 0;
88 spur->xp = 0;
90 init_whimstar(&player->whimstar);
94 void PDoWeapons(void)
96 // switching weapons. have to check for inputs_frozen since justpushed
97 // reads inputs[] directly, not pinputs[].
98 if (!player->inputs_locked)
100 if (justpushed(PREVWPNKEY)) stat_PrevWeapon();
101 if (justpushed(NEXTWPNKEY)) stat_NextWeapon();
104 // firing weapon
105 if (pinputs[FIREKEY])
107 FireWeapon();
108 RunWeapon(true);
110 else
112 RunWeapon(false);
115 PHandleSpur();
116 run_whimstar(&player->whimstar);
118 if (empty_timer)
119 empty_timer--;
123 void c------------------------------() {}
126 // called when player is trying to fire the current weapon
127 // i.e. the fire button is down.
128 void FireWeapon(void)
130 Weapon *curweapon = &player->weapons[player->curWeapon];
131 int level = curweapon->level;
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)
139 return;
141 else
143 curweapon->firetimer = curweapon->firerate[level];
146 else
147 { // else must push key for each shot
148 if (lastpinputs[FIREKEY])
149 return;
152 // check if we have enough ammo
153 if (curweapon->maxammo > 0 && curweapon->ammo <= 0)
155 sound(SND_GUN_CLICK);
156 if (empty_timer <= 0)
158 effect(player->CenterX(), player->CenterY(), EFFECT_EMPTY);
159 empty_timer = 50;
162 return;
165 // subtract ammo
166 if (curweapon->ammo)
167 curweapon->ammo--;
169 // fire!!
170 switch(player->curWeapon)
172 case WPN_NONE: break;
174 case WPN_POLARSTAR:
175 PFirePolarStar(level);
176 break;
178 case WPN_FIREBALL:
179 PFireFireball(level);
180 break;
182 case WPN_MGUN:
183 PFireMachineGun(level);
184 break;
186 case WPN_MISSILE:
187 case WPN_SUPER_MISSILE:
188 PFireMissile(level, (player->curWeapon == WPN_SUPER_MISSILE));
189 break;
191 case WPN_BLADE:
192 PFireBlade(level);
193 break;
195 case WPN_SNAKE:
196 PFireSnake(level);
197 break;
199 case WPN_NEMESIS:
200 PFireNemesis(level);
201 break;
203 case WPN_BUBBLER:
204 PFireBubbler(level);
205 break;
207 case WPN_SPUR:
208 PFireSpur();
209 break;
211 default:
212 console.Print("FireWeapon: cannot fire unimplemented weapon %d", player->curWeapon);
213 sound(SND_BONK_HEAD);
214 break;
219 // "run" the current weapon.
220 // firing = 1 if fire key is currently down, and 0 if it is not.
221 void RunWeapon(bool firing)
223 Weapon *curweapon = &player->weapons[player->curWeapon];
224 int level = curweapon->level;
226 // bubbler L1 has recharge but not rapid fire,
227 // so it recharges even if the key is held down.
228 if (firing && !curweapon->firerate[level] && lastpinputs[FIREKEY])
229 firing = false;
231 // recharge machine gun when it's not firing or it's not selected
232 if ((curweapon->rechargerate[level]) && \
233 (curweapon->ammo < curweapon->maxammo) && \
234 !firing)
236 // start recharging ammo
237 int rate = curweapon->rechargerate[level];
238 if ((player->equipmask & EQUIP_TURBOCHARGE) && player->curWeapon == WPN_MGUN)
240 rate = 2;
243 // it's greater than OR EQUAL TO, so that we can have rate=0 be no recharge.
244 // Otherwise there would be no value that recharges every frame.
245 if (++curweapon->rechargetimer >= rate)
247 curweapon->rechargetimer = 0;
248 curweapon->ammo++;
252 for(int i=0;i<WPN_COUNT;i++)
254 if (player->weapons[i].firetimer)
255 player->weapons[i].firetimer--;
257 if ((i != player->curWeapon) || \
258 (player->weapons[i].ammo >= player->weapons[i].maxammo) || \
259 firing)
261 player->weapons[i].rechargetimer = 0;
267 void c------------------------------() {}
270 // set up the specified bullet to be a shot of type btype
271 // (note: shared by Curly sand-zone boss)
272 void SetupBullet(Object *shot, int x, int y, int btype, int dir)
274 const BulletInfo *info = &bullet_table[btype];
276 shot->sprite = info->sprite;
277 shot->frame = info->frame;
278 shot->shot.ttl = info->timetolive;
279 shot->shot.damage = info->damage;
280 shot->shot.level = info->level;
281 shot->shot.btype = btype;
282 shot->shot.dir = dir;
283 shot->nxflags |= NXFLAG_NO_RESET_YINERTIA;
285 if (game.debug.infinite_damage)
286 shot->shot.damage = 255;
288 if (info->sound)
289 sound(info->sound);
291 if (info->makes_star == 1)
292 effect(x, y, EFFECT_STARPOOF);
294 if (info->manualsetup != 1)
296 switch(dir)
298 case LEFT:
299 shot->xinertia = -info->speed;
300 shot->dir = LEFT;
301 break;
303 case RIGHT:
304 shot->xinertia = info->speed;
305 shot->dir = RIGHT;
306 break;
308 case UP:
309 shot->yinertia = -info->speed;
310 shot->dir = RIGHT;
311 if (info->manualsetup != 2) { shot->sprite++; }
312 break;
314 case DOWN:
315 shot->yinertia = info->speed;
316 shot->dir = LEFT;
317 if (info->manualsetup != 2) { shot->sprite++; }
318 break;
321 if (info->makes_star == 2)
322 effect(x+shot->xinertia/2, y, EFFECT_STARPOOF);
324 // have to do this because inertia will get applied later in the tick before the first
325 // time it's drawn so it won't actually appear where we put it if we don't
326 x -= shot->xinertia;
327 y -= shot->yinertia;
330 // put shot center at [x,y],
331 // this also centers it within starpoof
332 shot->x = x - (shot->Width() / 2);
333 shot->y = y - (shot->Height() / 2);
337 // fire a basic, single bullet
338 static Object *FireSimpleBullet(int otype, int btype, int xoff, int yoff)
340 int x, y, dir;
342 // get location to fire from
343 GetPlayerShootPoint(&x, &y);
344 x += xoff;
345 y += yoff;
347 // create the shot
348 Object *shot = CreateObject(0, 0, otype);
350 // set up the shot
351 if (player->look)
352 dir = player->look;
353 else
354 dir = player->dir;
356 SetupBullet(shot, x, y, btype, dir);
357 return shot;
360 // fires a bullet at an offset from the exact center of the player's shoot point.
361 // FireSimpleBullet can do this too-- but it's xoff/yoff is absolute. This function
362 // takes a parameter for when you are shooting right and extrapolates out the other
363 // directions from that. ALSO, xoff/yoff on FireSimpleBullet moves the star;
364 // this function does not.
365 static Object *FireSimpleBulletOffset(int otype, int btype, int xoff, int yoff)
367 int dir;
369 if (player->look)
370 dir = player->look;
371 else
372 dir = player->dir;
374 switch(dir)
376 case RIGHT: break; // already in format for RIGHT frame
377 case LEFT: xoff = -xoff; break;
378 case UP: SWAP(xoff, yoff); yoff = -yoff; break;
379 case DOWN: SWAP(xoff, yoff); break;
382 Object *shot = FireSimpleBullet(otype, btype);
383 shot->x += xoff;
384 shot->y += yoff;
386 return shot;
391 void c------------------------------() {}
394 static void PFirePolarStar(int level)
396 // at level 3 only two shots per screen permitted
397 if (level < 2 || CountObjectsOfType(OBJ_POLAR_SHOT) < 2)
399 int xoff;
400 if (level == 2) xoff = -5<<CSF; else xoff = -4<<CSF;
402 FireSimpleBulletOffset(OBJ_POLAR_SHOT, B_PSTAR_L1+level, xoff, 0);
407 void c------------------------------() {}
410 // handles firing the Machine Gun
411 static void PFireMachineGun(int level)
413 Object *shot;
414 int x, y;
416 int dir = (player->look) ? player->look : player->dir;
418 if (level == 0)
419 { // level 1 is real easy! no frickin' layers!!
420 shot = FireSimpleBullet(OBJ_POLAR_SHOT, B_MGUN_L1, 0, 0);
421 shot->dir = dir;
423 if (player->look)
424 shot->xinertia = random(-0xAA, 0xAA);
425 else
426 shot->yinertia = random(-0xAA, 0xAA);
428 else
430 // drop an OBJ_MGUN_SHOOTER object to fire the layers (trail) of the MGun blast.
431 GetPlayerShootPoint(&x, &y);
432 FireLevel23MGun(x, y, level, dir);
435 // do machine-gun flying
436 if (player->look==DOWN && level==2)
438 PMgunFly();
442 // fire a level 2 or level 3 MGun blast from position x,y.
443 // Broken out here into a seperate sub so OBJ_CURLY_AI can use it also.
444 void FireLevel23MGun(int x, int y, int level, int dir)
446 static const uchar no_layers[] = { 1, 3, 5 };
447 static const int bultype_table[] = { 0, B_MGUN_L2, B_MGUN_L3 };
448 Object *shot;
450 // note: this relies on the player AI running before the entity AI...which it does...
451 // so leave it that way, else he wouldn't actually fire for 1 additional frame
452 shot = CreateObject(x, y, OBJ_MGUN_SPAWNER);
454 shot->dir = dir;
455 shot->mgun.bultype = bultype_table[level];
456 shot->mgun.nlayers = no_layers[level];
457 shot->mgun.wave_amt = random(-0xAA, 0xAA);
458 shot->invisible = true;
462 // handles flying when shooting down using Machine Gun at Level 3
463 void PMgunFly(void)
465 if (player->yinertia > 0)
467 player->yinertia >>= 1;
470 if (player->yinertia > -0x400)
472 player->yinertia -= 0x200;
473 if (player->yinertia < -0x400) player->yinertia = -0x400;
478 void c------------------------------() {}
481 // fire the missile launcher.
482 // level: 0 - 2: weapon level from 1 - 3
483 // is_super: bool: true if the player is firing the Super Missile Launcher
484 static void PFireMissile(int level, bool is_super)
486 Object *o;
487 int xoff, yoff;
489 int object_type = (!is_super) ? OBJ_MISSILE_SHOT : OBJ_SUPERMISSILE_SHOT;
491 // can only fire one missile at once on L1,
492 // two missiles on L2, and two sets of three missiles on L3.
493 static const uint8_t max_missiles_at_once[] = { 1, 2, 6 };
494 if (CountObjectsOfType(object_type) >= max_missiles_at_once[level])
496 // give back the previously-decremented ammo so they don't lose it (hack)
497 player->weapons[player->curWeapon].ammo++;
498 return;
501 int bullet_type = (!is_super) ? B_MISSILE_L1 : B_SUPER_MISSILE_L1;
502 bullet_type += level;
504 // level 1 & 2 fires just one missile
505 FireSimpleBulletOffset(object_type, bullet_type, -4<<CSF, 0);
507 // level 3 fires three missiles, they wave, and are "offset",
508 // so if it's level 3 fire two more missiles.
509 if (level == 2)
511 // norm super
512 static const int recoil_upper[] = { 0x500, 0xd00 };
513 static const int recoil_lower[] = { 0x700, 0x600 };
515 if (player->look==DOWN || player->look==UP) { xoff = (4<<CSF); yoff = 0; }
516 else { yoff = (4<<CSF); xoff = 0; }
518 // this one is higher
519 o = FireSimpleBullet(object_type, bullet_type, -xoff, -yoff);
520 if (o->shot.dir==LEFT) o->xinertia = recoil_upper[is_super];
521 else if (o->shot.dir==RIGHT) o->xinertia = -recoil_upper[is_super];
522 else if (o->shot.dir==UP) o->yinertia = recoil_upper[is_super];
523 else o->yinertia = -recoil_upper[is_super];
525 // this one is lower
526 o = FireSimpleBullet(object_type, bullet_type, xoff, yoff);
527 if (o->shot.dir==LEFT) o->xinertia = recoil_lower[is_super];
528 else if (o->shot.dir==RIGHT) o->xinertia = -recoil_lower[is_super];
529 else if (o->shot.dir==UP) o->yinertia = recoil_lower[is_super];
530 else o->yinertia = -recoil_lower[is_super];
535 void c------------------------------() {}
538 static void PFireFireball(int level)
540 static const int object_types[] = { OBJ_FIREBALL1, OBJ_FIREBALL23, OBJ_FIREBALL23 };
541 static uchar max_fireballs[] = { 2, 3, 4 };
542 int count;
544 count = (CountObjectsOfType(OBJ_FIREBALL1) + CountObjectsOfType(OBJ_FIREBALL23));
545 if (count >= max_fireballs[level])
547 return;
550 // the 8px offset fires the shot just a tiny bit behind the player--
551 // you can't see the difference but it makes the shot correctly bounce if
552 // you shoot while flat up against a wall, instead of embedding the fireball
553 // in the wall.
554 Object *fb = FireSimpleBulletOffset(object_types[level], B_FIREBALL1 + level, -8<<CSF, 0);
555 fb->dir = player->dir;
556 fb->nxflags &= ~NXFLAG_NO_RESET_YINERTIA;
558 switch(fb->shot.dir)
560 case LEFT: fb->xinertia = -0x400; break;
561 case RIGHT: fb->xinertia = 0x400; break;
563 case UP:
564 fb->xinertia = player->xinertia + ((player->dir==RIGHT) ? 128 : -128);
565 if (player->xinertia) fb->dir = (player->xinertia > 0) ? RIGHT:LEFT;
566 fb->yinertia = -0x5ff;
567 break;
569 case DOWN:
570 fb->xinertia = player->xinertia;
571 if (player->xinertia) fb->dir = (player->xinertia > 0) ? RIGHT:LEFT;
572 fb->yinertia = 0x5ff;
573 break;
578 static void PFireBlade(int level)
580 int numblades = CountObjectsOfType(OBJ_BLADE12_SHOT) + CountObjectsOfType(OBJ_BLADE3_SHOT);
581 if (numblades >= 1) return;
583 int dir = (player->look) ? player->look : player->dir;
585 int x = player->CenterX();
586 int y = player->CenterY();
588 if (level == 2)
590 if (dir == RIGHT || dir == LEFT)
592 y -= (3 << CSF);
593 x += (dir == LEFT) ? (3 << CSF) : -(3 << CSF);
596 else
598 switch(dir)
600 case RIGHT: x -= (6 << CSF); y -= (3 << CSF); break;
601 case LEFT: x += (6 << CSF); y -= (3 << CSF); break;
602 case UP: y += (6 << CSF); break;
603 case DOWN: y -= (6 << CSF); break;
607 Object *shot = CreateObject(x, y, (level != 2) ? OBJ_BLADE12_SHOT : OBJ_BLADE3_SHOT);
608 SetupBullet(shot, x, y, B_BLADE_L1+level, dir);
612 void c------------------------------() {}
615 static void PFireSnake(int level)
617 if (level == 2)
619 int count = (CountObjectsOfType(OBJ_SNAKE1_SHOT) + \
620 CountObjectsOfType(OBJ_SNAKE23_SHOT));
622 if (count >= 4)
623 return;
626 int object_type = (level == 0) ? OBJ_SNAKE1_SHOT : OBJ_SNAKE23_SHOT;
627 FireSimpleBulletOffset(object_type, B_SNAKE_L1+level, -5<<CSF, 0);
631 static void PFireNemesis(int level)
633 if (CountObjectsOfType(OBJ_NEMESIS_SHOT) >= 2)
634 return;
636 FireSimpleBullet(OBJ_NEMESIS_SHOT, B_NEMESIS_L1+level);
640 static void PFireBubbler(int level)
642 static const int max_bubbles[] = { 4, 16, 16 };
644 int count = CountObjectsOfType(OBJ_BUBBLER12_SHOT) + \
645 CountObjectsOfType(OBJ_BUBBLER3_SHOT);
647 if (count >= max_bubbles[level])
648 return;
650 int objtype = (level != 2) ? OBJ_BUBBLER12_SHOT : OBJ_BUBBLER3_SHOT;
651 FireSimpleBulletOffset(objtype, B_BUBBLER_L1+level, -4<<CSF, 0);
655 void c------------------------------() {}
658 // Spur fires an initial shot of Polar Star L3, then charges
659 // as long as key is down. Fires when key released.
660 // Released at L1: nothing
661 // Released at L2: thin beam
662 // Released at L3: dual beam
663 // Released at Max: thick beam
665 // Initial shot is not fired if key is held on a different weapon
666 // and then weapon is switched to spur.
668 // fires the regular Polar Star shot when you first push button
669 static void PFireSpur(void)
671 if (can_fire_spur())
672 FireSimpleBulletOffset(OBJ_POLAR_SHOT, B_PSTAR_L3, -4<<CSF, 0);
675 // fires and handles charged shots
676 static void PHandleSpur(void)
678 static const int FLASH_TIME = 10;
679 Weapon *spur = &player->weapons[WPN_SPUR];
681 if (player->curWeapon != WPN_SPUR)
683 spur->level = 0;
684 spur->xp = 0;
685 return;
688 if (pinputs[FIREKEY])
690 if (!IsWeaponMaxed())
692 int amt = (player->equipmask & EQUIP_TURBOCHARGE) ? 3 : 2;
693 AddXP(amt, true);
695 if (IsWeaponMaxed())
697 sound(SND_SPUR_MAXED);
699 else
701 spur->chargetimer++;
702 if (spur->chargetimer & 2)
704 sound(SND_SPUR_CHARGE_1 + spur->level);
708 else
709 { // keep flashing even once at max
710 statusbar.xpflashcount = FLASH_TIME;
712 if (player->equipmask & EQUIP_WHIMSTAR)
713 add_whimstar(&player->whimstar);
716 else
718 if (spur->chargetimer)
720 if (spur->level > 0 && can_fire_spur())
722 int level = IsWeaponMaxed() ? 2 : (spur->level - 1);
723 FireSimpleBulletOffset(OBJ_SPUR_SHOT, B_SPUR_L1+level, -4<<CSF, 0);
726 spur->chargetimer = 0;
729 spur->level = 0;
730 spur->xp = 0;
733 if (statusbar.xpflashcount > FLASH_TIME)
734 statusbar.xpflashcount = FLASH_TIME;
738 static bool can_fire_spur(void)
740 if (CountObjectsOfType(OBJ_SPUR_SHOT))
741 return false;
743 return true;
746 // returns true if the current weapon has full xp at level 3 (is showing "Max")
747 static bool IsWeaponMaxed(void)
749 Weapon *wpn = &player->weapons[player->curWeapon];
750 return (wpn->level == 2) && (wpn->xp == wpn->max_xp[2]);