From 395bea87abaf329ec0ed5cf3f94317293545dec3 Mon Sep 17 00:00:00 2001 From: Cuyler36 Date: Thu, 30 Jan 2025 03:46:00 -0500 Subject: [PATCH] Implement & link ac_gyo_test, ac_gyo_kaseki, link few others & fixes --- configure.py | 10 +- include/ac_gyoei.h | 2 + include/audio_defs.h | 2 + include/types.h | 2 + src/actor/ac_gyo_kaseki.c | 832 ++++++++++++++++++++++++++++++++++++ src/actor/ac_gyo_test.c | 872 ++++++++++++++++++++++++++++++++++++++ src/actor/ac_ins_hotaru.c | 2 +- 7 files changed, 1716 insertions(+), 6 deletions(-) create mode 100644 src/actor/ac_gyo_kaseki.c create mode 100644 src/actor/ac_gyo_test.c diff --git a/configure.py b/configure.py index dc6900c1..a4463af0 100644 --- a/configure.py +++ b/configure.py @@ -979,9 +979,9 @@ config.libs = [ Object(Matching, "actor/ac_goza.c"), Object(Matching, "actor/ac_groundhog_control.c"), Object(Matching, "actor/ac_gyo_kage.c"), - Object(NonMatching, "actor/ac_gyo_kaseki.c"), + Object(Matching, "actor/ac_gyo_kaseki.c"), Object(Matching, "actor/ac_gyo_release.c"), - Object(NonMatching, "actor/ac_gyo_test.c"), + Object(Matching, "actor/ac_gyo_test.c"), Object(Matching, "actor/ac_gyoei.c"), Object(Matching, "actor/ac_handOverItem.c"), Object(Matching, "actor/ac_haniwa.c"), @@ -996,13 +996,13 @@ config.libs = [ Object(Matching, "actor/ac_ins_dango.c"), Object(Matching, "actor/ac_ins_goki.c"), Object(Matching, "actor/ac_ins_hitodama.c"), - Object(NonMatching, "actor/ac_ins_hotaru.c"), + Object(Matching, "actor/ac_ins_hotaru.c"), Object(Matching, "actor/ac_ins_ka.c"), Object(Matching, "actor/ac_ins_kabuto.c"), - Object(NonMatching, "actor/ac_ins_kera.c"), + Object(Matching, "actor/ac_ins_kera.c"), Object(Matching, "actor/ac_ins_mino.c"), Object(Matching, "actor/ac_ins_semi.c"), - Object(NonMatching, "actor/ac_ins_tentou.c"), + Object(Matching, "actor/ac_ins_tentou.c"), Object(Matching, "actor/ac_ins_tonbo.c"), Object(Matching, "actor/ac_insect.c"), Object(Matching, "actor/ac_intro_demo.c"), diff --git a/include/ac_gyoei.h b/include/ac_gyoei.h index 6655284c..497b8b37 100644 --- a/include/ac_gyoei.h +++ b/include/ac_gyoei.h @@ -90,6 +90,8 @@ enum fish_type { #define aGYO_TYPE_INVALID -1 +#define aGYO_IS_FISH_TRASH(type) ((type) >= aGYO_TYPE_EMPTY_CAN && (type) <= aGYO_TYPE_OLD_TIRE) + enum { aGYO_SIZE_XXS, aGYO_SIZE_XS, diff --git a/include/audio_defs.h b/include/audio_defs.h index a7eed04a..2ecf072e 100644 --- a/include/audio_defs.h +++ b/include/audio_defs.h @@ -51,6 +51,7 @@ typedef enum audio_sound_effects { NA_SE_LIGHT_ON, NA_SE_LIGHT_OFF, + NA_SE_24 = 0x24, NA_SE_25 = 0x25, NA_SE_26 = 0x26, NA_SE_27 = 0x27, @@ -153,6 +154,7 @@ typedef enum audio_sound_effects { NA_SE_114 = 0x114, NA_SE_SEMI_ESCAPE = 0x115, + NA_SE_11A = 0x11A, NA_SE_11B = 0x11B, NA_SE_WEAR = 0x11C, diff --git a/include/types.h b/include/types.h index 7a317df3..af1f2aed 100644 --- a/include/types.h +++ b/include/types.h @@ -106,6 +106,8 @@ typedef u32 unknown; #define F32_IS_ZERO(v) (fabsf(v) < 0.008f) +#define DECREMENT_TIMER(timer) ((timer) == 0 ? 0 : --(timer)) + /* ARGB8 color format (32 bits) to RGB5A3 color format (16 bits) */ #define ARGB8_to_RGB5A3(argb8) \ ((u16)(((argb8) & 0xFF000000) >= 0xE0000000 \ diff --git a/src/actor/ac_gyo_kaseki.c b/src/actor/ac_gyo_kaseki.c new file mode 100644 index 00000000..c35ff71e --- /dev/null +++ b/src/actor/ac_gyo_kaseki.c @@ -0,0 +1,832 @@ +#include "ac_gyoei.h" + +#include "m_common_data.h" +#include "m_player_lib.h" +#include "ac_uki.h" + +enum { + aGKK_ACTION_SWIM, + aGKK_ACTION_SWIM2, + aGKK_ACTION_SWIM3, + aGKK_ACTION_SWIM4, + aGKK_ACTION_WAIT, + aGKK_ACTION_WAIT_MONSTER, + aGKK_ACTION_ESCAPE, + aGKK_ACTION_NEAR, + aGKK_ACTION_TOUCH, + aGKK_ACTION_BITE, + aGKK_ACTION_COMEBACK, + + aGKK_ACTION_NUM +}; + +static void aGKK_actor_move(ACTOR* actorx, GAME* game); +static void aGKK_setupAction(aGYO_CTRL_ACTOR* gyo, int action); +static void aGKK_set_scale(ACTOR* actorx, f32 scale); +static void aGKK_speed_reset(ACTOR* actorx); +static s16 aGKK_Get_flow_angle(ACTOR* actorx); +static s16 aGKK_Get_flow_angle_rv(ACTOR* actorx); +static void aGKK_set_angle(ACTOR* actorx, s16 angleY); +static f32 aGKK_Get_water_surface_position_y(xyz_t pos); +static int aGKK_check_offing(ACTOR* actorx); + +#include "../src/actor/ac_gyoei_type.c_inc" + +static f32 aGKK_speed[8] = { + 1.0f, 1.25f, 1.25f, 1.5f, 1.75f, 2.0f, 2.2f, 2.2f, +}; + +static f32 aGKK_back_speed[8] = { + -0.38f, -0.4f, -0.42f, -0.42f, -0.45f, -0.5f, -0.7f, -0.7f, +}; + +static s16 aGKK_touch_count[8] = { + 19, 19, 19, 19, 20, 22, 24, 24, +}; + +static f32 aGKK_touch_distance[8] = { + 12.0f, 13.0f, 15.0f, 15.0f, 20.0f, 25.0f, 30.0f, 30.0f, +}; + +static f32 aGYO_shadow_scale[8] = { + 0.3f, 0.4f, 0.5f, 0.5f, 0.6f, 0.8f, 1.2f, 10.0f, +}; + +static f32 aGYO_search_area[][5] = { + {40.0f, 40.0f, 40.0f, 50.0f, 60.0f}, // regular rod + {40.0f, 40.0f, 40.0f, 50.0f, 60.0f}, // gold rod +}; + +static f32 aGYO_search_angle[][5] = { + {3.0f, 7.0f, 30.0f, 50.0f, 180.0f}, // regular rod + {7.5f, 15.0f, 40.0f, 60.0f, 180.0f}, // gold rod +}; + +static f32 aGYO_bite_time[][5] = { + {10.0f, 11.0f, 12.0f, 15.0f, 45.0f}, // regular rod + {11.0f, 12.0f, 13.0f, 18.0f, 60.0f}, // gold rod +}; + +extern void aGKK_actor_init(ACTOR* actorx, GAME* game) { + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + f32 scale; + u32 attr; + int action; + + attr = mCoBG_Wpos2BgAttribute_Original(actorx->world.position); + if (mCoBG_CheckWaterAttribute(attr) == TRUE) { + actorx->world.position.y = aGKK_Get_water_surface_position_y(actorx->world.position); + } + + gyo->size_type = gyoei_type[gyo->gyo_type].size; + gyo->action = aGKK_ACTION_SWIM; + gyo->fwork0 = 19.0f; + scale = aGYO_shadow_scale[gyo->size_type] * 0.02f; + aGKK_set_scale(actorx, scale); + aGKK_speed_reset(actorx); + actorx->mv_proc = aGKK_actor_move; + aGKK_set_angle(actorx, aGKK_Get_flow_angle_rv(actorx)); + actorx->shape_info.draw_shadow = FALSE; + + if (gyo->size_type == aGYO_SIZE_WHALE) { + action = aGKK_ACTION_WAIT_MONSTER; + } else if ((mFI_BkNum2BlockKind(actorx->block_x, actorx->block_z) & mRF_BLOCKKIND_OFFING) == mRF_BLOCKKIND_OFFING) { + action = aGKK_ACTION_WAIT_MONSTER; + } else { + action = aGKK_ACTION_WAIT; + } + aGKK_setupAction(gyo, action); +} + +static int aGKK_get_uki_type(void) { + int ret = aGYO_ROD_NORMAL; + + if (Now_Private->equipment == ITM_GOLDEN_ROD) { + ret = aGYO_ROD_GOLDEN; + } + + return ret; +} + +static void aGKK_speed_reset(ACTOR* actorx) { + actorx->speed = 0.0f; + actorx->max_velocity_y = 0.0f; +} + +static void aGKK_set_scale(ACTOR* actorx, f32 scale) { + actorx->scale.x = scale; + actorx->scale.y = scale; + actorx->scale.z = scale; +} + +static void aGKK_set_angle(ACTOR* actorx, s16 angleY) { + actorx->world.angle.y = angleY; + actorx->shape_info.rotation.y = angleY; +} + +static xyz_t aGKK_pos_calc(xyz_t pos, GAME* game, s16 angleY, f32 dist, f32 rr) { + ACTOR* playerx = GET_PLAYER_ACTOR_GAME_ACTOR(game); + xyz_t ret; + f32 sin = sin_s(playerx->world.angle.y) * dist; + f32 cos = cos_s(playerx->world.angle.y) * dist; + f32 cos2 = cos_s(angleY) * -rr; + f32 sin2 = sin_s(angleY) * rr; + + ret.x = pos.x + sin + cos2; + ret.z = pos.z + cos + sin2; + return ret; +} + +static void aGKK_effect_sibuki(aGYO_CTRL_ACTOR* gyo, GAME* game, s16 arg) { + xyz_t pos = gyo->tools_class.actor_class.world.position; + f32 water_y = mCoBG_GetWaterHeight_File(pos, __FILE__, 356); + s16 flow_angle; + + switch (arg) { + case 4: + case 5: + case 6: + case 7: + case 8: { + static f32 rr[] = { 3.0f, 3.5f, 4.0f, 4.0f, 4.5f, 5.0f, 5.0f, 5.0f }; + + pos = aGKK_pos_calc(gyo->linked_actor->world.position, game, gyo->linked_actor->world.angle.y, 4.0f, rr[gyo->size_type]); + break; + } + } + + pos.y = water_y; + flow_angle = aGKK_Get_flow_angle((ACTOR*)gyo); + eEC_CLIP->effect_make_proc(eEC_EFFECT_TURI_MIZU, pos, 1, flow_angle, game, EMPTY_NO, arg, 0); +} + +static void aGKK_kage_make_actor(aGYO_CTRL_ACTOR* gyo, GAME* game, u8 state) { + GAME_PLAY* play = (GAME_PLAY*)game; + f32 height; + + if (state == 0) { + gyo->gyo_flags |= 0x20; + } + + height = aGKK_Get_water_surface_position_y(gyo->tools_class.actor_class.world.position); + Actor_info_make_actor( + // clang-format off + &play->actor_info, game, mAc_PROFILE_GYO_KAGE, + gyo->tools_class.actor_class.world.position.x, height, gyo->tools_class.actor_class.world.position.z, + 0, 0, 0, + play->block_table.block_x, play->block_table.block_z, + -1, EMPTY_NO, gyo->size_type, -1, ACTOR_OBJ_BANK_KEEP + // clang-format on + ); +} + +static void aGKK_fish_make_actor(aGYO_CTRL_ACTOR* gyo, GAME* game) { + GAME_PLAY* play = (GAME_PLAY*)game; + + // set uki actor's child actor to the fish actor being created + ((UKI_ACTOR*)gyo->linked_actor)->child_actor = Actor_info_make_actor( + // clang-format off + &play->actor_info, game, mAc_PROFILE_GYO_RELEASE, + gyo->tools_class.actor_class.world.position.x, gyo->tools_class.actor_class.world.position.y, gyo->tools_class.actor_class.world.position.z, + 0, gyo->swork4, 0, + play->block_table.block_x, play->block_table.block_z, + -1, EMPTY_NO, ITM_FISH_START + gyo->gyo_type, -1, ACTOR_OBJ_BANK_KEEP + // clang-format on + ); +} + +static void aGKK_effect_hamon(aGYO_CTRL_ACTOR* gyo, GAME* game, s16 arg) { + xyz_t pos = gyo->tools_class.actor_class.world.position; + f32 water_y = mCoBG_GetWaterHeight_File(gyo->tools_class.actor_class.world.position, __FILE__, 474); + s16 flow_angle = aGKK_Get_flow_angle((ACTOR*)gyo); + + pos.y = water_y; + eEC_CLIP->effect_make_proc(eEC_EFFECT_TURI_HAMON, pos, 1, flow_angle, game, EMPTY_NO, arg, 0); +} + +static int aGKK_random_check(f32 val) { + int ret = FALSE; + + if (RANDOM_F(val) < 1.0f) { + ret = TRUE; + } + + return ret; +} + +static int aGKK_warp_event(aGYO_CTRL_ACTOR* gyo) { + int ret = FALSE; + + if (mPlib_check_player_warp_forEvent()) { + ((UKI_ACTOR*)gyo->linked_actor)->gyo_command = 0; + aGKK_setupAction(gyo, aGKK_ACTION_ESCAPE); + ret = TRUE; + } + + return ret; +} + +static f32 aGKK_speed_calc(f32 speed, s16 angle) { + return speed * sin_s(angle); +} + +static void aGKK_position_calc(aGYO_CTRL_ACTOR* gyo) { + static f32 hosei[] = { + // clang-format off + -8.0f, + -10.0f, + -12.0f, + -12.0f, + -15.0f, + -20.0f, + -25.0f, + -25.0f, + // clang-format on + }; + + gyo->tools_class.actor_class.world.position.x += hosei[gyo->size_type] * sin_s(gyo->tools_class.actor_class.world.angle.y); + gyo->tools_class.actor_class.world.position.z += hosei[gyo->size_type] * cos_s(gyo->tools_class.actor_class.world.angle.y); +} + +static int aGKK_swim_speed_check(aGYO_CTRL_ACTOR* gyo, f32 target, f32 step, f32 speed) { + int ret = chase_f(&gyo->fwork3, target, step * 0.5f); + + gyo->tools_class.actor_class.speed = aGKK_speed_calc(speed, DEG2SHORT_ANGLE2(gyo->fwork3)); + return ret; +} + +static s16 aGKK_Get_flow_angle(ACTOR* actorx) { + xyz_t flow; + + mCoBG_GetWaterFlow(&flow, actorx->bg_collision_check.result.unit_attribute); + return atans_table(flow.z, flow.x); +} + +static s16 aGKK_Get_flow_angle_rv(ACTOR* actorx) { + return aGKK_Get_flow_angle(actorx) + DEG2SHORT_ANGLE2(180.0f); +} + +static f32 aGKK_Get_water_surface_position_y(xyz_t pos) { + f32 ret = mCoBG_GetWaterHeight_File(pos, __FILE__, 643); + + return ret - 8.0f; +} + +static int aGKK_search_Uki(aGYO_CTRL_ACTOR* gyo, GAME* game) { + GAME_PLAY* play; + f32 target_dist; + f32 target_y; + f32 escape_dist; + s16 target_angle; + s16 goal_angle; + s16 search_area; + int ret; + + play = (GAME_PLAY*)game; + search_area = gyoei_type[gyo->gyo_type].search_area; + ret = FALSE; + { + UKI_ACTOR* uki = (UKI_ACTOR*)Actor_info_name_search(&play->actor_info, mAc_PROFILE_UKI, ACTOR_PART_BG); + gyo->linked_actor = (ACTOR*)uki; + if (uki != NULL) { + target_dist = search_position_distance(&gyo->tools_class.actor_class.world.position, &uki->actor_class.world.position); + target_y = gyo->tools_class.actor_class.world.position.y - uki->actor_class.world.position.y; + target_angle = search_position_angleY(&gyo->tools_class.actor_class.world.position, &uki->actor_class.world.position); + goal_angle = target_angle - gyo->tools_class.actor_class.shape_info.rotation.y; + + if (gyo->size_type == aGYO_SIZE_XXS || gyo->size_type == aGYO_SIZE_XS) { + escape_dist = 17.0f; + } else { + escape_dist = 22.0f; + } + + if (uki->hit_water_flag && target_dist < escape_dist) { + aGKK_set_angle(((ACTOR*)gyo), target_angle + DEG2SHORT_ANGLE2(180.0f)); + aGKK_setupAction(gyo, aGKK_ACTION_ESCAPE); + } else { + int rod_type = aGKK_get_uki_type(); + + if ((gyo->gyo_flags & 1) == 0 && uki->cast_timer == 0 && + target_dist < aGYO_search_area[rod_type][search_area] && fabsf(target_y) < 10.0f && + goal_angle > DEG2SHORT_ANGLE2(-aGYO_search_angle[rod_type][search_area]) && + goal_angle < DEG2SHORT_ANGLE2(aGYO_search_angle[rod_type][search_area])) { + ret = TRUE; + } + } + } + } + + return ret; +} + +static int aGKK_player_near(aGYO_CTRL_ACTOR* gyo, GAME* game) { + ACTOR* playerx = GET_PLAYER_ACTOR_GAME_ACTOR(game); + f32 dist; + s16 target_angle; + xyz_t pos; + int ret = FALSE; + + dist = search_position_distance(&gyo->tools_class.actor_class.world.position, &playerx->world.position); + target_angle = search_position_angleY(&gyo->tools_class.actor_class.world.position, &playerx->world.position); + + if ( + // clang-format off + ((dist < 110.0f && mPlib_get_player_actor_main_index(game) == mPlayer_INDEX_DASH) || + (dist < 150.0f && ( + mPlib_Check_HitAxe(&pos) || + mPlib_Check_StopNet(&pos) || + mPlib_Check_HitScoop(&pos) + ))) || + gyo->escape_flag + // clang-format on + ) { + aGKK_set_angle((ACTOR*)gyo, target_angle + DEG2SHORT_ANGLE2(180.0f)); + switch (gyo->size_type) { + case aGYO_SIZE_XXS: + case aGYO_SIZE_XS: + case aGYO_SIZE_S: + case aGYO_SIZE_M: + aGKK_effect_hamon(gyo, game, 3); + break; + case aGYO_SIZE_L: + case aGYO_SIZE_XL: + case aGYO_SIZE_XXL: + aGKK_effect_hamon(gyo, game, 2); + break; + } + + aGKK_kage_make_actor(gyo, game, 0); + ret = TRUE; + } + + return ret; +} + +static int aGKK_check_offing(ACTOR* actorx) { + static f32 chkX[] = { 0.0f, 0.0f, -mFI_UT_WORLDSIZE_X_F * 2, mFI_UT_WORLDSIZE_X_F * 2 }; + static f32 chkZ[] = { -mFI_UT_WORLDSIZE_Z_F * 2, mFI_UT_WORLDSIZE_Z_F * 2, 0.0f, 0.0f }; + int ret = FALSE; + u32 blockkind = mFI_BkNum2BlockKind(actorx->block_x, actorx->block_z); + + if ((blockkind & mRF_BLOCKKIND_MARINE) == mRF_BLOCKKIND_MARINE && (blockkind & mRF_BLOCKKIND_OFFING) != mRF_BLOCKKIND_OFFING) { + int i; + + for (i = 0; i < 4; i++) { + xyz_t pos = actorx->world.position; + + pos.x += chkX[i]; + pos.z += chkZ[i]; + if (mCoBG_CheckWaterAttribute(mCoBG_Wpos2Attribute(pos, NULL)) == FALSE) { + ret = TRUE; + break; + } + } + } + + return ret; +} + +static int aGKK_check_wall(ACTOR* actorx) { + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + int ret = FALSE; + + if (actorx->bg_collision_check.result.hit_wall != mCoBG_DIDNT_HIT_WALL) { + int count = actorx->bg_collision_check.result.hit_wall_count; + + if (count != 0) { + int i; + + for (i = 0; i < count; i++) { + if (actorx->bg_collision_check.wall_info[i].type == mCoBG_WALL_TYPE0) { + s16 wall_angle = actorx->bg_collision_check.wall_info[i].angleY; + + aGKK_set_angle(actorx, wall_angle); + gyo->gyo_flags |= 0x40; + ret = TRUE; + break; + } + } + } + } + + if (ret == FALSE) { + xyz_t pos; + u32 attr; + f32 ground_y; + f32 water_y; + f32 diff_y; + + pos = actorx->world.position; + pos.z -= 10.0f; + attr = mCoBG_Wpos2Attribute(pos, NULL); + ground_y = mCoBG_GetBgY_AngleS_FromWpos(NULL, pos, 0.0f); + water_y = mCoBG_GetWaterHeight_File(pos, __FILE__, 870); + diff_y = water_y - ground_y; + + if (mCoBG_CheckSandUt_ForFish(&pos) == TRUE || diff_y < 20.0f) { + actorx->world.position = actorx->last_world_position; + aGKK_set_angle(actorx, 0); + gyo->gyo_flags |= 0x40; + ret = TRUE; + } else if (aGKK_check_offing(actorx) == FALSE) { + actorx->world.position = actorx->last_world_position; + aGKK_set_angle(actorx, DEG2SHORT_ANGLE2(180.0f)); + gyo->gyo_flags |= 0x40; + ret = TRUE; + } + } + + return ret; +} + +static int aGKK_check_uki(aGYO_CTRL_ACTOR* gyo, GAME* game) { + int ret = FALSE; + + if (aGKK_player_near(gyo, game) == FALSE && aGKK_search_Uki(gyo, game) == TRUE) { + aGKK_setupAction(gyo, aGKK_ACTION_NEAR); + ret = TRUE; + } + + return ret; +} + +static void aGKK_swim(ACTOR* actorx, GAME* game) { + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + + if (aGKK_check_wall(actorx) == TRUE) { + aGKK_setupAction(gyo, aGKK_ACTION_ESCAPE); + } else if (aGKK_swim_speed_check(gyo, 360.0f, 5.0f, 0.5f) == TRUE) { + aGKK_setupAction(gyo, aGKK_ACTION_WAIT); + } else { + aGKK_check_uki(gyo, game); + } +} + +static void aGKK_swim2(ACTOR* actorx, GAME* game) { + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + + if (aGKK_check_wall(actorx) == TRUE) { + aGKK_setupAction(gyo, aGKK_ACTION_ESCAPE); + } else if (aGKK_swim_speed_check(gyo, 180.0f, 5.0f, 0.5f) == TRUE) { + actorx->world.angle.y = actorx->shape_info.rotation.y; + aGKK_setupAction(gyo, aGKK_ACTION_WAIT); + } else { + aGKK_check_uki(gyo, game); + } +} + +static void aGKK_swim3(ACTOR* actorx, GAME* game) { + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + + if (aGKK_check_wall(actorx) == TRUE) { + aGKK_setupAction(gyo, aGKK_ACTION_ESCAPE); + } else if (chase_angle(&actorx->world.angle.y, gyo->swork3, 0x400) == TRUE) { + actorx->shape_info.rotation.y = actorx->world.angle.y; + aGKK_setupAction(gyo, aGKK_ACTION_SWIM4); + } else { + actorx->shape_info.rotation.y = actorx->world.angle.y; + aGKK_check_uki(gyo, game); + } +} + +static void aGKK_swim4(ACTOR* actorx, GAME* game) { + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + + if (aGKK_check_wall(actorx) == TRUE) { + aGKK_setupAction(gyo, aGKK_ACTION_ESCAPE); + } else { + actorx->shape_info.rotation.y += gyo->swork2; + + if (aGKK_swim_speed_check(gyo, 180.0f, 5.0f, 1.0f) == TRUE) { + actorx->world.angle.y = actorx->shape_info.rotation.y; + aGKK_setupAction(gyo, aGKK_ACTION_WAIT); + } else { + aGKK_check_uki(gyo, game); + } + } +} + +static void aGKK_wait(ACTOR* actorx, GAME* game) { + static int swim_action[] = { aGKK_ACTION_SWIM, aGKK_ACTION_SWIM2, aGKK_ACTION_SWIM }; + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + + if (DECREMENT_TIMER(gyo->work0) == 0) { + aGKK_speed_reset(actorx); + aGKK_setupAction(gyo, swim_action[RANDOM(ARRAY_COUNT(swim_action))]); + } else if (aGKK_check_wall(actorx) == TRUE) { + aGKK_setupAction(gyo, aGKK_ACTION_ESCAPE); + } else { + aGKK_check_uki((aGYO_CTRL_ACTOR*)actorx, game); + } +} + +static void aGKK_near(ACTOR* actorx, GAME* game) { + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + f32 target_dist; + f32 target_y; + s16 angle_y; + UKI_ACTOR* uki = (UKI_ACTOR*)gyo->linked_actor; + s16 search_area = gyoei_type[gyo->gyo_type].search_area; + int rod_type = aGKK_get_uki_type(); + + angle_y = search_position_angleY(&actorx->world.position, &uki->actor_class.world.position); + aGKK_set_angle(actorx, angle_y); + target_dist = search_position_distance(&actorx->world.position, &uki->actor_class.world.position); + target_y = actorx->world.position.y - uki->actor_class.world.position.y; + if (target_dist > aGYO_search_area[rod_type][search_area] || fabsf(target_y) > 10.0f) { + aGKK_setupAction(gyo, aGKK_ACTION_WAIT); + } else if (target_dist < aGKK_touch_distance[gyo->size_type]) { + if (uki->gyo_status == 1) { + uki->gyo_command = 1; + uki->gyo_type = gyo->gyo_type; + uki->child_actor = actorx; + uki->actor_class.world.angle.y = actorx->world.angle.y; + uki->actor_class.shape_info.rotation.y = actorx->shape_info.rotation.y; + aGKK_setupAction(gyo, aGKK_ACTION_TOUCH); + } else { + aGKK_setupAction(gyo, aGKK_ACTION_WAIT); + } + } +} + +static void aGKK_touch(ACTOR* actorx, GAME* game) { + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + UKI_ACTOR* uki; + + if (aGKK_warp_event(gyo)) { + return; + } + + uki = (UKI_ACTOR*)gyo->linked_actor; + uki->actor_class.world.angle.y = actorx->world.angle.y; + uki->actor_class.shape_info.rotation.y = actorx->shape_info.rotation.y; + + if (DECREMENT_TIMER(gyo->work0) == 0) { + f32 target_dist; + s16 angle; + + actorx->speed = aGKK_speed[gyo->size_type]; + angle = search_position_angleY(&actorx->world.position, &uki->actor_class.world.position); + aGKK_set_angle(actorx, angle); + target_dist = search_position_distance(&actorx->world.position, &uki->actor_class.world.position); + + if (target_dist < aGKK_touch_distance[gyo->size_type]) { + if ((aGKK_random_check(4.0f) == TRUE) || DECREMENT_TIMER(gyo->touch_counter) == 0) { + if (uki->gyo_status == 2) { + uki->gyo_command = 2; + + // If a free space exists, 1/20 chance of switching the fish out for trash + if (mPlib_Get_space_putin_item() >= 0 && aGKK_random_check(20.0f) == TRUE) { + static int gomi[] = { aGYO_TYPE_EMPTY_CAN, aGYO_TYPE_EMPTY_CAN, aGYO_TYPE_BOOT, aGYO_TYPE_BOOT, aGYO_TYPE_BOOT, aGYO_TYPE_OLD_TIRE, aGYO_TYPE_OLD_TIRE, aGYO_TYPE_OLD_TIRE }; + + gyo->gyo_type = gomi[gyo->size_type]; + uki->gyo_type = gyo->gyo_type; + } + + aGKK_setupAction(gyo, aGKK_ACTION_BITE); + } + } else { + uki->touched_flag = TRUE; + gyo->work0 = (int)((aGKK_touch_count[gyo->size_type] + RANDOM2_F(30.0f)) * 2.0f); + actorx->speed = aGKK_back_speed[gyo->size_type] + RANDOM2_F(0.2f); + } + } + } + + if (uki->status == 6) { + uki->gyo_command = 0; + aGKK_effect_sibuki(gyo, game, 0); + aGKK_kage_make_actor(gyo, game, 0); + } +} + +static void aGKK_bite(ACTOR* actorx, GAME* game) { + static s16 eff_arg[] = { 8, 7, 4, 4, 5, 6, 6, 6 }; + + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + UKI_ACTOR* uki; + + if (aGKK_warp_event(gyo)) { + return; + } + + uki = (UKI_ACTOR*)gyo->linked_actor; + if (uki->gyo_status == 4) { + static f32 rr[] = { 6.0f, 7.0f, 8.0f, 8.0f, 9.0f, 10.0f, 15.0f, 15.0f }; + xyz_t pos; + + pos = aGKK_pos_calc(uki->actor_class.world.position, game, uki->actor_class.world.angle.y, 8.0f, rr[gyo->size_type]); + actorx->world.position.x = pos.x; + actorx->world.position.z = pos.z; + + aGKK_set_angle(actorx, uki->actor_class.world.angle.y); + sAdo_OngenPos((u32)actorx, NA_SE_24, &actorx->world.position); + CLIP(gyo_clip)->hitcheck_gyoei_proc(&actorx->world.position, gyo->size_type); + + if (DECREMENT_TIMER(gyo->swork0) == 0) { + if (aGKK_random_check(2.0f) == TRUE) { + aGKK_effect_sibuki(gyo, game, eff_arg[gyo->size_type]); + } + + gyo->swork0 = 6; + } + } else { + actorx->world.position.x = uki->actor_class.world.position.x; + actorx->world.position.z = uki->actor_class.world.position.z; + + aGKK_set_angle(actorx, uki->actor_class.world.angle.y); + aGKK_position_calc(gyo); + + if (uki->gyo_status == 5) { + actorx->speed = 0.0f; + aGKK_setupAction(gyo, aGKK_ACTION_COMEBACK); + } else if (DECREMENT_TIMER(gyo->work0) == 0) { + uki->gyo_command = 0; + aGKK_kage_make_actor(gyo, game, 0); + } + } + + uki->gyo_pos = actorx->world.position; +} + +static void aGKK_comeback(ACTOR* actorx, GAME* game) { + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + f32 scale; + UKI_ACTOR* uki = (UKI_ACTOR*)gyo->linked_actor; + + actorx->world.position = uki->uki_pos; + aGKK_set_angle(actorx, uki->actor_class.world.angle.y); + + if (uki->gyo_status == 3) { + aGKK_position_calc(gyo); + if (aGKK_random_check(10.0f) == TRUE) { + aGKK_effect_hamon(gyo, game, 0); + } + } else if (uki->gyo_status == 5) { + ACTOR* playerx = GET_PLAYER_ACTOR_GAME_ACTOR(game); + + if ((gyo->gyo_flags & 4) == 0) { + if (gyo->gyo_type == aGYO_TYPE_OLD_TIRE) { + aGKK_kage_make_actor(gyo, game, 1); + } + + gyo->swork4 = playerx->world.angle.y; + gyo->gyo_flags |= 8; + } + } else if (uki->gyo_status == 6) { + gyo->gyo_flags |= 0x200; + if (gyo->anim_frame == 1) { + sAdo_OngenTrgStart(NA_SE_11A, &actorx->world.position); + } + } else if (uki->gyo_status == 8) { + uki->gyo_status = 0; + aGKK_fish_make_actor(gyo, game); + gyo->gyo_flags |= 0x20; + } else if (uki->gyo_status == 7) { + gyo->gyo_flags |= 0x10; + } else if (uki->gyo_status == 0) { + uki->gyo_command = 0; + uki->child_actor = NULL; + gyo->gyo_flags &= ~2; + gyo->gyo_flags |= 0x20; + return; + } + + if ((gyo->gyo_flags & 0x10) != 0) { + scale = uki->gyo_scale / 100.0f; + } else if (gyo->draw_type == aGYO_DRAW_TYPE_FISH) { + scale = 0.01f; + } else { + scale = aGYO_shadow_scale[gyo->size_type] * 0.02f; + } + + aGKK_set_scale(actorx, scale); + uki->gyo_pos = actorx->world.position; +} + +static void aGKK_escape(ACTOR* actorx, GAME* game) { + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + + if (aGKK_check_uki(gyo, game) == FALSE && ((gyo->gyo_flags & 0x40) != 0 || aGKK_check_wall(actorx) == FALSE)) { + if (DECREMENT_TIMER(gyo->work0) == 0) { + gyo->gyo_flags &= ~0x40; + aGKK_setupAction(gyo, aGKK_ACTION_WAIT); + } else { + chase_f(&actorx->speed, 0.0f, 0.01f); + } + } +} + +static void aGKK_swim_init(aGYO_CTRL_ACTOR* gyo) { + gyo->fwork3 = 50.0f; +} + +static void aGKK_swim2_init(aGYO_CTRL_ACTOR* gyo) { + gyo->fwork3 = 0.0f; + gyo->tools_class.actor_class.world.angle.y += (int)RANDOM2_F(DEG2SHORT_ANGLE2(180.0f)); +} + +static void aGKK_swim3_init(aGYO_CTRL_ACTOR* gyo) { + gyo->swork3 = gyo->tools_class.actor_class.world.angle.y + (int)RANDOM2_F(DEG2SHORT_ANGLE2(90.0f)); + gyo->tools_class.actor_class.shape_info.rotation.y = gyo->tools_class.actor_class.world.angle.y; +} + +static void aGKK_swim4_init(aGYO_CTRL_ACTOR* gyo) { + s16 angle; + + gyo->fwork3 = 5.0f; + if (gyo->tools_class.actor_class.shape_info.rotation.y > 0) { + angle = DEG2SHORT_ANGLE2(90.0f); + } else { + angle = DEG2SHORT_ANGLE2(-90.0f); + } + + angle -= gyo->tools_class.actor_class.world.angle.y; + gyo->swork2 = angle / 36; +} + +static void aGKK_near_init(aGYO_CTRL_ACTOR* gyo) { + gyo->gyo_flags |= 2; + ((ACTOR*)gyo)->speed = aGKK_speed[gyo->size_type]; + ((ACTOR*)gyo)->max_velocity_y = 12.0f; +} + +static void aGKK_touch_init(aGYO_CTRL_ACTOR* gyo) { + gyo->work0 = 0; + gyo->range = 12.0f; + gyo->touch_counter = 5; + aGKK_speed_reset((ACTOR*)gyo); +} + +static void aGKK_bite_init(aGYO_CTRL_ACTOR* gyo) { + gyo->work0 = (int)(aGYO_bite_time[aGKK_get_uki_type()][gyoei_type[gyo->gyo_type].bite_time] * 2.0f); + gyo->swork0 = 6; + aGKK_speed_reset((ACTOR*)gyo); +} + +static void aGKK_comeback_init(aGYO_CTRL_ACTOR* gyo) { + gyo->swork2 = 1 + RANDOM(3); + gyo->swork3 = 15 + RANDOM2(10); + aGKK_speed_reset((ACTOR*)gyo); +} + +static void aGKK_wait_init(aGYO_CTRL_ACTOR* gyo) { + gyo->gyo_flags &= ~2; + gyo->work0 = (int)((100.0f + RANDOM_F(30.0f)) * 2.0f); + ((ACTOR*)gyo)->speed = -0.15f + RANDOM2_F(0.2f); + ((ACTOR*)gyo)->max_velocity_y = 0.0f; +} + +static void aGKK_wait_monster_init(aGYO_CTRL_ACTOR* gyo) { + ((ACTOR*)gyo)->speed = 0.0f; + ((ACTOR*)gyo)->max_velocity_y = 0.0f; +} + +static void aGKK_escape_init(aGYO_CTRL_ACTOR* gyo) { + gyo->swork0 = 0; + gyo->work0 = 100; + ((ACTOR*)gyo)->speed = 1.0f; +} + +typedef void (*aGKK_INIT_PROC)(aGYO_CTRL_ACTOR* gyo); + +static void aGKK_setupAction(aGYO_CTRL_ACTOR* gyo, int action) { + static aGKK_INIT_PROC init_proc[] = { + aGKK_swim_init, + aGKK_swim2_init, + aGKK_swim3_init, + aGKK_swim4_init, + aGKK_wait_init, + aGKK_wait_monster_init, + aGKK_escape_init, + aGKK_near_init, + aGKK_touch_init, + aGKK_bite_init, + aGKK_comeback_init, + }; + + static aGYO_ACT_PROC act_proc[] = { + aGKK_swim, + aGKK_swim2, + aGKK_swim3, + aGKK_swim4, + aGKK_wait, + (aGYO_ACT_PROC)none_proc1, + aGKK_escape, + aGKK_near, + aGKK_touch, + aGKK_bite, + aGKK_comeback, + }; + + gyo->action = action; + gyo->act_proc = act_proc[action]; + init_proc[action](gyo); +} + +static void aGKK_actor_move(ACTOR* actorx, GAME* game) { + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + + actorx->world.position.y = aGKK_Get_water_surface_position_y(actorx->world.position); + (*gyo->act_proc)(actorx, game); +} diff --git a/src/actor/ac_gyo_test.c b/src/actor/ac_gyo_test.c new file mode 100644 index 00000000..33709d91 --- /dev/null +++ b/src/actor/ac_gyo_test.c @@ -0,0 +1,872 @@ +#include "ac_gyoei.h" + +#include "m_common_data.h" +#include "m_player_lib.h" +#include "ac_uki.h" + +enum { + aGTT_ACTION_SWIM, + aGTT_ACTION_WAIT, + aGTT_ACTION_ESCAPE, + aGTT_ACTION_NEAR, + aGTT_ACTION_TOUCH, + aGTT_ACTION_BITE, + aGTT_ACTION_COMEBACK, + + aGTT_ACTION_NUM +}; + +static void aGTT_actor_move(ACTOR* actorx, GAME* game); +static void aGTT_setupAction(aGYO_CTRL_ACTOR* gyo, int action); +static void aGTT_set_scale(ACTOR* actorx, f32 scale); +static void aGTT_speed_reset(ACTOR* actorx); +static s16 aGTT_Get_flow_angle(ACTOR* actorx); +static s16 aGTT_Get_flow_angle_rv(ACTOR* actorx); +static void aGTT_set_angle(ACTOR* actorx, s16 angleY); +static f32 aGTT_Get_water_surface_position_y(xyz_t pos); + +#include "../src/actor/ac_gyoei_type.c_inc" + +static f32 aGTT_speed[8] = { + 1.0f, 1.25f, 1.25f, 1.5f, 1.75f, 2.0f, 2.2f, 2.2f, +}; + +static f32 aGTT_back_speed[8] = { + -0.38f, -0.4f, -0.42f, -0.42f, -0.45f, -0.5f, -0.7f, -0.7f, +}; + +static s16 aGTT_touch_count[8] = { + 19, 19, 19, 19, 20, 22, 24, 24, +}; + +static f32 aGTT_touch_distance[8] = { + 12.0f, 13.0f, 15.0f, 15.0f, 20.0f, 25.0f, 30.0f, 30.0f, +}; + +static f32 aGYO_shadow_scale[8] = { + 0.3f, 0.4f, 0.5f, 0.5f, 0.6f, 0.8f, 1.2f, 10.0f, +}; + +static f32 aGYO_search_area[][5] = { + {40.0f, 40.0f, 40.0f, 50.0f, 60.0f}, // regular rod + {40.0f, 40.0f, 40.0f, 50.0f, 60.0f}, // gold rod +}; + +static f32 aGYO_search_angle[][5] = { + {3.0f, 7.0f, 30.0f, 50.0f, 180.0f}, // regular rod + {7.5f, 15.0f, 40.0f, 60.0f, 180.0f}, // gold rod +}; + +static f32 aGYO_bite_time[][5] = { + {10.0f, 11.0f, 12.0f, 15.0f, 45.0f}, // regular rod + {11.0f, 12.0f, 13.0f, 18.0f, 60.0f}, // gold rod +}; + +extern void aGTT_actor_init(ACTOR* actorx, GAME* game) { + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + f32 scale; + u32 attr; + + attr = mCoBG_Wpos2BgAttribute_Original(actorx->world.position); + if (mCoBG_CheckWaterAttribute(attr) == TRUE) { + actorx->world.position.y = aGTT_Get_water_surface_position_y(actorx->world.position); + } + + gyo->size_type = gyoei_type[gyo->gyo_type].size; + gyo->action = aGTT_ACTION_SWIM; + gyo->fwork0 = 19.0f; + scale = aGYO_shadow_scale[gyo->size_type] * 0.02f; + aGTT_set_scale(actorx, scale); + aGTT_speed_reset(actorx); + actorx->mv_proc = aGTT_actor_move; + aGTT_set_angle(actorx, aGTT_Get_flow_angle_rv(actorx)); + actorx->shape_info.draw_shadow = FALSE; + aGTT_setupAction(gyo, aGTT_ACTION_WAIT); +} + +static int aGYO_get_uki_type(void) { + int ret = aGYO_ROD_NORMAL; + + if (Now_Private->equipment == ITM_GOLDEN_ROD) { + ret = aGYO_ROD_GOLDEN; + } + + return ret; +} + +static void aGTT_speed_reset(ACTOR* actorx) { + actorx->speed = 0.0f; + actorx->max_velocity_y = 0.0f; +} + +static void aGTT_set_scale(ACTOR* actorx, f32 scale) { + actorx->scale.x = scale; + actorx->scale.y = scale; + actorx->scale.z = scale; +} + +static void aGTT_set_angle(ACTOR* actorx, s16 angleY) { + actorx->world.angle.y = angleY; + actorx->shape_info.rotation.y = angleY; +} + +static xyz_t aGTT_pos_calc(xyz_t pos, GAME* game, s16 angleY, f32 dist, f32 rr) { + ACTOR* playerx = GET_PLAYER_ACTOR_GAME_ACTOR(game); + xyz_t ret; + f32 sin = sin_s(playerx->world.angle.y) * dist; + f32 cos = cos_s(playerx->world.angle.y) * dist; + f32 cos2 = cos_s(angleY) * -rr; + f32 sin2 = sin_s(angleY) * rr; + + ret.x = pos.x + sin + cos2; + ret.z = pos.z + cos + sin2; + return ret; +} + +static void aGTT_effect_sibuki(aGYO_CTRL_ACTOR* gyo, GAME* game, s16 arg) { + xyz_t pos = gyo->tools_class.actor_class.world.position; + f32 water_y = mCoBG_GetWaterHeight_File(pos, __FILE__, 334); + s16 flow_angle; + + switch (arg) { + case 4: + case 5: + case 6: + case 7: + case 8: { + static f32 rr[] = { 3.0f, 3.5f, 4.0f, 4.0f, 4.5f, 5.0f, 5.0f, 5.0f }; + + pos = aGTT_pos_calc(gyo->linked_actor->world.position, game, gyo->linked_actor->world.angle.y, 4.0f, rr[gyo->size_type]); + break; + } + } + + pos.y = water_y; + flow_angle = aGTT_Get_flow_angle((ACTOR*)gyo); + eEC_CLIP->effect_make_proc(eEC_EFFECT_TURI_MIZU, pos, 1, flow_angle, game, EMPTY_NO, arg, 0); +} + +static void aGTT_kage_make_actor(aGYO_CTRL_ACTOR* gyo, GAME* game, u8 state) { + GAME_PLAY* play = (GAME_PLAY*)game; + f32 height; + + if (state == 0) { + gyo->gyo_flags |= 0x20; + } + + height = aGTT_Get_water_surface_position_y(gyo->tools_class.actor_class.world.position); + Actor_info_make_actor( + // clang-format off + &play->actor_info, game, mAc_PROFILE_GYO_KAGE, + gyo->tools_class.actor_class.world.position.x, height, gyo->tools_class.actor_class.world.position.z, + 0, 0, 0, + play->block_table.block_x, play->block_table.block_z, + -1, EMPTY_NO, gyo->size_type, -1, ACTOR_OBJ_BANK_KEEP + // clang-format on + ); +} + +static void aGTT_fish_make_actor(aGYO_CTRL_ACTOR* gyo, GAME* game) { + GAME_PLAY* play = (GAME_PLAY*)game; + + // set uki actor's child actor to the fish actor being created + ((UKI_ACTOR*)gyo->linked_actor)->child_actor = Actor_info_make_actor( + // clang-format off + &play->actor_info, game, mAc_PROFILE_GYO_RELEASE, + gyo->tools_class.actor_class.world.position.x, gyo->tools_class.actor_class.world.position.y, gyo->tools_class.actor_class.world.position.z, + 0, gyo->swork4, 0, + play->block_table.block_x, play->block_table.block_z, + -1, EMPTY_NO, ITM_FISH_START + gyo->gyo_type, -1, ACTOR_OBJ_BANK_KEEP + // clang-format on + ); +} + +static void aGTT_effect_hamon(aGYO_CTRL_ACTOR* gyo, GAME* game, s16 arg) { + xyz_t pos = gyo->tools_class.actor_class.world.position; + f32 water_y = mCoBG_GetWaterHeight_File(gyo->tools_class.actor_class.world.position, __FILE__, 451); + s16 flow_angle = aGTT_Get_flow_angle((ACTOR*)gyo); + + pos.y = water_y; + eEC_CLIP->effect_make_proc(eEC_EFFECT_TURI_HAMON, pos, 1, flow_angle, game, EMPTY_NO, arg, 0); +} + +static u8 aGTT_random_check(f32 val) { + u8 ret = FALSE; + + if (RANDOM_F(val) < 1.0f) { + ret = TRUE; + } + + return ret; +} + +static int aGTT_chase_s_angle(aGYO_CTRL_ACTOR* gyo, s16 target, s16 step) { + s16 angle; + + chase_angle(&gyo->tools_class.actor_class.world.angle.y, target, step); + gyo->tools_class.actor_class.shape_info.rotation.y = gyo->tools_class.actor_class.world.angle.y; + angle = ABS((s16)(gyo->tools_class.actor_class.world.angle.y - target)); + if (angle < step) { + gyo->gyo_flags &= ~0x80; + } + + return angle; +} + +static int aGTT_warp_event(aGYO_CTRL_ACTOR* gyo) { + int ret = FALSE; + + if (mPlib_check_player_warp_forEvent()) { + ((UKI_ACTOR*)gyo->linked_actor)->gyo_command = 0; + aGTT_setupAction(gyo, aGTT_ACTION_ESCAPE); + ret = TRUE; + } + + return ret; +} + +static f32 aGTT_speed_calc(f32 speed, s16 angle) { + return speed * sin_s(angle); +} + +static void aGTT_position_calc(aGYO_CTRL_ACTOR* gyo) { + static f32 hosei[] = { + // clang-format off + -8.0f, + -10.0f, + -12.0f, + -12.0f, + -15.0f, + -20.0f, + -25.0f, + -25.0f, + // clang-format on + }; + + gyo->tools_class.actor_class.world.position.x += hosei[gyo->size_type] * sin_s(gyo->tools_class.actor_class.world.angle.y); + gyo->tools_class.actor_class.world.position.z += hosei[gyo->size_type] * cos_s(gyo->tools_class.actor_class.world.angle.y); +} + +static int aGTT_swim_speed_check(aGYO_CTRL_ACTOR* gyo, f32 target, f32 step, f32 speed) { + int ret = chase_f(&gyo->fwork3, target, step * 0.5f); + + gyo->tools_class.actor_class.speed = aGTT_speed_calc(speed, DEG2SHORT_ANGLE2(gyo->fwork3)); + return ret; +} + +static int aGTT_swim_speed_change(aGYO_CTRL_ACTOR* gyo, f32 target, f32 step, f32 speed) { + int ret = chase_f(&gyo->fwork3, target, step * 0.5f); + s16 angle = DEG2SHORT_ANGLE2(gyo->fwork3); + + if (gyo->fwork3 > step) { + gyo->tools_class.actor_class.shape_info.rotation.y += gyo->swork2; + } else if (gyo->fwork3 == step) { + gyo->swork2 = (s16)(gyo->swork2 - gyo->tools_class.actor_class.world.angle.y) / (s16)(target / step); + } + + gyo->tools_class.actor_class.speed = aGTT_speed_calc(speed, angle); + if (ret == TRUE) { + gyo->tools_class.actor_class.world.angle.y = gyo->tools_class.actor_class.shape_info.rotation.y; + } + + return ret; +} + +static s16 aGTT_Get_flow_angle(ACTOR* actorx) { + xyz_t flow; + + mCoBG_GetWaterFlow(&flow, actorx->bg_collision_check.result.unit_attribute); + return atans_table(flow.z, flow.x); +} + +static s16 aGTT_Get_flow_angle_rv(ACTOR* actorx) { + return aGTT_Get_flow_angle(actorx) + DEG2SHORT_ANGLE2(180.0f); +} + +static void aGTT_flow_direction(ACTOR* actorx) { + static s16 angl_add_table[] = { 0x100, 0x400 }; + s16 flow_rv = aGTT_Get_flow_angle_rv(actorx); + + chase_angle(&actorx->world.angle.y, flow_rv, angl_add_table[ABS((s16)(actorx->world.angle.y - flow_rv)) > 0x4000]); + actorx->shape_info.rotation.y = actorx->world.angle.y; +} + +static f32 aGTT_Get_water_surface_position_y(xyz_t pos) { + f32 ret = mCoBG_GetWaterHeight_File(pos, __FILE__, 699); + + return ret - 8.0f; +} + +static int aGTT_search_Uki(ACTOR* actorx, GAME* game) { + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + GAME_PLAY* play; + f32 target_dist; + f32 target_y; + f32 escape_dist; + s16 target_angle; + s16 goal_angle; + s16 search_area; + int ret; + + play = (GAME_PLAY*)game; + search_area = gyoei_type[gyo->gyo_type].search_area; + ret = FALSE; + { + UKI_ACTOR* uki = (UKI_ACTOR*)Actor_info_name_search(&play->actor_info, mAc_PROFILE_UKI, ACTOR_PART_BG); + gyo->linked_actor = (ACTOR*)uki; + if (uki != NULL) { + target_dist = search_position_distance(&actorx->world.position, &uki->actor_class.world.position); + target_y = actorx->world.position.y - uki->actor_class.world.position.y; + target_angle = search_position_angleY(&actorx->world.position, &uki->actor_class.world.position); + goal_angle = target_angle - actorx->shape_info.rotation.y; + + if (gyo->size_type == aGYO_SIZE_XXS || gyo->size_type == aGYO_SIZE_XS) { + escape_dist = 17.0f; + } else { + escape_dist = 22.0f; + } + + if (uki->hit_water_flag && target_dist < escape_dist) { + aGTT_set_angle(actorx, target_angle + DEG2SHORT_ANGLE2(180.0f)); + aGTT_setupAction(gyo, aGTT_ACTION_ESCAPE); + } else { + int rod_type = aGYO_get_uki_type(); + + if ((gyo->gyo_flags & 1) == 0 && uki->cast_timer == 0 && + target_dist < aGYO_search_area[rod_type][search_area] && fabsf(target_y) < 10.0f && + goal_angle > DEG2SHORT_ANGLE2(-aGYO_search_angle[rod_type][search_area]) && + goal_angle < DEG2SHORT_ANGLE2(aGYO_search_angle[rod_type][search_area])) { + ret = TRUE; + } + } + } + } + + return ret; +} + +static int aGTT_player_near(ACTOR* actorx, GAME* game) { + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + ACTOR* playerx = GET_PLAYER_ACTOR_GAME_ACTOR(game); + f32 dist; + s16 target_angle; + xyz_t pos; + int ret = FALSE; + + dist = search_position_distance(&actorx->world.position, &playerx->world.position); + target_angle = search_position_angleY(&actorx->world.position, &playerx->world.position); + + if ( + // clang-format off + ((dist < 110.0f && mPlib_get_player_actor_main_index(game) == mPlayer_INDEX_DASH) || + (dist < 150.0f && ( + mPlib_Check_HitAxe(&pos) || + mPlib_Check_StopNet(&pos) || + mPlib_Check_HitScoop(&pos) + ))) || + gyo->escape_flag + // clang-format on + ) { + aGTT_set_angle(actorx, target_angle + DEG2SHORT_ANGLE2(180.0f)); + switch (gyo->size_type) { + case aGYO_SIZE_XXS: + case aGYO_SIZE_XS: + case aGYO_SIZE_S: + case aGYO_SIZE_M: + aGTT_effect_hamon(gyo, game, 3); + break; + case aGYO_SIZE_L: + case aGYO_SIZE_XL: + case aGYO_SIZE_XXL: + aGTT_effect_hamon(gyo, game, 2); + break; + } + + aGTT_kage_make_actor(gyo, game, 0); + ret = TRUE; + } + + return ret; +} + +static int aGYO_check_wall(ACTOR* actorx) { + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + int ret = FALSE; + + if (actorx->bg_collision_check.result.hit_wall != mCoBG_DIDNT_HIT_WALL) { + int count = actorx->bg_collision_check.result.hit_wall_count; + + if (count != 0) { + int i; + + for (i = 0; i < count; i++) { + if (actorx->bg_collision_check.wall_info[i].type == mCoBG_WALL_TYPE0) { + s16 wall_angle = actorx->bg_collision_check.wall_info[i].angleY; + s16 check_angle = wall_angle - (actorx->world.angle.y + DEG2SHORT_ANGLE2(180.0f)); + + if (check_angle > 0) { + aGTT_set_angle(actorx, actorx->world.angle.y + DEG2SHORT_ANGLE2(90.0f)); + } else { + aGTT_set_angle(actorx, actorx->world.angle.y - DEG2SHORT_ANGLE2(90.0f)); + } + + gyo->gyo_flags |= 0x40; + ret = TRUE; + break; + } + } + } + } + + return ret; +} + +static int aGYO_check_bridge(aGYO_CTRL_ACTOR* gyo) { + int ret = FALSE; + + if ((gyo->gyo_flags & 0x100) == 0) { + switch (gyo->action) { + case aGTT_ACTION_SWIM: + case aGTT_ACTION_WAIT: + case aGTT_ACTION_NEAR: + if (((ACTOR*)gyo)->bg_collision_check.result.is_in_water) { + switch (mCoBG_Wpos2Attribute(((ACTOR*)gyo)->world.position, NULL)) { + case mCoBG_ATTRIBUTE_STONE: + case mCoBG_ATTRIBUTE_WOOD: + break; + default: + ret = TRUE; + break; + } + } else { + ret = TRUE; + } + break; + default: + ret = TRUE; + break; + } + } + + return ret; +} + +static int aGYO_check_fall(aGYO_CTRL_ACTOR* gyo) { + int ret = FALSE; + + if (gyo->action == aGTT_ACTION_BITE || gyo->action == aGTT_ACTION_COMEBACK) { + ret = TRUE; + } else if ((gyo->gyo_flags & 0x100) == 0) { + f32 now_y = ((ACTOR*)gyo)->world.position.y; + f32 last_y = ((ACTOR*)gyo)->last_world_position.y; + + if (((ACTOR*)gyo)->bg_collision_check.result.unit_attribute == mCoBG_ATTRIBUTE_WATERFALL && now_y < last_y) { + if (gyo->action == aGTT_ACTION_BITE || gyo->action == aGTT_ACTION_COMEBACK) { + ((UKI_ACTOR*)gyo->linked_actor)->gyo_command = 0; + } + } else { + ret = TRUE; + } + } + + return ret; +} + +static void aGTT_wait(ACTOR* actorx, GAME* game) { + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + f32 rnd; + + aGTT_flow_direction(actorx); + + if (DECREMENT_TIMER(gyo->work0) == 0) { + rnd = RANDOM_F(3.0f); + + if (rnd < 1.0f) { + gyo->swim_flag = 0; + } else if (rnd < 2.0f) { + gyo->swim_flag = 1; + } else { + gyo->swim_flag = 2; + } + + aGTT_setupAction(gyo, aGTT_ACTION_SWIM); + } else { + if (aGTT_player_near(actorx, game) == FALSE && aGTT_search_Uki(actorx, game) == TRUE) { + aGTT_setupAction(gyo, aGTT_ACTION_NEAR); + } + } +} + +static void aGTT_swim(ACTOR* actorx, GAME* game) { + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + int wait_flag = FALSE; + + if (aGYO_check_wall(actorx) == TRUE) { + aGTT_setupAction(gyo, aGTT_ACTION_ESCAPE); + } else { + switch (gyo->swim_flag) { + case 0: + wait_flag = aGTT_swim_speed_check(gyo, 360.0f, 5.0f, 0.5f); + break; + case 1: + wait_flag = aGTT_swim_speed_check(gyo, 180.0f, 5.0f, 0.5f); + if (wait_flag == TRUE) { + actorx->world.angle.y = actorx->shape_info.rotation.y; + } + break; + case 2: + if ((gyo->gyo_flags & 0x80) != 0) { + aGTT_chase_s_angle(gyo, gyo->swork3, 0x400); + } else { + wait_flag = aGTT_swim_speed_change(gyo, 180.0f, 5.0f, 1.0f); + } + break; + } + + if (wait_flag == TRUE) { + aGTT_setupAction(gyo, aGTT_ACTION_WAIT); + } else { + if (aGTT_player_near(actorx, game) == FALSE && aGTT_search_Uki(actorx, game) == TRUE) { + aGTT_setupAction(gyo, aGTT_ACTION_NEAR); + } + } + } +} + +static void aGTT_near(ACTOR* actorx, GAME* game) { + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + f32 target_dist; + f32 target_y; + s16 angle_y; + UKI_ACTOR* uki = (UKI_ACTOR*)gyo->linked_actor; + s16 search_area = gyoei_type[gyo->gyo_type].search_area; + int rod_type = aGYO_get_uki_type(); + + angle_y = search_position_angleY(&actorx->world.position, &uki->actor_class.world.position); + aGTT_set_angle(actorx, angle_y); + target_dist = search_position_distance(&actorx->world.position, &uki->actor_class.world.position); + target_y = actorx->world.position.y - uki->actor_class.world.position.y; + if (target_dist > aGYO_search_area[rod_type][search_area] || fabsf(target_y) > 10.0f) { + aGTT_setupAction(gyo, aGTT_ACTION_WAIT); + } else if (target_dist < aGTT_touch_distance[gyo->size_type]) { + if (uki->gyo_status == 1) { + uki->gyo_command = 1; + uki->gyo_type = gyo->gyo_type; + uki->child_actor = actorx; + uki->actor_class.world.angle.y = actorx->world.angle.y; + uki->actor_class.shape_info.rotation.y = actorx->shape_info.rotation.y; + aGTT_setupAction(gyo, aGTT_ACTION_TOUCH); + } else { + aGTT_setupAction(gyo, aGTT_ACTION_WAIT); + } + } +} + +static void aGTT_touch(ACTOR* actorx, GAME* game) { + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + UKI_ACTOR* uki; + + if (aGTT_warp_event(gyo)) { + return; + } + + uki = (UKI_ACTOR*)gyo->linked_actor; + uki->actor_class.world.angle.y = actorx->world.angle.y; + uki->actor_class.shape_info.rotation.y = actorx->shape_info.rotation.y; + + if (DECREMENT_TIMER(gyo->work0) == 0) { + f32 target_dist; + s16 angle; + + actorx->speed = aGTT_speed[gyo->size_type]; + angle = search_position_angleY(&actorx->world.position, &uki->actor_class.world.position); + aGTT_set_angle(actorx, angle); + target_dist = search_position_distance(&actorx->world.position, &uki->actor_class.world.position); + + if (target_dist < aGTT_touch_distance[gyo->size_type]) { + if ((aGTT_random_check(4.0f) != FALSE && (aGYO_check_fall(gyo) == TRUE)) || DECREMENT_TIMER(gyo->touch_counter) == 0) { + if (uki->gyo_status == 2) { + uki->gyo_command = 2; + + // If a free space exists, 1/20 chance of switching the fish out for trash + if (mPlib_Get_space_putin_item() >= 0 && aGTT_random_check(20.0f) != 0) { + static int gomi[] = { aGYO_TYPE_EMPTY_CAN, aGYO_TYPE_EMPTY_CAN, aGYO_TYPE_BOOT, aGYO_TYPE_BOOT, aGYO_TYPE_BOOT, aGYO_TYPE_OLD_TIRE, aGYO_TYPE_OLD_TIRE, aGYO_TYPE_OLD_TIRE }; + + gyo->gyo_type = gomi[gyo->size_type]; + uki->gyo_type = gyo->gyo_type; + } + + aGTT_setupAction(gyo, aGTT_ACTION_BITE); + } + } else { + uki->touched_flag = TRUE; + gyo->work0 = (int)((aGTT_touch_count[gyo->size_type] + RANDOM2_F(30.0f)) * 2.0f); + actorx->speed = aGTT_back_speed[gyo->size_type] + RANDOM2_F(0.2f); + } + } + } + + if (uki->status == 6) { + uki->gyo_command = 0; + aGTT_effect_sibuki(gyo, game, 0); + aGTT_kage_make_actor(gyo, game, 0); + } +} + +static void aGTT_bite(ACTOR* actorx, GAME* game) { + static s16 eff_arg[] = { 8, 7, 4, 4, 5, 6, 6, 6 }; + + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + UKI_ACTOR* uki = (UKI_ACTOR*)gyo->linked_actor; + f32 now_y = actorx->world.position.y; + f32 last_y = actorx->last_world_position.y; + + if (aGTT_warp_event(gyo)) { + return; + } + + if (uki->gyo_status == 4) { + if (uki->actor_class.bg_collision_check.result.unit_attribute == mCoBG_ATTRIBUTE_WATERFALL) { + actorx->world.position = uki->actor_class.world.position; + + if (now_y < actorx->world.position.y) { + actorx->world.position.y = now_y; + } + + sAdo_OngenPos((u32)actorx, NA_SE_24, &actorx->world.position); + } else { + static f32 rr[] = { 6.0f, 7.0f, 8.0f, 8.0f, 9.0f, 10.0f, 15.0f, 15.0f }; + xyz_t pos; + + pos = aGTT_pos_calc(uki->actor_class.world.position, game, uki->actor_class.world.angle.y, 8.0f, rr[gyo->size_type]); + actorx->world.position.x = pos.x; + actorx->world.position.z = pos.z; + if (now_y < last_y) { + actorx->world.position.y = last_y; + } + + aGTT_set_angle(actorx, uki->actor_class.world.angle.y); + sAdo_OngenPos((u32)actorx, NA_SE_24, &actorx->world.position); + CLIP(gyo_clip)->hitcheck_gyoei_proc(&actorx->world.position, gyo->size_type); + + if (DECREMENT_TIMER(gyo->swork0) == 0) { + if (aGTT_random_check(2.0f)) { + aGTT_effect_sibuki(gyo, game, eff_arg[gyo->size_type]); + } + + gyo->swork0 = 3; + } + } + } else { + f32 now_y = actorx->world.position.y; + f32 last_y = actorx->last_world_position.y; + + actorx->world.position.x = uki->actor_class.world.position.x; + actorx->world.position.z = uki->actor_class.world.position.z; + + if (uki->actor_class.bg_collision_check.result.unit_attribute == mCoBG_ATTRIBUTE_WATERFALL) { + actorx->world.position = uki->actor_class.world.position; + if (now_y < actorx->world.position.y) { + actorx->world.position.y = now_y; + } + } else { + if (now_y < last_y) { + actorx->world.position.y = last_y; + } + } + + aGTT_set_angle(actorx, uki->actor_class.world.angle.y); + aGTT_position_calc(gyo); + + if (uki->gyo_status == 5) { + actorx->speed = 0.0f; + aGTT_setupAction(gyo, aGTT_ACTION_COMEBACK); + } else if (DECREMENT_TIMER(gyo->work0) == 0) { + uki->gyo_command = 0; + aGTT_kage_make_actor(gyo, game, 0); + } + } + + uki->gyo_pos = actorx->world.position; +} + +static void aGTT_comeback(ACTOR* actorx, GAME* game) { + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + f32 scale; + UKI_ACTOR* uki = (UKI_ACTOR*)gyo->linked_actor; + + actorx->world.position = uki->uki_pos; + aGTT_set_angle(actorx, uki->actor_class.world.angle.y); + + if (uki->gyo_status == 3) { + aGTT_position_calc(gyo); + if (aGTT_random_check(10.0f)) { + aGTT_effect_hamon(gyo, game, 0); + } + } else if (uki->gyo_status == 5) { + ACTOR* playerx = GET_PLAYER_ACTOR_GAME_ACTOR(game); + + if ((gyo->gyo_flags & 4) == 0) { + switch (gyo->gyo_type) { + case aGYO_TYPE_EMPTY_CAN: + case aGYO_TYPE_BOOT: + case aGYO_TYPE_OLD_TIRE: + aGTT_kage_make_actor(gyo, game, 1); + break; + } + + gyo->swork4 = playerx->world.angle.y; + gyo->gyo_flags |= 8; + } + } else if (uki->gyo_status == 6) { + gyo->gyo_flags |= 0x200; + if (gyo->anim_frame == 1) { + sAdo_OngenTrgStart(NA_SE_11A, &actorx->world.position); + } + } else if (uki->gyo_status == 8) { + uki->gyo_status = 0; + aGTT_fish_make_actor(gyo, game); + gyo->gyo_flags |= 0x20; + } else if (uki->gyo_status == 7) { + gyo->gyo_flags |= 0x10; + } else if (uki->gyo_status == 0) { + uki->gyo_command = 0; + uki->child_actor = NULL; + gyo->gyo_flags &= ~2; + gyo->gyo_flags |= 0x20; + return; + } + + if ((gyo->gyo_flags & 0x10) != 0) { + scale = uki->gyo_scale / 100.0f; + } else if (gyo->draw_type == aGYO_DRAW_TYPE_FISH) { + scale = 0.01f; + } else { + scale = aGYO_shadow_scale[gyo->size_type] * 0.02f; + } + + aGTT_set_scale(actorx, scale); + uki->gyo_pos = actorx->world.position; +} + +static void aGTT_escape(ACTOR* actorx, GAME* game) { + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + + if (aGTT_player_near(actorx, game) == FALSE) { + if (aGTT_search_Uki(actorx, game) == TRUE) { + aGTT_setupAction(gyo, aGTT_ACTION_NEAR); + } else if ((gyo->gyo_flags & 0x40) != 0 || aGYO_check_wall(actorx) == FALSE) { + if (DECREMENT_TIMER(gyo->work0) == 0) { + gyo->gyo_flags &= ~0x40; + aGTT_setupAction(gyo, aGTT_ACTION_WAIT); + } else { + chase_f(&actorx->speed, 0.0f, 0.02f); + } + } + } +} + +static void aGTT_swim_init(aGYO_CTRL_ACTOR* gyo) { + switch (gyo->swim_flag) { + case 0: + gyo->fwork3 = 50.0f; + gyo->swork2 = 0; + gyo->swork3 = 0; + break; + case 1: + gyo->fwork3 = 0.0f; + gyo->swork2 = 0; + gyo->swork3 = RANDOM2_F(DEG2SHORT_ANGLE2(180.0f)); + gyo->tools_class.actor_class.world.angle.y += gyo->swork3; + break; + case 2: + gyo->gyo_flags |= 0x80; + gyo->fwork3 = 0.0f; + gyo->swork2 = aGTT_Get_flow_angle_rv((ACTOR*)gyo); + gyo->swork3 = ((ACTOR*)gyo)->world.angle.y + (s16)RANDOM2_F(DEG2SHORT_ANGLE2(90.0f)); + ((ACTOR*)gyo)->shape_info.rotation.y = ((ACTOR*)gyo)->world.angle.y; + break; + } + + aGTT_speed_reset((ACTOR*)gyo); +} + +static void aGTT_near_init(aGYO_CTRL_ACTOR* gyo) { + gyo->gyo_flags |= 2; + ((ACTOR*)gyo)->speed = aGTT_speed[gyo->size_type]; + ((ACTOR*)gyo)->max_velocity_y = 12.0f; +} + +static void aGTT_touch_init(aGYO_CTRL_ACTOR* gyo) { + gyo->work0 = 0; + gyo->range = 12.0f; + gyo->touch_counter = 5; + aGTT_speed_reset((ACTOR*)gyo); +} + +static void aGTT_bite_init(aGYO_CTRL_ACTOR* gyo) { + gyo->work0 = (int)(aGYO_bite_time[aGYO_get_uki_type()][gyoei_type[gyo->gyo_type].bite_time] * 2.0f); + gyo->swork0 = 3; + aGTT_speed_reset((ACTOR*)gyo); +} + +static void aGTT_comeback_init(aGYO_CTRL_ACTOR* gyo) { + gyo->swork2 = 1 + RANDOM(3); + gyo->swork3 = 15 + RANDOM2(10); + aGTT_speed_reset((ACTOR*)gyo); +} + +static void aGTT_wait_init(aGYO_CTRL_ACTOR* gyo) { + gyo->gyo_flags &= ~2; + gyo->work0 = (int)((100.0f + RANDOM_F(30.0f)) * 2.0f); + ((ACTOR*)gyo)->speed = -0.15f + RANDOM2_F(0.2f); + ((ACTOR*)gyo)->max_velocity_y = 0.0f; +} + +static void aGTT_escape_init(aGYO_CTRL_ACTOR* gyo) { + gyo->swork0 = 0; + gyo->work0 = 100; + ((ACTOR*)gyo)->speed = 2.0f; +} + +typedef void (*aGTT_INIT_PROC)(aGYO_CTRL_ACTOR* gyo); + +static void aGTT_setupAction(aGYO_CTRL_ACTOR* gyo, int action) { + static aGTT_INIT_PROC init_proc[] = { + aGTT_swim_init, + aGTT_wait_init, + aGTT_escape_init, + aGTT_near_init, + aGTT_touch_init, + aGTT_bite_init, + aGTT_comeback_init, + }; + + static aGYO_ACT_PROC act_proc[] = { + aGTT_swim, + aGTT_wait, + aGTT_escape, + aGTT_near, + aGTT_touch, + aGTT_bite, + aGTT_comeback, + }; + + gyo->action = action; + gyo->act_proc = act_proc[action]; + init_proc[action](gyo); +} + +static void aGTT_actor_move(ACTOR* actorx, GAME* game) { + aGYO_CTRL_ACTOR* gyo = (aGYO_CTRL_ACTOR*)actorx; + + actorx->world.position.y = aGTT_Get_water_surface_position_y(actorx->world.position); + if (aGYO_check_bridge(gyo) == TRUE && aGYO_check_fall(gyo) == TRUE) { + (*gyo->act_proc)(actorx, game); + } else if ((gyo->gyo_flags & 0x100) == 0) { + gyo->work0 = 60; + gyo->gyo_flags |= 0x100; + } else if (DECREMENT_TIMER(gyo->work0) == 0) { + gyo->gyo_flags |= 0x20; + } +} diff --git a/src/actor/ac_ins_hotaru.c b/src/actor/ac_ins_hotaru.c index 7caf1739..f75346a4 100644 --- a/src/actor/ac_ins_hotaru.c +++ b/src/actor/ac_ins_hotaru.c @@ -441,7 +441,7 @@ static void aIHT_setupAction(aINS_INSECT_ACTOR* insect, int action, GAME* game) static aINS_ACTION_PROC act_proc[] = { aIHT_avoid, - (aINS_ACTION_PROC)none_proc1, + aIHT_avoid, aIHT_fly, };