summaryrefslogtreecommitdiff
path: root/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp
diff options
context:
space:
mode:
authorGarrett Cox <garrettjcox@gmail.com>2024-11-03 23:06:42 -0600
committerGarrett Cox <garrettjcox@gmail.com>2024-11-03 23:06:42 -0600
commitcbb4de5a18a7574cb83a4c910cf1dba2aa56080d (patch)
tree25486e6d7172461f9ed7eee89dd9eb7003575467 /mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp
parente908ee86e6005d3255d3d3ae1cbf75c5a8fe1366 (diff)
Split onFileCreate handler up
Diffstat (limited to 'mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp')
-rw-r--r--mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp330
1 files changed, 22 insertions, 308 deletions
diff --git a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp
index 09e4daa1f..928e40990 100644
--- a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp
+++ b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp
@@ -1,8 +1,7 @@
#include "MiscBehavior.h"
#include <libultraship/libultraship.h>
-#include <fstream>
-#include "BenPort.h"
#include "Rando/Spoiler/Spoiler.h"
+#include "Rando/Logic/Logic.h"
#include <boost_custom/container_hash/hash_32.hpp>
extern "C" {
@@ -10,38 +9,20 @@ extern "C" {
#include "ShipUtils.h"
}
-void FindReachableRegions(RandoRegionId currentRegion, std::set<RandoRegionId>& reachableRegions) {
- auto& randoStaticRegion = Rando::StaticData::Regions[currentRegion];
-
- for (auto& [neighborRegionId, accessLogicFunc] : randoStaticRegion.regions) {
- // Check if the region is accessible and hasn’t been visited yet
- if (reachableRegions.count(neighborRegionId) == 0 && accessLogicFunc()) {
- reachableRegions.insert(neighborRegionId); // Mark region as visited
- FindReachableRegions(neighborRegionId, reachableRegions); // Recursively visit neighbors
- }
- }
-}
-
-struct RandoPoolEntry {
- bool shuffled;
- RandoItemId vanillaItemId;
- RandoItemId placedItemId;
- bool itemPlaced;
- bool checkFilled;
- bool inPool;
-};
-
// Very primitive randomizer implementation, when a save is created, if rando is enabled
// we set the save type to rando and shuffle all checks and persist the results to the save
void Rando::MiscBehavior::OnFileCreate(s16 fileNum) {
if (CVarGetInteger("gRando.Enabled", 0)) {
gSaveContext.save.shipSaveInfo.saveType = SAVETYPE_RANDO;
+ // Zero out the rando struct
memset(&gSaveContext.save.shipSaveInfo.rando, 0, sizeof(gSaveContext.save.shipSaveInfo.rando));
+ // Copy whatever the current dungeon keys are, they're initialized as -1 in the save, not 0
memcpy(&gSaveContext.save.shipSaveInfo.rando.foundDungeonKeys,
&gSaveContext.save.saveInfo.inventory.dungeonKeys,
sizeof(gSaveContext.save.saveInfo.inventory.dungeonKeys));
try {
+ // SpoilerFileIndex == 0 means we're generating a new one
if (CVarGetInteger("gRando.SpoilerFileIndex", 0) == 0) {
bool hadInputSeed = true;
std::string inputSeed = CVarGetString("gRando.InputSeed", "");
@@ -53,6 +34,8 @@ void Rando::MiscBehavior::OnFileCreate(s16 fileNum) {
uint32_t finalSeed = boost::hash_32<std::string>{}(inputSeed);
Ship_Random_Seed(finalSeed);
+ // Persist options to the save
+ gSaveContext.save.shipSaveInfo.rando.finalSeed = finalSeed;
for (auto& [randoOptionId, randoStaticOption] : Rando::StaticData::Options) {
RANDO_SAVE_OPTIONS[randoOptionId] = CVarGetInteger(randoStaticOption.cvar, 0);
}
@@ -64,305 +47,36 @@ void Rando::MiscBehavior::OnFileCreate(s16 fileNum) {
}
}
- if (RANDO_SAVE_OPTIONS[RO_LOGIC] == RO_LOGIC_NO_LOGIC) {
- std::vector<RandoCheckId> checkPool;
- std::vector<RandoItemId> itemPool;
- for (auto& [randoCheckId, randoStaticCheck] : Rando::StaticData::Checks) {
- if (randoStaticCheck.randoCheckId == RC_UNKNOWN) {
- continue;
- }
-
- if (randoStaticCheck.randoCheckType == RCTYPE_SKULL_TOKEN &&
- RANDO_SAVE_OPTIONS[RO_SHUFFLE_GOLD_SKULLTULAS] == RO_GENERIC_NO) {
- continue;
- }
-
- if (randoStaticCheck.randoCheckType == RCTYPE_POT &&
- RANDO_SAVE_OPTIONS[RO_SHUFFLE_POTS] == RO_GENERIC_NO) {
- continue;
- }
-
- if (randoStaticCheck.randoCheckType == RCTYPE_SHOP) {
- if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_SHOPS] == RO_GENERIC_NO) {
- continue;
- } else {
- RANDO_SAVE_CHECKS[randoCheckId].price = Ship_Random(0, 200);
- }
- }
-
- checkPool.push_back(randoCheckId);
- itemPool.push_back(randoStaticCheck.randoItemId);
- }
-
- for (size_t i = 0; i < itemPool.size(); i++) {
- std::swap(itemPool[i], itemPool[Ship_Random(0, itemPool.size() - 1)]);
- }
-
- for (size_t i = 0; i < checkPool.size(); i++) {
- RANDO_SAVE_CHECKS[checkPool[i]].shuffled = true;
- RANDO_SAVE_CHECKS[checkPool[i]].randoItemId = itemPool[i];
- }
+ if (RANDO_SAVE_OPTIONS[RO_LOGIC] == RO_LOGIC_VANILLA) {
+ // uuhhh, we're done here
+ } else if (RANDO_SAVE_OPTIONS[RO_LOGIC] == RO_LOGIC_NO_LOGIC) {
+ Rando::Logic::ApplyNoLogicToSaveContext();
} else if (RANDO_SAVE_OPTIONS[RO_LOGIC] == RO_LOGIC_GLITCHLESS) {
- std::unordered_map<RandoCheckId, RandoPoolEntry> randoCheckPool;
- SaveContext copiedSaveContext;
- memcpy(&copiedSaveContext, &gSaveContext, sizeof(SaveContext));
-
- // As far as logic is concerned, we start in Deku form
- gSaveContext.save.playerForm = PLAYER_FORM_DEKU;
-
- // First loop through all regions and add checks/items to the pool
- for (auto& [randoRegionId, randoStaticRegion] : Rando::StaticData::Regions) {
- for (auto& [randoCheckId, _] : randoStaticRegion.checks) {
- auto& randoStaticCheck = Rando::StaticData::Checks[randoCheckId];
-
- if (randoStaticCheck.randoCheckType == RCTYPE_SKULL_TOKEN &&
- RANDO_SAVE_OPTIONS[RO_SHUFFLE_GOLD_SKULLTULAS] == RO_GENERIC_NO) {
- continue;
- }
-
- if (randoStaticCheck.randoCheckType == RCTYPE_POT &&
- RANDO_SAVE_OPTIONS[RO_SHUFFLE_POTS] == RO_GENERIC_NO) {
- continue;
- }
-
- if (randoStaticCheck.randoCheckType == RCTYPE_SHOP) {
- if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_SHOPS] == RO_GENERIC_NO) {
- continue;
- } else {
- RANDO_SAVE_CHECKS[randoCheckId].price = Ship_Random(0, 200);
- }
- }
-
- randoCheckPool[randoCheckId] = { true, randoStaticCheck.randoItemId,
- RI_UNKNOWN, false,
- false, false };
- }
- }
-
- // Recursive lambda for backtracking placement
- std::function<bool(std::set<RandoRegionId>)> PlaceItems =
- [&](std::set<RandoRegionId> reachableRegions) -> bool {
- // Crawl through all reachable regions and add any new reachable regions
- std::set<RandoRegionId> currentReachableRegions = reachableRegions;
- for (RandoRegionId regionId : reachableRegions) {
- FindReachableRegions(regionId, currentReachableRegions);
- }
-
- // Mark newly accessible checks
- std::vector<RandoCheckId> newChecksInPool;
- for (RandoRegionId regionId : currentReachableRegions) {
- auto& randoStaticRegion = Rando::StaticData::Regions[regionId];
- for (auto& [randoCheckId, accessLogicFunc] : randoStaticRegion.checks) {
- if (
- // Check is shuffled
- randoCheckPool[randoCheckId].shuffled &&
- // Check is not already in the pool
- randoCheckPool[randoCheckId].inPool == false) {
- // Check is accessible
- if (accessLogicFunc()) {
- randoCheckPool[randoCheckId].inPool = true;
- newChecksInPool.push_back(randoCheckId);
- } else {
- SPDLOG_INFO("Check {} is not accessible",
- Rando::StaticData::Checks[randoCheckId].name);
- }
- }
- }
- }
-
- int checksLeft = 0;
- std::vector<RandoCheckId> currentCheckPool;
- std::vector<std::pair<RandoItemId, RandoCheckId>> currentItemPool;
- for (auto& [randoCheckId, randoPoolEntry] : randoCheckPool) {
- if (randoPoolEntry.inPool) {
- if (!randoPoolEntry.itemPlaced) {
- currentItemPool.push_back({ randoPoolEntry.vanillaItemId, randoCheckId });
- }
- if (!randoPoolEntry.checkFilled) {
- currentCheckPool.push_back(randoCheckId);
- }
- }
- if (randoPoolEntry.shuffled && !randoPoolEntry.checkFilled) {
- checksLeft++;
- }
- }
- if (checksLeft == 0) {
- return true; // All items placed
- }
-
- if (currentItemPool.size() == 0) {
- for (RandoCheckId randoCheckId : newChecksInPool) {
- randoCheckPool[randoCheckId].inPool = false;
- }
- return false; // No more items to place
- }
-
- // Shuffle the pool
- if (currentItemPool.size() > 1) {
- for (size_t i = 0; i < currentItemPool.size(); i++) {
- std::swap(currentItemPool[i],
- currentItemPool[Ship_Random(0, currentItemPool.size() - 1)]);
- }
- }
-
- for (size_t i = 0; i < currentItemPool.size(); i++) {
- // Place the item in the check
- RandoCheckId randoCheckId = currentCheckPool[i];
- auto [randoItemId, randoCheckIdFromItem] = currentItemPool[i];
- SPDLOG_INFO("Placing item {} in check {}",
- Rando::StaticData::Items[randoItemId].spoilerName,
- Rando::StaticData::Checks[randoCheckId].name);
- randoCheckPool[randoCheckId].checkFilled = true;
- randoCheckPool[randoCheckId].placedItemId = randoItemId;
- randoCheckPool[randoCheckIdFromItem].itemPlaced = true;
- RandoItemId convertedItemId = ConvertItem(randoItemId);
- GiveItem(convertedItemId);
-
- // Recurse to place the next item
- if (PlaceItems(currentReachableRegions)) {
- return true; // Found a solution
- }
-
- SPDLOG_INFO("Failed to place item {} in check {}",
- Rando::StaticData::Items[randoItemId].spoilerName,
- Rando::StaticData::Checks[randoCheckId].name);
- // Backtrack: remove the item and try another check
- randoCheckPool[randoCheckId].checkFilled = false;
- randoCheckPool[randoCheckId].placedItemId = RI_UNKNOWN;
- randoCheckPool[randoCheckIdFromItem].itemPlaced = false;
- RemoveItem(convertedItemId);
- }
-
- for (RandoCheckId randoCheckId : newChecksInPool) {
- randoCheckPool[randoCheckId].inPool = false;
- }
-
- return false; // No valid placements for this item
- };
-
- if (!PlaceItems({ RR_CLOCK_TOWN_SOUTH })) {
- memcpy(&gSaveContext, &copiedSaveContext, sizeof(SaveContext));
- throw std::runtime_error("Failed to place all items in glitchless logic, input seed: " +
- inputSeed);
- } else {
- memcpy(&gSaveContext, &copiedSaveContext, sizeof(SaveContext));
-
- for (auto& [randoCheckId, randoPoolEntry] : randoCheckPool) {
- RANDO_SAVE_CHECKS[randoCheckId].randoItemId = randoPoolEntry.placedItemId;
- RANDO_SAVE_CHECKS[randoCheckId].shuffled = true;
- }
-
- SPDLOG_INFO("Successfully placed all items in glitchless logic, input seed: {}", inputSeed);
- }
+ Rando::Logic::ApplyGlitchlessLogicToSaveContext();
+ } else {
+ throw std::runtime_error("Logic option not implemented: " +
+ std::to_string(RANDO_SAVE_OPTIONS[RO_LOGIC]));
}
if (CVarGetInteger("gRando.GenerateSpoiler", 0)) {
- nlohmann::json j;
- j["type"] = "2S2H_RANDO_SPOILER";
- j["inputSeed"] = inputSeed;
- j["finalSeed"] = finalSeed;
-
- j["options"] = nlohmann::json::object();
- for (auto& [randoOptionId, randoStaticOption] : Rando::StaticData::Options) {
- j["options"][randoStaticOption.name] = RANDO_SAVE_OPTIONS[randoOptionId];
- }
-
- j["checks"] = nlohmann::json::object();
- for (auto& [randoCheckId, randoStaticCheck] : Rando::StaticData::Checks) {
- if (randoStaticCheck.randoCheckId == RC_UNKNOWN) {
- continue;
- }
-
- if (!RANDO_SAVE_CHECKS[randoCheckId].shuffled) {
- continue;
- }
+ nlohmann::json spoiler = Rando::Spoiler::GenerateFromSaveContext();
+ spoiler["inputSeed"] = inputSeed;
- if (randoStaticCheck.randoCheckType == RCTYPE_SHOP) {
- j["checks"][randoStaticCheck.name] = nlohmann::json::object();
- j["checks"][randoStaticCheck.name]["randoItemId"] =
- Rando::StaticData::Items[RANDO_SAVE_CHECKS[randoCheckId].randoItemId].spoilerName;
- j["checks"][randoStaticCheck.name]["price"] = RANDO_SAVE_CHECKS[randoCheckId].price;
- } else {
- j["checks"][randoStaticCheck.name] =
- Rando::StaticData::Items[RANDO_SAVE_CHECKS[randoCheckId].randoItemId].spoilerName;
- }
- }
-
- std::string spoilerFile = inputSeed + ".json";
- std::string spoilerFilePath =
- Ship::Context::GetPathRelativeToAppDirectory("randomizer/" + spoilerFile, appShortName);
- std::ofstream fileStream(spoilerFilePath);
- if (!fileStream.is_open()) {
- throw std::runtime_error("Failed to open spoiler file");
- }
-
- fileStream << j.dump(4);
+ std::string fileName = inputSeed + ".json";
+ Rando::Spoiler::SaveToFile(fileName, spoiler);
if (hadInputSeed) {
- CVarSetString("gRando.SpoilerFile", spoilerFile.c_str());
+ CVarSetString("gRando.SpoilerFile", fileName.c_str());
}
Rando::Spoiler::RefreshOptions();
}
Audio_PlaySfx(NA_SE_SY_ATTENTION_SOUND);
} else {
- std::string spoilerFile = CVarGetString("gRando.SpoilerFile", "");
- if (spoilerFile.empty()) {
- throw std::runtime_error("Attempted to load spoiler file with no file selected");
- }
-
- std::string spoilerFilePath =
- Ship::Context::GetPathRelativeToAppDirectory("randomizer/" + spoilerFile, appShortName);
- std::ifstream fileStream(spoilerFilePath);
- if (!fileStream.is_open()) {
- throw std::runtime_error("Failed to open spoiler file");
- }
-
- nlohmann::json j;
- try {
- fileStream >> j;
- } catch (nlohmann::json::exception& e) {
- throw std::runtime_error("Failed to parse spoiler file: " + std::string(e.what()));
- }
-
- if (!j.contains("type") || j["type"] != "2S2H_RANDO_SPOILER") {
- throw std::runtime_error("Spoiler file is not a valid spoiler file");
- }
-
- for (auto& [randoOptionId, randoStaticOption] : Rando::StaticData::Options) {
- RANDO_SAVE_OPTIONS[randoOptionId] = j["options"][randoStaticOption.name].get<int>();
- }
-
- for (auto& [randoCheckId, randoStaticCheck] : Rando::StaticData::Checks) {
- if (randoStaticCheck.randoCheckId == RC_UNKNOWN) {
- continue;
- }
-
- if (!j["checks"].contains(randoStaticCheck.name)) {
- continue;
- }
-
- // Check if it's an object or a string
- if (j["checks"][randoStaticCheck.name].is_object()) {
- std::string itemName = j["checks"][randoStaticCheck.name]["randoItemId"].get<std::string>();
- RandoItemId randoItemId = Rando::StaticData::GetItemIdFromName(itemName.c_str());
+ std::string fileName = CVarGetString("gRando.SpoilerFile", "");
+ nlohmann::json spoiler = Rando::Spoiler::LoadFromFile(fileName);
- RANDO_SAVE_CHECKS[randoCheckId].randoItemId = randoItemId;
- RANDO_SAVE_CHECKS[randoCheckId].shuffled = true;
-
- // If it has a price, set it
- if (j["checks"][randoStaticCheck.name].contains("price")) {
- RANDO_SAVE_CHECKS[randoCheckId].price =
- j["checks"][randoStaticCheck.name]["price"].get<uint16_t>();
- }
- } else {
- std::string itemName = j["checks"][randoStaticCheck.name].get<std::string>();
- RandoItemId randoItemId = Rando::StaticData::GetItemIdFromName(itemName.c_str());
-
- RANDO_SAVE_CHECKS[randoCheckId].randoItemId = randoItemId;
- RANDO_SAVE_CHECKS[randoCheckId].shuffled = true;
- }
- }
+ Rando::Spoiler::ApplyToSaveContext(spoiler);
Audio_PlaySfx(NA_SE_SY_ATTENTION_SOUND);
}