From 5900ce7b85eb4c89eed053d232869fc785815ce6 Mon Sep 17 00:00:00 2001 From: Garrett Cox Date: Wed, 18 Sep 2024 13:01:27 -0500 Subject: [PATCH] Initial commit for the develop-rando effort --- mm/2s2h/BenGui/BenMenuBar.cpp | 1 + mm/2s2h/BenJsonConversions.hpp | 34 ++++++ mm/2s2h/BenPort.cpp | 2 + mm/2s2h/CustomMessage/CustomMessage.h | 2 +- mm/2s2h/CustomMessage/ShipMessages.h | 2 +- .../GameInteractor/GameInteractor.cpp | 9 ++ .../GameInteractor/GameInteractor.h | 5 + mm/2s2h/Rando/ActorBehavior/ActorBehavior.cpp | 7 ++ mm/2s2h/Rando/ActorBehavior/ActorBehavior.h | 20 +++ mm/2s2h/Rando/ActorBehavior/EnBox.cpp | 43 +++++++ mm/2s2h/Rando/ActorBehavior/EnElforg.cpp | 63 ++++++++++ mm/2s2h/Rando/ActorBehavior/EnItem00.cpp | 74 +++++++++++ mm/2s2h/Rando/Rando.cpp | 115 ++++++++++++++++++ mm/2s2h/Rando/Rando.h | 13 ++ mm/2s2h/Rando/StaticData/Checks.cpp | 38 ++++++ mm/2s2h/Rando/StaticData/Items.cpp | 27 ++++ mm/2s2h/Rando/StaticData/StaticData.h | 43 +++++++ mm/2s2h/Rando/Types.h | 47 +++++++ mm/2s2h/SaveManager/Migrations/6.cpp | 9 ++ mm/2s2h/SaveManager/SaveManager.cpp | 4 +- mm/CMakeLists.txt | 8 ++ mm/include/z64save.h | 18 +++ mm/src/code/z_msgevent.c | 3 +- mm/src/code/z_sram_NES.c | 2 + .../actors/ovl_En_Guruguru/z_en_guruguru.c | 3 +- .../actors/ovl_player_actor/z_player.c | 6 +- .../ovl_file_choose/z_file_choose_NES.c | 3 + 27 files changed, 595 insertions(+), 6 deletions(-) create mode 100644 mm/2s2h/Rando/ActorBehavior/ActorBehavior.cpp create mode 100644 mm/2s2h/Rando/ActorBehavior/ActorBehavior.h create mode 100644 mm/2s2h/Rando/ActorBehavior/EnBox.cpp create mode 100644 mm/2s2h/Rando/ActorBehavior/EnElforg.cpp create mode 100644 mm/2s2h/Rando/ActorBehavior/EnItem00.cpp create mode 100644 mm/2s2h/Rando/Rando.cpp create mode 100644 mm/2s2h/Rando/Rando.h create mode 100644 mm/2s2h/Rando/StaticData/Checks.cpp create mode 100644 mm/2s2h/Rando/StaticData/Items.cpp create mode 100644 mm/2s2h/Rando/StaticData/StaticData.h create mode 100644 mm/2s2h/Rando/Types.h create mode 100644 mm/2s2h/SaveManager/Migrations/6.cpp diff --git a/mm/2s2h/BenGui/BenMenuBar.cpp b/mm/2s2h/BenGui/BenMenuBar.cpp index 009f6c075a..d6b6b3f9f2 100644 --- a/mm/2s2h/BenGui/BenMenuBar.cpp +++ b/mm/2s2h/BenGui/BenMenuBar.cpp @@ -697,6 +697,7 @@ void DrawEnhancementsMenu() { void DrawCheatsMenu() { if (UIWidgets::BeginMenu("Cheats")) { + UIWidgets::CVarCheckbox("Rando", "gRando.Enabled"); UIWidgets::CVarCheckbox("Infinite Health", "gCheats.InfiniteHealth"); UIWidgets::CVarCheckbox("Infinite Magic", "gCheats.InfiniteMagic"); UIWidgets::CVarCheckbox("Infinite Rupees", "gCheats.InfiniteRupees"); diff --git a/mm/2s2h/BenJsonConversions.hpp b/mm/2s2h/BenJsonConversions.hpp index 190f5202f4..bd32044cde 100644 --- a/mm/2s2h/BenJsonConversions.hpp +++ b/mm/2s2h/BenJsonConversions.hpp @@ -20,16 +20,50 @@ void from_json(const json& j, DpadSaveInfo& dpadEquips) { } } +void to_json(json& j, const RandoSaveInfoCheck& check) { + j = json{ + { "item", check.item }, + { "eligible", check.eligible }, + { "obtained", check.obtained }, + }; +} + +void from_json(const json& j, RandoSaveInfoCheck& check) { + j.at("item").get_to(check.item); + j.at("eligible").get_to(check.eligible); + j.at("obtained").get_to(check.obtained); +} + +void to_json(json& j, const RandoSaveInfo& rando) { + j = json{ + { "checks", rando.checks }, + }; +} + +void from_json(const json& j, RandoSaveInfo& rando) { + j.at("checks").get_to(rando.checks); +} + void to_json(json& j, const ShipSaveInfo& shipSaveInfo) { j = json { { "dpadEquips", shipSaveInfo.dpadEquips }, { "pauseSaveEntrance", shipSaveInfo.pauseSaveEntrance }, + { "saveType", shipSaveInfo.saveType }, }; + + if (shipSaveInfo.saveType == SAVETYPE_RANDO) { + j["rando"] = shipSaveInfo.rando; + } } void from_json(const json& j, ShipSaveInfo& shipSaveInfo) { j.at("dpadEquips").get_to(shipSaveInfo.dpadEquips); j.at("pauseSaveEntrance").get_to(shipSaveInfo.pauseSaveEntrance); + j.at("saveType").get_to(shipSaveInfo.saveType); + + if (shipSaveInfo.saveType == SAVETYPE_RANDO) { + j.at("rando").get_to(shipSaveInfo.rando); + } } void to_json(json& j, const ItemEquips& itemEquips) { diff --git a/mm/2s2h/BenPort.cpp b/mm/2s2h/BenPort.cpp index 83ba99efd2..ccb7b75495 100644 --- a/mm/2s2h/BenPort.cpp +++ b/mm/2s2h/BenPort.cpp @@ -53,6 +53,7 @@ CrowdControl* CrowdControl::Instance; #include "2s2h/Enhancements/GfxPatcher/AuthenticGfxPatches.h" #include "2s2h/DeveloperTools/DebugConsole.h" #include "2s2h/DeveloperTools/DeveloperTools.h" +#include "2s2h/Rando/Rando.h" #include "2s2h/SaveManager/SaveManager.h" // Resource Types/Factories @@ -472,6 +473,7 @@ extern "C" void InitOTR() { BenGui::SetupGuiElements(); InitEnhancements(); InitDeveloperTools(); + Rando::Init(); GfxPatcher_ApplyNecessaryAuthenticPatches(); DebugConsole_Init(); diff --git a/mm/2s2h/CustomMessage/CustomMessage.h b/mm/2s2h/CustomMessage/CustomMessage.h index 74f66bd038..e1d4c447f3 100644 --- a/mm/2s2h/CustomMessage/CustomMessage.h +++ b/mm/2s2h/CustomMessage/CustomMessage.h @@ -10,7 +10,7 @@ extern "C" { // Not really sure what the best ID is for this, but it needs to be between 0-255 // because it's used as a u8 somewhere in the chain -static const u16 CUSTOM_MESSAGE_ID = 0x004B; +#define CUSTOM_MESSAGE_ID 0x004B typedef enum ModId { MOD_ID_VANILLA = 0, diff --git a/mm/2s2h/CustomMessage/ShipMessages.h b/mm/2s2h/CustomMessage/ShipMessages.h index a157e111c8..762147ebe4 100644 --- a/mm/2s2h/CustomMessage/ShipMessages.h +++ b/mm/2s2h/CustomMessage/ShipMessages.h @@ -6,7 +6,7 @@ DEFINE_MESSAGE(HELLO_WORLD, 0x00, "\x06\x00" "\xFE" "\xFF\xFF" "\xFF\xFF" "\xFF\ " - The Moon" ) DEFINE_MESSAGE(GIVE_ITEM, 0x00, "\x02\x00" "\x0C" "\xFF\xFF" "\xFF\xFF" "\xFF\xFF" "\xFF\xFF" -"{{item}}" +"You recieved a(n) {{item}}!" ) DEFINE_MESSAGE(GIVE_ITEM_NO_STOP, 0x00, "\x02\x00" "\x0C" "\xFF\xFF" "\xFF\xFF" "\xFF\xFF" "\xFF\xFF" "{{item}}" "\x1C\x02\x10" diff --git a/mm/2s2h/Enhancements/GameInteractor/GameInteractor.cpp b/mm/2s2h/Enhancements/GameInteractor/GameInteractor.cpp index 36b456bb83..16ee5aed1d 100644 --- a/mm/2s2h/Enhancements/GameInteractor/GameInteractor.cpp +++ b/mm/2s2h/Enhancements/GameInteractor/GameInteractor.cpp @@ -50,6 +50,10 @@ void GameInteractor_ExecuteOnSaveInit(s16 fileNum) { GameInteractor::Instance->ExecuteHooks(fileNum); } +void GameInteractor_ExecuteOnSaveLoad(s16 fileNum) { + GameInteractor::Instance->ExecuteHooks(fileNum); +} + void GameInteractor_ExecuteBeforeEndOfCycleSave() { GameInteractor::Instance->ExecuteHooks(); } @@ -331,6 +335,11 @@ void GameInteractor_ProcessEvents(Actor* actor) { return; } + // If the player is not on the solid ground, stop + if (!(player->actor.bgCheckFlags & BGCHECKFLAG_GROUND)) { + return; + } + // If there is an event active, stop const auto& currentEvent = GameInteractor::Instance->currentEvent; bool shouldReturn = false; diff --git a/mm/2s2h/Enhancements/GameInteractor/GameInteractor.h b/mm/2s2h/Enhancements/GameInteractor/GameInteractor.h index c5428f0059..58e44387bc 100644 --- a/mm/2s2h/Enhancements/GameInteractor/GameInteractor.h +++ b/mm/2s2h/Enhancements/GameInteractor/GameInteractor.h @@ -62,6 +62,9 @@ typedef enum { GI_VB_DRAW_SLIME_BODY_ITEM, GI_VB_ZTARGET_SPEED_CHECK, GI_VB_GIVE_ITEM_FROM_ITEM00, + GI_VB_GIVE_ITEM_FROM_SCRIPT, + GI_VB_GIVE_ITEM_FROM_CHEST, + GI_VB_GIVE_ITEM_FROM_GURUGURU, } GIVanillaBehavior; typedef enum { @@ -312,6 +315,7 @@ class GameInteractor { DEFINE_HOOK(BeforeKaleidoDrawPage, (PauseContext * pauseCtx, u16 pauseIndex)); DEFINE_HOOK(AfterKaleidoDrawPage, (PauseContext * pauseCtx, u16 pauseIndex)); DEFINE_HOOK(OnSaveInit, (s16 fileNum)); + DEFINE_HOOK(OnSaveLoad, (s16 fileNum)); DEFINE_HOOK(BeforeEndOfCycleSave, ()); DEFINE_HOOK(AfterEndOfCycleSave, ()); DEFINE_HOOK(BeforeMoonCrashSaveReset, ()); @@ -363,6 +367,7 @@ void GameInteractor_ExecuteOnKaleidoUpdate(PauseContext* pauseCtx); void GameInteractor_ExecuteBeforeKaleidoDrawPage(PauseContext* pauseCtx, u16 pauseIndex); void GameInteractor_ExecuteAfterKaleidoDrawPage(PauseContext* pauseCtx, u16 pauseIndex); void GameInteractor_ExecuteOnSaveInit(s16 fileNum); +void GameInteractor_ExecuteOnSaveLoad(s16 fileNum); void GameInteractor_ExecuteBeforeEndOfCycleSave(); void GameInteractor_ExecuteAfterEndOfCycleSave(); void GameInteractor_ExecuteBeforeMoonCrashSaveReset(); diff --git a/mm/2s2h/Rando/ActorBehavior/ActorBehavior.cpp b/mm/2s2h/Rando/ActorBehavior/ActorBehavior.cpp new file mode 100644 index 0000000000..d1b3a768df --- /dev/null +++ b/mm/2s2h/Rando/ActorBehavior/ActorBehavior.cpp @@ -0,0 +1,7 @@ +#include "ActorBehavior.h" + +void Rando::InitActorBehavior(bool isRando) { + ActorBehavior::InitEnBoxBehavior(isRando); + ActorBehavior::InitEnElforgBehavior(isRando); + ActorBehavior::InitEnItem00Behavior(isRando); +} diff --git a/mm/2s2h/Rando/ActorBehavior/ActorBehavior.h b/mm/2s2h/Rando/ActorBehavior/ActorBehavior.h new file mode 100644 index 0000000000..13f5337285 --- /dev/null +++ b/mm/2s2h/Rando/ActorBehavior/ActorBehavior.h @@ -0,0 +1,20 @@ +#ifndef RANDO_ACTOR_BEHAVIOR_H +#define RANDO_ACTOR_BEHAVIOR_H + +#include "Rando/Rando.h" + +namespace Rando { + +void InitActorBehavior(bool isRando); + +namespace ActorBehavior { + +void InitEnBoxBehavior(bool isRando); +void InitEnElforgBehavior(bool isRando); +void InitEnItem00Behavior(bool isRando); + +} // namespace ActorBehavior + +} // namespace Rando + +#endif diff --git a/mm/2s2h/Rando/ActorBehavior/EnBox.cpp b/mm/2s2h/Rando/ActorBehavior/EnBox.cpp new file mode 100644 index 0000000000..8e3f8cd9ad --- /dev/null +++ b/mm/2s2h/Rando/ActorBehavior/EnBox.cpp @@ -0,0 +1,43 @@ +#include "ActorBehavior.h" +#include + +extern "C" { +#include "variables.h" + +s32 func_80832558(PlayState* play, Player* player, PlayerFuncD58 arg2); +void Player_SetAction_PreserveMoveFlags(PlayState* play, Player* player, PlayerActionFunc actionFunc, s32 arg3); +void Player_StopCutscene(Player* player); +void func_80848294(PlayState* play, Player* player); +} + +void Player_Action_65_override(Player* player, PlayState* play) { + if (PlayerAnimation_Update(play, &player->skelAnime)) { + Player_StopCutscene(player); + func_80848294(play, player); + } +} + +void func_80837C78_override(PlayState* play, Player* player) { + Player_SetAction_PreserveMoveFlags(play, player, Player_Action_65_override, 0); + player->stateFlags1 |= (PLAYER_STATE1_400 | PLAYER_STATE1_20000000); +} + +// This simply prevents the player from getting an item from the chest, but still +// plays the chest opening animation and ensure the treasure chest flag is set +void Rando::ActorBehavior::InitEnBoxBehavior(bool isRando) { + static uint32_t shouldHookId = 0; + GameInteractor::Instance->UnregisterGameHookForID(shouldHookId); + + shouldHookId = 0; + + if (!isRando) { + return; + } + + shouldHookId = REGISTER_VB_SHOULD(GI_VB_GIVE_ITEM_FROM_CHEST, { + EnBox* enBox = static_cast(opt); + Player* player = GET_PLAYER(gPlayState); + func_80832558(gPlayState, player, func_80837C78_override); + *should = false; + }); +} diff --git a/mm/2s2h/Rando/ActorBehavior/EnElforg.cpp b/mm/2s2h/Rando/ActorBehavior/EnElforg.cpp new file mode 100644 index 0000000000..53c53a4806 --- /dev/null +++ b/mm/2s2h/Rando/ActorBehavior/EnElforg.cpp @@ -0,0 +1,63 @@ +#include "ActorBehavior.h" +#include + +extern "C" { +#include "variables.h" +#include "functions.h" +#include "overlays/actors/ovl_En_Elforg/z_en_elforg.h" + +void EnElforg_SpawnSparkles(EnElforg* thisx, PlayState* play, s32 life); +} + +void EnElforg_DrawCustom(Actor* thisx, PlayState* play) { + EnElforg* enElforg = (EnElforg*)thisx; + Matrix_Scale(20.0f, 20.0f, 20.0f, MTXMODE_APPLY); + RandoCheck check = RC_UNKNOWN; + + if (STRAY_FAIRY_TYPE(thisx) == STRAY_FAIRY_TYPE_CLOCK_TOWN) { + check = RC_CLOCK_TOWN_STRAY_FAIRY; + } else if (STRAY_FAIRY_TYPE(&enElforg->actor) == STRAY_FAIRY_TYPE_COLLECTIBLE) { + auto checkData = Rando::StaticData::GetCheckFromFlag(FLAG_CYCL_SCENE_COLLECTIBLE, + STRAY_FAIRY_GET_FLAG(&enElforg->actor), play->sceneId); + check = checkData.check; + } else if (STRAY_FAIRY_TYPE(&enElforg->actor) == STRAY_FAIRY_TYPE_FREE_FLOATING) { + auto checkData = Rando::StaticData::GetCheckFromFlag(FLAG_CYCL_SCENE_SWITCH, + STRAY_FAIRY_GET_FLAG(&enElforg->actor), play->sceneId); + check = checkData.check; + } + + if (check == RC_UNKNOWN) { + return; + } + + auto checkSaveData = gSaveContext.save.shipSaveInfo.rando.checks[check]; + + EnElforg_SpawnSparkles(enElforg, play, 16); + thisx->shape.rot.y = thisx->shape.rot.y + 960; + + GetItem_Draw(play, Rando::StaticData::Items[checkSaveData.item].drawId); +} + +void Rando::ActorBehavior::InitEnElforgBehavior(bool isRando) { + static uint32_t onActorInitHookId = 0; + GameInteractor::Instance->UnregisterGameHookForID(onActorInitHookId); + + onActorInitHookId = 0; + + if (!isRando) { + return; + } + + onActorInitHookId = + GameInteractor::Instance->RegisterGameHookForID(ACTOR_EN_ELFORG, [](Actor* actor) { + EnElforg* enElforg = (EnElforg*)actor; + + // Currently this will override the draw of all stray fairies, but we should probably add a check for the + // ones we support until we capture all of them in the Checks static data. + if (STRAY_FAIRY_TYPE(&enElforg->actor) == STRAY_FAIRY_TYPE_CLOCK_TOWN || + STRAY_FAIRY_TYPE(&enElforg->actor) == STRAY_FAIRY_TYPE_FREE_FLOATING || + STRAY_FAIRY_TYPE(&enElforg->actor) == STRAY_FAIRY_TYPE_COLLECTIBLE) { + enElforg->actor.draw = EnElforg_DrawCustom; + } + }); +} diff --git a/mm/2s2h/Rando/ActorBehavior/EnItem00.cpp b/mm/2s2h/Rando/ActorBehavior/EnItem00.cpp new file mode 100644 index 0000000000..9a6bcd2523 --- /dev/null +++ b/mm/2s2h/Rando/ActorBehavior/EnItem00.cpp @@ -0,0 +1,74 @@ +#include "ActorBehavior.h" +#include + +extern "C" { +#include "functions.h" +#include "variables.h" +} + +void EnItem00_DrawCustomForFreestanding(Actor* thisx, PlayState* play) { + EnItem00* enItem00 = (EnItem00*)thisx; + Matrix_Scale(20.0f, 20.0f, 20.0f, MTXMODE_APPLY); + + auto checkData = Rando::StaticData::GetCheckFromFlag(FLAG_CYCL_SCENE_COLLECTIBLE, enItem00->collectibleFlag, + gPlayState->sceneId); + if (checkData.check == RC_UNKNOWN) { + return; + } + + auto checkSaveData = gSaveContext.save.shipSaveInfo.rando.checks[checkData.check]; + + GetItem_Draw(play, Rando::StaticData::Items[checkSaveData.item].drawId); +} + +void Rando::ActorBehavior::InitEnItem00Behavior(bool isRando) { + static uint32_t onActorInitHookId = 0; + static uint32_t shouldHookId = 0; + GameInteractor::Instance->UnregisterGameHookForID(onActorInitHookId); + GameInteractor::Instance->UnregisterGameHookForID(shouldHookId); + + onActorInitHookId = 0; + shouldHookId = 0; + + if (!isRando) { + return; + } + + onActorInitHookId = + GameInteractor::Instance->RegisterGameHookForID(ACTOR_EN_ITEM00, [](Actor* actor) { + EnItem00* item00 = (EnItem00*)actor; + + // If it's one of our items ignore it + if (item00->actor.params == ITEM00_NOTHING || item00->actor.params == (ITEM00_NOTHING | 0x8000)) { + return; + } + + auto checkData = Rando::StaticData::GetCheckFromFlag(FLAG_CYCL_SCENE_COLLECTIBLE, item00->collectibleFlag, + gPlayState->sceneId); + if (checkData.check == RC_UNKNOWN) { + return; + } + + auto checkSaveData = gSaveContext.save.shipSaveInfo.rando.checks[checkData.check]; + + if (checkSaveData.obtained) { + Actor_Kill(&item00->actor); + return; + } + + actor->draw = EnItem00_DrawCustomForFreestanding; + }); + + shouldHookId = REGISTER_VB_SHOULD(GI_VB_GIVE_ITEM_FROM_ITEM00, { + EnItem00* item00 = static_cast(opt); + + // If it's one of our items ignore it + if (item00->actor.params == ITEM00_NOTHING || item00->actor.params == (ITEM00_NOTHING | 0x8000)) { + return; + } + + Flags_SetCollectible(gPlayState, item00->collectibleFlag); + Actor_Kill(&item00->actor); + *should = false; + }); +} diff --git a/mm/2s2h/Rando/Rando.cpp b/mm/2s2h/Rando/Rando.cpp new file mode 100644 index 0000000000..0ebaf57099 --- /dev/null +++ b/mm/2s2h/Rando/Rando.cpp @@ -0,0 +1,115 @@ +#include "Rando.h" +#include "Enhancements/GameInteractor/GameInteractor.h" +#include +#include +#include "Rando/ActorBehavior/ActorBehavior.h" + +extern "C" { +#include "variables.h" +#include "functions.h" +} + +void RandomizerQueueCheck(RandoCheck check) { + auto checkData = Rando::StaticData::Checks[check]; + if (checkData.check == RC_UNKNOWN) { + return; + } + + auto& checkSaveData = gSaveContext.save.shipSaveInfo.rando.checks[check]; + + checkSaveData.eligible = true; + GameInteractor::Instance->events.emplace_back(GIEventGiveItem{ + .showGetItemCutscene = true, + .getItemText = Rando::StaticData::Items[checkSaveData.item].name, + .drawItem = + [checkSaveData]() { GetItem_Draw(gPlayState, Rando::StaticData::Items[checkSaveData.item].drawId); }, + .giveItem = + [&checkSaveData]() { + Item_Give(gPlayState, Rando::StaticData::Items[checkSaveData.item].itemId); + checkSaveData.obtained = true; + } }); +} + +void RandomizerOnFlagSetHandler(FlagType flagType, u32 flag) { + auto checkData = Rando::StaticData::GetCheckFromFlag(flagType, flag); + if (checkData.check == RC_UNKNOWN) { + return; + } + + RandomizerQueueCheck(checkData.check); +} + +void RandomizerOnSceneFlagSetHandler(s16 sceneId, FlagType flagType, u32 flag) { + auto checkData = Rando::StaticData::GetCheckFromFlag(flagType, flag, sceneId); + if (checkData.check == RC_UNKNOWN) { + return; + } + + RandomizerQueueCheck(checkData.check); +} + +void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, void* optionalArg) { + switch (id) { + case GI_VB_GIVE_ITEM_FROM_GURUGURU: + case GI_VB_GIVE_ITEM_FROM_SCRIPT: + *should = false; + break; + } +} + +// Very primitive randomizer implementation, when a save is created, if rando is enabled +// we set the save type to rando and shuffle all checks and persist the results to the save +void OnSaveInitHandler(s16 fileNum) { + if (CVarGetInteger("gRando.Enabled", 0)) { + gSaveContext.save.shipSaveInfo.saveType = SAVETYPE_RANDO; + + std::vector itemPool; + for (auto& [check, checkData] : Rando::StaticData::Checks) { + if (checkData.check != RC_UNKNOWN) { + itemPool.push_back(checkData.item); + } + } + + std::shuffle(itemPool.begin(), itemPool.end(), std::mt19937(std::random_device()())); + + for (auto& [check, checkData] : Rando::StaticData::Checks) { + if (checkData.check != RC_UNKNOWN) { + gSaveContext.save.shipSaveInfo.rando.checks[check].item = itemPool.back(); + itemPool.pop_back(); + } + } + } +} + +// When a save is loaded, we want to unregister all hooks and re-register them if it's a rando save +void OnSaveLoadHandler(s16 fileNum) { + static uint32_t onFlagSetHook = 0; + static uint32_t onSceneFlagSetHook = 0; + static uint32_t onVanillaBehaviorHook = 0; + + GameInteractor::Instance->UnregisterGameHook(onFlagSetHook); + GameInteractor::Instance->UnregisterGameHook(onSceneFlagSetHook); + GameInteractor::Instance->UnregisterGameHook(onVanillaBehaviorHook); + + onFlagSetHook = 0; + onSceneFlagSetHook = 0; + onVanillaBehaviorHook = 0; + + Rando::InitActorBehavior(gSaveContext.save.shipSaveInfo.saveType == SAVETYPE_RANDO); + + if (gSaveContext.save.shipSaveInfo.saveType != SAVETYPE_RANDO) { + return; + } + + onFlagSetHook = GameInteractor::Instance->RegisterGameHook(RandomizerOnFlagSetHandler); + onSceneFlagSetHook = + GameInteractor::Instance->RegisterGameHook(RandomizerOnSceneFlagSetHandler); + onVanillaBehaviorHook = GameInteractor::Instance->RegisterGameHook( + RandomizerOnVanillaBehaviorHandler); +} + +// Entry point for the rando module +void Rando::Init() { + GameInteractor::Instance->RegisterGameHook(OnSaveInitHandler); + GameInteractor::Instance->RegisterGameHook(OnSaveLoadHandler); +} diff --git a/mm/2s2h/Rando/Rando.h b/mm/2s2h/Rando/Rando.h new file mode 100644 index 0000000000..a3832f1f4f --- /dev/null +++ b/mm/2s2h/Rando/Rando.h @@ -0,0 +1,13 @@ +#ifndef RANDO_H +#define RANDO_H + +#include "StaticData/StaticData.h" +#include "Types.h" + +namespace Rando { + +void Init(); + +} // namespace Rando + +#endif diff --git a/mm/2s2h/Rando/StaticData/Checks.cpp b/mm/2s2h/Rando/StaticData/Checks.cpp new file mode 100644 index 0000000000..b798419f74 --- /dev/null +++ b/mm/2s2h/Rando/StaticData/Checks.cpp @@ -0,0 +1,38 @@ +#include "StaticData.h" + +namespace Rando { + +namespace StaticData { + +// clang-format off +std::unordered_map Checks = { + { RC_UNKNOWN, { RC_UNKNOWN, RCTYPE_UNKNOWN, SCENE_MAX, FLAG_NONE, 0x00, RI_UNKNOWN } }, + { RC_S_CLOCK_TOWN_POH, { RC_S_CLOCK_TOWN_POH, RCTYPE_FREESTANDING, SCENE_CLOCKTOWER, FLAG_CYCL_SCENE_COLLECTIBLE, 0x0A, RI_PIECE_OF_HEART } }, + { RC_S_CLOCK_TOWN_UPPER_CHEST, { RC_S_CLOCK_TOWN_UPPER_CHEST, RCTYPE_CHEST, SCENE_CLOCKTOWER, FLAG_CYCL_SCENE_CHEST, 0x00, RI_RED_RUPEE } }, + { RC_S_CLOCK_TOWN_CARPENTER_CHEST, { RC_S_CLOCK_TOWN_CARPENTER_CHEST, RCTYPE_CHEST, SCENE_CLOCKTOWER, FLAG_CYCL_SCENE_CHEST, 0x01, RI_PURPLE_RUPEE } }, + { RC_N_CLOCK_TOWN_POH, { RC_N_CLOCK_TOWN_POH, RCTYPE_FREESTANDING, SCENE_BACKTOWN, FLAG_CYCL_SCENE_COLLECTIBLE, 0x0A, RI_PIECE_OF_HEART } }, + { RC_N_CLOCK_TOWN_POH, { RC_N_CLOCK_TOWN_POH, RCTYPE_FREESTANDING, SCENE_BACKTOWN, FLAG_CYCL_SCENE_COLLECTIBLE, 0x0A, RI_PIECE_OF_HEART } }, + { RC_E_CLOCK_TOWN_UPPER_CHEST, { RC_E_CLOCK_TOWN_UPPER_CHEST, RCTYPE_CHEST, SCENE_TOWN, FLAG_CYCL_SCENE_CHEST, 0x0A, RI_SILVER_RUPEE } }, + { RC_LAUNDRY_DUDE, { RC_LAUNDRY_DUDE, RCTYPE_UNKNOWN, SCENE_ALLEY, FLAG_WEEK_EVENT_REG, WEEKEVENTREG_38_40, RI_MASK_BREMEN } }, + { RC_CLOCK_TOWN_STRAY_FAIRY, { RC_CLOCK_TOWN_STRAY_FAIRY, RCTYPE_STRAY_FAIRY, SCENE_ALLEY, FLAG_WEEK_EVENT_REG, WEEKEVENTREG_08_80, RI_CLOCK_TOWN_STRAY_FAIRY } }, + // { RC_CLOCK_TOWN_GREAT_FAIRY, { RC_CLOCK_TOWN_GREAT_FAIRY, RCTYPE_UNKNOWN, SCENE_YOUSEI_IZUMI, FLAG_CYCL_SCENE_SWITCH, 0x0A, RI_PROGRESSIVE_MAGIC } }, + { RC_STOCK_POT_INN_GRANDMA_SHORT_STORY, { RC_STOCK_POT_INN_GRANDMA_SHORT_STORY, RCTYPE_UNKNOWN, SCENE_YADOYA, FLAG_WEEK_EVENT_REG, WEEKEVENTREG_BOMBERS_NOTEBOOK_EVENT_RECEIVED_GRANDMA_SHORT_STORY_HP, RI_PIECE_OF_HEART } }, + { RC_STOCK_POT_INN_GRANDMA_LONG_STORY, { RC_STOCK_POT_INN_GRANDMA_LONG_STORY, RCTYPE_UNKNOWN, SCENE_YADOYA, FLAG_WEEK_EVENT_REG, WEEKEVENTREG_BOMBERS_NOTEBOOK_EVENT_RECEIVED_GRANDMA_LONG_STORY_HP, RI_PIECE_OF_HEART } }, + { RC_STOCK_POT_INN_ROOM_KEY, { RC_STOCK_POT_INN_ROOM_KEY, RCTYPE_UNKNOWN, SCENE_YADOYA, FLAG_WEEK_EVENT_REG, WEEKEVENTREG_RECEIVED_ROOM_KEY, RI_ROOM_KEY } }, + { RC_WOODFALL_LOBBY_STRAY_FAIRY, { RC_WOODFALL_LOBBY_STRAY_FAIRY, RCTYPE_STRAY_FAIRY, SCENE_MITURIN, FLAG_CYCL_SCENE_SWITCH, 0x2B, RI_WOODFALL_STRAY_FAIRY } }, + { RC_MAX, { RC_UNKNOWN, RCTYPE_UNKNOWN, SCENE_MAX, FLAG_NONE, 0x00, RI_UNKNOWN } }, +}; +// clang-format on + +RandoCheckData GetCheckFromFlag(FlagType flagType, s32 flag, s16 scene) { + for (auto& [check, data] : Checks) { + if (data.flagType == flagType && data.flag == flag && (scene == SCENE_MAX || data.scene == scene)) { + return data; + } + } + return Checks[RC_UNKNOWN]; +} + +} // namespace StaticData + +} // namespace Rando diff --git a/mm/2s2h/Rando/StaticData/Items.cpp b/mm/2s2h/Rando/StaticData/Items.cpp new file mode 100644 index 0000000000..2b07c95b20 --- /dev/null +++ b/mm/2s2h/Rando/StaticData/Items.cpp @@ -0,0 +1,27 @@ +#include "StaticData.h" + +namespace Rando { + +namespace StaticData { + +// clang-format off +std::unordered_map Items = { + { RI_UNKNOWN, { "Unknown", ITEM_NONE, GI_NONE, GID_NONE } }, + { RI_PIECE_OF_HEART, { "Piece of Heart", ITEM_HEART_PIECE, GI_HEART_PIECE, GID_HEART_PIECE } }, + { RI_CLOCK_TOWN_STRAY_FAIRY, { "Clock Town Stray Fairy", ITEM_STRAY_FAIRIES, GI_STRAY_FAIRY, GID_NONE } }, + { RI_PROGRESSIVE_MAGIC, { "Progressive Magic Meter", ITEM_NONE, GI_NONE, GID_MAGIC_JAR_SMALL } }, + { RI_GREEN_RUPEE, { "Green Rupee", ITEM_RUPEE_GREEN, GI_RUPEE_GREEN, GID_RUPEE_GREEN } }, + { RI_BLUE_RUPEE, { "Blue Rupee", ITEM_RUPEE_BLUE, GI_RUPEE_BLUE, GID_RUPEE_BLUE } }, + { RI_RED_RUPEE, { "Red Rupee", ITEM_RUPEE_RED, GI_RUPEE_RED, GID_RUPEE_RED } }, + { RI_PURPLE_RUPEE, { "Purple Rupee", ITEM_RUPEE_PURPLE, GI_RUPEE_PURPLE, GID_RUPEE_PURPLE } }, + { RI_SILVER_RUPEE, { "Silver Rupee", ITEM_RUPEE_SILVER, GI_RUPEE_SILVER, GID_RUPEE_SILVER } }, + { RI_ROOM_KEY, { "Room Key", ITEM_ROOM_KEY, GI_ROOM_KEY, GID_ROOM_KEY } }, + { RI_MASK_BREMEN, { "Bremen Mask", ITEM_MASK_BREMEN, GI_MASK_BREMEN, GID_MASK_BREMEN } }, + { RI_WOODFALL_STRAY_FAIRY, { "Woodfall Stray Fairy", ITEM_STRAY_FAIRIES, GI_STRAY_FAIRY, GID_NONE } }, + { RI_MAX, { "Unknown", ITEM_NONE, GI_MAX, GID_MAXIMUM } }, +}; +// clang-format on + +} // namespace StaticData + +} // namespace Rando diff --git a/mm/2s2h/Rando/StaticData/StaticData.h b/mm/2s2h/Rando/StaticData/StaticData.h new file mode 100644 index 0000000000..2f2ac40299 --- /dev/null +++ b/mm/2s2h/Rando/StaticData/StaticData.h @@ -0,0 +1,43 @@ +#ifndef RANDO_STATIC_DATA_H +#define RANDO_STATIC_DATA_H + +#include +#include "Rando/Types.h" +#include "Enhancements/GameInteractor/GameInteractor.h" + +extern "C" { +#include "z64item.h" +#include "z64scene.h" +} + +namespace Rando { + +namespace StaticData { + +struct RandoCheckData { + RandoCheck check; + RandoCheckType type; + SceneId scene; + FlagType flagType; + s32 flag; + RandoItem item; +}; + +extern std::unordered_map Checks; + +struct RandoItemData { + const char* name; + ItemId itemId; + GetItemId getItemId; + GetItemDrawId drawId; +}; + +extern std::unordered_map Items; + +RandoCheckData GetCheckFromFlag(FlagType flagType, s32 flag, s16 scene = SCENE_MAX); + +} // namespace StaticData + +} // namespace Rando + +#endif // RANDO_STATIC_DATA_H diff --git a/mm/2s2h/Rando/Types.h b/mm/2s2h/Rando/Types.h new file mode 100644 index 0000000000..b894967c93 --- /dev/null +++ b/mm/2s2h/Rando/Types.h @@ -0,0 +1,47 @@ +#ifndef RANDO_TYPES_H +#define RANDO_TYPES_H + +typedef enum { + RCTYPE_UNKNOWN, + RCTYPE_CHEST, + RCTYPE_FREESTANDING, + RCTYPE_SKULL_TOKEN, + RCTYPE_SHOP, + RCTYPE_STRAY_FAIRY, + RCTYPE_MAX, +} RandoCheckType; + +typedef enum { + RC_UNKNOWN, + RC_S_CLOCK_TOWN_POH, + RC_S_CLOCK_TOWN_UPPER_CHEST, + RC_S_CLOCK_TOWN_CARPENTER_CHEST, + RC_N_CLOCK_TOWN_POH, + RC_E_CLOCK_TOWN_UPPER_CHEST, + RC_LAUNDRY_DUDE, + RC_CLOCK_TOWN_STRAY_FAIRY, + RC_CLOCK_TOWN_GREAT_FAIRY, + RC_STOCK_POT_INN_GRANDMA_SHORT_STORY, + RC_STOCK_POT_INN_GRANDMA_LONG_STORY, + RC_STOCK_POT_INN_ROOM_KEY, + RC_WOODFALL_LOBBY_STRAY_FAIRY, + RC_MAX, +} RandoCheck; + +typedef enum { + RI_UNKNOWN, + RI_PIECE_OF_HEART, + RI_CLOCK_TOWN_STRAY_FAIRY, + RI_PROGRESSIVE_MAGIC, + RI_GREEN_RUPEE, + RI_BLUE_RUPEE, + RI_RED_RUPEE, + RI_PURPLE_RUPEE, + RI_SILVER_RUPEE, + RI_ROOM_KEY, + RI_MASK_BREMEN, + RI_WOODFALL_STRAY_FAIRY, + RI_MAX, +} RandoItem; + +#endif // RANDO_TYPES_H diff --git a/mm/2s2h/SaveManager/Migrations/6.cpp b/mm/2s2h/SaveManager/Migrations/6.cpp new file mode 100644 index 0000000000..a82c2b4026 --- /dev/null +++ b/mm/2s2h/SaveManager/Migrations/6.cpp @@ -0,0 +1,9 @@ +#include "2s2h/SaveManager/SaveManager.h" +#include "z64.h" + +void SaveManager_Migration_6(nlohmann::json& j) { + // if saveType doesn't exist, create it and set it to vanilla + if (!j["save"]["shipSaveInfo"].contains("saveType")) { + j["save"]["shipSaveInfo"]["saveType"] = SAVETYPE_VANILLA; + } +} diff --git a/mm/2s2h/SaveManager/SaveManager.cpp b/mm/2s2h/SaveManager/SaveManager.cpp index a841ca4396..a9aa9aa318 100644 --- a/mm/2s2h/SaveManager/SaveManager.cpp +++ b/mm/2s2h/SaveManager/SaveManager.cpp @@ -49,13 +49,14 @@ const std::filesystem::path savesFolderPath(Ship::Context::GetPathRelativeToAppD // - Create the migration file in the Migrations folder with the name `{CURRENT_SAVE_VERSION}.cpp` // - Add the migration function definition below and add it to the `migrations` map with the key being the previous // version -const uint32_t CURRENT_SAVE_VERSION = 5; +const uint32_t CURRENT_SAVE_VERSION = 6; void SaveManager_Migration_1(nlohmann::json& j); void SaveManager_Migration_2(nlohmann::json& j); void SaveManager_Migration_3(nlohmann::json& j); void SaveManager_Migration_4(nlohmann::json& j); void SaveManager_Migration_5(nlohmann::json& j); +void SaveManager_Migration_6(nlohmann::json& j); const std::unordered_map> migrations = { // Pre-1.0.0 Migrations, deprecated @@ -65,6 +66,7 @@ const std::unordered_map> migrati { 3, SaveManager_Migration_4 }, // Base Migration { 4, SaveManager_Migration_5 }, + { 5, SaveManager_Migration_6 }, }; int SaveManager_MigrateSave(nlohmann::json& j) { diff --git a/mm/CMakeLists.txt b/mm/CMakeLists.txt index 43c9aa7ec3..aa998ffcb5 100644 --- a/mm/CMakeLists.txt +++ b/mm/CMakeLists.txt @@ -178,6 +178,13 @@ file(GLOB_RECURSE soh__CustomMessage RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "2s2h/CustomMessage/*.hpp" ) +file(GLOB_RECURSE soh__Rando RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + "2s2h/Rando/*.c" + "2s2h/Rando/*.cpp" + "2s2h/Rando/*.h" + "2s2h/Rando/*.hpp" +) + #list(REMOVE_ITEM soh__Enhancements "soh/Enhancements/gamecommand.h") #list(FILTER soh__Enhancements EXCLUDE REGEX "soh/Enhancements/gfx.*") @@ -292,6 +299,7 @@ set(ALL_FILES ${soh__BenGui} ${soh__SaveManager} ${soh__CustomMessage} + ${soh__Rando} ${soh__Extractor} ${soh__Resource} ${src__} diff --git a/mm/include/z64save.h b/mm/include/z64save.h index 73cc19e1f4..5ab223c978 100644 --- a/mm/include/z64save.h +++ b/mm/include/z64save.h @@ -7,6 +7,7 @@ #include "z64math.h" #include "unk.h" #include "z64item.h" +#include "Rando/Types.h" struct GameState; struct PlayState; @@ -325,11 +326,28 @@ typedef struct DpadSaveInfo { u8 dpadSlots[4][4]; } DpadSaveInfo; +typedef enum { + SAVETYPE_VANILLA, + SAVETYPE_RANDO, +} SaveType; + +typedef struct RandoSaveInfoCheck { + RandoItem item; + bool eligible; + bool obtained; +} RandoSaveInfoCheck; + +typedef struct RandoSaveInfo { + RandoSaveInfoCheck checks[RC_MAX]; +} RandoSaveInfo; + // These are values added by 2S2H that we need to be persisted to the save file // See `ShipSaveContext` for values on the SaveContext that aren't persisted. typedef struct ShipSaveInfo { DpadSaveInfo dpadEquips; s32 pauseSaveEntrance; + SaveType saveType; + RandoSaveInfo rando; } ShipSaveInfo; // #endregion diff --git a/mm/src/code/z_msgevent.c b/mm/src/code/z_msgevent.c index 469d46c775..a5c32f0db2 100644 --- a/mm/src/code/z_msgevent.c +++ b/mm/src/code/z_msgevent.c @@ -1,4 +1,5 @@ #include "global.h" +#include "2s2h/Enhancements/GameInteractor/GameInteractor.h" #define MSCRIPT_CONTINUE 0 #define MSCRIPT_STOP 1 @@ -145,7 +146,7 @@ s32 MsgEvent_Cmd06(Actor* actor, PlayState* play, u8** scriptPtr, MsgEventCallba f32 yRange = fabsf(actor->playerHeightRel) + 1.0f; s16 skip = MSCRIPT_GET_16(script, 3); - if (Actor_HasParent(actor, play)) { + if (Actor_HasParent(actor, play) || !GameInteractor_Should(GI_VB_GIVE_ITEM_FROM_SCRIPT, true, &getItemId)) { *scriptPtr += skip; } else { Actor_OfferGetItem(actor, play, getItemId, xzRange, yRange); diff --git a/mm/src/code/z_sram_NES.c b/mm/src/code/z_sram_NES.c index 77977d9cbb..3a0786514c 100644 --- a/mm/src/code/z_sram_NES.c +++ b/mm/src/code/z_sram_NES.c @@ -982,6 +982,7 @@ void Sram_InitNewSave(void) { // #region 2S2H memcpy(&gSaveContext.save.shipSaveInfo.dpadEquips, &sSaveDefaultDpadItemEquips, sizeof(DpadSaveInfo)); gSaveContext.save.shipSaveInfo.pauseSaveEntrance = -1; + gSaveContext.save.shipSaveInfo.saveType = SAVETYPE_VANILLA; // #endregion Sram_GenerateRandomSaveFields(); @@ -1207,6 +1208,7 @@ void Sram_InitDebugSave(void) { // #region 2S2H memcpy(&gSaveContext.save.shipSaveInfo.dpadEquips, &sSaveDefaultDpadItemEquips, sizeof(DpadSaveInfo)); gSaveContext.save.shipSaveInfo.pauseSaveEntrance = -1; + gSaveContext.save.shipSaveInfo.saveType = SAVETYPE_VANILLA; // #endregion Sram_GenerateRandomSaveFields(); diff --git a/mm/src/overlays/actors/ovl_En_Guruguru/z_en_guruguru.c b/mm/src/overlays/actors/ovl_En_Guruguru/z_en_guruguru.c index dec53e5f99..79e0745dea 100644 --- a/mm/src/overlays/actors/ovl_En_Guruguru/z_en_guruguru.c +++ b/mm/src/overlays/actors/ovl_En_Guruguru/z_en_guruguru.c @@ -5,6 +5,7 @@ */ #include "z_en_guruguru.h" +#include "Enhancements/GameInteractor/GameInteractor.h" #define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_10) @@ -307,7 +308,7 @@ void func_80BC73F4(EnGuruguru* this) { void func_80BC7440(EnGuruguru* this, PlayState* play) { SkelAnime_Update(&this->skelAnime); - if (Actor_HasParent(&this->actor, play)) { + if (Actor_HasParent(&this->actor, play) || !GameInteractor_Should(GI_VB_GIVE_ITEM_FROM_GURUGURU, true, NULL)) { this->actor.parent = NULL; this->textIdIndex++; this->actor.textId = textIDs[this->textIdIndex]; diff --git a/mm/src/overlays/actors/ovl_player_actor/z_player.c b/mm/src/overlays/actors/ovl_player_actor/z_player.c index dc3678105f..00f102fbf1 100644 --- a/mm/src/overlays/actors/ovl_player_actor/z_player.c +++ b/mm/src/overlays/actors/ovl_player_actor/z_player.c @@ -9229,7 +9229,11 @@ s32 Player_ActionChange_2(Player* this, PlayState* play) { giEntry = &sGetItemTable[-this->getItemId - 1]; } - func_80832558(play, this, func_80837C78); + if (GameInteractor_Should(GI_VB_GIVE_ITEM_FROM_CHEST, true, chest)) { + // This inverts the sign of the getItemId and sets the player's action to GetItem + // (Player_Action_65) + func_80832558(play, this, func_80837C78); + } this->stateFlags1 |= (PLAYER_STATE1_400 | PLAYER_STATE1_800 | PLAYER_STATE1_20000000); func_80838830(this, giEntry->objectId); diff --git a/mm/src/overlays/gamestates/ovl_file_choose/z_file_choose_NES.c b/mm/src/overlays/gamestates/ovl_file_choose/z_file_choose_NES.c index 6befa39484..ae698e10a9 100644 --- a/mm/src/overlays/gamestates/ovl_file_choose/z_file_choose_NES.c +++ b/mm/src/overlays/gamestates/ovl_file_choose/z_file_choose_NES.c @@ -13,6 +13,7 @@ #include "interface/parameter_static/parameter_static.h" #include "misc/title_static/title_static.h" #include "2s2h/Enhancements/FrameInterpolation/FrameInterpolation.h" +#include "2s2h/Enhancements/GameInteractor/GameInteractor.h" #include "2s2h_assets.h" #include #include "BenPort.h" @@ -2231,6 +2232,8 @@ void FileSelect_LoadGame(GameState* thisx) { gSaveContext.hudVisibilityTimer = 0; gSaveContext.save.saveInfo.playerData.tatlTimer = 0; + + GameInteractor_ExecuteOnSaveLoad(gSaveContext.fileNum); } void (*sSelectModeUpdateFuncs[])(GameState*) = {