diff --git a/include/ac_ins_kera.h b/include/ac_ins_kera.h new file mode 100644 index 00000000..84b29fa3 --- /dev/null +++ b/include/ac_ins_kera.h @@ -0,0 +1,17 @@ +#ifndef AC_INS_KERA_H +#define AC_INS_KERA_H + +#include "types.h" +#include "ac_insect_h.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern void aIKR_actor_init(ACTOR* actorx, GAME* game); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/audio_defs.h b/include/audio_defs.h index f31ca0b1..a7eed04a 100644 --- a/include/audio_defs.h +++ b/include/audio_defs.h @@ -65,6 +65,9 @@ typedef enum audio_sound_effects { NA_SE_DRAWER_SHUT = 0x3A, NA_SE_ITEM_GET = 0x40, + NA_SE_MOLE_CRICKET_HIDE = 0x44, + NA_SE_MOLE_CRICKET_OUT = 0x45, // TODO: do ongen sounds have their own enum? + NA_SE_NOTE_G_LOW = MONO(0x42), NA_SE_NOTE_A_LOW = MONO(0x43), NA_SE_NOTE_B_LOW = MONO(0x44), diff --git a/src/actor/ac_ins_kera.c b/src/actor/ac_ins_kera.c new file mode 100644 index 00000000..791c4f5e --- /dev/null +++ b/src/actor/ac_ins_kera.c @@ -0,0 +1,509 @@ +/** + * @file ac_ins_kera.c + * @brief Mole cricket behavior, including hiding, emerging, and escaping. + */ + +#include "ac_ins_kera.h" + +#include "m_name_table.h" +#include "m_common_data.h" +#include "m_player_lib.h" + +// Actor-specific work variables +#define aIKR_AVOID_TIMER(ins) ((ins)->s32_work0) +#define aIKR_UT_X(ins) ((ins)->s32_work1) +#define aIKR_UT_Z(ins) ((ins)->s32_work2) +#define aIKR_SCALE_TOGGLE(ins) ((ins)->s32_work3) + +#define aIKR_DIRT_TIMER(ins) ((ins)->f32_work0) +#define aIKR_BK_CENTER_X(ins) ((ins)->f32_work2) +#define aIKR_BK_CENTER_Z(ins) ((ins)->f32_work3) + +enum { + aIKR_ACT_AVOID, + aIKR_ACT_LET_ESCAPE, + aIKR_ACT_HIDE, + aIKR_ACT_APPEAR, + aIKR_ACT_DIVE, + aIKR_ACT_DROWN, + aIKR_ACT_DUG, + + aIKR_ACT_NUM +}; + +static void aIKR_actor_move(ACTOR* actorx, GAME* game); +static void aIKR_setupAction(aINS_INSECT_ACTOR* insect, int action, GAME* game); +static void aIKR_set_avoid_player_angl(ACTOR* actorx, GAME* game); + +/** + * Initializes a mole cricket actor. + * + * @param actorx Pointer to the actor to initialize. + * @param game Pointer to the game context. + */ +extern void aIKR_actor_init(ACTOR* actorx, GAME* game) { + aINS_INSECT_ACTOR* insect = (aINS_INSECT_ACTOR*)actorx; + int action; + + insect->bg_range = 5.0f; + insect->item = ITM_INSECT33; + actorx->mv_proc = aIKR_actor_move; + insect->insect_flags.bit_4 = FALSE; + + if (actorx->actor_specific == aINS_INIT_NORMAL) { + int ux; + int uz; + + // Get center of the block + mFI_BkNum2WposXZ(&insect->f32_work2, &insect->f32_work3, actorx->block_x, actorx->block_z); + insect->f32_work2 += mFI_BK_WORLDSIZE_HALF_X_F; + insect->f32_work3 += mFI_BK_WORLDSIZE_HALF_Z_F; + + // Get unit + mFI_Wpos2UtNum(&ux, &uz, actorx->world.position); + insect->ut_x = ux; + insect->ut_z = uz; + action = aIKR_ACT_HIDE; + } else { + // aINS_INIT_RELEASE + aIKR_set_avoid_player_angl(actorx, game); + actorx->drawn = TRUE; + action = aIKR_ACT_LET_ESCAPE; + } + + aIKR_setupAction(insect, action, game); +} + +/** + * Sets the angle of the cricket so it avoids the player's direction. + * + * @param actorx Pointer to the mole cricket actor. + * @param game Pointer to the game context. + */ +static void aIKR_set_avoid_player_angl(ACTOR* actorx, GAME* game) { + ACTOR* playerx = GET_PLAYER_ACTOR_GAME_ACTOR(game); + + if (playerx != NULL) { + actorx->shape_info.rotation.y = actorx->world.angle.y = playerx->shape_info.rotation.y + (s16)RANDOM_CENTER_F(DEG2SHORT_ANGLE2(120.0f)); + } +} + +/** + * Calculates scale changes to simulate breathing/movement. + * Toggles scale growth direction using s32_work3. + * + * @param actorx Pointer to the mole cricket actor. + */ +static void aIKR_calc_move_scale(ACTOR* actorx) { + static f32 target_table[] = { 0.008f, 0.01f }; + static f32 drt_table[] = { -1.0f, 1.0f }; + aINS_INSECT_ACTOR* insect = (aINS_INSECT_ACTOR*)actorx; + f32 target; + f32 scale; + f32 drt; + + drt = drt_table[aIKR_SCALE_TOGGLE(insect)]; + scale = actorx->scale.z; + scale += (drt * 0.002f) * 0.5f; + target = target_table[aIKR_SCALE_TOGGLE(insect)]; + if (drt * (target - scale) <= 0.0f) { + aIKR_SCALE_TOGGLE(insect) = (aIKR_SCALE_TOGGLE(insect) ^ 1) & 1; + scale = target; + } + + actorx->scale.y = scale; + actorx->scale.z = scale; +} + +/** + * Checks if the player's shovel or scoop is digging near the cricket. + * + * @param actorx Pointer to the mole cricket actor. + * @return TRUE if scooping in the same tile, FALSE otherwise. + */ +static int aIKR_check_dig_hole_scoop(ACTOR* actorx) { + int ret = FALSE; + + if (CLIP(insect_clip)->pl_action == aINS_PL_ACT_DIG_SCOOP) { + int ux; + int uz; + + mFI_Wpos2UtNum(&ux, &uz, actorx->world.position); + if (CLIP(insect_clip)->pl_action_ut_x == ux && CLIP(insect_clip)->pl_action_ut_z == uz) { + ret = TRUE; + } + } + + return ret; +} + +/** + * Determines whether the cricket is within its active range from a center point. + * + * @param actorx Pointer to the mole cricket actor. + * @return FALSE if out of range, TRUE otherwise. + */ +static int aIKR_chk_active_range(ACTOR* actorx) { + aINS_INSECT_ACTOR* insect = (aINS_INSECT_ACTOR*)actorx; + f32 dx = aIKR_BK_CENTER_X(insect) - actorx->world.position.x; + f32 dz = aIKR_BK_CENTER_Z(insect) - actorx->world.position.z; + int ret = TRUE; + + if (SQ(dx) + SQ(dz) >= SQ(400.0f)) { + ret = FALSE; + } + + return ret; +} + +/** + * Checks for water attributes to see if diving is needed. + * + * @param actorx Pointer to the mole cricket actor. + * @return TRUE if water is directly ahead, FALSE otherwise. + */ +static int aIKR_chk_water_attr(ACTOR* actorx) { + aINS_INSECT_ACTOR* insect = (aINS_INSECT_ACTOR*)actorx; + int ret = FALSE; + + if (actorx->bg_collision_check.result.on_ground) { + xyz_t pos = actorx->world.position; + f32 range = insect->bg_range + actorx->speed; + s16 angleY = actorx->world.angle.y; + + pos.x += range * sin_s(angleY); + pos.z += range * cos_s(angleY); + if (mCoBG_CheckWaterAttribute(mCoBG_Wpos2BgAttribute_Original(pos))) { + ret = TRUE; + } + } + + return ret; +} + +/** + * Reorients the cricket if a wall collision is detected. + * + * @param actorx Pointer to the mole cricket actor. + */ +static void aIKR_calc_direction_angl(ACTOR* actorx) { + s16 step; + + if (actorx->bg_collision_check.result.hit_wall & mCoBG_HIT_WALL_FRONT) { + int count = actorx->bg_collision_check.result.hit_wall_count; + int i; + + if (count != 0) { + for (i = 0; i < count; i++) { + if (actorx->bg_collision_check.wall_info[i].type == mCoBG_WALL_TYPE0) { + actorx->world.angle.y = DEG2SHORT_ANGLE2(90.0f) + actorx->bg_collision_check.wall_info[i].angleY; + break; + } + } + } + } + + chase_angle(&actorx->shape_info.rotation.y, actorx->world.angle.y, 0x800); +} + +/** + * Allows the mole cricket to avoid the player or obstacles. + * Adjusts speed periodically, checks for water or out-of-range conditions. + * + * @param actorx Pointer to the mole cricket actor. + * @param game Pointer to the current game state. + */ +static void aIKR_avoid(ACTOR* actorx, GAME* game) { + aINS_INSECT_ACTOR* insect = (aINS_INSECT_ACTOR*)actorx; + + chase_angle(&actorx->shape_info.rotation.x, 0, 0x1000); + aIKR_AVOID_TIMER(insect)--; + if (aIKR_AVOID_TIMER(insect) <= 0) { + insect->target_speed = (1.1f - RANDOM_F(0.2f)) * 1.5f; + aIKR_AVOID_TIMER(insect) = 10; + } + + aIKR_calc_move_scale(actorx); + if (aIKR_chk_water_attr(actorx) == TRUE) { + aIKR_setupAction(insect, aIKR_ACT_DIVE, game); + } else if (aIKR_chk_active_range(actorx) == FALSE) { + int action; + + if (mCoBG_CheckHole_OrgAttr(mCoBG_Wpos2Attribute(actorx->world.position, NULL)) == TRUE) { + action = aIKR_ACT_DUG; + } else { + action = aIKR_ACT_LET_ESCAPE; + } + + aIKR_setupAction(insect, action, game); + } else { + if (insect->bg_type == aINS_BG_CHECK_TYPE_NO_UNIT_COLUMN_NO_ATTR) { + int ux; + int uz; + + mFI_Wpos2UtNum(&ux, &uz, actorx->world.position); + if (insect->ut_x != ux || insect->ut_z != uz) { + insect->bg_type = aINS_BG_CHECK_TYPE_REG_NO_ATTR; + } + } + + aIKR_calc_direction_angl(actorx); + sAdo_OngenPos((u32)actorx, NA_SE_MOLE_CRICKET_OUT, &actorx->world.position); + } +} + +/** + * Lets the mole cricket escape freely, checking if it should dive. + * + * @param actorx Pointer to the mole cricket actor. + * @param game Pointer to the current game state. + */ +static void aIKR_let_escape(ACTOR* actorx, GAME* game) { + aINS_INSECT_ACTOR* insect = (aINS_INSECT_ACTOR*)actorx; + + aIKR_calc_move_scale(actorx); + if (aIKR_chk_water_attr(actorx) == TRUE) { + aIKR_setupAction(insect, aIKR_ACT_DIVE, game); + } else { + aIKR_calc_direction_angl(actorx); + sAdo_OngenPos((u32)actorx, NA_SE_MOLE_CRICKET_OUT, &actorx->world.position); + } +} + +/** + * Keeps the mole cricket hidden until has been dug up. + * + * @param actorx Pointer to the mole cricket actor. + * @param game Pointer to the current game state. + */ +static void aIKR_hide(ACTOR* actorx, GAME* game) { + if (aIKR_check_dig_hole_scoop(actorx) == TRUE) { + int i; + + aIKR_set_avoid_player_angl(actorx, game); + aIKR_setupAction((aINS_INSECT_ACTOR*)actorx, aIKR_ACT_APPEAR, game); + for (i = 3; i < 6; i++) { + eEC_CLIP->effect_make_proc(eEC_EFFECT_DIG_MUD, actorx->world.position, 2, actorx->shape_info.rotation.y, game, RSV_NO, 0, 0x4000 | i); + } + } else { + sAdo_OngenPos((u32)actorx, NA_SE_MOLE_CRICKET_HIDE, &actorx->world.position); + } +} + +/** + * Manages the mole cricket emerging from underground. + * + * @param actorx Pointer to the mole cricket actor. + * @param game Pointer to the current game state. + */ +static void aIKR_appear(ACTOR* actorx, GAME* game) { + actorx->shape_info.rotation.x = atans_table(actorx->speed, -actorx->position_speed.y); + if (actorx->bg_collision_check.result.on_ground) { + aIKR_setupAction((aINS_INSECT_ACTOR*)actorx, aIKR_ACT_AVOID, game); + } +} + +/** + * Controls the cricket's dive into water and detects submersion depth. + * + * @param actorx Pointer to the mole cricket actor. + * @param game Pointer to the current game state. + */ +static void aIKR_dive(ACTOR* actorx, GAME* game) { + f32 water_y = mCoBG_GetWaterHeight_File(actorx->world.position, __FILE__, 541); + + actorx->shape_info.rotation.x = atans_table(actorx->speed, -actorx->position_speed.y); + if (actorx->world.position.y <= water_y) { + aIKR_setupAction((aINS_INSECT_ACTOR*)actorx, aIKR_ACT_DROWN, game); + } +} + +/** + * Handles movements after the cricket has dug underground. + * Continues until it times out or re-emerges. + * + * @param actorx Pointer to the mole cricket actor. + * @param game Pointer to the current game state. + */ +static void aIKR_dug(ACTOR* actorx, GAME* game) { + aINS_INSECT_ACTOR* insect = (aINS_INSECT_ACTOR*)actorx; + + aIKR_calc_move_scale(actorx); + aIKR_DIRT_TIMER(insect) -= 0.5f; + if (aIKR_DIRT_TIMER(insect) <= 0.0f) { + int type = 3.0f + RANDOM_F(3.0f); + + eEC_CLIP->effect_make_proc(eEC_EFFECT_DIG_MUD, actorx->world.position, 2, actorx->shape_info.rotation.y, game, RSV_NO, 0, 0x4000 | type); + aIKR_DIRT_TIMER(insect) = 6.0f; + } + + chase_angle(&actorx->shape_info.rotation.x, DEG2SHORT_ANGLE2(157.5f), 0x300); + sAdo_OngenPos((u32)actorx, NA_SE_MOLE_CRICKET_OUT, &actorx->world.position); +} + +/** + * Initializes the avoid action, setting initial speed. + * + * @param insect Pointer to the insect actor context. + * @param game Pointer to the game state. + */ +static void aIKR_avoid_init(aINS_INSECT_ACTOR* insect, GAME* game) { + insect->target_speed = 1.5f; + insect->speed_step = 0.3f; + insect->tools_actor.actor_class.shape_info.rotation.x = 0; +} + +/** + * Initializes the escape action, enabling gravity and slowly fading out the insect. + * + * @param insect Pointer to the insect actor context. + * @param game Pointer to the game state. + */ +static void aIKR_let_escape_init(aINS_INSECT_ACTOR* insect, GAME* game) { + insect->life_time = 0; + insect->alpha_time = 80; + insect->tools_actor.actor_class.gravity = 1.0f; + insect->tools_actor.actor_class.max_velocity_y = -20.0f; + aIKR_avoid_init(insect, game); + insect->bg_type = aINS_BG_CHECK_TYPE_REG_NO_ATTR; + insect->tools_actor.actor_class.shape_info.rotation.x = 0; + insect->insect_flags.bit_1 = TRUE; + insect->insect_flags.bit_2 = TRUE; +} + +/** + * Initializes the hidden state, making the actor invisible. + * + * @param insect Pointer to the insect actor context. + * @param game Pointer to the game state. + */ +static void aIKR_hide_init(aINS_INSECT_ACTOR* insect, GAME* game) { + mFI_Wpos2UtNum(&aIKR_UT_X(insect), &aIKR_UT_Z(insect), insect->tools_actor.actor_class.world.position); + insect->tools_actor.actor_class.drawn = FALSE; +} + +/** + * Initializes the emerging state with upward speed. + * + * @param insect Pointer to the insect actor context. + * @param game Pointer to the game state. + */ +static void aIKR_appear_init(aINS_INSECT_ACTOR* insect, GAME* game) { + insect->target_speed = 1.5f; + insect->speed_step = 0.3f; + insect->tools_actor.actor_class.speed = 1.5f; + insect->tools_actor.actor_class.gravity = 1.0f; + insect->tools_actor.actor_class.max_velocity_y = -20.0f; + insect->tools_actor.actor_class.position_speed.y = 5.0f; + insect->tools_actor.actor_class.drawn = TRUE; + insect->bg_type = aINS_BG_CHECK_TYPE_NO_UNIT_COLUMN_NO_ATTR; +} + +/** + * Initializes the dive into water with vertical speed. + * + * @param insect Pointer to the insect actor context. + * @param game Pointer to the game state. + */ +static void aIKR_dive_init(aINS_INSECT_ACTOR* insect, GAME* game) { + insect->target_speed = 1.5f; + insect->speed_step = 0.3f; + insect->tools_actor.actor_class.speed = insect->target_speed; + insect->tools_actor.actor_class.position_speed.y = 8.0f; + insect->insect_flags.bit_1 = TRUE; +} + +/** + * Initializes the drowning state, removing the actor from play. + * + * @param insect Pointer to the insect actor context. + * @param game Pointer to the game state. + */ +static void aIKR_drown_init(aINS_INSECT_ACTOR* insect, GAME* game) { + xyz_t pos = insect->tools_actor.actor_class.world.position; + + pos.y = mCoBG_GetWaterHeight_File(pos, __FILE__, 712); + eEC_CLIP->effect_make_proc(eEC_EFFECT_TURI_MIZU, pos, 1, insect->tools_actor.actor_class.world.angle.y, game, EMPTY_NO, 4, 0); + sAdo_OngenTrgStart(NA_SE_438, &insect->tools_actor.actor_class.world.position); + insect->insect_flags.bit_1 = TRUE; + insect->insect_flags.destruct = TRUE; + insect->tools_actor.actor_class.shape_info.draw_shadow = FALSE; +} + +/** + * Initializes the dug state, limiting the insect's movement. + * + * @param insect Pointer to the insect actor context. + * @param game Pointer to the game state. + */ +static void aIKR_dug_init(aINS_INSECT_ACTOR* insect, GAME* game) { + xyz_t pos = insect->tools_actor.actor_class.world.position; + + insect->life_time = 0; + insect->alpha_time = 80; + insect->tools_actor.actor_class.shape_info.rotation.x = 0; + insect->bg_type = aINS_BG_CHECK_TYPE_NONE; + insect->tools_actor.actor_class.gravity = 0.01f; + insect->tools_actor.actor_class.max_velocity_y = -0.2f; + insect->tools_actor.actor_class.position_speed.y = 0.0f; + insect->tools_actor.actor_class.speed = 0.0f; + insect->target_speed = 0.0f; + insect->speed_step = 0.0f; + insect->f32_work0 = 1.0f; + insect->insect_flags.bit_1 = TRUE; + insect->insect_flags.bit_2 = TRUE; + insect->tools_actor.actor_class.shape_info.draw_shadow = FALSE; +} + +typedef void (*aIKR_INIT_PROC)(aINS_INSECT_ACTOR* insect, GAME* game); + +/** + * Configures the mole cricket's current action and associated init function. + * + * @param insect Pointer to the insect actor context. + * @param action Which action to transition to. + * @param game Pointer to the game context. + */ +static void aIKR_setupAction(aINS_INSECT_ACTOR* insect, int action, GAME* game) { + static aIKR_INIT_PROC init_proc[] = { + aIKR_avoid_init, + aIKR_let_escape_init, + aIKR_hide_init, + aIKR_appear_init, + aIKR_dive_init, + aIKR_drown_init, + aIKR_dug_init, + }; + + static aINS_ACTION_PROC act_proc[] = { + aIKR_avoid, + aIKR_let_escape, + aIKR_hide, + aIKR_appear, + aIKR_dive, + (aINS_ACTION_PROC)none_proc1, + aIKR_dug, + }; + + insect->action = action; + insect->action_proc = act_proc[action]; + (*init_proc[action])(insect, game); +} + +/** + * Movement/update loop for the mole cricket actor. + * + * @param actorx Pointer to the mole cricket actor. + * @param game Pointer to the current game state. + */ +static void aIKR_actor_move(ACTOR* actorx, GAME* game) { + aINS_INSECT_ACTOR* insect = (aINS_INSECT_ACTOR*)actorx; + u32 catch_label = mPlib_Get_item_net_catch_label(); + + if (catch_label == (u32)actorx) { + insect->alpha0 = 255; + aIKR_setupAction(insect, aIKR_ACT_LET_ESCAPE, game); + } else { + insect->action_proc(actorx, game); + } +}