Files
Shipwright/soh/soh/Enhancements/randomizer/randomizer_check_objects.cpp
T
A Green Spoon 49e740b6f2 [Rando] Shuffle Icicles and Red Ice (#6462)
Separate options for Icicles (stalagmites and stalactites) and Red Ice. Icicles drop an item when broken and red ice gives an item when melted.

CMC options for icicles were more limited - the particle effect strobes because there are too many icicles in certain rooms. This currently uses Dampe's halo centered around the tips, which are visible for both types, but model replacement could be used here if a set was made.
2026-04-08 06:02:33 +00:00

283 lines
18 KiB
C++

#include "randomizer_check_objects.h"
#include "static_data.h"
#include "SeedContext.h"
#include <map>
#include <string>
#include <libultraship/bridge.h>
#include "z64.h"
#include "soh/OTRGlobals.h"
#include "soh/cvar_prefixes.h"
#include "fishsanity.h"
std::map<RandomizerCheckArea, std::string> rcAreaNames = {
{ RCAREA_KOKIRI_FOREST, "Kokiri Forest" },
{ RCAREA_LOST_WOODS, "Lost Woods" },
{ RCAREA_SACRED_FOREST_MEADOW, "Sacred Forest Meadow" },
{ RCAREA_HYRULE_FIELD, "Hyrule Field" },
{ RCAREA_LAKE_HYLIA, "Lake Hylia" },
{ RCAREA_GERUDO_VALLEY, "Gerudo Valley" },
{ RCAREA_GERUDO_FORTRESS, "Gerudo Fortress" },
{ RCAREA_WASTELAND, "Haunted Wasteland" },
{ RCAREA_DESERT_COLOSSUS, "Desert Colossus" },
{ RCAREA_MARKET, "Hyrule Market" },
{ RCAREA_HYRULE_CASTLE, "Hyrule Castle" },
{ RCAREA_KAKARIKO_VILLAGE, "Kakariko Village" },
{ RCAREA_GRAVEYARD, "Graveyard" },
{ RCAREA_DEATH_MOUNTAIN_TRAIL, "Death Mountain Trail" },
{ RCAREA_GORON_CITY, "Goron City" },
{ RCAREA_DEATH_MOUNTAIN_CRATER, "Death Mountain Crater" },
{ RCAREA_ZORAS_RIVER, "Zora's River" },
{ RCAREA_ZORAS_DOMAIN, "Zora's Domain" },
{ RCAREA_ZORAS_FOUNTAIN, "Zora's Fountain" },
{ RCAREA_LON_LON_RANCH, "Lon Lon Ranch" },
{ RCAREA_DEKU_TREE, "Deku Tree" },
{ RCAREA_DODONGOS_CAVERN, "Dodongo's Cavern" },
{ RCAREA_JABU_JABUS_BELLY, "Jabu Jabu's Belly" },
{ RCAREA_FOREST_TEMPLE, "Forest Temple" },
{ RCAREA_FIRE_TEMPLE, "Fire Temple" },
{ RCAREA_WATER_TEMPLE, "Water Temple" },
{ RCAREA_SPIRIT_TEMPLE, "Spirit Temple" },
{ RCAREA_SHADOW_TEMPLE, "Shadow Temple" },
{ RCAREA_BOTTOM_OF_THE_WELL, "Bottom of the Well" },
{ RCAREA_ICE_CAVERN, "Ice Cavern" },
{ RCAREA_GERUDO_TRAINING_GROUND, "Gerudo Training Ground" },
{ RCAREA_GANONS_CASTLE, "Ganon's Castle" },
{ RCAREA_INVALID, "Invalid" },
};
std::map<RandomizerCheckArea, std::vector<RandomizerCheck>> rcObjectsByArea = {};
std::map<RandomizerCheckArea, std::vector<RandomizerCheck>> RandomizerCheckObjects::GetAllRCObjectsByArea() {
if (rcObjectsByArea.size() == 0) {
for (auto& location : Rando::StaticData::GetLocationTable()) {
// There are some RCs that don't have checks implemented yet, prevent adding these to the map.
if (location.GetRandomizerCheck() != RC_UNKNOWN_CHECK) {
rcObjectsByArea[location.GetArea()].push_back(location.GetRandomizerCheck());
}
}
}
return rcObjectsByArea;
}
bool RandomizerCheckObjects::AreaIsDungeon(RandomizerCheckArea area) {
return area == RCAREA_GANONS_CASTLE || area == RCAREA_GERUDO_TRAINING_GROUND || area == RCAREA_ICE_CAVERN ||
area == RCAREA_BOTTOM_OF_THE_WELL || area == RCAREA_SHADOW_TEMPLE || area == RCAREA_SPIRIT_TEMPLE ||
area == RCAREA_WATER_TEMPLE || area == RCAREA_FIRE_TEMPLE || area == RCAREA_FOREST_TEMPLE ||
area == RCAREA_JABU_JABUS_BELLY || area == RCAREA_DODONGOS_CAVERN || area == RCAREA_DEKU_TREE;
}
bool RandomizerCheckObjects::AreaIsOverworld(RandomizerCheckArea area) {
return !AreaIsDungeon(area);
}
std::string RandomizerCheckObjects::GetRCAreaName(RandomizerCheckArea area) {
return rcAreaNames[area];
}
std::map<SceneID, RandomizerCheckArea> rcAreaBySceneID = {};
std::map<SceneID, RandomizerCheckArea> RandomizerCheckObjects::GetAllRCAreaBySceneID() {
// memoize on first request
if (rcAreaBySceneID.size() == 0) {
for (auto& location : Rando::StaticData::GetLocationTable()) {
// There are some RCs that don't have checks implemented yet, prevent adding these to the map.
if (location.GetRandomizerCheck() != RC_UNKNOWN_CHECK) {
rcAreaBySceneID[location.GetScene()] = location.GetArea();
}
}
// Add checkless Hyrule Market areas to the area return
for (int id = (int)SCENE_MARKET_ENTRANCE_DAY; id <= (int)SCENE_MARKET_RUINS; id++) {
rcAreaBySceneID[(SceneID)id] = RCAREA_MARKET;
}
rcAreaBySceneID[SCENE_TEMPLE_OF_TIME] = RCAREA_MARKET;
rcAreaBySceneID[SCENE_CASTLE_COURTYARD_GUARDS_DAY] = RCAREA_HYRULE_CASTLE;
rcAreaBySceneID[SCENE_CASTLE_COURTYARD_GUARDS_NIGHT] = RCAREA_HYRULE_CASTLE;
}
return rcAreaBySceneID;
}
RandomizerCheckArea RandomizerCheckObjects::GetRCAreaBySceneID(SceneID sceneId) {
std::map<SceneID, RandomizerCheckArea> areas = GetAllRCAreaBySceneID();
auto areaIt = areas.find(sceneId);
if (areaIt == areas.end())
return RCAREA_INVALID;
else
return areaIt->second;
}
void RandomizerCheckObjects::UpdateImGuiVisibility() {
auto ctx = Rando::Context::GetInstance();
for (auto& location : Rando::StaticData::GetLocationTable()) {
auto itemLoc = ctx->GetItemLocation(location.GetRandomizerCheck());
itemLoc->SetVisible(
(location.GetRandomizerCheck() != RC_UNKNOWN_CHECK) &&
(location.GetQuest() == RCQUEST_BOTH ||
location.GetQuest() == RCQUEST_MQ &&
((CVarGetInteger(CVAR_RANDOMIZER_SETTING("MQDungeons"), RO_MQ_DUNGEONS_NONE) ==
RO_MQ_DUNGEONS_SET_NUMBER &&
(CVarGetInteger(CVAR_RANDOMIZER_SETTING("MQDungeonCount"), MAX_MQ_DUNGEON_COUNT) >
0) || // at least one MQ dungeon
CVarGetInteger(CVAR_RANDOMIZER_SETTING("MQDungeons"), RO_MQ_DUNGEONS_NONE) ==
RO_MQ_DUNGEONS_RANDOM_NUMBER ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("MQDungeons"), RO_MQ_DUNGEONS_NONE) ==
RO_MQ_DUNGEONS_SELECTION)) ||
location.GetQuest() == RCQUEST_VANILLA &&
(CVarGetInteger(CVAR_RANDOMIZER_SETTING("MQDungeons"), RO_MQ_DUNGEONS_NONE) !=
RO_MQ_DUNGEONS_SET_NUMBER ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("MQDungeonCount"), MAX_MQ_DUNGEON_COUNT) <
MAX_MQ_DUNGEON_COUNT) // at least one vanilla dungeon
) &&
(location.GetRCType() != RCTYPE_SHOP ||
!(ctx->GetOption(RSK_SHOPSANITY).Is(RO_SHOPSANITY_OFF) ||
(ctx->GetOption(RSK_SHOPSANITY).Is(RO_SHOPSANITY_SPECIFIC_COUNT) &&
ctx->GetOption(RSK_SHOPSANITY_COUNT).Is(RO_SHOPSANITY_COUNT_ZERO_ITEMS)))) &&
(location.GetRCType() != RCTYPE_SCRUB ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleScrubs"), RO_SCRUBS_OFF) != RO_SCRUBS_OFF ||
location.GetRandomizerCheck() == RC_HF_DEKU_SCRUB_GROTTO ||
location.GetRandomizerCheck() == RC_LW_DEKU_SCRUB_GROTTO_FRONT ||
location.GetRandomizerCheck() ==
RC_LW_DEKU_SCRUB_NEAR_BRIDGE) && // The 3 scrubs that are always randomized
(location.GetRCType() != RCTYPE_MERCHANT ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleMerchants"), RO_SHUFFLE_MERCHANTS_OFF) !=
RO_SHUFFLE_MERCHANTS_OFF) &&
(location.GetRCType() != RCTYPE_BEGGAR ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleBeggar"), RO_GENERIC_NO)) &&
(location.GetRCType() != RCTYPE_SONG_LOCATION ||
(CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleSongs"), RO_SONG_SHUFFLE_SONG_LOCATIONS) !=
RO_SONG_SHUFFLE_SONG_LOCATIONS &&
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleSongs"), RO_SONG_SHUFFLE_SONG_LOCATIONS) !=
RO_SONG_SHUFFLE_OFF)) && // song locations
((location.GetRCType() != RCTYPE_BOSS_HEART_OR_OTHER_REWARD &&
location.GetRandomizerCheck() != RC_SONG_FROM_IMPA &&
location.GetRandomizerCheck() != RC_SHEIK_IN_ICE_CAVERN) ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleSongs"), RO_SONG_SHUFFLE_SONG_LOCATIONS) !=
RO_SONG_SHUFFLE_DUNGEON_REWARDS) && // song dungeon rewards
(location.GetRCType() != RCTYPE_DUNGEON_REWARD ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleDungeonReward"), RO_DUNGEON_REWARDS_END_OF_DUNGEON) !=
RO_DUNGEON_REWARDS_END_OF_DUNGEON) && // dungeon rewards end of dungeons
(location.GetRCType() != RCTYPE_OCARINA ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleOcarinas"), RO_GENERIC_NO)) && // ocarina locations
(location.GetRandomizerCheck() != RC_HC_ZELDAS_LETTER) && // don't show until we support shuffling letter
(location.GetRCType() !=
RCTYPE_GOSSIP_STONE) && // don't show gossip stones (maybe gossipsanity will be a thing eventually?)
(location.GetRCType() != RCTYPE_STATIC_HINT) && // don't show static hints
(location.GetRCType() != RCTYPE_LINKS_POCKET) && // links pocket can be set to nothing if needed
(location.GetRCType() !=
RCTYPE_CHEST_GAME) && // don't show non final reward chest game checks until we support shuffling them
(location.GetRCType() != RCTYPE_SKULL_TOKEN ||
(CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleTokens"), RO_TOKENSANITY_OFF) == RO_TOKENSANITY_ALL) ||
((CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleTokens"), RO_TOKENSANITY_OFF) ==
RO_TOKENSANITY_OVERWORLD) &&
RandomizerCheckObjects::AreaIsOverworld(location.GetArea())) ||
((CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleTokens"), RO_TOKENSANITY_OFF) ==
RO_TOKENSANITY_DUNGEONS) &&
RandomizerCheckObjects::AreaIsDungeon(location.GetArea()))) &&
(location.GetRCType() != RCTYPE_FREESTANDING ||
(CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleFreestanding"), RO_SHUFFLE_FREESTANDING_OFF) ==
RO_SHUFFLE_FREESTANDING_ALL) ||
((CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleFreestanding"), RO_SHUFFLE_FREESTANDING_OFF) ==
RO_SHUFFLE_FREESTANDING_OVERWORLD) &&
RandomizerCheckObjects::AreaIsOverworld(location.GetArea())) ||
((CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleFreestanding"), RO_SHUFFLE_FREESTANDING_OFF) ==
RO_SHUFFLE_FREESTANDING_DUNGEONS) &&
RandomizerCheckObjects::AreaIsDungeon(location.GetArea()))) &&
(location.GetRCType() != RCTYPE_BEEHIVE ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleBeehives"), RO_GENERIC_NO)) &&
(location.GetRCType() != RCTYPE_COW ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleCows"), RO_GENERIC_NO)) &&
(location.GetRCType() != RCTYPE_POT ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShufflePots"), RO_GENERIC_NO)) &&
(location.GetRCType() != RCTYPE_GRASS ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGrass"), RO_GENERIC_NO)) &&
(location.GetRCType() != RCTYPE_CRATE ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleCrates"), RO_GENERIC_NO)) &&
(location.GetRCType() != RCTYPE_NLCRATE ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleCrates"), RO_GENERIC_NO)) &&
(location.GetRCType() != RCTYPE_SMALL_CRATE ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleCrates"), RO_GENERIC_NO)) &&
(location.GetRCType() != RCTYPE_TREE ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleTrees"), RO_GENERIC_NO)) &&
(location.GetRCType() != RCTYPE_NLTREE ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleTrees"), RO_GENERIC_NO)) &&
(location.GetRCType() != RCTYPE_SIGN ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleSigns"), RO_GENERIC_NO)) &&
(location.GetRCType() != RCTYPE_WONDER_ITEM ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleWonderItems"), RO_GENERIC_NO)) &&
(location.GetRCType() != RCTYPE_ICICLE ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleIcicles"), RO_GENERIC_NO)) &&
(location.GetRCType() != RCTYPE_RED_ICE ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleRedIce"), RO_GENERIC_NO)) &&
(location.GetRCType() != RCTYPE_FISH ||
ctx->GetFishsanity()->GetFishLocationIncluded(&location, FSO_SOURCE_CVARS)) &&
(location.GetRCType() != RCTYPE_ADULT_TRADE ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleAdultTrade"), RO_GENERIC_NO)) &&
(location.GetRandomizerCheck() != RC_KF_KOKIRI_SWORD_CHEST ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleKokiriSword"), RO_GENERIC_NO)) &&
(location.GetRandomizerCheck() != RC_LH_HYRULE_LOACH ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("Fishsanity"), RO_GENERIC_NO) == RO_FISHSANITY_HYRULE_LOACH) &&
(location.GetRandomizerCheck() != RC_ZR_MAGIC_BEAN_SALESMAN ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleMerchants"), RO_SHUFFLE_MERCHANTS_OFF) % 2) &&
(location.GetRandomizerCheck() != RC_HC_MALON_EGG ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleWeirdEgg"), RO_GENERIC_NO)) &&
(location.GetRCType() != RCTYPE_FROG_SONG ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleFrogSongRupees"), RO_GENERIC_NO)) &&
(location.GetRCType() != RCTYPE_FOUNTAIN_FAIRY ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleFountainFairies"), RO_GENERIC_NO)) &&
(location.GetRCType() != RCTYPE_STONE_FAIRY ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleStoneFairies"), RO_GENERIC_NO)) &&
(location.GetRCType() != RCTYPE_BEAN_FAIRY ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleBeanFairies"), RO_GENERIC_NO)) &&
(location.GetRCType() != RCTYPE_SONG_FAIRY ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleFairySpots"), RO_GENERIC_NO)) &&
(location.GetRCType() != RCTYPE_BUTTERFLY_FAIRY ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleButterflyFairies"), RO_GENERIC_NO)) &&
((location.GetRCType() != RCTYPE_MAP && location.GetRCType() != RCTYPE_COMPASS) ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("StartingMapsCompasses"), RO_DUNGEON_ITEM_LOC_OWN_DUNGEON) !=
RO_DUNGEON_ITEM_LOC_VANILLA) &&
(location.GetRCType() != RCTYPE_SMALL_KEY ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("Keysanity"), RO_DUNGEON_ITEM_LOC_OWN_DUNGEON) !=
RO_DUNGEON_ITEM_LOC_VANILLA) &&
(location.GetRCType() != RCTYPE_BOSS_KEY ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("BossKeysanity"), RO_DUNGEON_ITEM_LOC_OWN_DUNGEON) !=
RO_DUNGEON_ITEM_LOC_VANILLA) &&
(location.GetRCType() != RCTYPE_GANON_BOSS_KEY ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), RO_GANON_BOSS_KEY_VANILLA) !=
RO_GANON_BOSS_KEY_VANILLA ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("TriforceHunt"), 0)) &&
(location.GetRandomizerCheck() != RC_TOT_LIGHT_ARROWS_CUTSCENE ||
(CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), RO_GANON_BOSS_KEY_VANILLA) !=
RO_GANON_BOSS_KEY_LACS_DUNGEONS &&
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), RO_GANON_BOSS_KEY_VANILLA) !=
RO_GANON_BOSS_KEY_LACS_MEDALLIONS &&
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), RO_GANON_BOSS_KEY_VANILLA) !=
RO_GANON_BOSS_KEY_LACS_REWARDS &&
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), RO_GANON_BOSS_KEY_VANILLA) !=
RO_GANON_BOSS_KEY_LACS_STONES &&
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), RO_GANON_BOSS_KEY_VANILLA) !=
RO_GANON_BOSS_KEY_LACS_TOKENS &&
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), RO_GANON_BOSS_KEY_VANILLA) !=
RO_GANON_BOSS_KEY_LACS_VANILLA)) && // LACS ganon boss key
(location.GetRandomizerCheck() != RC_KAK_100_GOLD_SKULLTULA_REWARD ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), RO_GANON_BOSS_KEY_VANILLA) !=
RO_GANON_BOSS_KEY_KAK_TOKENS) && // 100 skull reward ganon boss key
(location.GetRCType() != RCTYPE_GF_KEY && location.GetRandomizerCheck() != RC_TH_FREED_CARPENTERS ||
(CVarGetInteger(CVAR_RANDOMIZER_SETTING("FortressCarpenters"), RO_GF_CARPENTERS_NORMAL) ==
RO_GF_CARPENTERS_FREE &&
location.GetRCType() != RCTYPE_GF_KEY && location.GetRandomizerCheck() != RC_TH_FREED_CARPENTERS) ||
(CVarGetInteger(CVAR_RANDOMIZER_SETTING("FortressCarpenters"), RO_GF_CARPENTERS_NORMAL) ==
RO_GF_CARPENTERS_FAST &&
((location.GetRandomizerCheck() == RC_TH_FREED_CARPENTERS &&
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGerudoToken"), RO_GENERIC_NO) == RO_GENERIC_YES) ||
(location.GetRandomizerCheck() == RC_TH_1_TORCH_CARPENTER &&
CVarGetInteger(CVAR_RANDOMIZER_SETTING("GerudoKeys"), RO_GERUDO_KEYS_VANILLA) !=
RO_GERUDO_KEYS_VANILLA))) ||
(CVarGetInteger(CVAR_RANDOMIZER_SETTING("FortressCarpenters"), RO_GF_CARPENTERS_NORMAL) ==
RO_GF_CARPENTERS_NORMAL &&
((location.GetRandomizerCheck() == RC_TH_FREED_CARPENTERS &&
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGerudoToken"), RO_GENERIC_NO) == RO_GENERIC_YES) ||
(location.GetRCType() == RCTYPE_GF_KEY &&
CVarGetInteger(CVAR_RANDOMIZER_SETTING("GerudoKeys"), RO_GERUDO_KEYS_VANILLA) !=
RO_GERUDO_KEYS_VANILLA)))));
}
}