diff --git a/soh/include/z64save.h b/soh/include/z64save.h index 4aa540e12..77f7061ec 100644 --- a/soh/include/z64save.h +++ b/soh/include/z64save.h @@ -25,6 +25,12 @@ typedef struct { /* 0x5C */ s16 gsTokens; } Inventory; // size = 0x5E +typedef struct { + /* */ u8 heartPieces; + /* */ u8 heartContainers; + /* */ u8 dungeonKeys[19]; +} SohStats; + typedef struct { /* 0x00 */ u32 chest; /* 0x04 */ u32 swch; @@ -189,6 +195,7 @@ typedef struct { u16 adultTradeItems; u8 pendingIceTrapCount; u8 mqDungeonCount; + SohStats sohStats; } SaveContext; // size = 0x1428 typedef enum { diff --git a/soh/soh/Enhancements/custom-message/CustomMessageTypes.h b/soh/soh/Enhancements/custom-message/CustomMessageTypes.h index d00178757..991a44b19 100644 --- a/soh/soh/Enhancements/custom-message/CustomMessageTypes.h +++ b/soh/soh/Enhancements/custom-message/CustomMessageTypes.h @@ -14,6 +14,8 @@ typedef enum { TEXT_ALTAR_ADULT = 0x7088, TEXT_GANONDORF = 0x70CC, TEXT_GANONDORF_NOHINT = 0x70CD, + TEXT_HEART_CONTAINER = 0xC6, + TEXT_HEART_PIECE = 0xC2, TEXT_BLUE_RUPEE = 0xCC, TEXT_RED_RUPEE = 0xF0, TEXT_PURPLE_RUPEE = 0xF1, diff --git a/soh/soh/Enhancements/debugger/debugSaveEditor.cpp b/soh/soh/Enhancements/debugger/debugSaveEditor.cpp index d4c7fbea9..2d2ce0ccd 100644 --- a/soh/soh/Enhancements/debugger/debugSaveEditor.cpp +++ b/soh/soh/Enhancements/debugger/debugSaveEditor.cpp @@ -134,6 +134,7 @@ std::map itemMapping = { ITEM_MAP_ENTRY(ITEM_DUNGEON_MAP), ITEM_MAP_ENTRY(ITEM_KEY_SMALL), ITEM_MAP_ENTRY(ITEM_HEART_CONTAINER), + ITEM_MAP_ENTRY(ITEM_HEART_PIECE), ITEM_MAP_ENTRY(ITEM_MAGIC_SMALL), ITEM_MAP_ENTRY(ITEM_MAGIC_LARGE) }; @@ -1302,7 +1303,9 @@ void DrawQuestStatusTab() { float lineHeight = ImGui::GetTextLineHeightWithSpacing(); ImGui::Image(SohImGui::GetTextureByName(itemMapping[ITEM_KEY_SMALL].name), ImVec2(lineHeight, lineHeight)); ImGui::SameLine(); - ImGui::InputScalar("##Keys", ImGuiDataType_S8, gSaveContext.inventory.dungeonKeys + dungeonItemsScene); + if (ImGui::InputScalar("##Keys", ImGuiDataType_S8, gSaveContext.inventory.dungeonKeys + dungeonItemsScene)) { + gSaveContext.sohStats.dungeonKeys[dungeonItemsScene] = gSaveContext.inventory.dungeonKeys[dungeonItemsScene]; + }; } else { // dungeonItems is size 20 but dungeonKeys is size 19, so there are no keys for the last scene (Barinade's Lair) ImGui::Text("Barinade's Lair does not have small keys"); diff --git a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp index 612b56ad6..47ac3d94f 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp @@ -55,8 +55,8 @@ std::vector equipmentItems = { std::vector miscItems = { ITEM_TRACKER_ITEM(ITEM_BRACELET, 0, DrawItem), ITEM_TRACKER_ITEM(ITEM_SCALE_SILVER, 0, DrawItem), ITEM_TRACKER_ITEM(ITEM_WALLET_ADULT, 0, DrawItem), - ITEM_TRACKER_ITEM(ITEM_HEART_CONTAINER, 0, DrawItem), ITEM_TRACKER_ITEM(ITEM_MAGIC_SMALL, 0, DrawItem), ITEM_TRACKER_ITEM(QUEST_STONE_OF_AGONY, 1 << 21, DrawQuest), - ITEM_TRACKER_ITEM(QUEST_GERUDO_CARD, 1 << 22, DrawQuest), ITEM_TRACKER_ITEM(QUEST_SKULL_TOKEN, 1 << 23, DrawQuest), + ITEM_TRACKER_ITEM(ITEM_HEART_CONTAINER, 0, DrawItem), ITEM_TRACKER_ITEM(ITEM_HEART_PIECE, 0, DrawItem), ITEM_TRACKER_ITEM(ITEM_MAGIC_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM(QUEST_GERUDO_CARD, 1 << 22, DrawQuest), ITEM_TRACKER_ITEM(QUEST_SKULL_TOKEN, 1 << 23, DrawQuest), ITEM_TRACKER_ITEM(QUEST_STONE_OF_AGONY, 1 << 21, DrawQuest), }; std::vector dungeonRewardStones = { @@ -297,35 +297,46 @@ ItemTrackerNumbers GetItemCurrentAndMax(ItemTrackerItem item) { result.maxCapacity = result.currentCapacity = 100; result.currentAmmo = gSaveContext.inventory.gsTokens; break; + case ITEM_HEART_CONTAINER: + result.maxCapacity = result.currentCapacity = 8; + result.currentAmmo = gSaveContext.sohStats.heartContainers; + break; + case ITEM_HEART_PIECE: + result.maxCapacity = result.currentCapacity = 36; + result.currentAmmo = gSaveContext.sohStats.heartPieces; + break; case ITEM_KEY_SMALL: + // Though the ammo/capacity naming doesn't really make sense for keys, we are + // hijacking the same system to display key counts as there are enough similarities result.currentAmmo = MAX(gSaveContext.inventory.dungeonKeys[item.data], 0); + result.currentCapacity = gSaveContext.sohStats.dungeonKeys[item.data]; switch (item.data) { case SCENE_BMORI1: - result.maxCapacity = result.currentCapacity = FOREST_TEMPLE_SMALL_KEY_MAX; + result.maxCapacity = FOREST_TEMPLE_SMALL_KEY_MAX; break; case SCENE_HIDAN: - result.maxCapacity = result.currentCapacity = FIRE_TEMPLE_SMALL_KEY_MAX; + result.maxCapacity = FIRE_TEMPLE_SMALL_KEY_MAX; break; case SCENE_MIZUSIN: - result.maxCapacity = result.currentCapacity = WATER_TEMPLE_SMALL_KEY_MAX; + result.maxCapacity = WATER_TEMPLE_SMALL_KEY_MAX; break; case SCENE_JYASINZOU: - result.maxCapacity = result.currentCapacity = SPIRIT_TEMPLE_SMALL_KEY_MAX; + result.maxCapacity = SPIRIT_TEMPLE_SMALL_KEY_MAX; break; case SCENE_HAKADAN: - result.maxCapacity = result.currentCapacity = SHADOW_TEMPLE_SMALL_KEY_MAX; + result.maxCapacity = SHADOW_TEMPLE_SMALL_KEY_MAX; break; case SCENE_HAKADANCH: - result.maxCapacity = result.currentCapacity = BOTTOM_OF_THE_WELL_SMALL_KEY_MAX; + result.maxCapacity = BOTTOM_OF_THE_WELL_SMALL_KEY_MAX; break; case SCENE_MEN: - result.maxCapacity = result.currentCapacity = GERUDO_TRAINING_GROUNDS_SMALL_KEY_MAX; + result.maxCapacity = GERUDO_TRAINING_GROUNDS_SMALL_KEY_MAX; break; case SCENE_GERUDOWAY: - result.maxCapacity = result.currentCapacity = GERUDO_FORTRESS_SMALL_KEY_MAX; + result.maxCapacity = GERUDO_FORTRESS_SMALL_KEY_MAX; break; case SCENE_GANONTIKA: - result.maxCapacity = result.currentCapacity = GANONS_CASTLE_SMALL_KEY_MAX; + result.maxCapacity = GANONS_CASTLE_SMALL_KEY_MAX; break; } break; @@ -345,8 +356,32 @@ void DrawItemCount(ItemTrackerItem item) { ItemTrackerNumbers currentAndMax = GetItemCurrentAndMax(item); ImVec2 p = ImGui::GetCursorScreenPos(); int32_t trackerNumberDisplayMode = CVar_GetS32("gItemTrackerCapacityTrack", 1); + int32_t trackerKeyNumberDisplayMode = CVar_GetS32("gItemTrackerKeyTrack", 0); - if (currentAndMax.currentCapacity > 0 && trackerNumberDisplayMode != ITEM_TRACKER_NUMBER_NONE && IsValidSaveFile()) { + if (item.id == ITEM_KEY_SMALL && IsValidSaveFile()) { + std::string currentString = ""; + std::string maxString = std::to_string(currentAndMax.maxCapacity); + ImU32 currentColor = IM_COL_WHITE; + ImU32 maxColor = IM_COL_GREEN; + // "Collected / Max", "Current / Collected / Max", "Current / Max" + if (trackerKeyNumberDisplayMode == 1 || trackerKeyNumberDisplayMode == 2) { + currentString+= std::to_string(currentAndMax.currentAmmo); + currentString+= "/"; + } + if (trackerKeyNumberDisplayMode == 0 || trackerKeyNumberDisplayMode == 1) { + currentString+= std::to_string(currentAndMax.currentCapacity); + currentString+= "/"; + } + + ImGui::SetCursorScreenPos(ImVec2(p.x + (iconSize / 2) - (ImGui::CalcTextSize((currentString + maxString).c_str()).x / 2), p.y - 14)); + ImGui::PushStyleColor(ImGuiCol_Text, currentColor); + ImGui::Text(currentString.c_str()); + ImGui::PopStyleColor(); + ImGui::SameLine(0, 0.0f); + ImGui::PushStyleColor(ImGuiCol_Text, maxColor); + ImGui::Text(maxString.c_str()); + ImGui::PopStyleColor(); + } else if (currentAndMax.currentCapacity > 0 && trackerNumberDisplayMode != ITEM_TRACKER_NUMBER_NONE && IsValidSaveFile()) { std::string currentString = ""; std::string maxString = ""; ImU32 currentColor = IM_COL_WHITE; @@ -362,7 +397,8 @@ void DrawItemCount(ItemTrackerItem item) { item.id == ITEM_BOMBCHU || item.id == ITEM_BEAN || item.id == QUEST_SKULL_TOKEN || - item.id == ITEM_KEY_SMALL; + item.id == ITEM_HEART_CONTAINER || + item.id == ITEM_HEART_PIECE; bool shouldDisplayMax = !(trackerNumberDisplayMode == ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY || trackerNumberDisplayMode == ITEM_TRACKER_NUMBER_CURRENT_AMMO_ONLY); @@ -445,7 +481,11 @@ void DrawItem(ItemTrackerItem item) { switch (item.id) { case ITEM_HEART_CONTAINER: actualItemId = item.id; - hasItem = !!gSaveContext.doubleDefense; + hasItem = gSaveContext.sohStats.heartContainers > 0; + break; + case ITEM_HEART_PIECE: + actualItemId = item.id; + hasItem = gSaveContext.sohStats.heartPieces > 0; break; case ITEM_MAGIC_SMALL: case ITEM_MAGIC_LARGE: @@ -793,9 +833,6 @@ void UpdateVectors() { mainWindowItems.insert(mainWindowItems.end(), miscItems.begin(), miscItems.end()); } if (CVar_GetS32("gItemTrackerDungeonRewardsDisplayType", 1) == 1) { - if (CVar_GetS32("gItemTrackerMiscItemsDisplayType", 1) == 1) { - mainWindowItems.push_back(ITEM_TRACKER_ITEM(ITEM_NONE, 0, DrawItem)); - } mainWindowItems.insert(mainWindowItems.end(), dungeonRewardStones.begin(), dungeonRewardStones.end()); mainWindowItems.insert(mainWindowItems.end(), dungeonRewardMedallions.begin(), dungeonRewardMedallions.end()); } @@ -804,7 +841,6 @@ void UpdateVectors() { mainWindowItems.push_back(ITEM_TRACKER_ITEM(ITEM_NONE, 0, DrawItem)); mainWindowItems.push_back(ITEM_TRACKER_ITEM(ITEM_NONE, 0, DrawItem)); mainWindowItems.push_back(ITEM_TRACKER_ITEM(ITEM_NONE, 0, DrawItem)); - mainWindowItems.push_back(ITEM_TRACKER_ITEM(ITEM_NONE, 0, DrawItem)); } mainWindowItems.insert(mainWindowItems.end(), songItems.begin(), songItems.end()); } @@ -910,6 +946,7 @@ void DrawItemTracker(bool& open) { } const char* itemTrackerCapacityTrackOptions[5] = { "No Numbers", "Current Capacity", "Current Ammo", "Current Capacity / Max Capacity", "Current Ammo / Current Capacity" }; +const char* itemTrackerKeyTrackOptions[3] = { "Collected / Max", "Current / Collected / Max", "Current / Max" }; void DrawItemTrackerOptions(bool& open) { if (!open) { @@ -965,6 +1002,9 @@ void DrawItemTrackerOptions(bool& open) { if (CVar_GetS32("gItemTrackerCapacityTrack", 1) == ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY || CVar_GetS32("gItemTrackerCapacityTrack", 1) == ITEM_TRACKER_NUMBER_CURRENT_AMMO_ONLY) { PaddedEnhancementCheckbox("Align count to left side", "gItemTrackerCurrentOnLeft", 0); } + ImGui::Text("Key Count Tracking"); + UIWidgets::EnhancementCombobox("gItemTrackerKeyTrack", itemTrackerKeyTrackOptions, 3, 0); + UIWidgets::InsertHelpHoverText("Customize what numbers are shown for key tracking."); ImGui::TableNextColumn(); diff --git a/soh/soh/GameMenuBar.cpp b/soh/soh/GameMenuBar.cpp index 2ac4688dd..caffb04d2 100644 --- a/soh/soh/GameMenuBar.cpp +++ b/soh/soh/GameMenuBar.cpp @@ -254,8 +254,8 @@ namespace GameMenuBar { CVar_SetS32("gGuardVision", 0); // Enable passage of time on file select CVar_SetS32("gTimeFlowFileSelect", 0); - // Count Golden Skulltulas - CVar_SetS32("gInjectSkulltulaCount", 0); + // Inject Item Counts in messages + CVar_SetS32("gInjectItemCounts", 0); // Pull grave during the day CVar_SetS32("gDayGravePull", 0); // Pull out Ocarina to Summon Scarecrow @@ -353,8 +353,8 @@ namespace GameMenuBar { CVar_SetS32("gAssignableTunicsAndBoots", 1); // Enable passage of time on file select CVar_SetS32("gTimeFlowFileSelect", 1); - // Count Golden Skulltulas - CVar_SetS32("gInjectSkulltulaCount", 1); + // Inject Item Counts in messages + CVar_SetS32("gInjectItemCounts", 1); // Pause link animation (0 to 16) CVar_SetS32("gPauseLiveLink", 1); @@ -998,8 +998,8 @@ namespace GameMenuBar { UIWidgets::Tooltip("Allows the Lon Lon Ranch obstacle course reward to be shared across time periods"); UIWidgets::PaddedEnhancementCheckbox("Enable visible guard vision", "gGuardVision", true, false); UIWidgets::PaddedEnhancementCheckbox("Enable passage of time on file select", "gTimeFlowFileSelect", true, false); - UIWidgets::PaddedEnhancementCheckbox("Count Golden Skulltulas", "gInjectSkulltulaCount", true, false); - UIWidgets::Tooltip("Injects Golden Skulltula total count in pickup messages"); + UIWidgets::PaddedEnhancementCheckbox("Item counts in messages", "gInjectItemCounts", true, false); + UIWidgets::Tooltip("Injects item counts in pickup messages, like golden skulltula tokens and heart pieces"); UIWidgets::PaddedEnhancementCheckbox("Pull grave during the day", "gDayGravePull", true, false); UIWidgets::Tooltip("Allows graves to be pulled when child during the day"); diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 15128f9f4..ff538be42 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -1986,7 +1986,7 @@ extern "C" int CustomMessage_RetrieveIfExists(GlobalContext* globalCtx) { } } if (textId == TEXT_GS_NO_FREEZE || textId == TEXT_GS_FREEZE) { - if (CVar_GetS32("gInjectSkulltulaCount", 0) != 0) { + if (CVar_GetS32("gInjectItemCounts", 0) != 0) { // The freeze text cannot be manually dismissed and must be auto-dismissed. // This is fine and even wanted when skull tokens are not shuffled, but when // when they are shuffled we don't want to be able to manually dismiss the box. @@ -2004,6 +2004,14 @@ extern "C" int CustomMessage_RetrieveIfExists(GlobalContext* globalCtx) { CustomMessageManager::ReplaceStringInMessage(messageEntry, "{{gsCount}}", std::to_string(gSaveContext.inventory.gsTokens + 1)); } } + if (textId == TEXT_HEART_CONTAINER && CVar_GetS32("gInjectItemCounts", 0)) { + messageEntry = CustomMessageManager::Instance->RetrieveMessage(customMessageTableID, TEXT_HEART_CONTAINER); + CustomMessageManager::ReplaceStringInMessage(messageEntry, "{{heartContainerCount}}", std::to_string(gSaveContext.sohStats.heartContainers + 1)); + } + if (textId == TEXT_HEART_PIECE && CVar_GetS32("gInjectItemCounts", 0)) { + messageEntry = CustomMessageManager::Instance->RetrieveMessage(customMessageTableID, TEXT_HEART_PIECE); + CustomMessageManager::ReplaceStringInMessage(messageEntry, "{{heartPieceCount}}", std::to_string(gSaveContext.sohStats.heartPieces + 1)); + } if (messageEntry.textBoxType != -1) { font->charTexBuf[0] = (messageEntry.textBoxType << 4) | messageEntry.textBoxPos; switch (gSaveContext.language) { diff --git a/soh/soh/SaveManager.cpp b/soh/soh/SaveManager.cpp index f8ca5bfb4..f6c10e929 100644 --- a/soh/soh/SaveManager.cpp +++ b/soh/soh/SaveManager.cpp @@ -416,6 +416,11 @@ void SaveManager::InitFileNormal() { } gSaveContext.inventory.defenseHearts = 0; gSaveContext.inventory.gsTokens = 0; + gSaveContext.sohStats.heartPieces = 0; + gSaveContext.sohStats.heartContainers = 0; + for (int dungeon = 0; dungeon < ARRAY_COUNT(gSaveContext.sohStats.dungeonKeys); dungeon++) { + gSaveContext.sohStats.dungeonKeys[dungeon] = 0; + } for (int scene = 0; scene < ARRAY_COUNT(gSaveContext.sceneFlags); scene++) { gSaveContext.sceneFlags[scene].chest = 0; gSaveContext.sceneFlags[scene].swch = 0; @@ -561,6 +566,11 @@ void SaveManager::InitFileDebug() { } gSaveContext.inventory.defenseHearts = 0; gSaveContext.inventory.gsTokens = 0; + gSaveContext.sohStats.heartPieces = 8; + gSaveContext.sohStats.heartContainers = 8; + for (int dungeon = 0; dungeon < ARRAY_COUNT(gSaveContext.sohStats.dungeonKeys); dungeon++) { + gSaveContext.sohStats.dungeonKeys[dungeon] = 8; + } gSaveContext.horseData.scene = SCENE_SPOT00; gSaveContext.horseData.pos.x = -1840; @@ -955,6 +965,13 @@ void SaveManager::LoadBaseVersion2() { SaveManager::Instance->LoadData("defenseHearts", gSaveContext.inventory.defenseHearts); SaveManager::Instance->LoadData("gsTokens", gSaveContext.inventory.gsTokens); }); + SaveManager::Instance->LoadStruct("sohStats", []() { + SaveManager::Instance->LoadData("heartPieces", gSaveContext.sohStats.heartPieces); + SaveManager::Instance->LoadData("heartContainers", gSaveContext.sohStats.heartContainers); + SaveManager::Instance->LoadArray("dungeonKeys", ARRAY_COUNT(gSaveContext.sohStats.dungeonKeys), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.sohStats.dungeonKeys[i]); + }); + }); SaveManager::Instance->LoadArray("sceneFlags", ARRAY_COUNT(gSaveContext.sceneFlags), [](size_t i) { SaveManager::Instance->LoadStruct("", [&i]() { SaveManager::Instance->LoadData("chest", gSaveContext.sceneFlags[i].chest); @@ -1109,6 +1126,13 @@ void SaveManager::SaveBase() { SaveManager::Instance->SaveData("defenseHearts", gSaveContext.inventory.defenseHearts); SaveManager::Instance->SaveData("gsTokens", gSaveContext.inventory.gsTokens); }); + SaveManager::Instance->SaveStruct("sohStats", []() { + SaveManager::Instance->SaveData("heartPieces", gSaveContext.sohStats.heartPieces); + SaveManager::Instance->SaveData("heartContainers", gSaveContext.sohStats.heartContainers); + SaveManager::Instance->SaveArray("dungeonKeys", ARRAY_COUNT(gSaveContext.sohStats.dungeonKeys), [](size_t i) { + SaveManager::Instance->SaveData("", gSaveContext.sohStats.dungeonKeys[i]); + }); + }); SaveManager::Instance->SaveArray("sceneFlags", ARRAY_COUNT(gSaveContext.sceneFlags), [](size_t i) { SaveManager::Instance->SaveStruct("", [&i]() { SaveManager::Instance->SaveData("chest", gSaveContext.sceneFlags[i].chest); diff --git a/soh/soh/util.cpp b/soh/soh/util.cpp index 7a5d0205d..da36b986c 100644 --- a/soh/soh/util.cpp +++ b/soh/soh/util.cpp @@ -75,7 +75,7 @@ std::vector sceneNames = { "Castle Hedge Maze (Day)", "Castle Hedge Maze (Night)", "Cutscene Map", - "Dampé's Grave & Windmill", + "Dampďż˝'s Grave & Windmill", "Fishing Pond", "Castle Courtyard", "Bombchu Bowling Alley", @@ -231,7 +231,7 @@ std::vector itemNames = { "Gerudo's Card", "Gold Skulltula Token", "Heart Container", - "Piece of Heart [?]", + "Piece of Heart", "Big Key", "Compass", "Dungeon Map", diff --git a/soh/soh/z_message_OTR.cpp b/soh/soh/z_message_OTR.cpp index c5794a94f..0cef7c8c8 100644 --- a/soh/soh/z_message_OTR.cpp +++ b/soh/soh/z_message_OTR.cpp @@ -132,4 +132,22 @@ extern "C" void OTRMessage_Init() "\x08Missiles 10 unitĂ©s 99 Rubis\x09&&\x1B%gAcheter&Ne pas acheter%w", } ); + CustomMessageManager::Instance->CreateGetItemMessage( + customMessageTableID, (GetItemID)TEXT_HEART_CONTAINER, ITEM_HEART_CONTAINER, + { + TEXTBOX_TYPE_BLUE, TEXTBOX_POS_BOTTOM, + "You got a %rHeart Container%w!&You've collected %r{{heartContainerCount}}%w containers&in total!", + "Du erhältst ein %rHerzgefäß%w! Du&hast insgesamt %r{{heartContainerCount}}%w Gefäße&gesammelt!", + "Vous obtenez un %rRĂ©cipient de&coeur%w! Vous avez&collectĂ© %r{{heartContainerCount}}%w rĂ©cipients en tout!" + } + ); + CustomMessageManager::Instance->CreateGetItemMessage( + customMessageTableID, (GetItemID)TEXT_HEART_PIECE, ITEM_HEART_PIECE, + { + TEXTBOX_TYPE_BLUE, TEXTBOX_POS_BOTTOM, + "You got a %rHeart Piece%w!&You've collected %r{{heartPieceCount}}%w pieces&in total!", + "Du erhältst ein %rHerzteil%w! Du hast&insgesamt %r{{heartPieceCount}}%w Teile&gesammelt!", + "Vous obtenez un %rMorceau de&coeur%w! Vous avez&collectĂ© %r{{heartPieceCount}}%w morceaux en tout!" + } + ); } diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index b19ba1631..c37586cce 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -1760,6 +1760,7 @@ u8 Item_Give(GlobalContext* globalCtx, u8 item) { // but we check for a globalCtx here so the game won't crash if we do somehow get here. if (gSaveContext.n64ddFlag && globalCtx != NULL) { if (globalCtx->sceneNum == 10) { // ganon's tower -> ganon's castle + gSaveContext.sohStats.dungeonKeys[13]++; if (gSaveContext.inventory.dungeonKeys[13] < 0) { gSaveContext.inventory.dungeonKeys[13] = 1; PerformAutosave(globalCtx, item); @@ -1772,6 +1773,7 @@ u8 Item_Give(GlobalContext* globalCtx, u8 item) { } if (globalCtx->sceneNum == 92) { // Desert Colossus -> Spirit Temple. + gSaveContext.sohStats.dungeonKeys[6]++; if (gSaveContext.inventory.dungeonKeys[6] < 0) { gSaveContext.inventory.dungeonKeys[6] = 1; PerformAutosave(globalCtx, item); @@ -1783,6 +1785,7 @@ u8 Item_Give(GlobalContext* globalCtx, u8 item) { } } } + gSaveContext.sohStats.dungeonKeys[gSaveContext.mapIndex]++; if (gSaveContext.inventory.dungeonKeys[gSaveContext.mapIndex] < 0) { gSaveContext.inventory.dungeonKeys[gSaveContext.mapIndex] = 1; PerformAutosave(globalCtx, item); @@ -2132,11 +2135,13 @@ u8 Item_Give(GlobalContext* globalCtx, u8 item) { return ITEM_NONE; } else if ((item == ITEM_HEART_PIECE_2) || (item == ITEM_HEART_PIECE)) { gSaveContext.inventory.questItems += 1 << (QUEST_HEART_PIECE + 4); + gSaveContext.sohStats.heartPieces++; PerformAutosave(globalCtx, item); return ITEM_NONE; } else if (item == ITEM_HEART_CONTAINER) { gSaveContext.healthCapacity += 0x10; gSaveContext.health += 0x10; + gSaveContext.sohStats.heartContainers++; PerformAutosave(globalCtx, item); return ITEM_NONE; } else if (item == ITEM_HEART) { @@ -2447,6 +2452,7 @@ u16 Randomizer_Item_Give(GlobalContext* globalCtx, GetItemEntry giEntry) { } if ((item >= RG_FOREST_TEMPLE_SMALL_KEY) && (item <= RG_GANONS_CASTLE_SMALL_KEY)) { + gSaveContext.sohStats.dungeonKeys[mapIndex]++; if (gSaveContext.inventory.dungeonKeys[mapIndex] < 0) { gSaveContext.inventory.dungeonKeys[mapIndex] = 1; return RG_NONE; diff --git a/soh/src/code/z_sram.c b/soh/src/code/z_sram.c index 61e8fecbb..b43515386 100644 --- a/soh/src/code/z_sram.c +++ b/soh/src/code/z_sram.c @@ -461,13 +461,21 @@ void Sram_InitSave(FileChooseContext* fileChooseCtx) { if(Randomizer_GetSettingValue(RSK_KEYSANITY) == 0) { // TODO: If master quest there are different key counts gSaveContext.inventory.dungeonKeys[SCENE_BMORI1] = FOREST_TEMPLE_SMALL_KEY_MAX; // Forest + gSaveContext.sohStats.dungeonKeys[SCENE_BMORI1] = FOREST_TEMPLE_SMALL_KEY_MAX; // Forest gSaveContext.inventory.dungeonKeys[SCENE_HIDAN] = FIRE_TEMPLE_SMALL_KEY_MAX; // Fire + gSaveContext.sohStats.dungeonKeys[SCENE_HIDAN] = FIRE_TEMPLE_SMALL_KEY_MAX; // Fire gSaveContext.inventory.dungeonKeys[SCENE_MIZUSIN] = WATER_TEMPLE_SMALL_KEY_MAX; // Water + gSaveContext.sohStats.dungeonKeys[SCENE_MIZUSIN] = WATER_TEMPLE_SMALL_KEY_MAX; // Water gSaveContext.inventory.dungeonKeys[SCENE_JYASINZOU] = SPIRIT_TEMPLE_SMALL_KEY_MAX; // Spirit + gSaveContext.sohStats.dungeonKeys[SCENE_JYASINZOU] = SPIRIT_TEMPLE_SMALL_KEY_MAX; // Spirit gSaveContext.inventory.dungeonKeys[SCENE_HAKADAN] = SHADOW_TEMPLE_SMALL_KEY_MAX; // Shadow + gSaveContext.sohStats.dungeonKeys[SCENE_HAKADAN] = SHADOW_TEMPLE_SMALL_KEY_MAX; // Shadow gSaveContext.inventory.dungeonKeys[SCENE_HAKADANCH] = BOTTOM_OF_THE_WELL_SMALL_KEY_MAX; // BotW + gSaveContext.sohStats.dungeonKeys[SCENE_HAKADANCH] = BOTTOM_OF_THE_WELL_SMALL_KEY_MAX; // BotW gSaveContext.inventory.dungeonKeys[SCENE_MEN] = GERUDO_TRAINING_GROUNDS_SMALL_KEY_MAX; // GTG + gSaveContext.sohStats.dungeonKeys[SCENE_MEN] = GERUDO_TRAINING_GROUNDS_SMALL_KEY_MAX; // GTG gSaveContext.inventory.dungeonKeys[SCENE_GANONTIKA] = GANONS_CASTLE_SMALL_KEY_MAX; // Ganon + gSaveContext.sohStats.dungeonKeys[SCENE_GANONTIKA] = GANONS_CASTLE_SMALL_KEY_MAX; // Ganon } // "Start with" == 0 for Boss Kesanity