From 44227e89a62f83f7904d51aca855d56858f784ce Mon Sep 17 00:00:00 2001 From: Cuyler36 Date: Sun, 26 Jan 2025 16:14:51 -0500 Subject: [PATCH] Implement & link ac_ins_tonbo --- configure.py | 2 +- include/ac_ins_tonbo.h | 17 + include/ac_insect_h.h | 10 + include/sys_math.h | 3 + src/actor/ac_ins_tonbo.c | 847 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 878 insertions(+), 1 deletion(-) create mode 100644 include/ac_ins_tonbo.h create mode 100644 src/actor/ac_ins_tonbo.c diff --git a/configure.py b/configure.py index 3582efe3..3d14ad2d 100644 --- a/configure.py +++ b/configure.py @@ -1003,7 +1003,7 @@ config.libs = [ Object(NonMatching, "actor/ac_ins_mino.c"), Object(NonMatching, "actor/ac_ins_semi.c"), Object(NonMatching, "actor/ac_ins_tentou.c"), - Object(NonMatching, "actor/ac_ins_tonbo.c"), + Object(Matching, "actor/ac_ins_tonbo.c"), Object(Matching, "actor/ac_insect.c"), Object(Matching, "actor/ac_intro_demo.c"), Object(Matching, "actor/ac_kago.c"), diff --git a/include/ac_ins_tonbo.h b/include/ac_ins_tonbo.h new file mode 100644 index 00000000..10a82f93 --- /dev/null +++ b/include/ac_ins_tonbo.h @@ -0,0 +1,17 @@ +#ifndef AC_INS_TONBO_H +#define AC_INS_TONBO_H + +#include "types.h" +#include "ac_insect_h.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern void aITB_actor_init(ACTOR* actorx, GAME* game); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/ac_insect_h.h b/include/ac_insect_h.h index 335fbed6..facb2ba6 100644 --- a/include/ac_insect_h.h +++ b/include/ac_insect_h.h @@ -88,6 +88,16 @@ enum { aINS_PROGRAM_NUM }; +enum { + aINS_BG_CHECK_TYPE_NONE, + aINS_BG_CHECK_TYPE_REG_ATTR, + aINS_BG_CHECK_TYPE_REG_NO_ATTR, + aINS_BG_CHECK_TYPE_NO_UNIT_COLUMN_ATTR, + aINS_BG_CHECK_TYPE_NO_UNIT_COLUMN_NO_ATTR, + + aINS_BG_CHECK_TYPE_NUM +}; + /* sizeof(aINS_Init_c) == 0x18 */ typedef struct insect_init_s { /* 0x00 */ int insect_type; diff --git a/include/sys_math.h b/include/sys_math.h index c4add2db..04834af5 100644 --- a/include/sys_math.h +++ b/include/sys_math.h @@ -29,6 +29,9 @@ extern "C" { /* Macro to generate a random float in the range of [0, n) */ #define RANDOM_F(n) (fqrand() * (f32)(n)) +/* Macro to generate a random float in range of [-n/2, n/2) */ +#define RANDOM_CENTER_F(n) ((fqrand() - 0.5f) * (f32)(n)) + /* Macro to generate a random integer in the range of [0, n) */ #define RANDOM(n) ((int)RANDOM_F(n)) diff --git a/src/actor/ac_ins_tonbo.c b/src/actor/ac_ins_tonbo.c new file mode 100644 index 00000000..4af61426 --- /dev/null +++ b/src/actor/ac_ins_tonbo.c @@ -0,0 +1,847 @@ +/** + * @file ac_ins_tonbo.c + * @brief Implementation of the dragonfly actor. + * + * This file contains the functions and logic for initializing, updating, and controlling + * the behavior of dragonfly (tonbo) actors. + */ + +#include "ac_ins_tonbo.h" + +#include "m_name_table.h" +#include "m_common_data.h" +#include "m_player_lib.h" + +enum { + aITB_ACT_AVOID, + aITB_ACT_LET_ESCAPE, + aITB_ACT_FLY, + aITB_ACT_ONIYANMA_FLY, + aITB_ACT_WAIT, + aITB_ACT_TOUCH_WATER, + aITB_ACT_TOUCH_WATER_REVERSE, + aITB_ACT_HOVER_WAIT_ON_WATER, + aITB_ACT_FLY_ON_NOTICE, + aITB_ACT_REST_ON_NOTICE, + + aITB_ACT_NUM +}; + +#define aITB_BG_CHECK_RESULT_NONE 0 +#define aITB_BG_CHECK_RESULT_COLLIDE 1 +#define aITB_BG_CHECK_RESULT_ESCAPE 2 + +#define aITB_STOP_TYPE_NONE 0 +#define aITB_STOP_TYPE_COLLISION 1 +#define aITB_STOP_TYPE_REST 2 + +// Speed settings +#define aITB_ONIYANMA_FLY_SPEED 4.0f // Banded dragonfly fly speed +#define aITB_DARNER_FLY_SPEED 2.4f // Darner dragonfly fly speed +#define aITB_COMMON_FLY_SPEED 2.0f // Common & red dragonfly fly speed + +#define aITB_SPEED_VARIANCE 2.0f // Dragonfly speed variance +#define aITB_AVOID_SPEED 4.0f // Avoid speed + +// Range settings +#define aITB_ONIYAMA_MAX_RANGE (12 * mFI_UNIT_BASE_SIZE_F) // Banded dragonfly max range +#define aITB_OTHER_MAX_RANGE (6 * mFI_UNIT_BASE_SIZE_F) // Other dragonfly max range + +// Timer settings +#define aITB_DARNER_FLY_TIMER 24 // Darner dragonfly fly timer frames +#define aITB_OTHER_FLY_TIMER 20 // Common & red dragonfly fly timer frames +#define aITB_AVOID_TIMER 20 // Avoid timer frames +#define aITB_TOUCH_WATER_REVERSE_TIMER 6 // Touch water reverse timer frames +#define aITB_HOVER_WAIT_ON_WATER_TIMER 20 // Hover wait on water timer frames +#define aITB_REST_ON_NOTICE_TIMER 200 // Rest on notice timer frames + +// Dragonfly-specific work variables +#define aITB_GET_HEIGHT(ins) ((ins)->f32_work0) +#define aITB_SET_HEIGHT(ins, h) ((ins)->f32_work0 = (h)) +#define aITB_GET_RESERVE_DUMMY_HEIGHT(ins) ((ins)->f32_work1) +#define aITB_SET_RESERVE_DUMMY_HEIGHT(ins, h) ((ins)->f32_work1 = (h)) +#define aITB_GET_BK_CENTER_X(ins) ((ins)->f32_work2) +#define aITB_GET_BK_CENTER_Z(ins) ((ins)->f32_work3) + +#define aITB_GET_STOP_STATE(ins) ((ins)->s32_work0) +#define aITB_GET_REST_ANIME_TIMER(ins) ((ins)->s32_work2) + +static void aITB_actor_move(ACTOR* actorx, GAME* game); + +static void aITB_setupAction(aINS_INSECT_ACTOR* insect, int action, GAME* game); +static int aITB_check_live_condition(aINS_INSECT_ACTOR* insect); + +/** + * Initializes the dragonfly actor. + * + * @param actorx Pointer to the actor to initialize. + * @param game Pointer to the game context. + */ +extern void aITB_actor_init(ACTOR* actorx, GAME* game) { + aINS_INSECT_ACTOR* ins = (aINS_INSECT_ACTOR*)actorx; + + // Set movement procedure and gravity + actorx->mv_proc = &aITB_actor_move; + actorx->gravity = 0.1f; + ins->bg_type = aINS_BG_CHECK_TYPE_REG_ATTR; + + // Assign item type based on insect type + switch (ins->type) { + case aINS_INSECT_TYPE_COMMON_DRAGONFLY: + ins->item = ITM_INSECT09; + break; + case aINS_INSECT_TYPE_RED_DRAGONFLY: + ins->item = ITM_INSECT10; + break; + case aINS_INSECT_TYPE_DARNER_DRAGONFLY: + ins->item = ITM_INSECT11; + break; + case aINS_INSECT_TYPE_BANDED_DRAGONFLY: + ins->item = ITM_INSECT12; + break; + } + + ins->insect_flags.bit_4 = FALSE; + + // Check if the dragonfly was released + if (actorx->actor_specific == 0) { + // Dragonfly was not released + if (aITB_check_live_condition(ins) == TRUE) { + int act; + + // Set initial position and angle + aITB_SET_HEIGHT(ins, 52.0f + RANDOM_F(10.0f)); + actorx->world.position.y = 20.0f + mCoBG_GetBgY_OnlyCenter_FromWpos(actorx->world.position, 0.0f); + actorx->world.angle.y = DEG2SHORT_ANGLE3(180.0f) - RANDOM_F(DEG2SHORT_ANGLE3(360.0f)); + actorx->shape_info.rotation.y = actorx->world.angle.y; + mFI_BkNum2WposXZ(&aITB_GET_BK_CENTER_X(ins), &aITB_GET_BK_CENTER_Z(ins), actorx->block_x, actorx->block_z); + aITB_GET_BK_CENTER_X(ins) += mFI_BK_WORLDSIZE_HALF_X_F; + aITB_GET_BK_CENTER_Z(ins) += mFI_BK_WORLDSIZE_HALF_Z_F; + + // Set action based on insect type + if (ins->type == aINS_INSECT_TYPE_BANDED_DRAGONFLY) { + act = aITB_ACT_ONIYANMA_FLY; + } else { + act = aITB_ACT_FLY; + } + + aITB_setupAction(ins, act, game); + } + } else { + // Dragonfly was released + ACTOR* playerx = GET_PLAYER_ACTOR_GAME_ACTOR(game); + + if (playerx != NULL) { + // Set initial rotation based on player's rotation + actorx->shape_info.rotation.y = actorx->world.angle.y = playerx->shape_info.rotation.y + (s16)RANDOM_CENTER_F(DEG2SHORT_ANGLE2(120.0f)); + } + + aITB_setupAction(ins, aITB_ACT_LET_ESCAPE, game); + } +} + +/** + * Checks if the dragonfly can live in the current conditions. + * + * @param insect Pointer to the insect actor. + * @return TRUE if the dragonfly can live, FALSE otherwise. + */ +static int aITB_check_live_condition(aINS_INSECT_ACTOR* insect) { + int ret = TRUE; + + // Check that the dragonfly isn't at exactly ground height + if (mCoBG_ExistHeightGap_KeepAndNow(insect->tools_actor.actor_class.world.position) == TRUE) { + ret = FALSE; + // Check for rain + } else if (Common_Get(weather) == mEnv_WEATHER_RAIN) { + ret = FALSE; + } else { + // Check for valid unit + mActor_name_t* fg_p = mFI_GetUnitFG(insect->tools_actor.actor_class.world.position); + + if (fg_p == NULL || *fg_p == RSV_NO || ITEM_NAME_GET_TYPE(*fg_p) == NAME_TYPE_STRUCT) { + ret = FALSE; + } + } + + // Mark for destruction if conditions are not met + if (ret == FALSE) { + insect->insect_flags.destruct = TRUE; + } + + return ret; +} + +/** + * Checks for background collisions and adjusts the dragonfly's angle. + * + * @param actorx Pointer to the actor. + * @param game Pointer to the game context. + * @return aITB_BG_CHECK_RESULT_COLLIDE if collision detected, aITB_BG_CHECK_RESULT_ESCAPE if collided 4+ times, aITB_BG_CHECK_RESULT_NONE otherwise. + */ +static int aITB_BGcheck(ACTOR* actorx, GAME* game) { + aINS_INSECT_ACTOR* ins = (aINS_INSECT_ACTOR*)actorx; + int ret = aITB_BG_CHECK_RESULT_NONE; // no collision + + // Check for wall or ground collision + if (((actorx->bg_collision_check.result.hit_wall & mCoBG_HIT_WALL_FRONT) != 0 || + (actorx->bg_collision_check.result.hit_attribute_wall & mCoBG_HIT_WALL_FRONT) != 0) || + actorx->bg_collision_check.result.on_ground) { + s16 angle = 0; + int count = actorx->bg_collision_check.result.hit_wall_count; + int i; + + if (count != 0) { + for (i = 0; i < actorx->bg_collision_check.result.hit_wall_count; i++) { + if (actorx->bg_collision_check.wall_info[i].type == mCoBG_WALL_TYPE0) { + angle = actorx->bg_collision_check.wall_info[i].angleY - (DEG2SHORT_ANGLE2(180.0f) + actorx->world.angle.y); + break; + } + } + } + + // Adjust angle based on collision + if ((s16)(actorx->world.angle.y - angle) >= 0) { + angle += (int)RANDOM_F(DEG2SHORT_ANGLE2(45.0f)); + } else { + angle -= (int)RANDOM_F(DEG2SHORT_ANGLE2(45.0f)); + } + actorx->world.angle.y = angle; + + ins->continue_timer++; + if (ins->continue_timer >= 4) { + // Dragonfly has collided with wall or ground more than four times, + // so treat it as trapped and set escape action + aITB_setupAction(ins, aITB_ACT_LET_ESCAPE, game); + ret = aITB_BG_CHECK_RESULT_ESCAPE; + } else { + // Dragonfly has collided with wall or ground + ret = aITB_BG_CHECK_RESULT_COLLIDE; + } + } else { + ins->continue_timer = 0; + } + + return ret; +} + +/** + * Controls the height of the dragonfly. + * + * @param actorx Pointer to the actor. + */ +static void aITB_height_ctrl(ACTOR* actorx) { + aINS_INSECT_ACTOR* insect = (aINS_INSECT_ACTOR*)actorx; + f32 height = mCoBG_GetBgY_OnlyCenter_FromWpos(insect->tools_actor.actor_class.world.position, 0.0f); + + insect->tools_actor.actor_class.max_velocity_y = (aITB_GET_HEIGHT(insect) + height > insect->tools_actor.actor_class.world.position.y) ? 1.0f : -1.0f; +} + +/** + * Checks if the dragonfly touches water. + * + * @param insect Pointer to the insect actor. + * @param game Pointer to the game context. + * @return TRUE if the dragonfly touches water, FALSE otherwise. + */ +static int aITB_check_water_touch(aINS_INSECT_ACTOR* insect, GAME* game) { + GAME_PLAY* play = (GAME_PLAY*)game; + int bx; + int bz; + int ret = FALSE; + + if (mFI_Wpos2BlockNum(&bx, &bz, insect->tools_actor.actor_class.world.position) == TRUE) { + if (bz < FG_BLOCK_Z_NUM && (play->game_frame % 100) < 20) { + u32 attr = mCoBG_Wpos2Attribute(insect->tools_actor.actor_class.world.position, NULL); + + if (mCoBG_CheckWaterAttribute_OutOfSea(attr) == TRUE) { + ret = TRUE; + } + } + } + + return ret; +} + +/** + * Controls the flying behavior of the dragonfly. + * + * @param insect Pointer to the insect actor. + * @param game Pointer to the game context. + */ +static void aITB_fly_ctrl(aINS_INSECT_ACTOR* insect, GAME* game) { + if (insect->tools_actor.actor_class.speed == insect->target_speed) { + if (insect->timer <= 0) { + if (F32_IS_ZERO(insect->tools_actor.actor_class.speed)) { + int action; + + if (insect->type != aINS_INSECT_TYPE_BANDED_DRAGONFLY && aITB_check_water_touch(insect, game) == TRUE) { + action = aITB_ACT_TOUCH_WATER; + insect->flag = 1; + } else { + action = aITB_ACT_WAIT; + } + + aITB_setupAction(insect, action, game); + } else { + insect->target_speed = 0.0f; + } + } else { + insect->timer--; + } + } +} + +/** + * Sets the movement speed of the dragonfly. + * + * @param insect Pointer to the insect actor. + */ +static void aITB_move_spd_set(aINS_INSECT_ACTOR* insect) { + switch (insect->type) { + case aINS_INSECT_TYPE_BANDED_DRAGONFLY: + insect->target_speed = aITB_ONIYANMA_FLY_SPEED + RANDOM_F(aITB_SPEED_VARIANCE); + break; + case aINS_INSECT_TYPE_DARNER_DRAGONFLY: + insect->target_speed = aITB_DARNER_FLY_SPEED + RANDOM_F(aITB_SPEED_VARIANCE); + insect->timer = aITB_DARNER_FLY_TIMER; + break; + default: + insect->target_speed = aITB_COMMON_FLY_SPEED + RANDOM_F(aITB_SPEED_VARIANCE); + insect->timer = aITB_OTHER_FLY_TIMER; + break; + } + + insect->speed_step = 0.2f; +} + +/** + * Processes the animation of the dragonfly. + * + * @param insect Pointer to the insect actor. + */ +static void aITB_anime_proc(aINS_INSECT_ACTOR* insect) { + insect->_1E0 += 0.5f; + if (insect->_1E0 >= 2.0f) { + insect->_1E0 -= 2.0f; + } +} + +/** + * Checks if the dragonfly is near a reserved dummy. + * + * @param insect Pointer to the insect actor. + * @return TRUE if near a reserved dummy, FALSE otherwise. + */ +static int aITB_check_reserve_dummy(aINS_INSECT_ACTOR* insect) { + int ret = FALSE; + + if (insect->patience < 40.0f) { + mActor_name_t* fg_p = mFI_GetUnitFG(insect->tools_actor.actor_class.world.position); + + if (fg_p != NULL && *fg_p == DUMMY_RESERVE) { + aITB_GET_STOP_STATE(insect) = aITB_STOP_TYPE_REST; + aITB_SET_RESERVE_DUMMY_HEIGHT(insect, mCoBG_Wpos2BgUtCenterHeight_AddColumn(insect->tools_actor.actor_class.world.position)); + ret = TRUE; + } + } + + return ret; +} + +/** + * Checks for wall and ground collisions. + * + * @param insect Pointer to the insect actor. + * @return TRUE if collision detected, FALSE otherwise. + */ +static int aITB_check_wall_and_ground(aINS_INSECT_ACTOR* insect) { + int ret = FALSE; + + if ((insect->tools_actor.actor_class.bg_collision_check.result.hit_wall & mCoBG_HIT_WALL_FRONT) != 0 || + (insect->tools_actor.actor_class.bg_collision_check.result.hit_attribute_wall & mCoBG_HIT_WALL_FRONT) != 0 || + insect->tools_actor.actor_class.bg_collision_check.result.on_ground) { + aITB_GET_STOP_STATE(insect) = aITB_STOP_TYPE_COLLISION; + ret = TRUE; + } + + return ret; +} + +/** + * Checks if the dragonfly should stop. + * + * @param insect Pointer to the insect actor. + * @return TRUE if the dragonfly should stop, FALSE otherwise. + */ +static int aITB_check_stop(aINS_INSECT_ACTOR* insect) { + int ret = FALSE; + + aITB_GET_STOP_STATE(insect) = aITB_STOP_TYPE_NONE; + if (insect->type != aINS_INSECT_TYPE_BANDED_DRAGONFLY) { + if (aITB_check_reserve_dummy(insect) == TRUE) { + ret = TRUE; + } else if (aITB_check_wall_and_ground(insect) == TRUE) { + ret = TRUE; + } + } + + return ret; +} + +/** + * Avoid action for the dragonfly. + * + * @param actorx Pointer to the actor. + * @param game Pointer to the game context. + */ +static void aITB_avoid(ACTOR* actorx, GAME* game) { + aINS_INSECT_ACTOR* ins = (aINS_INSECT_ACTOR*)actorx; + + if (ins->type != aINS_INSECT_TYPE_BANDED_DRAGONFLY) { + aITB_fly_ctrl(ins, game); + } + + aITB_BGcheck(actorx, game); + add_calc_short_angle2(&actorx->shape_info.rotation.y, actorx->world.angle.y, 1.0f - sqrtf(0.7f), 2500, 0); +} + +/** + * Let escape action for the dragonfly. + * + * @param actorx Pointer to the actor. + * @param game Pointer to the game context. + */ +static void aITB_let_escape(ACTOR* actorx, GAME* game) { + aINS_INSECT_ACTOR* ins = (aINS_INSECT_ACTOR*)actorx; + f32 gravity = actorx->gravity; + + gravity *= 1.1f; + if (gravity > 12.0f) { + gravity = 12.0f; + } + + actorx->gravity = gravity; +} + +/** + * Fly action for the dragonfly. + * + * @param actorx Pointer to the actor. + * @param game Pointer to the game context. + */ +static void aITB_fly(ACTOR* actorx, GAME* game) { + aINS_INSECT_ACTOR* ins = (aINS_INSECT_ACTOR*)actorx; + + add_calc_short_angle2(&actorx->shape_info.rotation.y, actorx->world.angle.y, 1.0f - sqrtf(0.7f), 2500, 0); + aITB_height_ctrl(actorx); + if (aITB_check_stop(ins) == TRUE) { + int action = aITB_ACT_WAIT; + + if (aITB_GET_STOP_STATE(ins) == aITB_STOP_TYPE_REST) { + action = aITB_ACT_FLY_ON_NOTICE; + } + + aITB_setupAction(ins, action, game); + } else { + aITB_fly_ctrl(ins, game); + } +} + +/** + * Oniyanma fly action for the dragonfly. + * + * @param actorx Pointer to the actor. + * @param game Pointer to the game context. + */ +static void aITB_oniyanma_fly(ACTOR* actorx, GAME* game) { + aINS_INSECT_ACTOR* ins = (aINS_INSECT_ACTOR*)actorx; + f32 dx; + f32 dz; + + add_calc_short_angle2(&actorx->shape_info.rotation.y, actorx->world.angle.y, 1.0f - sqrtf(0.7f), 2500, 0); + aITB_height_ctrl(actorx); + aITB_BGcheck(actorx, game); + + dx = aITB_GET_BK_CENTER_X(ins) - actorx->world.position.x; + dz = aITB_GET_BK_CENTER_Z(ins) - actorx->world.position.z; + if (SQ(dx) + SQ(dz) >= SQ(aITB_ONIYAMA_MAX_RANGE)) { + // Banded dragonfly has flown too far from center of acre, so escape + aITB_setupAction(ins, aITB_ACT_LET_ESCAPE, game); + } +} + +/** + * Wait action for the dragonfly. + * + * @param actorx Pointer to the actor. + * @param game Pointer to the game context. + */ +static void aITB_wait(ACTOR* actorx, GAME* game) { + aINS_INSECT_ACTOR* ins = (aINS_INSECT_ACTOR*)actorx; + int diff_angle; + + add_calc_short_angle2(&actorx->shape_info.rotation.y, actorx->world.angle.y, 1.0f - sqrtf(0.7f), 2500, 0); + aITB_height_ctrl(actorx); + diff_angle = actorx->shape_info.rotation.y - actorx->world.angle.y; + if (ABS((s16)diff_angle) < DEG2SHORT_ANGLE2(2.8125f)) { + aITB_GET_STOP_STATE(ins) = aITB_STOP_TYPE_NONE; + aITB_setupAction(ins, aITB_ACT_FLY, game); + } +} + +/** + * Touch water action for the dragonfly. + * + * @param actorx Pointer to the actor. + * @param game Pointer to the game context. + */ +static void aITB_touch_water(ACTOR* actorx, GAME* game) { + aINS_INSECT_ACTOR* ins = (aINS_INSECT_ACTOR*)actorx; + + add_calc_short_angle2(&actorx->shape_info.rotation.y, actorx->world.angle.y, 1.0f - sqrtf(0.7f), 2500, 0); + + if (actorx->bg_collision_check.result.is_in_water) { + aITB_setupAction(ins, aITB_ACT_TOUCH_WATER_REVERSE, game); + } +} + +/** + * Touch water reverse action for the dragonfly. + * + * @param actorx Pointer to the actor. + * @param game Pointer to the game context. + */ +static void aITB_touch_water_reverse(ACTOR* actorx, GAME* game) { + aINS_INSECT_ACTOR* ins = (aINS_INSECT_ACTOR*)actorx; + + add_calc_short_angle2(&actorx->shape_info.rotation.y, actorx->world.angle.y, 1.0f - sqrtf(0.7f), 2500, 0); + + if (ins->timer > 0) { + ins->timer--; + } + + if (ins->timer == 0) { + aITB_setupAction(ins, aITB_ACT_HOVER_WAIT_ON_WATER, game); + } +} + +/** + * Hover wait on water action for the dragonfly. + * + * @param actorx Pointer to the actor. + * @param game Pointer to the game context. + */ +static void aITB_hover_wait_on_water(ACTOR* actorx, GAME* game) { + aINS_INSECT_ACTOR* ins = (aINS_INSECT_ACTOR*)actorx; + + if (ins->timer > 0) { + ins->timer--; + } + + if (ins->timer == 0) { + int action; + + if (ins->flag < 2) { + ins->flag++; + action = aITB_ACT_TOUCH_WATER; + } else { + action = aITB_ACT_WAIT; + } + + aITB_setupAction(ins, action, game); + } +} + +/** + * Fly on notice action for the dragonfly. + * + * @param actorx Pointer to the actor. + * @param game Pointer to the game context. + */ +static void aITB_fly_on_notice(ACTOR* actorx, GAME* game) { + aINS_INSECT_ACTOR* ins = (aINS_INSECT_ACTOR*)actorx; + + add_calc_short_angle2(&actorx->shape_info.rotation.y, actorx->world.angle.y, 1.0f - sqrtf(0.7f), 2500, 0); + + if (ins->patience < 90.0f) { + xyz_t pos_in_unit; + + add_calc(&actorx->world.position.y, aITB_GET_RESERVE_DUMMY_HEIGHT(ins), 1.0f - sqrtf(0.8f), 0.5f, 0.0f); + mFI_Wpos2UtCenterWpos(&pos_in_unit, actorx->world.position); + + if (SQ(pos_in_unit.x - actorx->world.position.x) + SQ(pos_in_unit.z - actorx->world.position.z) > SQ(5.0f)) { + actorx->world.angle.y = search_position_angleY(&actorx->world.position, &pos_in_unit); + } else { + f32 diff_y = aITB_GET_RESERVE_DUMMY_HEIGHT(ins) - actorx->world.position.y; + + if (SQ(diff_y) < SQ(3.0f)) { + aITB_setupAction(ins, aITB_ACT_REST_ON_NOTICE, game); + } + } + } else { + aITB_GET_STOP_STATE(ins) = aITB_STOP_TYPE_NONE; + ins->patience = 100.0f; + aITB_setupAction(ins, aITB_ACT_FLY, game); + } +} + +/** + * Rest on notice action for the dragonfly. + * + * @param actorx Pointer to the actor. + * @param game Pointer to the game context. + */ +static void aITB_rest_on_notice(ACTOR* actorx, GAME* game) { + aINS_INSECT_ACTOR* ins = (aINS_INSECT_ACTOR*)actorx; + + if (ins->timer > 0 && ins->patience < 90.0f) { + add_calc(&actorx->world.position.y, aITB_GET_RESERVE_DUMMY_HEIGHT(ins), 1.0f - sqrtf(0.8f), 0.5f, 0.0f); + aITB_GET_REST_ANIME_TIMER(ins)++; + if (aITB_GET_REST_ANIME_TIMER(ins) < 20) { + aITB_anime_proc(ins); + } + + if (ins->timer > 0) { + ins->timer--; + } + } else { + aITB_GET_STOP_STATE(ins) = aITB_STOP_TYPE_NONE; + ins->patience = 100.0f; + aITB_setupAction(ins, aITB_ACT_FLY, game); + } +} + +/** + * Initializes the avoid action for the dragonfly. + * + * @param insect Pointer to the insect actor. + * @param game Pointer to the game context. + */ +static void aITB_avoid_init(aINS_INSECT_ACTOR* insect, GAME* game) { + ACTOR* playerx = GET_PLAYER_ACTOR_GAME_ACTOR(game); + + if (playerx != NULL) { + insect->tools_actor.actor_class.world.angle.y = search_position_angleY(&playerx->world.position, &insect->tools_actor.actor_class.world.position); + } + + insect->continue_timer = 0; + insect->target_speed = aITB_AVOID_SPEED + RANDOM_F(aITB_SPEED_VARIANCE); + insect->timer = aITB_AVOID_TIMER; +} + +/** + * Initializes the let escape action for the dragonfly. + * + * @param insect Pointer to the insect actor. + * @param game Pointer to the game context. + */ +static void aITB_let_escape_init(aINS_INSECT_ACTOR* insect, GAME* game) { + insect->life_time = 0; + insect->alpha_time = 80; + aITB_SET_HEIGHT(insect, 0.0f); + insect->tools_actor.actor_class.speed = 4.0f; + insect->tools_actor.actor_class.max_velocity_y = 12.0f; + insect->tools_actor.actor_class.gravity = 0.06f; + insect->tools_actor.actor_class.shape_info.rotation.x = 0; + insect->tools_actor.actor_class.position_speed.y = 0.0f; + insect->insect_flags.bit_1 = TRUE; + insect->insect_flags.bit_2 = TRUE; +} + +/** + * Initializes the fly action for the dragonfly. + * + * @param insect Pointer to the insect actor. + * @param game Pointer to the game context. + */ +static void aITB_fly_init(aINS_INSECT_ACTOR* insect, GAME* game) { + insect->tools_actor.actor_class.shape_info.rotation.y = insect->tools_actor.actor_class.world.angle.y; + aITB_move_spd_set(insect); +} + +/** + * Initializes the wait action for the dragonfly. + * + * @param insect Pointer to the insect actor. + * @param game Pointer to the game context. + */ +static void aITB_wait_init(aINS_INSECT_ACTOR* insect, GAME* game) { + static s16 angl_add[] = { + DEG2SHORT_ANGLE2(-67.50f), + DEG2SHORT_ANGLE2(-33.75f), + DEG2SHORT_ANGLE2( 33.75f), + DEG2SHORT_ANGLE2( 67.50f), + }; + + int i; + s16 angle; + u32 attr; + + if (aITB_GET_STOP_STATE(insect) == aITB_STOP_TYPE_NONE) { + f32 dx = aITB_GET_BK_CENTER_X(insect) - insect->tools_actor.actor_class.world.position.x; + f32 dz = aITB_GET_BK_CENTER_Z(insect) - insect->tools_actor.actor_class.world.position.z; + + if (SQ(dx) + SQ(dz) < SQ(aITB_OTHER_MAX_RANGE)) { + // Fly around randomly + insect->tools_actor.actor_class.world.angle.y += angl_add[RANDOM(ARRAY_COUNT(angl_add))]; + } else { + // We're too far from the center of the acre, so turn towards it + insect->tools_actor.actor_class.world.angle.y = atans_table(dz, dx); + } + } + + insect->patience = 0.0f; + insect->continue_timer = 0; + aITB_GET_REST_ANIME_TIMER(insect) = 0; + insect->tools_actor.actor_class.speed = 0.0f; + insect->speed_step = 0.0f; + insect->timer = 0; + + angle = insect->tools_actor.actor_class.shape_info.rotation.y; + insect->tools_actor.actor_class.shape_info.rotation.y = insect->tools_actor.actor_class.world.angle.y; + mCoBG_BgCheckControll(NULL, (ACTOR*)insect, 12.0f, insect->bg_height, TRUE, mCoBG_REVERSE_TYPE_REVERSE, mCoBG_CHECK_TYPE_NORMAL); + insect->tools_actor.actor_class.shape_info.rotation.y = angle; + + for (i = 0; i < 4; i++) { + // Check up to 4 times for wall or ground collision. + // Each check should handle moving the actor slightly if + // it is too close to a wall or ground. + if (aITB_BGcheck((ACTOR*)insect, game) != aITB_BG_CHECK_RESULT_COLLIDE) { + return; + } + + insect->tools_actor.actor_class.shape_info.rotation.y = insect->tools_actor.actor_class.world.angle.y; + mCoBG_BgCheckControll(NULL, (ACTOR*)insect, 12.0f, insect->bg_height, TRUE, mCoBG_REVERSE_TYPE_REVERSE, mCoBG_CHECK_TYPE_NORMAL); + } + insect->tools_actor.actor_class.shape_info.rotation.y = angle; +} + +/** + * Initializes the touch water action for the dragonfly. + * + * @param insect Pointer to the insect actor. + * @param game Pointer to the game context. + */ +static void aITB_touch_water_init(aINS_INSECT_ACTOR* insect, GAME* game) { + aITB_wait_init(insect, game); + insect->tools_actor.actor_class.max_velocity_y = -1.0f; +} + +/** + * Initializes the touch water reverse action for the dragonfly. + * + * @param insect Pointer to the insect actor. + * @param game Pointer to the game context. + */ +static void aITB_touch_water_reverse_init(aINS_INSECT_ACTOR* insect, GAME* game) { + eEC_CLIP->effect_make_proc(eEC_EFFECT_TURI_HAMON, insect->tools_actor.actor_class.world.position, 1, insect->tools_actor.actor_class.world.angle.y, game, EMPTY_NO, 4, 0); + insect->tools_actor.actor_class.max_velocity_y = 4.0f; + insect->tools_actor.actor_class.position_speed.y = 4.0f; + insect->timer = aITB_TOUCH_WATER_REVERSE_TIMER; +} + +/** + * Initializes the hover wait on water action for the dragonfly. + * + * @param insect Pointer to the insect actor. + * @param game Pointer to the game context. + */ +static void aITB_hover_wait_on_water_init(aINS_INSECT_ACTOR* insect, GAME* game) { + insect->tools_actor.actor_class.max_velocity_y = 0.0f; + insect->tools_actor.actor_class.position_speed.y = 0.0f; + insect->timer = aITB_HOVER_WAIT_ON_WATER_TIMER; +} + +/** + * Initializes the fly on notice action for the dragonfly. + * + * @param insect Pointer to the insect actor. + * @param game Pointer to the game context. + */ +static void aITB_fly_on_notice_init(aINS_INSECT_ACTOR* insect, GAME* game) { + insect->patience = 0.0f; + insect->continue_timer = 0; + insect->flag = 0; + aITB_GET_REST_ANIME_TIMER(insect) = 0; + insect->tools_actor.actor_class.max_velocity_y = 0.0f; + insect->tools_actor.actor_class.speed = 1.0f; + insect->target_speed = 2.0f + RANDOM_F(aITB_SPEED_VARIANCE); +} + +/** + * Initializes the rest on notice action for the dragonfly. + * + * @param insect Pointer to the insect actor. + * @param game Pointer to the game context. + */ +static void aITB_rest_on_notice_init(aINS_INSECT_ACTOR* insect, GAME* game) { + insect->tools_actor.actor_class.speed = 0.0f; + insect->speed_step = 0.0f; + insect->target_speed = 0.0f; + insect->timer = aITB_REST_ON_NOTICE_TIMER; +} + +typedef void (*aITB_INIT_PROC)(aINS_INSECT_ACTOR* insect, GAME* game); + +/** + * Sets up the action for the dragonfly. + * + * @param insect Pointer to the insect actor. + * @param action Action to set up. + * @param game Pointer to the game context. + */ +static void aITB_setupAction(aINS_INSECT_ACTOR* insect, int action, GAME* game) { + static aITB_INIT_PROC init_proc[] = { + aITB_avoid_init, + aITB_let_escape_init, + aITB_fly_init, + aITB_fly_init, + aITB_wait_init, + aITB_touch_water_init, + aITB_touch_water_reverse_init, + aITB_hover_wait_on_water_init, + aITB_fly_on_notice_init, + aITB_rest_on_notice_init, + }; + + static aINS_ACTION_PROC act_proc[] = { + aITB_avoid, + aITB_let_escape, + aITB_fly, + aITB_oniyanma_fly, + aITB_wait, + aITB_touch_water, + aITB_touch_water_reverse, + aITB_hover_wait_on_water, + aITB_fly_on_notice, + aITB_rest_on_notice, + }; + + insect->action = action; + insect->action_proc = act_proc[action]; + init_proc[action](insect, game); +} + +/** + * Dragonfly move proc. + * + * @param actorx Pointer to the actor. + * @param game Pointer to the game context. + */ +static void aITB_actor_move(ACTOR* actorx, GAME* game) { + aINS_INSECT_ACTOR* ins = (aINS_INSECT_ACTOR*)actorx; + u32 catch_label = (u32)mPlib_Get_item_net_catch_label(); + + if (catch_label == (u32)ins) { + ins->alpha0 = 255; + aITB_setupAction(ins, aITB_ACT_LET_ESCAPE, game); + } else if (ins->insect_flags.bit_3 == TRUE && ins->insect_flags.bit_2 == FALSE) { + aITB_setupAction(ins, aITB_ACT_LET_ESCAPE, game); + } else { + ins->action_proc(actorx, game); + if (aITB_GET_STOP_STATE(ins) != aITB_STOP_TYPE_REST) { + aITB_anime_proc(ins); + } + } +}