Compare commits

...

37 Commits

Author SHA1 Message Date
briaguya 818addfdda delta bump (#3465) 2023-11-29 01:32:15 -05:00
Malkierian f14c390364 [Vanilla Fix] Slow Down Darunia's Dance (#3438)
* Fix Darunia's dancing animation speed (static for now; needs toggle later).

* Moved previous code to toggleable fix in enhancements. Tweaked speed factors, partially successful.
2023-11-29 00:38:48 -05:00
Adam Bird dd5e72a023 limit entrance discovered saving to end of operation (#3459) 2023-11-29 00:29:25 -05:00
rozlette d523b104d8 Change declaration of Morpha tentacle verts to combine them all and avoid overlaps that result in dropped verts 2023-11-28 21:09:34 -08:00
Garrett Cox fdcd9a7508 Race Integrity QoL (#3445)
* Add gDisableChangingSettings

* Add support for dropping a config file to overwrite CVars
2023-11-28 23:42:37 -05:00
Malkierian c4f34624ba Flag tracker (#3447)
* Hook into flagset hooks for processing check tracking. Has some manual workarounds (some breaks still need to be found).
Remove areaChecks and looping functionality as redundant.

* Additional vanilla handling.

* Fix tracker not showing MQ checks in MQ non-rando.
Fix tracker marking non-MQ variants of dungeon checks (e.g. map chest, etc) when collecting in MQ.

* Set all areas to spoiled if not rando.

* Revert attempt to spoil in randomizer based on MQ dungeon count as I don't know how that works at the moment.

* Restore and update spoiling based on MQ dungeon settings (none, selection, or count of 12).
Fix Anju As Adult check.

* Remove Anchor-specific code :baguette:

* Use `ClearAreaTotals()` in `Teardown()` instead of the duplicate code there.

* Update to `ClearAreaChecksAndTotals()` with `vec.clear()` added.

* Fix type spoiling again. Now spoils on 0 MQ dungeons, not rando, if the option is enabled in check tracker settings, selection, or set number of 12.
Fix vanilla checks being marked collected in MQ dungeons.

* Fix 100 GS check.

* ACTUALLY fix 100 GS: change flag type to `RandomizerInf()` in `item_location.cpp`, add RC to RandoInf for it to the table. Also don't send GI for flag if father, falsely triggers ZR frogs minigame.

* Fix gravedigging tour tracking.

* Fix membership card check tracking.
Change scene and flag values to any existing enums.
Clarifying formatting for the checking loop vOrMQ conditions.

* Fix Gravedigging Tour tracking.
Simplify Always Win Gravedigging Tour and Fix Gravedigging Tour Glitch applications.
Modified all necessary paths to use vanilla GDT PoH collection flag instead of randomizer variant.

* Fix Kak Potion Shop being "seen" when entering as child.
2023-11-28 21:25:48 -05:00
Adam Bird 33fe8776bb Fix bugs dont despawn cheat to work with soil patches (#3457) 2023-11-29 02:02:45 +01:00
briaguya 02256fac66 bump LUS to 1.4.0 (#3461) 2023-11-29 01:37:54 +01:00
briaguya 621afc99f0 sort patch otrs (#3430) 2023-11-29 01:34:00 +01:00
inspectredc 9d215b6dce Separate Arrows Equip Dupe Fix (#3450)
* add gseparatearrow check for equip dupe fix

* update gseparatearrows tooltip
2023-11-28 22:13:00 +01:00
aMannus 420bdab328 Random Enemy Sizes fixes (#3452) 2023-11-28 14:30:36 -05:00
Adam Bird 717074ff86 Fix: Missing TextIDs for MQ PAL and change lava size (#3449)
* Fix text offset for MQ pal and add text ID asserts

* correct lava texture size
2023-11-28 20:11:03 +01:00
Malkierian 3ab16b70c2 Changed all checks for !gPlayState to !GameInteractor::IsSaveLoaded() in mods.cpp for all the cheats, and added the same full check to others that really didn't need to be running outside of a game (like infinite rupees, magic, health, etc), and clear any triggering CVars. (#3441)
Changed `RegisterSwitchAge()` to clear the CVar instead of setting it to 0.
2023-11-27 23:00:25 +01:00
Malkierian 3cf9d655a7 Disable Fix Vine Fall when Climb Everything is enabled (#3439)
* Disable Fix Vine Fall when Climb Everything is enabled.

* Remove option disabling.
2023-11-26 11:34:54 -05:00
inspectredc 95f27ace2e Fix kokiri sword unequipping when using switch age cheat/enhancements (#3415)
* test fix for ks unequip

* remove unnecessary brackets
2023-11-24 09:40:42 -05:00
Salt d50ad4779d Fix #3417 Symlinks to Directories in Mods Dir Aren't Traversed (#3418) 2023-11-24 09:40:30 -05:00
Adam Bird f2df029efa fix: skybox crash for mask shop (#3427) 2023-11-24 09:38:45 -05:00
Garrett Cox bdb201201e Fix audio cutoff issues (#3428) 2023-11-24 09:38:13 -05:00
Adam Bird f4e4545180 Re-implement King Dodongo's Lava texture effects (#3434)
* fix alt backgrounds not always loading

* include gfx lookup with the original unload

* Add hook for alt toggle

* handle cpu modified texture for kd lava

* malloc array instead of illegal initialize
2023-11-23 09:07:30 -05:00
briaguya 0ddb0711ad Version bump to MacReady Charlie (#3406) 2023-11-15 23:22:09 -05:00
briaguya 3234256b03 bump lus (#3405) 2023-11-15 22:45:09 -05:00
inspectredc d8a7a6c764 Use Correct Player Boot Enums in CC (#3403) 2023-11-15 20:38:21 -05:00
Garrett Cox 2dfbbc63e3 Version bump to MacReady Bravo (#3398) 2023-11-14 21:14:47 -05:00
Garrett Cox 044d32a46f Add gFixEyesOpenWhileSleeping (#3365) 2023-11-14 20:47:07 -05:00
Malkierian afe032ea21 [Feature/fix] Save to temp file first (#3376)
* Add temp file flow to `SaveManager::SaveFileThreaded`.
Add "Save finish" info log message.

* Fix WiiU/Switch
2023-11-14 20:46:50 -05:00
Josh Bodner fb45b66903 Fix magic being zeroed out when using fast file select (#3389)
* Move to frame counter init to a place that fast file select also touches

* Undo removing old fix

* Reset on gameover
2023-11-14 17:08:45 -05:00
Malkierian ba987c49e2 SaveManager cleanup (#3386)
* Move threadpool initialization and `OnExitGame` registration from `SaveManager::Init` to SM's constructor.
Comment on `Init` to mention it's not an initializer for `SaveManager`.
Added check for `SaveManager::SaveSection` to prevent firing a save worker if the game is already exited from a reset.

* Removed `IsSaveLoaded` check in favor of another `ThreadPoolWait()` at the start of `SaveManager::Init()`.
2023-11-14 16:46:38 -05:00
AltoXorg bd0672767a Use substr method to determine file extension (#3390)
See https://github.com/HarbourMasters/OTRExporter/pull/12
2023-11-14 16:37:54 -05:00
Adam Bird e66eb8756d Fix: Prevent patching custom models (#3367)
* fix prevent patching custom models

* prevent patching chests textures for custom chest models

* add tooltip for cosmetic editor about custom models

* chest texture handling for alt toggles
2023-11-14 16:37:03 -05:00
Garrett Cox bf31f2b330 Stop hardcoding skeleton type to flex (#3397) 2023-11-14 16:36:05 -05:00
Malkierian 4e9040d761 [Feature] Remove performDelayedSave functionality from Autosave (#3387)
* Removes delayed save functionality, making autosave work everywhere except Ganon and Chamber of Sages scenes.

* Change AutoSave comment to remove the scenarios we no longer block autosave in.

* handle temp B on saving outside of kaleido

---------

Co-authored-by: Adam Bird <archez39@me.com>
2023-11-14 16:35:19 -05:00
Malkierian 304016ddd2 [Feature Fix] Tunics stolen by like likes now removed from the item buttons (#3375)
* Extends `Assignable Boots and Tunics` functionality to check for and remove Goron and Zora tunics from item buttons when like likes steal them.

* Comment documentation.
2023-11-14 00:12:08 -05:00
briaguya fe9c0fa4f7 bump lus (#3394) 2023-11-14 00:10:56 -05:00
Malkierian 384403edb5 Rename all instances of Desert Wasteland to Haunted Wasteland. (#3372) 2023-11-13 23:45:52 -05:00
Malkierian 60687aff0d Move everything in RandomizerCheckTracker::LoadFile() except the block to load the "trackerData" section to a new OnLoadGame hook function to fix crashes on transferred saves. (#3368) 2023-11-13 23:45:41 -05:00
Ralphie Morell cf88b3d2bf Fix edge case of MS shuffle (#3364) 2023-11-13 23:45:15 -05:00
Malkierian 78ffb41cd2 Moved the check for !seqInfo.canBeUsedAsReplacement in InitializeShufflePool to exclude them before modifying either shuffle pool. (#3370) 2023-11-13 18:11:29 -05:00
65 changed files with 1228 additions and 593 deletions
+2 -2
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")
project(Ship VERSION 8.0.0 LANGUAGES C CXX)
set(PROJECT_BUILD_NAME "MacReady Alfa" CACHE STRING "")
project(Ship VERSION 8.0.3 LANGUAGES C CXX)
set(PROJECT_BUILD_NAME "MacReady Delta" CACHE STRING "")
set(PROJECT_TEAM "github.com/harbourmasters" CACHE STRING "")
set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT soh)
+1 -1
View File
@@ -79,7 +79,7 @@
<Vtx/>
</Array>
<Array Name="gMorphaVtx_007BB8" Count="4" Offset="0x7BB8">
<Array Name="gMorphaVtx_006A18" Count="286" Offset="0x006A18">
<Vtx/>
</Array>
@@ -1,6 +1,6 @@
<Root>
<File Name="ovl_Boss_Dodongo" BaseAddress="0x808C1190" RangeStart="0x6238" RangeEnd="0x9238">
<Texture Name="sLavaFloorLavaTex" OutName="lava_floor_lava" Format="rgba16" Width="64" Height="64" Offset="0x6238"/>
<Texture Name="sLavaFloorLavaTex" OutName="lava_floor_lava" Format="rgba16" Width="32" Height="64" Offset="0x6238"/>
<Texture Name="sLavaFloorRockTex" OutName="lava_floor_rock" Format="rgba16" Width="32" Height="64" Offset="0x8238"/>
</File>
</Root>
@@ -8,7 +8,7 @@
<!-- Morpha's Title Card -->
<Texture Name="gMorphaTitleCardTex" Format="i8" Width="128" Height="120" Offset="0x1010"/>
<Texture Name="gMorphaWaterTex" Format="rgba16" Width="32" Height="32" Offset="0x8870"/>
<!-- DLists for Morpha's Core -->
<DList Name="gMorphaCoreMembraneDL" Offset="0x6700"/>
<DList Name="gMorphaCoreNucleusDL" Offset="0x6838"/>
@@ -69,17 +69,17 @@
<!-- Unused content -->
<!-- This is the dlist for EnVbBall for some reason. -->
<DList Name="gMorphaDL_000550" Offset="0x550"/>
<DList Name="gMorphaDL_000550" Offset="0x550"/>
<DList Name="gMorphaDL_000EC0" Offset="0xEC0"/>
<DList Name="gMorphaDL_000EF8" Offset="0xEF8"/>
<DList Name="gMorphaDL_007BF8" Offset="0x7BF8"/>
<Array Name="gMorphaVtx_006938" Count="14" Offset="0x6938">
<Vtx/>
</Array>
<Array Name="gMorphaVtx_007BB8" Count="4" Offset="0x7BB8">
<Array Name="gMorphaVtx_006A18" Count="286" Offset="0x006A18">
<Vtx/>
</Array>
@@ -1,6 +1,6 @@
<Root>
<File Name="ovl_Boss_Dodongo" BaseAddress="0x808B7370" RangeStart="0x61E8" RangeEnd="0x9238">
<Texture Name="sLavaFloorLavaTex" OutName="lava_floor_lava" Format="rgba16" Width="64" Height="64" Offset="0x61E8"/>
<File Name="ovl_Boss_Dodongo" BaseAddress="0x808B7370" RangeStart="0x61E8" RangeEnd="0x91E8">
<Texture Name="sLavaFloorLavaTex" OutName="lava_floor_lava" Format="rgba16" Width="32" Height="64" Offset="0x61E8"/>
<Texture Name="sLavaFloorRockTex" OutName="lava_floor_rock" Format="rgba16" Width="32" Height="64" Offset="0x81E8"/>
</File>
</Root>
@@ -1,14 +1,14 @@
<Root>
<File Name="nes_message_data_static">
<Text Name="nes_message_data_static" CodeOffset="0xF6910"/>
<Text Name="nes_message_data_static" CodeOffset="0xF68F0"/>
</File>
<File Name="ger_message_data_static">
<Text Name="ger_message_data_static" CodeOffset="0xF6910" LangOffset="0xFAB38"/>
<Text Name="ger_message_data_static" CodeOffset="0xF68F0" LangOffset="0xFAB18"/>
</File>
<File Name="fra_message_data_static">
<Text Name="fra_message_data_static" CodeOffset="0xF6910" LangOffset="0xFCC48"/>
<Text Name="fra_message_data_static" CodeOffset="0xF68F0" LangOffset="0xFCC28"/>
</File>
<File Name="staff_message_data_static">
<Text Name="staff_message_data_static" CodeOffset="0xFED58"/>
<Text Name="staff_message_data_static" CodeOffset="0xFED38"/>
</File>
</Root>
@@ -8,7 +8,7 @@
<!-- Morpha's Title Card -->
<Texture Name="gMorphaTitleCardTex" Format="i8" Width="128" Height="120" Offset="0x1010"/>
<Texture Name="gMorphaWaterTex" Format="rgba16" Width="32" Height="32" Offset="0x8870"/>
<!-- DLists for Morpha's Core -->
<DList Name="gMorphaCoreMembraneDL" Offset="0x6700"/>
<DList Name="gMorphaCoreNucleusDL" Offset="0x6838"/>
@@ -69,17 +69,17 @@
<!-- Unused content -->
<!-- This is the dlist for EnVbBall for some reason. -->
<DList Name="gMorphaDL_000550" Offset="0x550"/>
<DList Name="gMorphaDL_000550" Offset="0x550"/>
<DList Name="gMorphaDL_000EC0" Offset="0xEC0"/>
<DList Name="gMorphaDL_000EF8" Offset="0xEF8"/>
<DList Name="gMorphaDL_007BF8" Offset="0x7BF8"/>
<Array Name="gMorphaVtx_006938" Count="14" Offset="0x6938">
<Vtx/>
</Array>
<Array Name="gMorphaVtx_007BB8" Count="4" Offset="0x7BB8">
<Array Name="gMorphaVtx_006A18" Count="286" Offset="0x006A18">
<Vtx/>
</Array>
@@ -1,6 +1,6 @@
<Root>
<File Name="ovl_Boss_Dodongo" BaseAddress="0x808C1210" RangeStart="0x6238" RangeEnd="0x9238">
<Texture Name="sLavaFloorLavaTex" OutName="lava_floor_lava" Format="rgba16" Width="64" Height="64" Offset="0x6238"/>
<Texture Name="sLavaFloorLavaTex" OutName="lava_floor_lava" Format="rgba16" Width="32" Height="64" Offset="0x6238"/>
<Texture Name="sLavaFloorRockTex" OutName="lava_floor_rock" Format="rgba16" Width="32" Height="64" Offset="0x8238"/>
</File>
</Root>
@@ -8,7 +8,7 @@
<!-- Morpha's Title Card -->
<Texture Name="gMorphaTitleCardTex" Format="i8" Width="128" Height="120" Offset="0x1010"/>
<Texture Name="gMorphaWaterTex" Format="rgba16" Width="32" Height="32" Offset="0x8870"/>
<!-- DLists for Morpha's Core -->
<DList Name="gMorphaCoreMembraneDL" Offset="0x6700"/>
<DList Name="gMorphaCoreNucleusDL" Offset="0x6838"/>
@@ -69,17 +69,17 @@
<!-- Unused content -->
<!-- This is the dlist for EnVbBall for some reason. -->
<DList Name="gMorphaDL_000550" Offset="0x550"/>
<DList Name="gMorphaDL_000550" Offset="0x550"/>
<DList Name="gMorphaDL_000EC0" Offset="0xEC0"/>
<DList Name="gMorphaDL_000EF8" Offset="0xEF8"/>
<DList Name="gMorphaDL_007BF8" Offset="0x7BF8"/>
<Array Name="gMorphaVtx_006938" Count="14" Offset="0x6938">
<Vtx/>
</Array>
<Array Name="gMorphaVtx_007BB8" Count="4" Offset="0x7BB8">
<Array Name="gMorphaVtx_006A18" Count="286" Offset="0x006A18">
<Vtx/>
</Array>
@@ -1,6 +1,6 @@
<Root>
<File Name="ovl_Boss_Dodongo" BaseAddress="0x808C1190" RangeStart="0x6238" RangeEnd="0x9238">
<Texture Name="sLavaFloorLavaTex" OutName="lava_floor_lava" Format="rgba16" Width="64" Height="64" Offset="0x6238"/>
<Texture Name="sLavaFloorRockTex" OutName="lava_floor_rock" Format="rgba16" Width="32" Height="64" Offset="0x8238"/>
<File Name="ovl_Boss_Dodongo" BaseAddress="0x808C1190" RangeStart="0x61E8" RangeEnd="0x91E8">
<Texture Name="sLavaFloorLavaTex" OutName="lava_floor_lava" Format="rgba16" Width="32" Height="64" Offset="0x61E8"/>
<Texture Name="sLavaFloorRockTex" OutName="lava_floor_rock" Format="rgba16" Width="32" Height="64" Offset="0x81E8"/>
</File>
</Root>
@@ -8,7 +8,7 @@
<!-- Morpha's Title Card -->
<Texture Name="gMorphaTitleCardTex" Format="i8" Width="128" Height="120" Offset="0x1010"/>
<Texture Name="gMorphaWaterTex" Format="rgba16" Width="32" Height="32" Offset="0x8870"/>
<!-- DLists for Morpha's Core -->
<DList Name="gMorphaCoreMembraneDL" Offset="0x6700"/>
<DList Name="gMorphaCoreNucleusDL" Offset="0x6838"/>
@@ -69,17 +69,17 @@
<!-- Unused content -->
<!-- This is the dlist for EnVbBall for some reason. -->
<DList Name="gMorphaDL_000550" Offset="0x550"/>
<DList Name="gMorphaDL_000550" Offset="0x550"/>
<DList Name="gMorphaDL_000EC0" Offset="0xEC0"/>
<DList Name="gMorphaDL_000EF8" Offset="0xEF8"/>
<DList Name="gMorphaDL_007BF8" Offset="0x7BF8"/>
<Array Name="gMorphaVtx_006938" Count="14" Offset="0x6938">
<Vtx/>
</Array>
<Array Name="gMorphaVtx_007BB8" Count="4" Offset="0x7BB8">
<Array Name="gMorphaVtx_006A18" Count="286" Offset="0x006A18">
<Vtx/>
</Array>
@@ -1,6 +1,6 @@
<Root>
<File Name="ovl_Boss_Dodongo" BaseAddress="0x8089E470" RangeStart="0x61C8" RangeEnd="0x91C8">
<Texture Name="sLavaFloorLavaTex" OutName="lava_floor_lava" Format="rgba16" Width="64" Height="64" Offset="0x61C8"/>
<Texture Name="sLavaFloorLavaTex" OutName="lava_floor_lava" Format="rgba16" Width="32" Height="64" Offset="0x61C8"/>
<Texture Name="sLavaFloorRockTex" OutName="lava_floor_rock" Format="rgba16" Width="32" Height="64" Offset="0x81C8"/>
</File>
</Root>
@@ -8,7 +8,7 @@
<!-- Morpha's Title Card -->
<Texture Name="gMorphaTitleCardTex" Format="i8" Width="128" Height="120" Offset="0x1010"/>
<Texture Name="gMorphaWaterTex" Format="rgba16" Width="32" Height="32" Offset="0x8870"/>
<!-- DLists for Morpha's Core -->
<DList Name="gMorphaCoreMembraneDL" Offset="0x6700"/>
<DList Name="gMorphaCoreNucleusDL" Offset="0x6838"/>
@@ -69,17 +69,17 @@
<!-- Unused content -->
<!-- This is the dlist for EnVbBall for some reason. -->
<DList Name="gMorphaDL_000550" Offset="0x550"/>
<DList Name="gMorphaDL_000550" Offset="0x550"/>
<DList Name="gMorphaDL_000EC0" Offset="0xEC0"/>
<DList Name="gMorphaDL_000EF8" Offset="0xEF8"/>
<DList Name="gMorphaDL_007BF8" Offset="0x7BF8"/>
<Array Name="gMorphaVtx_006938" Count="14" Offset="0x6938">
<Vtx/>
</Array>
<Array Name="gMorphaVtx_007BB8" Count="4" Offset="0x7BB8">
<Array Name="gMorphaVtx_006A18" Count="286" Offset="0x006A18">
<Vtx/>
</Array>
@@ -1,6 +1,6 @@
<Root>
<File Name="ovl_Boss_Dodongo" BaseAddress="0x808C1190" RangeStart="0x61C8" RangeEnd="0x91C8">
<Texture Name="sLavaFloorLavaTex" OutName="lava_floor_lava" Format="rgba16" Width="64" Height="64" Offset="0x61C8"/>
<Texture Name="sLavaFloorLavaTex" OutName="lava_floor_lava" Format="rgba16" Width="32" Height="64" Offset="0x61C8"/>
<Texture Name="sLavaFloorRockTex" OutName="lava_floor_rock" Format="rgba16" Width="32" Height="64" Offset="0x81C8"/>
</File>
</Root>
+1 -1
View File
@@ -439,7 +439,7 @@ static std::unordered_map<u16, const char*> actorDescriptions = {
{ ACTOR_EN_DAIKU_KAKARIKO, "Carpenters (Kakariko)" },
{ ACTOR_BG_BOWL_WALL, "Bombchu Bowling Alley Wall" },
{ ACTOR_EN_WALL_TUBO, "Bombchu Bowling Alley Bullseyes" },
{ ACTOR_EN_PO_DESERT, "Poe Guide (Desert Wasteland)" },
{ ACTOR_EN_PO_DESERT, "Poe Guide (Haunted Wasteland)" },
{ ACTOR_EN_CROW, "Guay" },
{ ACTOR_DOOR_KILLER, "Fake Door" },
{ ACTOR_BG_SPOT11_OASIS, "Oasis (Desert Colossus)" },
@@ -400,8 +400,9 @@ void AudioCollection::InitializeShufflePool() {
if (shufflePoolInitialized) return;
for (auto& [seqId, seqInfo] : sequenceMap) {
if (!seqInfo.canBeUsedAsReplacement) continue;
const std::string cvarKey = "gAudioEditor.Excluded." + seqInfo.sfxKey;
if (CVarGetInteger(cvarKey.c_str(), 0) && !seqInfo.canBeUsedAsReplacement) {
if (CVarGetInteger(cvarKey.c_str(), 0)) {
excludedSequences.insert(&seqInfo);
} else {
includedSequences.insert(&seqInfo);
@@ -312,6 +312,7 @@ namespace GameControlEditor {
DrawHelpIcon("Allows the cursor on the pause menu to be over any slot. Sometimes required in rando to select "
"certain items.");
UIWidgets::Spacer(0);
ImGui::BeginDisabled(CVarGetInteger("gDisableChangingSettings", 0));
UIWidgets::PaddedEnhancementCheckbox("Enable walk speed modifiers", "gEnableWalkModify", true, false);
DrawHelpIcon("Hold the assigned button to change the maximum walking speed\nTo change the assigned button, go into the Ports tabs above");
if (CVarGetInteger("gEnableWalkModify", 0)) {
@@ -323,6 +324,7 @@ namespace GameControlEditor {
UIWidgets::PaddedEnhancementSliderFloat("Modifier 2: %d %%", "##WalkMod2", "gWalkModifierTwo", 0.0f, 5.0f, "", 1.0f, true, true, false, true);
window->EndGroupPanelPublic(0);
}
ImGui::EndDisabled();
UIWidgets::Spacer(0);
UIWidgets::PaddedEnhancementCheckbox("Answer Navi Prompt with L Button", "gNaviOnL");
DrawHelpIcon("Speak to Navi with L but enter first-person camera with C-Up");
@@ -1485,6 +1485,7 @@ void Draw_Placements(){
}
void DrawSillyTab() {
ImGui::BeginDisabled(CVarGetInteger("gDisableChangingSettings", 0));
if (CVarGetInteger("gLetItSnow", 0)) {
if (UIWidgets::EnhancementCheckbox("Let It Snow", "gLetItSnow")) {
LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
@@ -1569,6 +1570,7 @@ void DrawSillyTab() {
CVarClear("gCosmetics.Kak_Windmill_Speed.Changed");
LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
}
ImGui::EndDisabled();
}
// Copies the RGB values from one cosmetic option to another, multiplied by the passed in amount, this
@@ -1767,6 +1769,10 @@ void CosmeticsEditorWindow::DrawElement() {
ImGui::SameLine();
UIWidgets::EnhancementCombobox("gCosmetics.DefaultColorScheme", colorSchemes, COLORSCHEME_N64);
UIWidgets::EnhancementCheckbox("Advanced Mode", "gCosmetics.AdvancedMode");
UIWidgets::InsertHelpHoverText(
"Some cosmetic options may not apply if you have any mods that provide custom models for the cosmetic option.\n\n"
"For example, if you have custom Link model, then the Link's Hair color option will most likely not apply."
);
if (CVarGetInteger("gCosmetics.AdvancedMode", 0)) {
if (ImGui::Button("Lock All Advanced", ImVec2(ImGui::GetContentRegionAvail().x / 2, 30.0f))) {
for (auto& [id, cosmeticOption] : cosmeticOptions) {
@@ -380,13 +380,13 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) {
effect->category = kEffectCatBoots;
effect->timeRemaining = 30000;
effect->giEffect = new GameInteractionEffect::ForceEquipBoots();
effect->giEffect->parameters[0] = PLAYER_BOOTS_IRON;
effect->giEffect->parameters[0] = EQUIP_VALUE_BOOTS_IRON;
break;
case kEffectForceHoverBoots:
effect->category = kEffectCatBoots;
effect->timeRemaining = 30000;
effect->giEffect = new GameInteractionEffect::ForceEquipBoots();
effect->giEffect->parameters[0] = PLAYER_BOOTS_HOVER;
effect->giEffect->parameters[0] = EQUIP_VALUE_BOOTS_HOVER;
break;
case kEffectSlipperyFloor:
effect->category = kEffectCatSlipperyFloor;
+1
View File
@@ -207,6 +207,7 @@ static bool ResetHandler(std::shared_ptr<LUS::Console> Console, std::vector<std:
return 1;
}
gPlayState->gameplayFrames = 0;
SET_NEXT_GAMESTATE(&gPlayState->state, TitleSetup_Init, GameState);
gPlayState->state.running = false;
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnExitGame>(gSaveContext.fileNum);
+5
View File
@@ -1,3 +1,6 @@
#ifndef _ENHANCEMENT_TYPES_H_
#define _ENHANCEMENT_TYPES_H_
typedef enum {
WARP_MODE_OVERRIDE_OFF,
WARP_MODE_OVERRIDE_MQ_AS_VANILLA,
@@ -74,3 +77,5 @@ typedef enum {
DEKU_STICK_UNBREAKABLE,
DEKU_STICK_UNBREAKABLE_AND_ALWAYS_ON_FIRE,
} DekuStickType;
#endif
@@ -193,6 +193,8 @@ public:
DEFINE_HOOK(OnSetGameLanguage, void());
DEFINE_HOOK(OnAssetAltChange, void());
// Helpers
static bool IsSaveLoaded();
static bool IsGameplayPaused();
@@ -181,3 +181,9 @@ void GameInteractor_ExecuteOnUpdateFileNameSelection(int16_t charCode) {
void GameInteractor_ExecuteOnSetGameLanguage() {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSetGameLanguage>();
}
// MARK: - System
void GameInteractor_RegisterOnAssetAltChange(void (*fn)(void)) {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnAssetAltChange>(fn);
}
@@ -56,6 +56,10 @@ void GameInteractor_ExecuteOnUpdateFileNameSelection(int16_t charCode);
// MARK: - Game
void GameInteractor_ExecuteOnSetGameLanguage();
// MARK: - System
void GameInteractor_RegisterOnAssetAltChange(void (*fn)(void));
#ifdef __cplusplus
}
#endif
+31 -36
View File
@@ -35,8 +35,6 @@ extern PlayState* gPlayState;
extern void Overlay_DisplayText(float duration, const char* text);
uint32_t ResourceMgr_IsSceneMasterQuest(s16 sceneNum);
}
bool performDelayedSave = false;
bool performSave = false;
// TODO: When there's more uses of something like this, create a new GI::RawAction?
void ReloadSceneTogglingLinkAge() {
@@ -49,6 +47,7 @@ void ReloadSceneTogglingLinkAge() {
void RegisterInfiniteMoney() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([]() {
if (!GameInteractor::IsSaveLoaded()) return;
if (CVarGetInteger("gInfiniteMoney", 0) != 0) {
if (gSaveContext.rupees < CUR_CAPACITY(UPG_WALLET)) {
gSaveContext.rupees = CUR_CAPACITY(UPG_WALLET);
@@ -59,6 +58,7 @@ void RegisterInfiniteMoney() {
void RegisterInfiniteHealth() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([]() {
if (!GameInteractor::IsSaveLoaded()) return;
if (CVarGetInteger("gInfiniteHealth", 0) != 0) {
if (gSaveContext.health < gSaveContext.healthCapacity) {
gSaveContext.health = gSaveContext.healthCapacity;
@@ -69,6 +69,7 @@ void RegisterInfiniteHealth() {
void RegisterInfiniteAmmo() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([]() {
if (!GameInteractor::IsSaveLoaded()) return;
if (CVarGetInteger("gInfiniteAmmo", 0) != 0) {
// Deku Sticks
if (AMMO(ITEM_STICK) < CUR_CAPACITY(UPG_STICKS)) {
@@ -105,6 +106,7 @@ void RegisterInfiniteAmmo() {
void RegisterInfiniteMagic() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([]() {
if (!GameInteractor::IsSaveLoaded()) return;
if (CVarGetInteger("gInfiniteMagic", 0) != 0) {
if (gSaveContext.isMagicAcquired && gSaveContext.magic != (gSaveContext.isDoubleMagicAcquired + 1) * 0x30) {
gSaveContext.magic = (gSaveContext.isDoubleMagicAcquired + 1) * 0x30;
@@ -115,6 +117,7 @@ void RegisterInfiniteMagic() {
void RegisterInfiniteNayrusLove() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([]() {
if (!GameInteractor::IsSaveLoaded()) return;
if (CVarGetInteger("gInfiniteNayru", 0) != 0) {
gSaveContext.nayrusLoveTimer = 0x44B;
}
@@ -123,7 +126,7 @@ void RegisterInfiniteNayrusLove() {
void RegisterMoonJumpOnL() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([]() {
if (!gPlayState) return;
if (!GameInteractor::IsSaveLoaded()) return;
if (CVarGetInteger("gMoonJumpOnL", 0) != 0) {
Player* player = GET_PLAYER(gPlayState);
@@ -138,7 +141,7 @@ void RegisterMoonJumpOnL() {
void RegisterInfiniteISG() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([]() {
if (!gPlayState) return;
if (!GameInteractor::IsSaveLoaded()) return;
if (CVarGetInteger("gEzISG", 0) != 0) {
Player* player = GET_PLAYER(gPlayState);
@@ -150,7 +153,7 @@ void RegisterInfiniteISG() {
//Permanent quick put away (QPA) glitched damage value
void RegisterEzQPA() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([]() {
if (!gPlayState) return;
if (!GameInteractor::IsSaveLoaded()) return;
if (CVarGetInteger("gEzQPA", 0) != 0) {
Player* player = GET_PLAYER(gPlayState);
@@ -162,7 +165,7 @@ void RegisterEzQPA() {
void RegisterUnrestrictedItems() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([]() {
if (!gPlayState) return;
if (!GameInteractor::IsSaveLoaded()) return;
if (CVarGetInteger("gNoRestrictItems", 0) != 0) {
u8 sunsBackup = gPlayState->interfaceCtx.restrictions.sunsSong;
@@ -190,14 +193,16 @@ void RegisterFreezeTime() {
/// Switches Link's age and respawns him at the last entrance he entered.
void RegisterSwitchAge() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([]() {
if (!GameInteractor::IsSaveLoaded()) {
CVarClear("gSwitchAge");
return;
}
static bool warped = false;
static Vec3f playerPos;
static int16_t playerYaw;
static RoomContext* roomCtx;
static s32 roomNum;
if (!gPlayState) return;
if (CVarGetInteger("gSwitchAge", 0) && !warped) {
playerPos = GET_PLAYER(gPlayState)->actor.world.pos;
playerYaw = GET_PLAYER(gPlayState)->actor.shape.rot.y;
@@ -217,7 +222,7 @@ void RegisterSwitchAge() {
func_80097534(gPlayState, roomCtx); // load map for new room (unloading the previous room)
}
warped = false;
CVarSetInteger("gSwitchAge", 0);
CVarClear("gSwitchAge");
}
});
}
@@ -226,7 +231,8 @@ void RegisterSwitchAge() {
void RegisterOcarinaTimeTravel() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnOcarinaSongAction>([]() {
if (!gPlayState) {
if (!GameInteractor::IsSaveLoaded()) {
CVarClear("gTimeTravel");
return;
}
@@ -258,14 +264,12 @@ void RegisterOcarinaTimeTravel() {
void AutoSave(GetItemEntry itemEntry) {
u8 item = itemEntry.itemId;
bool performSave = false;
// Don't autosave immediately after buying items from shops to prevent getting them for free!
// Don't autosave in the Chamber of Sages since resuming from that map breaks the game
// Don't autosave during the Ganon fight when picking up the Master Sword
// Don't autosave in the fishing pond to prevent getting rod on B outside of the pond
// Don't autosave in the bombchu bowling alley to prevent having chus on B outside of the minigame
// Don't autosave in grottos since resuming from grottos breaks the game.
if ((CVarGetInteger("gAutosave", AUTOSAVE_OFF) != AUTOSAVE_OFF) && (gPlayState != NULL) && (gSaveContext.pendingSale == ITEM_NONE) &&
(gPlayState->gameplayFrames > 60 && gSaveContext.cutsceneIndex < 0xFFF0) && (gPlayState->sceneNum != SCENE_GANON_BOSS)) {
(gPlayState->gameplayFrames > 60 && gSaveContext.cutsceneIndex < 0xFFF0) && (gPlayState->sceneNum != SCENE_GANON_BOSS) && (gPlayState->sceneNum != SCENE_CHAMBER_OF_THE_SAGES)) {
if (((CVarGetInteger("gAutosave", AUTOSAVE_OFF) == AUTOSAVE_LOCATION_AND_ALL_ITEMS) || (CVarGetInteger("gAutosave", AUTOSAVE_OFF) == AUTOSAVE_ALL_ITEMS)) && (item != ITEM_NONE)) {
// Autosave for all items
performSave = true;
@@ -326,25 +330,9 @@ void AutoSave(GetItemEntry itemEntry) {
CVarGetInteger("gAutosave", AUTOSAVE_OFF) == AUTOSAVE_LOCATION) {
performSave = true;
}
if (gPlayState->sceneNum == SCENE_FAIRYS_FOUNTAIN || gPlayState->sceneNum == SCENE_GROTTOS ||
gPlayState->sceneNum == SCENE_CHAMBER_OF_THE_SAGES || gPlayState->sceneNum == SCENE_FISHING_POND ||
gPlayState->sceneNum == SCENE_BOMBCHU_BOWLING_ALLEY) {
if (CVarGetInteger("gAutosave", AUTOSAVE_OFF) == AUTOSAVE_LOCATION_AND_MAJOR_ITEMS ||
CVarGetInteger("gAutosave", AUTOSAVE_OFF) == AUTOSAVE_LOCATION_AND_ALL_ITEMS ||
CVarGetInteger("gAutosave", AUTOSAVE_OFF) == AUTOSAVE_LOCATION) {
performSave = false;
return;
}
if (performSave) {
performSave = false;
performDelayedSave = true;
}
return;
}
if (performSave || performDelayedSave) {
if (performSave) {
Play_PerformSave(gPlayState);
performSave = false;
performDelayedSave = false;
}
}
}
@@ -1045,8 +1033,16 @@ void RegisterRandomizedEnemySizes() {
Player* player = GET_PLAYER(gPlayState);
Actor* actor = static_cast<Actor*>(refActor);
// Only apply to enemies and bosses. Exclude the wobbly platforms in Jabu because they need to act like platforms.
if (!CVarGetInteger("gRandomizedEnemySizes", 0) || (actor->category != ACTORCAT_ENEMY && actor->category != ACTORCAT_BOSS) || actor->id == ACTOR_EN_BROB) {
// Exclude wobbly platforms in Jabu because they need to act like platforms.
// Exclude Dead Hand hands and Bongo Bongo main body because they make the fights (near) impossible.
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;
// Only apply to enemies and bosses.
if (!CVarGetInteger("gRandomizedEnemySizes", 0) || (actor->category != ACTORCAT_ENEMY && actor->category != ACTORCAT_BOSS) || excludedEnemy) {
return;
}
@@ -1055,9 +1051,8 @@ void RegisterRandomizedEnemySizes() {
uint8_t bigActor = rand() % 2;
// Big actor. Dodongo and Volvagia are always smaller because they're impossible when bigger.
if (bigActor && actor->id != ACTOR_BOSS_DODONGO && actor->id != ACTOR_BOSS_FD &&
actor->id != ACTOR_BOSS_FD2) {
// Big actor
if (bigActor && !smallOnlyEnemy) {
randomNumber = rand() % 200;
// Between 100% and 300% size.
randomScale = 1.0f + (randomNumber / 100);
+51 -1
View File
@@ -217,6 +217,31 @@ const std::vector<const char*> enhancementsCvars = {
"gFixTexturesOOB",
"gIvanCoopModeEnabled",
"gEnemySpawnsOverWaterboxes",
"gTreeStickDrops",
"gShadowTag",
"gRandomizedEnemySizes",
"gRandomizedEnemies",
"gMirroredWorldMode",
"gMirroredWorld",
"gHyperEnemies",
"gHookshotableReticle",
"gHideBunnyHood",
"gFixVineFall",
"gFileSelectMoreInfo",
"gEnemyHealthBar",
"gBushDropFix",
"gAllDogsRichard",
"gAddTraps.enabled",
"gAddTraps.Ammo",
"gAddTraps.Bomb",
"gAddTraps.Burn",
"gAddTraps.Ice",
"gAddTraps.Kill",
"gAddTraps.Knock",
"gAddTraps.Shock",
"gAddTraps.Speed",
"gAddTraps.Tele",
"gAddTraps.Void",
};
const std::vector<const char*> cheatCvars = {
@@ -269,7 +294,23 @@ const std::vector<const char*> cheatCvars = {
"gNoRedeadFreeze",
"gBombTimerMultiplier",
"gNoFishDespawn",
"gNoBugsDespawn"
"gNoBugsDespawn",
"gWalkModifierDoesntChangeJump",
"gStatsEnabled",
"gSaveStatesEnabled",
"gSaveStatePromise",
"gRegEditEnabled",
"gPreset0",
"gPreset1",
"gDekuStickCheat",
"gDebugWarpScreenTranslation",
"gDebugSaveFileMode",
"gCosmetics.Link_BodyScale.Changed",
"gCosmetics.Link_BodyScale.Value",
"gCosmetics.Link_HeadScale.Changed",
"gCosmetics.Link_HeadScale.Value",
"gCosmetics.Link_SwordScale.Changed",
"gCosmetics.Link_SwordScale.Value",
};
const std::vector<const char*> randomizerCvars = {
@@ -399,6 +440,15 @@ const std::vector<const char*> randomizerCvars = {
"gRandomizeGregHint",
"gRandoManualSeedEntry",
"gRandomizerSettingsEnabled",
"gRandomizeTriforceHuntTotalPieces",
"gRandomizeTriforceHuntRequiredPieces",
"gRandomizeTriforceHunt",
"gRandomizeShuffleMasterSword",
"gRandomizeSariaHint",
"gRandomizeRupeeNames",
"gRandomizeFrogsHint",
"gRandoRelevantNavi",
"gRandoQuestItemFanfares",
};
const std::vector<PresetEntry> vanillaPlusPresetEntries = {
@@ -24,11 +24,11 @@ void LocationTable_Init() {
//Lost Woods
locationTable[LW_NEAR_SHORTCUTS_GROTTO_CHEST] = ItemLocation::Chest (RC_LW_NEAR_SHORTCUTS_GROTTO_CHEST, 0x3E, 0x14, "LW Near Shortcuts Grotto Chest", LW_NEAR_SHORTCUTS_GROTTO_CHEST, BLUE_RUPEE, {}, SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
locationTable[LW_SKULL_KID] = ItemLocation::Base (RC_LW_SKULL_KID, 0x5B, "LW Skull Kid", LW_SKULL_KID, PIECE_OF_HEART, {}, SpoilerCollectionCheck::ItemGetInf(30), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
locationTable[LW_SKULL_KID] = ItemLocation::Base (RC_LW_SKULL_KID, 0x5B, "LW Skull Kid", LW_SKULL_KID, PIECE_OF_HEART, {}, SpoilerCollectionCheck::ItemGetInf(22), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
locationTable[LW_TRADE_COJIRO] = ItemLocation::Base (RC_LW_TRADE_COJIRO, 0x5B, "LW Trade Cojiro", LW_TRADE_COJIRO, ODD_MUSHROOM, {Category::cAdultTrade}, SpoilerCollectionCheck::RandomizerInf(), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
locationTable[LW_TRADE_ODD_POTION] = ItemLocation::Base (RC_LW_TRADE_ODD_POTION, 0x5B, "LW Trade Odd Potion", LW_TRADE_ODD_POTION, ODD_POTION, {Category::cAdultTrade}, SpoilerCollectionCheck::ItemGetInf(57), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
locationTable[LW_OCARINA_MEMORY_GAME] = ItemLocation::Base (RC_LW_OCARINA_MEMORY_GAME, 0x5B, "LW Ocarina Memory Game", LW_OCARINA_MEMORY_GAME, PIECE_OF_HEART, {}, SpoilerCollectionCheck::ItemGetInf(31), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
locationTable[LW_TARGET_IN_WOODS] = ItemLocation::Base (RC_LW_TARGET_IN_WOODS, 0x5B, "LW Target in Woods", LW_TARGET_IN_WOODS, PROGRESSIVE_SLINGSHOT, {}, SpoilerCollectionCheck::ItemGetInf(21), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
locationTable[LW_TRADE_ODD_POTION] = ItemLocation::Base (RC_LW_TRADE_ODD_POTION, 0x5B, "LW Trade Odd Potion", LW_TRADE_ODD_POTION, ODD_POTION, {Category::cAdultTrade}, SpoilerCollectionCheck::ItemGetInf(49), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
locationTable[LW_OCARINA_MEMORY_GAME] = ItemLocation::Base (RC_LW_OCARINA_MEMORY_GAME, 0x5B, "LW Ocarina Memory Game", LW_OCARINA_MEMORY_GAME, PIECE_OF_HEART, {}, SpoilerCollectionCheck::ItemGetInf(23), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
locationTable[LW_TARGET_IN_WOODS] = ItemLocation::Base (RC_LW_TARGET_IN_WOODS, 0x5B, "LW Target in Woods", LW_TARGET_IN_WOODS, PROGRESSIVE_SLINGSHOT, {}, SpoilerCollectionCheck::ItemGetInf(29), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
locationTable[LW_DEKU_SCRUB_NEAR_DEKU_THEATER_RIGHT] = ItemLocation::Base (RC_LW_DEKU_SCRUB_NEAR_DEKU_THEATER_RIGHT, 0x5B, "LW Deku Scrub Near Deku Theater Right",LW_DEKU_SCRUB_NEAR_DEKU_THEATER_RIGHT, BUY_DEKU_NUT_5, {Category::cDekuScrub}, SpoilerCollectionCheck::Scrub(), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
locationTable[LW_DEKU_SCRUB_NEAR_DEKU_THEATER_LEFT] = ItemLocation::Base (RC_LW_DEKU_SCRUB_NEAR_DEKU_THEATER_LEFT, 0x5B, "LW Deku Scrub Near Deku Theater Left", LW_DEKU_SCRUB_NEAR_DEKU_THEATER_LEFT, BUY_DEKU_STICK_1, {Category::cDekuScrub}, SpoilerCollectionCheck::Scrub(), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
locationTable[LW_DEKU_SCRUB_NEAR_BRIDGE] = ItemLocation::Base (RC_LW_DEKU_SCRUB_NEAR_BRIDGE, 0x5B, "LW Deku Scrub Near Bridge", LW_DEKU_SCRUB_NEAR_BRIDGE, PROGRESSIVE_STICK_UPGRADE, {Category::cDekuScrub, Category::cDekuScrubUpgrades}, SpoilerCollectionCheck::Scrub(), SpoilerCollectionCheckGroup::GROUP_LOST_WOODS);
@@ -53,7 +53,7 @@ void LocationTable_Init() {
//Lake Hylia
locationTable[LH_CHILD_FISHING] = ItemLocation::Base (RC_LH_CHILD_FISHING, 0x49, "LH Child Fishing", LH_CHILD_FISHING, PIECE_OF_HEART, {}, SpoilerCollectionCheck::RandomizerInf(), SpoilerCollectionCheckGroup::GROUP_LAKE_HYLIA);
locationTable[LH_ADULT_FISHING] = ItemLocation::Base (RC_LH_ADULT_FISHING, 0x49, "LH Adult Fishing", LH_ADULT_FISHING, PROGRESSIVE_SCALE, {}, SpoilerCollectionCheck::RandomizerInf(), SpoilerCollectionCheckGroup::GROUP_LAKE_HYLIA);
locationTable[LH_LAB_DIVE] = ItemLocation::Base (RC_LH_LAB_DIVE, 0x38, "LH Lab Dive", LH_LAB_DIVE, PIECE_OF_HEART, {}, SpoilerCollectionCheck::ItemGetInf(24), SpoilerCollectionCheckGroup::GROUP_LAKE_HYLIA);
locationTable[LH_LAB_DIVE] = ItemLocation::Base (RC_LH_LAB_DIVE, 0x38, "LH Lab Dive", LH_LAB_DIVE, PIECE_OF_HEART, {}, SpoilerCollectionCheck::ItemGetInf(16), SpoilerCollectionCheckGroup::GROUP_LAKE_HYLIA);
locationTable[LH_TRADE_FROG] = ItemLocation::Base (RC_LH_TRADE_FROG, 0x38, "LH Lab Trade Eyeball Frog", LH_TRADE_FROG, EYEDROPS, {Category::cAdultTrade}, SpoilerCollectionCheck::RandomizerInf(), SpoilerCollectionCheckGroup::GROUP_LAKE_HYLIA);
locationTable[LH_UNDERWATER_ITEM] = ItemLocation::Base (RC_LH_UNDERWATER_ITEM, 0x57, "LH Underwater Item", LH_UNDERWATER_ITEM, RUTOS_LETTER, {}, SpoilerCollectionCheck::EventChkInf(0x31), SpoilerCollectionCheckGroup::GROUP_LAKE_HYLIA);
locationTable[LH_SUN] = ItemLocation::Base (RC_LH_SUN, 0x57, "LH Sun", LH_SUN, FIRE_ARROWS, {}, SpoilerCollectionCheck::Chest(0x57, 0x1F), SpoilerCollectionCheckGroup::GROUP_LAKE_HYLIA);
@@ -73,7 +73,7 @@ void LocationTable_Init() {
//Gerudo Fortress
locationTable[GF_CHEST] = ItemLocation::Chest (RC_GF_CHEST, 0x5D, 0x00, "GF Chest", GF_CHEST, PIECE_OF_HEART, {}, SpoilerCollectionCheckGroup::GROUP_GERUDO_VALLEY);
locationTable[GF_HBA_1000_POINTS] = ItemLocation::Base (RC_GF_HBA_1000_POINTS, 0x5D, "GF HBA 1000 Points", GF_HBA_1000_POINTS, PIECE_OF_HEART, {}, SpoilerCollectionCheck::InfTable(0x19, 0x08), SpoilerCollectionCheckGroup::GROUP_GERUDO_VALLEY);
locationTable[GF_HBA_1500_POINTS] = ItemLocation::Base (RC_GF_HBA_1500_POINTS, 0x5D, "GF HBA 1500 Points", GF_HBA_1500_POINTS, PROGRESSIVE_BOW, {}, SpoilerCollectionCheck::ItemGetInf(7), SpoilerCollectionCheckGroup::GROUP_GERUDO_VALLEY);
locationTable[GF_HBA_1500_POINTS] = ItemLocation::Base (RC_GF_HBA_1500_POINTS, 0x5D, "GF HBA 1500 Points", GF_HBA_1500_POINTS, PROGRESSIVE_BOW, {}, SpoilerCollectionCheck::ItemGetInf(15), SpoilerCollectionCheckGroup::GROUP_GERUDO_VALLEY);
locationTable[GF_GERUDO_MEMBERSHIP_CARD] = ItemLocation::Base (RC_GF_GERUDO_MEMBERSHIP_CARD, 0x0C, "GF Gerudo Membership Card", GF_GERUDO_MEMBERSHIP_CARD, GERUDO_MEMBERSHIP_CARD, {}, SpoilerCollectionCheck::GerudoToken(), SpoilerCollectionCheckGroup::GROUP_GERUDO_VALLEY);
locationTable[GF_NORTH_F1_CARPENTER] = ItemLocation::Collectable(RC_GF_NORTH_F1_CARPENTER, 0x0C, 0x0C, "GF North F1 Carpenter", GF_NORTH_F1_CARPENTER, GERUDO_FORTRESS_SMALL_KEY, {Category::cVanillaGFSmallKey}, SpoilerCollectionCheckGroup::GROUP_GERUDO_VALLEY);
locationTable[GF_NORTH_F2_CARPENTER] = ItemLocation::Collectable(RC_GF_NORTH_F2_CARPENTER, 0x0C, 0x0A, "GF North F2 Carpenter", GF_NORTH_F2_CARPENTER, GERUDO_FORTRESS_SMALL_KEY, {Category::cVanillaGFSmallKey}, SpoilerCollectionCheckGroup::GROUP_GERUDO_VALLEY);
@@ -90,12 +90,12 @@ void LocationTable_Init() {
locationTable[COLOSSUS_DEKU_SCRUB_GROTTO_FRONT] = ItemLocation::GrottoScrub(RC_COLOSSUS_DEKU_SCRUB_GROTTO_FRONT, 0xFD, "Colossus Deku Scrub Grotto Front", COLOSSUS_DEKU_SCRUB_GROTTO_FRONT, BUY_GREEN_POTION, {Category::cDekuScrub}, SpoilerCollectionCheck::Scrub(), SpoilerCollectionCheckGroup::GROUP_GERUDO_VALLEY);
//Market
locationTable[MARKET_TREASURE_CHEST_GAME_REWARD] = ItemLocation::Chest (RC_MARKET_TREASURE_CHEST_GAME_REWARD, 0x10, "MK Treasure Chest Game Reward", MARKET_TREASURE_CHEST_GAME_REWARD, TREASURE_GAME_HEART, {}, SpoilerCollectionCheck::ItemGetInf(19), SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
locationTable[MARKET_BOMBCHU_BOWLING_FIRST_PRIZE] = ItemLocation::Base (RC_MARKET_BOMBCHU_BOWLING_FIRST_PRIZE, 0x4B, "MK Bombchu Bowling First Prize", MARKET_BOMBCHU_BOWLING_FIRST_PRIZE, PROGRESSIVE_BOMB_BAG, {}, SpoilerCollectionCheck::ItemGetInf(25), SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
locationTable[MARKET_BOMBCHU_BOWLING_SECOND_PRIZE] = ItemLocation::Base (RC_MARKET_BOMBCHU_BOWLING_SECOND_PRIZE, 0x4B, "MK Bombchu Bowling Second Prize", MARKET_BOMBCHU_BOWLING_SECOND_PRIZE, PIECE_OF_HEART, {}, SpoilerCollectionCheck::ItemGetInf(26), SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
locationTable[MARKET_TREASURE_CHEST_GAME_REWARD] = ItemLocation::Chest (RC_MARKET_TREASURE_CHEST_GAME_REWARD, 0x10, "MK Treasure Chest Game Reward", MARKET_TREASURE_CHEST_GAME_REWARD, TREASURE_GAME_HEART, {}, SpoilerCollectionCheck::ItemGetInf(27), SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
locationTable[MARKET_BOMBCHU_BOWLING_FIRST_PRIZE] = ItemLocation::Base (RC_MARKET_BOMBCHU_BOWLING_FIRST_PRIZE, 0x4B, "MK Bombchu Bowling First Prize", MARKET_BOMBCHU_BOWLING_FIRST_PRIZE, PROGRESSIVE_BOMB_BAG, {}, SpoilerCollectionCheck::ItemGetInf(17), SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
locationTable[MARKET_BOMBCHU_BOWLING_SECOND_PRIZE] = ItemLocation::Base (RC_MARKET_BOMBCHU_BOWLING_SECOND_PRIZE, 0x4B, "MK Bombchu Bowling Second Prize", MARKET_BOMBCHU_BOWLING_SECOND_PRIZE, PIECE_OF_HEART, {}, SpoilerCollectionCheck::ItemGetInf(18), SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
locationTable[MARKET_BOMBCHU_BOWLING_BOMBCHUS] = ItemLocation::Base (RC_MARKET_BOMBCHU_BOWLING_BOMBCHUS, 0x4B, "MK Bombchu Bowling Bombchus", NONE, BOMBCHU_DROP, {}, SpoilerCollectionCheck::None(), SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
locationTable[MARKET_LOST_DOG] = ItemLocation::Base (RC_MARKET_LOST_DOG, 0x35, "MK Lost Dog", MARKET_LOST_DOG, PIECE_OF_HEART, {}, SpoilerCollectionCheck::InfTable(0x19, 0x09), SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
locationTable[MARKET_SHOOTING_GALLERY_REWARD] = ItemLocation::Base (RC_MARKET_SHOOTING_GALLERY_REWARD, 0x42, "MK Shooting Gallery", MARKET_SHOOTING_GALLERY_REWARD, PROGRESSIVE_SLINGSHOT, {}, SpoilerCollectionCheck::ItemGetInf(5), SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
locationTable[MARKET_SHOOTING_GALLERY_REWARD] = ItemLocation::Base (RC_MARKET_SHOOTING_GALLERY_REWARD, 0x42, "MK Shooting Gallery", MARKET_SHOOTING_GALLERY_REWARD, PROGRESSIVE_SLINGSHOT, {}, SpoilerCollectionCheck::ItemGetInf(13), SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
locationTable[MARKET_10_BIG_POES] = ItemLocation::Base (RC_MARKET_10_BIG_POES, 0x4D, "MK 10 Big Poes", MARKET_10_BIG_POES, EMPTY_BOTTLE, {}, SpoilerCollectionCheck::RandomizerInf(), SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
locationTable[MARKET_TREASURE_CHEST_GAME_ITEM_1] = ItemLocation::Chest (RC_MARKET_TREASURE_CHEST_GAME_ITEM_1, 0x10, 0x01, "MK Chest Game First Room Chest", MARKET_TREASURE_CHEST_GAME_ITEM_1, TREASURE_GAME_SMALL_KEY, {Category::cChestMinigame}, SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
locationTable[MARKET_TREASURE_CHEST_GAME_ITEM_2] = ItemLocation::Chest (RC_MARKET_TREASURE_CHEST_GAME_ITEM_2, 0x10, 0x03, "MK Chest Game Second Room Chest", MARKET_TREASURE_CHEST_GAME_ITEM_2, TREASURE_GAME_SMALL_KEY, {Category::cChestMinigame}, SpoilerCollectionCheckGroup::GROUP_HYRULE_CASTLE);
@@ -115,14 +115,14 @@ void LocationTable_Init() {
locationTable[KAK_30_GOLD_SKULLTULA_REWARD] = ItemLocation::Base (RC_KAK_30_GOLD_SKULLTULA_REWARD, 0x50, "Kak 30 Gold Skulltula Reward", KAK_30_GOLD_SKULLTULA_REWARD, PROGRESSIVE_WALLET, {}, SpoilerCollectionCheck::EventChkInf(0xDC), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[KAK_40_GOLD_SKULLTULA_REWARD] = ItemLocation::Base (RC_KAK_40_GOLD_SKULLTULA_REWARD, 0x50, "Kak 40 Gold Skulltula Reward", KAK_40_GOLD_SKULLTULA_REWARD, BOMBCHU_10, {}, SpoilerCollectionCheck::EventChkInf(0xDD), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[KAK_50_GOLD_SKULLTULA_REWARD] = ItemLocation::Base (RC_KAK_50_GOLD_SKULLTULA_REWARD, 0x50, "Kak 50 Gold Skulltula Reward", KAK_50_GOLD_SKULLTULA_REWARD, PIECE_OF_HEART, {}, SpoilerCollectionCheck::EventChkInf(0xDE), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[KAK_100_GOLD_SKULLTULA_REWARD] = ItemLocation::Base (RC_KAK_100_GOLD_SKULLTULA_REWARD, 0x50, "Kak 100 Gold Skulltula Reward", KAK_100_GOLD_SKULLTULA_REWARD, HUGE_RUPEE, {}, SpoilerCollectionCheck::EventChkInf(0xDF), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[KAK_MAN_ON_ROOF] = ItemLocation::Base (RC_KAK_MAN_ON_ROOF, 0x52, "Kak Man on Roof", KAK_MAN_ON_ROOF, PIECE_OF_HEART, {}, SpoilerCollectionCheck::ItemGetInf(29), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[KAK_100_GOLD_SKULLTULA_REWARD] = ItemLocation::Base (RC_KAK_100_GOLD_SKULLTULA_REWARD, 0x50, "Kak 100 Gold Skulltula Reward", KAK_100_GOLD_SKULLTULA_REWARD, HUGE_RUPEE, {}, SpoilerCollectionCheck::RandomizerInf(), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[KAK_MAN_ON_ROOF] = ItemLocation::Base (RC_KAK_MAN_ON_ROOF, 0x52, "Kak Man on Roof", KAK_MAN_ON_ROOF, PIECE_OF_HEART, {}, SpoilerCollectionCheck::ItemGetInf(21), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[KAK_SHOOTING_GALLERY_REWARD] = ItemLocation::Base (RC_KAK_SHOOTING_GALLERY_REWARD, 0x42, "Kak Shooting Gallery Reward", KAK_SHOOTING_GALLERY_REWARD, PROGRESSIVE_BOW, {}, SpoilerCollectionCheck::Chest(0x42, 0x1F), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[KAK_TRADE_ODD_MUSHROOM] = ItemLocation::Base (RC_KAK_TRADE_ODD_MUSHROOM, 0x4E, "Kak Trade Odd Mushroom", KAK_TRADE_ODD_MUSHROOM, ODD_POTION, {Category::cAdultTrade}, SpoilerCollectionCheck::ItemGetInf(56), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[KAK_TRADE_ODD_MUSHROOM] = ItemLocation::Base (RC_KAK_TRADE_ODD_MUSHROOM, 0x4E, "Kak Trade Odd Mushroom", KAK_TRADE_ODD_MUSHROOM, ODD_POTION, {Category::cAdultTrade}, SpoilerCollectionCheck::ItemGetInf(48), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[KAK_GRANNYS_SHOP] = ItemLocation::Base (RC_KAK_GRANNYS_SHOP, 0x4E, "Kak Granny's Shop", KAK_GRANNYS_SHOP, BLUE_POTION_REFILL, {Category::cMerchant}, SpoilerCollectionCheck::RandomizerInf(), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[KAK_ANJU_AS_ADULT] = ItemLocation::Base (RC_KAK_ANJU_AS_ADULT, 0x52, "Kak Anju as Adult", KAK_ANJU_AS_ADULT, CLAIM_CHECK, {}, SpoilerCollectionCheck::ItemGetInf(36), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[KAK_ANJU_AS_CHILD] = ItemLocation::Base (RC_KAK_ANJU_AS_CHILD, 0x52, "Kak Anju as Child", KAK_ANJU_AS_CHILD, EMPTY_BOTTLE, {}, SpoilerCollectionCheck::ItemGetInf(4), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[KAK_TRADE_POCKET_CUCCO] = ItemLocation::Base (RC_KAK_TRADE_POCKET_CUCCO, 0x52, "Kak Trade Pocket Cucco", KAK_TRADE_POCKET_CUCCO, COJIRO, {Category::cAdultTrade}, SpoilerCollectionCheck::ItemGetInf(38), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[KAK_ANJU_AS_ADULT] = ItemLocation::Base (RC_KAK_ANJU_AS_ADULT, 0x52, "Kak Anju as Adult", KAK_ANJU_AS_ADULT, CLAIM_CHECK, {}, SpoilerCollectionCheck::ItemGetInf(44), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[KAK_ANJU_AS_CHILD] = ItemLocation::Base (RC_KAK_ANJU_AS_CHILD, 0x52, "Kak Anju as Child", KAK_ANJU_AS_CHILD, EMPTY_BOTTLE, {}, SpoilerCollectionCheck::ItemGetInf(12), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[KAK_TRADE_POCKET_CUCCO] = ItemLocation::Base (RC_KAK_TRADE_POCKET_CUCCO, 0x52, "Kak Trade Pocket Cucco", KAK_TRADE_POCKET_CUCCO, COJIRO, {Category::cAdultTrade}, SpoilerCollectionCheck::ItemGetInf(46), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[KAK_IMPAS_HOUSE_FREESTANDING_POH] = ItemLocation::Collectable(RC_KAK_IMPAS_HOUSE_FREESTANDING_POH, 0x37, 0x01, "Kak Impas House Freestanding PoH", KAK_IMPAS_HOUSE_FREESTANDING_POH, PIECE_OF_HEART, {}, SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[KAK_WINDMILL_FREESTANDING_POH] = ItemLocation::Collectable(RC_KAK_WINDMILL_FREESTANDING_POH, 0x48, 0x01, "Kak Windmill Freestanding PoH", KAK_WINDMILL_FREESTANDING_POH, PIECE_OF_HEART, {}, SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
@@ -133,7 +133,7 @@ void LocationTable_Init() {
locationTable[GRAVEYARD_HOOKSHOT_CHEST] = ItemLocation::Chest (RC_GRAVEYARD_HOOKSHOT_CHEST, 0x48, 0x00, "GY Hookshot Chest", GRAVEYARD_HOOKSHOT_CHEST, PROGRESSIVE_HOOKSHOT, {}, SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[GRAVEYARD_DAMPE_RACE_FREESTANDING_POH] = ItemLocation::Collectable(RC_GRAVEYARD_DAMPE_RACE_FREESTANDING_POH, 0x48, 0x07, "GY Dampe Race Freestanding PoH", GRAVEYARD_DAMPE_RACE_FREESTANDING_POH, PIECE_OF_HEART, {}, SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[GRAVEYARD_FREESTANDING_POH] = ItemLocation::Collectable(RC_GRAVEYARD_FREESTANDING_POH, 0x53, 0x04, "GY Freestanding PoH", GRAVEYARD_FREESTANDING_POH, PIECE_OF_HEART, {}, SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[GRAVEYARD_DAMPE_GRAVEDIGGING_TOUR] = ItemLocation::Collectable(RC_GRAVEYARD_DAMPE_GRAVEDIGGING_TOUR, 0x53, "GY Dampe Gravedigging Tour", GRAVEYARD_DAMPE_GRAVEDIGGING_TOUR, PIECE_OF_HEART, {}, SpoilerCollectionCheck::Gravedigger(0x53, 0x1F), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
locationTable[GRAVEYARD_DAMPE_GRAVEDIGGING_TOUR] = ItemLocation::Collectable(RC_GRAVEYARD_DAMPE_GRAVEDIGGING_TOUR, 0x53, "GY Dampe Gravedigging Tour", GRAVEYARD_DAMPE_GRAVEDIGGING_TOUR, PIECE_OF_HEART, {}, SpoilerCollectionCheck::Gravedigger(0x53, 0x19), SpoilerCollectionCheckGroup::GROUP_KAKARIKO);
//Death Mountain
locationTable[DMT_CHEST] = ItemLocation::Chest (RC_DMT_CHEST, 0x60, 0x01, "DMT Chest", DMT_CHEST, PURPLE_RUPEE, {}, SpoilerCollectionCheckGroup::GROUP_DEATH_MOUNTAIN);
@@ -191,7 +191,7 @@ void LocationTable_Init() {
locationTable[ZF_BOTTOM_FREESTANDING_POH] = ItemLocation::Collectable(RC_ZF_BOTTOM_FREESTANDING_POH, 0x59, 0x14, "ZF Bottom Freestanding PoH", ZF_BOTTOM_FREESTANDING_POH, PIECE_OF_HEART, {}, SpoilerCollectionCheckGroup::GROUP_ZORAS_DOMAIN);
//Lon Lon Ranch
locationTable[LLR_TALONS_CHICKENS] = ItemLocation::Base (RC_LLR_TALONS_CHICKENS, 0x4C, "LLR Talons Chickens", LLR_TALONS_CHICKENS, BOTTLE_WITH_MILK, {}, SpoilerCollectionCheck::ItemGetInf(10), SpoilerCollectionCheckGroup::GROUP_LON_LON_RANCH);
locationTable[LLR_TALONS_CHICKENS] = ItemLocation::Base (RC_LLR_TALONS_CHICKENS, 0x4C, "LLR Talons Chickens", LLR_TALONS_CHICKENS, BOTTLE_WITH_MILK, {}, SpoilerCollectionCheck::ItemGetInf(2), SpoilerCollectionCheckGroup::GROUP_LON_LON_RANCH);
locationTable[LLR_FREESTANDING_POH] = ItemLocation::Collectable(RC_LLR_FREESTANDING_POH, 0x4C, 0x01, "LLR Freestanding PoH", LLR_FREESTANDING_POH, PIECE_OF_HEART, {}, SpoilerCollectionCheckGroup::GROUP_LON_LON_RANCH);
locationTable[LLR_DEKU_SCRUB_GROTTO_LEFT] = ItemLocation::GrottoScrub(RC_LLR_DEKU_SCRUB_GROTTO_LEFT, 0xFC, "LLR Deku Scrub Grotto Left", LLR_DEKU_SCRUB_GROTTO_LEFT, BUY_DEKU_NUT_5, {Category::cDekuScrub}, SpoilerCollectionCheck::Scrub(), SpoilerCollectionCheckGroup::GROUP_LON_LON_RANCH);
locationTable[LLR_DEKU_SCRUB_GROTTO_RIGHT] = ItemLocation::GrottoScrub(RC_LLR_DEKU_SCRUB_GROTTO_RIGHT, 0xFC, "LLR Deku Scrub Grotto Right", LLR_DEKU_SCRUB_GROTTO_RIGHT, BUY_BOMBS_535, {Category::cDekuScrub}, SpoilerCollectionCheck::Scrub(), SpoilerCollectionCheckGroup::GROUP_LON_LON_RANCH);
@@ -1,5 +1,6 @@
#include "playthrough.hpp"
#include <libultraship/libultraship.h>
#include <boost_custom/container_hash/hash_32.hpp>
#include "custom_messages.hpp"
#include "fill.hpp"
@@ -8,6 +9,7 @@
#include "random.hpp"
#include "spoiler_log.hpp"
#include "soh/Enhancements/randomizer/randomizerTypes.h"
#include "variables.h"
namespace Playthrough {
@@ -39,6 +41,10 @@ int Playthrough_Init(uint32_t seed, std::unordered_map<RandomizerSettingKey, uin
}
}
if (CVarGetInteger("gRandomizerDontGenerateSpoiler", 0)) {
settingsStr += (char*)gBuildVersion;
}
uint32_t finalHash = boost::hash_32<std::string>{}(std::to_string(Settings::seed) + settingsStr);
Random_Init(finalHash);
Settings::hash = std::to_string(finalHash);
+41 -32
View File
@@ -360,37 +360,35 @@ std::unordered_map<std::string, RandomizerSettingKey> SpoilerfileSettingNameToEn
{ "Shuffle Dungeon Quest:Ganon's Castle", RSK_MQ_GANONS_CASTLE },
};
std::string sanitize(std::string stringValue) {
// Add backslashes.
for (auto i = stringValue.begin();;) {
auto const pos = std::find_if(i, stringValue.end(), [](char const c) { return '\\' == c || '\'' == c || '"' == c; });
if (pos == stringValue.end()) {
break;
}
i = std::next(stringValue.insert(pos, '\\'), 2);
}
// Removes others.
stringValue.erase(std::remove_if(stringValue.begin(), stringValue.end(), [](char const c) {
return '\n' == c || '\r' == c || '\0' == c || '\x1A' == c; }), stringValue.end());
return stringValue;
}
#pragma optimize("", off)
#pragma GCC push_options
#pragma GCC optimize ("O0")
bool Randomizer::SpoilerFileExists(const char* spoilerFileName) {
if (strcmp(spoilerFileName, "") != 0) {
std::ifstream spoilerFileStream(sanitize(spoilerFileName));
if (!spoilerFileStream) {
return false;
} else {
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;
}
return true;
}
}
return false;
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;
}
}
#pragma GCC pop_options
#pragma optimize("", on)
@@ -659,7 +657,7 @@ void Randomizer::LoadMasterQuestDungeons(const char* spoilerFileName) {
}
void Randomizer::ParseRandomizerSettingsFile(const char* spoilerFileName) {
std::ifstream spoilerFileStream(sanitize(spoilerFileName));
std::ifstream spoilerFileStream(SohUtils::Sanitize(spoilerFileName));
if (!spoilerFileStream)
return;
@@ -1293,7 +1291,7 @@ std::string FormatJsonHintText(std::string jsonHint) {
}
void Randomizer::ParseHintLocationsFile(const char* spoilerFileName) {
std::ifstream spoilerFileStream(sanitize(spoilerFileName));
std::ifstream spoilerFileStream(SohUtils::Sanitize(spoilerFileName));
if (!spoilerFileStream)
return;
@@ -1394,7 +1392,7 @@ void Randomizer::ParseHintLocationsFile(const char* spoilerFileName) {
}
void Randomizer::ParseRequiredTrialsFile(const char* spoilerFileName) {
std::ifstream spoilerFileStream(sanitize(spoilerFileName));
std::ifstream spoilerFileStream(SohUtils::Sanitize(spoilerFileName));
if (!spoilerFileStream) {
return;
}
@@ -1415,7 +1413,7 @@ void Randomizer::ParseRequiredTrialsFile(const char* spoilerFileName) {
}
void Randomizer::ParseMasterQuestDungeonsFile(const char* spoilerFileName) {
std::ifstream spoilerFileStream(sanitize(spoilerFileName));
std::ifstream spoilerFileStream(SohUtils::Sanitize(spoilerFileName));
if (!spoilerFileStream) {
return;
}
@@ -1495,7 +1493,7 @@ int16_t Randomizer::GetVanillaMerchantPrice(RandomizerCheck check) {
}
void Randomizer::ParseItemLocationsFile(const char* spoilerFileName, bool silent) {
std::ifstream spoilerFileStream(sanitize(spoilerFileName));
std::ifstream spoilerFileStream(SohUtils::Sanitize(spoilerFileName));
if (!spoilerFileStream)
return;
@@ -1558,7 +1556,7 @@ void Randomizer::ParseItemLocationsFile(const char* spoilerFileName, bool silent
}
void Randomizer::ParseEntranceDataFile(const char* spoilerFileName, bool silent) {
std::ifstream spoilerFileStream(sanitize(spoilerFileName));
std::ifstream spoilerFileStream(SohUtils::Sanitize(spoilerFileName));
if (!spoilerFileStream) {
return;
}
@@ -2547,6 +2545,7 @@ std::map<RandomizerCheck, RandomizerInf> rcToRandomizerInf = {
{ RC_LH_CHILD_FISHING, RAND_INF_CHILD_FISHING },
{ RC_LH_ADULT_FISHING, RAND_INF_ADULT_FISHING },
{ RC_MARKET_10_BIG_POES, RAND_INF_10_BIG_POES },
{ RC_KAK_100_GOLD_SKULLTULA_REWARD, RAND_INF_KAK_100_GOLD_SKULLTULA_REWARD },
};
RandomizerCheckObject Randomizer::GetCheckObjectFromActor(s16 actorId, s16 sceneNum, s32 actorParams = 0x00) {
@@ -3143,7 +3142,9 @@ void RandomizerSettingsWindow::DrawElement() {
UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f);
}
ImGui::BeginDisabled(CVarGetInteger("gDisableChangingSettings", 0));
DrawPresetSelector(PRESET_TYPE_RANDOMIZER);
ImGui::EndDisabled();
UIWidgets::Spacer(0);
UIWidgets::EnhancementCheckbox("Manual seed entry", "gRandoManualSeedEntry", false, "");
@@ -3166,13 +3167,17 @@ void RandomizerSettingsWindow::DrawElement() {
}
UIWidgets::Spacer(0);
ImGui::BeginDisabled(CVarGetInteger("gRandomizerDontGenerateSpoiler", 0) && gSaveContext.gameMode != GAMEMODE_FILE_SELECT);
if (ImGui::Button("Generate Randomizer")) {
GenerateRandomizer(CVarGetInteger("gRandoManualSeedEntry", 0) ? seedString : "");
}
ImGui::EndDisabled();
UIWidgets::Spacer(0);
std::string spoilerfilepath = CVarGetString("gSpoilerLog", "");
ImGui::Text("Spoiler File: %s", spoilerfilepath.c_str());
if (!CVarGetInteger("gRandomizerDontGenerateSpoiler", 0)) {
std::string spoilerfilepath = CVarGetString("gSpoilerLog", "");
ImGui::Text("Spoiler File: %s", spoilerfilepath.c_str());
}
// RANDOTODO settings presets
// std::string presetfilepath = CVarGetString("gLoadedPreset", "");
@@ -3180,6 +3185,8 @@ void RandomizerSettingsWindow::DrawElement() {
UIWidgets::PaddedSeparator();
ImGui::BeginDisabled(CVarGetInteger("gDisableChangingSettings", 0));
ImGuiWindow* window = ImGui::GetCurrentWindow();
static ImVec2 cellPadding(8.0f, 8.0f);
@@ -5224,6 +5231,8 @@ void RandomizerSettingsWindow::DrawElement() {
ImGui::EndTabBar();
}
ImGui::EndDisabled();
if (disableEditingRandoSettings) {
UIWidgets::ReEnableComponent("");
@@ -39,7 +39,7 @@ std::map<RandomizerCheck, RandomizerCheckObject> rcObjects = {
RC_OBJECT(RC_LW_DEKU_SCRUB_NEAR_DEKU_THEATER_LEFT, RCVORMQ_BOTH, RCTYPE_SCRUB, RCAREA_LOST_WOODS, ACTOR_EN_DNS, SCENE_LOST_WOODS, 0x01, GI_STICKS_1, "Deku Scrub Near Deku Theater Left", "LW Deku Scrub Near Deku Theater Left", false),
RC_OBJECT(RC_LW_DEKU_SCRUB_NEAR_BRIDGE, RCVORMQ_BOTH, RCTYPE_SCRUB, RCAREA_LOST_WOODS, ACTOR_EN_DNS, SCENE_LOST_WOODS, 0x09, GI_STICK_UPGRADE_20, "Deku Scrub Near Bridge", "LW Deku Scrub Near Bridge", true),
RC_OBJECT(RC_LW_DEKU_SCRUB_GROTTO_REAR, RCVORMQ_BOTH, RCTYPE_SCRUB, RCAREA_LOST_WOODS, ACTOR_EN_DNS, SCENE_GROTTOS, TWO_ACTOR_PARAMS(0x03,0xF5), GI_SEEDS_30, "Deku Scrub Grotto Rear", "LW Deku Scrub Grotto Rear", false),
RC_OBJECT(RC_LW_DEKU_SCRUB_GROTTO_FRONT, RCVORMQ_BOTH, RCTYPE_SCRUB, RCAREA_LOST_WOODS, ACTOR_EN_DNS, SCENE_GROTTOS, TWO_ACTOR_PARAMS(0x0A,0xF5), GI_NUT_UPGRADE_30, "Deku Scrub Grotto Front", "LW Deku Scrub Grotto Front", false),
RC_OBJECT(RC_LW_DEKU_SCRUB_GROTTO_FRONT, RCVORMQ_BOTH, RCTYPE_SCRUB, RCAREA_LOST_WOODS, ACTOR_EN_DNS, SCENE_GROTTOS, TWO_ACTOR_PARAMS(0x0A,0xF5), GI_NUT_UPGRADE_30, "Deku Scrub Grotto Front", "LW Deku Scrub Grotto Front", true),
RC_OBJECT(RC_DEKU_THEATER_SKULL_MASK, RCVORMQ_BOTH, RCTYPE_STANDARD, RCAREA_LOST_WOODS, ACTOR_ID_MAX, SCENE_GROTTOS, 0x00, GI_NONE, "Deku Theater Skull Mask", "Deku Theater Skull Mask", true),
RC_OBJECT(RC_DEKU_THEATER_MASK_OF_TRUTH, RCVORMQ_BOTH, RCTYPE_STANDARD, RCAREA_LOST_WOODS, ACTOR_ID_MAX, SCENE_GROTTOS, 0x00, GI_NONE, "Deku Theater Mask of Truth", "Deku Theater Mask of Truth", true),
RC_OBJECT(RC_LW_GS_BEAN_PATCH_NEAR_BRIDGE, RCVORMQ_BOTH, RCTYPE_SKULL_TOKEN, RCAREA_LOST_WOODS, ACTOR_EN_SI, SCENE_LOST_WOODS, 27905, GI_SKULL_TOKEN, "GS Bean Patch Near Bridge", "LW GS Bean Patch Near Bridge", true),
@@ -238,7 +238,7 @@ std::map<RandomizerCheck, RandomizerCheckObject> rcObjects = {
RC_OBJECT(RC_GRAVEYARD_HOOKSHOT_CHEST, RCVORMQ_BOTH, RCTYPE_STANDARD, RCAREA_GRAVEYARD, ACTOR_EN_BOX, SCENE_WINDMILL_AND_DAMPES_GRAVE, 4352, GI_HOOKSHOT, "Hookshot Chest", "GY Hookshot Chest", true),
RC_OBJECT(RC_GRAVEYARD_DAMPE_RACE_FREESTANDING_POH, RCVORMQ_BOTH, RCTYPE_STANDARD, RCAREA_GRAVEYARD, ACTOR_EN_ITEM00, SCENE_WINDMILL_AND_DAMPES_GRAVE, 1798, GI_HEART_PIECE, "Dampe Race Freestanding PoH", "GY Dampe Race Freestanding PoH", true),
RC_OBJECT(RC_GRAVEYARD_FREESTANDING_POH, RCVORMQ_BOTH, RCTYPE_STANDARD, RCAREA_GRAVEYARD, ACTOR_EN_ITEM00, SCENE_GRAVEYARD, 1030, GI_HEART_PIECE, "Freestanding PoH", "GY Freestanding PoH", true),
RC_OBJECT(RC_GRAVEYARD_DAMPE_GRAVEDIGGING_TOUR, RCVORMQ_BOTH, RCTYPE_STANDARD, RCAREA_GRAVEYARD, ACTOR_EN_ITEM00, SCENE_GRAVEYARD, 7942, GI_HEART_PIECE, "Dampe Gravedigging Tour", "GY Dampe Gravedigging Tour", true),
RC_OBJECT(RC_GRAVEYARD_DAMPE_GRAVEDIGGING_TOUR, RCVORMQ_BOTH, RCTYPE_STANDARD, RCAREA_GRAVEYARD, ACTOR_EN_ITEM00, SCENE_GRAVEYARD, 6406, GI_HEART_PIECE, "Dampe Gravedigging Tour", "GY Dampe Gravedigging Tour", true),
RC_OBJECT(RC_GRAVEYARD_GS_WALL, RCVORMQ_BOTH, RCTYPE_SKULL_TOKEN, RCAREA_GRAVEYARD, ACTOR_EN_SI, SCENE_GRAVEYARD, 20608, GI_SKULL_TOKEN, "GS Wall", "Graveyard GS Wall", true),
RC_OBJECT(RC_GRAVEYARD_GS_BEAN_PATCH, RCVORMQ_BOTH, RCTYPE_SKULL_TOKEN, RCAREA_GRAVEYARD, ACTOR_EN_SI, SCENE_GRAVEYARD, 28673, GI_SKULL_TOKEN, "GS Bean Patch", "Graveyard GS Bean Patch", true),
RC_OBJECT(RC_SONG_FROM_ROYAL_FAMILYS_TOMB, RCVORMQ_BOTH, RCTYPE_SONG_LOCATION, RCAREA_GRAVEYARD, ACTOR_ID_MAX, SCENE_ID_MAX, 0x00, GI_NONE, "Song from Composers Grave", "Song from Composers Grave", true),
@@ -783,7 +783,7 @@ std::map<RandomizerCheckArea, std::string> rcAreaNames = {
{ RCAREA_LAKE_HYLIA, "Lake Hylia"},
{ RCAREA_GERUDO_VALLEY, "Gerudo Valley"},
{ RCAREA_GERUDO_FORTRESS, "Gerudo Fortress"},
{ RCAREA_WASTELAND, "Desert Wasteland"},
{ RCAREA_WASTELAND, "Haunted Wasteland"},
{ RCAREA_DESERT_COLOSSUS, "Desert Colossus"},
{ RCAREA_MARKET, "Hyrule Market"},
{ RCAREA_HYRULE_CASTLE, "Hyrule Castle"},
@@ -73,21 +73,12 @@ bool showLinksPocket;
bool fortressFast;
bool fortressNormal;
bool bypassRandoCheck = true;
// persistent during gameplay
bool initialized;
bool doAreaScroll;
bool previousShowHidden = false;
bool hideShopRightChecks = true;
bool checkCollected = false;
int checkLoops = 0;
int checkCounter = 0;
u16 savedFrames = 0;
bool messageCloseCheck = false;
bool pendingSaleCheck = false;
bool transitionCheck = false;
std::map<uint32_t, RandomizerCheck> startingShopItem = { { SCENE_KOKIRI_SHOP, RC_KF_SHOP_ITEM_1 },
{ SCENE_BAZAAR, RC_MARKET_BAZAAR_ITEM_1 },
{ SCENE_POTION_SHOP_MARKET, RC_MARKET_POTION_SHOP_ITEM_1 },
@@ -118,12 +109,9 @@ bool showVOrMQ;
s8 areaChecksGotten[32]; //| "Kokiri Forest (4/9)"
bool optCollapseAll; // A bool that will collapse all checks once
bool optExpandAll; // A bool that will expand all checks once
RandomizerCheck lastItemGetCheck = RC_UNKNOWN_CHECK;
RandomizerCheck lastLocationChecked = RC_UNKNOWN_CHECK;
RandomizerCheckArea previousArea = RCAREA_INVALID;
RandomizerCheckArea currentArea = RCAREA_INVALID;
std::vector<RandomizerCheckArea> checkAreas;
std::vector<GetItemEntry> itemsReceived;
OSContPad* trackerButtonsPressed;
void BeginFloatWindows(std::string UniqueName, bool& open, ImGuiWindowFlags flags = 0);
@@ -194,10 +182,6 @@ Color_RGBA8 Color_Saved_Extra = { 0, 185, 0, 255 }; // Green
std::vector<uint32_t> buttons = { BTN_A, BTN_B, BTN_CUP, BTN_CDOWN, BTN_CLEFT, BTN_CRIGHT, BTN_L,
BTN_Z, BTN_R, BTN_START, BTN_DUP, BTN_DDOWN, BTN_DLEFT, BTN_DRIGHT };
void SetLastItemGetRC(RandomizerCheck rc) {
lastItemGetCheck = rc;
}
void DefaultCheckData(RandomizerCheck rc) {
gSaveContext.checkTrackerData[rc].status = RCSHOW_UNCHECKED;
gSaveContext.checkTrackerData[rc].skipped = 0;
@@ -253,9 +237,6 @@ void SetCheckCollected(RandomizerCheck rc) {
} else {
gSaveContext.checkTrackerData[rc].skipped = false;
}
if (!checkAreas.empty()) {
checkAreas.erase(checkAreas.begin());
}
SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionId, true);
doAreaScroll = true;
@@ -360,32 +341,18 @@ bool vector_contains_scene(std::vector<SceneID> vec, const int16_t scene) {
std::vector<SceneID> skipScenes = {SCENE_GANON_BOSS, SCENE_GANONS_TOWER_COLLAPSE_EXTERIOR, SCENE_GANON_BOSS, SCENE_INSIDE_GANONS_CASTLE_COLLAPSE, SCENE_GANONS_TOWER_COLLAPSE_INTERIOR};
bool EvaluateCheck(RandomizerCheckObject rco) {
if (HasItemBeenCollected(rco.rc) && gSaveContext.checkTrackerData[rco.rc].status != RCSHOW_COLLECTED &&
gSaveContext.checkTrackerData[rco.rc].status != RCSHOW_SAVED) {
SetCheckCollected(rco.rc);
return true;
}
return false;
}
bool CheckByArea(RandomizerCheckArea area = RCAREA_INVALID) {
if (area == RCAREA_INVALID) {
area = checkAreas.front();
}
if (area != RCAREA_INVALID) {
auto areaChecks = checksByArea.find(area)->second;
if (checkCounter >= areaChecks.size()) {
checkCounter = 0;
checkLoops++;
}
auto rco = areaChecks.at(checkCounter);
return EvaluateCheck(rco);
void ClearAreaChecksAndTotals() {
for (auto& [rcArea, vec] : checksByArea) {
vec.clear();
areaChecksGotten[rcArea] = 0;
}
}
void SetShopSeen(uint32_t sceneNum, bool prices) {
RandomizerCheck start = startingShopItem.find(sceneNum)->second;
if (sceneNum == SCENE_POTION_SHOP_KAKARIKO && !LINK_IS_ADULT) {
return;
}
if (GetCheckArea() == RCAREA_KAKARIKO_VILLAGE && sceneNum == SCENE_BAZAAR) {
start = RC_KAK_BAZAAR_ITEM_1;
}
@@ -452,266 +419,32 @@ bool HasItemBeenCollected(RandomizerCheck rc) {
return false;
}
void CheckTrackerDialogClosed() {
if (messageCloseCheck) {
messageCloseCheck = false;
}
}
void CheckTrackerShopSlotChange(uint8_t cursorSlot, int16_t basePrice) {
if (gPlayState->sceneNum == SCENE_HAPPY_MASK_SHOP) { // Happy Mask Shop is not used in rando, so is not tracked
return;
}
auto slot = startingShopItem.find(gPlayState->sceneNum)->second + cursorSlot;
if (GetCheckArea() == RCAREA_KAKARIKO_VILLAGE && gPlayState->sceneNum == SCENE_BAZAAR) {
slot = RC_KAK_BAZAAR_ITEM_1 + cursorSlot;
}
auto status = gSaveContext.checkTrackerData[slot].status;
if (status == RCSHOW_SEEN) {
gSaveContext.checkTrackerData[slot].status = RCSHOW_IDENTIFIED;
gSaveContext.checkTrackerData[slot].price = basePrice;
SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionId, true);
}
}
void CheckTrackerTransition(uint32_t sceneNum) {
if (!GameInteractor::IsSaveLoaded()) {
return;
}
gSaveContext;
if (transitionCheck) {
transitionCheck = false;
}
doAreaScroll = true;
previousArea = currentArea;
currentArea = GetCheckArea();
switch (sceneNum) {
case SCENE_KOKIRI_SHOP:
case SCENE_BAZAAR:
case SCENE_POTION_SHOP_MARKET:
case SCENE_BOMBCHU_SHOP:
case SCENE_POTION_SHOP_KAKARIKO:
case SCENE_GORON_SHOP:
case SCENE_ZORA_SHOP:
SetShopSeen(sceneNum, false);
break;
}
}
void CheckTrackerFrame() {
if (!GameInteractor::IsSaveLoaded()) {
return;
}
if (!checkAreas.empty() && !transitionCheck && !messageCloseCheck && !pendingSaleCheck) {
for (int i = 0; i < 10; i++) {
if (CheckByArea()) {
checkCounter = 0;
break;
} else {
checkCounter++;
}
}
if (checkLoops > 15) {
checkAreas.erase(checkAreas.begin());
checkLoops = 0;
}
}
if (savedFrames > 0 && !pendingSaleCheck && !messageCloseCheck) {
savedFrames--;
}
}
void CheckTrackerSaleEnd(GetItemEntry giEntry) {
if (pendingSaleCheck) {
pendingSaleCheck = false;
}
}
void CheckTrackerItemReceive(GetItemEntry giEntry) {
if (!GameInteractor::IsSaveLoaded() || vector_contains_scene(skipScenes, gPlayState->sceneNum)) {
return;
}
auto scene = static_cast<SceneID>(gPlayState->sceneNum);
// Vanilla special item checks
if (!IS_RANDO) {
if (giEntry.itemId == ITEM_SHIELD_DEKU) {
SetCheckCollected(RC_KF_SHOP_ITEM_3);
return;
}else if (giEntry.itemId == ITEM_KOKIRI_EMERALD) {
SetCheckCollected(RC_QUEEN_GOHMA);
return;
} else if (giEntry.itemId == ITEM_GORON_RUBY) {
SetCheckCollected(RC_KING_DODONGO);
return;
} else if (giEntry.itemId == ITEM_ZORA_SAPPHIRE) {
SetCheckCollected(RC_BARINADE);
return;
} else if (giEntry.itemId == ITEM_MEDALLION_FOREST) {
SetCheckCollected(RC_PHANTOM_GANON);
return;
} else if (giEntry.itemId == ITEM_MEDALLION_FIRE) {
SetCheckCollected(RC_VOLVAGIA);
return;
} else if (giEntry.itemId == ITEM_MEDALLION_WATER) {
SetCheckCollected(RC_MORPHA);
return;
} else if (giEntry.itemId == ITEM_MEDALLION_SHADOW) {
SetCheckCollected(RC_BONGO_BONGO);
return;
} else if (giEntry.itemId == ITEM_MEDALLION_SPIRIT) {
SetCheckCollected(RC_TWINROVA);
return;
} else if (giEntry.itemId == ITEM_MEDALLION_LIGHT) {
SetCheckCollected(RC_GIFT_FROM_SAGES);
return;
} else if (giEntry.itemId == ITEM_SONG_LULLABY) {
SetCheckCollected(RC_SONG_FROM_IMPA);
return;
} else if (giEntry.itemId == ITEM_SONG_EPONA) {
SetCheckCollected(RC_SONG_FROM_MALON);
return;
} else if (giEntry.itemId == ITEM_SONG_SARIA) {
SetCheckCollected(RC_SONG_FROM_SARIA);
return;
} else if (giEntry.itemId == ITEM_SONG_SUN) {
SetCheckCollected(RC_SONG_FROM_ROYAL_FAMILYS_TOMB);
return;
} else if (giEntry.itemId == ITEM_SONG_TIME) {
SetCheckCollected(RC_SONG_FROM_OCARINA_OF_TIME);
return;
} else if (giEntry.itemId == ITEM_SONG_STORMS) {
SetCheckCollected(RC_SONG_FROM_WINDMILL);
return;
} else if (giEntry.itemId == ITEM_SONG_MINUET) {
SetCheckCollected(RC_SHEIK_IN_FOREST);
return;
} else if (giEntry.itemId == ITEM_SONG_BOLERO) {
SetCheckCollected(RC_SHEIK_IN_CRATER);
return;
} else if (giEntry.itemId == ITEM_SONG_SERENADE) {
SetCheckCollected(RC_SHEIK_IN_ICE_CAVERN);
return;
} else if (giEntry.itemId == ITEM_SONG_NOCTURNE) {
SetCheckCollected(RC_SHEIK_IN_KAKARIKO);
return;
} else if (giEntry.itemId == ITEM_SONG_REQUIEM) {
SetCheckCollected(RC_SHEIK_AT_COLOSSUS);
return;
} else if (giEntry.itemId == ITEM_SONG_PRELUDE) {
SetCheckCollected(RC_SHEIK_AT_TEMPLE);
return;
} else if (giEntry.itemId == ITEM_BRACELET) {
SetCheckCollected(RC_GC_DARUNIAS_JOY);
return;
} else if (giEntry.itemId == ITEM_LETTER_ZELDA) {
SetCheckCollected(RC_HC_ZELDAS_LETTER);
return;
} else if (giEntry.itemId == ITEM_WEIRD_EGG) {
SetCheckCollected(RC_HC_MALON_EGG);
return;
} else if (giEntry.itemId == ITEM_BEAN) {
SetCheckCollected(RC_ZR_MAGIC_BEAN_SALESMAN);
return;
}
}
auto checkArea = GetCheckArea();
if (gSaveContext.pendingSale != ITEM_NONE) {
pendingSaleCheck = true;
checkAreas.push_back(checkArea);
return;
}
if (scene == SCENE_DESERT_COLOSSUS && (gSaveContext.entranceIndex == 485 || gSaveContext.entranceIndex == 489)) {
checkAreas.push_back(RCAREA_SPIRIT_TEMPLE);
return;
}
if (GET_PLAYER(gPlayState) == nullptr) {
transitionCheck = true;
return;
}
if (gPlayState->msgCtx.msgMode != MSGMODE_NONE) {
checkAreas.push_back(checkArea);
messageCloseCheck = true;
return;
}
if (IS_RANDO || (!IS_RANDO && giEntry.getItemCategory != ITEM_CATEGORY_JUNK)) {
checkAreas.push_back(checkArea);
checkCollected = true;
}
}
void InitTrackerData(bool isDebug) {
TrySetAreas();
for (auto& [rc, rco] : RandomizerCheckObjects::GetAllRCObjects()) {
if (rc != RC_UNKNOWN_CHECK && rc != RC_MAX) {
DefaultCheckData(rc);
}
}
UpdateAllOrdering();
UpdateInventoryChecks();
}
void SaveTrackerData(SaveContext* saveContext, int sectionID, bool gameSave) {
SaveManager::Instance->SaveArray("checks", ARRAY_COUNT(saveContext->checkTrackerData), [&](size_t i) {
if (saveContext->checkTrackerData[i].status == RCSHOW_COLLECTED) {
if (gameSave || savedFrames > 0) {
gSaveContext.checkTrackerData[i].status = saveContext->checkTrackerData[i].status = RCSHOW_SAVED;
UpdateAllOrdering();
UpdateInventoryChecks();
} else {
saveContext->checkTrackerData[i].status = RCSHOW_SCUMMED;
}
}
SaveManager::Instance->SaveStruct("", [&]() {
SaveManager::Instance->SaveData("status", saveContext->checkTrackerData[i].status);
SaveManager::Instance->SaveData("skipped", saveContext->checkTrackerData[i].skipped);
SaveManager::Instance->SaveData("price", saveContext->checkTrackerData[i].price);
SaveManager::Instance->SaveData("hintItem", saveContext->checkTrackerData[i].hintItem);
});
});
}
void SaveFile(SaveContext* saveContext, int sectionID, bool fullSave) {
SaveTrackerData(saveContext, sectionID, fullSave);
if (fullSave) {
savedFrames = 40;
}
}
void LoadFile() {
Teardown();
void CheckTrackerLoadGame(int32_t fileNum) {
LoadSettings();
TrySetAreas();
SaveManager::Instance->LoadArray("checks", RC_MAX, [](size_t i) {
SaveManager::Instance->LoadStruct("", [&]() {
SaveManager::Instance->LoadData("status", gSaveContext.checkTrackerData[i].status);
SaveManager::Instance->LoadData("skipped", gSaveContext.checkTrackerData[i].skipped);
SaveManager::Instance->LoadData("price", gSaveContext.checkTrackerData[i].price);
SaveManager::Instance->LoadData("hintItem", gSaveContext.checkTrackerData[i].hintItem);
});
RandomizerCheckTrackerData entry = gSaveContext.checkTrackerData[i];
RandomizerCheck rc = static_cast<RandomizerCheck>(i);
if (rc == RC_UNKNOWN_CHECK || rc == RC_MAX ||
for (auto [rc, rcObj] : RandomizerCheckObjects::GetAllRCObjects()) {
RandomizerCheckTrackerData rcTrackerData = gSaveContext.checkTrackerData[rc];
if (rc == RC_UNKNOWN_CHECK || rc == RC_MAX || rc == RC_LINKS_POCKET ||
!RandomizerCheckObjects::GetAllRCObjects().contains(rc))
return;
continue;
RandomizerCheckObject entry2;
RandomizerCheckObject realRcObj;
if (rc == RC_GIFT_FROM_SAGES && !IS_RANDO) {
entry2 = RCO_RAORU;
realRcObj = RCO_RAORU;
} else {
entry2 = RandomizerCheckObjects::GetAllRCObjects().find(rc)->second;
realRcObj = rcObj;
}
if (!IsVisibleInCheckTracker(entry2)) return;
if (!IsVisibleInCheckTracker(realRcObj)) continue;
checksByArea.find(entry2.rcArea)->second.push_back(entry2);
if (entry.status == RCSHOW_SAVED || entry.skipped) {
areaChecksGotten[entry2.rcArea]++;
checksByArea.find(realRcObj.rcArea)->second.push_back(realRcObj);
if (rcTrackerData.status == RCSHOW_SAVED || rcTrackerData.skipped) {
areaChecksGotten[realRcObj.rcArea]++;
}
if (areaChecksGotten[entry2.rcArea] != 0 || RandomizerCheckObjects::AreaIsOverworld(entry2.rcArea)) {
areasSpoiled |= (1 << entry2.rcArea);
if (areaChecksGotten[realRcObj.rcArea] != 0 || RandomizerCheckObjects::AreaIsOverworld(realRcObj.rcArea)) {
areasSpoiled |= (1 << realRcObj.rcArea);
}
});
}
if (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_LINKS_POCKET) != RO_LINKS_POCKET_NOTHING && IS_RANDO) {
s8 startingAge = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_STARTING_AGE);
RandomizerCheckArea startingArea;
@@ -743,16 +476,307 @@ void LoadFile() {
UpdateInventoryChecks();
}
void CheckTrackerShopSlotChange(uint8_t cursorSlot, int16_t basePrice) {
if (gPlayState->sceneNum == SCENE_HAPPY_MASK_SHOP) { // Happy Mask Shop is not used in rando, so is not tracked
return;
}
auto slot = startingShopItem.find(gPlayState->sceneNum)->second + cursorSlot;
if (GetCheckArea() == RCAREA_KAKARIKO_VILLAGE && gPlayState->sceneNum == SCENE_BAZAAR) {
slot = RC_KAK_BAZAAR_ITEM_1 + cursorSlot;
}
auto status = gSaveContext.checkTrackerData[slot].status;
if (status == RCSHOW_SEEN) {
gSaveContext.checkTrackerData[slot].status = RCSHOW_IDENTIFIED;
gSaveContext.checkTrackerData[slot].price = basePrice;
SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionId, true);
}
}
void CheckTrackerTransition(uint32_t sceneNum) {
if (!GameInteractor::IsSaveLoaded()) {
return;
}
doAreaScroll = true;
previousArea = currentArea;
currentArea = GetCheckArea();
switch (sceneNum) {
case SCENE_KOKIRI_SHOP:
case SCENE_BAZAAR:
case SCENE_POTION_SHOP_MARKET:
case SCENE_BOMBCHU_SHOP:
case SCENE_POTION_SHOP_KAKARIKO:
case SCENE_GORON_SHOP:
case SCENE_ZORA_SHOP:
SetShopSeen(sceneNum, false);
break;
}
}
void CheckTrackerFrame() {
if (!GameInteractor::IsSaveLoaded()) {
return;
}
// TODO: Move to OnAmmoChange hook once it gets added.
if (gSaveContext.checkTrackerData[RC_ZR_MAGIC_BEAN_SALESMAN].status != RCSHOW_COLLECTED &&
gSaveContext.checkTrackerData[RC_ZR_MAGIC_BEAN_SALESMAN].status != RCSHOW_SAVED) {
if (BEANS_BOUGHT >= 10) {
SetCheckCollected(RC_ZR_MAGIC_BEAN_SALESMAN);
}
}
}
void CheckTrackerItemReceive(GetItemEntry giEntry) {
if (!GameInteractor::IsSaveLoaded() || vector_contains_scene(skipScenes, gPlayState->sceneNum)) {
return;
}
auto scene = static_cast<SceneID>(gPlayState->sceneNum);
// Vanilla special item checks
if (!IS_RANDO) {
if (giEntry.itemId == ITEM_SHIELD_DEKU) {
SetCheckCollected(RC_KF_SHOP_ITEM_1);
return;
}else if (giEntry.itemId == ITEM_KOKIRI_EMERALD) {
SetCheckCollected(RC_QUEEN_GOHMA);
return;
} else if (giEntry.itemId == ITEM_GORON_RUBY) {
SetCheckCollected(RC_KING_DODONGO);
return;
} else if (giEntry.itemId == ITEM_ZORA_SAPPHIRE) {
SetCheckCollected(RC_BARINADE);
return;
} else if (giEntry.itemId == ITEM_MEDALLION_FOREST) {
SetCheckCollected(RC_PHANTOM_GANON);
return;
} else if (giEntry.itemId == ITEM_MEDALLION_FIRE) {
SetCheckCollected(RC_VOLVAGIA);
return;
} else if (giEntry.itemId == ITEM_MEDALLION_WATER) {
SetCheckCollected(RC_MORPHA);
return;
} else if (giEntry.itemId == ITEM_MEDALLION_SHADOW) {
SetCheckCollected(RC_BONGO_BONGO);
return;
} else if (giEntry.itemId == ITEM_MEDALLION_SPIRIT) {
SetCheckCollected(RC_TWINROVA);
return;
} else if (giEntry.itemId == ITEM_MEDALLION_LIGHT) {
SetCheckCollected(RC_GIFT_FROM_SAGES);
return;
} else if (giEntry.itemId == ITEM_SONG_EPONA) {
SetCheckCollected(RC_SONG_FROM_MALON);
return;
} else if (giEntry.itemId == ITEM_SONG_SARIA) {
SetCheckCollected(RC_SONG_FROM_SARIA);
return;
} else if (giEntry.itemId == ITEM_BEAN) {
SetCheckCollected(RC_ZR_MAGIC_BEAN_SALESMAN);
return;
} else if (giEntry.itemId == ITEM_BRACELET) {
SetCheckCollected(RC_GC_DARUNIAS_JOY);
return;
}/* else if (giEntry.itemId == ITEM_SONG_SUN) {
SetCheckCollected(RC_SONG_FROM_ROYAL_FAMILYS_TOMB);
return;
} else if (giEntry.itemId == ITEM_SONG_TIME) {
SetCheckCollected(RC_SONG_FROM_OCARINA_OF_TIME);
return;
} else if (giEntry.itemId == ITEM_SONG_STORMS) {
SetCheckCollected(RC_SONG_FROM_WINDMILL);
return;
} else if (giEntry.itemId == ITEM_SONG_MINUET) {
SetCheckCollected(RC_SHEIK_IN_FOREST);
return;
} else if (giEntry.itemId == ITEM_SONG_BOLERO) {
SetCheckCollected(RC_SHEIK_IN_CRATER);
return;
} else if (giEntry.itemId == ITEM_SONG_SERENADE) {
SetCheckCollected(RC_SHEIK_IN_ICE_CAVERN);
return;
} else if (giEntry.itemId == ITEM_SONG_NOCTURNE) {
SetCheckCollected(RC_SHEIK_IN_KAKARIKO);
return;
} else if (giEntry.itemId == ITEM_SONG_REQUIEM) {
SetCheckCollected(RC_SHEIK_AT_COLOSSUS);
return;
} else if (giEntry.itemId == ITEM_SONG_PRELUDE) {
SetCheckCollected(RC_SHEIK_AT_TEMPLE);
return;
}*/
}
}
void CheckTrackerSceneFlagSet(int16_t sceneNum, int16_t flagType, int32_t flag) {
if (flagType != FLAG_SCENE_TREASURE && flagType != FLAG_SCENE_COLLECTIBLE) {
return;
}
if (sceneNum == SCENE_GRAVEYARD && flag == 0x19 && flagType == FLAG_SCENE_COLLECTIBLE) { // Gravedigging tour special case
SetCheckCollected(RC_GRAVEYARD_DAMPE_GRAVEDIGGING_TOUR);
return;
}
for (auto [rc, rcObj] : RandomizerCheckObjects::GetAllRCObjects()) {
if (!IsVisibleInCheckTracker(rcObj)) {
continue;
}
SpoilerCollectionCheckType checkMatchType = flagType == FLAG_SCENE_TREASURE ? SpoilerCollectionCheckType::SPOILER_CHK_CHEST : SpoilerCollectionCheckType::SPOILER_CHK_COLLECTABLE;
SpoilerCollectionCheck scCheck = Location(rc)->GetCollectionCheck();
if (scCheck.scene == sceneNum && scCheck.flag == flag && scCheck.type == checkMatchType) {
SetCheckCollected(rc);
return;
}
}
}
void CheckTrackerFlagSet(int16_t flagType, int32_t flag) {
SpoilerCollectionCheckType checkMatchType = SpoilerCollectionCheckType::SPOILER_CHK_NONE;
switch (flagType) {
case FLAG_GS_TOKEN:
checkMatchType = SpoilerCollectionCheckType::SPOILER_CHK_GOLD_SKULLTULA;
break;
case FLAG_EVENT_CHECK_INF:
if ((flag == EVENTCHKINF_CARPENTERS_FREE(0) || flag == EVENTCHKINF_CARPENTERS_FREE(1) ||
flag == EVENTCHKINF_CARPENTERS_FREE(2) || flag == EVENTCHKINF_CARPENTERS_FREE(3))
&& GET_EVENTCHKINF_CARPENTERS_FREE_ALL()) {
SetCheckCollected(RC_GF_GERUDO_MEMBERSHIP_CARD);
return;
}
checkMatchType = SpoilerCollectionCheckType::SPOILER_CHK_EVENT_CHK_INF;
break;
case FLAG_INF_TABLE:
if (flag == INFTABLE_190) {
SetCheckCollected(RC_GF_HBA_1000_POINTS);
return;
} else if (flag == INFTABLE_11E) {
SetCheckCollected(RC_GC_ROLLING_GORON_AS_CHILD);
return;
} else if (flag == INFTABLE_GORON_CITY_DOORS_UNLOCKED) {
SetCheckCollected(RC_GC_ROLLING_GORON_AS_ADULT);
return;
} else if (flag == INFTABLE_139) {
SetCheckCollected(RC_ZD_KING_ZORA_THAWED);
return;
} else if (flag == INFTABLE_191) {
SetCheckCollected(RC_MARKET_LOST_DOG);
return;
}
if (!IS_RANDO) {
if (flag == INFTABLE_192) {
SetCheckCollected(RC_LW_DEKU_SCRUB_NEAR_BRIDGE);
return;
} else if (flag == INFTABLE_193) {
SetCheckCollected(RC_LW_DEKU_SCRUB_GROTTO_FRONT);
return;
}
}
break;
case FLAG_ITEM_GET_INF:
if (!IS_RANDO) {
if (flag == ITEMGETINF_OBTAINED_STICK_UPGRADE_FROM_STAGE) {
SetCheckCollected(RC_DEKU_THEATER_SKULL_MASK);
return;
} else if (flag == ITEMGETINF_OBTAINED_NUT_UPGRADE_FROM_STAGE) {
SetCheckCollected(RC_DEKU_THEATER_MASK_OF_TRUTH);
return;
} else if (flag == ITEMGETINF_0B) {
SetCheckCollected(RC_HF_DEKU_SCRUB_GROTTO);
return;
}
}
checkMatchType = SpoilerCollectionCheckType::SPOILER_CHK_ITEM_GET_INF;
break;
case FLAG_RANDOMIZER_INF:
checkMatchType = SpoilerCollectionCheckType::SPOILER_CHK_RANDOMIZER_INF;
break;
}
if (checkMatchType == SpoilerCollectionCheckType::SPOILER_CHK_NONE) {
return;
}
for (auto [rc, rcObj] : RandomizerCheckObjects::GetAllRCObjects()) {
if ((!IS_RANDO && ((rcObj.vOrMQ == RCVORMQ_MQ && !IS_MASTER_QUEST) ||
(rcObj.vOrMQ == RCVORMQ_VANILLA && IS_MASTER_QUEST))) ||
(IS_RANDO && ((OTRGlobals::Instance->gRandomizer->masterQuestDungeons.contains(rcObj.sceneId) &&
rcObj.vOrMQ == RCVORMQ_VANILLA) ||
!OTRGlobals::Instance->gRandomizer->masterQuestDungeons.contains(rcObj.sceneId) &&
rcObj.vOrMQ == RCVORMQ_MQ))) {
continue;
}
SpoilerCollectionCheck scCheck = Location(rc)->GetCollectionCheck();
SpoilerCollectionCheckType scCheckType = scCheck.type;
if (checkMatchType == SpoilerCollectionCheckType::SPOILER_CHK_RANDOMIZER_INF &&
(scCheckType == SpoilerCollectionCheckType::SPOILER_CHK_MERCHANT ||
scCheckType == SpoilerCollectionCheckType::SPOILER_CHK_SHOP_ITEM ||
scCheckType == SpoilerCollectionCheckType::SPOILER_CHK_COW ||
scCheckType == SpoilerCollectionCheckType::SPOILER_CHK_SCRUB ||
scCheckType == SpoilerCollectionCheckType::SPOILER_CHK_MASTER_SWORD ||
scCheckType == SpoilerCollectionCheckType::SPOILER_CHK_RANDOMIZER_INF)) {
if (flag == OTRGlobals::Instance->gRandomizer->GetRandomizerInfFromCheck(rc)) {
SetCheckCollected(rc);
return;
}
continue;
}
int16_t checkFlag = scCheck.flag;
if (checkMatchType == SpoilerCollectionCheckType::SPOILER_CHK_GOLD_SKULLTULA) {
checkFlag = rcObj.actorParams;
}
if (checkFlag == flag && scCheck.type == checkMatchType) {
SetCheckCollected(rc);
return;
}
}
}
void InitTrackerData(bool isDebug) {
TrySetAreas();
for (auto& [rc, rco] : RandomizerCheckObjects::GetAllRCObjects()) {
if (rc != RC_UNKNOWN_CHECK && rc != RC_MAX) {
DefaultCheckData(rc);
}
}
UpdateAllOrdering();
UpdateInventoryChecks();
}
void SaveTrackerData(SaveContext* saveContext, int sectionID, bool gameSave) {
SaveManager::Instance->SaveArray("checks", ARRAY_COUNT(saveContext->checkTrackerData), [&](size_t i) {
if (saveContext->checkTrackerData[i].status == RCSHOW_COLLECTED) {
if (gameSave) {
gSaveContext.checkTrackerData[i].status = saveContext->checkTrackerData[i].status = RCSHOW_SAVED;
UpdateAllOrdering();
UpdateInventoryChecks();
} else {
saveContext->checkTrackerData[i].status = RCSHOW_SCUMMED;
}
}
SaveManager::Instance->SaveStruct("", [&]() {
SaveManager::Instance->SaveData("status", saveContext->checkTrackerData[i].status);
SaveManager::Instance->SaveData("skipped", saveContext->checkTrackerData[i].skipped);
SaveManager::Instance->SaveData("price", saveContext->checkTrackerData[i].price);
SaveManager::Instance->SaveData("hintItem", saveContext->checkTrackerData[i].hintItem);
});
});
}
void SaveFile(SaveContext* saveContext, int sectionID, bool fullSave) {
SaveTrackerData(saveContext, sectionID, fullSave);
}
void LoadFile() {
SaveManager::Instance->LoadArray("checks", RC_MAX, [](size_t i) {
SaveManager::Instance->LoadStruct("", [&]() {
SaveManager::Instance->LoadData("status", gSaveContext.checkTrackerData[i].status);
SaveManager::Instance->LoadData("skipped", gSaveContext.checkTrackerData[i].skipped);
SaveManager::Instance->LoadData("price", gSaveContext.checkTrackerData[i].price);
SaveManager::Instance->LoadData("hintItem", gSaveContext.checkTrackerData[i].hintItem);
});
});
}
void Teardown() {
initialized = false;
for (auto& [rcArea, vec] : checksByArea) {
vec.clear();
areaChecksGotten[rcArea] = 0;
}
ClearAreaChecksAndTotals();
checksByArea.clear();
areasSpoiled = 0;
checkCollected = false;
checkLoops = 0;
lastLocationChecked = RC_UNKNOWN_CHECK;
}
@@ -922,7 +946,11 @@ void CheckTrackerWindow::DrawElement() {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(extraColor.r / 255.0f, extraColor.g / 255.0f,
extraColor.b / 255.0f, extraColor.a / 255.0f));
isThisAreaSpoiled = areasSpoiled & areaMask || CVarGetInteger("gCheckTrackerOptionMQSpoilers", 0);
isThisAreaSpoiled = areasSpoiled & areaMask || CVarGetInteger("gCheckTrackerOptionMQSpoilers", 0) || !IS_RANDO ||
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_NONE ||
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_SELECTION ||
(OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_SET_NUMBER &&
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_COUNT) == 12);
if (isThisAreaSpoiled) {
if (showVOrMQ && RandomizerCheckObjects::AreaIsDungeon(rcArea)) {
@@ -1148,8 +1176,8 @@ bool IsVisibleInCheckTracker(RandomizerCheckObject rcObj) {
}
else if (rcObj.vanillaCompletion) {
return (rcObj.vOrMQ == RCVORMQ_BOTH ||
rcObj.vOrMQ == RCVORMQ_MQ && OTRGlobals::Instance->gRandomizer->masterQuestDungeons.contains(rcObj.sceneId) ||
rcObj.vOrMQ == RCVORMQ_VANILLA && !OTRGlobals::Instance->gRandomizer->masterQuestDungeons.contains(rcObj.sceneId) ||
(rcObj.vOrMQ == RCVORMQ_MQ && IS_MASTER_QUEST) ||
(rcObj.vOrMQ == RCVORMQ_VANILLA && !IS_MASTER_QUEST) ||
rcObj.rc == RC_GIFT_FROM_SAGES) && rcObj.rc != RC_LINKS_POCKET;
}
return false;
@@ -1533,14 +1561,16 @@ void CheckTrackerWindow::InitElement() {
SaveManager::Instance->AddInitFunction(InitTrackerData);
sectionId = SaveManager::Instance->AddSaveFunction("trackerData", 1, SaveFile, true, -1);
SaveManager::Instance->AddLoadFunction("trackerData", 1, LoadFile);
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnLoadGame>(CheckTrackerLoadGame);
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnExitGame>([](uint32_t fileNum) {
Teardown();
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnItemReceive>(CheckTrackerItemReceive);
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSaleEnd>(CheckTrackerSaleEnd);
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>(CheckTrackerFrame);
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnTransitionEnd>(CheckTrackerTransition);
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnShopSlotChange>(CheckTrackerShopSlotChange);
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneFlagSet>(CheckTrackerSceneFlagSet);
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnFlagSet>(CheckTrackerFlagSet);
LocationTable_Init();
}
@@ -48,10 +48,7 @@ void Teardown();
void UpdateAllOrdering();
bool IsVisibleInCheckTracker(RandomizerCheckObject rcObj);
void InitTrackerData(bool isDebug);
void SetLastItemGetRC(RandomizerCheck rc);
RandomizerCheckArea GetCheckArea();
void CheckTrackerDialogClosed();
void ToggleShopRightChecks();
void UpdateCheck(uint32_t, RandomizerCheckTrackerData);
} // namespace CheckTracker
@@ -76,7 +76,7 @@ static s16 newIceCavernEntrance = ICE_CAVERN_ENTRANCE;
static s8 hasCopiedEntranceTable = 0;
static s8 hasModifiedEntranceTable = 0;
void Entrance_SetEntranceDiscovered(u16 entranceIndex);
void Entrance_SetEntranceDiscovered(u16 entranceIndex, u8 isReversedEntrance);
u8 Entrance_EntranceIsNull(EntranceOverride* entranceOverride) {
return entranceOverride->index == 0 && entranceOverride->destination == 0 && entranceOverride->blueWarp == 0
@@ -276,13 +276,13 @@ s16 Entrance_OverrideNextIndex(s16 nextEntranceIndex) {
return nextEntranceIndex;
}
Entrance_SetEntranceDiscovered(nextEntranceIndex);
Entrance_SetEntranceDiscovered(nextEntranceIndex, false);
EntranceTracker_SetLastEntranceOverride(nextEntranceIndex);
return Grotto_OverrideSpecialEntrance(Entrance_GetOverride(nextEntranceIndex));
}
s16 Entrance_OverrideDynamicExit(s16 dynamicExitIndex) {
Entrance_SetEntranceDiscovered(dynamicExitList[dynamicExitIndex]);
Entrance_SetEntranceDiscovered(dynamicExitList[dynamicExitIndex], false);
EntranceTracker_SetLastEntranceOverride(dynamicExitList[dynamicExitIndex]);
return Grotto_OverrideSpecialEntrance(Entrance_GetOverride(dynamicExitList[dynamicExitIndex]));
}
@@ -784,7 +784,7 @@ u8 Entrance_GetIsEntranceDiscovered(u16 entranceIndex) {
return 0;
}
void Entrance_SetEntranceDiscovered(u16 entranceIndex) {
void Entrance_SetEntranceDiscovered(u16 entranceIndex, u8 isReversedEntrance) {
// Skip if already set to save time from setting the connected entrance or
// if this entrance is outside of the randomized entrance range (i.e. is a dynamic entrance)
if (entranceIndex > MAX_ENTRANCE_RANDO_USED_INDEX || Entrance_GetIsEntranceDiscovered(entranceIndex)) {
@@ -796,14 +796,20 @@ void Entrance_SetEntranceDiscovered(u16 entranceIndex) {
if (idx < SAVEFILE_ENTRANCES_DISCOVERED_IDX_COUNT) {
u32 entranceBit = 1 << (entranceIndex - (idx * bitsPerIndex));
gSaveContext.sohStats.entrancesDiscovered[idx] |= entranceBit;
// Set connected
for (size_t i = 0; i < ENTRANCE_OVERRIDES_MAX_COUNT; i++) {
if (entranceIndex == gSaveContext.entranceOverrides[i].index) {
Entrance_SetEntranceDiscovered(gSaveContext.entranceOverrides[i].overrideDestination);
break;
// Set reverse entrance when not decoupled
if (!Randomizer_GetSettingValue(RSK_DECOUPLED_ENTRANCES) && !isReversedEntrance) {
for (size_t i = 0; i < ENTRANCE_OVERRIDES_MAX_COUNT; i++) {
if (entranceIndex == gSaveContext.entranceOverrides[i].index) {
Entrance_SetEntranceDiscovered(gSaveContext.entranceOverrides[i].overrideDestination, true);
break;
}
}
}
}
// Save entrancesDiscovered
Save_SaveSection(SECTION_ID_ENTRANCES);
// Save entrancesDiscovered when it is not the reversed entrance
if (!isReversedEntrance) {
Save_SaveSection(SECTION_ID_ENTRANCES);
}
}
@@ -63,7 +63,7 @@ void Entrance_OverrideSpawnScene(int32_t sceneNum, int32_t spawn);
int32_t Entrance_OverrideSpawnSceneRoom(int32_t sceneNum, int32_t spawn, int32_t room);
void Entrance_EnableFW(void);
uint8_t Entrance_GetIsEntranceDiscovered(uint16_t entranceIndex);
void Entrance_SetEntranceDiscovered(uint16_t entranceIndex);
void Entrance_SetEntranceDiscovered(uint16_t entranceIndex, uint8_t isReversedEntrance);
#ifdef __cplusplus
}
#endif
@@ -140,7 +140,7 @@ s16 Grotto_OverrideSpecialEntrance(s16 nextEntranceIndex) {
// If Link hits a grotto exit, load the entrance index from the grotto exit list
// based on the current grotto ID
if (nextEntranceIndex == 0x7FFF) {
Entrance_SetEntranceDiscovered(ENTRANCE_RANDO_GROTTO_EXIT_START + grottoId);
Entrance_SetEntranceDiscovered(ENTRANCE_RANDO_GROTTO_EXIT_START + grottoId, false);
EntranceTracker_SetLastEntranceOverride(ENTRANCE_RANDO_GROTTO_EXIT_START + grottoId);
nextEntranceIndex = grottoExitList[grottoId];
}
@@ -211,7 +211,7 @@ void Grotto_OverrideActorEntrance(Actor* thisx) {
if (grottoContent == grottoLoadTable[index].content && gPlayState->sceneNum == grottoLoadTable[index].scene) {
// Find the override for the matching index from the grotto Load List
Entrance_SetEntranceDiscovered(ENTRANCE_RANDO_GROTTO_LOAD_START + index);
Entrance_SetEntranceDiscovered(ENTRANCE_RANDO_GROTTO_LOAD_START + index, false);
EntranceTracker_SetLastEntranceOverride(ENTRANCE_RANDO_GROTTO_LOAD_START + index);
index = grottoLoadList[index];
@@ -219,7 +219,7 @@ std::unordered_map<RandomizerTrickArea, std::string> rtAreaNames = {
{ RTAREA_LAKE_HYLIA, "Lake Hylia"},
{ RTAREA_GERUDO_VALLEY, "Gerudo Valley"},
{ RTAREA_GERUDO_FORTRESS, "Gerudo Fortress"},
{ RTAREA_WASTELAND, "Desert Wasteland"},
{ RTAREA_WASTELAND, "Haunted Wasteland"},
{ RTAREA_DESERT_COLOSSUS, "Desert Colossus"},
{ RTAREA_MARKET, "Hyrule Market"},
{ RTAREA_HYRULE_CASTLE, "Hyrule Castle"},
+137 -4
View File
@@ -49,6 +49,9 @@
#include "Fonts.h"
#include <Utils/StringHelper.h>
#include "Enhancements/custom-message/CustomMessageManager.h"
#include "Enhancements/presets.h"
#include "util.h"
#include <boost_custom/container_hash/hash_32.hpp>
#if not defined (__SWITCH__) && not defined(__WIIU__)
#include "Extractor/Extract.h"
@@ -253,15 +256,26 @@ OTRGlobals::OTRGlobals() {
OTRFiles.push_back(sohOtrPath);
}
std::string patchesPath = LUS::Context::LocateFileAcrossAppDirs("mods", appShortName);
std::vector<std::string> patchOTRs = {};
if (patchesPath.length() > 0 && std::filesystem::exists(patchesPath)) {
if (std::filesystem::is_directory(patchesPath)) {
for (const auto& p : std::filesystem::recursive_directory_iterator(patchesPath)) {
for (const auto& p : std::filesystem::recursive_directory_iterator(patchesPath, std::filesystem::directory_options::follow_directory_symlink)) {
if (StringHelper::IEquals(p.path().extension().string(), ".otr")) {
OTRFiles.push_back(p.path().generic_string());
patchOTRs.push_back(p.path().generic_string());
}
}
}
}
std::sort(patchOTRs.begin(), patchOTRs.end(), [](const std::string& a, const std::string& b) {
return std::lexicographical_compare(
a.begin(), a.end(),
b.begin(), b.end(),
[](char c1, char c2) {
return std::tolower(c1) < std::tolower(c2);
}
);
});
OTRFiles.insert(OTRFiles.end(), patchOTRs.begin(), patchOTRs.end());
std::unordered_set<uint32_t> ValidHashes = {
OOT_PAL_MQ,
OOT_NTSC_JP_MQ,
@@ -1216,6 +1230,7 @@ extern "C" void Graph_StartFrame() {
case KbScancode::LUS_KB_TAB: {
// Toggle HD Assets
CVarSetInteger("gAltAssets", !CVarGetInteger("gAltAssets", 0));
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnAssetAltChange>();
ShouldClearTextureCacheAtEndOfFrame = true;
break;
}
@@ -1415,6 +1430,14 @@ extern "C" void ResourceMgr_DirtyDirectory(const char* resName) {
LUS::Context::GetInstance()->GetResourceManager()->DirtyDirectory(resName);
}
extern "C" void ResourceMgr_UnloadResource(const char* resName) {
std::string path = resName;
if (path.substr(0, 7) == "__OTR__") {
path = path.substr(7);
}
auto res = LUS::Context::GetInstance()->GetResourceManager()->UnloadResource(path);
}
// OTRTODO: There is probably a more elegant way to go about this...
// Kenix: This is definitely leaking memory when it's called.
extern "C" char** ResourceMgr_ListFiles(const char* searchMask, int* resultSize) {
@@ -1441,6 +1464,27 @@ extern "C" uint8_t ResourceMgr_FileExists(const char* filePath) {
return ExtensionCache.contains(path);
}
extern "C" uint8_t ResourceMgr_FileAltExists(const char* filePath) {
std::string path = filePath;
if (path.substr(0, 7) == "__OTR__") {
path = path.substr(7);
}
if (path.substr(0, 4) != "alt/") {
path = "alt/" + path;
}
return ExtensionCache.contains(path);
}
// Unloads a resource if an alternate version exists when alt assets are enabled
// The resource is only removed from the internal cache to prevent it from used in the next resource lookup
extern "C" void ResourceMgr_UnloadOriginalWhenAltExists(const char* resName) {
if (CVarGetInteger("gAltAssets", 0) && ResourceMgr_FileAltExists((char*) resName)) {
ResourceMgr_UnloadResource((char*) resName);
}
}
extern "C" void ResourceMgr_LoadFile(const char* resName) {
LUS::Context::GetInstance()->GetResourceManager()->LoadResource(resName);
}
@@ -1480,6 +1524,11 @@ extern "C" char* ResourceMgr_LoadFileFromDisk(const char* filePath) {
return data;
}
extern "C" uint8_t ResourceMgr_TexIsRaw(const char* texPath) {
auto res = std::static_pointer_cast<LUS::Texture>(GetResourceByNameHandlingMQ(texPath));
return res->Flags & TEX_FLAG_LOAD_AS_RAW;
}
extern "C" uint8_t ResourceMgr_ResourceIsBackground(char* texPath) {
auto res = GetResourceByNameHandlingMQ(texPath);
return res->GetInitData()->Type == LUS::ResourceType::SOH_Background;
@@ -1565,10 +1614,20 @@ extern "C" void ResourceMgr_PushCurrentDirectory(char* path)
extern "C" Gfx* ResourceMgr_LoadGfxByName(const char* path)
{
// When an alt resource exists for the DL, we need to unload the original asset
// to clear the cache so the alt asset will be loaded instead
// OTRTODO: If Alt loading over original cache is fixed, this line can most likely be removed
ResourceMgr_UnloadOriginalWhenAltExists(path);
auto res = std::static_pointer_cast<LUS::DisplayList>(GetResourceByNameHandlingMQ(path));
return (Gfx*)&res->Instructions[0];
}
extern "C" uint8_t ResourceMgr_FileIsCustomByName(const char* path) {
auto res = std::static_pointer_cast<LUS::DisplayList>(GetResourceByNameHandlingMQ(path));
return res->GetInitData()->IsCustom;
}
typedef struct {
int index;
Gfx instruction;
@@ -1600,6 +1659,11 @@ extern "C" void ResourceMgr_PatchGfxByName(const char* path, const char* patchNa
// index /= 2;
// }
// Do not patch custom assets as they most likely do not have the same instructions as authentic assets
if (res->GetInitData()->IsCustom) {
return;
}
Gfx* gfx = (Gfx*)&res->Instructions[index];
if (!originalGfx.contains(path) || !originalGfx[path].contains(patchName)) {
@@ -1616,6 +1680,11 @@ extern "C" void ResourceMgr_PatchGfxCopyCommandByName(const char* path, const ch
auto res = std::static_pointer_cast<LUS::DisplayList>(
LUS::Context::GetInstance()->GetResourceManager()->LoadResource(path));
// Do not patch custom assets as they most likely do not have the same instructions as authentic assets
if (res->GetInitData()->IsCustom) {
return;
}
Gfx* destinationGfx = (Gfx*)&res->Instructions[destinationIndex];
Gfx sourceGfx = res->Instructions[sourceIndex];
@@ -2508,6 +2577,70 @@ extern "C" void Gfx_RegisterBlendedTexture(const char* name, u8* mask, u8* repla
gfx_register_blended_texture(name, mask, replacement);
}
extern "C" void CheckTracker_OnMessageClose() {
CheckTracker::CheckTrackerDialogClosed();
// #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<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
+6
View File
@@ -86,11 +86,15 @@ uint32_t ResourceMgr_GetGameVersion(int index);
uint32_t ResourceMgr_GetGamePlatform(int index);
uint32_t ResourceMgr_GetGameRegion(int index);
void ResourceMgr_LoadDirectory(const char* resName);
void ResourceMgr_UnloadResource(const char* resName);
char** ResourceMgr_ListFiles(const char* searchMask, int* resultSize);
uint8_t ResourceMgr_FileExists(const char* resName);
uint8_t ResourceMgr_FileAltExists(const char* resName);
void ResourceMgr_UnloadOriginalWhenAltExists(const char* resName);
char* GetResourceDataByNameHandlingMQ(const char* path);
void ResourceMgr_LoadFile(const char* resName);
char* ResourceMgr_LoadFileFromDisk(const char* filePath);
uint8_t ResourceMgr_TexIsRaw(const char* texPath);
uint8_t ResourceMgr_ResourceIsBackground(char* texPath);
char* ResourceMgr_LoadJPEG(char* data, size_t dataSize);
uint16_t ResourceMgr_LoadTexWidthByName(char* texPath);
@@ -101,6 +105,7 @@ AnimationHeaderCommon* ResourceMgr_LoadAnimByName(const char* path);
char* ResourceMgr_GetNameByCRC(uint64_t crc, char* alloc);
Gfx* ResourceMgr_LoadGfxByCRC(uint64_t crc);
Gfx* ResourceMgr_LoadGfxByName(const char* path);
uint8_t ResourceMgr_FileIsCustomByName(const char* path);
void ResourceMgr_PatchGfxByName(const char* path, const char* patchName, int index, Gfx instruction);
void ResourceMgr_UnpatchGfxByName(const char* path, const char* patchName);
char* ResourceMgr_LoadArrayByNameAsVec3s(const char* path);
@@ -170,6 +175,7 @@ void EntranceTracker_SetLastEntranceOverride(s16 entranceIndex);
void Gfx_RegisterBlendedTexture(const char* name, u8* mask, u8* replacement);
void SaveManager_ThreadPoolWait();
void CheckTracker_OnMessageClose();
void SoH_ProcessDroppedFiles();
int32_t GetGIID(uint32_t itemID);
#endif
+63 -30
View File
@@ -47,6 +47,11 @@ std::filesystem::path SaveManager::GetFileName(int fileNum) {
return sSavePath / ("file" + std::to_string(fileNum + 1) + ".sav");
}
std::filesystem::path SaveManager::GetFileTempName(int fileNum) {
const std::filesystem::path sSavePath(LUS::Context::GetPathRelativeToAppDirectory("Save"));
return sSavePath / ("file" + std::to_string(fileNum + 1) + ".temp");
}
SaveManager::SaveManager() {
coreSectionIDsByName["base"] = SECTION_ID_BASE;
coreSectionIDsByName["randomizer"] = SECTION_ID_RANDOMIZER;
@@ -65,6 +70,10 @@ SaveManager::SaveManager() {
AddInitFunction(InitFileImpl);
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnExitGame>([this](uint32_t fileNum) { ThreadPoolWait(); });
smThreadPool = std::make_shared<BS::thread_pool>(1);
for (SaveFileMetaInfo& info : fileMetaInfo) {
info.valid = false;
info.deaths = 0;
@@ -357,12 +366,14 @@ void SaveManager::SaveRandomizer(SaveContext* saveContext, int sectionID, bool f
});
}
// Init() here is an extension of InitSram, and thus not truly an initializer for SaveManager itself. don't put any class initialization stuff here
void SaveManager::Init() {
// Wait on saves that snuck through the Wait in OnExitGame
ThreadPoolWait();
const std::filesystem::path sSavePath(LUS::Context::GetPathRelativeToAppDirectory("Save"));
const std::filesystem::path sGlobalPath = sSavePath / std::string("global.sav");
auto sOldSavePath = LUS::Context::GetPathRelativeToAppDirectory("oot_save.sav");
auto sOldBackupSavePath = LUS::Context::GetPathRelativeToAppDirectory("oot_save.bak");
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnExitGame>([this](uint32_t fileNum) { ThreadPoolWait(); });
// If the save directory does not exist, create it
if (!std::filesystem::exists(sSavePath)) {
@@ -403,7 +414,6 @@ void SaveManager::Init() {
} else {
CreateDefaultGlobal();
}
smThreadPool = std::make_shared<BS::thread_pool>(1);
// Load files to initialize metadata
for (int fileNum = 0; fileNum < MaxFiles; fileNum++) {
@@ -869,6 +879,32 @@ void SaveManager::InitFileMaxed() {
gSaveContext.sceneFlags[5].swch = 0x40000000;
}
#if defined(__WIIU__) || defined(__SWITCH__)
// std::filesystem::copy_file doesn't work properly with the Wii U's toolchain atm
int copy_file(const char* src, const char* dst) {
alignas(0x40) uint8_t buf[4096];
FILE* r = fopen(src, "r");
if (!r) {
return -1;
}
FILE* w = fopen(dst, "w");
if (!w) {
return -2;
}
size_t res;
while ((res = fread(buf, 1, sizeof(buf), r)) > 0) {
if (fwrite(buf, 1, res, w) != res) {
break;
}
}
fclose(r);
fclose(w);
return res >= 0 ? 0 : res;
}
#endif
// Threaded SaveFile takes copy of gSaveContext for local unmodified storage
void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext, int sectionID) {
@@ -910,19 +946,42 @@ void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext, int se
svi.func(saveContext, sectionID, false);
}
std::filesystem::path fileName = GetFileName(fileNum);
std::filesystem::path tempFile = GetFileTempName(fileNum);
if (std::filesystem::exists(tempFile)) {
std::filesystem::remove(tempFile);
}
#if defined(__SWITCH__) || defined(__WIIU__)
FILE* w = fopen(GetFileName(fileNum).c_str(), "w");
FILE* w = fopen(tempFile.c_str(), "w");
std::string json_string = saveBlock.dump(4);
fwrite(json_string.c_str(), sizeof(char), json_string.length(), w);
fclose(w);
#else
std::ofstream output(GetFileName(fileNum));
std::ofstream output(tempFile);
output << std::setw(4) << saveBlock << std::endl;
output.close();
#endif
if (std::filesystem::exists(fileName)) {
std::filesystem::remove(fileName);
}
#if defined(__SWITCH__) || defined(__WIIU__)
copy_file(tempFile.c_str(), fileName.c_str());
#else
std::filesystem::copy_file(tempFile, fileName);
#endif
if (std::filesystem::exists(tempFile)) {
std::filesystem::remove(tempFile);
}
delete saveContext;
InitMeta(fileNum);
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSaveFile>(fileNum);
SPDLOG_INFO("Save File Finish - fileNum: {}", fileNum);
}
// SaveSection creates a copy of gSaveContext to prevent mid-save data modification, and passes its reference to SaveFileThreaded
@@ -2105,32 +2164,6 @@ void SaveManager::LoadStruct(const std::string& name, LoadStructFunc func) {
}
}
#if defined(__WIIU__) || defined(__SWITCH__)
// std::filesystem::copy_file doesn't work properly with the Wii U's toolchain atm
int copy_file(const char* src, const char* dst) {
alignas(0x40) uint8_t buf[4096];
FILE* r = fopen(src, "r");
if (!r) {
return -1;
}
FILE* w = fopen(dst, "w");
if (!w) {
return -2;
}
size_t res;
while ((res = fread(buf, 1, sizeof(buf), r)) > 0) {
if (fwrite(buf, 1, res, w) != res) {
break;
}
}
fclose(r);
fclose(w);
return res >= 0 ? 0 : res;
}
#endif
void SaveManager::CopyZeldaFile(int from, int to) {
assert(std::filesystem::exists(GetFileName(from)));
DeleteZeldaFile(to);
+1
View File
@@ -142,6 +142,7 @@ class SaveManager {
private:
std::filesystem::path GetFileName(int fileNum);
std::filesystem::path GetFileTempName(int fileNum);
nlohmann::json saveBlock;
void ConvertFromUnversioned();
+22 -2
View File
@@ -492,6 +492,8 @@ extern std::shared_ptr<GameplayStatsWindow> mGameplayStatsWindow;
void DrawEnhancementsMenu() {
if (ImGui::BeginMenu("Enhancements"))
{
ImGui::BeginDisabled(CVarGetInteger("gDisableChangingSettings", 0));
DrawPresetSelector(PRESET_TYPE_ENHANCEMENTS);
UIWidgets::PaddedSeparator();
@@ -532,6 +534,8 @@ void DrawEnhancementsMenu() {
" - Small keys: Small silver chest\n"
" - Boss keys: Vanilla size and texture\n"
" - Skulltula Tokens: Small skulltula chest\n"
"\n"
"NOTE: Textures will not apply if you are using a mod pack with a custom chest model."
);
if (CVarGetInteger("gChestSizeAndTextureMatchesContents", CSMC_DISABLED) != CSMC_DISABLED) {
UIWidgets::PaddedEnhancementCheckbox("Chests of Agony", "gChestSizeDependsStoneOfAgony", true, false);
@@ -603,7 +607,7 @@ void DrawEnhancementsMenu() {
UIWidgets::PaddedEnhancementCheckbox("Nuts explode bombs", "gNutsExplodeBombs", true, false);
UIWidgets::Tooltip("Makes nuts explode bombs, similar to how they interact with bombchus. This does not affect bombflowers.");
UIWidgets::PaddedEnhancementCheckbox("Equip Multiple Arrows at Once", "gSeparateArrows", true, false);
UIWidgets::Tooltip("Allow the bow and magic arrows to be equipped at the same time on different slots");
UIWidgets::Tooltip("Allow the bow and magic arrows to be equipped at the same time on different slots. (Note this will disable the behaviour of the 'Equip Dupe' glitch)");
UIWidgets::PaddedEnhancementCheckbox("Bow as Child/Slingshot as Adult", "gBowSlingShotAmmoFix", true, false);
UIWidgets::Tooltip("Allows child to use bow with arrows.\nAllows adult to use slingshot with seeds.\n\nRequires glitches or 'Timeless Equipment' cheat to equip.");
UIWidgets::PaddedEnhancementCheckbox("Better Farore's Wind", "gBetterFW", true, false);
@@ -905,6 +909,7 @@ void DrawEnhancementsMenu() {
if (ImGui::BeginMenu("Mods")) {
if (UIWidgets::PaddedEnhancementCheckbox("Use Alternate Assets", "gAltAssets", false, false)) {
ShouldClearTextureCacheAtEndOfFrame = true;
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnAssetAltChange>();
}
UIWidgets::Tooltip("Toggle between standard assets and alternate assets. Usually mods will indicate if this setting has to be used or not.");
UIWidgets::PaddedEnhancementCheckbox("Disable Bomb Billboarding", "gDisableBombBillboarding", true, false);
@@ -1059,6 +1064,11 @@ void DrawEnhancementsMenu() {
UIWidgets::Tooltip("Fixes the bushes to drop items correctly rather than spawning undefined items.");
UIWidgets::PaddedEnhancementCheckbox("Fix falling from vine edges", "gFixVineFall", true, false);
UIWidgets::Tooltip("Prevents immediately falling off climbable surfaces if climbing on the edges.");
UIWidgets::PaddedEnhancementCheckbox("Fix Link's eyes open while sleeping", "gFixEyesOpenWhileSleeping", true, false);
UIWidgets::Tooltip("Fixes Link's eyes being open in the opening cutscene when he is supposed to be sleeping.");
UIWidgets::PaddedEnhancementCheckbox("Fix Darunia dancing too fast", "gEnhancements.FixDaruniaDanceSpeed",
true, false, false, "", UIWidgets::CheckboxGraphics::Cross, true);
UIWidgets::Tooltip("Fixes Darunia's dancing speed so he dances to the beat of Saria's Song, like in vanilla.");
ImGui::EndMenu();
}
@@ -1194,6 +1204,8 @@ void DrawEnhancementsMenu() {
UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f);
ImGui::EndDisabled();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(12.0f, 6.0f));
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
@@ -1235,6 +1247,8 @@ void DrawEnhancementsMenu() {
void DrawCheatsMenu() {
if (ImGui::BeginMenu("Cheats"))
{
ImGui::BeginDisabled(CVarGetInteger("gDisableChangingSettings", 0));
if (ImGui::BeginMenu("Infinite...")) {
UIWidgets::EnhancementCheckbox("Money", "gInfiniteMoney");
UIWidgets::PaddedEnhancementCheckbox("Health", "gInfiniteHealth", true, false);
@@ -1389,6 +1403,8 @@ void DrawCheatsMenu() {
}
UIWidgets::Tooltip("Clears the cutscene pointer to a value safe for wrong warps.");
ImGui::EndDisabled();
ImGui::EndMenu();
}
}
@@ -1402,6 +1418,8 @@ extern std::shared_ptr<DLViewerWindow> mDLViewerWindow;
void DrawDeveloperToolsMenu() {
if (ImGui::BeginMenu("Developer Tools")) {
ImGui::BeginDisabled(CVarGetInteger("gDisableChangingSettings", 0));
UIWidgets::EnhancementCheckbox("OoT Debug Mode", "gDebugEnabled");
UIWidgets::Tooltip("Enables Debug Mode, allowing you to select maps with L + R + Z, noclip with L + D-pad Right, and open the debug menu with L on the pause screen");
if (CVarGetInteger("gDebugEnabled", 0)) {
@@ -1474,6 +1492,8 @@ void DrawDeveloperToolsMenu() {
ImGui::PopStyleVar(3);
ImGui::PopStyleColor(1);
ImGui::EndDisabled();
ImGui::EndMenu();
}
}
@@ -1622,4 +1642,4 @@ void SohMenuBar::DrawElement() {
ImGui::EndMenuBar();
}
}
} // namespace SohGui
} // namespace SohGui
+29 -25
View File
@@ -103,38 +103,42 @@ void SkeletonFactoryV0::ParseFileXML(tinyxml2::XMLElement* reader, std::shared_p
{
std::shared_ptr<Skeleton> skel = std::static_pointer_cast<Skeleton>(resource);
std::string skeletonType = reader->Attribute("Type");
// std::string skeletonLimbType = reader->Attribute("LimbType");
int numLimbs = reader->IntAttribute("LimbCount");
int numDLs = reader->IntAttribute("DisplayListCount");
skel->type = SkeletonType::Flex; // Default to Flex for legacy reasons
if (reader->FindAttribute("Type")) {
std::string skeletonType = reader->Attribute("Type");
if (skeletonType == "Flex") {
skel->type = SkeletonType::Flex;
} else if (skeletonType == "Curve") {
skel->type = SkeletonType::Curve;
} else if (skeletonType == "Normal") {
skel->type = SkeletonType::Normal;
if (skeletonType == "Flex") {
skel->type = SkeletonType::Flex;
} else if (skeletonType == "Curve") {
skel->type = SkeletonType::Curve;
} else if (skeletonType == "Normal") {
skel->type = SkeletonType::Normal;
}
}
skel->type = SkeletonType::Flex;
skel->limbType = LimbType::LOD;
skel->limbType = LimbType::LOD; // Default to LOD for legacy reasons
if (reader->FindAttribute("LimbType")) {
std::string skeletonLimbType = reader->Attribute("LimbType");
// if (skeletonLimbType == "Standard")
// skel->limbType = LimbType::Standard;
// else if (skeletonLimbType == "LOD")
// skel->limbType = LimbType::LOD;
// else if (skeletonLimbType == "Curve")
// skel->limbType = LimbType::Curve;
// else if (skeletonLimbType == "Skin")
// skel->limbType = LimbType::Skin;
// else if (skeletonLimbType == "Legacy")
// Sskel->limbType = LimbType::Legacy;
if (skeletonLimbType == "Standard") {
skel->limbType = LimbType::Standard;
} else if (skeletonLimbType == "LOD") {
skel->limbType = LimbType::LOD;
} else if (skeletonLimbType == "Curve") {
skel->limbType = LimbType::Curve;
} else if (skeletonLimbType == "Skin") {
skel->limbType = LimbType::Skin;
} else if (skeletonLimbType == "Legacy") {
skel->limbType = LimbType::Legacy;
}
}
skel->limbCount = reader->IntAttribute("LimbCount");
skel->dListCount = reader->IntAttribute("DisplayListCount");
auto child = reader->FirstChildElement();
skel->limbCount = numLimbs;
skel->dListCount = numDLs;
while (child != nullptr) {
std::string childName = child->Name();
+1 -1
View File
@@ -260,7 +260,7 @@ void Audio_osWritebackDCache(void* mem, s32 size)
s32 osAiSetFrequency(u32 freq)
{
return 1;
}
s32 osEPiStartDma(OSPiHandle* handle, OSIoMesg* mb, s32 direction)
+18
View File
@@ -2,6 +2,7 @@
#include <string.h>
#include <vector>
#include <algorithm>
std::vector<std::string> sceneNames = {
"Inside the Deku Tree",
@@ -318,3 +319,20 @@ void SohUtils::CopyStringToCharArray(char* destination, std::string source, size
strncpy(destination, source.c_str(), size - 1);
destination[size - 1] = '\0';
}
std::string SohUtils::Sanitize(std::string stringValue) {
// Add backslashes.
for (auto i = stringValue.begin();;) {
auto const pos = std::find_if(i, stringValue.end(), [](char const c) { return '\\' == c || '\'' == c || '"' == c; });
if (pos == stringValue.end()) {
break;
}
i = std::next(stringValue.insert(pos, '\\'), 2);
}
// Removes others.
stringValue.erase(std::remove_if(stringValue.begin(), stringValue.end(), [](char const c) {
return '\n' == c || '\r' == c || '\0' == c || '\x1A' == c; }), stringValue.end());
return stringValue;
}
+2
View File
@@ -12,4 +12,6 @@ namespace SohUtils {
// Copies a string and ensures the destination is null terminated if the source string is larger than size
// Only up to size-1 characters are copied from the source string
void CopyStringToCharArray(char* destination, std::string source, size_t size);
std::string Sanitize(std::string stringValue);
} // namespace SohUtils
+6
View File
@@ -72,6 +72,9 @@ MessageTableEntry* OTRMessage_LoadTable(const char* filePath, bool isNES) {
_message_0xFFFC_nes = (char*)file->messages[i].msg.c_str();
}
// Assert that the first message starts at the first text ID
assert(table[0].textId == 0x0001);
return table;
}
@@ -104,6 +107,9 @@ extern "C" void OTRMessage_Init()
sStaffMessageEntryTablePtr[i].segment = file2->messages[i].msg.c_str();
sStaffMessageEntryTablePtr[i].msgSize = file2->messages[i].msg.size();
}
// Assert staff credits start at the first credits ID
assert(sStaffMessageEntryTablePtr[0].textId == 0x0500);
}
CustomMessageManager::Instance->AddCustomMessageTable(customMessageTableID);
+11
View File
@@ -204,6 +204,17 @@ u8 Inventory_DeleteEquipment(PlayState* play, s16 equipment) {
if (equipment == EQUIP_TYPE_TUNIC) {
gSaveContext.equips.equipment |= EQUIP_VALUE_TUNIC_KOKIRI << (EQUIP_TYPE_TUNIC * 4);
// non-vanilla: remove goron and zora tunics from item buttons if assignable tunics is on
if (CVarGetInteger("gAssignableTunicsAndBoots", 0) && equipValue != EQUIP_VALUE_TUNIC_KOKIRI) {
ItemID item = (equipValue == EQUIP_VALUE_TUNIC_GORON ? ITEM_TUNIC_GORON : ITEM_TUNIC_ZORA);
for (int i = 1; i < ARRAY_COUNT(gSaveContext.equips.buttonItems); i++) {
if (gSaveContext.equips.buttonItems[i] == item) {
gSaveContext.equips.buttonItems[i] = ITEM_NONE;
gSaveContext.equips.cButtonSlots[i - 1] = SLOT_NONE;
}
}
}
// end non-vanilla
}
if (equipment == EQUIP_TYPE_SWORD) {
+1 -1
View File
@@ -3384,7 +3384,7 @@ void Message_Update(PlayState* play) {
}
sLastPlayedSong = 0xFF;
osSyncPrintf("OCARINA_MODE=%d chk_ocarina_no=%d\n", play->msgCtx.ocarinaMode, msgCtx->unk_E3F2);
CheckTracker_OnMessageClose();
// TODO: OnMessageClose hook
break;
case MSGMODE_PAUSED:
break;
+5 -4
View File
@@ -1469,6 +1469,7 @@ void Inventory_SwapAgeEquipment(void) {
gSaveContext.equips.buttonItems[0] = ITEM_SWORD_MASTER;
} else {
gSaveContext.equips.buttonItems[0] = ITEM_NONE;
Flags_SetInfTable(INFTABLE_SWORDLESS);
}
if (gSaveContext.inventory.items[SLOT_NUT] != ITEM_NONE) {
@@ -1525,13 +1526,13 @@ void Inventory_SwapAgeEquipment(void) {
} else {
// When becoming child, set swordless flag if player doesn't have kokiri sword
// Only in rando to keep swordless link bugs in vanilla
if (IS_RANDO && (EQUIP_INV_SWORD_KOKIRI << (EQUIP_TYPE_SWORD * 4) & gSaveContext.inventory.equipment) == 0) {
if (IS_RANDO && CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_KOKIRI) == 0) {
Flags_SetInfTable(INFTABLE_SWORDLESS);
}
// When using enhancements, set swordless flag if player doesn't have kokiri sword or hasn't equipped a sword yet.
// Then set the child equips button items to item none to ensure kokiri sword is not equipped
if ((CVarGetInteger("gSwitchAge", 0) || CVarGetInteger("gSwitchTimeline", 0)) && ((EQUIP_INV_SWORD_KOKIRI << (EQUIP_TYPE_SWORD * 4) & gSaveContext.inventory.equipment) == 0 || Flags_GetInfTable(INFTABLE_SWORDLESS))) {
if ((CVarGetInteger("gSwitchAge", 0) || CVarGetInteger("gSwitchTimeline", 0)) && (CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_KOKIRI) == 0 || Flags_GetInfTable(INFTABLE_SWORDLESS))) {
Flags_SetInfTable(INFTABLE_SWORDLESS);
gSaveContext.childEquips.buttonItems[0] = ITEM_NONE;
}
@@ -1567,7 +1568,7 @@ void Inventory_SwapAgeEquipment(void) {
gSaveContext.equips.equipment = gSaveContext.childEquips.equipment;
gSaveContext.equips.equipment &= (u16) ~(0xF << (EQUIP_TYPE_SWORD * 4));
// Equips kokiri sword in the inventory screen only if kokiri sword exists in inventory and a sword has been equipped already
if (!((EQUIP_INV_SWORD_KOKIRI << (EQUIP_TYPE_SWORD * 4) & gSaveContext.inventory.equipment) == 0) && !Flags_GetInfTable(INFTABLE_SWORDLESS)) {
if (!(CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_KOKIRI) == 0) && !Flags_GetInfTable(INFTABLE_SWORDLESS)) {
gSaveContext.equips.equipment |= EQUIP_VALUE_SWORD_KOKIRI << (EQUIP_TYPE_SWORD * 4);
}
} else if (gSaveContext.childEquips.buttonItems[0] != ITEM_NONE) {
@@ -1597,7 +1598,7 @@ void Inventory_SwapAgeEquipment(void) {
(only kokiri tunic/boots equipped, no sword, no C-button items, no D-Pad items).
When becoming child, set swordless flag if player doesn't have kokiri sword
Only in rando to keep swordless link bugs in vanilla*/
if (EQUIP_INV_SWORD_KOKIRI << (EQUIP_TYPE_SWORD * 4) & gSaveContext.inventory.equipment == 0) {
if (CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_KOKIRI) == 0) {
Flags_SetInfTable(INFTABLE_SWORDLESS);
}
+27
View File
@@ -33,6 +33,7 @@ u64 D_801614D0[0xA00];
#endif
PlayState* gPlayState;
s16 firstInit = 0;
s16 gEnPartnerId;
@@ -490,6 +491,12 @@ void Play_Init(GameState* thisx) {
}
}
// Properly initialize the frame counter so it doesn't use garbage data
if (!firstInit) {
play->gameplayFrames = 0;
firstInit = 1;
}
// Invalid entrance, so immediately exit the game to opening title
if (gSaveContext.entranceIndex == -1) {
gSaveContext.entranceIndex = 0;
@@ -2329,8 +2336,28 @@ void Play_PerformSave(PlayState* play) {
if (play != NULL && gSaveContext.fileNum != 0xFF) {
Play_SaveSceneFlags(play);
gSaveContext.savedSceneNum = play->sceneNum;
// Track values from temp B
uint8_t prevB = gSaveContext.equips.buttonItems[0];
uint8_t prevStatus = gSaveContext.buttonStatus[0];
// Replicate the B button restore from minigames/epona that kaleido does
if (gSaveContext.equips.buttonItems[0] == ITEM_SLINGSHOT ||
gSaveContext.equips.buttonItems[0] == ITEM_BOW ||
gSaveContext.equips.buttonItems[0] == ITEM_BOMBCHU ||
gSaveContext.equips.buttonItems[0] == ITEM_FISHING_POLE ||
(gSaveContext.equips.buttonItems[0] == ITEM_NONE && !Flags_GetInfTable(INFTABLE_SWORDLESS))) {
gSaveContext.equips.buttonItems[0] = gSaveContext.buttonStatus[0];
Interface_RandoRestoreSwordless();
}
Save_SaveFile();
// Restore temp B values back
gSaveContext.equips.buttonItems[0] = prevB;
gSaveContext.buttonStatus[0] = prevStatus;
uint8_t triforceHuntCompleted =
IS_RANDO &&
gSaveContext.triforcePiecesCollected == Randomizer_GetSettingValue(RSK_TRIFORCE_HUNT_PIECES_REQUIRED) &&
+5
View File
@@ -277,6 +277,11 @@ void func_8009638C(Gfx** displayList, void* source, void* tlut, u16 width, u16 h
bg->b.imagePal = 0;
bg->b.imageFlip = CVarGetInteger("gMirroredWorld", 0) ? G_BG_FLAG_FLIPS : 0;
// When an alt resource exists for the background, we need to unload the original asset
// to clear the cache so the alt asset will be loaded instead
// OTRTODO: If Alt loading over original cache is fixed, this line can most likely be removed
ResourceMgr_UnloadOriginalWhenAltExists((char*) source);
if (ResourceMgr_ResourceIsBackground((char*) source)) {
char* blob = (char*) ResourceGetDataByName((char *) source);
swapAndConvertJPEG(blob);
+6 -1
View File
@@ -417,7 +417,12 @@ void func_800AEFC8(SkyboxContext* skyboxCtx, s16 skyboxId) {
s32 j;
s32 phi_s3 = 0;
if (skyboxId == SKYBOX_BAZAAR || (skyboxId > SKYBOX_HOUSE_KAKARIKO && skyboxId <= SKYBOX_BOMBCHU_SHOP)) {
//! @bug All shops only provide 2 faces for their sky box. Mask shop is missing from the condition
// meaning that the Mask shop will calculate 4 faces
// This effect is not noticed as the faces are behind the camera, but will cause a crash in SoH.
// SOH General: We have added the Mask shop to this check so only the 2 expected faces are calculated.
if (skyboxId == SKYBOX_BAZAAR || skyboxId == SKYBOX_HAPPY_MASK_SHOP ||
(skyboxId >= SKYBOX_KOKIRI_SHOP && skyboxId <= SKYBOX_BOMBCHU_SHOP)) {
for (j = 0, i = 0; i < 2; i++, j += 2) {
phi_s3 = func_800ADBB0(skyboxCtx, skyboxCtx->roomVtx, phi_s3, D_8012AEBC[i].unk_0, D_8012AEBC[i].unk_4,
D_8012AEBC[i].unk_8, D_8012AEBC[i].unk_C, D_8012AEBC[i].unk_10, i, j);
+8 -2
View File
@@ -59,8 +59,14 @@ void SkyboxDraw_Draw(SkyboxContext* skyboxCtx, GraphicsContext* gfxCtx, s16 skyb
gSPDisplayList(POLY_OPA_DISP++, skyboxCtx->dListBuf[2]);
gSPDisplayList(POLY_OPA_DISP++, skyboxCtx->dListBuf[3]);
if (skyboxId != SKYBOX_BAZAAR) {
if (skyboxId <= SKYBOX_HOUSE_KAKARIKO || skyboxId > SKYBOX_BOMBCHU_SHOP) {
//! @bug All shops only provide 2 faces for their sky box. Mask shop is missing from the condition
// meaning that the Mask shop will render the previously loaded sky box values, or uninitialized data.
// This effect is not noticed as the faces are behind the camera, but will cause a crash in SoH.
// SOH General: We have added the Mask shop to this check so only the 2 expected faces are rendered.
if (skyboxId != SKYBOX_BAZAAR && skyboxId != SKYBOX_HAPPY_MASK_SHOP) {
if (skyboxId < SKYBOX_KOKIRI_SHOP || skyboxId > SKYBOX_BOMBCHU_SHOP) {
// Skip remaining faces for most shop skyboxes
gDPPipeSync(POLY_OPA_DISP++);
gDPLoadTLUT_pal256(POLY_OPA_DISP++, skyboxCtx->palettes[2]);
gSPDisplayList(POLY_OPA_DISP++, skyboxCtx->dListBuf[4]);
@@ -5,9 +5,21 @@
#include "scenes/dungeons/ddan_boss/ddan_boss_room_1.h"
#include "soh/frame_interpolation.h"
#include "soh/Enhancements/boss-rush/BossRush.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include <stdlib.h> // malloc
#include <string.h> // memcpy
// OTRTODO: Replace usage of this method when we can clear the cache
// for a single texture without the need of a DL opcode in the render code
void gfx_texture_cache_clear();
#define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_WHILE_CULLED | ACTOR_FLAG_DRAW_WHILE_CULLED)
#define LAVA_TEX_WIDTH 32
#define LAVA_TEX_HEIGHT 64
#define LAVA_TEX_SIZE 2048
void BossDodongo_Init(Actor* thisx, PlayState* play);
void BossDodongo_Destroy(Actor* thisx, PlayState* play);
void BossDodongo_Update(Actor* thisx, PlayState* play);
@@ -59,6 +71,13 @@ static u8 sMaskTex8x8[8 * 8] = { { 0 } };
static u8 sMaskTex8x32[8 * 32] = { { 0 } };
static u8 sMaskTexLava[32 * 64] = { { 0 } };
static u32* sLavaFloorModifiedTexRaw = NULL;
static u32* sLavaWavyTexRaw = NULL;
static u16 sLavaFloorModifiedTex[LAVA_TEX_SIZE];
static u16 sLavaWavyTex[LAVA_TEX_SIZE];
static u8 hasRegisteredBlendedHook = 0;
static InitChainEntry sInitChain[] = {
ICHAIN_U8(targetMode, 5, ICHAIN_CONTINUE),
ICHAIN_S8(naviEnemyId, 0x0C, ICHAIN_CONTINUE),
@@ -66,6 +85,69 @@ static InitChainEntry sInitChain[] = {
ICHAIN_F32(targetArrowOffset, 8200.0f, ICHAIN_STOP),
};
void BossDodongo_RegisterBlendedLavaTextureUpdate() {
// Not in scene so there is nothing to do
if (gPlayState == NULL || gPlayState->sceneNum != SCENE_DODONGOS_CAVERN_BOSS) {
return;
}
// Free old textures
if (sLavaFloorModifiedTexRaw != NULL) {
free(sLavaFloorModifiedTexRaw);
sLavaFloorModifiedTexRaw = NULL;
}
if (sLavaWavyTexRaw != NULL) {
free(sLavaWavyTexRaw);
sLavaWavyTexRaw = NULL;
}
// Unload original textures to bypass cache result for lookups
ResourceMgr_UnloadOriginalWhenAltExists(sLavaFloorLavaTex);
ResourceMgr_UnloadOriginalWhenAltExists(sLavaFloorRockTex);
ResourceMgr_UnloadOriginalWhenAltExists(gDodongosCavernBossLavaFloorTex);
// When the texture is HD (raw) we need to work with u32 values for RGBA32
// Otherwise the original asset is u16 for RGBA16
if (ResourceMgr_TexIsRaw(gDodongosCavernBossLavaFloorTex)) {
u32* lavaTex = ResourceGetDataByName(sLavaFloorLavaTex);
size_t lavaSize = ResourceGetSizeByName(sLavaFloorLavaTex);
size_t floorSize = ResourceGetSizeByName(gDodongosCavernBossLavaFloorTex);
sLavaFloorModifiedTexRaw = malloc(lavaSize);
sLavaWavyTexRaw = malloc(floorSize);
memcpy(sLavaFloorModifiedTexRaw, lavaTex, lavaSize);
// 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);
}
memcpy(sLavaWavyTexRaw, sLavaFloorModifiedTexRaw, floorSize);
// Register the blended effect for the raw texture
Gfx_RegisterBlendedTexture(gDodongosCavernBossLavaFloorTex, sMaskTexLava, sLavaWavyTexRaw);
} else {
// When KD is dead, just immediately copy the rock texture
if (Flags_GetClear(gPlayState, gPlayState->roomCtx.curRoom.num)) {
u16* rockTex = ResourceGetDataByName(sLavaFloorRockTex);
memcpy(sLavaFloorModifiedTex, rockTex, sizeof(sLavaFloorModifiedTex));
} else {
u16* lavaTex = ResourceGetDataByName(sLavaFloorLavaTex);
memcpy(sLavaFloorModifiedTex, lavaTex, sizeof(sLavaFloorModifiedTex));
}
// Register the blended effect for the non-raw texture
memcpy(sLavaWavyTex, sLavaFloorModifiedTex, sizeof(sLavaWavyTex));
Gfx_RegisterBlendedTexture(gDodongosCavernBossLavaFloorTex, sMaskTexLava, sLavaWavyTex);
}
gfx_texture_cache_clear();
}
void func_808C12C4(u8* arg1, s16 arg2) {
if (arg2[arg1] != 0) {
sMaskTex8x16[arg2 / 2] = 1;
@@ -86,12 +168,51 @@ void func_808C12C4(u8* arg1, s16 arg2) {
}
}
void func_808C1554(void* arg0, void* floorTex, s32 arg2, f32 arg3) {
arg0 = GetResourceDataByNameHandlingMQ(arg0);
floorTex = ResourceGetDataByName(floorTex);
// 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) {
u16 width = ResourceGetTexWidthByName(arg0);
s32 size = ResourceGetTexHeightByName(arg0) * width;
u16* temp_s3 = SEGMENTED_TO_VIRTUAL(arg0);
u16* temp_s1 = SEGMENTED_TO_VIRTUAL(floorTex);
u32* temp_s3 = sLavaWavyTexRaw;
u32* temp_s1 = sLavaFloorModifiedTexRaw;
s32 i;
s32 i2;
u32* sp54 = malloc(size * sizeof(u32)); // Match the size for lava floor tex
s32 temp;
s32 temp2;
// Multiplier is used to try to scale the wavy effect to match the scale of the HD texture
// Applying sqrt(multiplier) to arg3 is to control how many pixels move left/right for the selected row
// Applying to arg2 and M_PI help to space out the wave effect
// It's not perfect but close enough
u16 multiplier = width / LAVA_TEX_WIDTH;
for (i = 0; i < size; i += width) {
temp = sinf((((i / width) + (s32)(((arg2 * multiplier) * 50.0f) / 100.0f)) & (width - 1)) * (M_PI / (16 * multiplier))) * (arg3 * sqrt(multiplier));
for (i2 = 0; i2 < width; i2++) {
sp54[i + ((temp + i2) & (width - 1))] = temp_s1[i + i2];
}
}
for (i = 0; i < width; i++) {
temp = sinf(((i + (s32)(((arg2 * multiplier) * 80.0f) / 100.0f)) & (width - 1)) * (M_PI / (16 * multiplier))) * (arg3 * sqrt(multiplier));
temp *= width;
for (i2 = 0; i2 < size; i2 += width) {
temp2 = (temp + i2) & (size - 1);
temp_s3[i + temp2] = sp54[i + i2];
}
}
free(sp54);
// Need to clear the cache after updating sLavaWavyTexRaw
gfx_texture_cache_clear();
}
// Modified to support CPU modified texture with the resource system
// Used for the original non-raw asset working with u16 values
void func_808C1554(void* arg0, void* floorTex, s32 arg2, f32 arg3) {
u16* temp_s3 = sLavaWavyTex;
u16* temp_s1 = sLavaFloorModifiedTex;
s16 i;
s16 i2;
u16 sp54[2048];
@@ -112,6 +233,9 @@ 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) {
@@ -187,27 +311,21 @@ void BossDodongo_Init(Actor* thisx, PlayState* play) {
Collider_SetJntSph(play, &this->collider, &this->actor, &sJntSphInit, this->items);
if (Flags_GetClear(play, play->roomCtx.curRoom.num)) { // KD is dead
u16* LavaFloorTex = ResourceGetDataByName(gDodongosCavernBossLavaFloorTex);
u16* LavaFloorRockTex = ResourceGetDataByName(sLavaFloorRockTex);
temp_s1_3 = SEGMENTED_TO_VIRTUAL(LavaFloorTex);
temp_s2 = SEGMENTED_TO_VIRTUAL(LavaFloorRockTex);
// SOH [General]
// Applying the "cooled off" lava rock CPU modified texture for re-visiting the scene
// is now handled by BossDodongo_RegisterBlendedLavaTextureUpdate below
Actor_Kill(&this->actor);
Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_DOOR_WARP1, -890.0f, -1523.76f,
-3304.0f, 0, 0, 0, WARP_DUNGEON_CHILD);
Actor_Spawn(&play->actorCtx, play, ACTOR_BG_BREAKWALL, -890.0f, -1523.76f, -3304.0f, 0, 0, 0, 0x6000, true);
Actor_Spawn(&play->actorCtx, play, ACTOR_ITEM_B_HEART, -690.0f, -1523.76f, -3304.0f, 0, 0, 0, 0, true);
for (int i = 0; i < ARRAY_COUNT(sMaskTexLava); i++) {
sMaskTexLava[i] = 1;
}
} else {
for (int i = 0; i < ARRAY_COUNT(sMaskTexLava); i++) {
sMaskTexLava[i] = 0;
}
}
this->actor.flags &= ~ACTOR_FLAG_TARGETABLE;
// #region SOH [General]
// Init mask values for all blended textures
for (int i = 0; i < ARRAY_COUNT(sMaskTex8x16); i++) {
sMaskTex8x16[i] = 0;
}
@@ -223,6 +341,12 @@ 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);
Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_017210, sMaskTex8x32, NULL);
Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_015D90, sMaskTex16x16, NULL);
@@ -234,10 +358,14 @@ void BossDodongo_Init(Actor* thisx, PlayState* play) {
Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_016990, sMaskTex32x16, NULL);
Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_016E10, sMaskTex32x16, NULL);
// OTRTODO: This is causing OOB memory reads with HD assets
// commenting this out means the lava will stay lava even after beating king d
//
// Gfx_RegisterBlendedTexture(gDodongosCavernBossLavaFloorTex, sMaskTexLava, sLavaFloorRockTex);
BossDodongo_RegisterBlendedLavaTextureUpdate();
// Register alt listener to update the blended lava for the replacement texture based on alt path
if (!hasRegisteredBlendedHook) {
GameInteractor_RegisterOnAssetAltChange(BossDodongo_RegisterBlendedLavaTextureUpdate);
hasRegisteredBlendedHook = 1;
}
// #endregion
}
void BossDodongo_Destroy(Actor* thisx, PlayState* play) {
@@ -1014,21 +1142,64 @@ void BossDodongo_Update(Actor* thisx, PlayState* play2) {
}
}
// TODO The lave floor bubbles with an effect that modifies the texture. This needs to be recreated shader-side.
//func_808C1554(gDodongosCavernBossLavaFloorTex, sLavaFloorLavaTex, this->unk_19E, this->unk_224);
// The lava bubbles with a wavy effect as a CPU modified texture
// This has been done by maintaining copied/modified texture values in the actor code
// The "cooling off" effect for the lava is pre-applied to the lava texture before applying
// the wavy effect. Since this is two effects and closely related to the actor, I've opted
// to handle them here rather than as a shader effect.
//
// Apply the corresponding wavy effect based on the texture being raw or not
if (ResourceMgr_TexIsRaw(gDodongosCavernBossLavaFloorTex)) {
func_808C1554_Raw(gDodongosCavernBossLavaFloorTex, sLavaFloorLavaTex, this->unk_19E, this->unk_224);
} else {
func_808C1554(gDodongosCavernBossLavaFloorTex, sLavaFloorLavaTex, this->unk_19E, this->unk_224);
}
}
// Apply the "cooling off" effect for the lava
if (this->unk_1C6 != 0) {
u16* ptr1 = ResourceGetDataByName(sLavaFloorLavaTex);
u16* ptr2 = ResourceGetDataByName(sLavaFloorRockTex);
s16 i2;
// Similar to above, the cooling off effect is a CPU modified texture effect
// Apply corresponding to the texture being raw or not
if (ResourceMgr_TexIsRaw(sLavaFloorRockTex)) {
u32* ptr1 = sLavaFloorModifiedTexRaw;
u32* ptr2 = ResourceGetDataByName(sLavaFloorRockTex);
u16 width = ResourceGetTexWidthByName(sLavaFloorRockTex);
u16 height = ResourceGetTexHeightByName(sLavaFloorRockTex);
s16 i2;
for (i2 = 0; i2 < 20; i2++) {
s16 new_var = this->unk_1C2 & 0x7FF;
// Get the scale based on the original texture size
u16 widthScale = width / LAVA_TEX_WIDTH;
u16 heightScale = height / LAVA_TEX_HEIGHT;
u32 size = width * height;
sMaskTexLava[new_var] = 1;
this->unk_1C2 += 37;
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];
}
}
this->unk_1C2 += 37;
}
} else {
u16* ptr1 = sLavaFloorModifiedTex;
u16* ptr2 = ResourceGetDataByName(sLavaFloorRockTex);
s16 i2;
for (i2 = 0; i2 < 20; i2++) {
s16 new_var = this->unk_1C2 & 0x7FF;
ptr1[new_var] = ptr2[new_var];
this->unk_1C2 += 37;
}
}
Math_SmoothStepToF(&this->unk_224, 0.0f, 1.0f, 0.01f, 0.0f);
}
+14 -2
View File
@@ -88,6 +88,7 @@ Gfx gKeyTreasureChestChestFrontDL[128] = {0};
Gfx gChristmasRedTreasureChestChestFrontDL[128] = {0};
Gfx gChristmasGreenTreasureChestChestFrontDL[128] = {0};
u8 hasCreatedRandoChestTextures = 0;
u8 hasCustomChestDLs = 0;
u8 hasChristmasChestTexturesAvailable = 0;
void EnBox_SetupAction(EnBox* this, EnBoxActionFunc actionFunc) {
@@ -690,7 +691,7 @@ void EnBox_UpdateSizeAndTexture(EnBox* this, PlayState* play) {
}
// Change texture
if (!isVanilla && (csmc == CSMC_BOTH || csmc == CSMC_TEXTURE)) {
if (!isVanilla && hasCreatedRandoChestTextures && !hasCustomChestDLs && (csmc == CSMC_BOTH || csmc == CSMC_TEXTURE)) {
switch (getItemCategory) {
case ITEM_CATEGORY_MAJOR:
this->boxBodyDL = gGoldTreasureChestChestFrontDL;
@@ -725,7 +726,7 @@ void EnBox_UpdateSizeAndTexture(EnBox* this, PlayState* play) {
}
}
if (CVarGetInteger("gLetItSnow", 0) && hasChristmasChestTexturesAvailable) {
if (CVarGetInteger("gLetItSnow", 0) && hasChristmasChestTexturesAvailable && hasCreatedRandoChestTextures && !hasCustomChestDLs) {
if (this->dyna.actor.scale.x == 0.01f) {
this->boxBodyDL = gChristmasRedTreasureChestChestFrontDL;
this->boxLidDL = gChristmasRedTreasureChestChestSideAndLidDL;
@@ -767,7 +768,18 @@ void EnBox_UpdateSizeAndTexture(EnBox* this, PlayState* play) {
}
void EnBox_CreateExtraChestTextures() {
// Don't patch textures for custom chest models, as they do not import textures the exact same way as vanilla chests
// OTRTODO: Make it so model packs can provide a unique DL per chest type, instead of us copying the brown chest and attempting to patch
if (ResourceMgr_FileIsCustomByName(gTreasureChestChestFrontDL) ||
ResourceMgr_FileIsCustomByName(gTreasureChestChestSideAndLidDL)) {
hasCustomChestDLs = 1;
return;
}
hasCustomChestDLs = 0;
if (hasCreatedRandoChestTextures) return;
Gfx gTreasureChestChestTextures[] = {
gsDPSetTextureImage(G_IM_FMT_RGBA, G_IM_SIZ_16b, 1, gSkullTreasureChestFrontTex),
gsDPSetTextureImage(G_IM_FMT_RGBA, G_IM_SIZ_16b, 1, gSkullTreasureChestSideAndTopTex),
+41 -3
View File
@@ -95,6 +95,26 @@ static AnimationInfo sAnimationInfo[] = {
{ &gDaruniaDancingEndAnim, 1.0f, 0.0f, -1.0f, ANIMMODE_ONCE, -6.0f },
};
// #region SOH [Enhancement] Only animations too fast need to be slowed down, otherwise not touched
static AnimationInfo sAnimationInfoFix[] = {
{ NULL },
{ NULL },
{ NULL },
{ NULL },
{ NULL },
{ NULL },
{ NULL },
{ &gDaruniaDancingLoop1Anim, 0.78f, 0.0f, -1.0f, ANIMMODE_ONCE, -10.0f }, //
{ &gDaruniaDancingLoop1Anim, 0.77f, 0.0f, -1.0f, ANIMMODE_ONCE, 0.0f }, // hop
{ &gDaruniaDancingLoop2Anim, 0.78f, 0.0f, -1.0f, ANIMMODE_ONCE, 0.0f }, // from hop to spin
{ &gDaruniaDancingLoop3Anim, 0.77f, 0.0f, -1.0f, ANIMMODE_ONCE, 0.0f }, // spin
{ NULL },
{ NULL },
{ &gDaruniaDancingLoop4Anim, 0.78f, 0.0f, -1.0f, ANIMMODE_ONCE, 0.0f }, // from spin to hop
{ NULL },
};
// #endregion
void EnDu_SetupAction(EnDu* this, EnDuActionFunc actionFunc) {
this->actionFunc = actionFunc;
}
@@ -255,7 +275,13 @@ void func_809FE040(EnDu* this) {
if (this->unk_1E6 >= 8) {
this->unk_1E6 = 0;
}
Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, animationIndices[this->unk_1E6]);
// #region SOH[Enhancement]
if (CVarGetInteger("gEnhancements.FixDaruniaDanceSpeed", 1)) {
Animation_ChangeByInfo(&this->skelAnime, sAnimationInfoFix, animationIndices[this->unk_1E6]);
// #endregion
} else {
Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, animationIndices[this->unk_1E6]);
}
}
}
@@ -271,7 +297,13 @@ void func_809FE104(EnDu* this) {
if (Animation_OnFrame(&this->skelAnime, this->skelAnime.endFrame)) {
this->unk_1E6++;
if (this->unk_1E6 < 4) {
Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, animationIndices[this->unk_1E6]);
// #region SOH[Enhancement]
if (CVarGetInteger("gEnhancements.FixDaruniaDanceSpeed", 1) && this->unk_1E6 <= 1) {
Animation_ChangeByInfo(&this->skelAnime, sAnimationInfoFix, animationIndices[this->unk_1E6]);
// #endregion
} else {
Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, animationIndices[this->unk_1E6]);
}
}
}
}
@@ -465,7 +497,13 @@ void func_809FE890(EnDu* this, PlayState* play) {
}
if (csAction->action == 7 || csAction->action == 8) {
this->unk_1E6 = 0;
Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, ENDU_ANIM_7);
// #region SOH[Enhancement]
if (CVarGetInteger("gEnhancements.FixDaruniaDanceSpeed", 1)) {
Animation_ChangeByInfo(&this->skelAnime, sAnimationInfoFix, ENDU_ANIM_7);
// #endregion
} else {
Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, ENDU_ANIM_7);
}
}
this->unk_1EA = csAction->action;
if (this->unk_1EA == 7) {
@@ -211,6 +211,13 @@ void EnInsect_Init(Actor* thisx, PlayState* play2) {
func_80A7D39C(this);
// For bugs that aren't linked to a soil patch, we remove the "short lived" flag to prevent them from despawning
// And exit early to not increment the "bugs dropped count"
if (CVarGetInteger("gNoBugsDespawn", 0) && this->soilActor == NULL) {
this->unk_314 &= ~4;
return;
}
D_80A7DEB8++;
} else {
rand = Rand_ZeroOne();
@@ -394,9 +401,6 @@ void func_80A7CAD0(EnInsect* this, PlayState* play) {
}
void func_80A7CBC8(EnInsect* this) {
if (CVarGetInteger("gNoBugsDespawn", 0) != 0) {
return;
}
this->unk_31A = 60;
func_80A7BF58(this);
this->skelAnime.playSpeed = 1.9f;
@@ -313,7 +313,9 @@ void EnSth_GiveReward(EnSth* this, PlayState* play) {
this->actor.parent = NULL;
EnSth_SetupAction(this, EnSth_RewardObtainedTalk);
gSaveContext.eventChkInf[EVENTCHKINF_SKULLTULA_REWARD_INDEX] |= this->eventFlag;
GameInteractor_ExecuteOnFlagSet(FLAG_EVENT_CHECK_INF, (EVENTCHKINF_SKULLTULA_REWARD_INDEX << 4) + sEventFlagsShift[this->actor.params]);
if (this->eventFlag != 0) {
GameInteractor_ExecuteOnFlagSet(FLAG_EVENT_CHECK_INF, (EVENTCHKINF_SKULLTULA_REWARD_INDEX << 4) + sEventFlagsShift[this->actor.params]);
}
} else {
EnSth_GivePlayerItem(this, play);
}
+23 -31
View File
@@ -408,7 +408,7 @@ s32 EnTk_ChooseReward(EnTk* this) {
f32 luck;
s32 reward;
if ((IS_RANDO || CVarGetInteger("gDampeWin", 0)) && !Flags_GetCollectible(gPlayState, 0x1F) && this->heartPieceSpawned == 0) {
if ((IS_RANDO || CVarGetInteger("gDampeWin", 0)) && !Flags_GetCollectible(gPlayState, COLLECTFLAG_GRAVEDIGGING_HEART_PIECE) && this->heartPieceSpawned == 0) {
return 3;
}
@@ -624,10 +624,8 @@ void EnTk_Dig(EnTk* this, PlayState* play) {
this->currentReward = EnTk_ChooseReward(this);
// merging in dampe tour fix seems messy, so i'm just wrapping this whole thing
// in an n64dd check for now
if (IS_RANDO || CVarGetInteger("gDampeWin", 0)) {
if (this->currentReward == 3) {
if (this->currentReward == 3) {
if (IS_RANDO || CVarGetInteger("gDampeWin", 0)) {
/*
* Upgrade the purple rupee reward to the heart piece if this
* is the first grand prize dig.
@@ -635,37 +633,31 @@ void EnTk_Dig(EnTk* this, PlayState* play) {
if (!Flags_GetItemGetInf(ITEMGETINF_1C) && !(IS_RANDO || CVarGetInteger("gDampeWin", 0))) {
Flags_SetItemGetInf(ITEMGETINF_1C);
this->currentReward = 4;
} else if ((IS_RANDO || CVarGetInteger("gDampeWin", 0)) && !Flags_GetCollectible(gPlayState, 0x1F) && this->heartPieceSpawned == 0) {
} else if ((IS_RANDO || CVarGetInteger("gDampeWin", 0)) && !Flags_GetCollectible(gPlayState, COLLECTFLAG_GRAVEDIGGING_HEART_PIECE) && this->heartPieceSpawned == 0) {
this->currentReward = 4;
}
}
if ((IS_RANDO || CVarGetInteger("gDampeWin", 0)) && this->currentReward == 4) {
Actor_Spawn(&play->actorCtx, play, ACTOR_EN_ITEM00, rewardPos.x, rewardPos.y, rewardPos.z, 0,
0, 0, 0x1F06, true);
this->heartPieceSpawned = 1;
} else {
Item_DropCollectible(play, &rewardPos, rewardParams[this->currentReward]);
/*
* Upgrade the purple rupee reward to the heart piece if this
* is the first grand prize dig.
*/
// If vanilla itemGetInf flag is not set, it's impossible for the new flag to be set, so return true.
// Otherwise if the gGravediggingTourFix is enabled and the new flag hasn't been set, return true.
// If true, spawn the heart piece and set the vanilla itemGetInf flag and new temp clear flag.
if (!heartPieceSpawned &&
(!(gSaveContext.itemGetInf[1] & ITEMGETINFFLAG_GRAVEDIGGING_HEART_PIECE) ||
CVarGetInteger("gGravediggingTourFix", 0) &&
!Flags_GetCollectible(play, COLLECTFLAG_GRAVEDIGGING_HEART_PIECE))) {
this->currentReward = 4;
gSaveContext.itemGetInf[1] |= ITEMGETINFFLAG_GRAVEDIGGING_HEART_PIECE;
heartPieceSpawned = true;
}
}
if (IS_RANDO && this->currentReward == 4) {
Actor_Spawn(&play->actorCtx, play, ACTOR_EN_ITEM00, rewardPos.x, rewardPos.y, rewardPos.z, 0, 0, 0, 0x1906, true);
this->heartPieceSpawned = 1;
} else {
if (this->currentReward == 3) {
/*
* Upgrade the purple rupee reward to the heart piece if this
* is the first grand prize dig.
*/
// If vanilla itemGetInf flag is not set, it's impossible for the new flag to be set, so return true.
// Otherwise if the gGravediggingTourFix is enabled and the new flag hasn't been set, return true.
// If true, spawn the heart piece and set the vanilla itemGetInf flag and new temp clear flag.
if (!heartPieceSpawned &&
(!(gSaveContext.itemGetInf[1] & ITEMGETINFFLAG_GRAVEDIGGING_HEART_PIECE) ||
CVarGetInteger("gGravediggingTourFix", 0) &&
!Flags_GetCollectible(play, COLLECTFLAG_GRAVEDIGGING_HEART_PIECE))) {
this->currentReward = 4;
gSaveContext.itemGetInf[1] |= ITEMGETINFFLAG_GRAVEDIGGING_HEART_PIECE;
heartPieceSpawned = true;
}
}
EnItem00* reward = Item_DropCollectible(play, &rewardPos, rewardParams[this->currentReward]);
if (this->currentReward == 4) {
reward->collectibleFlag = COLLECTFLAG_GRAVEDIGGING_HEART_PIECE;
@@ -10101,7 +10101,8 @@ void func_80847BA0(PlayState* play, Player* this) {
D_808535F0 = func_80041DB8(&play->colCtx, this->actor.wallPoly, this->actor.wallBgId);
if (CVarGetInteger("gFixVineFall", 0)) {
// conflicts arise from these two being enabled at once, and with ClimbEverything on, FixVineFall is redundant anyway
if (CVarGetInteger("gFixVineFall", 0) && !CVarGetInteger("gClimbEverything", 0)) {
/* This fixes the "started climbing a wall and then immediately fell off" bug.
* The main idea is if a climbing wall is detected, double-check that it will
* still be valid once climbing begins by doing a second raycast with a small
@@ -15169,6 +15170,10 @@ void func_80852C50(PlayState* play, Player* this, CsCmdActorAction* arg2) {
sp24 = D_808547C4[this->unk_446];
func_80852B4C(play, this, linkCsAction, &D_80854E50[ABS(sp24)]);
if (CVarGetInteger("gFixEyesOpenWhileSleeping", 0) && (play->csCtx.linkAction->action == 28 || play->csCtx.linkAction->action == 29)) {
this->skelAnime.jointTable[22].x = 8;
}
}
void func_80852E14(Player* this, PlayState* play) {
@@ -1,6 +1,7 @@
#include "file_choose.h"
#include <string.h>
#include <stdio.h>
#include "textures/title_static/title_static.h"
#include "textures/parameter_static/parameter_static.h"
@@ -1024,7 +1025,7 @@ void FileChoose_UpdateRandomizer() {
return;
}
if (!SpoilerFileExists(CVarGetString("gSpoilerLog", ""))) {
if (!SpoilerFileExists(CVarGetString("gSpoilerLog", "")) && !CVarGetInteger("gRandomizerDontGenerateSpoiler", 0)) {
CVarSetString("gSpoilerLog", "");
fileSelectSpoilerFileLoaded = false;
}
@@ -1051,6 +1052,10 @@ void FileChoose_UpdateRandomizer() {
Randomizer_LoadMasterQuestDungeons(fileLoc);
Randomizer_LoadEntranceOverrides(fileLoc, silent);
fileSelectSpoilerFileLoaded = true;
if (SpoilerFileExists(CVarGetString("gSpoilerLog", "")) && CVarGetInteger("gRandomizerDontGenerateSpoiler", 0)) {
remove(fileLoc);
}
}
}
@@ -1071,6 +1076,7 @@ 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)) {
@@ -1261,6 +1267,7 @@ 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))) {
@@ -3686,4 +3693,7 @@ void FileChoose_Init(GameState* thisx) {
Font_LoadOrderedFont(&this->font);
Audio_QueueSeqCmd(0xF << 28 | SEQ_PLAYER_BGM_MAIN << 24 | 0xA);
func_800F5E18(SEQ_PLAYER_BGM_MAIN, NA_BGM_FILE_SELECT, 0, 7, 1);
// Originally this was only set when transitioning from the title screen, but gSkipLogoTitle skips that process so we're ensuring it's set here
gSaveContext.gameMode = GAMEMODE_FILE_SELECT;
}
@@ -1054,8 +1054,9 @@ void KaleidoScope_UpdateItemEquip(PlayState* play) {
//Fix for Equip Dupe
if (pauseCtx->equipTargetItem == ITEM_BOW) {
if ((gSaveContext.equips.buttonItems[otherButtonIndex] >= ITEM_BOW_ARROW_FIRE) &&
(gSaveContext.equips.buttonItems[otherButtonIndex] <= ITEM_BOW_ARROW_LIGHT)) {
if (gSaveContext.equips.buttonItems[otherButtonIndex] >= ITEM_BOW_ARROW_FIRE &&
gSaveContext.equips.buttonItems[otherButtonIndex] <= ITEM_BOW_ARROW_LIGHT &&
!CVarGetInteger("gSeparateArrows", 0)) {
gSaveContext.equips.buttonItems[otherButtonIndex] = gSaveContext.equips.buttonItems[targetButtonIndex];
gSaveContext.equips.cButtonSlots[otherSlotIndex] = gSaveContext.equips.cButtonSlots[pauseCtx->equipTargetCBtn];
Interface_LoadItemIcon2(play, otherButtonIndex);
@@ -4290,6 +4290,8 @@ void KaleidoScope_Update(PlayState* play)
if (IS_RANDO && Randomizer_GetSettingValue(RSK_SHUFFLE_ENTRANCES)) {
Grotto_ForceGrottoReturn();
}
// Reset frame counter to prevent autosave on respawn
play->gameplayFrames = 0;
gSaveContext.nextTransitionType = 2;
gSaveContext.health = CVarGetInteger("gFullHealthSpawn", 0) ? gSaveContext.healthCapacity : 0x30;
Audio_QueueSeqCmd(0xF << 28 | SEQ_PLAYER_BGM_MAIN << 24 | 0xA);