mirror of
https://github.com/HarbourMasters/Shipwright
synced 2026-06-13 05:57:41 -04:00
Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e93ea5b919 | |||
| b61db77020 | |||
| b8a3998c51 | |||
| 31cbeb929b | |||
| d09cd4ffcd | |||
| a57cdc6f77 | |||
| 5bccc8a340 | |||
| dc4b27d65a | |||
| 0dcd52e5c7 | |||
| e5ad4e6f11 | |||
| e3ee258a92 | |||
| b728e671bf | |||
| 37db034815 | |||
| 5b4d8edf51 | |||
| 1a46d2ec96 | |||
| ea76550fc7 | |||
| ccfa31a245 | |||
| 806398a65b | |||
| 3221d8a988 | |||
| 69681e608f | |||
| 719bb87eda | |||
| bf37645d72 | |||
| 3b65eaa4ef | |||
| 3be7eff02c | |||
| 39dcc0a73c | |||
| 256ab01630 | |||
| 072838613a | |||
| 9c321862ca | |||
| 412b60a02f | |||
| 1876435e98 | |||
| cb71e22a79 | |||
| feb489cab0 | |||
| adb1e46ba9 | |||
| 13ebc31ec7 | |||
| ca38dba192 | |||
| 25eb09180d | |||
| d855742c2f | |||
| 8513fd8800 | |||
| 568813a2a4 | |||
| 7b7b799fad | |||
| 88099e6ebe | |||
| 69ef35d258 | |||
| 4122d8079e | |||
| 40425c8c28 | |||
| 695c05d339 | |||
| 77f2883510 | |||
| 876d512a4b | |||
| 978a219213 | |||
| b6f227961b | |||
| 14b464bab2 | |||
| efc4086c6a | |||
| 972ed22167 | |||
| 5494a81eb1 | |||
| ebea14f297 | |||
| 4729eef7c8 | |||
| 0b3ebd584d | |||
| 1be7533675 | |||
| d1b7edfa7c | |||
| 6fea1821a1 | |||
| 42ba25449e | |||
| d1643aa196 | |||
| 7edf44e170 | |||
| 53dc7f43ba | |||
| 5f139ef311 | |||
| 99c1f23d5b | |||
| 69e03dcc52 | |||
| 4aa6e2ec28 | |||
| 94b650ec67 | |||
| 57269c8e46 | |||
| 5e13570b83 | |||
| 5fe4680a20 | |||
| c439d62137 | |||
| 2cfb0f73dc | |||
| c61c1c0fec |
+1
-1
@@ -6,7 +6,7 @@ set(CMAKE_C_STANDARD 23 CACHE STRING "The C standard to use")
|
||||
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version")
|
||||
|
||||
project(Ship VERSION 9.2.0 LANGUAGES C CXX)
|
||||
project(Ship VERSION 9.2.3 LANGUAGES C CXX)
|
||||
include(CMake/soh-cvars.cmake)
|
||||
include(CMake/lus-cvars.cmake)
|
||||
set(SPDLOG_LEVEL_TRACE 0)
|
||||
|
||||
+1
-1
Submodule libultraship updated: 956f108212...fdcaf63367
@@ -172,6 +172,7 @@
|
||||
"153": "STICK UPGRADE 30",
|
||||
"154": "NUT UPGRADE 30",
|
||||
"155": "NUT UPGRADE 40",
|
||||
"157": "Roc's Feather",
|
||||
"255": "",
|
||||
"256": "Haunted Wasteland",
|
||||
"257": "Gerudos Fortress",
|
||||
|
||||
@@ -172,6 +172,7 @@
|
||||
"153": "AMÉLIORATION BÂTON MOJO 30",
|
||||
"154": "AMÉLIORATION NOIX MOJO 30",
|
||||
"155": "AMÉLIORATION NOIX MOJO 40",
|
||||
"157": "Plume de Roc",
|
||||
"255": "",
|
||||
"256": "Désert Hanté",
|
||||
"257": "Forteresse Gerudo",
|
||||
|
||||
@@ -172,6 +172,7 @@
|
||||
"153": "DEKU-STAB-KAPAZITÄT 30",
|
||||
"154": "DEKU-NUẞ-KAPAZITÄT 30",
|
||||
"155": "DEKU-NUẞ-KAPAZITÄT 40",
|
||||
"157": "Greifenfeder",
|
||||
"255": "",
|
||||
"256": "Gespensterwüste",
|
||||
"257": "Gerudo-Festung",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"gRandoSettings": {
|
||||
"BigPoeTargetCount": 1,
|
||||
"BlueFireArrows": 1,
|
||||
"BombchuBag": 1,
|
||||
"BombchuBag": 2,
|
||||
"BossKeysanity": 5,
|
||||
"ClosedForest": 2,
|
||||
"CuccosToReturn": 1,
|
||||
@@ -21,11 +21,13 @@
|
||||
"LacsRewardCount": 10,
|
||||
"LacsRewardOptions": 1,
|
||||
"LockOverworldDoors": 1,
|
||||
"MedallionLockedTrials": 1,
|
||||
"MixBosses": 1,
|
||||
"MixDungeons": 1,
|
||||
"MixGrottos": 1,
|
||||
"MixInteriors": 1,
|
||||
"MixOverworld": 1,
|
||||
"MixThievesHideout": 1,
|
||||
"MixedEntrances": 1,
|
||||
"RainbowBridge": 7,
|
||||
"ScrubsPrices": 2,
|
||||
@@ -35,12 +37,16 @@
|
||||
"Shuffle100GSReward": 1,
|
||||
"ShuffleAdultTrade": 1,
|
||||
"ShuffleBeanFairies": 1,
|
||||
"ShuffleBeanSouls": 1,
|
||||
"ShuffleBeehives": 1,
|
||||
"ShuffleBossEntrances": 2,
|
||||
"ShuffleBossSouls": 2,
|
||||
"ShuffleBushes": 1,
|
||||
"ShuffleChildWallet": 1,
|
||||
"ShuffleClimb": 1,
|
||||
"ShuffleCows": 1,
|
||||
"ShuffleCrates": 3,
|
||||
"ShuffleCrawl": 1,
|
||||
"ShuffleDekuNutBag": 1,
|
||||
"ShuffleDekuStickBag": 1,
|
||||
"ShuffleDungeonsEntrances": 2,
|
||||
@@ -50,7 +56,9 @@
|
||||
"ShuffleFreestanding": 3,
|
||||
"ShuffleFrogSongRupees": 1,
|
||||
"ShuffleGanonBossKey": 9,
|
||||
"ShuffleGanonTowerEntrance": 1,
|
||||
"ShuffleGerudoToken": 1,
|
||||
"ShuffleGrab": 1,
|
||||
"ShuffleGrass": 3,
|
||||
"ShuffleGrottosEntrances": 1,
|
||||
"ShuffleInteriorsEntrances": 2,
|
||||
@@ -59,18 +67,21 @@
|
||||
"ShuffleMerchants": 3,
|
||||
"ShuffleOcarinaButtons": 1,
|
||||
"ShuffleOcarinas": 1,
|
||||
"ShuffleOpenChest": 1,
|
||||
"ShuffleOverworldEntrances": 1,
|
||||
"ShuffleOverworldSpawns": 1,
|
||||
"ShuffleOwlDrops": 1,
|
||||
"ShufflePots": 3,
|
||||
"ShuffleScrubs": 2,
|
||||
"ShuffleSongs": 2,
|
||||
"ShuffleSpeak": 1,
|
||||
"ShuffleStoneFairies": 1,
|
||||
"ShuffleSwim": 1,
|
||||
"ShuffleThievesHideoutEntrances": 1,
|
||||
"ShuffleTrees": 1,
|
||||
"ShuffleTokens": 3,
|
||||
"ShuffleWarpSongs": 1,
|
||||
"ShuffleWeirdEgg": 1,
|
||||
"SkipEponaRace": 1,
|
||||
"StartingAge": 2,
|
||||
"StartingHearts": 0,
|
||||
"StartingMapsCompasses": 5,
|
||||
|
||||
@@ -8,8 +8,7 @@ extern "C" {
|
||||
#include "overlays/actors/ovl_En_Arrow/z_en_arrow.h"
|
||||
|
||||
s32 func_808351D4(Player* thisx, PlayState* play); // Arrow nocked
|
||||
s32 func_808353D8(Player* thisx, PlayState* play); // Aiming in first person
|
||||
void Player_InitItemAction(PlayState* play, Player* thisx, PlayerItemAction itemAction);
|
||||
void EnArrow_Init(Actor* thisx, PlayState* play);
|
||||
|
||||
extern PlayState* gPlayState;
|
||||
}
|
||||
@@ -20,16 +19,6 @@ extern PlayState* gPlayState;
|
||||
|
||||
static const s16 sMagicArrowCosts[] = { 4, 4, 8 };
|
||||
|
||||
#define MINIGAME_STATUS_ACTIVE 1
|
||||
|
||||
static const s16 BUTTON_FLASH_DURATION = 3;
|
||||
static const s16 BUTTON_FLASH_COUNT = 3;
|
||||
static const s16 BUTTON_HIGHLIGHT_ALPHA = 128;
|
||||
|
||||
static s16 sButtonFlashTimer = 0;
|
||||
static s16 sButtonFlashCount = 0;
|
||||
static s16 sJustCycledFrames = 0;
|
||||
|
||||
static const PlayerItemAction sArrowCycleOrder[] = {
|
||||
PLAYER_IA_BOW,
|
||||
PLAYER_IA_BOW_FIRE,
|
||||
@@ -54,11 +43,11 @@ static bool HasArrowType(PlayerItemAction itemAction) {
|
||||
case PLAYER_IA_BOW:
|
||||
return true;
|
||||
case PLAYER_IA_BOW_FIRE:
|
||||
return (INV_CONTENT(ITEM_ARROW_FIRE) == ITEM_ARROW_FIRE);
|
||||
return INV_CONTENT(ITEM_ARROW_FIRE) == ITEM_ARROW_FIRE && gSaveContext.magic >= sMagicArrowCosts[0];
|
||||
case PLAYER_IA_BOW_ICE:
|
||||
return (INV_CONTENT(ITEM_ARROW_ICE) == ITEM_ARROW_ICE);
|
||||
return INV_CONTENT(ITEM_ARROW_ICE) == ITEM_ARROW_ICE && gSaveContext.magic >= sMagicArrowCosts[1];
|
||||
case PLAYER_IA_BOW_LIGHT:
|
||||
return (INV_CONTENT(ITEM_ARROW_LIGHT) == ITEM_ARROW_LIGHT);
|
||||
return INV_CONTENT(ITEM_ARROW_LIGHT) == ITEM_ARROW_LIGHT && gSaveContext.magic >= sMagicArrowCosts[2];
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -77,15 +66,24 @@ static s32 GetBowItemForArrow(PlayerItemAction itemAction) {
|
||||
}
|
||||
}
|
||||
|
||||
static ArrowType GetArrowTypeForArrow(s8 itemAction) {
|
||||
switch (itemAction) {
|
||||
case PLAYER_IA_BOW_FIRE:
|
||||
return ARROW_FIRE;
|
||||
case PLAYER_IA_BOW_ICE:
|
||||
return ARROW_ICE;
|
||||
case PLAYER_IA_BOW_LIGHT:
|
||||
return ARROW_LIGHT;
|
||||
default:
|
||||
return ARROW_NORMAL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool CanCycleArrows() {
|
||||
Player* player = GET_PLAYER(gPlayState);
|
||||
|
||||
// don't allow cycling during minigames
|
||||
if (gSaveContext.minigameState == MINIGAME_STATUS_ACTIVE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !(player->stateFlags1 & PLAYER_STATE1_ON_HORSE) && player->rideActor == NULL &&
|
||||
return LINK_IS_ADULT && !gSaveContext.minigameState && gPlayState->sceneNum != SCENE_SHOOTING_GALLERY &&
|
||||
!(player->stateFlags1 & PLAYER_STATE1_ON_HORSE) && player->rideActor == NULL &&
|
||||
INV_CONTENT(SLOT_BOW) == ITEM_BOW &&
|
||||
(INV_CONTENT(ITEM_ARROW_FIRE) == ITEM_ARROW_FIRE || INV_CONTENT(ITEM_ARROW_ICE) == ITEM_ARROW_ICE ||
|
||||
INV_CONTENT(ITEM_ARROW_LIGHT) == ITEM_ARROW_LIGHT);
|
||||
@@ -113,66 +111,6 @@ static s8 GetNextArrowType(s8 currentArrowType) {
|
||||
static void UpdateButtonAlpha(s16 flashAlpha, bool isButtonBow, u16* buttonAlpha) {
|
||||
if (isButtonBow) {
|
||||
*buttonAlpha = flashAlpha;
|
||||
if (sButtonFlashTimer == 0) {
|
||||
*buttonAlpha = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void UpdateFlashEffect(PlayState* play) {
|
||||
if (sButtonFlashTimer <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
sButtonFlashTimer--;
|
||||
s16 flashAlpha = (sButtonFlashTimer % 3) ? BUTTON_HIGHLIGHT_ALPHA : 255;
|
||||
|
||||
if (sButtonFlashTimer == 0 && sButtonFlashCount < BUTTON_FLASH_COUNT - 1) {
|
||||
sButtonFlashTimer = BUTTON_FLASH_DURATION;
|
||||
sButtonFlashCount++;
|
||||
}
|
||||
UpdateButtonAlpha(flashAlpha,
|
||||
(gSaveContext.equips.buttonItems[1] == ITEM_BOW) ||
|
||||
(gSaveContext.equips.buttonItems[1] >= ITEM_BOW_ARROW_FIRE &&
|
||||
gSaveContext.equips.buttonItems[1] <= ITEM_BOW_ARROW_LIGHT),
|
||||
&play->interfaceCtx.cLeftAlpha);
|
||||
|
||||
UpdateButtonAlpha(flashAlpha,
|
||||
(gSaveContext.equips.buttonItems[2] == ITEM_BOW) ||
|
||||
(gSaveContext.equips.buttonItems[2] >= ITEM_BOW_ARROW_FIRE &&
|
||||
gSaveContext.equips.buttonItems[2] <= ITEM_BOW_ARROW_LIGHT),
|
||||
&play->interfaceCtx.cDownAlpha);
|
||||
|
||||
UpdateButtonAlpha(flashAlpha,
|
||||
(gSaveContext.equips.buttonItems[3] == ITEM_BOW) ||
|
||||
(gSaveContext.equips.buttonItems[3] >= ITEM_BOW_ARROW_FIRE &&
|
||||
gSaveContext.equips.buttonItems[3] <= ITEM_BOW_ARROW_LIGHT),
|
||||
&play->interfaceCtx.cRightAlpha);
|
||||
|
||||
if (CVarGetInteger(CVAR_ENHANCEMENT("DpadEquips"), 0)) {
|
||||
UpdateButtonAlpha(flashAlpha,
|
||||
(gSaveContext.equips.buttonItems[4] == ITEM_BOW) ||
|
||||
(gSaveContext.equips.buttonItems[4] >= ITEM_BOW_ARROW_FIRE &&
|
||||
gSaveContext.equips.buttonItems[4] <= ITEM_BOW_ARROW_LIGHT),
|
||||
&play->interfaceCtx.dpadRightAlpha);
|
||||
|
||||
UpdateButtonAlpha(flashAlpha,
|
||||
(gSaveContext.equips.buttonItems[5] == ITEM_BOW) ||
|
||||
(gSaveContext.equips.buttonItems[5] >= ITEM_BOW_ARROW_FIRE &&
|
||||
gSaveContext.equips.buttonItems[5] <= ITEM_BOW_ARROW_LIGHT),
|
||||
&play->interfaceCtx.dpadLeftAlpha);
|
||||
|
||||
UpdateButtonAlpha(flashAlpha,
|
||||
(gSaveContext.equips.buttonItems[6] == ITEM_BOW) ||
|
||||
(gSaveContext.equips.buttonItems[6] >= ITEM_BOW_ARROW_FIRE &&
|
||||
gSaveContext.equips.buttonItems[6] <= ITEM_BOW_ARROW_LIGHT),
|
||||
&play->interfaceCtx.dpadDownAlpha);
|
||||
|
||||
UpdateButtonAlpha(flashAlpha,
|
||||
(gSaveContext.equips.buttonItems[7] == ITEM_BOW) ||
|
||||
(gSaveContext.equips.buttonItems[7] >= ITEM_BOW_ARROW_FIRE &&
|
||||
gSaveContext.equips.buttonItems[7] <= ITEM_BOW_ARROW_LIGHT),
|
||||
&play->interfaceCtx.dpadUpAlpha);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,72 +131,53 @@ static void UpdateEquippedBow(PlayState* play, s8 arrowType) {
|
||||
}
|
||||
|
||||
gSaveContext.buttonStatus[i] = BTN_ENABLED;
|
||||
sButtonFlashTimer = BUTTON_FLASH_DURATION;
|
||||
sButtonFlashCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateFlashEffect(play);
|
||||
}
|
||||
|
||||
static void CycleToNextArrow(PlayState* play, Player* player) {
|
||||
s8 nextArrow = GetNextArrowType(player->heldItemAction);
|
||||
|
||||
if (player->heldActor != NULL && player->heldActor->id == ACTOR_EN_ARROW) {
|
||||
EnArrow* arrow = (EnArrow*)player->heldActor;
|
||||
|
||||
if (arrow->actor.child != NULL) {
|
||||
Actor_Kill(arrow->actor.child);
|
||||
}
|
||||
|
||||
Actor_Kill(&arrow->actor);
|
||||
}
|
||||
|
||||
Player_InitItemAction(play, player, (PlayerItemAction)nextArrow);
|
||||
UpdateEquippedBow(play, nextArrow);
|
||||
Audio_PlaySoundGeneral(NA_SE_PL_CHANGE_ARMS, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale,
|
||||
&gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
|
||||
sJustCycledFrames = 2;
|
||||
}
|
||||
|
||||
void ArrowCycleMain() {
|
||||
bool ArrowCycleMain() {
|
||||
if (gPlayState == nullptr || !CanCycleArrows()) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sJustCycledFrames > 0) {
|
||||
sJustCycledFrames--;
|
||||
}
|
||||
|
||||
UpdateFlashEffect(gPlayState);
|
||||
|
||||
Player* player = GET_PLAYER(gPlayState);
|
||||
Input* input = &gPlayState->state.input[0];
|
||||
|
||||
if (IsAimingBow(player) && CHECK_BTN_ANY(input->press.button, BTN_R)) {
|
||||
if (player->heldActor != NULL && player->heldActor->id == ACTOR_EN_ARROW) {
|
||||
if (IsHoldingMagicBow(player) && gSaveContext.magicState != MAGIC_STATE_IDLE && player->heldActor == NULL) {
|
||||
Audio_PlaySoundGeneral(NA_SE_SY_ERROR, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale,
|
||||
&gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
// reset magic state to IDLE before cycling to prevent error sound
|
||||
gSaveContext.magicState = MAGIC_STATE_IDLE;
|
||||
|
||||
CycleToNextArrow(gPlayState, player);
|
||||
s8 nextArrow = GetNextArrowType(player->heldItemAction);
|
||||
player->heldItemAction = nextArrow;
|
||||
player->itemAction = nextArrow;
|
||||
Actor* arrow = player->heldActor;
|
||||
|
||||
if (arrow->child != NULL) {
|
||||
Actor_Kill(arrow->child);
|
||||
arrow->child = NULL;
|
||||
}
|
||||
arrow->params = GetArrowTypeForArrow(nextArrow);
|
||||
EnArrow_Init(arrow, gPlayState);
|
||||
UpdateEquippedBow(gPlayState, nextArrow);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void RegisterArrowCycle() {
|
||||
COND_ID_HOOK(OnActorUpdate, ACTOR_PLAYER, CVAR_ARROW_CYCLE_VALUE, [](void* actor) { ArrowCycleMain(); });
|
||||
|
||||
// suppress shield input when R is held while aiming to allow arrow cycling
|
||||
COND_VB_SHOULD(VB_EXECUTE_PLAYER_ACTION_FUNC, CVAR_ARROW_CYCLE_VALUE, {
|
||||
Player* player = (Player*)va_arg(args, void*);
|
||||
Input* input = (Input*)va_arg(args, void*);
|
||||
if ((IsAimingBow(player) || sJustCycledFrames > 0) && CHECK_BTN_ANY(input->cur.button, BTN_R)) {
|
||||
input->cur.button &= ~BTN_R;
|
||||
input->press.button &= ~BTN_R;
|
||||
if (IsAimingBow(player) && CHECK_BTN_ANY(input->press.button, BTN_R)) {
|
||||
if (ArrowCycleMain()) {
|
||||
input->cur.button &= ~BTN_R;
|
||||
input->press.button &= ~BTN_R;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -270,9 +189,9 @@ void RegisterArrowCycle() {
|
||||
|
||||
if (gSaveContext.magic < sMagicArrowCosts[magicArrowType]) {
|
||||
*arrowType = ARROW_NORMAL;
|
||||
} else {
|
||||
*should = false;
|
||||
}
|
||||
|
||||
*should = false;
|
||||
});
|
||||
|
||||
COND_VB_SHOULD(VB_EN_ARROW_MAGIC_CONSUMPTION, CVAR_ARROW_CYCLE_VALUE, {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <libultraship/bridge.h>
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
|
||||
#include "soh/OTRGlobals.h"
|
||||
#include "soh/ShipInit.hpp"
|
||||
#include "z64save.h"
|
||||
|
||||
@@ -24,7 +25,11 @@ void OnGameFrameUpdateInfiniteAmmo() {
|
||||
AMMO(ITEM_BOW) = CUR_CAPACITY(UPG_QUIVER);
|
||||
AMMO(ITEM_SLINGSHOT) = CUR_CAPACITY(UPG_BULLET_BAG);
|
||||
if (INV_CONTENT(ITEM_BOMBCHU) != ITEM_NONE) {
|
||||
AMMO(ITEM_BOMBCHU) = 50;
|
||||
int chuCapacity = 50;
|
||||
if (IS_RANDO && RAND_GET_OPTION(RSK_BOMBCHU_BAG).Is(RO_BOMBCHU_BAG_PROGRESSIVE)) {
|
||||
chuCapacity = OTRGlobals::Instance->gRandoContext->GetBombchuCapacity();
|
||||
}
|
||||
AMMO(ITEM_BOMBCHU) = chuCapacity;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
#include "functions.h"
|
||||
#include "macros.h"
|
||||
#include "soh/Enhancements/randomizer/3drando/random.hpp"
|
||||
#include "soh/Enhancements/randomizer/SeedContext.h"
|
||||
#include "soh/Enhancements/enhancementTypes.h"
|
||||
#include "soh/ObjectExtension/ObjectExtension.h"
|
||||
#include "variables.h"
|
||||
#include "soh/OTRGlobals.h"
|
||||
#include "soh/cvar_prefixes.h"
|
||||
#include "soh/ResourceManagerHelpers.h"
|
||||
#include "soh/SohGui/MenuTypes.h"
|
||||
#include "soh/SohGui/SohMenu.h"
|
||||
#include "soh/SohGui/SohGui.hpp"
|
||||
|
||||
extern "C" {
|
||||
#include <z64.h>
|
||||
@@ -21,6 +18,7 @@ extern "C" {
|
||||
#include "src/overlays/actors/ovl_En_Blkobj/z_en_blkobj.h"
|
||||
#include "src/overlays/actors/ovl_En_Encount1/z_en_encount1.h"
|
||||
#include "src/overlays/actors/ovl_En_GeldB/z_en_geldb.h"
|
||||
#include "src/overlays/actors/ovl_En_Peehat/z_en_peehat.h"
|
||||
#include "src/overlays/actors/ovl_En_Rr/z_en_rr.h"
|
||||
#include "src/overlays/actors/ovl_En_Vali/z_en_vali.h"
|
||||
|
||||
@@ -39,8 +37,8 @@ extern std::shared_ptr<SohMenu> mSohMenu;
|
||||
typedef struct EnemyEntry {
|
||||
const char* cvar;
|
||||
const char* name;
|
||||
int16_t id;
|
||||
int16_t params;
|
||||
s16 id;
|
||||
s16 params;
|
||||
} EnemyEntry;
|
||||
|
||||
// clang-format off
|
||||
@@ -62,7 +60,7 @@ static EnemyEntry randomizedEnemySpawnTable[] = {
|
||||
{ CVAR_ENHANCEMENT("RandomizedEnemyList.Dinolfos"), "Dinolfos", ACTOR_EN_ZF, -2 }, // Dinolfos
|
||||
{ CVAR_ENHANCEMENT("RandomizedEnemyList.Dodongo"), "Dodongo", ACTOR_EN_DODONGO, -1 }, // Dodongo
|
||||
{ CVAR_ENHANCEMENT("RandomizedEnemyList.FireKeese"), "Fire Keese", ACTOR_EN_FIREFLY, 1 }, // Fire Keese
|
||||
// { CVAR_ENHANCEMENT("RandomizedEnemyList.FlareDancer"), "Flare Dancer", ACTOR_EN_FD, 0 }, // Flare Dancer (possible cause of crashes because of spawning flame actors on sloped ground)
|
||||
// { CVAR_ENHANCEMENT("RandomizedEnemyList.FlareDancer"), "Flare Dancer", ACTOR_EN_FD, 0 }, // Flare Dancer (possible cause of crashes because of spawning flame actors on sloped ground or overloading)
|
||||
{ CVAR_ENHANCEMENT("RandomizedEnemyList.FloorTile"), "Floor Tile", ACTOR_EN_YUKABYUN, 0 }, // Flying Floor Tile
|
||||
{ CVAR_ENHANCEMENT("RandomizedEnemyList.Floormaster"), "Floormaster", ACTOR_EN_FLOORMAS, 0 }, // Floormaster
|
||||
{ CVAR_ENHANCEMENT("RandomizedEnemyList.FlyingPeahat"), "Flying Peahat", ACTOR_EN_PEEHAT, -1 }, // Flying Peahat (big grounded, doesn't spawn larva)
|
||||
@@ -82,16 +80,16 @@ static EnemyEntry randomizedEnemySpawnTable[] = {
|
||||
{ CVAR_ENHANCEMENT("RandomizedEnemyList.InvisStalfos"), "Invisible Stalfos", ACTOR_EN_TEST, 0 }, // Stalfos (invisible)
|
||||
{ CVAR_ENHANCEMENT("RandomizedEnemyList.Keese"), "Keese", ACTOR_EN_FIREFLY, 2 }, // Regular Keese
|
||||
{ CVAR_ENHANCEMENT("RandomizedEnemyList.LargeBaba"), "Large Deku Baba", ACTOR_EN_DEKUBABA, 1 }, // Deku Baba (large)
|
||||
// { CVAR_ENHANCEMENT("RandomizedEnemyList.Leever"), "Leever", ACTOR_EN_REEBA, 0 }, // Leever Doesn't work (reliant on surface, without a spawner it kills itself too quickly)
|
||||
// { CVAR_ENHANCEMENT("RandomizedEnemyList.Leever"), "Leever", ACTOR_EN_REEBA, 0 }, // Leever Doesn't work (reliant on surface, without a spawner it kills itself too quickly)
|
||||
{ CVAR_ENHANCEMENT("RandomizedEnemyList.LikeLike"), "Like-Like", ACTOR_EN_RR, 0 }, // Like-Like
|
||||
{ CVAR_ENHANCEMENT("RandomizedEnemyList.Lizalfos"), "Lizalfos", ACTOR_EN_ZF, -1 }, // Lizalfos
|
||||
{ CVAR_ENHANCEMENT("RandomizedEnemyList.MadScrub"), "Mad Scrub", ACTOR_EN_DEKUNUTS, 768 }, // Mad Scrub (triple attack) (projectiles don't work)
|
||||
{ CVAR_ENHANCEMENT("RandomizedEnemyList.NormalWolfos"), "Wolfos (Normal)", ACTOR_EN_WF, 0 }, // Wolfos (normal)
|
||||
// { CVAR_ENHANCEMENT("RandomizedEnemyList.Octorok"), "Octorok", ACTOR_EN_OKUTA, 0 }, // Octorok Doesn't work (actor directly uses water box collision to handle hiding/popping up)
|
||||
// { CVAR_ENHANCEMENT("RandomizedEnemyList.Octorok"), "Octorok", ACTOR_EN_OKUTA, 0 }, // Octorok Doesn't work (actor directly uses water box collision to handle hiding/popping up)
|
||||
{ CVAR_ENHANCEMENT("RandomizedEnemyList.PeahatLarva"), "Peahat Larva", ACTOR_EN_PEEHAT, 1 }, // Flying Peahat Larva
|
||||
// { CVAR_ENHANCEMENT("RandomizedEnemyList.Poe"), "Poe", ACTOR_EN_POH, 0 }, // Poe Doesn't work (Seems to rely on other objects?)
|
||||
// { CVAR_ENHANCEMENT("RandomizedEnemyList.Poe"), "Poe", ACTOR_EN_POH, 2 }, // Poe (composer Sharp) Doesn't work (Seems to rely on other objects?)
|
||||
// { CVAR_ENHANCEMENT("RandomizedEnemyList.Poe"), "Poe", ACTOR_EN_POH, 3 }, // Poe (composer Flat) Doesn't work (Seems to rely on other objects?)
|
||||
// { CVAR_ENHANCEMENT("RandomizedEnemyList.Poe"), "Poe", ACTOR_EN_POH, 0 }, // Poe Doesn't work (Seems to rely on other objects?)
|
||||
// { CVAR_ENHANCEMENT("RandomizedEnemyList.Poe.Sharp"), "Poe (Sharp)", ACTOR_EN_POH, 2 }, // Poe (composer Sharp) Doesn't work (Seems to rely on other objects?)
|
||||
// { CVAR_ENHANCEMENT("RandomizedEnemyList.Poe.Flat"), "Poe (Flat)", ACTOR_EN_POH, 3 }, // Poe (composer Flat) Doesn't work (Seems to rely on other objects?)
|
||||
{ CVAR_ENHANCEMENT("RandomizedEnemyList.Redead"), "Redead", ACTOR_EN_RD, 1 }, // Redead (standing)
|
||||
{ CVAR_ENHANCEMENT("RandomizedEnemyList.RedTektite"), "Red Tektite", ACTOR_EN_TITE, -1 }, // Tektite (red)
|
||||
{ CVAR_ENHANCEMENT("RandomizedEnemyList.Shabom"), "Shabom", ACTOR_EN_BUBBLE, 0 }, // Shabom (bubble)
|
||||
@@ -161,125 +159,205 @@ static int enemiesToRandomize[] = {
|
||||
// ACTOR_EN_REEBA, // Leever (reliant on spawner (z_en_encount1.c))
|
||||
};
|
||||
|
||||
bool IsEnemyAllowedToSpawn(int16_t sceneNum, int8_t roomNum, EnemyEntry enemy) {
|
||||
uint32_t isMQ = ResourceMgr_IsSceneMasterQuest(sceneNum);
|
||||
|
||||
// Freezard - Child Link can only kill this with Deku Stick jumpslash or other equipment like bombs.
|
||||
// Beamos - Needs bombs.
|
||||
// Anubis - Needs fire.
|
||||
// Shell Blade & Spike - Child Link can't kill these with sword or Deku Stick.
|
||||
// Flare dancer, Arwing & Dark Link - Both go out of bounds way too easily, softlocking the player.
|
||||
// Wallmaster - Not easily visible, often makes players think they're softlocked and that there's no enemies left.
|
||||
// Club Moblin - Many issues with them falling or placing out of bounds. Maybe fixable in the future?
|
||||
bool enemiesToExcludeClearRooms =
|
||||
enemy.id == ACTOR_EN_FZ || enemy.id == ACTOR_EN_VM || enemy.id == ACTOR_EN_SB || enemy.id == ACTOR_EN_NY ||
|
||||
enemy.id == ACTOR_EN_CLEAR_TAG || enemy.id == ACTOR_EN_WALLMAS || enemy.id == ACTOR_EN_TORCH2 ||
|
||||
(enemy.id == ACTOR_EN_MB && enemy.params == 0) || enemy.id == ACTOR_EN_FD || enemy.id == ACTOR_EN_ANUBICE_TAG;
|
||||
|
||||
// Bari - Spawns 3 more enemies, potentially extremely difficult in timed rooms.
|
||||
bool enemiesToExcludeTimedRooms = enemiesToExcludeClearRooms || enemy.id == ACTOR_EN_VALI;
|
||||
|
||||
switch (sceneNum) {
|
||||
// Deku Tree
|
||||
case SCENE_DEKU_TREE:
|
||||
return (!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 1 || roomNum == 9)) &&
|
||||
!(isMQ && enemiesToExcludeClearRooms &&
|
||||
(roomNum == 4 || roomNum == 6 || roomNum == 9 || roomNum == 10)));
|
||||
// Dodongo's Cavern
|
||||
case SCENE_DODONGOS_CAVERN:
|
||||
return (!(!isMQ && enemiesToExcludeClearRooms && roomNum == 15) &&
|
||||
!(isMQ && enemiesToExcludeClearRooms && (roomNum == 5 || roomNum == 13 || roomNum == 14)));
|
||||
// Jabu Jabu
|
||||
case SCENE_JABU_JABU:
|
||||
return (!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 8 || roomNum == 9)) &&
|
||||
!(!isMQ && enemiesToExcludeTimedRooms && roomNum == 12) &&
|
||||
!(isMQ && enemiesToExcludeClearRooms && (roomNum == 11 || roomNum == 14)));
|
||||
// Forest Temple
|
||||
case SCENE_FOREST_TEMPLE:
|
||||
return (!(!isMQ && enemiesToExcludeClearRooms &&
|
||||
(roomNum == 6 || roomNum == 10 || roomNum == 18 || roomNum == 21)) &&
|
||||
!(isMQ && enemiesToExcludeClearRooms &&
|
||||
(roomNum == 5 || roomNum == 6 || roomNum == 18 || roomNum == 21)));
|
||||
// Fire Temple
|
||||
case SCENE_FIRE_TEMPLE:
|
||||
return (!(!isMQ && enemiesToExcludeClearRooms && roomNum == 15) &&
|
||||
!(isMQ && enemiesToExcludeClearRooms && (roomNum == 15 || roomNum == 17 || roomNum == 18)));
|
||||
// Water Temple
|
||||
case SCENE_WATER_TEMPLE:
|
||||
return (!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 13 || roomNum == 18 || roomNum == 19)) &&
|
||||
!(isMQ && enemiesToExcludeClearRooms && (roomNum == 13 || roomNum == 18)));
|
||||
// Spirit Temple
|
||||
case SCENE_SPIRIT_TEMPLE:
|
||||
return (!(!isMQ && enemiesToExcludeClearRooms &&
|
||||
(roomNum == 1 || roomNum == 10 || roomNum == 17 || roomNum == 20)) &&
|
||||
!(isMQ && enemiesToExcludeClearRooms &&
|
||||
(roomNum == 1 || roomNum == 2 || roomNum == 4 || roomNum == 10 || roomNum == 15 ||
|
||||
roomNum == 19 || roomNum == 20)));
|
||||
// Shadow Temple
|
||||
case SCENE_SHADOW_TEMPLE:
|
||||
return (
|
||||
!(!isMQ && enemiesToExcludeClearRooms &&
|
||||
(roomNum == 1 || roomNum == 7 || roomNum == 11 || roomNum == 14 || roomNum == 16 || roomNum == 17 ||
|
||||
roomNum == 19 || roomNum == 20)) &&
|
||||
!(isMQ && enemiesToExcludeClearRooms &&
|
||||
(roomNum == 1 || roomNum == 6 || roomNum == 7 || roomNum == 11 || roomNum == 14 || roomNum == 20)));
|
||||
// Ganon's Castle Trials
|
||||
case SCENE_INSIDE_GANONS_CASTLE:
|
||||
return (!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 2 || roomNum == 5 || roomNum == 9)) &&
|
||||
!(isMQ && enemiesToExcludeClearRooms &&
|
||||
(roomNum == 0 || roomNum == 2 || roomNum == 5 || roomNum == 9)));
|
||||
// Ice Caverns
|
||||
case SCENE_ICE_CAVERN:
|
||||
return (!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 1 || roomNum == 7)) &&
|
||||
!(isMQ && enemiesToExcludeClearRooms && (roomNum == 3 || roomNum == 7)));
|
||||
// Bottom of the Well
|
||||
// Exclude Dark Link from room with holes in the floor because it can pull you in a like-like making the player
|
||||
// fall down.
|
||||
case SCENE_BOTTOM_OF_THE_WELL:
|
||||
return (!(!isMQ && enemy.id == ACTOR_EN_TORCH2 && roomNum == 3));
|
||||
// Don't allow Dark Link in areas with lava void out zones as it voids out the player as well.
|
||||
// Gerudo Training Ground.
|
||||
case SCENE_GERUDO_TRAINING_GROUND:
|
||||
return (!(enemy.id == ACTOR_EN_TORCH2 && roomNum == 6) &&
|
||||
!(!isMQ && enemiesToExcludeTimedRooms && (roomNum == 1 || roomNum == 7)) &&
|
||||
!(!isMQ && enemiesToExcludeClearRooms && (roomNum == 3 || roomNum == 5 || roomNum == 10)) &&
|
||||
!(isMQ && enemiesToExcludeTimedRooms &&
|
||||
(roomNum == 1 || roomNum == 3 || roomNum == 5 || roomNum == 7)) &&
|
||||
!(isMQ && enemiesToExcludeClearRooms && roomNum == 10));
|
||||
// Don't allow certain enemies in Ganon's Tower because they would spawn up on the ceiling,
|
||||
// becoming impossible to kill.
|
||||
// Ganon's Tower.
|
||||
case SCENE_GANONS_TOWER:
|
||||
return (!(enemiesToExcludeClearRooms || enemy.id == ACTOR_EN_VALI ||
|
||||
(enemy.id == ACTOR_EN_ZF && enemy.params == -1)));
|
||||
// Ganon's Tower Escape.
|
||||
case SCENE_GANONS_TOWER_COLLAPSE_INTERIOR:
|
||||
return (!((enemiesToExcludeTimedRooms || (enemy.id == ACTOR_EN_ZF && enemy.params == -1)) && roomNum == 1));
|
||||
// Don't allow big Stalchildren, big Peahats and the large Bari (jellyfish) during the Gohma fight because they
|
||||
// can clip into Gohma and it crashes the game. Likely because Gohma on the ceiling can't handle collision with
|
||||
// other enemies.
|
||||
case SCENE_DEKU_TREE_BOSS:
|
||||
return (!enemiesToExcludeTimedRooms && !(enemy.id == ACTOR_EN_SKB && enemy.params == 20) &&
|
||||
!(enemy.id == ACTOR_EN_PEEHAT && enemy.params == -1));
|
||||
// Grottos.
|
||||
case SCENE_GROTTOS:
|
||||
return (!(enemiesToExcludeClearRooms && (roomNum == 2 || roomNum == 7)));
|
||||
// Royal Grave.
|
||||
case SCENE_ROYAL_FAMILYS_TOMB:
|
||||
return (!(enemiesToExcludeClearRooms && roomNum == 0));
|
||||
// Don't allow Dark Link in areas with lava void out zones as it voids out the player as well.
|
||||
// Death Mountain Crater.
|
||||
case SCENE_DEATH_MOUNTAIN_CRATER:
|
||||
return (enemy.id != ACTOR_EN_TORCH2);
|
||||
static bool IsExcludedFromClearRooms(s16 enemyId, s16 enemyParams) {
|
||||
switch (enemyId) {
|
||||
// Freezard - Child Link can only kill this with Deku Stick jumpslash or other equipment like bombs
|
||||
case ACTOR_EN_FZ:
|
||||
// Beamos - Needs bombs
|
||||
case ACTOR_EN_VM:
|
||||
// Shell Blade - It's annoying to kill these as Child Link with sword or Deku Stick
|
||||
case ACTOR_EN_SB:
|
||||
// Spike - Child Link can't kill these with sword or Deku Stick
|
||||
case ACTOR_EN_NY:
|
||||
// Arwing - Goes out of bounds way too easily, softlocking the player
|
||||
case ACTOR_EN_CLEAR_TAG:
|
||||
// Wallmaster - Not easily visible, often makes players think they're softlocked and that there's no enemies
|
||||
// left
|
||||
case ACTOR_EN_WALLMAS:
|
||||
// Dark Link - Goes out of bounds way too easily, softlocking the player
|
||||
case ACTOR_EN_TORCH2:
|
||||
// Flare dancer - Goes out of bounds way too easily, softlocking the player
|
||||
case ACTOR_EN_FD:
|
||||
// Anubis - Needs fire
|
||||
case ACTOR_EN_ANUBICE_TAG:
|
||||
return true;
|
||||
case ACTOR_EN_MB:
|
||||
return enemyParams == 0;
|
||||
default:
|
||||
return 1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsExcludedFromTimedRooms(s16 enemyId, s16 enemyParams) {
|
||||
switch (enemyId) {
|
||||
// Bari - Spawns 3 more enemies, potentially extremely difficult in timed rooms
|
||||
case ACTOR_EN_VALI:
|
||||
return true;
|
||||
default:
|
||||
return IsExcludedFromClearRooms(enemyId, enemyParams);
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsClearRoom(bool mq, s16 sceneNum, s8 roomNum) {
|
||||
switch (sceneNum) {
|
||||
case SCENE_DEKU_TREE:
|
||||
if (mq) {
|
||||
return roomNum == 4 || roomNum == 6 || roomNum == 9 || roomNum == 10;
|
||||
} else {
|
||||
return roomNum == 1 || roomNum == 9;
|
||||
}
|
||||
case SCENE_DODONGOS_CAVERN:
|
||||
if (mq) {
|
||||
return roomNum == 5 || roomNum == 6 || roomNum == 13 || roomNum == 14;
|
||||
} else {
|
||||
return roomNum == 15;
|
||||
}
|
||||
case SCENE_JABU_JABU:
|
||||
if (mq) {
|
||||
return roomNum == 11 || roomNum == 13 || roomNum == 14;
|
||||
} else {
|
||||
return roomNum == 8 || roomNum == 9;
|
||||
}
|
||||
case SCENE_FOREST_TEMPLE:
|
||||
if (mq) {
|
||||
return roomNum == 5 || roomNum == 6 || roomNum == 18 || roomNum == 21;
|
||||
} else {
|
||||
return roomNum == 6 || roomNum == 10 || roomNum == 18 || roomNum == 21;
|
||||
}
|
||||
case SCENE_FIRE_TEMPLE:
|
||||
if (mq) {
|
||||
return roomNum == 15 || roomNum == 17 || roomNum == 18;
|
||||
} else {
|
||||
return roomNum == 15;
|
||||
}
|
||||
case SCENE_WATER_TEMPLE:
|
||||
if (mq) {
|
||||
return roomNum == 13 || roomNum == 18;
|
||||
} else {
|
||||
return roomNum == 13 || roomNum == 18 || roomNum == 19;
|
||||
}
|
||||
case SCENE_SPIRIT_TEMPLE:
|
||||
if (mq) {
|
||||
return roomNum == 1 || roomNum == 2 || roomNum == 4 || roomNum == 10 || roomNum == 15 ||
|
||||
roomNum == 19 || roomNum == 20;
|
||||
} else {
|
||||
return roomNum == 1 || roomNum == 10 || roomNum == 17 || roomNum == 20 || roomNum == 27;
|
||||
}
|
||||
case SCENE_SHADOW_TEMPLE:
|
||||
if (mq) {
|
||||
return roomNum == 1 || roomNum == 6 || roomNum == 7 || roomNum == 11 || roomNum == 14 || roomNum == 20;
|
||||
} else {
|
||||
return roomNum == 1 || roomNum == 7 || roomNum == 11 || roomNum == 14 || roomNum == 16 ||
|
||||
roomNum == 17 || roomNum == 19 || roomNum == 20;
|
||||
}
|
||||
case SCENE_INSIDE_GANONS_CASTLE:
|
||||
if (mq) {
|
||||
return roomNum == 0 || roomNum == 2 || roomNum == 5 || roomNum == 9;
|
||||
} else {
|
||||
return roomNum == 2 || roomNum == 5 || roomNum == 9;
|
||||
}
|
||||
case SCENE_ICE_CAVERN:
|
||||
if (mq) {
|
||||
return roomNum == 3 || roomNum == 7;
|
||||
} else {
|
||||
return roomNum == 1 || roomNum == 7;
|
||||
}
|
||||
case SCENE_GERUDO_TRAINING_GROUND:
|
||||
if (mq) {
|
||||
return roomNum == 10;
|
||||
} else {
|
||||
return roomNum == 3 || roomNum == 5 || roomNum == 10;
|
||||
}
|
||||
case SCENE_GANONS_TOWER:
|
||||
return true;
|
||||
case SCENE_GROTTOS:
|
||||
return roomNum == 2 || roomNum == 7;
|
||||
case SCENE_ROYAL_FAMILYS_TOMB:
|
||||
return roomNum == 0;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsTimedRoom(bool mq, s16 sceneNum, s8 roomNum) {
|
||||
switch (sceneNum) {
|
||||
case SCENE_JABU_JABU:
|
||||
return !mq && roomNum == 12;
|
||||
case SCENE_GERUDO_TRAINING_GROUND:
|
||||
if (mq) {
|
||||
return roomNum == 1 || roomNum == 3 || roomNum == 5 || roomNum == 7;
|
||||
} else {
|
||||
return roomNum == 1 || roomNum == 7;
|
||||
}
|
||||
case SCENE_GANONS_TOWER_COLLAPSE_INTERIOR:
|
||||
return roomNum == 1;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsEnemyAllowedToSpawn(s16 sceneNum, s8 roomNum, EnemyEntry enemy, s16 posY, bool fromBari) {
|
||||
bool mq = ResourceMgr_IsSceneMasterQuest(sceneNum);
|
||||
|
||||
if (IsExcludedFromClearRooms(enemy.id, enemy.params) && IsClearRoom(mq, sceneNum, roomNum)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsExcludedFromTimedRooms(enemy.id, enemy.params) && IsTimedRoom(mq, sceneNum, roomNum)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't allow Lizalfos or Baris in Ganon's Tower because they would spawn up on the ceiling, becoming impossible to
|
||||
// kill.
|
||||
if (sceneNum == SCENE_GANONS_TOWER &&
|
||||
(enemy.id == ACTOR_EN_VALI || (enemy.id == ACTOR_EN_ZF && enemy.params == -1))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't allow Lizalfos in the first room of the interior of the castle collapse
|
||||
if (sceneNum == SCENE_GANONS_TOWER_COLLAPSE_INTERIOR && roomNum == 1 && enemy.id == ACTOR_EN_ZF &&
|
||||
enemy.params == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't allow big Stalchildren, big Peahats and Baris (big jellyfish) during the Gohma fight because they can clip
|
||||
// into Gohma and it crashes the game. Likely because Gohma on the ceiling can't handle collision with other
|
||||
// enemies.
|
||||
if (sceneNum == SCENE_DEKU_TREE_BOSS &&
|
||||
((enemy.id == ACTOR_EN_SKB && enemy.params == 20) || (enemy.id == ACTOR_EN_PEEHAT && enemy.params == -1) ||
|
||||
(enemy.id == ACTOR_EN_VALI))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't allow the following enemies in the first spawn of the first room in MQ Fire Temple loop as when spawned and
|
||||
// they get stuck in the room above
|
||||
// - Lizalfos/Dinolfos, Bari: they drop in
|
||||
// - Skulltulla: they appear above
|
||||
// - Flying Peehat: they rise above the ceiling
|
||||
if (mq && sceneNum == SCENE_FIRE_TEMPLE && roomNum == 15 && posY == 64 &&
|
||||
(enemy.id == ACTOR_EN_ZF || enemy.id == ACTOR_EN_VALI || enemy.id == ACTOR_EN_ST ||
|
||||
enemy.id == ACTOR_EN_PEEHAT)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't allow Stalfos in the child spirit clear room as they jump out of bounds frequently
|
||||
if (sceneNum == SCENE_SPIRIT_TEMPLE && roomNum == 1 && enemy.id == ACTOR_EN_TEST) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't allow baris to spawn another bari
|
||||
if (fromBari && enemy.id == ACTOR_EN_VALI) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::vector<EnemyEntry> selectedEnemyList;
|
||||
|
||||
void GetSelectedEnemies() {
|
||||
static void UpdateSelectedEnemies() {
|
||||
selectedEnemyList.clear();
|
||||
|
||||
for (int i = 0; i < ARRAY_COUNT(randomizedEnemySpawnTable); i++) {
|
||||
if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemyList.All"), 0)) {
|
||||
selectedEnemyList.push_back(randomizedEnemySpawnTable[i]);
|
||||
@@ -287,108 +365,119 @@ void GetSelectedEnemies() {
|
||||
selectedEnemyList.push_back(randomizedEnemySpawnTable[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedEnemyList.size() == 0) {
|
||||
selectedEnemyList.push_back(randomizedEnemySpawnTable[0]);
|
||||
}
|
||||
}
|
||||
|
||||
EnemyEntry GetRandomizedEnemyEntry(uint32_t seed, PlayState* play) {
|
||||
static EnemyEntry GetRandomizedEnemyEntry(u32 seed, PlayState* play, s16 posY, bool fromBari) {
|
||||
std::vector<EnemyEntry> filteredEnemyList = {};
|
||||
|
||||
if (selectedEnemyList.size() == 0) {
|
||||
GetSelectedEnemies();
|
||||
UpdateSelectedEnemies();
|
||||
}
|
||||
|
||||
for (EnemyEntry enemy : selectedEnemyList) {
|
||||
if (IsEnemyAllowedToSpawn(play->sceneNum, play->roomCtx.curRoom.num, enemy)) {
|
||||
if (IsEnemyAllowedToSpawn(play->sceneNum, play->roomCtx.curRoom.num, enemy, posY, fromBari)) {
|
||||
filteredEnemyList.push_back(enemy);
|
||||
}
|
||||
}
|
||||
|
||||
if (filteredEnemyList.size() == 0) {
|
||||
filteredEnemyList = selectedEnemyList;
|
||||
}
|
||||
|
||||
if (CVAR_ENEMY_RANDOMIZER_VALUE == ENEMY_RANDOMIZER_RANDOM_SEEDED) {
|
||||
uint32_t finalSeed =
|
||||
seed + (IS_RANDO ? Rando::Context::GetInstance()->GetSeed() : gSaveContext.ship.stats.fileCreatedAt);
|
||||
Random_Init(finalSeed);
|
||||
uint32_t randomNumber = Random(0, filteredEnemyList.size());
|
||||
return filteredEnemyList[randomNumber];
|
||||
} else {
|
||||
uint32_t randomSelectedEnemy = Random(0, filteredEnemyList.size());
|
||||
return filteredEnemyList[randomSelectedEnemy];
|
||||
uint64_t randomState = 0;
|
||||
|
||||
ShipUtils::RandInit(
|
||||
seed + (IS_RANDO ? Rando::Context::GetInstance()->GetSeed() : gSaveContext.ship.stats.fileCreatedAt),
|
||||
&randomState);
|
||||
|
||||
return ShipUtils::RandomElement(filteredEnemyList, false, &randomState);
|
||||
}
|
||||
|
||||
return ShipUtils::RandomElement(filteredEnemyList, false);
|
||||
}
|
||||
|
||||
bool IsEnemyFoundToRandomize(int16_t sceneNum, int8_t roomNum, int16_t actorId, int16_t params, float posX) {
|
||||
|
||||
uint32_t isMQ = ResourceMgr_IsSceneMasterQuest(sceneNum);
|
||||
static bool IsEnemyFoundToRandomize(s16 sceneNum, s8 roomNum, s16 actorId, s16 params, f32 posX) {
|
||||
u32 isMQ = ResourceMgr_IsSceneMasterQuest(sceneNum);
|
||||
|
||||
for (int i = 0; i < ARRAY_COUNT(enemiesToRandomize); i++) {
|
||||
if (actorId == enemiesToRandomize[i]) {
|
||||
switch (actorId) {
|
||||
// Only randomize the main component of Electric Tailparasans, not the tail segments they spawn.
|
||||
case ACTOR_EN_TP:
|
||||
return (params == -1);
|
||||
// Only randomize the initial Deku Scrub actor (single and triple attack), not the flower they spawn.
|
||||
case ACTOR_EN_DEKUNUTS:
|
||||
return (params == -256 || params == 768);
|
||||
// Don't randomize the OoB wallmaster in the Silver Rupee room because it's only there to
|
||||
// not trigger unlocking the door after killing the other wallmaster in authentic gameplay.
|
||||
case ACTOR_EN_WALLMAS:
|
||||
return (!(!isMQ && sceneNum == SCENE_GERUDO_TRAINING_GROUND && roomNum == 2 && posX == -2345));
|
||||
// Only randomize initial Floormaster actor (it can split and does some spawning on init).
|
||||
case ACTOR_EN_FLOORMAS:
|
||||
return (params == 0 || params == -32768);
|
||||
// Only randomize the initial eggs, not the enemies that spawn from them.
|
||||
case ACTOR_EN_GOMA:
|
||||
return (params >= 0 && params <= 9);
|
||||
// Only randomize Skullwalltulas, not Golden Skulltulas.
|
||||
case ACTOR_EN_SW:
|
||||
return (params == 0);
|
||||
// Don't randomize Nabooru because it'll break the cutscene and the door.
|
||||
// Don't randomize Iron Knuckle in MQ Spirit Trial because it's needed to
|
||||
// break the thrones in the room to access a button.
|
||||
case ACTOR_EN_IK:
|
||||
return (params != 1280 && !(isMQ && sceneNum == SCENE_INSIDE_GANONS_CASTLE && roomNum == 17));
|
||||
// Only randomize the initial spawn of the huge jellyfish. It spawns another copy when hit with a sword.
|
||||
case ACTOR_EN_VALI:
|
||||
return (params == -1);
|
||||
// Don't randomize Lizalfos in Dodongo's Cavern because the gates won't work correctly otherwise.
|
||||
case ACTOR_EN_ZF:
|
||||
return (params != 1280 && params != 1281 && params != 1536 && params != 1537);
|
||||
// Don't randomize the Wolfos in SFM because it's needed to open the gate.
|
||||
case ACTOR_EN_WF:
|
||||
return (params != 7936);
|
||||
// Don't randomize the Stalfos in Forest Temple because other enemies fall through the hole and don't
|
||||
// trigger the platform. Don't randomize the Stalfos spawning on the boat in Shadow Temple, as
|
||||
// randomizing them places the new enemies down in the river.
|
||||
case ACTOR_EN_TEST:
|
||||
return (params != 1 && !(sceneNum == SCENE_SHADOW_TEMPLE && roomNum == 21));
|
||||
// Only randomize the enemy variant of Armos Statue.
|
||||
// Leave one Armos unrandomized in the Spirit Temple room where an armos is needed to push down a
|
||||
// button.
|
||||
case ACTOR_EN_AM:
|
||||
return ((params == -1 || params == 255) && !(sceneNum == SCENE_SPIRIT_TEMPLE && posX == 2141));
|
||||
// Don't randomize Shell Blades and Spikes in the underwater portion in Water Temple as it's impossible
|
||||
// to kill most other enemies underwater with just hookshot and they're required to be killed for a
|
||||
// grate to open.
|
||||
case ACTOR_EN_SB:
|
||||
case ACTOR_EN_NY:
|
||||
return (!(!isMQ && sceneNum == SCENE_WATER_TEMPLE && roomNum == 2));
|
||||
case ACTOR_EN_SKJ:
|
||||
return !(sceneNum == SCENE_LOST_WOODS && LINK_IS_CHILD);
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
if (actorId != enemiesToRandomize[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (actorId) {
|
||||
// Only randomize the main component of Electric Tailparasans, not the tail segments they spawn.
|
||||
case ACTOR_EN_TP:
|
||||
return params == -1;
|
||||
// Only randomize the initial Deku Scrub actor (single and triple attack), not the flower they spawn.
|
||||
case ACTOR_EN_DEKUNUTS:
|
||||
return params == -256 || params == 768;
|
||||
// Don't randomize the OoB wallmaster in the Silver Rupee room because it's only there to
|
||||
// not trigger unlocking the door after killing the other wallmaster in authentic gameplay.
|
||||
case ACTOR_EN_WALLMAS:
|
||||
return !(!isMQ && sceneNum == SCENE_GERUDO_TRAINING_GROUND && roomNum == 2 && posX == -2345);
|
||||
// Only randomize initial Floormaster actor (it can split and does some spawning on init).
|
||||
case ACTOR_EN_FLOORMAS:
|
||||
return params == 0 || params == -32768;
|
||||
// Only randomize the initial eggs, not the enemies that spawn from them.
|
||||
case ACTOR_EN_GOMA:
|
||||
return params >= 0 && params <= 9;
|
||||
// Only randomize Skullwalltulas, not Golden Skulltulas.
|
||||
case ACTOR_EN_SW:
|
||||
return params == 0;
|
||||
// Don't randomize Nabooru because it'll break the cutscene and the door.
|
||||
// Don't randomize Iron Knuckle in MQ Spirit Trial because it's needed to
|
||||
// break the thrones in the room to access a button.
|
||||
case ACTOR_EN_IK:
|
||||
return params != 1280 && !(isMQ && sceneNum == SCENE_INSIDE_GANONS_CASTLE && roomNum == 17);
|
||||
// Only randomize the initial spawn of the huge jellyfish. It spawns another copy when hit with a sword.
|
||||
case ACTOR_EN_VALI:
|
||||
return params == -1;
|
||||
// Don't randomize Lizalfos in Dodongo's Cavern because the gates won't work correctly otherwise.
|
||||
case ACTOR_EN_ZF:
|
||||
return params != 1280 && params != 1281 && params != 1536 && params != 1537;
|
||||
// Don't randomize the right baby dodongo on the first tunnel in Dodongo's Cavern as in vanilla you use them
|
||||
// isntead of bombs to blow up a wall
|
||||
case ACTOR_EN_DODOJR:
|
||||
return !(sceneNum == SCENE_DODONGOS_CAVERN && roomNum == 1 && posX == 1972);
|
||||
// Don't randomize the Wolfos in SFM because it's needed to open the gate.
|
||||
case ACTOR_EN_WF:
|
||||
return params != 7936;
|
||||
// Don't randomize the Stalfos in Forest Temple because other enemies fall through the hole and don't
|
||||
// trigger the platform. Don't randomize the Stalfos spawning on the boat in Shadow Temple, as
|
||||
// randomizing them places the new enemies down in the river.
|
||||
case ACTOR_EN_TEST:
|
||||
return params != 1 && !(sceneNum == SCENE_SHADOW_TEMPLE && roomNum == 21);
|
||||
// Only randomize the enemy variant of Armos Statue.
|
||||
// Leave one Armos unrandomized in the Spirit Temple room where an armos is needed to push down a
|
||||
// button.
|
||||
case ACTOR_EN_AM:
|
||||
return (params == -1 || params == 255) && !(sceneNum == SCENE_SPIRIT_TEMPLE && posX == 2141);
|
||||
// Don't randomize Shell Blades and Spikes in the underwater portion in Water Temple as it's impossible
|
||||
// to kill most other enemies underwater with just hookshot and they're required to be killed for a
|
||||
// grate to open.
|
||||
case ACTOR_EN_SB:
|
||||
case ACTOR_EN_NY:
|
||||
return !(!isMQ && sceneNum == SCENE_WATER_TEMPLE && roomNum == 2);
|
||||
// Don't randomize Skull Kids in Lost Woods as child as they're not enemies
|
||||
case ACTOR_EN_SKJ:
|
||||
return !(sceneNum == SCENE_LOST_WOODS && LINK_IS_CHILD);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If no enemy is found, don't randomize the actor.
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t GetRandomizedEnemy(PlayState* play, int16_t* actorId, s16* posX, s16* posY, s16* posZ, int16_t* rotX,
|
||||
int16_t* rotY, int16_t* rotZ, int16_t* params) {
|
||||
|
||||
uint32_t isMQ = ResourceMgr_IsSceneMasterQuest(play->sceneNum);
|
||||
static u8 GetRandomizedEnemy(PlayState* play, s16* actorId, s16* posX, s16* posY, s16* posZ, s16* rotX, s16* rotY,
|
||||
s16* rotZ, s16* params, s16 offset = 0, bool fromBari = false) {
|
||||
u32 isMQ = ResourceMgr_IsSceneMasterQuest(play->sceneNum);
|
||||
|
||||
// Hack to remove enemies that wrongfully spawn because of bypassing object dependency with enemy randomizer on.
|
||||
// This should probably be handled on OTR generation in the future when object dependency is fully removed.
|
||||
@@ -414,7 +503,6 @@ uint8_t GetRandomizedEnemy(PlayState* play, int16_t* actorId, s16* posX, s16* po
|
||||
}
|
||||
|
||||
if (IsEnemyFoundToRandomize(play->sceneNum, play->roomCtx.curRoom.num, *actorId, *params, *posX)) {
|
||||
|
||||
// When replacing Iron Knuckles in Spirit Temple, move them away from the throne because
|
||||
// some enemies can get stuck on the throne.
|
||||
if (*actorId == ACTOR_EN_IK && play->sceneNum == SCENE_SPIRIT_TEMPLE) {
|
||||
@@ -445,17 +533,27 @@ uint8_t GetRandomizedEnemy(PlayState* play, int16_t* actorId, s16* posX, s16* po
|
||||
pos.x = *posX;
|
||||
pos.y = *posY + 50;
|
||||
pos.z = *posZ;
|
||||
raycastResult = BgCheck_AnyRaycastFloor1(&play->colCtx, &poly, &pos);
|
||||
|
||||
// If ground is found below actor, move actor to that height.
|
||||
if (raycastResult > BGCHECK_Y_MIN) {
|
||||
*posY = raycastResult;
|
||||
// the forest temple second twisted hallway spawns after the enemies so we need to "find the floor" manually
|
||||
if (play->sceneNum == SCENE_FOREST_TEMPLE && play->roomCtx.curRoom.num == 20 && *posZ > -3000) {
|
||||
// when hallway is twisted (play->actorCtx.flags.tempSwch & 1), one spawn has the floor at 1235.165 &
|
||||
// the other at 1239.094 but that changes based on the player position
|
||||
// when not twisted, the whole floor is at 1228
|
||||
|
||||
*posY = 1228.0;
|
||||
} else {
|
||||
raycastResult = BgCheck_AnyRaycastFloor1(&play->colCtx, &poly, &pos);
|
||||
|
||||
// If ground is found below actor, move actor to that height.
|
||||
if (raycastResult > BGCHECK_Y_MIN) {
|
||||
*posY = raycastResult;
|
||||
}
|
||||
}
|
||||
|
||||
// Get randomized enemy ID and parameter.
|
||||
uint32_t seed =
|
||||
play->sceneNum + *actorId + (int)*posX + (int)*posY + (int)*posZ + *rotX + *rotY + *rotZ + *params;
|
||||
EnemyEntry randomEnemy = GetRandomizedEnemyEntry(seed, play);
|
||||
u32 seed =
|
||||
play->sceneNum + *actorId + (int)*posX + (int)*posY + (int)*posZ + *rotX + *rotY + *rotZ + *params + offset;
|
||||
EnemyEntry randomEnemy = GetRandomizedEnemyEntry(seed, play, *posY, fromBari);
|
||||
|
||||
*actorId = randomEnemy.id;
|
||||
*params = randomEnemy.params;
|
||||
@@ -530,6 +628,25 @@ void CustomStalfosPairFightDestroy(Actor* thisx, PlayState* play) {
|
||||
ObjectExtension::GetInstance().Remove<CustomStalfosPairFightData>(thisx);
|
||||
}
|
||||
|
||||
struct CustomPeehatLarvaData {
|
||||
EnPeehat* peehat = nullptr;
|
||||
ActorFunc originalDestroy = nullptr;
|
||||
};
|
||||
|
||||
static ObjectExtension::Register<CustomPeehatLarvaData> CustomPeehatLarvaDataRegister;
|
||||
|
||||
void CustomPeehatLarvaDestroy(Actor* thisx, PlayState* play) {
|
||||
assert(ObjectExtension::GetInstance().Has<CustomPeehatLarvaData>(thisx));
|
||||
|
||||
CustomPeehatLarvaData* customPeehatLarvaData = ObjectExtension::GetInstance().Get<CustomPeehatLarvaData>(thisx);
|
||||
|
||||
customPeehatLarvaData->peehat->unk_2FA -= 1;
|
||||
|
||||
customPeehatLarvaData->originalDestroy(thisx, play);
|
||||
|
||||
ObjectExtension::GetInstance().Remove<CustomPeehatLarvaData>(thisx);
|
||||
}
|
||||
|
||||
void RegisterEnemyRandomizer() {
|
||||
COND_ID_HOOK(OnActorInit, ACTOR_EN_MB, ENEMY_RANDOMIZER_ENABLED, FixClubMoblinScale);
|
||||
|
||||
@@ -627,11 +744,20 @@ void RegisterEnemyRandomizer() {
|
||||
ActorContext* actorCtx = va_arg(args, ActorContext*);
|
||||
ActorEntry* actorEntry = va_arg(args, ActorEntry*);
|
||||
PlayState* play = va_arg(args, PlayState*);
|
||||
Actor* actor = va_arg(args, Actor*);
|
||||
Actor** actor = va_arg(args, Actor**);
|
||||
|
||||
if (!GetRandomizedEnemy(play, &actorEntry->id, &actorEntry->pos.x, &actorEntry->pos.y, &actorEntry->pos.z,
|
||||
&actorEntry->rot.x, &actorEntry->rot.y, &actorEntry->rot.z, &actorEntry->params)) {
|
||||
*should = false;
|
||||
s16 actorId = actorEntry->id;
|
||||
s16 posX = actorEntry->pos.x;
|
||||
s16 posY = actorEntry->pos.y;
|
||||
s16 posZ = actorEntry->pos.z;
|
||||
s16 rotX = actorEntry->rot.x;
|
||||
s16 rotY = actorEntry->rot.y;
|
||||
s16 rotZ = actorEntry->rot.z;
|
||||
s16 params = actorEntry->params;
|
||||
|
||||
*should = false;
|
||||
if (GetRandomizedEnemy(play, &actorId, &posX, &posY, &posZ, &rotX, &rotY, &rotZ, ¶ms)) {
|
||||
*actor = Actor_Spawn(actorCtx, play, actorId, posX, posY, posZ, rotX, rotY, rotZ, params);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -749,14 +875,18 @@ void RegisterEnemyRandomizer() {
|
||||
s16 rotZ = 0;
|
||||
s16 params = 0;
|
||||
|
||||
for (s32 i = 0; i < 3; i++) {
|
||||
// Offset small jellyfish with Enemy Randomizer, otherwise it gets
|
||||
// stuck in a loop spawning more big jellyfish with seeded spawns.
|
||||
if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0)) {
|
||||
rotY += rand() % 50;
|
||||
}
|
||||
s16 homePosX = vali->actor.home.pos.x;
|
||||
s16 homePosY = vali->actor.home.pos.y;
|
||||
s16 homePosZ = vali->actor.home.pos.z;
|
||||
|
||||
if (!GetRandomizedEnemy(play, &actorId, &posX, &posY, &posZ, &rotX, &rotY, &rotZ, ¶ms)) {
|
||||
s16 homeRotX = vali->actor.home.rot.x;
|
||||
s16 homeRotY = vali->actor.home.rot.y;
|
||||
s16 homeRotZ = vali->actor.home.rot.z;
|
||||
|
||||
for (s32 i = 0; i < 3; i++) {
|
||||
// use the home pos & rot to make it consistent
|
||||
if (!GetRandomizedEnemy(play, &actorId, &homePosX, &homePosY, &homePosZ, &homeRotX, &homeRotY, &homeRotZ,
|
||||
¶ms, i * 1000, true)) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
@@ -912,9 +1042,43 @@ void RegisterEnemyRandomizer() {
|
||||
|
||||
*should = false;
|
||||
});
|
||||
|
||||
COND_VB_SHOULD(VB_PEEHAT_SPAWN_LARVAS, ENEMY_RANDOMIZER_ENABLED, {
|
||||
EnPeehat* peehat = va_arg(args, EnPeehat*);
|
||||
PlayState* play = va_arg(args, PlayState*);
|
||||
|
||||
s16 actorId = ACTOR_EN_PEEHAT;
|
||||
s16 homePosX = peehat->actor.home.pos.x;
|
||||
s16 homePosY = peehat->actor.home.pos.y + 50.0f;
|
||||
s16 homePosZ = peehat->actor.home.pos.z;
|
||||
s16 rotX = 0;
|
||||
s16 rotY = 0;
|
||||
s16 rotZ = 0;
|
||||
s16 params = PEAHAT_TYPE_LARVA;
|
||||
|
||||
// 3 is MAX_LARVA
|
||||
for (s32 i = 3 - peehat->unk_2FA; i > 0; i--) {
|
||||
if (!GetRandomizedEnemy(play, &actorId, &homePosX, &homePosY, &homePosZ, &rotX, &rotY, &rotZ, ¶ms,
|
||||
i * 1000)) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
Actor* enemy =
|
||||
Actor_Spawn(&play->actorCtx, play, actorId, homePosX, homePosY, homePosZ, rotX, rotY, rotZ, params);
|
||||
|
||||
if (enemy == NULL) {
|
||||
assert(false);
|
||||
} else {
|
||||
peehat->unk_2FA++;
|
||||
ObjectExtension::GetInstance().Set<CustomPeehatLarvaData>(
|
||||
enemy, CustomPeehatLarvaData{ .peehat = peehat, .originalDestroy = enemy->destroy });
|
||||
enemy->destroy = CustomPeehatLarvaDestroy;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static const std::map<int32_t, const char*> enemyRandomizerModes = {
|
||||
static const std::map<s32, const char*> enemyRandomizerModes = {
|
||||
{ ENEMY_RANDOMIZER_OFF, "Disabled" },
|
||||
{ ENEMY_RANDOMIZER_RANDOM, "Random" },
|
||||
{ ENEMY_RANDOMIZER_RANDOM_SEEDED, "Random (Seeded)" },
|
||||
@@ -925,7 +1089,7 @@ void RegisterEnemyRandomizerWidgets() {
|
||||
|
||||
SohGui::mSohMenu->AddWidget(path, "Enemy Randomizer", WIDGET_CVAR_COMBOBOX)
|
||||
.CVar(CVAR_ENHANCEMENT("RandomizedEnemies"))
|
||||
.Callback([](WidgetInfo& info) { GetSelectedEnemies(); })
|
||||
.Callback([](WidgetInfo& info) { UpdateSelectedEnemies(); })
|
||||
.Options(
|
||||
UIWidgets::ComboboxOptions()
|
||||
.DefaultIndex(ENEMY_RANDOMIZER_OFF)
|
||||
@@ -956,7 +1120,7 @@ void RegisterEnemyRandomizerWidgets() {
|
||||
SohGui::mSohMenu->AddWidget(path, "Select all Enemies", WIDGET_CVAR_CHECKBOX)
|
||||
.CVar(CVAR_ENHANCEMENT("RandomizedEnemyList.All"))
|
||||
.PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0); })
|
||||
.Callback([](WidgetInfo& info) { GetSelectedEnemies(); });
|
||||
.Callback([](WidgetInfo& info) { UpdateSelectedEnemies(); });
|
||||
|
||||
SohGui::mSohMenu->AddWidget(path, "Enemy List", WIDGET_SEPARATOR).PreFunc([](WidgetInfo& info) {
|
||||
info.isHidden = !CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0);
|
||||
@@ -971,7 +1135,7 @@ void RegisterEnemyRandomizerWidgets() {
|
||||
info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemyList.All"), 0);
|
||||
info.options->disabledTooltip = "These options are disabled because \"Select All Enemies\" is enabled.";
|
||||
})
|
||||
.Callback([](WidgetInfo& info) { GetSelectedEnemies(); });
|
||||
.Callback([](WidgetInfo& info) { UpdateSelectedEnemies(); });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ typedef enum {
|
||||
static AltTrapType roll = ADD_TRAP_MAX;
|
||||
static int statusTimer = -1;
|
||||
static int eventTimer = -1;
|
||||
static EntranceIndex teleportRoll = ENTR_MAX;
|
||||
|
||||
const char* altTrapTypeCvars[] = {
|
||||
CVAR_ENHANCEMENT("ExtraTraps.Ice"), CVAR_ENHANCEMENT("ExtraTraps.Burn"),
|
||||
@@ -41,6 +42,12 @@ const char* altTrapTypeCvars[] = {
|
||||
CVAR_ENHANCEMENT("ExtraTraps.Kill"), CVAR_ENHANCEMENT("ExtraTraps.Teleport"),
|
||||
};
|
||||
|
||||
const std::array<EntranceIndex, 7> teleportDestinations = {
|
||||
ENTR_LINKS_HOUSE_CHILD_SPAWN, ENTR_SACRED_FOREST_MEADOW_WARP_PAD, ENTR_DEATH_MOUNTAIN_CRATER_WARP_PAD,
|
||||
ENTR_LAKE_HYLIA_WARP_PAD, ENTR_DESERT_COLOSSUS_WARP_PAD, ENTR_GRAVEYARD_WARP_PAD,
|
||||
ENTR_TEMPLE_OF_TIME_WARP_PAD,
|
||||
};
|
||||
|
||||
std::vector<AltTrapType> getEnabledAddTraps() {
|
||||
std::vector<AltTrapType> enabledAddTraps;
|
||||
for (int i = 0; i < ADD_TRAP_MAX; i++) {
|
||||
@@ -102,6 +109,7 @@ static void RollRandomTrap(uint64_t seed) {
|
||||
break;
|
||||
case ADD_TELEPORT_TRAP:
|
||||
eventTimer = 3;
|
||||
teleportRoll = ShipUtils::RandomElement(teleportDestinations, &state);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -135,32 +143,7 @@ static void OnPlayerUpdate() {
|
||||
&gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
|
||||
break;
|
||||
case ADD_TELEPORT_TRAP: {
|
||||
int entrance;
|
||||
int index = Random(0, 7);
|
||||
switch (index) {
|
||||
case 0:
|
||||
entrance = GI_TP_DEST_SERENADE;
|
||||
break;
|
||||
case 1:
|
||||
entrance = GI_TP_DEST_REQUIEM;
|
||||
break;
|
||||
case 2:
|
||||
entrance = GI_TP_DEST_BOLERO;
|
||||
break;
|
||||
case 3:
|
||||
entrance = GI_TP_DEST_MINUET;
|
||||
break;
|
||||
case 4:
|
||||
entrance = GI_TP_DEST_NOCTURNE;
|
||||
break;
|
||||
case 5:
|
||||
entrance = GI_TP_DEST_PRELUDE;
|
||||
break;
|
||||
default:
|
||||
entrance = GI_TP_DEST_LINKSHOUSE;
|
||||
break;
|
||||
}
|
||||
GameInteractor::RawAction::TeleportPlayer(entrance);
|
||||
GameInteractor::RawAction::TeleportPlayer(teleportRoll);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
#include "soh/ShipInit.hpp"
|
||||
|
||||
extern "C" {
|
||||
#include <variables.h>
|
||||
|
||||
f32 Fishing_GetMinimumRequiredScore();
|
||||
}
|
||||
|
||||
void BuildFishingMessage(uint16_t* textId, bool* loadFromMessageTable) {
|
||||
if (gSaveContext.minigameScore == 0) {
|
||||
gSaveContext.minigameScore = Fishing_GetMinimumRequiredScore();
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterFishingMessages() {
|
||||
COND_ID_HOOK(OnOpenText, 0x40AE, CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0), BuildFishingMessage);
|
||||
COND_ID_HOOK(OnOpenText, 0x4080, CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0), BuildFishingMessage);
|
||||
}
|
||||
|
||||
static RegisterShipInitFunc initFunc(RegisterFishingMessages, { CVAR_ENHANCEMENT("CustomizeFishing") });
|
||||
@@ -17,10 +17,10 @@ static void RegisterBossDefeatTimestamps() {
|
||||
BOSS_DEFEAT_TIMESTAMP(ACTOR_BOSS_SST, TIMESTAMP_DEFEAT_BONGO_BONGO);
|
||||
BOSS_DEFEAT_TIMESTAMP(ACTOR_BOSS_TW, TIMESTAMP_DEFEAT_TWINROVA);
|
||||
BOSS_DEFEAT_TIMESTAMP(ACTOR_BOSS_GANON, TIMESTAMP_DEFEAT_GANONDORF);
|
||||
BOSS_DEFEAT_TIMESTAMP(ACTOR_BOSS_GANON2, TIMESTAMP_DEFEAT_GANON);
|
||||
|
||||
COND_ID_HOOK(OnBossDefeat, ACTOR_BOSS_GANON2, true,
|
||||
[](void* refActor) { gSaveContext.ship.stats.gameComplete = true; });
|
||||
COND_ID_HOOK(OnBossDefeat, ACTOR_BOSS_GANON2, true, [](void* refActor) {
|
||||
gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] = GAMEPLAYSTAT_TOTAL_TIME;
|
||||
gSaveContext.ship.stats.gameComplete = true;
|
||||
});
|
||||
}
|
||||
|
||||
static RegisterShipInitFunc initFunc(RegisterBossDefeatTimestamps);
|
||||
|
||||
@@ -84,7 +84,8 @@ static void DisableFixedCamera_RestoreAllCameraData() {
|
||||
|
||||
// Helper to check if a camera type is a fixed camera
|
||||
static bool IsFixedCameraType(s16 type) {
|
||||
return type == CAM_SET_PREREND_FIXED || type == CAM_SET_PREREND_PIVOT || type == CAM_SET_PIVOT_FROM_SIDE;
|
||||
return type == CAM_SET_PREREND_FIXED || type == CAM_SET_PREREND_PIVOT || type == CAM_SET_PIVOT_FROM_SIDE ||
|
||||
type == CAM_SET_MARKET_BALCONY;
|
||||
}
|
||||
|
||||
static void RegisterDisableFixedCamera() {
|
||||
|
||||
@@ -6,11 +6,11 @@ extern "C" {
|
||||
|
||||
void BuildSkulltulaMessage(uint16_t* textId, bool* loadFromMessageTable) {
|
||||
CustomMessage msg =
|
||||
CustomMessage("You got a %rGold Skulltula Token%w!&You've collected %r[[gsCount]]%w tokens&in total!",
|
||||
"Ein %rGoldenes Skulltula-Symbol%w!&Du hast nun insgesamt %r[[gsCount]]&%wGoldene "
|
||||
"Skulltula-Symbole&gesammelt!",
|
||||
"Vous obtenez un %rSymbole de&Skulltula d'or%w! Vous avez&collecté %r[[gsCount]]%w symboles en "
|
||||
"tout!",
|
||||
CustomMessage("You got a %rGold Skulltula Token%w!&You've collected %r[[d]]%w |token|tokens|&in total!",
|
||||
"Ein %rGoldenes Skulltula-Symbol%w!&Du hast nun insgesamt %r[[d]]&%w|Goldenes "
|
||||
"Skulltula-Symbol|Goldene Skulltula-Symbole|&gesammelt!",
|
||||
"Vous obtenez un %rSymbole de&Skulltula d'or%w! Vous avez&collecté %r[[d]]%w |symbole|symboles| "
|
||||
"en tout!",
|
||||
TEXTBOX_TYPE_BLUE);
|
||||
// The freeze text cannot be manually dismissed and must be auto-dismissed.
|
||||
// This is fine and even wanted when skull tokens are not shuffled, but when
|
||||
@@ -24,8 +24,8 @@ void BuildSkulltulaMessage(uint16_t* textId, bool* loadFromMessageTable) {
|
||||
// Auto dismiss textbox after 0x3C (60) frames (about 3 seconds for OoT)
|
||||
msg = msg + "\x0E\x3C";
|
||||
}
|
||||
int16_t gsCount = gSaveContext.inventory.gsTokens + (IS_RANDO ? 1 : 0);
|
||||
msg.Replace("[[gsCount]]", std::to_string(gsCount));
|
||||
int16_t gsCount = gSaveContext.inventory.gsTokens;
|
||||
msg.InsertNumber(gsCount);
|
||||
msg.AutoFormat(ITEM_SKULL_TOKEN);
|
||||
msg.LoadIntoFont();
|
||||
*loadFromMessageTable = false;
|
||||
|
||||
@@ -36,7 +36,7 @@ void RegisterAdditionalReticles() {
|
||||
bool shouldRegister = CVAR_BOW_RETICLE_VALUE || CVAR_BOOMERANG_RETICLE_VALUE;
|
||||
|
||||
COND_VB_SHOULD(VB_DRAW_ADDITIONAL_RETICLES, shouldRegister, {
|
||||
Player* player = GET_PLAYER(gPlayState);
|
||||
Player* player = va_arg(args, Player*);
|
||||
Actor* heldActor = player->heldActor;
|
||||
if (CVAR_BOW_RETICLE_VALUE &&
|
||||
((player->heldItemAction >= PLAYER_IA_BOW && player->heldItemAction <= PLAYER_IA_BOW_LIGHT) ||
|
||||
|
||||
@@ -2,11 +2,17 @@
|
||||
|
||||
extern "C" {
|
||||
#include <variables.h>
|
||||
extern PlayState* gPlayState;
|
||||
}
|
||||
|
||||
// RANDOTODO: Port the rest of the behavior for this enhancement here.
|
||||
|
||||
void BuildNightGuardMessage(uint16_t* textId, bool* loadFromMessageTable) {
|
||||
// Other guards should not have their text overridden
|
||||
if (gPlayState->sceneNum != SCENE_MARKET_ENTRANCE_NIGHT) {
|
||||
return;
|
||||
}
|
||||
|
||||
CustomMessage msg = CustomMessage("You look bored. Wanna go out for a walk?\x1B%gYes&No%w",
|
||||
"Du siehst gelangweilt aus. Willst Du einen Spaziergang machen?\x1B%gJa&Nein%w",
|
||||
"Tu as l'air de t'ennuyer. Tu veux aller faire un tour?\x1B%gOui&Non%w");
|
||||
|
||||
@@ -86,8 +86,7 @@ void RegisterShouldPlayBlueWarp() {
|
||||
* should also account for the difference between your first and following visits to the blue warp.
|
||||
*/
|
||||
REGISTER_VB_SHOULD(VB_PLAY_TRANSITION_CS, {
|
||||
// Do nothing when in a boss rush
|
||||
if (IS_BOSS_RUSH) {
|
||||
if (IS_BOSS_RUSH || gSaveContext.gameMode == GAMEMODE_END_CREDITS) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
#include "soh/ShipInit.hpp"
|
||||
|
||||
extern "C" {
|
||||
#include "src/overlays/actors/ovl_Obj_Lightswitch/z_obj_lightswitch.h"
|
||||
#include "src/overlays/actors/ovl_Bg_Spot06_Objects/z_bg_spot06_objects.h"
|
||||
#include "src/overlays/actors/ovl_Bg_Jya_Bombchuiwa/z_bg_jya_bombchuiwa.h"
|
||||
extern PlayState* gPlayState;
|
||||
}
|
||||
|
||||
#define SKIP_MISC_INTERACTIONS_NAME CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions")
|
||||
#define SKIP_MISC_INTERACTIONS_VALUE CVarGetInteger(SKIP_MISC_INTERACTIONS_NAME, IS_RANDO)
|
||||
|
||||
static void RegisterSkipTimerDelay() {
|
||||
// Skip Water Temple gate delay
|
||||
COND_ID_HOOK(OnActorUpdate, ACTOR_BG_SPOT06_OBJECTS, SKIP_MISC_INTERACTIONS_VALUE, [](void* actor) {
|
||||
auto spot06 = static_cast<BgSpot06Objects*>(actor);
|
||||
if (spot06->dyna.actor.params == 0) {
|
||||
spot06->timer = 0;
|
||||
}
|
||||
});
|
||||
|
||||
// Skip Spirit Sun on Floor activation delay
|
||||
COND_ID_HOOK(OnActorUpdate, ACTOR_BG_JYA_BOMBCHUIWA, SKIP_MISC_INTERACTIONS_VALUE, [](void* actor) {
|
||||
auto jya = static_cast<BgJyaBombchuiwa*>(actor);
|
||||
if (!(jya->drawFlags & 4) && jya->timer > 0 && jya->timer < 9) {
|
||||
jya->timer = 9;
|
||||
}
|
||||
});
|
||||
|
||||
// Skip Spirit Sun on Floor & Sun on Block activation delay
|
||||
COND_ID_HOOK(OnActorUpdate, ACTOR_OBJ_LIGHTSWITCH, SKIP_MISC_INTERACTIONS_VALUE, [](void* actor) {
|
||||
if (gPlayState->sceneNum == SCENE_SPIRIT_TEMPLE &&
|
||||
(gPlayState->roomCtx.curRoom.num == 4 || gPlayState->roomCtx.curRoom.num == 8)) {
|
||||
auto sun = static_cast<ObjLightswitch*>(actor);
|
||||
sun->toggleDelay = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static RegisterShipInitFunc initFunc_SkipTimerDelay(RegisterSkipTimerDelay,
|
||||
{ SKIP_MISC_INTERACTIONS_NAME, "IS_RANDO" });
|
||||
@@ -0,0 +1,18 @@
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
|
||||
#include "soh/ShipInit.hpp"
|
||||
|
||||
extern "C" {
|
||||
#include "src/overlays/actors/ovl_Bg_Spot06_Objects/z_bg_spot06_objects.h"
|
||||
extern SaveContext gSaveContext;
|
||||
}
|
||||
|
||||
static void RegisterSpot06GateSkip() {
|
||||
COND_VB_SHOULD(VB_BG_SPOT06_OBJECTS_GATE_SKIP,
|
||||
CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), IS_RANDO), {
|
||||
BgSpot06Objects* actor = va_arg(args, BgSpot06Objects*);
|
||||
actor->timer = 0;
|
||||
*should = false;
|
||||
});
|
||||
}
|
||||
|
||||
static RegisterShipInitFunc initFunc(RegisterSpot06GateSkip, { CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint") });
|
||||
@@ -53,6 +53,7 @@ extern std::shared_ptr<SohMenu> mSohMenu;
|
||||
#define SEQ_COUNT_INSTRUMENT 6
|
||||
#define SEQ_COUNT_SFX 57
|
||||
#define SEQ_COUNT_VOICE 108
|
||||
#define SEQ_COUNT_ENDING 5
|
||||
|
||||
size_t AuthenticCountBySequenceType(SeqType type) {
|
||||
switch (type) {
|
||||
@@ -74,6 +75,8 @@ size_t AuthenticCountBySequenceType(SeqType type) {
|
||||
return SEQ_COUNT_INSTRUMENT;
|
||||
case SEQ_VOICE:
|
||||
return SEQ_COUNT_VOICE;
|
||||
case SEQ_ENDING:
|
||||
return SEQ_COUNT_ENDING;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@@ -109,19 +112,22 @@ void UpdateCurrentBGM(u16 seqKey, SeqType seqType) {
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t seeded_audio_state = 0;
|
||||
|
||||
void RandomizeGroup(SeqType type, bool manual = true) {
|
||||
std::vector<u16> values;
|
||||
|
||||
uint64_t localRngState = 0;
|
||||
uint64_t* shuffleState = nullptr;
|
||||
|
||||
if (!manual) {
|
||||
if (CVarGetInteger(CVAR_AUDIO("RandomizeAudioGenModes"), 0) == RANDOMIZE_ON_FILE_LOAD_SEEDED ||
|
||||
CVarGetInteger(CVAR_AUDIO("RandomizeAudioGenModes"), 0) == RANDOMIZE_ON_RANDO_GEN_ONLY) {
|
||||
int randomizeMode = CVarGetInteger(CVAR_AUDIO("RandomizeAudioGenModes"), 0);
|
||||
if (randomizeMode == RANDOMIZE_ON_FILE_LOAD_SEEDED || randomizeMode == RANDOMIZE_ON_RANDO_GEN_ONLY) {
|
||||
|
||||
uint32_t finalSeed = type + (IS_RANDO ? Rando::Context::GetInstance()->GetSeed()
|
||||
: static_cast<uint32_t>(gSaveContext.ship.stats.fileCreatedAt));
|
||||
ShipUtils::RandInit(finalSeed, &seeded_audio_state);
|
||||
ShipUtils::RandInit(finalSeed, &localRngState);
|
||||
shuffleState = &localRngState;
|
||||
}
|
||||
// For RANDOMIZE_ON_NEW_SCENE, shuffleState remains nullptr, which uses the global RNG
|
||||
}
|
||||
|
||||
// An empty IncludedSequences set means that the AudioEditor window has never been drawn
|
||||
@@ -141,7 +147,7 @@ void RandomizeGroup(SeqType type, bool manual = true) {
|
||||
if (!values.size())
|
||||
return;
|
||||
}
|
||||
ShipUtils::Shuffle(values, &seeded_audio_state);
|
||||
ShipUtils::Shuffle(values, shuffleState);
|
||||
for (const auto& [seqId, seqData] : AudioCollection::Instance->GetAllSequences()) {
|
||||
const std::string cvarKey = AudioCollection::Instance->GetCvarKey(seqData.sfxKey);
|
||||
const std::string cvarLockKey = AudioCollection::Instance->GetCvarLockKey(seqData.sfxKey);
|
||||
@@ -803,7 +809,8 @@ void AudioEditor::DrawElement() {
|
||||
}
|
||||
|
||||
std::vector<SeqType> allTypes = {
|
||||
SEQ_BGM_WORLD, SEQ_BGM_EVENT, SEQ_BGM_BATTLE, SEQ_OCARINA, SEQ_FANFARE, SEQ_INSTRUMENT, SEQ_SFX, SEQ_VOICE,
|
||||
SEQ_BGM_WORLD, SEQ_BGM_EVENT, SEQ_BGM_BATTLE, SEQ_OCARINA, SEQ_FANFARE,
|
||||
SEQ_INSTRUMENT, SEQ_SFX, SEQ_VOICE, SEQ_ENDING,
|
||||
};
|
||||
|
||||
void AudioEditor_RandomizeAll() {
|
||||
|
||||
@@ -1889,11 +1889,11 @@ void DrawSillyTab() {
|
||||
|
||||
UIWidgets::Separator(true, true, 2.0f, 2.0f);
|
||||
|
||||
UIWidgets::CVarCheckbox("Let It Snow", CVAR_GENERAL("LetItSnow"),
|
||||
UIWidgets::CheckboxOptions()
|
||||
.Color(THEME_COLOR)
|
||||
.Tooltip("Makes snow fall, changes chest texture colors to red and green, etc, for "
|
||||
"December holidays.\nWill reset on restart outside of December 23-25."));
|
||||
UIWidgets::CVarCheckbox(
|
||||
"Let It Snow", CVAR_GENERAL("LetItSnow"),
|
||||
UIWidgets::CheckboxOptions()
|
||||
.Color(THEME_COLOR)
|
||||
.Tooltip("Makes snow fall for December holidays.\nWill reset on restart outside of December 23-25."));
|
||||
|
||||
UIWidgets::Separator(true, true, 2.0f, 2.0f);
|
||||
|
||||
@@ -2104,24 +2104,28 @@ void ApplySideEffects(CosmeticOption& cosmeticOption) {
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t seeded_cosmetics_state = 0;
|
||||
|
||||
void RandomizeColor(CosmeticOption& cosmeticOption, bool manual = true) {
|
||||
ImVec4 randomColor;
|
||||
|
||||
if (!manual && CVarGetInteger(CVAR_COSMETIC("RandomizeCosmeticsGenModes"), 0) == RANDOMIZE_ON_FILE_LOAD_SEEDED ||
|
||||
!manual && CVarGetInteger(CVAR_COSMETIC("RandomizeCosmeticsGenModes"), 0) == RANDOMIZE_ON_RANDO_GEN_ONLY) {
|
||||
uint64_t local_seed_state = 0;
|
||||
uint64_t* randomState = nullptr;
|
||||
|
||||
uint32_t finalSeed = cosmeticOption.defaultColor.r + cosmeticOption.defaultColor.g +
|
||||
cosmeticOption.defaultColor.b + cosmeticOption.defaultColor.a +
|
||||
(IS_RANDO ? Rando::Context::GetInstance()->GetSeed()
|
||||
: static_cast<uint32_t>(gSaveContext.ship.stats.fileCreatedAt));
|
||||
if (!manual) {
|
||||
int randomizeMode = CVarGetInteger(CVAR_COSMETIC("RandomizeCosmeticsGenModes"), 0);
|
||||
if (randomizeMode == RANDOMIZE_ON_FILE_LOAD_SEEDED || randomizeMode == RANDOMIZE_ON_RANDO_GEN_ONLY) {
|
||||
|
||||
randomColor = GetRandomValue(finalSeed, &seeded_cosmetics_state);
|
||||
} else {
|
||||
randomColor = GetRandomValue();
|
||||
uint32_t finalSeed = cosmeticOption.defaultColor.r + cosmeticOption.defaultColor.g +
|
||||
cosmeticOption.defaultColor.b + cosmeticOption.defaultColor.a +
|
||||
(IS_RANDO ? Rando::Context::GetInstance()->GetSeed()
|
||||
: static_cast<uint32_t>(gSaveContext.ship.stats.fileCreatedAt));
|
||||
|
||||
randomState = &local_seed_state;
|
||||
ShipUtils::RandInit(finalSeed, randomState);
|
||||
}
|
||||
// For RANDOMIZE_ON_NEW_SCENE, randomState remains nullptr, which uses the global RNG
|
||||
}
|
||||
|
||||
randomColor = GetRandomValue(randomState);
|
||||
Color_RGBA8 newColor;
|
||||
newColor.r = static_cast<uint8_t>(randomColor.x * 255.0f);
|
||||
newColor.g = static_cast<uint8_t>(randomColor.y * 255.0f);
|
||||
@@ -2703,6 +2707,7 @@ void RegisterCosmeticHooks() {
|
||||
[](s16 sceneNum) { CosmeticsEditor_AutoRandomizeAll(); });
|
||||
|
||||
COND_HOOK(OnGameFrameUpdate, true, CosmeticsUpdateTick);
|
||||
COND_HOOK(OnAssetAltChange, true, []() { ApplyOrResetCustomGfxPatches(true); });
|
||||
}
|
||||
|
||||
void RegisterCosmeticWidgets() {
|
||||
|
||||
@@ -121,6 +121,8 @@ static bool IsDummyPlayer(const Player* player) {
|
||||
return player != nullptr && player->actor.update == DummyPlayer_Update;
|
||||
}
|
||||
|
||||
static bool sPrevAltAssetsEnabled = false;
|
||||
|
||||
void PatchOrUnpatch(const char* resource, const char* gfx, const char* dlist1, const char* dlist2, const char* dlist3,
|
||||
const char* alternateDL) {
|
||||
if (resource == NULL || gfx == NULL || dlist1 == NULL || dlist2 == NULL) {
|
||||
@@ -128,6 +130,7 @@ void PatchOrUnpatch(const char* resource, const char* gfx, const char* dlist1, c
|
||||
}
|
||||
|
||||
const bool altAssetsRuntime = ResourceMgr_IsAltAssetsEnabled();
|
||||
const bool altAssetsChanged = (altAssetsRuntime != sPrevAltAssetsEnabled);
|
||||
|
||||
if (!altAssetsRuntime) {
|
||||
// Alt assets are off; ensure any prior patches using these names are reverted.
|
||||
@@ -136,11 +139,16 @@ void PatchOrUnpatch(const char* resource, const char* gfx, const char* dlist1, c
|
||||
if (dlist3 != NULL) {
|
||||
ResourceMgr_UnpatchGfxByName(resource, dlist3);
|
||||
}
|
||||
// Drop any cached version of the resource so it reloads clean (unpatched) next use.
|
||||
ResourceMgr_UnloadResource(resource);
|
||||
if (altAssetsChanged) {
|
||||
ResourceMgr_UnloadResource(resource);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (altAssetsChanged) {
|
||||
ResourceMgr_UnloadResource(resource);
|
||||
}
|
||||
|
||||
if (!ResourceGetIsCustomByName(gfx)) {
|
||||
return;
|
||||
}
|
||||
@@ -510,4 +518,6 @@ void UpdatePatchCustomEquipmentDlists() {
|
||||
}
|
||||
|
||||
ApplyCommonEquipmentPatches();
|
||||
|
||||
sPrevAltAssetsEnabled = ResourceMgr_IsAltAssetsEnabled();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "debugSaveEditor.h"
|
||||
#include "soh/Enhancements/randomizer/randomizerTypes.h"
|
||||
#include "soh/util.h"
|
||||
#include "soh/SohGui/ImGuiUtils.h"
|
||||
#include "soh/OTRGlobals.h"
|
||||
@@ -1392,6 +1393,39 @@ void DrawEquipmentTab() {
|
||||
"40",
|
||||
};
|
||||
DrawUpgrade("Deku Nut Capacity", UPG_NUTS, nutNames);
|
||||
|
||||
if (IS_RANDO &&
|
||||
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_BOMBCHU_BAG) == RO_BOMBCHU_BAG_PROGRESSIVE) {
|
||||
const std::vector<std::string> bombchuNames = {
|
||||
"None",
|
||||
"20",
|
||||
"30",
|
||||
"50",
|
||||
};
|
||||
ImGui::Text("%s", "Bombchu Bag Capacity");
|
||||
ImGui::SameLine();
|
||||
ImGui::PushID("Bombchu Bag Capacity");
|
||||
PushStyleCombobox(THEME_COLOR);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
auto value = gSaveContext.ship.quest.data.randomizer.bombchuUpgradeLevel;
|
||||
auto name = value < bombchuNames.size() ? bombchuNames[value].c_str() : "Glitched";
|
||||
if (ImGui::BeginCombo("##upgrade", name)) {
|
||||
for (size_t i = 0; i < bombchuNames.size(); i++) {
|
||||
if (ImGui::Selectable(bombchuNames[i].c_str())) {
|
||||
gSaveContext.ship.quest.data.randomizer.bombchuUpgradeLevel = i;
|
||||
if (i > 0) {
|
||||
INV_CONTENT(ITEM_BOMBCHU) = ITEM_BOMBCHU;
|
||||
} else {
|
||||
INV_CONTENT(ITEM_BOMBCHU) = ITEM_NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
PopStyleCombobox();
|
||||
ImGui::PopID();
|
||||
UIWidgets::Tooltip("Bombchu Bag Capapcity");
|
||||
}
|
||||
}
|
||||
|
||||
// Draws a toggleable icon for a quest item that is faded when disabled
|
||||
|
||||
@@ -52,16 +52,6 @@ typedef enum {
|
||||
/* 0x08 */ GI_COLOR_BLACK,
|
||||
} GIColors;
|
||||
|
||||
typedef enum {
|
||||
/* */ GI_TP_DEST_LINKSHOUSE = ENTR_LINKS_HOUSE_CHILD_SPAWN,
|
||||
/* */ GI_TP_DEST_MINUET = ENTR_SACRED_FOREST_MEADOW_WARP_PAD,
|
||||
/* */ GI_TP_DEST_BOLERO = ENTR_DEATH_MOUNTAIN_CRATER_WARP_PAD,
|
||||
/* */ GI_TP_DEST_SERENADE = ENTR_LAKE_HYLIA_WARP_PAD,
|
||||
/* */ GI_TP_DEST_REQUIEM = ENTR_DESERT_COLOSSUS_WARP_PAD,
|
||||
/* */ GI_TP_DEST_NOCTURNE = ENTR_GRAVEYARD_WARP_PAD,
|
||||
/* */ GI_TP_DEST_PRELUDE = ENTR_TEMPLE_OF_TIME_WARP_PAD,
|
||||
} GITeleportDestinations;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -225,6 +215,9 @@ class GameInteractor {
|
||||
static GameInteractionEffectQueryResult RemoveEffect(RemovableGameInteractionEffect& effect);
|
||||
|
||||
// Game Hooks
|
||||
//
|
||||
// Hooks should be idempotent and execution order is not guaranteed.
|
||||
// If two operations must happen in a specific order, they should be placed in the same hook.
|
||||
HOOK_ID nextHookId = 1;
|
||||
|
||||
template <typename H> struct RegisteredGameHooks {
|
||||
|
||||
@@ -203,6 +203,14 @@ typedef enum {
|
||||
// - None
|
||||
VB_BG_BREAKWALL_BREAK,
|
||||
|
||||
// #### `result`
|
||||
// ```c
|
||||
// this->timer > 0 && this->timer <= 100
|
||||
// ```
|
||||
// #### `args`
|
||||
// - `*BgSpot06Objects`
|
||||
VB_BG_SPOT06_OBJECTS_GATE_SKIP,
|
||||
|
||||
// #### `result`
|
||||
// ```c
|
||||
// gSaveContext.bgsFlag
|
||||
@@ -532,10 +540,11 @@ typedef enum {
|
||||
|
||||
// #### `result`
|
||||
// ```c
|
||||
// true
|
||||
// (this->heldItemAction == PLAYER_IA_HOOKSHOT) ||
|
||||
// (this->heldItemAction == PLAYER_IA_LONGSHOT)
|
||||
// ```
|
||||
// #### `args`
|
||||
// - None
|
||||
// - '*Player'
|
||||
VB_DRAW_ADDITIONAL_RETICLES,
|
||||
|
||||
// #### `result`
|
||||
@@ -1355,6 +1364,14 @@ typedef enum {
|
||||
// - `*DoorShutter`
|
||||
VB_LOCK_BOSS_DOOR,
|
||||
|
||||
// #### `result`
|
||||
// ```c
|
||||
// true
|
||||
// ```
|
||||
// #### `args`
|
||||
// - `*EnMs`
|
||||
VB_MAGIC_BEAN_SALESMAN_TAKE_MONEY,
|
||||
|
||||
// #### `result`
|
||||
// ```c
|
||||
// CHECK_QUEST_ITEM(QUEST_SONG_EPONA)
|
||||
@@ -1518,6 +1535,14 @@ typedef enum {
|
||||
// - `*BossGanondrof`
|
||||
VB_PHANTOM_GANON_DEATH_SCENE,
|
||||
|
||||
// #### `result`
|
||||
// ```c
|
||||
// true
|
||||
// ```
|
||||
// #### `args`
|
||||
// - None
|
||||
VB_PLAY_BEAN_PLANTING_CS,
|
||||
|
||||
// #### `result`
|
||||
// ##### In `DoorWarp1_ChildWarpOut` - `SCENE_DODONGOS_CAVERN_BOSS`
|
||||
// ```c
|
||||
@@ -2663,7 +2688,7 @@ typedef enum {
|
||||
// - `*ActorContext`
|
||||
// - `*ActorEntry`
|
||||
// - `*PlayState`
|
||||
// - `*Actor`
|
||||
// - `**Actor`
|
||||
VB_SPAWN_ACTOR_ENTRY,
|
||||
|
||||
// #### `result`
|
||||
@@ -2759,7 +2784,16 @@ typedef enum {
|
||||
// ```
|
||||
// #### `args`
|
||||
// - `*int32_t (camId)`
|
||||
VB_SHOULD_LOAD_BG_IMAGE
|
||||
VB_SHOULD_LOAD_BG_IMAGE,
|
||||
|
||||
// #### `result`
|
||||
// ```c
|
||||
// true
|
||||
// ```
|
||||
// #### `args`
|
||||
// - `*EnPeehat`
|
||||
// - `*PlayState`
|
||||
VB_PEEHAT_SPAWN_LARVAS,
|
||||
} GIVanillaBehavior;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -2328,12 +2328,12 @@ void StaticData::HintTable_Init() {
|
||||
{QM_RED, QM_BLUE, QM_GREEN}));
|
||||
|
||||
hintTextTable[RHT_MALON_HINT_OBSTICLE_COURSE] = HintText(CustomMessage("How about trying the #Obstacle Course?# If you beat my time I'll let you keep my favourite #cow# Elsie and her toy #[[1]]#!^"
|
||||
"Challenge the #Obstacle Course?#&\x1B&#Let's go&No thanks#",
|
||||
"Challenge the #Obstacle Course?#\x1B#Let's go&No thanks#",
|
||||
/*german*/ "Warum versuchst Du Dich nicht mit Epona an dem #Hindernisparcours#?^"
|
||||
"Gelingt es Dir den Rekord zu brechen, bekommst Du meine #Lieblingskuh# Elsie^und ihr Lieblingsspielzeug, #[[1]]#!^"
|
||||
"Wie sieht's aus?&Möchtest Du es versuchen?\x1B&#Ja!&Nein!#",
|
||||
"Wie sieht's aus?&Möchtest Du es versuchen?\x1B#Ja!&Nein!#",
|
||||
/*french*/ "Que dirais-tu d'essayer le #Parcours d'Obstacles#? Si tu bats mon temps, je te donnerai ma vache préférée, Elsie, et son jouet #[[1]]#!^"
|
||||
"Tenter le #Parcours d'Obstacles#?&\x1B&#Allons-y&Non merci#",
|
||||
"Tenter le #Parcours d'Obstacles#?\x1B#Allons-y&Non merci#",
|
||||
{QM_RED, QM_BLUE, QM_GREEN, QM_RED, QM_GREEN}));
|
||||
|
||||
hintTextTable[RHT_MALON_HINT_TURNING_EVIL] = HintText(CustomMessage("@? Is that you? ^If I ran the ranch, I'd build an #Obstacle Course#, and whoever gets the best time would win a #cow#!^"
|
||||
|
||||
@@ -1318,6 +1318,14 @@ void StaticData::HintTable_Init_Exclude_Dungeon() {
|
||||
/*german*/ "Man erzählt sich, daß sich bewacht von einem #Ring der Flammen#, im Geistertempel #[[1]]# |befände|befänden|.",
|
||||
/*french*/ "Selon moi, protégé par un #cercle de flammes# dans le Temple de l'Esprit se trouve #[[1]]#.", {QM_RED, QM_GREEN}));
|
||||
|
||||
hintTextTable[RHT_SPIRIT_TEMPLE_BOULDER_ROOM_SUN_FAIRY] = HintText(CustomMessage("They say that #calling the sun past rolling boulders in Spirit Temple# reveals #[[1]]#.",
|
||||
/*german*/ TODO_TRANSLATE,
|
||||
/*french*/ TODO_TRANSLATE, {QM_RED, QM_GREEN}));
|
||||
|
||||
hintTextTable[RHT_SPIRIT_TEMPLE_ARMOS_ROOM_SUN_FAIRY] = HintText(CustomMessage("They say that #calling the sun in the spotlight by statues# reveals #[[1]]#.",
|
||||
/*german*/ TODO_TRANSLATE,
|
||||
/*french*/ TODO_TRANSLATE, {QM_RED, QM_GREEN}));
|
||||
|
||||
hintTextTable[RHT_CRATE_SPIRIT_TEMPLE] = HintText(CustomMessage("They say that a #crate in Spirit Temple# contains #[[1]]#.",
|
||||
/*german*/ "Man erzählt sich, daß eine #Kiste im Geistertempel# #[[1]]# enthielte.",
|
||||
/*french*/ "Selon moi, une #caisse dans le Temple de l'Esprit# contient #[[1]]#.", {QM_RED, QM_GREEN}));
|
||||
|
||||
@@ -2128,7 +2128,7 @@ void StaticData::HintTable_Init_Exclude_Overworld() {
|
||||
/*french*/ "Selon moi, un #arbre au Ranch Lon Lon# cache #[[1]]#.", { QM_RED, QM_GREEN }));
|
||||
|
||||
hintTextTable[RHT_BUSH_HYRULE_FIELD] =
|
||||
HintText(CustomMessage("They say that a #bush in Hyrle Field# contains #[[1]]#.",
|
||||
HintText(CustomMessage("They say that a #bush in Hyrule Field# contains #[[1]]#.",
|
||||
/*german*/ "",
|
||||
/*french*/ "Selon moi, un #buisson dans la Plaine d'Hyrule# cache #[[1]]#.", { QM_RED, QM_GREEN }));
|
||||
hintTextTable[RHT_BUSH_ZORAS_FOUNTAIN] =
|
||||
|
||||
@@ -149,6 +149,7 @@ static void PlaceItemsForType(RandomizerCheckType rctype, bool overworldActive =
|
||||
void GenerateItemPool() {
|
||||
// RANDOTODO proper removal of items not in pool or logically relevant instead of dummy checks.
|
||||
auto ctx = Rando::Context::GetInstance();
|
||||
ctx->possibleIceTrapModels.clear();
|
||||
itemPool.clear();
|
||||
junkPool.clear();
|
||||
plentifulPool.clear();
|
||||
|
||||
@@ -126,6 +126,7 @@ void GenerateStartingInventory() {
|
||||
AddItemToInventory(RG_NOCTURNE_OF_SHADOW, ctx->GetOption(RSK_STARTING_NOCTURNE_OF_SHADOW) ? 1 : 0);
|
||||
AddItemToInventory(RG_PRELUDE_OF_LIGHT, ctx->GetOption(RSK_STARTING_PRELUDE_OF_LIGHT) ? 1 : 0);
|
||||
AddItemToInventory(RG_KOKIRI_SWORD, ctx->GetOption(RSK_STARTING_KOKIRI_SWORD) ? 1 : 0);
|
||||
AddItemToInventory(RG_MAGIC_BEAN_PACK, ctx->GetOption(RSK_STARTING_BEANS) ? 1 : 0);
|
||||
// if (ProgressiveGoronSword) {
|
||||
// AddItemToInventory(RG_PROGRESSIVE_GORONSWORD, StartingBiggoronSword.Value<uint8_t>());
|
||||
// } else {
|
||||
|
||||
@@ -13,7 +13,7 @@ extern PlayState* gPlayState;
|
||||
|
||||
void BuildHintStoneMessage(uint16_t* textId, bool* loadFromMessageTable) {
|
||||
if ((RAND_GET_OPTION(RSK_GOSSIP_STONE_HINTS).Is(RO_GOSSIP_STONES_NEED_TRUTH) &&
|
||||
Player_GetMask(gPlayState) == PLAYER_MASK_TRUTH) ||
|
||||
Player_GetMask(gPlayState) != PLAYER_MASK_TRUTH) ||
|
||||
(RAND_GET_OPTION(RSK_GOSSIP_STONE_HINTS).Is(RO_GOSSIP_STONES_NEED_STONE) &&
|
||||
CHECK_QUEST_ITEM(QUEST_STONE_OF_AGONY) == 0)) {
|
||||
return;
|
||||
|
||||
@@ -24,8 +24,8 @@ extern PlayState* gPlayState;
|
||||
RAND_GET_OPTION(RSK_SHUFFLE_MERCHANTS).Is(RO_SHUFFLE_MERCHANTS_ALL))
|
||||
|
||||
void BuildMerchantMessage(CustomMessage& msg, RandomizerCheck rc, bool mysterious = true) {
|
||||
RandomizerGet rgid = RAND_GET_ITEM(rc)->GetPlacedRandomizerGet();
|
||||
uint16_t price = RAND_GET_ITEM(rc)->GetPrice();
|
||||
auto location = RAND_GET_ITEM(rc);
|
||||
RandomizerGet rgid = location->GetPlacedRandomizerGet();
|
||||
CustomMessage itemName;
|
||||
std::string color = Rando::StaticData::RetrieveItem(static_cast<RandomizerGet>(rgid)).GetColor();
|
||||
if (mysterious) {
|
||||
@@ -36,10 +36,15 @@ void BuildMerchantMessage(CustomMessage& msg, RandomizerCheck rc, bool mysteriou
|
||||
itemName = CustomMessage(RAND_GET_OVERRIDE(rc).GetTrickName());
|
||||
color = "%g";
|
||||
} else {
|
||||
itemName = CustomMessage(Rando::StaticData::RetrieveItem(rgid).GetName());
|
||||
const Rando::Item& item = Rando::StaticData::RetrieveItem(rgid);
|
||||
if (Rando::StaticData::GetLocation(rc)->IsShop()) {
|
||||
itemName = CustomMessage(Rando::StaticData::RetrieveItem(rgid).GetName());
|
||||
} else {
|
||||
itemName = item.GetHint().GetHintMessage();
|
||||
}
|
||||
}
|
||||
msg.Replace("[[color]]", color);
|
||||
msg.InsertNames({ itemName, CustomMessage(std::to_string(price)) });
|
||||
msg.InsertNames({ itemName, CustomMessage(std::to_string(location->GetPrice())) });
|
||||
}
|
||||
|
||||
void BuildBeanGuyMessage(uint16_t* textId, bool* loadFromMessageTable) {
|
||||
@@ -49,9 +54,9 @@ void BuildBeanGuyMessage(uint16_t* textId, bool* loadFromMessageTable) {
|
||||
"I never thought I'd say this, but I'm selling the last %rMagic Bean%w.^%y99 Rupees%w, no "
|
||||
"less.\x1B%gYes&No%w",
|
||||
"Ich hätte nie gedacht, daß ich das sage, aber ich verkaufe die letzte^%rWundererbse%w für %y99 "
|
||||
"Rubine%w.\x1B&%gJa&Nein%w",
|
||||
"Je te vends mon dernier %rHaricot&magique%g pour %y99 Rubis%w.\x1B&%gAcheterNe pas acheter%w");
|
||||
msg.Format();
|
||||
"Rubine%w.\x1B%gJa&Nein%w",
|
||||
"Je te vends mon dernier %rHaricot&magique%w pour %y99 Rubis%w.\x1B%gAcheter&Ne pas acheter%w");
|
||||
msg.AutoFormat();
|
||||
} else if (*textId == TEXT_BEAN_SALESMAN_BUY_FOR_10) {
|
||||
msg = CustomMessage("Want to buy [[color]][[1]]%w for %y[[2]] Rupees%w?\x1B%gYes&No%w",
|
||||
"Möchten Sie [[color]][[1]]%w für %y[[2]] Rubin%w kaufen?\x1B%gJa&Nein%w",
|
||||
@@ -105,6 +110,8 @@ void BuildCarpetGuyMessage(uint16_t* textId, bool* loadFromMessageTable) {
|
||||
BuildMerchantMessage(msg, RC_WASTELAND_BOMBCHU_SALESMAN,
|
||||
!RAND_GET_OPTION(RSK_MERCHANT_TEXT_HINT) ||
|
||||
CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("MysteriousShuffle"), 0));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
msg.AutoFormat();
|
||||
msg.LoadIntoFont();
|
||||
|
||||
@@ -140,7 +140,7 @@ void BuildSkulltulaPeopleMessage(uint16_t* textId, bool* loadFromMessageTable) {
|
||||
"et j'aurai quelque chose à te donner! [[color]]([[1]])%w");
|
||||
msg.InsertNumber(count);
|
||||
msg.Replace("[[color]]", item.GetColor());
|
||||
msg.InsertNames({ item.GetName() });
|
||||
msg.InsertNames({ item.GetHint().GetHintMessage() });
|
||||
msg.AutoFormat();
|
||||
msg.LoadIntoFont();
|
||||
*loadFromMessageTable = false;
|
||||
@@ -155,12 +155,10 @@ void Build100SkullsHintMessage(uint16_t* textId, bool* loadFromMessageTable) {
|
||||
/*french*/
|
||||
"Yeaaarrgh! Je suis maudit!^Détruit encore %y100 Araignées de la Malédiction%w "
|
||||
"et j'aurai quelque chose à te donner! [[color]]([[1]])%w");
|
||||
msg.Replace("[[color]]", Rando::StaticData::RetrieveItem(
|
||||
RAND_GET_ITEM_LOC(RC_KAK_100_GOLD_SKULLTULA_REWARD)->GetPlacedRandomizerGet())
|
||||
.GetColor());
|
||||
msg.InsertNames(
|
||||
{ Rando::StaticData::RetrieveItem(RAND_GET_ITEM_LOC(RC_KAK_100_GOLD_SKULLTULA_REWARD)->GetPlacedRandomizerGet())
|
||||
.GetName() });
|
||||
Rando::Item& item =
|
||||
Rando::StaticData::RetrieveItem(RAND_GET_ITEM_LOC(RC_KAK_100_GOLD_SKULLTULA_REWARD)->GetPlacedRandomizerGet());
|
||||
msg.Replace("[[color]]", item.GetColor());
|
||||
msg.InsertNames({ item.GetHint().GetHintMessage() });
|
||||
msg.AutoFormat();
|
||||
msg.LoadIntoFont();
|
||||
*loadFromMessageTable = false;
|
||||
@@ -180,78 +178,43 @@ void BuildGregHintMessage(uint16_t* textId, bool* loadFromMessageTable) {
|
||||
}
|
||||
}
|
||||
|
||||
void BuildMysteriousWarpMessage() {
|
||||
CustomMessage msg = CustomMessage(
|
||||
"Warp to&%ra mysterious place?%w&" + CustomMessage::TWO_WAY_CHOICE() + "%gOK&No%w",
|
||||
"Zu&%reinem mysteriösen Ort%w?&" + CustomMessage::TWO_WAY_CHOICE() + "%gOK&No%w",
|
||||
"Se téléporter vers&%run endroit mystérieux%w?&" + CustomMessage::TWO_WAY_CHOICE() + "%rOK!&Non%w");
|
||||
msg.LoadIntoFont();
|
||||
static void BuildWarpMessage(RandomizerHint rh, bool* loadFromMessageTable) {
|
||||
if (!RAND_GET_OPTION(RSK_WARP_SONG_HINTS)) {
|
||||
CustomMessage msg = CustomMessage(
|
||||
"Warp to&%ra mysterious place?%w&" + CustomMessage::TWO_WAY_CHOICE() + "%gOK&No%w",
|
||||
"Zu&%reinem mysteriösen Ort%w?&" + CustomMessage::TWO_WAY_CHOICE() + "%gOK&No%w",
|
||||
"Se téléporter vers&%run endroit mystérieux%w?&" + CustomMessage::TWO_WAY_CHOICE() + "%rOK!&Non%w");
|
||||
msg.AutoFormat();
|
||||
msg.LoadIntoFont();
|
||||
} else {
|
||||
CustomMessage msg = RAND_GET_HINT(rh)->GetHintMessage(MF_AUTO_FORMAT);
|
||||
msg.LoadIntoFont();
|
||||
}
|
||||
*loadFromMessageTable = false;
|
||||
}
|
||||
|
||||
void BuildMinuetWarpMessage(uint16_t* textId, bool* loadFromMessageTable) {
|
||||
if (!RAND_GET_OPTION(RSK_WARP_SONG_HINTS)) {
|
||||
BuildMysteriousWarpMessage();
|
||||
*loadFromMessageTable = false;
|
||||
return;
|
||||
}
|
||||
CustomMessage msg = RAND_GET_HINT(RH_MINUET_WARP_LOC)->GetHintMessage(MF_AUTO_FORMAT);
|
||||
msg.LoadIntoFont();
|
||||
*loadFromMessageTable = false;
|
||||
BuildWarpMessage(RH_MINUET_WARP_LOC, loadFromMessageTable);
|
||||
}
|
||||
|
||||
void BuildBoleroWarpMessage(uint16_t* textId, bool* loadFromMessageTable) {
|
||||
if (!RAND_GET_OPTION(RSK_WARP_SONG_HINTS)) {
|
||||
BuildMysteriousWarpMessage();
|
||||
*loadFromMessageTable = false;
|
||||
return;
|
||||
}
|
||||
CustomMessage msg = RAND_GET_HINT(RH_BOLERO_WARP_LOC)->GetHintMessage(MF_AUTO_FORMAT);
|
||||
msg.LoadIntoFont();
|
||||
*loadFromMessageTable = false;
|
||||
BuildWarpMessage(RH_BOLERO_WARP_LOC, loadFromMessageTable);
|
||||
}
|
||||
|
||||
void BuildSerenadeWarpMessage(uint16_t* textId, bool* loadFromMessageTable) {
|
||||
if (!RAND_GET_OPTION(RSK_WARP_SONG_HINTS)) {
|
||||
BuildMysteriousWarpMessage();
|
||||
*loadFromMessageTable = false;
|
||||
return;
|
||||
}
|
||||
CustomMessage msg = RAND_GET_HINT(RH_SERENADE_WARP_LOC)->GetHintMessage(MF_AUTO_FORMAT);
|
||||
msg.LoadIntoFont();
|
||||
*loadFromMessageTable = false;
|
||||
BuildWarpMessage(RH_SERENADE_WARP_LOC, loadFromMessageTable);
|
||||
}
|
||||
|
||||
void BuildRequiemWarpMessage(uint16_t* textId, bool* loadFromMessageTable) {
|
||||
if (!RAND_GET_OPTION(RSK_WARP_SONG_HINTS)) {
|
||||
BuildMysteriousWarpMessage();
|
||||
*loadFromMessageTable = false;
|
||||
return;
|
||||
}
|
||||
CustomMessage msg = RAND_GET_HINT(RH_REQUIEM_WARP_LOC)->GetHintMessage(MF_AUTO_FORMAT);
|
||||
msg.LoadIntoFont();
|
||||
*loadFromMessageTable = false;
|
||||
BuildWarpMessage(RH_REQUIEM_WARP_LOC, loadFromMessageTable);
|
||||
}
|
||||
|
||||
void BuildNocturneWarpMessage(uint16_t* textId, bool* loadFromMessageTable) {
|
||||
if (!RAND_GET_OPTION(RSK_WARP_SONG_HINTS)) {
|
||||
BuildMysteriousWarpMessage();
|
||||
*loadFromMessageTable = false;
|
||||
return;
|
||||
}
|
||||
CustomMessage msg = RAND_GET_HINT(RH_NOCTURNE_WARP_LOC)->GetHintMessage(MF_AUTO_FORMAT);
|
||||
msg.LoadIntoFont();
|
||||
*loadFromMessageTable = false;
|
||||
BuildWarpMessage(RH_NOCTURNE_WARP_LOC, loadFromMessageTable);
|
||||
}
|
||||
|
||||
void BuildPreludeWarpMessage(uint16_t* textId, bool* loadFromMessageTable) {
|
||||
if (!RAND_GET_OPTION(RSK_WARP_SONG_HINTS)) {
|
||||
BuildMysteriousWarpMessage();
|
||||
*loadFromMessageTable = false;
|
||||
return;
|
||||
}
|
||||
CustomMessage msg = RAND_GET_HINT(RH_PRELUDE_WARP_LOC)->GetHintMessage(MF_AUTO_FORMAT);
|
||||
msg.LoadIntoFont();
|
||||
*loadFromMessageTable = false;
|
||||
BuildWarpMessage(RH_PRELUDE_WARP_LOC, loadFromMessageTable);
|
||||
}
|
||||
|
||||
void BuildFrogsHintMessage(uint16_t* textId, bool* loadFromMessageTable) {
|
||||
@@ -421,15 +384,17 @@ void RegisterStaticHints() {
|
||||
COND_ID_HOOK(OnOpenText, TEXT_CHEST_GAME_REAL_GAMBLER, RAND_GET_OPTION(RSK_GREG_HINT), BuildGregHintMessage);
|
||||
COND_ID_HOOK(OnOpenText, TEXT_CHEST_GAME_THANKS_A_LOT, RAND_GET_OPTION(RSK_GREG_HINT), BuildGregHintMessage);
|
||||
// Warp
|
||||
COND_ID_HOOK(OnOpenText, TEXT_WARP_MINUET_OF_FOREST, RAND_GET_OPTION(RSK_WARP_SONG_HINTS), BuildMinuetWarpMessage);
|
||||
COND_ID_HOOK(OnOpenText, TEXT_WARP_BOLERO_OF_FIRE, RAND_GET_OPTION(RSK_WARP_SONG_HINTS), BuildBoleroWarpMessage);
|
||||
COND_ID_HOOK(OnOpenText, TEXT_WARP_SERENADE_OF_WATER, RAND_GET_OPTION(RSK_WARP_SONG_HINTS),
|
||||
COND_ID_HOOK(OnOpenText, TEXT_WARP_MINUET_OF_FOREST, RAND_GET_OPTION(RSK_SHUFFLE_WARP_SONGS),
|
||||
BuildMinuetWarpMessage);
|
||||
COND_ID_HOOK(OnOpenText, TEXT_WARP_BOLERO_OF_FIRE, RAND_GET_OPTION(RSK_SHUFFLE_WARP_SONGS), BuildBoleroWarpMessage);
|
||||
COND_ID_HOOK(OnOpenText, TEXT_WARP_SERENADE_OF_WATER, RAND_GET_OPTION(RSK_SHUFFLE_WARP_SONGS),
|
||||
BuildSerenadeWarpMessage);
|
||||
COND_ID_HOOK(OnOpenText, TEXT_WARP_REQUIEM_OF_SPIRIT, RAND_GET_OPTION(RSK_WARP_SONG_HINTS),
|
||||
COND_ID_HOOK(OnOpenText, TEXT_WARP_REQUIEM_OF_SPIRIT, RAND_GET_OPTION(RSK_SHUFFLE_WARP_SONGS),
|
||||
BuildRequiemWarpMessage);
|
||||
COND_ID_HOOK(OnOpenText, TEXT_WARP_NOCTURNE_OF_SHADOW, RAND_GET_OPTION(RSK_WARP_SONG_HINTS),
|
||||
COND_ID_HOOK(OnOpenText, TEXT_WARP_NOCTURNE_OF_SHADOW, RAND_GET_OPTION(RSK_SHUFFLE_WARP_SONGS),
|
||||
BuildNocturneWarpMessage);
|
||||
COND_ID_HOOK(OnOpenText, TEXT_WARP_PRELUDE_OF_LIGHT, RAND_GET_OPTION(RSK_WARP_SONG_HINTS), BuildPreludeWarpMessage);
|
||||
COND_ID_HOOK(OnOpenText, TEXT_WARP_PRELUDE_OF_LIGHT, RAND_GET_OPTION(RSK_SHUFFLE_WARP_SONGS),
|
||||
BuildPreludeWarpMessage);
|
||||
// Frogs
|
||||
COND_ID_HOOK(OnOpenText, TEXT_FROGS_UNDERWATER, RAND_GET_OPTION(RSK_FROGS_HINT), BuildFrogsHintMessage);
|
||||
// Loach
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <libultraship/libultra.h>
|
||||
#include "global.h"
|
||||
#include "soh/ObjectExtension/ObjectExtension.h"
|
||||
#include "item_category_adj.h"
|
||||
|
||||
extern "C" {
|
||||
#include "variables.h"
|
||||
@@ -35,24 +36,7 @@ extern "C" void ObjKibako2_RandomizerDraw(Actor* thisx, PlayState* play) {
|
||||
|
||||
GetItemEntry crateItem =
|
||||
Rando::Context::GetInstance()->GetFinalGIEntry(crateIdentity->randomizerCheck, true, GI_NONE);
|
||||
getItemCategory = crateItem.getItemCategory;
|
||||
|
||||
// If they have bombchus, don't consider the bombchu item major
|
||||
if (INV_CONTENT(ITEM_BOMBCHU) == ITEM_BOMBCHU &&
|
||||
((crateItem.modIndex == MOD_RANDOMIZER && crateItem.getItemId == RG_PROGRESSIVE_BOMBCHU_BAG) ||
|
||||
(crateItem.modIndex == MOD_NONE &&
|
||||
(crateItem.getItemId == GI_BOMBCHUS_5 || crateItem.getItemId == GI_BOMBCHUS_10 ||
|
||||
crateItem.getItemId == GI_BOMBCHUS_20)))) {
|
||||
getItemCategory = ITEM_CATEGORY_JUNK;
|
||||
// If it's a bottle and they already have one, consider the item lesser
|
||||
} else if ((crateItem.modIndex == MOD_RANDOMIZER && crateItem.getItemId >= RG_BOTTLE_WITH_RED_POTION &&
|
||||
crateItem.getItemId <= RG_BOTTLE_WITH_POE) ||
|
||||
(crateItem.modIndex == MOD_NONE &&
|
||||
(crateItem.getItemId == GI_BOTTLE || crateItem.getItemId == GI_MILK_BOTTLE))) {
|
||||
if (gSaveContext.inventory.items[SLOT_BOTTLE_1] != ITEM_NONE) {
|
||||
getItemCategory = ITEM_CATEGORY_LESSER;
|
||||
}
|
||||
}
|
||||
getItemCategory = Randomizer_AdjustItemCategory(crateItem);
|
||||
|
||||
// Change texture
|
||||
switch (getItemCategory) {
|
||||
@@ -102,24 +86,7 @@ extern "C" void ObjKibako_RandomizerDraw(Actor* thisx, PlayState* play) {
|
||||
|
||||
GetItemEntry smallCrateItem =
|
||||
Rando::Context::GetInstance()->GetFinalGIEntry(crateIdentity->randomizerCheck, true, GI_NONE);
|
||||
getItemCategory = smallCrateItem.getItemCategory;
|
||||
|
||||
// If they have bombchus, don't consider the bombchu item major
|
||||
if (INV_CONTENT(ITEM_BOMBCHU) == ITEM_BOMBCHU &&
|
||||
((smallCrateItem.modIndex == MOD_RANDOMIZER && smallCrateItem.getItemId == RG_PROGRESSIVE_BOMBCHU_BAG) ||
|
||||
(smallCrateItem.modIndex == MOD_NONE &&
|
||||
(smallCrateItem.getItemId == GI_BOMBCHUS_5 || smallCrateItem.getItemId == GI_BOMBCHUS_10 ||
|
||||
smallCrateItem.getItemId == GI_BOMBCHUS_20)))) {
|
||||
getItemCategory = ITEM_CATEGORY_JUNK;
|
||||
// If it's a bottle and they already have one, consider the item lesser
|
||||
} else if ((smallCrateItem.modIndex == MOD_RANDOMIZER && smallCrateItem.getItemId >= RG_BOTTLE_WITH_RED_POTION &&
|
||||
smallCrateItem.getItemId <= RG_BOTTLE_WITH_POE) ||
|
||||
(smallCrateItem.modIndex == MOD_NONE &&
|
||||
(smallCrateItem.getItemId == GI_BOTTLE || smallCrateItem.getItemId == GI_MILK_BOTTLE))) {
|
||||
if (gSaveContext.inventory.items[SLOT_BOTTLE_1] != ITEM_NONE) {
|
||||
getItemCategory = ITEM_CATEGORY_LESSER;
|
||||
}
|
||||
}
|
||||
getItemCategory = Randomizer_AdjustItemCategory(smallCrateItem);
|
||||
|
||||
// Change texture
|
||||
switch (getItemCategory) {
|
||||
|
||||
@@ -106,11 +106,13 @@ void RegisterShuffleFairies() {
|
||||
|
||||
COND_VB_SHOULD(VB_BOTTLE_ACTOR, shouldRegister, {
|
||||
Actor* actor = va_arg(args, Actor*);
|
||||
const auto fairyIdentity = ObjectExtension::GetInstance().Get<CheckIdentity>(actor);
|
||||
if (fairyIdentity != nullptr && fairyIdentity->randomizerInf != RAND_INF_MAX) {
|
||||
Flags_SetRandomizerInf(fairyIdentity->randomizerInf);
|
||||
actor->parent = &GET_PLAYER(gPlayState)->actor;
|
||||
*should = false;
|
||||
if (actor->id == ACTOR_EN_ELF) {
|
||||
const auto fairyIdentity = ObjectExtension::GetInstance().Get<CheckIdentity>(actor);
|
||||
if (fairyIdentity != nullptr && fairyIdentity->randomizerInf != RAND_INF_MAX) {
|
||||
Flags_SetRandomizerInf(fairyIdentity->randomizerInf);
|
||||
actor->parent = &GET_PLAYER(gPlayState)->actor;
|
||||
*should = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <soh/OTRGlobals.h>
|
||||
#include "soh_assets.h"
|
||||
#include "static_data.h"
|
||||
#include "item_category_adj.h"
|
||||
#include "soh/ObjectExtension/ObjectExtension.h"
|
||||
|
||||
extern "C" {
|
||||
@@ -38,7 +39,7 @@ extern "C" void EnKusa_RandomizerDraw(Actor* thisx, PlayState* play) {
|
||||
if (csmc && (!requiresStoneAgony || (requiresStoneAgony && CHECK_QUEST_ITEM(QUEST_STONE_OF_AGONY)))) {
|
||||
auto itemEntry =
|
||||
Rando::Context::GetInstance()->GetFinalGIEntry(grassIdentity->randomizerCheck, true, GI_NONE);
|
||||
GetItemCategory getItemCategory = itemEntry.getItemCategory;
|
||||
GetItemCategory getItemCategory = Randomizer_AdjustItemCategory(itemEntry);
|
||||
|
||||
switch (getItemCategory) {
|
||||
case ITEM_CATEGORY_JUNK:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "soh/OTRGlobals.h"
|
||||
#include "soh_assets.h"
|
||||
#include "static_data.h"
|
||||
#include "item_category_adj.h"
|
||||
#include "soh/ObjectExtension/ObjectExtension.h"
|
||||
|
||||
extern "C" {
|
||||
@@ -28,7 +29,7 @@ extern "C" void ObjTsubo_RandomizerDraw(Actor* thisx, PlayState* play) {
|
||||
if (csmc && (!requiresStoneAgony || (requiresStoneAgony && CHECK_QUEST_ITEM(QUEST_STONE_OF_AGONY)))) {
|
||||
auto itemEntry =
|
||||
Rando::Context::GetInstance()->GetFinalGIEntry(potIdentity->randomizerCheck, true, GI_NONE);
|
||||
GetItemCategory getItemCategory = itemEntry.getItemCategory;
|
||||
GetItemCategory getItemCategory = Randomizer_AdjustItemCategory(itemEntry);
|
||||
|
||||
switch (getItemCategory) {
|
||||
case ITEM_CATEGORY_LESSER:
|
||||
@@ -54,7 +55,7 @@ extern "C" void ObjTsubo_RandomizerDraw(Actor* thisx, PlayState* play) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gPotStandardDL);
|
||||
gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gPotMajorDL);
|
||||
}
|
||||
} else {
|
||||
gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gPotDL);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "soh_assets.h"
|
||||
#include "static_data.h"
|
||||
#include "soh/ObjectExtension/ObjectExtension.h"
|
||||
#include "item_category_adj.h"
|
||||
|
||||
extern "C" {
|
||||
#include "variables.h"
|
||||
@@ -60,24 +61,7 @@ extern "C" void EnWood02_RandomizerDraw(Actor* thisx, PlayState* play) {
|
||||
getItemCategory = ITEM_CATEGORY_JUNK;
|
||||
} else {
|
||||
treeItem = Rando::Context::GetInstance()->GetFinalGIEntry(treeIdentity->randomizerCheck, true, GI_NONE);
|
||||
getItemCategory = treeItem.getItemCategory;
|
||||
|
||||
// If they have bombchus, don't consider the bombchu item major
|
||||
if (INV_CONTENT(ITEM_BOMBCHU) == ITEM_BOMBCHU &&
|
||||
((treeItem.modIndex == MOD_RANDOMIZER && treeItem.getItemId == RG_PROGRESSIVE_BOMBCHU_BAG) ||
|
||||
(treeItem.modIndex == MOD_NONE &&
|
||||
(treeItem.getItemId == GI_BOMBCHUS_5 || treeItem.getItemId == GI_BOMBCHUS_10 ||
|
||||
treeItem.getItemId == GI_BOMBCHUS_20)))) {
|
||||
getItemCategory = ITEM_CATEGORY_JUNK;
|
||||
// If it's a bottle and they already have one, consider the item lesser
|
||||
} else if ((treeItem.modIndex == MOD_RANDOMIZER && treeItem.getItemId >= RG_BOTTLE_WITH_RED_POTION &&
|
||||
treeItem.getItemId <= RG_BOTTLE_WITH_POE) ||
|
||||
(treeItem.modIndex == MOD_NONE &&
|
||||
(treeItem.getItemId == GI_BOTTLE || treeItem.getItemId == GI_MILK_BOTTLE))) {
|
||||
if (gSaveContext.inventory.items[SLOT_BOTTLE_1] != ITEM_NONE) {
|
||||
getItemCategory = ITEM_CATEGORY_LESSER;
|
||||
}
|
||||
}
|
||||
getItemCategory = Randomizer_AdjustItemCategory(treeItem);
|
||||
}
|
||||
|
||||
GraphicsContext* gfxCtx = play->state.gfxCtx;
|
||||
|
||||
@@ -513,17 +513,13 @@ const HintText Hint::GetItemHintText(uint8_t slot, bool mysterious) const {
|
||||
auto ctx = Rando::Context::GetInstance();
|
||||
RandomizerCheck hintedCheck = locations[slot];
|
||||
RandomizerGet targetRG = ctx->GetItemLocation(hintedCheck)->GetPlacedRandomizerGet();
|
||||
CustomMessage msg;
|
||||
if (mysterious) {
|
||||
return StaticData::hintTextTable[RHT_MYSTERIOUS_ITEM];
|
||||
} else if (!ctx->GetOption(RSK_HINT_CLARITY).Is(RO_HINT_CLARITY_AMBIGUOUS) &&
|
||||
targetRG == RG_ICE_TRAP) { // RANDOTODO store in item hint instead of item
|
||||
msg = CustomMessage({ ctx->overrides[hintedCheck].GetTrickName() });
|
||||
} else if (targetRG == RG_ICE_TRAP) { // RANDOTODO store in item hint instead of item
|
||||
return HintText(CustomMessage({ ctx->overrides[hintedCheck].GetTrickName() }));
|
||||
} else {
|
||||
msg = ctx->GetItemLocation(hintedCheck)->GetPlacedItem().GetName();
|
||||
return ctx->GetItemLocation(hintedCheck)->GetPlacedItem().GetHint();
|
||||
}
|
||||
msg = CustomMessage(ctx->GetItemLocation(hintedCheck)->GetPlacedItem().GetArticle()) + msg;
|
||||
return HintText(msg);
|
||||
}
|
||||
|
||||
const HintText Hint::GetAreaHintText(uint8_t slot) const {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "soh/SaveManager.h"
|
||||
#include "soh/ShipInit.hpp"
|
||||
#include "soh/ObjectExtension/ObjectExtension.h"
|
||||
#include "item_category_adj.h"
|
||||
|
||||
extern "C" {
|
||||
#include "macros.h"
|
||||
@@ -370,6 +371,7 @@ void RandomizerOnPlayerUpdateForRCQueueHandler() {
|
||||
GetItemID vanillaItem = (GetItemID)Rando::StaticData::RetrieveItem(vanillaRandomizerGet).GetItemID();
|
||||
GetItemEntry getItemEntry =
|
||||
Rando::Context::GetInstance()->GetFinalGIEntry(rc, true, (GetItemID)vanillaRandomizerGet);
|
||||
GetItemCategory getItemCategory = Randomizer_AdjustItemCategory(getItemEntry);
|
||||
|
||||
if (loc->HasObtained()) {
|
||||
SPDLOG_INFO("RC {} already obtained, skipping", static_cast<uint32_t>(rc));
|
||||
@@ -393,13 +395,8 @@ void RandomizerOnPlayerUpdateForRCQueueHandler() {
|
||||
// crude fix to ensure map hints are readable. Ideally replace with better hint tracking.
|
||||
!(getItemEntry.getItemId >= RG_DEKU_TREE_MAP && getItemEntry.getItemId <= RG_ICE_CAVERN_MAP &&
|
||||
getItemEntry.modIndex == MOD_RANDOMIZER) &&
|
||||
(getItemEntry.getItemCategory == ITEM_CATEGORY_JUNK ||
|
||||
getItemEntry.getItemCategory == ITEM_CATEGORY_SKULLTULA_TOKEN ||
|
||||
getItemEntry.getItemCategory == ITEM_CATEGORY_HEALTH ||
|
||||
getItemEntry.getItemCategory == ITEM_CATEGORY_LESSER ||
|
||||
// Treat small keys as junk if Skeleton Key is obtained.
|
||||
(getItemEntry.getItemCategory == ITEM_CATEGORY_SMALL_KEY &&
|
||||
Flags_GetRandomizerInf(RAND_INF_HAS_SKELETON_KEY))))))) {
|
||||
(getItemCategory == ITEM_CATEGORY_JUNK || getItemCategory == ITEM_CATEGORY_SKULLTULA_TOKEN ||
|
||||
getItemCategory == ITEM_CATEGORY_HEALTH || getItemCategory == ITEM_CATEGORY_LESSER))))) {
|
||||
Item_DropCollectible(gPlayState, &spawnPos, static_cast<int16_t>(ITEM00_SOH_GIVE_ITEM_ENTRY | 0x8000));
|
||||
}
|
||||
}
|
||||
@@ -446,6 +443,54 @@ void RandomizerOnItemReceiveHandler(GetItemEntry receivedItemEntry) {
|
||||
randomizerQueuedItemEntry = GET_ITEM_NONE;
|
||||
}
|
||||
|
||||
if (receivedItemEntry.modIndex == MOD_RANDOMIZER && receivedItemEntry.getItemId == RG_MAGIC_BEAN_PACK) {
|
||||
if (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SKIP_PLANTING_BEANS)) {
|
||||
gSaveContext.sceneFlags[SCENE_DEATH_MOUNTAIN_CRATER].swch |= (1 << 3);
|
||||
if (gPlayState->sceneNum == SCENE_DEATH_MOUNTAIN_CRATER) {
|
||||
Flags_SetSwitch(gPlayState, 3);
|
||||
}
|
||||
gSaveContext.sceneFlags[SCENE_DEATH_MOUNTAIN_TRAIL].swch |= (1 << 6);
|
||||
if (gPlayState->sceneNum == SCENE_DEATH_MOUNTAIN_TRAIL) {
|
||||
Flags_SetSwitch(gPlayState, 6);
|
||||
}
|
||||
gSaveContext.sceneFlags[SCENE_DESERT_COLOSSUS].swch |= (1 << 24);
|
||||
if (gPlayState->sceneNum == SCENE_DESERT_COLOSSUS) {
|
||||
Flags_SetSwitch(gPlayState, 24);
|
||||
}
|
||||
gSaveContext.sceneFlags[SCENE_GERUDO_VALLEY].swch |= (1 << 3);
|
||||
if (gPlayState->sceneNum == SCENE_GERUDO_VALLEY) {
|
||||
Flags_SetSwitch(gPlayState, 3);
|
||||
}
|
||||
gSaveContext.sceneFlags[SCENE_GRAVEYARD].swch |= (1 << 3);
|
||||
if (gPlayState->sceneNum == SCENE_GRAVEYARD) {
|
||||
Flags_SetSwitch(gPlayState, 3);
|
||||
}
|
||||
gSaveContext.sceneFlags[SCENE_KOKIRI_FOREST].swch |= (1 << 9);
|
||||
if (gPlayState->sceneNum == SCENE_KOKIRI_FOREST) {
|
||||
Flags_SetSwitch(gPlayState, 9);
|
||||
}
|
||||
gSaveContext.sceneFlags[SCENE_LAKE_HYLIA].swch |= (1 << 1);
|
||||
if (gPlayState->sceneNum == SCENE_LAKE_HYLIA) {
|
||||
Flags_SetSwitch(gPlayState, 1);
|
||||
}
|
||||
gSaveContext.sceneFlags[SCENE_LOST_WOODS].swch |= (1 << 4) | (1 << 18);
|
||||
if (gPlayState->sceneNum == SCENE_LOST_WOODS) {
|
||||
Flags_SetSwitch(gPlayState, 4);
|
||||
Flags_SetSwitch(gPlayState, 18);
|
||||
}
|
||||
gSaveContext.sceneFlags[SCENE_ZORAS_RIVER].swch |= (1 << 3);
|
||||
if (gPlayState->sceneNum == SCENE_ZORAS_RIVER) {
|
||||
Flags_SetSwitch(gPlayState, 3);
|
||||
}
|
||||
ObjBean* bean = (ObjBean*)Actor_Find(&gPlayState->actorCtx, ACTOR_OBJ_BEAN, ACTORCAT_BG);
|
||||
if (bean != nullptr) {
|
||||
Flags_SetSwitch(gPlayState, bean->dyna.actor.params & 0x3F);
|
||||
func_80B8FE00(bean);
|
||||
}
|
||||
AMMO(ITEM_BEAN) = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (receivedItemEntry.modIndex == MOD_NONE &&
|
||||
(receivedItemEntry.itemId == ITEM_HEART_PIECE || receivedItemEntry.itemId == ITEM_HEART_PIECE_2 ||
|
||||
receivedItemEntry.itemId == ITEM_HEART_CONTAINER)) {
|
||||
@@ -980,6 +1025,15 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l
|
||||
OTRGlobals::Instance->gRandoContext->GetItemLocation(RC_ZR_MAGIC_BEAN_SALESMAN)->GetPrice();
|
||||
} else if (RAND_GET_OPTION(RSK_SKIP_PLANTING_BEANS)) {
|
||||
*should = gSaveContext.rupees >= 60;
|
||||
} else if (BEANS_BOUGHT == 9) {
|
||||
*should = gSaveContext.rupees >= 99;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VB_MAGIC_BEAN_SALESMAN_TAKE_MONEY: {
|
||||
if (BEANS_BOUGHT == 9) {
|
||||
Rupees_ChangeBy(-99);
|
||||
*should = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -2562,7 +2616,16 @@ void RandomizerOnPlayerUpdateHandler() {
|
||||
gSaveContext.respawn[RESPAWN_MODE_DOWN].yaw = respawn->second.yaw;
|
||||
}
|
||||
|
||||
Play_TriggerVoidOut(gPlayState);
|
||||
if (gPlayState->sceneNum == SCENE_GROTTOS) {
|
||||
// RESPAWN_MODE_DOWN isn't refreshed on grotto entry, reload grotto instead
|
||||
gPlayState->nextEntranceIndex = gSaveContext.entranceIndex;
|
||||
gPlayState->transitionTrigger = TRANS_TRIGGER_START;
|
||||
gPlayState->transitionType = TRANS_TYPE_FADE_BLACK;
|
||||
gSaveContext.nextTransitionType = TRANS_TYPE_FADE_BLACK;
|
||||
gSaveContext.respawnFlag = 0;
|
||||
} else {
|
||||
Play_TriggerVoidOut(gPlayState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2575,8 +2638,14 @@ void RandomizerOnPlayerUpdateHandler() {
|
||||
}
|
||||
|
||||
if (!GameInteractor::IsGameplayPaused() && RAND_GET_OPTION(RSK_TRIFORCE_HUNT).IsNot(RO_TRIFORCE_HUNT_OFF)) {
|
||||
// Warp to credits
|
||||
if (GameInteractor::State::TriforceHuntCreditsWarpActive) {
|
||||
// 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;
|
||||
gSaveContext.nextCutsceneIndex = 0xFFF2;
|
||||
gPlayState->transitionTrigger = TRANS_TRIGGER_START;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <memory>
|
||||
|
||||
#include "3drando/text.hpp"
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
#include <stdint.h>
|
||||
#include "item_category_adj.h"
|
||||
#include "z64item.h"
|
||||
#include "variables.h"
|
||||
#include "macros.h"
|
||||
|
||||
GetItemCategory Randomizer_AdjustItemCategory(GetItemEntry item) {
|
||||
GetItemCategory category = item.getItemCategory;
|
||||
|
||||
// Downgrade bombchus to lesser if the player already has bombchus
|
||||
if (INV_CONTENT(ITEM_BOMBCHU) == ITEM_BOMBCHU &&
|
||||
((item.modIndex == MOD_RANDOMIZER && item.getItemId == RG_PROGRESSIVE_BOMBCHU_BAG) ||
|
||||
(item.modIndex == MOD_NONE &&
|
||||
(item.getItemId == GI_BOMBCHUS_5 || item.getItemId == GI_BOMBCHUS_10 || item.getItemId == GI_BOMBCHUS_20)))) {
|
||||
category = ITEM_CATEGORY_LESSER;
|
||||
}
|
||||
|
||||
// Downgrade bottles to lesser if the player already has a bottle
|
||||
if ((item.modIndex == MOD_RANDOMIZER && item.getItemId >= RG_BOTTLE_WITH_RED_POTION &&
|
||||
item.getItemId <= RG_BOTTLE_WITH_POE) ||
|
||||
(item.modIndex == MOD_NONE && (item.getItemId == GI_BOTTLE || item.getItemId == GI_MILK_BOTTLE))) {
|
||||
if (gSaveContext.inventory.items[SLOT_BOTTLE_1] != ITEM_NONE) {
|
||||
category = ITEM_CATEGORY_LESSER;
|
||||
}
|
||||
}
|
||||
|
||||
return category;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef ITEM_CATEGORY_ADJ_H
|
||||
#define ITEM_CATEGORY_ADJ_H
|
||||
|
||||
#include "../item-tables/ItemTableTypes.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
GetItemCategory Randomizer_AdjustItemCategory(GetItemEntry item);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "soh_assets.h"
|
||||
#include "static_data.h"
|
||||
#include "SeedContext.h"
|
||||
#include "logic.h"
|
||||
#include "textures/icon_item_24_static/icon_item_24_static.h"
|
||||
#include "textures/icon_item_static/icon_item_static.h"
|
||||
#include "z64object.h"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "location.h"
|
||||
#include "static_data.h"
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
#include "option.h"
|
||||
|
||||
@@ -65,7 +64,7 @@ bool Rando::Location::IsOverworld() const {
|
||||
}
|
||||
|
||||
bool Rando::Location::IsShop() const {
|
||||
return scene >= SCENE_BAZAAR && scene <= SCENE_BOMBCHU_SHOP;
|
||||
return (scene >= SCENE_BAZAAR && scene <= SCENE_BOMBCHU_SHOP) || scene == SCENE_TEST01;
|
||||
}
|
||||
|
||||
bool Rando::Location::IsVanillaCompletion() const {
|
||||
|
||||
@@ -31,8 +31,8 @@ void RegionTable_Init_BottomOfTheWell() {
|
||||
EVENT_ACCESS(LOGIC_BOTW_LOWERED_WATER, logic->CanUse(RG_ZELDAS_LULLABY)),
|
||||
}, {
|
||||
//Locations
|
||||
LOCATION(RC_BOTTOM_OF_THE_WELL_FRONT_LEFT_FAKE_WALL_CHEST, ctx->GetTrickOption(RT_LENS_BOTW) || logic->CanUse(RG_LENS_OF_TRUTH) && logic->HasItem(RG_OPEN_CHEST)),
|
||||
LOCATION(RC_BOTTOM_OF_THE_WELL_RIGHT_BOTTOM_FAKE_WALL_CHEST, ctx->GetTrickOption(RT_LENS_BOTW) || logic->CanUse(RG_LENS_OF_TRUTH) && logic->HasItem(RG_OPEN_CHEST)),
|
||||
LOCATION(RC_BOTTOM_OF_THE_WELL_FRONT_LEFT_FAKE_WALL_CHEST, (ctx->GetTrickOption(RT_LENS_BOTW) || logic->CanUse(RG_LENS_OF_TRUTH)) && logic->HasItem(RG_OPEN_CHEST)),
|
||||
LOCATION(RC_BOTTOM_OF_THE_WELL_RIGHT_BOTTOM_FAKE_WALL_CHEST, (ctx->GetTrickOption(RT_LENS_BOTW) || logic->CanUse(RG_LENS_OF_TRUTH)) && logic->HasItem(RG_OPEN_CHEST)),
|
||||
LOCATION(RC_BOTTOM_OF_THE_WELL_FRONT_CENTER_BOMBABLE_CHEST, logic->HasExplosives() && logic->HasItem(RG_OPEN_CHEST)),
|
||||
LOCATION(RC_BOTTOM_OF_THE_WELL_BACK_LEFT_BOMBABLE_CHEST, logic->HasExplosives() && (ctx->GetTrickOption(RT_LENS_BOTW) || logic->CanUse(RG_LENS_OF_TRUTH)) && logic->HasItem(RG_OPEN_CHEST)),
|
||||
LOCATION(RC_BOTTOM_OF_THE_WELL_UNDERWATER_FRONT_CHEST, (logic->Get(LOGIC_BOTW_LOWERED_WATER) && logic->HasItem(RG_OPEN_CHEST)) || logic->CanOpenUnderwaterChest()),
|
||||
@@ -265,11 +265,10 @@ void RegionTable_Init_BottomOfTheWell() {
|
||||
// Fairies are in slingshot wonder item, & pot behind grate. Pot can also be broken with boomerang trick
|
||||
EVENT_ACCESS(LOGIC_FAIRY_ACCESS, (logic->IsChild && logic->CanUse(RG_FAIRY_SLINGSHOT)) ||
|
||||
(AnyAgeTime([]{return logic->BlastOrSmash();}) && logic->CanHitEyeTargets()) ||
|
||||
//Item extension can get a fairy in 1 of 2 ways: we can either shoot the pot through the grate and let the fairy fly through the wall
|
||||
//or we can shoot the eye target through the boulder, but not as adult with bow.
|
||||
//The former cannot be done if the pot has an item in it, as it cannot be collected this way.
|
||||
(ctx->GetTrickOption(RT_ITEM_EXTENSION) &&
|
||||
(logic->IsChild || ctx->GetOption(RSK_SHUFFLE_POTS).Is(RO_SHUFFLE_POTS_OFF) || ctx->GetOption(RSK_SHUFFLE_POTS).Is(RO_SHUFFLE_POTS_OVERWORLD)) ? logic->CanHitEyeTargets() : logic->CanUse(RG_FAIRY_SLINGSHOT))),
|
||||
//Item extension can get a fairy by either shooting the pot through the grate and letting the fairy fly through the wall
|
||||
//This cannot be done if the pot has an item in it, as it cannot be collected this way.
|
||||
(ctx->GetTrickOption(RT_ITEM_EXTENSION) && (ctx->GetOption(RSK_SHUFFLE_POTS).Is(RO_SHUFFLE_POTS_OFF) || ctx->GetOption(RSK_SHUFFLE_POTS).Is(RO_SHUFFLE_POTS_OVERWORLD)) && logic->CanHitEyeTargets()) ||
|
||||
(ctx->GetTrickOption(RT_VISIBLE_COLLISION) && logic->IsChild ? logic->CanHitEyeTargets() : logic->CanUse(RG_FAIRY_SLINGSHOT))),
|
||||
//It is possible to hit the water switch with a pot from RR_BOTW_MQ_MIDDLE, however the hitbox for making it activate is very unintuitive
|
||||
//You have to throw the pot from further back to hit the switch from the front instead of the top, trying to hit the "fingers" directly
|
||||
//This unintuitiveness means it should be a trick. ZL is needed to get a clear path to carry the pot
|
||||
@@ -282,7 +281,7 @@ void RegionTable_Init_BottomOfTheWell() {
|
||||
//Not even bow extension seems to get adult's bow to work
|
||||
//this would be a trick
|
||||
LOCATION(RC_BOTTOM_OF_THE_WELL_MQ_OUTER_LOBBY_POT, (AnyAgeTime([]{return logic->BlastOrSmash();}) && logic->CanHitEyeTargets()) ||
|
||||
(ctx->GetTrickOption(RT_ITEM_EXTENSION) && logic->IsChild ? logic->CanHitEyeTargets() : logic->CanUse(RG_FAIRY_SLINGSHOT))),
|
||||
(ctx->GetTrickOption(RT_VISIBLE_COLLISION) && logic->IsChild ? logic->CanHitEyeTargets() : logic->CanUse(RG_FAIRY_SLINGSHOT))),
|
||||
LOCATION(RC_BOTTOM_OF_THE_WELL_MQ_BOMB_LEFT_HEART, logic->HasExplosives()),
|
||||
LOCATION(RC_BOTTOM_OF_THE_WELL_MQ_BOMB_RIGHT_HEART, logic->HasExplosives()),
|
||||
}, {
|
||||
|
||||
@@ -244,7 +244,7 @@ void RegionTable_Init_DekuTree() {
|
||||
}, {
|
||||
//Locations
|
||||
LOCATION(RC_DEKU_TREE_MQ_MAP_CHEST, logic->HasItem(RG_OPEN_CHEST)),
|
||||
LOCATION(RC_DEKU_TREE_MQ_GS_LOBBY, logic->CanGetEnemyDrop(RE_GOLD_SKULLTULA)),
|
||||
LOCATION(RC_DEKU_TREE_MQ_GS_LOBBY, (logic->CanBreakCrates() || ctx->GetTrickOption(RT_VISIBLE_COLLISION)) && logic->CanGetEnemyDrop(RE_GOLD_SKULLTULA)),
|
||||
LOCATION(RC_DEKU_TREE_MQ_LOBBY_HEART, true),
|
||||
LOCATION(RC_DEKU_TREE_MQ_2F_GRASS_1, logic->CanCutShrubs()),
|
||||
LOCATION(RC_DEKU_TREE_MQ_2F_GRASS_2, logic->CanCutShrubs()),
|
||||
|
||||
@@ -725,7 +725,7 @@ void RegionTable_Init_FireTemple() {
|
||||
|
||||
areaTable[RR_FIRE_TEMPLE_MQ_MAZE_CRATE_CAGE] = Region("Fire Temple MQ Maze Crate Cage", SCENE_FIRE_TEMPLE, {}, {
|
||||
//Locations
|
||||
LOCATION(RC_FIRE_TEMPLE_MQ_LIZALFOS_MAZE_LOWER_CHEST, logic->HasItem(RG_OPEN_CHEST)),
|
||||
LOCATION(RC_FIRE_TEMPLE_MQ_LIZALFOS_MAZE_LOWER_CHEST, logic->HasItem(RG_OPEN_CHEST) && (ctx->GetTrickOption(RT_VISIBLE_COLLISION) || logic->CanBreakCrates())),
|
||||
LOCATION(RC_FIRE_TEMPLE_MQ_LIZALFOS_MAZE_LOWER_CRATE_1, logic->CanBreakCrates()),
|
||||
LOCATION(RC_FIRE_TEMPLE_MQ_LIZALFOS_MAZE_LOWER_CRATE_2, logic->CanBreakCrates()),
|
||||
LOCATION(RC_FIRE_TEMPLE_MQ_LIZALFOS_MAZE_LOWER_CRATE_3, logic->CanBreakCrates()),
|
||||
@@ -739,9 +739,8 @@ void RegionTable_Init_FireTemple() {
|
||||
areaTable[RR_FIRE_TEMPLE_MQ_UPPER_LIZALFOS_MAZE] = Region("Fire Temple MQ Upper Lizalfos Maze", SCENE_FIRE_TEMPLE, {}, {}, {
|
||||
//Exits
|
||||
ENTRANCE(RR_FIRE_TEMPLE_MQ_LOWER_LIZALFOS_MAZE, true),
|
||||
//this cage is much more lenient than the lower cage as the switch is close to the front. sling, rang and bow all hit the switch easily, though might be too unintuitive for default logic
|
||||
//This shouldn't come up in most cases anyway as most methods to get here need either a melee weapon or explosives
|
||||
ENTRANCE(RR_FIRE_TEMPLE_MQ_MAZE_BOX_CAGE, AnyAgeTime([]{return logic->CanJumpslash() || logic->HasExplosives();})),
|
||||
ENTRANCE(RR_FIRE_TEMPLE_MQ_MAZE_BOX_CAGE, AnyAgeTime([]{return logic->CanJumpslash() || logic->HasExplosives() ||
|
||||
(ctx->GetTrickOption(RT_VISIBLE_COLLISION) && (logic->CanUse(RG_FAIRY_BOW) || logic->CanUse(RG_FAIRY_SLINGSHOT) || logic->CanUse(RG_BOOMERANG)));})),
|
||||
ENTRANCE(RR_FIRE_TEMPLE_MQ_SHORTCUT_CLIMB, logic->HasExplosives()),
|
||||
//Implies RR_FIRE_TEMPLE_MQ_LOWER_LIZALFOS_MAZE access
|
||||
ENTRANCE(RR_FIRE_TEMPLE_MQ_ABOVE_MAZE, logic->HasExplosives() && logic->CanUse(RG_MEGATON_HAMMER) && (logic->CanUse(RG_LONGSHOT) || (logic->CanUse(RG_HOOKSHOT) && logic->CanUse(RG_SONG_OF_TIME)))),
|
||||
@@ -750,7 +749,7 @@ void RegionTable_Init_FireTemple() {
|
||||
|
||||
areaTable[RR_FIRE_TEMPLE_MQ_MAZE_BOX_CAGE] = Region("Fire Temple MQ Maze Box Cage", SCENE_FIRE_TEMPLE, {}, {
|
||||
//Locations
|
||||
LOCATION(RC_FIRE_TEMPLE_MQ_LIZALFOS_MAZE_UPPER_CHEST, logic->HasItem(RG_OPEN_CHEST)),
|
||||
LOCATION(RC_FIRE_TEMPLE_MQ_LIZALFOS_MAZE_UPPER_CHEST, logic->HasItem(RG_OPEN_CHEST) && (ctx->GetTrickOption(RT_VISIBLE_COLLISION) || logic->CanBreakCrates())),
|
||||
LOCATION(RC_FIRE_TEMPLE_MQ_LIZALFOS_MAZE_UPPER_CRATE_1, logic->CanBreakCrates()),
|
||||
LOCATION(RC_FIRE_TEMPLE_MQ_LIZALFOS_MAZE_UPPER_CRATE_2, logic->CanBreakCrates()),
|
||||
LOCATION(RC_FIRE_TEMPLE_MQ_LIZALFOS_MAZE_UPPER_CRATE_3, logic->CanBreakCrates()),
|
||||
@@ -1062,7 +1061,7 @@ void RegionTable_Init_FireTemple() {
|
||||
|
||||
areaTable[RR_FIRE_TEMPLE_BOSS_ROOM] = Region("Fire Temple Boss Room", SCENE_FIRE_TEMPLE_BOSS, {
|
||||
// Events
|
||||
EVENT_ACCESS(LOGIC_FIRE_TEMPLE_CLEAR, logic->FireTimer() >= 64 && logic->CanKillEnemy(RE_VOLVAGIA)),
|
||||
EVENT_ACCESS(LOGIC_FIRE_TEMPLE_CLEAR, logic->CanUse(RG_GORON_TUNIC) && logic->CanKillEnemy(RE_VOLVAGIA)),
|
||||
}, {
|
||||
// Locations
|
||||
LOCATION(RC_FIRE_TEMPLE_VOLVAGIA_HEART, logic->Get(LOGIC_FIRE_TEMPLE_CLEAR)),
|
||||
|
||||
@@ -461,7 +461,8 @@ void RegionTable_Init_GanonsCastle() {
|
||||
|
||||
areaTable[RR_GANONS_CASTLE_MQ_FIRE_TRIAL_OPEN_DOOR] = Region("Ganon's Castle MQ Fire Trial Open Door", SCENE_INSIDE_GANONS_CASTLE, {}, {}, {
|
||||
//Exits
|
||||
ENTRANCE(RR_GANONS_CASTLE_MQ_MAIN, true)
|
||||
ENTRANCE(RR_GANONS_CASTLE_MQ_MAIN, true),
|
||||
ENTRANCE(RR_GANONS_CASTLE_MQ_FIRE_TRIAL_FROM_OPEN, true),
|
||||
});
|
||||
|
||||
areaTable[RR_GANONS_CASTLE_MQ_FIRE_TRIAL_FROM_OPEN] = Region("Ganon's Castle MQ Fire Trial From Open Door", SCENE_INSIDE_GANONS_CASTLE, {
|
||||
@@ -476,7 +477,7 @@ void RegionTable_Init_GanonsCastle() {
|
||||
|
||||
areaTable[RR_GANONS_CASTLE_MQ_FIRE_TRIAL_FROM_BARRED] = Region("Ganon's Castle MQ Fire Trial From Barred Door", SCENE_INSIDE_GANONS_CASTLE, {}, {}, {
|
||||
//Exits
|
||||
ENTRANCE(RR_GANONS_CASTLE_MQ_FIRE_TRIAL_BARRED_DOOR, true)
|
||||
ENTRANCE(RR_GANONS_CASTLE_MQ_FIRE_TRIAL_BARRED_DOOR, true),
|
||||
});
|
||||
|
||||
areaTable[RR_GANONS_CASTLE_MQ_FIRE_TRIAL_BARRED_DOOR] = Region("Ganon's Castle MQ Fire Trial Barred Door", SCENE_INSIDE_GANONS_CASTLE, {}, {}, {
|
||||
|
||||
@@ -50,9 +50,9 @@ void RegionTable_Init_IceCavern() {
|
||||
}, {
|
||||
//Locations
|
||||
LOCATION(RC_ICE_CAVERN_MAP_CHEST, logic->BlueFire() && logic->HasItem(RG_OPEN_CHEST)),
|
||||
// very easy to break pot through ice
|
||||
// Bow extension is possible, but very precise: X = 403, Z = 2062-3, Rot = -11475, needs a setup and is its own trick
|
||||
LOCATION(RC_ICE_CAVERN_FROZEN_POT_1, (logic->CanBreakPots() && logic->BlueFire()) || logic->HasExplosives() ||
|
||||
(ctx->GetTrickOption(RT_VISIBLE_COLLISION) && ((logic->CanStandingShield() && logic->CanUse(RG_KOKIRI_SWORD)) || logic->CanUse(RG_MASTER_SWORD) || logic->CanUse(RG_BIGGORON_SWORD) || logic->CanUse(RG_MEGATON_HAMMER))) ||
|
||||
(ctx->GetTrickOption(RT_VISIBLE_COLLISION) && logic->CanJumpslash()) ||
|
||||
(ctx->GetTrickOption(RT_ITEM_EXTENSION) && logic->CanUse(RG_HOOKSHOT))),
|
||||
LOCATION(RC_ICE_CAVERN_MAP_ROOM_LEFT_HEART, true),
|
||||
LOCATION(RC_ICE_CAVERN_MAP_ROOM_MIDDLE_HEART, true),
|
||||
|
||||
@@ -155,7 +155,7 @@ void RegionTable_Init_ShadowTemple() {
|
||||
LOCATION(RC_SHADOW_TEMPLE_FALLING_SPIKES_POT_3, logic->CanUse(RG_BOOMERANG)),
|
||||
}, {
|
||||
//Exits
|
||||
ENTRANCE(RR_SHADOW_TEMPLE_LOWER_HUGE_PIT, true),
|
||||
ENTRANCE(RR_SHADOW_TEMPLE_LOWER_HUGE_PIT, !!ctx->GetTrickOption(RT_VISIBLE_COLLISION)),
|
||||
ENTRANCE(RR_SHADOW_TEMPLE_STONE_UMBRELLA_UPPER, ctx->GetTrickOption(RT_SHADOW_UMBRELLA_CLIP) || (ctx->GetTrickOption(RT_DAMAGE_BOOST_SIMPLE) && logic->TakeDamage()) || (logic->IsAdult && ((ctx->GetTrickOption(RT_SHADOW_UMBRELLA_HOVER) && logic->CanUse(RG_HOVER_BOOTS)) || logic->HasItem(RG_GORONS_BRACELET)))),
|
||||
});
|
||||
|
||||
@@ -638,7 +638,8 @@ void RegionTable_Init_ShadowTemple() {
|
||||
//Locations
|
||||
LOCATION(RC_SHADOW_TEMPLE_MQ_AFTER_WIND_ENEMY_CHEST, logic->CanKillEnemy(RE_GIBDO) && logic->HasItem(RG_OPEN_CHEST)),
|
||||
LOCATION(RC_SHADOW_TEMPLE_MQ_AFTER_WIND_HIDDEN_CHEST, logic->HasExplosives() && (ctx->GetTrickOption(RT_LENS_SHADOW_MQ) || logic->CanUse(RG_LENS_OF_TRUTH)) && logic->HasItem(RG_OPEN_CHEST)),
|
||||
LOCATION(RC_SHADOW_TEMPLE_MQ_GS_AFTER_WIND, logic->HasExplosives()),
|
||||
//The various methods for this can be a bit specific, might be worthy of it's own trick when it becomes relevant with dungeon shortcut settings.
|
||||
LOCATION(RC_SHADOW_TEMPLE_MQ_GS_AFTER_WIND, logic->HasExplosives() || (ctx->GetTrickOption(RT_VISIBLE_COLLISION) && logic->CanGetEnemyDrop(RE_GOLD_SKULLTULA))),
|
||||
LOCATION(RC_SHADOW_TEMPLE_MQ_BEFORE_BOAT_POT_1, logic->CanBreakPots()),
|
||||
LOCATION(RC_SHADOW_TEMPLE_MQ_BEFORE_BOAT_POT_2, logic->CanBreakPots()),
|
||||
}, {
|
||||
|
||||
@@ -134,7 +134,6 @@ void RegionTable_Init_SpiritTemple() {
|
||||
LOCATION(RC_SPIRIT_TEMPLE_CHILD_CLIMB_EAST_CHEST, SpiritShared(RR_SPIRIT_TEMPLE_SUN_ON_FLOOR_2F, []{return logic->CanHitSwitch(ED_BOMB_THROW) && logic->HasItem(RG_OPEN_CHEST);})),
|
||||
LOCATION(RC_SPIRIT_TEMPLE_GS_SUN_ON_FLOOR_ROOM, SpiritShared(RR_SPIRIT_TEMPLE_SUN_ON_FLOOR_2F, []{return logic->CanKillEnemy(RE_GOLD_SKULLTULA, logic->TakeDamage() ? ED_SHORT_JUMPSLASH : ED_BOMB_THROW);}, false,
|
||||
RR_SPIRIT_TEMPLE_SUN_ON_FLOOR_1F, []{return logic->CanGetEnemyDrop(RE_GOLD_SKULLTULA, ED_BOOMERANG);})),
|
||||
LOCATION(RC_SPIRIT_TEMPLE_BOULDER_ROOM_SUN_FAIRY, logic->CanUse(RG_SUNS_SONG) && (logic->CanUse(RG_FAIRY_BOW) || logic->CanUse(RG_HOOKSHOT) || logic->CanUse(RG_FAIRY_SLINGSHOT) || logic->CanUse(RG_BOOMERANG) || logic->CanUse(RG_BOMBCHU_5) || (logic->CanUse(RG_BOMB_BAG) && logic->IsAdult && ctx->GetTrickOption(RT_SPIRIT_LOWER_ADULT_SWITCH))) && (logic->CanUse(RG_HOVER_BOOTS) || logic->CanJumpslash())),
|
||||
}, {
|
||||
//Exits
|
||||
ENTRANCE(RR_SPIRIT_TEMPLE_SUN_ON_FLOOR_1F, true),
|
||||
|
||||
@@ -1358,7 +1358,7 @@ void RegionTable_Init_WaterTemple() {
|
||||
|
||||
areaTable[RR_WATER_TEMPLE_MQ_CRATE_VORTEX_CAGE] = Region("Water Temple MQ Crate Vortex Cage", SCENE_WATER_TEMPLE, {}, {
|
||||
//Locations
|
||||
LOCATION(RC_WATER_TEMPLE_MQ_GS_FREESTANDING_KEY_AREA, logic->CanGetEnemyDrop(RE_GOLD_SKULLTULA) && logic->CanBreakCrates()),
|
||||
LOCATION(RC_WATER_TEMPLE_MQ_GS_FREESTANDING_KEY_AREA, logic->CanGetEnemyDrop(RE_GOLD_SKULLTULA) && (logic->CanBreakCrates() || ctx->GetTrickOption(RT_VISIBLE_COLLISION))),
|
||||
LOCATION(RC_WATER_TEMPLE_MQ_WHIRLPOOL_BEHIND_GATE_CRATE_1, logic->CanBreakCrates()),
|
||||
LOCATION(RC_WATER_TEMPLE_MQ_WHIRLPOOL_BEHIND_GATE_CRATE_2, logic->CanBreakCrates()),
|
||||
LOCATION(RC_WATER_TEMPLE_MQ_WHIRLPOOL_BEHIND_GATE_CRATE_3, logic->CanBreakCrates()),
|
||||
|
||||
@@ -95,7 +95,7 @@ void RegionTable_Init_CastleGrounds() {
|
||||
//Exits
|
||||
ENTRANCE(RR_HC_GATE, true),
|
||||
ENTRANCE(RR_HC_STORMS_GROTTO, logic->CanOpenStormsGrotto()),
|
||||
ENTRANCE(RR_HC_GARDEN, (logic->CanUse(RG_WEIRD_EGG) && logic->HasItem(RG_POWER_BRACELET) && logic->HasItem(RG_SPEAK_HYLIAN)) ||
|
||||
ENTRANCE(RR_HC_DRAIN_LEDGE, (logic->CanUse(RG_WEIRD_EGG) && logic->HasItem(RG_POWER_BRACELET) && logic->HasItem(RG_SPEAK_HYLIAN)) ||
|
||||
(ctx->GetTrickOption(RT_DAMAGE_BOOST_SIMPLE) && logic->TakeDamage() && logic->HasExplosives() && logic->CanJumpslash())),
|
||||
});
|
||||
|
||||
|
||||
+24
-24
@@ -30,17 +30,17 @@ void RegionTable_Init_DeathMountainCrater() {
|
||||
ENTRANCE(RR_DMC_CRACKED_WALL, (logic->FireTimer() >= 16 || logic->Hearts() >= 3)),
|
||||
ENTRANCE(RR_DMC_SCRUB, logic->FireTimer() >= 16 || logic->Hearts() >= 3),
|
||||
ENTRANCE(RR_DMC_BLOCKED_EXIT, ((logic->FireTimer() >= 24 || logic->Hearts() >= 5) && logic->DMCUpperToPots()) ||
|
||||
(logic->IsAdult && (logic->FireTimer() >= 56 || logic->Hearts() >= 11) && logic->ReachDistantScarecrow() && logic->TakeDamage())),
|
||||
(logic->IsAdult && (logic->FireTimer() >= 56 || logic->Hearts() >= 11) && logic->ReachDistantScarecrow() && logic->TakeDamage() && ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS))),
|
||||
ENTRANCE(RR_DMC_POTS, ((logic->FireTimer() >= 32 || logic->Hearts() >= 6) && logic->DMCUpperToPots()) ||
|
||||
(logic->IsAdult && (logic->FireTimer() >= 48 || logic->Hearts() >= 9) && logic->ReachDistantScarecrow() && logic->TakeDamage())),
|
||||
(logic->IsAdult && (logic->FireTimer() >= 48 || logic->Hearts() >= 9) && logic->ReachDistantScarecrow() && logic->TakeDamage() && ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS))),
|
||||
ENTRANCE(RR_DMC_POT_GROTTO_EXIT, ((logic->FireTimer() >= 32 || logic->Hearts() >= 6) && logic->DMCUpperToPots()) ||
|
||||
(logic->IsAdult && (logic->FireTimer() >= 48 || logic->Hearts() >= 9) && logic->ReachDistantScarecrow() && logic->TakeDamage())),
|
||||
(logic->IsAdult && (logic->FireTimer() >= 48 || logic->Hearts() >= 9) && logic->ReachDistantScarecrow() && logic->TakeDamage() && ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS))),
|
||||
ENTRANCE(RR_DMC_CENTRAL, ((logic->FireTimer() >= 64 || logic->Hearts() >= 12) && logic->DMCUpperToPots() && logic->DMCPotsToPad()) ||
|
||||
(logic->IsAdult && (logic->FireTimer() >= 48 || logic->Hearts() >= 9) && logic->ReachDistantScarecrow() && logic->TakeDamage())),
|
||||
(logic->IsAdult && (logic->FireTimer() >= 48 || logic->Hearts() >= 9) && logic->ReachDistantScarecrow() && logic->TakeDamage() && ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS))),
|
||||
ENTRANCE(RR_DMC_FAR_PLATFORM, (logic->IsAdult && (logic->FireTimer() >= 72 || logic->Hearts() >= 14) && logic->DMCUpperToPots() && logic->DMCPotsToPad() && logic->ReachDistantScarecrow()) ||
|
||||
(logic->FireTimer() >= 24 || logic->Hearts() >= 5) && logic->TakeDamage()),
|
||||
(logic->FireTimer() >= 24 || logic->Hearts() >= 5) && logic->TakeDamage() && ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS)),
|
||||
ENTRANCE(RR_DMC_TEMPLE_EXIT, ((logic->FireTimer() >= 72 || logic->Hearts() >= 14) && logic->DMCUpperToPots() && logic->DMCPotsToPad()) ||
|
||||
(logic->IsAdult && (logic->FireTimer() >= 48 || logic->Hearts() >= 9) && logic->ReachDistantScarecrow() && logic->TakeDamage())),
|
||||
(logic->IsAdult && (logic->FireTimer() >= 48 || logic->Hearts() >= 9) && logic->ReachDistantScarecrow() && logic->TakeDamage() && ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS))),
|
||||
});
|
||||
|
||||
areaTable[RR_DMC_ROCKS_GROTTO_ENTRY] = Region("DMC Rocks Grotto Entry", SCENE_DEATH_MOUNTAIN_CRATER, {
|
||||
@@ -56,17 +56,17 @@ void RegionTable_Init_DeathMountainCrater() {
|
||||
ENTRANCE(RR_DMC_CRACKED_WALL, (logic->FireTimer() >= 16 || logic->Hearts() >= 3)),
|
||||
ENTRANCE(RR_DMC_SCRUB, logic->FireTimer() >= 16 || logic->Hearts() >= 3),
|
||||
ENTRANCE(RR_DMC_BLOCKED_EXIT, ((logic->FireTimer() >= 32 || logic->Hearts() >= 6) && logic->DMCUpperToPots()) ||
|
||||
(logic->IsAdult && (logic->FireTimer() >= 48 || logic->Hearts() >= 9) && logic->ReachDistantScarecrow() && logic->TakeDamage())),
|
||||
(logic->IsAdult && (logic->FireTimer() >= 48 || logic->Hearts() >= 9) && logic->ReachDistantScarecrow() && logic->TakeDamage() && ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS))),
|
||||
ENTRANCE(RR_DMC_POTS, ((logic->FireTimer() >= 32 || logic->Hearts() >= 6) && logic->DMCUpperToPots()) ||
|
||||
(logic->IsAdult && (logic->FireTimer() >= 40 || logic->Hearts() >= 8) && logic->ReachDistantScarecrow() && logic->TakeDamage())),
|
||||
(logic->IsAdult && (logic->FireTimer() >= 40 || logic->Hearts() >= 8) && logic->ReachDistantScarecrow() && logic->TakeDamage() && ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS))),
|
||||
ENTRANCE(RR_DMC_POT_GROTTO_EXIT, ((logic->FireTimer() >= 40 || logic->Hearts() >= 8) && logic->DMCUpperToPots()) ||
|
||||
(logic->IsAdult && (logic->FireTimer() >= 40 || logic->Hearts() >= 8) && logic->ReachDistantScarecrow() && logic->TakeDamage())),
|
||||
(logic->IsAdult && (logic->FireTimer() >= 40 || logic->Hearts() >= 8) && logic->ReachDistantScarecrow() && logic->TakeDamage() && ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS))),
|
||||
ENTRANCE(RR_DMC_CENTRAL, ((logic->FireTimer() >= 64 || logic->Hearts() >= 12) && logic->DMCUpperToPots() && logic->DMCPotsToPad()) ||
|
||||
(logic->IsAdult && (logic->FireTimer() >= 40 || logic->Hearts() >= 3) && logic->ReachDistantScarecrow() && logic->TakeDamage())),
|
||||
(logic->IsAdult && (logic->FireTimer() >= 40 || logic->Hearts() >= 3) && logic->ReachDistantScarecrow() && logic->TakeDamage() && ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS))),
|
||||
ENTRANCE(RR_DMC_FAR_PLATFORM, (logic->IsAdult && (logic->FireTimer() >= 72 || logic->Hearts() >= 14) && logic->DMCUpperToPots() && logic->DMCPotsToPad() && logic->ReachDistantScarecrow()) ||
|
||||
(logic->FireTimer() >= 16 || logic->Hearts() >= 3) && logic->TakeDamage()),
|
||||
(logic->FireTimer() >= 16 || logic->Hearts() >= 3) && logic->TakeDamage() && ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS)),
|
||||
ENTRANCE(RR_DMC_TEMPLE_EXIT, ((logic->FireTimer() >= 64 || logic->Hearts() >= 12) && logic->DMCUpperToPots() && logic->DMCPotsToPad()) ||
|
||||
(logic->IsAdult && (logic->FireTimer() >= 40 || logic->Hearts() >= 3) && logic->ReachDistantScarecrow() && logic->TakeDamage())),
|
||||
(logic->IsAdult && (logic->FireTimer() >= 40 || logic->Hearts() >= 3) && logic->ReachDistantScarecrow() && logic->TakeDamage() && ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS))),
|
||||
});
|
||||
|
||||
areaTable[RR_DMC_BLOCKED_ENTRY] = Region("DMC Blocked Entry", SCENE_DEATH_MOUNTAIN_CRATER, {}, {
|
||||
@@ -92,13 +92,13 @@ void RegionTable_Init_DeathMountainCrater() {
|
||||
ENTRANCE(RR_DMC_POTS, logic->FireTimer() >= 8 || logic->Hearts() >= 2),
|
||||
ENTRANCE(RR_DMC_POT_GROTTO_EXIT, logic->FireTimer() >= 16 || logic->Hearts() >= 3),
|
||||
ENTRANCE(RR_DMC_CENTRAL, ((logic->FireTimer() >= 40 || logic->Hearts() >= 8) && logic->DMCPotsToPad()) ||
|
||||
(logic->IsAdult && (logic->FireTimer() >= 48 || logic->Hearts() >= 9) && logic->ReachDistantScarecrow() && logic->TakeDamage() && (logic->HasItem(RG_CLIMB) || logic->CanUse(RG_HOOKSHOT)))),
|
||||
(logic->IsAdult && (logic->FireTimer() >= 48 || logic->Hearts() >= 9) && logic->ReachDistantScarecrow() && logic->TakeDamage() && ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS) && logic->CanClimbLadder())),
|
||||
ENTRANCE(RR_DMC_FAR_PLATFORM, (logic->IsAdult && (logic->FireTimer() >= 48 || logic->Hearts() >= 9) && logic->DMCPotsToPad() && logic->ReachDistantScarecrow()) ||
|
||||
((logic->FireTimer() >= 32 || logic->Hearts() >= 6) && logic->TakeDamage() && (logic->HasItem(RG_CLIMB) || logic->CanUse(RG_HOOKSHOT))) ||
|
||||
((logic->FireTimer() >= 32 || logic->Hearts() >= 6) && logic->TakeDamage() && ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS) && logic->CanClimbLadder()) ||
|
||||
(logic->IsAdult && (logic->FireTimer() >= 48 || logic->Hearts() >= 9) && logic->DMCPotsToPad() && CanPlantBean(RR_DMC_CENTRAL, RG_DEATH_MOUNTAIN_CRATER_BEAN_SOUL)) ||
|
||||
((logic->FireTimer() >= 32 || logic->Hearts() >= 6) && logic->TakeDamage() && ctx->GetTrickOption(RT_DMC_HOVER_BEAN_POH) && logic->CanUse(RG_HOVER_BOOTS))),
|
||||
((logic->FireTimer() >= 32 || logic->Hearts() >= 6) && logic->TakeDamage() && ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS) && ctx->GetTrickOption(RT_DMC_HOVER_BEAN_POH) && logic->CanUse(RG_HOVER_BOOTS))),
|
||||
ENTRANCE(RR_DMC_TEMPLE_EXIT, ((logic->FireTimer() >= 48 || logic->Hearts() >= 9) && logic->DMCPotsToPad()) ||
|
||||
(logic->IsAdult && (logic->FireTimer() >= 56 || logic->Hearts() >= 11) && logic->TakeDamage() && logic->ReachDistantScarecrow() && (logic->HasItem(RG_CLIMB) || logic->CanUse(RG_HOOKSHOT)))),
|
||||
(logic->IsAdult && (logic->FireTimer() >= 56 || logic->Hearts() >= 11) && logic->TakeDamage() && ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS) && logic->ReachDistantScarecrow() && logic->CanClimbLadder())),
|
||||
});
|
||||
|
||||
areaTable[RR_DMC_POTS_ENTRY] = Region("DMC Pots Entry", SCENE_DEATH_MOUNTAIN_CRATER, {}, {
|
||||
@@ -124,11 +124,11 @@ void RegionTable_Init_DeathMountainCrater() {
|
||||
ENTRANCE(RR_DMC_POTS, true),
|
||||
ENTRANCE(RR_DMC_POT_GROTTO_EXIT, logic->FireTimer() >= 8 || logic->Hearts() >= 2),
|
||||
ENTRANCE(RR_DMC_CENTRAL, ((logic->FireTimer() >= 32 || logic->Hearts() >= 6) && logic->DMCPotsToPad()) ||
|
||||
(logic->IsAdult && (logic->FireTimer() >= 56 || logic->Hearts() >= 11) && logic->TakeDamage() && logic->ReachDistantScarecrow() && (logic->HasItem(RG_CLIMB) || logic->CanUse(RG_HOOKSHOT)))),
|
||||
(logic->IsAdult && (logic->FireTimer() >= 56 || logic->Hearts() >= 11) && logic->TakeDamage() && ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS) && logic->ReachDistantScarecrow() && logic->CanClimbLadder())),
|
||||
ENTRANCE(RR_DMC_FAR_PLATFORM, (logic->IsAdult && (logic->FireTimer() >= 40 || logic->Hearts() >= 8) && logic->DMCPotsToPad() && logic->ReachDistantScarecrow()) ||
|
||||
((logic->FireTimer() >= 32 || logic->Hearts() >= 6) && logic->TakeDamage() && (logic->HasItem(RG_CLIMB) || logic->CanUse(RG_HOOKSHOT)))),
|
||||
((logic->FireTimer() >= 32 || logic->Hearts() >= 6) && logic->TakeDamage() && ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS) && logic->CanClimbLadder())),
|
||||
ENTRANCE(RR_DMC_TEMPLE_EXIT, ((logic->FireTimer() >= 40 || logic->Hearts() >= 8) && logic->DMCPotsToPad()) ||
|
||||
(logic->IsAdult && (logic->FireTimer() >= 56 || logic->Hearts() >= 11) && logic->TakeDamage() && logic->ReachDistantScarecrow() && (logic->HasItem(RG_CLIMB) || logic->CanUse(RG_HOOKSHOT)))),
|
||||
(logic->IsAdult && (logic->FireTimer() >= 56 || logic->Hearts() >= 11) && logic->TakeDamage() && ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS) && logic->ReachDistantScarecrow() && logic->CanClimbLadder())),
|
||||
});
|
||||
|
||||
areaTable[RR_DMC_POT_GROTTO_ENTRY] = Region("DMC Pot Grotto Entry", SCENE_DEATH_MOUNTAIN_CRATER, {}, {
|
||||
@@ -154,11 +154,11 @@ void RegionTable_Init_DeathMountainCrater() {
|
||||
ENTRANCE(RR_DMC_POTS, logic->FireTimer() >= 8 || logic->Hearts() >= 2),
|
||||
ENTRANCE(RR_DMC_POT_GROTTO_EXIT, true),
|
||||
ENTRANCE(RR_DMC_CENTRAL, ((logic->FireTimer() >= 32 || logic->Hearts() >= 6) && logic->DMCPotsToPad()) ||
|
||||
(logic->IsAdult && (logic->FireTimer() >= 56 || logic->Hearts() >= 11) && logic->TakeDamage() && logic->ReachDistantScarecrow() && (logic->HasItem(RG_CLIMB) || logic->CanUse(RG_HOOKSHOT)))),
|
||||
(logic->IsAdult && (logic->FireTimer() >= 56 || logic->Hearts() >= 11) && logic->TakeDamage() && ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS) && logic->ReachDistantScarecrow() && logic->CanClimbLadder())),
|
||||
ENTRANCE(RR_DMC_FAR_PLATFORM, (logic->IsAdult && (logic->FireTimer() >= 40 || logic->Hearts() >= 8) && logic->DMCPotsToPad() && logic->ReachDistantScarecrow()) ||
|
||||
((logic->FireTimer() >= 40 || logic->Hearts() >= 8) && logic->TakeDamage() && (logic->HasItem(RG_CLIMB) || logic->CanUse(RG_HOOKSHOT)))),
|
||||
((logic->FireTimer() >= 40 || logic->Hearts() >= 8) && logic->TakeDamage() && ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS) && logic->CanClimbLadder())),
|
||||
ENTRANCE(RR_DMC_TEMPLE_EXIT, ((logic->FireTimer() >= 40 || logic->Hearts() >= 8) && logic->DMCPotsToPad()) ||
|
||||
(logic->IsAdult && (logic->FireTimer() >= 56 || logic->Hearts() >= 11) && logic->TakeDamage() && logic->ReachDistantScarecrow() && (logic->HasItem(RG_CLIMB) || logic->CanUse(RG_HOOKSHOT)))),
|
||||
(logic->IsAdult && (logic->FireTimer() >= 56 || logic->Hearts() >= 11) && logic->TakeDamage() && ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS) && logic->ReachDistantScarecrow() && logic->CanClimbLadder())),
|
||||
});
|
||||
|
||||
areaTable[RR_DMC_PAD_ENTRY] = Region("DMC Pad Entry", SCENE_DEATH_MOUNTAIN_CRATER, {}, {
|
||||
@@ -188,7 +188,7 @@ void RegionTable_Init_DeathMountainCrater() {
|
||||
ENTRANCE(RR_DMC_POT_GROTTO_EXIT, (logic->FireTimer() >= 16 || logic->Hearts() >= 3) && logic->DMCPadToPots() ||
|
||||
((logic->IsAdult && logic->FireTimer() >= 24 || logic->Hearts() >= 5) && CanPlantBean(RR_DMC_CENTRAL, RG_DEATH_MOUNTAIN_CRATER_BEAN_SOUL))),
|
||||
ENTRANCE(RR_DMC_CENTRAL, (logic->FireTimer() >= 16 || logic->Hearts() >= 3)),
|
||||
ENTRANCE(RR_DMC_FAR_PLATFORM, ((logic->FireTimer() >= 56 || logic->Hearts() >= 11) && logic->TakeDamage() && logic->CanClimbLadder() && logic->DMCPadToPots()) ||
|
||||
ENTRANCE(RR_DMC_FAR_PLATFORM, ((logic->FireTimer() >= 56 || logic->Hearts() >= 11) && logic->TakeDamage() && ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS) && logic->CanClimbLadder() && logic->DMCPadToPots()) ||
|
||||
((logic->FireTimer() >= 40 || logic->Hearts() >= 8) && ctx->GetTrickOption(RT_DMC_HOVER_BEAN_POH) && logic->CanUse(RG_HOVER_BOOTS) && logic->CanUse(RG_LONGSHOT)) ||
|
||||
(logic->IsAdult && (logic->FireTimer() >= 24 || logic->Hearts() >= 5) && CanPlantBean(RR_DMC_CENTRAL, RG_DEATH_MOUNTAIN_CRATER_BEAN_SOUL))||
|
||||
(logic->IsAdult && (logic->FireTimer() >= 16 || logic->Hearts() >= 3) && logic->ReachDistantScarecrow())),
|
||||
@@ -232,7 +232,7 @@ void RegionTable_Init_DeathMountainCrater() {
|
||||
(logic->IsAdult && (logic->FireTimer() >= 56 || logic->Hearts() >= 11) && CanPlantBean(RR_DMC_CENTRAL, RG_DEATH_MOUNTAIN_CRATER_BEAN_SOUL)))),
|
||||
ENTRANCE(RR_DMC_CENTRAL, logic->HasItem(RG_CLIMB) && (logic->FireTimer() >= 48 || logic->Hearts() >= 9)),
|
||||
ENTRANCE(RR_DMC_FAR_PLATFORM, logic->HasItem(RG_CLIMB) &&
|
||||
(((logic->FireTimer() >= 88 || logic->Hearts() >= 3) && logic->TakeDamage() && logic->CanClimbLadder() && logic->DMCPadToPots()) ||
|
||||
(((logic->FireTimer() >= 88 || logic->Hearts() >= 3) && logic->TakeDamage() && ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS) && logic->CanClimbLadder() && logic->DMCPadToPots()) ||
|
||||
((logic->FireTimer() >= 72 || logic->Hearts() >= 14) && ctx->GetTrickOption(RT_DMC_HOVER_BEAN_POH) && logic->CanUse(RG_HOVER_BOOTS) && logic->CanUse(RG_LONGSHOT)) ||
|
||||
(logic->IsAdult && (logic->FireTimer() >= 56 || logic->Hearts() >= 11) && CanPlantBean(RR_DMC_CENTRAL, RG_DEATH_MOUNTAIN_CRATER_BEAN_SOUL))||
|
||||
(logic->IsAdult && (logic->FireTimer() >= 48 || logic->Hearts() >= 9) && logic->ReachDistantScarecrow()))),
|
||||
|
||||
@@ -37,7 +37,8 @@ void RegionTable_Init_DeathMountainTrail() {
|
||||
//Locations
|
||||
LOCATION(RC_DMT_GS_FALLING_ROCKS_PATH, logic->IsAdult && logic->CanGetNightTimeGS() &&
|
||||
(logic->CanUse(RG_MEGATON_HAMMER) || (ctx->GetTrickOption(RT_DISTANT_BOULDER_COLLISION) && logic->CanUse(RG_LONGSHOT)) || (ctx->GetTrickOption(RT_ITEM_EXTENSION) && logic->CanUse(RG_HOOKSHOT)) ||
|
||||
(ctx->GetTrickOption(RT_DMT_UPPER_GS) && (logic->CanJumpslash() || logic->CanUse(RG_DINS_FIRE) || logic->HasExplosives() || ((ctx->GetTrickOption(RT_DISTANT_BOULDER_COLLISION) || ctx->GetTrickOption(RT_ITEM_EXTENSION)) && (logic->CanUse(RG_FAIRY_BOW) || logic->CanUse(RG_FAIRY_SLINGSHOT))))))),
|
||||
(ctx->GetTrickOption(RT_DMT_UPPER_GS) && (logic->CanJumpslash() || logic->CanUse(RG_DINS_FIRE) || logic->HasExplosives() || (ctx->GetTrickOption(RT_ITEM_EXTENSION) && logic->CanUse(RG_FAIRY_SLINGSHOT)) ||
|
||||
(ctx->GetTrickOption(RT_DISTANT_BOULDER_COLLISION) && logic->CanKillEnemy(RE_GOLD_SKULLTULA, ED_LONGSHOT)))))),
|
||||
}, {
|
||||
//Exits
|
||||
ENTRANCE(RR_DEATH_MOUNTAIN_TRAIL, true),
|
||||
@@ -63,7 +64,7 @@ void RegionTable_Init_DeathMountainTrail() {
|
||||
//Exits
|
||||
ENTRANCE(RR_DEATH_MOUNTAIN_ROCKFALL, true),
|
||||
ENTRANCE(RR_DMC_UPPER_ENTRY, true),
|
||||
ENTRANCE(RR_DMT_OWL_FLIGHT, logic->IsChild && (logic->HasItem(RG_SPEAK_DEKU) || logic->HasItem(RG_SPEAK_GERUDO) || logic->HasItem(RG_SPEAK_GORON) || logic->HasItem(RG_SPEAK_HYLIAN) || logic->HasItem(RG_SPEAK_ZORA))),
|
||||
ENTRANCE(RR_DMT_OWL_FLIGHT, logic->IsChild && (logic->HasItem(RG_SPEAK_DEKU) || logic->HasItem(RG_SPEAK_GERUDO) || logic->HasItem(RG_SPEAK_GORON) || logic->HasItem(RG_SPEAK_KOKIRI) || logic->HasItem(RG_SPEAK_HYLIAN) || logic->HasItem(RG_SPEAK_ZORA))),
|
||||
ENTRANCE(RR_DMT_GREAT_FAIRY_FOUNTAIN, AnyAgeTime([]{return logic->BlastOrSmash();})),
|
||||
});
|
||||
|
||||
|
||||
@@ -200,9 +200,10 @@ void RegionTable_Init_GerudoFortress() {
|
||||
LOCATION(RC_GF_ABOVE_JAIL_CRATE, true),
|
||||
}, {
|
||||
//Exits
|
||||
//you don't take fall damage if you land on the rock with the flag on for some reason
|
||||
//there's a trick to reach RR_GF_LONG_ROOF
|
||||
ENTRANCE(RR_GF_OUTSKIRTS, ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS) || logic->TakeDamage()),
|
||||
//For some reason, you take fall damage if you backflip onto the fortress but not onto the sand
|
||||
//It's unintuitive to avoid being caught on landing, but that sends you to the same place anyway...
|
||||
ENTRANCE(RR_GF_OUTSKIRTS, true),
|
||||
ENTRANCE(RR_GF_NEAR_CHEST, logic->CanUse(RG_LONGSHOT)),
|
||||
ENTRANCE(RR_GF_BELOW_CHEST, logic->TakeDamage()),
|
||||
ENTRANCE(RR_GF_JAIL_WINDOW, logic->CanUse(RG_HOOKSHOT)),
|
||||
|
||||
@@ -19,7 +19,7 @@ void RegionTable_Init_GerudoValley() {
|
||||
ENTRANCE(RR_GV_CRATE_LEDGE, (logic->IsChild && logic->HasItem(RG_POWER_BRACELET)) || logic->CanUse(RG_LONGSHOT)),
|
||||
ENTRANCE(RR_GV_GROTTO_LEDGE, true),
|
||||
ENTRANCE(RR_GV_FORTRESS_SIDE, (logic->IsAdult && (logic->SummonEpona() || logic->CanUse(RG_LONGSHOT) || ctx->GetOption(RSK_GERUDO_FORTRESS).Is(RO_GF_CARPENTERS_FREE) || logic->Get(LOGIC_TH_RESCUED_ALL_CARPENTERS))) || (ctx->GetTrickOption(RT_HOVER_BOOST_SIMPLE) && logic->CanUse(RG_MEGATON_HAMMER) && logic->CanUse(RG_HOVER_BOOTS)) ||
|
||||
((logic->IsChild || ctx->GetTrickOption(RT_ITEM_EXTENSION)) && logic->CanUse(RG_HOOKSHOT)) || (logic->IsChild && ctx->GetTrickOption(RT_GV_CHILD_CUCCO_JUMP) && logic->HasItem(RG_POWER_BRACELET) && logic->CanJumpslash())),
|
||||
((logic->IsChild || ctx->GetTrickOption(RT_GV_HOOKSHOT_BRIDGE)) && logic->CanUse(RG_HOOKSHOT)) || (logic->IsChild && ctx->GetTrickOption(RT_GV_CHILD_CUCCO_JUMP) && logic->HasItem(RG_POWER_BRACELET) && logic->CanJumpslash())),
|
||||
ENTRANCE(RR_GV_WATERFALL_ALCOVE, logic->IsChild && logic->HasItem(RG_POWER_BRACELET)),
|
||||
ENTRANCE(RR_GV_LOWER_STREAM, logic->IsChild && logic->HasItem(RG_POWER_BRACELET)),
|
||||
});
|
||||
|
||||
@@ -243,7 +243,8 @@ void RegionTable_Init_Kakariko() {
|
||||
}, {
|
||||
//Locations
|
||||
LOCATION(RC_KAK_TRADE_ODD_MUSHROOM, logic->IsAdult && logic->CanUse(RG_ODD_MUSHROOM)),
|
||||
LOCATION(RC_KAK_GRANNYS_SHOP, logic->IsAdult && logic->HasItem(RG_SPEAK_HYLIAN) && (logic->CanUse(RG_ODD_MUSHROOM) || logic->TradeQuestStep(RG_ODD_MUSHROOM))),
|
||||
LOCATION(RC_KAK_GRANNYS_SHOP, logic->IsAdult && logic->HasItem(RG_SPEAK_HYLIAN) &&
|
||||
(logic->CanUse(RG_ODD_MUSHROOM) || logic->TradeQuestStep(RG_ODD_MUSHROOM)) && GetCheckPrice() <= GetWalletCapacity()),
|
||||
}, {
|
||||
// Exits
|
||||
ENTRANCE(RR_KAK_BACKYARD, true),
|
||||
|
||||
@@ -70,7 +70,9 @@ void RegionTable_Init_KokiriForest() {
|
||||
}, {
|
||||
//Exits
|
||||
ENTRANCE(RR_KF_BOULDER_LOOP, logic->CanUse(RG_CRAWL)),
|
||||
ENTRANCE(RR_KF_LINKS_PORCH, logic->IsChild ? logic->CanClimbLadder() : logic->HasItem(RG_CLIMB) || logic->CanUse(RG_HOVER_BOOTS)),
|
||||
//The Deku Baba blocks the setup as Adult, and stunning doesn't last long enough to perform it.
|
||||
ENTRANCE(RR_KF_LINKS_PORCH, logic->HasItem(RG_CLIMB) || logic->CanUse(RG_HOVER_BOOTS) ||
|
||||
((logic->IsChild || logic->CanKillEnemy(RE_DEKU_BABA) || logic->Get(LOGIC_FOREST_TEMPLE_CLEAR)) && logic->CanClimbLadder())),
|
||||
ENTRANCE(RR_KF_MIDOS_HOUSE, true),
|
||||
ENTRANCE(RR_KF_SARIAS_HOUSE, true),
|
||||
ENTRANCE(RR_KF_HOUSE_OF_TWINS, true),
|
||||
|
||||
@@ -83,11 +83,11 @@ void RegionTable_Init_LakeHylia() {
|
||||
//Exits
|
||||
ENTRANCE(RR_HF_TO_LAKE_HYLIA, true),
|
||||
ENTRANCE(RR_LH_FROM_SHORTCUT, true),
|
||||
ENTRANCE(RR_LH_OWL_FLIGHT, logic->IsChild && (logic->HasItem(RG_SPEAK_DEKU) || logic->HasItem(RG_SPEAK_GERUDO) || logic->HasItem(RG_SPEAK_GORON) || logic->HasItem(RG_SPEAK_HYLIAN) || logic->HasItem(RG_SPEAK_ZORA))),
|
||||
ENTRANCE(RR_LH_OWL_FLIGHT, logic->IsChild && (logic->HasItem(RG_SPEAK_DEKU) || logic->HasItem(RG_SPEAK_GERUDO) || logic->HasItem(RG_SPEAK_GORON) || logic->HasItem(RG_SPEAK_KOKIRI) || logic->HasItem(RG_SPEAK_HYLIAN) || logic->HasItem(RG_SPEAK_ZORA))),
|
||||
ENTRANCE(RR_LH_FISHING_ISLAND, ((logic->IsChild || logic->Get(LOGIC_WATER_TEMPLE_CLEAR)) && logic->HasItem(RG_BRONZE_SCALE)) || (logic->IsAdult && (logic->ReachScarecrow() || CanPlantBean(RR_LAKE_HYLIA, RG_LAKE_HYLIA_BEAN_SOUL)))),
|
||||
ENTRANCE(RR_LH_LAB, logic->CanOpenOverworldDoor(RG_HYLIA_LAB_KEY)),
|
||||
ENTRANCE(RR_LH_FROM_WATER_TEMPLE, true),
|
||||
ENTRANCE(RR_LH_GROTTO, logic->HasItem(RG_POWER_BRACELET) && (logic->IsAdult || logic->HasItem(RG_SPEAK_DEKU) || logic->HasItem(RG_SPEAK_GERUDO) || logic->HasItem(RG_SPEAK_GORON) || logic->HasItem(RG_SPEAK_HYLIAN) || logic->HasItem(RG_SPEAK_ZORA))),
|
||||
ENTRANCE(RR_LH_GROTTO, logic->HasItem(RG_POWER_BRACELET) && (logic->IsAdult || logic->HasItem(RG_SPEAK_DEKU) || logic->HasItem(RG_SPEAK_GERUDO) || logic->HasItem(RG_SPEAK_GORON) || logic->HasItem(RG_SPEAK_KOKIRI) || logic->HasItem(RG_SPEAK_HYLIAN) || logic->HasItem(RG_SPEAK_ZORA))),
|
||||
});
|
||||
|
||||
areaTable[RR_LH_FROM_SHORTCUT] = Region("LH From Shortcut", SCENE_LAKE_HYLIA, TIME_DOESNT_PASS, {RA_LAKE_HYLIA}, {}, {}, {
|
||||
|
||||
@@ -59,7 +59,7 @@ void RegionTable_Init_ZoraRiver() {
|
||||
}, {
|
||||
//Exits
|
||||
ENTRANCE(RR_ZR_FRONT, logic->IsAdult || logic->HasItem(RG_BRONZE_SCALE) || logic->HasItem(RG_POWER_BRACELET) || logic->BlastOrSmash() || logic->HasItem(RG_HOVER_BOOTS)),
|
||||
ENTRANCE(RR_ZR_ATOP_LADDER, (logic->IsAdult || logic->HasItem(RG_POWER_BRACELET)) && (logic->CanClimbLadder() || CanPlantBean(RR_ZORAS_RIVER, RG_ZORAS_RIVER_BEAN_SOUL))),
|
||||
ENTRANCE(RR_ZR_ATOP_LADDER, ((logic->IsAdult || logic->HasItem(RG_POWER_BRACELET)) && logic->CanClimbLadder()) || (logic->IsAdult && CanPlantBean(RR_ZORAS_RIVER, RG_ZORAS_RIVER_BEAN_SOUL))),
|
||||
ENTRANCE(RR_ZR_PILLAR, (logic->IsChild && logic->HasItem(RG_POWER_BRACELET)) || logic->CanUse(RG_HOVER_BOOTS) || (logic->IsAdult && ctx->GetTrickOption(RT_ZR_LOWER))),
|
||||
ENTRANCE(RR_ZR_FROM_SHORTCUT, logic->HasItem(RG_SILVER_SCALE) || logic->CanUse(RG_IRON_BOOTS)),
|
||||
ENTRANCE(RR_ZR_STORMS_GROTTO, logic->CanOpenStormsGrotto()),
|
||||
|
||||
@@ -1396,9 +1396,20 @@ uint8_t Logic::Hearts() {
|
||||
}
|
||||
|
||||
uint8_t Logic::DungeonCount() {
|
||||
return Get(LOGIC_DEKU_TREE_CLEAR) + Get(LOGIC_DODONGOS_CAVERN_CLEAR) + Get(LOGIC_JABU_JABUS_BELLY_CLEAR) +
|
||||
Get(LOGIC_FOREST_TEMPLE_CLEAR) + Get(LOGIC_FIRE_TEMPLE_CLEAR) + Get(LOGIC_WATER_TEMPLE_CLEAR) +
|
||||
Get(LOGIC_SPIRIT_TEMPLE_CLEAR) + Get(LOGIC_SHADOW_TEMPLE_CLEAR);
|
||||
if (CalculatingAvailableChecks) {
|
||||
return CheckEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP) +
|
||||
CheckEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP) +
|
||||
CheckEventChkInf(EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP) +
|
||||
CheckEventChkInf(EVENTCHKINF_USED_FOREST_TEMPLE_BLUE_WARP) +
|
||||
CheckEventChkInf(EVENTCHKINF_USED_FIRE_TEMPLE_BLUE_WARP) +
|
||||
CheckEventChkInf(EVENTCHKINF_USED_WATER_TEMPLE_BLUE_WARP) +
|
||||
CheckRandoInf(RAND_INF_DUNGEONS_DONE_SPIRIT_TEMPLE) +
|
||||
CheckRandoInf(RAND_INF_DUNGEONS_DONE_SHADOW_TEMPLE);
|
||||
} else {
|
||||
return Get(LOGIC_DEKU_TREE_CLEAR) + Get(LOGIC_DODONGOS_CAVERN_CLEAR) + Get(LOGIC_JABU_JABUS_BELLY_CLEAR) +
|
||||
Get(LOGIC_FOREST_TEMPLE_CLEAR) + Get(LOGIC_FIRE_TEMPLE_CLEAR) + Get(LOGIC_WATER_TEMPLE_CLEAR) +
|
||||
Get(LOGIC_SPIRIT_TEMPLE_CLEAR) + Get(LOGIC_SHADOW_TEMPLE_CLEAR);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Logic::StoneCount() {
|
||||
|
||||
@@ -3589,49 +3589,28 @@ static std::unordered_map<RandomizerGet, GameplayStatTimestamp> randomizerGetToS
|
||||
// Gameplay stat tracking: Update time the item was acquired
|
||||
// (special cases for rando items)
|
||||
void Randomizer_GameplayStats_SetTimestamp(uint16_t item) {
|
||||
|
||||
u32 time = static_cast<u32>(GAMEPLAYSTAT_TOTAL_TIME);
|
||||
|
||||
// Have items in Link's pocket shown as being obtained at 0.1 seconds
|
||||
if (time == 0) {
|
||||
time = 1;
|
||||
}
|
||||
|
||||
// Use ITEM_KEY_BOSS to timestamp Ganon's boss key
|
||||
if (item == RG_GANONS_CASTLE_BOSS_KEY) {
|
||||
gSaveContext.ship.stats.itemTimestamp[ITEM_KEY_BOSS] = time;
|
||||
return;
|
||||
}
|
||||
|
||||
if (randomizerGetToStatsTimeStamp.contains((RandomizerGet)item)) {
|
||||
gSaveContext.ship.stats.itemTimestamp[randomizerGetToStatsTimeStamp[(RandomizerGet)item]] = time;
|
||||
return;
|
||||
}
|
||||
|
||||
// Count any bottled item as a bottle
|
||||
if (item >= RG_EMPTY_BOTTLE && item <= RG_BOTTLE_WITH_BIG_POE) {
|
||||
if (gSaveContext.ship.stats.itemTimestamp[ITEM_BOTTLE] == 0) {
|
||||
if (gSaveContext.ship.stats.itemTimestamp[item] == 0) {
|
||||
if (item == RG_GANONS_CASTLE_BOSS_KEY) {
|
||||
gSaveContext.ship.stats.itemTimestamp[ITEM_KEY_BOSS] = time;
|
||||
} else if (item == RG_MASTER_SWORD) {
|
||||
gSaveContext.ship.stats.itemTimestamp[ITEM_SWORD_MASTER] = time;
|
||||
} else if (randomizerGetToStatsTimeStamp.contains((RandomizerGet)item)) {
|
||||
gSaveContext.ship.stats.itemTimestamp[randomizerGetToStatsTimeStamp[(RandomizerGet)item]] = time;
|
||||
} else if (item >= RG_EMPTY_BOTTLE && item <= RG_BOTTLE_WITH_BIG_POE) {
|
||||
gSaveContext.ship.stats.itemTimestamp[ITEM_BOTTLE] = time;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Count any bombchu pack as bombchus
|
||||
if ((item >= RG_BOMBCHU_5 && item <= RG_BOMBCHU_20) || item == RG_PROGRESSIVE_BOMBCHU_BAG) {
|
||||
if (gSaveContext.ship.stats.itemTimestamp[ITEM_BOMBCHU] = 0) {
|
||||
} else if ((item >= RG_BOMBCHU_5 && item <= RG_BOMBCHU_20) || item == RG_PROGRESSIVE_BOMBCHU_BAG) {
|
||||
gSaveContext.ship.stats.itemTimestamp[ITEM_BOMBCHU] = time;
|
||||
} else if (item == RG_MAGIC_SINGLE) {
|
||||
gSaveContext.ship.stats.itemTimestamp[ITEM_SINGLE_MAGIC] = time;
|
||||
} else if (item == RG_DOUBLE_DEFENSE) {
|
||||
gSaveContext.ship.stats.itemTimestamp[ITEM_DOUBLE_DEFENSE] = time;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (item == RG_MAGIC_SINGLE) {
|
||||
gSaveContext.ship.stats.itemTimestamp[ITEM_SINGLE_MAGIC] = time;
|
||||
return;
|
||||
}
|
||||
|
||||
if (item == RG_DOUBLE_DEFENSE) {
|
||||
gSaveContext.ship.stats.itemTimestamp[ITEM_DOUBLE_DEFENSE] = time;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3867,51 +3846,6 @@ extern "C" u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) {
|
||||
if (INV_CONTENT(ITEM_BEAN) == ITEM_NONE) {
|
||||
INV_CONTENT(ITEM_BEAN) = ITEM_BEAN;
|
||||
AMMO(ITEM_BEAN) = 10;
|
||||
if (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SKIP_PLANTING_BEANS)) {
|
||||
gSaveContext.sceneFlags[SCENE_DEATH_MOUNTAIN_CRATER].swch |= (1 << 3);
|
||||
if (gPlayState->sceneNum == SCENE_DEATH_MOUNTAIN_CRATER) {
|
||||
Flags_SetSwitch(gPlayState, 3);
|
||||
}
|
||||
gSaveContext.sceneFlags[SCENE_DEATH_MOUNTAIN_TRAIL].swch |= (1 << 6);
|
||||
if (gPlayState->sceneNum == SCENE_DEATH_MOUNTAIN_TRAIL) {
|
||||
Flags_SetSwitch(gPlayState, 6);
|
||||
}
|
||||
gSaveContext.sceneFlags[SCENE_DESERT_COLOSSUS].swch |= (1 << 24);
|
||||
if (gPlayState->sceneNum == SCENE_DESERT_COLOSSUS) {
|
||||
Flags_SetSwitch(gPlayState, 24);
|
||||
}
|
||||
gSaveContext.sceneFlags[SCENE_GERUDO_VALLEY].swch |= (1 << 3);
|
||||
if (gPlayState->sceneNum == SCENE_GERUDO_VALLEY) {
|
||||
Flags_SetSwitch(gPlayState, 3);
|
||||
}
|
||||
gSaveContext.sceneFlags[SCENE_GRAVEYARD].swch |= (1 << 3);
|
||||
if (gPlayState->sceneNum == SCENE_GRAVEYARD) {
|
||||
Flags_SetSwitch(gPlayState, 3);
|
||||
}
|
||||
gSaveContext.sceneFlags[SCENE_KOKIRI_FOREST].swch |= (1 << 9);
|
||||
if (gPlayState->sceneNum == SCENE_KOKIRI_FOREST) {
|
||||
Flags_SetSwitch(gPlayState, 9);
|
||||
}
|
||||
gSaveContext.sceneFlags[SCENE_LAKE_HYLIA].swch |= (1 << 1);
|
||||
if (gPlayState->sceneNum == SCENE_LAKE_HYLIA) {
|
||||
Flags_SetSwitch(gPlayState, 1);
|
||||
}
|
||||
gSaveContext.sceneFlags[SCENE_LOST_WOODS].swch |= (1 << 4) | (1 << 18);
|
||||
if (gPlayState->sceneNum == SCENE_LOST_WOODS) {
|
||||
Flags_SetSwitch(gPlayState, 4);
|
||||
Flags_SetSwitch(gPlayState, 18);
|
||||
}
|
||||
gSaveContext.sceneFlags[SCENE_ZORAS_RIVER].swch |= (1 << 3);
|
||||
if (gPlayState->sceneNum == SCENE_ZORAS_RIVER) {
|
||||
Flags_SetSwitch(gPlayState, 3);
|
||||
}
|
||||
ObjBean* bean = (ObjBean*)Actor_Find(&gPlayState->actorCtx, ACTOR_OBJ_BEAN, ACTORCAT_BG);
|
||||
if (bean != nullptr) {
|
||||
Flags_SetSwitch(gPlayState, bean->dyna.actor.params & 0x3F);
|
||||
func_80B8FE00(bean);
|
||||
}
|
||||
AMMO(ITEM_BEAN) = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case RG_DOUBLE_DEFENSE:
|
||||
@@ -3947,13 +3881,7 @@ extern "C" u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) {
|
||||
|
||||
if (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT) ==
|
||||
RO_TRIFORCE_HUNT_WIN) {
|
||||
gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED] =
|
||||
static_cast<u32>(GAMEPLAYSTAT_TOTAL_TIME);
|
||||
gSaveContext.ship.stats.gameComplete = 1;
|
||||
Play_PerformSave(play);
|
||||
Notification::Emit({
|
||||
.message = "Game autosaved",
|
||||
});
|
||||
// Save and warp are deferred until item queue drains
|
||||
GameInteractor_SetTriforceHuntCreditsWarpActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ RANDO_ENUM_ITEM(RT_LH_WATER_HOOKSHOT)
|
||||
RANDO_ENUM_ITEM(RT_GV_CRATE_HOVERS)
|
||||
RANDO_ENUM_ITEM(RT_GV_CHILD_TENT)
|
||||
RANDO_ENUM_ITEM(RT_GV_CHILD_CUCCO_JUMP)
|
||||
RANDO_ENUM_ITEM(RT_GV_HOOKSHOT_BRIDGE)
|
||||
RANDO_ENUM_ITEM(RT_PASS_GUARDS_WITH_NOTHING)
|
||||
RANDO_ENUM_ITEM(RT_GF_WASTELAND_GATE_SIDEHOP_SKIP)
|
||||
RANDO_ENUM_ITEM(RT_GF_ADULT_SKIP_WASTELAND_GATE)
|
||||
|
||||
@@ -1082,13 +1082,21 @@ void CheckTrackerWindow::DrawElement() {
|
||||
}
|
||||
UIWidgets::PushStyleCombobox(THEME_COLOR);
|
||||
if (CVarGetInteger(CVAR_TRACKER_CHECK("SearchInputVisible"), 1)) {
|
||||
if (checkSearch.Draw("", ImGui::GetContentRegionAvail().x - 6)) {
|
||||
if (checkSearch.Draw("", ImGui::GetContentRegionAvail().x - 42)) {
|
||||
UpdateFilters();
|
||||
}
|
||||
std::string checkSearchText = "";
|
||||
checkSearchText = checkSearch.InputBuf;
|
||||
std::string checkSearchText = checkSearch.InputBuf;
|
||||
checkSearchText.erase(std::remove(checkSearchText.begin(), checkSearchText.end(), ' '),
|
||||
checkSearchText.end());
|
||||
ImGui::SameLine();
|
||||
if (UIWidgets::Button(ICON_FA_ERASER, UIWidgets::ButtonOptions()
|
||||
.Size(UIWidgets::Sizes::Inline)
|
||||
.Color(THEME_COLOR)
|
||||
.Padding(ImVec2(10.f, 6.f)))) {
|
||||
checkSearch.Clear();
|
||||
UpdateFilters();
|
||||
doAreaScroll = true;
|
||||
}
|
||||
if (checkSearchText.length() < 1) {
|
||||
ImGui::SameLine(20.0f);
|
||||
ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.4f), "Search...");
|
||||
@@ -1557,7 +1565,8 @@ bool IsCheckShuffled(RandomizerCheck rc) {
|
||||
(loc->GetRCType() != RCTYPE_SCRUB || showScrubs ||
|
||||
(showMajorScrubs && (rc == RC_LW_DEKU_SCRUB_NEAR_BRIDGE || // The 3 scrubs that are always randomized
|
||||
rc == RC_HF_DEKU_SCRUB_GROTTO || rc == RC_LW_DEKU_SCRUB_GROTTO_FRONT))) &&
|
||||
(loc->GetRCType() != RCTYPE_MERCHANT || showMerchants) &&
|
||||
((loc->GetRCType() != RCTYPE_MERCHANT || showMerchants) ||
|
||||
(rc == RC_ZR_MAGIC_BEAN_SALESMAN && showBeans)) &&
|
||||
(loc->GetRCType() != RCTYPE_SONG_LOCATION || showSongs) &&
|
||||
(loc->GetRCType() != RCTYPE_BEEHIVE || showBeehives) &&
|
||||
(loc->GetRCType() != RCTYPE_OCARINA || showOcarinas) &&
|
||||
@@ -1595,8 +1604,7 @@ bool IsCheckShuffled(RandomizerCheck rc) {
|
||||
rc == RC_DMT_TRADE_CLAIM_CHECK // even when shuffle adult trade is off
|
||||
) &&
|
||||
(rc != RC_KF_KOKIRI_SWORD_CHEST || showKokiriSword) && (rc != RC_TOT_MASTER_SWORD || showMasterSword) &&
|
||||
(rc != RC_LH_HYRULE_LOACH || showHyruleLoach) && (rc != RC_ZR_MAGIC_BEAN_SALESMAN || showBeans) &&
|
||||
(rc != RC_HC_MALON_EGG || showWeirdEgg) &&
|
||||
(rc != RC_LH_HYRULE_LOACH || showHyruleLoach) && (rc != RC_HC_MALON_EGG || showWeirdEgg) &&
|
||||
(loc->GetRCType() != RCTYPE_FROG_SONG || showFrogSongRupees) &&
|
||||
((loc->GetRCType() != RCTYPE_MAP && loc->GetRCType() != RCTYPE_COMPASS) || showStartingMapsCompasses) &&
|
||||
(loc->GetRCType() != RCTYPE_FOUNTAIN_FAIRY || showFountainFairies) &&
|
||||
|
||||
@@ -700,17 +700,18 @@ void Entrance_OverrideSpawnScene(s32 sceneNum, s32 spawn) {
|
||||
modifiedLinkActorEntry.rot = gPlayState->linkActorEntry->rot;
|
||||
modifiedLinkActorEntry.params = gPlayState->linkActorEntry->params;
|
||||
|
||||
if (Randomizer_GetSettingValue(RSK_SHUFFLE_DUNGEON_ENTRANCES) == RO_DUNGEON_ENTRANCE_SHUFFLE_ON_PLUS_GANON) {
|
||||
// Move Ganon's Castle exit spawn to be on the small ledge near the castle and not over the void
|
||||
// to prevent Link from falling if the bridge isn't spawned
|
||||
if (sceneNum == SCENE_OUTSIDE_GANONS_CASTLE && spawn == 1) {
|
||||
modifiedLinkActorEntry.pos.x = 0xFEA8;
|
||||
modifiedLinkActorEntry.pos.y = 0x065C;
|
||||
modifiedLinkActorEntry.pos.z = 0x0290;
|
||||
modifiedLinkActorEntry.rot.y = 0x0700;
|
||||
modifiedLinkActorEntry.params = 0x0DFF; // stationary spawn
|
||||
gPlayState->linkActorEntry = &modifiedLinkActorEntry;
|
||||
}
|
||||
// Move Ganon's Castle exit spawn to be on the small ledge near the castle and not over the void
|
||||
// to prevent Link from falling if the bridge isn't spawned
|
||||
if (sceneNum == SCENE_OUTSIDE_GANONS_CASTLE && spawn == 1 &&
|
||||
(Randomizer_GetSettingValue(RSK_SHUFFLE_DUNGEON_ENTRANCES) == RO_DUNGEON_ENTRANCE_SHUFFLE_ON_PLUS_GANON ||
|
||||
Randomizer_GetSettingValue(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF ||
|
||||
Randomizer_GetSettingValue(RSK_SHUFFLE_GANONS_TOWER_ENTRANCE))) {
|
||||
modifiedLinkActorEntry.pos.x = 0xFEA8;
|
||||
modifiedLinkActorEntry.pos.y = 0x065C;
|
||||
modifiedLinkActorEntry.pos.z = 0x0290;
|
||||
modifiedLinkActorEntry.rot.y = 0x0700;
|
||||
modifiedLinkActorEntry.params = 0x0DFF; // stationary spawn
|
||||
gPlayState->linkActorEntry = &modifiedLinkActorEntry;
|
||||
}
|
||||
|
||||
if (Randomizer_GetSettingValue(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF) {
|
||||
|
||||
@@ -537,11 +537,24 @@ ItemTrackerNumbers GetItemCurrentAndMax(ItemTrackerItem item) {
|
||||
: 500;
|
||||
result.currentAmmo = gSaveContext.rupees;
|
||||
break;
|
||||
case ITEM_BOMBCHU:
|
||||
result.currentCapacity = INV_CONTENT(ITEM_BOMBCHU) == ITEM_BOMBCHU ? 50 : 0;
|
||||
case ITEM_BOMBCHU: {
|
||||
auto bombchuBag = RAND_GET_OPTION(RSK_BOMBCHU_BAG);
|
||||
|
||||
uint8_t capacity = 0;
|
||||
|
||||
if (INV_CONTENT(ITEM_BOMBCHU) == ITEM_BOMBCHU) {
|
||||
if (bombchuBag.Is(RO_BOMBCHU_BAG_PROGRESSIVE)) {
|
||||
capacity = OTRGlobals::Instance->gRandoContext->GetBombchuCapacity();
|
||||
} else {
|
||||
capacity = 50;
|
||||
}
|
||||
}
|
||||
|
||||
result.currentCapacity = capacity;
|
||||
result.maxCapacity = 50;
|
||||
result.currentAmmo = AMMO(ITEM_BOMBCHU);
|
||||
break;
|
||||
}
|
||||
case ITEM_BEAN:
|
||||
result.currentCapacity = INV_CONTENT(ITEM_BEAN) == ITEM_BEAN ? 10 : 0;
|
||||
result.maxCapacity = 10;
|
||||
@@ -702,7 +715,7 @@ void DrawItemCount(ItemTrackerItem item, bool hideMax) {
|
||||
bool shouldDisplayAmmo = trackerNumberDisplayMode == ITEM_TRACKER_NUMBER_AMMO ||
|
||||
trackerNumberDisplayMode == ITEM_TRACKER_NUMBER_CURRENT_AMMO_ONLY ||
|
||||
// These items have a static capacity, so display ammo instead
|
||||
item.id == ITEM_BOMBCHU || item.id == ITEM_BEAN || item.id == QUEST_SKULL_TOKEN ||
|
||||
item.id == ITEM_BEAN || item.id == QUEST_SKULL_TOKEN ||
|
||||
item.id == ITEM_HEART_CONTAINER || item.id == ITEM_HEART_PIECE;
|
||||
|
||||
bool shouldDisplayMax = !(trackerNumberDisplayMode == ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY ||
|
||||
|
||||
@@ -1677,6 +1677,10 @@ void Settings::CreateOptions() {
|
||||
OPT_TRICK(RT_GV_CHILD_CUCCO_JUMP, RCQUEST_BOTH, RA_GERUDO_VALLEY, { Tricks::Tag::INTERMEDIATE },
|
||||
"Gerudo Valley Jump Fence with Cucco", "GVCUC",
|
||||
"Using cucco as child, it's possible to jumpslash over the gate.");
|
||||
OPT_TRICK(RT_GV_HOOKSHOT_BRIDGE, RCQUEST_BOTH, RA_GERUDO_VALLEY, { Tricks::Tag::ADVANCED },
|
||||
"Gerudo Valley Bridge with only Hookshot", "GVHSBrg",
|
||||
"Using Hookshot Extension and a precise setup, you can cross the broken bridge in Gerudo Valley with "
|
||||
"only a Hookshot.");
|
||||
OPT_TRICK(RT_PASS_GUARDS_WITH_NOTHING, RCQUEST_BOTH, RA_GERUDO_FORTRESS, { Tricks::Tag::NOVICE },
|
||||
"Sneak Past Moving Gerudo Guards with No Items", "Guards",
|
||||
"The logic normally guarantees Bow or Hookshot to stun them from a distance,"
|
||||
|
||||
@@ -29,6 +29,8 @@ extern "C" {
|
||||
#include "src/overlays/actors/ovl_Bg_Dy_Yoseizo/z_bg_dy_yoseizo.h"
|
||||
#include "src/overlays/actors/ovl_En_Dnt_Demo/z_en_dnt_demo.h"
|
||||
#include "src/overlays/actors/ovl_En_Po_Sisters/z_en_po_sisters.h"
|
||||
#include "src/overlays/actors/ovl_Obj_Lightswitch/z_obj_lightswitch.h"
|
||||
#include "src/overlays/actors/ovl_Bg_Jya_Bombchuiwa/z_bg_jya_bombchuiwa.h"
|
||||
#include <overlays/actors/ovl_Boss_Ganondrof/z_boss_ganondrof.h>
|
||||
#include <overlays/actors/ovl_En_Ik/z_en_ik.h>
|
||||
#include <objects/object_gnd/object_gnd.h>
|
||||
@@ -328,6 +330,15 @@ void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_li
|
||||
RateLimitedSuccessChime();
|
||||
break;
|
||||
}
|
||||
case ACTOR_BG_JYA_BOMBCHUIWA: {
|
||||
BgJyaBombchuiwa* bombchuiwa = (BgJyaBombchuiwa*)actor;
|
||||
if (!(bombchuiwa->drawFlags & 4) && bombchuiwa->timer >= 0 && bombchuiwa->timer < 9) {
|
||||
bombchuiwa->timer = 9;
|
||||
}
|
||||
*should = false;
|
||||
RateLimitedSuccessChime();
|
||||
break;
|
||||
}
|
||||
case ACTOR_EN_GO2: {
|
||||
EnGo2* biggoron = (EnGo2*)actor;
|
||||
biggoron->isAwake = true;
|
||||
@@ -376,8 +387,14 @@ void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_li
|
||||
RateLimitedSuccessChime();
|
||||
break;
|
||||
}
|
||||
case ACTOR_OBJ_LIGHTSWITCH: {
|
||||
ObjLightswitch* lightswitch = (ObjLightswitch*)actor;
|
||||
lightswitch->toggleDelay = 0;
|
||||
*should = false;
|
||||
RateLimitedSuccessChime();
|
||||
break;
|
||||
}
|
||||
case ACTOR_BG_ICE_SHUTTER:
|
||||
case ACTOR_OBJ_LIGHTSWITCH:
|
||||
case ACTOR_OBJ_SYOKUDAI:
|
||||
case ACTOR_OBJ_TIMEBLOCK:
|
||||
case ACTOR_EN_PO_SISTERS:
|
||||
@@ -554,6 +571,7 @@ void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_li
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VB_PLAY_BEAN_PLANTING_CS:
|
||||
case VB_PLAY_EYEDROP_CREATION_ANIM:
|
||||
case VB_PLAY_EYEDROPS_CS:
|
||||
case VB_PLAY_DROP_FISH_FOR_JABU_CS:
|
||||
@@ -1222,41 +1240,41 @@ void TimeSaverOnFlagSetHandler(int16_t flagType, int16_t flag) {
|
||||
case FLAG_EVENT_CHECK_INF:
|
||||
switch (flag) {
|
||||
case EVENTCHKINF_SPOKE_TO_SARIA_ON_BRIDGE:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_FAIRY_OCARINA).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_FAIRY_OCARINA);
|
||||
break;
|
||||
case EVENTCHKINF_OBTAINED_KOKIRI_EMERALD_DEKU_TREE_DEAD:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_KOKIRI_EMERALD).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_KOKIRI_EMERALD);
|
||||
break;
|
||||
case EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_GORON_RUBY).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_GORON_RUBY);
|
||||
break;
|
||||
case EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_ZORA_SAPPHIRE).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_ZORA_SAPPHIRE);
|
||||
break;
|
||||
case EVENTCHKINF_USED_FOREST_TEMPLE_BLUE_WARP:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_FOREST_MEDALLION).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_FOREST_MEDALLION);
|
||||
break;
|
||||
case EVENTCHKINF_USED_FIRE_TEMPLE_BLUE_WARP:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_FIRE_MEDALLION).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_FIRE_MEDALLION);
|
||||
break;
|
||||
case EVENTCHKINF_USED_WATER_TEMPLE_BLUE_WARP:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_WATER_MEDALLION).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_WATER_MEDALLION);
|
||||
break;
|
||||
case EVENTCHKINF_RETURNED_TO_TEMPLE_OF_TIME_WITH_ALL_MEDALLIONS:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_LIGHT_ARROWS).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_LIGHT_ARROWS);
|
||||
break;
|
||||
case EVENTCHKINF_TIME_TRAVELED_TO_ADULT:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_LIGHT_MEDALLION).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_LIGHT_MEDALLION);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case FLAG_RANDOMIZER_INF:
|
||||
switch (flag) {
|
||||
case RAND_INF_DUNGEONS_DONE_SHADOW_TEMPLE:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_SHADOW_MEDALLION).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_SHADOW_MEDALLION);
|
||||
break;
|
||||
case RAND_INF_DUNGEONS_DONE_SPIRIT_TEMPLE:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_SPIRIT_MEDALLION).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_SPIRIT_MEDALLION);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@@ -1268,22 +1286,22 @@ void TimeSaverOnFlagSetHandler(int16_t flagType, int16_t flag) {
|
||||
case FLAG_RANDOMIZER_INF:
|
||||
switch (flag) {
|
||||
case RAND_INF_ZF_GREAT_FAIRY_REWARD:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_FARORES_WIND).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_FARORES_WIND);
|
||||
break;
|
||||
case RAND_INF_HC_GREAT_FAIRY_REWARD:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_DINS_FIRE).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_DINS_FIRE);
|
||||
break;
|
||||
case RAND_INF_COLOSSUS_GREAT_FAIRY_REWARD:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_NAYRUS_LOVE).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_NAYRUS_LOVE);
|
||||
break;
|
||||
case RAND_INF_DMT_GREAT_FAIRY_REWARD:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_MAGIC_SINGLE).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_MAGIC_SINGLE);
|
||||
break;
|
||||
case RAND_INF_DMC_GREAT_FAIRY_REWARD:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_MAGIC_DOUBLE).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_MAGIC_DOUBLE);
|
||||
break;
|
||||
case RAND_INF_OGC_GREAT_FAIRY_REWARD:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_DOUBLE_DEFENSE).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_DOUBLE_DEFENSE);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@@ -1309,47 +1327,44 @@ void TimeSaverOnFlagSetHandler(int16_t flagType, int16_t flag) {
|
||||
case FLAG_EVENT_CHECK_INF:
|
||||
switch (flag) {
|
||||
case EVENTCHKINF_LEARNED_ZELDAS_LULLABY:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_ZELDAS_LULLABY).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_ZELDAS_LULLABY);
|
||||
break;
|
||||
case EVENTCHKINF_LEARNED_MINUET_OF_FOREST:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_MINUET_OF_FOREST).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_MINUET_OF_FOREST);
|
||||
break;
|
||||
case EVENTCHKINF_LEARNED_BOLERO_OF_FIRE:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_BOLERO_OF_FIRE).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_BOLERO_OF_FIRE);
|
||||
break;
|
||||
case EVENTCHKINF_LEARNED_SERENADE_OF_WATER:
|
||||
vanillaQueuedItemEntry =
|
||||
Rando::StaticData::RetrieveItem(RG_SERENADE_OF_WATER).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_SERENADE_OF_WATER);
|
||||
break;
|
||||
case EVENTCHKINF_LEARNED_REQUIEM_OF_SPIRIT:
|
||||
vanillaQueuedItemEntry =
|
||||
Rando::StaticData::RetrieveItem(RG_REQUIEM_OF_SPIRIT).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_REQUIEM_OF_SPIRIT);
|
||||
break;
|
||||
case EVENTCHKINF_BONGO_BONGO_ESCAPED_FROM_WELL:
|
||||
vanillaQueuedItemEntry =
|
||||
Rando::StaticData::RetrieveItem(RG_NOCTURNE_OF_SHADOW).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_NOCTURNE_OF_SHADOW);
|
||||
break;
|
||||
case EVENTCHKINF_LEARNED_PRELUDE_OF_LIGHT:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_PRELUDE_OF_LIGHT).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_PRELUDE_OF_LIGHT);
|
||||
break;
|
||||
case EVENTCHKINF_LEARNED_SARIAS_SONG:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_SARIAS_SONG).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_SARIAS_SONG);
|
||||
break;
|
||||
case EVENTCHKINF_LEARNED_SONG_OF_TIME:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_SONG_OF_TIME).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_SONG_OF_TIME);
|
||||
break;
|
||||
case EVENTCHKINF_LEARNED_SONG_OF_STORMS:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_SONG_OF_STORMS).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_SONG_OF_STORMS);
|
||||
break;
|
||||
case EVENTCHKINF_LEARNED_SUNS_SONG:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_SUNS_SONG).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_SUNS_SONG);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case FLAG_RANDOMIZER_INF:
|
||||
switch (flag) {
|
||||
case RAND_INF_LEARNED_EPONA_SONG:
|
||||
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_EPONAS_SONG).GetGIEntry_Copy();
|
||||
TimeSaverQueueItem(RG_EPONAS_SONG);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@@ -1396,12 +1411,10 @@ static void TimeSaverRegisterHooks() {
|
||||
TimeSaverOnSceneInitHandler);
|
||||
COND_HOOK(OnVanillaBehavior, true, TimeSaverOnVanillaBehaviorHandler);
|
||||
COND_HOOK(OnActorInit, true, TimeSaverOnActorInitHandler);
|
||||
COND_HOOK(OnSceneInit, true, [](int16_t sceneNum) { successChimeCooldown = 0; });
|
||||
|
||||
// item queue for use outside rando, rando has its own queue
|
||||
COND_HOOK(OnLoadGame, !IS_RANDO, [](int32_t fileNum) {
|
||||
vanillaQueuedItemEntry = GET_ITEM_NONE;
|
||||
successChimeCooldown = 0;
|
||||
});
|
||||
COND_HOOK(OnLoadGame, !IS_RANDO, [](int32_t fileNum) { vanillaQueuedItemEntry = GET_ITEM_NONE; });
|
||||
COND_HOOK(OnItemReceive, !IS_RANDO, TimeSaverOnItemReceiveHandler);
|
||||
COND_HOOK(OnPlayerUpdate, !IS_RANDO, TimeSaverOnPlayerUpdateHandler);
|
||||
COND_HOOK(OnFlagSet,
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <ship/resource/type/Json.h>
|
||||
#include <libultraship/classes.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <spdlog/fmt/fmt.h>
|
||||
|
||||
#include "soh/ShipInit.hpp"
|
||||
#include "message_data_static.h"
|
||||
@@ -37,11 +36,10 @@ nlohmann::json fileChooseMap = nullptr;
|
||||
std::string GetParameritizedText(std::string key, TextBank bank, const char* arg) {
|
||||
switch (bank) {
|
||||
case TEXT_BANK_SCENES: {
|
||||
return sceneMap[key].get<std::string>();
|
||||
break;
|
||||
return sceneMap.value(key, "unknown");
|
||||
}
|
||||
case TEXT_BANK_MISC: {
|
||||
auto value = miscMap[key].get<std::string>();
|
||||
auto value = miscMap.value(key, "unknown");
|
||||
|
||||
std::string searchString = "$0";
|
||||
size_t index = value.find(searchString);
|
||||
@@ -49,15 +47,11 @@ std::string GetParameritizedText(std::string key, TextBank bank, const char* arg
|
||||
if (index != std::string::npos) {
|
||||
assert(arg != nullptr);
|
||||
value.replace(index, searchString.size(), std::string(arg));
|
||||
return value;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
|
||||
break;
|
||||
return value;
|
||||
}
|
||||
case TEXT_BANK_KALEIDO: {
|
||||
auto value = kaleidoMap[key].get<std::string>();
|
||||
auto value = kaleidoMap.value(key, "unknown");
|
||||
|
||||
std::string searchString = "$0";
|
||||
size_t index = value.find(searchString);
|
||||
@@ -65,15 +59,11 @@ std::string GetParameritizedText(std::string key, TextBank bank, const char* arg
|
||||
if (index != std::string::npos) {
|
||||
assert(arg != nullptr);
|
||||
value.replace(index, searchString.size(), std::string(arg));
|
||||
return value;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
|
||||
break;
|
||||
return value;
|
||||
}
|
||||
case TEXT_BANK_FILECHOOSE: {
|
||||
auto value = fileChooseMap[key].get<std::string>();
|
||||
auto value = fileChooseMap.value(key, "unknown");
|
||||
|
||||
std::string searchString = "$0";
|
||||
size_t index = value.find(searchString);
|
||||
@@ -81,14 +71,11 @@ std::string GetParameritizedText(std::string key, TextBank bank, const char* arg
|
||||
if (index != std::string::npos) {
|
||||
assert(arg != nullptr);
|
||||
value.replace(index, searchString.size(), std::string(arg));
|
||||
return value;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
|
||||
break;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
const char* GetLanguageCode() {
|
||||
|
||||
@@ -191,6 +191,9 @@ inline void from_json(const json& j, SaveContext& saveContext) {
|
||||
j.at("swordHealth").get_to(saveContext.swordHealth);
|
||||
std::vector<u32> sceneFlagsArray;
|
||||
j.at("sceneFlags").get_to(sceneFlagsArray);
|
||||
if (sceneFlagsArray.size() < 124 * 4) {
|
||||
sceneFlagsArray.resize(124 * 4, 0);
|
||||
}
|
||||
for (int i = 0; i < 124; i++) {
|
||||
saveContext.sceneFlags[i].chest = sceneFlagsArray[i * 4];
|
||||
saveContext.sceneFlags[i].swch = sceneFlagsArray[i * 4 + 1];
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
#include "soh/OTRGlobals.h"
|
||||
#include "soh/Enhancements/randomizer/randomizerEnums/RandomizerCheck.h"
|
||||
|
||||
static bool isResultOfHandling = false;
|
||||
|
||||
@@ -39,6 +40,10 @@ void Anchor::HandlePacket_SetCheckStatus(nlohmann::json payload) {
|
||||
auto randoContext = Rando::Context::GetInstance();
|
||||
|
||||
RandomizerCheck rc = payload["rc"].get<RandomizerCheck>();
|
||||
if (rc < 0 || rc >= RC_MAX) {
|
||||
SPDLOG_ERROR("[Anchor] SET_CHECK_STATUS: rc {} out of range", (int)rc);
|
||||
return;
|
||||
}
|
||||
RandomizerCheckStatus status = payload["status"].get<RandomizerCheckStatus>();
|
||||
bool skipped = payload["skipped"].get<bool>();
|
||||
|
||||
|
||||
@@ -41,6 +41,13 @@ void Anchor::HandlePacket_SetFlag(nlohmann::json payload) {
|
||||
s16 flagType = payload["flagType"].get<s16>();
|
||||
s16 flag = payload["flag"].get<s16>();
|
||||
|
||||
// sceneNum == SCENE_ID_MAX is a sentinel meaning "global flag" (handled below); only larger
|
||||
// values would index gSaveContext.sceneFlags out of bounds.
|
||||
if (sceneNum < 0 || sceneNum > SCENE_ID_MAX) {
|
||||
SPDLOG_ERROR("[Anchor] SET_FLAG: sceneNum {} out of range", sceneNum);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sceneNum == SCENE_ID_MAX) {
|
||||
auto effect = new GameInteractionEffect::SetFlag();
|
||||
effect->parameters[0] = flagType;
|
||||
|
||||
@@ -39,6 +39,12 @@ void Anchor::HandlePacket_TeleportTo(nlohmann::json payload) {
|
||||
|
||||
s32 entranceIndex = payload["entranceIndex"].get<s32>();
|
||||
s8 roomIndex = payload["roomIndex"].get<s8>();
|
||||
|
||||
if (entranceIndex < 0 || roomIndex < 0) {
|
||||
SPDLOG_ERROR("[Anchor] TELEPORT_TO: invalid entranceIndex {} or roomIndex {}", entranceIndex, (int)roomIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
PosRot posRot = payload["posRot"].get<PosRot>();
|
||||
|
||||
gPlayState->nextEntranceIndex = entranceIndex;
|
||||
|
||||
@@ -41,6 +41,13 @@ void Anchor::HandlePacket_UnsetFlag(nlohmann::json payload) {
|
||||
s16 flagType = payload["flagType"].get<s16>();
|
||||
s16 flag = payload["flag"].get<s16>();
|
||||
|
||||
// sceneNum == SCENE_ID_MAX is a sentinel meaning "global flag" (handled below); only larger
|
||||
// values would index gSaveContext.sceneFlags out of bounds.
|
||||
if (sceneNum < 0 || sceneNum > SCENE_ID_MAX) {
|
||||
SPDLOG_ERROR("[Anchor] UNSET_FLAG: sceneNum {} out of range", sceneNum);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sceneNum == SCENE_ID_MAX) {
|
||||
auto effect = new GameInteractionEffect::UnsetFlag();
|
||||
effect->parameters[0] = flagType;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "soh/Network/Anchor/Anchor.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
#include "soh/OTRGlobals.h"
|
||||
|
||||
@@ -33,6 +34,12 @@ void Anchor::HandlePacket_UpdateDungeonItems(nlohmann::json payload) {
|
||||
}
|
||||
|
||||
u16 mapIndex = payload["mapIndex"].get<u16>();
|
||||
// dungeonKeys is shorter than dungeonItems (19 vs 20), so bound by the smaller of the two.
|
||||
if (mapIndex >= ARRAY_COUNT(gSaveContext.inventory.dungeonItems) ||
|
||||
mapIndex >= ARRAY_COUNT(gSaveContext.inventory.dungeonKeys)) {
|
||||
SPDLOG_ERROR("[Anchor] UPDATE_DUNGEON_ITEMS: mapIndex {} out of range", mapIndex);
|
||||
return;
|
||||
}
|
||||
gSaveContext.inventory.dungeonItems[mapIndex] = payload["dungeonItems"].get<u8>();
|
||||
gSaveContext.inventory.dungeonKeys[mapIndex] = payload["dungeonKeys"].get<s8>();
|
||||
}
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <spdlog/fmt/fmt.h>
|
||||
#include <regex>
|
||||
#include "soh/OTRGlobals.h"
|
||||
#include "soh/ShipInit.hpp"
|
||||
|
||||
extern "C" {
|
||||
#include <z64.h>
|
||||
@@ -30,19 +29,19 @@ void CrowdControl::OnDisconnected() {
|
||||
}
|
||||
|
||||
void CrowdControl::OnIncomingJson(nlohmann::json payload) {
|
||||
Effect* incomingEffect = ParseMessage(payload);
|
||||
std::unique_ptr<Effect> incomingEffect = ParseMessage(payload);
|
||||
if (!incomingEffect) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If effect is not a timed effect, execute and return result.
|
||||
if (!incomingEffect->timeRemaining) {
|
||||
EffectResult result = CrowdControl::ExecuteEffect(incomingEffect);
|
||||
EffectResult result = CrowdControl::ExecuteEffect(incomingEffect.get());
|
||||
EmitMessage(incomingEffect->id, incomingEffect->timeRemaining, result);
|
||||
} else {
|
||||
// If another timed effect is already active that conflicts with the incoming effect.
|
||||
bool isConflictingEffectActive = false;
|
||||
for (Effect* effect : activeEffects) {
|
||||
for (const auto& effect : activeEffects) {
|
||||
if (effect != incomingEffect && effect->category == incomingEffect->category &&
|
||||
effect->id < incomingEffect->id) {
|
||||
isConflictingEffectActive = true;
|
||||
@@ -53,14 +52,14 @@ void CrowdControl::OnIncomingJson(nlohmann::json payload) {
|
||||
|
||||
if (!isConflictingEffectActive) {
|
||||
// Check if effect can be applied, if it can't, let CC know.
|
||||
EffectResult result = CrowdControl::CanApplyEffect(incomingEffect);
|
||||
EffectResult result = CrowdControl::CanApplyEffect(incomingEffect.get());
|
||||
if (result == EffectResult::Retry || result == EffectResult::Failure) {
|
||||
EmitMessage(incomingEffect->id, incomingEffect->timeRemaining, result);
|
||||
return;
|
||||
}
|
||||
|
||||
activeEffectsMutex.lock();
|
||||
activeEffects.push_back(incomingEffect);
|
||||
activeEffects.push_back(std::move(incomingEffect));
|
||||
activeEffectsMutex.unlock();
|
||||
}
|
||||
}
|
||||
@@ -75,17 +74,15 @@ void CrowdControl::ProcessActiveEffects() {
|
||||
auto it = activeEffects.begin();
|
||||
|
||||
while (it != activeEffects.end()) {
|
||||
Effect* effect = *it;
|
||||
Effect* effect = it->get();
|
||||
EffectResult result = CrowdControl::ExecuteEffect(effect);
|
||||
|
||||
if (result == EffectResult::Success) {
|
||||
// If time remaining has reached 0, we have finished the effect.
|
||||
if (effect->timeRemaining <= 0) {
|
||||
it = activeEffects.erase(std::remove(activeEffects.begin(), activeEffects.end(), effect),
|
||||
activeEffects.end());
|
||||
GameInteractor::RemoveEffect(
|
||||
*dynamic_cast<RemovableGameInteractionEffect*>(effect->giEffect.get()));
|
||||
delete effect;
|
||||
it = activeEffects.erase(it);
|
||||
} else {
|
||||
// If we have a success after previously being paused, tell CC to resume timer.
|
||||
if (effect->isPaused) {
|
||||
@@ -169,7 +166,7 @@ CrowdControl::EffectResult CrowdControl::TranslateGiEnum(GameInteractionEffectQu
|
||||
return result;
|
||||
}
|
||||
|
||||
CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) {
|
||||
std::unique_ptr<CrowdControl::Effect> CrowdControl::ParseMessage(nlohmann::json dataReceived) {
|
||||
if (!dataReceived.contains("id") || !dataReceived.contains("type")) {
|
||||
SPDLOG_ERROR("[CrowdControl] Invalid payload received:\n{}", dataReceived.dump());
|
||||
return nullptr;
|
||||
@@ -177,13 +174,16 @@ CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) {
|
||||
|
||||
SPDLOG_INFO("[CrowdControl] Received payload:\n{}", dataReceived.dump());
|
||||
|
||||
if (!dataReceived.contains("code")) {
|
||||
// "parameters" is intentionally not required: most effects (spawn enemies, teleports, status
|
||||
// effects, etc.) carry no parameters. Its absence is handled safely below, and any type error
|
||||
// is caught by the guard in Network::HandleRemoteJson.
|
||||
if (!dataReceived.contains("code") || !dataReceived.contains("viewer")) {
|
||||
// This seems to happen when the CC session ends
|
||||
SPDLOG_ERROR("[CrowdControl] Payload does not contain code, ignoring.");
|
||||
SPDLOG_ERROR("[CrowdControl] Payload does not contain code or viewer, ignoring.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Effect* effect = new Effect();
|
||||
auto effect = std::make_unique<Effect>();
|
||||
effect->lastExecutionResult = EffectResult::Initiate;
|
||||
effect->id = dataReceived["id"];
|
||||
effect->viewerName = dataReceived["viewer"];
|
||||
@@ -195,9 +195,15 @@ CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) {
|
||||
receivedParameter = dataReceived["parameters"][0];
|
||||
}
|
||||
|
||||
auto it = effectStringToEnum.find(effectName);
|
||||
if (it == effectStringToEnum.end()) {
|
||||
SPDLOG_ERROR("[CrowdControl] Unknown effect code: {}", effectName);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Assign GameInteractionEffect + values to CC effect.
|
||||
// Categories are mostly used for checking for conflicting timed effects.
|
||||
switch (effectStringToEnum[effectName]) {
|
||||
switch (it->second) {
|
||||
|
||||
// Spawn Enemies and Objects
|
||||
case kEffectSpawnCuccoStorm:
|
||||
@@ -625,37 +631,37 @@ CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) {
|
||||
case kEffectTpLinksHouse:
|
||||
effect->giEffect = std::make_unique<GameInteractionEffect::TeleportPlayer>();
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
|
||||
GI_TP_DEST_LINKSHOUSE;
|
||||
ENTR_LINKS_HOUSE_CHILD_SPAWN;
|
||||
break;
|
||||
case kEffectTpMinuet:
|
||||
effect->giEffect = std::make_unique<GameInteractionEffect::TeleportPlayer>();
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
|
||||
GI_TP_DEST_MINUET;
|
||||
ENTR_SACRED_FOREST_MEADOW_WARP_PAD;
|
||||
break;
|
||||
case kEffectTpBolero:
|
||||
effect->giEffect = std::make_unique<GameInteractionEffect::TeleportPlayer>();
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
|
||||
GI_TP_DEST_BOLERO;
|
||||
ENTR_DEATH_MOUNTAIN_CRATER_WARP_PAD;
|
||||
break;
|
||||
case kEffectTpSerenade:
|
||||
effect->giEffect = std::make_unique<GameInteractionEffect::TeleportPlayer>();
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
|
||||
GI_TP_DEST_SERENADE;
|
||||
ENTR_LAKE_HYLIA_WARP_PAD;
|
||||
break;
|
||||
case kEffectTpRequiem:
|
||||
effect->giEffect = std::make_unique<GameInteractionEffect::TeleportPlayer>();
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
|
||||
GI_TP_DEST_REQUIEM;
|
||||
ENTR_DESERT_COLOSSUS_WARP_PAD;
|
||||
break;
|
||||
case kEffectTpNocturne:
|
||||
effect->giEffect = std::make_unique<GameInteractionEffect::TeleportPlayer>();
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
|
||||
GI_TP_DEST_NOCTURNE;
|
||||
ENTR_GRAVEYARD_WARP_PAD;
|
||||
break;
|
||||
case kEffectTpPrelude:
|
||||
effect->giEffect = std::make_unique<GameInteractionEffect::TeleportPlayer>();
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
|
||||
GI_TP_DEST_PRELUDE;
|
||||
ENTR_TEMPLE_OF_TIME_WARP_PAD;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -64,14 +64,14 @@ class CrowdControl : public Network {
|
||||
|
||||
std::thread ccThreadProcess;
|
||||
|
||||
std::vector<Effect*> activeEffects;
|
||||
std::vector<std::unique_ptr<Effect>> activeEffects;
|
||||
std::mutex activeEffectsMutex;
|
||||
|
||||
void HandleRemoteData(nlohmann::json payload);
|
||||
void ProcessActiveEffects();
|
||||
|
||||
void EmitMessage(uint32_t eventId, long timeRemaining, EffectResult status);
|
||||
Effect* ParseMessage(nlohmann::json payload);
|
||||
std::unique_ptr<Effect> ParseMessage(nlohmann::json payload);
|
||||
EffectResult ExecuteEffect(Effect* effect);
|
||||
EffectResult CanApplyEffect(Effect* effect);
|
||||
EffectResult TranslateGiEnum(GameInteractionEffectQueryResult giResult);
|
||||
|
||||
@@ -157,5 +157,9 @@ void Network::HandleRemoteJson(std::string payload) {
|
||||
return;
|
||||
}
|
||||
|
||||
OnIncomingJson(jsonPayload);
|
||||
try {
|
||||
OnIncomingJson(jsonPayload);
|
||||
} catch (const std::exception& e) {
|
||||
SPDLOG_ERROR("[Network] Exception handling incoming JSON: {}", e.what());
|
||||
} catch (...) { SPDLOG_ERROR("[Network] Unknown exception handling incoming JSON"); }
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
#include <libultraship/bridge.h>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "soh/OTRGlobals.h"
|
||||
#include "soh/util.h"
|
||||
|
||||
template <class DstType, class SrcType> bool IsType(const SrcType* src) {
|
||||
return dynamic_cast<const DstType*>(src) != nullptr;
|
||||
@@ -98,12 +96,12 @@ void Sail::OnIncomingJson(nlohmann::json payload) {
|
||||
return;
|
||||
}
|
||||
|
||||
GameInteractionEffectBase* giEffect = EffectFromJson(payload["effect"]);
|
||||
auto giEffect = EffectFromJson(payload["effect"]);
|
||||
if (giEffect) {
|
||||
GameInteractionEffectQueryResult result;
|
||||
if (effectType == "remove") {
|
||||
if (IsType<RemovableGameInteractionEffect>(giEffect)) {
|
||||
result = dynamic_cast<RemovableGameInteractionEffect*>(giEffect)->Remove();
|
||||
if (IsType<RemovableGameInteractionEffect>(giEffect.get())) {
|
||||
result = dynamic_cast<RemovableGameInteractionEffect*>(giEffect.get())->Remove();
|
||||
} else {
|
||||
result = GameInteractionEffectQueryResult::NotPossible;
|
||||
}
|
||||
@@ -133,7 +131,7 @@ void Sail::OnIncomingJson(nlohmann::json payload) {
|
||||
} catch (...) { SPDLOG_ERROR("[Sail] Unknown exception handling remote JSON"); }
|
||||
}
|
||||
|
||||
GameInteractionEffectBase* Sail::EffectFromJson(nlohmann::json payload) {
|
||||
std::unique_ptr<GameInteractionEffectBase> Sail::EffectFromJson(nlohmann::json payload) {
|
||||
if (!payload.contains("name")) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -141,7 +139,7 @@ GameInteractionEffectBase* Sail::EffectFromJson(nlohmann::json payload) {
|
||||
std::string name = payload["name"].get<std::string>();
|
||||
|
||||
if (name == "SetSceneFlag") {
|
||||
auto effect = new GameInteractionEffect::SetSceneFlag();
|
||||
auto effect = std::make_unique<GameInteractionEffect::SetSceneFlag>();
|
||||
if (payload.contains("parameters")) {
|
||||
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
||||
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
|
||||
@@ -149,7 +147,7 @@ GameInteractionEffectBase* Sail::EffectFromJson(nlohmann::json payload) {
|
||||
}
|
||||
return effect;
|
||||
} else if (name == "UnsetSceneFlag") {
|
||||
auto effect = new GameInteractionEffect::UnsetSceneFlag();
|
||||
auto effect = std::make_unique<GameInteractionEffect::UnsetSceneFlag>();
|
||||
if (payload.contains("parameters")) {
|
||||
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
||||
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
|
||||
@@ -157,171 +155,171 @@ GameInteractionEffectBase* Sail::EffectFromJson(nlohmann::json payload) {
|
||||
}
|
||||
return effect;
|
||||
} else if (name == "SetFlag") {
|
||||
auto effect = new GameInteractionEffect::SetFlag();
|
||||
auto effect = std::make_unique<GameInteractionEffect::SetFlag>();
|
||||
if (payload.contains("parameters")) {
|
||||
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
||||
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
|
||||
}
|
||||
return effect;
|
||||
} else if (name == "UnsetFlag") {
|
||||
auto effect = new GameInteractionEffect::UnsetFlag();
|
||||
auto effect = std::make_unique<GameInteractionEffect::UnsetFlag>();
|
||||
if (payload.contains("parameters")) {
|
||||
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
||||
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
|
||||
}
|
||||
return effect;
|
||||
} else if (name == "ModifyHeartContainers") {
|
||||
auto effect = new GameInteractionEffect::ModifyHeartContainers();
|
||||
auto effect = std::make_unique<GameInteractionEffect::ModifyHeartContainers>();
|
||||
if (payload.contains("parameters")) {
|
||||
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
||||
}
|
||||
return effect;
|
||||
} else if (name == "FillMagic") {
|
||||
return new GameInteractionEffect::FillMagic();
|
||||
return std::make_unique<GameInteractionEffect::FillMagic>();
|
||||
} else if (name == "EmptyMagic") {
|
||||
return new GameInteractionEffect::EmptyMagic();
|
||||
return std::make_unique<GameInteractionEffect::EmptyMagic>();
|
||||
} else if (name == "ModifyRupees") {
|
||||
auto effect = new GameInteractionEffect::ModifyRupees();
|
||||
auto effect = std::make_unique<GameInteractionEffect::ModifyRupees>();
|
||||
if (payload.contains("parameters")) {
|
||||
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
||||
}
|
||||
return effect;
|
||||
} else if (name == "NoUI") {
|
||||
return new GameInteractionEffect::NoUI();
|
||||
return std::make_unique<GameInteractionEffect::NoUI>();
|
||||
} else if (name == "ModifyGravity") {
|
||||
auto effect = new GameInteractionEffect::ModifyGravity();
|
||||
auto effect = std::make_unique<GameInteractionEffect::ModifyGravity>();
|
||||
if (payload.contains("parameters")) {
|
||||
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
||||
}
|
||||
return effect;
|
||||
} else if (name == "ModifyHealth") {
|
||||
auto effect = new GameInteractionEffect::ModifyHealth();
|
||||
auto effect = std::make_unique<GameInteractionEffect::ModifyHealth>();
|
||||
if (payload.contains("parameters")) {
|
||||
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
||||
}
|
||||
return effect;
|
||||
} else if (name == "SetPlayerHealth") {
|
||||
auto effect = new GameInteractionEffect::SetPlayerHealth();
|
||||
auto effect = std::make_unique<GameInteractionEffect::SetPlayerHealth>();
|
||||
if (payload.contains("parameters")) {
|
||||
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
||||
}
|
||||
return effect;
|
||||
} else if (name == "FreezePlayer") {
|
||||
return new GameInteractionEffect::FreezePlayer();
|
||||
return std::make_unique<GameInteractionEffect::FreezePlayer>();
|
||||
} else if (name == "BurnPlayer") {
|
||||
return new GameInteractionEffect::BurnPlayer();
|
||||
return std::make_unique<GameInteractionEffect::BurnPlayer>();
|
||||
} else if (name == "ElectrocutePlayer") {
|
||||
return new GameInteractionEffect::ElectrocutePlayer();
|
||||
return std::make_unique<GameInteractionEffect::ElectrocutePlayer>();
|
||||
} else if (name == "KnockbackPlayer") {
|
||||
auto effect = new GameInteractionEffect::KnockbackPlayer();
|
||||
auto effect = std::make_unique<GameInteractionEffect::KnockbackPlayer>();
|
||||
if (payload.contains("parameters")) {
|
||||
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
||||
}
|
||||
return effect;
|
||||
} else if (name == "ModifyLinkSize") {
|
||||
auto effect = new GameInteractionEffect::ModifyLinkSize();
|
||||
auto effect = std::make_unique<GameInteractionEffect::ModifyLinkSize>();
|
||||
if (payload.contains("parameters")) {
|
||||
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
||||
}
|
||||
return effect;
|
||||
} else if (name == "InvisibleLink") {
|
||||
return new GameInteractionEffect::InvisibleLink();
|
||||
return std::make_unique<GameInteractionEffect::InvisibleLink>();
|
||||
} else if (name == "PacifistMode") {
|
||||
return new GameInteractionEffect::PacifistMode();
|
||||
return std::make_unique<GameInteractionEffect::PacifistMode>();
|
||||
} else if (name == "DisableZTargeting") {
|
||||
return new GameInteractionEffect::DisableZTargeting();
|
||||
return std::make_unique<GameInteractionEffect::DisableZTargeting>();
|
||||
} else if (name == "WeatherRainstorm") {
|
||||
return new GameInteractionEffect::WeatherRainstorm();
|
||||
return std::make_unique<GameInteractionEffect::WeatherRainstorm>();
|
||||
} else if (name == "ReverseControls") {
|
||||
return new GameInteractionEffect::ReverseControls();
|
||||
return std::make_unique<GameInteractionEffect::ReverseControls>();
|
||||
} else if (name == "ForceEquipBoots") {
|
||||
auto effect = new GameInteractionEffect::ForceEquipBoots();
|
||||
auto effect = std::make_unique<GameInteractionEffect::ForceEquipBoots>();
|
||||
if (payload.contains("parameters")) {
|
||||
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
||||
}
|
||||
return effect;
|
||||
} else if (name == "ModifyMovementSpeedMultiplier") {
|
||||
auto effect = new GameInteractionEffect::ModifyMovementSpeedMultiplier();
|
||||
auto effect = std::make_unique<GameInteractionEffect::ModifyMovementSpeedMultiplier>();
|
||||
if (payload.contains("parameters")) {
|
||||
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
||||
}
|
||||
return effect;
|
||||
} else if (name == "OneHitKO") {
|
||||
return new GameInteractionEffect::OneHitKO();
|
||||
return std::make_unique<GameInteractionEffect::OneHitKO>();
|
||||
} else if (name == "ModifyDefenseModifier") {
|
||||
auto effect = new GameInteractionEffect::ModifyDefenseModifier();
|
||||
auto effect = std::make_unique<GameInteractionEffect::ModifyDefenseModifier>();
|
||||
if (payload.contains("parameters")) {
|
||||
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
||||
}
|
||||
return effect;
|
||||
} else if (name == "GiveOrTakeShield") {
|
||||
auto effect = new GameInteractionEffect::GiveOrTakeShield();
|
||||
auto effect = std::make_unique<GameInteractionEffect::GiveOrTakeShield>();
|
||||
if (payload.contains("parameters")) {
|
||||
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
||||
}
|
||||
return effect;
|
||||
} else if (name == "TeleportPlayer") {
|
||||
auto effect = new GameInteractionEffect::TeleportPlayer();
|
||||
auto effect = std::make_unique<GameInteractionEffect::TeleportPlayer>();
|
||||
if (payload.contains("parameters")) {
|
||||
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
||||
}
|
||||
return effect;
|
||||
} else if (name == "ClearAssignedButtons") {
|
||||
auto effect = new GameInteractionEffect::ClearAssignedButtons();
|
||||
auto effect = std::make_unique<GameInteractionEffect::ClearAssignedButtons>();
|
||||
if (payload.contains("parameters")) {
|
||||
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
||||
}
|
||||
return effect;
|
||||
} else if (name == "SetTimeOfDay") {
|
||||
auto effect = new GameInteractionEffect::SetTimeOfDay();
|
||||
auto effect = std::make_unique<GameInteractionEffect::SetTimeOfDay>();
|
||||
if (payload.contains("parameters")) {
|
||||
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
||||
}
|
||||
return effect;
|
||||
} else if (name == "SetCollisionViewer") {
|
||||
return new GameInteractionEffect::SetCollisionViewer();
|
||||
return std::make_unique<GameInteractionEffect::SetCollisionViewer>();
|
||||
} else if (name == "RandomizeCosmetics") {
|
||||
return new GameInteractionEffect::RandomizeCosmetics();
|
||||
return std::make_unique<GameInteractionEffect::RandomizeCosmetics>();
|
||||
} else if (name == "PressButton") {
|
||||
auto effect = new GameInteractionEffect::PressButton();
|
||||
auto effect = std::make_unique<GameInteractionEffect::PressButton>();
|
||||
if (payload.contains("parameters")) {
|
||||
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
||||
}
|
||||
return effect;
|
||||
} else if (name == "PressRandomButton") {
|
||||
auto effect = new GameInteractionEffect::PressRandomButton();
|
||||
auto effect = std::make_unique<GameInteractionEffect::PressRandomButton>();
|
||||
if (payload.contains("parameters")) {
|
||||
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
||||
}
|
||||
return effect;
|
||||
} else if (name == "AddOrTakeAmmo") {
|
||||
auto effect = new GameInteractionEffect::AddOrTakeAmmo();
|
||||
auto effect = std::make_unique<GameInteractionEffect::AddOrTakeAmmo>();
|
||||
if (payload.contains("parameters")) {
|
||||
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
||||
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
|
||||
}
|
||||
return effect;
|
||||
} else if (name == "RandomBombFuseTimer") {
|
||||
return new GameInteractionEffect::RandomBombFuseTimer();
|
||||
return std::make_unique<GameInteractionEffect::RandomBombFuseTimer>();
|
||||
} else if (name == "DisableLedgeGrabs") {
|
||||
return new GameInteractionEffect::DisableLedgeGrabs();
|
||||
return std::make_unique<GameInteractionEffect::DisableLedgeGrabs>();
|
||||
} else if (name == "RandomWind") {
|
||||
return new GameInteractionEffect::RandomWind();
|
||||
return std::make_unique<GameInteractionEffect::RandomWind>();
|
||||
} else if (name == "RandomBonks") {
|
||||
return new GameInteractionEffect::RandomBonks();
|
||||
return std::make_unique<GameInteractionEffect::RandomBonks>();
|
||||
} else if (name == "PlayerInvincibility") {
|
||||
return new GameInteractionEffect::PlayerInvincibility();
|
||||
return std::make_unique<GameInteractionEffect::PlayerInvincibility>();
|
||||
} else if (name == "SlipperyFloor") {
|
||||
return new GameInteractionEffect::SlipperyFloor();
|
||||
return std::make_unique<GameInteractionEffect::SlipperyFloor>();
|
||||
} else if (name == "SpawnEnemyWithOffset") {
|
||||
auto effect = new GameInteractionEffect::SpawnEnemyWithOffset();
|
||||
auto effect = std::make_unique<GameInteractionEffect::SpawnEnemyWithOffset>();
|
||||
if (payload.contains("parameters")) {
|
||||
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
||||
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
|
||||
}
|
||||
return effect;
|
||||
} else if (name == "SpawnActor") {
|
||||
auto effect = new GameInteractionEffect::SpawnActor();
|
||||
auto effect = std::make_unique<GameInteractionEffect::SpawnActor>();
|
||||
if (payload.contains("parameters")) {
|
||||
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
||||
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
#define NETWORK_SAIL_H
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "soh/Network/Network.h"
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
|
||||
class Sail : public Network {
|
||||
private:
|
||||
GameInteractionEffectBase* EffectFromJson(nlohmann::json payload);
|
||||
std::unique_ptr<GameInteractionEffectBase> EffectFromJson(nlohmann::json payload);
|
||||
void RegisterHooks();
|
||||
|
||||
public:
|
||||
|
||||
+10
-12
@@ -403,7 +403,7 @@ void OTRGlobals::RunExtract(int argc, char* argv[]) {
|
||||
std::vector<std::string> args;
|
||||
if (argc > 1) {
|
||||
for (int i = 1; i < argc; i++) {
|
||||
args.push_back(argv[argc]);
|
||||
args.push_back(argv[i]);
|
||||
}
|
||||
}
|
||||
Extractor extract;
|
||||
@@ -445,6 +445,11 @@ void OTRGlobals::RunExtract(int argc, char* argv[]) {
|
||||
|
||||
std::shared_ptr<BS::thread_pool> threadPool = std::make_shared<BS::thread_pool>(1);
|
||||
std::optional<std::future<void>> extractionTask;
|
||||
|
||||
#if not defined(__SWITCH__) && not defined(__WIIU__)
|
||||
CheckAndCreateModFolder();
|
||||
#endif
|
||||
|
||||
while (!extractDone) {
|
||||
if (SohGui::PopupsQueued() > 0 || extractionTask.has_value()) {
|
||||
goto render;
|
||||
@@ -457,7 +462,7 @@ void OTRGlobals::RunExtract(int argc, char* argv[]) {
|
||||
#elif (defined(__WIIU__) || defined(__SWITCH__))
|
||||
extractStep = ES_VERIFY;
|
||||
#else
|
||||
extractStep = ES_EXTRACT;
|
||||
extractStep = args.empty() ? ES_EXTRACT : ES_EXTRACT_ARGS;
|
||||
#endif
|
||||
} else {
|
||||
std::string msg;
|
||||
@@ -542,11 +547,7 @@ void OTRGlobals::RunExtract(int argc, char* argv[]) {
|
||||
"OK", "", [&]() { exit(0); });
|
||||
} else {
|
||||
windowsStep = WS_DONE;
|
||||
if (args.size() > 0) {
|
||||
extractStep = ES_EXTRACT_ARGS;
|
||||
} else {
|
||||
extractStep = ES_EXTRACT;
|
||||
}
|
||||
extractStep = args.empty() ? ES_EXTRACT : ES_EXTRACT_ARGS;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -557,7 +558,7 @@ void OTRGlobals::RunExtract(int argc, char* argv[]) {
|
||||
}
|
||||
case ES_EXTRACT_ARGS: {
|
||||
#if !defined(__SWITCH__) && !defined(__WIIU__)
|
||||
if (args.size() == 0) {
|
||||
if (args.empty()) {
|
||||
SohGui::RegisterPopup(
|
||||
"Run Ship of Harkinian", "All files have been processed. Run SoH?", "Yes", "No",
|
||||
[&]() {
|
||||
@@ -760,10 +761,6 @@ void OTRGlobals::RunExtract(int argc, char* argv[]) {
|
||||
#elif defined(__WIIU__)
|
||||
Ship::WiiU::Init(appShortName);
|
||||
#endif
|
||||
|
||||
#if not defined(__SWITCH__) && not defined(__WIIU__)
|
||||
CheckAndCreateModFolder();
|
||||
#endif
|
||||
}
|
||||
|
||||
void OTRGlobals::Initialize() {
|
||||
@@ -2539,6 +2536,7 @@ bool SoH_HandleConfigDrop(char* filePath) {
|
||||
->ClearBindings();
|
||||
|
||||
Rando::Settings::GetInstance()->UpdateAllOptions();
|
||||
SohGui::MarkRandomizerMenusDirty();
|
||||
gui->SaveConsoleVariablesNextFrame();
|
||||
ShipInit::Init("*");
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ static std::map<int32_t, const char*> languages = {
|
||||
};
|
||||
void UpdateMenuTricks();
|
||||
void UpdateMenuLocations();
|
||||
void MarkRandomizerMenusDirty();
|
||||
|
||||
class SohMenu : public Ship::Menu {
|
||||
public:
|
||||
|
||||
@@ -491,16 +491,18 @@ void SohMenu::AddMenuEnhancements() {
|
||||
AddWidget(path, "Skip Tower Escape", WIDGET_CVAR_CHECKBOX)
|
||||
.CVar(CVAR_ENHANCEMENT("TimeSavers.SkipTowerEscape"))
|
||||
.Options(CheckboxOptions().Tooltip("Skip the tower escape sequence between Ganondorf and Ganon."));
|
||||
AddWidget(path, "Skip Scarecrow's Song", WIDGET_CVAR_CHECKBOX)
|
||||
AddWidget(path, "Skip Playing Scarecrow's Song", WIDGET_CVAR_CHECKBOX)
|
||||
.CVar(CVAR_ENHANCEMENT("InstantScarecrow"))
|
||||
.PreFunc([](WidgetInfo& info) {
|
||||
info.options->disabled =
|
||||
IS_RANDO && OTRGlobals::Instance->gRandoContext->GetOption(RSK_SKIP_SCARECROWS_SONG);
|
||||
info.options->disabledTooltip = "This setting is forcefully enabled because a randomized "
|
||||
"save file with the option \"Skip Scarecrow Song\" is currently loaded.";
|
||||
info.options->disabledTooltip = "This setting is forcefully enabled because a randomized save "
|
||||
"file with the option \"Skip Scarecrow's Song\" is currently loaded.";
|
||||
})
|
||||
.Options(CheckboxOptions().Tooltip(
|
||||
"Pierre appears when an Ocarina is pulled out. Requires learning the Scarecrow's Song first."));
|
||||
"Pierre appears when an Ocarina is pulled out. Requires learning the Scarecrow's Song first.\n"
|
||||
"Without the randomizer option \"Skip Scarecrow's Song\" enabled for a seed, this still requires you "
|
||||
"to teach the scarecrow the song as both ages before summoning."));
|
||||
AddWidget(path, "Faster Rupee Accumulator", WIDGET_CVAR_CHECKBOX)
|
||||
.CVar(CVAR_ENHANCEMENT("FasterRupeeAccumulator"))
|
||||
.Options(CheckboxOptions().Tooltip("Causes your Wallet to fill and empty faster when you gain or lose money."));
|
||||
@@ -1799,7 +1801,7 @@ void SohMenu::AddMenuEnhancements() {
|
||||
.CVar(CVAR_CHEAT("SpeedModifier.DoesntChangeJump"));
|
||||
AddWidget(path, "Multiplier:", WIDGET_CVAR_SLIDER_FLOAT)
|
||||
.CVar(CVAR_CHEAT("SpeedModifier.Value"))
|
||||
.Options(FloatSliderOptions().IsPercentage().Min(1.0f).Max(5.0f).DefaultValue(1.0f).ShowButtons(true).Format(
|
||||
.Options(FloatSliderOptions().IsPercentage().Min(0.01f).Max(5.0f).DefaultValue(1.0f).ShowButtons(true).Format(
|
||||
"%.0f%%"));
|
||||
AddWidget(path, "Button Combination:", WIDGET_CVAR_BTN_SELECTOR)
|
||||
.CVar(CVAR_CHEAT("SpeedModifier.Btn"))
|
||||
|
||||
@@ -181,6 +181,11 @@ void DrawLocationsMenu(WidgetInfo& info) {
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
void MarkRandomizerMenusDirty() {
|
||||
locationsDirty = true;
|
||||
tricksDirty = true;
|
||||
}
|
||||
|
||||
void UpdateMenuLocations() {
|
||||
RandomizerCheckObjects::UpdateImGuiVisibility();
|
||||
// todo: this efficiently when we build out cvar array support
|
||||
|
||||
@@ -1250,16 +1250,7 @@ bool CVarBtnSelector(const char* label, const char* cvarName, const BtnSelectorO
|
||||
}
|
||||
} // namespace UIWidgets
|
||||
|
||||
ImVec4 GetRandomValue() {
|
||||
ImVec4 NewColor;
|
||||
NewColor.x = (float)ShipUtils::RandomDouble();
|
||||
NewColor.y = (float)ShipUtils::RandomDouble();
|
||||
NewColor.z = (float)ShipUtils::RandomDouble();
|
||||
return NewColor;
|
||||
}
|
||||
|
||||
ImVec4 GetRandomValue(uint32_t seed, uint64_t* state) {
|
||||
ShipUtils::RandInit(seed, state);
|
||||
ImVec4 GetRandomValue(uint64_t* state) {
|
||||
ImVec4 NewColor;
|
||||
NewColor.x = (float)ShipUtils::RandomDouble(state);
|
||||
NewColor.y = (float)ShipUtils::RandomDouble(state);
|
||||
|
||||
@@ -1089,8 +1089,7 @@ void InsertHelpHoverText(const std::string& text);
|
||||
void InsertHelpHoverText(const char* text);
|
||||
} // namespace UIWidgets
|
||||
|
||||
ImVec4 GetRandomValue();
|
||||
ImVec4 GetRandomValue(uint32_t seed, uint64_t* state = nullptr);
|
||||
ImVec4 GetRandomValue(uint64_t* state = nullptr);
|
||||
|
||||
Color_RGBA8 RGBA8FromVec(ImVec4 vec);
|
||||
ImVec4 VecFromRGBA8(Color_RGBA8 color);
|
||||
|
||||
+16
-13
@@ -2032,8 +2032,10 @@ s32 GiveItemEntryWithoutActor(PlayState* play, GetItemEntry getItemEntry) {
|
||||
PLAYER_STATE1_CLIMBING_LEDGE | PLAYER_STATE1_JUMPING | PLAYER_STATE1_FREEFALL | PLAYER_STATE1_FIRST_PERSON |
|
||||
PLAYER_STATE1_CLIMBING_LADDER)) &&
|
||||
Player_GetExplosiveHeld(player) < 0) {
|
||||
if (((player->heldActor != NULL) && ((getItemEntry.getItemId > GI_NONE) && (getItemEntry.getItemId < GI_MAX)) ||
|
||||
(IS_RANDO && (getItemEntry.getItemId > RG_NONE) && (getItemEntry.getItemId < RG_MAX))) ||
|
||||
if (((player->heldActor != NULL && (getItemEntry.modIndex == MOD_NONE && getItemEntry.getItemId > GI_NONE &&
|
||||
getItemEntry.getItemId < GI_MAX)) ||
|
||||
(getItemEntry.modIndex == MOD_RANDOMIZER && getItemEntry.getItemId > RG_NONE &&
|
||||
getItemEntry.getItemId < RG_MAX)) ||
|
||||
(!(player->stateFlags1 & (PLAYER_STATE1_CARRYING_ACTOR | PLAYER_STATE1_IN_CUTSCENE)))) {
|
||||
if ((getItemEntry.getItemId != GI_NONE)) {
|
||||
player->getItemEntry = getItemEntry;
|
||||
@@ -2073,8 +2075,10 @@ s32 GiveItemEntryFromActor(Actor* actor, PlayState* play, GetItemEntry getItemEn
|
||||
PLAYER_STATE1_CLIMBING_LADDER)) &&
|
||||
Player_GetExplosiveHeld(player) < 0) {
|
||||
if ((((player->heldActor != NULL) || (actor == player->talkActor)) &&
|
||||
((!IS_RANDO && ((getItemEntry.getItemId > GI_NONE) && (getItemEntry.getItemId < GI_MAX))) ||
|
||||
(IS_RANDO && ((getItemEntry.getItemId > RG_NONE) && (getItemEntry.getItemId < RG_MAX))))) ||
|
||||
((getItemEntry.getItemId == MOD_NONE &&
|
||||
((getItemEntry.getItemId > GI_NONE) && (getItemEntry.getItemId < GI_MAX))) ||
|
||||
(getItemEntry.getItemId == MOD_RANDOMIZER &&
|
||||
((getItemEntry.getItemId > RG_NONE) && (getItemEntry.getItemId < RG_MAX))))) ||
|
||||
(!(player->stateFlags1 & (PLAYER_STATE1_CARRYING_ACTOR | PLAYER_STATE1_IN_CUTSCENE)))) {
|
||||
if ((actor->xzDistToPlayer < xzRange) && (fabsf(actor->yDistToPlayer) < yRange)) {
|
||||
s16 yawDiff = actor->yawTowardsPlayer - player->actor.shape.rot.y;
|
||||
@@ -2118,8 +2122,7 @@ s32 Actor_OfferGetItem(Actor* actor, PlayState* play, s32 getItemId, f32 xzRange
|
||||
PLAYER_STATE1_CLIMBING_LADDER)) &&
|
||||
Player_GetExplosiveHeld(player) < 0) {
|
||||
if ((((player->heldActor != NULL) || (actor == player->talkActor)) &&
|
||||
((!IS_RANDO && ((getItemId > GI_NONE) && (getItemId < GI_MAX))) ||
|
||||
(IS_RANDO && ((getItemId > RG_NONE) && (getItemId < RG_MAX))))) ||
|
||||
((getItemId > GI_NONE) && (getItemId < GI_MAX))) ||
|
||||
(!(player->stateFlags1 & (PLAYER_STATE1_CARRYING_ACTOR | PLAYER_STATE1_IN_CUTSCENE)))) {
|
||||
if ((actor->xzDistToPlayer < xzRange) && (fabsf(actor->yDistToPlayer) < yRange)) {
|
||||
s16 yawDiff = actor->yawTowardsPlayer - player->actor.shape.rot.y;
|
||||
@@ -3471,7 +3474,7 @@ Actor* Actor_SpawnEntry(ActorContext* actorCtx, ActorEntry* actorEntry, PlayStat
|
||||
gMapLoading = 1;
|
||||
Actor* ret;
|
||||
|
||||
if (GameInteractor_Should(VB_SPAWN_ACTOR_ENTRY, true, actorCtx, actorEntry, play, ret)) {
|
||||
if (GameInteractor_Should(VB_SPAWN_ACTOR_ENTRY, true, actorCtx, actorEntry, play, &ret)) {
|
||||
ret = Actor_Spawn(actorCtx, play, actorEntry->id, actorEntry->pos.x, actorEntry->pos.y, actorEntry->pos.z,
|
||||
actorEntry->rot.x, actorEntry->rot.y, actorEntry->rot.z, actorEntry->params);
|
||||
}
|
||||
@@ -3491,12 +3494,6 @@ Actor* Actor_Delete(ActorContext* actorCtx, Actor* actor, PlayState* play) {
|
||||
// Execute before actor memory is freed
|
||||
GameInteractor_ExecuteOnActorDestroy(actor);
|
||||
|
||||
dbEntry = ActorDB_Retrieve(actor->id);
|
||||
|
||||
if (HREG(20) != 0) {
|
||||
osSyncPrintf("アクタークラス削除 [%s]\n", dbEntry->name); // "Actor class deleted [%s]"
|
||||
}
|
||||
|
||||
if ((player != NULL) && (actor == player->focusActor)) {
|
||||
Player_ReleaseLockOn(player);
|
||||
Camera_ChangeMode(Play_GetCamera(play, Play_GetActiveCamId(play)), 0);
|
||||
@@ -3517,6 +3514,12 @@ Actor* Actor_Delete(ActorContext* actorCtx, Actor* actor, PlayState* play) {
|
||||
Audio_StopSfxByPos(&actor->projectedPos);
|
||||
Actor_Destroy(actor, play);
|
||||
|
||||
dbEntry = ActorDB_Retrieve(actor->id);
|
||||
|
||||
if (HREG(20) != 0) {
|
||||
osSyncPrintf("アクタークラス削除 [%s]\n", dbEntry->name); // "Actor class deleted [%s]"
|
||||
}
|
||||
|
||||
newHead = Actor_RemoveFromCategory(play, actorCtx, actor);
|
||||
|
||||
// #region SOH [ObjectExtension]
|
||||
|
||||
+16
-11
@@ -3,7 +3,6 @@
|
||||
#include "objects/gameplay_field_keep/gameplay_field_keep.h"
|
||||
#include "objects/object_link_boy/object_link_boy.h"
|
||||
#include "objects/object_link_child/object_link_child.h"
|
||||
#include "objects/object_triforce_spot/object_triforce_spot.h"
|
||||
#include "overlays/actors/ovl_Demo_Effect/z_demo_effect.h"
|
||||
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
@@ -1317,12 +1316,14 @@ s32 Player_OverrideLimbDrawGameplayCommon(PlayState* play, s32 limbIndex, Gfx**
|
||||
if (limbIndex == PLAYER_LIMB_HEAD) {
|
||||
if (CVarGetInteger(CVAR_COSMETIC("Link.HeadScale.Changed"), 0)) {
|
||||
f32 scale = CVarGetFloat(CVAR_COSMETIC("Link.HeadScale.Value"), 1.0f);
|
||||
Matrix_Scale(scale, scale, scale, MTXMODE_APPLY);
|
||||
if (scale > 1.2f) {
|
||||
Matrix_Translate(-((LINK_IS_ADULT ? 320.0f : 200.0f) * scale), 0.0f, 0.0f, MTXMODE_APPLY);
|
||||
} else if (scale < 1.0f) {
|
||||
Matrix_Translate((LINK_IS_ADULT ? 3600.0f : 2900.0f) * ABS(scale - 1.0f), 0.0f, 0.0f,
|
||||
MTXMODE_APPLY);
|
||||
if (scale != 1.0f) {
|
||||
Matrix_Scale(scale, scale, scale, MTXMODE_APPLY);
|
||||
if (scale > 1.2f) {
|
||||
Matrix_Translate(-((LINK_IS_ADULT ? 320.0f : 200.0f) * scale), 0.0f, 0.0f, MTXMODE_APPLY);
|
||||
} else if (scale < 1.0f) {
|
||||
Matrix_Translate((LINK_IS_ADULT ? 3600.0f : 2900.0f) * ABS(scale - 1.0f), 0.0f, 0.0f,
|
||||
MTXMODE_APPLY);
|
||||
}
|
||||
}
|
||||
}
|
||||
rot->x += this->headLimbRot.z;
|
||||
@@ -1331,8 +1332,10 @@ s32 Player_OverrideLimbDrawGameplayCommon(PlayState* play, s32 limbIndex, Gfx**
|
||||
} else if (limbIndex == PLAYER_LIMB_L_HAND) {
|
||||
if (CVarGetInteger(CVAR_COSMETIC("Link.SwordScale.Changed"), 0)) {
|
||||
f32 scale = CVarGetFloat(CVAR_COSMETIC("Link.SwordScale.Value"), 1.0f);
|
||||
Matrix_Scale(scale, scale, scale, MTXMODE_APPLY);
|
||||
Matrix_Translate(-((LINK_IS_ADULT ? 320.0f : 200.0f) * scale), 0.0f, 0.0f, MTXMODE_APPLY);
|
||||
if (scale != 1.0f) {
|
||||
Matrix_Scale(scale, scale, scale, MTXMODE_APPLY);
|
||||
Matrix_Translate(-((LINK_IS_ADULT ? 320.0f : 200.0f) * (scale - 1.0f)), 0.0f, 0.0f, MTXMODE_APPLY);
|
||||
}
|
||||
}
|
||||
} else if (limbIndex == PLAYER_LIMB_UPPER) {
|
||||
if (this->upperLimbYawSecondary != 0) {
|
||||
@@ -1906,8 +1909,10 @@ void Player_PostLimbDrawGameplay(PlayState* play, s32 limbIndex, Gfx** dList, Ve
|
||||
}
|
||||
|
||||
if (this->actor.scale.y >= 0.0f) {
|
||||
if (GameInteractor_Should(VB_DRAW_ADDITIONAL_RETICLES, (this->heldItemAction == PLAYER_IA_HOOKSHOT) ||
|
||||
(this->heldItemAction == PLAYER_IA_LONGSHOT))) {
|
||||
if (GameInteractor_Should(VB_DRAW_ADDITIONAL_RETICLES,
|
||||
(this->heldItemAction == PLAYER_IA_HOOKSHOT) ||
|
||||
(this->heldItemAction == PLAYER_IA_LONGSHOT),
|
||||
this)) {
|
||||
Matrix_MultVec3f(&D_80126184, &this->unk_3C8);
|
||||
|
||||
if (heldActor != NULL) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "z_bg_spot06_objects.h"
|
||||
#include "objects/object_spot06_objects/object_spot06_objects.h"
|
||||
#include "soh/Enhancements/custom-message/CustomMessageTypes.h"
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
|
||||
|
||||
#define FLAGS ACTOR_FLAG_HOOKSHOT_PULLS_ACTOR
|
||||
|
||||
@@ -266,7 +267,7 @@ void BgSpot06Objects_GateWaitForSwitch(BgSpot06Objects* this, PlayState* play) {
|
||||
* This is where the gate waits a few frames before rising after the switch is set.
|
||||
*/
|
||||
void BgSpot06Objects_GateWaitToOpen(BgSpot06Objects* this, PlayState* play) {
|
||||
if (this->timer != 0) {
|
||||
if (GameInteractor_Should(VB_BG_SPOT06_OBJECTS_GATE_SKIP, this->timer != 0, this)) {
|
||||
this->timer--;
|
||||
}
|
||||
|
||||
@@ -604,7 +605,7 @@ void BgSpot06Objects_WaterPlaneCutsceneRise(BgSpot06Objects* this, PlayState* pl
|
||||
play->roomCtx.unk_74[0] = 0; // Apply the moving under water texture to lake hylia ground
|
||||
}
|
||||
} else {
|
||||
Math_SmoothStepToF(&this->lakeHyliaWaterLevel, 1.0f, 0.1f, 1.0f, 0.001f);
|
||||
Math_SmoothStepToF(&this->lakeHyliaWaterLevel, 1.0f, 0.1f, IS_RANDO ? 10.0f : 1.0f, 0.001f);
|
||||
play->colCtx.colHeader->waterBoxes[LHWB_GERUDO_VALLEY_RIVER_LOWER].ySurface = WATER_LEVEL_RIVER_LOWERED;
|
||||
play->colCtx.colHeader->waterBoxes[LHWB_MAIN_1].ySurface = this->dyna.actor.world.pos.y;
|
||||
play->colCtx.colHeader->waterBoxes[LHWB_MAIN_2].ySurface = this->dyna.actor.world.pos.y;
|
||||
@@ -634,7 +635,7 @@ void BgSpot06Objects_WaterPlaneCutsceneLower(BgSpot06Objects* this, PlayState* p
|
||||
this->actionFunc = BgSpot06Objects_DoNothing;
|
||||
} else {
|
||||
// Go slightly beyond -681 so the smoothing doesn't slow down too much (matches the reverse of water rise func)
|
||||
Math_SmoothStepToF(&this->lakeHyliaWaterLevel, -682.0f, 0.1f, 1.0f, 0.001f);
|
||||
Math_SmoothStepToF(&this->lakeHyliaWaterLevel, -682.0f, 0.1f, 10.0f, 0.01f);
|
||||
play->colCtx.colHeader->waterBoxes[LHWB_GERUDO_VALLEY_RIVER_LOWER].ySurface = WATER_LEVEL_RIVER_LOWERED;
|
||||
play->colCtx.colHeader->waterBoxes[LHWB_GERUDO_VALLEY_RIVER_LOWER].zMin = WATER_LEVEL_RIVER_LOWER_Z - 50;
|
||||
play->colCtx.colHeader->waterBoxes[LHWB_MAIN_1].ySurface = yPos;
|
||||
|
||||
@@ -4246,7 +4246,7 @@ void BossTw_BlastIce(BossTw* this, PlayState* play) {
|
||||
Math_ApproachF(&sKotakePtr->workf[UNK_F9], 80.0f, 1.0f, 3.0f);
|
||||
Math_ApproachF(&sKotakePtr->workf[UNK_F11], 255.0f, 1.0f, 10.0f);
|
||||
Math_ApproachF(&sKotakePtr->workf[UNK_F12], 0.04f, 0.1f, 0.002f);
|
||||
Math_ApproachF(&sKotakePtr->workf[UNK_F16], 70.0f, 1.0f, 5.0f);
|
||||
Math_ApproachF(&sKotakePtr->workf[UNK_F16], 70.0f, 1.0f, -5.0f);
|
||||
|
||||
if ((this->timers[0] == 70) || (this->timers[0] == 30)) {
|
||||
sKotakePtr->workf[UNK_F16] = 10.0f;
|
||||
@@ -5037,9 +5037,8 @@ void BossTw_DrawEffects(PlayState* play) {
|
||||
if (sp18F == 0) {
|
||||
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(gTwinrovaIceSurroundingPlayerMaterialDL));
|
||||
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 195, 225, 235, 255);
|
||||
gSPSegment(
|
||||
POLY_XLU_DISP++, 8,
|
||||
Gfx_TwoTexScrollEx(play->state.gfxCtx, 0, 0, 0, 0x20, 0x40, 1, 0, 0, 0x20, 0x20, 0, 0, 0, 0));
|
||||
gSPSegment(POLY_XLU_DISP++, 8,
|
||||
Gfx_TwoTexScroll(play->state.gfxCtx, 0, 0, 0, 0x20, 0x40, 1, 0, 0, 0x20, 0x20));
|
||||
sp18F++;
|
||||
BossTw_InitRand(1, 0x71AC, 0x263A);
|
||||
}
|
||||
@@ -5091,9 +5090,8 @@ void BossTw_DrawEffects(PlayState* play) {
|
||||
}
|
||||
|
||||
gSPSegment(POLY_XLU_DISP++, 8,
|
||||
Gfx_TwoTexScrollEx(play->state.gfxCtx, 0, (currentEffect->frame * 3) & 0x7F,
|
||||
(-currentEffect->frame * 15) & 0xFF, 0x20, 0x40, 1, 0, 0, 0x20, 0x20, 3, -15,
|
||||
0, 0));
|
||||
Gfx_TwoTexScroll(play->state.gfxCtx, 0, (currentEffect->frame * 3) & 0x7F,
|
||||
(-currentEffect->frame * 15) & 0xFF, 0x20, 0x40, 1, 0, 0, 0x20, 0x20));
|
||||
Matrix_Translate(currentEffect->pos.x, currentEffect->pos.y, currentEffect->pos.z, MTXMODE_NEW);
|
||||
Matrix_ReplaceRotation(&play->billboardMtxF);
|
||||
Matrix_Scale(currentEffect->workf[EFF_SCALE], currentEffect->workf[EFF_SCALE], 1.0f, MTXMODE_APPLY);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "soh/OTRGlobals.h"
|
||||
#include "soh/ResourceManagerHelpers.h"
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
|
||||
#include "soh/Enhancements/randomizer/item_category_adj.h"
|
||||
|
||||
#define FLAGS 0
|
||||
|
||||
@@ -580,21 +581,7 @@ void EnBox_UpdateTexture(EnBox* this, PlayState* play) {
|
||||
this->dyna.actor.room != 6); // Exclude treasure game chests except for the final room
|
||||
|
||||
if (!isVanilla) {
|
||||
getItemCategory = chestItem.getItemCategory;
|
||||
// If they have bombchus, don't consider the bombchu item major
|
||||
if ((INV_CONTENT(ITEM_BOMBCHU) == ITEM_BOMBCHU &&
|
||||
((chestItem.modIndex == MOD_RANDOMIZER && chestItem.getItemId == RG_PROGRESSIVE_BOMBCHU_BAG) ||
|
||||
(chestItem.modIndex == MOD_NONE &&
|
||||
(chestItem.getItemId == GI_BOMBCHUS_5 || chestItem.getItemId == GI_BOMBCHUS_10 ||
|
||||
chestItem.getItemId == GI_BOMBCHUS_20)))) ||
|
||||
// If it's a bottle and they already have one, consider the item lesser
|
||||
((chestItem.modIndex == MOD_RANDOMIZER && chestItem.getItemId >= RG_BOTTLE_WITH_RED_POTION &&
|
||||
chestItem.getItemId <= RG_BOTTLE_WITH_POE) ||
|
||||
(chestItem.modIndex == MOD_NONE &&
|
||||
(chestItem.getItemId == GI_BOTTLE || chestItem.getItemId == GI_MILK_BOTTLE)) &&
|
||||
gSaveContext.inventory.items[SLOT_BOTTLE_1] != ITEM_NONE)) {
|
||||
getItemCategory = ITEM_CATEGORY_LESSER;
|
||||
}
|
||||
getItemCategory = Randomizer_AdjustItemCategory(chestItem);
|
||||
}
|
||||
|
||||
switch (this->type) {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "objects/object_fz/object_fz.h"
|
||||
#include "soh/frame_interpolation.h"
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
|
||||
#include "soh/ObjectExtension/ActorMaximumHealth.h"
|
||||
|
||||
#define FLAGS \
|
||||
(ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \
|
||||
@@ -887,8 +886,8 @@ void EnFz_DrawIceSmoke(EnFz* this, PlayState* play) {
|
||||
|
||||
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 195, 225, 235, iceSmoke->primAlpha);
|
||||
gSPSegment(POLY_XLU_DISP++, 0x08,
|
||||
Gfx_TwoTexScrollEx(play->state.gfxCtx, 0, 3 * (iceSmoke->timer + (3 * i)),
|
||||
15 * (iceSmoke->timer + (3 * i)), 32, 64, 1, 0, 0, 32, 32, 3, 15, 0, 0));
|
||||
Gfx_TwoTexScroll(play->state.gfxCtx, 0, 3 * (iceSmoke->timer + (3 * i)),
|
||||
15 * (iceSmoke->timer + (3 * i)), 32, 64, 1, 0, 0, 32, 32));
|
||||
Matrix_Translate(iceSmoke->pos.x, iceSmoke->pos.y, iceSmoke->pos.z, MTXMODE_NEW);
|
||||
Matrix_ReplaceRotation(&play->billboardMtxF);
|
||||
Matrix_Scale(iceSmoke->xyScale, iceSmoke->xyScale, 1.0f, MTXMODE_APPLY);
|
||||
|
||||
@@ -1183,7 +1183,7 @@ void func_80A99048(EnKo* this, PlayState* play) {
|
||||
if (ENKO_TYPE == ENKO_TYPE_CHILD_5) {
|
||||
this->collider.base.ocFlags1 |= 0x40;
|
||||
}
|
||||
if (GameInteractor_Should(VB_KOKIRI_GET_FOREST_QUEST_STATE2, false, this)) {
|
||||
if (GameInteractor_Should(VB_KOKIRI_GET_FOREST_QUEST_STATE2, true, this)) {
|
||||
this->forestQuestState = EnKo_GetForestQuestState2(this);
|
||||
}
|
||||
Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, sOsAnimeLookup[ENKO_TYPE][this->forestQuestState]);
|
||||
|
||||
@@ -150,7 +150,9 @@ void EnMs_Talk(EnMs* this, PlayState* play) {
|
||||
|
||||
void EnMs_Sell(EnMs* this, PlayState* play) {
|
||||
if (Actor_HasParent(&this->actor, play)) {
|
||||
Rupees_ChangeBy(-sPrices[BEANS_BOUGHT]);
|
||||
if (GameInteractor_Should(VB_MAGIC_BEAN_SALESMAN_TAKE_MONEY, true, this)) {
|
||||
Rupees_ChangeBy(-sPrices[BEANS_BOUGHT]);
|
||||
}
|
||||
this->actor.parent = NULL;
|
||||
this->actionFunc = EnMs_TalkAfterPurchase;
|
||||
} else {
|
||||
|
||||
@@ -298,17 +298,19 @@ void EnPeehat_HitWhenGrounded(EnPeehat* this, PlayState* play) {
|
||||
s32 i;
|
||||
|
||||
this->colCylinder.base.acFlags &= ~AC_HIT;
|
||||
for (i = MAX_LARVA - this->unk_2FA; i > 0; i--) {
|
||||
Actor* larva =
|
||||
Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_EN_PEEHAT,
|
||||
Rand_CenteredFloat(25.0f) + this->actor.world.pos.x,
|
||||
Rand_CenteredFloat(25.0f) + (this->actor.world.pos.y + 50.0f),
|
||||
Rand_CenteredFloat(25.0f) + this->actor.world.pos.z, 0, 0, 0, PEAHAT_TYPE_LARVA);
|
||||
if (GameInteractor_Should(VB_PEEHAT_SPAWN_LARVAS, true, this, play)) {
|
||||
for (i = MAX_LARVA - this->unk_2FA; i > 0; i--) {
|
||||
Actor* larva =
|
||||
Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_EN_PEEHAT,
|
||||
Rand_CenteredFloat(25.0f) + this->actor.world.pos.x,
|
||||
Rand_CenteredFloat(25.0f) + (this->actor.world.pos.y + 50.0f),
|
||||
Rand_CenteredFloat(25.0f) + this->actor.world.pos.z, 0, 0, 0, PEAHAT_TYPE_LARVA);
|
||||
|
||||
if (larva != NULL) {
|
||||
larva->velocity.y = 6.0f;
|
||||
larva->shape.rot.y = larva->world.rot.y = Rand_CenteredFloat(0xFFFF);
|
||||
this->unk_2FA++;
|
||||
if (larva != NULL) {
|
||||
larva->velocity.y = 6.0f;
|
||||
larva->shape.rot.y = larva->world.rot.y = Rand_CenteredFloat(0xFFFF);
|
||||
this->unk_2FA++;
|
||||
}
|
||||
}
|
||||
}
|
||||
this->unk_2D4 = 8;
|
||||
|
||||
@@ -432,6 +432,8 @@ static FishingEffect sFishingEffects[FISHING_EFFECT_COUNT];
|
||||
static Vec3f sStreamSoundProjectedPos;
|
||||
static s16 sFishOnHandParams;
|
||||
|
||||
f32 Fishing_GetMinimumRequiredScore();
|
||||
|
||||
u8 AllHyruleLoaches() {
|
||||
return CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0) &&
|
||||
CVarGetInteger(CVAR_ENHANCEMENT("AllHyruleLoaches"), 0);
|
||||
@@ -904,12 +906,16 @@ void Fishing_Init(Actor* thisx, PlayState* play2) {
|
||||
if (sLinkAge == LINK_AGE_CHILD) {
|
||||
if ((HIGH_SCORE(HS_FISHING) & HS_FISH_LENGTH_CHILD) != 0) {
|
||||
sFishingRecordLength = HIGH_SCORE(HS_FISHING) & HS_FISH_LENGTH_CHILD;
|
||||
} else if (CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0)) {
|
||||
sFishingRecordLength = Fishing_GetMinimumRequiredScore();
|
||||
} else {
|
||||
sFishingRecordLength = 40.0f; // 6 lbs
|
||||
}
|
||||
} else {
|
||||
if ((HIGH_SCORE(HS_FISHING) & HS_FISH_LENGTH_ADULT) != 0) {
|
||||
sFishingRecordLength = (HIGH_SCORE(HS_FISHING) & HS_FISH_LENGTH_ADULT) >> 0x18;
|
||||
} else if (CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0)) {
|
||||
sFishingRecordLength = Fishing_GetMinimumRequiredScore();
|
||||
} else {
|
||||
sFishingRecordLength = 45.0f; // 7 lbs
|
||||
}
|
||||
|
||||
@@ -551,7 +551,9 @@ void ObjBean_WaitForBean(ObjBean* this, PlayState* play) {
|
||||
void func_80B8FE00(ObjBean* this) {
|
||||
this->actionFunc = func_80B8FE3C;
|
||||
ObjBean_SetDrawMode(this, BEAN_STATE_DRAW_LEAVES);
|
||||
this->timer = 60;
|
||||
if (GameInteractor_Should(VB_PLAY_BEAN_PLANTING_CS, true)) {
|
||||
this->timer = 60;
|
||||
}
|
||||
}
|
||||
|
||||
// Link is looking at the soft soil
|
||||
|
||||
@@ -256,9 +256,8 @@ void ObjSwitch_SetOn(ObjSwitch* this, PlayState* play) {
|
||||
} else {
|
||||
OnePointCutscene_AttentionSetSfx(play, &this->dyna.actor, NA_SE_SY_TRE_BOX_APPEAR);
|
||||
}
|
||||
this->cooldownOn = true;
|
||||
}
|
||||
|
||||
this->cooldownOn = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,8 +270,8 @@ void ObjSwitch_SetOff(ObjSwitch* this, PlayState* play) {
|
||||
if ((this->dyna.actor.params >> 4 & 7) == 1) {
|
||||
if (GameInteractor_Should(VB_PLAY_ONEPOINT_ACTOR_CS, true, this)) {
|
||||
OnePointCutscene_AttentionSetSfx(play, &this->dyna.actor, NA_SE_SY_TRE_BOX_APPEAR);
|
||||
this->cooldownOn = true;
|
||||
}
|
||||
this->cooldownOn = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user