1 
2 #include "nx.h"
3 #include "player.fdh"
4 #include "inventory.h"
5 #include "playerstats.fdh"
6 
7 Player *player = NULL;
8 static void InitWeapon(int wpn, int l1, int l2, int l3, int maxammo=0);
9 
10 bool pinputs[INPUT_COUNT];
11 bool lastpinputs[INPUT_COUNT];
12 
PInitFirstTime()13 void PInitFirstTime()
14 {
15 	player->dir = RIGHT;
16 	player->hp = player->maxHealth = 3;
17 	player->nxflags |= NXFLAG_FOLLOW_SLOPE;
18 
19 	player->ninventory = 0;
20 
21 	memset(player->weapons, 0, sizeof(player->weapons));
22 
23 	InitWeapon(WPN_POLARSTAR, 		10, 20, 10);
24 	InitWeapon(WPN_MGUN, 			30, 40, 10, 100);
25 	InitWeapon(WPN_MISSILE,			10, 20, 10, 10);
26 	InitWeapon(WPN_FIREBALL,		10, 20, 20);
27 	InitWeapon(WPN_BLADE,			15, 18, 0);
28 	InitWeapon(WPN_BUBBLER,			10, 20, 5);
29 	InitWeapon(WPN_SUPER_MISSILE,	30, 60, 10, 10);
30 	InitWeapon(WPN_SNAKE,			30, 40, 16);
31 	InitWeapon(WPN_SPUR,			40, 60, 200);
32 	InitWeapon(WPN_NEMESIS,			1,  1,  0);
33 
34 	player->weapons[WPN_MGUN].SetFireRate(6, 6, 6);
35 	player->weapons[WPN_MGUN].SetRechargeRate(5, 5, 5);
36 
37 	player->weapons[WPN_BUBBLER].SetFireRate(0, 7, 7);
38 	player->weapons[WPN_BUBBLER].SetRechargeRate(20, 1, 1);
39 
40 	player->curWeapon = WPN_NONE;
41 
42 	if (player->XPText) delete player->XPText;
43 	player->XPText = new FloatText(SPR_WHITENUMBERS);
44 
45 	// initialize player repel points
46 	PInitRepel();
47 }
48 
49 
InitWeapon(int wpn,int l1,int l2,int l3,int maxammo)50 static void InitWeapon(int wpn, int l1, int l2, int l3, int maxammo)
51 {
52 	player->weapons[wpn].max_xp[0] = l1;
53 	player->weapons[wpn].max_xp[1] = l2;
54 	player->weapons[wpn].max_xp[2] = l3;
55 	player->weapons[wpn].maxammo = maxammo;
56 }
57 
58 
InitPlayer(void)59 void InitPlayer(void)
60 {
61 	player->lookaway = false;
62 	player->walking = false;
63 	player->dead = false;
64 	player->drowned = false;
65 	player->disabled = false;
66 
67 	player->hurt_time = 0;
68 	player->hurt_flash_state = 0;
69 	player->water_shield_frame = 0;
70 	player->movementmode = MOVEMODE_NORMAL;
71 	player->inputs_locked_lasttime = true;
72 
73 	player->booststate = BOOST_OFF;
74 	player->lastbooststate = BOOST_OFF;
75 	player->boosterfuel = BOOSTER_FUEL_QTY;
76 
77 	player->xinertia = 0;
78 	player->yinertia = 0;
79 
80 	player->riding = NULL;
81 	player->lastriding = NULL;
82 	player->cannotride = NULL;
83 
84 	player->DamageText->Reset();
85 	player->XPText->Reset();
86 	statusbar.xpflashcount = 0;
87 
88 	PResetWeapons();
89 	PSelectSprite();
90 
91 	// this prevents a splash if we start underwater, and prevents us
92 	// from drowning immediately since our air isn't yet set up
93 	player->touchattr = TA_WATER;
94 	player->airleft = 1000;
95 	player->airshowtimer = 0;
96 }
97 
~Player()98 Player::~Player()
99 {
100 	if (XPText)
101 	{
102 		delete XPText;
103 		XPText = NULL;
104 	}
105 }
106 
107 /*
108 void c------------------------------() {}
109 */
110 
HandlePlayer(void)111 void HandlePlayer(void)
112 {
113 	// freeze player for the split-second between <TRA to a new map and the
114 	// start of the on-entry script for that map. (Fixes: player could shoot during
115 	// end sequence if he holds key down).
116 	if (game.switchstage.mapno != -1)
117 		return;
118 
119 	PUpdateInput();
120 
121 	if (!player->dead)
122 	{
123 		PHandleAttributes();			// handle special tile attributes
124 		PHandleSolidMushyObjects();		// handle objects like bugs marked "solid / mushy"
125 
126 		PDoWeapons();	// p_arms.cpp
127 		PDoHurtFlash();
128 
129 		switch(player->movementmode)
130 		{
131 			case MOVEMODE_NORMAL:
132 			{
133 				PDoBooster();
134 				PDoBoosterEnd();
135 				PDoWalking();
136 				PDoLooking();
137 				PDoJumping();
138 				PDoFalling();
139 				PSelectFrame();
140 			}
141 			break;
142 
143 			case MOVEMODE_ZEROG:		// Ironhead battle/UNI 1
144 			{
145 				PHandleZeroG();
146 			}
147 			break;
148 
149 			case MOVEMODE_DEBUG:
150 			{
151 				player->xinertia = player->yinertia = 0;
152 				player->blockl = player->blockr = player->blockd = player->blocku = 0;
153 
154 				if (inputs[DOWNKEY]) player->y += 0x1000;
155 				if (inputs[UPKEY]) player->y -= 0x1000;
156 				if (inputs[LEFTKEY]) { player->x -= 0x1000; player->dir = LEFT; }
157 				if (inputs[RIGHTKEY]) { player->x += 0x1000; player->dir = RIGHT; }
158 
159 				map_scroll_jump(player->x, player->y);
160 
161 				player->frame = 2;
162 			}
163 			break;
164 
165 			default:
166 			{
167 				player->xinertia = player->yinertia = 0;
168 			}
169 			break;
170 		}
171 
172 		// handle some special features, like damage and bouncy, of
173 		// 100% solid objects such as moving blocks. It's put at the end
174 		// so that we can see the desired inertia of the player before
175 		// it's canceled out by any block points that are set. That way
176 		// we can tell if the player is trying to move into it.
177 		PHandleSolidBrickObjects();
178 	}
179 
180 	// apply inertia
181 	PDoPhysics();
182 
183 	// thud sound when land on some objects
184 	if (player->riding && !player->lastriding &&
185 		(player->riding->nxflags & NXFLAG_THUD_ON_RIDING))
186 	{
187 		sound(SND_THUD);
188 	}
189 }
190 
191 // player aftermove routine
HandlePlayer_am(void)192 void HandlePlayer_am(void)
193 {
194 	// if player is riding some sort of platform apply it's inertia to him
195 	if (player->riding)
196 	{
197 		player->apply_xinertia(player->riding->xinertia);
198 		player->apply_yinertia(player->riding->yinertia);
199 	}
200 
201 	// keep player out of blocks "SMB1 style"
202 	PDoRepel();
203 
204 	// handle landing and bonking head
205 	if (player->blockd && player->yinertia > 0)
206 	{
207 		if (player->yinertia > 0x400 && !player->hide)
208 			sound(SND_THUD);
209 
210 		player->yinertia = 0;
211 		player->jumping = 0;
212 	}
213 	else if (player->blocku && player->yinertia < 0)
214 	{
215 		// he behaves a bit differently when bonking his head on a
216 		// solid-brick object vs. bonking his head on the map.
217 
218 		// bonk-head star effect
219 		if (player->yinertia < -0x200 && !player->hide && \
220 			player->blocku == BLOCKED_MAP)
221 		{
222 			sound(SND_BONK_HEAD);
223 			effect(player->CenterX(), player->y, EFFECT_BONKPLUS);
224 		}
225 
226 		// bounces off ceiling with booster 0.8
227 		if (player->booststate == BOOST_08)
228 		{
229 			player->yinertia = 0x200;
230 		}
231 		else if (player->bopped_object && player->bopped_object->yinertia != 0)
232 		{
233 			// no clear yinertia when bop head on OBJ_BLOCK_MOVEV in labyrinth.
234 		}
235 		else
236 		{
237 			player->yinertia = 0;
238 		}
239 
240 		player->jumping = false;
241 	}
242 
243 	player->lastwalking = player->walking;
244 	player->lastriding = player->riding;
245 	player->inputs_locked_lasttime = player->inputs_locked;
246 	memcpy(lastpinputs, pinputs, sizeof(lastpinputs));
247 }
248 
249 /*
250 void c------------------------------() {}
251 */
252 
PDoPhysics(void)253 void PDoPhysics(void)
254 {
255 	if (player->xinertia > 0x5ff)  player->xinertia = 0x5ff;
256 	if (player->xinertia < -0x5ff) player->xinertia = -0x5ff;
257 	if (player->yinertia > 0x5ff)  player->yinertia = 0x5ff;
258 	if (player->yinertia < -0x5ff) player->yinertia = -0x5ff;
259 
260 	if (player->blockd && player->yinertia > 0)
261 		player->yinertia = 0;
262 
263 	player->apply_yinertia(player->yinertia);
264 
265 	// if xinertia is less than the decel speed then maintain the value but don't actually
266 	// move anything. It seems a bit odd...but that's the best I can figure to make it
267 	// behave like the original.
268 	if (player->xinertia > player->decelspeed || player->xinertia < -player->decelspeed)
269 	{
270 		player->apply_xinertia(player->xinertia);
271 	}
272 }
273 
PUpdateInput(void)274 void PUpdateInput(void)
275 {
276 int i;
277 static unsigned inventory_delay = 0;
278 
279 	if (player->inputs_locked || player->disabled)
280 	{
281 		memset(pinputs, 0, sizeof(pinputs));
282 	}
283 	else
284 	{
285 		memcpy(pinputs, inputs, sizeof(pinputs));
286 
287 		// prevent jumping/shooting when leaving a messagebox
288 		if (player->inputs_locked_lasttime)
289 		{
290 			for(i=0;i<INPUT_COUNT;i++)
291 				lastpinputs[i] |= pinputs[i];
292 		}
293 
294       if (inventory_delay != 0)
295          inventory_delay--;
296       else
297       {
298          // allow entering inventory
299          if (justpushed(INVENTORYKEY))
300          {
301             if (!game.frozen && !player->dead && GetCurrentScript() == -1)
302             {
303                game.setmode(GM_INVENTORY);
304                inventory_delay = 15;
305             }
306          }
307       }
308 
309 		// Map System
310 		if (justpushed(MAPSYSTEMKEY) && (FindInventory(ITEM_MAP_SYSTEM)!=-1))
311 		{
312 			if (!game.frozen && !player->dead && GetCurrentScript() == -1)
313 			{
314 				if (fade.getstate() == FS_NO_FADE && game.switchstage.mapno == -1)
315 				{
316 					game.setmode(GM_MAP_SYSTEM, game.mode);
317 				}
318 			}
319 		}
320 	}
321 }
322 
323 
324 // handles tile attributes of tiles player is touching
PHandleAttributes(void)325 void PHandleAttributes(void)
326 {
327 static const Point pattrpoints[] = { {8, 8}, {8, 14} };
328 static const Point hurt_bottom_attrpoint =   {8, 7};
329 unsigned int attr;
330 int tile;
331 
332 	// get attributes of tiles player it touching.
333 	// first, we'll check the top pattrpoint alone; this is the point at
334 	// which you go underwater, when that point is lower than the water level.
335 	// ** There is a spot in Labyrinth W just after the Shop where the positioning
336 	// of this point is a minor element in the gameplay, and so it must be set
337 	// correctly. If set too high you will not be underwater after climbing up the
338 	// small slope and you can just jump over the wall that you shouldn't be able to.
339 	attr = player->GetAttributes(&pattrpoints[0], 1, &tile);
340 
341 	// water handler -- water uses only the top pattrpoint
342 	if (attr & TA_WATER)
343 	{
344 		// check if we just entered the water
345 		if (!(player->touchattr & TA_WATER))
346 		{
347 			// splash on entering water quick enough
348 			if ((player->yinertia > 0x200 && !player->blockd) || \
349 				(player->xinertia < -0x200 || player->xinertia > 0x200))
350 			{
351 				int x = player->CenterX();
352 				int y = player->CenterY();
353 				int splashtype = !(player->touchattr & TA_HURTS_PLAYER) ? \
354 									OBJ_WATER_DROPLET : OBJ_LAVA_DROPLET;
355 
356 				for(int i=0;i<8;i++)
357 				{
358 					Object *o = CreateObject(x + (random(-8, 8) << CSF), y, splashtype);
359 					o->xinertia = random(-0x200, 0x200) + player->xinertia;
360 					o->yinertia = random(-0x200, 0x80) - (player->yinertia >> 1);
361 				}
362 
363 				sound(SND_SPLASH);
364 			}
365 		}
366 
367 		// setup physics constants for water
368 		player->walkspeed = 0x196;
369 		player->fallspeed = 0x2ff;
370 
371 		player->fallaccel = 0x28;
372 		player->jumpfallaccel = 0x14;
373 
374 		player->walkaccel = 0x2a;
375 		player->jumpwalkaccel = 0x10;
376 
377 		player->decelspeed = 0x19;
378 		// was set at 0x280 but I believe that makes it impossible to clear one of the long
379 		// spike jumps in River
380 		player->jumpvelocity = 0x2c0;
381 
382 		// decrement air left
383 		if (player->equipmask & EQUIP_AIRTANK)
384 		{
385 			player->airleft = 1000;
386 			player->airshowtimer = 0;
387 		}
388 		else
389 		{
390 			player->airshowtimer = 60;
391 			if (!player->drowned)
392 			{
393 				if (!player->inputs_locked) player->airleft--;
394 
395 				if (player->airleft <= 0)
396 				{	// player drowned
397 					// if flag 4000 is set, then we do not drown, but are in the Almond
398 					// level after Core battle, and should instead execute script 1100.
399 					if (game.flags[4000])
400 					{	// "your senses dim and the world grows dark"
401 						StartScript(1100);
402 					}
403 					else
404 					{	// nope sorry buddy, no such luck this time
405 						Object *o = CreateObject(player->x, player->y, OBJ_NULL);
406 						o->sprite = SPR_PDROWNED;
407 						o->dir = player->dir;
408 
409 						killplayer(SCRIPT_DROWNED);
410 					}
411 
412 					player->drowned = 1;
413 				}
414 			}
415 		}
416 	}
417 	else
418 	{
419 		// setup normal physics constants
420 		player->walkspeed = 0x32c;////0x030e;
421 		player->fallspeed = 0x5ff;
422 
423 		player->fallaccel = 0x50;
424 		player->jumpfallaccel = 0x20;
425 
426 		player->walkaccel = 0x55;
427 		player->jumpwalkaccel = 0x20;
428 
429 		player->decelspeed = 0x33;
430 		player->jumpvelocity = 0x500;
431 
432 		// reset air supply
433 		player->airleft = 1000;
434 		if (player->airshowtimer) player->airshowtimer--;
435 	}
436 
437 	// add in the bottom pattrpoint, but don't let it set the "water" bit.
438 	// only the top pattrpoint can set "water".
439 	attr |= (player->GetAttributes(&pattrpoints[1], 1, &tile) & ~TA_WATER);
440 
441    // If the tile has "hurt" bit, we recheck it with the the different bottom attrpoint.
442    // This fixes bottom spikes in water level, last cave... Standart bottom attrpoint
443    // allows intersection with spike only for 1 pixel, but origianl game allows 8 pixels
444    // of safe intersection.
445 	if (attr & TA_HURTS_PLAYER)
446 	{
447 		attr &= ~TA_HURTS_PLAYER;
448 		attr |= (player->GetAttributes(&hurt_bottom_attrpoint, 1, &tile) & ~TA_WATER);
449 	}
450 
451 	if (attr & TA_HURTS_PLAYER)
452 		hurtplayer(10);
453 
454 	// water current/wind:
455 	// for water currents--get the sum total of several points on the player to see
456 	// all the directions he's getting blown around at (support multiple directions)
457 	DoWaterCurrents();
458 	player->touchattr = attr;
459 }
460 
461 // handes player being blown around by water currents
DoWaterCurrents(void)462 void DoWaterCurrents(void)
463 {
464 static Point currentpoints[] = { {7, 8},
465 								 {1, 2}, {1, 8}, {1, 14},
466 								 {7, 2}, {7, 14},
467 								 {15,2}, {15, 8}, {15, 14} };
468 int i;
469 static const int current_dir[] = { LEFTMASK, UPMASK, RIGHTMASK, DOWNMASK };
470 uint8_t currentmask;
471 int tile;
472 
473 	// check each point in currentpoints[] for a water current, and if found,
474 	// add it to the list of directions we're being blown
475 	currentmask = 0;
476 	for(i=0;i<9;i++)
477 	{
478 		//DebugCrosshair(player->x+(currentpoints[i].x<<CSF),player->y+(currentpoints[i].y<<CSF), 255,0,0);
479 
480 		if (player->GetAttributes(&currentpoints[i], 1, &tile) & TA_CURRENT)
481 		{
482 			currentmask |= current_dir[tilecode[tile] & 3];
483 		}
484 
485 		// if the center point (the first one) has no current, then don't
486 		// bother checking the rest. as during 90% of the game you are NOT underwater.
487 		if (!currentmask) return;
488 	}
489 
490 	// these constants are very critical for Waterway to work properly.
491 	// please be careful with them.
492 	if (currentmask & LEFTMASK)  player->xinertia -= 0x88;
493 	if (currentmask & RIGHTMASK) player->xinertia += 0x88;
494 	if (currentmask & UPMASK)    player->yinertia -= 0x80;
495 	if (currentmask & DOWNMASK)  player->yinertia += 0x50;
496 }
497 
498 
PDoWalking(void)499 void PDoWalking(void)
500 {
501 int walk_accel;
502 int limit;
503 
504 	walk_accel = (player->blockd) ? player->walkaccel : player->jumpwalkaccel;
505 
506 	// walking/moving
507 	if (pinputs[LEFTKEY] || pinputs[RIGHTKEY])
508 	{
509 		// we check both without an else so that both keys down=turn right & walk in place
510 		if (pinputs[LEFTKEY])
511 		{
512 			player->walking = true;
513 			player->dir = LEFT;
514 
515 			if (player->xinertia > -player->walkspeed)
516 			{
517 				player->xinertia -= walk_accel;
518 
519 				if (player->xinertia < -player->walkspeed)
520 					player->xinertia = -player->walkspeed;
521 			}
522 		}
523 
524 		if (pinputs[RIGHTKEY])
525 		{
526 			player->walking = true;
527 			player->dir = RIGHT;
528 
529 			if (player->xinertia < player->walkspeed)
530 			{
531 				player->xinertia += walk_accel;
532 
533 				if (player->xinertia > player->walkspeed)
534 					player->xinertia = player->walkspeed;
535 			}
536 		}
537 
538 		if (player->walking && !player->lastwalking)
539 			player->walkanimframe = 1;
540 	}
541 	else
542 	{
543 		player->walking = false;
544 		player->walkanimframe = 0;
545 		player->walkanimtimer = 0;
546 		// tap sound when stopped walking
547 		if (player->lastwalking && player->blockd)
548 			sound(SND_PLAYER_WALK);
549 	}
550 
551 	// deceleration
552 	if (player->blockd && player->yinertia >= 0)
553 	{	// deceleration on ground...
554 		// always move towards zero at decelspeed
555 		if (player->xinertia > 0)
556 		{
557 			if (player->blockr && !pinputs[RIGHTKEY])
558 			{
559 				player->xinertia = 0;
560 			}
561 			else if (player->xinertia > player->decelspeed)
562 			{
563 				player->xinertia -= player->decelspeed;
564 			}
565 			else
566 			{
567 				player->xinertia = 0;
568 			}
569 		}
570 		else if (player->xinertia < 0)
571 		{
572 			if (player->blockl && !pinputs[LEFTKEY])
573 			{
574 				player->xinertia = 0;
575 			}
576 			else if (player->xinertia < -player->decelspeed)
577 			{
578 				player->xinertia += player->decelspeed;
579 			}
580 			else
581 			{
582 				player->xinertia = 0;
583 			}
584 		}
585 	}
586 	else		// deceleration in air...
587 	{
588 		// implements 2 things
589 		//	1) if player partially hits a brick while in air, his inertia is lesser after he passes it
590 		//	2) but, if he's trying to turn around, let him! don't "stick" him to it just because
591 		//		of a high inertia when he hit it
592 		if (player->blockr)
593 		{
594 			limit = (player->dir == RIGHT) ? 0x180 : 0;
595 			if (player->xinertia > limit) player->xinertia = limit;
596 		}
597 
598 		if (player->blockl)
599 		{
600 			limit = (player->dir == LEFT) ? -0x180 : 0;
601 			if (player->xinertia < limit) player->xinertia = limit;
602 		}
603 	}
604 }
605 
PDoFalling(void)606 void PDoFalling(void)
607 {
608 	if (player->disabled)
609 		return;
610 
611 	if (player->booststate)
612 		return;
613 
614 	if (game.curmap == STAGE_KINGS_TABLE && \
615 		fade.getstate() == FS_FADING)
616 		return;
617 
618 	// needed to be able to see the falling blocks during
619 	// good-ending Helicopter cutscene (otherwise your
620 	// invisible character falls and the blocks spawn too low).
621 	if (player->hide)
622 	{
623 		player->xinertia = 0;
624 		player->yinertia = 0;
625 		return;
626 	}
627 
628 	// use jump gravity as long as Jump Key is down and we're moving up,
629 	// regardless of whether a jump was ever actually initiated.
630 	// this is for the fans that blow up--you can push JUMP to climb higher.
631 	if (player->yinertia < 0 && pinputs[JUMPKEY])
632 	{	// use jump gravity
633 		if (player->yinertia < player->fallspeed)
634 		{
635 			player->yinertia += player->jumpfallaccel;
636 			if (player->yinertia > player->fallspeed) player->yinertia = player->fallspeed;
637 		}
638 	}
639 	else
640 	{	// use normal gravity
641 		if (player->yinertia < player->fallspeed)
642 		{
643 			player->yinertia += player->fallaccel;
644 			if (player->yinertia > player->fallspeed) player->yinertia = player->fallspeed;
645 		}
646 
647 		// if we no longer qualify for jump gravity then the jump is over
648 		player->jumping = 0;
649 	}
650 }
651 
652 
PDoJumping(void)653 void PDoJumping(void)
654 {
655 	// jumping
656 	if (pinputs[JUMPKEY] && !lastpinputs[JUMPKEY])
657 	{
658 		if (player->blockd)
659 		{
660 			if (!player->jumping)
661 			{
662 				player->jumping = true;
663 				player->yinertia -= player->jumpvelocity;
664 				sound(SND_PLAYER_JUMP);
665 			}
666 		}
667 		else if ((player->equipmask & (EQUIP_BOOSTER08 | EQUIP_BOOSTER20)))
668 		{
669 			PStartBooster();
670 		}
671 	}
672 }
673 
674 
PDoLooking(void)675 void PDoLooking(void)
676 {
677 int lookscroll_want;
678 int i, key;
679 
680 	// looking/aiming up and down
681 	player->look = lookscroll_want = 0;
682 
683 	if (pinputs[DOWNKEY])
684 	{
685 		if (!player->blockd)
686 		{
687 			player->look = DOWN;
688 		}
689 		else if (!lastpinputs[DOWNKEY])
690 		{	// activating scripts/talking to NPC's
691 
692 			if (!player->walking && !player->lookaway && \
693 				!pinputs[JUMPKEY] && !pinputs[FIREKEY])
694 			{
695 				if (!inputs[DEBUG_MOVE_KEY])
696 				{
697 					player->lookaway = true;
698 					player->xinertia = 0;
699 					PTryActivateScript();
700 				}
701 			}
702 		}
703 
704 		// can still scroll screen down while standing, even though
705 		// it doesn't show any different frame.
706 		lookscroll_want = DOWN;
707 	}
708 
709 	if (pinputs[UPKEY])
710 	{
711 		player->look = lookscroll_want = UP;
712 	}
713 
714 	// when looking, pause a second to be sure they really want to do it
715 	// before triggering any real screen scrolling
716 	if (player->lookscroll != lookscroll_want)
717 	{
718 		if (player->lookscroll_timer >= 4 || !lookscroll_want)
719 		{
720 			player->lookscroll = lookscroll_want;
721 		}
722 		else
723 		{
724 			player->lookscroll_timer++;
725 		}
726 	}
727 	else
728 	{
729 		player->lookscroll_timer = 0;
730 	}
731 
732 	// deactivation of lookaway
733 	if (player->lookaway)
734 	{
735 		// keys which deactivate lookaway when you are facing away from player
736 		static const char actionkeys[] = \
737 			{ LEFTKEY, RIGHTKEY, UPKEY, JUMPKEY, FIREKEY, (char)-1 };
738 
739 		// stop looking away if any keys are pushed
740 		for(i=0;;i++)
741 		{
742 			key = actionkeys[i];
743 			if (key == -1) break;
744 
745 			if (pinputs[key])
746 			{
747 				player->lookaway = false;
748 				break;
749 			}
750 		}
751 
752 		if (!player->blockd)
753 			player->lookaway = false;
754 	}
755 }
756 
757 /*
758 void c------------------------------() {}
759 */
760 
761 // called when the player has just turned on the booster
PStartBooster(void)762 void PStartBooster(void)
763 {
764 	if (player->boosterfuel <= 0)
765 		return;
766 
767 	// booster 2.0 lets you pick a direction and tacks inertia
768 	// solid in that direction when first activated
769 	if ((player->equipmask & EQUIP_BOOSTER20))
770 	{
771 		// default boost direction if no key is pressed
772 		player->booststate = BOOST_UP;
773 
774 		// in order of precedence
775 		if (inputs[LEFTKEY] || inputs[RIGHTKEY])
776 			player->booststate = BOOST_HOZ;
777 
778 		if (inputs[DOWNKEY])
779 			player->booststate = BOOST_DOWN;
780 
781 		if (inputs[UPKEY])
782 			player->booststate = BOOST_UP;
783 
784 		// set initial inertia full on
785 		if (player->booststate == BOOST_UP || player->booststate == BOOST_DOWN)
786 			player->xinertia = 0;
787 
788 		switch(player->booststate)
789 		{
790 			case BOOST_UP:
791 				player->yinertia = -0x5ff;
792 			break;
793 
794 			case BOOST_DOWN:
795 				player->yinertia = 0x5ff;
796 			break;
797 
798 			case BOOST_HOZ:
799 			{
800 				player->yinertia = 0;
801 
802 				if (inputs[LEFTKEY])
803 					player->xinertia = -0x5ff;
804 				else
805 					player->xinertia = 0x5ff;
806 			}
807 			break;
808 		}
809 	}
810 	else
811 	{
812 		player->booststate = BOOST_08;
813 
814 		// help it overcome gravity
815 		if (player->yinertia > 0x100)
816 			player->yinertia >>= 1;
817 	}
818 
819 	PBoosterSmokePuff();
820 }
821 
822 // called every tick to run the booster
PDoBooster(void)823 void PDoBooster(void)
824 {
825 	if (!(player->equipmask & (EQUIP_BOOSTER08 | EQUIP_BOOSTER20)))
826 	{
827 		player->booststate = BOOST_OFF;
828 		return;
829 	}
830 
831 	if (!pinputs[JUMPKEY])
832 	{
833 		player->booststate = BOOST_OFF;
834 
835 		if (player->blockd)
836 			player->boosterfuel = BOOSTER_FUEL_QTY;
837 
838 		return;
839 	}
840 
841 	if (!player->booststate)
842 		return;
843 
844 	// player seems to want it active...check the fuel
845 	if (player->boosterfuel <= 0)
846 	{
847 		player->booststate = BOOST_OFF;
848 		return;
849 	}
850 	else
851 	{
852 		player->boosterfuel--;
853 	}
854 
855 	// ok so then, booster is active right now
856 	bool sputtering = false;
857 
858 	switch(player->booststate)
859 	{
860 		case BOOST_HOZ:
861 		{
862 			if ((player->dir == LEFT && player->blockl) || \
863 				(player->dir == RIGHT && player->blockr))
864 			{
865 				player->yinertia = -0x100;
866 			}
867 
868 			// this probably isn't the right way to do this, but this
869 			// bit makes the hurt-hop work if you get hit during a sideways boost
870 			//if (player->hitwhileboosting)
871 			//	player->yinertia = -0x400;
872 
873 			if (player->dir == LEFT)  player->xinertia -= 0x20;
874 			if (player->dir == RIGHT) player->xinertia += 0x20;
875 		}
876 		break;
877 
878 		case BOOST_UP:
879 		{
880 			player->yinertia -= 0x20;
881 		}
882 		break;
883 
884 		case BOOST_DOWN:
885 		{
886 			player->yinertia += 0x20;
887 		}
888 		break;
889 
890 		case BOOST_08:
891 		{
892 			// top speed and sputtering
893 			if (player->yinertia < -0x400)
894 			{
895 				player->yinertia += 0x20;
896 				sputtering = true;	// no sound/smoke this frame
897 			}
898 			else
899 			{
900 				player->yinertia -= 0x20;
901 			}
902 		}
903 		break;
904 	}
905 
906 	// don't land if we booster through a one-tile high corridor,
907 	// but do land if we're, well, landing on something (yinertia not negative).
908 	// must be done after booster inertia applied to work properly.
909 	// for 1) there's a place in the village next to Mahin that is good for testing this,
910 	// for 2) the gaps in outer wall by the little house.
911 	if (player->blockd)
912 	{
913 		if (player->yinertia < 0)
914 			player->blockd = false;
915 		else
916 		{
917 			player->booststate = BOOST_OFF;
918 			return;
919 		}
920 	}
921 
922 	// smoke and sound effects
923 	if ((player->boosterfuel % 3) == 1 && !sputtering)
924 	{
925 		PBoosterSmokePuff();
926 	}
927 }
928 
929 // called every tick just after PDoBooster returns.
930 // tones down player's inertia a bit once the Booster 2.0 stops firing
PDoBoosterEnd()931 void PDoBoosterEnd()
932 {
933 	// put here to be sure it catches all the different ways the Booster can get turned off
934 	//if (!player->booststate)
935 		//player->hitwhileboosting = false;
936 
937 	if (player->booststate != player->lastbooststate)
938 	{
939 		if (player->booststate == BOOST_OFF && (player->equipmask & EQUIP_BOOSTER20))
940 		{
941 			switch(player->lastbooststate)
942 			{
943 				case BOOST_HOZ:
944 					player->xinertia >>= 1;
945 				break;
946 
947 				case BOOST_UP:
948 					player->yinertia >>= 1;
949 				break;
950 			}
951 		}
952 	}
953 
954 	// in the original touching a slope while boosting horizontally
955 	// disables the booster. In that case, we don't want to half the xinertia,
956 	// which is why it's here.
957 	//if (player->booststate == BOOST_HOZ && CheckStandOnSlope(player))
958 		//player->booststate = BOOST_OFF;
959 
960 	player->lastbooststate = player->booststate;
961 }
962 
963 // spawn a Booster smoke puff
PBoosterSmokePuff()964 void PBoosterSmokePuff()
965 {
966 	// these are the directions the SMOKE is traveling, not the player
967 	//                                 RT   LT    UP    DN
968 	static const int smoke_xoffs[] = { 10,   4,   7,    7  };
969 	static const int smoke_yoffs[] = { 10,  10,   0,   14  };
970 	int smokedir;
971 
972 	switch(player->booststate)
973 	{
974 		case BOOST_HOZ: smokedir = (player->dir ^ 1); break;
975 		case BOOST_UP:	smokedir = DOWN; break;
976 		case BOOST_DOWN:smokedir = UP; break;
977 		case BOOST_08:	smokedir = DOWN; break;
978 		default:		return;
979 	}
980 
981 	int x = player->x + (smoke_xoffs[smokedir] << CSF);
982 	int y = player->y + (smoke_yoffs[smokedir] << CSF);
983 
984 	Caret *smoke = effect(x, y, EFFECT_SMOKETRAIL_SLOW);
985 	smoke->MoveAtDir(smokedir, 0x200);
986 
987 	sound(SND_BOOSTER);
988 }
989 
990 /*
991 void c------------------------------() {}
992 */
993 
994 // handle some special characteristics of solid-brick objects,
995 // such as bouncy and damage. Unlike with FLAG_SOLID_MUSHY; the
996 // block/l/r/u/d flags for these objects have already been set in
997 // UpdateBlockStates, so we don't have to worry about those.
PHandleSolidBrickObjects(void)998 void PHandleSolidBrickObjects(void)
999 {
1000 int i;
1001 SIFSprite *sprite = player->Sprite();
1002 Object *o;
1003 
1004 	// calculate total inertia of player--this is needed so that
1005 	// the forcefields in the Monster X arena will damage you if
1006 	// the treads carry you into them.
1007 	int p_xinertia = player->xinertia;
1008 	int p_yinertia = player->yinertia;
1009 	if (player->riding)
1010 	{
1011 		p_xinertia += player->riding->xinertia;
1012 		p_yinertia += player->riding->yinertia;
1013 	}
1014 
1015 	for(i=0;i<nOnscreenObjects;i++)
1016 	{
1017 		o = onscreen_objects[i];
1018 		if (!(o->flags & FLAG_SOLID_BRICK)) continue;
1019 
1020 		// left, right, and up contact damage
1021 		if (o->damage > 0)
1022 		{
1023 			if (player->blockl && player->CheckSolidIntersect(o, &sprite->block_l))
1024 			{
1025 				if (p_xinertia < 0 || o->xinertia > 0)
1026 					o->DealContactDamage();
1027 			}
1028 
1029 			if (player->blockr && player->CheckSolidIntersect(o, &sprite->block_r))
1030 			{
1031 				if (p_xinertia > 0 || o->xinertia < 0)
1032 					o->DealContactDamage();
1033 			}
1034 
1035 			if (player->blocku && player->CheckSolidIntersect(o, &sprite->block_u))
1036 			{
1037 				if (p_yinertia < 0 || o->yinertia > 0)
1038 					o->DealContactDamage();
1039 			}
1040 		}
1041 
1042 		// stuff for when you are standing on it
1043 		if (player->blockd && player->CheckSolidIntersect(o, &sprite->block_d))
1044 		{
1045 			if (o->damage && (player->yinertia >= 0 || o->yinertia < 0))
1046 				o->DealContactDamage();
1047 
1048 			// don't do weird glitchy shit if we jump while being carried upward
1049 			// by an object moving faster than us. handles if you jump while flying
1050 			// momorin's rocket.
1051 			if (player->yinertia < 0 && o->yinertia < player->yinertia)
1052 				player->yinertia = 0;
1053 
1054 			// handle FLAG_BOUNCY--used eg by treads on Monster X when tipped up
1055 			if (o->flags & FLAG_BOUNCY)
1056 			{
1057 				if (player->yinertia > (o->yinertia - 0x200))
1058 					player->yinertia = (o->yinertia - 0x200);
1059 			}
1060 			else if (o->yinertia <= player->yinertia)
1061 			{
1062 				// snap his Y right on top if it
1063 				player->y = o->SolidTop() - (sprites[player->sprite].block_d[0].y << CSF);
1064 			}
1065 		}
1066 	}
1067 }
1068 
1069 
PHandleSolidMushyObjects(void)1070 void PHandleSolidMushyObjects(void)
1071 {
1072 	for(int i=0;i<nOnscreenObjects;i++)
1073 	{
1074 		Object *o = onscreen_objects[i];
1075 
1076 		if (o->flags & FLAG_SOLID_MUSHY)
1077 			PRunSolidMushy(o);
1078 	}
1079 }
1080 
1081 // handle "solid mushy" objects, such as bugs. These objects are solid but not 100% super
1082 // solid like a brick. Their solidity is more of an "it repels the player" kind of way.
1083 // NOTE: This is also responsible for the horizontal motion you see when hit by many kinds
1084 // of enemies. The hurtplayer damage routine makes you hop vertically, but it is this that
1085 // throws you away horizontally.
PRunSolidMushy(Object * o)1086 void PRunSolidMushy(Object *o)
1087 {
1088 	// cache these, so we're not calling the same functions over and over again
1089 	const int p_left = player->SolidLeft();
1090 	const int p_right = player->SolidRight();
1091 	const int p_top = player->SolidTop();
1092 	const int p_bottom = player->SolidBottom();
1093 
1094 	const int o_left = o->SolidLeft();
1095 	const int o_right = o->SolidRight();
1096 	const int o_top = o->SolidTop();
1097 	const int o_bottom = o->SolidBottom();
1098 
1099 	static const int MUSHY_MARGIN = (3<<CSF);
1100 	static const int STAND_MARGIN = (1<<CSF);
1101 	static const int REPEL_FORCE = 0x200;
1102 
1103 	// hitting sides of object
1104 	if ((p_top < (o_bottom - MUSHY_MARGIN)) && (p_bottom > (o_top + MUSHY_MARGIN)))
1105 	{
1106 		// left side
1107 		if ((p_right > o_left) && (p_right < o->CenterX()))
1108 		{
1109 			if (player->xinertia > -REPEL_FORCE)
1110 				player->xinertia -= REPEL_FORCE;
1111 		}
1112 
1113 		// right side
1114 		if ((p_left < o_right) && (p_left > o->CenterX()))
1115 		{
1116 			if (player->xinertia < REPEL_FORCE)
1117 				player->xinertia += REPEL_FORCE;
1118 		}
1119 	}
1120 
1121 	// bonking head on object or standing on object
1122 
1123 	// to tell if we are within horizontal bounds to be standing on the object,
1124 	// we will check if we have NOT FALLEN OFF the object.
1125 	if (p_left > (o_right - STAND_MARGIN) || p_right < (o_left + STAND_MARGIN))
1126 	{ }
1127 	else
1128 	{
1129 		// standing on object
1130 		if (p_bottom >= o_top && p_bottom <= o->CenterY())
1131 		{
1132 			if (o->flags & FLAG_BOUNCY)
1133 			{
1134 				if (player->yinertia > (o->yinertia - 0x200))
1135 					player->yinertia = (o->yinertia - 0x200);
1136 			}
1137 			else
1138 			{
1139 				// force to top of sprite if we're REALLY far into it
1140 				int em_fline = o->SolidTop() + (3 << CSF);
1141 				if (player->SolidBottom() > em_fline)
1142 				{
1143 					int over_amt = (em_fline - player->SolidBottom());
1144 					int dec_amt = (3 << CSF);
1145 
1146 					if (over_amt < dec_amt) dec_amt = over_amt;
1147 					if (dec_amt < (1<<CSF)) dec_amt = (1<<CSF);
1148 
1149 					player->apply_yinertia(-dec_amt);
1150 				}
1151 
1152 				player->blockd = true;
1153 				player->riding = o;
1154 			}
1155 		}
1156 		else if (p_top < o_bottom && p_top > o->CenterY())
1157 		{
1158 			// hit bottom of object with head
1159 			if (player->yinertia < 0)
1160 				player->yinertia = 0;
1161 		}
1162 	}
1163 }
1164 
1165 /*
1166 void c------------------------------() {}
1167 */
1168 
1169 // does "damage" points of damage to the player
1170 // if even_if_controls_locked is true the damage is
1171 // dealt even if the player's input is locked.
hurtplayer(int damage)1172 void hurtplayer(int damage)
1173 {
1174 	if (damage == 0) return;
1175 	if (!player || !player->hp) return;
1176 
1177 	if (player->hurt_time)
1178 		return;
1179 
1180 	if (player->hide)
1181 		return;
1182 
1183 	player->hp -= damage;
1184 	player->DamageText->AddQty(damage);
1185 
1186 	player->lookaway = 0;
1187 	player->hurt_time = 128;
1188 
1189 	if (player->equipmask & EQUIP_WHIMSTAR)
1190 		remove_whimstar(&player->whimstar);
1191 
1192 	//if (player->booststate)
1193 		//player->hitwhileboosting = true;
1194 
1195 	if (player->hp <= 0)
1196 	{
1197 		sound(SND_PLAYER_DIE);
1198 		SmokeClouds(player, 64, 16, 16);
1199 
1200 		killplayer(SCRIPT_DIED);
1201 	}
1202 	else
1203 	{
1204 		sound(SND_PLAYER_HURT);
1205 
1206 		// hop
1207 		if (player->movementmode != MOVEMODE_ZEROG)
1208 			player->yinertia = -0x400;
1209 	}
1210 
1211 	// decrement weapon XP.
1212 	if (player->equipmask & EQUIP_ARMS_BARRIER)
1213 		SubXP(damage);
1214 	else
1215 		SubXP(damage * 2);
1216 }
1217 
1218 // set the player state to "dead" and execute script "script"
killplayer(int script)1219 void killplayer(int script)
1220 {
1221 	player->hp = 0;
1222 	player->dead = true;
1223 	player->hide = true;
1224 	player->xinertia = 0;
1225 	player->yinertia = 0;
1226 	player->riding = NULL;			// why exactly did I say this? i dunno, but not touching for safety
1227 	StopLoopSounds();				// important for Almond
1228 	StartScript(script);
1229 }
1230 
1231 /*
1232 void c------------------------------() {}
1233 */
1234 
1235 // this is basically a replacement for most of the player code,
1236 // used when the player is in <UNI0001 (the Ironhead battle).
PHandleZeroG(void)1237 void PHandleZeroG(void)
1238 {
1239 	if (!player->inputs_locked)
1240 	{
1241 		if (inputs[LEFTKEY] || inputs[RIGHTKEY])
1242 		{
1243 			if (inputs[LEFTKEY])  player->xinertia -= 0x100;
1244 			if (inputs[RIGHTKEY]) player->xinertia += 0x100;
1245 		}
1246 		else
1247 		{	// decel
1248 			if (player->xinertia < 0x80 || player->xinertia > -0x80)
1249 			{
1250 				player->xinertia = 0;
1251 			}
1252 			else
1253 			{
1254 				player->xinertia += (player->xinertia > 0) ? -0x80 : 0x80;
1255 			}
1256 		}
1257 
1258 		if (inputs[UPKEY] || inputs[DOWNKEY])
1259 		{
1260 			if (inputs[UPKEY]) player->yinertia -= 0x100;
1261 			if (inputs[DOWNKEY]) player->yinertia += 0x100;
1262 		}
1263 		else
1264 		{	// decel
1265 			if (player->yinertia < 0x80 || player->yinertia > -0x80)
1266 			{
1267 				player->yinertia = 0;
1268 			}
1269 			else
1270 			{
1271 				player->xinertia += (player->xinertia > 0) ? -0x80 : 0x80;
1272 			}
1273 		}
1274 	}
1275 	else
1276 	{	// decel for when inputs locked after victory
1277 		if (player->xinertia < 0x80 && player->xinertia > -0x40)
1278 		{
1279 			player->xinertia = 0;
1280 		}
1281 		else
1282 		{
1283 			player->xinertia += (player->xinertia > 0) ? -0x80 : 0x80;
1284 		}
1285 
1286 		if (player->yinertia < 0x80 && player->yinertia > -0x40)
1287 		{
1288 			player->yinertia = 0;
1289 		}
1290 		else
1291 		{
1292 			player->yinertia += (player->yinertia > 0) ? -0x80 : 0x80;
1293 		}
1294 	}
1295 
1296 	if (player->xinertia > 0x400)  player->xinertia = 0x400;
1297 	if (player->xinertia < -0x400) player->xinertia = -0x400;
1298 	if (player->yinertia > 0x400)  player->yinertia = 0x400;
1299 	if (player->yinertia < -0x400) player->yinertia = -0x400;
1300 
1301 	player->frame = (player->yinertia > 0) ? 1 : 2;
1302 }
1303 
1304 /*
1305 void c------------------------------() {}
1306 */
1307 
PInitRepel(void)1308 void PInitRepel(void)
1309 {
1310 const int s = SPR_MYCHAR;
1311 int i;
1312 
1313 	player->nrepel_l = sprites[s].block_l.count;
1314 	player->nrepel_r = sprites[s].block_r.count;
1315 	player->nrepel_d = sprites[s].block_d.count;
1316 	player->nrepel_u = sprites[s].block_u.count;
1317 
1318 	for(i=0;i<player->nrepel_l;i++)
1319 	{
1320 		player->repel_l[i].x = sprites[s].block_l[i].x + 1;
1321 		player->repel_l[i].y = sprites[s].block_l[i].y;
1322 	}
1323 
1324 	for(i=0;i<player->nrepel_r;i++)
1325 	{
1326 		player->repel_r[i].x = sprites[s].block_r[i].x - 1;
1327 		player->repel_r[i].y = sprites[s].block_r[i].y;
1328 	}
1329 
1330 	for(i=0;i<player->nrepel_d;i++)
1331 	{
1332 		player->repel_d[i].x = sprites[s].block_d[i].x;
1333 		player->repel_d[i].y = sprites[s].block_d[i].y - 1;
1334 	}
1335 
1336 	for(i=0;i<player->nrepel_u;i++)
1337 	{
1338 		player->repel_u[i].x = sprites[s].block_u[i].x;
1339 		player->repel_u[i].y = sprites[s].block_u[i].y + 1;
1340 	}
1341 }
1342 
1343 // the player's block points are assymetrical--block u/d are closer together than block l/r.
1344 // So it's quite possible to get e.g. your blockl points embedded in a wall by
1345 // falling off the top of it. This function implements a SMB1-style "repel" that
1346 // allows this to happen but then pushes the player out of the block over the next
1347 // few frames.
PDoRepel(void)1348 void PDoRepel(void)
1349 {
1350 	// since this function is called from the aftermove, regular player->blockl etc
1351 	// won't be updated until the following frame, so we always check the attributes
1352 	// directly here.
1353 	static const int REPEL_SPEED =	(1<<CSF);
1354 
1355 	// pushes player out of walls if he become embedded in them, ala Super Mario 1.
1356 	// this can happen for example because his R,L block points are further out than
1357 	// his D block points so it's possible to fall really close to a block and
1358 	// embed the R or L points further into the block than they should be
1359 	if (player->CheckAttribute(player->repel_r, player->nrepel_r, TA_SOLID_PLAYER))
1360 	{
1361 		if (!player->CheckAttribute(&sprites[player->sprite].block_l, TA_SOLID_PLAYER))
1362 		{
1363 			player->x -= REPEL_SPEED;
1364 		}
1365 	}
1366 
1367 	if (player->CheckAttribute(player->repel_l, player->nrepel_l, TA_SOLID_PLAYER))
1368 	{
1369 		if (!player->CheckAttribute(&sprites[player->sprite].block_r, TA_SOLID_PLAYER))
1370 		{
1371 			player->x += REPEL_SPEED;
1372 		}
1373 	}
1374 
1375 	// vertical repel doesn't happen normally, but if we get embedded in a
1376 	// block somehow, it can happen.
1377 	/*
1378 	// do repel down
1379 	if (player->CheckAttribute(player->repel_u, player->nrepel_u, TA_SOLID_PLAYER))
1380 	{
1381 		if (!player->CheckAttribute(&sprites[player->sprite].block_d, TA_SOLID_PLAYER))
1382 		{
1383 			player->y += REPEL_SPEED;
1384 			//debug("REPEL [down]");
1385 		}
1386 	}
1387 
1388 	// do repel up
1389 	if (player->CheckAttribute(player->repel_d, player->nrepel_d, TA_SOLID_PLAYER))
1390 	{
1391 		if (!player->CheckAttribute(&sprites[player->sprite].block_u, TA_SOLID_PLAYER))
1392 		{
1393 			player->y -= REPEL_SPEED;
1394 			//debug("REPEL [up]");
1395 		}
1396 	}
1397 	*/
1398 }
1399 
1400 /*
1401 void c------------------------------() {}
1402 */
1403 
1404 // called when you press down.
1405 // Tries to find an SCRIPTONACTIVATE object you are standing near and activate it.
1406 // if it can't find anything to activate, spawns the "question mark" effect.
PTryActivateScript()1407 void PTryActivateScript()
1408 {
1409 	if (RunScriptAtX(player->CenterX()))
1410 		return;
1411 
1412 	if (player->dir == RIGHT)
1413 	{
1414 		if (RunScriptAtX(player->Right()) || RunScriptAtX(player->Left()))
1415 			return;
1416 	}
1417 	else
1418 	{
1419 		if (RunScriptAtX(player->Left()) || RunScriptAtX(player->Right()))
1420 			return;
1421 	}
1422 
1423 	// e.g. Plantation Rocket
1424 	if (player->riding && (player->riding->flags & FLAG_SCRIPTONACTIVATE))
1425 	{
1426 		StartScript(player->riding->id2);
1427 		return;
1428 	}
1429 
1430 	effect(player->CenterX(), player->CenterY(), EFFECT_QMARK);
1431 }
1432 
RunScriptAtX(int x)1433 static bool RunScriptAtX(int x)
1434 {
1435 	if (RunScriptAtLocation(x, player->y + (8 << CSF)) || \
1436 		RunScriptAtLocation(x, player->y + (14 << CSF)) || \
1437 		RunScriptAtLocation(x, player->y + (2 << CSF)))
1438 	{
1439 		return true;
1440 	}
1441 
1442 	return false;
1443 }
1444 
RunScriptAtLocation(int x,int y)1445 static bool RunScriptAtLocation(int x, int y)
1446 {
1447 	// top-to-bottom scan
1448 	for(int i=nOnscreenObjects-1; i>=0; i--)
1449 	{
1450 		Object *o = onscreen_objects[i];
1451 
1452 		if (o->flags & FLAG_SCRIPTONACTIVATE)
1453 		{
1454 			if (x >= o->Left() && x <= o->Right() && \
1455 				y >= o->Top() && y <= o->Bottom())
1456 			{
1457 				StartScript(o->id2);
1458 				return true;
1459 			}
1460 		}
1461 	}
1462 
1463 	return false;
1464 }
1465 
1466 /*
1467 void c------------------------------() {}
1468 */
1469 
1470 // does the invincibility flash when the player has recently been hurt
PDoHurtFlash(void)1471 void PDoHurtFlash(void)
1472 {
1473 	// note that hurt_flash_state is NOT cleared when timer reaches 0,
1474 	// but this is ok because the number of blinks are and always should be even.
1475 	// (if not it wouldn't look right when he unhurts).
1476 	if (player->hurt_time)
1477 	{
1478 		player->hurt_time--;
1479 		player->hurt_flash_state = (player->hurt_time & 2);
1480 	}
1481 }
1482 
1483 // decides which player frame to show
PSelectFrame(void)1484 void PSelectFrame(void)
1485 {
1486 	if (player->lookaway)
1487 	{	// looking away
1488 		player->frame = 11;
1489 	}
1490 	else if (!player->blockd || player->yinertia < 0)
1491 	{	// jumping/falling
1492 		player->frame = (player->yinertia > 0) ? 1 : 2;
1493 	}
1494 	else if (player->walking)
1495 	{	// do walk animation
1496 		static const uint8_t pwalkanimframes[] = { 0, 1, 0, 2 };
1497 
1498 		if (++player->walkanimtimer >= 5)
1499 		{
1500 			player->walkanimtimer = 0;
1501 			if (++player->walkanimframe >= 4) player->walkanimframe = 0;
1502 			if (pwalkanimframes[player->walkanimframe]==0) sound(SND_PLAYER_WALK);
1503 		}
1504 
1505 		player->frame = pwalkanimframes[player->walkanimframe];
1506 	}
1507 	else
1508 	{	// standing
1509 		player->frame = 0;
1510 	}
1511 
1512 	// switch frames to "up" or "down" versions if we're looking
1513 	if (player->look)
1514 	{
1515 		if (player->look == UP)
1516 		{
1517 			if (!player->blockd || player->yinertia < 0)
1518 				player->frame = 4;
1519 			else
1520 				player->frame += 3;
1521 		}
1522 		else
1523 		{
1524 			player->frame += 6;
1525 		}
1526 	}
1527 
1528 	// mimiga mask support-- it would be better to make equipmask private,
1529 	// and funnel all player->equipmask changes through a setter function,
1530 	// then I'd feel safe doing this only when equipped items are changed.
1531 	PSelectSprite();
1532 }
1533 
1534 // mimiga mask support
PSelectSprite(void)1535 void PSelectSprite(void)
1536 {
1537 	player->sprite = (player->equipmask & EQUIP_MIMIGA_MASK) ? \
1538 					SPR_MYCHAR_MIMIGA : SPR_MYCHAR;
1539 }
1540 
1541 /*
1542 void c------------------------------() {}
1543 */
1544 
1545 
1546 // returns the sprite and frame # to be used for drawing the given weapon
GetSpriteForGun(int wpn,int look,int * spr,int * frame)1547 void GetSpriteForGun(int wpn, int look, int *spr, int *frame)
1548 {
1549 int s;
1550 
1551 	switch(wpn)
1552 	{
1553 		case WPN_SUPER_MISSILE: s = SPR_SUPER_MLAUNCHER; break;
1554 		case WPN_NEMESIS: s = SPR_NEMESIS; break;
1555 		case WPN_BUBBLER: s = SPR_BUBBLER; break;
1556 		case WPN_SPUR: s = SPR_SPUR; break;
1557 
1558 		default:
1559 			s = SPR_WEAPONS_START + (wpn * 2);
1560 		break;
1561 	}
1562 
1563 	if (look)
1564 	{
1565 		s++;
1566 		*frame = (look == DOWN);
1567 	}
1568 	else
1569 	{
1570 		*frame = 0;
1571 	}
1572 
1573 	*spr = s;
1574 }
1575 
1576 
1577 // returns the point that a player's shot should be centered on when firing
GetPlayerShootPoint(int * x_out,int * y_out)1578 void GetPlayerShootPoint(int *x_out, int *y_out)
1579 {
1580 int spr, frame;
1581 int x, y;
1582 
1583 	GetSpriteForGun(player->curWeapon, player->look, &spr, &frame);
1584 
1585 	// we have to figure out where the gun is being carried, then figure out where the
1586 	// gun's sprite is drawn relative to that, then finally we can offset in the
1587 	// shoot point of the gun's sprite.
1588 	x = player->x + (sprites[player->sprite].frame[player->frame].dir[player->dir].actionpoint.x << CSF);
1589 	x -= sprites[spr].frame[frame].dir[player->dir].drawpoint.x << CSF;
1590 	x += sprites[spr].frame[frame].dir[player->dir].actionpoint.x << CSF;
1591 
1592 	y = player->y + (sprites[player->sprite].frame[player->frame].dir[player->dir].actionpoint.y << CSF);
1593 	y -= sprites[spr].frame[frame].dir[player->dir].drawpoint.y << CSF;
1594 	y += sprites[spr].frame[frame].dir[player->dir].actionpoint.y << CSF;
1595 
1596 	*x_out = x;
1597 	*y_out = y;
1598 }
1599 
1600 // draws the player
DrawPlayer(void)1601 void DrawPlayer(void)
1602 {
1603 int scr_x, scr_y;
1604 
1605 	if (player->hide || player->disabled)
1606 		return;
1607 
1608 	// keep his floattext position linked--do NOT update this if he is hidden
1609 	// so that floattext doesn't follow him after he dies.
1610 	player->DamageText->UpdatePos(player);
1611 	player->XPText->UpdatePos(player);
1612 
1613 	// get screen position to draw him at
1614 	scr_x = (player->x >> CSF) - (map.displayed_xscroll >> CSF);
1615 	scr_y = (player->y >> CSF) - (map.displayed_yscroll >> CSF);
1616 
1617 	// draw his gun
1618 	if (player->curWeapon != WPN_NONE && player->curWeapon != WPN_BLADE)
1619 	{
1620 		int spr, frame;
1621 		GetSpriteForGun(player->curWeapon, player->look, &spr, &frame);
1622 
1623 		// draw the gun at the player's Action Point. Since guns have their Draw Point set
1624 		// to point at their handle, this places the handle in the player's hand.
1625 		draw_sprite_at_dp(scr_x + sprites[player->sprite].frame[player->frame].dir[player->dir].actionpoint.x, \
1626 						  scr_y + sprites[player->sprite].frame[player->frame].dir[player->dir].actionpoint.y, \
1627 						  spr, frame, player->dir);
1628 	}
1629 
1630 	// draw the player sprite
1631 	if (!player->hurt_flash_state)
1632 	{
1633 		draw_sprite(scr_x, scr_y, player->sprite, player->frame, player->dir);
1634 
1635 		// draw the air bubble shield if we have it on
1636 		if (((player->touchattr & TA_WATER) && (player->equipmask & EQUIP_AIRTANK)) || \
1637 			player->movementmode == MOVEMODE_ZEROG)
1638 		{
1639 			draw_sprite_at_dp(scr_x, scr_y, SPR_WATER_SHIELD, \
1640 							  player->water_shield_frame, player->dir);
1641 
1642 			if (++player->water_shield_timer > 1)
1643 			{
1644 				player->water_shield_frame ^= 1;
1645 				player->water_shield_timer = 0;
1646 			}
1647 		}
1648 	}
1649 
1650 	if (player->equipmask & EQUIP_WHIMSTAR)
1651 		draw_whimstars(&player->whimstar);
1652 }
1653 
1654 
1655