mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-05-25 23:15:06 -04:00
update goal flags during play
This commit is contained in:
+1
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
Reference in New Issue
Block a user