341 lines
11 KiB
C
341 lines
11 KiB
C
/**
|
|
* Behavior for bhvPiranhaPlant.
|
|
* This controls Piranha Plants, which alternate between sleeping, attacking,
|
|
* and dying, primarily depending on Mario's proximity and interaction state.
|
|
*/
|
|
|
|
/**
|
|
* Reset the Piranha Plant back to a sleeping animation, no matter what state
|
|
* it was in previously, and make it intangible. If Mario is close, transition
|
|
* directly to the sleeping state.
|
|
*/
|
|
void piranha_plant_act_idle(void) {
|
|
cur_obj_become_intangible();
|
|
cur_obj_init_animation_with_sound(8);
|
|
|
|
#if BUGFIX_PIRANHA_PLANT_STATE_RESET
|
|
/**
|
|
* This call is necessary because a Piranha Plant may enter this state
|
|
* with a scale below 1, which would cause it to appear shrunken. See
|
|
* documentation for, and calls to, piranha_plant_reset_when_far().
|
|
*/
|
|
cur_obj_scale(1);
|
|
#endif
|
|
|
|
if (o->oDistanceToMario < 1200.0f) {
|
|
o->oAction = PIRANHA_PLANT_ACT_SLEEPING;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the player has interacted with the Piranha Plant. If the Piranha
|
|
* Plant was attacked, move it to the dying state. If the player interacted
|
|
* with it through some other means (e.g. by running into it), move it to the
|
|
* woken up state.
|
|
*
|
|
* @return 1 if the player interacted with the Piranha Plant, 0 otherwise
|
|
*/
|
|
s32 piranha_plant_check_interactions(void) {
|
|
s32 i;
|
|
s32 interacted = 1;
|
|
if (o->oInteractStatus & INT_STATUS_INTERACTED) {
|
|
func_80321080(50);
|
|
if (o->oInteractStatus & INT_STATUS_WAS_ATTACKED) {
|
|
cur_obj_play_sound_2(SOUND_OBJ2_PIRANHA_PLANT_DYING);
|
|
|
|
// Spawn 20 intangible purple particles that quickly dissipate.
|
|
for (i = 0; i < 20; i++) {
|
|
spawn_object(o, MODEL_PURPLE_MARBLE, bhvPurpleParticle);
|
|
}
|
|
o->oAction = PIRANHA_PLANT_ACT_ATTACKED;
|
|
} else {
|
|
o->oAction = PIRANHA_PLANT_ACT_WOKEN_UP;
|
|
}
|
|
o->oInteractStatus = 0;
|
|
} else {
|
|
interacted = 0;
|
|
}
|
|
return interacted;
|
|
}
|
|
|
|
#define PIRANHA_PLANT_SLEEP_MUSIC_PLAYING 0
|
|
|
|
/**
|
|
* Make the Piranha Plant sleep. If Mario moves too quickly, move the Piranha
|
|
* Plant to the woken up state. Otherwise, play the lullaby if Mario is close
|
|
* enough. If the player interacts with the Piranha Plant, it will act according
|
|
* to piranha_plant_check_interactions().
|
|
*/
|
|
void piranha_plant_act_sleeping(void) {
|
|
cur_obj_become_tangible();
|
|
o->oInteractType = INTERACT_BOUNCE_TOP;
|
|
|
|
cur_obj_init_animation_with_sound(8);
|
|
|
|
cur_obj_set_hitbox_radius_and_height(250.0f, 200.0f);
|
|
cur_obj_set_hurtbox_radius_and_height(150.0f, 100.0f);
|
|
|
|
#if BUGFIX_PIRANHA_PLANT_SLEEP_DAMAGE
|
|
/**
|
|
* Make Piranha Plants harmless, but tangible, while they sleep.
|
|
*/
|
|
o->oDamageOrCoinValue = 0;
|
|
#elif defined(VERSION_EU)
|
|
/**
|
|
* Make Piranha Plants harmful when sleeping - but do it explicitly.
|
|
*/
|
|
o->oDamageOrCoinValue = 3;
|
|
#endif
|
|
|
|
if (o->oDistanceToMario < 400.0f) {
|
|
if (mario_moving_fast_enough_to_make_piranha_plant_bite()) {
|
|
o->oAction = PIRANHA_PLANT_ACT_WOKEN_UP;
|
|
}
|
|
} else if (o->oDistanceToMario < 1000.0f) {
|
|
play_secondary_music(SEQ_EVENT_PIRANHA_PLANT, 0, 255, 1000);
|
|
o->oPiranhaPlantSleepMusicState = PIRANHA_PLANT_SLEEP_MUSIC_PLAYING;
|
|
} else if (o->oPiranhaPlantSleepMusicState == PIRANHA_PLANT_SLEEP_MUSIC_PLAYING) {
|
|
o->oPiranhaPlantSleepMusicState++;
|
|
func_80321080(50);
|
|
}
|
|
piranha_plant_check_interactions();
|
|
}
|
|
|
|
/**
|
|
* Make the Piranha Plant wake up and stop the lullaby. After a few frames, move
|
|
* to the biting state.
|
|
*/
|
|
void piranha_plant_act_woken_up(void) {
|
|
#if BUGFIX_PIRANHA_PLANT_SLEEP_DAMAGE || defined(VERSION_EU)
|
|
/**
|
|
* Make Piranha Plants damage the player while awake. This call is only
|
|
* necessary in the US version because it is set to 3 by default and is
|
|
* never changed in the JP version.
|
|
*/
|
|
o->oDamageOrCoinValue = 3;
|
|
#endif
|
|
if (o->oTimer == 0)
|
|
func_80321080(50);
|
|
|
|
if (piranha_plant_check_interactions() == 0)
|
|
if (o->oTimer > 10)
|
|
o->oAction = PIRANHA_PLANT_ACT_BITING;
|
|
}
|
|
|
|
#if BUGFIX_PIRANHA_PLANT_STATE_RESET
|
|
/**
|
|
* If the Piranha Plant is far from the player, move it to the idle state.
|
|
*
|
|
* This fixes an issue where a player where could unload a Piranha Plant
|
|
* during another state, then re-enter its activation radius to resume it from
|
|
* that state.
|
|
*
|
|
* For example, if one exits the Piranha Plant's activation radius while it is
|
|
* dying:
|
|
* - In the JP version, it will continue its animation where it left off,
|
|
* leading to a potentially confusing player experience if the player had
|
|
* been away for a long time.
|
|
* - In the US version, it will respawn intact when you re-enter its
|
|
* activation radius. One could then kill it again to receive its blue coin.
|
|
*/
|
|
void piranha_plant_reset_when_far(void) {
|
|
if (o->activeFlags & ACTIVE_FLAG_FAR_AWAY) {
|
|
o->oAction = PIRANHA_PLANT_ACT_IDLE;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Make the Piranha Plant play a falling-over animation and move to the dying
|
|
* state.
|
|
*/
|
|
void piranha_plant_attacked(void) {
|
|
cur_obj_become_intangible();
|
|
cur_obj_init_animation_with_sound(2);
|
|
o->oInteractStatus = 0;
|
|
if (cur_obj_check_if_near_animation_end())
|
|
o->oAction = PIRANHA_PLANT_ACT_SHRINK_AND_DIE;
|
|
#if BUGFIX_PIRANHA_PLANT_STATE_RESET
|
|
piranha_plant_reset_when_far(); // see this function's comment
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Make the Piranha Plant play a sound of defeat, shrink, and then spawn a
|
|
* blue coin. Then, move it to the waiting state.
|
|
*/
|
|
void piranha_plant_act_shrink_and_die(void) {
|
|
if (o->oTimer == 0) {
|
|
cur_obj_play_sound_2(SOUND_OBJ_ENEMY_DEFEAT_SHRINK);
|
|
o->oPiranhaPlantScale = 1.0f;
|
|
}
|
|
|
|
/**
|
|
* Note that this if-statement occurs unconditionally after the above if-
|
|
* statement. Since the Piranha Plant's scale is 1.0f by default, perhaps
|
|
* this was intentional. However, it is equally plausible that the
|
|
* programmers meant to type `else if`.
|
|
*/
|
|
if (o->oPiranhaPlantScale > 0.0f) {
|
|
// Shrink by 0.04 per frame.
|
|
o->oPiranhaPlantScale = o->oPiranhaPlantScale - 0.04;
|
|
} else {
|
|
o->oPiranhaPlantScale = 0.0f;
|
|
cur_obj_spawn_loot_blue_coin();
|
|
o->oAction = PIRANHA_PLANT_ACT_WAIT_TO_RESPAWN;
|
|
}
|
|
|
|
cur_obj_scale(o->oPiranhaPlantScale);
|
|
|
|
#if BUGFIX_PIRANHA_PLANT_STATE_RESET
|
|
piranha_plant_reset_when_far(); // see this function's comment
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Wait for Mario to move far away, then respawn the Piranha Plant.
|
|
*/
|
|
void piranha_plant_act_wait_to_respawn(void) {
|
|
if (o->oDistanceToMario > 1200.0f) {
|
|
o->oAction = PIRANHA_PLANT_ACT_RESPAWN;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the Piranha Plant to the sleeping animation and unshrink it. When fully-
|
|
* grown, set it to the idle state.
|
|
*/
|
|
void piranha_plant_act_respawn(void) {
|
|
cur_obj_init_animation_with_sound(8);
|
|
if (o->oTimer == 0) {
|
|
o->oPiranhaPlantScale = 0.3f;
|
|
}
|
|
|
|
/**
|
|
* This state only occurs after PIRANHA_PLANT_ACT_WAIT_TO_RESPAWN, which
|
|
* in turn only occurs after PIRANHA_PLANT_ACT_SHRINK_AND_DIE. The latter
|
|
* sets the Piranha Plant's scale to 0, therefore the Piranha Plant will
|
|
* grow from the ground unconditionally when in this state.
|
|
*/
|
|
if (o->oPiranhaPlantScale < 1.0) {
|
|
// Grow by 0.02 per frame.
|
|
o->oPiranhaPlantScale += 0.02;
|
|
} else {
|
|
o->oPiranhaPlantScale = 1.0f;
|
|
o->oAction = PIRANHA_PLANT_ACT_IDLE;
|
|
}
|
|
cur_obj_scale(o->oPiranhaPlantScale);
|
|
}
|
|
|
|
/**
|
|
* The frames of the Piranha Plant's biting animation on which to play a bite
|
|
* sound.
|
|
*/
|
|
static s8 sPiranhaPlantBiteSoundFrames[] = { 12, 28, 50, 64, -1 };
|
|
|
|
/**
|
|
* Make the Piranha Plant bite in the direction of the player. If the player
|
|
* moves far away, move it to the stopped biting state. If the player is wearing
|
|
* the Metal Cap and touches the Piranha Plant while it is attacking, the
|
|
* Piranha Plant will move to the attacked state.
|
|
*/
|
|
void piranha_plant_act_biting(void) {
|
|
s32 frame = o->header.gfx.unk38.animFrame;
|
|
|
|
cur_obj_become_tangible();
|
|
|
|
o->oInteractType = INTERACT_DAMAGE;
|
|
|
|
cur_obj_init_animation_with_sound(0);
|
|
|
|
cur_obj_set_hitbox_radius_and_height(150.0f, 100.0f);
|
|
cur_obj_set_hurtbox_radius_and_height(150.0f, 100.0f);
|
|
|
|
// Play a bite sound effect on certain frames.
|
|
if (is_item_in_array(frame, sPiranhaPlantBiteSoundFrames)) {
|
|
cur_obj_play_sound_2(SOUND_OBJ2_PIRANHA_PLANT_BITE);
|
|
}
|
|
|
|
// Move to face the player.
|
|
o->oMoveAngleYaw = approach_s16_symmetric(o->oMoveAngleYaw, o->oAngleToMario, 0x400);
|
|
|
|
if (o->oDistanceToMario > 500.0f)
|
|
if (cur_obj_check_if_near_animation_end())
|
|
o->oAction = PIRANHA_PLANT_ACT_STOPPED_BITING;
|
|
|
|
// If the player is wearing the Metal Cap and interacts with the Piranha
|
|
// Plant, the Piranha Plant will die.
|
|
if (o->oInteractStatus & INT_STATUS_INTERACTED)
|
|
if (gMarioState->flags & MARIO_METAL_CAP)
|
|
o->oAction = PIRANHA_PLANT_ACT_ATTACKED;
|
|
}
|
|
|
|
/**
|
|
* Check whether the player is moving fast enough to cause the Piranha Plant to
|
|
* start biting.
|
|
*
|
|
* This is called from both the "stopped biting" state and the "sleeping" state.
|
|
*/
|
|
s32 mario_moving_fast_enough_to_make_piranha_plant_bite(void) {
|
|
if (gMarioStates->vel[1] > 10.0f)
|
|
return 1;
|
|
if (gMarioStates->forwardVel > 10.0f)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Make the Piranha Plant quickly nod to indicate that it has stopped biting.
|
|
* If the player has drawn closer during this short animation, make the Piranha
|
|
* Plant start biting again. Otherwise, make it go back to sleep.
|
|
*/
|
|
void piranha_plant_act_stopped_biting(void) {
|
|
cur_obj_become_intangible();
|
|
cur_obj_init_animation_with_sound(6);
|
|
|
|
if (cur_obj_check_if_near_animation_end())
|
|
o->oAction = PIRANHA_PLANT_ACT_SLEEPING;
|
|
|
|
/**
|
|
* Note that this state only occurs initially when the player goes further
|
|
* than 500.0f units from the Piranha Plant while it is biting. This if-
|
|
* statement activates only when the player has drawn within 400.0f units
|
|
* of the Piranha Plant during the short time the Piranha Plant's nod
|
|
* animation plays.
|
|
*/
|
|
if (o->oDistanceToMario < 400.0f)
|
|
if (mario_moving_fast_enough_to_make_piranha_plant_bite())
|
|
o->oAction = PIRANHA_PLANT_ACT_BITING;
|
|
}
|
|
|
|
/**
|
|
* Table of functions corresponding to the actions the Piranha Plant can take.
|
|
*/
|
|
void (*TablePiranhaPlantActions[])(void) = {
|
|
piranha_plant_act_idle, // PIRANHA_PLANT_ACT_IDLE,
|
|
piranha_plant_act_sleeping, // PIRANHA_PLANT_ACT_SLEEPING,
|
|
piranha_plant_act_biting, // PIRANHA_PLANT_ACT_BITING,
|
|
piranha_plant_act_woken_up, // PIRANHA_PLANT_ACT_WOKEN_UP,
|
|
piranha_plant_act_stopped_biting, // PIRANHA_PLANT_ACT_STOPPED_BITING,
|
|
piranha_plant_attacked, // PIRANHA_PLANT_ATTACKED,
|
|
piranha_plant_act_shrink_and_die, // PIRANHA_PLANT_ACT_SHRINK_AND_DIE,
|
|
piranha_plant_act_wait_to_respawn, // PIRANHA_PLANT_ACT_WAIT_TO_RESPAWN,
|
|
piranha_plant_act_respawn // PIRANHA_PLANT_ACT_RESPAWN
|
|
};
|
|
|
|
/**
|
|
* Main loop for bhvPiranhaPlant.
|
|
*/
|
|
void bhv_piranha_plant_loop(void) {
|
|
cur_obj_call_action_function(TablePiranhaPlantActions);
|
|
|
|
// In WF, hide all Piranha Plants once high enough up.
|
|
if (gCurrLevelNum == LEVEL_WF) {
|
|
if (gMarioObject->oPosY > 3400.0f)
|
|
cur_obj_hide();
|
|
else
|
|
cur_obj_unhide();
|
|
}
|
|
o->oInteractStatus = 0;
|
|
}
|