Merge tag '8.0.4' into HEAD

MacReady Echo
This commit is contained in:
Adam Bird 2023-12-18 00:34:59 -05:00
commit 03da69d7b7
22 changed files with 355 additions and 147 deletions

View File

@ -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") set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version")
project(Ship VERSION 8.0.3 LANGUAGES C CXX) project(Ship VERSION 8.0.4 LANGUAGES C CXX)
set(PROJECT_BUILD_NAME "MacReady Delta" CACHE STRING "") set(PROJECT_BUILD_NAME "MacReady Echo" CACHE STRING "")
set(PROJECT_TEAM "github.com/harbourmasters" CACHE STRING "") set(PROJECT_TEAM "github.com/harbourmasters" CACHE STRING "")
set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT soh) set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT soh)

@ -1 +1 @@
Subproject commit 4600eedcc18f496319c99e07b8b2b4f11a0f6e64 Subproject commit 7a8d314a0ecc7d6ece1b12626a0ae917ee4ed666

View File

@ -51,6 +51,7 @@ typedef enum {
TEXT_WARP_NOCTURNE_OF_SHADOW = 0x891, TEXT_WARP_NOCTURNE_OF_SHADOW = 0x891,
TEXT_WARP_PRELUDE_OF_LIGHT = 0x892, TEXT_WARP_PRELUDE_OF_LIGHT = 0x892,
TEXT_WARP_RANDOM_REPLACED_TEXT = 0x9200, 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_SIGN = 0x346, // 0x3yy for cuttable sign range
TEXT_LAKE_HYLIA_WATER_SWITCH_NAVI = 0x1B3, // 0x1yy for Navi msg range TEXT_LAKE_HYLIA_WATER_SWITCH_NAVI = 0x1B3, // 0x1yy for Navi msg range
} TextIDs; } TextIDs;

View File

@ -100,6 +100,7 @@ void GameInteractor_SetTriforceHuntCreditsWarpActive(uint8_t state);
#include <thread> #include <thread>
#include <vector> #include <vector>
#include <functional> #include <functional>
#include <string>
#ifdef ENABLE_REMOTE_CONTROL #ifdef ENABLE_REMOTE_CONTROL
#include <SDL2/SDL_net.h> #include <SDL2/SDL_net.h>
@ -218,6 +219,7 @@ public:
DEFINE_HOOK(OnSetGameLanguage, void()); DEFINE_HOOK(OnSetGameLanguage, void());
DEFINE_HOOK(OnFileDropped, void(std::string filePath));
DEFINE_HOOK(OnAssetAltChange, void()); DEFINE_HOOK(OnAssetAltChange, void());
// Helpers // Helpers

View File

@ -618,7 +618,7 @@ void DrawGameplayStatsOptionsTab() {
} }
void GameplayStatsWindow::DrawElement() { 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)) { if (!ImGui::Begin("Gameplay Stats", &mIsVisible, ImGuiWindowFlags_NoFocusOnAppearing)) {
ImGui::End(); ImGui::End();
return; return;

View File

@ -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); 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. // Dodongo, Volvagia and Dead Hand are always smaller because they're impossible when bigger.
uint8_t smallOnlyEnemy = uint8_t smallOnlyEnemy = actor->id == ACTOR_BOSS_DODONGO || actor->id == ACTOR_BOSS_FD ||
actor->id == ACTOR_BOSS_DODONGO || actor->id == ACTOR_BOSS_FD || actor->id == ACTOR_BOSS_FD2 || ACTOR_EN_DH; actor->id == ACTOR_BOSS_FD2 || actor->id == ACTOR_EN_DH;
// Only apply to enemies and bosses. // Only apply to enemies and bosses.
if (!CVarGetInteger("gRandomizedEnemySizes", 0) || (actor->category != ACTORCAT_ENEMY && actor->category != ACTORCAT_BOSS) || excludedEnemy) { if (!CVarGetInteger("gRandomizedEnemySizes", 0) || (actor->category != ACTORCAT_ENEMY && actor->category != ACTORCAT_BOSS) || excludedEnemy) {

View File

@ -104,7 +104,7 @@ void AreaTable_Init_DeathMountain() {
Entrance(DEATH_MOUNTAIN_TRAIL, {[]{return true;}}), Entrance(DEATH_MOUNTAIN_TRAIL, {[]{return true;}}),
Entrance(GC_WOODS_WARP, {[]{return GCWoodsWarpOpen;}}), Entrance(GC_WOODS_WARP, {[]{return GCWoodsWarpOpen;}}),
Entrance(GC_SHOP, {[]{return (IsAdult && StopGCRollingGoronAsAdult) || (IsChild && (CanBlastOrSmash || GoronBracelet || GoronCityChildFire || CanUse(BOW)));}}), 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));}}), 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));}}),
}); });

