mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-06-23 23:35:16 -04:00
add permalink sharing
This commit is contained in:
@@ -1610,6 +1610,7 @@ set(DUSK_FILES
|
||||
src/dusk/randomizer/generator/seedgen/settings.hpp
|
||||
src/dusk/randomizer/generator/test/test.cpp
|
||||
src/dusk/randomizer/generator/test/test.hpp
|
||||
src/dusk/randomizer/generator/utility/base64pp.hpp
|
||||
src/dusk/randomizer/generator/utility/color.cpp
|
||||
src/dusk/randomizer/generator/utility/color.hpp
|
||||
src/dusk/randomizer/generator/utility/crc32.hpp
|
||||
|
||||
@@ -783,4 +783,17 @@ namespace randomizer::logic::entrance_shuffle
|
||||
}
|
||||
return reverseEntrances;
|
||||
}
|
||||
|
||||
const std::set<std::string>& GetPossibleMixedPoolTypes() {
|
||||
static std::set<std::string> possibleMixedPoolTypes = {
|
||||
TypeToStr(DUNGEON),
|
||||
TypeToStr(BOSS),
|
||||
TypeToStr(GROTTO),
|
||||
TypeToStr(INTERIOR),
|
||||
TypeToStr(CAVE),
|
||||
TypeToStr(OVERWORLD),
|
||||
};
|
||||
|
||||
return possibleMixedPoolTypes;
|
||||
}
|
||||
} // namespace randomizer::logic::entrance_shuffle
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace randomizer::logic::entrance_shuffle
|
||||
|
||||
void SetShuffledEntrances(entrance::EntrancePools& entrancePools);
|
||||
entrance::EntrancePool GetReverseEntrances(const entrance::EntrancePool& entrances);
|
||||
const std::set<std::string>& GetPossibleMixedPoolTypes();
|
||||
|
||||
class EntranceShuffleError: public std::runtime_error
|
||||
{
|
||||
|
||||
@@ -451,4 +451,21 @@ namespace randomizer::logic::item_pool
|
||||
|
||||
return completeItemPool;
|
||||
}
|
||||
|
||||
const std::map<std::string, int>& GetValidStartingInventoryItems() {
|
||||
static std::map<std::string, int> validStartingInventoryItems{};
|
||||
if (validStartingInventoryItems.empty()) {
|
||||
validStartingInventoryItems = minimalItemPool;
|
||||
for (const auto& [item, count] : standardItemPool) {
|
||||
validStartingInventoryItems[item] += count;
|
||||
}
|
||||
|
||||
// Remove junk
|
||||
validStartingInventoryItems.erase("Purple Rupee Links House");
|
||||
validStartingInventoryItems.erase("Green Rupee");
|
||||
validStartingInventoryItems.erase("Orange Rupee");
|
||||
validStartingInventoryItems.erase("Silver Rupee");
|
||||
}
|
||||
return validStartingInventoryItems;
|
||||
}
|
||||
} // namespace randomizer::logic::item_pool
|
||||
|
||||
@@ -39,4 +39,6 @@ namespace randomizer::logic::item_pool
|
||||
std::map<std::string, int> GetInitialJunkPool();
|
||||
|
||||
ItemPool GetCompleteItemPool(const world::WorldPool& worlds);
|
||||
|
||||
const std::map<std::string, int>& GetValidStartingInventoryItems();
|
||||
} // namespace randomizer::logic::item_pool
|
||||
|
||||
@@ -148,4 +148,17 @@ namespace randomizer::logic::location
|
||||
{
|
||||
this->_registeredLocationCategories = registeredLocationCategories;
|
||||
}
|
||||
|
||||
const std::set<std::string>& GetAllRandomizerLocationNames() {
|
||||
static std::set<std::string> locationNames{};
|
||||
|
||||
if (locationNames.empty()) {
|
||||
auto locationDataTree = LOAD_EMBED_YAML(RANDO_DATA_PATH "locations.yaml");
|
||||
for (const auto& locationNode : locationDataTree) {
|
||||
locationNames.insert(locationNode["Name"].as<std::string>());
|
||||
}
|
||||
}
|
||||
|
||||
return locationNames;
|
||||
}
|
||||
} // namespace randomizer::logic::location
|
||||
|
||||
@@ -110,4 +110,10 @@ namespace randomizer::logic::location
|
||||
};
|
||||
|
||||
using LocationPool = std::vector<Location*>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @return A set of all randomizer location names
|
||||
*/
|
||||
const std::set<std::string>& GetAllRandomizerLocationNames();
|
||||
} // namespace randomizer::logic::location
|
||||
|
||||
@@ -35,9 +35,7 @@ namespace randomizer::logic::spoiler_log
|
||||
{
|
||||
log << "Dusklight Version: " << DUSK_WC_DESCRIBE << std::endl;
|
||||
log << "Seed: " << randomizer->GetConfig().GetSeed() << std::endl;
|
||||
|
||||
// TODO: Setting string
|
||||
|
||||
log << "Permalink: " << randomizer->GetConfig().GetPermalink() << std::endl;
|
||||
log << "Hash: " << randomizer->GetConfig().GetHash() << std::endl;
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,8 @@ namespace randomizer
|
||||
{
|
||||
utility::time::ScopedTimer<"Seed generation took ", std::chrono::milliseconds> timer;
|
||||
this->_config.LoadFromFile(GetConfigPath(), GetPrefPath());
|
||||
// Set permalink now so that resolving random settings doesn't change it
|
||||
this->_config.SetPermalink(this->_config.GetPermalink());
|
||||
|
||||
utility::platform::Log(std::string("Seed: ") + this->_config.GetSeed());
|
||||
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
#include "config.hpp"
|
||||
|
||||
#include "packed_bits.hpp"
|
||||
#include "seed.hpp"
|
||||
#include "../utility/base64pp.hpp"
|
||||
#include "../utility/crc32.hpp"
|
||||
#include "../utility/log.hpp"
|
||||
#include "../utility/platform.hpp"
|
||||
#include "../utility/random.hpp"
|
||||
#include "../utility/yaml.hpp"
|
||||
#include "../logic/entrance_shuffle.hpp"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <iostream>
|
||||
#include <version.h>
|
||||
|
||||
// Fields which aren't part of settings_list.yaml
|
||||
constexpr std::string_view SEED = "Seed";
|
||||
@@ -362,6 +367,193 @@ namespace randomizer::seedgen::config
|
||||
return this->_hash;
|
||||
}
|
||||
|
||||
std::string Config::GetPermalink() {
|
||||
// If a permalink was set, return that instead
|
||||
if (!this->_permalink.empty()) {
|
||||
return this->_permalink;
|
||||
}
|
||||
|
||||
std::string permalink{};
|
||||
|
||||
permalink += DUSK_WC_DESCRIBE;
|
||||
permalink += '\0';
|
||||
permalink += std::to_string(settings::GetSettingInfoHash());
|
||||
permalink += '\0';
|
||||
permalink += this->_seed;
|
||||
permalink += '\0';
|
||||
|
||||
// Pack the settings up
|
||||
PackedBitsWriter bitsWriter{};
|
||||
// Regular Settings
|
||||
for (const auto& [settingName, setting] : GetSettings().GetMap()) {
|
||||
if (setting.GetInfo()->GetType() != settings::Type::STANDARD) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto optionIndex = setting.GetCurrentOptionIndex();
|
||||
auto bitLength = setting.GetInfo()->GetOptionsBitLength();
|
||||
bitsWriter.write(optionIndex, bitLength);
|
||||
}
|
||||
// Starting Items
|
||||
const auto& startingInventory = GetSettings().GetStartingInventory();
|
||||
for (const auto& [itemName, maxCount] : logic::item_pool::GetValidStartingInventoryItems()) {
|
||||
int count = 0;
|
||||
if (startingInventory.contains(itemName)) {
|
||||
count = startingInventory.at(itemName);
|
||||
}
|
||||
|
||||
int numBits = std::bit_width(static_cast<uint32_t>(maxCount));
|
||||
bitsWriter.write(count, numBits);
|
||||
}
|
||||
// Excluded Locations
|
||||
for (const auto& locationName : logic::location::GetAllRandomizerLocationNames()) {
|
||||
if (GetSettings().GetExcludedLocations().contains(locationName)) {
|
||||
bitsWriter.write(1, 1);
|
||||
} else {
|
||||
bitsWriter.write(0, 1);
|
||||
}
|
||||
}
|
||||
// Mixed Entrance Pools
|
||||
const auto& mixedEntrancePools = GetSettings().GetMixedEntrancePools();
|
||||
const auto& possibleMixedPoolTypes = logic::entrance_shuffle::GetPossibleMixedPoolTypes();
|
||||
for (const auto& entranceType : possibleMixedPoolTypes) {
|
||||
uint32_t poolIndex = 0;
|
||||
uint32_t counter = 0;
|
||||
for (const auto& pool : mixedEntrancePools) {
|
||||
counter += 1;
|
||||
if (utility::container::ElementInContainer(pool, entranceType)) {
|
||||
poolIndex = counter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
bitsWriter.write(poolIndex, std::bit_width(possibleMixedPoolTypes.size()));
|
||||
}
|
||||
|
||||
bitsWriter.flush();
|
||||
for (auto byte : bitsWriter.bytes) {
|
||||
permalink += byte;
|
||||
}
|
||||
permalink = b64_encode(permalink);
|
||||
|
||||
return permalink;
|
||||
}
|
||||
|
||||
std::optional<std::string> Config::LoadFromPermalink(std::string b64permalink) {
|
||||
|
||||
// Strip trailing spaces
|
||||
std::erase_if(b64permalink, [](unsigned char ch){ return std::isspace(ch); });
|
||||
|
||||
std::string permalink = b64_decode(b64permalink);
|
||||
// Empty string gets returned if there was an error
|
||||
if (permalink.empty()) {
|
||||
return "Pasted permalink is invalid and could not be decoded. (You likely miscopied it.)";
|
||||
}
|
||||
|
||||
// Split the string into 4 parts along the null terminator delimiter
|
||||
// 1st part - Version string
|
||||
// 2nd part - setting info hash
|
||||
// 3rd part - seed string
|
||||
// 4th part - packed bits representing settings
|
||||
std::vector<std::string> permaParts = {};
|
||||
constexpr char delimiter = '\0';
|
||||
size_t pos = permalink.find(delimiter);
|
||||
while (pos != std::string::npos) {
|
||||
if (permaParts.size() != 3) {
|
||||
permaParts.push_back(permalink.substr(0, pos));
|
||||
permalink.erase(0, pos + 1);
|
||||
}
|
||||
else {
|
||||
permaParts.push_back(permalink);
|
||||
break;
|
||||
}
|
||||
|
||||
pos = permalink.find(delimiter);
|
||||
}
|
||||
|
||||
if (permaParts.size() != 4) {
|
||||
return "Pasted permalink does not have the expected number of parts.";
|
||||
}
|
||||
|
||||
const auto& permaVersion = permaParts[0];
|
||||
const auto& permaSettingsInfoHash = permaParts[1];
|
||||
const auto& permaSeed = permaParts[2];
|
||||
const auto& permaPackedSettings = permaParts[3];
|
||||
|
||||
if (permaSettingsInfoHash != std::to_string(settings::GetSettingInfoHash())) {
|
||||
return fmt::format("Pasted permalink was generated with an incompatible Dusklight version.\n"
|
||||
"Your version: {}\nPermalink version: {}", DUSK_WC_DESCRIBE, permaVersion);
|
||||
}
|
||||
|
||||
const std::vector<char> bytes(permaPackedSettings.begin(), permaPackedSettings.end());
|
||||
PackedBitsReader bitsReader{bytes};
|
||||
Config newConfig{};
|
||||
|
||||
for (auto& [settingName, setting] : newConfig.GetSettings().GetMap()) {
|
||||
if (setting.GetInfo()->GetType() != settings::Type::STANDARD) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto bitLength = setting.GetInfo()->GetOptionsBitLength();
|
||||
auto optionIndex = bitsReader.read(bitLength);
|
||||
setting.SetCurrentOption(optionIndex);
|
||||
}
|
||||
// Starting Items
|
||||
auto& startingInventory = newConfig.GetSettings().GetModifiableStartingInventory();
|
||||
for (const auto& [itemName, maxCount] : logic::item_pool::GetValidStartingInventoryItems()) {
|
||||
int count = 0;
|
||||
int numBits = std::bit_width(static_cast<uint32_t>(maxCount));
|
||||
count = bitsReader.read(numBits);
|
||||
|
||||
if (count > 0) {
|
||||
startingInventory[itemName] = count;
|
||||
}
|
||||
}
|
||||
// Excluded Locations
|
||||
auto& excludedLocations = newConfig.GetSettings().GetModifiableExcludedLocations();
|
||||
for (const auto& locationName : logic::location::GetAllRandomizerLocationNames()) {
|
||||
if (bitsReader.read(1) == 1) {
|
||||
excludedLocations.insert(locationName);
|
||||
}
|
||||
}
|
||||
|
||||
// Mixed Entrance Pools
|
||||
auto& mixedEntrancePools = newConfig.GetSettings().GetModifiableMixedEntrancePools();
|
||||
const auto& possibleMixedPoolTypes = logic::entrance_shuffle::GetPossibleMixedPoolTypes();
|
||||
for (const auto& entranceType : possibleMixedPoolTypes) {
|
||||
auto poolIndex = bitsReader.read(std::bit_width(possibleMixedPoolTypes.size()));
|
||||
if (poolIndex == 0) {
|
||||
continue;
|
||||
}
|
||||
poolIndex -= 1;
|
||||
if (poolIndex < possibleMixedPoolTypes.size()) {
|
||||
while (poolIndex >= mixedEntrancePools.size()) {
|
||||
mixedEntrancePools.push_back({});
|
||||
}
|
||||
auto& pool = *std::next(mixedEntrancePools.begin(), poolIndex);
|
||||
pool.push_back(entranceType);
|
||||
}
|
||||
}
|
||||
|
||||
if (!bitsReader.reached_last_byte()) {
|
||||
return "Pasted permalink is incorrect length. (You likely miscopied it.)";
|
||||
}
|
||||
|
||||
// Once we've gotten all the info, copy it over to this config
|
||||
this->SetSeed(permaSeed);
|
||||
for (auto& settings : this->_settingsList) {
|
||||
for (auto& [settingName, setting] : settings.GetMap()) {
|
||||
if (setting.GetInfo()->GetType() == settings::Type::STANDARD) {
|
||||
setting.SetCurrentOption(newConfig.GetSettings().GetMap().at(settingName).GetCurrentOptionIndex());
|
||||
}
|
||||
}
|
||||
settings.GetModifiableExcludedLocations() = newConfig.GetSettings().GetExcludedLocations();
|
||||
settings.GetModifiableStartingInventory() = newConfig.GetSettings().GetStartingInventory();
|
||||
settings.GetModifiableMixedEntrancePools() = newConfig.GetSettings().GetMixedEntrancePools();
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
int SeedRNG(Config& config,
|
||||
bool resolveNonStandardRandom /* = false */,
|
||||
bool ignoreInvalidPlandomizer /* = true */)
|
||||
|
||||
@@ -13,34 +13,6 @@ namespace YAML
|
||||
|
||||
namespace randomizer::seedgen::config
|
||||
{
|
||||
enum struct [[nodiscard]] ConfigError
|
||||
{
|
||||
NONE = 0,
|
||||
COULD_NOT_OPEN,
|
||||
MISSING_KEY,
|
||||
DIFFERENT_FILE_VERSION,
|
||||
DIFFERENT_RANDO_VERSION,
|
||||
BAD_PERMALINK,
|
||||
INVALID_VALUE,
|
||||
MODEL_ERROR,
|
||||
UNKNOWN,
|
||||
COUNT
|
||||
};
|
||||
|
||||
enum struct [[nodiscard]] PermalinkError
|
||||
{
|
||||
NONE = 0,
|
||||
EMPTY,
|
||||
BAD_ENCODING,
|
||||
MISSING_PARTS,
|
||||
INVALID_VERSION,
|
||||
INCORRECT_LENGTH,
|
||||
COULD_NOT_READ,
|
||||
UNHANDLED_OPTION,
|
||||
COULD_NOT_LOAD_LOCATIONS,
|
||||
UNKNOWN,
|
||||
COUNT
|
||||
};
|
||||
|
||||
class Config
|
||||
{
|
||||
@@ -68,8 +40,9 @@ namespace randomizer::seedgen::config
|
||||
void WritePreferencesToFile(const fspath& preferencesPath);
|
||||
void WriteToFile(const fspath& filePath, const fspath& preferencesPath);
|
||||
|
||||
// PermalinkError loadPermalink(std::string b64permalink);
|
||||
// std::string getPermalink(const bool& internal = false) const;
|
||||
std::optional<std::string> LoadFromPermalink(std::string b64permalink);
|
||||
std::string GetPermalink();
|
||||
void SetPermalink(const std::string& newPermalink) { this->_permalink = newPermalink; }
|
||||
|
||||
/**
|
||||
* @brief Returns the hash for the config.
|
||||
@@ -85,6 +58,7 @@ namespace randomizer::seedgen::config
|
||||
|
||||
std::string _seed;
|
||||
std::string _hash;
|
||||
std::string _permalink;
|
||||
std::list<settings::Settings> _settingsList;
|
||||
bool _isUsingPlandomizer = false;
|
||||
bool _isGeneratingSpoilerLog = true;
|
||||
|
||||
@@ -102,4 +102,8 @@ class PackedBitsReader
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
bool reached_last_byte() const {
|
||||
return current_byte_index == bytes.size() - 1;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
#include "settings.hpp"
|
||||
|
||||
#include "../utility/crc32.hpp"
|
||||
#include "../utility/endian.hpp"
|
||||
#include "../utility/log.hpp"
|
||||
#include "../utility/container.hpp"
|
||||
#include "../utility/file.hpp"
|
||||
#include "../utility/random.hpp"
|
||||
#include "../utility/string.hpp"
|
||||
#include "../utility/yaml.hpp"
|
||||
#include "../logic/location.hpp"
|
||||
#include "../logic/entrance_shuffle.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <algorithm>
|
||||
@@ -26,18 +30,18 @@ namespace randomizer::seedgen::settings
|
||||
return types.at(str);
|
||||
}
|
||||
|
||||
SettingInfo::SettingInfo(const int& id,
|
||||
SettingInfo::SettingInfo(int id,
|
||||
const std::string& name,
|
||||
const Type& type,
|
||||
Type type,
|
||||
const std::vector<std::string>& options,
|
||||
const std::vector<std::string>& descriptions,
|
||||
const int& defaultOptionIndex,
|
||||
const bool& hasRandomOption,
|
||||
const int& randomOptionIndex,
|
||||
const int& randomLow,
|
||||
const int& randomHigh,
|
||||
const bool& trackerImportant,
|
||||
const bool& needInGame):
|
||||
int defaultOptionIndex,
|
||||
bool hasRandomOption,
|
||||
int randomOptionIndex,
|
||||
int randomLow,
|
||||
int randomHigh,
|
||||
bool trackerImportant,
|
||||
bool needInGame):
|
||||
_id(id),
|
||||
_name(name),
|
||||
_type(type),
|
||||
@@ -71,6 +75,9 @@ namespace randomizer::seedgen::settings
|
||||
this->_optionsAreNumbers = std::ranges::all_of(options, [](const std::string& option) {
|
||||
return !option.empty() && std::ranges::all_of(option, ::isdigit);
|
||||
});
|
||||
|
||||
// Set options bitlength
|
||||
this->_optionsBitLength = std::bit_width(this->_options.size());
|
||||
}
|
||||
|
||||
std::string SettingInfo::GetDefaultOption() const
|
||||
@@ -80,7 +87,7 @@ namespace randomizer::seedgen::settings
|
||||
|
||||
int SettingInfo::GetIndexOfOption(const std::string& option) const
|
||||
{
|
||||
return randomizer::utility::container::GetIndex(this->_options, option);
|
||||
return utility::container::GetIndex(this->_options, option);
|
||||
}
|
||||
|
||||
std::string SettingInfo::GetRandomOption() const
|
||||
@@ -93,7 +100,7 @@ namespace randomizer::seedgen::settings
|
||||
this->_currentOptionIndex = info->GetIndexOfOption(option);
|
||||
}
|
||||
|
||||
void Setting::SetCurrentOption(const int& newOptionIndex)
|
||||
void Setting::SetCurrentOption(int newOptionIndex)
|
||||
{
|
||||
if (newOptionIndex >= this->GetInfo()->GetOptions().size())
|
||||
{
|
||||
@@ -122,7 +129,7 @@ namespace randomizer::seedgen::settings
|
||||
int Setting::GetCurrentOptionAsNumber() const {
|
||||
try {
|
||||
return std::stoi(this->GetCurrentOption());
|
||||
} catch (...) {
|
||||
} catch (const std::invalid_argument&) {
|
||||
throw std::runtime_error("Option \"" + GetCurrentOption() + "\" for setting \"" + this->GetInfo()->GetName() +
|
||||
"\" cannot be turned into a number");
|
||||
}
|
||||
@@ -134,7 +141,7 @@ namespace randomizer::seedgen::settings
|
||||
{
|
||||
this->_isUsingRandomOption = true;
|
||||
auto randomOption =
|
||||
randomizer::utility::random::Random(this->GetInfo()->GetRandomLow(), this->GetInfo()->GetRandomHigh());
|
||||
utility::random::Random(this->GetInfo()->GetRandomLow(), this->GetInfo()->GetRandomHigh());
|
||||
this->SetCurrentOption(randomOption);
|
||||
LOG_TO_DEBUG("Chose \"" + this->GetInfo()->GetOptions()[randomOption] + " as random option for setting \"" +
|
||||
this->GetInfo()->GetName());
|
||||
@@ -300,7 +307,7 @@ namespace randomizer::seedgen::settings
|
||||
}
|
||||
|
||||
// Calculate default option index
|
||||
auto defaultOptionIndex = randomizer::utility::container::GetIndex(options, defaultOption);
|
||||
auto defaultOptionIndex = utility::container::GetIndex(options, defaultOption);
|
||||
if (defaultOptionIndex == -1)
|
||||
{
|
||||
throw std::runtime_error(std::string("Default Option \"") + defaultOption + "\" is not defined for setting \"" +
|
||||
@@ -328,17 +335,17 @@ namespace randomizer::seedgen::settings
|
||||
if (settingNode["Random Low"])
|
||||
{
|
||||
auto randomLowStr = settingNode["Random Low"].as<std::string>();
|
||||
randomLow = randomizer::utility::container::GetIndex(options, randomLowStr);
|
||||
randomLow = utility::container::GetIndex(options, randomLowStr);
|
||||
if (randomLow == -1)
|
||||
{
|
||||
throw std::runtime_error(std::string("Random Low Option \"") + randomLowStr +
|
||||
"\" is not defined for setting \"" + name + "\"");
|
||||
}
|
||||
}
|
||||
if (settingNode["Random high"])
|
||||
if (settingNode["Random High"])
|
||||
{
|
||||
auto randomHighStr = settingNode["Random High"].as<std::string>();
|
||||
randomHigh = randomizer::utility::container::GetIndex(options, randomHighStr);
|
||||
randomHigh = utility::container::GetIndex(options, randomHighStr);
|
||||
if (randomHigh == -1)
|
||||
{
|
||||
throw std::runtime_error(std::string("Random High Option \"") + randomHighStr +
|
||||
@@ -347,13 +354,13 @@ namespace randomizer::seedgen::settings
|
||||
}
|
||||
|
||||
// Generate the random option if it's not already there
|
||||
if (hasRandomOption && randomizer::utility::container::GetIndex(options, randomAlias) != -1)
|
||||
if (hasRandomOption && utility::container::GetIndex(options, randomAlias) != -1)
|
||||
{
|
||||
options.push_back(randomAlias);
|
||||
descriptions.push_back("A random option will be chosen");
|
||||
}
|
||||
|
||||
int randomOptionIndex = randomizer::utility::container::GetIndex(options, randomAlias);
|
||||
int randomOptionIndex = utility::container::GetIndex(options, randomAlias);
|
||||
|
||||
// Insert the data for the setting
|
||||
auto info = std::make_unique<SettingInfo>(settingIdCounter++,
|
||||
@@ -374,4 +381,35 @@ namespace randomizer::seedgen::settings
|
||||
return std::move(settingInfoMap);
|
||||
}
|
||||
|
||||
uint32_t GetSettingInfoHash() {
|
||||
static uint32_t settingsInfoHash = 0;
|
||||
if (settingsInfoHash == 0) {
|
||||
auto allSettingInfo = GetAllSettingsInfo();
|
||||
for (const auto& [settingName, settingInfo] : *allSettingInfo) {
|
||||
settingsInfoHash = utility::crc32(settingName.data(), settingName.length(), settingsInfoHash);
|
||||
for (const auto& optionName : settingInfo->GetOptions()) {
|
||||
settingsInfoHash = utility::crc32(optionName.data(), optionName.length(), settingsInfoHash);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [itemName, maxRef] : logic::item_pool::GetValidStartingInventoryItems()) {
|
||||
int maxCount = maxRef;
|
||||
settingsInfoHash = utility::crc32(itemName.data(), itemName.length(), settingsInfoHash);
|
||||
if constexpr (std::endian::native == std::endian::big) {
|
||||
maxCount = Utility::Endian::byteswap(maxCount);
|
||||
}
|
||||
settingsInfoHash = utility::crc32(&maxCount, sizeof(maxCount), settingsInfoHash);
|
||||
}
|
||||
|
||||
for (const auto& locationName : logic::location::GetAllRandomizerLocationNames()) {
|
||||
settingsInfoHash = utility::crc32(locationName.data(), locationName.length(), settingsInfoHash);
|
||||
}
|
||||
|
||||
for (const auto& entranceType : logic::entrance_shuffle::GetPossibleMixedPoolTypes()) {
|
||||
settingsInfoHash = utility::crc32(entranceType.data(), entranceType.length(), settingsInfoHash);
|
||||
}
|
||||
}
|
||||
return settingsInfoHash;
|
||||
}
|
||||
|
||||
}; // namespace randomizer::seedgen::settings
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
namespace randomizer::seedgen::settings
|
||||
{
|
||||
class SettingInfo;
|
||||
using SettingInfoMap_t = std::unordered_map<std::string, std::unique_ptr<SettingInfo>>;
|
||||
using SettingInfoMap_t = std::map<std::string, std::unique_ptr<SettingInfo>>;
|
||||
|
||||
/**
|
||||
* @brief Enum for different types of settings.
|
||||
@@ -48,18 +48,18 @@ namespace randomizer::seedgen::settings
|
||||
class SettingInfo
|
||||
{
|
||||
public:
|
||||
SettingInfo(const int& id,
|
||||
SettingInfo(int id,
|
||||
const std::string& name,
|
||||
const Type& type,
|
||||
Type type,
|
||||
const std::vector<std::string>& options,
|
||||
const std::vector<std::string>& descriptions,
|
||||
const int& defaultOptionIndex,
|
||||
const bool& hasRandomOption,
|
||||
const int& randomOptionIndex,
|
||||
const int& randomLow,
|
||||
const int& randomHigh,
|
||||
const bool& trackerImportant,
|
||||
const bool& needInGame);
|
||||
int defaultOptionIndex,
|
||||
bool hasRandomOption,
|
||||
int randomOptionIndex,
|
||||
int randomLow,
|
||||
int randomHigh,
|
||||
bool trackerImportant,
|
||||
bool needInGame);
|
||||
|
||||
int GetID() const { return this->_id; }
|
||||
|
||||
@@ -76,12 +76,12 @@ namespace randomizer::seedgen::settings
|
||||
/**
|
||||
* @brief Returns a vector of strings of the setting's available options.
|
||||
*/
|
||||
std::vector<std::string> GetOptions() const { return this->_options; }
|
||||
const std::vector<std::string>& GetOptions() const { return this->_options; }
|
||||
|
||||
/**
|
||||
* @brief Returns a vector of strings of the setting's options' descriptions.
|
||||
*/
|
||||
std::vector<std::string> GetDescriptions() const { return this->_descriptions; }
|
||||
const std::vector<std::string>& GetDescriptions() const { return this->_descriptions; }
|
||||
|
||||
/**
|
||||
* @brief Returns the index of the default option in the options vector for the setting.
|
||||
@@ -101,6 +101,7 @@ namespace randomizer::seedgen::settings
|
||||
bool TrackerImportant() const { return this->_trackerImportant; }
|
||||
bool NeedInGame() const { return this->_needInGame; }
|
||||
bool OptionsAreNumbers() const { return this->_optionsAreNumbers; }
|
||||
int GetOptionsBitLength() const {return this->_optionsBitLength; }
|
||||
|
||||
private:
|
||||
int _id = -1;
|
||||
@@ -116,6 +117,7 @@ namespace randomizer::seedgen::settings
|
||||
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
|
||||
int _optionsBitLength = 0;
|
||||
|
||||
// Variables that hold the setting's name and options when being checked
|
||||
// in a logical requirement string.
|
||||
@@ -135,14 +137,13 @@ namespace randomizer::seedgen::settings
|
||||
Setting(SettingInfo* info, const std::string& option);
|
||||
|
||||
void SetCurrentOption(const std::string& newOption);
|
||||
void SetCurrentOption(const int& newOptionIndex);
|
||||
void SetCurrentOption(int newOptionIndex);
|
||||
std::string GetCurrentOption() const;
|
||||
int GetCurrentOptionAsNumber() const;
|
||||
const int& GetCurrentOptionIndex() const { return this->_currentOptionIndex; }
|
||||
const bool& IsUsingRandomOption() const { return this->_isUsingRandomOption; }
|
||||
int GetCurrentOptionIndex() const { return this->_currentOptionIndex; }
|
||||
bool IsUsingRandomOption() const { return this->_isUsingRandomOption; }
|
||||
SettingInfo* GetInfo() const { return this->_info; }
|
||||
const std::string& GetCustomOption() const { return this->_customOption; }
|
||||
// void SetCustomOption(const std::string& newCustomOption);
|
||||
void ResolveIfRandom();
|
||||
|
||||
bool operator==(const char* optionName) const;
|
||||
@@ -158,7 +159,7 @@ namespace randomizer::seedgen::settings
|
||||
// Check to make sure all listed options exist
|
||||
for (const auto& optionName : {optionNames...})
|
||||
{
|
||||
if (!randomizer::utility::container::ElementInContainer(this->GetInfo()->GetOptions(), optionName))
|
||||
if (!utility::container::ElementInContainer(this->GetInfo()->GetOptions(), optionName))
|
||||
{
|
||||
throw std::runtime_error("\"" + std::string(optionName) + "\" is not a known option for setting \"" +
|
||||
this->GetInfo()->GetName() + "\"");
|
||||
@@ -222,4 +223,11 @@ namespace randomizer::seedgen::settings
|
||||
*/
|
||||
std::unique_ptr<SettingInfoMap_t> LoadAllSettingsInfo();
|
||||
|
||||
/**
|
||||
* @brief Generates a hash based on the current settings info. This can be used
|
||||
* to determine if settings between different permalinks are valid
|
||||
* @return the hash for the current settings info
|
||||
*/
|
||||
uint32_t GetSettingInfoHash();
|
||||
|
||||
}; // namespace randomizer::seedgen::settings
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "base64pp/base64pp.h"
|
||||
|
||||
// extra wrappers for convenience
|
||||
inline std::string b64_encode(const std::string& asciiStr) {
|
||||
std::vector<uint8_t> permalinkBytes = {};
|
||||
for(const char& ch : asciiStr)
|
||||
{
|
||||
permalinkBytes.push_back(ch);
|
||||
}
|
||||
|
||||
std::span<const uint8_t> span(permalinkBytes.begin(), permalinkBytes.end());
|
||||
|
||||
return base64pp::encode(span);
|
||||
}
|
||||
|
||||
inline std::string b64_decode(const std::string& b64Str) {
|
||||
const auto optional = base64pp::decode(b64Str);
|
||||
if(!optional.has_value())
|
||||
{
|
||||
// Return an empty string if there was an error
|
||||
return "";
|
||||
}
|
||||
|
||||
const auto& bytes = optional.value();
|
||||
return {bytes.begin(), bytes.end()};
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
#include "rando_config.hpp"
|
||||
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include "bool_button.hpp"
|
||||
#include "dusk/config.hpp"
|
||||
#include "dusk/data.hpp"
|
||||
@@ -15,8 +12,11 @@
|
||||
#include "pane.hpp"
|
||||
#include "rando_seed_generation.hpp"
|
||||
#include "string_button.hpp"
|
||||
|
||||
#include "d/d_file_select.h"
|
||||
#include "SDL3/SDL_clipboard.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
namespace dusk::ui {
|
||||
struct ConfigBoolProps {
|
||||
@@ -823,6 +823,29 @@ RandomizerWindow::RandomizerWindow(dFile_select_c* fileSelect /*= nullptr*/) : m
|
||||
rightPane, delete_seed_callback
|
||||
);
|
||||
|
||||
leftPane.add_section("Permalink");
|
||||
leftPane.register_control(
|
||||
leftPane.add_button("Copy Permalink")
|
||||
.on_pressed([] {
|
||||
CopyPermalinkToClipboard();
|
||||
}),
|
||||
rightPane, [](Pane& pane) {
|
||||
pane.clear();
|
||||
pane.add_text("Copy your current settings permalink to share with others.");
|
||||
auto text = pane.add_text(fmt::format("Current Permalink: {}", GetRandomizerConfig().GetPermalink()));
|
||||
text->SetProperty("word-break", "break-word");
|
||||
});
|
||||
|
||||
leftPane.register_control(
|
||||
leftPane.add_button("Paste Permalink")
|
||||
.on_pressed([] {
|
||||
PastePermalinkFromClipboard();
|
||||
}),
|
||||
rightPane, [](Pane& pane) {
|
||||
pane.clear();
|
||||
pane.add_text("Paste in a permalink from your clipboard. This will overwrite your current settings.");
|
||||
});
|
||||
|
||||
leftPane.add_section("Presets");
|
||||
leftPane.register_control(
|
||||
leftPane.add_button("Save Current Settings as Preset")
|
||||
@@ -1250,7 +1273,7 @@ void SaveNewRandomizerPreset(const std::string& presetName, bool overwriteExisti
|
||||
try {
|
||||
GetRandomizerConfig().WriteSettingsToFile(presetFilepath);
|
||||
} catch (std::exception& e) {
|
||||
auto modal = dynamic_cast<Modal*>(&push_document(std::make_unique<Modal>(Modal::Props{
|
||||
push_document(std::make_unique<Modal>(Modal::Props{
|
||||
.title = "Error Saving Preset",
|
||||
.bodyRml = fmt::format("Error: {}", e.what()),
|
||||
.actions = {
|
||||
@@ -1262,7 +1285,7 @@ void SaveNewRandomizerPreset(const std::string& presetName, bool overwriteExisti
|
||||
}
|
||||
},
|
||||
.icon = "error"
|
||||
})));
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1277,10 +1300,10 @@ void ApplyExistingRandomizerPreset(const std::filesystem::path& presetFilePath)
|
||||
// Don't overwrite the seed with the one from the preset
|
||||
auto seed = GetRandomizerConfig().GetSeed();
|
||||
try {
|
||||
GetRandomizerConfig().LoadFromFile(presetFilePath, GetRandomizerPreferencesPath(), false, false);
|
||||
GetRandomizerConfig().LoadFromFile(presetFilePath, GetRandomizerPreferencesPath(), false, true);
|
||||
GetRandomizerConfig().SetSeed(seed);
|
||||
} catch (std::exception& e) {
|
||||
auto modal = dynamic_cast<Modal*>(&push_document(std::make_unique<Modal>(Modal::Props{
|
||||
push_document(std::make_unique<Modal>(Modal::Props{
|
||||
.title = "Error Loading Preset",
|
||||
.bodyRml = fmt::format("Error: {}", e.what()),
|
||||
.actions = {
|
||||
@@ -1292,7 +1315,7 @@ void ApplyExistingRandomizerPreset(const std::filesystem::path& presetFilePath)
|
||||
}
|
||||
},
|
||||
.icon = "error"
|
||||
})));
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1303,6 +1326,55 @@ void ApplyExistingRandomizerPreset(const std::filesystem::path& presetFilePath)
|
||||
});
|
||||
}
|
||||
|
||||
void CopyPermalinkToClipboard() {
|
||||
if (SDL_SetClipboardText(GetRandomizerConfig().GetPermalink().data())) {
|
||||
push_toast(Toast{
|
||||
.content = "Permalink Copied",
|
||||
.duration = std::chrono::seconds(3)
|
||||
});
|
||||
} else {
|
||||
push_document(std::make_unique<Modal>(Modal::Props{
|
||||
.title = "Permalink Error",
|
||||
.bodyRml = fmt::format("Could not copy permalink to clipboard. Reason: {}", SDL_GetError()),
|
||||
.actions = {
|
||||
ModalAction{
|
||||
.label = "Okay",
|
||||
.onPressed = [](Modal& modal) {
|
||||
modal.pop();
|
||||
}
|
||||
}
|
||||
},
|
||||
.icon = "error"
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
void PastePermalinkFromClipboard() {
|
||||
if (SDL_HasClipboardText()) {
|
||||
std::string clipBoardText = SDL_GetClipboardText();
|
||||
auto result = GetRandomizerConfig().LoadFromPermalink(clipBoardText);
|
||||
if (result.has_value()) {
|
||||
auto modal = dynamic_cast<Modal*>(&push_document(std::make_unique<Modal>(Modal::Props{
|
||||
.title = "Permalink Error",
|
||||
.bodyRml = result.value(),
|
||||
.actions = {
|
||||
ModalAction{
|
||||
.label = "Okay",
|
||||
.onPressed = [](Modal& modal) {
|
||||
modal.pop();
|
||||
}
|
||||
}
|
||||
},
|
||||
.icon = "error"
|
||||
})));
|
||||
modal->root()->SetProperty("white-space", "pre-line");
|
||||
} else {
|
||||
push_toast(Toast{.content = "Applied Permalink", .duration = std::chrono::seconds(3)});
|
||||
SaveRandomizerConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path GetRandomizerPath() {
|
||||
return data::configured_data_path() / "randomizer";
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ class Pane;
|
||||
|
||||
void SaveNewRandomizerPreset(const std::string& presetName, bool overwriteExisting = false);
|
||||
void ApplyExistingRandomizerPreset(const std::filesystem::path& presetFilePath);
|
||||
void CopyPermalinkToClipboard();
|
||||
void PastePermalinkFromClipboard();
|
||||
std::filesystem::path GetRandomizerPath();
|
||||
std::filesystem::path GetRandomizerSettingsPath();
|
||||
std::filesystem::path GetRandomizerPreferencesPath();
|
||||
|
||||
Reference in New Issue
Block a user