Merge branch 'develop-dewey' into merge-dewey

This commit is contained in:
serprex
2026-10-18 21:20:57 +00:00
53 changed files with 644 additions and 373 deletions
+1 -5
View File
@@ -82,11 +82,7 @@ if (WIN32)
endif()
if(NOT DEFINED VCPKG_ROOT)
if(NOT DEFINED ENV{VCPKG_ROOT})
set(VCPKG_ROOT ${VCPKG_FALLBACK_ROOT})
else()
set(VCPKG_ROOT $ENV{VCPKG_ROOT})
endif()
set(VCPKG_ROOT ${VCPKG_FALLBACK_ROOT})
endif()
# Installs a new copy of Vcpkg or updates an existing one
+2 -1
View File
@@ -79,7 +79,8 @@ If you want to playtest a continuous integration build, you can find them at the
* [Windows](https://nightly.link/HarbourMasters/Shipwright/workflows/generate-builds/develop/soh-windows.zip)
* [macOS](https://nightly.link/HarbourMasters/Shipwright/workflows/generate-builds/develop/soh-mac.zip)
* [Linux](https://nightly.link/HarbourMasters/Shipwright/workflows/generate-builds/develop/soh-linux.zip)
* [Linux X86_64](https://nightly.link/HarbourMasters/Shipwright/workflows/generate-builds/develop/soh-linux-x86_64.zip)
* [Linux ARM](https://nightly.link/HarbourMasters/Shipwright/workflows/generate-builds/develop/soh-linux-aarch64.zip)
### Further Reading
More detailed documentation can be found in the 'docs' directory, including the aforementioned [building instructions](docs/BUILDING.md).
+35 -9
View File
@@ -1171,6 +1171,38 @@
"rewards on slider does not change."
]
},
"wincon": {
"name": "Win Condition",
"description": [
"Defeat Ganon - Defeat Ganon to complete the game.\n",
"\n",
"Anywhere - Collect the Triforce to complete the game. It can appear anywhere in the world.\n",
"\n",
"Trigger - These settings put the completing the game on a trigger, ",
"playing the credits when the requirements are met:\n",
"- Stones: Obtain the specified amount of Spiritual Stones.\n",
"- Medallions: Obtain the specified amount of medallions.\n",
"- Dungeon rewards: Obtain the specified total sum of Spiritual Stones or medallions.\n",
"- Dungeons: Complete the specified amount of dungeons. Dungeons are considered complete after stepping into ",
"the blue warp after the boss.\n",
"- Tokens: Obtain the specified amount of Skulltula tokens.\n",
"- Triforce Pieces: Obtain the specified number of Triforce Pieces."
]
},
"wincon_options": {
"name": "Win Condition Options",
"description": [
"Standard Rewards - Greg does not change logic, Greg does not help obtain Ganon's Soul, max ",
"number of rewards on slider does not change.\n",
"\n",
"Greg as Reward - Greg does change logic (can be part of expected path for obtaining ",
"Ganon's Soul), Greg helps obtain Ganon's Soul, max number of rewards on slider increases by 1 to ",
"account for Greg.\n",
"\n",
"Greg as Wildcard - Greg does not change logic, Greg helps obtain Ganon's Soul, max number of ",
"rewards on slider does not change."
]
},
"big_poe_count": {
"name": "Big Poe Target Count",
"description": "The Poe collector will give a reward for turning in this many Big Poes."
@@ -1568,12 +1600,6 @@
"ganons_soul_triforce_count": {
"name": "Ganon's Soul Triforce Piece Count"
},
"wincon": {
"name": "Win Condition"
},
"wincon_options": {
"name": "Win Condition Reward Options"
},
"wincon_medallion_count": {
"name": "Win Condition Medallion Count"
},
@@ -2303,7 +2329,7 @@
},
"pass_guards_with_nothing": {
"name": "Sneak Past Moving Gerudo Guards with No Items",
"description": "The logic normally guarantees Bow or Hookshot to stun them from a distance, but every moving guard can be passed with basic movement and AI manipulation"
"description": "The logic normally guarantees Bow or Hookshot to stun them from a distance, but every moving guard can be passed with basic movement and AI manipulation. This trick also enables reading the sign outside GTG without Gerudo card, which is barely possible."
},
"gf_wasteland_gate_sidehop_skip": {
"name": "Gerudo's Fortress Gate Skip with Sidehop",
@@ -2677,7 +2703,7 @@
},
"water_dragon_jump_dive": {
"name": "Water Temple Dragon Statue Jump Dive",
"description": "If you come into the dragon statue room from the serpent river, you can sidehop down from above and get into the tunnel without needing either Iron Boots or a Scale. Alternativly, you can do this while equipping irons in midair to get into the tunnel without swim. This trick applies to both Vanilla and Master Quest. In Vanilla, you must shoot the switch from above with the Bow, and then quickly get through the tunnel before the gate closes."
"description": "If you come into the dragon statue room from the serpent river, you can sidehop down from above and get into the tunnel without needing either Iron Boots or a Scale. Alternatively, you can do this while equipping irons in midair to get into the tunnel without swim. This trick applies to both Vanilla and Master Quest. In Vanilla, you must shoot the switch from above with the Bow, and then quickly get through the tunnel before the gate closes."
},
"water_adult_dragon": {
"name": "Water Temple Dragon Statue Switch from Above the Water as Adult",
@@ -2913,7 +2939,7 @@
},
"gtg_mq_with_hookshot": {
"name": "Gerudo Training Ground MQ Left Side Silver Rupees with Hookshot",
"description": "The highest Silver Rupee can be obtained by hookshooting the target and then immediately jumpslashing toward the Rupee."
"description": "The highest Silver Rupee can be obtained by hookshotting the target and then immediately jumpslashing toward the Rupee."
},
"gtg_mq_without_hookshot": {
"name": "Gerudo Training Ground MQ Left Side Silver Rupees without Hookshot",
+3 -2
View File
@@ -594,8 +594,9 @@ typedef enum {
/* 0x7A */ GID_SONG_TIME,
/* 0x7B */ GID_SONG_STORM,
/* 0x7C */ GID_TRIFORCE_PIECE,
/* 0x7D */ GID_FISHING_POLE,
/* 0x7E */ GID_MAXIMUM
/* 0x7D */ GID_TRIFORCE,
/* 0x7E */ GID_FISHING_POLE,
/* 0x7F */ GID_MAXIMUM
} GetItemDrawID;
@@ -1,5 +1,6 @@
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/ShipInit.hpp"
#include "soh/Enhancements/randomizer/SeedContext.h"
extern "C" {
#include "z64save.h"
@@ -23,7 +24,9 @@ static void RegisterBossDefeatTimestamps() {
BOSS_DEFEAT_TIMESTAMP(ACTOR_BOSS_GANON, TIMESTAMP_DEFEAT_GANONDORF);
COND_ID_HOOK(OnBossDefeat, ACTOR_BOSS_GANON2, true, [](void* refActor) {
gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] = static_cast<u32>(GAMEPLAYSTAT_TOTAL_TIME);
gSaveContext.ship.stats.gameComplete = true;
if (!IS_RANDO || RAND_GET_OPTION(RSK_WINCON).Is(RO_WINCON_DEFEAT_GANON)) {
gSaveContext.ship.stats.gameComplete = true;
}
});
}
@@ -88,11 +88,22 @@ static void RegisterBombchuBowlingNoBigCucco() {
}
static void RegisterBombchuBowlingAmmo() {
COND_VB_SHOULD(VB_SET_BOMBCHU_BOWLING_AMMO,
CVAR_BOWLING_VALUE && (CVAR_BOWLING_AMMO_VALUE != CUCCO_BOWLING_AMMO_DEFAULT), {
gPlayState->bombchuBowlingStatus = CVAR_BOWLING_AMMO_VALUE;
*should = false;
});
static s32 prevAmmoValue = CUCCO_BOWLING_AMMO_DEFAULT;
s32 nextAmmoValue = CVAR_BOWLING_VALUE ? CVAR_BOWLING_AMMO_VALUE : CUCCO_BOWLING_AMMO_DEFAULT;
if ((prevAmmoValue != nextAmmoValue) && (gPlayState->bombchuBowlingStatus > 0)) {
gPlayState->bombchuBowlingStatus += nextAmmoValue - prevAmmoValue;
if (gPlayState->bombchuBowlingStatus <= 0) {
gPlayState->bombchuBowlingStatus = -1;
}
prevAmmoValue = nextAmmoValue;
}
COND_VB_SHOULD(VB_SET_BOMBCHU_BOWLING_AMMO, nextAmmoValue != CUCCO_BOWLING_AMMO_DEFAULT, {
prevAmmoValue = CVAR_BOWLING_AMMO_VALUE;
gPlayState->bombchuBowlingStatus = prevAmmoValue;
*should = false;
});
}
static void RegisterBombchuBowlingFirstPrize() {
+3
View File
@@ -64,6 +64,9 @@ static void RegisterSpeedModifiers() {
COND_VB_SHOULD(VB_PLAYER_LIMIT_JUMP_SPEED, speedModifierActive || bunnyHoodActive, {
Player* player = va_arg(args, Player*);
if (ShouldAmplifyJump(player)) {
f32 maxSpeed =
(R_RUN_SPEED_LIMIT / 100.0f) * Ship_GetBunnyHoodJumpFactor(player) * GetSpeedModifierJumpFactor();
player->linearVelocity = CLAMP(player->linearVelocity, -maxSpeed, maxSpeed);
*should = false;
}
});
@@ -2,7 +2,9 @@
#include <libultraship/bridge/consolevariablebridge.h>
#include "soh/Enhancements/custom-message/CustomMessageTypes.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/randomizer/SeedContext.h"
#include "soh/ShipInit.hpp"
extern "C" {
@@ -29,7 +31,13 @@ void RegisterGanonIntros() {
}
});
COND_VB_SHOULD(VB_PLAY_GANONDORF_INTRO_CS, CVAR_VALUE || IS_RANDO || IS_BOSS_RUSH, { *should = false; });
COND_VB_SHOULD(VB_PLAY_GANONDORF_INTRO_CS, CVAR_VALUE || IS_RANDO || IS_BOSS_RUSH, {
va_arg(args, void*);
u16 textId = va_arg(args, int);
if (textId != TEXT_GANONDORF || !IS_RANDO || !RAND_GET_OPTION(RSK_GANONDORF_HINT)) {
*should = false;
}
});
// Skip Ganon rising from the rubble, transforming and landing. Skip to knocking sword away.
COND_VB_SHOULD(VB_PLAY_GANON_INTRO_CS, CVAR_VALUE, {
@@ -8,6 +8,7 @@ extern "C" {
s32 EnKz_FollowPath(EnKz* enKz, PlayState* play);
s32 EnKz_SetMovedPos(EnKz* enKz, PlayState* play);
void EnKz_Mweep(EnKz* enKz, PlayState* play);
void EnKz_SetupMweep(EnKz* enKz, PlayState* play);
void EnKz_Wait(EnKz* enKz, PlayState* play);
}
@@ -35,7 +36,11 @@ static void EnKz_MweepNoCutscene(EnKz* enKz, PlayState* play) {
static void EnKz_InterceptMweep(void* actorPtr) {
EnKz* enKz = (EnKz*)actorPtr;
if (enKz->actionFunc != EnKz_SetupMweep) {
if (enKz->actionFunc == EnKz_Mweep) {
Play_ChangeCameraStatus(gPlayState, enKz->gameplayCamera, CAM_STAT_ACTIVE);
Play_ClearCamera(gPlayState, enKz->cutsceneCamera);
Player_SetCsActionWithHaltedActors(gPlayState, &enKz->actor, 7);
} else if (enKz->actionFunc != EnKz_SetupMweep) {
return;
}
@@ -468,7 +468,7 @@ void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_li
*should = false;
// We should still come out of a softlock if the setting was changed during forced dialogue
if (GET_PLAYER(gPlayState)->csAction != 7) {
if (GET_PLAYER(gPlayState)->csAction == 8) {
Player_SetCsAction(gPlayState, NULL, 7);
}
}
+24 -2
View File
@@ -61,6 +61,26 @@ static Gfx* LoadCustomGfx(const char* path) {
return ResourceMgr_LoadGfxByName(path);
}
// Shooting Gallery being weird giving bow and then load slingshot model when child Link
// This function is used to determine if bow or slingshot model should be loaded
static bool UsesBowModel(Player* player) {
if (CVarGetInteger(CVAR_ENHANCEMENT("BowSlingshotAmmoFix"), 0) ||
CVarGetInteger(CVAR_ENHANCEMENT("EquipmentAlwaysVisible"), 0)) {
return !Player_HoldsSlingshot(player);
}
return LINK_IS_ADULT;
}
// Detects cutscenes that swap the left hand DL without updating leftHandType.
static bool IsLeftHandSwappedByCutscene(Player* player) {
Gfx** dLists = player->leftHandDLists;
if (dLists == &gPlayerLeftHandOpenDLs[0] || dLists == &gPlayerLeftHandOpenDLs[1] ||
dLists == &gPlayerLeftHandClosedDLs[0] || dLists == &gPlayerLeftHandClosedDLs[1]) {
return true;
}
return dLists == &gPlayerLeftHandBgsDLs[1] && player->leftHandType != PLAYER_MODELTYPE_LH_BGS;
}
static u8 sLastValidSwordEquip = EQUIP_VALUE_SWORD_NONE;
static u8 GetEquippedSwordValue(PlayState* play) {
@@ -250,6 +270,8 @@ static void RegisterCustomEquipment() {
}
break;
}
if (IsLeftHandSwappedByCutscene(player))
break;
switch ((u8)player->leftHandType) {
case PLAYER_MODELTYPE_LH_SWORD: {
if (player == GET_PLAYER(play)) {
@@ -317,7 +339,7 @@ static void RegisterCustomEquipment() {
case PLAYER_MODELTYPE_RH_BOW_SLINGSHOT_2:
fpsHand = isAdult ? gCustomAdultFPSHandDL : gCustomChildFPSHandDL;
fpsWeapon =
Player_HoldsBow(player)
UsesBowModel(player)
? ResolveCustomChain({ gCustomFPSBowDL, gCustomBowDL, nullptr })
: ResolveCustomChain({ gCustomFPSSlingshotDL, gCustomSlingshotDL, nullptr });
break;
@@ -391,7 +413,7 @@ static void RegisterCustomEquipment() {
break;
case PLAYER_MODELTYPE_RH_BOW_SLINGSHOT:
case PLAYER_MODELTYPE_RH_BOW_SLINGSHOT_2:
customDL = Player_HoldsBow(player) ? gCustomBowDL : gCustomSlingshotDL;
customDL = UsesBowModel(player) ? gCustomBowDL : gCustomSlingshotDL;
break;
case PLAYER_MODELTYPE_RH_HOOKSHOT:
customDL = (player->heldItemAction == PLAYER_IA_HOOKSHOT) ? gCustomHookshotDL
@@ -436,6 +436,34 @@ std::string decodeNTSCPlayerNameChar(int code) {
return ret;
}
char decodePlayerNameChar(uint8_t code, uint8_t language) {
if (language == NAME_LANGUAGE_PAL) {
return z2ASCII(code);
}
const std::string decoded = decodeNTSCPlayerNameChar(code);
return (decoded.length() == 1 && decoded[0] >= 32 && decoded[0] <= 126) ? decoded[0] : ' ';
}
uint8_t encodePlayerNameChar(char c, uint8_t language) {
const bool pal = language == NAME_LANGUAGE_PAL;
if (c >= '0' && c <= '9') {
return c - '0';
}
if (c >= 'A' && c <= 'Z') {
return (pal ? 10 : 171) + (c - 'A');
}
if (c >= 'a' && c <= 'z') {
return (pal ? 36 : 197) + (c - 'a');
}
if (c == '-') {
return pal ? 0x3F : 0xE4;
}
if (c == '.') {
return pal ? 0x40 : 0xEA;
}
return pal ? 0x3E : 0xDF;
}
enum MagicLevel { MAGIC_LEVEL_NONE, MAGIC_LEVEL_SINGLE, MAGIC_LEVEL_DOUBLE };
std::map<int8_t, const char*> magicLevelMap = {
@@ -528,13 +556,8 @@ void DrawGeneralTab() {
// Convert current name to ASCII for display
if (!nameInputInitialized || ImGui::IsWindowFocused()) {
for (int i = 0; i < 8; i++) {
if (gSaveContext.ship.filenameLanguage == NAME_LANGUAGE_PAL) {
playerNameInput[i] = z2ASCII(gSaveContext.playerName[i]);
} else {
const std::string decoded = decodeNTSCPlayerNameChar(gSaveContext.playerName[i]);
playerNameInput[i] =
(decoded.length() == 1 && decoded[0] >= 32 && decoded[0] <= 126) ? decoded[0] : ' ';
}
playerNameInput[i] =
decodePlayerNameChar(gSaveContext.playerName[i], gSaveContext.ship.filenameLanguage);
}
playerNameInput[8] = '\0';
nameInputInitialized = true;
@@ -545,12 +568,11 @@ void DrawGeneralTab() {
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 8.0f));
if (ImGui::InputText("##PlayerNameInput", playerNameInput, 9)) {
// Convert ASCII input back to game encoding
bool ended = false;
for (int i = 0; i < 8; i++) {
if (playerNameInput[i] == '\0') {
gSaveContext.playerName[i] = 223;
} else {
gSaveContext.playerName[i] = playerNameInput[i];
}
ended = ended || playerNameInput[i] == '\0';
gSaveContext.playerName[i] =
encodePlayerNameChar(ended ? ' ' : playerNameInput[i], gSaveContext.ship.filenameLanguage);
}
nameInputInitialized = false;
}
@@ -563,6 +585,7 @@ void DrawGeneralTab() {
// Filename encoding
const bool hasPAL = (sGerMessageEntryTablePtr != nullptr) && (sFraMessageEntryTablePtr != nullptr);
const bool hasNTSC = (sJpnMessageEntryTablePtr != nullptr);
const uint8_t oldNameLanguage = gSaveContext.ship.filenameLanguage;
if (hasPAL && hasNTSC) {
Combobox("Player Name Language", &gSaveContext.ship.filenameLanguage, filenameLanguageMap,
comboboxOptionsBase.Tooltip("Encoding used for Player Name"));
@@ -576,6 +599,13 @@ void DrawGeneralTab() {
comboboxOptionsBase.Tooltip("Encoding used for Player Name"));
ImGui::EndDisabled();
}
if (canEditName && gSaveContext.ship.filenameLanguage != oldNameLanguage) {
for (int i = 0; i < 8; i++) {
gSaveContext.playerName[i] = encodePlayerNameChar(
decodePlayerNameChar(gSaveContext.playerName[i], oldNameLanguage), gSaveContext.ship.filenameLanguage);
}
nameInputInitialized = false;
}
UIWidgets::EndCard();
UIWidgets::BeginCard("healthCard");
@@ -41,6 +41,14 @@ typedef enum {
// - None
VB_AMY_SOLVE,
// #### `result`
// ```c
// true
// ```
// #### `args`
// - `*Color_RGB8`
VB_APPLY_MAGIC_METER_FILL_COLOR,
// #### `result`
// ```c
// true
@@ -50,6 +58,14 @@ typedef enum {
// - `s8*` sCsState
VB_BARINADE_DEATH_SCENE,
// #### `result`
// ```c
// Flags_GetEventChkInf(EVENTCHKINF_BEGAN_BARINA_BATTLE)
// ```
// #### `args`
// - `*BossVa`
VB_BARINADE_PLAY_REMATCH_INTRO,
// #### `result`
// ```c
// this->actor.textId == 0x401A
@@ -1858,6 +1874,14 @@ typedef enum {
// - `*EnMs`
VB_MAGIC_BEAN_SALESMAN_TAKE_MONEY,
// #### `result`
// ```c
// true
// ```
// #### `args`
// - None
VB_MAGIC_METER_DEPLETE_WHILE_CONSUMING,
// #### `result`
// ```c
// CHECK_QUEST_ITEM(QUEST_SONG_EPONA)
@@ -2265,6 +2289,7 @@ typedef enum {
// ```
// #### `args`
// - `*BossGanon`
// - `u16` text ID
VB_PLAY_GANONDORF_INTRO_CS,
// #### `result`
+2
View File
@@ -293,6 +293,7 @@ void LoadStatsVersion1() {
SaveManager::Instance->LoadData("fileCreatedAt", gSaveContext.ship.stats.fileCreatedAt);
SaveManager::Instance->LoadData("playTimer", gSaveContext.ship.stats.playTimer);
SaveManager::Instance->LoadData("pauseTimer", gSaveContext.ship.stats.pauseTimer);
SaveManager::Instance->LoadData("gameComplete", gSaveContext.ship.stats.gameComplete);
SaveManager::Instance->LoadArray(
"itemTimestamps", ARRAY_COUNT(gSaveContext.ship.stats.itemTimestamp),
[](size_t i) { SaveManager::Instance->LoadData("", gSaveContext.ship.stats.itemTimestamp[i]); });
@@ -343,6 +344,7 @@ void SaveStats(const SaveContext& saveContext, int sectionID, bool fullSave) {
SaveManager::Instance->SaveData("fileCreatedAt", saveContext.ship.stats.fileCreatedAt);
SaveManager::Instance->SaveData("playTimer", saveContext.ship.stats.playTimer);
SaveManager::Instance->SaveData("pauseTimer", saveContext.ship.stats.pauseTimer);
SaveManager::Instance->SaveData("gameComplete", saveContext.ship.stats.gameComplete);
SaveManager::Instance->SaveArray(
"itemTimestamps", ARRAY_COUNT(saveContext.ship.stats.itemTimestamp),
[&](size_t i) { SaveManager::Instance->SaveData("", saveContext.ship.stats.itemTimestamp[i]); });
+4 -4
View File
@@ -25,10 +25,10 @@ char* GameplayStats_GetCurrentTime();
? (!gSaveContext.ship.stats.firstInput \
? 0 \
: ((GetUnixTimestamp() - gSaveContext.ship.stats.firstInput) / 100)) \
: (gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] \
? gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] \
: (gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED] \
? gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED] \
: (gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED] \
? gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED] \
: (gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] \
? gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] \
: gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TIMESPLITS_COMPLETED]))) \
: (gSaveContext.ship.stats.playTimer / 2 + gSaveContext.ship.stats.pauseTimer / 3))
#define CURRENT_MODE_TIMER \
@@ -2152,6 +2152,11 @@ void StaticData::HintTable_Init() {
| WINCON HINT TEXT |
---------------------------*/
hintTextTable[RHT_WINCON_DEFEAT_GANON_HINT] = HintText(CustomMessage("$wAnd the #Triforce# will be granted once #the evil one# is defeated.^",
/*german*/ "$wUnd das #Triforce# wird gewährt, sobald #der Böse# besiegt wurde.^",
/*french*/ "$wAussi, la #Triforce# sera accordée une fois que #le Malin# aura été vaincu.^",
{QM_YELLOW, QM_PINK}));
hintTextTable[RHT_WINCON_ANYWHERE_HINT] = HintText(CustomMessage("$wAnd the #Triforce# will be hidden somewhere&#in Hyrule#.^",
/*german*/ "$wUnd das #Triforce# wird irgendwo #in Hyrule# zu finden sein.^",
/*french*/ "$wAussi, la #Triforce# se trouve quelque part #dans Hyrule#.^",
@@ -64,6 +64,7 @@ CheckIdentity ShuffleFairies_GetFairyIdentity(int32_t params, ActorID id) {
CheckIdentity fairyIdentity;
s16 sceneNum = gPlayState->sceneNum;
fairyIdentity.randomizerInf = RAND_INF_MAX;
fairyIdentity.randomizerCheck = RC_UNKNOWN_CHECK;
if (sceneNum == SCENE_TEMPLE_OF_TIME_EXTERIOR_NIGHT || sceneNum == SCENE_TEMPLE_OF_TIME_EXTERIOR_RUINS) {
sceneNum = SCENE_TEMPLE_OF_TIME_EXTERIOR_DAY;
@@ -73,7 +74,6 @@ CheckIdentity ShuffleFairies_GetFairyIdentity(int32_t params, ActorID id) {
if (location->GetRandomizerCheck() == RC_UNKNOWN_CHECK) {
SPDLOG_WARN("FairyGetIdentity did not receive a valid RC value ({}).", location->GetRandomizerCheck());
assert(false);
} else {
fairyIdentity.randomizerInf = static_cast<RandomizerInf>(location->GetCollectionCheck().flag);
fairyIdentity.randomizerCheck = location->GetRandomizerCheck();
@@ -84,6 +84,9 @@ CheckIdentity ShuffleFairies_GetFairyIdentity(int32_t params, ActorID id) {
static bool SpawnFairy(f32 posX, f32 posY, f32 posZ, int32_t params, FairyType fairyType, ActorID id) {
CheckIdentity fairyIdentity = ShuffleFairies_GetFairyIdentity(params, id);
if (fairyIdentity.randomizerCheck == RC_UNKNOWN_CHECK) {
return false;
}
if (!Flags_GetRandomizerInf(fairyIdentity.randomizerInf)) {
Actor* fairy =
Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_EN_ELF, posX, posY - 30.0f, posZ, 0, 0, 0, fairyType);
@@ -334,8 +334,8 @@ void Rando::StaticData::RegisterSilverLocations() {
locationTable[RC_SPIRIT_NEAR_DOOR_SUN_SILVER] = Location::Collectable(RC_SPIRIT_NEAR_DOOR_SUN_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_SPIRIT_TEMPLE, TWO_ACTOR_PARAMS(-984, -450), "Near Door Sun Silver", RHT_SPIRIT_TEMPLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_SPIRIT_NEAR_DOOR_SUN_SILVER));
locationTable[RC_SPIRIT_NW_BOULDER_SILVER] = Location::Collectable(RC_SPIRIT_NW_BOULDER_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_SPIRIT_TEMPLE, TWO_ACTOR_PARAMS(1284, -1355), "Northwest Boulder Silver", RHT_SPIRIT_TEMPLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_SPIRIT_NW_BOULDER_SILVER));
locationTable[RC_SPIRIT_SW_BOULDER_SILVER] = Location::Collectable(RC_SPIRIT_SW_BOULDER_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_SPIRIT_TEMPLE, TWO_ACTOR_PARAMS(1284, -813), "Southwest Boulder Silver", RHT_SPIRIT_TEMPLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_SPIRIT_SW_BOULDER_SILVER));
locationTable[RC_SPIRIT_SE_BOULDER_SILVER] = Location::Collectable(RC_SPIRIT_SE_BOULDER_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_SPIRIT_TEMPLE, TWO_ACTOR_PARAMS(1856, -944), "Southeast Boulder Super", RHT_SPIRIT_TEMPLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_SPIRIT_SE_BOULDER_SILVER));
locationTable[RC_SPIRIT_NE_BOULDER_SILVER] = Location::Collectable(RC_SPIRIT_NE_BOULDER_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_SPIRIT_TEMPLE, TWO_ACTOR_PARAMS(1856, -1219), "Notheast Boulder Silver", RHT_SPIRIT_TEMPLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_SPIRIT_NE_BOULDER_SILVER));
locationTable[RC_SPIRIT_SE_BOULDER_SILVER] = Location::Collectable(RC_SPIRIT_SE_BOULDER_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_SPIRIT_TEMPLE, TWO_ACTOR_PARAMS(1856, -944), "Southeast Boulder Silver", RHT_SPIRIT_TEMPLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_SPIRIT_SE_BOULDER_SILVER));
locationTable[RC_SPIRIT_NE_BOULDER_SILVER] = Location::Collectable(RC_SPIRIT_NE_BOULDER_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_SPIRIT_TEMPLE, TWO_ACTOR_PARAMS(1856, -1219), "Northeast Boulder Silver", RHT_SPIRIT_TEMPLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_SPIRIT_NE_BOULDER_SILVER));
locationTable[RC_SPIRIT_AIRBORNE_BOULDER_SILVER] = Location::Collectable(RC_SPIRIT_AIRBORNE_BOULDER_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_SPIRIT_TEMPLE, TWO_ACTOR_PARAMS(1573, -920), "Airborne Boulder Silver", RHT_SPIRIT_TEMPLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_SPIRIT_AIRBORNE_BOULDER_SILVER));
locationTable[RC_BOTW_B1_LADDER_SILVER] = Location::Collectable(RC_BOTW_B1_LADDER_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_BOTTOM_OF_THE_WELL, TWO_ACTOR_PARAMS(-796, -150), "B1 Ladder Silver", RHT_BOTW_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_BOTW_B1_LADDER_SILVER));
locationTable[RC_BOTW_B2_LADDER_SILVER] = Location::Collectable(RC_BOTW_B2_LADDER_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_BOTTOM_OF_THE_WELL, TWO_ACTOR_PARAMS(-614, -297), "B2 Ladder Silver", RHT_BOTW_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_BOTW_B2_LADDER_SILVER));
@@ -378,7 +378,7 @@ void Rando::StaticData::RegisterSilverLocations() {
locationTable[RC_GANONS_CASTLE_UNDER_LEDGE_FOREST_SILVER] = Location::Collectable(RC_GANONS_CASTLE_UNDER_LEDGE_FOREST_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_INSIDE_GANONS_CASTLE, TWO_ACTOR_PARAMS(1651, 2021), "Under Ledge Forest Trial Silver", RHT_GANONS_CASTLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_GANONS_CASTLE_UNDER_LEDGE_FOREST_SILVER));
locationTable[RC_GANONS_CASTLE_ON_TARGET_FOREST_SILVER] = Location::Collectable(RC_GANONS_CASTLE_ON_TARGET_FOREST_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_INSIDE_GANONS_CASTLE, TWO_ACTOR_PARAMS(1634, 1550), "On Target Forest Trial Silver", RHT_GANONS_CASTLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_GANONS_CASTLE_ON_TARGET_FOREST_SILVER));
locationTable[RC_GANONS_CASTLE_TORCH_SLUG_FIRE_SILVER] = Location::Collectable(RC_GANONS_CASTLE_TORCH_SLUG_FIRE_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_INSIDE_GANONS_CASTLE, TWO_ACTOR_PARAMS(-854, -3679), "Torch Slug Fire Trial Silver", RHT_GANONS_CASTLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_GANONS_CASTLE_TORCH_SLUG_FIRE_SILVER));
locationTable[RC_GANONS_CASTLE_FLAME_JETS_FIRE_SILVER] = Location::Collectable(RC_GANONS_CASTLE_FLAME_JETS_FIRE_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_INSIDE_GANONS_CASTLE, TWO_ACTOR_PARAMS(-1555, -2317), "Flame Jets Fire Temple Silver", RHT_GANONS_CASTLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_GANONS_CASTLE_FLAME_JETS_FIRE_SILVER));
locationTable[RC_GANONS_CASTLE_FLAME_JETS_FIRE_SILVER] = Location::Collectable(RC_GANONS_CASTLE_FLAME_JETS_FIRE_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_INSIDE_GANONS_CASTLE, TWO_ACTOR_PARAMS(-1555, -2317), "Flame Jets Fire Trial Silver", RHT_GANONS_CASTLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_GANONS_CASTLE_FLAME_JETS_FIRE_SILVER));
locationTable[RC_GANONS_CASTLE_DISTANT_PLATFORM_FIRE_SILVER] = Location::Collectable(RC_GANONS_CASTLE_DISTANT_PLATFORM_FIRE_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_INSIDE_GANONS_CASTLE, TWO_ACTOR_PARAMS(-515, -3253), "Distant Platform Fire Trial Silver", RHT_GANONS_CASTLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_GANONS_CASTLE_DISTANT_PLATFORM_FIRE_SILVER));
locationTable[RC_GANONS_CASTLE_CLOSE_PLATFORM_FIRE_SILVER] = Location::Collectable(RC_GANONS_CASTLE_CLOSE_PLATFORM_FIRE_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_INSIDE_GANONS_CASTLE, TWO_ACTOR_PARAMS(-604, -2592), "Close Platform Fire Trial Silver", RHT_GANONS_CASTLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_GANONS_CASTLE_CLOSE_PLATFORM_FIRE_SILVER));
locationTable[RC_GANONS_GANONS_CASTLE_UNDER_PILLAR_FIRE_SILVER] = Location::Collectable(RC_GANONS_GANONS_CASTLE_UNDER_PILLAR_FIRE_SILVER, RCQUEST_VANILLA, RCTYPE_SILVER, ACTOR_EN_G_SWITCH, SCENE_INSIDE_GANONS_CASTLE, TWO_ACTOR_PARAMS(-1867, -2754), "Under Pillar Fire Trial Silver", RHT_GANONS_CASTLE_SILVER, RG_BLUE_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_GANONS_GANONS_CASTLE_UNDER_PILLAR_FIRE_SILVER));
+4
View File
@@ -357,6 +357,10 @@ extern "C" void Randomizer_DrawTriforcePieceGI(PlayState* play, GetItemEntry get
if (rando->GetRandoSettingValue(RSK_GANONS_SOUL) == RO_GANONS_SOUL_TRIFORCE_PIECES) {
fullTriforce = fullTriforce || (rando->GetRandoSettingValue(RSK_GANONS_SOUL_TRIFORCE_COUNT) == current);
}
if (getItemEntry.getItemId == RG_TRIFORCE) {
fullTriforce = true;
triforcePieceScale = 0.035f;
}
Matrix_Scale(triforcePieceScale, triforcePieceScale, triforcePieceScale, MTXMODE_APPLY);
@@ -177,6 +177,11 @@ int8_t GetSceneTotalSmallKeys(const SaveContext* saveContext, const SceneID scen
return FindCurrentSmallKeys(saveContext, scene);
}
int8_t GetSceneUnspentSmallKeyMax(const SaveContext* saveContext, const SceneID scene) {
int8_t used = GetSceneTotalSmallKeys(saveContext, scene) - FindCurrentSmallKeys(saveContext, scene);
return GetSceneSmallKeyMax(scene) - used;
}
int8_t DungeonInfo::GetUsedSmallKeys(SaveContext* saveContext) const {
return FindUsedSmallKeys(saveContext, scene, GetDoorFlags());
}
@@ -83,6 +83,8 @@ int8_t FindTotalSmallKeys(const SaveContext* saveContext, const SceneID scene, s
uint8_t GetSceneSmallKeyMax(SceneID scene);
/// Small keys held for the scene plus the ones already spent there.
int8_t GetSceneTotalSmallKeys(const SaveContext* saveContext, SceneID scene);
/// The scene's small key max, minus keys already used on doors. Key rings and skeleton key set held keys to this.
int8_t GetSceneUnspentSmallKeyMax(const SaveContext* saveContext, SceneID scene);
typedef enum {
DEKU_TREE,
+3 -1
View File
@@ -650,7 +650,9 @@ CustomMessage Hint::GetWinconText() {
auto ctx = Rando::Context::GetInstance();
CustomMessage winconMessage;
if (ctx->GetOption(RSK_WINCON).Is(RO_WINCON_ANYWHERE)) {
if (ctx->GetOption(RSK_WINCON).Is(RO_WINCON_DEFEAT_GANON)) {
return StaticData::hintTextTable[RHT_WINCON_DEFEAT_GANON_HINT].GetHintMessage();
} else if (ctx->GetOption(RSK_WINCON).Is(RO_WINCON_ANYWHERE)) {
return StaticData::hintTextTable[RHT_WINCON_ANYWHERE_HINT].GetHintMessage();
} else if (ctx->GetOption(RSK_WINCON).Is(RO_WINCON_STONES)) {
winconMessage = StaticData::hintTextTable[RHT_WINCON_STONES_HINT].GetHintMessage();
@@ -11,6 +11,7 @@
#include "soh/Enhancements/randomizer/fishsanity.h"
#include "soh/Enhancements/randomizer/static_data.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/SohGui/ImGuiUtils.h"
#include "soh/Notification/Notification.h"
#include "soh/SaveManager.h"
@@ -478,6 +479,9 @@ static void PlantAllBeans() {
gSaveContext.sceneFlags[patch.scene].swch |= 1 << patch.swchFlag;
if (gPlayState->sceneNum == patch.scene) {
Flags_SetSwitch(gPlayState, patch.swchFlag);
} else {
// Flags_SetSwitch fires this for the current scene, other scenes need it so Anchor syncs them
GameInteractor_ExecuteOnSceneFlagSet(patch.scene, FLAG_SCENE_SWITCH, patch.swchFlag);
}
}
ObjBean* bean = (ObjBean*)Actor_Find(&gPlayState->actorCtx, ACTOR_OBJ_BEAN, ACTORCAT_BG);
@@ -1018,6 +1022,24 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l
case VB_CRAWL:
*should = *should && Flags_GetRandomizerInf(RAND_INF_CAN_CRAWL);
break;
case VB_BARINADE_PLAY_REMATCH_INTRO:
*should =
*should && (!RAND_GET_OPTION(RSK_SHUFFLE_BOSS_SOULS) || Flags_GetRandomizerInf(RAND_INF_BARINADE_SOUL));
break;
case VB_MAGIC_METER_DEPLETE_WHILE_CONSUMING:
*should = *should && !Flags_GetRandomizerInf(RAND_INF_HAS_INFINITE_MAGIC_METER);
break;
case VB_APPLY_MAGIC_METER_FILL_COLOR: {
Color_RGB8* color = va_arg(args, Color_RGB8*);
if (Flags_GetRandomizerInf(RAND_INF_HAS_INFINITE_MAGIC_METER)) {
*color = { 0, 0, 200 };
if (CVarGetInteger(CVAR_COSMETIC("Consumable_MagicInfinite.Changed"), 0)) {
*color = CVarGetColor24(CVAR_COSMETIC("Consumable_MagicInfinite.Value"), *color);
}
}
break;
}
case VB_CAN_BUY_SHOP_SHIELD_OR_TUNIC: {
// Gate non-randomized shop shields/tunics behind finding a non-shop copy.
if (RAND_GET_OPTION(RSK_SHOP_SHIELDS_AND_TUNICS_ONLY_REFILL).Is(RO_GENERIC_ON)) {
@@ -1868,7 +1890,7 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l
Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_GANONS_TOWER);
randomizerQueuedChecks.push(RC_GANON);
CheckTriggers();
gPlayState->nextEntranceIndex = ENTR_OUTSIDE_GANONS_CASTLE_1_2;
gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(ENTR_OUTSIDE_GANONS_CASTLE_1_2);
gPlayState->transitionTrigger = TRANS_TRIGGER_START;
gPlayState->transitionType = TRANS_TYPE_FADE_WHITE;
gSaveContext.nextTransitionType = TRANS_TYPE_FADE_WHITE_SLOW;
@@ -2742,9 +2764,6 @@ void RandomizerOnPlayerUpdateHandler() {
// Warp to credits once item queue has drained to avoid losing queued items
if (GameInteractor::State::TriforceHuntCreditsWarpActive && randomizerQueuedChecks.empty() &&
randomizerQueuedCheck == RC_UNKNOWN_CHECK) {
gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED] =
static_cast<u32>(GAMEPLAYSTAT_TOTAL_TIME);
gSaveContext.ship.stats.gameComplete = 1;
Play_PerformSave(gPlayState);
Notification::Emit({ .message = "Game autosaved" });
gPlayState->nextEntranceIndex = ENTR_CHAMBER_OF_THE_SAGES_0;
@@ -472,7 +472,7 @@ void Rando::StaticData::InitItemTable() {
itemTable[RG_DEKU_NUT_BAG] = Item(RG_DEKU_NUT_BAG, Text{ "Deku Nut Bag", "Sac de Noix Mojo", "Deku-Nuß-Tasche" }, ITEMTYPE_ITEM, GI_NUT_UPGRADE_30, true, LOGIC_PROGRESSIVE_NUT_BAG, RHT_NONE, RG_DEKU_NUT_BAG, OBJECT_GI_NUTS, GID_NUTS, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER, {"a ", "un ", "eine "}).CustomIcon(gItemIconDekuNutTex);
itemTable[RG_DEKU_NUT_BAG].SetCustomDrawFunc(Randomizer_DrawNutBag);
itemTable[RG_TRIFORCE] = Item(RG_TRIFORCE, Text{ "Triforce", "Triforce", "Triforce" }, ITEMTYPE_ITEM, RG_TRIFORCE, true, LOGIC_NONE, RHT_NONE, RG_TRIFORCE, OBJECT_GI_BOMB_2, GID_TRIFORCE_PIECE, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER, {"the ", "la ", "die "}).CustomIcon(gTriforcePieceTex);;
itemTable[RG_TRIFORCE] = Item(RG_TRIFORCE, Text{ "Triforce", "Triforce", "Triforce" }, ITEMTYPE_ITEM, RG_TRIFORCE, true, LOGIC_NONE, RHT_NONE, RG_TRIFORCE, OBJECT_GI_BOMB_2, GID_TRIFORCE, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER, {"the ", "la ", "die "}).CustomIcon(gTriforcePieceTex);;
itemTable[RG_HINT] = Item(RG_HINT, Text{ "Hint", "Indice", "Hinweis" }, ITEMTYPE_EVENT, RG_HINT, false, LOGIC_NONE, RHT_NONE, ITEM_CATEGORY_LESSER);
// Individual stages of progressive items (only here for GetItemEntry purposes, not for use in seed gen)
itemTable[RG_HOOKSHOT] = Item(RG_HOOKSHOT, Text{ "Hookshot", "Grappin", "Fanghaken" }, ITEMTYPE_ITEM, GI_HOOKSHOT, true, LOGIC_PROGRESSIVE_HOOKSHOT, RHT_HOOKSHOT, ITEM_HOOKSHOT, OBJECT_GI_HOOKSHOT, GID_HOOKSHOT, 0x36, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE, {"the ", "le ", "den "});
@@ -42,7 +42,7 @@ bool LocationAccess::CheckConditionAtAgeTime(bool& age, bool& time) const {
}
bool LocationAccess::ConditionsMet(Region* parentRegion, bool calculatingAvailableChecks) const {
// WARNING enterance validation can run this after resetting the access for sphere 0 validation
// WARNING entrance validation can run this after resetting the access for sphere 0 validation
// When refactoring ToD access, either fix the above or do not assume that we
// have any access at all just because this is being run
bool conditionsMet = false;
@@ -644,7 +644,7 @@ void RegionTable_Init_FireTemple() {
//Only adult is tall enough to use a bow shot without climbing to UPPER
//Logical ways to cross are hovers, longshot or running through the lava and then climbing back up as adult (child can't reach the ledge).
//The Damage logic here is for jumping down and running across the lava to get in dins range of the south torch
//Fairies cannot be used for this as it is time sensetive, and NL is only useful with sticks as it disables other magic while in use, so it's tunic or raw damage taking ability.
//Fairies cannot be used for this as it is time sensitive, and NL is only useful with sticks as it disables other magic while in use, so it's tunic or raw damage taking ability.
//testing tells me you take 3 ticks of lava damage, which is 12 internal damage or 3/4 of a heart at x1 damage multiplier, performing this run
LOCATION(RC_FIRE_TEMPLE_MQ_NEAR_BOSS_CHEST, logic->CanUse(RG_DINS_FIRE) && logic->HasItem(RG_OPEN_CHEST) &&
(logic->CanUse(RG_HOVER_BOOTS) || logic->CanUse(RG_LONGSHOT) || logic->CanMegajump() ||
@@ -744,7 +744,7 @@ void RegionTable_Init_ForestTemple() {
LOCATION(RC_FOREST_TEMPLE_MQ_RAISED_ISLAND_COURTYARD_LOWER_CHEST, logic->HasItem(RG_OPEN_CHEST)),
}, {
//Exits
//Skipping swim here is non-trival, needs a roll-jump. If a swim lock is added it's probably wise to copy deku baba events here
//Skipping swim here is non-trivial, needs a roll-jump. If a swim lock is added it's probably wise to copy deku baba events here
ENTRANCE(RR_FOREST_TEMPLE_MQ_NE_COURTYARD, true),
ENTRANCE(RR_FOREST_TEMPLE_MQ_NE_COURTYARD_LEDGE_ABOVE_ISLAND, logic->CanUse(RG_SONG_OF_TIME)),
});
@@ -638,7 +638,7 @@ void RegionTable_Init_GanonsCastle() {
LOCATION(RC_GANONS_CASTLE_MQ_FIRE_TRIAL_POT_2, logic->CanBreakPots()),
}, {
//Exits
ENTRANCE(RR_GANONS_CASTLE_MQ_FIRE_TRIAL_BARRED_DOOR, true),
ENTRANCE(RR_GANONS_CASTLE_MQ_FIRE_TRIAL_FROM_BARRED, true),
});
areaTable[RR_GANONS_CASTLE_MQ_WATER_TRIAL_GEYSER_ROOM] = Region("Ganon's Castle MQ Water Trial Geyser Room", SCENE_INSIDE_GANONS_CASTLE, {
@@ -939,7 +939,7 @@ void RegionTable_Init_GanonsCastle() {
//Exits
//I got the trick going backwards, but only while taking damage. this isn't relevant anyway though
ENTRANCE(RR_GANONS_CASTLE_MQ_LIGHT_TRIAL_BOULDER_ROOM_FRONT, logic->CanUse(RG_HOOKSHOT) || ctx->GetTrickOption(RT_GANON_MQ_LIGHT_TRIAL) || (logic->IsAdult && logic->CanGroundJump())),
ENTRANCE(RR_GANONS_CASTLE_MQ_LIGHT_TRIAL_FINAL_ROOM, logic->SmallKeys(SCENE_INSIDE_GANONS_CASTLE, 3)),
ENTRANCE(RR_GANONS_CASTLE_MQ_LIGHT_TRIAL_FAKE_FINAL_ROOM, logic->SmallKeys(SCENE_INSIDE_GANONS_CASTLE, 3)),
});
areaTable[RR_GANONS_CASTLE_MQ_LIGHT_TRIAL_FAKE_FINAL_ROOM] = Region("Ganon's Castle MQ Light Trial Fake Final Room", SCENE_INSIDE_GANONS_CASTLE, {}, {}, {
@@ -955,10 +955,7 @@ void RegionTable_Init_GanonsCastle() {
//Locations
LOCATION(RC_GANONS_CASTLE_MQ_LIGHT_TRIAL_POT_1, logic->CanBreakPots()),
LOCATION(RC_GANONS_CASTLE_MQ_LIGHT_TRIAL_POT_2, logic->CanBreakPots()),
}, {
//Exits
ENTRANCE(RR_GANONS_CASTLE_MQ_LIGHT_TRIAL_BOULDER_ROOM_BACK, logic->SmallKeys(SCENE_INSIDE_GANONS_CASTLE, 3)),
});
}, {});
#pragma endregion
@@ -328,7 +328,7 @@ void RegionTable_Init_IceCavern() {
LOCATION(RC_ICE_CAVERN_MQ_MAP_RED_ICE, logic->BlueFire()),
}, {});
//RANDOTODO the default state of the toggle switchs in MQ ice are wierd, investigate for doordsanity
//RANDOTODO the default state of the toggle switches in MQ ice are weird, investigate for doorsanity
areaTable[RR_ICE_CAVERN_MQ_SCARECROW_ROOM] = Region("Ice Cavern MQ Scarecrow Room", SCENE_ICE_CAVERN, {}, {
//Locations
//Implies being able to kill the skull if you hit the switch
@@ -736,7 +736,7 @@ void RegionTable_Init_ShadowTemple() {
ENTRANCE(RR_SHADOW_TEMPLE_MQ_SPIKES_DOOR_PLATFORM, (((ctx->GetTrickOption(RT_LENS_SHADOW_MQ) || logic->CanUse(RG_LENS_OF_TRUTH)) && logic->CanUse(logic->IsAdult && AnyAgeTime([]{return logic->CanKillEnemy(RE_REDEAD);}) ? RG_HOOKSHOT : RG_LONGSHOT)) &&
//regardless, we can't assume the player does not have the silvers, so we need to cross from the glass to the platform with hovers or a jumpslash jump (as adult)
(logic->CanUse(RG_HOVER_BOOTS) || (logic->IsAdult && logic->CanJumpslash()))) ||
//alternativly, a midair groundjump from the invisible spikes takes adult directly there
//alternatively, a midair groundjump from the invisible spikes takes adult directly there
((ctx->GetTrickOption(RT_LENS_SHADOW_MQ) || logic->CanUse(RG_LENS_OF_TRUTH)) && logic->IsAdult && logic->CanMidairGroundJump()))
});
@@ -262,7 +262,7 @@ void RegionTable_Init_SpiritTemple() {
RR_SPIRIT_TEMPLE_STATUE_ROOM_CHILD, []{return logic->CanGetEnemyDrop(RE_GOLD_SKULLTULA, ED_LONGSHOT);})),
}, {
//Exits
//Adult can barely make it with just a jump, but it's a bit tight with a bad retry if it is relevent, so it would be it's own trick.
//Adult can barely make it with just a jump, but it's a bit tight with a bad retry if it is relevant, so it would be it's own trick.
ENTRANCE(RR_SPIRIT_TEMPLE_STATUE_ROOM_CHILD, logic->CanUse(RG_HOVER_BOOTS) || logic->BunnyHood() || logic->CanJumpslash() || logic->CanUse(RG_HOOKSHOT)),
ENTRANCE(RR_SPIRIT_TEMPLE_STATUE_ROOM, true),
ENTRANCE(RR_SPIRIT_TEMPLE_GS_LEDGE, logic->IsAdult && (ctx->GetTrickOption(RT_SPIRIT_STATUE_JUMP) || logic->BunnyHood())),
@@ -582,7 +582,7 @@ void RegionTable_Init_SpiritTemple() {
// Exits
ENTRANCE(RR_SPIRIT_TEMPLE_STATUE_ROOM, true),
//Bunny hood jumps with a jumpslash can reach either hand and with good timing the platform as child.
//The latter is definitely a trick, the former may not be but I am dissalowing it because I would have to spend a week writing a new branch of spirit key logic.
//The latter is definitely a trick, the former may not be but I am disallowing it because I would have to spend a week writing a new branch of spirit key logic.
//If this interaction with the hands is added, SpiritSharedLogic needs updating for it
//RANDOTODO bunny hood megajumps let child into adult side too, saving for a future PR
ENTRANCE(RR_SPIRIT_TEMPLE_INNER_WEST_HAND, logic->CanUse(RG_HOVER_BOOTS) || (logic->BunnyHood() && logic->CanJumpslash() && logic->IsAdult) || logic->CanMegajump()),
@@ -673,7 +673,7 @@ void RegionTable_Init_SpiritTemple() {
LOCATION(RC_SPIRIT_TEMPLE_MQ_GIBDO_BOULDER, logic->BlastOrSmash()),
}, {
//Exits
//climbing the grate through the celing allows you to go backwards here, but that's a clip not visible collision
//climbing the grate through the ceiling allows you to go backwards here, but that's a clip not visible collision
ENTRANCE(RR_SPIRIT_TEMPLE_MQ_TURNTABLE, logic->Get(LOGIC_SPIRIT_MQ_GIBDOS_CLEARED)),
});
@@ -1211,7 +1211,7 @@ void RegionTable_Init_SpiritTemple() {
// Exits
ENTRANCE(RR_SPIRIT_TEMPLE_MQ_STATUE_ROOM, true),
//Bunny hood jumps with a jumpslash can reach either hand and with good timing the platform as child.
//The latter is definitely a trick, the former may not be but I am dissalowing it because I would have to spend a week writing a new branch of spirit key logic.
//The latter is definitely a trick, the former may not be but I am disallowing it because I would have to spend a week writing a new branch of spirit key logic.
//If this interaction with the hands is added, SpiritSharedLogic needs updating for it
ENTRANCE(RR_SPIRIT_TEMPLE_MQ_INNER_RIGHT_HAND, logic->CanUse(RG_HOVER_BOOTS) || (logic->BunnyHood() && logic->CanJumpslash() && logic->IsAdult) || logic->CanMegajump()),
ENTRANCE(RR_SPIRIT_TEMPLE_MQ_INNER_LEFT_HAND, logic->CanUse(RG_HOVER_BOOTS) || (logic->BunnyHood() && logic->CanJumpslash() && logic->IsAdult)),
@@ -258,11 +258,11 @@ void RegionTable_Init_WaterTemple() {
//Implies CanAvoid(RE_STINGER)
//the full logic for the puzzle, as it is cut down here for optimisation because of how the water rising starts requiring scale
//EVENT_ACCESS(LOGIC_WATER_PUSHED_B1_BLOCK, logic->HasItem(RG_GORONS_BRACELET) && logic->HasExplosives() &&
// (logic->CanUse(RG_HOOKSHOT) || logic->HasItem(RG_BRONZE_SCALE) || logic->CanUse(RG_HOVER_BOOTS) || (logic->IsAdult && logiic->BunnyHood()))),
// (logic->CanUse(RG_HOOKSHOT) || logic->HasItem(RG_BRONZE_SCALE) || logic->CanUse(RG_HOVER_BOOTS) || (logic->IsAdult && logic->BunnyHood()))),
}, {
//Locations //we can always get the pots by shooting them from afar and diving for the item...
LOCATION(RC_WATER_TEMPLE_BASEMENT_BLOCK_PUZZLE_POT_1, (logic->CanBreakPots(ED_LONGSHOT, true, true) && logic->HasItem(RG_BRONZE_SCALE)) ||
//or we can use irons and hookshot the pot, hookshoting to the target door if needed to cross the gap
//or we can use irons and hookshot the pot, hookshotting to the target door if needed to cross the gap
(logic->CanUse(RG_IRON_BOOTS) && logic->CanUse(RG_HOOKSHOT) && logic->WaterTimer() >= 8)),
LOCATION(RC_WATER_TEMPLE_BASEMENT_BLOCK_PUZZLE_POT_2, (logic->CanBreakPots(ED_LONGSHOT, true, true) && logic->HasItem(RG_BRONZE_SCALE)) ||
(logic->CanUse(RG_IRON_BOOTS) && logic->CanUse(RG_HOOKSHOT) && logic->WaterTimer() >= 8)),
@@ -1122,7 +1122,7 @@ void RegionTable_Init_WaterTemple() {
LOCATION(RC_WATER_TEMPLE_MQ_WONDER_HOOKSHOT_STAIRCASE_LEFT_3, logic->CanUse(RG_HOOKSHOT)),
}, {
//Exits
//you can also make it with hover boots and good timing, using the momentom of the slope, but it's a trick
//you can also make it with hover boots and good timing, using the momentum of the slope, but it's a trick
ENTRANCE(RR_WATER_TEMPLE_MQ_WATERFALL, logic->CanUse(RG_LONGSHOT) && logic->CanHitSwitch(ED_FAR)),
ENTRANCE(RR_WATER_TEMPLE_MQ_STALFOS_PIT, true),
});
@@ -1142,7 +1142,7 @@ void RegionTable_Init_WaterTemple() {
//Exits
ENTRANCE(RR_WATER_TEMPLE_MQ_STALFOS_PIT, logic->CanUse(RG_HOOKSHOT) && (logic->IsAdult || (logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 8))),
//Child can use the second method (going via the wall target near the half raised target) with bunny hood, but while the first jump is just wall assisted, the second is inconsistent
//It is also possible for them to jump around the statue with bunny, or even climb up sometimes. This needs more investigation before catagorisation as they also seem inconsistent without a setup
//It is also possible for them to jump around the statue with bunny, or even climb up sometimes. This needs more investigation before categorisation as they also seem inconsistent without a setup
ENTRANCE(RR_WATER_TEMPLE_MQ_STALFOS_PIT_POTS, (logic->IsAdult && logic->CanUse(RG_HOOKSHOT)) || (logic->CanUse(RG_HOOKSHOT) && (logic->IsAdult || logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 8) && (logic->CanUse(RG_HOVER_BOOTS) || logic->Get(LOGIC_WATER_MQ_STALFOS_PIT)))),
ENTRANCE(RR_WATER_TEMPLE_MQ_STALFOS_PIT_UPPER, logic->Get(LOGIC_WATER_MQ_STALFOS_PIT) && (logic->IsAdult || logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 8) && logic->CanUse(RG_HOOKSHOT)),
});
@@ -49,7 +49,7 @@ void RegionTable_Init_GerudoFortress() {
EVENT_ACCESS(LOGIC_GTG_GATE_OPEN, logic->IsAdult && logic->HasItem(RG_GERUDO_MEMBERSHIP_CARD) && logic->HasItem(RG_CHILD_WALLET) && logic->HasItem(RG_SPEAK_GERUDO)),
}, {
//Locations
LOCATION(RC_GF_GTG_ENTRANCE_RECTANGLE_SIGN, logic->IsAdult && logic->CanRead()),
LOCATION(RC_GF_GTG_ENTRANCE_RECTANGLE_SIGN, logic->IsAdult && logic->CanRead() && (ctx->GetTrickOption(RT_PASS_GUARDS_WITH_NOTHING) || logic->HasItem(RG_GERUDO_MEMBERSHIP_CARD))),
}, {
//Exits
ENTRANCE(RR_GF_TO_GTG, logic->Get(LOGIC_GTG_GATE_OPEN) && (logic->IsAdult || ctx->GetOption(RSK_SHUFFLE_DUNGEON_ENTRANCES))),
@@ -57,7 +57,7 @@ void RegionTable_Init_GerudoFortress() {
ENTRANCE(RR_GF_JAIL_WINDOW, logic->CanUse(RG_HOOKSHOT)),
ENTRANCE(RR_GF_OUTSKIRTS, true),
ENTRANCE(RR_GF_NEAR_GROTTO, logic->IsChild || logic->CanPassEnemy(RE_GERUDO_GUARD)),
// RANDTODO: Add tricks for getting past the gerudo guarding the hba range
// RANDOTODO: Add tricks for getting past the gerudo guarding the hba range
ENTRANCE(RR_GF_ABOVE_GTG, logic->IsChild || logic->CanPassEnemy(RE_GERUDO_GUARD)),
ENTRANCE(RR_GF_TOP_OF_UPPER_VINES, logic->HasItem(RG_GERUDO_MEMBERSHIP_CARD) && logic->CanUse(RG_LONGSHOT)),
ENTRANCE(RR_GF_HBA_RANGE, logic->IsChild || logic->HasItem(RG_GERUDO_MEMBERSHIP_CARD)),
@@ -121,10 +121,10 @@ void RegionTable_Init_GerudoValley() {
LOCATION(RC_GV_CRATE_BRIDGE_2, logic->IsChild && logic->CanBreakCrates()),
LOCATION(RC_GV_CRATE_BRIDGE_3, logic->IsChild && logic->CanBreakCrates()),
LOCATION(RC_GV_CRATE_BRIDGE_4, logic->IsChild && logic->CanBreakCrates()),
LOCATION(RC_GV_ROCK_ACROSS_BRIDGE_1, logic->IsAdult),
LOCATION(RC_GV_ROCK_ACROSS_BRIDGE_2, logic->IsAdult),
LOCATION(RC_GV_ROCK_ACROSS_BRIDGE_3, logic->IsAdult),
LOCATION(RC_GV_ROCK_ACROSS_BRIDGE_4, logic->IsAdult),
LOCATION(RC_GV_ROCK_ACROSS_BRIDGE_1, logic->IsAdult && logic->CanBreakRocks()),
LOCATION(RC_GV_ROCK_ACROSS_BRIDGE_2, logic->IsAdult && logic->CanBreakRocks()),
LOCATION(RC_GV_ROCK_ACROSS_BRIDGE_3, logic->IsAdult && logic->CanBreakRocks()),
LOCATION(RC_GV_ROCK_ACROSS_BRIDGE_4, logic->IsAdult && logic->CanBreakRocks()),
LOCATION(RC_GV_BOULDER_ACROSS_BRIDGE, logic->IsAdult && logic->BlastOrSmash()),
LOCATION(RC_GV_BRONZE_BOULDER_ACROSS_BRIDGE_1, logic->IsAdult && logic->CanUse(RG_MEGATON_HAMMER)),
LOCATION(RC_GV_BRONZE_BOULDER_ACROSS_BRIDGE_2, logic->IsAdult && logic->CanUse(RG_MEGATON_HAMMER)),
@@ -14,54 +14,54 @@ void RegionTable_Init_HyruleField() {
LOCATION(RC_HF_OCARINA_OF_TIME_ITEM, logic->IsChild && logic->StoneCount() == 3 && logic->HasItem(RG_BRONZE_SCALE)),
LOCATION(RC_SONG_FROM_OCARINA_OF_TIME, logic->IsChild && logic->StoneCount() == 3 && logic->HasItem(RG_BRONZE_SCALE)),
LOCATION(RC_HF_POND_STORMS_FAIRY, logic->CanUse(RG_SONG_OF_STORMS)),
LOCATION(RC_HF_CENTRAL_GRASS_1, logic->CanCutShrubs()),
LOCATION(RC_HF_CENTRAL_GRASS_2, logic->CanCutShrubs()),
LOCATION(RC_HF_CENTRAL_GRASS_3, logic->CanCutShrubs()),
LOCATION(RC_HF_CENTRAL_GRASS_4, logic->CanCutShrubs()),
LOCATION(RC_HF_CENTRAL_GRASS_5, logic->CanCutShrubs()),
LOCATION(RC_HF_CENTRAL_GRASS_6, logic->CanCutShrubs()),
LOCATION(RC_HF_CENTRAL_GRASS_7, logic->CanCutShrubs()),
LOCATION(RC_HF_CENTRAL_GRASS_8, logic->CanCutShrubs()),
LOCATION(RC_HF_CENTRAL_GRASS_9, logic->CanCutShrubs()),
LOCATION(RC_HF_CENTRAL_GRASS_10, logic->CanCutShrubs()),
LOCATION(RC_HF_CENTRAL_GRASS_11, logic->CanCutShrubs()),
LOCATION(RC_HF_CENTRAL_GRASS_12, logic->CanCutShrubs()),
LOCATION(RC_HF_SOUTH_GRASS_1, logic->CanCutShrubs()),
LOCATION(RC_HF_SOUTH_GRASS_2, logic->CanCutShrubs()),
LOCATION(RC_HF_SOUTH_GRASS_3, logic->CanCutShrubs()),
LOCATION(RC_HF_SOUTH_GRASS_4, logic->CanCutShrubs()),
LOCATION(RC_HF_SOUTH_GRASS_5, logic->CanCutShrubs()),
LOCATION(RC_HF_SOUTH_GRASS_6, logic->CanCutShrubs()),
LOCATION(RC_HF_SOUTH_GRASS_7, logic->CanCutShrubs()),
LOCATION(RC_HF_SOUTH_GRASS_8, logic->CanCutShrubs()),
LOCATION(RC_HF_SOUTH_GRASS_9, logic->CanCutShrubs()),
LOCATION(RC_HF_SOUTH_GRASS_10, logic->CanCutShrubs()),
LOCATION(RC_HF_SOUTH_GRASS_11, logic->CanCutShrubs()),
LOCATION(RC_HF_SOUTH_GRASS_12, logic->CanCutShrubs()),
LOCATION(RC_HF_NEAR_MARKET_GRASS_1, logic->CanCutShrubs()),
LOCATION(RC_HF_NEAR_MARKET_GRASS_2, logic->CanCutShrubs()),
LOCATION(RC_HF_NEAR_MARKET_GRASS_3, logic->CanCutShrubs()),
LOCATION(RC_HF_NEAR_MARKET_GRASS_4, logic->CanCutShrubs()),
LOCATION(RC_HF_NEAR_MARKET_GRASS_5, logic->CanCutShrubs()),
LOCATION(RC_HF_NEAR_MARKET_GRASS_6, logic->CanCutShrubs()),
LOCATION(RC_HF_NEAR_MARKET_GRASS_7, logic->CanCutShrubs()),
LOCATION(RC_HF_NEAR_MARKET_GRASS_8, logic->CanCutShrubs()),
LOCATION(RC_HF_NEAR_MARKET_GRASS_9, logic->CanCutShrubs()),
LOCATION(RC_HF_NEAR_MARKET_GRASS_10, logic->CanCutShrubs()),
LOCATION(RC_HF_NEAR_MARKET_GRASS_11, logic->CanCutShrubs()),
LOCATION(RC_HF_NEAR_MARKET_GRASS_12, logic->CanCutShrubs()),
LOCATION(RC_HF_NEAR_KF_GRASS_1, logic->CanCutShrubs()),
LOCATION(RC_HF_NEAR_KF_GRASS_2, logic->CanCutShrubs()),
LOCATION(RC_HF_NEAR_KF_GRASS_3, logic->CanCutShrubs()),
LOCATION(RC_HF_NEAR_KF_GRASS_4, logic->CanCutShrubs()),
LOCATION(RC_HF_NEAR_KF_GRASS_5, logic->CanCutShrubs()),
LOCATION(RC_HF_NEAR_KF_GRASS_6, logic->CanCutShrubs()),
LOCATION(RC_HF_NEAR_KF_GRASS_7, logic->CanCutShrubs()),
LOCATION(RC_HF_NEAR_KF_GRASS_8, logic->CanCutShrubs()),
LOCATION(RC_HF_NEAR_KF_GRASS_9, logic->CanCutShrubs()),
LOCATION(RC_HF_NEAR_KF_GRASS_10, logic->CanCutShrubs()),
LOCATION(RC_HF_NEAR_KF_GRASS_11, logic->CanCutShrubs()),
LOCATION(RC_HF_NEAR_KF_GRASS_12, logic->CanCutShrubs()),
LOCATION(RC_HF_CENTRAL_GRASS_1, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_CENTRAL_GRASS_2, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_CENTRAL_GRASS_3, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_CENTRAL_GRASS_4, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_CENTRAL_GRASS_5, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_CENTRAL_GRASS_6, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_CENTRAL_GRASS_7, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_CENTRAL_GRASS_8, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_CENTRAL_GRASS_9, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_CENTRAL_GRASS_10, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_CENTRAL_GRASS_11, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_CENTRAL_GRASS_12, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_SOUTH_GRASS_1, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_SOUTH_GRASS_2, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_SOUTH_GRASS_3, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_SOUTH_GRASS_4, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_SOUTH_GRASS_5, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_SOUTH_GRASS_6, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_SOUTH_GRASS_7, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_SOUTH_GRASS_8, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_SOUTH_GRASS_9, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_SOUTH_GRASS_10, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_SOUTH_GRASS_11, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_SOUTH_GRASS_12, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_NEAR_MARKET_GRASS_1, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_NEAR_MARKET_GRASS_2, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_NEAR_MARKET_GRASS_3, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_NEAR_MARKET_GRASS_4, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_NEAR_MARKET_GRASS_5, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_NEAR_MARKET_GRASS_6, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_NEAR_MARKET_GRASS_7, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_NEAR_MARKET_GRASS_8, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_NEAR_MARKET_GRASS_9, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_NEAR_MARKET_GRASS_10, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_NEAR_MARKET_GRASS_11, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_NEAR_MARKET_GRASS_12, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_NEAR_KF_GRASS_1, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_NEAR_KF_GRASS_2, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_NEAR_KF_GRASS_3, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_NEAR_KF_GRASS_4, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_NEAR_KF_GRASS_5, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_NEAR_KF_GRASS_6, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_NEAR_KF_GRASS_7, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_NEAR_KF_GRASS_8, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_NEAR_KF_GRASS_9, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_NEAR_KF_GRASS_10, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_NEAR_KF_GRASS_11, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_NEAR_KF_GRASS_12, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_HF_SILVER_BOULDER, logic->CanUse(RG_SILVER_GAUNTLETS)),
LOCATION(RC_HF_ROCK_1, logic->CanBreakRocks()),
LOCATION(RC_HF_ROCK_2, logic->CanBreakRocks()),
@@ -39,48 +39,48 @@ void RegionTable_Init_LakeHylia() {
LOCATION(RC_LH_LAB_GOSSIP_STONE, true),
LOCATION(RC_LH_SOUTHEAST_GOSSIP_STONE, true),
LOCATION(RC_LH_SOUTHWEST_GOSSIP_STONE, true),
LOCATION(RC_LH_GRASS_1, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_2, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_3, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_4, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_5, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_6, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_7, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_8, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_9, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_10, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_11, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_12, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_13, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_14, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_15, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_16, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_17, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_18, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_19, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_20, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_21, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_22, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_23, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_24, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_25, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_26, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_27, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_28, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_29, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_30, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_31, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_32, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_33, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_34, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_35, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_36, logic->CanCutShrubs()),
LOCATION(RC_LH_CHILD_GRASS_1, logic->IsChild && logic->CanCutShrubs()),
LOCATION(RC_LH_CHILD_GRASS_2, logic->IsChild && logic->CanCutShrubs()),
LOCATION(RC_LH_CHILD_GRASS_3, logic->IsChild && logic->CanCutShrubs()),
LOCATION(RC_LH_CHILD_GRASS_4, logic->IsChild && logic->CanCutShrubs()),
LOCATION(RC_LH_WARP_PAD_GRASS_1, logic->CanCutShrubs()),
LOCATION(RC_LH_WARP_PAD_GRASS_2, logic->CanCutShrubs()),
LOCATION(RC_LH_GRASS_1, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_2, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_3, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_4, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_5, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_6, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_7, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_8, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_9, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_10, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_11, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_12, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_13, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_14, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_15, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_16, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_17, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_18, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_19, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_20, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_21, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_22, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_23, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_24, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_25, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_26, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_27, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_28, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_29, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_30, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_31, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_32, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_33, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_34, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_35, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_GRASS_36, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_CHILD_GRASS_1, logic->IsChild && (logic->CanCutShrubs() || logic->SummonEpona())),
LOCATION(RC_LH_CHILD_GRASS_2, logic->IsChild && (logic->CanCutShrubs() || logic->SummonEpona())),
LOCATION(RC_LH_CHILD_GRASS_3, logic->IsChild && (logic->CanCutShrubs() || logic->SummonEpona())),
LOCATION(RC_LH_CHILD_GRASS_4, logic->IsChild && (logic->CanCutShrubs() || logic->SummonEpona())),
LOCATION(RC_LH_WARP_PAD_GRASS_1, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_WARP_PAD_GRASS_2, logic->CanCutShrubs() || logic->SummonEpona()),
LOCATION(RC_LH_SCARECROW_BUTTERFLY_FAIRY, logic->IsChild && logic->CanUse(RG_STICKS)),
LOCATION(RC_LH_LAB_RECTANGLE_SIGN, logic->CanRead()),
LOCATION(RC_LH_NORTH_EXIT_ARROW_SIGN, logic->CanRead()),
+4 -4
View File
@@ -1079,7 +1079,7 @@ bool Logic::CanKillEnemy(RandomizerEnemy enemy, EnemyDistance distance, bool wal
// RANDOTODO make a function to track our ammo vs his HP when ammo capacity is taken into account in logic
// all swords can at least trade blows with dark link, and even with 1 damage a slash it works out
return CanUseSword() ||
// Boomerang is a relaible, infinite ammo stun, so it enables any way to get enough damage with the
// Boomerang is a reliable, infinite ammo stun, so it enables any way to get enough damage with the
// ammo we have Max HP dark link has 40 HP, bows and bombs do 2 so 20 ammo, stick jumpslash does 4 so
// 10 sticks
(CanUse(RG_BOOMERANG) &&
@@ -1209,7 +1209,7 @@ bool Logic::CanPassEnemy(RandomizerEnemy enemy, EnemyDistance distance, bool wal
}
// Can we avoid this enemy while climbing up a wall, or doing a difficult platforming challenge?
// use grounded if the challenge is such that the enemy interfears even if it cannot hit link out of the air
// use grounded if the challenge is such that the enemy interferes even if it cannot hit link out of the air
bool Logic::CanAvoidEnemy(RandomizerEnemy enemy, EnemyDistance distance, bool grounded, uint8_t quantity) {
// DISTANCE AND WALL ASSUMED, add more arguments later if needed
if (CanKillEnemy(enemy, distance, true, quantity)) {
@@ -1505,7 +1505,7 @@ bool Logic::WaterRisingTargetTo3FCentral() {
* changed out of logic
*
* Extending from these 3, LOGIC_WATER_COULD_LOW_FROM_HIGH and LOGIC_WATER_COULD_HIGH_FROM_MID tell us if we can move
* from 1 level to the next, without us first having to confirm we can always do the preceeding level first. These allow
* from 1 level to the next, without us first having to confirm we can always do the preceding level first. These allow
* us to check for conditions where we can complete a water level loop and reach any level from any level before we know
* for sure we have real access. MIDDLE_EMBLEM always requires low water, so FROM_LOW is implied in
* LOGIC_WATER_COULD_MIDDLE.
@@ -1838,7 +1838,7 @@ uint16_t Logic::FireTimer() {
}
// Tunic is not required if you are using irons to do something that a simple gold scale dive could do, and you are not
// in water temple. (celing swimming and long walks through water do not count)
// in water temple. (ceiling swimming and long walks through water do not count)
uint16_t Logic::WaterTimer() {
if (CanUse(RG_ZORA_TUNIC)) {
return UINT16_MAX;
@@ -1172,13 +1172,13 @@ extern "C" u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) {
// This isn't technically necessary, because keys will no longer be consumed,
// but for the player's sanity we display that they _have_ keys.
for (Rando::DungeonInfo* dungeon : Rando::Context::GetInstance()->GetDungeons()->GetDungeonList()) {
uint8_t keys = Rando::GetSceneSmallKeyMax(dungeon->GetScene());
if (keys > 0) {
gSaveContext.inventory.dungeonKeys[dungeon->GetScene()] = keys;
if (Rando::GetSceneSmallKeyMax(dungeon->GetScene()) > 0) {
gSaveContext.inventory.dungeonKeys[dungeon->GetScene()] =
Rando::GetSceneUnspentSmallKeyMax(&gSaveContext, dungeon->GetScene());
}
}
gSaveContext.inventory.dungeonKeys[SCENE_THIEVES_HIDEOUT] =
Rando::GetSceneSmallKeyMax(SCENE_THIEVES_HIDEOUT);
Rando::GetSceneUnspentSmallKeyMax(&gSaveContext, SCENE_THIEVES_HIDEOUT);
} else if (item >= RG_KEATON_MASK && item <= RG_MASK_OF_TRUTH) {
if (!ChildTradeSlotOccupied()) {
INV_CONTENT(ITEM_TRADE_CHILD) = (int)ITEM_MASK_KEATON + (item - RG_KEATON_MASK);
@@ -1351,7 +1351,8 @@ extern "C" u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) {
if ((item >= RG_FOREST_TEMPLE_KEY_RING) && (item <= RG_TREASURE_GAME_KEY_RING)) {
u8 keysOnRing = Rando::GetSceneSmallKeyMax((SceneID)mapIndex);
gSaveContext.ship.stats.dungeonKeys[mapIndex] = keysOnRing;
gSaveContext.inventory.dungeonKeys[mapIndex] = keysOnRing;
gSaveContext.inventory.dungeonKeys[mapIndex] =
Rando::GetSceneUnspentSmallKeyMax(&gSaveContext, (SceneID)mapIndex);
return Return_Item_Entry(giEntry, RG_NONE);
}
@@ -1401,6 +1402,9 @@ extern "C" u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) {
}
break;
case RG_TRIFORCE:
gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED] =
static_cast<u32>(GAMEPLAYSTAT_TOTAL_TIME);
gSaveContext.ship.stats.gameComplete = 1;
GameInteractor_SetTriforceHuntCreditsWarpActive(true);
break;
case RG_TRIFORCE_PIECE:
@@ -1258,6 +1258,7 @@ RANDO_ENUM_ITEM(RHT_GANONS_SOUL_DUNGEONS_HINT)
RANDO_ENUM_ITEM(RHT_GANONS_SOUL_TOKENS_HINT)
RANDO_ENUM_ITEM(RHT_GANONS_SOUL_TRIFORCE_PIECES_HINT)
// Wincon
RANDO_ENUM_ITEM(RHT_WINCON_DEFEAT_GANON_HINT)
RANDO_ENUM_ITEM(RHT_WINCON_ANYWHERE_HINT)
RANDO_ENUM_ITEM(RHT_WINCON_STONES_HINT)
RANDO_ENUM_ITEM(RHT_WINCON_MEDALLIONS_HINT)
@@ -13,7 +13,7 @@
// Randomizer tricks and glitches (beta) based on knowledge of what is achievable in Ship of Harkinian randomizer,
// excludes Item manipulation (e.g. RBA/GIM/SRM) and Wrong Warping and Ganon BK Skip
// TODO Fill and alphabetize (area based tricks must have area in name and come alphetized after general tricks)
// TODO Fill and alphabetize (area based tricks must have area in name and come alphabetized after general tricks)
// TODO test commented out tricks to see if consistent with console on SoH
// TODO add area specific tricks
RANDO_ENUM_BEGIN(RandomizerTrick)
@@ -1,5 +1,7 @@
#include <algorithm>
#include <array>
#include <map>
#include <span>
#include <string>
#include <vector>
@@ -280,65 +282,83 @@ std::vector<ItemTrackerItem> silverRupeeItems = {
ITEM_TRACKER_RG_CUSTOM(RG_GANONS_CASTLE_MQ_SILVER_SHADOW, "ShaTri", ITEM_RUPEE_SILVER, 0, DrawItem),
};
std::vector<ItemTrackerDungeon> itemTrackerDungeonsWithMapsHorizontal = {
{ SCENE_DEKU_TREE, { ITEM_DUNGEON_MAP, ITEM_COMPASS } },
{ SCENE_DODONGOS_CAVERN, { ITEM_DUNGEON_MAP, ITEM_COMPASS } },
{ SCENE_JABU_JABU, { ITEM_DUNGEON_MAP, ITEM_COMPASS } },
{ SCENE_FOREST_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } },
{ SCENE_FIRE_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } },
{ SCENE_WATER_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } },
{ SCENE_SPIRIT_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } },
{ SCENE_SHADOW_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } },
{ SCENE_INSIDE_GANONS_CASTLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } },
{ SCENE_BOTTOM_OF_THE_WELL, { ITEM_KEY_SMALL, ITEM_DUNGEON_MAP, ITEM_COMPASS } },
{ SCENE_ICE_CAVERN, { ITEM_DUNGEON_MAP, ITEM_COMPASS } },
{ SCENE_GERUDO_TRAINING_GROUND, { ITEM_KEY_SMALL } },
};
// Dungeon items stacked top to bottom in one grid column, scene in data. Unused slots have no drawFunc
using ItemTrackerDungeonColumn = std::array<ItemTrackerItem, 4>;
std::vector<ItemTrackerDungeon> itemTrackerDungeonsHorizontal = {
{ SCENE_FOREST_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } },
{ SCENE_FIRE_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } },
{ SCENE_WATER_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } },
{ SCENE_SPIRIT_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } },
{ SCENE_SHADOW_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } },
{ SCENE_INSIDE_GANONS_CASTLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } },
{ SCENE_BOTTOM_OF_THE_WELL, { ITEM_KEY_SMALL } },
{ SCENE_GERUDO_TRAINING_GROUND, { ITEM_KEY_SMALL } },
};
static ItemTrackerItem Key(SceneID scene) {
return ITEM_TRACKER_ITEM(ITEM_KEY_SMALL, "", static_cast<uint32_t>(scene), DrawDungeonItem);
}
static ItemTrackerItem Boss(SceneID scene) {
return ITEM_TRACKER_ITEM(ITEM_KEY_BOSS, "", static_cast<uint32_t>(scene), DrawDungeonItem);
}
static ItemTrackerItem Map(SceneID scene) {
return ITEM_TRACKER_ITEM(ITEM_DUNGEON_MAP, "", static_cast<uint32_t>(scene), DrawDungeonItem);
}
static ItemTrackerItem Compass(SceneID scene) {
return ITEM_TRACKER_ITEM(ITEM_COMPASS, "", static_cast<uint32_t>(scene), DrawDungeonItem);
}
std::vector<ItemTrackerDungeon> itemTrackerDungeonsWithMapsCompact = {
{ SCENE_FOREST_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } },
{ SCENE_FIRE_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } },
{ SCENE_WATER_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } },
{ SCENE_SPIRIT_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } },
{ SCENE_SHADOW_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS, ITEM_DUNGEON_MAP, ITEM_COMPASS } },
{ SCENE_BOTTOM_OF_THE_WELL, { ITEM_KEY_SMALL, ITEM_DUNGEON_MAP, ITEM_COMPASS } },
{ SCENE_DEKU_TREE, { ITEM_DUNGEON_MAP, ITEM_COMPASS } },
{ SCENE_DODONGOS_CAVERN, { ITEM_DUNGEON_MAP, ITEM_COMPASS } },
{ SCENE_JABU_JABU, { ITEM_DUNGEON_MAP, ITEM_COMPASS } },
{ SCENE_ICE_CAVERN, { ITEM_DUNGEON_MAP, ITEM_COMPASS } },
{ SCENE_INSIDE_GANONS_CASTLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } },
{ SCENE_GERUDO_TRAINING_GROUND, { ITEM_KEY_SMALL } },
};
static const auto itemTrackerDungeonsWithMapsHorizontal = std::to_array<ItemTrackerDungeonColumn>({
{ Map(SCENE_DEKU_TREE), Compass(SCENE_DEKU_TREE) },
{ Map(SCENE_DODONGOS_CAVERN), Compass(SCENE_DODONGOS_CAVERN) },
{ Map(SCENE_JABU_JABU), Compass(SCENE_JABU_JABU) },
{ Key(SCENE_FOREST_TEMPLE), Boss(SCENE_FOREST_TEMPLE), Map(SCENE_FOREST_TEMPLE), Compass(SCENE_FOREST_TEMPLE) },
{ Key(SCENE_FIRE_TEMPLE), Boss(SCENE_FIRE_TEMPLE), Map(SCENE_FIRE_TEMPLE), Compass(SCENE_FIRE_TEMPLE) },
{ Key(SCENE_WATER_TEMPLE), Boss(SCENE_WATER_TEMPLE), Map(SCENE_WATER_TEMPLE), Compass(SCENE_WATER_TEMPLE) },
{ Key(SCENE_SPIRIT_TEMPLE), Boss(SCENE_SPIRIT_TEMPLE), Map(SCENE_SPIRIT_TEMPLE), Compass(SCENE_SPIRIT_TEMPLE) },
{ Key(SCENE_SHADOW_TEMPLE), Boss(SCENE_SHADOW_TEMPLE), Map(SCENE_SHADOW_TEMPLE), Compass(SCENE_SHADOW_TEMPLE) },
{ Key(SCENE_INSIDE_GANONS_CASTLE), Boss(SCENE_GANONS_TOWER) },
{ Key(SCENE_BOTTOM_OF_THE_WELL), Map(SCENE_BOTTOM_OF_THE_WELL), Compass(SCENE_BOTTOM_OF_THE_WELL) },
{ Map(SCENE_ICE_CAVERN), Compass(SCENE_ICE_CAVERN) },
{ Key(SCENE_GERUDO_TRAINING_GROUND), Key(SCENE_THIEVES_HIDEOUT), Key(SCENE_TREASURE_BOX_SHOP) },
});
std::vector<ItemTrackerDungeon> itemTrackerDungeonsCompact = {
{ SCENE_FOREST_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } },
{ SCENE_FIRE_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } },
{ SCENE_WATER_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } },
{ SCENE_SPIRIT_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } },
{ SCENE_SHADOW_TEMPLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } },
{ SCENE_INSIDE_GANONS_CASTLE, { ITEM_KEY_SMALL, ITEM_KEY_BOSS } },
{ SCENE_BOTTOM_OF_THE_WELL, { ITEM_KEY_SMALL } },
{ SCENE_GERUDO_TRAINING_GROUND, { ITEM_KEY_SMALL } },
{ SCENE_THIEVES_HIDEOUT, { ITEM_KEY_SMALL } },
};
static const auto itemTrackerDungeonsHorizontal = std::to_array<ItemTrackerDungeonColumn>({
{ Key(SCENE_FOREST_TEMPLE), Boss(SCENE_FOREST_TEMPLE) },
{ Key(SCENE_FIRE_TEMPLE), Boss(SCENE_FIRE_TEMPLE) },
{ Key(SCENE_WATER_TEMPLE), Boss(SCENE_WATER_TEMPLE) },
{ Key(SCENE_SPIRIT_TEMPLE), Boss(SCENE_SPIRIT_TEMPLE) },
{ Key(SCENE_SHADOW_TEMPLE), Boss(SCENE_SHADOW_TEMPLE) },
{ Key(SCENE_INSIDE_GANONS_CASTLE), Boss(SCENE_GANONS_TOWER) },
{ Key(SCENE_BOTTOM_OF_THE_WELL), Key(SCENE_TREASURE_BOX_SHOP) },
{ Key(SCENE_GERUDO_TRAINING_GROUND), Key(SCENE_THIEVES_HIDEOUT) },
});
static const auto itemTrackerDungeonsWithMapsCompact = std::to_array<ItemTrackerDungeonColumn>({
{ Key(SCENE_FOREST_TEMPLE), Boss(SCENE_FOREST_TEMPLE), Map(SCENE_FOREST_TEMPLE), Compass(SCENE_FOREST_TEMPLE) },
{ Key(SCENE_FIRE_TEMPLE), Boss(SCENE_FIRE_TEMPLE), Map(SCENE_FIRE_TEMPLE), Compass(SCENE_FIRE_TEMPLE) },
{ Key(SCENE_WATER_TEMPLE), Boss(SCENE_WATER_TEMPLE), Map(SCENE_WATER_TEMPLE), Compass(SCENE_WATER_TEMPLE) },
{ Key(SCENE_SPIRIT_TEMPLE), Boss(SCENE_SPIRIT_TEMPLE), Map(SCENE_SPIRIT_TEMPLE), Compass(SCENE_SPIRIT_TEMPLE) },
{ Key(SCENE_SHADOW_TEMPLE), Boss(SCENE_SHADOW_TEMPLE), Map(SCENE_SHADOW_TEMPLE), Compass(SCENE_SHADOW_TEMPLE) },
{ Key(SCENE_BOTTOM_OF_THE_WELL), Map(SCENE_BOTTOM_OF_THE_WELL), Compass(SCENE_BOTTOM_OF_THE_WELL),
Key(SCENE_TREASURE_BOX_SHOP) },
{ Map(SCENE_DEKU_TREE), Compass(SCENE_DEKU_TREE) },
{ Map(SCENE_DODONGOS_CAVERN), Compass(SCENE_DODONGOS_CAVERN) },
{ Map(SCENE_JABU_JABU), Compass(SCENE_JABU_JABU) },
{ Map(SCENE_ICE_CAVERN), Compass(SCENE_ICE_CAVERN) },
{ Key(SCENE_INSIDE_GANONS_CASTLE), Boss(SCENE_GANONS_TOWER) },
{ Key(SCENE_GERUDO_TRAINING_GROUND), Key(SCENE_THIEVES_HIDEOUT) },
});
static const auto itemTrackerDungeonsCompact = std::to_array<ItemTrackerDungeonColumn>({
{ Key(SCENE_FOREST_TEMPLE), Boss(SCENE_FOREST_TEMPLE) },
{ Key(SCENE_FIRE_TEMPLE), Boss(SCENE_FIRE_TEMPLE) },
{ Key(SCENE_WATER_TEMPLE), Boss(SCENE_WATER_TEMPLE) },
{ Key(SCENE_SPIRIT_TEMPLE), Boss(SCENE_SPIRIT_TEMPLE) },
{ Key(SCENE_SHADOW_TEMPLE), Boss(SCENE_SHADOW_TEMPLE) },
{ Key(SCENE_INSIDE_GANONS_CASTLE), Boss(SCENE_GANONS_TOWER) },
{ Key(SCENE_BOTTOM_OF_THE_WELL) },
{ Key(SCENE_GERUDO_TRAINING_GROUND) },
{ Key(SCENE_THIEVES_HIDEOUT) },
{ Key(SCENE_TREASURE_BOX_SHOP) },
});
std::map<uint16_t, std::string> itemTrackerDungeonShortNames = {
{ SCENE_FOREST_TEMPLE, "FRST" }, { SCENE_FIRE_TEMPLE, "FIRE" }, { SCENE_WATER_TEMPLE, "WATR" },
{ SCENE_SPIRIT_TEMPLE, "SPRT" }, { SCENE_SHADOW_TEMPLE, "SHDW" }, { SCENE_BOTTOM_OF_THE_WELL, "BOTW" },
{ SCENE_DEKU_TREE, "DEKU" }, { SCENE_DODONGOS_CAVERN, "DCVN" }, { SCENE_JABU_JABU, "JABU" },
{ SCENE_ICE_CAVERN, "ICE" }, { SCENE_INSIDE_GANONS_CASTLE, "GANON" }, { SCENE_GERUDO_TRAINING_GROUND, "GTG" },
{ SCENE_THIEVES_HIDEOUT, "HIDE" },
{ SCENE_THIEVES_HIDEOUT, "HIDE" }, { SCENE_TREASURE_BOX_SHOP, "TCG" },
};
std::vector<ItemTrackerItem> dungeonItems = {};
@@ -1439,55 +1459,56 @@ void DrawItemsInACircle(std::vector<ItemTrackerItem> items) {
}
}
static bool ShowDungeonItem(const ItemTrackerItem& item) {
if (item.data == SCENE_TREASURE_BOX_SHOP) {
return IS_RANDO && RAND_GET_OPTION(RSK_SHUFFLE_CHEST_MINIGAME);
}
if (item.data == SCENE_THIEVES_HIDEOUT) {
return !IS_RANDO || RAND_GET_OPTION(RSK_GERUDO_FORTRESS).IsNot(RO_GF_CARPENTERS_FREE);
}
return true;
}
/**
* GetDungeonItemsVector
* Loops over dungeons and creates vectors of items in the correct order
* to then call DrawItemsInRows
* Lays out columns in blocks of `columns` wide, each block as tall as its tallest column,
* and flattens them row by row for DrawItemsInRows
*/
std::vector<ItemTrackerItem> GetDungeonItemsVector(std::vector<ItemTrackerDungeon> dungeons, size_t columns = 6) {
std::vector<ItemTrackerItem> dungeonItems = {};
size_t rowCount = 0;
for (size_t i = 0; i < dungeons.size(); i++) {
if (dungeons[i].items.size() > rowCount)
rowCount = static_cast<int32_t>(dungeons[i].items.size());
}
for (size_t i = 0; i < rowCount; i++) {
for (size_t j = 0; j < std::min(dungeons.size(), columns); j++) {
if (dungeons[j].items.size() > i) {
switch (dungeons[j].items[i]) {
case ITEM_KEY_SMALL:
dungeonItems.push_back(ITEM_TRACKER_ITEM(ITEM_KEY_SMALL, "", dungeons[j].id, DrawDungeonItem));
break;
case ITEM_KEY_BOSS:
// Swap Ganon's Castle boss key to the right scene ID manually
if (dungeons[j].id == SCENE_INSIDE_GANONS_CASTLE) {
dungeonItems.push_back(
ITEM_TRACKER_ITEM(ITEM_KEY_BOSS, "", SCENE_GANONS_TOWER, DrawDungeonItem));
} else {
dungeonItems.push_back(
ITEM_TRACKER_ITEM(ITEM_KEY_BOSS, "", dungeons[j].id, DrawDungeonItem));
}
break;
case ITEM_DUNGEON_MAP:
dungeonItems.push_back(
ITEM_TRACKER_ITEM(ITEM_DUNGEON_MAP, "", dungeons[j].id, DrawDungeonItem));
break;
case ITEM_COMPASS:
dungeonItems.push_back(ITEM_TRACKER_ITEM(ITEM_COMPASS, "", dungeons[j].id, DrawDungeonItem));
break;
}
} else {
dungeonItems.push_back(ITEM_TRACKER_ITEM(ITEM_NONE, "", 0, DrawItem));
std::vector<ItemTrackerItem> GetDungeonItemsVector(std::span<const ItemTrackerDungeonColumn> layout,
size_t columns = 6) {
struct ShownColumn {
ItemTrackerDungeonColumn items;
size_t size = 0;
};
std::vector<ShownColumn> shown;
for (const auto& column : layout) {
ShownColumn kept;
for (const auto& item : column) {
if (item.drawFunc != nullptr && ShowDungeonItem(item)) {
kept.items[kept.size++] = item;
}
}
if (kept.size > 0) {
shown.push_back(kept);
}
}
if (dungeons.size() > columns) {
std::vector<ItemTrackerItem> nextDungeonItems =
GetDungeonItemsVector(std::vector<ItemTrackerDungeon>(dungeons.begin() + columns, dungeons.end()), columns);
dungeonItems.insert(dungeonItems.end(), nextDungeonItems.begin(), nextDungeonItems.end());
std::vector<ItemTrackerItem> dungeonItems = {};
for (size_t start = 0; start < shown.size(); start += columns) {
size_t end = std::min(start + columns, shown.size());
size_t rowCount = 0;
for (size_t j = start; j < end; j++) {
rowCount = std::max(rowCount, shown[j].size);
}
for (size_t i = 0; i < rowCount; i++) {
for (size_t j = start; j < end; j++) {
if (i < shown[j].size) {
dungeonItems.push_back(shown[j].items[i]);
} else {
dungeonItems.push_back(ITEM_TRACKER_ITEM(ITEM_NONE, "", 0, DrawItem));
}
}
}
}
return dungeonItems;
@@ -1563,18 +1584,12 @@ void UpdateVectors() {
SECTION_DISPLAY_SEPARATE) {
if (CVarGetInteger(CVAR_TRACKER_ITEM("DungeonItems.DisplayMaps"), 1)) {
dungeonItems = GetDungeonItemsVector(itemTrackerDungeonsWithMapsHorizontal, 12);
// Manually adding Thieves Hideout to an open spot so we don't get an additional row for one item
dungeonItems[23] = ITEM_TRACKER_ITEM(ITEM_KEY_SMALL, "", SCENE_THIEVES_HIDEOUT, DrawDungeonItem);
} else {
// Manually adding Thieves Hideout to an open spot so we don't get an additional row for one item
dungeonItems = GetDungeonItemsVector(itemTrackerDungeonsHorizontal, 8);
dungeonItems[15] = ITEM_TRACKER_ITEM(ITEM_KEY_SMALL, "", SCENE_THIEVES_HIDEOUT, DrawDungeonItem);
}
} else {
if (CVarGetInteger(CVAR_TRACKER_ITEM("DungeonItems.DisplayMaps"), 1)) {
dungeonItems = GetDungeonItemsVector(itemTrackerDungeonsWithMapsCompact);
// Manually adding Thieves Hideout to an open spot so we don't get an additional row for one item
dungeonItems[35] = ITEM_TRACKER_ITEM(ITEM_KEY_SMALL, "", SCENE_THIEVES_HIDEOUT, DrawDungeonItem);
} else {
dungeonItems = GetDungeonItemsVector(itemTrackerDungeonsCompact);
}
@@ -72,11 +72,6 @@ static std::array<const char*, 16> itemTrackerWindowIDs = { "Item Tracker",
"Total Checks" };
void ItemTracker_LoadFromPreset(const nlohmann::json& trackerInfo);
typedef struct ItemTrackerDungeon {
uint32_t id;
std::vector<uint32_t> items;
} ItemTrackerDungeon;
class ItemTrackerSettingsWindow final : public Ship::GuiWindow {
public:
using GuiWindow::GuiWindow;
+4 -4
View File
@@ -447,7 +447,7 @@ void Settings::CreateOptions() {
OPT_U8(RSK_MQ_DUNGEON_RANDOM, {"None", "Set Number", "Random", "Selection Only"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("MQDungeons"), WIDGET_CVAR_COMBOBOX, RO_MQ_DUNGEONS_NONE, false, nullptr, IMFLAG_NONE);
OPT_CALLBACK(RSK_MQ_DUNGEON_RANDOM, {
switch (CVarGetInteger(CVAR_RANDOMIZER_SETTING("MQDungeons"), RO_MQ_DUNGEONS_NONE)) {
// If No MQ Dungeons, add a separator after the combobx and hide
// If No MQ Dungeons, add a separator after the combobox and hide
// the count slider and the toggle for individual dungeon selections.
case RO_MQ_DUNGEONS_NONE:
mOptions[RSK_MQ_DUNGEON_COUNT].Hide();
@@ -1099,7 +1099,7 @@ void Settings::CreateOptions() {
mOptions[RSK_LOACH_HINT].Enable();
} else {
mOptions[RSK_LOACH_HINT].Disable(
"Loach hint is only avaliable with \"Fishsanity\" set to \"Shuffle only Hyrule Loach\"\nas that's the only "
"Loach hint is only available with \"Fishsanity\" set to \"Shuffle only Hyrule Loach\"\nas that's the only "
"setting where you present the loach to the fishing pond owner.");
}
});
@@ -2771,7 +2771,7 @@ void Context::FinalizeSettings(const std::set<RandomizerCheck>& excludedLocation
mqSet += 1;
break;
case RO_MQ_SET_RANDOM:
// 50% per dungeon, rolled separatly so people can either have a linear distribtuion
// 50% per dungeon, rolled separately so people can either have a linear distribution
// or a bell curve for the number of MQ dungeons per seed.
if (Random(0, 2)) {
dungeon->SetMQ();
@@ -2815,7 +2815,7 @@ void Context::FinalizeSettings(const std::set<RandomizerCheck>& excludedLocation
}
// otherwise, every dungeon is possible
} else {
// if count is MAX_MQ_DUNGEON_COUNT, we know everything is MQ, so can skip some setps and not set Known
// if count is MAX_MQ_DUNGEON_COUNT, we know everything is MQ, so can skip some steps and not set Known
if (mOptions[RSK_MQ_DUNGEON_RANDOM].Is(RO_MQ_DUNGEONS_SET_NUMBER) && mqCount == MAX_MQ_DUNGEON_COUNT) {
randMQOption.resize(MAX_MQ_DUNGEON_COUNT);
for (int i = 0; i < MAX_MQ_DUNGEON_COUNT; i++) {
+20 -1
View File
@@ -318,6 +318,18 @@ bool Extractor::GetRomPathFromBox() {
}
mCurrentRomPath = nameBuffer;
#else
if (!pfd::settings::available()) {
std::string msg =
"No file picker was found on your system. Ship of Harkinian needs one of these to select a ROM:\n\n"
" - GNOME, Xfce, Cinnamon and other GTK desktops: install \"zenity\"\n"
" - KDE Plasma: install \"kdialog\"\n\n"
"On other distributions or window managers, search your package manager.\n\n"
"Alternatively, place your ROM in:\n" +
mSearchPath + "\nand restart.";
ShowErrorBox("File Picker Unavailable", msg.c_str());
return false;
}
auto selection = pfd::open_file("Select a file", mSearchPath, { "N64 Roms", "*.z64 *.n64 *.v64" }).result();
if (selection.empty()) {
@@ -354,7 +366,7 @@ size_t Extractor::GetCurRomSize() const {
bool Extractor::ValidateAndFixRom() {
// The MQ debug rom sometimes has the header patched to look like a US rom.
// Check the crc as if it were changed back, but leave mRomData as the original dump for torch.
// Check the crc as if it were changed back. CallTorch changes it back for real.
const uint8_t region = mRomData[0x3E];
if (GetRomVerCrc() == OOT_PAL_GC_MQ_DBG) {
mRomData[0x3E] = 'P';
@@ -654,6 +666,13 @@ bool Extractor::CallTorch(std::string installPath, std::string exportdir, std::a
*totalExtract = SohTorch::CountAssetFiles(srcDir + "/" + GetTorchVersionDir());
*extractCount = 0;
// MQ debug dumps only hold 54MB of data, the rest is dump-specific padding, and some have
// the region byte changed. Trim and restore it so every dump matches the one config.yml hash.
if (GetRomVerCrc() == OOT_PAL_GC_MQ_DBG && mRomData.size() == MB64) {
mRomData.resize(MB54);
mRomData[0x3E] = 'P';
}
// config.yml decides whether this is oot.o2r or oot-mq.o2r.
std::string archiveName = SohTorch::Extract(std::move(mRomData), srcDir, tempdir, portVersion, extractCount);
bool success = !archiveName.empty();
+6
View File
@@ -254,4 +254,10 @@ void DummyPlayer_Destroy(Actor* actor, PlayState* play) {
// asserts. Set the id back to ACTOR_PLAYER so that `numLoaded` will be decremented
// correctly.
actor->id = ACTOR_PLAYER;
// Drop client's pointer so packet handlers don't use after free
uint32_t clientId = Anchor::Instance->GetDummyPlayerClientId(actor);
if (Anchor::Instance->clients.contains(clientId) && Anchor::Instance->clients[clientId].player == (Player*)actor) {
Anchor::Instance->clients[clientId].player = nullptr;
}
}
@@ -104,18 +104,50 @@ inline void from_json(const json& j, SohStats& sohStats) {
j.at("fileCreatedAt").get_to(sohStats.fileCreatedAt);
}
#define SILVER_RUPEE_FIELDS(X) \
X(silverShadowBlades) \
X(silverShadowPit) \
X(silverShadowSpikes) \
X(silverSpiritChild) \
X(silverSpiritSun) \
X(silverSpiritBoulders) \
X(silverBotw) \
X(silverIceCavernBlades) \
X(silverIceCavernBlock) \
X(silverGtgSlope) \
X(silverGtgLava) \
X(silverGtgWater) \
X(silverGanonLight) \
X(silverGanonForest) \
X(silverGanonFire) \
X(silverGanonSpirit) \
X(silverMqDodongosCavern) \
X(silverMqShadowInvisibleBlades) \
X(silverMqSpiritLobby) \
X(silverMqSpiritBigWall) \
X(silverMqGanonWater) \
X(silverMqGanonShadow)
inline void to_json(json& j, const ShipRandomizerSaveContextData& shipRandomizerSaveContextData) {
j = json{
{ "triforcePiecesCollected", shipRandomizerSaveContextData.triforcePiecesCollected },
{ "bombchuUpgradeLevel", shipRandomizerSaveContextData.bombchuUpgradeLevel },
};
#define SILVER_TO_JSON(field) j[#field] = shipRandomizerSaveContextData.field;
SILVER_RUPEE_FIELDS(SILVER_TO_JSON)
#undef SILVER_TO_JSON
}
inline void from_json(const json& j, ShipRandomizerSaveContextData& shipRandomizerSaveContextData) {
j.at("triforcePiecesCollected").get_to(shipRandomizerSaveContextData.triforcePiecesCollected);
j.at("bombchuUpgradeLevel").get_to(shipRandomizerSaveContextData.bombchuUpgradeLevel);
#define SILVER_FROM_JSON(field) shipRandomizerSaveContextData.field = j.value(#field, (s8)0);
SILVER_RUPEE_FIELDS(SILVER_FROM_JSON)
#undef SILVER_FROM_JSON
}
#undef SILVER_RUPEE_FIELDS
inline void to_json(json& j, const ShipQuestSpecificSaveContextData& shipQuestSpecificSaveContextData) {
j = json{
{ "randomizer", shipQuestSpecificSaveContextData.randomizer },
+43 -15
View File
@@ -430,7 +430,12 @@ void OTRGlobals::RunExtract(int argc, char* argv[]) {
Extractor extract;
PromptSteps promptStep = PS_FILE_CHECK;
bool generatedIsMQ = false;
// Whether generatedIsMQ is set, i.e. the first prompted rom extracted fine
bool generatedFirst = false;
bool lastExtractFailed = false;
std::atomic<size_t> extractCount = 0, totalExtract = 0;
// Set by the extraction thread, shown as a popup by the main thread once the task finishes
std::atomic<bool> extractFailed = false;
std::string installPath = std::filesystem::absolute(Ship::Context::GetAppBundlePath()).string();
std::string dataPath = std::filesystem::absolute(Ship::Context::GetAppDirectoryPath(appShortName)).string();
@@ -613,15 +618,17 @@ void OTRGlobals::RunExtract(int argc, char* argv[]) {
std::string msg = "Archive for current ROM, " + archive + ", already exists.\nExtract again?";
SohGui::RegisterPopup("Confirm Re-extract", msg.c_str(), "Yes", "No", [&]() {
extractionTask = threadPool->submit_task([&]() -> void {
extract.CallTorch(installPath, Ship::Context::GetAppDirectoryPath(appShortName),
&extractCount, &totalExtract);
extractFailed =
!extract.CallTorch(installPath, Ship::Context::GetAppDirectoryPath(appShortName),
&extractCount, &totalExtract);
extractCount = totalExtract = 0;
});
});
} else {
extractionTask = threadPool->submit_task([&]() -> void {
extract.CallTorch(installPath, Ship::Context::GetAppDirectoryPath(appShortName),
&extractCount, &totalExtract);
extractFailed =
!extract.CallTorch(installPath, Ship::Context::GetAppDirectoryPath(appShortName),
&extractCount, &totalExtract);
extractCount = totalExtract = 0;
});
}
@@ -677,9 +684,13 @@ void OTRGlobals::RunExtract(int argc, char* argv[]) {
continue;
}
extractionTask = threadPool->submit_task([&]() -> void {
extract.CallTorch(installPath, Ship::Context::GetAppDirectoryPath(appShortName),
&extractCount, &totalExtract);
generatedIsMQ = extract.IsMasterQuest();
extractFailed =
!extract.CallTorch(installPath, Ship::Context::GetAppDirectoryPath(appShortName),
&extractCount, &totalExtract);
if (!extractFailed) {
generatedIsMQ = extract.IsMasterQuest();
generatedFirst = true;
}
promptStep = PS_SECOND;
extractCount = 0;
totalExtract = 0;
@@ -687,17 +698,28 @@ void OTRGlobals::RunExtract(int argc, char* argv[]) {
continue;
}
case PS_SECOND: {
// Keep asking until the user says no, so a failed or cancelled rom can be retried
SohGui::RegisterPopup(
"Extraction Complete", "ROM Extracted. Extract another?", "Yes", "No",
lastExtractFailed ? "Extract Another" : "Extraction Complete",
lastExtractFailed ? "Try extracting another ROM?" : "ROM Extracted. Extract another?",
"Yes", "No",
[&]() {
if (!extract.ManuallySearchForRomMatchingType(generatedIsMQ ? RomSearchMode::Vanilla
: RomSearchMode::MQ)) {
extractStep = ES_VERIFY;
} else {
RomSearchMode mode = RomSearchMode::Both;
if (generatedFirst) {
mode = generatedIsMQ ? RomSearchMode::Vanilla : RomSearchMode::MQ;
}
if (extract.ManuallySearchForRomMatchingType(mode)) {
extractionTask = threadPool->submit_task([&]() -> void {
extract.CallTorch(installPath, Ship::Context::GetAppDirectoryPath(appShortName),
&extractCount, &totalExtract);
extractStep = ES_VERIFY;
extractFailed = !extract.CallTorch(
installPath, Ship::Context::GetAppDirectoryPath(appShortName),
&extractCount, &totalExtract);
if (!extractFailed && generatedFirst) {
extractStep = ES_VERIFY;
} else if (!extractFailed) {
// A retry of the first rom worked, so still offer the other type
generatedIsMQ = extract.IsMasterQuest();
generatedFirst = true;
}
extractCount = 0;
totalExtract = 0;
});
@@ -754,6 +776,12 @@ void OTRGlobals::RunExtract(int argc, char* argv[]) {
} catch (const std::exception& e) {
SohGui::RegisterPopup("Extraction Crashed", e.what(), "Close", "", []() { exit(1); });
}
lastExtractFailed = extractFailed;
if (extractFailed) {
SohGui::RegisterPopup("Extraction Failed",
"The ROM could not be extracted. Check the log for details.", "OK", "");
extractFailed = false;
}
extractionTask.reset();
} else {
if (!ImGui::IsPopupOpen("ROM Extraction")) {
+3 -3
View File
@@ -1186,12 +1186,12 @@ void AudioSeq_SequenceChannelProcessScript(SequenceChannel* channel) {
u16 seqId = AudioEditor_GetReplacementSeq(seqPlayer->seqId);
SequenceData sDat = ResourceMgr_LoadSeqByName(sequenceMap[seqId]);
// The game apparantely would sometimes do negative array lookups, the result of which would
// The game apparently would sometimes do negative array lookups, the result of which would
// get rejected by AudioHeap_SearchCaches, never changing the actual fontid.
if (result > sDat.numFonts)
if (result >= sDat.numFonts)
break;
fontId = sDat.fonts[(sDat.numFonts - result - 1)];
fontId = sDat.fonts[sDat.numFonts - result - 1];
}
if (AudioHeap_SearchCaches(FONT_TABLE, CACHE_EITHER, fontId)) {
+2
View File
@@ -54,6 +54,8 @@ int SDL_main(int argc, char* argv[]) {
#endif
// Allow non-ascii characters for Windows
setlocale(LC_ALL, ".UTF8");
// Keep '.' as the decimal point, user locale may use ','
setlocale(LC_NUMERIC, "C");
#else //_WIN32
int main(int argc, char* argv[]) {
+28 -8
View File
@@ -114,6 +114,7 @@ void GetItem_DrawJewelGoron(PlayState* play, s16 drawId);
void GetItem_DrawJewelZora(PlayState* play, s16 drawId);
void GetItem_DrawGenericMusicNote(PlayState* play, s16 drawId);
void GetItem_DrawTriforcePiece(PlayState* play, s16 drawId);
void GetItem_DrawTriforce(PlayState* play, s16 drawId);
void GetItem_DrawFishingPole(PlayState* play, s16 drawId);
typedef struct {
@@ -384,14 +385,15 @@ DrawItemTableEntry sDrawItemTable[] = {
{ GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Generic
{ GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Zelda's Lullaby
{ GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Epona's song
{ GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Saria's song
{ GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Sun's song
{ GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Song of time
{ GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Song of storms
{ GetItem_DrawTriforcePiece, { gTriforcePiece0DL } }, // Triforce Piece
{ GetItem_DrawFishingPole, { gGiFishingPoleDL } }, // Fishing Pole
{ GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Zelda's Lullaby
{ GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Epona's song
{ GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Saria's song
{ GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Sun's song
{ GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Song of time
{ GetItem_DrawGenericMusicNote, { gGiSongNoteDL } }, // Song of storms
{ GetItem_DrawTriforcePiece, { gTriforcePiece0DL } }, // Triforce Piece
{ GetItem_DrawTriforce, { gTriforcePieceCompletedDL } }, // Triforce
{ GetItem_DrawFishingPole, { gGiFishingPoleDL } }, // Fishing Pole
};
/**
@@ -1027,6 +1029,24 @@ void GetItem_DrawTriforcePiece(PlayState* play, s16 drawId) {
CLOSE_DISPS(play->state.gfxCtx);
}
void GetItem_DrawTriforce(PlayState* play, s16 drawId) {
OPEN_DISPS(play->state.gfxCtx);
Gfx_SetupDL_25Opa(play->state.gfxCtx);
Matrix_Scale(0.035f, 0.035f, 0.035f, MTXMODE_APPLY);
uint8_t index = gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected % 3;
Gfx* triforceDL = (Gfx*)gTriforcePieceCompletedDL;
gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(play->state.gfxCtx, (char*)__FILE__, __LINE__),
G_MTX_MODELVIEW | G_MTX_LOAD);
gSPDisplayList(POLY_OPA_DISP++, triforceDL);
CLOSE_DISPS(play->state.gfxCtx);
}
void GetItem_DrawFishingPole(PlayState* play, s16 drawId) {
Vec3f pos;
OPEN_DISPS(play->state.gfxCtx);
+6 -23
View File
@@ -3263,7 +3263,7 @@ void Interface_UpdateMagicBar(PlayState* play) {
break;
case MAGIC_STATE_CONSUME:
if (!Flags_GetRandomizerInf(RAND_INF_HAS_INFINITE_MAGIC_METER)) {
if (GameInteractor_Should(VB_MAGIC_METER_DEPLETE_WHILE_CONSUMING, true)) {
gSaveContext.magic -= 2;
if (gSaveContext.magic <= 0) {
gSaveContext.magic = 0;
@@ -3454,7 +3454,6 @@ void Interface_DrawMagicBar(PlayState* play) {
Color_RGB8 magicbar_yellow = { 250, 250, 0 }; // Magic bar being used
Color_RGB8 magicbar_green = { R_MAGIC_FILL_COLOR(0), R_MAGIC_FILL_COLOR(1),
R_MAGIC_FILL_COLOR(2) }; // Magic bar fill
Color_RGB8 magicbar_blue = { 0, 0, 200 }; // Infinite magic bar
if (CVarGetInteger(CVAR_COSMETIC("Consumable.MagicActive.Changed"), 0)) {
magicbar_yellow = CVarGetColor24(CVAR_COSMETIC("Consumable.MagicActive.Value"), magicbar_yellow);
@@ -3462,9 +3461,7 @@ void Interface_DrawMagicBar(PlayState* play) {
if (CVarGetInteger(CVAR_COSMETIC("Consumable.Magic.Changed"), 0)) {
magicbar_green = CVarGetColor24(CVAR_COSMETIC("Consumable.Magic.Value"), magicbar_green);
}
if (CVarGetInteger("gCosmetics.Consumable_MagicInfinite.Changed", 0)) {
magicbar_blue = CVarGetColor24("gCosmetics.Consumable_MagicInfinite.Value", magicbar_blue);
}
GameInteractor_Should(VB_APPLY_MAGIC_METER_FILL_COLOR, true, &magicbar_green);
OPEN_DISPS(play->state.gfxCtx);
@@ -3591,30 +3588,16 @@ void Interface_DrawMagicBar(PlayState* play) {
// Fill the rest of the bar with the normal magic color
gDPPipeSync(OVERLAY_DISP++);
if (Flags_GetRandomizerInf(RAND_INF_HAS_INFINITE_MAGIC_METER)) {
// Blue magic
gDPSetPrimColor(OVERLAY_DISP++, 0, 0, magicbar_blue.r, magicbar_blue.g, magicbar_blue.b,
interfaceCtx->magicAlpha);
} else {
// Green magic (default)
gDPSetPrimColor(OVERLAY_DISP++, 0, 0, magicbar_green.r, magicbar_green.g, magicbar_green.b,
interfaceCtx->magicAlpha);
}
gDPSetPrimColor(OVERLAY_DISP++, 0, 0, magicbar_green.r, magicbar_green.g, magicbar_green.b,
interfaceCtx->magicAlpha);
gSPWideTextureRectangle(OVERLAY_DISP++, rMagicFillX << 2, (magicBarY + 3) << 2,
(rMagicFillX + gSaveContext.magicTarget) << 2, (magicBarY + 10) << 2,
G_TX_RENDERTILE, 0, 0, 1 << 10, 1 << 10);
} else {
// Fill the whole bar with the normal magic color
if (Flags_GetRandomizerInf(RAND_INF_HAS_INFINITE_MAGIC_METER)) {
// Blue magic
gDPSetPrimColor(OVERLAY_DISP++, 0, 0, magicbar_blue.r, magicbar_blue.g, magicbar_blue.b,
interfaceCtx->magicAlpha);
} else {
// Green magic (default)
gDPSetPrimColor(OVERLAY_DISP++, 0, 0, magicbar_green.r, magicbar_green.g, magicbar_green.b,
interfaceCtx->magicAlpha);
}
gDPSetPrimColor(OVERLAY_DISP++, 0, 0, magicbar_green.r, magicbar_green.g, magicbar_green.b,
interfaceCtx->magicAlpha);
gDPLoadMultiBlock_4b(OVERLAY_DISP++, gMagicMeterFillTex, 0, G_TX_RENDERTILE, G_IM_FMT_I, 16, 16, 0,
G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK,
@@ -918,7 +918,7 @@ void BossGanon_IntroCutscene(BossGanon* this, PlayState* play) {
this->csTimer = 0;
this->csCamFov = 60.0f;
BossGanon_SetIntroCsCamera(this, 12);
if (GameInteractor_Should(VB_PLAY_GANONDORF_INTRO_CS, true, this)) {
if (GameInteractor_Should(VB_PLAY_GANONDORF_INTRO_CS, true, this, 0x70CB)) {
Message_StartTextbox(play, 0x70CB, NULL);
}
}
@@ -942,7 +942,7 @@ void BossGanon_IntroCutscene(BossGanon* this, PlayState* play) {
this->csState = 19;
this->csTimer = 0;
if (GameInteractor_Should(VB_PLAY_GANONDORF_INTRO_CS, true, this)) {
if (GameInteractor_Should(VB_PLAY_GANONDORF_INTRO_CS, true, this, 0x70CC)) {
Message_StartTextbox(play, 0x70CC, NULL);
}
Animation_MorphToPlayOnce(&this->skelAnime, &gGanondorfRaiseHandStartAnim, -5.0f);
@@ -985,7 +985,7 @@ void BossGanon_IntroCutscene(BossGanon* this, PlayState* play) {
}
if ((this->csTimer > 80) && (Message_GetState(&play->msgCtx) == TEXT_STATE_NONE)) {
if (!GameInteractor_Should(VB_PLAY_GANONDORF_INTRO_CS, true, this)) {
if (!GameInteractor_Should(VB_PLAY_GANONDORF_INTRO_CS, true, this, 0)) {
this->timers[2] = 30;
this->csCamAt.x = this->unk_1FC.x - 10.0f;
this->csCamAt.y = this->unk_1FC.y + 30.0f;
+30 -34
View File
@@ -672,43 +672,39 @@ void BossVa_Init(Actor* thisx, PlayState* play2) {
} else {
this->actor.colChkInfo.damageTable = sDamageTable;
sPhase2Timer = 0xFFFF;
if (Flags_GetEventChkInf(EVENTCHKINF_BEGAN_BARINA_BATTLE)) {
if (Randomizer_GetSettingValue(RSK_SHUFFLE_BOSS_SOULS) &&
!Flags_GetRandomizerInf(RAND_INF_BARINADE_SOUL)) {
sCsState = BOSSVA_BATTLE;
} else {
sCsState = INTRO_CALL_BARI;
sDoorState = 100;
Player_SetCsActionWithHaltedActors(play, &this->actor, 1);
play->envCtx.screenFillColor[0] = 0xDC;
play->envCtx.screenFillColor[1] = 0xDC;
play->envCtx.screenFillColor[2] = 0xBE;
play->envCtx.screenFillColor[3] = 0xD2;
func_80064520(play, &play->csCtx);
sSubCamId = Play_CreateSubCamera(play);
Play_ChangeCameraStatus(play, CAM_ID_MAIN, CAM_STAT_WAIT);
Play_ChangeCameraStatus(play, sSubCamId, CAM_STAT_ACTIVE);
sSubCamEyeNext.x = sSubCamEye.x = 140.0f;
sSubCamEyeNext.y = sSubCamEye.y = 205.0f;
sSubCamEyeNext.z = sSubCamEye.z = -20.0f;
sSubCamAtNext.x = sSubCamAt.x = 10.0f;
sSubCamAtNext.y = sSubCamAt.y = 50.0f;
sSubCamAtNext.z = sSubCamAt.z = -220.0f;
Play_CameraSetAtEye(play, sSubCamId, &sSubCamAt, &sSubCamEye);
this->timer = 20;
if (GameInteractor_Should(VB_BARINADE_PLAY_REMATCH_INTRO,
Flags_GetEventChkInf(EVENTCHKINF_BEGAN_BARINA_BATTLE), this)) {
sCsState = INTRO_CALL_BARI;
sDoorState = 100;
Player_SetCsActionWithHaltedActors(play, &this->actor, 1);
play->envCtx.screenFillColor[0] = 0xDC;
play->envCtx.screenFillColor[1] = 0xDC;
play->envCtx.screenFillColor[2] = 0xBE;
play->envCtx.screenFillColor[3] = 0xD2;
func_80064520(play, &play->csCtx);
sSubCamId = Play_CreateSubCamera(play);
Play_ChangeCameraStatus(play, CAM_ID_MAIN, CAM_STAT_WAIT);
Play_ChangeCameraStatus(play, sSubCamId, CAM_STAT_ACTIVE);
sSubCamEyeNext.x = sSubCamEye.x = 140.0f;
sSubCamEyeNext.y = sSubCamEye.y = 205.0f;
sSubCamEyeNext.z = sSubCamEye.z = -20.0f;
sSubCamAtNext.x = sSubCamAt.x = 10.0f;
sSubCamAtNext.y = sSubCamAt.y = 50.0f;
sSubCamAtNext.z = sSubCamAt.z = -220.0f;
Play_CameraSetAtEye(play, sSubCamId, &sSubCamAt, &sSubCamEye);
this->timer = 20;
for (i = BOSSVA_BARI_LOWER_5; i >= BOSSVA_BARI_UPPER_1; i--) {
Actor_SpawnAsChild(
&play->actorCtx, &this->actor, play, ACTOR_BOSS_VA,
sInitPosOffsets[i].x + this->actor.world.pos.x,
sInitPosOffsets[i].y + this->actor.world.pos.y,
sInitPosOffsets[i].z + this->actor.world.pos.z, sInitRot[i].x + this->actor.world.rot.x,
sInitRot[i].y + this->actor.world.rot.y, sInitRot[i].z + this->actor.world.rot.z, i);
}
sSubCamAtMaxVelFrac = sSubCamEyeMaxVelFrac = sZeroVec;
for (i = BOSSVA_BARI_LOWER_5; i >= BOSSVA_BARI_UPPER_1; i--) {
Actor_SpawnAsChild(
&play->actorCtx, &this->actor, play, ACTOR_BOSS_VA,
sInitPosOffsets[i].x + this->actor.world.pos.x,
sInitPosOffsets[i].y + this->actor.world.pos.y,
sInitPosOffsets[i].z + this->actor.world.pos.z, sInitRot[i].x + this->actor.world.rot.x,
sInitRot[i].y + this->actor.world.rot.y, sInitRot[i].z + this->actor.world.rot.z, i);
}
sSubCamAtMaxVelFrac = sSubCamEyeMaxVelFrac = sZeroVec;
} else {
sCsState = INTRO_START;
sDoorState = 5;