Basic Entrance Rando

This commit is contained in:
jdflyer
2026-08-14 12:56:40 -07:00
parent 162829a8cf
commit 08cda98337
10 changed files with 145 additions and 49 deletions
+1
View File
@@ -1,5 +1,6 @@
cmake_minimum_required(VERSION 3.25)
project(randomizer CXX)
set(CMAKE_CXX_STANDARD 23)
if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
set(DUSK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../.." CACHE PATH "Path to dusk source root")
@@ -356,4 +356,31 @@ namespace randomizer::logic::entrance
return {parentAreaName, connectedAreaName};
}
bool Entrance::SetGameInfo(const YAML::Node& node) {
bool ret = true;
std::string stageIdString = node["Stage"].as<std::string>();
if (stageIdString == "null") {
stageIdString = "-1";
ret = false;
}
int stageId = std::stoi(stageIdString);
if (stageId == -1) {
stageId = 0xFF;
}
SetStageId(static_cast<uint8_t>(stageId));
std::string roomNoString = node["Room"].as<std::string>();
if (roomNoString == "null") {
roomNoString = "-1";
ret = false;
}
SetRoomNo(static_cast<int8_t>(std::stoi(roomNoString)));
SetPointNo(static_cast<int16_t>(std::stoi("0x0"+node["Spawn"].as<std::string>(),nullptr,16)));
int layer = std::stoi("0x0"+node["State"].as<std::string>(),nullptr,16);
if (layer == 0xFF) {
layer = -1;
}
SetLayerNo(static_cast<int8_t>(layer));
return ret;
}
} // namespace randomizer::logic::entrance
@@ -5,6 +5,8 @@
#include <unordered_set>
#include <map>
#include <string>
#include <cstdint>
#include "../utility/yaml.hpp"
// Forward Declarations
namespace randomizer::logic::area
@@ -148,6 +150,16 @@ namespace randomizer::logic::entrance
*/
Entrance* AssumeReachable();
void SetStageId(uint8_t stageId) { _stageId = stageId; }
void SetRoomNo(int8_t roomNo) { _roomNo = roomNo; }
void SetLayerNo(int8_t layerNo) { _layerNo = layerNo; }
void SetPointNo(int16_t pointNo) { _pointNo = pointNo; }
uint8_t GetStageId() const { return _stageId; }
int8_t GetRoomNo() const { return _roomNo; }
int8_t GetLayerNo() const { return _layerNo; }
int16_t GetPointNo() const { return _pointNo; }
bool SetGameInfo(const YAML::Node& node);
private:
int _id = -1;
area::Area* _parentArea = nullptr;
@@ -159,6 +171,11 @@ namespace randomizer::logic::entrance
std::string _alias = "";
world::World* _world = nullptr;
uint8_t _stageId = 0xFF;
int8_t _roomNo = -1;
int8_t _layerNo = -1;
int16_t _pointNo = -1;
/**
* @brief The local requirement for this entrance assuming we have access to its parent area.
*/
@@ -65,7 +65,7 @@ namespace randomizer::logic::entrance_shuffle
auto forwardEntrance = world->GetEntrance(forwardEntry["Connection"].as<std::string>());
forwardEntrance->SetType(type);
// TODO: Set actual entrance data
forwardEntrance->SetGameInfo(forwardEntry);
forwardEntrance->SetID(world->GetNewEntranceID());
forwardEntrance->SetPrimary(true);
forwardEntrance->SetAlias(
@@ -78,7 +78,7 @@ namespace randomizer::logic::entrance_shuffle
auto returnEntrance = world->GetEntrance(returnEntry["Connection"].as<std::string>());
returnEntrance->SetType(type);
// TODO: Set actual entrance data
returnEntrance->SetGameInfo(returnEntry);
returnEntrance->SetID(world->GetNewEntranceID());
returnEntrance->SetAlias(
returnEntry["Alias"] ? returnEntry["Alias"].as<std::string>() : "");
@@ -471,7 +471,7 @@ namespace randomizer::logic::entrance_shuffle
// 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;
int retries = 10000;
while (retries > 0)
{
std::unordered_map<Entrance*, Entrance*> rollbacks = {};
@@ -4,6 +4,7 @@
#include "../item.hpp"
#include <iterator>
#include <cstdint>
BitVector::BitVector(const std::list<int>& bits)
{
@@ -10,6 +10,7 @@
#include <set>
#include <memory>
#include <stdexcept>
#include <cstdint>
namespace randomizer::seedgen::settings
{
+9 -4
View File
@@ -45,8 +45,12 @@ DEFINE_HOOK(&dSv_player_item_c::setLineUpItem, dSv_player_item_c__setLineUpItem)
DEFINE_HOOK(&dSv_info_c::onSwitch, dSv_info_c__onSwitch);
/*DEFINE_HOOK_SYMBOL("__Z21dComIfGp_setNextStagePKcsaafjiasii",
void(char const*, s16, s8, s8, f32, u32, int, s8, s16, int, int), setNextStage);*/
#ifdef _MSVC_LANG
#define setNextStage_sig "?dComIfGp_setNextStage@@YAXPEBDFCCMIHCFHH@Z"
#else
#define setNextStage_sig "_Z21dComIfGp_setNextStagePKcsaafjiasii"
#endif
DEFINE_HOOK_SYMBOL(setNextStage_sig, void(char const *, s16, s8, s8, f32, u32, int, s8, s16, int, int), setNextStage);
DEFINE_HOOK_SYMBOL("daObj_Gb_Create", int(fopAc_ac_c*), ObjGb_Create);
@@ -1433,7 +1437,7 @@ ModResult initialize() {
ADD_HOOK_PRE(dSv_info_c__onSwitch, hookPreSaveInfoOnSwitch);
//ADD_HOOK_PRE(setNextStage, hookPreSetNextStage);
ADD_HOOK_PRE(setNextStage, hookPreSetNextStage);
ADD_HOOK_PRE(ObjGb_Create, hookPreObjGbCreate);
@@ -1491,6 +1495,7 @@ ModResult uninstall() {
mods::hook::uninstall<dSv_player_item_c__setLineUpItem>(svc_hook);
mods::hook::uninstall<dSv_info_c__onSwitch>(svc_hook);
mods::hook::uninstall<setNextStage>();
mods::hook::uninstall<ObjGb_Create>(svc_hook);
@@ -1524,4 +1529,4 @@ ModResult uninstall() {
return MOD_OK;
}
}
}
+50 -30
View File
@@ -13,6 +13,7 @@
#include "../generator/randomizer.hpp"
#include "../generator/utility/text.hpp"
#include "../generator/utility/string.hpp"
#include "../generator/logic/entrance_shuffle.hpp"
#include <fstream>
#include <mods/svc/log.hpp>
@@ -136,11 +137,11 @@ std::optional<std::string> RandomizerContext::WriteToFile() {
}
for (const auto& [key, override] : mEntranceOverrides) {
out["mEntranceOverrides"][key] = std::bit_cast<int>(override);
out["mEntranceOverrides"][std::bit_cast<uint64_t>(key)] = std::bit_cast<uint64_t>(override);
}
for (const auto& [key, override] : mReturnToPlaceOverrides) {
out["mReturnToPlaceOverrides"][key] = std::bit_cast<int>(override);
out["mReturnToPlaceOverrides"][key] = std::bit_cast<uint64_t>(override);
}
seedData << YAML::Dump(out);
@@ -320,15 +321,15 @@ std::optional<std::string> RandomizerContext::LoadFromHash(const std::string& ha
// Entrance Overrides
for (const auto& entranceNode : in["mEntranceOverrides"]) {
auto key = entranceNode.first.as<int>();
auto override = std::bit_cast<EntranceOverride>(entranceNode.second.as<int>());
const auto key = std::bit_cast<EntranceOverride>(entranceNode.first.as<uint64_t>());;
const auto override = std::bit_cast<EntranceOverride>(entranceNode.second.as<uint64_t>());
this->mEntranceOverrides[key] = override;
}
// Return to Place Overrides
for (const auto& entranceNode : in["mReturnToPlaceOverrides"]) {
auto key = entranceNode.first.as<int>();
auto override = std::bit_cast<EntranceOverride>(entranceNode.second.as<int>());
const auto override = std::bit_cast<EntranceOverride>(entranceNode.second.as<uint64_t>());
this->mReturnToPlaceOverrides[key] = override;
}
@@ -862,16 +863,21 @@ int randomizer_getItemAtLocation(const std::string& locationName) {
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
};
.stageId = static_cast<u8>(getStageID(stageName)), .roomNo = roomNo, .mapLayer = mapLayer, .pointNo = static_cast<s16>(pointNo)};
int key = std::bit_cast<int>(override);
if (randomizer_GetContext().mEntranceOverrides.contains(key)) {
auto& newOverride = randomizer_GetContext().mEntranceOverrides[key];
// Debugging: Dump mEntranceOverrides
// for (const auto& [key,val] : randomizer_GetContext().mEntranceOverrides) {
// mods::log::info("({},{},{},{}) -> ({},{},{},{})",key.stageId,key.roomNo,key.mapLayer,key.pointNo,val.stageId,val.roomNo,val.mapLayer,val.pointNo);
// }
if (randomizer_GetContext().mEntranceOverrides.contains(override)) {
auto& newOverride = randomizer_GetContext().mEntranceOverrides[override];
mods::log::debug("Original Stage:{}, {}, {}, {}",stageName,roomNo,pointNo,mapLayer);
stageName = allStages[newOverride.stageId];
pointNo = newOverride.pointNo;
roomNo = newOverride.roomNo;
mapLayer = newOverride.mapLayer;
mods::log::debug("New Stage:{}, {}, {}, {}",stageName,roomNo,pointNo,mapLayer);
}
}
@@ -1728,34 +1734,34 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) {
// 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
.stageId = StageIDs::Mirror_Chamber,
.roomNo = 4,
.mapLayer = -1,
.pointNo = 0,
};
RandomizerContext::EntranceOverride override = {
StageIDs::Bulblin_Camp,
3,
3,
-1
.stageId = StageIDs::Bulblin_Camp,
.roomNo = 3,
.mapLayer = -1,
.pointNo = 3,
};
randoData.mEntranceOverrides[std::bit_cast<int>(original)] = override;
randoData.mEntranceOverrides[original] = override;
}
// Vanilla Return to Place Overrides. Will need to change when boss/miniboss ER is implemented
static const std::list<std::pair<std::list<int>, RandomizerContext::EntranceOverride>> defaultPlaceOverrides{
{{Forest_Temple, Ook, Diababa}, {Forest_Temple, 22, 0, -1}},
{{Goron_Mines, Dangoro, Fyrus}, {Goron_Mines, 1, 0, -1}},
{{Lakebed_Temple, Deku_Toad, Morpheel}, {Lakebed_Temple, 0, 0, -1}},
{{Arbiters_Grounds, Death_Sword, Stallord}, {Arbiters_Grounds, 0, 0, -1}},
{{Snowpeak_Ruins, Darkhammer, Blizzeta}, {Snowpeak_Ruins, 0, 0, -1}},
{{Temple_of_Time, Darknut, Armogohma}, {Temple_of_Time, 0, 0, -1}},
{{City_in_the_Sky, Aeralfos, Argorok}, {City_in_the_Sky, 0, 3, -1}},
static const std::vector<std::pair<std::vector<int>, RandomizerContext::EntranceOverride>> defaultPlaceOverrides{
{{Forest_Temple, Ook, Diababa}, {.stageId = Forest_Temple, .roomNo = 22, .mapLayer = -1, .pointNo = 0}},
{{Goron_Mines, Dangoro, Fyrus}, {.stageId = Goron_Mines, .roomNo = 1, .mapLayer = -1, .pointNo = 0}},
{{Lakebed_Temple, Deku_Toad, Morpheel}, {.stageId = Lakebed_Temple, .roomNo = 0, .mapLayer = -1, .pointNo = 0}},
{{Arbiters_Grounds, Death_Sword, Stallord}, {.stageId = Arbiters_Grounds, .roomNo = 0, .mapLayer = -1, .pointNo = 0}},
{{Snowpeak_Ruins, Darkhammer, Blizzeta}, {.stageId = Snowpeak_Ruins, .roomNo = 0, .mapLayer = -1, .pointNo = 0}},
{{Temple_of_Time, Darknut, Armogohma}, {.stageId = Temple_of_Time, .roomNo = 0, .mapLayer = -1, .pointNo = 0}},
{{City_in_the_Sky, Aeralfos, Argorok}, {.stageId = City_in_the_Sky, .roomNo = 0, .mapLayer = -1, .pointNo = 3}},
{{Palace_of_Twilight, Phantom_Zant_1,
Phantom_Zant_2, Zant_Main_Room, Zant_Fight}, {Palace_of_Twilight, 0, 0, -1}},
{{Hyrule_Castle, Ganondorf_Castle, Ganondorf_Field}, {Hyrule_Castle, 11, 0, -1}},
Phantom_Zant_2, Zant_Main_Room, Zant_Fight}, {.stageId = Palace_of_Twilight, .roomNo = 0, .mapLayer = -1, .pointNo = 0}},
{{Hyrule_Castle, Ganondorf_Castle, Ganondorf_Field}, {.stageId = Hyrule_Castle, .roomNo = 11, .mapLayer = -1, .pointNo = 0}},
};
// Return to Place Overrides
@@ -1765,6 +1771,20 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) {
}
}
// Apply entrance randomization
auto shuffleData = LOAD_EMBED_YAML(RANDO_DATA_PATH "entrance_shuffle_data.yaml");
if (world->AnyEntranceRandomizerEnabled()) {
for (const auto& entrance : world->GetShuffledEntrances()) {
randomizer::logic::entrance::Entrance* replacesEntrance = entrance->GetReplaces();
RandomizerContext::EntranceOverride forward = {.stageId = entrance->GetStageId(), .roomNo = entrance->GetRoomNo(), .mapLayer = entrance->GetLayerNo(), .pointNo = entrance->GetPointNo()};
RandomizerContext::EntranceOverride replaces = {.stageId = replacesEntrance->GetStageId(), .roomNo = replacesEntrance->GetRoomNo(), .mapLayer = replacesEntrance->GetLayerNo(), .pointNo = replacesEntrance->GetPointNo()};
if (forward.stageId == 0xFF || replaces.stageId == 0xFF || forward.roomNo == -1 || replaces.roomNo == -1) {
continue;
}
randoData.mEntranceOverrides[replaces] = forward;
}
}
return std::move(randoData);
}
@@ -1807,4 +1827,4 @@ bool GenerateAndWriteSeed(std::string& generationStatusMsg) {
generationStatusMsg = fmt::format("Seed generated! Hash: {}", randoData.mHash);
return true;
}
}
+25 -12
View File
@@ -20,7 +20,7 @@
class RandomizerContext {
public:
static constexpr size_t ACTR_CRC_SIZE = 32;
static constexpr size_t TGSC_CRC_SIZE = 35; // 3 extra bytes for scale x, y, z
static constexpr size_t TGSC_CRC_SIZE = 35; // 3 extra bytes for scale x, y, z
static constexpr size_t OBJ_DELETE_SIZE = 1;
static constexpr u8 ROOM_STAGE = 0xFF;
@@ -37,7 +37,7 @@ public:
std::unordered_map<u8, std::list<u8>> mStartRegionFlags{};
std::list<u8> mStartingInventory{};
struct itemLocationData{
struct itemLocationData {
int itemId{0xFF};
int stage{0xFF};
u16 flag{0xFFFF};
@@ -50,7 +50,7 @@ public:
std::unordered_map<u16, u8> mSkyCharacterOverrides{};
std::unordered_map<u16, u8> mGoldenWolfOverrides{};
std::unordered_map<u16, u8> mShopOverrides{};
std::unordered_map<u16, u16> mTwilitInsectOverrides{}; // Just used in tracker for now
std::unordered_map<u16, u16> mTwilitInsectOverrides{}; // Just used in tracker for now
std::unordered_map<u32, itemLocationData> mFlowItemMessageOverrides{};
std::unordered_map<std::string, itemLocationData> mItemLocations{};
@@ -71,15 +71,27 @@ public:
// Map of language -> map of key -> string
std::unordered_map<int, std::unordered_map<u32, std::string>> mTextOverrides{};
// Padded to 8 bytes so bit_cast can convert it to a uint64_t
struct EntranceOverride {
u8 stageId = 0xFF;
s8 roomNo = -1;
s8 pointNo = -1;
s8 mapLayer = -1;
u8 _pad0;
s16 pointNo = -1;
u16 _pad1;
bool operator==(const EntranceOverride& rhs) const = default;
};
// keyed by stageId << 24 | pointNo << 16 | roomNo << 8 | mapLayer
std::unordered_map<int, EntranceOverride> mEntranceOverrides{};
// Packs an EntranceOverride into a uint64_t
struct EntranceOverrideHash {
size_t operator()(const EntranceOverride& x) const noexcept {
return std::hash<uint64_t>{}(
((uint64_t)x.stageId << 32) | (x.roomNo << 24) | (x.mapLayer << 16) | x.pointNo);
}
};
std::unordered_map<EntranceOverride, EntranceOverride, EntranceOverrideHash>
mEntranceOverrides{};
// Overrides for returning to spawn. Keyed by stageId
std::unordered_map<int, EntranceOverride> mReturnToPlaceOverrides{};
@@ -153,7 +165,7 @@ public:
static constexpr u8 EVENT_ITEM_QUEUE_SIZE = 10;
RandomizerState() {mInitialized = false;}
RandomizerState() { mInitialized = false; }
int _create();
int _delete();
@@ -161,19 +173,19 @@ public:
int draw();
void addItemToEventQueue(u8 item);
void initGiveItemToPlayer();
//void handleBonkDamage();
// void handleBonkDamage();
void handleTimeOfDayChange();
void handleTimeSpeed();
void offLoad();
void handlePoeItem(u8 bitSw);
u8 getGiveItemToPlayerStatus() const { return mEventItemStatus;}
u8 getGiveItemToPlayerStatus() const { return mEventItemStatus; }
u8 getTimeChange() const { return mTimeChange; }
bool getRoomReloadingState() const { return mRoomReloadingState; }
bool getHasPendingToDChange() const { return mHasPendingToDChange; }
void setGiveItemToPlayerStatus(u8 status) { mEventItemStatus = status;}
void setGiveItemToPlayerStatus(u8 status) { mEventItemStatus = status; }
void setHasPendingToDChange(bool hasPending) { mHasPendingToDChange = hasPending; }
void setTimeChange(u8 newTimeChange) { mTimeChange = newTimeChange; }
void setRoomReloadingState(bool newState) { mRoomReloadingState = newState; }
@@ -217,7 +229,8 @@ 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);
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
@@ -285,4 +298,4 @@ u32 getStageObjCRC32(u8* data, size_t size);
*/
bool GenerateAndWriteSeed(std::string& generationStatusMsg);
#endif //DUSK_RANDOMIZER_CONTEXT_HPP
#endif // DUSK_RANDOMIZER_CONTEXT_HPP
+11
View File
@@ -420,6 +420,17 @@ ModResult buildSeedOptionsTab(ModContext* ctx, UiWindowHandle, UiElementHandle l
add_select_setting(leftPane, "Lower Hyrule Castle Chandelier");
add_select_setting(leftPane, "Skip Bridge Donation");
add_section(leftPane, "Entrance Randomizer Settings");
add_select_setting(leftPane, "Randomize Starting Spawn");
add_select_setting(leftPane, "Randomize Dungeon Entrances");
add_select_setting(leftPane, "Randomize Boss Entrances");
add_select_setting(leftPane, "Randomize Grotto Entrances");
add_select_setting(leftPane, "Randomize Cave Entrances");
add_select_setting(leftPane, "Randomize Interior Entrances");
add_select_setting(leftPane, "Randomize Overworld Entrances");
add_select_setting(leftPane, "Decouple Double Door Entrances");
add_select_setting(leftPane, "Decouple Entrances");
add_section(leftPane, "Additional Settings");
add_select_setting(leftPane, "Starting Time of Day");
add_select_setting(leftPane, "Logic Transform Anywhere");