diff --git a/soh/assets/custom/presets/Speedrun - 2 Faster Quest.json b/soh/assets/custom/presets/Speedrun - 2 Faster Quest.json index 70fbdfaa5f..0b9dd42df5 100644 --- a/soh/assets/custom/presets/Speedrun - 2 Faster Quest.json +++ b/soh/assets/custom/presets/Speedrun - 2 Faster Quest.json @@ -18,6 +18,7 @@ "Autosave": 0, "BetterAmmoRendering": 0, "BetterFarore": 0, + "BetterOwl": 1, "BombchuBowlingFirstPrize": 4, "BombchusOOB": 1, "BowArrowCycle": 1, @@ -42,7 +43,9 @@ "EquipmentCanBeRemoved": 0, "FastBoomerang": 0, "FastChests": 1, + "FasterHeavyBlockLift": 1, "FasterPauseMenu": 0, + "FasterShadowShip": 1, "FileSelectMoreInfo": 1, "FirstPersonGauntlets": 0, "FishNeverEscape": 1, @@ -88,18 +91,19 @@ "ShowDoorLocksOnBothSides": 0, "SkipArrowAnimation": 1, "SkipText": 1, + "SkulltulaFreeze": 1, "SlowTextSpeed": 5, "StaticExplosionRadius": 0, "StrengthDisabled": 0, "SwordToggle": 0, - "TextSpeed": 5, + "TextSpeed": 6, "TimeFlowFileSelect": 0, "TimeSavers": { "SkipCutscene": { "BossIntro": 1, "Entrances": 1, "Intro": 1, - "LearnSong": 0, + "LearnSong": 1, "OnePoint": 0, "QuickBossDeaths": 1, "Story": 1 @@ -118,6 +122,8 @@ "exempt": [ "/gDeveloperTools/LogLevel", "/gDeveloperTools/ResourceLogging", + "/gEnhancements/AskToEquip", + "/gEnhancements/BetterSaveMenu", "/gEnhancements/DrawLineupTick", "/gEnhancements/GraveHoles", "/gEnhancements/IncludeHeldInputsBufferWindow", diff --git a/soh/assets/custom/presets/Speedrun - 3 Faster Quest with Cutscenes.json b/soh/assets/custom/presets/Speedrun - 3 Faster Quest with Cutscenes.json deleted file mode 100644 index d1a2a94f76..0000000000 --- a/soh/assets/custom/presets/Speedrun - 3 Faster Quest with Cutscenes.json +++ /dev/null @@ -1,123 +0,0 @@ -{ - "blocks": { - "enhancements": { - "gCheats": { - "EasyFrameAdvance": 0, - "HookshotReachMultiplier": 1.0, - "MoonJumpOnL": 0, - "SpeedModifier": { - "Value": 1.0 - } - }, - "gDeveloperTools": null, - "gEnhancements": { - "3DSceneRender": 0, - "AlwaysShowDungeonMinimapIcon": 0, - "AskToEquip": 0, - "AssignableTunicsAndBoots": 0, - "Autosave": 0, - "BetterAmmoRendering": 0, - "BetterFarore": 0, - "BombchuBowlingFirstPrize": 4, - "BombchusOOB": 1, - "BowArrowCycle": 1, - "ChildHookshotSoftlock": 1, - "CustomizeBombchuBowling": 1, - "CustomizeFishing": 1, - "CustomizeOcarinaGame": 0, - "DampeWin": 1, - "DisableBlackBars": 0, - "DisableBombBillboarding": 0, - "DisableCritWiggle": 1, - "DisableFixedCamera": 0, - "DisableGrottoRotation": 0, - "DisableLOD": 1, - "DisableLinkSwordTrail": 0, - "DpadEquips": 1, - "DpadNoDropOcarinaInput": 1, - "DrawLineupTick": 0, - "DynamicWalletIcon": 1, - "EarlyEyeballFrog": 1, - "EquipmentAlwaysVisible": 0, - "EquipmentCanBeRemoved": 0, - "FastBoomerang": 0, - "FastChests": 1, - "FasterPauseMenu": 0, - "FileSelectMoreInfo": 1, - "FirstPersonGauntlets": 0, - "FishNeverEscape": 1, - "FixDampeGoingBackwards": 0, - "FixDaruniaDanceSpeed": 1, - "FixDungeonMinimapIcon": 1, - "FixSawSoftlock": 1, - "FixVineFall": 1, - "ForgeTime": 1, - "GravediggingTourFix": 1, - "HoverFishing": 1, - "HyperBosses": 0, - "HyperEnemies": 0, - "IncludeHeldInputsBufferWindow": 0, - "InjectItemCounts": { - "GoldSkulltula": 1, - "HeartContainer": 1, - "HeartPiece": 1 - }, - "ItemUnequip": 0, - "MinimalUI": 0, - "MweepSpeed": 5.0, - "N64WeirdFrames": 1, - "NewDrops": 0, - "NoHUDHeartAnimation": 0, - "NoInputForCredits": 1, - "OcarinaGame": { - "StartingNotes": 8 - }, - "PauseAnyCursor": 2, - "PauseBufferWindow": 5, - "PauseMenuAnimatedLink": 0, - "QuickBongoKill": 1, - "QuickPutaway": 1, - "QuitFishingAtDoor": 1, - "RedGanonBlood": 1, - "RememberMapToggleState": 0, - "ReworkedTargeting": { - "Enabled": 0 - }, - "ScaleAdultEquipmentAsChild": 1, - "SceneSpecificDirtPathFix": 1, - "ShowDoorLocksOnBothSides": 0, - "SkipArrowAnimation": 1, - "SkipText": 1, - "SlowTextSpeed": 5, - "StaticExplosionRadius": 0, - "StrengthDisabled": 0, - "SwordToggle": 0, - "TextSpeed": 5, - "TimeFlowFileSelect": 0, - "TimeSavers": { - "SkipCutscene": { - "Intro": 1, - "QuickBossDeaths": 1 - } - }, - "ToTMedallionsColors": 0, - "ToggleStrength": 0, - "VisualAgony": 0, - "WideShutterDoorRange": 1 - }, - "gRandoEnhancements": null - } - }, - "exempt": [ - "/gDeveloperTools/LogLevel", - "/gDeveloperTools/ResourceLogging", - "/gEnhancements/DrawLineupTick", - "/gEnhancements/GraveHoles", - "/gEnhancements/IncludeHeldInputsBufferWindow", - "/gEnhancements/PauseBufferWindow" - ], - "fileType": 2, - "presetName": "Speedrun - 3 Faster Quest with Cutscenes", - "isBuiltIn": true, - "ConfigVersion": 7 -} diff --git a/soh/soh/Enhancements/speedrun/Speedrun.cpp b/soh/soh/Enhancements/speedrun/Speedrun.cpp index 6ee47ebb43..00a9686ffe 100644 --- a/soh/soh/Enhancements/speedrun/Speedrun.cpp +++ b/soh/soh/Enhancements/speedrun/Speedrun.cpp @@ -47,7 +47,8 @@ static const std::array sOwnedBlocks = { }; #define SPEEDRUN_PRESET_NONE "None" -#define SPEEDRUN_MAX_OPTIONS_ON_SCREEN 6 +#define SPEEDRUN_MAX_OPTIONS_ON_SCREEN 4 +#define SPEEDRUN_EXEMPT_ROWS 4 // {display name, preset name}, ending with "None" which has no preset. static std::vector> sPresetChoices; @@ -59,6 +60,9 @@ static nlohmann::json sExempt = nlohmann::json::array(); static std::string sPresetName = SPEEDRUN_PRESET_NONE; static uint32_t sSettingsHash = 0; static bool sMenuLocked = false; +// "Name: value" of each exempt setting for the preset picked in the menu, and which preset they were made for. +static std::vector sExemptLines; +static int sExemptLinesIndex = -1; // Everything else, menu included, is hidden so settings can't be changed mid-run. static const std::vector sAllowedWindows = { @@ -104,10 +108,10 @@ static void SetOwnedBlocks(const nlohmann::json& blocks) { ShipInit::InitAll(); } -static std::vector GetExemptPaths() { +static std::vector GetExemptPaths(const nlohmann::json& exempt) { std::vector paths; - for (const auto& path : sExempt) { + for (const auto& path : exempt) { try { paths.emplace_back(path.get()); } catch (const std::exception& e) { SPDLOG_ERROR("Speedrun: bad exempt path {}: {}", path.dump(), e.what()); } @@ -117,8 +121,8 @@ static std::vector GetExemptPaths() { } // Copies the player's exempt settings onto blocks. Ones the player never set keep the preset's value. -static void OverlayExemptSettings(nlohmann::json& blocks, const nlohmann::json& custom) { - for (const auto& path : GetExemptPaths()) { +static void OverlayExemptSettings(nlohmann::json& blocks, const nlohmann::json& custom, const nlohmann::json& exempt) { + for (const auto& path : GetExemptPaths(exempt)) { if (custom.contains(path)) { blocks[path] = custom[path]; } @@ -253,7 +257,7 @@ extern "C" bool Ship_QuestDebugEnabled(u8 questId) { // same build and settings. nlohmann sorts object keys, so the dump is stable. static uint32_t HashSettings() { nlohmann::json stripped = sSettings; - for (const auto& path : GetExemptPaths()) { + for (const auto& path : GetExemptPaths(sExempt)) { if (stripped.contains(path)) { stripped[path.parent_pointer()].erase(path.back()); } @@ -279,6 +283,41 @@ static void EmitHashNotification() { }); } +// The settings a new file gets with this preset: the player's settings, then the preset, then the exempt ones put back. +static nlohmann::json BuildFileSettings(const std::string& presetKey, const nlohmann::json& custom, + const nlohmann::json& exempt) { + nlohmann::json settings = custom; + + if (!presetKey.empty()) { + // Applied to the file's copy only, never the player's config. + settings = applyPresetToBlocks(presetKey, settings, { PRESET_SECTION_ENHANCEMENTS }); + } + + OverlayExemptSettings(settings, custom, exempt); + return settings; +} + +static void UpdateExemptLines(uint8_t presetIndex) { + if (sExemptLinesIndex == presetIndex) { + return; + } + sExemptLinesIndex = presetIndex; + sExemptLines.clear(); + + const std::string& presetKey = sPresetChoices[presetIndex].second; + nlohmann::json exempt = GetPresetExempt(presetKey); + nlohmann::json settings = BuildFileSettings(presetKey, GetOwnedBlocks(), exempt); + + for (const auto& path : GetExemptPaths(exempt)) { + std::string value = "default"; + if (settings.contains(path)) { + const auto& entry = settings[path]; + value = entry.is_string() ? entry.get() : entry.dump(); + } + sExemptLines.push_back(spdlog::fmt_lib::format("{}: {}", path.back(), value)); + } +} + extern "C" void Speedrun_LoadPresetChoices(FileChooseContext* fileChooseContext) { // "None" keeps the player's current settings. sPresetChoices = GetSpeedrunPresets(); @@ -287,6 +326,8 @@ extern "C" void Speedrun_LoadPresetChoices(FileChooseContext* fileChooseContext) fileChooseContext->speedrunIndex = 0; fileChooseContext->speedrunOffset = 0; } + // Player may have changed settings since the menu was last open. + sExemptLinesIndex = -1; } extern "C" void FileChoose_UpdateSpeedrunMenu(GameState* gameState) { @@ -335,6 +376,15 @@ extern "C" void FileChoose_DrawSpeedrunMenuWindowContents(FileChooseContext* fil static_cast(92 + textYOffset), false); } } + + // Exempt settings of the selected preset, with the values the new file will get, in columns under the list. + UpdateExemptLines(fileChooseContext->speedrunIndex); + for (size_t i = 0; i < sExemptLines.size(); i++) { + int16_t x = 65 + (int16_t)(i / SPEEDRUN_EXEMPT_ROWS) * 100; + int16_t y = 87 + SPEEDRUN_MAX_OPTIONS_ON_SCREEN * 16 + (int16_t)(i % SPEEDRUN_EXEMPT_ROWS) * 7; + Interface_DrawTextLine(fileChooseContext->state.gfxCtx, (char*)sExemptLines[i].c_str(), x, y, 180, 180, 180, + textAlpha, 0.5f, false); + } } extern "C" void Speedrun_InitSaveFile(u8 presetIndex) { @@ -342,16 +392,9 @@ extern "C" void Speedrun_InitSaveFile(u8 presetIndex) { sPresetName = sPresetChoices[presetIndex].first; nlohmann::json custom = GetOwnedBlocks(); - sSettings = custom; sConfigVersion = SOH::GetLatestConfigVersion(); sExempt = GetPresetExempt(presetKey); - - if (!presetKey.empty()) { - // Applied to the file's copy only, never the player's config. - sSettings = applyPresetToBlocks(presetKey, sSettings, { PRESET_SECTION_ENHANCEMENTS }); - } - - OverlayExemptSettings(sSettings, custom); + sSettings = BuildFileSettings(presetKey, custom, sExempt); // Runs are always timed in real time, whatever the "RTA Timing on new files" option says. gSaveContext.ship.stats.rtaTiming = 1; @@ -416,7 +459,7 @@ static void LoadSaveSection() { blocks = GetOwnedBlocks(); } - OverlayExemptSettings(blocks, custom); + OverlayExemptSettings(blocks, custom, sExempt); // No cosmetics during a run. Some scale Link or his sword, which changes his hitbox and reach. blocks[CVAR_PREFIX_COSMETIC] = nlohmann::json::object();