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