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(¤tpoints[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