diff --git a/include/d/d_item.h b/include/d/d_item.h index 23787a7340..533908bc6c 100644 --- a/include/d/d_item.h +++ b/include/d/d_item.h @@ -526,6 +526,9 @@ int checkItemGet(u8, int); BOOL isHeart(u8 item_no); int isBomb(u8); int isArrow(u8); +#if TARGET_PC +int isRupee(u8); +#endif int addBombCount(u8, u8); BOOL isBottleItem(u8 item_no); u8 check_itemno(int i_itemNo); diff --git a/src/d/actor/d_a_obj_item.cpp b/src/d/actor/d_a_obj_item.cpp index b9c68d9103..ae8d2263ea 100644 --- a/src/d/actor/d_a_obj_item.cpp +++ b/src/d/actor/d_a_obj_item.cpp @@ -364,8 +364,6 @@ int daItem_c::_daItem_create() { if (flag) { CreateInit(); } else { -#if TARGET_PC -#endif phase_state = dComIfG_resLoad(&mPhase, dItem_data::getFieldArc(M_ITEMNO_MODEL_ITEM_ID)); if (phase_state == cPhs_COMPLEATE_e) { if (!fopAcM_entrySolidHeap(this, CheckFieldItemCreateHeap, @@ -615,7 +613,8 @@ void daItem_c::procWaitGetDemoEvent() { procInitSimpleGetDemo(); itemGet(); - if (!haveItem) { + // Don't potentially unset item bits in rando unless they're rupees + if (!haveItem IF_DUSK(&& (!randomizer_IsActive() || isRupee(m_itemNo)))) { dComIfGs_offItemFirstBit(m_itemNo); } } else { @@ -834,8 +833,15 @@ void daItem_c::mode_wait() { itemActionForArrow(); break; case dItemNo_BOOMERANG_e: +#if TARGET_PC + if (!randomizer_IsActive()) { + itemActionForBoomerang(); + break; + } +#else itemActionForBoomerang(); break; +#endif case dItemNo_GREEN_RUPEE_e: case dItemNo_BLUE_RUPEE_e: case dItemNo_YELLOW_RUPEE_e: @@ -1051,6 +1057,10 @@ void daItem_c::itemGet() { execItemGet(m_itemNo); break; case dItemNo_BOOMERANG_e: +#if TARGET_PC + // Let boomerang fall through in rando + if (!randomizer_IsActive()) +#endif break; case dItemNo_ARROW_10_e: case dItemNo_ARROW_20_e: diff --git a/src/d/d_item.cpp b/src/d/d_item.cpp index bdc70ddd21..ab0ea890e9 100644 --- a/src/d/d_item.cpp +++ b/src/d/d_item.cpp @@ -3355,6 +3355,25 @@ BOOL isHeart(u8 i_itemNo) { return is_heart; } +#if TARGET_PC +BOOL isRupee(u8 i_itemNo) { + switch (i_itemNo) { + case dItemNo_GREEN_RUPEE_e: + case dItemNo_BLUE_RUPEE_e: + case dItemNo_YELLOW_RUPEE_e: + case dItemNo_RED_RUPEE_e: + case dItemNo_PURPLE_RUPEE_e: + case dItemNo_ORANGE_RUPEE_e: + case dItemNo_SILVER_RUPEE_e: + return true; + default: + break; + } + + return false; +} +#endif + BOOL isInsect(u8 i_itemNo) { BOOL is_insect = false; diff --git a/src/d/d_stage.cpp b/src/d/d_stage.cpp index aa153ebd74..5ed5a918b5 100644 --- a/src/d/d_stage.cpp +++ b/src/d/d_stage.cpp @@ -27,6 +27,9 @@ #include "dusk/logging.h" #include "dusk/string.hpp" #include "dusk/randomizer/game/randomizer_context.hpp" +#include "dusk/randomizer/game/flags.h" +#include "dusk/randomizer/game/stages.h" +#include "dusk/randomizer/game/tools.h" #include #include #endif @@ -1731,6 +1734,41 @@ static int dStage_playerInit(dStage_dt_c* i_stage, void* i_data, int num, void* i_stage->setPlayer(player); i_stage->setPlayerNum(num); +#if TARGET_PC + // Modify entrance types in certain situations to avoid crashes + if (randomizer_IsActive()) { + for (size_t i = 0; i < num; ++i) { + u8& entranceType = reinterpret_cast(&player_data[i].base.parameters)[2]; + switch (entranceType) { + // Only replace the entrance type if it is a door. + case 0x80: + case 0xA0: + case 0xB0: + { + if (dComIfGs_getTransformStatus() == TF_STATUS_WOLF) { + // Change the entrance type to play the animation of walking out of the loading zone instead of entering + // through the door. + entranceType = 0x50; + } + break; + } + + // Water swimming entrance. If we have this, but there isn't any water to spawn in, the game hangs + case 0xD0: + { + // If there's no water, change to non-swimming entrance + if (getStageID() == Lake_Hylia && !dComIfGs_isEventBit(WARPED_METEOR_TO_ZORAS_DOMAIN)) { + entranceType = 0x50; + } + break; + } + default: + break; + } + } + } +#endif + if (dComIfGp_getPlayer(0) != NULL || dComIfGp_getStartStageRoomNo() != i_stage->getRoomNo()) { return 1; } diff --git a/src/dusk/config.cpp b/src/dusk/config.cpp index aed43089c4..b90e5b551c 100644 --- a/src/dusk/config.cpp +++ b/src/dusk/config.cpp @@ -23,9 +23,13 @@ using json = nlohmann::json; aurora::Module DuskConfigLog("dusk::config"); -static absl::flat_hash_map RegisteredConfigVars; static bool RegistrationDone = false; +static absl::flat_hash_map& registered_config_vars() { + static absl::flat_hash_map vars; + return vars; +} + static std::filesystem::path GetConfigJsonPath() { return dusk::ConfigPath / ConfigFileName; } @@ -200,16 +204,17 @@ namespace dusk::config { } void dusk::config::Register(ConfigVarBase& configVar) { + auto& registeredConfigVars = registered_config_vars(); const auto& name = configVar.getName(); if (RegistrationDone) { DuskConfigLog.fatal("Tried to register CVar {} after registrations closed!", name); } - if (RegisteredConfigVars.contains(name)) { + if (registeredConfigVars.contains(name)) { DuskConfigLog.fatal("Tried to register CVar {} twice!", name); } - RegisteredConfigVars[name] = &configVar; + registeredConfigVars[name] = &configVar; configVar.markRegistered(); } @@ -234,6 +239,7 @@ void dusk::config::LoadFromUserPreferences() { } static void LoadFromPath(const char* path) { + auto& registeredConfigVars = registered_config_vars(); auto data = dusk::io::FileStream::ReadAllBytes(path); json j = json::parse(data); @@ -244,8 +250,8 @@ static void LoadFromPath(const char* path) { for (const auto& el : j.items()) { const auto& key = el.key(); - auto configVar = RegisteredConfigVars.find(key); - if (configVar == RegisteredConfigVars.end()) { + auto configVar = registeredConfigVars.find(key); + if (configVar == registeredConfigVars.end()) { DuskConfigLog.error("Unknown key '{}' found in config!", key); continue; } @@ -293,7 +299,7 @@ void dusk::config::Save() { json j; - for (const auto& pair : RegisteredConfigVars) { + for (const auto& pair : registered_config_vars()) { const auto layer = pair.second->getLayer(); if (layer == ConfigVarLayer::Value || layer == ConfigVarLayer::Speedrun) { j[pair.first] = pair.second->getImpl()->dumpToJson(*pair.second); @@ -317,8 +323,9 @@ void dusk::config::ClearAllActionBindings(int port) { } ConfigVarBase* dusk::config::GetConfigVar(std::string_view name) { - const auto configVar = RegisteredConfigVars.find(name); - if (configVar != RegisteredConfigVars.end()) { + auto& registeredConfigVars = registered_config_vars(); + const auto configVar = registeredConfigVars.find(name); + if (configVar != registeredConfigVars.end()) { return configVar->second; } @@ -326,7 +333,7 @@ ConfigVarBase* dusk::config::GetConfigVar(std::string_view name) { } void dusk::config::EnumerateRegistered(std::function callback) { - for (auto& pair : RegisteredConfigVars) { + for (auto& pair : registered_config_vars()) { callback(*pair.second); } } diff --git a/src/dusk/randomizer/game/verify_item_functions.cpp b/src/dusk/randomizer/game/verify_item_functions.cpp index 9fd204656f..6125bd4777 100644 --- a/src/dusk/randomizer/game/verify_item_functions.cpp +++ b/src/dusk/randomizer/game/verify_item_functions.cpp @@ -8,10 +8,9 @@ bool haveItem(u32 item) { return checkItemGet((u8)item, 1); } -u32 getProgressiveSword() { - static constexpr u8 progressiveItemsList[] = {dItemNo_Randomizer_WOOD_STICK_e, dItemNo_Randomizer_SWORD_e, dItemNo_Randomizer_MASTER_SWORD_e}; - - u32 listLength = sizeof(progressiveItemsList) / sizeof(progressiveItemsList[0]); +template +u32 getProgressiveItem(const std::array& progressiveItemsList) { + u32 listLength = N; for (int i = 0; i < listLength; i++) { const u32 item = progressiveItemsList[i]; @@ -22,48 +21,42 @@ u32 getProgressiveSword() { } // All previous obtained, so return last upgrade - return dItemNo_Randomizer_LIGHT_SWORD_e; + return progressiveItemsList[listLength - 1]; +} + +u32 getProgressiveSword() { + static constexpr std::array progressiveItemsList = { + dItemNo_Randomizer_WOOD_STICK_e, + dItemNo_Randomizer_SWORD_e, + dItemNo_Randomizer_MASTER_SWORD_e, + dItemNo_Randomizer_LIGHT_SWORD_e, + }; + + return getProgressiveItem(progressiveItemsList); }; u32 getProgressiveBow() { - static const u8 progressiveItemsList[] = {dItemNo_Randomizer_BOW_e, dItemNo_Randomizer_ARROW_LV2_e}; + static constexpr std::array progressiveItemsList = { + dItemNo_Randomizer_BOW_e, + dItemNo_Randomizer_ARROW_LV2_e, + dItemNo_Randomizer_ARROW_LV3_e, + }; - u32 listLength = sizeof(progressiveItemsList) / sizeof(progressiveItemsList[0]); - for (int i = 0; i < listLength; i++) - { - const u32 item = progressiveItemsList[i]; - if (!haveItem(item)) - { - return item; - } - } - - // All previous obtained, so return last upgrade - return dItemNo_Randomizer_ARROW_LV3_e; + return getProgressiveItem(progressiveItemsList); }; u32 getProgressiveSkill() { - static const u8 progressiveItemsList[] = { + static constexpr std::array progressiveItemsList = { dItemNo_Randomizer_ENDING_BLOW_e, dItemNo_Randomizer_SHIELD_ATTACK_e, dItemNo_Randomizer_BACK_SLICE_e, dItemNo_Randomizer_HELM_SPLITTER_e, dItemNo_Randomizer_MORTAL_DRAW_e, - dItemNo_Randomizer_JUMP_STRIKE_e + dItemNo_Randomizer_JUMP_STRIKE_e, + dItemNo_Randomizer_GREAT_SPIN_e, }; - u32 listLength = sizeof(progressiveItemsList) / sizeof(progressiveItemsList[0]); - for (int i = 0; i < listLength; i++) - { - const u32 item = progressiveItemsList[i]; - if (!haveItem(item)) - { - return item; - } - } - - // All previous obtained, so return last upgrade - return dItemNo_Randomizer_GREAT_SPIN_e; + return getProgressiveItem(progressiveItemsList); }; u32 getProgressiveSkybook() { @@ -87,58 +80,34 @@ u32 getProgressiveSkybook() { }; u32 getProgressiveKeyShard() { - static const u8 progressiveItemsList[] = {dItemNo_Randomizer_L2_KEY_PIECES1_e, dItemNo_Randomizer_L2_KEY_PIECES2_e}; + static constexpr std::array progressiveItemsList = { + dItemNo_Randomizer_L2_KEY_PIECES1_e, + dItemNo_Randomizer_L2_KEY_PIECES2_e, + dItemNo_Randomizer_LV2_BOSS_KEY_e, + }; - u32 listLength = sizeof(progressiveItemsList) / sizeof(progressiveItemsList[0]); - for (int i = 0; i < listLength; i++) - { - const u32 item = progressiveItemsList[i]; - if (!haveItem(item)) - { - return item; - } - } - - // All previous obtained, so return last upgrade - return dItemNo_Randomizer_LV2_BOSS_KEY_e; + return getProgressiveItem(progressiveItemsList); }; u32 getProgressiveMirrorShard() { - static const u8 progressiveItemsList[] = { + static constexpr std::array progressiveItemsList = { dItemNo_Randomizer_MIRROR_PIECE_1_e, dItemNo_Randomizer_MIRROR_PIECE_2_e, - dItemNo_Randomizer_MIRROR_PIECE_3_e + dItemNo_Randomizer_MIRROR_PIECE_3_e, + dItemNo_Randomizer_MIRROR_PIECE_4_e, }; - u32 listLength = sizeof(progressiveItemsList) / sizeof(progressiveItemsList[0]); - for (int i = 0; i < listLength; i++) - { - const u32 item = progressiveItemsList[i]; - if (!haveItem(item)) - { - return item; - } - } - - // All previous obtained, so return last upgrade - return dItemNo_Randomizer_MIRROR_PIECE_4_e; + return getProgressiveItem(progressiveItemsList); }; u32 getProgressiveFusedShadow() { - static const u8 progressiveItemsList[] = {dItemNo_Randomizer_FUSED_SHADOW_1_e, dItemNo_Randomizer_FUSED_SHADOW_2_e}; + static constexpr std::array progressiveItemsList = { + dItemNo_Randomizer_FUSED_SHADOW_1_e, + dItemNo_Randomizer_FUSED_SHADOW_2_e, + dItemNo_Randomizer_FUSED_SHADOW_3_e, + }; - u32 listLength = sizeof(progressiveItemsList) / sizeof(progressiveItemsList[0]); - for (int i = 0; i < listLength; i++) - { - const u32 item = progressiveItemsList[i]; - if (!haveItem(item)) - { - return item; - } - } - - // All previous obtained, so return last upgrade - return dItemNo_Randomizer_FUSED_SHADOW_3_e; + return getProgressiveItem(progressiveItemsList); }; u8 getWarashibeItemCount() { diff --git a/src/dusk/randomizer/generator/logic/entrance.cpp b/src/dusk/randomizer/generator/logic/entrance.cpp index e6e0475903..b27d8d2c47 100644 --- a/src/dusk/randomizer/generator/logic/entrance.cpp +++ b/src/dusk/randomizer/generator/logic/entrance.cpp @@ -7,7 +7,6 @@ namespace randomizer::logic::entrance { - std::unordered_set NON_ASSUMED_TYPES = {Type::SPAWN, Type::WARP_PORTAL}; Type TypeFromStr(const std::string& str) { diff --git a/src/dusk/randomizer/generator/logic/entrance.hpp b/src/dusk/randomizer/generator/logic/entrance.hpp index 13fa2080ef..8159d4353c 100644 --- a/src/dusk/randomizer/generator/logic/entrance.hpp +++ b/src/dusk/randomizer/generator/logic/entrance.hpp @@ -49,7 +49,7 @@ namespace randomizer::logic::entrance ALL, }; - extern std::unordered_set NON_ASSUMED_TYPES; + static const std::unordered_set NON_ASSUMED_TYPES = {SPAWN, WARP_PORTAL}; /** * @brief Takes a string representation of a Type and returns the diff --git a/src/dusk/randomizer/generator/logic/entrance_shuffle.cpp b/src/dusk/randomizer/generator/logic/entrance_shuffle.cpp index fb08d1d70b..68605ab208 100644 --- a/src/dusk/randomizer/generator/logic/entrance_shuffle.cpp +++ b/src/dusk/randomizer/generator/logic/entrance_shuffle.cpp @@ -2,16 +2,16 @@ #include "item_pool.hpp" #include "search.hpp" - -#include "../utility/file.hpp" #include "../utility/random.hpp" #include "../utility/yaml.hpp" +#include + using namespace randomizer::logic::entrance; namespace randomizer::logic::entrance_shuffle { - void ShuffleWorldEntrances(world::World* world, world::WorldPool& worlds) + void ShuffleWorldEntrances(world::World* world) { SetAllEntrancesData(world); @@ -19,20 +19,24 @@ namespace randomizer::logic::entrance_shuffle auto targetEntrancePools = CreateTargetPools(entrancePools); // Set plando entrances first - SetPlandomizedEntrances(world, worlds, entrancePools, targetEntrancePools); + try { + SetPlandomizedEntrances(world, entrancePools, targetEntrancePools); + } catch (std::runtime_error& e) { + throw std::runtime_error("Plandomizer Error: " + std::string(e.what())); + } // Then shuffle non-assumed types (currently this is just spawn) - ShuffleNonAssumedEntrancesPools(world, worlds, entrancePools, targetEntrancePools); + ShuffleNonAssumedEntrancesPools(world, entrancePools, targetEntrancePools); // Shuffle the rest of the entrance pools for (auto& [entranceType, entrancePool] : entrancePools) { - ShuffleEntrancePool(world, worlds, entrancePool, targetEntrancePools[entranceType]); + ShuffleEntrancePool(entrancePool, targetEntrancePools[entranceType]); } // Validate the world one last time to ensure everything worked - auto completeItemPool = item_pool::GetCompleteItemPool(worlds); - ValidateWorld(world, worlds, nullptr, completeItemPool); + auto completeItemPool = item_pool::GetCompleteItemPool(world->GetRandomizer()->GetWorlds()); + ValidateWorld(world, nullptr, completeItemPool); } void SetAllEntrancesData(world::World* world) @@ -344,11 +348,11 @@ namespace randomizer::logic::entrance_shuffle } void SetPlandomizedEntrances(world::World* world, - world::WorldPool& worlds, EntrancePools& entrancePools, EntrancePools& targetEntrancePools) { LOG_TO_DEBUG("Now placing plandomizer entrances"); + auto& worlds = world->GetRandomizer()->GetWorlds(); auto itemPool = item_pool::GetCompleteItemPool(worlds); for (auto& [plandoEntrance, plandoTarget] : world->GetPlandomizerEntrances()) @@ -360,20 +364,20 @@ namespace randomizer::logic::entrance_shuffle // Throw error if entrance/target types are not shuffleable if (entranceType == Type::INVALID) { - throw std::runtime_error("Plandomizer Error: " + entranceToConnect->GetOriginalName() + + throw std::runtime_error(entranceToConnect->GetOriginalName() + " is not an entrance that can be shuffled"); } if (plandoTarget->GetType() == Type::INVALID) { - throw std::runtime_error("Plandomizer Error: " + plandoTarget->GetOriginalName() + + throw std::runtime_error(plandoTarget->GetOriginalName() + " is not an entrance that can be shuffled"); } // Throw error if entrance type is shuffleable, but the type itself is not randomized currently if (!entrancePools.contains(entranceType)) { - throw std::runtime_error("Plandomizer Error: " + entranceToConnect->GetOriginalName() + "'s type " + - TypeToStr(entranceType) + " is not being shuffled and thus can't be plandomized."); + throw std::runtime_error("Entrance type " + TypeToStr(entranceType) + " for " + + entranceToConnect->GetOriginalName() + " is not being shuffled and thus can't be plandomized."); } // Get the appropriate pools @@ -383,13 +387,13 @@ namespace randomizer::logic::entrance_shuffle // If entrances are coupled, but the user tries to plandomize a non-primary connection, get the primary connection // instead if (world->Setting("Decouple Entrances") == "Off" && - randomizer::utility::container::ElementInContainer(entrancePool, entranceToConnect->GetReverse())) + utility::container::ElementInContainer(entrancePool, entranceToConnect->GetReverse())) { entranceToConnect = entranceToConnect->GetReverse(); targetToConnect = targetToConnect->GetReverse(); } - if (randomizer::utility::container::ElementInContainer(entrancePool, entranceToConnect)) + if (utility::container::ElementInContainer(entrancePool, entranceToConnect)) { bool validTargetFound = false; for (auto& target : targetPool) @@ -404,14 +408,14 @@ namespace randomizer::logic::entrance_shuffle // If the spawn entrance isn't placed, then we can't validate the world if (world->GetEntrance("Links Spawn -> Outside Links House")->GetConnectedArea() != nullptr) { - ValidateWorld(world, worlds, entranceToConnect, itemPool); + ValidateWorld(world, entranceToConnect, itemPool); } validTargetFound = true; ConfirmReplacement(entranceToConnect, target); } catch(const EntranceShuffleError& e) { - throw std::runtime_error("Could not connect plandomized entrance " + + throw std::runtime_error("Could not connect entrance " + entranceToConnect->GetOriginalName() + " to " + target->GetOriginalName() + " Reason:\n" + e.what()); } @@ -425,8 +429,8 @@ namespace randomizer::logic::entrance_shuffle // If we found our target, delete the entrance and it's now connected target from their respective pools if (validTargetFound) { - randomizer::utility::container::Erase(entrancePool, entranceToConnect); - randomizer::utility::container::Erase(targetPool, targetToConnect->GetAssumed()); + utility::container::Erase(entrancePool, entranceToConnect); + utility::container::Erase(targetPool, targetToConnect->GetAssumed()); } // Otherwise, the target is invalid else @@ -438,7 +442,7 @@ namespace randomizer::logic::entrance_shuffle else { throw std::runtime_error("Plandomizer Error: " + entranceToConnect->GetOriginalName() + - " for some reason could not be found."); + " could not be found."); } } @@ -446,18 +450,25 @@ namespace randomizer::logic::entrance_shuffle } void ShuffleNonAssumedEntrancesPools(world::World* world, - world::WorldPool& worlds, EntrancePools& entrancePools, EntrancePools& targetEntrancePools) { + // If we aren't shuffling any non-assumed types, return early + if (std::ranges::none_of(entrancePools | std::ranges::views::keys, [](const auto& type) { + return NON_ASSUMED_TYPES.contains(type); + })) { + return; + } + + auto& worlds = world->GetRandomizer()->GetWorlds(); auto completeItemPool = item_pool::GetCompleteItemPool(worlds); // The idea here is we want to try shuffling all the non-assumed entrances - // at the same time since we can't validate the world after each one individually - // (That would require assuming access to entrances which we can't guarantee access to) + // at the same time since we can't validate the world after each one individually. + // (That would require assuming access to entrances which we can't guarantee access to.) // Realistically, this should never take more than 1 or 2 tries unless there's some wacky - // plandomizer stuff going on. Currently the only non-assumed entrance we're shuffling is the randomized spawn - // but if we ever shuffle warp portals, they'll go here too. + // plandomizer stuff going on. Currently, the only non-assumed entrance we're shuffling + // is the randomized spawn, but if we ever shuffle warp portals, they'll go here too. int retries = 20; while (retries > 0) @@ -497,11 +508,11 @@ namespace randomizer::logic::entrance_shuffle bool successfulConnection = false; try { - ValidateWorld(world, worlds, nullptr, completeItemPool); + ValidateWorld(world, nullptr, completeItemPool); for (auto& [entrance, target] : rollbacks) { ConfirmReplacement(entrance, target); - randomizer::utility::container::Erase(targetEntrancePools[entrance->GetType()], target); + utility::container::Erase(targetEntrancePools[entrance->GetType()], target); } // Once we've made a valid world, delete all other targets that didn't get used for (auto& [entranceType, targetPool] : targetEntrancePools) @@ -540,9 +551,7 @@ namespace randomizer::logic::entrance_shuffle } } - void ShuffleEntrancePool(world::World* world, - world::WorldPool& worlds, - EntrancePool& entrancePool, + void ShuffleEntrancePool(EntrancePool& entrancePool, EntrancePool& targetEntrancePool, int retries /* = 20*/) { @@ -552,7 +561,7 @@ namespace randomizer::logic::entrance_shuffle std::unordered_map rollbacks = {}; try { - ShuffleEntrances(worlds, entrancePool, targetEntrancePool, rollbacks); + ShuffleEntrances(entrancePool, targetEntrancePool, rollbacks); for (auto& [entrance, target] : rollbacks) { ConfirmReplacement(entrance, target); @@ -576,13 +585,18 @@ namespace randomizer::logic::entrance_shuffle "generate successfully."); } - void ShuffleEntrances(world::WorldPool& worlds, - EntrancePool& entrancePool, + void ShuffleEntrances(EntrancePool& entrancePool, EntrancePool& targetEntrancePool, std::unordered_map& rollbacks) { + // This shouldn't be empty, but just incase + if (entrancePool.empty()) { + return; + } + + auto& worlds = entrancePool.front()->GetWorld()->GetRandomizer()->GetWorlds(); auto completeItemPool = item_pool::GetCompleteItemPool(worlds); - randomizer::utility::random::ShufflePool(entrancePool); + utility::random::ShufflePool(entrancePool); for (auto& entrance : entrancePool) { @@ -591,7 +605,7 @@ namespace randomizer::logic::entrance_shuffle { continue; } - randomizer::utility::random::ShufflePool(targetEntrancePool); + utility::random::ShufflePool(targetEntrancePool); // Loop through and find a valid target entrance to connect to for (auto& target : targetEntrancePool) @@ -607,7 +621,7 @@ namespace randomizer::logic::entrance_shuffle target->GetReplaces()->GetParentArea()->GetName() + " [W" + std::to_string(entrance->GetWorld()->GetID()) + "]"); - if (ReplaceEntrance(worlds, entrance, target, rollbacks, completeItemPool)) + if (ReplaceEntrance(entrance, target, rollbacks, completeItemPool)) { break; } @@ -631,8 +645,7 @@ namespace randomizer::logic::entrance_shuffle } } - bool ReplaceEntrance(world::WorldPool& worlds, - Entrance* entrance, + bool ReplaceEntrance(Entrance* entrance, Entrance* target, std::unordered_map& rollbacks, const item_pool::ItemPool& completeItemPool) @@ -641,7 +654,7 @@ namespace randomizer::logic::entrance_shuffle { CheckEntrancesCompatibility(entrance, target); ChangeConnections(entrance, target); - ValidateWorld(entrance->GetWorld(), worlds, entrance, completeItemPool); + ValidateWorld(entrance->GetWorld(), entrance, completeItemPool); rollbacks[entrance] = target; return true; } @@ -717,11 +730,11 @@ namespace randomizer::logic::entrance_shuffle } void ValidateWorld(world::World* world, - world::WorldPool& worlds, Entrance* entrance, const item_pool::ItemPool& completeItemPool) { // Validate that all logic is still satisfied + auto& worlds = world->GetRandomizer()->GetWorlds(); auto verifyLogicError = search::VerifyLogic(&worlds, completeItemPool); if (verifyLogicError.has_value()) { diff --git a/src/dusk/randomizer/generator/logic/entrance_shuffle.hpp b/src/dusk/randomizer/generator/logic/entrance_shuffle.hpp index a16f8f8bf7..85e6dcd8e0 100644 --- a/src/dusk/randomizer/generator/logic/entrance_shuffle.hpp +++ b/src/dusk/randomizer/generator/logic/entrance_shuffle.hpp @@ -5,30 +5,24 @@ namespace randomizer::logic::entrance_shuffle { - void ShuffleWorldEntrances(world::World* world, world::WorldPool& worlds); + void ShuffleWorldEntrances(world::World* world); void SetAllEntrancesData(world::World* world); entrance::EntrancePools CreateEntrancePools(world::World* world); entrance::EntrancePools CreateTargetPools(entrance::EntrancePools& entrancePools); entrance::EntrancePool AssumeEntrancePool(entrance::EntrancePool& entrancePool); void SetPlandomizedEntrances(world::World* world, - world::WorldPool& worlds, entrance::EntrancePools& entrancePools, entrance::EntrancePools& targetEntrancePools); void ShuffleNonAssumedEntrancesPools(world::World* world, - world::WorldPool& worlds, entrance::EntrancePools& entrancePools, entrance::EntrancePools& targetEntrancePools); - void ShuffleEntrancePool(world::World* world, - world::WorldPool& worlds, - entrance::EntrancePool& entrancePool, + void ShuffleEntrancePool(entrance::EntrancePool& entrancePool, entrance::EntrancePool& targetEntrancePool, int retries = 20); - void ShuffleEntrances(world::WorldPool& worlds, - entrance::EntrancePool& entrancePool, + void ShuffleEntrances(entrance::EntrancePool& entrancePool, entrance::EntrancePool& targetEntrancePool, std::unordered_map& rollbacks); - bool ReplaceEntrance(world::WorldPool& worlds, - entrance::Entrance* entrance, + bool ReplaceEntrance(entrance::Entrance* entrance, entrance::Entrance* target, std::unordered_map& rollbacks, const item_pool::ItemPool& completeItemPool); @@ -40,7 +34,6 @@ namespace randomizer::logic::entrance_shuffle void ConfirmReplacement(entrance::Entrance* entrance, entrance::Entrance* target); void DeleteTargetEntrance(entrance::Entrance* target); void ValidateWorld(world::World* world, - world::WorldPool& worlds, entrance::Entrance* entrance, const item_pool::ItemPool& completeItemPool); diff --git a/src/dusk/randomizer/generator/logic/plandomizer.cpp b/src/dusk/randomizer/generator/logic/plandomizer.cpp index 991f708555..a0cd43cee4 100644 --- a/src/dusk/randomizer/generator/logic/plandomizer.cpp +++ b/src/dusk/randomizer/generator/logic/plandomizer.cpp @@ -4,14 +4,12 @@ #include "../utility/yaml.hpp" #include "../utility/file.hpp" -#include "../utility/log.hpp" namespace randomizer::logic::plandomizer { void LoadPlandomizerData(world::WorldPool& worlds, const fspath& filepath, const bool& ignoreErrors /*false*/) { // Verify the file exists before trying to open it - // TODO: TRY CATCH HERE utility::file::Verify(filepath); auto plandoTree = LoadYAML(filepath); @@ -33,8 +31,8 @@ namespace randomizer::logic::plandomizer return; } - throw std::runtime_error("Plandomizer file locations for " + worldStr + - " is not a map. Please check your syntax before trying again."); + throw std::runtime_error("Locations for " + worldStr + + " is not a map. Please check your plandomizer file syntax."); } for (const auto& locationNode : locations) @@ -50,7 +48,7 @@ namespace randomizer::logic::plandomizer } else { - throw std::runtime_error("Plandomizer Error: Missing key \"item\" in node:\n" + + throw std::runtime_error("Missing key \"item\" in node:\n" + YAML::Dump(locationNode)); } @@ -59,7 +57,7 @@ namespace randomizer::logic::plandomizer worldId = locationNode.second["World"].as(); if (worldId < 1 || worldId > worlds.size()) { - std::string errorMsg = "Plandomizer Error: Bad World ID \"" + std::to_string(worldId) + + std::string errorMsg = "Bad World ID \"" + std::to_string(worldId) + "\"\nOnly " + std::to_string(worlds.size()) + " worlds are being generated."; throw std::runtime_error(errorMsg); diff --git a/src/dusk/randomizer/generator/logic/world.cpp b/src/dusk/randomizer/generator/logic/world.cpp index d0caf8d041..7c924a7529 100644 --- a/src/dusk/randomizer/generator/logic/world.cpp +++ b/src/dusk/randomizer/generator/logic/world.cpp @@ -633,6 +633,21 @@ namespace randomizer::logic::world location->SetCurrentItem(item); utility::container::Erase(this->_itemPool, item); } + + // If no world has entrance randomizer enabled, check to see if our plandomized item placements work + if (std::ranges::none_of(this->GetRandomizer()->GetWorlds(), [](const auto& world) { + return world->AnyEntranceRandomizerEnabled(); + })) { + if (!this->_plandomizerLocations.empty() && Setting("Logic Rules") != "No Logic") { + auto& worlds = this->GetRandomizer()->GetWorlds(); + auto completeItemPool = item_pool::GetCompleteItemPool(worlds); + auto verifyLogicError = search::VerifyLogic(&worlds, completeItemPool); + if (verifyLogicError.has_value()) + { + throw std::runtime_error("Plandomizer item placements do not work! Reason:\n" + verifyLogicError.value()); + } + } + } } void World::SetNonProgressLocations() @@ -1194,7 +1209,7 @@ namespace randomizer::logic::world } entrance::EntrancePool World::GetShuffleableEntrances(const entrance::Type& type, - const bool& onlyPrimary /* = false */) + bool onlyPrimary /* = false */) { entrance::EntrancePool shuffleableEntrances = {}; for (const auto& [areaName, area] : this->GetAreaTable()) @@ -1213,7 +1228,7 @@ namespace randomizer::logic::world entrance::EntrancePool World::GetShuffledEntrances( const entrance::Type& type /* = entrance::Type::ALL */, - const bool& onlyPrimary /* = false */) + bool onlyPrimary /* = false */) { auto entrances = this->GetShuffleableEntrances(type, onlyPrimary); @@ -1282,4 +1297,14 @@ namespace randomizer::logic::world } return settings.GetMap().at(settingName); } + + bool World::AnyEntranceRandomizerEnabled() { + return Setting("Randomize Starting Spawn") != "Off" || + Setting("Randomize Dungeon Entrances") != "Off" || + Setting("Randomize Boss Entrances") != "Off" || + Setting("Randomize Grotto Entrances") != "Off" || + Setting("Randomize Cave Entrances") != "Off" || + Setting("Randomize Interior Entrances") != "Off" || + Setting("Randomize Overworld Entrances") != "Off"; + } } // namespace randomizer::logic::world diff --git a/src/dusk/randomizer/generator/logic/world.hpp b/src/dusk/randomizer/generator/logic/world.hpp index d944e62e15..caf996df80 100644 --- a/src/dusk/randomizer/generator/logic/world.hpp +++ b/src/dusk/randomizer/generator/logic/world.hpp @@ -137,10 +137,10 @@ namespace randomizer::logic::world entrance::Entrance* GetEntrance(const std::string& originalName); int GetNewEntranceID(); entrance::EntrancePool GetShuffleableEntrances(const entrance::Type& type, - const bool& onlyPrimary = false); + bool onlyPrimary = false); entrance::EntrancePool GetShuffledEntrances( const entrance::Type& type = entrance::Type::ALL, - const bool& onlyPrimary = false); + bool onlyPrimary = false); std::unordered_map& GetExitTimeFormCache(); int GetMacroIndex(const std::string& macroName) const; @@ -149,6 +149,7 @@ namespace randomizer::logic::world std::string GetEventName(const int& eventIndex); seedgen::settings::Setting& Setting(const std::string& settingName); + bool AnyEntranceRandomizerEnabled(); TextDatabase& GetTextDatabase() { return this->_textDatabase; } const std::string& GetText(const std::string& name, Text::Type type = Text::STANDARD, Text::Language language = Text::ENGLISH) { diff --git a/src/dusk/randomizer/generator/randomizer.cpp b/src/dusk/randomizer/generator/randomizer.cpp index 449ed7945c..6d9307b3e1 100644 --- a/src/dusk/randomizer/generator/randomizer.cpp +++ b/src/dusk/randomizer/generator/randomizer.cpp @@ -106,7 +106,11 @@ namespace randomizer // Process Plando Data for all worlds if (this->_config.IsUsingPlandomizer()) { - logic::plandomizer::LoadPlandomizerData(this->_worlds, this->_config.GetPlandomizerPath()); + try { + logic::plandomizer::LoadPlandomizerData(this->_worlds, this->_config.GetPlandomizerPath()); + } catch (const std::runtime_error& e) { + throw std::runtime_error("Plandomizer Error: " + std::string(e.what())); + } } // Pre Entrance Shuffle Tasks @@ -118,7 +122,7 @@ namespace randomizer utility::platform::Log("Shuffling Entrances..."); for (auto& world : this->_worlds) { - logic::entrance_shuffle::ShuffleWorldEntrances(world.get(), this->_worlds); + logic::entrance_shuffle::ShuffleWorldEntrances(world.get()); } // Post Entrance Shuffle Tasks diff --git a/src/dusk/randomizer/generator/seedgen/seed.cpp b/src/dusk/randomizer/generator/seedgen/seed.cpp index 9749498845..152f12389e 100644 --- a/src/dusk/randomizer/generator/seedgen/seed.cpp +++ b/src/dusk/randomizer/generator/seedgen/seed.cpp @@ -2,12 +2,11 @@ #include "../utility/random.hpp" -#include #include namespace randomizer::seedgen::seed { - static const std::vector nouns = { + static constexpr std::array nouns = { "Aeralfos", "Agitha", "Ant", "Argorok", "Armos", "Ashei", "Auru", "BackSlice", "Bari", "Barnes", "Beamos", "Beth", "BigBaba", "Blizzeta", "Bo", "Bokoblin", "Bombfish", "Borville", "Bulblin", "Butterfly", "CastleTown", "Charlo", "Cheese", "Chilfos", "Chu", "Chudley", "Clawshot", @@ -24,7 +23,7 @@ namespace randomizer::seedgen::seed "Telma", "Temple", "TileWorm", "Toadpoli", "Trill", "Twilight", "Uli", "WolfLink", "Zant", "Zelda", "Zora"}; - static const std::vector adjectives = { + static constexpr std::array adjectives = { "Abnormal", "Absent", "Absolute", "Abstract", "Absurd", "Accurate", "Active", "Actual", "Adjacent", "Aesthetic", "Aggressive", "Alert", "Alien", "Alternate", "Amazing", "Ambitious", "Amusing", "Ancient", "Angry", "Anxious", "Apparent", "Artistic", "Astute", "Atomic", diff --git a/src/dusk/ui/editor.cpp b/src/dusk/ui/editor.cpp index 8aa3c3894d..43e348b4a9 100644 --- a/src/dusk/ui/editor.cpp +++ b/src/dusk/ui/editor.cpp @@ -9,6 +9,7 @@ #include "d/d_kankyo.h" #include "d/d_meter2_info.h" #include "dusk/map_loader_definitions.h" +#include "dusk/randomizer/game/verify_item_functions.h" #include "number_button.hpp" #include "pane.hpp" #include "select_button.hpp" @@ -1097,10 +1098,21 @@ std::vector collect_crystal_toggle_entries( .isSelected = [index] { return dComIfGs_isCollectCrystal(index); }, .setSelected = [index](bool selected) { + static constexpr u8 fusedShadowItemNos[] = { + dItemNo_Randomizer_FUSED_SHADOW_1_e, + dItemNo_Randomizer_FUSED_SHADOW_2_e, + dItemNo_Randomizer_FUSED_SHADOW_3_e, + }; if (selected) { dComIfGs_onCollectCrystal(index); + if (randomizer_IsActive()) { + dComIfGs_onItemFirstBit(fusedShadowItemNos[index]); + } } else { dComIfGs_offCollectCrystal(index); + if (randomizer_IsActive()) { + dComIfGs_offItemFirstBit(fusedShadowItemNos[index]); + } } }, }); @@ -1119,10 +1131,23 @@ std::vector collect_mirror_toggle_entries( .isSelected = [index] { return dComIfGs_isCollectMirror(index); }, .setSelected = [index](bool selected) { + static constexpr u8 mirrorShardItemNos[] = { + dItemNo_Randomizer_MIRROR_PIECE_1_e, + dItemNo_Randomizer_MIRROR_PIECE_2_e, + dItemNo_Randomizer_MIRROR_PIECE_3_e, + dItemNo_Randomizer_MIRROR_PIECE_4_e, + }; + if (selected) { + if (randomizer_IsActive()) { + dComIfGs_onItemFirstBit(mirrorShardItemNos[index]); + } dComIfGs_onCollectMirror(index); } else { dComIfGs_offCollectMirror(index); + if (randomizer_IsActive()) { + dComIfGs_offItemFirstBit(mirrorShardItemNos[index]); + } } }, }); diff --git a/src/dusk/ui/rando_config.cpp b/src/dusk/ui/rando_config.cpp index a447968668..da9a4487f2 100644 --- a/src/dusk/ui/rando_config.cpp +++ b/src/dusk/ui/rando_config.cpp @@ -421,6 +421,8 @@ Modal* RandomizerWindow::show_seed_gen_modal(std::string_view message) { }, .icon = "verifying", }))); + // Allow manual line breaks in this modal for error messages + modal->root()->SetProperty("white-space", "pre-line"); if (auto* doc = top_document()) { doc->focus(); @@ -1189,7 +1191,7 @@ void RandomizerWindow::update() { m_genSeedModal->set_icon("error"); } - m_genSeedModal->set_body(generationStatusMsg); + m_genSeedModal->set_body(escape(generationStatusMsg)); m_genSeedModal->add_action({ .label = "OK", .onPressed = [this](Modal& modal) { @@ -1198,6 +1200,7 @@ void RandomizerWindow::update() { m_genSeedModal = nullptr; } }); + m_genSeedModal->focus(); seedGenStatus.store(SeedGenerateStatus::Ready); } diff --git a/src/dusk/ui/window.hpp b/src/dusk/ui/window.hpp index 5d732da70b..955ebeeb07 100644 --- a/src/dusk/ui/window.hpp +++ b/src/dusk/ui/window.hpp @@ -65,6 +65,7 @@ public: void show() override; void hide(bool close) override; bool visible() const override; + Rml::Element* root() { return mRoot;} protected: Rml::Element* mRoot = nullptr;