NXEngine v1.0.0.5
[NXEngine.git] / player.cpp
blobeb74fb173776e214616afb87d7075ff7d390c599
2 #include "nx.h"
3 #include "player.fdh"
5 Player *player = NULL;
6 static void InitWeapon(int wpn, int l1, int l2, int l3, int maxammo=0);
8 bool pinputs[INPUT_COUNT];
9 bool lastpinputs[INPUT_COUNT];
11 void PInitFirstTime()
13 player->dir = RIGHT;
14 player->hp = player->maxHealth = 3;
15 player->nxflags |= NXFLAG_FOLLOW_SLOPE;
17 player->ninventory = 0;
19 memset(player->weapons, 0, sizeof(player->weapons));
21 InitWeapon(WPN_POLARSTAR, 10, 20, 10);
22 InitWeapon(WPN_MGUN, 30, 40, 10, 100);
23 InitWeapon(WPN_MISSILE, 10, 20, 10, 10);
24 InitWeapon(WPN_FIREBALL, 10, 20, 20);
25 InitWeapon(WPN_BLADE, 15, 18, 0);
26 InitWeapon(WPN_BUBBLER, 10, 20, 5);
27 InitWeapon(WPN_SUPER_MISSILE, 30, 60, 10, 10);
28 InitWeapon(WPN_SNAKE, 30, 40, 16);
29 InitWeapon(WPN_SPUR, 40, 60, 200);
30 InitWeapon(WPN_NEMESIS, 1, 1, 0);
32 player->weapons[WPN_MGUN].SetFireRate(6, 6, 6);
33 player->weapons[WPN_MGUN].SetRechargeRate(5, 5, 5);
35 player->weapons[WPN_BUBBLER].SetFireRate(0, 7, 7);
36 player->weapons[WPN_BUBBLER].SetRechargeRate(20, 1, 1);
38 player->curWeapon = WPN_NONE;
40 if (player->XPText) delete player->XPText;
41 player->XPText = new FloatText(SPR_WHITENUMBERS);
43 // initialize player repel points
44 PInitRepel();
48 static void InitWeapon(int wpn, int l1, int l2, int l3, int maxammo)
50 player->weapons[wpn].max_xp[0] = l1;
51 player->weapons[wpn].max_xp[1] = l2;
52 player->weapons[wpn].max_xp[2] = l3;
53 player->weapons[wpn].maxammo = maxammo;
57 void InitPlayer(void)
59 player->lookaway = false;
60 player->walking = false;
61 player->dead = false;
62 player->drowned = false;
63 player->disabled = false;
65 player->hurt_time = 0;
66 player->hurt_flash_state = 0;
67 player->water_shield_frame = 0;
68 player->movementmode = MOVEMODE_NORMAL;
69 player->inputs_locked_lasttime = true;
71 player->booststate = BOOST_OFF;
72 player->lastbooststate = BOOST_OFF;
73 player->boosterfuel = BOOSTER_FUEL_QTY;
75 player->xinertia = 0;
76 player->yinertia = 0;
78 player->riding = NULL;
79 player->lastriding = NULL;
80 player->cannotride = NULL;
82 player->DamageText->Reset();
83 player->XPText->Reset();
84 statusbar.xpflashcount = 0;
86 PResetWeapons();
87 PSelectSprite();
89 // this prevents a splash if we start underwater, and prevents us
90 // from drowning immediately since our air isn't yet set up
91 player->touchattr = TA_WATER;
92 player->airleft = 1000;
93 player->airshowtimer = 0;
96 Player::~Player()
98 if (XPText)
100 delete XPText;
101 XPText = NULL;
106 void c------------------------------() {}
109 void HandlePlayer(void)
111 // freeze player for the split-second between <TRA to a new map and the
112 // start of the on-entry script for that map. (Fixes: player could shoot during
113 // end sequence if he holds key down).
114 if (game.switchstage.mapno != -1)
115 return;
117 PUpdateInput();
119 if (!player->dead)
121 PHandleAttributes(); // handle special tile attributes
122 PHandleSolidMushyObjects(); // handle objects like bugs marked "solid / mushy"
124 PDoWeapons(); // p_arms.cpp
125 PDoHurtFlash();
127 switch((inputs[DEBUG_MOVE_KEY] && settings->enable_debug_keys) ? MOVEMODE_DEBUG : \
128 player->movementmode)
130 case MOVEMODE_NORMAL:
132 PDoBooster();
133 PDoBoosterEnd();
134 PDoWalking();
135 PDoLooking();
136 PDoJumping();
137 PDoFalling();
138 PSelectFrame();
140 break;
142 case MOVEMODE_ZEROG: // Ironhead battle/UNI 1
144 PHandleZeroG();
146 break;
148 case MOVEMODE_DEBUG:
150 player->xinertia = player->yinertia = 0;
151 player->blockl = player->blockr = player->blockd = player->blocku = 0;
153 if (inputs[DOWNKEY]) player->y += 0x1000;
154 if (inputs[UPKEY]) player->y -= 0x1000;
155 if (inputs[LEFTKEY]) { player->x -= 0x1000; player->dir = LEFT; }
156 if (inputs[RIGHTKEY]) { player->x += 0x1000; player->dir = RIGHT; }
158 map_scroll_jump(player->x, player->y);
160 player->frame = 2;
162 break;
164 default:
166 player->xinertia = player->yinertia = 0;
168 break;
171 // handle some special features, like damage and bouncy, of
172 // 100% solid objects such as moving blocks. It's put at the end
173 // so that we can see the desired inertia of the player before
174 // it's canceled out by any block points that are set. That way
175 // we can tell if the player is trying to move into it.
176 PHandleSolidBrickObjects();
179 // apply inertia
180 PDoPhysics();
182 // thud sound when land on some objects
183 if (player->riding && !player->lastriding &&
184 (player->riding->nxflags & NXFLAG_THUD_ON_RIDING))
186 sound(SND_THUD);
190 // player aftermove routine
191 void HandlePlayer_am(void)
193 //debug("xinertia: %s", strhex(player->xinertia));
194 //debug("yinertia: %s", strhex(player->yinertia));
195 //debug("booststate: %d", player->booststate);
196 //debug("y: %d", player->y>>CSF);
197 //debug("riding %x", player->riding);
198 //debug("block: %d%d%d%d", player->blockl, player->blockr, player->blocku, player->blockd);
200 // if player is riding some sort of platform apply it's inertia to him
201 if (player->riding)
203 player->apply_xinertia(player->riding->xinertia);
204 player->apply_yinertia(player->riding->yinertia);
207 // keep player out of blocks "SMB1 style"
208 PDoRepel();
210 // handle landing and bonking head
211 if (player->blockd && player->yinertia > 0)
213 if (player->yinertia > 0x400 && !player->hide)
214 sound(SND_THUD);
216 player->yinertia = 0;
217 player->jumping = 0;
219 else if (player->blocku && player->yinertia < 0)
221 // he behaves a bit differently when bonking his head on a
222 // solid-brick object vs. bonking his head on the map.
224 // bonk-head star effect
225 if (player->yinertia < -0x200 && !player->hide && \
226 player->blocku == BLOCKED_MAP)
228 sound(SND_BONK_HEAD);
229 effect(player->CenterX(), player->y, EFFECT_BONKPLUS);
232 // bounces off ceiling with booster 0.8
233 if (player->booststate == BOOST_08)
235 player->yinertia = 0x200;
237 else if (player->bopped_object && player->bopped_object->yinertia != 0)
239 // no clear yinertia when bop head on OBJ_BLOCK_MOVEV in labyrinth.
241 else
243 player->yinertia = 0;
246 player->jumping = false;
249 player->lastwalking = player->walking;
250 player->lastriding = player->riding;
251 player->inputs_locked_lasttime = player->inputs_locked;
252 memcpy(lastpinputs, pinputs, sizeof(lastpinputs));
256 void c------------------------------() {}
259 void PDoPhysics(void)
261 if (player->xinertia > 0x5ff) player->xinertia = 0x5ff;
262 if (player->xinertia < -0x5ff) player->xinertia = -0x5ff;
263 if (player->yinertia > 0x5ff) player->yinertia = 0x5ff;
264 if (player->yinertia < -0x5ff) player->yinertia = -0x5ff;
266 if (player->blockd && player->yinertia > 0)
267 player->yinertia = 0;
269 player->apply_yinertia(player->yinertia);
271 // if xinertia is less than the decel speed then maintain the value but don't actually
272 // move anything. It seems a bit odd...but that's the best I can figure to make it
273 // behave like the original.
274 if (player->xinertia > player->decelspeed || player->xinertia < -player->decelspeed)
276 player->apply_xinertia(player->xinertia);
280 void PUpdateInput(void)
282 int i;
284 if (player->inputs_locked || player->disabled)
286 memset(pinputs, 0, sizeof(pinputs));
288 else
290 memcpy(pinputs, inputs, sizeof(pinputs));
292 // prevent jumping/shooting when leaving a messagebox
293 if (player->inputs_locked_lasttime)
295 for(i=0;i<INPUT_COUNT;i++)
296 lastpinputs[i] |= pinputs[i];
299 // allow entering inventory
300 if (justpushed(INVENTORYKEY))
302 if (!game.frozen && !player->dead && GetCurrentScript() == -1)
304 game.setmode(GM_INVENTORY);
308 // Map System
309 if (justpushed(MAPSYSTEMKEY))
311 if (!game.frozen && !player->dead && GetCurrentScript() == -1)
313 if (fade.getstate() == FS_NO_FADE && game.switchstage.mapno == -1)
315 game.setmode(GM_MAP_SYSTEM, game.mode);
323 // handles tile attributes of tiles player is touching
324 void PHandleAttributes(void)
326 static const Point pattrpoints[] = { {8, 8}, {8, 14} };
327 unsigned int attr;
328 int tile;
330 // get attributes of tiles player it touching.
331 // first, we'll check the top pattrpoint alone; this is the point at
332 // which you go underwater, when that point is lower than the water level.
333 // ** There is a spot in Labyrinth W just after the Shop where the positioning
334 // of this point is a minor element in the gameplay, and so it must be set
335 // correctly. If set too high you will not be underwater after climbing up the
336 // small slope and you can just jump over the wall that you shouldn't be able to.
337 attr = player->GetAttributes(&pattrpoints[0], 1, &tile);
339 // water handler -- water uses only the top pattrpoint
340 if (attr & TA_WATER)
342 // check if we just entered the water
343 if (!(player->touchattr & TA_WATER))
345 // splash on entering water quick enough
346 if ((player->yinertia > 0x200 && !player->blockd) || \
347 (player->xinertia < -0x200 || player->xinertia > 0x200))
349 int x = player->CenterX();
350 int y = player->CenterY();
351 int splashtype = !(player->touchattr & TA_HURTS_PLAYER) ? \
352 OBJ_WATER_DROPLET : OBJ_LAVA_DROPLET;
354 for(int i=0;i<8;i++)
356 Object *o = CreateObject(x + (random(-8, 8) << CSF), y, splashtype);
357 o->xinertia = random(-0x200, 0x200) + player->xinertia;
358 o->yinertia = random(-0x200, 0x80) - (player->yinertia >> 1);
361 sound(SND_SPLASH);
365 // setup physics constants for water
366 player->walkspeed = 0x196;
367 player->fallspeed = 0x2ff;
369 player->fallaccel = 0x28;
370 player->jumpfallaccel = 0x10;
372 player->walkaccel = 0x2a;
373 player->jumpwalkaccel = 0x10;
375 player->decelspeed = 0x19;
376 // was set at 0x280 but I believe that makes it impossible to clear one of the long
377 // spike jumps in River
378 player->jumpvelocity = 0x2c0;
380 // decrement air left
381 if (player->equipmask & EQUIP_AIRTANK)
383 player->airleft = 1000;
384 player->airshowtimer = 0;
386 else
388 player->airshowtimer = 60;
389 if (!player->drowned)
391 if (!player->inputs_locked) player->airleft--;
393 if (player->airleft <= 0 && !game.debug.god)
394 { // player drowned
395 // if flag 4000 is set, then we do not drown, but are in the Almond
396 // level after Core battle, and should instead execute script 1100.
397 if (game.flags[4000])
398 { // "your senses dim and the world grows dark"
399 StartScript(1100);
401 else
402 { // nope sorry buddy, no such luck this time
403 Object *o = CreateObject(player->x, player->y, OBJ_NULL);
404 o->sprite = SPR_PDROWNED;
405 o->dir = player->dir;
407 killplayer(SCRIPT_DROWNED);
410 player->drowned = 1;
415 else
417 // setup normal physics constants
418 player->walkspeed = 0x32c;////0x030e;
419 player->fallspeed = 0x5ff;
421 player->fallaccel = 0x50;
422 player->jumpfallaccel = 0x20;
424 player->walkaccel = 0x55;
425 player->jumpwalkaccel = 0x20;
427 player->decelspeed = 0x33;
428 player->jumpvelocity = 0x500;
430 // reset air supply
431 player->airleft = 1000;
432 if (player->airshowtimer) player->airshowtimer--;
435 // add in the bottom pattrpoint, but don't let it set the "water" bit.
436 // only the top pattrpoint can set "water".
437 attr |= (player->GetAttributes(&pattrpoints[1], 1, &tile) & ~TA_WATER);
439 if (attr & TA_HURTS_PLAYER)
440 hurtplayer(10);
442 // water current/wind:
443 // for water currents--get the sum total of several points on the player to see
444 // all the directions he's getting blown around at (support multiple directions)
445 DoWaterCurrents();
446 player->touchattr = attr;
449 // handes player being blown around by water currents
450 void DoWaterCurrents(void)
452 static Point currentpoints[] = { {7, 8},
453 {1, 2}, {1, 8}, {1, 14},
454 {7, 2}, {7, 14},
455 {15,2}, {15, 8}, {15, 14} };
456 int i;
457 static const int current_dir[] = { LEFTMASK, UPMASK, RIGHTMASK, DOWNMASK };
458 uint8_t currentmask;
459 int tile;
461 // check each point in currentpoints[] for a water current, and if found,
462 // add it to the list of directions we're being blown
463 currentmask = 0;
464 for(i=0;i<9;i++)
466 //DebugCrosshair(player->x+(currentpoints[i].x<<CSF),player->y+(currentpoints[i].y<<CSF), 255,0,0);
468 if (player->GetAttributes(&currentpoints[i], 1, &tile) & TA_CURRENT)
470 currentmask |= current_dir[tilecode[tile] & 3];
473 // if the center point (the first one) has no current, then don't
474 // bother checking the rest. as during 90% of the game you are NOT underwater.
475 if (!currentmask) return;
478 // these constants are very critical for Waterway to work properly.
479 // please be careful with them.
480 if (currentmask & LEFTMASK) player->xinertia -= 0x88;
481 if (currentmask & RIGHTMASK) player->xinertia += 0x88;
482 if (currentmask & UPMASK) player->yinertia -= 0x80;
483 if (currentmask & DOWNMASK) player->yinertia += 0x50;
487 void PDoWalking(void)
489 int walk_accel;
490 int limit;
492 walk_accel = (player->blockd) ? player->walkaccel : player->jumpwalkaccel;
494 // walking/moving
495 if (pinputs[LEFTKEY] || pinputs[RIGHTKEY])
497 // we check both without an else so that both keys down=turn right & walk in place
498 if (pinputs[LEFTKEY])
500 player->walking = true;
501 player->dir = LEFT;
503 if (player->xinertia > -player->walkspeed)
505 player->xinertia -= walk_accel;
507 if (player->xinertia < -player->walkspeed)
508 player->xinertia = -player->walkspeed;
512 if (pinputs[RIGHTKEY])
514 player->walking = true;
515 player->dir = RIGHT;
517 if (player->xinertia < player->walkspeed)
519 player->xinertia += walk_accel;
521 if (player->xinertia > player->walkspeed)
522 player->xinertia = player->walkspeed;
526 if (player->walking && !player->lastwalking)
527 player->walkanimframe = 1;
529 else
531 player->walking = false;
532 player->walkanimframe = 0;
533 player->walkanimtimer = 0;
534 // tap sound when stopped walking
535 if (player->lastwalking && player->blockd)
536 sound(SND_PLAYER_WALK);
539 // deceleration
540 if (player->blockd && player->yinertia >= 0)
541 { // deceleration on ground...
542 // always move towards zero at decelspeed
543 if (player->xinertia > 0)
545 if (player->blockr && !pinputs[RIGHTKEY])
547 player->xinertia = 0;
549 else if (player->xinertia > player->decelspeed)
551 player->xinertia -= player->decelspeed;
553 else
555 player->xinertia = 0;
558 else if (player->xinertia < 0)
560 if (player->blockl && !pinputs[LEFTKEY])
562 player->xinertia = 0;
564 else if (player->xinertia < -player->decelspeed)
566 player->xinertia += player->decelspeed;
568 else
570 player->xinertia = 0;
574 else // deceleration in air...
576 // implements 2 things
577 // 1) if player partially hits a brick while in air, his inertia is lesser after he passes it
578 // 2) but, if he's trying to turn around, let him! don't "stick" him to it just because
579 // of a high inertia when he hit it
580 if (player->blockr)
582 limit = (player->dir == RIGHT) ? 0x180 : 0;
583 if (player->xinertia > limit) player->xinertia = limit;
586 if (player->blockl)
588 limit = (player->dir == LEFT) ? -0x180 : 0;
589 if (player->xinertia < limit) player->xinertia = limit;
594 void PDoFalling(void)
596 if (player->disabled)
597 return;
599 if (player->booststate)
600 return;
602 if (game.curmap == STAGE_KINGS_TABLE && \
603 fade.getstate() == FS_FADING)
604 return;
606 // needed to be able to see the falling blocks during
607 // good-ending Helicopter cutscene (otherwise your
608 // invisible character falls and the blocks spawn too low).
609 if (player->hide)
611 player->xinertia = 0;
612 player->yinertia = 0;
613 return;
616 // use jump gravity as long as Jump Key is down and we're moving up,
617 // regardless of whether a jump was ever actually initiated.
618 // this is for the fans that blow up--you can push JUMP to climb higher.
619 if (player->yinertia < 0 && pinputs[JUMPKEY])
620 { // use jump gravity
621 if (player->yinertia < player->fallspeed)
623 player->yinertia += player->jumpfallaccel;
624 if (player->yinertia > player->fallspeed) player->yinertia = player->fallspeed;
627 else
628 { // use normal gravity
629 if (player->yinertia < player->fallspeed)
631 player->yinertia += player->fallaccel;
632 if (player->yinertia > player->fallspeed) player->yinertia = player->fallspeed;
635 // if we no longer qualify for jump gravity then the jump is over
636 player->jumping = 0;
641 void PDoJumping(void)
643 // jumping
644 if (pinputs[JUMPKEY] && !lastpinputs[JUMPKEY])
646 if (player->blockd)
648 if (!player->jumping)
650 player->jumping = true;
651 player->yinertia -= player->jumpvelocity;
652 sound(SND_PLAYER_JUMP);
655 else if ((player->equipmask & (EQUIP_BOOSTER08 | EQUIP_BOOSTER20)))
657 PStartBooster();
663 void PDoLooking(void)
665 int lookscroll_want;
666 int i, key;
668 // looking/aiming up and down
669 player->look = lookscroll_want = 0;
671 if (pinputs[DOWNKEY])
673 if (!player->blockd)
675 player->look = DOWN;
677 else if (!lastpinputs[DOWNKEY])
678 { // activating scripts/talking to NPC's
680 if (!player->walking && !player->lookaway && \
681 !pinputs[JUMPKEY] && !pinputs[FIREKEY])
683 if (!inputs[DEBUG_MOVE_KEY] || !settings->enable_debug_keys)
685 player->lookaway = true;
686 player->xinertia = 0;
687 PTryActivateScript();
692 // can still scroll screen down while standing, even though
693 // it doesn't show any different frame.
694 lookscroll_want = DOWN;
697 if (pinputs[UPKEY])
699 player->look = lookscroll_want = UP;
702 // when looking, pause a second to be sure they really want to do it
703 // before triggering any real screen scrolling
704 if (player->lookscroll != lookscroll_want)
706 if (player->lookscroll_timer >= 4 || !lookscroll_want)
708 player->lookscroll = lookscroll_want;
710 else
712 player->lookscroll_timer++;
715 else
717 player->lookscroll_timer = 0;
720 // deactivation of lookaway
721 if (player->lookaway)
723 // keys which deactivate lookaway when you are facing away from player
724 static const char actionkeys[] = \
725 { LEFTKEY, RIGHTKEY, UPKEY, JUMPKEY, FIREKEY, -1 };
727 // stop looking away if any keys are pushed
728 for(i=0;;i++)
730 key = actionkeys[i];
731 if (key == -1) break;
733 if (pinputs[key])
735 player->lookaway = false;
736 break;
740 if (!player->blockd)
741 player->lookaway = false;
746 void c------------------------------() {}
749 // called when the player has just turned on the booster
750 void PStartBooster(void)
752 if (player->boosterfuel <= 0)
753 return;
755 // booster 2.0 lets you pick a direction and tacks inertia
756 // solid in that direction when first activated
757 if ((player->equipmask & EQUIP_BOOSTER20))
759 // default boost direction if no key is pressed
760 player->booststate = BOOST_UP;
762 // in order of precedence
763 if (inputs[LEFTKEY] || inputs[RIGHTKEY])
764 player->booststate = BOOST_HOZ;
766 if (inputs[DOWNKEY])
767 player->booststate = BOOST_DOWN;
769 if (inputs[UPKEY])
770 player->booststate = BOOST_UP;
772 // set initial inertia full on
773 if (player->booststate == BOOST_UP || player->booststate == BOOST_DOWN)
774 player->xinertia = 0;
776 switch(player->booststate)
778 case BOOST_UP:
779 player->yinertia = -0x5ff;
780 break;
782 case BOOST_DOWN:
783 player->yinertia = 0x5ff;
784 break;
786 case BOOST_HOZ:
788 player->yinertia = 0;
790 if (inputs[LEFTKEY])
791 player->xinertia = -0x5ff;
792 else
793 player->xinertia = 0x5ff;
795 break;
798 else
800 player->booststate = BOOST_08;
802 // help it overcome gravity
803 if (player->yinertia > 0x100)
804 player->yinertia >>= 1;
807 PBoosterSmokePuff();
810 // called every tick to run the booster
811 void PDoBooster(void)
813 /*static const char *statedesc[] = { "OFF", "UP", "DN", "HOZ", "0.8" };
814 debug("fuel: %d", player->boosterfuel);
815 debug("booststate: %s", statedesc[player->booststate]);
816 debug("xinertia: %d", player->xinertia);
817 debug("yinertia: %d", player->yinertia);*/
819 if (!(player->equipmask & (EQUIP_BOOSTER08 | EQUIP_BOOSTER20)))
821 player->booststate = BOOST_OFF;
822 return;
825 if (!pinputs[JUMPKEY])
827 player->booststate = BOOST_OFF;
829 if (player->blockd)
830 player->boosterfuel = BOOSTER_FUEL_QTY;
832 return;
835 if (!player->booststate)
836 return;
838 // player seems to want it active...check the fuel
839 if (player->boosterfuel <= 0)
841 player->booststate = BOOST_OFF;
842 return;
844 else
846 player->boosterfuel--;
849 // ok so then, booster is active right now
850 bool sputtering = false;
852 switch(player->booststate)
854 case BOOST_HOZ:
856 if ((player->dir == LEFT && player->blockl) || \
857 (player->dir == RIGHT && player->blockr))
859 player->yinertia = -0x100;
862 // this probably isn't the right way to do this, but this
863 // bit makes the hurt-hop work if you get hit during a sideways boost
864 //if (player->hitwhileboosting)
865 // player->yinertia = -0x400;
867 if (player->dir == LEFT) player->xinertia -= 0x20;
868 if (player->dir == RIGHT) player->xinertia += 0x20;
870 break;
872 case BOOST_UP:
874 player->yinertia -= 0x20;
876 break;
878 case BOOST_DOWN:
880 player->yinertia += 0x20;
882 break;
884 case BOOST_08:
886 // top speed and sputtering
887 if (player->yinertia < -0x400)
889 player->yinertia += 0x20;
890 sputtering = true; // no sound/smoke this frame
892 else
894 player->yinertia -= 0x20;
897 break;
900 // don't land if we booster through a one-tile high corridor,
901 // but do land if we're, well, landing on something (yinertia not negative).
902 // must be done after booster inertia applied to work properly.
903 // for 1) there's a place in the village next to Mahin that is good for testing this,
904 // for 2) the gaps in outer wall by the little house.
905 if (player->blockd)
907 if (player->yinertia < 0)
908 player->blockd = false;
909 else
911 player->booststate = BOOST_OFF;
912 return;
916 // smoke and sound effects
917 if ((player->boosterfuel % 3) == 1 && !sputtering)
919 PBoosterSmokePuff();
923 // called every tick just after PDoBooster returns.
924 // tones down player's inertia a bit once the Booster 2.0 stops firing
925 void PDoBoosterEnd()
927 // put here to be sure it catches all the different ways the Booster can get turned off
928 //if (!player->booststate)
929 //player->hitwhileboosting = false;
931 if (player->booststate != player->lastbooststate)
933 if (player->booststate == BOOST_OFF && (player->equipmask & EQUIP_BOOSTER20))
935 switch(player->lastbooststate)
937 case BOOST_HOZ:
938 player->xinertia >>= 1;
939 break;
941 case BOOST_UP:
942 player->yinertia >>= 1;
943 break;
948 // in the original touching a slope while boosting horizontally
949 // disables the booster. In that case, we don't want to half the xinertia,
950 // which is why it's here.
951 //if (player->booststate == BOOST_HOZ && CheckStandOnSlope(player))
952 //player->booststate = BOOST_OFF;
954 player->lastbooststate = player->booststate;
957 // spawn a Booster smoke puff
958 void PBoosterSmokePuff()
960 // these are the directions the SMOKE is traveling, not the player
961 // RT LT UP DN
962 static const int smoke_xoffs[] = { 10, 4, 7, 7 };
963 static const int smoke_yoffs[] = { 10, 10, 0, 14 };
964 int smokedir;
966 switch(player->booststate)
968 case BOOST_HOZ: smokedir = (player->dir ^ 1); break;
969 case BOOST_UP: smokedir = DOWN; break;
970 case BOOST_DOWN:smokedir = UP; break;
971 case BOOST_08: smokedir = DOWN; break;
972 default: return;
975 int x = player->x + (smoke_xoffs[smokedir] << CSF);
976 int y = player->y + (smoke_yoffs[smokedir] << CSF);
978 Caret *smoke = effect(x, y, EFFECT_SMOKETRAIL_SLOW);
979 smoke->MoveAtDir(smokedir, 0x200);
981 sound(SND_BOOSTER);
985 void c------------------------------() {}
988 // handle some special characteristics of solid-brick objects,
989 // such as bouncy and damage. Unlike with FLAG_SOLID_MUSHY; the
990 // block/l/r/u/d flags for these objects have already been set in
991 // UpdateBlockStates, so we don't have to worry about those.
992 void PHandleSolidBrickObjects(void)
994 int i;
995 SIFSprite *sprite = player->Sprite();
996 Object *o;
998 // calculate total inertia of player--this is needed so that
999 // the forcefields in the Monster X arena will damage you if
1000 // the treads carry you into them.
1001 int p_xinertia = player->xinertia;
1002 int p_yinertia = player->yinertia;
1003 if (player->riding)
1005 p_xinertia += player->riding->xinertia;
1006 p_yinertia += player->riding->yinertia;
1009 for(i=0;i<nOnscreenObjects;i++)
1011 o = onscreen_objects[i];
1012 if (!(o->flags & FLAG_SOLID_BRICK)) continue;
1014 // left, right, and up contact damage
1015 if (o->damage > 0)
1017 if (player->blockl && player->CheckSolidIntersect(o, &sprite->block_l))
1019 if (p_xinertia < 0 || o->xinertia > 0)
1020 o->DealContactDamage();
1023 if (player->blockr && player->CheckSolidIntersect(o, &sprite->block_r))
1025 if (p_xinertia > 0 || o->xinertia < 0)
1026 o->DealContactDamage();
1029 if (player->blocku && player->CheckSolidIntersect(o, &sprite->block_u))
1031 if (p_yinertia < 0 || o->yinertia > 0)
1032 o->DealContactDamage();
1036 // stuff for when you are standing on it
1037 if (player->blockd && player->CheckSolidIntersect(o, &sprite->block_d))
1039 if (o->damage && (player->yinertia >= 0 || o->yinertia < 0))
1040 o->DealContactDamage();
1042 // don't do weird glitchy shit if we jump while being carried upward
1043 // by an object moving faster than us. handles if you jump while flying
1044 // momorin's rocket.
1045 if (player->yinertia < 0 && o->yinertia < player->yinertia)
1046 player->yinertia = 0;
1048 // handle FLAG_BOUNCY--used eg by treads on Monster X when tipped up
1049 if (o->flags & FLAG_BOUNCY)
1051 if (player->yinertia > (o->yinertia - 0x200))
1052 player->yinertia = (o->yinertia - 0x200);
1054 else if (o->yinertia <= player->yinertia)
1056 // snap his Y right on top if it
1057 player->y = o->SolidTop() - (sprites[player->sprite].block_d[0].y << CSF);
1064 void PHandleSolidMushyObjects(void)
1066 for(int i=0;i<nOnscreenObjects;i++)
1068 Object *o = onscreen_objects[i];
1070 if (o->flags & FLAG_SOLID_MUSHY)
1071 PRunSolidMushy(o);
1075 // handle "solid mushy" objects, such as bugs. These objects are solid but not 100% super
1076 // solid like a brick. Their solidity is more of an "it repels the player" kind of way.
1077 // NOTE: This is also responsible for the horizontal motion you see when hit by many kinds
1078 // of enemies. The hurtplayer damage routine makes you hop vertically, but it is this that
1079 // throws you away horizontally.
1080 void PRunSolidMushy(Object *o)
1082 // cache these, so we're not calling the same functions over and over again
1083 const int p_left = player->SolidLeft();
1084 const int p_right = player->SolidRight();
1085 const int p_top = player->SolidTop();
1086 const int p_bottom = player->SolidBottom();
1088 const int o_left = o->SolidLeft();
1089 const int o_right = o->SolidRight();
1090 const int o_top = o->SolidTop();
1091 const int o_bottom = o->SolidBottom();
1093 static const int MUSHY_MARGIN = (3<<CSF);
1094 static const int STAND_MARGIN = (1<<CSF);
1095 static const int REPEL_FORCE = 0x200;
1097 // hitting sides of object
1098 if ((p_top < (o_bottom - MUSHY_MARGIN)) && (p_bottom > (o_top + MUSHY_MARGIN)))
1100 // left side
1101 if ((p_right > o_left) && (p_right < o->CenterX()))
1103 if (player->xinertia > -REPEL_FORCE)
1104 player->xinertia -= REPEL_FORCE;
1107 // right side
1108 if ((p_left < o_right) && (p_left > o->CenterX()))
1110 if (player->xinertia < REPEL_FORCE)
1111 player->xinertia += REPEL_FORCE;
1115 // bonking head on object or standing on object
1117 // to tell if we are within horizontal bounds to be standing on the object,
1118 // we will check if we have NOT FALLEN OFF the object.
1119 if (p_left > (o_right - STAND_MARGIN) || p_right < (o_left + STAND_MARGIN))
1121 else
1123 // standing on object
1124 if (p_bottom >= o_top && p_bottom <= o->CenterY())
1126 if (o->flags & FLAG_BOUNCY)
1128 if (player->yinertia > (o->yinertia - 0x200))
1129 player->yinertia = (o->yinertia - 0x200);
1131 else
1133 // force to top of sprite if we're REALLY far into it
1134 int em_fline = o->SolidTop() + (3 << CSF);
1135 if (player->SolidBottom() > em_fline)
1137 int over_amt = (em_fline - player->SolidBottom());
1138 int dec_amt = (3 << CSF);
1140 if (over_amt < dec_amt) dec_amt = over_amt;
1141 if (dec_amt < (1<<CSF)) dec_amt = (1<<CSF);
1143 player->apply_yinertia(-dec_amt);
1146 player->blockd = true;
1147 player->riding = o;
1150 else if (p_top < o_bottom && p_top > o->CenterY())
1152 // hit bottom of object with head
1153 if (player->yinertia < 0)
1154 player->yinertia = 0;
1160 void c------------------------------() {}
1163 // does "damage" points of damage to the player
1164 // if even_if_controls_locked is true the damage is
1165 // dealt even if the player's input is locked.
1166 void hurtplayer(int damage)
1168 if (damage == 0) return;
1169 if (!player || !player->hp) return;
1170 if (settings->enable_debug_keys && (game.debug.god || inputs[DEBUG_MOVE_KEY])) return;
1172 if (player->hurt_time)
1173 return;
1175 if (player->hide)
1176 return;
1178 player->hp -= damage;
1179 player->DamageText->AddQty(damage);
1181 player->lookaway = 0;
1182 player->hurt_time = 128;
1184 if (player->equipmask & EQUIP_WHIMSTAR)
1185 remove_whimstar(&player->whimstar);
1187 //if (player->booststate)
1188 //player->hitwhileboosting = true;
1190 if (player->hp <= 0)
1192 sound(SND_PLAYER_DIE);
1193 SmokeClouds(player, 64, 16, 16);
1195 killplayer(SCRIPT_DIED);
1197 else
1199 sound(SND_PLAYER_HURT);
1201 // hop
1202 if (player->movementmode != MOVEMODE_ZEROG)
1203 player->yinertia = -0x400;
1206 // decrement weapon XP.
1207 if (player->equipmask & EQUIP_ARMS_BARRIER)
1208 SubXP(damage);
1209 else
1210 SubXP(damage * 2);
1213 // set the player state to "dead" and execute script "script"
1214 void killplayer(int script)
1216 Replay::end_record();
1217 Replay::end_playback();
1219 player->hp = 0;
1220 player->dead = true;
1221 player->hide = true;
1222 player->xinertia = 0;
1223 player->yinertia = 0;
1224 player->riding = NULL; // why exactly did I say this? i dunno, but not touching for safety
1225 StopLoopSounds(); // important for Almond
1226 StartScript(script);
1230 void c------------------------------() {}
1233 // this is basically a replacement for most of the player code,
1234 // used when the player is in <UNI0001 (the Ironhead battle).
1235 void PHandleZeroG(void)
1237 if (!player->inputs_locked)
1239 if (inputs[LEFTKEY] || inputs[RIGHTKEY])
1241 if (inputs[LEFTKEY]) player->xinertia -= 0x100;
1242 if (inputs[RIGHTKEY]) player->xinertia += 0x100;
1244 else
1245 { // decel
1246 if (player->xinertia < 0x80 || player->xinertia > -0x80)
1248 player->xinertia = 0;
1250 else
1252 player->xinertia += (player->xinertia > 0) ? -0x80 : 0x80;
1256 if (inputs[UPKEY] || inputs[DOWNKEY])
1258 if (inputs[UPKEY]) player->yinertia -= 0x100;
1259 if (inputs[DOWNKEY]) player->yinertia += 0x100;
1261 else
1262 { // decel
1263 if (player->yinertia < 0x80 || player->yinertia > -0x80)
1265 player->yinertia = 0;
1267 else
1269 player->xinertia += (player->xinertia > 0) ? -0x80 : 0x80;
1273 else
1274 { // decel for when inputs locked after victory
1275 if (player->xinertia < 0x80 && player->xinertia > -0x40)
1277 player->xinertia = 0;
1279 else
1281 player->xinertia += (player->xinertia > 0) ? -0x80 : 0x80;
1284 if (player->yinertia < 0x80 && player->yinertia > -0x40)
1286 player->yinertia = 0;
1288 else
1290 player->yinertia += (player->yinertia > 0) ? -0x80 : 0x80;
1294 if (player->xinertia > 0x400) player->xinertia = 0x400;
1295 if (player->xinertia < -0x400) player->xinertia = -0x400;
1296 if (player->yinertia > 0x400) player->yinertia = 0x400;
1297 if (player->yinertia < -0x400) player->yinertia = -0x400;
1299 player->frame = (player->yinertia > 0) ? 1 : 2;
1303 void c------------------------------() {}
1306 void PInitRepel(void)
1308 const int s = SPR_MYCHAR;
1309 int i;
1311 player->nrepel_l = sprites[s].block_l.count;
1312 player->nrepel_r = sprites[s].block_r.count;
1313 player->nrepel_d = sprites[s].block_d.count;
1314 player->nrepel_u = sprites[s].block_u.count;
1316 for(i=0;i<player->nrepel_l;i++)
1318 player->repel_l[i].x = sprites[s].block_l[i].x + 1;
1319 player->repel_l[i].y = sprites[s].block_l[i].y;
1322 for(i=0;i<player->nrepel_r;i++)
1324 player->repel_r[i].x = sprites[s].block_r[i].x - 1;
1325 player->repel_r[i].y = sprites[s].block_r[i].y;
1328 for(i=0;i<player->nrepel_d;i++)
1330 player->repel_d[i].x = sprites[s].block_d[i].x;
1331 player->repel_d[i].y = sprites[s].block_d[i].y - 1;
1334 for(i=0;i<player->nrepel_u;i++)
1336 player->repel_u[i].x = sprites[s].block_u[i].x;
1337 player->repel_u[i].y = sprites[s].block_u[i].y + 1;
1341 // the player's block points are assymetrical--block u/d are closer together than block l/r.
1342 // So it's quite possible to get e.g. your blockl points embedded in a wall by
1343 // falling off the top of it. This function implements a SMB1-style "repel" that
1344 // allows this to happen but then pushes the player out of the block over the next
1345 // few frames.
1346 void PDoRepel(void)
1348 // since this function is called from the aftermove, regular player->blockl etc
1349 // won't be updated until the following frame, so we always check the attributes
1350 // directly here.
1351 static const int REPEL_SPEED = (1<<CSF);
1353 if (settings->enable_debug_keys && inputs[DEBUG_MOVE_KEY])
1354 return;
1356 // pushes player out of walls if he become embedded in them, ala Super Mario 1.
1357 // this can happen for example because his R,L block points are further out than
1358 // his D block points so it's possible to fall really close to a block and
1359 // embed the R or L points further into the block than they should be
1360 if (player->CheckAttribute(player->repel_r, player->nrepel_r, TA_SOLID_PLAYER))
1362 if (!player->CheckAttribute(&sprites[player->sprite].block_l, TA_SOLID_PLAYER))
1364 player->x -= REPEL_SPEED;
1365 //debug("REPEL [to left]");
1369 if (player->CheckAttribute(player->repel_l, player->nrepel_l, TA_SOLID_PLAYER))
1371 if (!player->CheckAttribute(&sprites[player->sprite].block_r, TA_SOLID_PLAYER))
1373 player->x += REPEL_SPEED;
1374 //debug("REPEL [to right]");
1378 // vertical repel doesn't happen normally, but if we get embedded in a
1379 // block somehow, it can happen.
1381 // do repel down
1382 if (player->CheckAttribute(player->repel_u, player->nrepel_u, TA_SOLID_PLAYER))
1384 if (!player->CheckAttribute(&sprites[player->sprite].block_d, TA_SOLID_PLAYER))
1386 player->y += REPEL_SPEED;
1387 //debug("REPEL [down]");
1391 // do repel up
1392 if (player->CheckAttribute(player->repel_d, player->nrepel_d, TA_SOLID_PLAYER))
1394 if (!player->CheckAttribute(&sprites[player->sprite].block_u, TA_SOLID_PLAYER))
1396 player->y -= REPEL_SPEED;
1397 //debug("REPEL [up]");
1404 void c------------------------------() {}
1407 // called when you press down.
1408 // Tries to find an SCRIPTONACTIVATE object you are standing near and activate it.
1409 // if it can't find anything to activate, spawns the "question mark" effect.
1410 void PTryActivateScript()
1412 if (RunScriptAtX(player->CenterX()))
1413 return;
1415 if (player->dir == RIGHT)
1417 if (RunScriptAtX(player->Right()) || RunScriptAtX(player->Left()))
1418 return;
1420 else
1422 if (RunScriptAtX(player->Left()) || RunScriptAtX(player->Right()))
1423 return;
1426 // e.g. Plantation Rocket
1427 if (player->riding && (player->riding->flags & FLAG_SCRIPTONACTIVATE))
1429 StartScript(player->riding->id2);
1430 return;
1433 effect(player->CenterX(), player->CenterY(), EFFECT_QMARK);
1436 static bool RunScriptAtX(int x)
1438 if (RunScriptAtLocation(x, player->y + (8 << CSF)) || \
1439 RunScriptAtLocation(x, player->y + (14 << CSF)) || \
1440 RunScriptAtLocation(x, player->y + (2 << CSF)))
1442 return true;
1445 return false;
1448 static bool RunScriptAtLocation(int x, int y)
1450 // top-to-bottom scan
1451 for(int i=nOnscreenObjects-1; i>=0; i--)
1453 Object *o = onscreen_objects[i];
1455 if (o->flags & FLAG_SCRIPTONACTIVATE)
1457 if (x >= o->Left() && x <= o->Right() && \
1458 y >= o->Top() && y <= o->Bottom())
1460 StartScript(o->id2);
1461 return true;
1466 return false;
1470 void c------------------------------() {}
1473 // does the invincibility flash when the player has recently been hurt
1474 void PDoHurtFlash(void)
1476 // note that hurt_flash_state is NOT cleared when timer reaches 0,
1477 // but this is ok because the number of blinks are and always should be even.
1478 // (if not it wouldn't look right when he unhurts).
1479 if (player->hurt_time)
1481 player->hurt_time--;
1482 player->hurt_flash_state = (player->hurt_time & 2);
1486 // decides which player frame to show
1487 void PSelectFrame(void)
1489 if (player->lookaway)
1490 { // looking away
1491 player->frame = 11;
1493 else if (!player->blockd || player->yinertia < 0)
1494 { // jumping/falling
1495 player->frame = (player->yinertia > 0) ? 1 : 2;
1497 else if (player->walking)
1498 { // do walk animation
1499 static const uint8_t pwalkanimframes[] = { 0, 1, 0, 2 };
1501 if (++player->walkanimtimer >= 5)
1503 player->walkanimtimer = 0;
1504 if (++player->walkanimframe >= 4) player->walkanimframe = 0;
1505 if (pwalkanimframes[player->walkanimframe]==0) sound(SND_PLAYER_WALK);
1508 player->frame = pwalkanimframes[player->walkanimframe];
1510 else
1511 { // standing
1512 player->frame = 0;
1515 // switch frames to "up" or "down" versions if we're looking
1516 if (player->look)
1518 if (player->look == UP)
1520 if (!player->blockd || player->yinertia < 0)
1521 player->frame = 4;
1522 else
1523 player->frame += 3;
1525 else
1527 player->frame += 6;
1531 // mimiga mask support-- it would be better to make equipmask private,
1532 // and funnel all player->equipmask changes through a setter function,
1533 // then I'd feel safe doing this only when equipped items are changed.
1534 PSelectSprite();
1537 // mimiga mask support
1538 void PSelectSprite(void)
1540 player->sprite = (player->equipmask & EQUIP_MIMIGA_MASK) ? \
1541 SPR_MYCHAR_MIMIGA : SPR_MYCHAR;
1545 void c------------------------------() {}
1549 // returns the sprite and frame # to be used for drawing the given weapon
1550 void GetSpriteForGun(int wpn, int look, int *spr, int *frame)
1552 int s;
1554 switch(wpn)
1556 case WPN_SUPER_MISSILE: s = SPR_SUPER_MLAUNCHER; break;
1557 case WPN_NEMESIS: s = SPR_NEMESIS; break;
1558 case WPN_BUBBLER: s = SPR_BUBBLER; break;
1559 case WPN_SPUR: s = SPR_SPUR; break;
1561 default:
1562 s = SPR_WEAPONS_START + (wpn * 2);
1563 break;
1566 if (look)
1568 s++;
1569 *frame = (look == DOWN);
1571 else
1573 *frame = 0;
1576 *spr = s;
1580 // returns the point that a player's shot should be centered on when firing
1581 void GetPlayerShootPoint(int *x_out, int *y_out)
1583 int spr, frame;
1584 int x, y;
1586 GetSpriteForGun(player->curWeapon, player->look, &spr, &frame);
1588 // we have to figure out where the gun is being carried, then figure out where the
1589 // gun's sprite is drawn relative to that, then finally we can offset in the
1590 // shoot point of the gun's sprite.
1591 x = player->x + (sprites[player->sprite].frame[player->frame].dir[player->dir].actionpoint.x << CSF);
1592 x -= sprites[spr].frame[frame].dir[player->dir].drawpoint.x << CSF;
1593 x += sprites[spr].frame[frame].dir[player->dir].actionpoint.x << CSF;
1595 y = player->y + (sprites[player->sprite].frame[player->frame].dir[player->dir].actionpoint.y << CSF);
1596 y -= sprites[spr].frame[frame].dir[player->dir].drawpoint.y << CSF;
1597 y += sprites[spr].frame[frame].dir[player->dir].actionpoint.y << CSF;
1599 *x_out = x;
1600 *y_out = y;
1603 // draws the player
1604 void DrawPlayer(void)
1606 int scr_x, scr_y;
1608 if (player->hide || player->disabled)
1609 return;
1611 // keep his floattext position linked--do NOT update this if he is hidden
1612 // so that floattext doesn't follow him after he dies.
1613 player->DamageText->UpdatePos(player);
1614 player->XPText->UpdatePos(player);
1616 // get screen position to draw him at
1617 scr_x = (player->x >> CSF) - (map.displayed_xscroll >> CSF);
1618 scr_y = (player->y >> CSF) - (map.displayed_yscroll >> CSF);
1620 // draw his gun
1621 if (player->curWeapon != WPN_NONE && player->curWeapon != WPN_BLADE)
1623 int spr, frame;
1624 GetSpriteForGun(player->curWeapon, player->look, &spr, &frame);
1626 // draw the gun at the player's Action Point. Since guns have their Draw Point set
1627 // to point at their handle, this places the handle in the player's hand.
1628 draw_sprite_at_dp(scr_x + sprites[player->sprite].frame[player->frame].dir[player->dir].actionpoint.x, \
1629 scr_y + sprites[player->sprite].frame[player->frame].dir[player->dir].actionpoint.y, \
1630 spr, frame, player->dir);
1633 // draw the player sprite
1634 if (!player->hurt_flash_state)
1636 draw_sprite(scr_x, scr_y, player->sprite, player->frame, player->dir);
1638 // draw the air bubble shield if we have it on
1639 if (((player->touchattr & TA_WATER) && (player->equipmask & EQUIP_AIRTANK)) || \
1640 player->movementmode == MOVEMODE_ZEROG)
1642 draw_sprite_at_dp(scr_x, scr_y, SPR_WATER_SHIELD, \
1643 player->water_shield_frame, player->dir);
1645 if (++player->water_shield_timer > 1)
1647 player->water_shield_frame ^= 1;
1648 player->water_shield_timer = 0;
1653 if (player->equipmask & EQUIP_WHIMSTAR)
1654 draw_whimstars(&player->whimstar);