From d797bc452015824f1efbd3692c70b977a1ac5c5b Mon Sep 17 00:00:00 2001 From: ComplexPlane <46565903+ComplexPlane@users.noreply.github.com> Date: Thu, 11 Sep 2025 19:46:48 -0400 Subject: [PATCH] Fix spooky action patch --- configs/default-config.txt | 7 +++ src/internal/tickable.h | 2 +- src/mkb/mkb2.us.lst | 4 +- src/mkb/mkb2_ghidra.h | 57 ++++++++++++++-------- src/patches/fixes/fix_spooky_action.cpp | 64 +++++++++++++++++++++++++ src/patches/fixes/fix_spooky_action.h | 5 ++ src/utils/vecutil.h | 1 + 7 files changed, 116 insertions(+), 24 deletions(-) create mode 100644 src/patches/fixes/fix_spooky_action.cpp create mode 100644 src/patches/fixes/fix_spooky_action.h diff --git a/configs/default-config.txt b/configs/default-config.txt index a97adc4..53e0368 100644 --- a/configs/default-config.txt +++ b/configs/default-config.txt @@ -161,6 +161,12 @@ // 4:3. Additionally fixes View Stage stretching and Sand's haze breaking in // widescreen. Sprites will be fixed in the future. +// fix-spooky-action +// +// Fixes an issue where accumulated floating-point error during collision +// calculations may apply a small undesired positiion/velocity to the ball. This is +// most noticeable on stages with many, fast, or very rotational animations. + // ---------------------------------------------------------------------------- // 'enabled' - Applies the patch @@ -197,6 +203,7 @@ four-digit-banana-counter: disabled fix-minimap-color: disabled fix-widescreen: disabled + fix-spooky-action: disabled } // Toggles which party games are accessible from the party game menu. diff --git a/src/internal/tickable.h b/src/internal/tickable.h index 0b4a15e..caa1e2d 100644 --- a/src/internal/tickable.h +++ b/src/internal/tickable.h @@ -27,7 +27,7 @@ namespace tickable { // Capacity of the tickable manager vector, increase if needed // This only stores pointers, so memory impact should be low -constexpr size_t PATCH_CAPACITY = 32; +constexpr size_t PATCH_CAPACITY = 48; // Represents a patch, or code that ticks every frame struct Tickable { diff --git a/src/mkb/mkb2.us.lst b/src/mkb/mkb2.us.lst index d4bbc4c..8942d51 100644 --- a/src/mkb/mkb2.us.lst +++ b/src/mkb/mkb2.us.lst @@ -4525,7 +4525,7 @@ 802C15A0:stcoli_sub11 802C16DC:g_cone_coli_something 802C16DC:stcoli_sub12 -802C19AC:g_something_with_physicsball_restitution +802C19AC:collide_ball_with_plane 802C19AC:stcoli_sub13 802C1CEC:line_intersects_rect 802C1CEC:stcoli_sub14 @@ -4555,7 +4555,7 @@ 802C4968:stcoli_sub30 802C49C0:inv_tf_physicsball_by_mtxa 802C49C0:stcoli_sub31 -802C4A18:tf_physball_to_itemgroup_space +802C4A18:tf_physicsball_to_itemgroup_space 802C4A18:stcoli_sub32 802C4C38:FUN_801eea00 802C4C58:g_something_w_ig_and_coli_headers diff --git a/src/mkb/mkb2_ghidra.h b/src/mkb/mkb2_ghidra.h index d5dac5c..db2f08c 100644 --- a/src/mkb/mkb2_ghidra.h +++ b/src/mkb/mkb2_ghidra.h @@ -142,6 +142,14 @@ typedef undefined2 StobjType; typedef struct Vec Vec, *PVec; +enum { + COLI_FLAG_OCCURRED=1, + COLI_FLAG_UNK1=2 +}; +typedef undefined4 ColiFlag; + +typedef struct ColiPlane ColiPlane, *PColiPlane; + typedef struct GmaModel GmaModel, *PGmaModel; typedef struct S16Vec S16Vec, *PS16Vec; @@ -192,6 +200,21 @@ enum { }; typedef undefined4 GXTexFmt; +struct Vec { + float x; + float y; + float z; +} __attribute__((__packed__)); +static_assert(sizeof(Vec) == 0xc); + +struct ColiPlane { + struct Vec point; + struct Vec normal; + u16 g_flags1; + u16 g_flags2; +} __attribute__((__packed__)); +static_assert(sizeof(ColiPlane) == 0x1c); + struct S16Vec { /* Often used for rotations */ s16 x; s16 y; @@ -231,27 +254,18 @@ struct GXTexObj { } __attribute__((__packed__)); static_assert(sizeof(GXTexObj) == 0x20); -struct Vec { - float x; - float y; - float z; -} __attribute__((__packed__)); -static_assert(sizeof(Vec) == 0xc); - struct PhysicsBall { /* A representation of a Ball with just the physics/collision-related info */ - dword flags; + ColiFlag flags; struct Vec pos; struct Vec prev_pos; struct Vec vel; float radius; float acceleration; float restitution; - dword g_jerk; - undefined field_0x38[0xc]; - struct Vec g_some_vec; - undefined field_0x50[0x4]; - dword field25_0x54; - float field26_0x58; + dword hardest_coli_speed; + struct ColiPlane hardest_coli_plane; + dword hardest_coli_ig_idx; + float friction; dword itemgroup_idx; /* The itemgroup that this PhysicsBall is relative to, aka in the local space of */ } __attribute__((__packed__)); static_assert(sizeof(PhysicsBall) == 0x60); @@ -2185,7 +2199,7 @@ struct Ball { int field48_0x108; struct Vec ape_facedir_point; /* The point of interest that the monkey looks at (goal, banana, etc) */ float something_with_ape_facedir; /* Approaches 1 the closer you are to the point of interest */ - struct Vec g_last_collision_normal; /* Maybe inverse of the normal of the last triangle collided with? */ + struct Vec g_last_coli_normal; /* Maybe inverse of the normal of the last triangle collided with? */ undefined field_0x128[0x4]; dword g_race_flags; short g_other_counter; @@ -2293,7 +2307,7 @@ typedef struct Itemgroup Itemgroup, *PItemgroup; struct Itemgroup { /* Contains the current animation-related state of each item group in a stage (each thing corresponding to a collision header in the stagedef) */ dword playback_state; /* Corresponding to the switch playback type which is controlling the item group, see PlaybackState */ - dword anim_frame; + s32 anim_frame; struct Vec position; struct Vec prev_position; struct S16Vec rotation; @@ -8774,7 +8788,7 @@ extern "C" { void set_ball_properties(struct Ball * ball, int constants_idx); void ball_collision_stars(struct Ball * ball); void init_physicsball_from_ball(struct Ball * ball, struct PhysicsBall * physicsball); - void g_copy_physicsball_to_ball(struct Ball * ball, struct PhysicsBall * physicsball); + void apply_physicsball_to_ball(struct Ball * ball, struct PhysicsBall * physicsball); void g_ball_ape_rotation(struct Ball * ball); void spawn_postgoal_ball_sparkle(void); void g_some_ballfunc(struct Ball * param_1); @@ -8796,6 +8810,7 @@ extern "C" { void g_sphere_coli_something(struct PhysicsBall * param_1, struct StagedefColiSphere * param_2); void g_cone_coli_something(struct PhysicsBall * param_1, struct StagedefColiCone * param_2); void g_something_with_physicsball_restitution(struct PhysicsBall * physicsball, struct Vec * param_2); + void collide_ball_with_plane(struct PhysicsBall * physicsball, struct ColiPlane * plane); BOOL32 line_intersects_rect(struct Vec * lineStart, struct Vec * lineEnd, struct Rect * rect); void stobj_jamabar_child_coli(struct PhysicsBall * physicsball, struct Stobj * stobj); void raycast_stage_down(struct Vec * origin, struct RaycastHit * out_hit, struct Vec * out_vel_at_point); @@ -8813,9 +8828,9 @@ extern "C" { void stcoli_sub29(float * param_1, float * param_2, float * param_3, float * param_4, undefined4 param_5, undefined4 param_6, undefined4 param_7, undefined4 param_8); void tf_physicsball_by_mtxa(struct PhysicsBall * physicsball1, struct PhysicsBall * physicsball2); void inv_tf_physicsball_by_mtxa(struct PhysicsBall * src_physicsball, struct PhysicsBall * dest_physicsball); - void tf_physball_to_itemgroup_space(struct PhysicsBall * physicsball, int itemgroup_idx); - uint g_something_w_ig_and_coli_headers(struct Itemgroup * ig_list, struct StagedefColiHeader * coli_header_list, undefined4 param_3, struct Vec * physicsball_x); - undefined4 g_something_w_ig_and_coli_headers_2(struct Itemgroup * ig_list, struct StagedefColiHeader * coli_header_list, struct Vec * physicsball_pos); + void tf_physicsball_to_itemgroup_space(struct PhysicsBall * physicsball, int dest_ig_idx); + uint g_is_ball_in_ig_coli_range(struct Itemgroup * ig_list, struct StagedefColiHeader * coli_header_list, undefined4 param_3, struct Vec * physicsball_x); + BOOL32 g_ball_ig_bound_sphere_overlap(struct Itemgroup * ig_anim, struct StagedefColiHeader * ig_def, struct Vec * physicsball_pos); void event_world_init(void); void event_world_tick(void); void event_world_dest(void); @@ -8823,7 +8838,7 @@ extern "C" { void event_stage_init(void); void event_stage_tick(void); void event_stage_dest(void); - double g_advance_itemgroup_anim_frame(struct Itemgroup * itemgroup, struct StagedefColiHeader * colis_header); + float advance_itemgroup_anim(struct Itemgroup * itemgroup, struct StagedefColiHeader * colis_header); void g_advance_stage_animation(void); void g_transform_some_itemgroup_vec(void); GmaModel * get_GmaBuffer_entry(struct GmaBuffer * buffer, char * name); diff --git a/src/patches/fixes/fix_spooky_action.cpp b/src/patches/fixes/fix_spooky_action.cpp new file mode 100644 index 0000000..310ba0f --- /dev/null +++ b/src/patches/fixes/fix_spooky_action.cpp @@ -0,0 +1,64 @@ +#include "fix_spooky_action.h" + +#include "patch.h" +#include "tickable.h" +#include "vecutil.h" +#include + +namespace { + +// Our special collision flag that's reset for each itemgroup +constexpr u32 COLI_FLAG_IG = 1 << 7; + +patch::Tramp s_init_physicsball_tramp; +patch::Tramp s_tf_physicsball_tramp; +patch::Tramp s_collide_physicsball_tramp; + +mkb::PhysicsBall s_clean_physicsball; + +void init_physicsball_from_ball(mkb::Ball* ball, mkb::PhysicsBall* physicsball) { + s_init_physicsball_tramp.dest(ball, physicsball); + s_clean_physicsball = *physicsball; +} + +void tf_physicsball_to_itemgroup_space(mkb::PhysicsBall* physicsball, int dest_ig_idx) { + if (physicsball->flags & COLI_FLAG_IG) { + physicsball->flags &= ~COLI_FLAG_IG; + s_clean_physicsball = *physicsball; + } + else { + *physicsball = s_clean_physicsball; + } + s_tf_physicsball_tramp.dest(physicsball, dest_ig_idx); +} + +void collide_ball_with_plane(mkb::PhysicsBall* physicsball, mkb::ColiPlane* plane) { + Vec prev_pos = physicsball->pos; + Vec prev_vel = physicsball->vel; + s_collide_physicsball_tramp.dest(physicsball, plane); + if (!VEC_EQUAL_EXACT(prev_pos, physicsball->pos) || + !VEC_EQUAL_EXACT(prev_vel, physicsball->vel)) { + physicsball->flags |= COLI_FLAG_IG; + } +} + +}// namespace + +namespace fix_spooky_action { + +TICKABLE_DEFINITION(( + .name = "fix-spooky-action", + .description = "Prevent accumulated floating point error from transforming ball during collision", + .init_main_loop = init_main_loop, )) + +void init_main_loop() { + + patch::hook_function(s_init_physicsball_tramp, mkb::init_physicsball_from_ball, + init_physicsball_from_ball); + patch::hook_function(s_tf_physicsball_tramp, mkb::tf_physicsball_to_itemgroup_space, + tf_physicsball_to_itemgroup_space); + patch::hook_function(s_collide_physicsball_tramp, mkb::collide_ball_with_plane, + collide_ball_with_plane); +} + +}// namespace fix_spooky_action \ No newline at end of file diff --git a/src/patches/fixes/fix_spooky_action.h b/src/patches/fixes/fix_spooky_action.h new file mode 100644 index 0000000..2748ed1 --- /dev/null +++ b/src/patches/fixes/fix_spooky_action.h @@ -0,0 +1,5 @@ +#pragma once + +namespace fix_spooky_action { +void init_main_loop(); +} \ No newline at end of file diff --git a/src/utils/vecutil.h b/src/utils/vecutil.h index 7f93fcc..b52845f 100644 --- a/src/utils/vecutil.h +++ b/src/utils/vecutil.h @@ -13,3 +13,4 @@ #define VEC_DOT(v1, v2) ((v1).x * (v2).x + (v1).y * (v2).y + (v1).z * (v2).z) #define VEC_LEN_SQ(v) (VEC_DOT((v), (v))) #define VEC_ZERO (Vec3f{0, 0, 0}) +#define VEC_EQUAL_EXACT(v1, v2) ((v1.x == v2.x && v1.y == v2.y && v1.z == v2.z)) \ No newline at end of file