Adds `OnTransitionEnd` hook to `GameInteractor`, called at the end of a transition after all mid-transition setup is called. (#2635)

Transitions final autosave location to `OnTransitionEnd`.

Adds functionality to autosave to set a delayed save flag if AutoSave is set to Major or All Items and one is obtained in a grotto, to avoid grotto autosaving but still provide for the autosave when location-based saving is off.

Fixes small bug with item lookup where sometimes an item's `modIndex` would sometimes be reported one way, but the way Randomizer does things it would be in a the other `modIndex`, and the lookup would fail.

Minor variable name clarification in ItemTableManager.

Modifies AutoSave to account for item ID overlap from `MOD_RANDOMIZER` table (all items in the randomizer table is considered major for AutoSave purposes except ice traps).
This commit is contained in:
Malkierian 2023-04-02 01:47:23 -07:00 committed by GitHub
parent b099b5649b
commit 04d0cd8532
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 237 additions and 70 deletions

View File

@ -146,6 +146,7 @@ public:
DEFINE_HOOK(OnGameFrameUpdate, void());
DEFINE_HOOK(OnItemReceive, void(GetItemEntry itemEntry));
DEFINE_HOOK(OnSaleEnd, void(GetItemEntry itemEntry));
DEFINE_HOOK(OnTransitionEnd, void(int16_t sceneNum));
DEFINE_HOOK(OnSceneInit, void(int16_t sceneNum));
DEFINE_HOOK(OnPlayerUpdate, void());
DEFINE_HOOK(OnActorUpdate, void(void* actor));

View File

@ -22,6 +22,10 @@ void GameInteractor_ExecuteOnSaleEndHooks(GetItemEntry itemEntry) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSaleEnd>(itemEntry);
}
void GameInteractor_ExecuteOnTransitionEndHooks(int16_t sceneNum) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnTransitionEnd>(sceneNum);
}
void GameInteractor_ExecuteOnSceneInitHooks(int16_t sceneNum) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSceneInit>(sceneNum);
}

View File

@ -6,6 +6,7 @@ extern "C" void GameInteractor_ExecuteOnExitGame(int32_t fileNum);
extern "C" void GameInteractor_ExecuteOnGameFrameUpdate();
extern "C" void GameInteractor_ExecuteOnItemReceiveHooks(GetItemEntry itemEntry);
extern "C" void GameInteractor_ExecuteOnSaleEndHooks(GetItemEntry itemEntry);
extern "C" void GameInteractor_ExecuteOnTransitionEndHooks(int16_t sceneNum);
extern "C" void GameInteractor_ExecuteOnSceneInit(int16_t sceneNum);
extern "C" void GameInteractor_ExecuteOnPlayerUpdate();
extern "C" void GameInteractor_ExecuteOnActorUpdate(void* actor);

View File

