update goal flags during play

This commit is contained in:
gymnast86
2026-05-06 03:27:14 -07:00
parent 4fddaba76b
commit 2ececa3206
8 changed files with 270 additions and 29 deletions
+1 -1
View File
@@ -1398,7 +1398,7 @@ BOOL dSv_event_c::isEventBit(const u16 i_no) const {
case CITY_IN_THE_SKY_CLEARED: // Would like to find where this is checked and patch it there.
{
if (!dComIfGs_isEventBit(FIXED_THE_MIRROR_OF_TWILIGHT)) {
if (randomizer_GetContext().mSettings.at("Palace of Twilight Requirements") == "Vanilla") {
if (randomizer_GetContext().mSettings[RandomizerContext::PALACE_OF_TWILIGHT_REQUIREMENTS] != RandomizerContext::VANILLA) {
return false;
}
}
+190 -26
View File
@@ -17,10 +17,11 @@
#include "d/actor/d_a_alink.h"
#include "d/d_com_inf_game.h"
#include "d/d_meter2_info.h"
#include "d/d_meter2.h"
#include "d/d_meter2_draw.h"
#include "d/d_meter2_info.h"
#include "d/d_msg_flow.h"
#include "flags.h"
std::optional<std::string> RandomizerContext::WriteToFile() {
@@ -31,8 +32,8 @@ std::optional<std::string> RandomizerContext::WriteToFile() {
YAML::Node out{};
for (const auto& [settingName, option] : this->mSettings) {
out["mSettings"][settingName] = option;
for (const auto& [setting, option] : this->mSettings) {
out["mSettings"][setting] = option;
}
// NOTE: When dumping u8s, they must be converted to u16s (or higher), otherwise they get dumped
@@ -105,9 +106,9 @@ std::optional<std::string> RandomizerContext::LoadFromHash(const std::string& ha
// Necessary settings
for (const auto& settingNode : in["mSettings"] ) {
const auto& settingName = settingNode.first.as<std::string>();
const auto& option = settingNode.second.as<std::string>();
this->mSettings[settingName] = option;
const auto& setting = settingNode.first.as<int>();
const auto& option = settingNode.second.as<int>();
this->mSettings[setting] = option;
}
// Event flags
@@ -235,6 +236,49 @@ std::string RandomizerContext::GetSeedDataPath() const {
return std::string(SDL_GetPrefPath(dusk::OrgName, dusk::AppName)) + "randomizer/seeds/" + this->mHash + "/seed.dat";
}
s32 RandomizerContext::settingToEnum(const std::string& settingName) {
static const std::unordered_map<std::string, s32> nameToEnum = {
{"Hyrule Barrier Dungeons", HYRULE_BARRIER_DUNGEONS},
{"Hyrule Barrier Requirements", HYRULE_BARRIER_REQUIREMENTS},
{"Hyrule Barrier Fused Shadows", HYRULE_BARRIER_FUSED_SHADOWS},
{"Hyrule Barrier Mirror Shards", HYRULE_BARRIER_MIRROR_SHARDS},
{"Hyrule Castle Big Key Requirements", HYRULE_BIG_KEY_REQUIREMENTS},
{"Hyrule Barrier Poe Souls", HYRULE_BARRIER_POE_SOULS},
{"Hyrule Barrier Hearts", HYRULE_BARRIER_HEARTS},
{"Hyrule Castle Big Key Mirror Shards", HYRULE_BIG_KEY_MIRROR_SHARDS},
{"Hyrule Castle Big Key Fused Shadows", HYRULE_BIG_KEY_FUSED_SHADOWS},
{"Hyrule Castle Big Key Dungeons", HYRULE_BIG_KEY_DUNGEONS},
{"Hyrule Castle Big Key Poe Souls", HYRULE_BIG_KEY_POE_SOULS},
{"Hyrule Castle Big Key Hearts", HYRULE_BIG_KEY_HEARTS},
{"Palace of Twilight Requirements", PALACE_OF_TWILIGHT_REQUIREMENTS},
};
if (nameToEnum.contains(settingName)) {
return nameToEnum.at(settingName);
}
return -1;
}
s32 RandomizerContext::optionToEnum(const std::string& optionName) {
static const std::unordered_map<std::string, s32> nameToEnum = {
{"None", NONE},
{"Vanilla", VANILLA},
{"Open", OPEN},
{"Fused Shadows", FUSED_SHADOWS},
{"Mirror Shards", MIRROR_SHARDS},
{"Poe Souls", POE_SOULS},
{"Hearts", HEARTS},
{"Dungeons", DUNGEONS},
};
if (nameToEnum.contains(optionName)) {
return nameToEnum.at(optionName);
}
return -1;
}
RandomizerState g_randomizerState;
int RandomizerState::_create() {
@@ -253,6 +297,96 @@ int RandomizerState::_delete() {
return 1;
}
/*
* Updates flags for Hyrule Castle Barrier, Palace of Twilight Access,
* and Hyrule Castle Big Key chest. Maybe a bit overkill to check this every frame, but
* it keeps it all in one place for now.
*/
static void updateGoalFlags() {
auto& settings = randomizer_GetContext().mSettings;
// Hyrule Castle Barrier
if (!dComIfGs_isEventBit(BARRIER_GONE)) {
bool destroyBarrier = false;
switch (settings[RandomizerContext::HYRULE_BARRIER_REQUIREMENTS]) {
case RandomizerContext::VANILLA:
destroyBarrier = dComIfGs_isEventBit(PALACE_OF_TWILIGHT_CLEARED);
break;
case RandomizerContext::FUSED_SHADOWS:
destroyBarrier = numFusedShadows() >= settings[RandomizerContext::HYRULE_BARRIER_FUSED_SHADOWS];
break;
case RandomizerContext::MIRROR_SHARDS:
destroyBarrier = numMirrorShards() >= settings[RandomizerContext::HYRULE_BARRIER_MIRROR_SHARDS];
break;
case RandomizerContext::DUNGEONS:
destroyBarrier = numCompletedDungeons() >= settings[RandomizerContext::HYRULE_BARRIER_DUNGEONS];
break;
case RandomizerContext::POE_SOULS:
destroyBarrier = dComIfGs_getPohSpiritNum() >= settings[RandomizerContext::HYRULE_BARRIER_POE_SOULS];
break;
case RandomizerContext::HEARTS:
destroyBarrier = dComIfGs_getMaxLife() >= 5 * settings[RandomizerContext::HYRULE_BARRIER_HEARTS];
break;
default:
break;
}
if (destroyBarrier) {
dComIfGs_onEventBit(BARRIER_GONE);
}
}
// Hyrule Castle Big Key Gate
if (!dComIfGs_isStageSwitch(0x18, 0x4B)) {
bool openGate = false;
switch (settings[RandomizerContext::HYRULE_BIG_KEY_REQUIREMENTS]) {
case RandomizerContext::FUSED_SHADOWS:
openGate = numFusedShadows() >= settings[RandomizerContext::HYRULE_BIG_KEY_FUSED_SHADOWS];
break;
case RandomizerContext::MIRROR_SHARDS:
openGate = numMirrorShards() >= settings[RandomizerContext::HYRULE_BIG_KEY_MIRROR_SHARDS];
break;
case RandomizerContext::DUNGEONS:
openGate = numCompletedDungeons() >= settings[RandomizerContext::HYRULE_BIG_KEY_DUNGEONS];
break;
case RandomizerContext::POE_SOULS:
openGate = dComIfGs_getPohSpiritNum() >= settings[RandomizerContext::HYRULE_BIG_KEY_POE_SOULS];
break;
case RandomizerContext::HEARTS:
openGate = dComIfGs_getMaxLife() >= 5 * settings[RandomizerContext::HYRULE_BIG_KEY_HEARTS];
break;
default:
break;
}
if (openGate) {
dComIfGs_onStageSwitch(0x18, 0x4B);
}
}
// Palace of Twlight Access
if (!dComIfGs_isEventBit(FIXED_THE_MIRROR_OF_TWILIGHT)) {
bool openPalace = false;
switch (settings[RandomizerContext::PALACE_OF_TWILIGHT_REQUIREMENTS]) {
case RandomizerContext::VANILLA:
openPalace = dComIfGs_isEventBit(CITY_IN_THE_SKY_CLEARED);
break;
case RandomizerContext::FUSED_SHADOWS:
openPalace = numFusedShadows() >= 3;
break;
case RandomizerContext::MIRROR_SHARDS:
openPalace = numMirrorShards() >= 4;
break;
default:
break;
}
if (openPalace) {
dComIfGs_onEventBit(FIXED_THE_MIRROR_OF_TWILIGHT);
}
}
}
int RandomizerState::execute() {
if (!mInitialized) {
return 0;
@@ -468,6 +602,9 @@ void RandomizerState::offLoad()
// Clear the danBit that starts a conversation when entering the ranch so the player can do goats as needed.
dComIfGs_offSaveDunSwitch(0x1);
}
// Check and update our goal flags
updateGoalFlags();
}
bool checkFoolishItemEffectReady()
@@ -566,30 +703,28 @@ u32 getActorCRC32(stage_actor_data_class* actor) {
return zng_crc32(0, reinterpret_cast<u8*>(actor), RandomizerContext::ACTOR_CRC_SIZE);
}
/*
* Generates a seed and writes the necessary seed files to the players seed directory
*/
void GenerateAndWriteSeed(std::string& generationStatusMsg) {
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;
}
const auto& world = r.GetWorlds()[0];
RandomizerContext WriteSeedData(const std::unique_ptr<randomizer::logic::world::World>& world) {
RandomizerContext randoData{};
// Settings we need to check ingame
for (const auto& [setting, info] : *randomizer::seedgen::settings::GetAllSettingsInfo()) {
if (info->NeedInGame()) {
randoData.mSettings[setting] = world->Setting(setting).GetCurrentOption();
auto settingEnum = RandomizerContext::settingToEnum(setting);
if (settingEnum == -1) {
throw std::runtime_error("Setting \"" + setting + "\" does not have an associated enum value");
}
auto option = world->Setting(setting).GetCurrentOption();
int optionEnum{};
// If this setting's options are just numbers, get the numeric value
if (info->OptionsAreNumbers()) {
optionEnum = world->Setting(setting).GetCurrentOptionAsNumber();
} else {
optionEnum = RandomizerContext::optionToEnum(option);
}
if (optionEnum == -1) {
throw std::runtime_error("Option \"" + option + "\" for setting \"" + setting + "\" does not have an associated enum value");
}
randoData.mSettings[settingEnum] = optionEnum;
}
}
@@ -920,11 +1055,40 @@ void GenerateAndWriteSeed(std::string& generationStatusMsg) {
}
}
return std::move(randoData);
}
/*
* Generates a seed and writes the necessary seed files to the players seed directory
*/
void GenerateAndWriteSeed(std::string& generationStatusMsg) {
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("Seed Generation failed. Reason:\n{}", generationResult.value());
return;
}
const auto& world = r.GetWorlds()[0];
RandomizerContext randoData{};
try {
randoData = WriteSeedData(world);
} catch (const std::runtime_error& e) {
generationStatusMsg =
fmt::format("Failed to write seed data. Reason:\n{}", e.what());
return;
}
randoData.mHash = r.GetConfig().GetHash();
auto writeToFileResult = randoData.WriteToFile();
if (writeToFileResult.has_value()) {
generationStatusMsg =
fmt::format("Failed to write seed data. Reason: {}", writeToFileResult.value());
fmt::format("Failed to write seed data to file. Reason:\n{}", writeToFileResult.value());
return;
}
@@ -24,7 +24,8 @@ public:
u32 mSeedID{0};
std::string mHash{""};
std::unordered_map<std::string, std::string> mSettings{};
// Maps enum of necessary setting to enum of value
std::unordered_map<int, int> mSettings{};
std::list<u16> mStartEventFlags{};
std::unordered_map<u8, std::list<u8>> mStartRegionFlags{};
@@ -48,6 +49,37 @@ public:
std::optional<std::string> WriteToFile();
std::optional<std::string> LoadFromHash(const std::string& hash);
std::string GetSeedDataPath() const;
enum Settings {
HYRULE_BARRIER_REQUIREMENTS,
HYRULE_BARRIER_FUSED_SHADOWS,
HYRULE_BARRIER_MIRROR_SHARDS,
HYRULE_BARRIER_POE_SOULS,
HYRULE_BARRIER_HEARTS,
HYRULE_BARRIER_DUNGEONS,
HYRULE_BIG_KEY_REQUIREMENTS,
HYRULE_BIG_KEY_FUSED_SHADOWS,
HYRULE_BIG_KEY_MIRROR_SHARDS,
HYRULE_BIG_KEY_POE_SOULS,
HYRULE_BIG_KEY_HEARTS,
HYRULE_BIG_KEY_DUNGEONS,
PALACE_OF_TWILIGHT_REQUIREMENTS,
};
enum Options {
NONE,
VANILLA,
OPEN,
FUSED_SHADOWS,
MIRROR_SHARDS,
POE_SOULS,
HEARTS,
DUNGEONS,
};
static int settingToEnum(const std::string& settingName);
static int optionToEnum(const std::string& optionName);
};
/*
+25
View File
@@ -124,4 +124,29 @@ u16 getItemMessageID(u8 itemId) {
}
return itemId + 0x65;
}
int numCompletedDungeons() {
int numCompleted{0};
// Loop through dungeon area node ids
for (int i = 0x10; i < 0x18; ++i) {
numCompleted += dComIfGs_isStageBossEnemy(i);
}
return numCompleted;
}
int numFusedShadows() {
int numFusedShadows{0};
for (int i = 0; i < 3; ++i) {
numFusedShadows += dComIfGs_isCollectCrystal(i);
}
return numFusedShadows;
}
int numMirrorShards() {
int numMirrorShards{0};
for (int i = 0; i < 4; ++i) {
numMirrorShards += dComIfGs_isCollectMirror(i);
}
return numMirrorShards;
}
+3
View File
@@ -14,3 +14,6 @@ int initCreatePlayerItem(u32 item, u32 flag, const cXyz* pos, int roomNo, const
int getStageID(const char* stage = NULL);
bool playerIsOnTitleScreen();
u16 getItemMessageID(u8 itemId);
int numCompletedDungeons();
int numFusedShadows();
int numMirrorShards();
@@ -69,7 +69,7 @@
Tracker Important: True
Default Option: Vanilla
Options:
- Open: "The barrier around Hyrule Castle is dispelled from the beginning."
- Open: "The Mirror of Twilight is open at the start of the game."
- Fused Shadows: "The player must collect all 3 Fused Shadows."
- Mirror Shards: "The player must collect all 4 Mirror Shards."
- Vanilla: "The player must complete City in the Sky."
@@ -66,6 +66,11 @@ namespace randomizer::seedgen::settings
utility::str::Erase(logicOption, "'", ")", "(");
this->_logicOptions.push_back(logicOption);
}
// Note: Assumes an option is never a negative number
this->_optionsAreNumbers = std::ranges::all_of(options, [](const std::string& option) {
return !option.empty() && std::ranges::all_of(option, ::isdigit);
});
}
std::string SettingInfo::GetDefaultOption() const
@@ -114,6 +119,15 @@ namespace randomizer::seedgen::settings
return this->_info->GetOptions()[this->_currentOptionIndex];
}
int Setting::GetCurrentOptionAsNumber() const {
try {
return std::stoi(this->GetCurrentOption());
} catch (...) {
throw std::runtime_error("Option \"" + GetCurrentOption() + "\" for setting \"" + this->GetInfo()->GetName() +
"\" cannot be turned into a number");
}
}
void Setting::ResolveIfRandom()
{
if (this->GetCurrentOptionIndex() == this->GetInfo()->GetRandomOptionIndex())
@@ -100,6 +100,7 @@ namespace randomizer::seedgen::settings
int GetRandomHigh() const { return this->_randomHigh; }
bool TrackerImportant() const { return this->_trackerImportant; }
bool NeedInGame() const { return this->_needInGame; }
bool OptionsAreNumbers() const { return this->_optionsAreNumbers; }
private:
int _id = -1;
@@ -114,6 +115,7 @@ namespace randomizer::seedgen::settings
int _randomHigh = 0; // Upper bound when choosing a random option
bool _trackerImportant = false; // Whether or not this setting can affect trackers
bool _needInGame = false; // Whether or not we need to read this setting during gameplay
bool _optionsAreNumbers = false;// Whether this setting's options are all numbers
// Variables that hold the setting's name and options when being checked
// in a logical requirement string.
@@ -135,6 +137,7 @@ namespace randomizer::seedgen::settings
void SetCurrentOption(const std::string& newOption);
void SetCurrentOption(const int& newOptionIndex);
std::string GetCurrentOption() const;
int GetCurrentOptionAsNumber() const;
const int& GetCurrentOptionIndex() const { return this->_currentOptionIndex; }
const bool& IsUsingRandomOption() const { return this->_isUsingRandomOption; }
SettingInfo* GetInfo() const { return this->_info; }