diff --git a/config/rel_slices.yml b/config/rel_slices.yml index dbeaecaa..f5ddddb0 100644 --- a/config/rel_slices.yml +++ b/config/rel_slices.yml @@ -516,6 +516,10 @@ m_random_field_ovl.c: .rodata: [0x80648ED0, 0x80648ED8] .data: [0x8069E918, 0x8069F070] .bss: [0x813010D0, 0x813013E0] +ac_fuusen.c: + .text: [0x8050DAC4, 0x8050F06C] + .rodata: [0x80648FE8, 0x80649098] + .data: [0x8069F190, 0x8069F320] m_mail_check_ovl.c: .text: [0x8050F06C, 0x8050F838] .data: [0x8069F320, 0x8069FA40] diff --git a/include/ac_fuusen.h b/include/ac_fuusen.h index 336c5979..62a752d5 100644 --- a/include/ac_fuusen.h +++ b/include/ac_fuusen.h @@ -3,11 +3,47 @@ #include "types.h" #include "m_actor.h" +#include "c_keyframe.h" #ifdef __cplusplus extern "C" { #endif +#define aFSN_ESCAPE_TIMER 1554 + +enum { + aFSN_ACTION_BIRTH, + aFSN_ACTION_MOVING, + aFSN_ACTION_WOOD_STOP, + aFSN_ACTION_ESCAPE, + + aFSN_ACTION_NUM +}; + +typedef struct fuusen_actor_s FUUSEN_ACTOR; + +struct fuusen_actor_s { + ACTOR actor_class; + int action; + mActor_proc action_proc; + char* segment_p; + int type_idx; + int escape_timer; + int timer; + int count; + int look_up_flag; + int wind_idx; + s16 fuwafuwa_cycle; + f32 wind_power; + f32 y_offset; + u8 wind_change_flag; + cKF_SkeletonInfo_R_c keyframe; + s_xyz work[5]; + s_xyz morph[5]; + int _254; + Mtx mtx[2][3]; +}; + extern ACTOR_PROFILE Fuusen_Profile; #ifdef __cplusplus diff --git a/include/bg_item_h.h b/include/bg_item_h.h index ed9ed9dc..2d315eab 100644 --- a/include/bg_item_h.h +++ b/include/bg_item_h.h @@ -19,10 +19,18 @@ typedef struct background_item_shadow_s { typedef void (*bIT_SHADOW_DRAW_PROC)(GAME*, bIT_ShadowData_c*, int); +typedef int (*bIT_FRUIT_SET_PROC)(mActor_name_t item, int ut_x, int ut_z, s16 p0, s16 p1); +typedef void (*bIT_ITEM_TREE_FRUIT_DROP_PROC)(mActor_name_t item, int ut_x, int ut_z, xyz_t* pos); + + typedef struct background_item_clip_s { void* _00; bIT_SHADOW_DRAW_PROC draw_shadow_proc; - void* _08[(0x54 - 0x08) / sizeof(void*)]; + void* _08[(0x30 - 0x08) / sizeof(void*)]; + bIT_FRUIT_SET_PROC fruit_set_proc; + void* _34; + bIT_ITEM_TREE_FRUIT_DROP_PROC item_tree_fruit_drop_proc; + void* _3C[(0x54 - 0x3C) / sizeof(void*)]; } bIT_Clip_c; #ifdef __cplusplus diff --git a/include/m_actor.h b/include/m_actor.h index 8c3ae3a4..afea886d 100644 --- a/include/m_actor.h +++ b/include/m_actor.h @@ -439,7 +439,7 @@ typedef enum bank_id { ACTOR_OBJ_BANK_397, ACTOR_OBJ_BANK_398, ACTOR_OBJ_BANK_399, - ACTOR_OBJ_BANK_400, + ACTOR_OBJ_BANK_FUUSEN, ACTOR_OBJ_BANK_401, ACTOR_OBJ_BANK_402, ACTOR_OBJ_BANK_403, diff --git a/include/m_collision_bg.h b/include/m_collision_bg.h index 731ad911..a1a55192 100644 --- a/include/m_collision_bg.h +++ b/include/m_collision_bg.h @@ -259,6 +259,7 @@ extern int mCoBG_Attr2CheckPlaceNpc(u32 attribute); extern int mCoBG_ExistHeightGap_KeepAndNow(xyz_t wpos); extern void mCoBG_GetNorm_By3Point(xyz_t* norm, xyz_t* p0, xyz_t* p1, xyz_t* p2); extern int mCoBG_SearchWaterLimitDistN(xyz_t* water_pos, xyz_t wpos, s16 angle, float max_dist, int divisor); +extern f32 mCoBG_GetBalloonGroundY(const xyz_t* pos); extern void mCoBG_InitMoveBgData(); extern void mCoBG_InitBlockBgCheckMode(); diff --git a/include/m_debug_display.h b/include/m_debug_display.h index e35f10e5..c2f1708f 100644 --- a/include/m_debug_display.h +++ b/include/m_debug_display.h @@ -15,8 +15,7 @@ struct debug_display_s{ Debug_display* next; }; extern void Debug_Display_init(); -extern Debug_display* Debug_Display_new(f32 posX, f32 posY, f32 posZ, f32 scaleX, f32 scaleY, f32 scaleZ, -s16 rotX, s16 rotY, s16 rotZ, s8 r, s8 g, s8 b, s8 alpha, s16 type, GRAPH* graph); +extern Debug_display* Debug_Display_new(f32 posX, f32 posY, f32 posZ, f32 scaleX, f32 scaleY, f32 scaleZ, s16 rotX, s16 rotY, s16 rotZ, u8 r, u8 g, u8 b, u8 alpha, s16 type, GRAPH* graph); extern void Debug_Display_output(GAME_PLAY* play); diff --git a/include/m_player_lib.h b/include/m_player_lib.h index e946c6d3..0adb78ac 100644 --- a/include/m_player_lib.h +++ b/include/m_player_lib.h @@ -46,6 +46,8 @@ extern int mPlib_able_submenu_type1(GAME* game); extern void mPlib_request_main_demo_wait_from_submenu(ACTOR* force_speak_label); extern void mPlib_Load_PlayerTexAndPallet(void* tex_p, void* pal_p, int idx); extern void mPlib_request_main_give_from_submenu(mActor_name_t disp_item, int submenu_ovl, int present_flag, int counter_flag); +extern int mPlib_Check_tree_shaken_big(const xyz_t* pos); +extern int mPlib_Check_tree_shaken_little(const xyz_t* pos); extern mPlayer_change_data_from_submenu_c* mPlib_Get_change_data_from_submenu_p(); diff --git a/src/ac_fuusen.c b/src/ac_fuusen.c new file mode 100644 index 00000000..832817c8 --- /dev/null +++ b/src/ac_fuusen.c @@ -0,0 +1,595 @@ +#include "ac_fuusen.h" + +#include "m_actor_shadow.h" +#include "m_common_data.h" +#include "m_debug_display.h" +#include "m_fuusen.h" +#include "m_play.h" +#include "m_player_lib.h" +#include "m_rcp.h" +#include "sys_matrix.h" +#include "zurumode.h" + +static void aFSN_actor_ct(ACTOR* actorx, GAME* game); +static void aFSN_actor_dt(ACTOR* actorx, GAME* game); +static void aFSN_actor_move(ACTOR* actorx, GAME* game); +static void aFSN_actor_draw(ACTOR* actorx, GAME* game); + +ACTOR_PROFILE Fuusen_Profile = { + mAc_PROFILE_FUUSEN, + ACTOR_PART_CONTROL, + ACTOR_STATE_NO_MOVE_WHILE_CULLED, + EMPTY_NO, + ACTOR_OBJ_BANK_FUUSEN, + sizeof(FUUSEN_ACTOR), + &aFSN_actor_ct, + &aFSN_actor_dt, + &aFSN_actor_move, + &aFSN_actor_draw, + NULL +}; + +static void aFSN_setupAction(FUUSEN_ACTOR* fuusen_actor, GAME* game, int action); + +extern cKF_Skeleton_R_c cKF_bs_r_act_balloon; +extern cKF_Animation_R_c cKF_ba_r_act_balloon; + +static void aFSN_actor_ct(ACTOR* actorx, GAME* game) { + FUUSEN_ACTOR* fuusen = (FUUSEN_ACTOR*)actorx; + cKF_SkeletonInfo_R_c* keyframe_p = &fuusen->keyframe; + static xyz_t Init_Size = { 0.01f, 0.01f, 0.01f }; + f32 balloon_ground_y = mCoBG_GetBalloonGroundY(&actorx->world.position); + + fuusen->escape_timer = 2000; + cKF_SkeletonInfo_R_ct(keyframe_p, &cKF_bs_r_act_balloon, &cKF_ba_r_act_balloon, fuusen->work, fuusen->morph); + cKF_SkeletonInfo_R_init_standard_repeat(keyframe_p, &cKF_ba_r_act_balloon, NULL); + Shape_Info_init(actorx, 0.0f, &mAc_ActorShadowCircle, 10.0f, 10.0f); + actorx->shape_info.draw_shadow = FALSE; + cKF_SkeletonInfo_R_play(keyframe_p); + keyframe_p->frame_control.speed = 1.0f; + xyz_t_move(&actorx->scale, &Init_Size); + actorx->world.position.y = balloon_ground_y + 200.0f; + fuusen->y_offset = 110.0f; + fuusen->segment_p = ((GAME_PLAY*)game)->object_exchange.banks[actorx->data_bank_id].ram_start; + aFSN_setupAction(fuusen, game, aFSN_ACTION_BIRTH); +} + +static void aFSN_actor_dt(ACTOR* actorx, GAME* game) { + FUUSEN_ACTOR* fuusen = (FUUSEN_ACTOR*)actorx; + + if (fuusen->look_up_flag == TRUE) { + Balloon_look_up(); + } + else { + Balloon_kill(); + } +} + +static void aFSN_birth(ACTOR* actorx, GAME* game) { + FUUSEN_ACTOR* fuusen = (FUUSEN_ACTOR*)actorx; + + aFSN_setupAction(fuusen, game, aFSN_ACTION_MOVING); +} + +static void aFSN_moving(ACTOR* actorx, GAME* game) { + FUUSEN_ACTOR* fuusen = (FUUSEN_ACTOR*)actorx; + GAME_PLAY* play = (GAME_PLAY*)game; + f32 balloon_y = mCoBG_GetBalloonGroundY(&actorx->world.position) + fuusen->y_offset; + xyz_t screen_pos; + mActor_name_t* fg_item_p; + + if (fuusen->escape_timer > 0) { + fuusen->escape_timer--; + } + else { + /* Escape if flown to the edge of the map */ + if ( + (actorx->world.position.x <= 660.0f || actorx->world.position.x >= 3820.0f) || + (actorx->world.position.z <= 660.0f || actorx->world.position.z >= 4460.0f) + ) { + fuusen->escape_timer = aFSN_ESCAPE_TIMER; + aFSN_setupAction(fuusen, game, aFSN_ACTION_ESCAPE); + return; + } + + /* Escape if flown into the train station acre? */ + if ( + (actorx->world.position.x <= 2440.0f && actorx->world.position.x >= 2040.0f) && + (actorx->world.position.z <= 960.0f && actorx->world.position.z >= 800.0f) + ) { + fuusen->escape_timer = aFSN_ESCAPE_TIMER; + aFSN_setupAction(fuusen, game, aFSN_ACTION_ESCAPE); + return; + } + } + + fuusen->wind_power = mEnv_GetWindPowerF(); + actorx->speed = fuusen->wind_power * 0.5f + 1.0f; + fuusen->fuwafuwa_cycle += 250; + add_calc( + &actorx->world.position.y, + balloon_y + sin_s(fuusen->fuwafuwa_cycle) * 10.0f, + 1.0f - sqrtf(0.7f), + 0.5f, 0.0f + ); + + Game_play_Projection_Trans(play, &actorx->world.position, &screen_pos); + + if (-40.0f > screen_pos.x || screen_pos.x > 360.0f || -40.0f > screen_pos.y || screen_pos.y > 280.0f) { + return; + } + + mCoBG_BgCheckControll(NULL, actorx, 12.0f, 0.0f, 0, 0, 0); + + if ( + (actorx->bg_collision_check.result.hit_wall & mCoBG_HIT_WALL) || + (actorx->bg_collision_check.result.hit_attribute_wall & mCoBG_HIT_WALL) + ) { + fuusen->y_offset += 0.05f; // raise quickly over walls + + if (fuusen->y_offset >= 300.0f) { + fuusen->y_offset = 300.0f; + } + } + else if (fuusen->y_offset > 110.0f) { + fuusen->y_offset -= 0.005f; // slowly fall + } + + fg_item_p = mFI_GetUnitFG(actorx->world.position); + + if ( + fg_item_p != NULL && + ( + *fg_item_p == TREE || + *fg_item_p == TREE_APPLE_NOFRUIT_0 || + *fg_item_p == TREE_APPLE_NOFRUIT_1 || + *fg_item_p == TREE_APPLE_NOFRUIT_2 || + *fg_item_p == TREE_ORANGE_NOFRUIT_0 || + *fg_item_p == TREE_ORANGE_NOFRUIT_1 || + *fg_item_p == TREE_ORANGE_NOFRUIT_2 || + *fg_item_p == TREE_PEACH_NOFRUIT_0 || + *fg_item_p == TREE_PEACH_NOFRUIT_1 || + *fg_item_p == TREE_PEACH_NOFRUIT_2 || + *fg_item_p == TREE_PEAR_NOFRUIT_0 || + *fg_item_p == TREE_PEAR_NOFRUIT_1 || + *fg_item_p == TREE_PEAR_NOFRUIT_2 || + *fg_item_p == TREE_CHERRY_NOFRUIT_0 || + *fg_item_p == TREE_CHERRY_NOFRUIT_1 || + *fg_item_p == TREE_CHERRY_NOFRUIT_2 || + *fg_item_p == TREE_PALM_NOFRUIT_0 || + *fg_item_p == TREE_PALM_NOFRUIT_1 || + *fg_item_p == TREE_PALM_NOFRUIT_2 || + *fg_item_p == CEDAR_TREE || + *fg_item_p == GOLD_TREE + ) + ) { + f32 dx; + f32 dy; + f32 dz; + + mFI_Wpos2UtCenterWpos(&screen_pos, actorx->world.position); + + if (*fg_item_p == CEDAR_TREE) { + screen_pos.y = mCoBG_GetBgY_OnlyCenter_FromWpos2(actorx->world.position, 0.0f) + 100.0f; + screen_pos.z += 20.0f; + } + else { + screen_pos.x -= 2.5f; + screen_pos.y = mCoBG_GetBgY_OnlyCenter_FromWpos2(actorx->world.position, 0.0f) + 97.5f; + screen_pos.z += 7.5f; + } + + dx = screen_pos.x - actorx->world.position.x; + dy = screen_pos.y - actorx->world.position.y; + dz = screen_pos.z - actorx->world.position.z; + + if (*fg_item_p == CEDAR_TREE) { + if (dx * dx + dz * dz < 484.0f && dy * dy < 225.0f) { + aFSN_setupAction(fuusen, game, aFSN_ACTION_WOOD_STOP); + } + } + else if (dx * dx + dz * dz < 225.0f && dy * dy < 225.0f) { + aFSN_setupAction(fuusen, game, aFSN_ACTION_WOOD_STOP); + } + } + else { + if (fuusen->wind_change_flag == FALSE) { + static int senkou_check_data[] = { -2500, 0, 2500 }; + s16 new_angle; + int i; + + for (i = 0; i < ARRAY_COUNT(senkou_check_data); i++) { + xyz_t_move(&screen_pos, &actorx->world.position); + new_angle = (int)actorx->world.angle.y + (s16)senkou_check_data[i]; + screen_pos.x += 80.0f * sin_s(new_angle); + screen_pos.z += 80.0f * cos_s(new_angle); + + fg_item_p = mFI_GetUnitFG(screen_pos); + if ( + fg_item_p != NULL && + ( + *fg_item_p == TREE || + *fg_item_p == TREE_APPLE_NOFRUIT_0 || + *fg_item_p == TREE_APPLE_NOFRUIT_1 || + *fg_item_p == TREE_APPLE_NOFRUIT_2 || + *fg_item_p == TREE_ORANGE_NOFRUIT_0 || + *fg_item_p == TREE_ORANGE_NOFRUIT_1 || + *fg_item_p == TREE_ORANGE_NOFRUIT_2 || + *fg_item_p == TREE_PEACH_NOFRUIT_0 || + *fg_item_p == TREE_PEACH_NOFRUIT_1 || + *fg_item_p == TREE_PEACH_NOFRUIT_2 || + *fg_item_p == TREE_PEAR_NOFRUIT_0 || + *fg_item_p == TREE_PEAR_NOFRUIT_1 || + *fg_item_p == TREE_PEAR_NOFRUIT_2 || + *fg_item_p == TREE_CHERRY_NOFRUIT_0 || + *fg_item_p == TREE_CHERRY_NOFRUIT_1 || + *fg_item_p == TREE_CHERRY_NOFRUIT_2 || + *fg_item_p == TREE_PALM_NOFRUIT_0 || + *fg_item_p == TREE_PALM_NOFRUIT_1 || + *fg_item_p == TREE_PALM_NOFRUIT_2 || + *fg_item_p == CEDAR_TREE || + *fg_item_p == GOLD_TREE + ) + ) { + actorx->world.angle.y = new_angle; + fuusen->wind_change_flag = TRUE; + break; + } + } + } + + if (fuusen->wind_change_flag == FALSE) { + actorx->world.angle.y = mEnv_GetWindAngleS(); + } + } +} + +static void aFSN_wood_stop(ACTOR* actorx, GAME* game) { + FUUSEN_ACTOR* fuusen = (FUUSEN_ACTOR*)actorx; + + fuusen->escape_timer--; + + if (fuusen->escape_timer <= aFSN_ESCAPE_TIMER) { + fuusen->escape_timer = aFSN_ESCAPE_TIMER; + aFSN_setupAction(fuusen, game, aFSN_ACTION_ESCAPE); + } + else { + xyz_t pos; + xyz_t pos2; + mActor_name_t* fg_item_p; + f32 dx; + f32 dz; + + mFI_Wpos2UtCenterWpos(&pos, actorx->world.position); + xyz_t_move(&pos2, &pos); + fg_item_p = mFI_GetUnitFG(actorx->world.position); + + if (fg_item_p != NULL && *fg_item_p == CEDAR_TREE) { + pos.y = mCoBG_GetBgY_OnlyCenter_FromWpos2(actorx->world.position, 0.0f) + 100.0f; + pos.z += 20.0f; + } + else { + pos.x -= 2.5f; + pos.y = mCoBG_GetBgY_OnlyCenter_FromWpos2(actorx->world.position, 0.0f) + 97.5f; + pos.z += 7.5f; + } + + dx = actorx->world.position.x - pos.x; + dz = actorx->world.position.z - pos.z; + + if (sqrtf(dx * dx + dz * dz) > 2.0f) { + add_calc(&actorx->world.position.x, pos.x, 1.0f - sqrtf(0.7f), 0.5f, 0.0f); + add_calc(&actorx->world.position.y, pos.y, 1.0f - sqrtf(0.7f), 0.5f, 0.0f); + add_calc(&actorx->world.position.z, pos.z, 1.0f - sqrtf(0.7f), 0.5f, 0.0f); + } + else if (mPlib_Check_tree_shaken_big(&pos2)) { + actorx->shape_info.rotation.z = 0; + aFSN_setupAction(fuusen, game, aFSN_ACTION_ESCAPE); + } + else if (mPlib_Check_tree_shaken_little(&pos2)) { + if ((fuusen->count & 4) == 0) { + actorx->shape_info.rotation.z = 500; + } + else { + actorx->shape_info.rotation.z = -500; + actorx->shape_info.rotation.z = 0; // set again??? + } + + fuusen->count++; + } + else { + actorx->shape_info.rotation.z = 0; + fuusen->count = 0; + } + } +} + +static void aFSN_escape(ACTOR* actorx, GAME* game) { + FUUSEN_ACTOR* fuusen = (FUUSEN_ACTOR*)actorx; + f32 balloon_y = mCoBG_GetBalloonGroundY(&actorx->world.position); + + if (fuusen->count == 0) { + xyz_t pos; + + mFI_Wpos2UtCenterWpos(&pos, actorx->world.position); + if (Common_Get(clip).bg_item_clip != NULL && Common_Get(clip).bg_item_clip->item_tree_fruit_drop_proc != NULL) { + int ut_x; + int ut_z; + + if (mFI_Wpos2UtNum(&ut_x, &ut_z, pos)) { + (*Common_Get(clip).bg_item_clip->fruit_set_proc)(ITM_PRESENT, ut_x, ut_z, 1, 1); + fuusen->count = 1; + } + } + } + + if (fuusen->count == 1 && actorx->world.position.y > balloon_y + 500.0f) { + Actor_delete(actorx); + } +} + +static void aFSN_birth_init(FUUSEN_ACTOR* fuusen, GAME* game) { + static int data_index_data[16] = { + 0, 1, 1, 2, + 2, 3, 3, 4, + 4, 5, 5, 6, + 6, 7, 7, 0 + }; + + static xyz_t birth_pos_data[8] = { + { 1600.0f, 100.0f, 500.0f }, /* X:2.5, Z:0.8 - Acre Q-2 (above A-2) */ + { 500.0f, 100.0f, 500.0f }, /* X:0.8, Z:0.8 - Acre Q-0 (top-left corner acre) */ + { 500.0f, 100.0f, 1600.0f }, /* X:0.8, Z:2.5 - Acre B-0 (left of B-1) */ + { 500.0f, 100.0f, 4620.0f }, /* X:0.8, Z:7.2 - Acre G-0 (bottom-left corner acre)*/ + { 1600.0f, 100.0f, 4620.0f }, /* X:2.5, Z:7.2 - Acre G-2 (below F-2)*/ + { 3980.0f, 100.0f, 4620.0f }, /* X:6.2, Z:7.2 - Acre F-6 (bottom-right corner) */ + { 3980.0f, 100.0f, 1600.0f }, /* X:6.2, Z:2.5 - Acre B-6 (right of B-5) */ + { 3980.0f, 100.0f, 500.0f } /* X:6.2, Z:0.8 - Acre Q-6 (top-right corner)*/ + }; + + static xyz_t birth_pos_random_data[8] = { + { 1280.0f, 100.0f, 0.0f }, + { 960.0f, 100.0f, 960.0f }, + { 0.0f, 100.0f, 1920.0f }, + { 960.0f, 100.0f, -960.0f }, + { 1280.0f, 100.0f, 0.0f }, + { -960.0f, 100.0f, -960.0f }, + { 0.0f, 100.0f, 1920.0f }, + { -960.0f, 100.0f, 960.0f } + }; + + GAME_PLAY* play = (GAME_PLAY*)game; + f32 balloon_y = mCoBG_GetBalloonGroundY(&fuusen->actor_class.world.position); + int randomize_z; + + fuusen->actor_class.world.angle.y = mEnv_GetWindAngleS(); + fuusen->wind_idx = ((s16)(fuusen->actor_class.world.angle.y & 0xF000)) >> 12; + fuusen->wind_idx &= 0xF; + fuusen->wind_idx = data_index_data[fuusen->wind_idx]; + + xyz_t_move(&fuusen->actor_class.world.position, &birth_pos_data[fuusen->wind_idx]); + randomize_z = FALSE; + fuusen->actor_class.world.position.y = balloon_y + 200.0f; + fuusen->type_idx = play->game_frame % 5; + fuusen->timer = 10; + + if (birth_pos_random_data[fuusen->wind_idx].x != 0.0f && birth_pos_random_data[fuusen->wind_idx].z != 0.0f) { + randomize_z = play->game_frame & 1; // randomly choose between X & Z + } + else if (birth_pos_random_data[fuusen->wind_idx].z != 0.0f) { + randomize_z = TRUE; // only Z can be randomized + } + + if (randomize_z == FALSE) { + /* Adjust X position */ + if (birth_pos_random_data[fuusen->wind_idx].x != 0.0f) { + f32 rng = fqrand() * fabsf(birth_pos_random_data[fuusen->wind_idx].x); + + if (birth_pos_random_data[fuusen->wind_idx].x > 0.0f) { + fuusen->actor_class.world.position.x += rng; + } + else { + fuusen->actor_class.world.position.x -= rng; + } + } + } + else { + /* Adjust Z position */ + if (birth_pos_random_data[fuusen->wind_idx].z != 0.0f) { + f32 rng = fqrand() * fabsf(birth_pos_random_data[fuusen->wind_idx].z); + + if (birth_pos_random_data[fuusen->wind_idx].z > 0.0f) { + fuusen->actor_class.world.position.z += rng; + } + else { + fuusen->actor_class.world.position.z -= rng; + } + } + } +} + +static void aFSN_moving_init(FUUSEN_ACTOR* fuusen, GAME* game) { + fuusen->wind_change_flag = FALSE; +} + +static void aFSN_wood_stop_init(FUUSEN_ACTOR* fuusen, GAME* game) { + fuusen->actor_class.speed = 0.0f; + fuusen->escape_timer = 18000 + aFSN_ESCAPE_TIMER; + sAdo_OngenTrgStart(0x402, &fuusen->actor_class.world.position); +} + +static void aFSN_escape_init(FUUSEN_ACTOR* fuusen, GAME* game) { + PLAYER_ACTOR* player = GET_PLAYER_ACTOR((GAME_PLAY*)game); + ACTOR* actorx = (ACTOR*)fuusen; + f32 dx = player->actor_class.world.position.x - actorx->world.position.x; + f32 dz = player->actor_class.world.position.z - actorx->world.position.z; + + fuusen->count = 0; + actorx->max_velocity_y = 5.0f; + actorx->gravity = 0.5f; + fuusen->look_up_flag = FALSE; + + if (fuusen->escape_timer == aFSN_ESCAPE_TIMER) { + fuusen->count = 1; + + /* If the balloon is within 1 acre of distance to the player when it flies away, the 'look up flag' is set */ + if (sqrtf(dx * dx + dz * dz) < (mFI_UNIT_BASE_SIZE_F * UT_BASE_NUM)) { + fuusen->look_up_flag = TRUE; + } + } +} + +typedef void (*aFSN_INIT_PROC)(FUUSEN_ACTOR*, GAME*); + +static void aFSN_setupAction(FUUSEN_ACTOR* fuusen, GAME* game, int action) { + static aFSN_INIT_PROC init_proc[aFSN_ACTION_NUM] = { + &aFSN_birth_init, + &aFSN_moving_init, + &aFSN_wood_stop_init, + &aFSN_escape_init + }; + + static mActor_proc act_proc[aFSN_ACTION_NUM] = { + &aFSN_birth, + &aFSN_moving, + &aFSN_wood_stop, + &aFSN_escape + }; + + fuusen->action = action; + fuusen->action_proc = act_proc[action]; + (*init_proc[action])(fuusen, game); +} + +static void aFSN_actor_move(ACTOR* actorx, GAME* game) { + FUUSEN_ACTOR* fuusen = (FUUSEN_ACTOR*)actorx; + cKF_SkeletonInfo_R_c* keyframe_p = &fuusen->keyframe; + + if (fuusen->timer == 0) { + Actor_position_moveF(actorx); + } + else if (fuusen->timer > 0) { + fuusen->timer--; + } + + if (fuusen_DEBUG_mode_flag != FALSE && zurumode_flag != 0) { + PLAYER_ACTOR* player = GET_PLAYER_ACTOR((GAME_PLAY*)game); + int rot_y = (int)actorx->player_angle_y - (s16)-0x8000; + + Debug_Display_new( + player->actor_class.world.position.x + sin_s(actorx->player_angle_y - (s16)-0x8000) * 30.0f, + player->actor_class.world.position.y + 60.0f, + player->actor_class.world.position.z + cos_s(actorx->player_angle_y - (s16)-0x8000) * 30.0f, + 1.0f, 1.0f, 1.0f, + 0, rot_y, 0, + 250, 100, 120, 128, + 4, + game->graph + ); + } + + cKF_SkeletonInfo_R_play(keyframe_p); + (*fuusen->action_proc)(actorx, game); +} + +static int aFSN_actor_draw_before(GAME* game, cKF_SkeletonInfo_R_c* keyframe, int joint_num, Gfx** gfx_pp, u8* data_p, void* arg, s_xyz* joint_p, xyz_t* pos_p) { + static rgba_t balloon_prim_data[] = { + { 255, 210, 200, 255 }, + { 200, 230, 200, 255 }, + { 255, 250, 200, 255 }, + { 220, 255, 200, 255 }, + { 240, 210, 255, 255 } + }; + + static rgba_t balloon_env_data[] = { + { 255, 40, 0, 255 }, + { 0, 180, 255, 255 }, + { 255, 200, 0, 255 }, + { 100, 255, 0, 255 }, + { 200, 30, 255, 255 } + }; + + FUUSEN_ACTOR* fuusen = (FUUSEN_ACTOR*)arg; + GRAPH* graph = game->graph; + Gfx* gfx; + + if (joint_num == 3) { + OPEN_DISP(graph); + gfx = NOW_POLY_OPA_DISP; + + gDPSetPrimColor(gfx++, 0, 255, balloon_prim_data[fuusen->type_idx].r, balloon_prim_data[fuusen->type_idx].g, balloon_prim_data[fuusen->type_idx].b, balloon_prim_data[fuusen->type_idx].a); + gDPSetEnvColor(gfx++, balloon_env_data[fuusen->type_idx].r, balloon_env_data[fuusen->type_idx].g, balloon_env_data[fuusen->type_idx].b, balloon_env_data[fuusen->type_idx].a); + + SET_POLY_OPA_DISP(gfx); + CLOSE_DISP(graph); + } + + OPEN_DISP(graph); + gfx = NOW_POLY_OPA_DISP; + + if (joint_num != 3) { + gDPSetTexEdgeAlpha(gfx++, 80); + } + else { + gDPSetTexEdgeAlpha(gfx++, 144); + } + + SET_POLY_OPA_DISP(gfx); + CLOSE_DISP(graph); + + return TRUE; +} + +extern Gfx present_DL_mode[]; +extern Gfx present_DL_vtx[]; + +static void aFSN_actor_draw(ACTOR* actorx, GAME* game) { + static xyz_t offset0 = { 0.0f, 0.0f, 0.0f }; + + FUUSEN_ACTOR* fuusen = (FUUSEN_ACTOR*)actorx; + Mtx* mtx = fuusen->mtx[game->frame_counter & 1]; + GAME_PLAY* play = (GAME_PLAY*)game; + GRAPH* graph = game->graph; + Gfx* gfx; + + if ( + Camera2_CheckCullingMode() == FALSE || + Camera2_CheckEnterCullingArea(actorx->world.position.x, actorx->world.position.z, 60.0f) == FALSE + ) { + Matrix_push(); + + if (fuusen->action != aFSN_ACTION_ESCAPE || fuusen->escape_timer == aFSN_ESCAPE_TIMER || (fuusen->action == aFSN_ACTION_ESCAPE && fuusen->count == 0)) { + /* Draw present */ + Matrix_translate(actorx->world.position.x, actorx->world.position.y, actorx->world.position.z, 0); + Matrix_scale(0.01f, 0.01f, 0.01f, 1); + Matrix_RotateX(actorx->shape_info.rotation.x, 1); + Matrix_RotateZ(actorx->shape_info.rotation.z, 1); + Matrix_RotateY(actorx->shape_info.rotation.y, 1); + _texture_z_light_fog_prim(graph); + + OPEN_DISP(graph); + gfx = NOW_POLY_OPA_DISP; + + gSPMatrix(gfx++, _Matrix_to_Mtx_new(graph), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(gfx++, present_DL_mode); + gSPDisplayList(gfx++, present_DL_vtx); + + SET_POLY_OPA_DISP(gfx); + CLOSE_DISP(graph); + } + + Setpos_HiliteReflect_init(&actorx->world.position, (GAME_PLAY*)game); + cKF_Si3_draw_R_SV(game, &fuusen->keyframe, mtx, &aFSN_actor_draw_before, NULL, actorx); + + OPEN_DISP(graph); + gfx = NOW_POLY_OPA_DISP; + + gDPSetTexEdgeAlpha(gfx++, 144); + + SET_POLY_OPA_DISP(gfx); + CLOSE_DISP(graph); + + mAc_ActorShadowDraw_ShadowDrawFlagOn(actorx, play, 0, offset0, 170.0f); + Matrix_pull(); + } +}