diff --git a/.github/workflows/build-patches.yml b/.github/workflows/build-patches.yml index 882c0fb2..de9b459d 100644 --- a/.github/workflows/build-patches.yml +++ b/.github/workflows/build-patches.yml @@ -30,7 +30,7 @@ jobs: xdelta3 -e -S none -s base-jp.z64 fp-jp.z64 fp-jp.xdelta xdelta3 -e -S none -s base-us.z64 fp-us.z64 fp-us.xdelta - name: Upload JP patch - uses: actions/upload-artifact@v2.2.4 + uses: actions/upload-artifact@v4 with: name: fp-jp.xdelta path: fp-jp.xdelta diff --git a/genhooks b/genhooks index 51ec7bac..360ea1f6 100755 --- a/genhooks +++ b/genhooks @@ -45,6 +45,7 @@ genhook "$sym_update_player_input_rom_hook" "jal $sym_fpUpdateInput;" genhook "$sym_fire_flower_hook" "nop;" genhook "$sym_peekaboo_hook" "jal $sym_fpIsAbilityActive;" +genhook "$sym_get_npc_unsafe_hook" "jal $sym_fpGetNpcUnsafe;" # Remove calls to update backup save in slot 7 to make room for fp settings saves genhook "$sym_flush_backups_hook_1" "nop;" diff --git a/lib/libpm-jp.a b/lib/libpm-jp.a index 8a942441..4ae7896c 100644 --- a/lib/libpm-jp.a +++ b/lib/libpm-jp.a @@ -5,8 +5,9 @@ pm_gItemTable = 0x800878B0; pm_gItemHudScripts = 0x8008A650; pm_gAreas = 0x800934C0; pm_viFrames = 0x80093B64; +pm_gEncounterSubState = 0x8009A5B0; pm_timeFreezeMode = 0x8009A5B8; -pm_gameState = 0x8009A5E0; +pm_gEncounterState = 0x8009A5E0; pm_gSoundManager = 0x8009A620; nuGfxCfb_ptr = 0x8009A62C; pm_masterGfxPos = 0x8009A64C; @@ -19,9 +20,9 @@ pm_gCameras = 0x800B1D60; pm_gEffectInstances = 0x800B4378; __osEventStateTab = 0x800D9F60; pm_gCurrentSaveFile = 0x800DACA0; -pm_battleState = 0x800DC048; +pm_gBattleState = 0x800DC048; pm_gBattleStatus = 0x800DC050; -pm_battleState2 = 0x800DC4BC; +pm_gBattleSubState = 0x800DC4BC; pm_playerActionsTable = 0x800F7E1C; pm_popupMenuVar = 0x8010D800; pm_gPartnerActionStatus = 0x8010ED70; @@ -47,6 +48,7 @@ pm_update_camera_zone_interp = 0x80031124; pm_setCurtainDrawCallback = 0x8002BEC4; pm_setCurtainFadeGoal = 0x8002BED4; pm_setGameMode = 0x80033180; +pm_get_npc_unsafe = 0x8003A7AC; pm_get_npc_safe = 0x8003A808; pm_au_sfx_reset_players = 0x8004D168; pm_snd_ambient_stop_all = 0x8005547C; @@ -72,12 +74,14 @@ pm_disable_player_input = 0x800E0158; pm_is_ability_active = 0x800E9CE8; pm_hidePopupMenu = 0x800F1340; pm_destroyPopupMenu = 0x800F14C8; +pm_clear_printers = 0x80128884; pm_set_screen_overlay_alpha = 0x8013D184; pm_setMapTransitionEffect = 0x8013D350; pm_updateExitMapScreenOverlay = 0x8013D35C; pm_clearWindows = 0x8014C4A0; pm_playSfx = 0x8014ED64; pm_bgmSetSong = 0x8014F9C8; +pm_bgm_pop_battle_song = 0x8014FF1C; pm_useIdleAnimation = 0x8026F26C; pm_gotoMap = 0x802CA400; pm_saveGame = 0x802DC150; @@ -101,5 +105,7 @@ fire_flower_hook = 0x803AD0A0; peekaboo_hook = 0x801AF3A0; +get_npc_unsafe_hook = 0x8004165C; + flush_backups_hook_1 = 0x8002B064; flush_backups_hook_2 = 0x8002B074; \ No newline at end of file diff --git a/lib/libpm-us.a b/lib/libpm-us.a index cd337ddb..1110ee20 100644 --- a/lib/libpm-us.a +++ b/lib/libpm-us.a @@ -5,8 +5,9 @@ pm_gItemTable = 0x800878E0; pm_gItemHudScripts = 0x8008A680; pm_gAreas = 0x800934F0; pm_viFrames = 0x80093B94; +pm_gEncounterSubState = 0x8009A5D0; pm_timeFreezeMode = 0x8009A5D8; -pm_gameState = 0x8009A600; +pm_gEncounterState = 0x8009A600; pm_gSoundManager = 0x8009A640; nuGfxCfb_ptr = 0x8009A64C; pm_masterGfxPos = 0x8009A66C; @@ -19,9 +20,9 @@ pm_gCameras = 0x800B1D80; pm_gEffectInstances = 0x800B4398; __osEventStateTab = 0x800D9F80; pm_gCurrentSaveFile = 0x800DACC0; -pm_battleState = 0x800DC068; +pm_gBattleState = 0x800DC068; pm_gBattleStatus = 0x800DC070; -pm_battleState2 = 0x800DC4DC; +pm_gBattleSubState = 0x800DC4DC; pm_playerActionsTable = 0x800F7C8C; pm_popupMenuVar = 0x8010D640; pm_gPartnerActionStatus = 0x8010EBB0; @@ -47,6 +48,7 @@ pm_setCurtainFadeGoal = 0x8002BF14; pm_update_cameras = 0x8002D400; pm_update_camera_zone_interp = 0x80031494; pm_setGameMode = 0x800334F0; +pm_get_npc_unsafe = 0x8003AAEC; pm_get_npc_safe = 0x8003AB48; pm_au_sfx_reset_players = 0x8004D4BC; pm_snd_ambient_stop_all = 0x800557CC; @@ -72,12 +74,14 @@ pm_disable_player_input = 0x800E0178; pm_is_ability_active = 0x800E9D48; pm_hidePopupMenu = 0x800F13B0; pm_destroyPopupMenu = 0x800F1538; +pm_clear_printers = 0x80123674; pm_set_screen_overlay_alpha = 0x801380D4; pm_setMapTransitionEffect = 0x801382A0; pm_updateExitMapScreenOverlay = 0x801382AC; pm_clearWindows = 0x801473F0; pm_playSfx = 0x80149CB4; pm_bgmSetSong = 0x8014A918; +pm_bgm_pop_battle_song = 0x8014AE6C; pm_useIdleAnimation = 0x8026F0EC; pm_gotoMap = 0x802CA400; pm_saveGame = 0x802E11A0; @@ -101,5 +105,7 @@ fire_flower_hook = 0x803A4DA0; peekaboo_hook = 0x801A7110; +get_npc_unsafe_hook = 0x8004199C; + flush_backups_hook_1 = 0x8002B0A4; flush_backups_hook_2 = 0x8002B0B4; \ No newline at end of file diff --git a/src/fp.c b/src/fp.c index 76835476..7502b547 100644 --- a/src/fp.c +++ b/src/fp.c @@ -3,6 +3,7 @@ #include "common.h" #include "fp/practice/timer.h" #include "fp/practice/trainer.h" +#include "fp/warps/bosses.h" #include "io/io.h" #include "sys/crash_screen.h" #include "sys/input.h" @@ -472,6 +473,7 @@ void fpUpdate(void) { } fpUpdateWarps(); + bossesUpdateWarps(); // Override updateMode so update_cameras switch always defaults if (fp.freeCam) { @@ -579,6 +581,14 @@ HOOK s32 fpIsAbilityActive(s32 ability) { return pm_is_ability_active(ability); } +HOOK pm_Npc *fpGetNpcUnsafe(s16 npcId) { + if (npcId == BOSSES_DUMMY_ID) { + bossesDummyNpc.pos = pm_gPlayerStatus.position; + return &bossesDummyNpc; + } + return pm_get_npc_unsafe(npcId); +} + #include #include #include diff --git a/src/fp/practice/trainer.c b/src/fp/practice/trainer.c index baede669..8f99a750 100644 --- a/src/fp/practice/trainer.c +++ b/src/fp/practice/trainer.c @@ -490,8 +490,8 @@ static void updateBlockTrainer(void) { } // Either goombario or mario attacking - if ((pm_battleState2 == 3 && pm_gPlayerStatus.playerData.currentPartner == PARTNER_GOOMBARIO) || - pm_battleState2 == 4) { + if ((pm_gBattleSubState == 3 && pm_gPlayerStatus.playerData.currentPartner == PARTNER_GOOMBARIO) || + pm_gBattleSubState == 4) { if (pm_gActionCommandStatus.state == 10 && pm_gGameStatus.pressedButtons[0].a) { acLastAPress = pm_gGameStatus.frameCounter; } else if (pm_gActionCommandStatus.state == 11) { @@ -518,24 +518,24 @@ static void updateBlockTrainer(void) { static void updateClippyTrainer(void) { if (settings->trainerClippyEnabled) { - if (pm_gGameStatus.pressedButtons[0].cr && pm_gCurrentEncounter.eFirstStrike != 2) { - if (pm_gameState == 2 && pm_gPartnerActionStatus.partnerActionState == 1) { + if (pm_gGameStatus.pressedButtons[0].cr && pm_gCurrentEncounter.firstStrikeType != 2) { + if (pm_gEncounterState == 2 && pm_gPartnerActionStatus.partnerActionState == 1) { clippyStatus = CLIPPY_EARLY; } else if (clippyFramesSinceBattle > 0) { clippyStatus = CLIPPY_LATE; - } else if (pm_gameState == 3 && clippyFramesSinceBattle == 0) { + } else if (pm_gEncounterState == 3 && clippyFramesSinceBattle == 0) { clippyStatus = CLIPPY_SUCCESS; } } - if (pm_gameState == 3) { + if (pm_gEncounterState == 3) { clippyFramesSinceBattle++; switch (clippyStatus) { case CLIPPY_EARLY: fpLog("early"); break; case CLIPPY_LATE: fpLog("late"); break; } clippyStatus = CLIPPY_NONE; - } else if (pm_gameState != 3) { + } else if (pm_gEncounterState != 3) { clippyFramesSinceBattle = 0; } } diff --git a/src/fp/warps/bosses.c b/src/fp/warps/bosses.c index 071a5bd6..870248b3 100644 --- a/src/fp/warps/bosses.c +++ b/src/fp/warps/bosses.c @@ -1,221 +1,173 @@ +#include "bosses.h" #include "common.h" -#include "fp.h" #include "menu/menu.h" #include -static void bowserHallwayProc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH8_REACHED_PEACHS_CASTLE; - fpSetGlobalFlag(0x1fa, FALSE); // hallway not defeated - fpWarp(AREA_PEACHS_CASTLE, 0x7, 0x0); -} - -static void bowserPhase1Proc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH8_REACHED_PEACHS_CASTLE; - fpSetGlobalFlag(0x1fc, FALSE); // bridge not broken - fpSetGlobalFlag(0x1fd, FALSE); // not sure, but prevents a crash - fpSetGlobalFlag(0x1fe, TRUE); // skip camera zoom in - fpWarp(AREA_PEACHS_CASTLE, 0x13, 0x0); -} - -static void bowserPhase2Proc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH8_REACHED_PEACHS_CASTLE; - fpSetGlobalFlag(0x1fc, TRUE); // bridge broken - fpWarp(AREA_PEACHS_CASTLE, 0x13, 0x1); -} - -static void goombaKingProc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH0_DEFEATED_GOOMBA_BROS; - fpSetGlobalFlag(0x02d, TRUE); // skip cutscene - fpWarp(AREA_GOOMBA_VILLAGE, 0x9, 0x0); -} - -static void koopaBrosProc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH1_KOOPA_BROS_FIRING_BLASTERS; - fpWarp(AREA_KOOPA_BROS_FORTRESS, 0xa, 0x0); -} - -static void tutankoopaProc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH2_SOLVED_ARTIFACT_PUZZLE; - fpWarp(AREA_DRY_DRY_RUINS, 0xe, 0x0); -} - -static void tubbaBlubbaProc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH3_HEART_ESCAPED_WINDY_MILL; - fpWarp(AREA_GUSTY_GULCH, 0x4, 0x0); -} - -static void generalGuyProc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH4_OPENED_GENERAL_GUY_ROOM; - fpWarp(AREA_SHY_GUYS_TOY_BOX, 0xe, 0x0); -} - -static void lavaPiranhaProc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH5_KOLORADO_IN_TREASURE_ROOM; - fpWarp(AREA_VOLCANO, 0xd, 0x1); -} - -static void huffNPuffProc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH6_GREW_MAGIC_BEANSTALK; - fpWarp(AREA_FLOWER_FIELDS, 0xf, 0x0); -} - -static void crystalKingProc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH7_SOLVED_ALBINO_DINO_PUZZLE; - fpWarp(AREA_CRYSTAL_PALACE, 0x17, 0x0); -} - -static void jrPlaygroundProc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH0_FOUND_HAMMER; - u8 *partner = &pm_gPlayerStatus.playerData.currentPartner; - if (*partner == 4 || *partner == 6 || *partner == 8 || *partner == 9) { // flying partners cause a softlock - *partner = 1; // goombario +typedef struct BattleInfo { + const char *name; + s16 battleId; + s16 stageId; + s32 songId; +} BattleInfo; + +typedef struct BattlePage { + const char *name; + u8 battleCount; + BattleInfo battles[]; +} BattlePage; + +// clang-format off +static BattlePage pageBowser = { + "bowser", + 3, + { + {"hallway", 0x2302, -1, 102}, + {"final phase 1", 0x2303, -1, 103}, + {"final phase 2", 0x2304, -1, 5}, } - fpWarp(AREA_GOOMBA_VILLAGE, 0x3, 0x0); -} - -static void jrPleasantPathProc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH1_STAR_SPRIT_DEPARTED; - fpWarp(AREA_KOOPA_VILLAGE_PLEASANT_PATH, 0x4, 0x1); -} - -static void jrForeverForestProc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH3_STAR_SPRIT_DEPARTED; - fpSetGlobalFlag(0x39f, FALSE); // jr not defeated - fpWarp(AREA_FOREVER_FOREST, 0x6, 0x3); -} - -static void jrToadTownPortProc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH5_STAR_SPIRIT_DEPARTED; - fpSetGlobalFlag(0x4c2, FALSE); // jr not defeated - fpWarp(AREA_TOAD_TOWN, 0x6, 0x1); -} - -static void jrShiverSnowfieldProc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH7_MAYOR_MURDER_SOLVED; - fpWarp(AREA_SHIVER_REGION, 0x2, 0x0); -} - -static void jrBowsersCastleProc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH8_REACHED_BOWSERS_CASTLE; - fpSetGlobalByte(0x12c, 0); - fpWarp(AREA_BOWSERS_CASTLE, 0x1c, 0x0); -} - -static void goombaBrosProc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH0_SMASHED_GATE_BLOCK; - fpWarp(AREA_GOOMBA_VILLAGE, 0x6, 0x0); -} - -static void tubbasHeartProc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH3_WENT_DOWN_THE_WELL; - fpWarp(AREA_GUSTY_GULCH, 0x8, 0x0); -} - -static void lanternGhostProc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH4_SOLVED_COLOR_PUZZLE; - fpWarp(AREA_SHY_GUYS_TOY_BOX, 0xb, 0x0); -} - -static void fuzzipedeProc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH5_ENTERED_WHALE; - fpWarp(AREA_WHALE, 0x1, 0x0); -} - -static void lakilesterProc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH6_SPOKE_WITH_THE_SUN; - fpWarp(AREA_FLOWER_FIELDS, 0x8, 0x1); -} - -static void monstarProc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH7_DEFEATED_JR_TROOPA; - fpWarp(AREA_SHIVER_REGION, 0x4, 0x0); -} - -static void blooperProc(struct MenuItem *item, void *data) { - fpSetGlobalFlag(0x1ab, FALSE); // blooper not defeated - fpSetGlobalFlag(0x1ac, FALSE); // electro blooper not defeated - fpSetGlobalFlag(0x1af, FALSE); // ch5 pipe switch - fpWarp(AREA_SEWERS, 0x7, 0x1); -} - -static void electroBlooperProc(struct MenuItem *item, void *data) { - fpSetGlobalFlag(0x1ab, TRUE); // blooper defeated - fpSetGlobalFlag(0x1ac, FALSE); // electro blooper not defeated - fpSetGlobalFlag(0x1af, FALSE); // ch5 pipe switch - fpWarp(AREA_SEWERS, 0x7, 0x1); -} - -static void superBlooperProc(struct MenuItem *item, void *data) { - fpSetGlobalFlag(0x1ab, TRUE); // blooper defeated - fpSetGlobalFlag(0x1ac, TRUE); // electro blooper defeated - fpSetGlobalFlag(0x1af, FALSE); // ch5 pipe switch - fpWarp(AREA_SEWERS, 0x7, 0x1); -} - -static void buzzarProc(struct MenuItem *item, void *data) { - fpSetGlobalFlag(0x2c4, FALSE); // buzzar not defeated - fpWarp(AREA_MT_RUGGED, 0x4, 0x1); -} - -static void antiGuyProc(struct MenuItem *item, void *data) { - fpSetGlobalFlag(0x451, FALSE); // anti guy not defeated - fpWarp(AREA_SHY_GUYS_TOY_BOX, 0xc, 0x1); -} - -static void kentCKoopaProc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH6_RETURNED_TO_TOAD_TOWN; - fpSetGlobalFlag(0x262, FALSE); // kent not defeated - fpWarp(AREA_KOOPA_VILLAGE_PLEASANT_PATH, 0x4, 0x0); -} - -static void antiGuysUnitProc(struct MenuItem *item, void *data) { - STORY_PROGRESS = STORY_CH8_REACHED_BOWSERS_CASTLE; - fpSetGlobalByte(0x12b, 0); - fpWarp(AREA_BOWSERS_CASTLE, 0x1b, 0x0); -} - -static void chanProc(struct MenuItem *item, void *data) { - fpSetGlobalByte(0x1C, 0); - if (pm_gGameStatus.areaID == 0x1 && pm_gGameStatus.mapID == 0x1 && !pm_gGameStatus.isBattle) { - fpLog("dojo set to chan"); - } else { - fpWarp(AREA_TOAD_TOWN, 0x1, 0x1); +}; + +static BattlePage pageChapterBosses = { + "chapter bosses", + 8, + { + {"goomba king", 0x101, -1, 7}, + {"koopa bros.", 0x700, -1, 9}, + {"tutankoopa", 0xc00, 1, 10}, + {"tubba blubba", 0xe10, 1, 11}, + {"general guy", 0x1100, -1, 12}, + {"lava piranha", 0x1700, 6, 13}, + {"huff n. puff", 0x1900, 7, 14}, + {"crystal king", 0x2000, -1, 15}, } -} - -static void leeProc(struct MenuItem *item, void *data) { - fpSetGlobalByte(0x1C, 1); - if (pm_gGameStatus.areaID == 0x1 && pm_gGameStatus.mapID == 0x1 && !pm_gGameStatus.isBattle) { - fpLog("dojo set to lee"); - } else { - fpWarp(AREA_TOAD_TOWN, 0x1, 0x1); +}; + +static BattlePage pageJrTroopa = { + "jr. troopa", + 6, + { + {"playground", 0x202, -1, 4}, + {"pleasant path", 0x203, -1, 4}, + {"forever forest", 0x204, -1, 4}, + {"toad town port", 0x205, -1, 4}, + {"shiver snowfield", 0x206, -1, 4}, + {"bowser's castle", 0x207, -1, 4}, } -} - -static void master1Proc(struct MenuItem *item, void *data) { - fpSetGlobalByte(0x1C, 2); - if (pm_gGameStatus.areaID == 0x1 && pm_gGameStatus.mapID == 0x1 && !pm_gGameStatus.isBattle) { - fpLog("dojo set to master 1"); - } else { - fpWarp(AREA_TOAD_TOWN, 0x1, 0x1); +}; + +static BattlePage pageMinorBosses = { + "minor bosses", + 6, + { + {"goomba bros.", 0x100, 1, 3}, + {"tubba's heart", 0xe0f, -1, 11}, + {"big lantern ghost", 0x1200, -1, 3}, + {"fuzzipede", 0x1300, 0, 3}, + {"lakilester", 0x1905, 1, 3}, + {"monstar", 0x1e00, 5, 3}, } -} - -static void master2Proc(struct MenuItem *item, void *data) { - fpSetGlobalByte(0x1C, 3); - if (pm_gGameStatus.areaID == 0x1 && pm_gGameStatus.mapID == 0x1 && !pm_gGameStatus.isBattle) { - fpLog("dojo set to master 2"); - } else { - fpWarp(AREA_TOAD_TOWN, 0x1, 0x1); +}; + +static BattlePage pageOptionalBosses = { + "optional bosses", + 7, + { + {"blooper", 0x1b00, 0, 3}, + {"electro blooper", 0x1b01, 0, 3}, + {"super blooper", 0x1b02, 0, 3}, + {"buzzar", 0x90c, 2, 3}, + {"anti guy", 0x102c, 0, 3}, + {"kent c. koopa", 0x518, 0, 3}, + {"anti guys unit", 0x2400, 0, 3}, } -} - -static void master3Proc(struct MenuItem *item, void *data) { - fpSetGlobalByte(0x1C, 4); - if (pm_gGameStatus.areaID == 0x1 && pm_gGameStatus.mapID == 0x1 && !pm_gGameStatus.isBattle) { - fpLog("dojo set to master 3"); - } else { - fpWarp(AREA_TOAD_TOWN, 0x1, 0x1); +}; + +static BattlePage pageDojo = { + "dojo", + 5, + { + {"chan", 0x300, -1, -1}, + {"lee", 0x301, -1, -1}, + {"master 1", 0x302, -1, 44}, + {"master 2", 0x303, -1, 44}, + {"master 3", 0x304, -1, 44}, + } +}; +// clang-format on + +static BattlePage *pageList[] = {&pageBowser, &pageChapterBosses, &pageJrTroopa, + &pageMinorBosses, &pageOptionalBosses, &pageDojo}; + +pm_Npc bossesDummyNpc = {0}; + +static pm_Enemy dummyEnemy = {.npcID = BOSSES_DUMMY_ID}; +static pm_Encounter dummyEncounter = {.enemy[0] = &dummyEnemy}; +static s8 warpCountdown = 0; +static bool leavingBattle = FALSE; +static u8 page = 0; +static u8 battle = 0; + +static void bossWarp() { + BattleInfo info = pageList[page]->battles[battle]; + + dummyEncounter.battle = info.battleId; + dummyEncounter.stage = info.stageId; + + pm_EncounterStatus *es = &pm_gCurrentEncounter; + es->curEncounter = &dummyEncounter; + es->curEnemy = &dummyEnemy; + es->hitType = 1; + es->firstStrikeType = 0; + es->forbidFleeing = FALSE; + es->scriptedBattle = TRUE; + es->songID = info.songId; + + pm_gEncounterState = 3; // ENCOUNTER_STATE_PRE_BATTLE + pm_gEncounterSubState = 0; // ENCOUNTER_SUBSTATE_PRE_BATTLE_INIT + warpCountdown = 31; + // may want to try and clear speech bubbles, but it doesn't seem to crash, so not worried for now +} + +static void bossWarpProc(struct MenuItem *item, void *data) { + if (warpCountdown > 0) { + // prevent warp spamming + return; + } + page = (u8)((s32)data >> 8); + battle = (u8)((s32)data & 0xFF); + pm_clearWindows(); + pm_clear_printers(); + // isBattle is also true when paused, so checking game mode + if (pm_gGameStatus.isBattle && pm_gameMode != 10 && pm_gameMode != 11) { + // end battle cleanly so next fight can start fresh + pm_gBattleState = 32; // BATTLE_STATE_END_BATTLE + pm_gBattleSubState = 2; // BTL_SUBSTATE_END_BATTLE_EXEC_STAGE_SCRIPT + pm_bgm_pop_battle_song(); + leavingBattle = TRUE; + return; + } + bossWarp(); +} + +void bossesUpdateWarps() { + if (leavingBattle && !pm_gGameStatus.isBattle) { + leavingBattle = FALSE; + bossWarp(); + } else if (warpCountdown == 30) { + // speed up warp fadeout + pm_gCurrentEncounter.fadeOutAmount = 0xFF; + pm_gCurrentEncounter.battleStartCountdown = 0; + warpCountdown--; + } else if (warpCountdown > 30) { + if (pm_gameMode == 10) { + // unpause + pm_setGameMode(11); + } else if (pm_gameMode != 11) { + // delay countdown until unpaused + warpCountdown--; + } + } else if (warpCountdown > 0) { + warpCountdown--; } } @@ -227,80 +179,26 @@ void createBossesMenu(struct Menu *menu) { menu->selector = menuAddSubmenu(menu, 0, yMain++, NULL, "return"); s32 pageCount = 6; - struct Menu *pages = malloc(sizeof(*pages) * pageCount); + struct Menu *pages = malloc(sizeof(*pages) * ARRAY_LENGTH(pageList)); struct MenuItem *tab = menuAddTab(menu, 0, yMain++, pages, pageCount); - for (s32 i = 0; i < pageCount; ++i) { - struct Menu *page = &pages[i]; + for (u8 pageIdx = 0; pageIdx < pageCount; ++pageIdx) { + struct Menu *page = &pages[pageIdx]; menuInit(page, MENU_NOVALUE, MENU_NOVALUE, MENU_NOVALUE); - } - - /* bowser */ - s32 yTab = 0; - struct Menu *page = &pages[0]; - menuAddStatic(page, 0, yTab++, "bowser", 0xC0C0C0); - menuAddButton(page, 0, yTab++, "hallway", bowserHallwayProc, NULL); - menuAddButton(page, 0, yTab++, "final phase 1", bowserPhase1Proc, NULL); - menuAddButton(page, 0, yTab++, "final phase 2", bowserPhase2Proc, NULL); - yTab++; - menuAddStatic(page, 0, yTab, "phase 2 hp:", 0xC0C0C0); - menuAddIntinput(page, 12, yTab++, 10, 2, menuByteModProc, &pm_gCurrentSaveFile.globalBytes[0x18a]); - - /* chapter bosses */ - yTab = 0; - page = &pages[1]; - menuAddStatic(page, 0, yTab++, "chapter bosses", 0xC0C0C0); - menuAddButton(page, 0, yTab++, "goomba king", goombaKingProc, NULL); - menuAddButton(page, 0, yTab++, "koopa bros.", koopaBrosProc, NULL); - menuAddButton(page, 0, yTab++, "tutankoopa", tutankoopaProc, NULL); - menuAddButton(page, 0, yTab++, "tubba blubba", tubbaBlubbaProc, NULL); - menuAddButton(page, 0, yTab++, "general guy", generalGuyProc, NULL); - menuAddButton(page, 0, yTab++, "lava piranha", lavaPiranhaProc, NULL); - menuAddButton(page, 0, yTab++, "huff n. puff", huffNPuffProc, NULL); - menuAddButton(page, 0, yTab++, "crystal king", crystalKingProc, NULL); - /* jr troopa */ - yTab = 0; - page = &pages[2]; - menuAddStatic(page, 0, yTab++, "jr. troopa", 0xC0C0C0); - menuAddButton(page, 0, yTab++, "playground", jrPlaygroundProc, NULL); - menuAddButton(page, 0, yTab++, "pleasant path", jrPleasantPathProc, NULL); - menuAddButton(page, 0, yTab++, "forever forest", jrForeverForestProc, NULL); - menuAddButton(page, 0, yTab++, "toad town port", jrToadTownPortProc, NULL); - menuAddButton(page, 0, yTab++, "shiver snowfield", jrShiverSnowfieldProc, NULL); - menuAddButton(page, 0, yTab++, "bowser's castle", jrBowsersCastleProc, NULL); - - /* minor bosses */ - yTab = 0; - page = &pages[3]; - menuAddStatic(page, 0, yTab++, "minor bosses", 0xC0C0C0); - menuAddButton(page, 0, yTab++, "goomba bros.", goombaBrosProc, NULL); - menuAddButton(page, 0, yTab++, "tubba's heart", tubbasHeartProc, NULL); - menuAddButton(page, 0, yTab++, "big lantern ghost", lanternGhostProc, NULL); - menuAddButton(page, 0, yTab++, "fuzzipede", fuzzipedeProc, NULL); - menuAddButton(page, 0, yTab++, "lakilester", lakilesterProc, NULL); - menuAddButton(page, 0, yTab++, "monstar", monstarProc, NULL); - - /* optional bosses */ - yTab = 0; - page = &pages[4]; - menuAddStatic(page, 0, yTab++, "optional bosses", 0xC0C0C0); - menuAddButton(page, 0, yTab++, "blooper", blooperProc, NULL); - menuAddButton(page, 0, yTab++, "electro blooper", electroBlooperProc, NULL); - menuAddButton(page, 0, yTab++, "super blooper", superBlooperProc, NULL); - menuAddButton(page, 0, yTab++, "buzzar", buzzarProc, NULL); - menuAddButton(page, 0, yTab++, "anti guy", antiGuyProc, NULL); - menuAddButton(page, 0, yTab++, "kent c. koopa", kentCKoopaProc, NULL); - menuAddButton(page, 0, yTab++, "anti guys unit", antiGuysUnitProc, NULL); - - /* dojo */ - yTab = 0; - page = &pages[5]; - menuAddStatic(page, 0, yTab++, "dojo", 0xC0C0C0); - menuAddButton(page, 0, yTab++, "chan", chanProc, NULL); - menuAddButton(page, 0, yTab++, "lee", leeProc, NULL); - menuAddButton(page, 0, yTab++, "master 1", master1Proc, NULL); - menuAddButton(page, 0, yTab++, "master 2", master2Proc, NULL); - menuAddButton(page, 0, yTab++, "master 3", master3Proc, NULL); + s32 yTab = 0; + BattlePage *battlePage = pageList[pageIdx]; + menuAddStatic(page, 0, yTab++, battlePage->name, 0xC0C0C0); + for (u8 battleIdx = 0; battleIdx < battlePage->battleCount; battleIdx++) { + s32 data = pageIdx << 8; + data |= battleIdx; + menuAddButton(page, 0, yTab++, battlePage->battles[battleIdx].name, bossWarpProc, (void *)data); + } + if (pageIdx == 0) { + yTab++; + menuAddStatic(page, 0, yTab, "phase 2 hp:", 0xC0C0C0); + menuAddIntinput(page, 12, yTab++, 10, 2, menuByteModProc, &pm_gCurrentSaveFile.globalBytes[0x18a]); + } + } menuTabGoto(tab, 0); menuAddButton(menu, 8, 0, "<", menuTabPrevProc, tab); diff --git a/src/fp/warps/bosses.h b/src/fp/warps/bosses.h index 471a2967..22fa9ba5 100644 --- a/src/fp/warps/bosses.h +++ b/src/fp/warps/bosses.h @@ -2,6 +2,12 @@ #define BOSSES_H #include "menu/menu.h" +#define BOSSES_DUMMY_ID (s16)0xDEAD + +extern pm_Npc bossesDummyNpc; + +void bossesUpdateWarps(void); + void createBossesMenu(struct Menu *menu); #endif diff --git a/src/pm64.h b/src/pm64.h index 6bbf1420..9f595f85 100644 --- a/src/pm64.h +++ b/src/pm64.h @@ -825,44 +825,117 @@ typedef struct { /* 0x45C */ char unk_45C[4]; } pm_BattleStatus; // size = 0x460 -typedef struct { +typedef struct pm_Enemy { + /* 0x00 */ s32 flags; + /* 0x04 */ s8 encounterIndex; + /* 0x05 */ s8 encountered; + /* 0x06 */ u8 scriptGroup; /* scripts launched for this npc controller will be assigned this group */ + /* 0x07 */ s8 hitboxIsActive; // when set, contact will trigger a first strike + /* 0x08 */ s16 npcID; + /* 0x0A */ s16 spawnPos[3]; + /* 0x10 */ Vec3s unk_10; // TODO hitbox pos? + /* 0x16 */ char unk_16[2]; + /* 0x18 */ void *npcSettings; + /* 0x1C */ void *initBytecode; + /* 0x20 */ void *interactBytecode; + /* 0x24 */ void *aiBytecode; + /* 0x28 */ void *hitBytecode; + /* 0x2C */ void *auxBytecode; + /* 0x30 */ void *defeatBytecode; + /* 0x34 */ struct pm_Evt *initScript; + /* 0x38 */ struct pm_Evt *interactScript; + /* 0x3C */ struct pm_Evt *aiScript; + /* 0x40 */ struct pm_Evt *hitScript; + /* 0x44 */ struct pm_Evt *auxScript; + /* 0x48 */ struct pm_Evt *defeatScript; + /* 0x4C */ s32 initScriptID; + /* 0x50 */ s32 interactScriptID; + /* 0x54 */ s32 aiScriptID; + /* 0x58 */ s32 hitScriptID; + /* 0x5C */ s32 auxScriptID; + /* 0x60 */ s32 defeatScriptID; + /* 0x64 */ void *unk_64; + /* 0x68 */ char unk_68[4]; + /* 0x6C */ union { + /* */ s32 varTable[16]; + /* */ f32 varTableF[16]; + /* */ void *varTablePtr[16]; + /* */ }; + /* 0xAC */ u8 aiDetectFlags; // detect player flags: 1 = require line of sight | 2 = adjust hitbox for moving player + /* 0xAD */ char unk_AD[3]; + /* 0xB0 */ u32 aiFlags; + /* 0xB4 */ s8 aiSuspendTime; + /* 0xB5 */ s8 instigatorValue; // value passed to first actor in formation if battle triggered with this enemy + /* 0xB6 */ char unk_B6[2]; + /* 0xB8 */ void *unk_B8; // some bytecode + /* 0xBC */ struct pm_Evt *unk_BC; // some script + /* 0xC0 */ s32 unk_C0; // some script ID + /* 0xC4 */ s32 unk_C4; + /* 0xC8 */ s32 unk_C8; + /* 0xCC */ s32 *animList; + /* 0xD0 */ void *territory; + /* 0xD4 */ void *drops; + /* 0xD8 */ u32 tattleMsg; + /* 0xDC */ s32 unk_DC; + /* 0xE0 */ s16 savedNpcYaw; + /* 0xE2 */ char unk_E2[6]; +} pm_Enemy; // size = 0xE8 + +typedef struct pm_Encounter { + /* 0x00 */ s32 count; + /* 0x04 */ pm_Enemy *enemy[16]; + /* 0x44 */ s16 battle; + /* 0x46 */ s16 stage; + /* 0x48 */ s16 encounterID; + /* 0x4A */ char unk_4C[2]; +} pm_Encounter; // size = 0x4C + +typedef struct pm_FieldStatus { + /* 0x00 */ s8 status; + /* 0x01 */ char pad_01; + /* 0x02 */ s16 duration; +} pm_FieldStatus; + +typedef struct pm_EncounterStatus { /* 0x000 */ s32 flags; - /* 0x004 */ s8 eFirstStrike; /* 0 = none, 1 = player, 2 = enemy */ - /* 0x005 */ s8 hitType; /* 1 = none/enemy, 2 = jump */ - /* 0x006 */ s8 hitTier; /* 0 = normal, 1 = super, 2 = ultra */ + /* 0x004 */ s8 firstStrikeType; /* 0 = none, 1 = player, 2 = enemy */ + /* 0x005 */ s8 hitType; /* 1 = none/enemy, 2 = jump */ + /* 0x006 */ s8 hitTier; /* 0 = normal, 1 = super, 2 = ultra */ /* 0x007 */ char unk_07; /* 0x008 */ s8 unk_08; - /* 0x009 */ s8 battleOutcome; /* 0 = won, 1 = lost */ - /* 0x00A */ s8 unk_0A; - /* 0x00B */ s8 merleeCoinBonus; /* triple coins when != 0 */ - /* 0x00C */ u8 damageTaken; /* valid after battle */ + /* 0x009 */ s8 battleOutcome; /* 0 = won, 1 = lost */ + /* 0x00A */ s8 battleTriggerCooldown; ///< set to 15 after victory, 45 after fleeing + /* 0x00B */ bool hasMerleeCoinBonus; /* triple coins when TRUE */ + /* 0x00C */ u8 damageTaken; /* valid after battle */ /* 0x00D */ char unk_0D; /* 0x00E */ s16 coinsEarned; /* valid after battle */ - /* 0x010 */ char unk_10; - /* 0x011 */ u8 allowFleeing; - /* 0x012 */ s8 unk_12; - /* 0x013 */ u8 dropWhackaBump; + /* 0x010 */ s8 instigatorValue; + /* 0x011 */ s8 forbidFleeing; + /* 0x012 */ s8 scriptedBattle; ///< battle started by StartBattle but not by encounter + /* 0x013 */ s8 dropWhackaBump; /* 0x014 */ s32 songID; /* 0x018 */ s32 unk_18; /* 0x01C */ s8 numEncounters; /* number of encounters for current map (in list) */ - /* 0x01D */ s8 currentAreaIndex; - /* 0x01E */ u8 currentMapIndex; - /* 0x01F */ u8 currentEntryIndex; + /* 0x01D */ s8 curAreaIndex; + /* 0x01E */ u8 curMapIndex; + /* 0x01F */ u8 curEntryIndex; /* 0x020 */ s8 mapID; /* 0x021 */ s8 resetMapEncounterFlags; /* 0x022 */ char unk_22[2]; /* 0x024 */ s32 *npcGroupList; - /* 0x028 */ s32 *encounterList[24]; /* struct Encounter* */ - /* 0x088 */ s32 *currentEncounter; /* struct Encounter* */ - /* 0x08C */ s32 *currentEnemy; /* struct Enemy* */ + /* 0x028 */ pm_Encounter *encounterList[24]; + /* 0x088 */ pm_Encounter *curEncounter; + /* 0x08C */ pm_Enemy *curEnemy; /* 0x090 */ s32 fadeOutAmount; /* 0x094 */ s32 unk_94; /* 0x098 */ s32 fadeOutAccel; /* 0x09C */ s32 battleStartCountdown; - /* 0x0A0 */ char unk_A0[16]; + /* 0x0A0 */ pm_FieldStatus dizzyAttack; + /* 0x0A4 */ pm_FieldStatus unusedAttack1; + /* 0x0A8 */ pm_FieldStatus unusedAttack2; + /* 0x0AC */ pm_FieldStatus unusedAttack3; /* 0x0B0 */ s32 defeatFlags[60][12]; - /* 0xFB0 */ s16 recentMaps[2]; - /* 0xFB4 */ char unk_FB4[4]; + /* 0xBF0 */ s16 recentMaps[2]; } pm_EncounterStatus; // size = 0xFB8 typedef struct { @@ -1214,8 +1287,9 @@ extern_data pm_ItemData pm_gItemTable[0x16C]; extern_data pm_IconHudScriptPair pm_gItemHudScripts[337]; extern_data pm_Area pm_gAreas[29]; extern_data u32 pm_viFrames; +extern_data s32 pm_gEncounterSubState; extern_data s32 pm_timeFreezeMode; -extern_data s32 pm_gameState; +extern_data s32 pm_gEncounterState; extern_data void *pm_gSoundManager; extern_data u16 *nuGfxCfb_ptr; extern_data Gfx *pm_masterGfxPos; @@ -1227,9 +1301,9 @@ extern_data pm_EncounterStatus pm_gCurrentEncounter; extern_data pm_Camera pm_gCameras[4]; extern_data pm_EffectInstance *pm_gEffectInstances[96]; extern_data pm_SaveData pm_gCurrentSaveFile; -extern_data s32 pm_battleState; +extern_data s32 pm_gBattleState; extern_data pm_BattleStatus pm_gBattleStatus; -extern_data s32 pm_battleState2; +extern_data s32 pm_gBattleSubState; extern_data pm_Action pm_playerActionsTable[39]; extern_data s32 pm_popupMenuVar; extern_data pm_PartnerActionStatus pm_gPartnerActionStatus; @@ -1256,7 +1330,9 @@ void pm_setCurtainFadeGoal(f32 goal); void pm_update_cameras(void); void pm_update_camera_zone_interp(pm_Camera *camera); void pm_setGameMode(s32 mode); +pm_Npc *pm_get_npc_unsafe(s32 npcID); pm_Npc *pm_get_npc_safe(s32 npcID); +void pm_free_npc(pm_Npc *npc); void pm_au_sfx_reset_players(void *soundManager); void pm_snd_ambient_stop_all(s32 time); void pm_removeEffect(pm_EffectInstance *effect); @@ -1273,12 +1349,14 @@ void pm_disable_player_input(void); s32 pm_is_ability_active(s32 arg0); void pm_hidePopupMenu(void); void pm_destroyPopupMenu(void); +void pm_clear_printers(void); void pm_set_screen_overlay_alpha(s32 idx, f32 alpha); s32 pm_setMapTransitionEffect(s32 transition); s32 pm_updateExitMapScreenOverlay(s16 *progress); void pm_clearWindows(void); void pm_playSfx(s32 sound_id); void pm_bgmSetSong(s32 player_index, s32 song_id, s32 variation, s32 fade_out_time, s16 volume); +void pm_bgm_pop_battle_song(void); pm_ApiStatus pm_useIdleAnimation(pm_Evt *script, s32 isInitialCall); pm_ApiStatus pm_gotoMap(pm_Evt *script, s32 isInitialCall); void pm_saveGame(void);