Merge remote-tracking branch 'origin/randomizer' into rando-archi

This commit is contained in:
CraftyBoss
2026-06-08 16:44:48 -07:00
18 changed files with 260 additions and 153 deletions
+3
View File
@@ -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);
+13 -3
View File
@@ -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:
+19
View File
@@ -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;
+38
View File
@@ -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 <format>
#include <fmt/ranges.h>
#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<u8*>(&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;
}
+16 -9
View File
@@ -23,9 +23,13 @@ using json = nlohmann::json;
aurora::Module DuskConfigLog("dusk::config");
static absl::flat_hash_map<std::string_view, ConfigVarBase*> RegisteredConfigVars;
static bool RegistrationDone = false;
static absl::flat_hash_map<std::string_view, ConfigVarBase*>& registered_config_vars() {
static absl::flat_hash_map<std::string_view, ConfigVarBase*> 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<void(ConfigVarBase&)> callback) {
for (auto& pair : RegisteredConfigVars) {
for (auto& pair : registered_config_vars()) {
callback(*pair.second);
}
}
@@ -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<typename T, size_t N>
u32 getProgressiveItem(const std::array<T, N>& 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() {
@@ -7,7 +7,6 @@
namespace randomizer::logic::entrance
{
std::unordered_set<Type> NON_ASSUMED_TYPES = {Type::SPAWN, Type::WARP_PORTAL};
Type TypeFromStr(const std::string& str)
{
@@ -49,7 +49,7 @@ namespace randomizer::logic::entrance
ALL,
};
extern std::unordered_set<Type> 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
@@ -2,16 +2,16 @@
#include "item_pool.hpp"
#include "search.hpp"
#include "../utility/file.hpp"
#include "../utility/random.hpp"
#include "../utility/yaml.hpp"
#include <ranges>
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<Entrance*, Entrance*> 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<Entrance*, Entrance*>& 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<Entrance*, Entrance*>& 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())
{
@@ -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<entrance::Entrance*, entrance::Entrance*>& rollbacks);
bool ReplaceEntrance(world::WorldPool& worlds,
entrance::Entrance* entrance,
bool ReplaceEntrance(entrance::Entrance* entrance,
entrance::Entrance* target,
std::unordered_map<entrance::Entrance*, entrance::Entrance*>& 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);
@@ -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<int>();
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);
+27 -2
View File
@@ -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
@@ -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<entrance::Entrance*, int>& 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) {
+6 -2
View File
@@ -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
@@ -2,12 +2,11 @@
#include "../utility/random.hpp"
#include <vector>
#include <string>
namespace randomizer::seedgen::seed
{
static const std::vector<std::string> 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<std::string> 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",
+25
View File
@@ -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<ToggleEntry> 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<ToggleEntry> 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]);
}
}
},
});
+4 -1
View File
@@ -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);
}
+1
View File
@@ -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;