Files
dusklight/src/dusk/randomizer/logic/spoiler_log.cpp
T
2026-04-11 23:01:46 -07:00

250 lines
9.1 KiB
C++

#include "spoiler_log.hpp"
#include "entrance_shuffle.hpp"
#include "../randomizer.hpp"
#include "../utility/file.hpp"
#include "../utility/platform.hpp"
#include "../utility/yaml.hpp"
#include <algorithm>
#include <iostream>
#include <fstream>
namespace randomizer::logic::spoiler_log
{
std::string SpoilerFormatLocation(const location::Location* location, const size_t& longestNameLength)
{
const auto numSpaces = longestNameLength - location->GetName().length();
const std::string spaces(numSpaces, ' ');
return location->GetName() + ": " + spaces + location->GetCurrentItem()->GetName();
}
std::string SpoilerFormatEntrance(const entrance::Entrance* entrance, const size_t& longestNameLength)
{
const auto numSpaces = longestNameLength - entrance->GetAlias().length();
const std::string spaces(numSpaces, ' ');
const auto replacement = entrance->GetReplaces();
return entrance->GetAlias() + ": " + spaces + replacement->GetAliasFrom();
}
void LogBasicInfo(std::ofstream& log, Randomizer* randomizer)
{
log << "Dusk Randomizer Version: " << "1.0.0" << std::endl;
log << "Seed: " << randomizer->GetConfig().GetSeed() << std::endl;
// TODO: Setting string
log << "Hash: " << randomizer->GetConfig().GetHash() << std::endl;
}
void LogSettings(std::ofstream& log, Randomizer* randomizer)
{
log << std::endl << "# Settings" << std::endl;
log << YAML::Dump(randomizer->GetConfig().SettingsToYaml()) << std::endl;
}
void GenerateSpoilerLog(randomizer::Randomizer* randomizer)
{
randomizer::utility::platform::Log("Generating Spoiler Log");
// Create logs folder if it doesn't exist
if (!randomizer::utility::file::dirExists(LOGS_PATH))
{
randomizer::utility::file::create_directories(LOGS_PATH);
}
auto& config = randomizer->GetConfig();
auto& worlds = randomizer->GetWorlds();
std::string filepath = std::string(LOGS_PATH) + config.GetHash() + " Spoiler Log.txt";
std::ofstream spoilerLog;
spoilerLog.open(filepath);
LogBasicInfo(spoilerLog, randomizer);
// Gather worlds with starting inventories
std::list<world::World*> worldswithStartingInventories = {};
for (const auto& world : worlds)
{
if (!world->GetStartingItemPool().empty())
{
worldswithStartingInventories.push_back(world.get());
}
}
// Print starting inventories if there are any
if (!worldswithStartingInventories.empty())
{
spoilerLog << std::endl << "Starting Inventory:" << std::endl;
for (const auto& world : worldswithStartingInventories)
{
spoilerLog << " World " << world->GetID() << ":" << std::endl;
for (const auto& item : world->GetStartingItemPool())
{
spoilerLog << " - " << item->GetName() << std::endl;
}
}
}
// TODO: Print required dungeons
// Get name lengths for pretty formatting
size_t longestNameLength = 0;
for (const auto& sphere : randomizer->GetPlaythroughSpheres())
{
for (const auto& location : sphere)
{
longestNameLength = std::max(location->GetName().length(), longestNameLength);
}
}
// Print playthrough
int sphereNum = 0;
spoilerLog << std::endl << "Playthrough:" << std::endl;
for (auto& sphere : randomizer->GetPlaythroughSpheres())
{
sphereNum += 1;
spoilerLog << " Sphere " << sphereNum << ":" << std::endl;
sphere.sort([](const auto& a, const auto& b) { return a->GetName()[0] < b->GetName()[0]; });
for (const auto& location : sphere)
{
spoilerLog << " " << SpoilerFormatLocation(location, longestNameLength) << std::endl;
}
}
// Get name lengths for pretty formatting
longestNameLength = 0;
for (const auto& sphere : randomizer->GetEntranceSpheres())
{
for (const auto& entrance : sphere)
{
longestNameLength = std::max(entrance->GetAlias().length(), longestNameLength);
}
}
// Print entrance playthrough
sphereNum = 0;
if (longestNameLength != 0)
{
spoilerLog << std::endl << "Entrance Playthrough:" << std::endl;
}
for (auto& sphere : randomizer->GetEntranceSpheres())
{
sphereNum += 1;
if (sphere.empty())
{
continue;
}
spoilerLog << " Sphere " << sphereNum << ":" << std::endl;
sphere.sort([](auto& e1, auto& e2) { return e1->GetID() < e2->GetID(); });
for (const auto& entrance : sphere)
{
spoilerLog << " " << SpoilerFormatEntrance(entrance, longestNameLength) << std::endl;
}
}
// Recalculate longest name length for all locations
longestNameLength = 0;
for (const auto& world : worlds)
{
for (const auto& location : world->GetAllLocations())
{
longestNameLength = std::max(location->GetName().length(), longestNameLength);
}
}
// Print All Locations
spoilerLog << std::endl << "All Locations:" << std::endl;
for (const auto& world : worlds)
{
spoilerLog << " World " << world->GetID() << ":" << std::endl;
for (const auto& location : world->GetAllLocations())
{
spoilerLog << " " << SpoilerFormatLocation(location, longestNameLength) << std::endl;
}
}
// Recalculate longest name length for all shuffled entrances
longestNameLength = 0;
for (const auto& world : worlds)
{
for (const auto& entrance : world->GetShuffledEntrances())
{
longestNameLength = std::max(entrance->GetAlias().length(), longestNameLength);
}
}
// Print all randomized entrances
if (longestNameLength != 0)
{
spoilerLog << std::endl << "All Entrances:" << std::endl;
}
for (const auto& world : worlds)
{
auto entrances = world->GetShuffledEntrances();
if (!entrances.empty())
{
spoilerLog << " World " << world->GetID() << ":" << std::endl;
// Create entrance pools to easily separate the entrances by type
auto entrancePools = entrance_shuffle::CreateEntrancePools(world.get());
auto mixedPools = world->GetSettings().GetMixedEntrancePools();
for (auto& [entranceType, entrancePool] : entrancePools)
{
auto typeStr = entrance::TypeToStr(entranceType);
// If this is a mixed pool, display the types it mixed
if (typeStr.starts_with("Mixed Pool"))
{
typeStr += " (";
auto& pool = mixedPools.front();
for (const auto& type : pool)
{
typeStr += type + " + ";
}
typeStr.erase(typeStr.end() - 3, typeStr.end()); // Remove the last " + "
typeStr += ")";
mixedPools.pop_front();
}
spoilerLog << " " << typeStr << ":" << std::endl;
std::ranges::sort(entrancePool, [](auto& e1, auto& e2) {
return e1->GetID() < e2->GetID();
});
for (const auto& entrance : entrancePool)
{
// Ignore entrances that are impossible
if (entrance->GetRequirement()._type == requirement::Type::IMPOSSIBLE)
{
continue;
}
spoilerLog << " " << SpoilerFormatEntrance(entrance, longestNameLength) << std::endl;
}
}
}
}
// TODO: Hints
// Log Settings
LogSettings(spoilerLog, randomizer);
spoilerLog.close();
randomizer::utility::platform::Log("Wrote spoiler log to " + filepath);
}
void GenerateAntiSpoilerLog(randomizer::Randomizer* randomizer)
{
// Create logs folder if it doesn't exist
if (!randomizer::utility::file::dirExists(LOGS_PATH))
{
randomizer::utility::file::create_directories(LOGS_PATH);
}
std::string filepath = std::string(LOGS_PATH) + randomizer->GetConfig().GetHash() + " Anti-Spoiler Log.txt";
std::ofstream antiSpoilerLog;
antiSpoilerLog.open(filepath);
LogBasicInfo(antiSpoilerLog, randomizer);
LogSettings(antiSpoilerLog, randomizer);
}
} // namespace randomizer::logic::spoiler_log