View File

@ -365,31 +365,16 @@ std::unordered_map<std::string, RandomizerSettingKey> SpoilerfileSettingNameToEn
#pragma GCC push_options #pragma GCC push_options
#pragma GCC optimize ("O0") #pragma GCC optimize ("O0")
bool Randomizer::SpoilerFileExists(const char* spoilerFileName) { bool Randomizer::SpoilerFileExists(const char* spoilerFileName) {
try { if (strcmp(spoilerFileName, "") != 0) {
if (strcmp(spoilerFileName, "") != 0) { std::ifstream spoilerFileStream(SohUtils::Sanitize(spoilerFileName));
std::ifstream spoilerFileStream(SohUtils::Sanitize(spoilerFileName)); if (!spoilerFileStream) {
if (!spoilerFileStream) { return false;
return false; } else {
}
json spoilerFileJson;
spoilerFileStream >> spoilerFileJson;
if (!spoilerFileJson.contains("version") || !spoilerFileJson.contains("finalSeed")) {
return false;
}
return true; 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 GCC pop_options
#pragma optimize("", on) #pragma optimize("", on)
@ -495,6 +480,13 @@ void Randomizer::LoadHintLocations(const char* spoilerFileName) {
"Zu {{location}}?\x1B&%gOK&No%w\x02", "Zu {{location}}?\x1B&%gOK&No%w\x02",
"Se téléporter vers&{{location}}?\x1B&%gOK!&Non%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, CustomMessageManager::Instance->CreateMessage(Randomizer::hintMessageTableID, TEXT_LAKE_HYLIA_WATER_SWITCH_SIGN,
CustomMessage("Water level control system.&Keep away!", CustomMessage("Water level control system.&Keep away!",
"Wasserstand Kontrollsystem&Finger weg!", "Wasserstand Kontrollsystem&Finger weg!",

View File

@ -78,6 +78,7 @@ bool initialized;
bool doAreaScroll; bool doAreaScroll;
bool previousShowHidden = false; bool previousShowHidden = false;
bool hideShopRightChecks = true; bool hideShopRightChecks = true;
bool alwaysShowGS = false;
std::map<uint32_t, RandomizerCheck> startingShopItem = { { SCENE_KOKIRI_SHOP, RC_KF_SHOP_ITEM_1 }, std::map<uint32_t, RandomizerCheck> startingShopItem = { { SCENE_KOKIRI_SHOP, RC_KF_SHOP_ITEM_1 },
{ SCENE_BAZAAR, RC_MARKET_BAZAAR_ITEM_1 }, { SCENE_BAZAAR, RC_MARKET_BAZAAR_ITEM_1 },
@ -434,7 +435,6 @@ void CheckTrackerLoadGame(int32_t fileNum) {
} else { } else {
realRcObj = rcObj; realRcObj = rcObj;
} }
if (!IsVisibleInCheckTracker(realRcObj)) continue;
checksByArea.find(realRcObj.rcArea)->second.push_back(realRcObj); checksByArea.find(realRcObj.rcArea)->second.push_back(realRcObj);
if (rcTrackerData.status == RCSHOW_SAVED || rcTrackerData.skipped) { if (rcTrackerData.status == RCSHOW_SAVED || rcTrackerData.skipped) {
@ -514,6 +514,10 @@ void CheckTrackerTransition(uint32_t sceneNum) {
} }
void CheckTrackerFrame() { void CheckTrackerFrame() {
if (IS_RANDO) {
hideShopRightChecks = CVarGetInteger("gCheckTrackerOptionHideRightShopChecks", 1);
alwaysShowGS = CVarGetInteger("gCheckTrackerOptionAlwaysShowGSLocs", 0);
}
if (!GameInteractor::IsSaveLoaded()) { if (!GameInteractor::IsSaveLoaded()) {
return; 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 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 OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_LINKS_POCKET) != RO_LINKS_POCKET_NOTHING
:false; :false;
hideShopRightChecks = IS_RANDO ? CVarGetInteger("gCheckTrackerOptionHideRightShopChecks", 1) : false;
if (IS_RANDO) { if (IS_RANDO) {
switch (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_TOKENS)) { switch (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_TOKENS)) {
@ -1148,7 +1151,7 @@ bool IsVisibleInCheckTracker(RandomizerCheckObject rcObj) {
) && ) &&
(rcObj.rcType != RCTYPE_MERCHANT || showMerchants) && (rcObj.rcType != RCTYPE_MERCHANT || showMerchants) &&
(rcObj.rcType != RCTYPE_OCARINA || showOcarinas) && (rcObj.rcType != RCTYPE_OCARINA || showOcarinas) &&
(rcObj.rcType != RCTYPE_SKULL_TOKEN || (rcObj.rcType != RCTYPE_SKULL_TOKEN || alwaysShowGS ||
(showOverworldTokens && RandomizerCheckObjects::AreaIsOverworld(rcObj.rcArea)) || (showOverworldTokens && RandomizerCheckObjects::AreaIsOverworld(rcObj.rcArea)) ||
(showDungeonTokens && RandomizerCheckObjects::AreaIsDungeon(rcObj.rcArea)) (showDungeonTokens && RandomizerCheckObjects::AreaIsDungeon(rcObj.rcArea))
) && ) &&
@ -1518,7 +1521,9 @@ void CheckTrackerSettingsWindow::DrawElement() {
UIWidgets::EnhancementCheckbox("Vanilla/MQ Dungeon Spoilers", "gCheckTrackerOptionMQSpoilers"); 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::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::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(); ImGui::TableNextColumn();

View File

@ -121,6 +121,8 @@ GameInteractorSail* GameInteractorSail::Instance;
#include "soh/config/ConfigUpdaters.h" #include "soh/config/ConfigUpdaters.h"
void SoH_ProcessDroppedFiles(std::string filePath);
OTRGlobals* OTRGlobals::Instance; OTRGlobals* OTRGlobals::Instance;
SaveManager* SaveManager::Instance; SaveManager* SaveManager::Instance;
CustomMessageManager* CustomMessageManager::Instance; CustomMessageManager* CustomMessageManager::Instance;
@ -1087,9 +1089,9 @@ extern "C" void InitOTR() {
OTRGlobals::Instance = new OTRGlobals(); OTRGlobals::Instance = new OTRGlobals();
CustomMessageManager::Instance = new CustomMessageManager(); CustomMessageManager::Instance = new CustomMessageManager();
ItemTableManager::Instance = new ItemTableManager(); ItemTableManager::Instance = new ItemTableManager();
GameInteractor::Instance = new GameInteractor();
SaveManager::Instance = new SaveManager(); SaveManager::Instance = new SaveManager();
SohGui::SetupGuiElements(); SohGui::SetupGuiElements();
GameInteractor::Instance = new GameInteractor();
AudioCollection::Instance = new AudioCollection(); AudioCollection::Instance = new AudioCollection();
ActorDB::Instance = new ActorDB(); ActorDB::Instance = new ActorDB();
#ifdef __APPLE__ #ifdef __APPLE__
@ -1114,6 +1116,11 @@ extern "C" void InitOTR() {
InitMods(); InitMods();
ActorDB::AddBuiltInCustomActors(); ActorDB::AddBuiltInCustomActors();
// #region SOH [Randomizer] TODO: Remove these and refactor spoiler file handling for randomizer
CVarClear("gRandomizerNewFileDropped");
CVarClear("gRandomizerDroppedFile");
// #endregion
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnFileDropped>(SoH_ProcessDroppedFiles);
time_t now = time(NULL); time_t now = time(NULL);
tm *tm_now = localtime(&now); tm *tm_now = localtime(&now);
@ -1303,6 +1310,16 @@ extern "C" void Graph_StartFrame() {
} }
} }
#endif #endif
if (CVarGetInteger("gNewFileDropped", 0)) {
std::string filePath = SohUtils::Sanitize(CVarGetString("gDroppedFile", ""));
if (!filePath.empty()) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnFileDropped>(filePath);
}
CVarClear("gNewFileDropped");
CVarClear("gDroppedFile");
}
OTRGlobals::Instance->context->GetWindow()->StartFrame(); OTRGlobals::Instance->context->GetWindow()->StartFrame();
} }
@ -2170,10 +2187,10 @@ Color_RGB8 GetColorForControllerLED() {
if (source == LED_SOURCE_CUSTOM) { if (source == LED_SOURCE_CUSTOM) {
color = CVarGetColor24("gLedPort1Color", { 255, 255, 255 }); color = CVarGetColor24("gLedPort1Color", { 255, 255, 255 });
} }
if (criticalOverride || source == LED_SOURCE_HEALTH) { if (gPlayState && (criticalOverride || source == LED_SOURCE_HEALTH)) {
if (HealthMeter_IsCritical()) { if (HealthMeter_IsCritical()) {
color = { 0xFF, 0, 0 }; 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) { if (gSaveContext.health / gSaveContext.healthCapacity <= 0.4f) {
color = { 0xFF, 0xFF, 0 }; color = { 0xFF, 0xFF, 0 };
} else { } 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); 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) { } else if (textId == TEXT_LAKE_HYLIA_WATER_SWITCH_NAVI || textId == TEXT_LAKE_HYLIA_WATER_SWITCH_SIGN) {
messageEntry = CustomMessageManager::Instance->RetrieveMessage(Randomizer::hintMessageTableID, textId); 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 } else if (textId == 0x3052 || (textId >= 0x3069 && textId <= 0x3070)) { //Fire Temple gorons
u16 choice = Random(0, NUM_GORON_MESSAGES); u16 choice = Random(0, NUM_GORON_MESSAGES);
messageEntry = OTRGlobals::Instance->gRandomizer->GetGoronMessage(choice); 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); 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 void SoH_ProcessDroppedFiles(std::string filePath) {
extern "C" void SoH_ProcessDroppedFiles() { try {
const char* droppedFile = CVarGetString("gDroppedFile", ""); std::ifstream configStream(filePath);
if (CVarGetInteger("gNewFileDropped", 0) && strcmp(droppedFile, "") != 0) { if (!configStream) {
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<std::string>().c_str());
} else if (value.is_number_integer()) {
CVarSetInteger(path.c_str(), value.get<int>());
} else if (value.is_number_float()) {
CVarSetFloat(path.c_str(), value.get<float>());
}
}
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::ConsoleWindow>(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))->ClearBindings();
gui->SaveConsoleVariablesOnNextTick();
uint32_t finalHash = boost::hash_32<std::string>{}(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; 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<std::string>().c_str());
} else if (value.is_number_integer()) {
CVarSetInteger(path.c_str(), value.get<int>());
} else if (value.is_number_float()) {
CVarSetFloat(path.c_str(), value.get<float>());
}
}
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::ConsoleWindow>(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))->ClearBindings();
gui->SaveConsoleVariablesOnNextTick();
uint32_t finalHash = boost::hash_32<std::string>{}(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 // #endregion

View File

@ -178,7 +178,6 @@ void EntranceTracker_SetLastEntranceOverride(s16 entranceIndex);
void Gfx_RegisterBlendedTexture(const char* name, u8* mask, u8* replacement); void Gfx_RegisterBlendedTexture(const char* name, u8* mask, u8* replacement);
void SaveManager_ThreadPoolWait(); void SaveManager_ThreadPoolWait();
void CheckTracker_OnMessageClose(); void CheckTracker_OnMessageClose();
void SoH_ProcessDroppedFiles();
GetItemID RetrieveGetItemIDFromItemID(ItemID itemID); GetItemID RetrieveGetItemIDFromItemID(ItemID itemID);
RandomizerGet RetrieveRandomizerGetFromItemID(ItemID itemID); RandomizerGet RetrieveRandomizerGetFromItemID(ItemID itemID);

View File

@ -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; gSaveContext.equips.equipment = gSaveContext.adultEquips.equipment;
} }
} else { } 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 = gSaveContext.childEquips.equipment;
gSaveContext.equips.equipment &= (u16) ~(0xF << (EQUIP_TYPE_SWORD * 4)); gSaveContext.equips.equipment &= (u16) ~(0xF << (EQUIP_TYPE_SWORD * 4));
gSaveContext.equips.equipment |= EQUIP_VALUE_SWORD_KOKIRI << (EQUIP_TYPE_SWORD * 4); gSaveContext.equips.equipment |= EQUIP_VALUE_SWORD_KOKIRI << (EQUIP_TYPE_SWORD * 4);

View File

@ -69,7 +69,7 @@ static u8 sMaskTex16x32[16 * 32] = { { 0 } };
static u8 sMaskTex32x16[32 * 16] = { { 0 } }; static u8 sMaskTex32x16[32 * 16] = { { 0 } };
static u8 sMaskTex8x8[8 * 8] = { { 0 } }; static u8 sMaskTex8x8[8 * 8] = { { 0 } };
static u8 sMaskTex8x32[8 * 32] = { { 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* sLavaFloorModifiedTexRaw = NULL;
static u32* sLavaWavyTexRaw = NULL; static u32* sLavaWavyTexRaw = NULL;
@ -112,6 +112,20 @@ void BossDodongo_RegisterBlendedLavaTextureUpdate() {
u32* lavaTex = ResourceGetDataByName(sLavaFloorLavaTex); u32* lavaTex = ResourceGetDataByName(sLavaFloorLavaTex);
size_t lavaSize = ResourceGetSizeByName(sLavaFloorLavaTex); size_t lavaSize = ResourceGetSizeByName(sLavaFloorLavaTex);
size_t floorSize = ResourceGetSizeByName(gDodongosCavernBossLavaFloorTex); 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); sLavaFloorModifiedTexRaw = malloc(lavaSize);
sLavaWavyTexRaw = malloc(floorSize); sLavaWavyTexRaw = malloc(floorSize);
@ -121,7 +135,6 @@ void BossDodongo_RegisterBlendedLavaTextureUpdate() {
// When KD is dead, just immediately copy the rock texture // When KD is dead, just immediately copy the rock texture
if (Flags_GetClear(gPlayState, gPlayState->roomCtx.curRoom.num)) { if (Flags_GetClear(gPlayState, gPlayState->roomCtx.curRoom.num)) {
u32* rockTex = ResourceGetDataByName(sLavaFloorRockTex); u32* rockTex = ResourceGetDataByName(sLavaFloorRockTex);
size_t rockSize = ResourceGetSizeByName(sLavaFloorRockTex);
memcpy(sLavaFloorModifiedTexRaw, rockTex, rockSize); memcpy(sLavaFloorModifiedTexRaw, rockTex, rockSize);
} }
@ -145,6 +158,13 @@ void BossDodongo_RegisterBlendedLavaTextureUpdate() {
Gfx_RegisterBlendedTexture(gDodongosCavernBossLavaFloorTex, sMaskTexLava, sLavaWavyTex); 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(); 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 // 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) { 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); u16 width = ResourceGetTexWidthByName(arg0);
s32 size = ResourceGetTexHeightByName(arg0) * width; s32 size = ResourceGetTexHeightByName(arg0) * width;
@ -203,9 +228,6 @@ void func_808C1554_Raw(void* arg0, void* floorTex, s32 arg2, f32 arg3) {
} }
free(sp54); free(sp54);
// Need to clear the cache after updating sLavaWavyTexRaw
gfx_texture_cache_clear();
} }
// Modified to support CPU modified texture with the resource system // 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]; 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) { 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; this->actor.flags &= ~ACTOR_FLAG_TARGETABLE;
// #region SOH [General] // #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++) { for (int i = 0; i < ARRAY_COUNT(sMaskTex8x16); i++) {
sMaskTex8x16[i] = 0; sMaskTex8x16[i] = 0;
} }
@ -341,10 +360,6 @@ void BossDodongo_Init(Actor* thisx, PlayState* play) {
for (int i = 0; i < ARRAY_COUNT(sMaskTex32x16); i++) { for (int i = 0; i < ARRAY_COUNT(sMaskTex32x16); i++) {
sMaskTex32x16[i] = 0; 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 // Register all blended textures
Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_015890, sMaskTex8x16, NULL); 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++) { for (i2 = 0; i2 < 20; i2++) {
s16 new_var = this->unk_1C2 & (LAVA_TEX_SIZE - 1); 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 // Raw lava must be registered, otherwise skip the effect for incompatible texture pack
for (size_t j = 0; j < heightScale; j++) { // and instead set the mask to simulate the lava disappearing by turning black
for (size_t i3 = 0; i3 < widthScale; i3++) { if (sLavaFloorModifiedTexRaw != NULL) {
s32 scaledIndex = (indexStart + i3 + (j * width)) & (size - 1); // Compute the index to a scaled position (scaling pseudo x,y as a 1D value)
ptr1[scaledIndex] = ptr2[scaledIndex]; 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; this->unk_1C2 += 37;
@ -1322,8 +1345,16 @@ void BossDodongo_Draw(Actor* thisx, PlayState* play) {
gSPInvalidateTexCache(POLY_OPA_DISP++, sMaskTex32x16); 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)) { if ((this->unk_1C0 >= 2) && (this->unk_1C0 & 1)) {

View File

@ -75,7 +75,6 @@ static InitChainEntry sInitChain[] = {
}; };
static UNK_TYPE sUnused; static UNK_TYPE sUnused;
GetItemEntry sItem;
Gfx gSkullTreasureChestChestSideAndLidDL[116] = {0}; Gfx gSkullTreasureChestChestSideAndLidDL[116] = {0};
Gfx gGoldTreasureChestChestSideAndLidDL[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); 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 && 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)) { 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); GetItemEntry blueRupee = ItemTable_RetrieveEntry(MOD_NONE, GI_RUPEE_BLUE);
// RANDOTODO treasure chest game rando // 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)) || 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) { this->actionFunc == EnBox_Open && this->skelanime.curFrame > 45 && this->iceSmokeTimer < 100) {
if (!CVarGetInteger("gAddTraps.enabled", 0)) { if (!CVarGetInteger("gAddTraps.enabled", 0)) {
EnBox_SpawnIceSmoke(this, play); EnBox_SpawnIceSmoke(this, play);

View File

@ -3,6 +3,7 @@
#include "overlays/actors/ovl_En_Syateki_Itm/z_en_syateki_itm.h" #include "overlays/actors/ovl_En_Syateki_Itm/z_en_syateki_itm.h"
#include "objects/object_ossan/object_ossan.h" #include "objects/object_ossan/object_ossan.h"
#include "soh/Enhancements/randomizer/randomizer_entrance.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) #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; this->getItemId = GI_RUPEE_PURPLE;
} }
} else { } 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->getItemEntry = Randomizer_GetItemFromKnownCheck(RC_KAK_SHOOTING_GALLERY_REWARD, GI_QUIVER_50);
this->getItemId = this->getItemEntry.getItemId; this->getItemId = this->getItemEntry.getItemId;
Flags_SetTreasure(play, 0x1F); Flags_SetTreasure(play, 0x1F);
@ -448,6 +450,9 @@ void EnSyatekiMan_FinishPrize(EnSyatekiMan* this, PlayState* play) {
Flags_SetItemGetInf(ITEMGETINF_0D); Flags_SetItemGetInf(ITEMGETINF_0D);
} else if ((this->getItemId == GI_QUIVER_40) || (this->getItemId == GI_QUIVER_50)) { } else if ((this->getItemId == GI_QUIVER_40) || (this->getItemId == GI_QUIVER_50)) {
Flags_SetItemGetInf(ITEMGETINF_0E); 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->gameResult = SYATEKI_RESULT_NONE;
this->actor.parent = this->tempGallery; this->actor.parent = this->tempGallery;

View File

@ -11003,7 +11003,14 @@ void Player_UseTunicBoots(Player* this, PlayState* play) {
s32 i; s32 i;
s32 item; s32 item;
s32 itemAction; 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++) { for (i = 0; i < ARRAY_COUNT(sItemButtons); i++) {
if (CHECK_BTN_ALL(sControlInput->press.button, sItemButtons[i])) { if (CHECK_BTN_ALL(sControlInput->press.button, sItemButtons[i])) {
break; break;

View File

@ -1030,18 +1030,18 @@ void FileChoose_UpdateRandomizer() {
fileSelectSpoilerFileLoaded = false; fileSelectSpoilerFileLoaded = false;
} }
if ((CVarGetInteger("gNewFileDropped", 0) != 0) || (CVarGetInteger("gNewSeedGenerated", 0) != 0) || if ((CVarGetInteger("gRandomizerNewFileDropped", 0) != 0) || (CVarGetInteger("gNewSeedGenerated", 0) != 0) ||
(!fileSelectSpoilerFileLoaded && SpoilerFileExists(CVarGetString("gSpoilerLog", "")))) { (!fileSelectSpoilerFileLoaded && SpoilerFileExists(CVarGetString("gSpoilerLog", "")))) {
if (CVarGetInteger("gNewFileDropped", 0) != 0) { if (CVarGetInteger("gRandomizerNewFileDropped", 0) != 0) {
CVarSetString("gSpoilerLog", CVarGetString("gDroppedFile", "None")); CVarSetString("gSpoilerLog", CVarGetString("gRandomizerDroppedFile", "None"));
} }
bool silent = true; bool silent = true;
if ((CVarGetInteger("gNewFileDropped", 0) != 0) || (CVarGetInteger("gNewSeedGenerated", 0) != 0)) { if ((CVarGetInteger("gRandomizerNewFileDropped", 0) != 0) || (CVarGetInteger("gNewSeedGenerated", 0) != 0)) {
silent = false; silent = false;
} }
CVarSetInteger("gNewSeedGenerated", 0); CVarSetInteger("gNewSeedGenerated", 0);
CVarSetInteger("gNewFileDropped", 0); CVarSetInteger("gRandomizerNewFileDropped", 0);
CVarSetString("gDroppedFile", ""); CVarSetString("gRandomizerDroppedFile", "");
fileSelectSpoilerFileLoaded = false; fileSelectSpoilerFileLoaded = false;
const char* fileLoc = CVarGetString("gSpoilerLog", ""); const char* fileLoc = CVarGetString("gSpoilerLog", "");
Randomizer_LoadSettings(fileLoc); Randomizer_LoadSettings(fileLoc);
@ -1076,7 +1076,6 @@ void FileChoose_UpdateMainMenu(GameState* thisx) {
Input* input = &this->state.input[0]; Input* input = &this->state.input[0];
bool dpad = CVarGetInteger("gDpadText", 0); bool dpad = CVarGetInteger("gDpadText", 0);
SoH_ProcessDroppedFiles();
FileChoose_UpdateRandomizer(); FileChoose_UpdateRandomizer();
if (CHECK_BTN_ALL(input->press.button, BTN_START) || CHECK_BTN_ALL(input->press.button, BTN_A)) { 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; s8 i = 0;
bool dpad = CVarGetInteger("gDpadText", 0); bool dpad = CVarGetInteger("gDpadText", 0);
SoH_ProcessDroppedFiles();
FileChoose_UpdateRandomizer(); FileChoose_UpdateRandomizer();
if (ABS(this->stickRelX) > 30 || (dpad && CHECK_BTN_ANY(input->press.button, BTN_DLEFT | BTN_DRIGHT))) { if (ABS(this->stickRelX) > 30 || (dpad && CHECK_BTN_ANY(input->press.button, BTN_DLEFT | BTN_DRIGHT))) {

View File

@ -456,7 +456,6 @@ void FileChoose_DrawNameEntry(GameState* thisx) {
this->prevConfigMode = CM_MAIN_MENU; this->prevConfigMode = CM_MAIN_MENU;
this->configMode = CM_NAME_ENTRY_TO_MAIN; this->configMode = CM_NAME_ENTRY_TO_MAIN;
CVarSetInteger("gOnFileSelectNameEntry", 0); CVarSetInteger("gOnFileSelectNameEntry", 0);
CVarSetInteger("gNewFileDropped", 0);
this->nameBoxAlpha[this->buttonIndex] = this->nameAlpha[this->buttonIndex] = 200; this->nameBoxAlpha[this->buttonIndex] = this->nameAlpha[this->buttonIndex] = 200;
this->connectorAlpha[this->buttonIndex] = 255; this->connectorAlpha[this->buttonIndex] = 255;
func_800AA000(300.0f, 0xB4, 0x14, 0x64); func_800AA000(300.0f, 0xB4, 0x14, 0x64);

View File

@ -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 // Offset the U value of each vertex to be in the mirror boundary for the map textures
if (mirroredWorld) { if (mirroredWorld) {
for (size_t i = 0; i < 8; i++) { 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[0]);
gSPInvalidateTexCache(POLY_KAL_DISP++, interfaceCtx->mapSegment[1]); 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, gDPLoadTextureBlock_4b(POLY_KAL_DISP++, interfaceCtx->mapSegmentName[0], G_IM_FMT_CI, MAP_48x85_TEX_WIDTH,
G_TX_WRAP | G_TX_NOMIRROR, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD); 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 // Swap vertices to render left half on the right and vice-versa
if (mirroredWorld) { if (mirroredWorld) {
@ -363,9 +364,9 @@ void KaleidoScope_DrawDungeonMap(PlayState* play, GraphicsContext* gfxCtx) {
gSP1Quadrangle(POLY_KAL_DISP++, 0, 2, 3, 1, 0); gSP1Quadrangle(POLY_KAL_DISP++, 0, 2, 3, 1, 0);
} }
gDPLoadTextureBlock_4b(POLY_KAL_DISP++, interfaceCtx->mapSegmentName[1], G_IM_FMT_CI, 48, 85, 0, gDPLoadTextureBlock_4b(POLY_KAL_DISP++, interfaceCtx->mapSegmentName[1], G_IM_FMT_CI, MAP_48x85_TEX_WIDTH,
G_TX_WRAP | mirrorMode, G_TX_WRAP | G_TX_NOMIRROR, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, MAP_48x85_TEX_HEIGHT, 0, G_TX_WRAP | mirrorMode, G_TX_WRAP | G_TX_NOMIRROR, G_TX_NOMASK,
G_TX_NOLOD); G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD);
if (mirroredWorld) { if (mirroredWorld) {
gSP1Quadrangle(POLY_KAL_DISP++, 0, 2, 3, 1, 0); gSP1Quadrangle(POLY_KAL_DISP++, 0, 2, 3, 1, 0);

View File

@ -13,6 +13,10 @@ extern u8 gItemAgeReqs[];
extern u8 gAreaGsFlags[]; extern u8 gAreaGsFlags[];
extern bool gSelectingMask; 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_ADULT LINK_AGE_ADULT
#define AGE_REQ_CHILD LINK_AGE_CHILD #define AGE_REQ_CHILD LINK_AGE_CHILD
#define AGE_REQ_NONE 9 #define AGE_REQ_NONE 9

View File

@ -1205,6 +1205,8 @@ Gfx* KaleidoScope_DrawPageSections(Gfx* gfx, Vtx* vertices, void** textures) {
return gfx; return gfx;
} }
static uint8_t mapBlendMask[MAP_48x85_TEX_WIDTH * MAP_48x85_TEX_HEIGHT];
void KaleidoScope_DrawPages(PlayState* play, GraphicsContext* gfxCtx) { void KaleidoScope_DrawPages(PlayState* play, GraphicsContext* gfxCtx) {
static Color_RGB8 D_8082ACF4[12] = { static Color_RGB8 D_8082ACF4[12] = {
{ 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 255, 255, 0 }, { 0, 0, 0 }, { 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 if (pauseCtx->pageIndex) { // pageIndex != PAUSE_ITEM
gDPPipeSync(OVERLAY_DISP++); gDPPipeSync(OVERLAY_DISP++);
gDPSetCombineMode(OVERLAY_DISP++, G_CC_MODULATEIA, G_CC_MODULATEIA); 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; 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) { void KaleidoScope_LoadDungeonMap(PlayState* play) {
InterfaceContext* interfaceCtx = &play->interfaceCtx; 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[0] = sDungeonMapTexs[R_MAP_TEX_INDEX];
interfaceCtx->mapSegmentName[1] = sDungeonMapTexs[R_MAP_TEX_INDEX + 1]; 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) { void KaleidoScope_UpdateDungeonMap(PlayState* play) {
@ -3333,19 +3444,29 @@ void KaleidoScope_UpdateDungeonMap(PlayState* play) {
KaleidoScope_LoadDungeonMap(play); KaleidoScope_LoadDungeonMap(play);
Map_SetFloorPalettesData(play, pauseCtx->dungeonMapSlot - 3); 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 ((play->sceneNum >= SCENE_DEKU_TREE) && (play->sceneNum <= SCENE_TREASURE_BOX_SHOP)) {
if ((VREG(30) + 3) == pauseCtx->cursorPoint[PAUSE_MAP]) { if ((VREG(30) + 3) == pauseCtx->cursorPoint[PAUSE_MAP]) {
// HDTODO: Handle Runtime Modified Textures (HD) KaleidoScope_OverridePalIndexCI4(interfaceCtx->mapSegment[0], size, interfaceCtx->mapPaletteIndex, 14);
KaleidoScope_OverridePalIndexCI4(interfaceCtx->mapSegment[0], 2040, interfaceCtx->mapPaletteIndex, 14);
} }
} }
if ((play->sceneNum >= SCENE_DEKU_TREE) && (play->sceneNum <= SCENE_TREASURE_BOX_SHOP)) { if ((play->sceneNum >= SCENE_DEKU_TREE) && (play->sceneNum <= SCENE_TREASURE_BOX_SHOP)) {
if ((VREG(30) + 3) == pauseCtx->cursorPoint[PAUSE_MAP]) { if ((VREG(30) + 3) == pauseCtx->cursorPoint[PAUSE_MAP]) {
// HDTODO: Handle Runtime Modified Textures (HD) KaleidoScope_OverridePalIndexCI4(interfaceCtx->mapSegment[1], size, interfaceCtx->mapPaletteIndex, 14);
KaleidoScope_OverridePalIndexCI4(interfaceCtx->mapSegment[1], 2040, 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) void KaleidoScope_Update(PlayState* play)

View File

@ -1762,6 +1762,13 @@ static MapMarkData sMapMarkJabuJabuBellyMq[] = {
} }, } },
{ MAP_MARK_NONE, 0, { 0 } }, { 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[] = { static MapMarkData sMapMarkForestTempleMq[] = {