Merge pull request #6446 from HarbourMasters/develop-ackbar

merge develop-ackbar into develop
This commit is contained in:
Philip Dubé
2026-03-30 06:42:08 +00:00
committed by GitHub
23 changed files with 126 additions and 97 deletions

View File

@@ -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);

View File

@@ -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");

View File

@@ -225,6 +225,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 {

View File

@@ -1371,6 +1371,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)

View File

@@ -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();

View File

@@ -49,9 +49,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",

View File

@@ -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) {

View File

@@ -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:

View File

@@ -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:

View File

@@ -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;

View File

@@ -980,6 +980,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;
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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()),

View File

@@ -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()),

View File

@@ -50,10 +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 with most weapons
// Bow extesnion is possible, but very precise: X = 403, Z = 2062-3, Rot = -11475, needs a setup and is its own trick
// 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),

View File

@@ -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()),
}, {

View File

@@ -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()),

View File

@@ -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)),

View File

@@ -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),

View File

@@ -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 ||

View File

@@ -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) {

View File

@@ -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 {