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
];
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
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
;
59 player
->lookaway
= false;
60 player
->walking
= 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
;
78 player
->riding
= NULL
;
79 player
->lastriding
= NULL
;
80 player
->cannotride
= NULL
;
82 player
->DamageText
->Reset();
83 player
->XPText
->Reset();
84 statusbar
.xpflashcount
= 0;
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;
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)
121 PHandleAttributes(); // handle special tile attributes
122 PHandleSolidMushyObjects(); // handle objects like bugs marked "solid / mushy"
124 PDoWeapons(); // p_arms.cpp
127 switch((inputs
[DEBUG_MOVE_KEY
] && settings
->enable_debug_keys
) ? MOVEMODE_DEBUG
: \
128 player
->movementmode
)
130 case MOVEMODE_NORMAL
:
142 case MOVEMODE_ZEROG
: // Ironhead battle/UNI 1
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
);
166 player
->xinertia
= player
->yinertia
= 0;
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();
182 // thud sound when land on some objects
183 if (player
->riding
&& !player
->lastriding
&&
184 (player
->riding
->nxflags
& NXFLAG_THUD_ON_RIDING
))
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
203 player
->apply_xinertia(player
->riding
->xinertia
);
204 player
->apply_yinertia(player
->riding
->yinertia
);
207 // keep player out of blocks "SMB1 style"
210 // handle landing and bonking head
211 if (player
->blockd
&& player
->yinertia
> 0)
213 if (player
->yinertia
> 0x400 && !player
->hide
)
216 player
->yinertia
= 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.
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)
284 if (player
->inputs_locked
|| player
->disabled
)
286 memset(pinputs
, 0, sizeof(pinputs
));
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
);
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} };
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
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
;
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);
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;
388 player
->airshowtimer
= 60;
389 if (!player
->drowned
)
391 if (!player
->inputs_locked
) player
->airleft
--;
393 if (player
->airleft
<= 0 && !game
.debug
.god
)
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"
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
);
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;
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
)
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)
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},
455 {15,2}, {15, 8}, {15, 14} };
457 static const int current_dir
[] = { LEFTMASK
, UPMASK
, RIGHTMASK
, DOWNMASK
};
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
466 //DebugCrosshair(player->x+(currentpoints[i].x<<CSF),player->y+(currentpoints[i].y<<CSF), 255,0,0);
468 if (player
->GetAttributes(¤tpoints
[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)
492 walk_accel
= (player
->blockd
) ? player
->walkaccel
: player
->jumpwalkaccel
;
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;
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;
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;
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
);
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
;
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
;
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
582 limit
= (player
->dir
== RIGHT
) ? 0x180 : 0;
583 if (player
->xinertia
> limit
) player
->xinertia
= limit
;
588 limit
= (player
->dir
== LEFT
) ? -0x180 : 0;
589 if (player
->xinertia
< limit
) player
->xinertia
= limit
;
594 void PDoFalling(void)
596 if (player
->disabled
)
599 if (player
->booststate
)
602 if (game
.curmap
== STAGE_KINGS_TABLE
&& \
603 fade
.getstate() == FS_FADING
)
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).
611 player
->xinertia
= 0;
612 player
->yinertia
= 0;
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
;
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
641 void PDoJumping(void)
644 if (pinputs
[JUMPKEY
] && !lastpinputs
[JUMPKEY
])
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
)))
663 void PDoLooking(void)
668 // looking/aiming up and down
669 player
->look
= lookscroll_want
= 0;
671 if (pinputs
[DOWNKEY
])
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
;
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
;
712 player
->lookscroll_timer
++;
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
731 if (key
== -1) break;
735 player
->lookaway
= false;
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)
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
;
767 player
->booststate
= BOOST_DOWN
;
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
)
779 player
->yinertia
= -0x5ff;
783 player
->yinertia
= 0x5ff;
788 player
->yinertia
= 0;
791 player
->xinertia
= -0x5ff;
793 player
->xinertia
= 0x5ff;
800 player
->booststate
= BOOST_08
;
802 // help it overcome gravity
803 if (player
->yinertia
> 0x100)
804 player
->yinertia
>>= 1;
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
;
825 if (!pinputs
[JUMPKEY
])
827 player
->booststate
= BOOST_OFF
;
830 player
->boosterfuel
= BOOSTER_FUEL_QTY
;
835 if (!player
->booststate
)
838 // player seems to want it active...check the fuel
839 if (player
->boosterfuel
<= 0)
841 player
->booststate
= BOOST_OFF
;
846 player
->boosterfuel
--;
849 // ok so then, booster is active right now
850 bool sputtering
= false;
852 switch(player
->booststate
)
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;
874 player
->yinertia
-= 0x20;
880 player
->yinertia
+= 0x20;
886 // top speed and sputtering
887 if (player
->yinertia
< -0x400)
889 player
->yinertia
+= 0x20;
890 sputtering
= true; // no sound/smoke this frame
894 player
->yinertia
-= 0x20;
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.
907 if (player
->yinertia
< 0)
908 player
->blockd
= false;
911 player
->booststate
= BOOST_OFF
;
916 // smoke and sound effects
917 if ((player
->boosterfuel
% 3) == 1 && !sputtering
)
923 // called every tick just after PDoBooster returns.
924 // tones down player's inertia a bit once the Booster 2.0 stops firing
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
)
938 player
->xinertia
>>= 1;
942 player
->yinertia
>>= 1;
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
962 static const int smoke_xoffs
[] = { 10, 4, 7, 7 };
963 static const int smoke_yoffs
[] = { 10, 10, 0, 14 };
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;
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);
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)
995 SIFSprite
*sprite
= player
->Sprite();
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
;
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
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
)
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
)))
1101 if ((p_right
> o_left
) && (p_right
< o
->CenterX()))
1103 if (player
->xinertia
> -REPEL_FORCE
)
1104 player
->xinertia
-= REPEL_FORCE
;
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
))
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);
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;
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
)
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
);
1199 sound(SND_PLAYER_HURT
);
1202 if (player
->movementmode
!= MOVEMODE_ZEROG
)
1203 player
->yinertia
= -0x400;
1206 // decrement weapon XP.
1207 if (player
->equipmask
& EQUIP_ARMS_BARRIER
)
1213 // set the player state to "dead" and execute script "script"
1214 void killplayer(int script
)
1216 Replay::end_record();
1217 Replay::end_playback();
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;
1246 if (player
->xinertia
< 0x80 || player
->xinertia
> -0x80)
1248 player
->xinertia
= 0;
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;
1263 if (player
->yinertia
< 0x80 || player
->yinertia
> -0x80)
1265 player
->yinertia
= 0;
1269 player
->xinertia
+= (player
->xinertia
> 0) ? -0x80 : 0x80;
1274 { // decel for when inputs locked after victory
1275 if (player
->xinertia
< 0x80 && player
->xinertia
> -0x40)
1277 player
->xinertia
= 0;
1281 player
->xinertia
+= (player
->xinertia
> 0) ? -0x80 : 0x80;
1284 if (player
->yinertia
< 0x80 && player
->yinertia
> -0x40)
1286 player
->yinertia
= 0;
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
;
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
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
1351 static const int REPEL_SPEED
= (1<<CSF
);
1353 if (settings
->enable_debug_keys
&& inputs
[DEBUG_MOVE_KEY
])
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.
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]");
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()))
1415 if (player
->dir
== RIGHT
)
1417 if (RunScriptAtX(player
->Right()) || RunScriptAtX(player
->Left()))
1422 if (RunScriptAtX(player
->Left()) || RunScriptAtX(player
->Right()))
1426 // e.g. Plantation Rocket
1427 if (player
->riding
&& (player
->riding
->flags
& FLAG_SCRIPTONACTIVATE
))
1429 StartScript(player
->riding
->id2
);
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
)))
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
);
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
)
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
];
1515 // switch frames to "up" or "down" versions if we're looking
1518 if (player
->look
== UP
)
1520 if (!player
->blockd
|| player
->yinertia
< 0)
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.
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
)
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;
1562 s
= SPR_WEAPONS_START
+ (wpn
* 2);
1569 *frame
= (look
== DOWN
);
1580 // returns the point that a player's shot should be centered on when firing
1581 void GetPlayerShootPoint(int *x_out
, int *y_out
)
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
;
1604 void DrawPlayer(void)
1608 if (player
->hide
|| player
->disabled
)
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
);
1621 if (player
->curWeapon
!= WPN_NONE
&& player
->curWeapon
!= WPN_BLADE
)
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
);