mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-06-10 20:28:19 -04:00
Merge remote-tracking branch 'origin/randomizer' into rando-archi
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user