Files
BanjoRecomp/patches/note_saving.c
2026-01-19 14:45:02 -05:00

467 lines
17 KiB
C

#include "patches.h"
#include "bk_api.h"
#include "misc_funcs.h"
#include "save_extension.h"
#include "note_saving.h"
#include "functions.h"
#include "prop.h"
#include "core2/timedfunc.h"
// Vanilla declarations.
typedef struct map_info{
s16 map_id;
s16 level_id;
char* name;
}MapInfo;
extern struct {
s32 unk0;
s32 unk4;
u8 unk8[0x25];
} gFileProgressFlags;
MapInfo * func_8030AD00(enum map_e map_id);
enum map_e map_get(void);
enum level_e level_get(void);
void progressDialog_showDialogMaskZero(s32);
void fxSparkle_musicNote(s16 position[3]);
void item_inc(enum item_e item);
void item_adjustByDiffWithoutHud(enum item_e item, s32 diff);
bool func_802FADD4(enum item_e item_id);
s32 item_getCount(enum item_e item);
s32 jiggyscore_leveltotal(s32 lvl);
void itemPrint_reset(void);
s32 bitfield_get_bit(u8 *array, s32 index);
extern s32 D_80385F30[0x2C];
extern s32 D_80385FE8;
extern struct {
Cube *cubes;
f32 margin;
s32 min[3];
s32 max[3];
s32 stride[2];
s32 cubeCnt;
s32 unk2C;
s32 width[3];
Cube *unk3C; // fallback cube?
Cube *unk40; // some other fallback cube?
s32 unk44; // index of some sort
} sCubeList;
// New declarations.
typedef struct {
u16 static_note_count;
u16 dynamic_note_count;
u16 start_note_index;
u16 level_id;
} MapNoteData;
MapNoteData map_note_data[512]; // One entry per map, with room for extras in case any mods add additional maps.
u16 level_note_counts[256]; // One entry per level, with room for extras in case any mods add additional levels.
typedef struct {
u32 note_index;
} NoteSavingExtensionData;
PropExtensionId note_saving_prop_extension_id;
// Note saving can only savely be changed while in the lair, so this value is only updated when in the lair.
bool note_saving_enabled_cached = FALSE;
// Override for allowing mods to disable note saving.
bool note_saving_override_disabled = FALSE;
u32 spawned_static_note_count = 0;
// Per-map values containing the number of dynamic notes to despawn for the current session.
// This is calculated when a new level is entered for every map in the level.
u8 map_dynamic_note_despawn_counts[512];
bool recomp_in_demo_playback_game_mode();
void init_note_saving() {
note_saving_prop_extension_id = bkrecomp_extend_prop_all(sizeof(NoteSavingExtensionData));
// Collected from map data.
map_note_data[MAP_2_MM_MUMBOS_MOUNTAIN].static_note_count = 85;
map_note_data[MAP_2_MM_MUMBOS_MOUNTAIN].dynamic_note_count = 5;
map_note_data[MAP_5_TTC_BLUBBERS_SHIP].static_note_count = 8;
map_note_data[MAP_6_TTC_NIPPERS_SHELL].static_note_count = 6;
map_note_data[MAP_7_TTC_TREASURE_TROVE_COVE].static_note_count = 82;
map_note_data[MAP_A_TTC_SANDCASTLE].static_note_count = 4;
map_note_data[MAP_B_CC_CLANKERS_CAVERN].static_note_count = 72;
map_note_data[MAP_C_MM_TICKERS_TOWER].static_note_count = 6;
map_note_data[MAP_D_BGS_BUBBLEGLOOP_SWAMP].static_note_count = 83;
map_note_data[MAP_D_BGS_BUBBLEGLOOP_SWAMP].dynamic_note_count = 5;
map_note_data[MAP_E_MM_MUMBOS_SKULL].static_note_count = 4;
map_note_data[MAP_10_BGS_MR_VILE].static_note_count = 6;
map_note_data[MAP_11_BGS_TIPTUP].static_note_count = 6;
map_note_data[MAP_12_GV_GOBIS_VALLEY].static_note_count = 70;
map_note_data[MAP_13_GV_MEMORY_GAME].static_note_count = 4;
map_note_data[MAP_14_GV_SANDYBUTTS_MAZE].static_note_count = 7;
map_note_data[MAP_15_GV_WATER_PYRAMID].static_note_count = 4;
map_note_data[MAP_16_GV_RUBEES_CHAMBER].static_note_count = 8;
map_note_data[MAP_1A_GV_INSIDE_JINXY].static_note_count = 7;
map_note_data[MAP_1B_MMM_MAD_MONSTER_MANSION].static_note_count = 47;
map_note_data[MAP_1C_MMM_CHURCH].static_note_count = 10;
map_note_data[MAP_1D_MMM_CELLAR].static_note_count = 4;
map_note_data[MAP_21_CC_WITCH_SWITCH_ROOM].static_note_count = 6;
map_note_data[MAP_22_CC_INSIDE_CLANKER].static_note_count = 16;
map_note_data[MAP_23_CC_GOLDFEATHER_ROOM].static_note_count = 6;
map_note_data[MAP_24_MMM_TUMBLARS_SHED].static_note_count = 4;
map_note_data[MAP_25_MMM_WELL].static_note_count = 7;
map_note_data[MAP_26_MMM_NAPPERS_ROOM].static_note_count = 8;
map_note_data[MAP_27_FP_FREEZEEZY_PEAK].static_note_count = 82;
map_note_data[MAP_29_MMM_NOTE_ROOM].static_note_count = 9;
map_note_data[MAP_2D_MMM_BEDROOM].static_note_count = 4;
map_note_data[MAP_2F_MMM_WATERDRAIN_BARREL].static_note_count = 5;
map_note_data[MAP_30_MMM_MUMBOS_SKULL].static_note_count = 2;
map_note_data[MAP_31_RBB_RUSTY_BUCKET_BAY].static_note_count = 43;
map_note_data[MAP_34_RBB_ENGINE_ROOM].static_note_count = 16;
map_note_data[MAP_35_RBB_WAREHOUSE].static_note_count = 4;
map_note_data[MAP_37_RBB_CONTAINER_1].static_note_count = 8;
map_note_data[MAP_38_RBB_CONTAINER_3].static_note_count = 4;
map_note_data[MAP_39_RBB_CREW_CABIN].static_note_count = 4;
map_note_data[MAP_3B_RBB_STORAGE_ROOM].static_note_count = 5;
map_note_data[MAP_3C_RBB_KITCHEN].static_note_count = 5;
map_note_data[MAP_3D_RBB_NAVIGATION_ROOM].static_note_count = 4;
map_note_data[MAP_3F_RBB_CAPTAINS_CABIN].static_note_count = 3;
map_note_data[MAP_40_CCW_HUB].static_note_count = 4;
map_note_data[MAP_43_CCW_SPRING].static_note_count = 16;
map_note_data[MAP_44_CCW_SUMMER].static_note_count = 16;
map_note_data[MAP_45_CCW_AUTUMN].static_note_count = 37;
map_note_data[MAP_46_CCW_WINTER].static_note_count = 16;
map_note_data[MAP_48_FP_MUMBOS_SKULL].static_note_count = 6;
map_note_data[MAP_4C_CCW_AUTUMN_MUMBOS_SKULL].static_note_count = 4;
map_note_data[MAP_53_FP_CHRISTMAS_TREE].static_note_count = 12;
map_note_data[MAP_5C_CCW_AUTUMN_ZUBBA_HIVE].static_note_count = 4;
map_note_data[MAP_60_CCW_AUTUMN_NABNUTS_HOUSE].static_note_count = 3;
map_note_data[MAP_8B_RBB_ANCHOR_ROOM].static_note_count = 4;
}
RECOMP_EXPORT void bkrecomp_notesaving_clear_all_map_note_counts() {
for (u32 i = 0; i < ARRLEN(map_note_data); i++) {
map_note_data[i].static_note_count = 0;
map_note_data[i].dynamic_note_count = 0;
}
}
RECOMP_EXPORT void bkrecomp_notesaving_set_map_static_note_count(u32 map_id, u16 static_note_count) {
if (map_id >= ARRLEN(map_note_data)) {
recomp_error("Mod error: Attempted to set static note count of an invalid map ID\n");
}
map_note_data[map_id].static_note_count = static_note_count;
}
RECOMP_EXPORT void bkrecomp_notesaving_set_map_dynamic_note_count(u32 map_id, u16 dynamic_note_count) {
if (map_id >= ARRLEN(map_note_data)) {
recomp_error("Mod error: Attempted to set dynamic note count of an invalid map ID\n");
}
map_note_data[map_id].dynamic_note_count = dynamic_note_count;
}
RECOMP_EXPORT void bkrecomp_notesaving_force_disabled(bool disabled) {
note_saving_override_disabled = disabled;
}
// Notes are always saved, but this function controls whether to use the saved data to prevent notes from spawning and adjust the note score.
RECOMP_EXPORT s32 bkrecomp_note_saving_enabled() {
return recomp_get_note_saving_enabled();
}
RECOMP_EXPORT s32 bkrecomp_note_saving_active() {
return note_saving_enabled_cached;
}
void calculate_map_start_note_indices() {
for (u32 map_id = 0; map_id < ARRLEN(map_note_data); map_id++) {
MapNoteData* note_data = &map_note_data[map_id];
MapInfo* info = func_8030AD00(map_id);
if (info != NULL) {
note_data->level_id = info->level_id;
note_data->start_note_index = level_note_counts[info->level_id];
level_note_counts[info->level_id] += note_data->static_note_count + note_data->dynamic_note_count;
}
else {
note_data->level_id = 0;
}
}
}
s32 level_id_to_level_array_index(enum level_e level_id) {
switch (level_id) {
case LEVEL_1_MUMBOS_MOUNTAIN:
return 0;
case LEVEL_2_TREASURE_TROVE_COVE:
return 1;
case LEVEL_3_CLANKERS_CAVERN:
return 2;
case LEVEL_4_BUBBLEGLOOP_SWAMP:
return 3;
case LEVEL_5_FREEZEEZY_PEAK:
return 4;
case LEVEL_7_GOBIS_VALLEY:
return 5;
case LEVEL_A_MAD_MONSTER_MANSION:
return 6;
case LEVEL_9_RUSTY_BUCKET_BAY:
return 7;
case LEVEL_8_CLICK_CLOCK_WOOD:
return 8;
default:
return -1;
}
}
bool is_note_collected(enum map_e map_id, enum level_e level_id, u8 note_index) {
if (map_id >= ARRLEN(map_note_data)) {
return FALSE;
}
s32 level_array_index = level_id_to_level_array_index(level_id);
if (level_array_index == -1 || level_array_index >= ARRLEN(loaded_file_extension_data.level_notes)) {
return FALSE;
}
MapNoteData *note_data = &map_note_data[map_id];
note_index += note_data->start_note_index;
u32 byte_index = note_index / 8;
u32 bit_index = note_index % 8;
return (loaded_file_extension_data.level_notes[level_array_index].bytes[byte_index] & (1 << bit_index)) != 0;
}
void set_note_collected(enum map_e map_id, enum level_e level_id, u8 note_index) {
if (map_id >= ARRLEN(map_note_data)) {
return;
}
s32 level_array_index = level_id_to_level_array_index(level_id);
if (level_array_index == -1 || level_array_index >= ARRLEN(loaded_file_extension_data.level_notes)) {
return;
}
MapNoteData *note_data = &map_note_data[map_id];
note_index += note_data->start_note_index;
u32 byte_index = note_index / 8;
u32 bit_index = note_index % 8;
loaded_file_extension_data.level_notes[level_array_index].bytes[byte_index] |= (1 << bit_index);
}
void collect_dynamic_note(enum map_e map_id, enum level_e level_id) {
if (map_id < ARRLEN(map_note_data)) {
MapNoteData *map_data = &map_note_data[map_id];
s32 start_note_index = map_data->static_note_count + map_data->start_note_index;
s32 map_dynamic_note_count = map_data->dynamic_note_count;
// Set the first unset dynamic note bit for this map.
for (s32 i = 0; i < map_dynamic_note_count; i++) {
s32 note_index = start_note_index + i;
if (!is_note_collected(map_id, level_id, note_index)) {
set_note_collected(map_id, level_id, note_index);
break;
}
}
}
}
s32 dynamic_note_collected_count(enum map_e map_id) {
s32 ret = 0;
if (map_id < ARRLEN(map_note_data)) {
MapNoteData *map_data = &map_note_data[map_id];
s32 start_note_index = map_data->static_note_count + map_data->start_note_index;
s32 map_dynamic_note_count = map_data->dynamic_note_count;
// Check the dynamic note bits for this map.
for (s32 i = 0; i < map_dynamic_note_count; i++) {
s32 note_index = start_note_index + i;
if (is_note_collected(map_id, map_data->level_id, note_index)) {
ret++;
}
}
}
return ret;
}
void note_saving_on_map_load() {
spawned_static_note_count = 0;
// Prevent the note score passed dialog from running if note saving is enabled.
if (note_saving_enabled_cached) {
// This flag controls whether Bottles will tell you when you pass your note score.
levelSpecificFlags_set(LEVEL_FLAG_34_UNKNOWN, TRUE);
}
}
void note_saving_update() {
// When in the lair or file select, update the cached note saving enabled state.
if (level_get() == LEVEL_6_LAIR || map_get() == MAP_91_FILE_SELECT) {
if (note_saving_override_disabled) {
note_saving_enabled_cached = FALSE;
}
else {
note_saving_enabled_cached = bkrecomp_note_saving_enabled();
}
}
}
void note_saving_handle_static_note(Cube *c, Prop *p) {
// If note saving is enabled, check if this note has been collected and remove it if so.
if (note_saving_enabled_cached) {
if (is_note_collected(map_get(), level_get(), spawned_static_note_count)) {
// Clear the note's alive bit.
p->spriteProp.unk8_4 = FALSE;
}
}
NoteSavingExtensionData* note_data = (NoteSavingExtensionData*)bkrecomp_get_extended_prop_data(c, p, note_saving_prop_extension_id);
note_data->note_index = spawned_static_note_count;
spawned_static_note_count++;
}
void note_saving_handle_dynamic_note(Actor *actor, ActorMarker *marker) {
if (note_saving_enabled_cached) {
s32 map_id = map_get();
if (map_id < ARRLEN(map_dynamic_note_despawn_counts)) {
if (map_dynamic_note_despawn_counts[map_id] > 0) {
map_dynamic_note_despawn_counts[map_id]--;
// Clear the note's alive bit so it doesn't draw for good measure.
marker->propPtr->unk8_4 = FALSE;
// Set the actor as despawned.
actor->despawn_flag = TRUE;
}
}
}
}
bool prop_in_cube(Cube *c, Prop *p) {
s32 prop_index = p - c->prop2Ptr;
if (prop_index >= 0 && prop_index < c->prop2Cnt) {
return TRUE;
}
return FALSE;
}
Cube *find_cube_for_prop(Prop *p) {
for (s32 i = 0; i < sCubeList.cubeCnt; i++) {
Cube *cur_cube = &sCubeList.cubes[i];
if (prop_in_cube(cur_cube, p)) {
return cur_cube;
}
}
if (prop_in_cube(sCubeList.unk3C, p)) {
return sCubeList.unk3C;
}
if (prop_in_cube(sCubeList.unk40, p)) {
return sCubeList.unk40;
}
return NULL;
}
// @recomp Patched to track collected notes.
RECOMP_PATCH void __baMarker_resolveMusicNoteCollision(Prop *arg0) {
// @recomp Set that the note was collected if this isn't demo playback.
if (!recomp_in_demo_playback_game_mode()) {
// Check if this is an actor prop and collect a dynamic note if so.
if (arg0->is_actor) {
collect_dynamic_note(map_get(), level_get());
}
// Otherwise, make sure this is a sprite prop and use the prop data.
else if (!arg0->is_3d) {
Cube *prop_cube = find_cube_for_prop(arg0);
if (prop_cube != NULL) {
NoteSavingExtensionData* note_data = (NoteSavingExtensionData*)bkrecomp_get_extended_prop_data(prop_cube, arg0, note_saving_prop_extension_id);
set_note_collected(map_get(), level_get(), note_data->note_index);
}
}
}
if (!func_802FADD4(ITEM_1B_VILE_VILE_SCORE)) {
item_inc(ITEM_C_NOTE);
} else {
item_adjustByDiffWithoutHud(ITEM_C_NOTE, 1);
}
if (item_getCount(ITEM_C_NOTE) < 100) {
coMusicPlayer_playMusic(COMUSIC_9_NOTE_COLLECTED, 16000);
timedFunc_set_1(0.75f, progressDialog_showDialogMaskZero, FILEPROG_3_MUSIC_NOTE_TEXT);
}
fxSparkle_musicNote(arg0->unk4);
}
s32 get_collected_note_count(enum level_e level) {
s32 level_array_index = level_id_to_level_array_index(level);
if (level_array_index == -1) {
return 0;
}
s32 count = 0;
for (int i = 0; i < ARRLEN(loaded_file_extension_data.level_notes[0].bytes); i++) {
u8 cur_byte = loaded_file_extension_data.level_notes[level_array_index].bytes[i];
count += __builtin_popcount(cur_byte);
}
return count;
}
// @recomp Patched to restore the saved note count when entering a level and reset the per-map collected dynamic note counts.
RECOMP_PATCH void itemscore_levelReset(enum level_e level){
int i;
for(i = 0; i < 6; i++){
D_80385F30[ITEM_0_HOURGLASS_TIMER + i] = 0;
D_80385F30[ITEM_6_HOURGLASS + i] = 0;
}
D_80385F30[ITEM_C_NOTE] = 0;
D_80385F30[ITEM_E_JIGGY] = jiggyscore_leveltotal(level);
D_80385F30[ITEM_12_JINJOS] = 0;
D_80385F30[ITEM_17_AIR] = 3600;
D_80385F30[ITEM_18_GOLD_BULLIONS] = 0;
D_80385F30[ITEM_19_ORANGE] = 0;
D_80385F30[ITEM_23_ACORNS] = 0;
D_80385F30[ITEM_1A_PLAYER_VILE_SCORE] = 0;
D_80385F30[ITEM_1B_VILE_VILE_SCORE] = 0;
D_80385F30[ITEM_1F_GREEN_PRESENT] = 0;
D_80385F30[ITEM_20_BLUE_PRESENT] = 0;
D_80385F30[ITEM_21_RED_PRESENT] = 0;
D_80385F30[ITEM_22_CATERPILLAR] = 0;
itemPrint_reset();
D_80385FE8 = 1;
// @recomp If note saving is currently enabled, set load the note count for the current level.
if (note_saving_enabled_cached) {
D_80385F30[ITEM_C_NOTE] = get_collected_note_count(level);
}
// @recomp Set the number of dynamic notes to respawn for each map in the level.
for (s32 map_id = 0; map_id < ARRLEN(map_note_data); map_id++) {
MapNoteData *map_data = &map_note_data[map_id];
if (map_data->level_id == level) {
map_dynamic_note_despawn_counts[map_id] = dynamic_note_collected_count(map_id);
}
else {
map_dynamic_note_despawn_counts[map_id] = 0;
}
}
}
// @recomp Patched to return true for FILEPROG_99_PAST_50_NOTE_DOOR_TEXT if note saving is enabled.
// That flag controls whether to show the "Grunty's magic stops you from taking the notes..." dialog.
RECOMP_PATCH bool fileProgressFlag_get(enum file_progress_e index) {
if (note_saving_enabled_cached && index == FILEPROG_99_PAST_50_NOTE_DOOR_TEXT) {
return TRUE;
}
return bitfield_get_bit(gFileProgressFlags.unk8, index);
}