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