add imgui menu for generating and selecting seeds

This commit is contained in:
gymnast86
2026-04-16 19:50:17 -07:00
parent 4cd911a114
commit 2b4598821b
31 changed files with 629 additions and 215 deletions
+4
View File
@@ -1362,6 +1362,8 @@ set(DUSK_FILES
src/dusk/imgui/ImGuiMenuTools.hpp
src/dusk/imgui/ImGuiMenuEnhancements.cpp
src/dusk/imgui/ImGuiMenuEnhancements.hpp
src/dusk/imgui/ImGuiMenuRandomizer.cpp
src/dusk/imgui/ImGuiMenuRandomizer.hpp
src/dusk/imgui/ImGuiPreLaunchWindow.cpp
src/dusk/imgui/ImGuiPreLaunchWindow.hpp
src/dusk/imgui/ImGuiFirstRunPreset.hpp
@@ -1389,6 +1391,8 @@ set(DUSK_FILES
src/dusk/randomizer/game/tools.h
src/dusk/randomizer/game/verify_item_functions.cpp
src/dusk/randomizer/game/verify_item_functions.h
src/dusk/randomizer/game/randomizer_context.cpp
src/dusk/randomizer/game/randomizer_context.hpp
src/dusk/randomizer/generator/logic/area.cpp
src/dusk/randomizer/generator/logic/area.hpp
src/dusk/randomizer/generator/logic/dungeon.cpp
+4 -8
View File
@@ -1,6 +1,10 @@
#ifndef D_COM_D_COM_INF_GAME_H
#define D_COM_D_COM_INF_GAME_H
#if TARGET_PC
#include "dusk/randomizer/game/randomizer_context.hpp"
#endif
#include "d/d_attention.h"
#include "d/d_cc_s.h"
#include "d/d_event.h"
@@ -1269,14 +1273,6 @@ int dComIfGd_setShadow(u32 param_0, s8 param_1, J3DModel* param_2, cXyz* param_3
dKy_tevstr_c* param_9, s16 param_10, f32 param_11, TGXTexObj* param_12);
#if TARGET_PC
inline dSv_randomizer_c& dComIfG_getRandomizer() {
return g_dComIfG_gameInfo.info.mRandomizer;
}
inline BOOL dComIfG_isRandomizer() {
return g_dComIfG_gameInfo.info.mRandomizer.mActive;
}
void dComIfGs_setupRandomizerSave();
#endif
-15
View File
@@ -964,18 +964,6 @@ public:
/* 0x8 */ s8 m_no;
};
#if TARGET_PC
class dSv_randomizer_c {
public:
BOOL mActive{false};
std::unordered_map<std::string, std::unordered_map<u8, u8>> mTreasureChestOverrides{};
std::unordered_map<std::string, std::unordered_map<u8, u8>> mPoeOverrides{};
u8 mStartHour{0};
void clear();
};
#endif
class dSv_info_c {
public:
@@ -1059,9 +1047,6 @@ public:
#if DEBUG
/* 0xF80 */ flagFile_c mFlagFile;
#endif
#if TARGET_PC
/* 0xF38 */ dSv_randomizer_c mRandomizer;
#endif
}; // Size: 0xF38
class dSv_event_flag_c {
+8 -8
View File
@@ -1023,12 +1023,12 @@ void daAlink_c::setGetItemFace(u16 i_itemNo) {
|| i_itemNo == dItemNo_DROP_CONTAINER02_e
#if TARGET_PC
// Rando items for facial expressions
|| (dComIfG_isRandomizer() && i_itemNo == dItemNo_Randomizer_WOOD_STICK_e)
|| (dComIfG_isRandomizer() && i_itemNo == dItemNo_Randomizer_SWORD_e)
|| (dComIfG_isRandomizer() && i_itemNo == dItemNo_Randomizer_SHIELD_e)
|| (dComIfG_isRandomizer() && i_itemNo == dItemNo_Randomizer_MASTER_SWORD_e)
|| (dComIfG_isRandomizer() && i_itemNo == dItemNo_Randomizer_LIGHT_SWORD_e)
|| (dComIfG_isRandomizer() && i_itemNo == dItemNo_Randomizer_MAGIC_LV1_e)
|| (randomizer_IsActive() && i_itemNo == dItemNo_Randomizer_WOOD_STICK_e)
|| (randomizer_IsActive() && i_itemNo == dItemNo_Randomizer_SWORD_e)
|| (randomizer_IsActive() && i_itemNo == dItemNo_Randomizer_SHIELD_e)
|| (randomizer_IsActive() && i_itemNo == dItemNo_Randomizer_MASTER_SWORD_e)
|| (randomizer_IsActive() && i_itemNo == dItemNo_Randomizer_LIGHT_SWORD_e)
|| (randomizer_IsActive() && i_itemNo == dItemNo_Randomizer_MAGIC_LV1_e)
#endif
|| i_itemNo == dItemNo_DROP_CONTAINER03_e)
{
@@ -1037,7 +1037,7 @@ void daAlink_c::setGetItemFace(u16 i_itemNo) {
setFaceBasicTexture(FTANM_K_A);
setFaceBasicBck(dRes_ID_ALANM_BCK_FK_e);
#if TARGET_PC
} else if (i_itemNo == dItemNo_TOMATO_PUREE_e || i_itemNo == dItemNo_TASTE_e || (dComIfG_isRandomizer() && i_itemNo == dItemNo_Randomizer_FOOLISH_ITEM_e)) {
} else if (i_itemNo == dItemNo_TOMATO_PUREE_e || i_itemNo == dItemNo_TASTE_e || (randomizer_IsActive() && i_itemNo == dItemNo_Randomizer_FOOLISH_ITEM_e)) {
#else
} else if (i_itemNo == dItemNo_TOMATO_PUREE_e || i_itemNo == dItemNo_TASTE_e) {
#endif
@@ -2494,7 +2494,7 @@ void daAlink_c::setGetSubBgm(int i_itemNo) {
};
#if TARGET_PC
u32 se_type = dComIfG_isRandomizer() ? getSeTypeRandomizer[i_itemNo] : getSeType[i_itemNo];
u32 se_type = randomizer_IsActive() ? getSeTypeRandomizer[i_itemNo] : getSeType[i_itemNo];
#else
u32 se_type = getSeType[i_itemNo];
#endif
+1 -1
View File
@@ -533,7 +533,7 @@ int daDitem_c::execute() {
#if TARGET_PC
// Certain items use field models that are too big to fit in link's hands so we want to scale them down to fit.
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
switch (m_itemNo)
{
case dItemNo_Randomizer_MIRROR_PIECE_1_e:
+1 -1
View File
@@ -1270,7 +1270,7 @@ void daNpc_Fairy_c::AppearDemoCall() {
mEvtNo = EVT_APPEAR_50F_02;
}
#if TARGET_PC
} else if (dComIfGs_checkEmptyBottle() || dComIfG_isRandomizer()) {
} else if (dComIfGs_checkEmptyBottle() || randomizer_IsActive()) {
#else
} else if (dComIfGs_checkEmptyBottle()) {
#endif
+2 -2
View File
@@ -153,7 +153,7 @@ void daItem_c::CreateInit() {
show();
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
// Adjust item scale based on item ID
switch(m_itemNo)
{
@@ -230,7 +230,7 @@ void daItem_c::CreateInit() {
animPlay(1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f);
#if TARGET_PC
if (m_itemNo == dItemNo_BOOMERANG_e && !dComIfG_isRandomizer()) {
if (m_itemNo == dItemNo_BOOMERANG_e && !randomizer_IsActive()) {
#else
if (m_itemNo == dItemNo_BOOMERANG_e) {
#endif
+2 -2
View File
@@ -1787,11 +1787,11 @@ cPhs_Step daTbox_c::create1st() {
field_0x980 = home.angle.x;
#if TARGET_PC
// The upper 8 bits of home.angle.z hold the itemId
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
home.angle.z &= ~0xFF00;
auto stage = dComIfGp_getStartStageName();
auto tboxId = static_cast<u8>(getTboxNo());
home.angle.z |= dComIfG_getRandomizer().mTreasureChestOverrides[stage][tboxId] << 8;
home.angle.z |= randomizer_GetContext().mTreasureChestOverrides[stage][tboxId] << 8;
}
#endif
field_0x982 = home.angle.z;
+1 -1
View File
@@ -62,7 +62,7 @@ int CheckFieldItemCreateHeap(fopAc_ac_c* i_this) {
u8 item_no = a_this->getItemNo();
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
switch (item_no)
{
case dItemNo_Randomizer_EMPTY_BOTTLE_e:
+18 -79
View File
@@ -29,11 +29,10 @@
#if TARGET_PC
#include "dusk/randomizer/generator/randomizer.hpp"
#include "dusk/randomizer/generator/utility/yaml.hpp"
#include "dusk/randomizer/game/tools.h"
#include "dusk/randomizer/game/stages.h"
#include "dusk/randomizer/game/flags.h"
#include "dusk/randomizer//game/verify_item_functions.h"
#include "dusk/randomizer/game/randomizer_context.hpp"
#endif
@@ -173,7 +172,7 @@ int dComIfG_play_c::getLayerNo_common_common(const char* i_stageName, int i_room
#if TARGET_PC
// Special layer checks for randomizer
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
int stageID = getStageID(i_stageName);
bool condition = false;
bool darkIsClear = false;
@@ -2866,65 +2865,26 @@ int dComIfGd_setShadow(u32 param_0, s8 param_1, J3DModel* param_2, cXyz* param_3
#if TARGET_PC
void dComIfGs_setupRandomizerSave() {
randomizer::Randomizer randomizer;
if (randomizer.Generate())
throw std::runtime_error("Randomizer Generation Faild");
auto world = randomizer.GetWorlds()[0].get();
// Setup randomizer data
auto& randoData = g_dComIfG_gameInfo.info.mRandomizer;
randoData.mActive = TRUE;
for (const auto& location : world->GetAllLocations()) {
const auto& metaData = location->GetMetadata();
if (location->HasCategories("Chest")) {
const auto& stage = metaData[0]["Stage"].as<std::string>();
const auto& tboxId = metaData[0]["Tbox ID"].as<u8>();
const auto& itemId = location->GetCurrentItem()->GetID();
randoData.mTreasureChestOverrides[stage][tboxId] = itemId;
}
}
// Setup file based on randomizer data
auto& randoData = randomizer_GetContext();
randoData.mActive = true;
randoData.mCreatingSave = true;
// Set starting flags
auto startFlags = LoadYAML(RANDO_DATA_PATH "startflags.yaml");
// Event Flags
for (const auto& flagNode : startFlags["EventFlags"]) {
if (flagNode.IsScalar()) {
const auto& flag = flagNode.as<u16>();
dComIfGs_onEventBit(flag);
} else if (flagNode.IsMap()) {
const auto& condition = flagNode.begin()->first.as<std::string>();
if (world->EvaluateSettingCondition(condition)) {
DuskLog.debug("Setting flags for {}", condition);
for (const auto& conditionalFlag : flagNode.begin()->second) {
const auto& flag = conditionalFlag.as<u16>();
dComIfGs_onEventBit(flag);
}
}
for (const auto& flag : randoData.mStartEventFlags) {
dComIfGs_onEventBit(flag);
}
// Region Flags
for (const auto& [region, flags] : randoData.mStartRegionFlags) {
for (const auto& flag : flags) {
dComIfGs_onRegionFlag(region, flag);
}
}
// Region Flags
for (const auto& regionNode : startFlags["RegionFlags"]) {
const auto& region = regionNode.first.as<std::string>();
const auto& index = regionNode.second["Index"].as<int>();
const auto& flags = regionNode.second["Flags"];
DuskLog.debug("Setting region flags for {}", region);
// This seems kinda scuffed so maybe we change it later
for (const auto& flagNode : flags) {
if (flagNode.IsScalar()) {
const auto& flag = flagNode.as<int>();
dComIfGs_onRegionFlag(index, flag);
} else if (flagNode.IsMap()) {
const auto& condition = flagNode.begin()->first.as<std::string>();
if (world->EvaluateSettingCondition(condition)) {
for (const auto& conditionalFlag : flagNode.begin()->second) {
const auto& flag = conditionalFlag.as<int>();
dComIfGs_onRegionFlag(index, flag);
}
}
}
}
}
// Map bits (fills in overworld on map)
dComIfGs_setRegionBit(randoData.mMapBits);
// Other flags based on starting flags
if (dComIfGs_isEventBit(CLEARED_FARON_TWILIGHT))
@@ -2963,35 +2923,14 @@ void dComIfGs_setupRandomizerSave() {
dComIfGs_onTransformLV(3);
}
}
if (world->Setting("Unlock Map Regions") == "On")
{
int mapBits = 0x20;
if (world->Setting("Snowpeak Does Not Require Reekfish Scent") == "On") {mapBits |= 0x40;}
if (dComIfGs_isEventBit(CLEARED_LANAYRU_TWILIGHT)) {mapBits |= 0x10;}
if (dComIfGs_isEventBit(CLEARED_ELDIN_TWILIGHT)) {mapBits |= 0x08;}
if (dComIfGs_isEventBit(CLEARED_FARON_TWILIGHT)) {mapBits |= 0x04;}
if (world->Setting("Skip Prologue") == "On") {mapBits |= 0x02;}
dComIfGs_setRegionBit(mapBits);
}
// Set starting inventory
for (const auto& item: world->GetStartingItemPool()) {
execItemGet(static_cast<u8>(item->GetID()));
for (const auto& itemId: randoData.mStartingInventory) {
execItemGet(itemId);
}
// Set starting time of day
const auto startTimeSetting = world->Setting("Starting Time of Day");
if (startTimeSetting == "Morning")
randoData.mStartHour = 6;
else if (startTimeSetting == "Noon")
randoData.mStartHour = 12;
else if (startTimeSetting == "Evening")
randoData.mStartHour = 18;
else if (startTimeSetting == "Night")
randoData.mStartHour = 24;
DuskLog.debug("Created Rando Save");
randoData.mCreatingSave = false;
}
#endif
+2 -2
View File
@@ -1536,8 +1536,8 @@ void dFile_select_c::nameInput2() {
dComIfGs_setHorseName(mpName->getInputStrPtr());
mIsSelectEnd = true;
#if TARGET_PC
// Create a randomizer save if Z is being held
if (mDoCPd_c::getHoldZ(PAD_1)) {
// Create a randomizer save if one is selected
if (!randomizer_GetContext().mHash.empty()) {
dComIfGs_setupRandomizerSave();
}
#endif
+46 -46
View File
@@ -537,7 +537,7 @@ static void (*item_func_ptr_randomizer[256])() = {
inline void getItemFunc(u8 i_itemNo) {
dComIfGs_onItemFirstBit(i_itemNo);
#if TARGET_PC
(dComIfG_isRandomizer() ? item_func_ptr_randomizer : item_func_ptr)[i_itemNo]();
(randomizer_IsActive() ? item_func_ptr_randomizer : item_func_ptr)[i_itemNo]();
#else
item_func_ptr[i_itemNo]();
#endif
@@ -545,7 +545,7 @@ inline void getItemFunc(u8 i_itemNo) {
void execItemGet(u8 i_itemNo) {
#if TARGET_PC
if (dComIfG_isRandomizer())
if (randomizer_IsActive())
i_itemNo = verifyProgressiveItem(i_itemNo);
#endif
getItemFunc(i_itemNo);
@@ -1074,7 +1074,7 @@ static int (*item_getcheck_func_ptr_randomizer[256])() = {
inline int getCheckItemFunc(u8 i_itemNo) {
#if TARGET_PC
return (dComIfG_isRandomizer() ? item_getcheck_func_ptr_randomizer : item_getcheck_func_ptr)[i_itemNo]();
return (randomizer_IsActive() ? item_getcheck_func_ptr_randomizer : item_getcheck_func_ptr)[i_itemNo]();
#else
return item_getcheck_func_ptr[i_itemNo]();
#endif
@@ -1233,7 +1233,7 @@ void item_func_SMALL_KEY() {
void item_func_KAKERA_HEART() {
dComIfGp_setItemMaxLifeCount(1);
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
// TODO rando
/*
Pasting rando code until the framework has been updated
@@ -1259,7 +1259,7 @@ void item_func_UTUWA_HEART() {
int tmp = dStage_stagInfo_GetSaveTbl(stag_info);
dComIfGs_onStageLife();
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
// TODO rando
/*
Pasting rando code until the framework has been updated
@@ -1334,7 +1334,7 @@ void item_func_WEAR_ZORA() {}
void item_func_MAGIC_LV1() {
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
dComIfGs_onEventBit(0xD04); // Can transform at will
dComIfGs_onEventBit(0x501); // Midna Charge Unlocked
return;
@@ -1355,7 +1355,7 @@ void item_func_WALLET_LV1() {
void item_func_WALLET_LV2() {
dComIfGs_setWalletSize(BIG_WALLET);
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
// TODO rando
// Putting rando code here until the framework gets built:
/*
@@ -1386,7 +1386,7 @@ void item_func_WALLET_LV3() {
dComIfGs_setWalletSize(GIANT_WALLET);
#if TARGET_PC
// TODO rando
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
// Putting rando code here until the framework gets built:
/*
Basically we fill the wallet if desired and then we change the color of the rupee icon to red
@@ -1451,7 +1451,7 @@ void item_func_WOOD_STICK() {
dComIfGs_setCollectSword(COLLECT_WOODEN_SWORD);
dComIfGs_setSelectEquipSword(dItemNo_WOOD_STICK_e);
#if TARGET_PC
if (!dComIfG_isRandomizer())
if (!randomizer_IsActive())
#endif
dComIfGs_onSwitch(28, dComIfGp_roomControl_getStayNo());
}
@@ -1514,7 +1514,7 @@ void item_func_PACHINKO() {
void item_func_COPY_ROD_2() {
#if TARGET_PC
if (dComIfG_isRandomizer())
if (randomizer_IsActive())
dComIfGs_onEventBit(0x2580); // Power up dominion rod
else
#endif
@@ -1729,7 +1729,7 @@ void item_func_LV3_SOUP() {
void item_func_LETTER() {
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
if (dComIfGs_getItem(SLOT_21, true) != dItemNo_Randomizer_HORSE_FLUTE_e)
dComIfGs_setItem(SLOT_21, dItemNo_Randomizer_LETTER_e);
} else
@@ -1739,7 +1739,7 @@ void item_func_LETTER() {
void item_func_BILL() {
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
if (dComIfGs_getItem(SLOT_21, true) != dItemNo_Randomizer_HORSE_FLUTE_e)
dComIfGs_setItem(SLOT_21, dItemNo_Randomizer_BILL_e);
} else
@@ -1751,7 +1751,7 @@ void item_func_WOOD_STATUE() {
/* dSv_event_flag_c::F_283 - Hyrule Field - Get wood carving */
dComIfGs_onEventBit(dSv_event_flag_c::saveBitLabels[283]);
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
if (dComIfGs_getItem(SLOT_21, true) != dItemNo_Randomizer_HORSE_FLUTE_e)
dComIfGs_setItem(SLOT_21, dItemNo_Randomizer_WOOD_STATUE_e);
} else
@@ -1761,7 +1761,7 @@ void item_func_WOOD_STATUE() {
void item_func_IRIAS_PENDANT() {
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
if (dComIfGs_getItem(SLOT_21, true) != dItemNo_Randomizer_HORSE_FLUTE_e)
dComIfGs_setItem(SLOT_21, dItemNo_Randomizer_IRIAS_PENDANT_e);
} else
@@ -1771,7 +1771,7 @@ void item_func_IRIAS_PENDANT() {
void item_func_HORSE_FLUTE() {
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
if (dComIfGs_getItem(SLOT_21, true) != dItemNo_Randomizer_NONE_e)
dComIfGs_setItem(SLOT_21, dItemNo_Randomizer_HORSE_FLUTE_e);
} else
@@ -1837,7 +1837,7 @@ void item_func_LAKE_HYLIA_PORTAL() {
void item_func_RAFRELS_MEMO() {
#if TARGET_PC
if (dComIfG_isRandomizer())
if (randomizer_IsActive())
dComIfGs_setItem(SLOT_7, dItemNo_Randomizer_RAFRELS_MEMO_e);
else
#endif
@@ -1846,7 +1846,7 @@ void item_func_RAFRELS_MEMO() {
void item_func_ASHS_SCRIBBLING() {
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
if (!dComIfGs_isEventBit(0x3B80)) { // Got earring from Ralis
dComIfGs_setItem(SLOT_19, dItemNo_Randomizer_ASHS_SCRIBBLING_e);
}
@@ -1928,7 +1928,7 @@ void item_func_DROP_CONTAINER02() {
void item_func_DROP_CONTAINER03() {
dComIfGs_onLightDropGetFlag(LANAYRU_VESSEL);
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
dComIfGs_onEventBit(0x1E80); // Malo Mart Fundraising Starts
}
#endif
@@ -1938,7 +1938,7 @@ void item_func_FILLED_CONTAINER() {}
void item_func_MIRROR_PIECE_2() {
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
dComIfGs_onCollectMirror(1);
// TODO rando
/*
@@ -1955,7 +1955,7 @@ void item_func_MIRROR_PIECE_2() {
void item_func_MIRROR_PIECE_3() {
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
dComIfGs_onCollectMirror(2);
// TODO rando
/*
@@ -1972,7 +1972,7 @@ void item_func_MIRROR_PIECE_3() {
void item_func_MIRROR_PIECE_4() {
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
dComIfGs_onCollectMirror(3);
// TODO rando
/*
@@ -2144,7 +2144,7 @@ void item_func_FUSED_SHADOW_1() {
}
void item_func_FUSED_SHADOW_2() {
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
dComIfGs_onCollectCrystal(1);
/*
Adding rando code until framework is implemented
@@ -2158,7 +2158,7 @@ void item_func_FUSED_SHADOW_2() {
}
void item_func_FUSED_SHADOW_3() {
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
dComIfGs_onCollectCrystal(2);
/*
Adding rando code until framework is implemented
@@ -2179,7 +2179,7 @@ void item_func_FUSED_SHADOW_3() {
void item_func_MIRROR_PIECE_1() {
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
dComIfGs_onCollectMirror(0);
// TODO rando
/*
@@ -2197,7 +2197,7 @@ void item_func_MIRROR_PIECE_1() {
void item_func_POU_SPIRIT() {
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
dComIfGs_addPohSpiritNum();
// TODO rando
/*
@@ -2252,7 +2252,7 @@ void item_func_ANCIENT_DOCUMENT() {
void item_func_AIR_LETTER() {
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
u8 letterCount = dComIfGs_getAncientDocumentNum();
dComIfGs_setAncientDocumentNum(letterCount + 1);
} else
@@ -2262,7 +2262,7 @@ void item_func_AIR_LETTER() {
void item_func_ANCIENT_DOCUMENT2() {
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
dComIfGs_onEventBit(0x3B08); // Repaired Cannon at Lake
dComIfGs_setAncientDocumentNum(6);
}
@@ -2280,7 +2280,7 @@ void item_func_LINKS_SAVINGS() {
void item_func_SMALL_KEY2() {
#if TARGET_PC
if (dComIfG_isRandomizer())
if (randomizer_IsActive())
dComIfGs_onStageSwitch(0x2, 0x14); // Unlock North Faron Gate
else
#endif
@@ -2297,7 +2297,7 @@ void item_func_POU_FIRE4() {}
void item_func_BOSSRIDER_KEY() {
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
dComIfGs_onStageSwitch(0x3, 0x69); // Started Escort
dComIfGs_onStageSwitch(0x3, 0x65); // Followed Rutella to Graveyard
dComIfGs_onEventBit(0x840); // Started Zora Escort
@@ -2308,7 +2308,7 @@ void item_func_BOSSRIDER_KEY() {
void item_func_TOMATO_PUREE() {
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
dComIfGs_onEventBit(0x480); // Told Yeta about Pumpkin
dComIfGs_onEventBit(0x2); // Yeto put Pumpkin in Soup
dComIfGs_onEventBit(0x1440); // SPR Lobby Door Unlocked
@@ -2319,7 +2319,7 @@ void item_func_TOMATO_PUREE() {
void item_func_TASTE() {
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
dComIfGs_onEventBit(0x120); // Told Yeta about Cheese
dComIfGs_onEventBit(0x1); // Yeto put Pumpkin in Soup
dComIfGs_onEventBit(0x1420); // SPR Lobby west Door Unlocked
@@ -2330,7 +2330,7 @@ void item_func_TASTE() {
void item_func_LV5_BOSS_KEY() {
#if TARGET_PC
if (dComIfG_isRandomizer())
if (randomizer_IsActive())
dComIfGs_onDungeonItemBossKey(0x14);
else
#endif
@@ -2351,7 +2351,7 @@ void item_func_KEY_OF_CARAVAN() {}
void item_func_LV2_BOSS_KEY() {
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
dComIfGs_onDungeonItemBossKey(0x11);
execItemGet(dItemNo_Randomizer_L2_KEY_PIECES3_e);
} else
@@ -2361,7 +2361,7 @@ void item_func_LV2_BOSS_KEY() {
void item_func_KEY_OF_FILONE() {
#if TARGET_PC
if (dComIfG_isRandomizer())
if (randomizer_IsActive())
dComIfGs_onStageSwitch(0x2, 0xC); // Unlock Coro Gate
else
#endif
@@ -2564,7 +2564,7 @@ int item_getcheck_func_WEAR_ZORA() {
int item_getcheck_func_MAGIC_LV1() {
#if TARGET_PC
if (dComIfG_isRandomizer())
if (randomizer_IsActive())
return dComIfGs_isItemFirstBit(dItemNo_Randomizer_MAGIC_LV1_e);
#endif
return -1;
@@ -2580,7 +2580,7 @@ int item_getcheck_func_WALLET_LV1() {
int item_getcheck_func_WALLET_LV2() {
#if TARGET_PC
if (dComIfG_isRandomizer())
if (randomizer_IsActive())
return dComIfGs_isItemFirstBit(dItemNo_Randomizer_WALLET_LV2_e);
#endif
return -1;
@@ -2588,7 +2588,7 @@ int item_getcheck_func_WALLET_LV2() {
int item_getcheck_func_WALLET_LV3() {
#if TARGET_PC
if (dComIfG_isRandomizer())
if (randomizer_IsActive())
return dComIfGs_isItemFirstBit(dItemNo_Randomizer_WALLET_LV3_e);
#endif
return -1;
@@ -2855,7 +2855,7 @@ int item_getcheck_func_LV3_SOUP() {
int item_getcheck_func_LETTER() {
#if TARGET_PC
if (dComIfG_isRandomizer())
if (randomizer_IsActive())
return dComIfGs_isItemFirstBit(dItemNo_Randomizer_LETTER_e);
#endif
return dComIfGs_getItem(SLOT_21, true) == dItemNo_LETTER_e ? TRUE : FALSE;
@@ -2863,7 +2863,7 @@ int item_getcheck_func_LETTER() {
int item_getcheck_func_BILL() {
#if TARGET_PC
if (dComIfG_isRandomizer())
if (randomizer_IsActive())
return dComIfGs_isItemFirstBit(dItemNo_Randomizer_BILL_e);
#endif
return dComIfGs_getItem(SLOT_21, true) == dItemNo_BILL_e ? TRUE : FALSE;
@@ -2871,7 +2871,7 @@ int item_getcheck_func_BILL() {
int item_getcheck_func_WOOD_STATUE() {
#if TARGET_PC
if (dComIfG_isRandomizer())
if (randomizer_IsActive())
return dComIfGs_isItemFirstBit(dItemNo_Randomizer_WOOD_STATUE_e);
#endif
return dComIfGs_getItem(SLOT_21, true) == dItemNo_WOOD_STATUE_e ? TRUE : FALSE;
@@ -2879,7 +2879,7 @@ int item_getcheck_func_WOOD_STATUE() {
int item_getcheck_func_IRIAS_PENDANT() {
#if TARGET_PC
if (dComIfG_isRandomizer())
if (randomizer_IsActive())
return dComIfGs_isItemFirstBit(dItemNo_Randomizer_IRIAS_PENDANT_e);
#endif
return dComIfGs_getItem(SLOT_21, true) == dItemNo_IRIAS_PENDANT_e ? TRUE : FALSE;
@@ -2887,7 +2887,7 @@ int item_getcheck_func_IRIAS_PENDANT() {
int item_getcheck_func_HORSE_FLUTE() {
#if TARGET_PC
if (dComIfG_isRandomizer())
if (randomizer_IsActive())
return dComIfGs_isItemFirstBit(dItemNo_Randomizer_HORSE_FLUTE_e);
#endif
return dComIfGs_getItem(SLOT_22, true) == dItemNo_HORSE_FLUTE_e ? TRUE : FALSE;
@@ -2945,7 +2945,7 @@ int item_getcheck_func_FILLED_CONTAINER() {
int item_getcheck_func_MIRROR_PIECE_2() {
#if TARGET_PC
if (dComIfG_isRandomizer())
if (randomizer_IsActive())
return dComIfGs_isItemFirstBit(dItemNo_Randomizer_MIRROR_PIECE_2_e);
#endif
return -1;
@@ -2953,7 +2953,7 @@ int item_getcheck_func_MIRROR_PIECE_2() {
int item_getcheck_func_MIRROR_PIECE_3() {
#if TARGET_PC
if (dComIfG_isRandomizer())
if (randomizer_IsActive())
return dComIfGs_isItemFirstBit(dItemNo_Randomizer_MIRROR_PIECE_3_e);
#endif
return -1;
@@ -2961,7 +2961,7 @@ int item_getcheck_func_MIRROR_PIECE_3() {
int item_getcheck_func_MIRROR_PIECE_4() {
#if TARGET_PC
if (dComIfG_isRandomizer())
if (randomizer_IsActive())
return dComIfGs_isItemFirstBit(dItemNo_Randomizer_MIRROR_PIECE_4_e);
#endif
return -1;
@@ -3181,7 +3181,7 @@ int item_getcheck_func_POU_FIRE4() {
int item_getcheck_func_BOSSRIDER_KEY() {
#if TARGET_PC
if (dComIfG_isRandomizer())
if (randomizer_IsActive())
return dComIfGs_isItemFirstBit(dItemNo_Randomizer_BOSSRIDER_KEY_e);
#endif
return -1;
+3 -3
View File
@@ -1147,12 +1147,12 @@ dItem_itemInfo dItem_data::item_info_randomizer[] = {
};
dItem_itemResource* dItem_data_getItemResource() {
return dComIfG_isRandomizer() ? dItem_data::item_resource_randomizer : dItem_data::item_resource;
return randomizer_IsActive() ? dItem_data::item_resource_randomizer : dItem_data::item_resource;
}
dItem_fieldItemResource* dItem_data_getFieldItemResource() {
return dComIfG_isRandomizer() ? dItem_data::field_item_res_randomizer : dItem_data::field_item_res;
return randomizer_IsActive() ? dItem_data::field_item_res_randomizer : dItem_data::field_item_res;
}
dItem_itemInfo* dItem_data_getItemInfo() {
return dComIfG_isRandomizer() ? dItem_data::item_info_randomizer : dItem_data::item_info;
return randomizer_IsActive() ? dItem_data::item_info_randomizer : dItem_data::item_info;
}
#endif
+2 -2
View File
@@ -1620,7 +1620,7 @@ u8 dMenu_Ring_c::getItemNum(u8 i_slotNo) {
case dItemNo_Randomizer_ANCIENT_DOCUMENT_e:
case dItemNo_Randomizer_AIR_LETTER_e:
case dItemNo_Randomizer_ANCIENT_DOCUMENT2_e:
if (dComIfG_isRandomizer())
if (randomizer_IsActive())
ret = dComIfGs_getAncientDocumentNum();
break;
#endif
@@ -1667,7 +1667,7 @@ u8 dMenu_Ring_c::getItemMaxNum(u8 i_slotNo) {
case dItemNo_Randomizer_ANCIENT_DOCUMENT_e:
case dItemNo_Randomizer_AIR_LETTER_e:
case dItemNo_Randomizer_ANCIENT_DOCUMENT2_e:
if (dComIfG_isRandomizer())
if (randomizer_IsActive())
ret = 6;
break;
#endif
+1
View File
@@ -41,6 +41,7 @@
#if TARGET_PC
#include "dusk/memory.h"
#include "dusk/randomizer/game/tools.h"
#endif
#if DEBUG
+4 -9
View File
@@ -449,7 +449,7 @@ void dSv_player_item_c::setLineUpItem() {
#if TARGET_PC
// Allow rando to use all item slots
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
mItemSlots[23] = 7;
}
#endif
@@ -1205,7 +1205,7 @@ void dSv_memBit_c::onDungeonItem(int i_no) {
JUT_ASSERT(2969, 0 <= i_no && i_no < DSV_MEMBIT_ENUM_MAX);
#if TARGET_PC
// Don't use the stage life collection flag for rando
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
if (i_no == STAGE_LIFE) {
return;
}
@@ -1251,7 +1251,7 @@ void dSv_memBit_c::offDungeonItem(int i_no) {
JUT_ASSERT(2983, 0 <= i_no && i_no < DSV_MEMBIT_ENUM_MAX);
#if TARGET_PC
// Don't use the stage life collection flag for rando
if (dComIfG_isRandomizer() && i_no == STAGE_LIFE) {
if (randomizer_IsActive() && i_no == STAGE_LIFE) {
return;
}
#endif
@@ -1261,7 +1261,7 @@ void dSv_memBit_c::offDungeonItem(int i_no) {
s32 dSv_memBit_c::isDungeonItem(int i_no) const {
JUT_ASSERT(2998, 0 <= i_no && i_no < DSV_MEMBIT_ENUM_MAX);
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
// Don't use the stage life collection flag for rando
if (i_no == STAGE_LIFE) {
return FALSE;
@@ -1644,11 +1644,6 @@ dSv_memory2_c* dSv_save_c::getSave2(int i_stage2No) {
return &mSave2[i_stage2No];
}
void dSv_randomizer_c::clear() {
mTreasureChestOverrides.clear();
mPoeOverrides.clear();
mActive = FALSE;
}
void dSv_info_c::getSave(int i_stageNo) {
JUT_ASSERT(4133, 0 <= i_stageNo && i_stageNo < dSv_save_c::STAGE_MAX);
+2 -2
View File
@@ -2931,9 +2931,9 @@ int dStage_changeScene4Event(int i_exitId, s8 room_no, int i_wipe, bool param_3,
if (timeH < 31) {
#if TARGET_PC
// If randomizer is active and we're loading the first spawn, set our starting time of day
if (dComIfG_isRandomizer() && strcmp(scls_info->mStage, "F_SP103") == 0 &&
if (randomizer_IsActive() && strcmp(scls_info->mStage, "F_SP103") == 0 &&
scls_info->mRoom == 1 && scls_info->mStart == 1)
timeH = dComIfG_getRandomizer().mStartHour;
timeH = randomizer_GetContext().mStartHour;
#endif
dKy_set_nexttime(15.0f * timeH);
}
+3
View File
@@ -239,6 +239,7 @@ namespace dusk {
if (showMenu && ImGui::BeginMainMenuBar()) {
m_menuGame.draw();
m_menuEnhancements.draw();
m_menuRandomizer.draw();
m_menuTools.draw();
ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 80.0f * ImGuiScale());
@@ -276,6 +277,8 @@ namespace dusk {
m_menuTools.ShowSaveEditor();
}
m_menuTools.ShowStateShare();
m_menuRandomizer.windowRandoStats();
m_menuRandomizer.windowRandoGeneration();
DuskDebugPad(); // temporary, remove later
// Only show cursor when menu or any windows are open
+2
View File
@@ -10,6 +10,7 @@
#include "ImGuiMenuEnhancements.hpp"
#include "ImGuiMenuGame.hpp"
#include "ImGuiMenuTools.hpp"
#include "ImGuiMenuRandomizer.hpp"
#include "ImGuiPreLaunchWindow.hpp"
#include "imgui.h"
@@ -39,6 +40,7 @@ private:
ImGuiFirstRunPreset m_firstRunPreset;
ImGuiMenuGame m_menuGame;
ImGuiMenuEnhancements m_menuEnhancements;
ImGuiMenuRandomizer m_menuRandomizer;
ImGuiPreLaunchWindow m_preLaunchWindow;
// Keep always last
+304
View File
@@ -0,0 +1,304 @@
#include "imgui.h"
#include "ImGuiConsole.hpp"
#include "ImGuiMenuRandomizer.hpp"
#include <mutex>
#include "dusk/app_info.hpp"
#include "dusk/logging.h"
#include "dusk/randomizer/game/randomizer_context.hpp"
#include "dusk/randomizer/game/tools.h"
#include "dusk/randomizer/generator/randomizer.hpp"
#include "SDL3/SDL_filesystem.h"
#include <thread>
#include <filesystem>
namespace dusk {
static bool generatingSeed = false;
static std::string generationStatusMsg{};
static std::mutex generationStatusMsgMutex{};
void GenerateSeed() {
std::lock_guard lock(generationStatusMsgMutex);
const auto result = SDL_GetPrefPath(dusk::OrgName, dusk::AppName);
if (!result) {
DuskLog.fatal("Unable to get PrefPath: {}", SDL_GetError());
}
randomizer::Randomizer r;
r.SetBaseOutputPath(result);
auto generationResult = r.Generate();
if (generationResult.has_value()) {
generationStatusMsg = fmt::format("Generation failed with the following error:\n{}", generationResult.value());
return;
}
auto& world = r.GetWorlds()[0];
RandomizerContext randoData{};
// Chest overrides
for (const auto& location : world->GetAllLocations()) {
const auto& metaData = location->GetMetadata();
if (location->HasCategories("Chest")) {
const auto& stage = metaData[0]["Stage"].as<std::string>();
const auto& tboxId = metaData[0]["Tbox ID"].as<u8>();
const auto& itemId = location->GetCurrentItem()->GetID();
randoData.mTreasureChestOverrides[stage][tboxId] = itemId;
}
}
// Set starting inventory
for (const auto& item: world->GetStartingItemPool()) {
randoData.mStartingInventory.push_back(item->GetID());
}
// Set starting flags
auto startFlags = LoadYAML(RANDO_DATA_PATH "startflags.yaml");
// Event Flags
for (const auto& flagNode : startFlags["EventFlags"]) {
if (flagNode.IsScalar()) {
const auto& flag = flagNode.as<u16>();
randoData.mStartEventFlags.push_back(flag);
} else if (flagNode.IsMap()) {
const auto& condition = flagNode.begin()->first.as<std::string>();
if (world->EvaluateSettingCondition(condition)) {
DuskLog.debug("Setting flags for {}", condition);
for (const auto& conditionalFlag : flagNode.begin()->second) {
const auto& flag = conditionalFlag.as<u16>();
randoData.mStartEventFlags.push_back(flag);
}
}
}
}
// Region Flags
for (const auto& regionNode : startFlags["RegionFlags"]) {
const auto& region = regionNode.first.as<std::string>();
const auto& index = regionNode.second["Index"].as<int>();
const auto& flags = regionNode.second["Flags"];
DuskLog.debug("Setting region flags for {}", region);
// This seems kinda scuffed so maybe we change it later
for (const auto& flagNode : flags) {
if (flagNode.IsScalar()) {
const auto& flag = flagNode.as<int>();
randoData.mStartRegionFlags[index].push_back(flag);
} else if (flagNode.IsMap()) {
const auto& condition = flagNode.begin()->first.as<std::string>();
if (world->EvaluateSettingCondition(condition)) {
for (const auto& conditionalFlag : flagNode.begin()->second) {
const auto& flag = conditionalFlag.as<int>();
randoData.mStartRegionFlags[index].push_back(flag);
}
}
}
}
}
if (world->Setting("Unlock Map Regions") == "On")
{
auto& bits = randoData.mMapBits;
bits = 0x20;
if (world->Setting("Snowpeak Does Not Require Reekfish Scent") == "On") {bits |= 0x40;}
if (world->Setting("Lanayru Twilight Cleared") == "On") {bits |= 0x10;}
if (world->Setting("Eldin Twilight Cleared") == "On") {bits |= 0x08;}
if (world->Setting("Faron Twilight Cleared") == "On") {bits |= 0x04;}
if (world->Setting("Skip Prologue") == "On") {bits |= 0x02;}
}
// Set starting time of day
const auto startTimeSetting = world->Setting("Starting Time of Day");
if (startTimeSetting == "Morning")
randoData.mStartHour = 6;
else if (startTimeSetting == "Noon")
randoData.mStartHour = 12;
else if (startTimeSetting == "Evening")
randoData.mStartHour = 18;
else if (startTimeSetting == "Night")
randoData.mStartHour = 24;
randoData.mHash = r.GetConfig().GetHash();
auto writeToFileResult = randoData.WriteToFile();
if (writeToFileResult.has_value()) {
generationStatusMsg =
fmt::format("Failed to write seed data. Reason: {}", writeToFileResult.value());
return;
}
generationStatusMsg = fmt::format("Seed generated! Hash: {}", randoData.mHash);
}
static void StartSeedGeneration() {
if (generatingSeed) {
return;
}
generatingSeed = true;
GenerateSeed();
generatingSeed = false;
DuskLog.debug("{}", generationStatusMsg);
}
ImGuiMenuRandomizer::ImGuiMenuRandomizer() {}
void ImGuiMenuRandomizer::draw() {
if (ImGui::BeginMenu("Randomizer")) {
if (ImGui::BeginMenu("Generate")) {
if (ImGui::MenuItem("Generate Seed", nullptr, false, !generatingSeed)) {
std::thread randoGenerationThread(StartSeedGeneration);
randoGenerationThread.detach();
m_showRandoGeneration = true;
}
ImGui::EndMenu();
}
std::string loadSeedText = "Load Seed";
if (!playerIsOnTitleScreen()) {
loadSeedText += " (Must be on title screen)";
}
if (ImGui::BeginMenu(loadSeedText.c_str(), playerIsOnTitleScreen())) {
std::string seedDirectory =
std::string(SDL_GetPrefPath(dusk::OrgName, dusk::AppName)) + "randomizer/seeds";
std::list<std::string> hashes{};
for (const auto& entry : std::filesystem::directory_iterator(seedDirectory)) {
if (entry.is_directory()) {
hashes.push_back(entry.path().filename().string());
}
}
if (hashes.empty()) {
if (ImGui::MenuItem("No seeds generated")) {}
}
for (const auto& hash : hashes) {
std::string name = hash;
if (randomizer_GetContext().mHash == hash) {
name += " (Current Seed)";
}
if (ImGui::MenuItem(name.c_str())) {
if (!randomizer_IsActive()) {
randomizer_GetContext() = RandomizerContext();
randomizer_GetContext().LoadFromHash(hash);
}
}
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Delete Seed")) {
std::string seedDirectory =
std::string(SDL_GetPrefPath(dusk::OrgName, dusk::AppName)) + "randomizer/seeds";
std::list<std::string> hashes{};
for (const auto& entry : std::filesystem::directory_iterator(seedDirectory)) {
if (entry.is_directory()) {
hashes.push_back(entry.path().filename().string());
}
}
if (hashes.empty()) {
if (ImGui::MenuItem("No seeds generated")) {}
}
for (const auto& hash : hashes) {
std::string name = hash;
if (randomizer_GetContext().mHash == hash) {
name += " (Current Seed)";
}
if (ImGui::MenuItem(name.c_str(), nullptr, false, playerIsOnTitleScreen() || randomizer_GetContext().mHash != hash)) {
const std::string hashDirectory = seedDirectory + "/" + hash;
if (randomizer_GetContext().mHash != hash) {
std::filesystem::remove_all(hashDirectory);
} else if (!randomizer_IsActive()){
// If the user selected the currently seed, but it's not active, we'll allow the delete
std::filesystem::remove_all(hashDirectory);
randomizer_GetContext() = RandomizerContext();
}
}
}
ImGui::EndMenu();
}
std::string name = "Deactive Randomizer";
if (!playerIsOnTitleScreen()) {
name += " (Must be on title screen)";
}
if (ImGui::MenuItem(name.c_str(), nullptr, false, playerIsOnTitleScreen())) {
// Reset the main randomizer context only if we're not active
if (!randomizer_IsActive()) {
randomizer_GetContext() = RandomizerContext();
}
}
ImGui::Checkbox("Show Rando Stats", &m_showRandoStats);
ImGui::EndMenu();
}
}
void ImGuiMenuRandomizer::windowRandoGeneration() {
if (!m_showRandoGeneration) {
return;
}
ImGuiWindowFlags windowFlags =
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_AlwaysAutoResize;
ImGui::SetNextWindowBgAlpha(0.65f);
if (!ImGui::Begin("Randomizer Generation", &m_showRandoGeneration, windowFlags)) {
ImGui::End();
return;
}
// Print "Generating..." until the seed finishes generating (at which point it will release
// the lock on the mutex)
std::string generationText = "Generating Randomizer Seed...";
if (generationStatusMsgMutex.try_lock()) {
generationText = generationStatusMsg;
generationStatusMsgMutex.unlock();
}
ImGui::Text(generationText.c_str());
ImGui::End();
}
void ImGuiMenuRandomizer::windowRandoStats() {
if (!m_showRandoStats) {
return;
}
ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav;
ImGui::SetNextWindowBgAlpha(0.65f);
if (ImGui::Begin("Rando Stats", nullptr, windowFlags)) {
const char* seed = randomizer_GetContext().mHash.empty() ? "None" : randomizer_GetContext().mHash.c_str();
ImGui::Text("Current Seed: %s", seed);
ImGui::Text("Status: ");
ImGui::SameLine();
if (randomizer_IsActive()) {
ImGui::TextColored(ImVec4(0.f, 1.f, 0.f, 1.f), "ACTIVE");
} else {
ImGui::TextColored(ImVec4(1.f, 0.f, 0.f, 1.f), "NOT ACTIVE");
}
}
ImGui::End();
}
} // namespace dusk
+21
View File
@@ -0,0 +1,21 @@
#ifndef DUSK_IMGUI_MENU_RANDOMIZER_HPP
#define DUSK_IMGUI_MENU_RANDOMIZER_HPP
namespace dusk {
class ImGuiMenuRandomizer {
public:
ImGuiMenuRandomizer();
void draw();
void windowRandoStats();
void windowRandoGeneration();
private:
bool m_showRandoStats{false};
bool m_showRandoGeneration{false};
};
}
#endif //DUSK_IMGUI_MENU_RANDOMIZER_HPP
@@ -0,0 +1,109 @@
#include "randomizer_context.hpp"
#include "../generator/utility/yaml.hpp"
#include "dusk/app_info.hpp"
#include "dusk/logging.h"
#include "dusk/main.h"
#include "dusk/randomizer/game/tools.h"
#include "SDL3/SDL_filesystem.h"
#include <fstream>
RandomizerContext& randomizer_GetContext() {
static RandomizerContext instance;
return instance;
}
bool randomizer_IsActive() {
return dusk::IsGameLaunched && (!playerIsOnTitleScreen() || randomizer_GetContext().mCreatingSave) && randomizer_GetContext().mActive;
}
std::optional<std::string> RandomizerContext::WriteToFile() {
std::ofstream seedData(this->GetSeedDataPath());
if (!seedData.is_open()) {
return "Could not open seed data file";
}
YAML::Node out{};
// NOTE: When dumping u8s, they must be converted to u16s (or higher), otherwise they get dumped
// as single characters and not numbers
out["mStartEventFlags"] = this->mStartEventFlags;
for (const auto& [region, flags] : this->mStartRegionFlags) {
const std::list<u16> u16Flags(flags.begin(), flags.end());
out["mStartRegionFlags"][static_cast<u16>(region)] = u16Flags;
}
const std::list<u16> u16Inventory(this->mStartingInventory.begin(), this->mStartingInventory.end());
out["mStartingInventory"] = u16Inventory;
for (const auto& [stageName, chestOverride] : this->mTreasureChestOverrides) {
for (const auto& [tboxId, itemId] : chestOverride) {
out["mTreasureChestOverrides"][stageName][static_cast<u16>(tboxId)] = static_cast<u16>(itemId);
}
}
out["mStartHour"] = static_cast<u16>(this->mStartHour);
out["mMapBits"] = static_cast<u16>(this->mMapBits);
seedData << YAML::Dump(out);
seedData.close();
return std::nullopt;
}
std::optional<std::string> RandomizerContext::LoadFromHash(const std::string& hash) {
this->mHash = hash;
auto in = LoadYAML(this->GetSeedDataPath());
// Event flags
for (const auto& flag : in["mStartEventFlags"]) {
this->mStartEventFlags.push_back(flag.as<u16>());
}
// Region Flags
for (const auto& regionNode : in["mStartRegionFlags"]) {
const auto& regionId = regionNode.first.as<u8>();
for (const auto& flag : regionNode.second) {
this->mStartRegionFlags[regionId].push_back(flag.as<u8>());
}
}
// Starting inventory
for (const auto& itemId : in["mStartingInventory"]) {
this->mStartingInventory.push_back(itemId.as<u8>());
}
// Chest overrides
for (const auto& stageNode : in["mTreasureChestOverrides"]) {
const auto& stageName = stageNode.first.as<std::string>();
// Single nodes with a zero in their key will get dumped as sequences
if (stageNode.second.IsSequence()) {
this->mTreasureChestOverrides[stageName][0] = stageNode.second[0].as<u8>();
} else {
for (const auto& chestItemPair : stageNode.second) {
auto tboxId = chestItemPair.first.as<u8>();
auto itemId = chestItemPair.second.as<u8>();
this->mTreasureChestOverrides[stageName][tboxId] = itemId;
}
}
}
// Starting hour
this->mStartHour = in["mStartHour"].as<u8>();
// Starting map bits
this->mMapBits = in["mMapBits"].as<u8>();
DuskLog.debug("Loaded Randomizer Seed {}", this->mHash);
return std::nullopt;
}
std::string RandomizerContext::GetSeedDataPath() const {
return std::string(SDL_GetPrefPath(dusk::OrgName, dusk::AppName)) + "randomizer/seeds/" + this->mHash + "/seed.dat";
}
@@ -0,0 +1,36 @@
#ifndef DUSK_RANDOMIZER_CONTEXT_HPP
#define DUSK_RANDOMIZER_CONTEXT_HPP
#include <dolphin/types.h>
#include <optional>
#include <string>
#include <unordered_map>
class RandomizerContext {
public:
RandomizerContext() = default;
bool mActive{false};
bool mCreatingSave{false};
u32 mSeedID{0};
std::string mHash{""};
std::list<u16> mStartEventFlags{};
std::unordered_map<u8, std::list<u8>> mStartRegionFlags{};
std::list<u8> mStartingInventory{};
std::unordered_map<std::string, std::unordered_map<u8, u8>> mTreasureChestOverrides{};
std::unordered_map<std::string, std::unordered_map<u8, u8>> mPoeOverrides{};
u8 mStartHour{0};
u8 mMapBits{};
std::optional<std::string> WriteToFile();
std::optional<std::string> LoadFromHash(const std::string& hash);
std::string GetSeedDataPath() const;
};
RandomizerContext& randomizer_GetContext();
bool randomizer_IsActive();
#endif //DUSK_RANDOMIZER_CONTEXT_HPP
+6
View File
@@ -106,4 +106,10 @@ int getStageID(const char* stage)
}
// Didn't find the current stage for some reason
return -1;
}
bool playerIsOnTitleScreen() {
// Player is either on title screen movie stage (S_MV000) or on title screen map layer 10
return strcmp(dComIfGp_getStartStageName(), "S_MV000") == 0 ||
(strcmp(dComIfGp_getStartStageName(), "F_SP102") == 0 && dComIfG_play_c::getLayerNo(0) == 10);
}
+1
View File
@@ -8,3 +8,4 @@ u8 setNextWarashibeItem();
void offWarashibeItem(u8 item);
int initCreatePlayerItem(u32 item, u32 flag, const cXyz* pos, int roomNo, const csXyz* angle, const cXyz* scale);
int getStageID(const char* stage);
bool playerIsOnTitleScreen();
@@ -45,20 +45,20 @@ namespace randomizer::logic::spoiler_log
log << YAML::Dump(randomizer->GetConfig().SettingsToYaml()) << std::endl;
}
void GenerateSpoilerLog(randomizer::Randomizer* randomizer)
void GenerateSpoilerLog(Randomizer* randomizer)
{
randomizer::utility::platform::Log("Generating Spoiler Log");
utility::platform::Log("Generating Spoiler Log");
// Create logs folder if it doesn't exist
if (!randomizer::utility::file::dirExists(LOGS_PATH))
// Create folders
if (!utility::file::dirExists(randomizer->GetSeedOutputPath()))
{
randomizer::utility::file::create_directories(LOGS_PATH);
utility::file::create_directories(randomizer->GetSeedOutputPath());
}
auto& config = randomizer->GetConfig();
auto& worlds = randomizer->GetWorlds();
std::string filepath = std::string(LOGS_PATH) + config.GetHash() + " Spoiler Log.txt";
std::string filepath = std::string(randomizer->GetSeedOutputPath()) + config.GetHash() + " Spoiler Log.txt";
std::ofstream spoilerLog;
spoilerLog.open(filepath);
@@ -228,18 +228,18 @@ namespace randomizer::logic::spoiler_log
spoilerLog.close();
randomizer::utility::platform::Log("Wrote spoiler log to " + filepath);
utility::platform::Log("Wrote spoiler log to " + filepath);
}
void GenerateAntiSpoilerLog(randomizer::Randomizer* randomizer)
void GenerateAntiSpoilerLog(Randomizer* randomizer)
{
// Create logs folder if it doesn't exist
if (!randomizer::utility::file::dirExists(LOGS_PATH))
if (!utility::file::dirExists(randomizer->GetSeedOutputPath()))
{
randomizer::utility::file::create_directories(LOGS_PATH);
utility::file::create_directories(randomizer->GetSeedOutputPath());
}
std::string filepath = std::string(LOGS_PATH) + randomizer->GetConfig().GetHash() + " Anti-Spoiler Log.txt";
std::string filepath = std::string(randomizer->GetSeedOutputPath()) + randomizer->GetConfig().GetHash() + " Anti-Spoiler Log.txt";
std::ofstream antiSpoilerLog;
antiSpoilerLog.open(filepath);
+9 -4
View File
@@ -15,7 +15,7 @@
namespace randomizer
{
int Randomizer::Generate()
std::optional<std::string> Randomizer::Generate()
{
try
{
@@ -25,17 +25,17 @@ namespace randomizer
{
std::cout << "============================================================" << std::endl;
std::cout << "The following exception occured: " << e.what() << std::endl;
return 1;
return e.what();
}
return 0;
return std::nullopt;
}
void Randomizer::GenerateWorlds()
{
utility::time::ScopedTimer<"Seed generation took ", std::chrono::milliseconds> timer;
this->_worlds.clear();
this->_config.LoadFromFile(SETTINGS_PATH, PREFERENCES_PATH);
this->_config.LoadFromFile(this->GetBaseOutputPath() + "settings.yaml", this->GetBaseOutputPath() + "preferences.yaml");
utility::platform::Log(std::string("Seed: ") + this->_config.GetSeed());
@@ -111,4 +111,9 @@ namespace randomizer
}
logic::spoiler_log::GenerateAntiSpoilerLog(this);
}
std::string Randomizer::GetSeedOutputPath()
{
return this->_baseOutputPath + "seeds/" + this->_config.GetHash() + "/";
}
} // namespace randomizer
+10 -2
View File
@@ -2,6 +2,8 @@
#include "logic/world.hpp"
#include <optional>
namespace randomizer
{
class Randomizer
@@ -12,9 +14,9 @@ namespace randomizer
/**
* @brief Generates a complete randomizer seed
*
* @return 0 if no errors. 1 if there were errors
* @return a std::optional<std::string> containing a message if there was an error.
*/
int Generate();
std::optional<std::string> Generate();
void GenerateWorlds();
auto& GetConfig() { return this->_config; }
@@ -26,6 +28,10 @@ namespace randomizer
auto& GetPlaythroughSpheres() { return this->_playthroughSpheres; }
auto& GetEntranceSpheres() { return this->_entranceSpheres; }
std::string GetSeedOutputPath();
const std::string& GetBaseOutputPath() const { return this->_baseOutputPath; };
void SetBaseOutputPath(const std::string& path) { this->_baseOutputPath = path + "randomizer/"; };
private:
seedgen::config::Config _config{};
logic::world::WorldPool _worlds{};
@@ -37,5 +43,7 @@ namespace randomizer
// Playthrough data
std::list<std::list<logic::location::Location*>> _playthroughSpheres{};
std::list<std::list<logic::entrance::Entrance*>> _entranceSpheres{};
std::string _baseOutputPath{RANDO_SAVE_PATH};
};
} // namespace randomizer
@@ -72,7 +72,7 @@ namespace randomizer::seedgen::config
// If the option doesn't exist, revert to default and rewrite later if necessary
if (settingInfo->GetIndexOfOption(settingOption) == -1)
{
randomizer::utility::platform::Log(std::string("Setting \"") + settingName + "\" has no option \"" +
utility::platform::Log(std::string("Setting \"") + settingName + "\" has no option \"" +
settingOption + "\". Reverting to default \"" +
settingInfo->GetDefaultOption() + "\"");
settingOption = settingInfo->GetDefaultOption();
@@ -148,7 +148,7 @@ namespace randomizer::seedgen::config
// If the option doesn't exist, revert to default and rewrite later if necessary
if (preferenceInfo->GetIndexOfOption(preferenceOption) == -1)
{
randomizer::utility::platform::Log(std::string("Preference \"") + preferenceName + " has no option \"" +
utility::platform::Log(std::string("Preference \"") + preferenceName + " has no option \"" +
preferenceOption + "\". Reverting to default \"" +
preferenceInfo->GetDefaultOption() + "\"");
preferenceOption = preferenceInfo->GetDefaultOption();
@@ -182,7 +182,7 @@ namespace randomizer::seedgen::config
{
settings.InsertSetting(settingName,
settings::Setting(settingInfo.get(), settingInfo->GetDefaultOption()));
randomizer::utility::platform::Log(std::string("Added missing setting \"") + settingName + "\"");
utility::platform::Log(std::string("Added missing setting \"") + settingName + "\"");
if (settingInfo->GetType() == settings::Type::STANDARD)
{
rewriteSettings = true;
@@ -196,7 +196,7 @@ namespace randomizer::seedgen::config
if (!settingsTree["Seed"])
{
this->_seed = seed::GenerateSeed();
randomizer::utility::platform::Log("Seed is missing. Generated new seed.");
utility::platform::Log("Seed is missing. Generated new seed.");
rewriteSettings = true;
}
if (!settingsTree["Plandomizer"] || !settingsTree["Generate Spoiler Log"] || !settingsTree["Starting Inventory"] ||
@@ -212,12 +212,12 @@ namespace randomizer::seedgen::config
// Rewrite files if deemed necessary
if (allowRewrite && rewriteSettings)
{
randomizer::utility::platform::Log(std::string("Rewriting ") + settingsPath.generic_string());
utility::platform::Log(std::string("Rewriting ") + settingsPath.generic_string());
this->WriteSettingsToFile(settingsPath);
}
if (allowRewrite && rewritePreferences)
{
randomizer::utility::platform::Log(std::string("Rewriting ") + preferencesPath.generic_string());
utility::platform::Log(std::string("Rewriting ") + preferencesPath.generic_string());
this->WritePreferencesToFile(preferencesPath);
}
}
@@ -335,7 +335,7 @@ namespace randomizer::seedgen::config
int WriteDefaultSettings(const fspath& settingsPath)
{
randomizer::utility::platform::Log("Creating Default Settings");
utility::platform::Log("Creating Default Settings");
std::ofstream settingsFile(settingsPath);
if (settingsFile.is_open() == false)
{
@@ -369,7 +369,7 @@ namespace randomizer::seedgen::config
int WriteDefaultPreferences(const fspath& preferencesPath)
{
randomizer::utility::platform::Log("Creating Default Preferences");
utility::platform::Log("Creating Default Preferences");
std::ofstream preferencesFile(preferencesPath);
if (preferencesFile.is_open() == false)
{
@@ -402,7 +402,7 @@ namespace randomizer::seedgen::config
{
// Seed with system time incase we have to choose random preferences during seeding
auto seed = static_cast<uint32_t>(std::random_device {}());
randomizer::utility::random::RandomInit(seed);
utility::random::RandomInit(seed);
// Seed the rng using a combination of the seed and standard settings
std::string hashStr = config.GetSeed();
@@ -444,7 +444,7 @@ namespace randomizer::seedgen::config
if (config.IsUsingPlandomizer())
{
std::string plandomizerContents;
auto retVal = randomizer::utility::file::GetContents(config.GetPlandomizerPath(), plandomizerContents);
auto retVal = utility::file::GetContents(config.GetPlandomizerPath(), plandomizerContents);
if (!ignoreInvalidPlandomizer && retVal != 0)
{
LOG_TO_ERROR("Could not read plandomizer file at \"" + config.GetPlandomizerPath().generic_string() + "\"");
@@ -460,7 +460,7 @@ namespace randomizer::seedgen::config
}
const size_t integerSeed = zng_crc32(0L, reinterpret_cast<const uint8_t*>(hashStr.data()), hashStr.length());
randomizer::utility::random::RandomInit(integerSeed);
utility::random::RandomInit(integerSeed);
return 0;
}
+2 -3
View File
@@ -1,12 +1,11 @@
cmake_minimum_required(VERSION 3.16)
set(RANDOMIZER_ONLY "0" CACHE STRING "Runs only the randomizer generator")
set(RANDO_SAVE_PATH "${CMAKE_BINARY_DIR}/randomizer")
set(RANDO_SAVE_PATH "${CMAKE_BINARY_DIR}/randomizer/")
set(GAME_COMPILE_DEFS ${GAME_COMPILE_DEFS}
RANDOMIZER_ONLY=${RANDOMIZER_ONLY}
RANDO_SAVE_PATH="${RANDO_SAVE_PATH}"
LOGS_PATH="${RANDO_SAVE_PATH}/logs/"
RANDO_SAVE_PATH="${RANDO_SAVE_PATH}"
RANDO_DATA_PATH="${CMAKE_SOURCE_DIR}/src/dusk/randomizer/generator/data/")
if(RANDO_ERROR_LOG)
+3 -3
View File
@@ -1395,7 +1395,7 @@ fpc_ProcID fopAcM_createItemForPresentDemo(cXyz const* i_pos, int i_itemNo, u8 p
int i_itemBitNo, int i_roomNo, csXyz const* i_angle,
cXyz const* i_scale) {
#if TARGET_PC
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
i_itemNo = verifyProgressiveItem(i_itemNo);
}
#endif
@@ -1624,7 +1624,7 @@ fpc_ProcID fopAcM_createItemForBoss(const cXyz* i_pos, int i_itemNo, int i_roomN
return fpcM_ERROR_PROCESS_ID_e;
}
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
i_itemNo = verifyProgressiveItem(i_itemNo);
if (i_itemNo == dItemNo_Randomizer_UTAWA_HEART_e)
{
@@ -1665,7 +1665,7 @@ fpc_ProcID fopAcM_createItemForMidBoss(const cXyz* i_pos, int i_itemNo, int i_ro
}
// If we are fighting Ook in randomizer, we want to handle the boomerang check a different way.
if (dComIfG_isRandomizer()) {
if (randomizer_IsActive()) {
if (daAlink_c::checkStageName("D_MN05B")) {
i_itemNo = verifyProgressiveItem(i_itemNo);
return initCreatePlayerItem(i_itemNo, 0xFF, i_pos, i_roomNo, i_angle, i_scale);