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