add permalink sharing

This commit is contained in:
gymnast86
2026-06-20 07:57:49 -07:00
parent a3de85fb4b
commit 4d8fcfa551
17 changed files with 449 additions and 78 deletions
+1
View File
@@ -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()};
}
+81 -9
View File
@@ -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";
}
+2
View File
@@ -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();