From 0fafbe687f36913a02330c977dab053b313fc255 Mon Sep 17 00:00:00 2001 From: Cuyler36 Date: Fri, 6 Jun 2025 17:43:48 -0400 Subject: [PATCH] Link ac_snowman --- configure.py | 2 +- include/ac_gyoei_h.h | 7 + include/ac_museum_fish_priv.h | 2 - include/ac_snowman.h | 16 +- include/audio_defs.h | 2 + include/m_lib.h | 4 + include/m_snowman.h | 9 + src/actor/ac_snowman.c | 564 ++++++++++++++++++++++++++++++++-- 8 files changed, 573 insertions(+), 33 deletions(-) diff --git a/configure.py b/configure.py index eb2cf7b4..880ab3e1 100644 --- a/configure.py +++ b/configure.py @@ -1064,7 +1064,7 @@ config.libs = [ Object(Matching, "actor/ac_shop_umbrella.c"), Object(Matching, "actor/ac_shrine.c"), Object(Matching, "actor/ac_sign.c"), - Object(NonMatching, "actor/ac_snowman.c"), + Object(Matching, "actor/ac_snowman.c"), Object(Matching, "actor/ac_station.c"), Object(Matching, "actor/ac_structure.c"), Object(Matching, "actor/ac_super.c"), diff --git a/include/ac_gyoei_h.h b/include/ac_gyoei_h.h index 6c49ce7b..4e665793 100644 --- a/include/ac_gyoei_h.h +++ b/include/ac_gyoei_h.h @@ -9,6 +9,13 @@ extern "C" { #endif +enum { + aGYO_BALLCHECK_TYPE_NORMAL, + aGYO_BALLCHECK_TYPE_SNOWMAN, + + aGYO_BALLCHECK_TYPE_NUM +}; + /* sizeof(aGYO_Init_c) == 0x18 */ typedef struct gyoei_init_s { /* 0x00 */ int fish_type; diff --git a/include/ac_museum_fish_priv.h b/include/ac_museum_fish_priv.h index ac5801ef..37f08ed9 100644 --- a/include/ac_museum_fish_priv.h +++ b/include/ac_museum_fish_priv.h @@ -29,8 +29,6 @@ enum { #define MY_CLAMP(v, l, h) MIN(MY_MAX((l), (v)), (h)) #define ARRAY_LEN(a) (sizeof(a) / sizeof(*a)) -#define CALC_EASE(x) (1.0f - sqrtf(1.0f - (x))) -#define CALC_EASE2(x) CALC_EASE(CALC_EASE(x)) #define SHORT2DEG_ANGLE2(x) ((x) * (360.0f / 65536.0f)) #define RANDOMF_RANGE(min, max) ((f32)(min) + (f32)RANDOM_F((f32)(max) - (f32)(min))) #define RANDOM2F_RANGE(min, max) ((f32)(min) + (f32)RANDOM2_F((f32)(max) - (f32)(min))) diff --git a/include/ac_snowman.h b/include/ac_snowman.h index 6b15f450..322cc474 100644 --- a/include/ac_snowman.h +++ b/include/ac_snowman.h @@ -3,6 +3,7 @@ #include "types.h" #include "m_actor.h" +#include "m_snowman.h" #ifdef __cplusplus extern "C" { @@ -10,6 +11,15 @@ extern "C" { #define aSMAN_MOVE_DIST_MAX (6400.0f) +#define aSMAN_FLAG_HEAD_JUMP (1 << 0) +#define aSMAN_FLAG_ON_GROUND (1 << 1) +#define aSMAN_FLAG_MOVED (1 << 2) +#define aSMAN_FLAG_IN_HOLE (1 << 3) +#define aSMAN_FLAG_4 (1 << 4) // unused? +#define aSMAN_FLAG_COMBINED (1 << 5) +#define aSMAN_FLAG_NO_SPEAK (1 << 6) +#define aSMAN_FLAG_DELETE (1 << 7) + typedef int (*aSMAN_PROC)(ACTOR* actorx, GAME* game); typedef struct snowman_actor_s { @@ -17,18 +27,18 @@ typedef struct snowman_actor_s { /* 0x174 */ ClObjPipe_c col_pipe; /* 0x190 */ aSMAN_PROC process; /* 0x194 */ ACTOR* col_actor; - /* 0x198 */ xyz_t _198; + /* 0x198 */ xyz_t combine_dist; /* 0x1A4 */ xyz_t rev_pos; /* 0x1B0 */ xyz_t fg_pos; /* 0x1BC */ f32 y_ofs; - /* 0x1C0 */ f32 _1C0; + /* 0x1C0 */ f32 fall_height; /* 0x1C4 */ f32 base_speed; /* 0x1C8 */ f32 accel; /* 0x1CC */ f32 roll_speed; /* 0x1D0 */ f32 normalized_scale; /* normalized ball scale, [0, 1.0f] */ /* 0x1D4 */ f32 body_scale; /* 0x1D8 */ f32 move_dist; /* [0, 6400.0f] */ - /* 0x1DC */ f32 _1DC; + /* 0x1DC */ f32 impact_speed; /* 0x1E0 */ int result; /* 0x1E4 */ int msg_no; /* 0x1E8 */ int snowman_part; diff --git a/include/audio_defs.h b/include/audio_defs.h index 1bc1de73..56c71367 100644 --- a/include/audio_defs.h +++ b/include/audio_defs.h @@ -156,6 +156,8 @@ typedef enum audio_sound_effects { NA_SE_KA_BUZZ = 0xCF, + NA_SE_104 = 0x104, + NA_SE_107 = 0x107, NA_SE_108 = 0x108, NA_SE_ROD_STROKE = 0x109, diff --git a/include/m_lib.h b/include/m_lib.h index 1ccdfab8..6606eeb6 100644 --- a/include/m_lib.h +++ b/include/m_lib.h @@ -25,6 +25,10 @@ extern "C" { #define M_CLAMP(v, l, h) MAX(MIN((v), (l)), (h)) #endif +// Easing macros +#define CALC_EASE(x) (1.0f - sqrtf(1.0f - (x))) +#define CALC_EASE2(x) CALC_EASE(CALC_EASE(x)) + /* Float modulo operator */ #define MOD_F(a, m) (a - (int)((a) * (1.0f / (m))) * (m)) diff --git a/include/m_snowman.h b/include/m_snowman.h index 5469f043..a5dd7899 100644 --- a/include/m_snowman.h +++ b/include/m_snowman.h @@ -11,6 +11,15 @@ extern "C" { #define mSN_SAVE_COUNT 3 +enum { + mSN_RESULT_PERFECT, + mSN_RESULT_GOOD, + mSN_RESULT_OK, + mSN_RESULT_BAD, + + mSN_RESULT_NUM +}; + /* sizeof(mSN_snowman_data_c) == 4 */ typedef struct snowman_data_s { /* 0x00 */ u8 exists; /* when non-zero, snowman 'exists' */ diff --git a/src/actor/ac_snowman.c b/src/actor/ac_snowman.c index c4a7bcc8..1d151f3f 100644 --- a/src/actor/ac_snowman.c +++ b/src/actor/ac_snowman.c @@ -61,6 +61,9 @@ static void aSMAN_process_combine_body_init(ACTOR* actorx); static void aSMAN_process_player_push_init(ACTOR* actorx, GAME* game); static void aSMAN_process_air_init(ACTOR* actorx); static void aSMAN_process_hole_init(ACTOR* actorx); +static void aSMAN_process_player_push_scroll_init(ACTOR* actorx); +static void aSMAN_process_swim_init(ACTOR* actorx); +static void aSMAN_process_combine_head_init(ACTOR* actorx); static int aSMAN_process_normal(ACTOR* actorx, GAME* game); static int aSMAN_process_player_push(ACTOR* actorx, GAME* game); @@ -69,6 +72,8 @@ static int aSMAN_process_combine_body(ACTOR* actorx, GAME* game); static int aSMAN_process_combine_head_jump(ACTOR* actorx, GAME* game); static int aSMAN_process_swim(ACTOR* actorx, GAME* game); static int aSMAN_process_air(ACTOR* actorx, GAME* game); +static int aSMAN_process_hole(ACTOR* actorx, GAME* game); +static int aSMAN_process_combine_head(ACTOR* actorx, GAME* game); static void aSMAN_actor_ct(ACTOR* actorx, GAME* game) { static int part_tbl[] = { aSMAN_PART0, aSMAN_PART1 }; @@ -122,7 +127,7 @@ static void aSNOWMAN_Set_PSnowman_info(SNOWMAN_ACTOR* actor) { sman_info.data.body_size = actor->body_scale * 255; sman_info.data.score = actor->result; mCoBG_SetPlussOffset(actor->actor_class.world.position, 0, mCoBG_ATTRIBUTE_NONE); - if ((actor->flags & 0x80) == 0) { + if ((actor->flags & aSMAN_FLAG_DELETE) == 0) { mSN_regist_snowman_society(&sman_info); } } @@ -132,8 +137,8 @@ static void aSMAN_actor_dt(ACTOR* actorx, GAME* game) { ClObjPipe_dt(game, &actor->col_pipe); if (actor->snowman_part == aSMAN_PART0) { - if (actor->flags & 0x20) { - if (actor->flags & 1) { + if (actor->flags & aSMAN_FLAG_COMBINED) { + if (actor->flags & aSMAN_FLAG_HEAD_JUMP) { if (actor->fg_pos.x != 0.0f || actor->fg_pos.z != 0.0f) { aSNOWMAN_Set_PSnowman_info(actor); } else { @@ -144,7 +149,7 @@ static void aSMAN_actor_dt(ACTOR* actorx, GAME* game) { mEv_clear_common_place(mEv_EVENT_SNOWMAN_SEASON, 100 + aSMAN_PART0); mEv_clear_common_area(mEv_EVENT_SNOWMAN_SEASON, aSMAN_PART0); } else { - if ((actor->flags & 8) || (actor->flags & 4) || !mRlib_Set_Position_Check(actorx)) { + if ((actor->flags & aSMAN_FLAG_IN_HOLE) || (actor->flags & aSMAN_FLAG_MOVED) || !mRlib_Set_Position_Check(actorx)) { mEv_clear_common_place(mEv_EVENT_SNOWMAN_SEASON, 100 + aSMAN_PART0); mEv_clear_common_area(mEv_EVENT_SNOWMAN_SEASON, aSMAN_PART0); } else { @@ -156,8 +161,8 @@ static void aSMAN_actor_dt(ACTOR* actorx, GAME* game) { } } } else { - if (actor->flags & 0x20) { - if (actor->flags & 1) { + if (actor->flags & aSMAN_FLAG_COMBINED) { + if (actor->flags & aSMAN_FLAG_HEAD_JUMP) { if (actor->fg_pos.x != 0.0f || actor->fg_pos.z != 0.0f) { aSNOWMAN_Set_PSnowman_info(actor); } else { @@ -168,7 +173,7 @@ static void aSMAN_actor_dt(ACTOR* actorx, GAME* game) { mEv_clear_common_place(mEv_EVENT_SNOWMAN_SEASON, 100 + aSMAN_PART1); mEv_clear_common_area(mEv_EVENT_SNOWMAN_SEASON, aSMAN_PART1); } else { - if ((actor->flags & 8) || (actor->flags & 4) || !mRlib_Set_Position_Check(actorx)) { + if ((actor->flags & aSMAN_FLAG_IN_HOLE) || (actor->flags & aSMAN_FLAG_MOVED) || !mRlib_Set_Position_Check(actorx)) { mEv_clear_common_place(mEv_EVENT_SNOWMAN_SEASON, 100 + aSMAN_PART1); mEv_clear_common_area(mEv_EVENT_SNOWMAN_SEASON, aSMAN_PART1); } else { @@ -301,7 +306,7 @@ static void aSMAN_House_Tree_Rev_Check(ACTOR* actorx) { SNOWMAN_ACTOR* actor = (SNOWMAN_ACTOR*)actorx; if (mRlib_HeightGapCheck_And_ReversePos(actorx) != TRUE) { - actor->flags |= 4; + actor->flags |= aSMAN_FLAG_MOVED; Actor_delete(actorx); } } @@ -365,7 +370,7 @@ static int aSMAN_get_ground_norm(ACTOR* actorx, xyz_t* norm, xyz_t* pos) { if (mRlib_Get_ground_norm_inHole(actorx, norm, &hole_dist, &angle, &norm_rate, 1.0f - actor->normalized_scale * 0.5f)) { aSMAN_get_hole_offset(actorx, hole_dist); - if ((actor->flags & 8) == 0) { + if ((actor->flags & aSMAN_FLAG_IN_HOLE) == 0) { s16 angle_mod; f32 cos = cos_s(angle); f32 sin = sin_s(angle); @@ -388,7 +393,7 @@ static int aSMAN_get_ground_norm(ACTOR* actorx, xyz_t* norm, xyz_t* pos) { norm->y = sin_s(norm_rate); if (hole_dist < 1.0f) { if (ABS(actorx->position_speed.x) < 1.0f && ABS(actorx->position_speed.z) < 1.0f) { - actor->flags |= 8; + actor->flags |= aSMAN_FLAG_IN_HOLE; actorx->state_bitfield &= ~ACTOR_STATE_NO_MOVE_WHILE_CULLED; actorx->status_data.weight = MASSTYPE_HEAVY; actorx->speed = 0.0f; @@ -503,18 +508,18 @@ static void aSMAN_MakeBreakEffect(SNOWMAN_ACTOR* actor, GAME* game) { static int aSMAN_status_check_in_move(SNOWMAN_ACTOR* actor, GAME* game) { int ret = FALSE; - if ((actor->flags & 2) != 0 && (actor->flags & 0x20) == 0) { - actor->flags |= 4; + if ((actor->flags & aSMAN_FLAG_ON_GROUND) != 0 && (actor->flags & aSMAN_FLAG_COMBINED) == 0) { + actor->flags |= aSMAN_FLAG_MOVED; aSMAN_MakeBreakEffect(actor, game); Actor_delete((ACTOR*)actor); ret = TRUE; } - if (actor->flags & 0x20) { + if (actor->flags & aSMAN_FLAG_COMBINED) { ret = TRUE; } - if (actor->flags & 8) { + if (actor->flags & aSMAN_FLAG_IN_HOLE) { ret = TRUE; } @@ -531,7 +536,7 @@ static void aSMAN_position_move(ACTOR* actorx) { mCoBG_GetBgY_AngleS_FromWpos(&actor->ground_angle, actorx->world.position, 0.0f); chase_f(&actorx->speed, actor->base_speed, actor->accel); - if ((actor->flags & 0x20) != 0 || (actor->flags & 8) != 0 || + if ((actor->flags & aSMAN_FLAG_COMBINED) != 0 || (actor->flags & aSMAN_FLAG_IN_HOLE) != 0 || actor->process == aSMAN_process_combine_body || actor->process == aSMAN_process_player_push_scroll) { return; } @@ -544,7 +549,7 @@ static int aSMAN_BGcheck(ACTOR* actorx, GAME* game) { SNOWMAN_ACTOR* actor = (SNOWMAN_ACTOR*)actorx; f32 r = actor->normalized_scale * 20.0f + 10.0f; - if ((actor->flags & 0x20) != 0 || actor->process == aSMAN_process_combine_head_jump || actor->process == aSMAN_process_combine_body) { + if ((actor->flags & aSMAN_FLAG_COMBINED) != 0 || actor->process == aSMAN_process_combine_head_jump || actor->process == aSMAN_process_combine_body) { return FALSE; } @@ -558,8 +563,8 @@ static int aSMAN_BGcheck(ACTOR* actorx, GAME* game) { if (actorx->world.position.y - actorx->last_world_position.y > 60.0f) { actorx->world.position = actorx->last_world_position; - actor->flags |= 0x4; - actor->flags |= 0x2; + actor->flags |= aSMAN_FLAG_MOVED; + actor->flags |= aSMAN_FLAG_ON_GROUND; return FALSE; } } @@ -571,8 +576,8 @@ static int aSMAN_BGcheck(ACTOR* actorx, GAME* game) { f32 speed = actorx->speed * sin_s(colAngle); if (ABS(speed) > 5.0f) { - actor->flags |= 0x4; - actor->flags |= 0x2; + actor->flags |= aSMAN_FLAG_MOVED; + actor->flags |= aSMAN_FLAG_ON_GROUND; return FALSE; } @@ -625,7 +630,7 @@ static int aSMAN_snowman_hit_check(SNOWMAN_ACTOR* actor, GAME* game, ACTOR* oc_a attr = mCoBG_Wpos2Attribute(center_pos, NULL); mFI_Wpos2UtNum_inBlock(&b_ux, &b_uz, center_pos); - if (attr == mCoBG_ATTRIBUTE_STONE || attr == mCoBG_ATTRIBUTE_WOOD || mCoBG_Wpos2CheckSlateCol(¢er_pos, FALSE) || b_ux == 0 || b_ux == (UT_X_NUM - 1) || b_uz == 0 || b_uz == (UT_Z_NUM - 1) || (oc_actor->flags & 0x8) != 0 || oc_actor->ground_angle.x != 0 || oc_actor->ground_angle.z != 0 || !aSNOWMAN_player_block_check((ACTOR*)actor, game)) { + if (attr == mCoBG_ATTRIBUTE_STONE || attr == mCoBG_ATTRIBUTE_WOOD || mCoBG_Wpos2CheckSlateCol(¢er_pos, FALSE) || b_ux == 0 || b_ux == (UT_X_NUM - 1) || b_uz == 0 || b_uz == (UT_Z_NUM - 1) || (oc_actor->flags & aSMAN_FLAG_IN_HOLE) != 0 || oc_actor->ground_angle.x != 0 || oc_actor->ground_angle.z != 0 || !aSNOWMAN_player_block_check((ACTOR*)actor, game)) { return FALSE; } @@ -657,7 +662,7 @@ static int aSMAN_snowman_hit_check(SNOWMAN_ACTOR* actor, GAME* game, ACTOR* oc_a } static void aSMAN_OBJcheck(SNOWMAN_ACTOR* actor, GAME* game) { - if ((actor->flags & 0x20) != 0) { + if ((actor->flags & aSMAN_FLAG_COMBINED) != 0) { return; } @@ -797,10 +802,10 @@ static void aSMAN_calc_objChkRange(ACTOR* actorx) { actor->col_pipe.attribute.pipe.height = (actor->normalized_scale * 15.0f + 5.0f) * 2.5f; actor->col_pipe.attribute.pipe.offset = -(actor->normalized_scale * 15.0f + 5.0f); - if ((actor->flags & 0x8) != 0) { + if ((actor->flags & aSMAN_FLAG_IN_HOLE) != 0) { actor->col_pipe.attribute.pipe.radius = 20; actorx->status_data.weight = MASSTYPE_HEAVY; - } else if (actor->process == aSMAN_process_combine_body && (actor->flags & 0x20) == 0) { + } else if (actor->process == aSMAN_process_combine_body && (actor->flags & aSMAN_FLAG_COMBINED) == 0) { actor->col_pipe.attribute.pipe.radius = 20; } else { actor->col_pipe.attribute.pipe.radius = actor->normalized_scale * 15.0f + 5.0f; @@ -823,7 +828,7 @@ static int aSMAN_Player_push_Request(ACTOR* actorx, GAME* game) { return TRUE; } - if ((actor->flags & 0x8) != 0) { + if ((actor->flags & aSMAN_FLAG_IN_HOLE) != 0) { aSMAN_process_hole_init(actorx); return FALSE; } @@ -893,5 +898,510 @@ static void aSMAN_process_player_push_init(ACTOR* actorx, GAME* game) { actor->process = aSMAN_process_player_push; } -static void aSMAN_actor_move(ACTOR* actorx, GAME* game); -static void aSMAN_actor_draw(ACTOR* actorx, GAME* game); +static int aSMAN_process_player_push(ACTOR* actorx, GAME* game) { + SNOWMAN_ACTOR* actor = (SNOWMAN_ACTOR*)actorx; + ACTOR* playerx = GET_PLAYER_ACTOR_GAME_ACTOR(game); + ACTOR* oc_actorx = aSMAN_get_oc_actor(actor); + s16 player_angle = search_position_angleY(&playerx->world.position, &actorx->world.position); + s16 move_angle = gamePT->mcon.move_angle + DEG2SHORT_ANGLE2(90.0f); + s16 diff_angle = DIFF_SHORT_ANGLE(move_angle, player_angle); + f32 move_pR = gamePT->mcon.move_pR; + + add_calc0(&actor->y_ofs, 1.0f - sqrtf(0.5f), 50.0f); + add_calc(&actor->accel, 0.1f, 1.0f - sqrtf(0.8f), 0.005f, 0.0005f); + + if (oc_actorx != NULL && aSMAN_snowman_hit_check(actor, game, oc_actorx) == TRUE) { + return FALSE; + } + + if (mPlib_Check_Label_main_wade_snowball(actorx)) { + aSMAN_process_player_push_scroll_init(actorx); + } else if (!mPlib_Check_Label_main_push_snowball(actorx)) { + aSMAN_process_normal_init(actorx); + return FALSE; + } else { + xyz_t norm; + s16 norm_angle_diff = aSMAN_get_norm_push_angle_distance(actorx, &norm, player_angle); + + { + if ((ABS(diff_angle) > DEG2SHORT_ANGLE2(55.0f) || actor->normalized_scale <= 0.2f || aSMAN_player_push_HitWallCheck(actorx, game)) || (((!F32_IS_ZERO(norm.x) || !F32_IS_ZERO(norm.z)) && ABS(norm_angle_diff) < DEG2SHORT_ANGLE2(120.0f)) || move_pR == 0.0f)) { + aSMAN_player_push_free(game); + aSMAN_process_normal_init(actorx); + return FALSE; + } else { + xyz_t aim_pos = playerx->world.position; + f32 dist; + f32 speed; + s16 player_angle_y; + + speed = (actor->normalized_scale * 20.0f + 10.0f) + 10.0f; + player_angle_y = playerx->shape_info.rotation.y; + actor->base_speed = move_pR * (3.0f - actor->normalized_scale * 1.5f) * cos_s(diff_angle); + if (mPlib_CheckButtonOnly_forDush()) { + actor->base_speed *= 1.5f; + } + actorx->world.angle.y = move_angle; + mRlib_spdF_Angle_to_spdXZ(&actorx->position_speed, &actorx->speed, &actorx->world.angle.y); + aSMAN_set_speed_relations_norm_player_push(actorx, game); + mRlib_spdF_Angle_to_spdXZ(&aim_pos, &speed, &player_angle); + xyz_t_sub(&actorx->world.position, &aim_pos, &aim_pos); + aim_pos.y = mCoBG_GetBgY_AngleS_FromWpos(NULL, aim_pos, 0.0f); + + add_calc_short_angle2(&player_angle_y, player_angle, CALC_EASE2(0.5f), 455, 45); + dist = search_position_distanceXZ(&actorx->world.position, &actorx->last_world_position); + dist *= 0.4f; + mPlib_SetParam_for_push_snowball(&aim_pos, player_angle_y, dist < 1.0f ? dist : 1.0f); + + mCoBG_BgCheckControll(NULL, playerx, 18.0f, 0.0f, mCoBG_WALL_TYPE1, mCoBG_REVERSE_TYPE_REVERSE, mCoBG_CHECK_TYPE_PLAYER); + if ((actorx->bg_collision_check.result.hit_wall & mCoBG_HIT_WALL) != 0) { + s16 wall_angle = mRlib_Get_HitWallAngleY(actorx); + s16 wall_angle_diff = DIFF_SHORT_ANGLE(wall_angle - actorx->world.angle.y, DEG2SHORT_ANGLE(-180.0f)); + + if (wall_angle_diff < DEG2SHORT_ANGLE2(20.0f) && move_pR * cos_s(wall_angle_diff) > 0.7f) { + actor->timer++; + if (actor->timer > 120) { + actor->flags |= aSMAN_FLAG_MOVED; + actor->flags |= aSMAN_FLAG_ON_GROUND; + } + } else { + actor->timer = 0; + } + } else { + actor->timer = 0; + } + + aSMAN_Make_Effect_Ground(actorx, game); + if (actorx->speed > 1.0f) { + sAdo_OngenPos((u32)actorx, 52, &actorx->world.position); + } + aSMAN_player_push_scroll_request(actorx, game); + } + } + } + + if (!actorx->bg_collision_check.result.on_ground) { + aSMAN_process_air_init(actorx); + aSMAN_player_push_free(game); + } + + actor->roll_speed = actorx->speed; + aSMAN_calc_scale(actorx); + return TRUE; +} + +static void aSMAN_process_player_push_scroll_init(ACTOR* actorx) { + SNOWMAN_ACTOR* actor = (SNOWMAN_ACTOR*)actorx; + + actor->process = aSMAN_process_player_push_scroll; +} + +static int aSMAN_process_player_push_scroll(ACTOR* actorx, GAME* game) { + SNOWMAN_ACTOR* actor = (SNOWMAN_ACTOR*)actorx; + + if (!mPlib_Check_Label_main_wade_snowball(actorx)) { + aSMAN_process_player_push_init(actorx, game); + } else { + xyz_t pos; + f32 rev_x = actor->rev_pos.x; + f32 rev_z = actor->rev_pos.z; + f32 rev_xz = sqrtf(SQ(rev_x) + SQ(rev_z)); + + mPlib_GetSnowballPos_forWadeSnowball(&pos); + actorx->world.position = pos; + if ((actor->flags & aSMAN_FLAG_ON_GROUND) != 0 || rev_xz > (actor->normalized_scale * 20.0f + 10.0f) * 0.5f) { + actor->flags |= aSMAN_FLAG_MOVED; + actor->flags |= aSMAN_FLAG_ON_GROUND; + mPlib_Set_crash_snowball_for_wade(TRUE); + return FALSE; + } + } + + return TRUE; +} + +static void aSMAN_process_swim_init(ACTOR* actorx) { + SNOWMAN_ACTOR* actor = (SNOWMAN_ACTOR*)actorx; + + actorx->shape_info.draw_shadow = FALSE; + actor->timer = 0; + actor->flags |= aSMAN_FLAG_MOVED; + actor->process = aSMAN_process_swim; +} + +static int aSMAN_process_swim(ACTOR* actorx, GAME* game) { + SNOWMAN_ACTOR* actor = (SNOWMAN_ACTOR*)actorx; + + aSMAN_set_speed_relations_swim(actorx); + if (actorx->scale.x < 0.001f) { + Actor_delete(actorx); + return FALSE; + } + + xyz_t_mult_v(&actorx->scale, 0.999f); + actor->normalized_scale = (actorx->scale.x - 0.01f) / 0.02f; + if (actorx->scale.x >= 0.01f) { + if (CLIP(gyo_clip) != NULL) { + CLIP(gyo_clip)->ballcheck_gyoei_proc(&actorx->world.position, (actorx->scale.x * 30.0f) / 0.03f, aGYO_BALLCHECK_TYPE_SNOWMAN); + } + } + + if (!actorx->bg_collision_check.result.is_in_water && actorx->bg_collision_check.result.unit_attribute == mCoBG_ATTRIBUTE_WATERFALL) { + aSMAN_process_air_init(actorx); + } + + if (actor->timer < 32) { + if (((game->frame_counter & 3) == 0 && actor->timer < 16) || (game->frame_counter & 7) == 0) { + xyz_t pos = actorx->world.position; + pos.y = mCoBG_GetWaterHeight_File(pos, __FILE__, 1855); + eEC_CLIP->effect_make_proc(eEC_EFFECT_TURI_HAMON, pos, 1, actorx->world.angle.y, game, actorx->npc_id, 5, (s16)(actor->normalized_scale * 100.0f)); + } + + actor->timer++; + } + + return TRUE; +} + +static void aSMAN_process_air_init(ACTOR* actorx) { + SNOWMAN_ACTOR* actor = (SNOWMAN_ACTOR*)actorx; + f32 bg_y = mCoBG_GetBgY_AngleS_FromWpos(NULL, actorx->world.position, 0.0f); + f32 min_speed = (actor->normalized_scale * 20.0f + 10.0f) * 0.1f; + + if (actorx->world.position.y - bg_y > 20.0f) { + sAdo_OngenTrgStart(0x43D, &actorx->world.position); + } + + actor->fall_height = actorx->world.position.y; + actorx->gravity = 0.8f; + actorx->max_velocity_y = -30.0f; + if (actorx->speed < min_speed) { + actorx->speed = min_speed; + } + actorx->position_speed.y = 3.2f; + actor->process = aSMAN_process_air; +} + +static int aSMAN_process_air(ACTOR* actorx, GAME* game) { + SNOWMAN_ACTOR* actor = (SNOWMAN_ACTOR*)actorx; + + add_calc0(&actor->y_ofs, CALC_EASE(0.5f), 50.0f); + if (actorx->bg_collision_check.result.hit_wall & mCoBG_HIT_WALL) { + s16 wall_angle = mRlib_Get_HitWallAngleY(actorx); + + add_calc_short_angle2(&actorx->world.angle.y, wall_angle, CALC_EASE2(0.2f), DEG2SHORT_ANGLE2(22.5f), 45); + add_calc(&actorx->speed, 3.0f, CALC_EASE(0.5f), 0.25f, 0.05f); + } + + if (actorx->bg_collision_check.result.is_in_water) { + xyz_t pos = actorx->world.position; + + pos.y = mCoBG_GetWaterHeight_File(pos, __FILE__, 1934); + eEC_CLIP->effect_make_proc(eEC_EFFECT_AMI_MIZU, pos, 1, 0, game, actorx->npc_id, 2, (s16)(actor->normalized_scale * 100.0f)); + if (CLIP(gyo_clip) != NULL) { + CLIP(gyo_clip)->ballcheck_gyoei_proc(&actorx->world.position, (actorx->scale.x * 30.0f) / 0.03f, aGYO_BALLCHECK_TYPE_NORMAL); + } + + sAdo_OngenTrgStart(NA_SE_27, &actorx->world.position); + aSMAN_process_swim_init(actorx); + } else if (actorx->bg_collision_check.result.on_ground) { + if (actor->fall_height - actorx->world.position.y >= 55.0f || mCoBG_ExistHeightGap_KeepAndNow_Detail(actorx->world.position)) { + actor->flags |= aSMAN_FLAG_MOVED; + actor->flags |= aSMAN_FLAG_ON_GROUND; + } else { + aSMAN_process_normal_init(actorx); + } + } + + return TRUE; +} + +static void aSMAN_process_hole_init(ACTOR* actorx) { + SNOWMAN_ACTOR* actor = (SNOWMAN_ACTOR*)actorx; + + actorx->gravity = 0.0f; + actorx->speed = 0.0f; + actor->roll_speed = 0.0f; + actor->accel = 0.1f; + actor->base_speed = 0.0f; + actor->process = aSMAN_process_hole; +} + +static int aSMAN_process_hole(ACTOR* actorx, GAME* game) { + return TRUE; +} + +static int aSMAN_decide_scale_result(f32 head_scale, f32 body_scale) { + f32 ratio = head_scale / body_scale; + f32 adjusted_ratio = ABS(ratio - 0.85f); + int result; + + if (adjusted_ratio <= 0.05f) { // [0%, 5%] margin = perfect + result = mSN_RESULT_PERFECT; + } else if (adjusted_ratio <= 0.15f) { // (5%, 15%] margin = good + result = mSN_RESULT_GOOD; + } else if (adjusted_ratio <= 0.25f) { // (15%, 25%] margin = okay + result = mSN_RESULT_OK; + } else { // anything larger = bad + result = mSN_RESULT_BAD; + } + + return result; +} + +static int aSMAN_get_snowman_indx(void) { + mSN_snowman_data_c* snowman_p = Save_Get(snowmen).snowmen_data; + int i; + + for (i = 0; i < mSN_SAVE_COUNT; i++) { + if (!snowman_p->exists) { + return i; + } + + snowman_p++; + } + + return -1; +} + +static void aSMAN_process_combine_head_jump_init(ACTOR* actorx, GAME* game) { + SNOWMAN_ACTOR* actor = (SNOWMAN_ACTOR*)actorx; + ACTOR* oc_actorx = actor->col_actor; + SNOWMAN_ACTOR* oc_actor = (SNOWMAN_ACTOR*)oc_actorx; + xyz_t oc_pos = oc_actorx->world.position; + xyz_t pos; + f32 height = (actor->normalized_scale * 20.0f + 10.0f) + (oc_actor->normalized_scale * 20.0f + 10.0f); + + mPlib_request_main_demo_wait_type1(game, FALSE, actorx); + actor->flags |= aSMAN_FLAG_COMBINED; + actor->flags |= aSMAN_FLAG_HEAD_JUMP; + actor->timer = 0; + actorx->parent_actor = oc_actorx; + actor->roll_speed = 0.0f; + mFI_Wpos2UtCenterWpos(&pos, oc_pos); + oc_pos.y += height * 0.6f; + oc_pos.x = pos.x; + oc_pos.z = pos.z; + xyz_t_sub(&actorx->world.position, &oc_pos, &actor->combine_dist); + xyz_t_mult_v(&actor->combine_dist, 1.0f/60.0f); + actorx->shape_info.draw_shadow = FALSE; + actor->result = aSMAN_decide_scale_result(actorx->scale.x, oc_actorx->scale.x); + actor->msg_no = 0x08A1 + ((Common_Get(snowman_msg_id) + aSMAN_get_snowman_indx()) % mSN_SAVE_COUNT) + actor->result * 3; + actor->body_scale = oc_actor->normalized_scale; + + if (actor->result == mSN_RESULT_PERFECT) { + aSMAN_SendPresentMail(); + } + + Save_Set(snowman_year, Common_Get(time.rtc_time.year) % 100); + Save_Set(snowman_month, Common_Get(time.rtc_time.month)); + Save_Set(snowman_day, Common_Get(time.rtc_time.day)); + Save_Set(snowman_hour, Common_Get(time.rtc_time.hour)); + + mQst_NextSnowman(actorx->world.position); + sAdo_OngenTrgStart(NA_SE_104, &actorx->world.position); + actor->process = aSMAN_process_combine_head_jump; +} + +static int aSMAN_process_combine_head_jump(ACTOR* actorx, GAME* game) { + SNOWMAN_ACTOR* actor = (SNOWMAN_ACTOR*)actorx; + + if (actor->timer == 0 && mPlib_check_label_player_demo_wait(game, actorx)) { + mPlib_Set_able_force_speak_label(actorx); + } + + if (actor->timer == 60) { + int i; + s16 angle = DEG2SHORT_ANGLE2(-90.0f); + xyz_t pos = actorx->world.position; + + pos.y -= (actor->normalized_scale * 20.0f + 10.0f) * 0.7f; + for (i = 0; i < 5; i++) { + eEC_CLIP->effect_make_proc(eEC_EFFECT_DUST, pos, 1, angle, game, actorx->npc_id, 0, 9); + angle += DEG2SHORT_ANGLE2(45.0f); + } + actorx->shape_info.ofs_y = 0.0f; + aSMAN_FG_Position_Get(actorx); + aSMAN_process_combine_head_init(actorx); + return FALSE; + } else { + xyz_t_sub(&actorx->world.position, &actor->combine_dist, &actorx->world.position); + actorx->shape_info.ofs_y = (actor->timer * 8.0f + actor->timer * -(1.0f/7.5f) * actor->timer) * 25.0f; + add_calc_short_angle2(&actor->head_vec.x, 0, CALC_EASE(0.5f), DEG2SHORT_ANGLE2(22.5f), 91); + add_calc_short_angle2(&actor->head_vec.y, 0, CALC_EASE(0.5f), DEG2SHORT_ANGLE2(22.5f), 91); + add_calc_short_angle2(&actor->head_vec.z, 0, CALC_EASE(0.5f), DEG2SHORT_ANGLE2(22.5f), 91); + actor->timer++; + return TRUE; + } +} + +static void aSMAN_set_talk_info_combine_head_init(ACTOR* actorx) { + static int base_msg_no[] = { 0x0895, 0x0898, 0x089B, 0x089E }; + SNOWMAN_ACTOR* actor = (SNOWMAN_ACTOR*)actorx; + + actor->head_vec = ZeroSVec; + mDemo_Set_msg_num(base_msg_no[actor->result] + RANDOM(3)); + mDemo_Set_talk_turn(TRUE); +} + +static void aSMAN_set_talk_info_normal_init(ACTOR* actorx) { + SNOWMAN_ACTOR* actor = (SNOWMAN_ACTOR*)actorx; + + mDemo_Set_msg_num(actor->msg_no); + mDemo_Set_ListenAble(); + mDemo_Set_camera(CAMERA2_PROCESS_NORMAL); + mPlib_Set_able_hand_all_item_in_demo(FALSE); +} + +static void aSMAN_process_combine_head_init(ACTOR* actorx) { + SNOWMAN_ACTOR* actor = (SNOWMAN_ACTOR*)actorx; + ACTOR* parentx = actorx->parent_actor; + SNOWMAN_ACTOR* parent = (SNOWMAN_ACTOR*)parentx; + f32 ofs_y; + f32 ofs; + + ofs_y = ((actor->normalized_scale * 20.0f + 10.0f) + (parent->normalized_scale * 20.0f + 10.0f)); + ofs_y *= 0.6f; + actor->timer = 0; + ofs = ((actorx->scale.y + actorx->parent_actor->scale.y) * 10.0f); + Actor_world_to_eye(actorx, (ofs * 0.8f) * 100.0f); + actorx->world.position.x = parentx->world.position.x; + actorx->world.position.z = parentx->world.position.z; + actorx->world.position.y = parentx->world.position.y + ofs_y; + actorx->shape_info.rotation.x = DEG2SHORT_ANGLE2(-17.578125f); + mCoBG_SetPlussOffset(actorx->world.position, 3, mCoBG_ATTRIBUTE_NONE); + if ((actorx->state_bitfield & ACTOR_STATE_NO_CULL) == 0) { + actor->flags |= aSMAN_FLAG_NO_SPEAK; + } else { + mDemo_Request(mDemo_TYPE_SPEAK, actorx, aSMAN_set_talk_info_combine_head_init); + } + + actor->process = aSMAN_process_combine_head; +} + +static int aSMAN_process_combine_head(ACTOR* actorx, GAME* game) { + SNOWMAN_ACTOR* actor = (SNOWMAN_ACTOR*)actorx; + + if ((actor->flags & aSMAN_FLAG_NO_SPEAK) == 0) { + if (mDemo_Check(mDemo_TYPE_SPEAK, actorx) == TRUE) { + if (!mDemo_Check_ListenAble()) { + mDemo_Set_ListenAble(); + mDemo_Set_camera(CAMERA2_PROCESS_NORMAL); + mPlib_Set_able_hand_all_item_in_demo(FALSE); + } + } else { + actor->flags |= aSMAN_FLAG_NO_SPEAK; + } + } else if (!mRlib_PSnowman_NormalTalk(actorx, (GAME_PLAY*)game, &actor->impact_speed, aSMAN_set_talk_info_normal_init)) { + SNOWMAN_ACTOR* parent = (SNOWMAN_ACTOR*)actorx->parent_actor; + + actor->flags |= aSMAN_FLAG_MOVED; + actor->flags |= aSMAN_FLAG_DELETE; + parent->flags |= aSMAN_FLAG_DELETE; + aSMAN_MakeBreakEffect(actor, game); + mQst_BackSnowman(actorx->world.position); + Actor_delete(actorx); + return FALSE; + } + + return TRUE; +} + +static void aSMAN_process_combine_body_init(ACTOR* actorx) { + SNOWMAN_ACTOR* actor = (SNOWMAN_ACTOR*)actorx; + xyz_t pos; + f32 speed_x; + f32 speed_z; + + mFI_Wpos2UtCenterWpos(&pos, actorx->world.position); + xyz_t_sub(&actorx->world.position, &pos, &actor->combine_dist); + xyz_t_mult_v(&actor->combine_dist, 1.0f/60.0f); + speed_x = actor->combine_dist.x; + speed_z = actor->combine_dist.z; + actor->roll_speed = sqrtf(SQ(speed_x) + SQ(speed_z)); + actorx->world.angle.y = atans_table(-speed_z, -speed_x); + actor->timer = 0; + actorx->speed = 0.0f; + actor->accel = 0.0f; + actor->base_speed = 0.0f; + actor->process = aSMAN_process_combine_body; +} + +static int aSMAN_process_combine_body(ACTOR* actorx, GAME* game) { + SNOWMAN_ACTOR* actor = (SNOWMAN_ACTOR*)actorx; + + if ((actor->flags & aSMAN_FLAG_COMBINED) == 0) { + if (actor->timer >= 60) { + actor->roll_speed = 0.0f; + actor->flags |= aSMAN_FLAG_COMBINED; + } else { + xyz_t_sub(&actorx->world.position, &actor->combine_dist, &actorx->world.position); + actor->timer++; + } + } + + if ((actor->flags & aSMAN_FLAG_DELETE) != 0) { + actor->flags |= aSMAN_FLAG_MOVED; + aSMAN_MakeBreakEffect(actor, game); + Actor_delete(actorx); + } + + return TRUE; +} + +static void aSMAN_actor_move(ACTOR* actorx, GAME* game) { + SNOWMAN_ACTOR* actor = (SNOWMAN_ACTOR*)actorx; + GAME_PLAY* play = (GAME_PLAY*)game; + + if ((actorx->state_bitfield & ACTOR_STATE_NO_CULL) == 0) { + if ((actor->flags & aSMAN_FLAG_COMBINED) != 0 && (actor->flags & aSMAN_FLAG_HEAD_JUMP) != 0 && !aSNOWMAN_player_block_check(actorx, game) && (actor->flags & aSMAN_FLAG_NO_SPEAK) != 0) { + Actor_delete(actorx); + Actor_delete(actorx->parent_actor); + return; + } else if (actorx->speed == 0.0f && actor->process != aSMAN_process_combine_head) { + actor->col_pipe.collision_obj.collision_flags0 &= ~ClObj_FLAG_COLLIDED; + actor->col_pipe.collision_obj.collided_actor = NULL; + return; + } + } + + aSMAN_status_check_in_move(actor, game); + aSMAN_position_move(actorx); + aSMAN_BGcheck(actorx, game); + actor->process(actorx, game); + aSMAN_calc_objChkRange(actorx); + aSMAN_calc_axis(actorx); + + if ((actor->flags & aSMAN_FLAG_COMBINED) == 0) { + CollisionCheck_Uty_ActorWorldPosSetPipeC(actorx, &actor->col_pipe); + CollisionCheck_setOC(game, &play->collision_check, &actor->col_pipe.collision_obj); + } +} + +extern Gfx act_darumaA_model[]; +extern Gfx act_darumaB_model[]; + +static void aSMAN_actor_draw(ACTOR* actorx, GAME* game) { + static Gfx* displayList[] = { act_darumaA_model, act_darumaB_model }; + SNOWMAN_ACTOR* actor = (SNOWMAN_ACTOR*)actorx; + GRAPH* graph = game->graph; + int idx; + + if ((actor->flags & aSMAN_FLAG_HEAD_JUMP) == 0 || actor->process != aSMAN_process_combine_head) { + Matrix_translate(0.0f, actor->y_ofs, 0.0, 1); + Matrix_rotateXYZ(actor->head_vec.x, actor->head_vec.y, actor->head_vec.z, 1); + idx = 1; + } else { + idx = 0; + mRlib_PSnowmanBreakNeckSwing(&actor->head_vec.y, actor->impact_speed, actor->normalized_scale); + } + + _texture_z_light_fog_prim(graph); + + OPEN_POLY_OPA_DISP(graph); + + gDPPipeSync(POLY_OPA_DISP++); + gSPMatrix(POLY_OPA_DISP++, _Matrix_to_Mtx_new(graph), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, displayList[idx]); + + CLOSE_POLY_OPA_DISP(graph); +}