diff --git a/CMakeLists.txt b/CMakeLists.txt index ab4367b8b..0cabaef20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,8 +5,8 @@ set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard to use") set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version") -project(Ship VERSION 8.0.3 LANGUAGES C CXX) -set(PROJECT_BUILD_NAME "MacReady Delta" CACHE STRING "") +project(Ship VERSION 8.0.4 LANGUAGES C CXX) +set(PROJECT_BUILD_NAME "MacReady Echo" CACHE STRING "") set(PROJECT_TEAM "github.com/harbourmasters" CACHE STRING "") set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT soh) diff --git a/libultraship b/libultraship index 4600eedcc..7a8d314a0 160000 --- a/libultraship +++ b/libultraship @@ -1 +1 @@ -Subproject commit 4600eedcc18f496319c99e07b8b2b4f11a0f6e64 +Subproject commit 7a8d314a0ecc7d6ece1b12626a0ae917ee4ed666 diff --git a/soh/soh/Enhancements/custom-message/CustomMessageTypes.h b/soh/soh/Enhancements/custom-message/CustomMessageTypes.h index 916e1a7f8..d93bae983 100644 --- a/soh/soh/Enhancements/custom-message/CustomMessageTypes.h +++ b/soh/soh/Enhancements/custom-message/CustomMessageTypes.h @@ -51,6 +51,7 @@ typedef enum { TEXT_WARP_NOCTURNE_OF_SHADOW = 0x891, TEXT_WARP_PRELUDE_OF_LIGHT = 0x892, TEXT_WARP_RANDOM_REPLACED_TEXT = 0x9200, + TEXT_SHOOTING_GALLERY_MAN_COME_BACK_WITH_BOW = 0x9210, TEXT_LAKE_HYLIA_WATER_SWITCH_SIGN = 0x346, // 0x3yy for cuttable sign range TEXT_LAKE_HYLIA_WATER_SWITCH_NAVI = 0x1B3, // 0x1yy for Navi msg range } TextIDs; diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor.h b/soh/soh/Enhancements/game-interactor/GameInteractor.h index 91244e592..be27e74a2 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor.h @@ -100,6 +100,7 @@ void GameInteractor_SetTriforceHuntCreditsWarpActive(uint8_t state); #include #include #include +#include #ifdef ENABLE_REMOTE_CONTROL #include @@ -218,6 +219,7 @@ public: DEFINE_HOOK(OnSetGameLanguage, void()); + DEFINE_HOOK(OnFileDropped, void(std::string filePath)); DEFINE_HOOK(OnAssetAltChange, void()); // Helpers diff --git a/soh/soh/Enhancements/gameplaystats.cpp b/soh/soh/Enhancements/gameplaystats.cpp index a6e5a47e8..efcf44efb 100644 --- a/soh/soh/Enhancements/gameplaystats.cpp +++ b/soh/soh/Enhancements/gameplaystats.cpp @@ -618,7 +618,7 @@ void DrawGameplayStatsOptionsTab() { } void GameplayStatsWindow::DrawElement() { - ImGui::SetNextWindowSize(ImVec2(480, 550), ImGuiCond_Appearing); + ImGui::SetNextWindowSize(ImVec2(480, 550), ImGuiCond_FirstUseEver); if (!ImGui::Begin("Gameplay Stats", &mIsVisible, ImGuiWindowFlags_NoFocusOnAppearing)) { ImGui::End(); return; diff --git a/soh/soh/Enhancements/mods.cpp b/soh/soh/Enhancements/mods.cpp index 039d11f16..7ad8473f1 100644 --- a/soh/soh/Enhancements/mods.cpp +++ b/soh/soh/Enhancements/mods.cpp @@ -1041,8 +1041,8 @@ void RegisterRandomizedEnemySizes() { uint8_t excludedEnemy = actor->id == ACTOR_EN_BROB || actor->id == ACTOR_EN_DHA || (actor->id == ACTOR_BOSS_SST && actor->params == -1); // Dodongo, Volvagia and Dead Hand are always smaller because they're impossible when bigger. - uint8_t smallOnlyEnemy = - actor->id == ACTOR_BOSS_DODONGO || actor->id == ACTOR_BOSS_FD || actor->id == ACTOR_BOSS_FD2 || ACTOR_EN_DH; + uint8_t smallOnlyEnemy = actor->id == ACTOR_BOSS_DODONGO || actor->id == ACTOR_BOSS_FD || + actor->id == ACTOR_BOSS_FD2 || actor->id == ACTOR_EN_DH; // Only apply to enemies and bosses. if (!CVarGetInteger("gRandomizedEnemySizes", 0) || (actor->category != ACTORCAT_ENEMY && actor->category != ACTORCAT_BOSS) || excludedEnemy) { diff --git a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_death_mountain.cpp b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_death_mountain.cpp index 8988b045b..19960f1be 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_death_mountain.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_death_mountain.cpp @@ -104,7 +104,7 @@ void AreaTable_Init_DeathMountain() { Entrance(DEATH_MOUNTAIN_TRAIL, {[]{return true;}}), Entrance(GC_WOODS_WARP, {[]{return GCWoodsWarpOpen;}}), Entrance(GC_SHOP, {[]{return (IsAdult && StopGCRollingGoronAsAdult) || (IsChild && (CanBlastOrSmash || GoronBracelet || GoronCityChildFire || CanUse(BOW)));}}), - Entrance(GC_DARUNIAS_CHAMBER, {[]{return (IsAdult && StopGCRollingGoronAsAdult) || GCDaruniasDoorOpenChild;}}), + Entrance(GC_DARUNIAS_CHAMBER, {[]{return (IsAdult && StopGCRollingGoronAsAdult) || (IsChild && GCDaruniasDoorOpenChild);}}), Entrance(GC_GROTTO_PLATFORM, {[]{return IsAdult && ((CanPlay(SongOfTime) && ((EffectiveHealth > 2) || CanUse(GORON_TUNIC) || CanUse(LONGSHOT) || CanUse(NAYRUS_LOVE))) || (EffectiveHealth > 1 && CanUse(GORON_TUNIC) && CanUse(HOOKSHOT)) || (CanUse(NAYRUS_LOVE) && CanUse(HOOKSHOT)) || (EffectiveHealth > 2 && CanUse(HOOKSHOT) && LogicGoronCityGrotto));}}), }); diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index 4609d0f98..a850222bc 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -365,31 +365,16 @@ std::unordered_map SpoilerfileSettingNameToEn #pragma GCC push_options #pragma GCC optimize ("O0") bool Randomizer::SpoilerFileExists(const char* spoilerFileName) { - try { - if (strcmp(spoilerFileName, "") != 0) { - std::ifstream spoilerFileStream(SohUtils::Sanitize(spoilerFileName)); - if (!spoilerFileStream) { - return false; - } - - json spoilerFileJson; - spoilerFileStream >> spoilerFileJson; - - if (!spoilerFileJson.contains("version") || !spoilerFileJson.contains("finalSeed")) { - return false; - } - + if (strcmp(spoilerFileName, "") != 0) { + std::ifstream spoilerFileStream(SohUtils::Sanitize(spoilerFileName)); + if (!spoilerFileStream) { + return false; + } else { return true; } - - return false; - } catch (std::exception& e) { - SPDLOG_ERROR("Error checking if spoiler file exists: {}", e.what()); - return false; - } catch (...) { - SPDLOG_ERROR("Error checking if spoiler file exists"); - return false; } + + return false; } #pragma GCC pop_options #pragma optimize("", on) @@ -495,6 +480,13 @@ void Randomizer::LoadHintLocations(const char* spoilerFileName) { "Zu {{location}}?\x1B&%gOK&No%w\x02", "Se téléporter vers&{{location}}?\x1B&%gOK!&Non%w\x02")); + // Bow Shooting Gallery reminder + CustomMessageManager::Instance->CreateMessage(Randomizer::hintMessageTableID, TEXT_SHOOTING_GALLERY_MAN_COME_BACK_WITH_BOW, + CustomMessage("Come back when you have your own&bow and you'll get a %rdifferent prize%w!", + "Komm wieder sobald du deinen eigenen&Bogen hast, um einen %rspeziellen Preis%w zu&erhalten!", + "J'aurai %rune autre récompense%w pour toi&lorsque tu auras ton propre arc.")); + + // Lake Hylia water level system CustomMessageManager::Instance->CreateMessage(Randomizer::hintMessageTableID, TEXT_LAKE_HYLIA_WATER_SWITCH_SIGN, CustomMessage("Water level control system.&Keep away!", "Wasserstand Kontrollsystem&Finger weg!", diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp index af3275fa8..4a9872102 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp @@ -78,6 +78,7 @@ bool initialized; bool doAreaScroll; bool previousShowHidden = false; bool hideShopRightChecks = true; +bool alwaysShowGS = false; std::map startingShopItem = { { SCENE_KOKIRI_SHOP, RC_KF_SHOP_ITEM_1 }, { SCENE_BAZAAR, RC_MARKET_BAZAAR_ITEM_1 }, @@ -434,7 +435,6 @@ void CheckTrackerLoadGame(int32_t fileNum) { } else { realRcObj = rcObj; } - if (!IsVisibleInCheckTracker(realRcObj)) continue; checksByArea.find(realRcObj.rcArea)->second.push_back(realRcObj); if (rcTrackerData.status == RCSHOW_SAVED || rcTrackerData.skipped) { @@ -514,6 +514,10 @@ void CheckTrackerTransition(uint32_t sceneNum) { } void CheckTrackerFrame() { + if (IS_RANDO) { + hideShopRightChecks = CVarGetInteger("gCheckTrackerOptionHideRightShopChecks", 1); + alwaysShowGS = CVarGetInteger("gCheckTrackerOptionAlwaysShowGSLocs", 0); + } if (!GameInteractor::IsSaveLoaded()) { return; } @@ -1084,7 +1088,6 @@ void LoadSettings() { showLinksPocket = IS_RANDO ? // don't show Link's Pocket if not randomizer, or if rando and pocket is disabled OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_LINKS_POCKET) != RO_LINKS_POCKET_NOTHING :false; - hideShopRightChecks = IS_RANDO ? CVarGetInteger("gCheckTrackerOptionHideRightShopChecks", 1) : false; if (IS_RANDO) { switch (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_TOKENS)) { @@ -1148,7 +1151,7 @@ bool IsVisibleInCheckTracker(RandomizerCheckObject rcObj) { ) && (rcObj.rcType != RCTYPE_MERCHANT || showMerchants) && (rcObj.rcType != RCTYPE_OCARINA || showOcarinas) && - (rcObj.rcType != RCTYPE_SKULL_TOKEN || + (rcObj.rcType != RCTYPE_SKULL_TOKEN || alwaysShowGS || (showOverworldTokens && RandomizerCheckObjects::AreaIsOverworld(rcObj.rcArea)) || (showDungeonTokens && RandomizerCheckObjects::AreaIsDungeon(rcObj.rcArea)) ) && @@ -1518,7 +1521,9 @@ void CheckTrackerSettingsWindow::DrawElement() { UIWidgets::EnhancementCheckbox("Vanilla/MQ Dungeon Spoilers", "gCheckTrackerOptionMQSpoilers"); UIWidgets::Tooltip("If enabled, Vanilla/MQ dungeons will show on the tracker immediately. Otherwise, Vanilla/MQ dungeon locations must be unlocked."); UIWidgets::EnhancementCheckbox("Hide right-side shop item checks", "gCheckTrackerOptionHideRightShopChecks", false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip("If enabled, will prevent the tracker from displaying slots 1-4 in all shops. Requires save reload."); + UIWidgets::Tooltip("If enabled, will prevent the tracker from displaying slots 1-4 in all shops."); + UIWidgets::EnhancementCheckbox("Always show gold skulltulas", "gCheckTrackerOptionAlwaysShowGSLocs", false, ""); + UIWidgets::Tooltip("If enabled, will show GS locations in the tracker regardless of tokensanity settings."); ImGui::TableNextColumn(); diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 3068da96f..63001a2b2 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -121,6 +121,8 @@ GameInteractorSail* GameInteractorSail::Instance; #include "soh/config/ConfigUpdaters.h" +void SoH_ProcessDroppedFiles(std::string filePath); + OTRGlobals* OTRGlobals::Instance; SaveManager* SaveManager::Instance; CustomMessageManager* CustomMessageManager::Instance; @@ -1087,9 +1089,9 @@ extern "C" void InitOTR() { OTRGlobals::Instance = new OTRGlobals(); CustomMessageManager::Instance = new CustomMessageManager(); ItemTableManager::Instance = new ItemTableManager(); + GameInteractor::Instance = new GameInteractor(); SaveManager::Instance = new SaveManager(); SohGui::SetupGuiElements(); - GameInteractor::Instance = new GameInteractor(); AudioCollection::Instance = new AudioCollection(); ActorDB::Instance = new ActorDB(); #ifdef __APPLE__ @@ -1114,6 +1116,11 @@ extern "C" void InitOTR() { InitMods(); ActorDB::AddBuiltInCustomActors(); + // #region SOH [Randomizer] TODO: Remove these and refactor spoiler file handling for randomizer + CVarClear("gRandomizerNewFileDropped"); + CVarClear("gRandomizerDroppedFile"); + // #endregion + GameInteractor::Instance->RegisterGameHook(SoH_ProcessDroppedFiles); time_t now = time(NULL); tm *tm_now = localtime(&now); @@ -1303,6 +1310,16 @@ extern "C" void Graph_StartFrame() { } } #endif + + if (CVarGetInteger("gNewFileDropped", 0)) { + std::string filePath = SohUtils::Sanitize(CVarGetString("gDroppedFile", "")); + if (!filePath.empty()) { + GameInteractor::Instance->ExecuteHooks(filePath); + } + CVarClear("gNewFileDropped"); + CVarClear("gDroppedFile"); + } + OTRGlobals::Instance->context->GetWindow()->StartFrame(); } @@ -2170,10 +2187,10 @@ Color_RGB8 GetColorForControllerLED() { if (source == LED_SOURCE_CUSTOM) { color = CVarGetColor24("gLedPort1Color", { 255, 255, 255 }); } - if (criticalOverride || source == LED_SOURCE_HEALTH) { + if (gPlayState && (criticalOverride || source == LED_SOURCE_HEALTH)) { if (HealthMeter_IsCritical()) { color = { 0xFF, 0, 0 }; - } else if (source == LED_SOURCE_HEALTH) { + } else if (gSaveContext.healthCapacity != 0 && source == LED_SOURCE_HEALTH) { if (gSaveContext.health / gSaveContext.healthCapacity <= 0.4f) { color = { 0xFF, 0xFF, 0 }; } else { @@ -2566,6 +2583,8 @@ extern "C" int CustomMessage_RetrieveIfExists(PlayState* play) { messageEntry = OTRGlobals::Instance->gRandomizer->GetWarpSongMessage(textId, Randomizer_GetSettingValue(RSK_WARP_SONG_HINTS) == RO_GENERIC_OFF); } else if (textId == TEXT_LAKE_HYLIA_WATER_SWITCH_NAVI || textId == TEXT_LAKE_HYLIA_WATER_SWITCH_SIGN) { messageEntry = CustomMessageManager::Instance->RetrieveMessage(Randomizer::hintMessageTableID, textId); + } else if (textId == TEXT_SHOOTING_GALLERY_MAN_COME_BACK_WITH_BOW) { + messageEntry = CustomMessageManager::Instance->RetrieveMessage(Randomizer::hintMessageTableID, TEXT_SHOOTING_GALLERY_MAN_COME_BACK_WITH_BOW); } else if (textId == 0x3052 || (textId >= 0x3069 && textId <= 0x3070)) { //Fire Temple gorons u16 choice = Random(0, NUM_GORON_MESSAGES); messageEntry = OTRGlobals::Instance->gRandomizer->GetGoronMessage(choice); @@ -2655,70 +2674,74 @@ extern "C" void Gfx_RegisterBlendedTexture(const char* name, u8* mask, u8* repla gfx_register_blended_texture(name, mask, replacement); } -// #region SOH [TODO] Ideally this should move to being event based, it's currently run every frame on the file select screen -extern "C" void SoH_ProcessDroppedFiles() { - const char* droppedFile = CVarGetString("gDroppedFile", ""); - if (CVarGetInteger("gNewFileDropped", 0) && strcmp(droppedFile, "") != 0) { - try { - std::ifstream configStream(SohUtils::Sanitize(droppedFile)); - if (!configStream) { - return; - } - - nlohmann::json configJson; - configStream >> configJson; - - if (!configJson.contains("CVars")) { - return; - } - - clearCvars(enhancementsCvars); - clearCvars(cheatCvars); - clearCvars(randomizerCvars); - - // Flatten everything under CVars into a single array - auto cvars = configJson["CVars"].flatten(); - - for (auto& [key, value] : cvars.items()) { - // Replace slashes with dots in key, and remove leading dot - std::string path = key; - std::replace(path.begin(), path.end(), '/', '.'); - if (path[0] == '.') { - path.erase(0, 1); - } - if (value.is_string()) { - CVarSetString(path.c_str(), value.get().c_str()); - } else if (value.is_number_integer()) { - CVarSetInteger(path.c_str(), value.get()); - } else if (value.is_number_float()) { - CVarSetFloat(path.c_str(), value.get()); - } - } - - auto gui = LUS::Context::GetInstance()->GetWindow()->GetGui(); - gui->GetGuiWindow("Console")->Hide(); - gui->GetGuiWindow("Actor Viewer")->Hide(); - gui->GetGuiWindow("Collision Viewer")->Hide(); - gui->GetGuiWindow("Save Editor")->Hide(); - gui->GetGuiWindow("Display List Viewer")->Hide(); - gui->GetGuiWindow("Stats")->Hide(); - std::dynamic_pointer_cast(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))->ClearBindings(); - - gui->SaveConsoleVariablesOnNextTick(); - - uint32_t finalHash = boost::hash_32{}(configJson.dump()); - gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Configuration Loaded. Hash: %d", finalHash); - } catch (std::exception& e) { - SPDLOG_ERROR("Failed to load config file: {}", e.what()); - auto gui = LUS::Context::GetInstance()->GetWindow()->GetGui(); - gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Failed to load config file"); - return; - } catch (...) { - SPDLOG_ERROR("Failed to load config file"); - auto gui = LUS::Context::GetInstance()->GetWindow()->GetGui(); - gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Failed to load config file"); +void SoH_ProcessDroppedFiles(std::string filePath) { + try { + std::ifstream configStream(filePath); + if (!configStream) { return; } + + nlohmann::json configJson; + configStream >> configJson; + + // #region SOH [Randomizer] TODO: Refactor spoiler file handling for randomizer + if (configJson.contains("version") && configJson.contains("finalSeed")) { + CVarSetString("gRandomizerDroppedFile", filePath.c_str()); + CVarSetInteger("gRandomizerNewFileDropped", 1); + return; + } + // #endregion + + if (!configJson.contains("CVars")) { + return; + } + + clearCvars(enhancementsCvars); + clearCvars(cheatCvars); + clearCvars(randomizerCvars); + + // Flatten everything under CVars into a single array + auto cvars = configJson["CVars"].flatten(); + + for (auto& [key, value] : cvars.items()) { + // Replace slashes with dots in key, and remove leading dot + std::string path = key; + std::replace(path.begin(), path.end(), '/', '.'); + if (path[0] == '.') { + path.erase(0, 1); + } + if (value.is_string()) { + CVarSetString(path.c_str(), value.get().c_str()); + } else if (value.is_number_integer()) { + CVarSetInteger(path.c_str(), value.get()); + } else if (value.is_number_float()) { + CVarSetFloat(path.c_str(), value.get()); + } + } + + auto gui = LUS::Context::GetInstance()->GetWindow()->GetGui(); + gui->GetGuiWindow("Console")->Hide(); + gui->GetGuiWindow("Actor Viewer")->Hide(); + gui->GetGuiWindow("Collision Viewer")->Hide(); + gui->GetGuiWindow("Save Editor")->Hide(); + gui->GetGuiWindow("Display List Viewer")->Hide(); + gui->GetGuiWindow("Stats")->Hide(); + std::dynamic_pointer_cast(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))->ClearBindings(); + + gui->SaveConsoleVariablesOnNextTick(); + + uint32_t finalHash = boost::hash_32{}(configJson.dump()); + gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Configuration Loaded. Hash: %d", finalHash); + } catch (std::exception& e) { + SPDLOG_ERROR("Failed to load config file: {}", e.what()); + auto gui = LUS::Context::GetInstance()->GetWindow()->GetGui(); + gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Failed to load config file"); + return; + } catch (...) { + SPDLOG_ERROR("Failed to load config file"); + auto gui = LUS::Context::GetInstance()->GetWindow()->GetGui(); + gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Failed to load config file"); + return; } } // #endregion diff --git a/soh/soh/OTRGlobals.h b/soh/soh/OTRGlobals.h index 48cd0cd48..518aac8d1 100644 --- a/soh/soh/OTRGlobals.h +++ b/soh/soh/OTRGlobals.h @@ -178,7 +178,6 @@ void EntranceTracker_SetLastEntranceOverride(s16 entranceIndex); void Gfx_RegisterBlendedTexture(const char* name, u8* mask, u8* replacement); void SaveManager_ThreadPoolWait(); void CheckTracker_OnMessageClose(); -void SoH_ProcessDroppedFiles(); GetItemID RetrieveGetItemIDFromItemID(ItemID itemID); RandomizerGet RetrieveRandomizerGetFromItemID(ItemID itemID); diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index 3bc7c9c6a..46a87d649 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -1521,6 +1521,13 @@ void Inventory_SwapAgeEquipment(void) { } } + // In Rando, when switching to adult for the second+ time, if a sword was not previously + // equiped in MS shuffle, then we need to set the swordless flag again + if (IS_RANDO && Randomizer_GetSettingValue(RSK_SHUFFLE_MASTER_SWORD) && + gSaveContext.equips.buttonItems[0] == ITEM_NONE) { + Flags_SetInfTable(INFTABLE_SWORDLESS); + } + gSaveContext.equips.equipment = gSaveContext.adultEquips.equipment; } } else { @@ -1589,6 +1596,13 @@ void Inventory_SwapAgeEquipment(void) { } } + // In Rando, when switching to child from a swordless adult, and child Link previously had a + // sword equiped, then we need to unset the swordless flag to match + if (IS_RANDO && Randomizer_GetSettingValue(RSK_SHUFFLE_MASTER_SWORD) && + gSaveContext.equips.buttonItems[0] != ITEM_NONE) { + Flags_UnsetInfTable(INFTABLE_SWORDLESS); + } + gSaveContext.equips.equipment = gSaveContext.childEquips.equipment; gSaveContext.equips.equipment &= (u16) ~(0xF << (EQUIP_TYPE_SWORD * 4)); gSaveContext.equips.equipment |= EQUIP_VALUE_SWORD_KOKIRI << (EQUIP_TYPE_SWORD * 4); diff --git a/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c b/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c index f4c46d952..b077d8ef7 100644 --- a/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c +++ b/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c @@ -69,7 +69,7 @@ static u8 sMaskTex16x32[16 * 32] = { { 0 } }; static u8 sMaskTex32x16[32 * 16] = { { 0 } }; static u8 sMaskTex8x8[8 * 8] = { { 0 } }; static u8 sMaskTex8x32[8 * 32] = { { 0 } }; -static u8 sMaskTexLava[32 * 64] = { { 0 } }; +static u8 sMaskTexLava[LAVA_TEX_WIDTH * LAVA_TEX_HEIGHT] = { { 0 } }; static u32* sLavaFloorModifiedTexRaw = NULL; static u32* sLavaWavyTexRaw = NULL; @@ -112,6 +112,20 @@ void BossDodongo_RegisterBlendedLavaTextureUpdate() { u32* lavaTex = ResourceGetDataByName(sLavaFloorLavaTex); size_t lavaSize = ResourceGetSizeByName(sLavaFloorLavaTex); size_t floorSize = ResourceGetSizeByName(gDodongosCavernBossLavaFloorTex); + size_t rockSize = ResourceGetSizeByName(sLavaFloorRockTex); + + // If the sizes don't match, then don't bother with the blended effect to avoid crashing + if (floorSize != lavaSize || floorSize != rockSize) { + uint8_t maskVal = !!Flags_GetClear(gPlayState, gPlayState->roomCtx.curRoom.num); + + if (sMaskTexLava[0] != maskVal) { + for (int i = 0; i < ARRAY_COUNT(sMaskTexLava); i++) { + sMaskTexLava[i] = maskVal; + } + } + Gfx_RegisterBlendedTexture(gDodongosCavernBossLavaFloorTex, sMaskTexLava, NULL); + return; + } sLavaFloorModifiedTexRaw = malloc(lavaSize); sLavaWavyTexRaw = malloc(floorSize); @@ -121,7 +135,6 @@ void BossDodongo_RegisterBlendedLavaTextureUpdate() { // When KD is dead, just immediately copy the rock texture if (Flags_GetClear(gPlayState, gPlayState->roomCtx.curRoom.num)) { u32* rockTex = ResourceGetDataByName(sLavaFloorRockTex); - size_t rockSize = ResourceGetSizeByName(sLavaFloorRockTex); memcpy(sLavaFloorModifiedTexRaw, rockTex, rockSize); } @@ -145,6 +158,13 @@ void BossDodongo_RegisterBlendedLavaTextureUpdate() { Gfx_RegisterBlendedTexture(gDodongosCavernBossLavaFloorTex, sMaskTexLava, sLavaWavyTex); } + // Set all true for the lava as it will always replace the scene texture + if (sMaskTexLava[0] == 0) { + for (int i = 0; i < ARRAY_COUNT(sMaskTexLava); i++) { + sMaskTexLava[i] = 1; + } + } + gfx_texture_cache_clear(); } @@ -170,6 +190,11 @@ void func_808C12C4(u8* arg1, s16 arg2) { // Same as func_808C1554 but works with u32 values for RGBA32 raw textures void func_808C1554_Raw(void* arg0, void* floorTex, s32 arg2, f32 arg3) { + // Raw lava not registered, so abort the wave modification + if (sLavaWavyTexRaw == NULL || sLavaFloorModifiedTexRaw == NULL) { + return; + } + u16 width = ResourceGetTexWidthByName(arg0); s32 size = ResourceGetTexHeightByName(arg0) * width; @@ -203,9 +228,6 @@ void func_808C1554_Raw(void* arg0, void* floorTex, s32 arg2, f32 arg3) { } free(sp54); - - // Need to clear the cache after updating sLavaWavyTexRaw - gfx_texture_cache_clear(); } // Modified to support CPU modified texture with the resource system @@ -233,9 +255,6 @@ void func_808C1554(void* arg0, void* floorTex, s32 arg2, f32 arg3) { temp_s3[i + temp2] = sp54[i + i2]; } } - - // Need to clear the cache after updating sLavaWavyTex - gfx_texture_cache_clear(); } void func_808C17C8(PlayState* play, Vec3f* arg1, Vec3f* arg2, Vec3f* arg3, f32 arg4, s16 arg5) { @@ -325,7 +344,7 @@ void BossDodongo_Init(Actor* thisx, PlayState* play) { this->actor.flags &= ~ACTOR_FLAG_TARGETABLE; // #region SOH [General] - // Init mask values for all blended textures + // Init mask values for all KD blended textures for (int i = 0; i < ARRAY_COUNT(sMaskTex8x16); i++) { sMaskTex8x16[i] = 0; } @@ -341,10 +360,6 @@ void BossDodongo_Init(Actor* thisx, PlayState* play) { for (int i = 0; i < ARRAY_COUNT(sMaskTex32x16); i++) { sMaskTex32x16[i] = 0; } - // Set all true for the lava as it will always replace the scene texture - for (int i = 0; i < ARRAY_COUNT(sMaskTexLava); i++) { - sMaskTexLava[i] = 1; - } // Register all blended textures Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_015890, sMaskTex8x16, NULL); @@ -1174,15 +1189,23 @@ void BossDodongo_Update(Actor* thisx, PlayState* play2) { for (i2 = 0; i2 < 20; i2++) { s16 new_var = this->unk_1C2 & (LAVA_TEX_SIZE - 1); - // Compute the index to a scaled position (scaling pseudo x,y as a 1D value) - s32 indexStart = ((new_var % LAVA_TEX_WIDTH) * widthScale) + ((new_var / LAVA_TEX_WIDTH) * width * heightScale); - // From the starting index, apply extra pixels right/down based on the scale - for (size_t j = 0; j < heightScale; j++) { - for (size_t i3 = 0; i3 < widthScale; i3++) { - s32 scaledIndex = (indexStart + i3 + (j * width)) & (size - 1); - ptr1[scaledIndex] = ptr2[scaledIndex]; + // Raw lava must be registered, otherwise skip the effect for incompatible texture pack + // and instead set the mask to simulate the lava disappearing by turning black + if (sLavaFloorModifiedTexRaw != NULL) { + // Compute the index to a scaled position (scaling pseudo x,y as a 1D value) + s32 indexStart = + ((new_var % LAVA_TEX_WIDTH) * widthScale) + ((new_var / LAVA_TEX_WIDTH) * width * heightScale); + + // From the starting index, apply extra pixels right/down based on the scale + for (size_t j = 0; j < heightScale; j++) { + for (size_t i3 = 0; i3 < widthScale; i3++) { + s32 scaledIndex = (indexStart + i3 + (j * width)) & (size - 1); + ptr1[scaledIndex] = ptr2[scaledIndex]; + } } + } else { + sMaskTexLava[new_var] = 1; } this->unk_1C2 += 37; @@ -1322,8 +1345,16 @@ void BossDodongo_Draw(Actor* thisx, PlayState* play) { gSPInvalidateTexCache(POLY_OPA_DISP++, sMaskTex32x16); } - if (this->unk_1C6 != 0) { - gSPInvalidateTexCache(POLY_OPA_DISP++, sMaskTexLava); + gSPInvalidateTexCache(POLY_OPA_DISP++, sMaskTexLava); + + // Using WORK_DISP to invalidate these textures as they are used in drawing the scene textures which happens + // before actors are drawn. WORK_DISP comes before POLAY_OPA_DISP. It is probably not meant for this, but it + // at least works for now. + // Alternatively, having a way to invalidate just these pointers from the Update func should be sufficient. + if (sLavaFloorModifiedTexRaw != NULL) { + gSPInvalidateTexCache(WORK_DISP++, sLavaWavyTexRaw); + } else { + gSPInvalidateTexCache(WORK_DISP++, sLavaWavyTex); } if ((this->unk_1C0 >= 2) && (this->unk_1C0 & 1)) { diff --git a/soh/src/overlays/actors/ovl_En_Box/z_en_box.c b/soh/src/overlays/actors/ovl_En_Box/z_en_box.c index e4cccb501..a4d86ff61 100644 --- a/soh/src/overlays/actors/ovl_En_Box/z_en_box.c +++ b/soh/src/overlays/actors/ovl_En_Box/z_en_box.c @@ -75,7 +75,6 @@ static InitChainEntry sInitChain[] = { }; static UNK_TYPE sUnused; -GetItemEntry sItem; Gfx gSkullTreasureChestChestSideAndLidDL[116] = {0}; Gfx gGoldTreasureChestChestSideAndLidDL[116] = {0}; @@ -472,7 +471,7 @@ void EnBox_WaitOpen(EnBox* this, PlayState* play) { func_8002DBD0(&this->dyna.actor, &sp4C, &player->actor.world.pos); if (sp4C.z > -50.0f && sp4C.z < 0.0f && fabsf(sp4C.y) < 10.0f && fabsf(sp4C.x) < 20.0f && Player_IsFacingActor(&this->dyna.actor, 0x3000, play)) { - sItem = Randomizer_GetItemFromActor(this->dyna.actor.id, play->sceneNum, this->dyna.actor.params, this->dyna.actor.params >> 5 & 0x7F); + GetItemEntry sItem = Randomizer_GetItemFromActor(this->dyna.actor.id, play->sceneNum, this->dyna.actor.params, this->dyna.actor.params >> 5 & 0x7F); GetItemEntry blueRupee = ItemTable_RetrieveEntry(MOD_NONE, GI_RUPEE_BLUE); // RANDOTODO treasure chest game rando @@ -628,7 +627,7 @@ void EnBox_Update(Actor* thisx, PlayState* play) { } if (((!IS_RANDO && ((this->dyna.actor.params >> 5 & 0x7F) == 0x7C)) || - (IS_RANDO && ABS(sItem.getItemId) == RG_ICE_TRAP)) && + (IS_RANDO && this->getItemEntry.getItemId == RG_ICE_TRAP)) && this->actionFunc == EnBox_Open && this->skelanime.curFrame > 45 && this->iceSmokeTimer < 100) { if (!CVarGetInteger("gAddTraps.enabled", 0)) { EnBox_SpawnIceSmoke(this, play); diff --git a/soh/src/overlays/actors/ovl_En_Syateki_Man/z_en_syateki_man.c b/soh/src/overlays/actors/ovl_En_Syateki_Man/z_en_syateki_man.c index 888b0ef34..6ab91ddc0 100644 --- a/soh/src/overlays/actors/ovl_En_Syateki_Man/z_en_syateki_man.c +++ b/soh/src/overlays/actors/ovl_En_Syateki_Man/z_en_syateki_man.c @@ -3,6 +3,7 @@ #include "overlays/actors/ovl_En_Syateki_Itm/z_en_syateki_itm.h" #include "objects/object_ossan/object_ossan.h" #include "soh/Enhancements/randomizer/randomizer_entrance.h" +#include "soh/Enhancements/custom-message/CustomMessageTypes.h" #define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_UPDATE_WHILE_CULLED | ACTOR_FLAG_NO_LOCKON) @@ -371,7 +372,8 @@ void EnSyatekiMan_EndGame(EnSyatekiMan* this, PlayState* play) { this->getItemId = GI_RUPEE_PURPLE; } } else { - if(IS_RANDO && !Flags_GetTreasure(play, 0x1F)) { + // Only give the adult rando reward when the player has a quiver + if (IS_RANDO && !Flags_GetTreasure(play, 0x1F) && CUR_UPG_VALUE(UPG_QUIVER) > 0) { this->getItemEntry = Randomizer_GetItemFromKnownCheck(RC_KAK_SHOOTING_GALLERY_REWARD, GI_QUIVER_50); this->getItemId = this->getItemEntry.getItemId; Flags_SetTreasure(play, 0x1F); @@ -448,6 +450,9 @@ void EnSyatekiMan_FinishPrize(EnSyatekiMan* this, PlayState* play) { Flags_SetItemGetInf(ITEMGETINF_0D); } else if ((this->getItemId == GI_QUIVER_40) || (this->getItemId == GI_QUIVER_50)) { Flags_SetItemGetInf(ITEMGETINF_0E); + } else if (IS_RANDO && LINK_IS_ADULT && CUR_UPG_VALUE(UPG_QUIVER) == 0) { + // In Rando without a quiver, display a message reminding the player to come back with a bow + Message_StartTextbox(play, TEXT_SHOOTING_GALLERY_MAN_COME_BACK_WITH_BOW, NULL); } this->gameResult = SYATEKI_RESULT_NONE; this->actor.parent = this->tempGallery; diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index c0bb84212..64429ed03 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -11003,7 +11003,14 @@ void Player_UseTunicBoots(Player* this, PlayState* play) { s32 i; s32 item; s32 itemAction; - if (!(this->stateFlags1 & PLAYER_STATE1_INPUT_DISABLED || this->stateFlags1 & PLAYER_STATE1_IN_ITEM_CS || this->stateFlags1 & PLAYER_STATE1_IN_CUTSCENE || this->stateFlags1 & PLAYER_STATE1_TEXT_ON_SCREEN || this->stateFlags2 & PLAYER_STATE2_OCARINA_PLAYING)) { + if (!( + this->stateFlags1 & PLAYER_STATE1_INPUT_DISABLED || + this->stateFlags1 & PLAYER_STATE1_IN_ITEM_CS || + this->stateFlags1 & PLAYER_STATE1_IN_CUTSCENE || + this->stateFlags1 & PLAYER_STATE1_TEXT_ON_SCREEN || + this->stateFlags1 & PLAYER_STATE1_DEAD || + this->stateFlags2 & PLAYER_STATE2_OCARINA_PLAYING + )) { for (i = 0; i < ARRAY_COUNT(sItemButtons); i++) { if (CHECK_BTN_ALL(sControlInput->press.button, sItemButtons[i])) { break; diff --git a/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c b/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c index 61645de1e..f50106291 100644 --- a/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c +++ b/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c @@ -1030,18 +1030,18 @@ void FileChoose_UpdateRandomizer() { fileSelectSpoilerFileLoaded = false; } - if ((CVarGetInteger("gNewFileDropped", 0) != 0) || (CVarGetInteger("gNewSeedGenerated", 0) != 0) || + if ((CVarGetInteger("gRandomizerNewFileDropped", 0) != 0) || (CVarGetInteger("gNewSeedGenerated", 0) != 0) || (!fileSelectSpoilerFileLoaded && SpoilerFileExists(CVarGetString("gSpoilerLog", "")))) { - if (CVarGetInteger("gNewFileDropped", 0) != 0) { - CVarSetString("gSpoilerLog", CVarGetString("gDroppedFile", "None")); + if (CVarGetInteger("gRandomizerNewFileDropped", 0) != 0) { + CVarSetString("gSpoilerLog", CVarGetString("gRandomizerDroppedFile", "None")); } bool silent = true; - if ((CVarGetInteger("gNewFileDropped", 0) != 0) || (CVarGetInteger("gNewSeedGenerated", 0) != 0)) { + if ((CVarGetInteger("gRandomizerNewFileDropped", 0) != 0) || (CVarGetInteger("gNewSeedGenerated", 0) != 0)) { silent = false; } CVarSetInteger("gNewSeedGenerated", 0); - CVarSetInteger("gNewFileDropped", 0); - CVarSetString("gDroppedFile", ""); + CVarSetInteger("gRandomizerNewFileDropped", 0); + CVarSetString("gRandomizerDroppedFile", ""); fileSelectSpoilerFileLoaded = false; const char* fileLoc = CVarGetString("gSpoilerLog", ""); Randomizer_LoadSettings(fileLoc); @@ -1076,7 +1076,6 @@ void FileChoose_UpdateMainMenu(GameState* thisx) { Input* input = &this->state.input[0]; bool dpad = CVarGetInteger("gDpadText", 0); - SoH_ProcessDroppedFiles(); FileChoose_UpdateRandomizer(); if (CHECK_BTN_ALL(input->press.button, BTN_START) || CHECK_BTN_ALL(input->press.button, BTN_A)) { @@ -1267,7 +1266,6 @@ void FileChoose_UpdateQuestMenu(GameState* thisx) { s8 i = 0; bool dpad = CVarGetInteger("gDpadText", 0); - SoH_ProcessDroppedFiles(); FileChoose_UpdateRandomizer(); if (ABS(this->stickRelX) > 30 || (dpad && CHECK_BTN_ANY(input->press.button, BTN_DLEFT | BTN_DRIGHT))) { diff --git a/soh/src/overlays/gamestates/ovl_file_choose/z_file_nameset_PAL.c b/soh/src/overlays/gamestates/ovl_file_choose/z_file_nameset_PAL.c index 377c23eb3..5c5cb3e6d 100644 --- a/soh/src/overlays/gamestates/ovl_file_choose/z_file_nameset_PAL.c +++ b/soh/src/overlays/gamestates/ovl_file_choose/z_file_nameset_PAL.c @@ -456,7 +456,6 @@ void FileChoose_DrawNameEntry(GameState* thisx) { this->prevConfigMode = CM_MAIN_MENU; this->configMode = CM_NAME_ENTRY_TO_MAIN; CVarSetInteger("gOnFileSelectNameEntry", 0); - CVarSetInteger("gNewFileDropped", 0); this->nameBoxAlpha[this->buttonIndex] = this->nameAlpha[this->buttonIndex] = 200; this->connectorAlpha[this->buttonIndex] = 255; func_800AA000(300.0f, 0xB4, 0x14, 0x64); diff --git a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_map_PAL.c b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_map_PAL.c index 238a1e4e2..28723d3d0 100644 --- a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_map_PAL.c +++ b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_map_PAL.c @@ -343,7 +343,7 @@ void KaleidoScope_DrawDungeonMap(PlayState* play, GraphicsContext* gfxCtx) { // Offset the U value of each vertex to be in the mirror boundary for the map textures if (mirroredWorld) { for (size_t i = 0; i < 8; i++) { - pauseCtx->mapPageVtx[60 + i].v.tc[0] += 48 << 5; + pauseCtx->mapPageVtx[60 + i].v.tc[0] += MAP_48x85_TEX_WIDTH << 5; } } @@ -353,8 +353,9 @@ void KaleidoScope_DrawDungeonMap(PlayState* play, GraphicsContext* gfxCtx) { gSPInvalidateTexCache(POLY_KAL_DISP++, interfaceCtx->mapSegment[0]); gSPInvalidateTexCache(POLY_KAL_DISP++, interfaceCtx->mapSegment[1]); - gDPLoadTextureBlock_4b(POLY_KAL_DISP++, interfaceCtx->mapSegmentName[0], G_IM_FMT_CI, 48, 85, 0, G_TX_WRAP | mirrorMode, - G_TX_WRAP | G_TX_NOMIRROR, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD); + gDPLoadTextureBlock_4b(POLY_KAL_DISP++, interfaceCtx->mapSegmentName[0], G_IM_FMT_CI, MAP_48x85_TEX_WIDTH, + MAP_48x85_TEX_HEIGHT, 0, G_TX_WRAP | mirrorMode, G_TX_WRAP | G_TX_NOMIRROR, G_TX_NOMASK, + G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD); // Swap vertices to render left half on the right and vice-versa if (mirroredWorld) { @@ -363,9 +364,9 @@ void KaleidoScope_DrawDungeonMap(PlayState* play, GraphicsContext* gfxCtx) { gSP1Quadrangle(POLY_KAL_DISP++, 0, 2, 3, 1, 0); } - gDPLoadTextureBlock_4b(POLY_KAL_DISP++, interfaceCtx->mapSegmentName[1], G_IM_FMT_CI, 48, 85, 0, - G_TX_WRAP | mirrorMode, G_TX_WRAP | G_TX_NOMIRROR, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, - G_TX_NOLOD); + gDPLoadTextureBlock_4b(POLY_KAL_DISP++, interfaceCtx->mapSegmentName[1], G_IM_FMT_CI, MAP_48x85_TEX_WIDTH, + MAP_48x85_TEX_HEIGHT, 0, G_TX_WRAP | mirrorMode, G_TX_WRAP | G_TX_NOMIRROR, G_TX_NOMASK, + G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD); if (mirroredWorld) { gSP1Quadrangle(POLY_KAL_DISP++, 0, 2, 3, 1, 0); diff --git a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope.h b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope.h index 6f4ed7ad2..8c214d9b0 100644 --- a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope.h +++ b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope.h @@ -13,6 +13,10 @@ extern u8 gItemAgeReqs[]; extern u8 gAreaGsFlags[]; extern bool gSelectingMask; +#define MAP_48x85_TEX_WIDTH 48 +#define MAP_48x85_TEX_HEIGHT 85 +#define MAP_48x85_TEX_SIZE ((MAP_48x85_TEX_WIDTH * MAP_48x85_TEX_HEIGHT) / 2) // 48x85 CI4 texture + #define AGE_REQ_ADULT LINK_AGE_ADULT #define AGE_REQ_CHILD LINK_AGE_CHILD #define AGE_REQ_NONE 9 diff --git a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c index 8fe817f2f..4167324d7 100644 --- a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c +++ b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c @@ -1205,6 +1205,8 @@ Gfx* KaleidoScope_DrawPageSections(Gfx* gfx, Vtx* vertices, void** textures) { return gfx; } +static uint8_t mapBlendMask[MAP_48x85_TEX_WIDTH * MAP_48x85_TEX_HEIGHT]; + void KaleidoScope_DrawPages(PlayState* play, GraphicsContext* gfxCtx) { static Color_RGB8 D_8082ACF4[12] = { { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 255, 255, 0 }, { 0, 0, 0 }, @@ -1373,6 +1375,10 @@ void KaleidoScope_DrawPages(PlayState* play, GraphicsContext* gfxCtx) { } } + // Need to invalidate the blend mask every frame. Ideally this would be done in KaleidoScope_DrawDungeonMap + // but the reference is not shared between files + gSPInvalidateTexCache(POLY_KAL_DISP++, mapBlendMask); + if (pauseCtx->pageIndex) { // pageIndex != PAUSE_ITEM gDPPipeSync(OVERLAY_DISP++); gDPSetCombineMode(OVERLAY_DISP++, G_CC_MODULATEIA, G_CC_MODULATEIA); @@ -3315,13 +3321,118 @@ void KaleidoScope_UpdateCursorSize(PauseContext* pauseCtx) { pauseCtx->cursorVtx[14].v.ob[1] = pauseCtx->cursorVtx[15].v.ob[1] = pauseCtx->cursorVtx[12].v.ob[1] - 16; } +// Modifed map texture buffers for registered blend effects and the room indicator color +static uint8_t mapLeftTexModified[MAP_48x85_TEX_SIZE]; +static uint8_t mapRightTexModified[MAP_48x85_TEX_SIZE]; +static uint8_t* mapLeftTexModifiedRaw = NULL; +static uint8_t* mapRightTexModifiedRaw = NULL; + +// Load dungeon maps into the interface context +// SoH [General] - Modified to account for our resource system and HD textures void KaleidoScope_LoadDungeonMap(PlayState* play) { InterfaceContext* interfaceCtx = &play->interfaceCtx; + // Free old textures + if (mapLeftTexModifiedRaw != NULL) { + free(mapLeftTexModifiedRaw); + mapLeftTexModifiedRaw = NULL; + } + if (mapRightTexModifiedRaw != NULL) { + free(mapRightTexModifiedRaw); + mapRightTexModifiedRaw = NULL; + } + + // Unload original textures to bypass cache result for lookups + ResourceMgr_UnloadOriginalWhenAltExists(sDungeonMapTexs[R_MAP_TEX_INDEX]); + ResourceMgr_UnloadOriginalWhenAltExists(sDungeonMapTexs[R_MAP_TEX_INDEX + 1]); + interfaceCtx->mapSegmentName[0] = sDungeonMapTexs[R_MAP_TEX_INDEX]; interfaceCtx->mapSegmentName[1] = sDungeonMapTexs[R_MAP_TEX_INDEX + 1]; - interfaceCtx->mapSegment[0] = ResourceGetDataByName(sDungeonMapTexs[R_MAP_TEX_INDEX]); - interfaceCtx->mapSegment[1] = ResourceGetDataByName(sDungeonMapTexs[R_MAP_TEX_INDEX + 1]); + + // When the texture is HD (raw) we need to copy a dynamic amount of data + // Otherwise the original asset has a static size + if (ResourceMgr_TexIsRaw(interfaceCtx->mapSegmentName[0])) { + u32 width = ResourceGetTexWidthByName(interfaceCtx->mapSegmentName[0]); + u32 height = ResourceGetTexHeightByName(interfaceCtx->mapSegmentName[0]); + size_t size = (width * height) / 2; // account for CI4 size + + // Resource size being larger than the calculated CI size means it is most likely not a CI4 texture + // Abort early end undo the blended effect by clearing the mask to avoid crashing + if (size < ResourceGetTexSizeByName(interfaceCtx->mapSegmentName[0])) { + if (mapBlendMask[0] != 0) { + for (size_t i = 0; i < ARRAY_COUNT(mapBlendMask); i++) { + mapBlendMask[i] = 0; + } + } + + interfaceCtx->mapSegment[0] = NULL; + interfaceCtx->mapSegment[1] = NULL; + + Gfx_RegisterBlendedTexture(interfaceCtx->mapSegmentName[0], mapBlendMask, NULL); + Gfx_RegisterBlendedTexture(interfaceCtx->mapSegmentName[1], mapBlendMask, NULL); + return; + } + + u8* map1TexRaw = ResourceGetDataByName(interfaceCtx->mapSegmentName[0]); + u8* map2TexRaw = ResourceGetDataByName(interfaceCtx->mapSegmentName[1]); + + mapLeftTexModifiedRaw = malloc(size); + mapRightTexModifiedRaw = malloc(size); + + memcpy(mapLeftTexModifiedRaw, map1TexRaw, size); + memcpy(mapRightTexModifiedRaw, map2TexRaw, size); + + interfaceCtx->mapSegment[0] = mapLeftTexModifiedRaw; + interfaceCtx->mapSegment[1] = mapRightTexModifiedRaw; + } else { + u8* map1Tex = ResourceGetDataByName(interfaceCtx->mapSegmentName[0]); + u8* map2Tex = ResourceGetDataByName(interfaceCtx->mapSegmentName[1]); + + memcpy(mapLeftTexModified, map1Tex, MAP_48x85_TEX_SIZE); + memcpy(mapRightTexModified, map2Tex, MAP_48x85_TEX_SIZE); + + interfaceCtx->mapSegment[0] = mapLeftTexModified; + interfaceCtx->mapSegment[1] = mapRightTexModified; + } + + // Mark and register the blend mask for the copied textures + if (mapBlendMask[0] != 1) { + for (size_t i = 0; i < ARRAY_COUNT(mapBlendMask); i++) { + mapBlendMask[i] = 1; + } + } + + Gfx_RegisterBlendedTexture(interfaceCtx->mapSegmentName[0], mapBlendMask, interfaceCtx->mapSegment[0]); + Gfx_RegisterBlendedTexture(interfaceCtx->mapSegmentName[1], mapBlendMask, interfaceCtx->mapSegment[1]); +} + +static uint8_t registeredDungeonMapTextureHook = false; + +void KaleidoScope_RegisterUpdatedDungeonMapTexture() { + if (gPlayState == NULL) { + return; + } + + PauseContext* pauseCtx = &gPlayState->pauseCtx; + + // Kaleido is not open in a dungeon so there is nothing to do + if (R_PAUSE_MENU_MODE < 3 || pauseCtx->state < 4 || pauseCtx->state > 7 || !sInDungeonScene) { + return; + } + + KaleidoScope_UpdateDungeonMap(gPlayState); + + // KaleidoScope_UpdateDungeonMap will update the palette index for the current floor if the cursor is on the floor + // If the player toggles alt assets while the cursor is not in the floor level, then we handle the palette index here + if (gPlayState->sceneNum >= SCENE_DEKU_TREE && gPlayState->sceneNum <= SCENE_TREASURE_BOX_SHOP && + (VREG(30) + 3) == pauseCtx->dungeonMapSlot && (VREG(30) + 3) != pauseCtx->cursorPoint[PAUSE_MAP]) { + + InterfaceContext* interfaceCtx = &gPlayState->interfaceCtx; + int32_t size = ResourceGetTexSizeByName(interfaceCtx->mapSegmentName[0]); + + KaleidoScope_OverridePalIndexCI4(interfaceCtx->mapSegment[0], size, interfaceCtx->mapPaletteIndex, 14); + KaleidoScope_OverridePalIndexCI4(interfaceCtx->mapSegment[1], size, interfaceCtx->mapPaletteIndex, 14); + } } void KaleidoScope_UpdateDungeonMap(PlayState* play) { @@ -3333,19 +3444,29 @@ void KaleidoScope_UpdateDungeonMap(PlayState* play) { KaleidoScope_LoadDungeonMap(play); Map_SetFloorPalettesData(play, pauseCtx->dungeonMapSlot - 3); + s32 size = MAP_48x85_TEX_SIZE; + + if (ResourceMgr_TexIsRaw(interfaceCtx->mapSegmentName[0])) { + size = ResourceGetTexSizeByName(interfaceCtx->mapSegmentName[0]); + } + if ((play->sceneNum >= SCENE_DEKU_TREE) && (play->sceneNum <= SCENE_TREASURE_BOX_SHOP)) { if ((VREG(30) + 3) == pauseCtx->cursorPoint[PAUSE_MAP]) { - // HDTODO: Handle Runtime Modified Textures (HD) - KaleidoScope_OverridePalIndexCI4(interfaceCtx->mapSegment[0], 2040, interfaceCtx->mapPaletteIndex, 14); + KaleidoScope_OverridePalIndexCI4(interfaceCtx->mapSegment[0], size, interfaceCtx->mapPaletteIndex, 14); } } if ((play->sceneNum >= SCENE_DEKU_TREE) && (play->sceneNum <= SCENE_TREASURE_BOX_SHOP)) { if ((VREG(30) + 3) == pauseCtx->cursorPoint[PAUSE_MAP]) { - // HDTODO: Handle Runtime Modified Textures (HD) - KaleidoScope_OverridePalIndexCI4(interfaceCtx->mapSegment[1], 2040, interfaceCtx->mapPaletteIndex, 14); + KaleidoScope_OverridePalIndexCI4(interfaceCtx->mapSegment[1], size, interfaceCtx->mapPaletteIndex, 14); } } + + // Register alt listener to update the blended dungeon map textures on alt toggle + if (!registeredDungeonMapTextureHook) { + registeredDungeonMapTextureHook = true; + GameInteractor_RegisterOnAssetAltChange(KaleidoScope_RegisterUpdatedDungeonMapTexture); + } } void KaleidoScope_Update(PlayState* play) diff --git a/soh/src/overlays/misc/ovl_map_mark_data/z_map_mark_data.c b/soh/src/overlays/misc/ovl_map_mark_data/z_map_mark_data.c index a73ca8265..fec1d8033 100644 --- a/soh/src/overlays/misc/ovl_map_mark_data/z_map_mark_data.c +++ b/soh/src/overlays/misc/ovl_map_mark_data/z_map_mark_data.c @@ -1762,6 +1762,13 @@ static MapMarkData sMapMarkJabuJabuBellyMq[] = { } }, { MAP_MARK_NONE, 0, { 0 } }, }, + // Jabu-Jabu's Belly minimap 16 + // SoH [General] - This entry corresponds to Big Octorok's room and is missing in the MQ game + // N64 hardware does an OoB read and lands on MQ Forest Temple room 0 + // To avoid UB with OoB for SoH, the correct entry is now added below + { + { MAP_MARK_NONE, 0, { 0 } }, + }, }; static MapMarkData sMapMarkForestTempleMq[] = {