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

This commit is contained in:
CraftyBoss
2026-06-24 03:22:56 -07:00
15 changed files with 198 additions and 38 deletions
+12
View File
@@ -12,6 +12,11 @@
#include "d/d_com_inf_game.h"
#include <cstring>
#if TARGET_PC
#include "dusk/randomizer/game/stages.h"
#include "dusk/randomizer/game/tools.h"
#endif
static int daObj_Gb_Draw(obj_gb_class* i_this) {
g_env_light.settingTevStruct(0x10, &i_this->current.pos, &i_this->tevStr);
g_env_light.setLightTevColorType_MAJI(i_this->mModel, &i_this->tevStr);
@@ -169,6 +174,13 @@ static int useHeapInit(fopAc_ac_c* actor) {
static int daObj_Gb_Create(fopAc_ac_c* actor) {
fopAcM_ct(actor, obj_gb_class);
obj_gb_class* i_this = (obj_gb_class*)actor;
#if TARGET_PC
// Only spawn the added wall in randomizer if it should exist
if (randomizer_IsActive() && getStageID() == StageIDs::Mirror_Chamber &&
!randomizer_mirrorChamberWallShouldExist()) {
return cPhs_ERROR_e;
}
#endif
int rv = dComIfG_resLoad(&i_this->mPhase, "Obj_gb");
if (rv == cPhs_COMPLEATE_e) {
+7
View File
@@ -2013,6 +2013,13 @@ stage_arrow_class* dComIfGp_getRoomArrow(int i_roomNo) {
void dComIfGp_setNextStage(char const* i_stage, s16 i_point, s8 i_roomNo, s8 i_layer,
f32 i_lastSpeed, u32 i_lastMode, int i_setPoint, s8 i_wipe,
s16 i_lastAngle, int param_9, int i_wipeSpeedT) {
#if TARGET_PC
// In rando, override this entrance if applicable
if (randomizer_IsActive()) {
randomizer_checkAndOverrideEntranceData(i_stage, i_roomNo, i_point, i_layer);
}
#endif
if (i_layer >= 15) {
i_layer = -1;
}
@@ -125,6 +125,10 @@ std::optional<std::string> RandomizerContext::WriteToFile(const fspath& path) {
textData << YAML::EndMap;
textData << YAML::EndMap;
for (const auto& [key, override] : mEntranceOverrides) {
out["mEntranceOverrides"][key] = std::bit_cast<int>(override);
}
seedData << YAML::Dump(out);
seedData << '\n' << textData.c_str();
seedData.close();
@@ -286,6 +290,13 @@ std::optional<std::string> RandomizerContext::LoadFromPath(const fspath& path) {
}
}
// Entrance Overrides
for (const auto& entranceNode : in["mEntranceOverrides"]) {
auto key = entranceNode.first.as<int>();
auto override = std::bit_cast<EntranceOverride>(entranceNode.second.as<int>());
this->mEntranceOverrides[key] = override;
}
dusk::ui::push_toast(dusk::ui::Toast{
.title = "Randomizer",
.content = fmt::format("Loaded Randomizer Seed {}", this->mHash),
@@ -299,7 +310,7 @@ std::filesystem::path RandomizerContext::GetSeedDataPath() const {
}
int RandomizerContext::SettingToEnum(const std::string& settingName) {
static const std::unordered_map<std::string, int> nameToEnum = {
static const std::map<std::string, int> nameToEnum = {
{"Hyrule Barrier Dungeons", HYRULE_BARRIER_DUNGEONS},
{"Hyrule Barrier Requirements", HYRULE_BARRIER_REQUIREMENTS},
{"Hyrule Barrier Fused Shadows", HYRULE_BARRIER_FUSED_SHADOWS},
@@ -317,6 +328,7 @@ int RandomizerContext::SettingToEnum(const std::string& settingName) {
{"Skip Minor Cutscenes", SKIP_MINOR_CUTSCENES},
{"Skip Major Cutscenes", SKIP_MAJOR_CUTSCENES},
{"Skip Bridge Donation", SKIP_BRIDGE_DONATION},
{"Mirror Chamber Access", MIRROR_CHAMBER_ACCESS},
};
if (nameToEnum.contains(settingName)) {
@@ -327,7 +339,7 @@ int RandomizerContext::SettingToEnum(const std::string& settingName) {
}
int RandomizerContext::OptionToEnum(const std::string& optionName) {
static const std::unordered_map<std::string, int> nameToEnum = {
static const std::map<std::string, int> nameToEnum = {
{"On", ON},
{"Off", OFF},
{"None", NONE},
@@ -342,6 +354,8 @@ int RandomizerContext::OptionToEnum(const std::string& optionName) {
{"Ordon Sword", ORDON_SWORD},
{"Master Sword", MASTER_SWORD},
{"Light Sword", LIGHT_SWORD},
{"Closed", CLOSED},
{"Barrier", BARRIER},
};
if (nameToEnum.contains(optionName)) {
@@ -539,7 +553,7 @@ static void updateGoalFlags() {
}
}
// Palace of Twlight Access
// Palace of Twilight Access
if (!dComIfGs_isEventBit(FIXED_THE_MIRROR_OF_TWILIGHT)) {
bool openPalace = false;
switch (settings[RandomizerContext::PALACE_OF_TWILIGHT_REQUIREMENTS]) {
@@ -808,6 +822,21 @@ int randomizer_getItemAtLocation(const std::string& locationName) {
return randomizer_GetContext().mItemLocations[locationName].itemId;
}
void randomizer_checkAndOverrideEntranceData(const char*& stageName, s8& roomNo, s16& pointNo, s8& mapLayer) {
RandomizerContext::EntranceOverride override = {
static_cast<u8>(getStageID(stageName)), roomNo, static_cast<s8>(pointNo), mapLayer
};
int key = std::bit_cast<int>(override);
if (randomizer_GetContext().mEntranceOverrides.contains(key)) {
auto& newOverride = randomizer_GetContext().mEntranceOverrides[key];
stageName = allStages[newOverride.stageId];
pointNo = newOverride.pointNo;
roomNo = newOverride.roomNo;
mapLayer = newOverride.mapLayer;
}
}
static void randomizer_setTempFlag(RandomizerContext::itemLocationData data) {
// If stage is 0xFF, then this is an event flag
if (data.stage == 0xFF) {
@@ -871,6 +900,12 @@ bool randomizer_checkTempleOfTimeRequirement() {
return false;
}
bool randomizer_mirrorChamberWallShouldExist() {
auto mirrorChamberAccess = randomizer_GetContext().mSettings[RandomizerContext::MIRROR_CHAMBER_ACCESS];
return mirrorChamberAccess == RandomizerContext::CLOSED ||
(mirrorChamberAccess == RandomizerContext::BARRIER && !dComIfGs_isStageBossEnemy(0x13));
}
u8 randomizer_getRandomFoolishItemModelID() {
static constexpr auto foolishItemModels = std::to_array<u8>({
dItemNo_Randomizer_ARMOR_e,
@@ -1219,7 +1254,7 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) {
const auto& stageName = stageNode.first.as<std::string>();
for (const auto& roomNode : stageNode.second) {
u8 roomNo{};
// Special value for
// Special value for actors always on the stage and not just one specific room
if (roomNode.first.as<std::string>() == "Stage") {
roomNo = RandomizerContext::ROOM_STAGE;
} else {
@@ -1331,6 +1366,27 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) {
}
}
// Entrance Overrides
if (world->Setting("Mirror Chamber Access") == "Closed") {
// Set exiting the Arbiter's Grounds Boss Room to spawn at the Arbiter's Grounds entrance
// if mirror chamber access is closed
RandomizerContext::EntranceOverride original = {
StageIDs::Mirror_Chamber,
4,
0,
-1
};
RandomizerContext::EntranceOverride override = {
StageIDs::Bulblin_Camp,
3,
3,
-1
};
randoData.mEntranceOverrides[std::bit_cast<int>(original)] = override;
}
return std::move(randoData);
}
@@ -70,14 +70,15 @@ public:
// Map of language -> map of key -> string
std::unordered_map<int, std::unordered_map<u32, std::string>> mTextOverrides{};
// TODO: hook this up to generator data
struct {
// for now use hardcoded values for this
std::string mapName = "F_SP103"; // (Ordon) Outside Link's House
int pointNo = 1;
int roomNo = 1;
int mapLayer = -1;
} mStartLocation;
struct EntranceOverride {
u8 stageId = 0xFF;
s8 roomNo = -1;
s8 pointNo = -1;
s8 mapLayer = -1;
};
// keyed by stageId << 24 | pointNo << 16 | roomNo << 8 | mapLayer
std::unordered_map<int, EntranceOverride> mEntranceOverrides{};
std::optional<std::string> WriteToFile();
std::optional<std::string> WriteToFile(const fspath& path);;
@@ -103,6 +104,7 @@ public:
SKIP_MINOR_CUTSCENES,
SKIP_MAJOR_CUTSCENES,
SKIP_BRIDGE_DONATION,
MIRROR_CHAMBER_ACCESS,
};
enum Options {
@@ -120,6 +122,8 @@ public:
ORDON_SWORD,
MASTER_SWORD,
LIGHT_SWORD,
BARRIER,
CLOSED,
};
static int SettingToEnum(const std::string& settingName);
@@ -208,6 +212,10 @@ bool randomizer_IsActive();
int randomizer_getItemAtLocation(const std::string& locationName);
/*
* @brief Overrides the given entrance paramaters if an override exists for them
*/
void randomizer_checkAndOverrideEntranceData(const char*& i_Name, s8& i_RoomNo, s16& i_Point, s8& i_Layer);
/*
* @brief Puts the associated flag into the randomizer state's temporary flag
* variable. This allows the tracker/Archipelago to know a location has been checked
@@ -219,6 +227,8 @@ void randomizer_setTempFlagForFLWOverride(u32 key);
bool randomizer_checkTempleOfTimeRequirement();
bool randomizer_mirrorChamberWallShouldExist();
u8 randomizer_getRandomFoolishItemModelID();
/**
@@ -5,9 +5,9 @@
- Type: Spawn
Forward:
Connection: Links Spawn -> Outside Links House
Stage: -1
Room: -1
Spawn: ""
Stage: 43
Room: 1
Spawn: "01"
Spawn Type: ""
Parameters: ""
State: "FF"
@@ -449,11 +449,11 @@
Return:
Connection: Arbiters Grounds Boss Room -> Mirror Chamber Lower
Alias: Arbiters Grounds Boss Room -> Mirror Chamber
Stage: -1
Room: -1
Spawn: ""
Spawn Type: ""
Parameters: ""
Stage: 60
Room: 4
Spawn: "00"
Spawn Type: "10"
Parameters: "501F"
State: "FF"
- Type: Boss
@@ -57,7 +57,16 @@
event: 42
parameters: 0x00000000
next node index: 0x10C
#5: # zel_05.bmg
5: # zel_05.bmg
# Patch Gor Liggs to not set the flag for the third key shard
# Change event index 17 to event index 42 which does nothing
- index: 0x258
type: event
event: 42
parameters: 0x00000000
next node index: 0x1AC
#6: # zel_06.bmg
#7: # zel_07.bmg
#8: # zel_08.bmg
@@ -3470,6 +3470,28 @@ F_SP124:
layers:
- 0
# Mirror Chamber
F_SP125:
# Room 4 - Main Chamber
4:
# Add barrier to prevent players from going back to the Arbiters Boss Room
# depending on settings
- action: add
name: Obj_gb
parameters: 0x800F0601
position:
x: 1794.0
y: 2523.0
z: -17400.0
angle:
x: 0xFF7F
y: 0x0000
z: 0x0000
set id: 0xFFFF
layers:
- 0
- 1
# Upper Zora's River
F_SP126:
# Room 0 - Main area
@@ -79,6 +79,15 @@
- Closed: "Midna will block the player from leaving Faron Woods until Forest Temple is completed."
- Open: "Midna will not prevent the player from leaving Faron Woods."
- Name: Mirror Chamber Access
Need In Game: True
Tracker Important: True
Default Option: Open
Options:
- Open: "The entrance is open and operates like normal. If you start with the Mirror Chamber Portal, you can access the Stallord boss fight without going through Arbiter's Grounds."
- Barrier: "A barrier is placed in front of the Mirror Chamber entrance and goes away once Stallord is defeated."
- Closed: "The Mirror Chamber is isolated from the world and cannot be reached from the Stallord boss room. To access it, players will either need the portal or access from Palace of Twilight."
######################
## Item Pool ##
######################
@@ -126,7 +126,7 @@
Can Warp: True
Exits:
Mirror Chamber Upper: Nothing
Arbiters Grounds Boss Room: Nothing
Arbiters Grounds Boss Room: Mirror_Chamber_Access == Open or (Mirror_Chamber_Access == Barrier and 'Can_Complete_Arbiters_Grounds')
- Name: Mirror Chamber Upper
Map Sector: Desert Province
@@ -102,12 +102,14 @@
Snowpeak Summit Lower: Nothing
- Name: Snowpeak Ruins East Door Interior
Region: Snowpeak Ruins
Can Transform: Never
Exits:
Snowpeak Ruins East Door Exterior: Can_Open_Doors
Snowpeak Ruins Entrance: Nothing
- Name: Snowpeak Ruins West Door Interior
Region: Snowpeak Ruins
Can Transform: Never
Exits:
Snowpeak Ruins West Door Exterior: Can_Open_Doors
+1 -1
View File
@@ -250,7 +250,7 @@ namespace randomizer::logic::area
this->AddHintRegion(region);
LOG_TO_DEBUG("Assigned \"" + region + "\" as hint region to \"" + this->GetName() + "\"");
// Also assign any loactions in this area to the dungeon if there are any dungeon regions
// Also assign any locations in this area to the dungeon if there are any dungeon regions
if (dungeons.contains(region))
{
auto locAccs = this->GetLocations();
@@ -423,6 +423,15 @@ namespace randomizer::logic::item_pool
startingItems["Castle Town Portal"] = 1;
}
// Automatically give players the Mirror Chamber Portal if Mirror Chamber Access is closed
// and they aren't both randomizing and decoupling dungeon entrances. Otherwise, there's no
// way to access the chamber
if (world->Setting("Mirror Chamber Access") == "Closed" &&
!(world->Setting("Randomize Dungeon Entrances") == "On" && world->Setting("Decouple Entrances") == "On"))
{
startingItems["Mirror Chamber Portal"] = 1;
}
// Add each item to the world's _startingItemPool and erase it from the regular _itemPool
for (const auto& [itemName, count] : startingItems)
{
@@ -65,19 +65,19 @@ namespace randomizer::logic::spoiler_log
LogBasicInfo(spoilerLog, randomizer);
// Gather worlds with starting inventories
std::list<world::World*> worldswithStartingInventories = {};
std::list<world::World*> worldsWithStartingInventories = {};
for (const auto& world : worlds)
{
if (!world->GetStartingItemPool().empty())
{
worldswithStartingInventories.push_back(world.get());
worldsWithStartingInventories.push_back(world.get());
}
}
// Print starting inventories if there are any
if (!worldswithStartingInventories.empty())
if (!worldsWithStartingInventories.empty())
{
spoilerLog << std::endl << "All Starting Items:" << std::endl;
for (const auto& world : worldswithStartingInventories)
for (const auto& world : worldsWithStartingInventories)
{
spoilerLog << " World " << world->GetID() << ":" << std::endl;
for (const auto& item : world->GetStartingItemPool())
@@ -87,7 +87,31 @@ namespace randomizer::logic::spoiler_log
}
}
// TODO: Print required dungeons
// Gather worlds with required dungeons
std::list<world::World*> worldsWithRequiredDungeons = {};
for (const auto& world : worlds)
{
for (const auto& [dungeonName, dungeon] : world->GetDungeonTable()) {
if (dungeon->IsRequired()) {
worldsWithRequiredDungeons.push_back(world.get());
break;
}
}
}
// Print required dungeons if there are any
if (!worldsWithRequiredDungeons.empty())
{
spoilerLog << std::endl << "Required Dungeons:" << std::endl;
for (const auto& world : worldsWithRequiredDungeons)
{
spoilerLog << " World " << world->GetID() << ":" << std::endl;
for (const auto& [dungeonName, dungeon] : world->GetDungeonTable()) {
if (dungeon->IsRequired()) {
spoilerLog << " - " << dungeonName << std::endl;
}
}
}
}
// Get name lengths for pretty formatting
size_t longestNameLength = 0;
@@ -728,22 +728,22 @@ namespace randomizer::logic::world
for (auto& [areaName, area] : this->_areaTable)
{
area->AssignHintRegionsAndDungeonLocations();
}
for (auto& [areaName, area] : this->_areaTable)
{
// Also assign dungeons their starting entrance
for (const auto& exit : area->GetExits())
{
auto parentRegions = exit->GetParentArea()->GetHintRegions();
auto connectedRegions = exit->GetConnectedArea()->GetHintRegions();
if (!parentRegions.contains("None"))
for (auto& [dungeonName, dungeon] : this->_dungeons)
{
for (auto& [dungeonName, dungeon] : this->_dungeons)
// If this exit leads into a dungeon and its parent area is not part of the dungeon
// then this is the entrance that leads into the dungeon
if (connectedRegions.contains(dungeonName) && !parentRegions.contains(dungeonName))
{
// If this exit leads into a dungeon and its parent area is not part of the dungeon
// then this is the entrance that leads into the dungeon
if (connectedRegions.contains(dungeonName) && !parentRegions.contains(dungeonName))
{
dungeon->AddStartingEntrance(exit);
}
dungeon->AddStartingEntrance(exit);
}
}
}
+2 -2
View File
@@ -926,6 +926,7 @@ RandomizerWindow::RandomizerWindow(dFile_select_c* fileSelect /*= nullptr*/) : m
});
rando_config_group(leftPane, rightPane, "Palace of Twilight Requirements");
rando_config_group(leftPane, rightPane, "Faron Woods Logic");
rando_config_group(leftPane, rightPane, "Mirror Chamber Access");
// leftPane.add_section("World (TODO)");
@@ -1220,8 +1221,7 @@ RandomizerWindow::RandomizerWindow(dFile_select_c* fileSelect /*= nullptr*/) : m
leftPane.register_control(leftPane.add_button("Warp to Start").on_pressed([] {
mDoAud_seStartMenu(kSoundClick);
auto& locData = randomizer_GetContext().mStartLocation;
dComIfGp_setNextStage(locData.mapName.c_str(), locData.pointNo, locData.roomNo, locData.mapLayer);
dComIfGp_setNextStage("F_SP103", 1, 1, -1);
}), rightPane, [](Pane& pane) {
pane.clear();
pane.add_rml("Respawns the player at their appropriate starting location.");