@ -20,10 +20,10 @@ bool ItemTableManager::AddItemEntry(uint16_t tableID, uint16_t getItemID, GetIte
} catch (const std::out_of_range& oor) { return false; }
}
GetItemEntry ItemTableManager::RetrieveItemEntry(uint16_t tableID, uint16_t itemID) {
GetItemEntry ItemTableManager::RetrieveItemEntry(uint16_t tableID, uint16_t getItemID) {
try {
ItemTable* itemTable = RetrieveItemTable(tableID);
GetItemEntry getItemEntry = itemTable->at(itemID);
GetItemEntry getItemEntry = itemTable->at(getItemID);
getItemEntry.drawItemId = getItemEntry.itemId;
getItemEntry.drawModIndex = getItemEntry.modIndex;
return getItemEntry;

View File

@ -13,7 +13,7 @@ class ItemTableManager {
~ItemTableManager();
bool AddItemTable(uint16_t tableID);
bool AddItemEntry(uint16_t tableID, uint16_t getItemID, GetItemEntry getItemEntry);
GetItemEntry RetrieveItemEntry(uint16_t tableID, uint16_t itemID);
GetItemEntry RetrieveItemEntry(uint16_t tableID, uint16_t getItemID);
bool ClearItemTable(uint16_t tableID);
private:

View File

@ -11,6 +11,8 @@ extern "C" {
extern SaveContext gSaveContext;
extern PlayState* gPlayState;
}
bool performDelayedSave = false;
bool performSave = false;
void RegisterInfiniteMoney() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([]() {
@ -177,60 +179,81 @@ void AutoSave(GetItemEntry itemEntry) {
// Don't autosave during the Ganon fight when picking up the Master Sword
// Don't autosave in grottos since resuming from grottos breaks the game.
if ((CVarGetInteger("gAutosave", 0) > 0) && (gPlayState != NULL) && (gSaveContext.pendingSale == ITEM_NONE) &&
(gPlayState->sceneNum != SCENE_YOUSEI_IZUMI_TATE) && (gPlayState->sceneNum != SCENE_KAKUSIANA) &&
(gPlayState->sceneNum != SCENE_KENJYANOMA) && (gPlayState->sceneNum != SCENE_GANON_DEMO) &&
(gPlayState->gameplayFrames > 60 && gSaveContext.cutsceneIndex < 0xFFF0)) {
if ((CVarGetInteger("gAutosave", 0) == 2) || (CVarGetInteger("gAutosave", 0) == 5) && (item != ITEM_NONE)) {
(gPlayState->gameplayFrames > 60 && gSaveContext.cutsceneIndex < 0xFFF0) && (gPlayState->sceneNum != SCENE_GANON_DEMO)) {
if (((CVarGetInteger("gAutosave", 0) == 2) || (CVarGetInteger("gAutosave", 0) == 5)) && (item != ITEM_NONE)) {
// Autosave for all items
Play_PerformSave(gPlayState);
performSave = true;
} else if ((CVarGetInteger("gAutosave", 0) == 1) || (CVarGetInteger("gAutosave", 0) == 4) && (item != ITEM_NONE)) {
} else if (((CVarGetInteger("gAutosave", 0) == 1) || (CVarGetInteger("gAutosave", 0) == 4)) && (item != ITEM_NONE)) {
// Autosave for major items
switch (item) {
case ITEM_STICK:
case ITEM_NUT:
case ITEM_BOMB:
case ITEM_BOW:
case ITEM_SEEDS:
case ITEM_FISHING_POLE:
case ITEM_MAGIC_SMALL:
case ITEM_MAGIC_LARGE:
case ITEM_INVALID_4:
case ITEM_INVALID_5:
case ITEM_INVALID_6:
case ITEM_INVALID_7:
case ITEM_HEART:
case ITEM_RUPEE_GREEN:
case ITEM_RUPEE_BLUE:
case ITEM_RUPEE_RED:
case ITEM_RUPEE_PURPLE:
case ITEM_RUPEE_GOLD:
case ITEM_INVALID_8:
case ITEM_STICKS_5:
case ITEM_STICKS_10:
case ITEM_NUTS_5:
case ITEM_NUTS_10:
case ITEM_BOMBS_5:
case ITEM_BOMBS_10:
case ITEM_BOMBS_20:
case ITEM_BOMBS_30:
case ITEM_ARROWS_SMALL:
case ITEM_ARROWS_MEDIUM:
case ITEM_ARROWS_LARGE:
case ITEM_SEEDS_30:
case ITEM_NONE:
break;
case ITEM_BOMBCHU:
case ITEM_BOMBCHUS_5:
case ITEM_BOMBCHUS_20:
if (!CVarGetInteger("gBombchuDrops", 0)) {
Play_PerformSave(gPlayState);
}
break;
default:
Play_PerformSave(gPlayState);
break;
if (itemEntry.modIndex == 0) {
switch (item) {
case ITEM_STICK:
case ITEM_NUT:
case ITEM_BOMB:
case ITEM_BOW:
case ITEM_SEEDS:
case ITEM_FISHING_POLE:
case ITEM_MAGIC_SMALL:
case ITEM_MAGIC_LARGE:
case ITEM_INVALID_4:
case ITEM_INVALID_5:
case ITEM_INVALID_6:
case ITEM_INVALID_7:
case ITEM_HEART:
case ITEM_RUPEE_GREEN:
case ITEM_RUPEE_BLUE:
case ITEM_RUPEE_RED:
case ITEM_RUPEE_PURPLE:
case ITEM_RUPEE_GOLD:
case ITEM_INVALID_8:
case ITEM_STICKS_5:
case ITEM_STICKS_10:
case ITEM_NUTS_5:
case ITEM_NUTS_10:
case ITEM_BOMBS_5:
case ITEM_BOMBS_10:
case ITEM_BOMBS_20:
case ITEM_BOMBS_30:
case ITEM_ARROWS_SMALL:
case ITEM_ARROWS_MEDIUM:
case ITEM_ARROWS_LARGE:
case ITEM_SEEDS_30:
case ITEM_NONE:
break;
case ITEM_BOMBCHU:
case ITEM_BOMBCHUS_5:
case ITEM_BOMBCHUS_20:
if (!CVarGetInteger("gBombchuDrops", 0)) {
performSave = true;
}
break;
default:
performSave = true;
break;
}
} else if (itemEntry.modIndex == 1 && item != RG_ICE_TRAP) {
performSave = true;
}
} else if ((CVarGetInteger("gAutosave", 0) > 0 && (CVarGetInteger("gAutosave", 0) < 4))) {
performSave = true;
}
if ((gPlayState->sceneNum == SCENE_YOUSEI_IZUMI_TATE) || (gPlayState->sceneNum == SCENE_KAKUSIANA) ||
(gPlayState->sceneNum == SCENE_KENJYANOMA)) {
if ((CVarGetInteger("gAutosave", 0) > 0 && (CVarGetInteger("gAutosave", 0) < 4))) {
performSave = false;
return;
}
if (performSave) {
performSave = false;
performDelayedSave = true;
}
return;
}
if (performSave || performDelayedSave) {
Play_PerformSave(gPlayState);
performSave = false;
performDelayedSave = false;
}
}
}
@ -238,6 +261,7 @@ void AutoSave(GetItemEntry itemEntry) {
void RegisterAutoSave() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnItemReceive>([](GetItemEntry itemEntry) { AutoSave(itemEntry); });
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSaleEnd>([](GetItemEntry itemEntry) { AutoSave(itemEntry); });
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnTransitionEnd>([](int32_t sceneNum) { AutoSave(GET_ITEM_NONE); });
}
void RegisterRupeeDash() {

View File

@ -553,6 +553,138 @@ extern "C" void VanillaItemTable_Init() {
}
}
std::unordered_map<uint32_t, uint32_t> ItemIDtoGetItemID{
{ ITEM_ARROWS_LARGE, GI_ARROWS_LARGE },
{ ITEM_ARROWS_MEDIUM, GI_ARROWS_MEDIUM },
{ ITEM_ARROWS_SMALL, GI_ARROWS_SMALL },
{ ITEM_ARROW_FIRE, GI_ARROW_FIRE },
{ ITEM_ARROW_ICE, GI_ARROW_ICE },
{ ITEM_ARROW_LIGHT, GI_ARROW_LIGHT },
{ ITEM_BEAN, GI_BEAN },
{ ITEM_BIG_POE, GI_BIG_POE },
{ ITEM_BLUE_FIRE, GI_BLUE_FIRE },
{ ITEM_BOMB, GI_BOMBS_1 },
{ ITEM_BOMBCHU, GI_BOMBCHUS_10 },
{ ITEM_BOMBCHUS_20, GI_BOMBCHUS_20 },
{ ITEM_BOMBCHUS_5, GI_BOMBCHUS_5 },
{ ITEM_BOMBS_10, GI_BOMBS_10 },
{ ITEM_BOMBS_20, GI_BOMBS_20 },
{ ITEM_BOMBS_30, GI_BOMBS_30 },
{ ITEM_BOMBS_5, GI_BOMBS_5 },
{ ITEM_BOMB_BAG_20, GI_BOMB_BAG_20 },
{ ITEM_BOMB_BAG_30, GI_BOMB_BAG_30 },
{ ITEM_BOMB_BAG_40, GI_BOMB_BAG_40 },
{ ITEM_BOOMERANG, GI_BOOMERANG },
{ ITEM_BOOTS_HOVER, GI_BOOTS_HOVER },
{ ITEM_BOOTS_IRON, GI_BOOTS_IRON },
{ ITEM_BOTTLE, GI_BOTTLE },
{ ITEM_BOW, GI_BOW },
{ ITEM_BRACELET, GI_BRACELET },
{ ITEM_BUG, GI_BUGS },
{ ITEM_BULLET_BAG_30, GI_BULLET_BAG_30 },
{ ITEM_BULLET_BAG_40, GI_BULLET_BAG_40 },
{ ITEM_BULLET_BAG_50, GI_BULLET_BAG_50 }, { ITEM_CHICKEN, GI_CHICKEN },
{ ITEM_CLAIM_CHECK, GI_CLAIM_CHECK },
{ ITEM_COJIRO, GI_COJIRO },
{ ITEM_COMPASS, GI_COMPASS },
{ ITEM_DINS_FIRE, GI_DINS_FIRE },
{ ITEM_DUNGEON_MAP, GI_MAP },
{ ITEM_EYEDROPS, GI_EYEDROPS },
{ ITEM_FAIRY, GI_FAIRY },
{ ITEM_FARORES_WIND, GI_FARORES_WIND },
{ ITEM_FISH, GI_FISH },
{ ITEM_FROG, GI_FROG },
{ ITEM_GAUNTLETS_GOLD, GI_GAUNTLETS_GOLD },
{ ITEM_GAUNTLETS_SILVER, GI_GAUNTLETS_SILVER },
{ ITEM_GERUDO_CARD, GI_GERUDO_CARD },
{ ITEM_HAMMER, GI_HAMMER },
{ ITEM_HEART, GI_HEART },
{ ITEM_HEART_CONTAINER, GI_HEART_CONTAINER },
{ ITEM_HEART_CONTAINER, GI_HEART_CONTAINER_2 },
{ ITEM_HEART_PIECE_2, GI_HEART_PIECE },
{ ITEM_HEART_PIECE_2, GI_HEART_PIECE_WIN },
{ ITEM_HOOKSHOT, GI_HOOKSHOT },
{ ITEM_KEY_BOSS, GI_KEY_BOSS },
{ ITEM_KEY_SMALL, GI_DOOR_KEY },
{ ITEM_KEY_SMALL, GI_KEY_SMALL },
{ ITEM_LENS, GI_LENS },
{ ITEM_LETTER_RUTO, GI_LETTER_RUTO },
{ ITEM_LETTER_ZELDA, GI_LETTER_ZELDA },
{ ITEM_LONGSHOT, GI_LONGSHOT },
{ ITEM_MAGIC_LARGE, GI_MAGIC_LARGE },
{ ITEM_MAGIC_SMALL, GI_MAGIC_SMALL },
{ ITEM_MASK_BUNNY, GI_MASK_BUNNY },
{ ITEM_MASK_GERUDO, GI_MASK_GERUDO },
{ ITEM_MASK_GORON, GI_MASK_GORON },
{ ITEM_MASK_KEATON, GI_MASK_KEATON },
{ ITEM_MASK_SKULL, GI_MASK_SKULL },
{ ITEM_MASK_SPOOKY, GI_MASK_SPOOKY },
{ ITEM_MASK_TRUTH, GI_MASK_TRUTH },
{ ITEM_MASK_ZORA, GI_MASK_ZORA },
{ ITEM_MILK, GI_MILK },
{ ITEM_MILK_BOTTLE, GI_MILK_BOTTLE },
{ ITEM_NAYRUS_LOVE, GI_NAYRUS_LOVE },
{ ITEM_NUT, GI_NUTS_5 },
{ ITEM_NUTS_10, GI_NUTS_10 },
{ ITEM_NUTS_5, GI_NUTS_5 },
{ ITEM_NUTS_5, GI_NUTS_5_2 },
{ ITEM_NUT_UPGRADE_30, GI_NUT_UPGRADE_30 },
{ ITEM_NUT_UPGRADE_40, GI_NUT_UPGRADE_40 },
{ ITEM_OCARINA_FAIRY, GI_OCARINA_FAIRY },
{ ITEM_OCARINA_TIME, GI_OCARINA_OOT },
{ ITEM_ODD_MUSHROOM, GI_ODD_MUSHROOM },
{ ITEM_ODD_POTION, GI_ODD_POTION },
{ ITEM_POCKET_CUCCO, GI_POCKET_CUCCO },
{ ITEM_POCKET_EGG, GI_POCKET_EGG },
{ ITEM_POE, GI_POE },
{ ITEM_POTION_BLUE, GI_POTION_BLUE },
{ ITEM_POTION_GREEN, GI_POTION_GREEN },
{ ITEM_POTION_RED, GI_POTION_RED },
{ ITEM_PRESCRIPTION, GI_PRESCRIPTION },
{ ITEM_QUIVER_40, GI_QUIVER_40 },
{ ITEM_QUIVER_50, GI_QUIVER_50 },
{ ITEM_RUPEE_BLUE, GI_RUPEE_BLUE },
{ ITEM_RUPEE_BLUE, GI_RUPEE_BLUE_LOSE },
{ ITEM_RUPEE_GOLD, GI_RUPEE_GOLD },
{ ITEM_RUPEE_GREEN, GI_RUPEE_GREEN },
{ ITEM_RUPEE_GREEN, GI_RUPEE_GREEN_LOSE },
{ ITEM_RUPEE_PURPLE, GI_RUPEE_PURPLE },
{ ITEM_RUPEE_PURPLE, GI_RUPEE_PURPLE_LOSE },
{ ITEM_RUPEE_RED, GI_RUPEE_RED },
{ ITEM_RUPEE_RED, GI_RUPEE_RED_LOSE },
{ ITEM_SAW, GI_SAW },
{ ITEM_SCALE_GOLDEN, GI_SCALE_GOLD },
{ ITEM_SCALE_SILVER, GI_SCALE_SILVER },
{ ITEM_SEEDS, GI_SEEDS_5 },
{ ITEM_SEEDS_30, GI_SEEDS_30 },
{ ITEM_SHIELD_DEKU, GI_SHIELD_DEKU },
{ ITEM_SHIELD_HYLIAN, GI_SHIELD_HYLIAN },
{ ITEM_SHIELD_MIRROR, GI_SHIELD_MIRROR },
{ ITEM_SKULL_TOKEN, GI_SKULL_TOKEN },
{ ITEM_SLINGSHOT, GI_SLINGSHOT },
{ ITEM_STICK, GI_STICKS_1 },
{ ITEM_STICKS_10, GI_STICKS_10 },
{ ITEM_STICKS_5, GI_STICKS_5 },
{ ITEM_STICK_UPGRADE_20, GI_STICK_UPGRADE_20 },
{ ITEM_STICK_UPGRADE_30, GI_STICK_UPGRADE_30 },
{ ITEM_STONE_OF_AGONY, GI_STONE_OF_AGONY },
{ ITEM_SWORD_BGS, GI_SWORD_BGS },
{ ITEM_SWORD_BGS, GI_SWORD_KNIFE },
{ ITEM_SWORD_BROKEN, GI_SWORD_BROKEN },
{ ITEM_SWORD_KOKIRI, GI_SWORD_KOKIRI },
{ ITEM_TUNIC_GORON, GI_TUNIC_GORON },
{ ITEM_TUNIC_ZORA, GI_TUNIC_ZORA },
{ ITEM_WALLET_ADULT, GI_WALLET_ADULT },
{ ITEM_WALLET_GIANT, GI_WALLET_GIANT },
{ ITEM_WEIRD_EGG, GI_WEIRD_EGG }
};
extern "C" uint32_t GetGIID(uint32_t itemID) {
if (ItemIDtoGetItemID.contains(itemID))
return ItemIDtoGetItemID.at(itemID);
return -1;
}
extern "C" void OTRExtScanner() {
auto lst = *OTRGlobals::Instance->context->GetResourceManager()->ListFiles("*.*").get();

View File

@ -138,6 +138,8 @@ void Entrance_ClearEntranceTrackingData(void);
void Entrance_InitEntranceTrackingData(void);
void EntranceTracker_SetCurrentGrottoID(s16 entranceIndex);
void EntranceTracker_SetLastEntranceOverride(s16 entranceIndex);
uint32_t GetGIID(uint32_t itemID);
#endif
#endif

View File

@ -1704,8 +1704,13 @@ u8 Return_Item_Entry(GetItemEntry itemEntry, ItemID returnItem ) {
}
// Processes Item_Give returns
u8 Return_Item(u8 item, ModIndex modId, ItemID returnItem) {
return Return_Item_Entry(ItemTable_RetrieveEntry(modId, item), returnItem);
u8 Return_Item(u8 itemID, ModIndex modId, ItemID returnItem) {
uint32_t get = GetGIID(itemID);
if (get == -1) {
modId = MOD_RANDOMIZER;
get = itemID;
}
return Return_Item_Entry(ItemTable_RetrieveEntry(modId, get), returnItem);
}
/**
@ -2313,7 +2318,7 @@ u8 Item_Give(PlayState* play, u8 item) {
}
u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) {
uint16_t item = giEntry.itemId;
uint16_t item = giEntry.getItemId;
uint16_t temp;
uint16_t i;
uint16_t slot;
@ -6196,11 +6201,16 @@ void Interface_Update(PlayState* play) {
Audio_PlaySoundGeneral(NA_SE_SY_RUPY_COUNT, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
}
if (gSaveContext.rupeeAccumulator == 0) {
u16 tempSaleItem = gSaveContext.pendingSale;
u16 tempSaleMod = gSaveContext.pendingSaleMod;
gSaveContext.pendingSale = ITEM_NONE;
gSaveContext.pendingSaleMod = MOD_NONE;
GameInteractor_ExecuteOnSaleEndHooks(ItemTable_RetrieveEntry(tempSaleMod,tempSaleItem));
if (gSaveContext.pendingSale != ITEM_NONE) {
u16 tempSaleItem = gSaveContext.pendingSale;
u16 tempSaleMod = gSaveContext.pendingSaleMod;
gSaveContext.pendingSale = ITEM_NONE;
gSaveContext.pendingSaleMod = MOD_NONE;
if (tempSaleMod == 0) {
tempSaleItem = GetGIID(tempSaleItem);
}
GameInteractor_ExecuteOnSaleEndHooks(ItemTable_RetrieveEntry(tempSaleMod, tempSaleItem));
}
}
} else {
gSaveContext.rupeeAccumulator = 0;

View File

@ -869,15 +869,8 @@ void Play_Update(PlayState* play) {
gTrnsnUnkState = 0;
R_UPDATE_RATE = 3;
}
// Autosave on area transition unless you're in a cutscene or a grotto since resuming from grottos breaks the game
// Also don't save when you first load a file to prevent consumables like magic from being lost
// Also don't save if there's a pending shop sale to prevent getting the item for a discount!
if ((CVarGetInteger("gAutosave", 0) >= 1) && (CVarGetInteger("gAutosave", 0) <= 3) &&
(gSaveContext.cutsceneIndex < 0xFFF0) && (play->gameplayFrames > 60) && (gSaveContext.pendingSale == ITEM_NONE) &&
(play->sceneNum != SCENE_YOUSEI_IZUMI_TATE) && (play->sceneNum != SCENE_KAKUSIANA) && (play->sceneNum != SCENE_KENJYANOMA)) {
Play_PerformSave(play);
}
GameInteractor_ExecuteOnTransitionEndHooks(play->sceneNum);
}
play->sceneLoadFlag = 0;
} else {