mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-08-20 05:16:24 -04:00
Merge branch 'rando-mod' of https://github.com/TwilitRealm/dusk into rando-mod
This commit is contained in:
Vendored
+1
-1
Submodule extern/aurora updated: 8005d336ab...1d10fa1bc5
@@ -85,6 +85,11 @@ set(RANDOMIZER_SOURCES
|
||||
src/verify_item_functions.cpp
|
||||
src/paths.cpp
|
||||
src/session.cpp
|
||||
src/hooks.cpp
|
||||
src/ui/config_store.cpp
|
||||
src/ui/rando_seed_generation.cpp
|
||||
src/ui/rando_config.cpp
|
||||
src/ui/ui.cpp
|
||||
)
|
||||
|
||||
# Embed the generator's YAML data into the mod library. Paths are relative to this
|
||||
@@ -96,7 +101,6 @@ file(GLOB_RECURSE RANDOMIZER_DATA RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
# to both the mod and the standalone generator
|
||||
add_library(randomizer_embeds OBJECT)
|
||||
|
||||
b_embed(randomizer_embeds "res/shadow_crystal.bti")
|
||||
foreach (RANDOMIZER_FILE IN LISTS RANDOMIZER_DATA)
|
||||
if (RANDOMIZER_FILE MATCHES "^generator/data/tests")
|
||||
continue()
|
||||
@@ -105,13 +109,13 @@ foreach (RANDOMIZER_FILE IN LISTS RANDOMIZER_DATA)
|
||||
endforeach ()
|
||||
|
||||
add_mod(randomizer
|
||||
FEATURES game
|
||||
FEATURES game fmt
|
||||
SOURCES src/mod.cpp ${RANDOMIZER_GENERATOR_SOURCES} ${RANDOMIZER_SOURCES}
|
||||
MOD_JSON mod.json
|
||||
RES_DIR res
|
||||
)
|
||||
|
||||
target_link_libraries(randomizer PRIVATE yaml-cpp::yaml-cpp base64pp fmt::fmt randomizer_embeds)
|
||||
target_link_libraries(randomizer PRIVATE yaml-cpp::yaml-cpp base64pp randomizer_embeds)
|
||||
|
||||
string(LENGTH "${CMAKE_CURRENT_SOURCE_DIR}/" RANDOMIZER_SOURCE_PATH_SIZE)
|
||||
target_compile_definitions(randomizer PRIVATE
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <mods/api.h>
|
||||
|
||||
namespace randomizer::hooks {
|
||||
ModResult initialize();
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "d/d_com_inf_game.h"
|
||||
#include "randomizer_context.hpp"
|
||||
#include "custom_flow_ids.hpp"
|
||||
#include "utilities.h"
|
||||
#include "tools.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#include "mods/svc/log.h"
|
||||
|
||||
#include "session.hpp"
|
||||
#include "ui/ui.hpp"
|
||||
#include "hooks.hpp"
|
||||
|
||||
DEFINE_MOD();
|
||||
IMPORT_SERVICE(HostService, svc_host);
|
||||
@@ -10,6 +12,8 @@ IMPORT_SERVICE(HookService, svc_hook);
|
||||
IMPORT_SERVICE(UiService, svc_ui);
|
||||
IMPORT_SERVICE(ResourceService, svc_res);
|
||||
IMPORT_SERVICE(ConfigService, svc_config);
|
||||
IMPORT_SERVICE(SaveService, svc_save);
|
||||
IMPORT_SERVICE(StageService, svc_stage);
|
||||
|
||||
extern "C" {
|
||||
|
||||
@@ -21,17 +25,30 @@ MOD_EXPORT ModResult mod_initialize(ModError* error) {
|
||||
svc_hook,
|
||||
svc_ui,
|
||||
svc_res,
|
||||
svc_config
|
||||
svc_config,
|
||||
svc_save,
|
||||
svc_stage,
|
||||
});
|
||||
if (result != MOD_OK) {
|
||||
return mods::set_error(error, result, "failed to initialize session");
|
||||
}
|
||||
|
||||
result = randomizer::hooks::initialize();
|
||||
if (result != MOD_OK) {
|
||||
return mods::set_error(error, result, "failed to initialize hooks");
|
||||
}
|
||||
|
||||
result = randomizer::ui::initialize();
|
||||
if (result != MOD_OK) {
|
||||
return mods::set_error(error, result, "failed to initialize ui");
|
||||
}
|
||||
|
||||
svc_log->info(mod_ctx, "randomizer initialized");
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
MOD_EXPORT ModResult mod_update(ModError*) {
|
||||
randomizer::ui::update();
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,12 @@
|
||||
namespace randomizer::paths {
|
||||
|
||||
std::filesystem::path GetRandomizerPath() {
|
||||
// TODO: need a more permanent directory than this
|
||||
return session::svc_mng.host->mod_dir(session::svc_mng.mod_ctx);
|
||||
const char* dataDir = nullptr;
|
||||
if (session::svc_mng.host->data_dir(session::svc_mng.mod_ctx, &dataDir) != MOD_OK) {
|
||||
session::svc_mng.host->fail(session::svc_mng.mod_ctx, MOD_ERROR, "Failed to get data directory");
|
||||
}
|
||||
|
||||
return dataDir;
|
||||
}
|
||||
|
||||
std::filesystem::path GetRandomizerSettingsPath() {
|
||||
@@ -17,6 +21,10 @@ std::filesystem::path GetRandomizerPreferencesPath() {
|
||||
return GetRandomizerPath() / "preferences.yaml";
|
||||
}
|
||||
|
||||
std::filesystem::path GetRandomizerPresetsPath() {
|
||||
return GetRandomizerPath() / "presets";
|
||||
}
|
||||
|
||||
std::filesystem::path GetRandomizerSeedsPath() {
|
||||
return GetRandomizerPath() / "seeds";
|
||||
}
|
||||
|
||||
@@ -4,11 +4,10 @@
|
||||
|
||||
namespace randomizer::paths {
|
||||
|
||||
// Root of the randomizer's writable data (settings, preferences, generated seeds),
|
||||
// under the host's configured data path: <data>/randomizer/.
|
||||
std::filesystem::path GetRandomizerPath();
|
||||
std::filesystem::path GetRandomizerSettingsPath();
|
||||
std::filesystem::path GetRandomizerPreferencesPath();
|
||||
std::filesystem::path GetRandomizerPresetsPath();
|
||||
std::filesystem::path GetRandomizerSeedsPath();
|
||||
|
||||
} // namespace randomizer::paths
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "../generator/utility/string.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <mods/svc/log.hpp>
|
||||
|
||||
#include "custom_flow_ids.hpp"
|
||||
#include "d/actor/d_a_alink.h"
|
||||
@@ -24,7 +25,6 @@
|
||||
#include "d/d_meter2_info.h"
|
||||
#include "d/d_msg_class.h"
|
||||
#include "d/d_msg_flow.h"
|
||||
#include "fmt/format.h"
|
||||
#include "m_Do/m_Do_audio.h"
|
||||
|
||||
std::optional<std::string> RandomizerContext::WriteToFile() {
|
||||
@@ -154,7 +154,7 @@ std::optional<std::string> RandomizerContext::LoadFromHash(const std::string& ha
|
||||
this->mHash = hash;
|
||||
|
||||
if (!std::filesystem::exists(this->GetSeedDataPath())) {
|
||||
randomizer::session::LogError(fmt::format("Failed to load Hash: {}", hash).c_str());
|
||||
mods::log::error("Failed to load Hash: {}", hash);
|
||||
mHash.clear();
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -332,12 +332,14 @@ std::optional<std::string> RandomizerContext::LoadFromHash(const std::string& ha
|
||||
this->mReturnToPlaceOverrides[key] = override;
|
||||
}
|
||||
|
||||
// TODO: setup ui service to allow pushing toasts
|
||||
/*dusk::ui::push_toast(dusk::ui::Toast{
|
||||
.title = "Randomizer",
|
||||
.content = fmt::format("Loaded Randomizer Seed {}", this->mHash),
|
||||
.duration = std::chrono::seconds(3),
|
||||
});*/
|
||||
UiToastDesc desc = UI_TOAST_DESC_INIT;
|
||||
desc.type = "success";
|
||||
desc.title_rml = "Randomizer";
|
||||
std::string body_text = fmt::format("Loaded Randomizer Seed {}", this->mHash);
|
||||
desc.body_rml = body_text.c_str();
|
||||
desc.duration_ms = 3000;
|
||||
randomizer::session::svc_mng.ui->push_toast(randomizer::session::svc_mng.mod_ctx, &desc);
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
#include "session.hpp"
|
||||
|
||||
#include <mods/svc/log.hpp>
|
||||
|
||||
#include "randomizer_context.hpp"
|
||||
#include "flags.h"
|
||||
#include "item_ids.h"
|
||||
#include "tools.h"
|
||||
#include "stages.h"
|
||||
|
||||
#include "d/d_com_inf_game.h"
|
||||
#include "d/d_item.h"
|
||||
#include "d/d_meter2_info.h"
|
||||
|
||||
namespace randomizer::session {
|
||||
ServiceManager svc_mng;
|
||||
|
||||
@@ -9,5 +21,127 @@ ModResult initialize(const ServiceManager& services) {
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
void setupRandomizerFile() {
|
||||
// Setup file based on randomizer data
|
||||
auto& randoData = randomizer_GetContext();
|
||||
randoData.mCreatingSave = true;
|
||||
|
||||
// Set starting flags
|
||||
// Event Flags
|
||||
for (const auto& flag : randoData.mStartEventFlags) {
|
||||
dComIfGs_onEventBit(flag);
|
||||
}
|
||||
// Region Flags
|
||||
for (const auto& [region, flags] : randoData.mStartRegionFlags) {
|
||||
for (const auto& flag : flags) {
|
||||
onRegionFlag(region, flag);
|
||||
}
|
||||
}
|
||||
|
||||
// Map bits (fills in overworld on map)
|
||||
setRegionBit(randoData.mMapBits);
|
||||
|
||||
// Other flags based on starting flags
|
||||
if (dComIfGs_isEventBit(CLEARED_FARON_TWILIGHT))
|
||||
{
|
||||
dComIfGs_onDarkClearLV(0);
|
||||
dComIfGs_setLightDropNum(0, 0x10);
|
||||
execItemGet(dItemNo_Randomizer_DROP_CONTAINER_e);
|
||||
execItemGet(dItemNo_Randomizer_WEAR_KOKIRI_e);
|
||||
}
|
||||
|
||||
if (dComIfGs_isEventBit(CLEARED_ELDIN_TWILIGHT))
|
||||
{
|
||||
dComIfGs_onDarkClearLV(1);
|
||||
dComIfGs_setLightDropNum(1, 0x10);
|
||||
execItemGet(dItemNo_Randomizer_DROP_CONTAINER02_e);
|
||||
}
|
||||
|
||||
if (dComIfGs_isEventBit(CLEARED_LANAYRU_TWILIGHT))
|
||||
{
|
||||
dComIfGs_onDarkClearLV(2);
|
||||
dComIfGs_setLightDropNum(2, 0x10);
|
||||
execItemGet(dItemNo_Randomizer_DROP_CONTAINER03_e);
|
||||
}
|
||||
|
||||
if (randoData.mSettings[RandomizerContext::SKIP_MINOR_CUTSCENES] == RandomizerContext::ON)
|
||||
{
|
||||
// Add letter data in this order to more or less reflect an order they can be obtained in game
|
||||
static const int letterOrder[] = {3, 2, 4, 7, 5, 6, 13, 12, 10, 9, 8, 15, 0, 14, 11};
|
||||
int letterNum = 0;
|
||||
for (int i : letterOrder) {
|
||||
if (dMenu_Letter::getLetterName(i) != 0) {
|
||||
dComIfGs_onLetterGetFlag(i);
|
||||
dComIfGs_setGetNumber(letterNum++, i + 1);
|
||||
}
|
||||
}
|
||||
setAllLetterRead();
|
||||
}
|
||||
|
||||
// If MDH and the twilights are pre-completed
|
||||
if (dComIfGs_isEventBit(MIDNAS_DESPERATE_HOUR_COMPLETED))
|
||||
{
|
||||
if ((dComIfGs_getSaveData()->getPlayer().getPlayerStatusB().mDarkClearLevelFlag & 0x7) == 0x7)
|
||||
{
|
||||
dComIfGs_onDarkClearLV(3);
|
||||
dComIfGs_onTransformLV(3); // Puts Midna on players back
|
||||
}
|
||||
}
|
||||
|
||||
// Set starting inventory
|
||||
for (const auto& itemId: randoData.mStartingInventory) {
|
||||
execItemGet(itemId);
|
||||
}
|
||||
|
||||
g_randomizerState = RandomizerState();
|
||||
mods::log::debug("Created Rando Save");
|
||||
randoData.mCreatingSave = false;
|
||||
}
|
||||
|
||||
void registerStageEdits() {
|
||||
auto& ctx = randomizer_GetContext();
|
||||
auto stage_of = [](u32 key) -> const char* {
|
||||
const u32 stage_id = key >> 16;
|
||||
if (stage_id >= sizeof(allStages) / sizeof(allStages[0])) {
|
||||
return nullptr;
|
||||
}
|
||||
return allStages[stage_id];
|
||||
};
|
||||
|
||||
for (const auto& [key, patches] : ctx.mObjectPatches) {
|
||||
const char* stage = stage_of(key);
|
||||
if (stage == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const u8 room = (key >> 8) & 0xFF;
|
||||
const s8 layer = static_cast<s8>(key & 0xFF);
|
||||
for (const auto& [crc, bytes] : patches) {
|
||||
StageActorHandle handle{};
|
||||
|
||||
ModResult res;
|
||||
if (bytes.size() == RandomizerContext::OBJ_DELETE_SIZE) {
|
||||
res = svc_mng.stage->delete_actor(mod_ctx, stage, room, layer, crc, &handle);
|
||||
} else {
|
||||
res = svc_mng.stage->patch_actor(
|
||||
mod_ctx, stage, room, layer, crc, bytes.data(), bytes.size(), &handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [key, additions] : ctx.mObjectAdditions) {
|
||||
const char* stage = stage_of(key);
|
||||
if (stage == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const u8 room = (key >> 8) & 0xFF;
|
||||
const s8 layer = static_cast<s8>(key & 0xFF);
|
||||
for (const auto& bytes : additions) {
|
||||
StageActorHandle handle{};
|
||||
svc_mng.stage->add_actor(mod_ctx, stage, room, layer, bytes.data(), bytes.size(), &handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "mods/svc/hook.h"
|
||||
#include "mods/svc/ui.h"
|
||||
#include "mods/svc/resource.h"
|
||||
#include "mods/svc/save.h"
|
||||
#include "mods/svc/stage.h"
|
||||
|
||||
namespace randomizer::session {
|
||||
struct ServiceManager {
|
||||
@@ -16,21 +18,14 @@ struct ServiceManager {
|
||||
const UiService* ui;
|
||||
const ResourceService* resource;
|
||||
const ConfigService* config;
|
||||
const SaveService* save;
|
||||
const StageService* stage;
|
||||
};
|
||||
|
||||
extern ServiceManager svc_mng;
|
||||
|
||||
ModResult initialize(const ServiceManager& services);
|
||||
|
||||
inline void LogError(const char* msg) {
|
||||
svc_mng.log->error(svc_mng.mod_ctx, msg);
|
||||
}
|
||||
|
||||
inline void LogDebug(const char* msg) {
|
||||
svc_mng.log->debug(svc_mng.mod_ctx, msg);
|
||||
}
|
||||
|
||||
inline void LogWarn(const char* msg) {
|
||||
svc_mng.log->warn(svc_mng.mod_ctx, msg);
|
||||
}
|
||||
void setupRandomizerFile();
|
||||
void registerStageEdits();
|
||||
}
|
||||
@@ -6,14 +6,14 @@
|
||||
#include "d/d_item.h"
|
||||
#include "d/d_item_data.h"
|
||||
#include "f_op/f_op_actor_mng.h"
|
||||
#include "fmt/format.h"
|
||||
#include "item_ids.h"
|
||||
#include "randomizer_context.hpp"
|
||||
#include "session.hpp"
|
||||
#include "stages.h"
|
||||
#include "utilities.h"
|
||||
#include "verify_item_functions.h"
|
||||
|
||||
#include <mods/svc/log.hpp>
|
||||
|
||||
bool playerIsInRoomStage(s32 room, const char* stage)
|
||||
{
|
||||
// Only check room if it is valid
|
||||
@@ -713,7 +713,7 @@ int getStageSaveId(int id) {
|
||||
case 42: // F_SP102 (Title Screen / King Bulblin 1)
|
||||
return 0xFF;
|
||||
default:
|
||||
randomizer::session::LogWarn(fmt::format("Failed to find Save Id for ID: {}" , id).c_str());
|
||||
mods::log::warn("Failed to find Save Id for ID: {}" , id);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -745,4 +745,58 @@ bool tracker_isStageItem(int stage, int flag) {
|
||||
// Need to subtract 0x80 (MEMORY_ITEM constant in d_save.cpp) because the above function does it
|
||||
return g_dComIfG_gameInfo.info.getSavedata().getSave(stage).getBit().isItem(flag - 0x80);
|
||||
}
|
||||
}
|
||||
|
||||
// Kinda hacky, but will do for now
|
||||
void onRegionFlag(int i_stageNo, int i_no) {
|
||||
auto regionFlags = reinterpret_cast<u8*>(&dComIfGs_getSaveData()->getSave(i_stageNo).getBit());
|
||||
const int offset = i_no / 8;
|
||||
const int shift = i_no % 8;
|
||||
regionFlags[offset] |= (0x80 >> shift);
|
||||
}
|
||||
|
||||
void setRegionBit(u8 i_region) {
|
||||
dComIfGs_getSaveData()->getPlayer().getPlayerFieldLastStayInfo().mRegion |= i_region;
|
||||
}
|
||||
|
||||
void setAllLetterGet() {
|
||||
dComIfGs_getSaveData()->getPlayer().getLetterInfo().mLetterGetFlags[0] |= 0xFFFF;
|
||||
}
|
||||
|
||||
void setAllLetterRead() {
|
||||
dComIfGs_getSaveData()->getPlayer().getLetterInfo().mLetterReadFlags[0] |= 0xFFFF;
|
||||
}
|
||||
|
||||
constexpr const char* skyCharacterBlobName = "sky_characters";
|
||||
u8 g_skyCharacters = 0;
|
||||
|
||||
void saveAncientDocumentNum() {
|
||||
auto* ctx = randomizer::session::svc_mng.mod_ctx;
|
||||
randomizer::session::svc_mng.save->set_blob(ctx, skyCharacterBlobName, &g_skyCharacters, sizeof(g_skyCharacters));
|
||||
}
|
||||
|
||||
void loadAncientDocumentNum() {
|
||||
g_skyCharacters = 0;
|
||||
size_t size = sizeof(g_skyCharacters);
|
||||
auto* ctx = randomizer::session::svc_mng.mod_ctx;
|
||||
randomizer::session::svc_mng.save->get_blob(ctx, skyCharacterBlobName, &g_skyCharacters, &size);
|
||||
}
|
||||
|
||||
u8 getAncientDocumentNum() {
|
||||
return g_skyCharacters;
|
||||
}
|
||||
|
||||
void setAncientDocumentNum(u8 num) {
|
||||
g_skyCharacters = num;
|
||||
}
|
||||
|
||||
u8 getAreaKeyNum(int i_stageNo) {
|
||||
stage_stag_info_class* stagInfo = dComIfGp_getStageStagInfo();
|
||||
if (stagInfo != nullptr) {
|
||||
if (i_stageNo == dStage_stagInfo_GetSaveTbl(stagInfo)) {
|
||||
return dComIfGs_getKeyNum();
|
||||
}
|
||||
}
|
||||
|
||||
return dComIfGs_getSaveData()->getSave(i_stageNo).getBit().getKeyNum();
|
||||
}
|
||||
@@ -49,6 +49,17 @@ int getLocationItem(randomizer::logic::location::Location* location);
|
||||
int getStageSaveId(int id);
|
||||
int getStageSaveId(const char* stage);
|
||||
|
||||
void onRegionFlag(int i_stageNo, int i_no);
|
||||
void setRegionBit(u8 i_region);
|
||||
void setAllLetterGet();
|
||||
void setAllLetterRead();
|
||||
void setAncientDocumentNum(u8 num);
|
||||
u8 getAncientDocumentNum();
|
||||
u8 getAreaKeyNum(int);
|
||||
|
||||
void saveAncientDocumentNum();
|
||||
void loadAncientDocumentNum();
|
||||
|
||||
bool tracker_isEventBit(u16 flag);
|
||||
bool tracker_isStageSwitch(int stage, int flag);
|
||||
bool tracker_isStageItem(int stage, int flag);
|
||||
@@ -0,0 +1,32 @@
|
||||
#include "config_store.hpp"
|
||||
|
||||
#include <mods/svc/log.hpp>
|
||||
|
||||
#include "../../generator/seedgen/seed.hpp"
|
||||
#include "../paths.hpp"
|
||||
|
||||
namespace randomizer::ui {
|
||||
|
||||
seedgen::config::Config& GetRandomizerConfig() {
|
||||
static seedgen::config::Config s_config{paths::GetRandomizerSettingsPath(),
|
||||
paths::GetRandomizerPreferencesPath()};
|
||||
return s_config;
|
||||
}
|
||||
|
||||
void SaveRandomizerConfig() {
|
||||
GetRandomizerConfig().WriteToFile(paths::GetRandomizerSettingsPath(),
|
||||
paths::GetRandomizerPreferencesPath());
|
||||
}
|
||||
|
||||
bool TryCreateRandomSeed() {
|
||||
auto& config = GetRandomizerConfig();
|
||||
|
||||
if (config.GetSeed().empty()) {
|
||||
config.SetSeed(seedgen::seed::GenerateSeed());
|
||||
SaveRandomizerConfig();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace randomizer::ui
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "../../generator/seedgen/config.hpp"
|
||||
#include "../../generator/seedgen/settings.hpp"
|
||||
|
||||
namespace randomizer::ui {
|
||||
|
||||
seedgen::config::Config& GetRandomizerConfig();
|
||||
void SaveRandomizerConfig();
|
||||
seedgen::settings::Setting* FindSetting(const std::string& key);
|
||||
bool TryCreateRandomSeed();
|
||||
|
||||
} // namespace randomizer::ui
|
||||
@@ -0,0 +1,613 @@
|
||||
#include "rando_config.hpp"
|
||||
|
||||
#include <mods/svc/log.hpp>
|
||||
|
||||
#include "../session.hpp"
|
||||
#include "../tools.h"
|
||||
#include "../paths.hpp"
|
||||
#include "../../generator/seedgen/seed.hpp"
|
||||
#include "../../generator/utility/string.hpp"
|
||||
|
||||
#include "rando_seed_generation.hpp"
|
||||
#include "config_store.hpp"
|
||||
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <map>
|
||||
|
||||
#include "../randomizer_context.hpp"
|
||||
#include "d/d_file_select.h"
|
||||
|
||||
namespace randomizer::ui {
|
||||
|
||||
seedgen::settings::Setting* FindSetting(const std::string& key) {
|
||||
if (key.empty()) {
|
||||
mods::log::error("Key is empty! Unable to find setting.");
|
||||
}
|
||||
|
||||
// TODO: handle multi-world selection
|
||||
auto& settings = GetRandomizerConfig().GetSettings();
|
||||
try {
|
||||
return &settings.GetMap().at(key);
|
||||
} catch (std::exception e) {
|
||||
mods::log::error("Failed to get Settings Key: {}", key);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::pair<std::string, std::string>>& GetStartingInventoryLayoutOrder() {
|
||||
static const std::vector<std::pair<std::string, std::string>> layoutOrder = {
|
||||
// { display name , logic item name }
|
||||
{"Shadow Crystal", "Shadow Crystal"},
|
||||
{"Horse Call", "Horse Call"},
|
||||
{"Fishing Rod", "Progressive Fishing Rod"},
|
||||
{"Slingshot", "Slingshot"},
|
||||
{"Lantern", "Lantern"},
|
||||
{"Gale Boomerang", "Gale Boomerang"},
|
||||
{"Iron Boots", "Iron Boots"},
|
||||
{"Bow", "Progressive Bow"},
|
||||
{"Hawkeye", "Hawkeye"},
|
||||
{"Bomb Bags", "Bomb Bag"},
|
||||
{"Giant Bomb Bags", "Giant Bomb Bag"},
|
||||
{"Clawshot", "Progressive Clawshot"},
|
||||
{"Spinner", "Spinner"},
|
||||
{"Ball and Chain", "Ball and Chain"},
|
||||
{"Dominion Rod", "Progressive Dominion Rod"},
|
||||
{"Empty Bottle", "Empty Bottle"},
|
||||
{"Auru's Memo", "Aurus Memo"},
|
||||
{"Ashei's Sketch", "Asheis Sketch"},
|
||||
{"Sky Book", "Progressive Sky Book"},
|
||||
{"Sword", "Progressive Sword"},
|
||||
{"Ordon Shield", "Ordon Shield"},
|
||||
{"Hylian Shield", "Hylian Shield"},
|
||||
{"Zora Armor", "Zora Armor"},
|
||||
{"Magic Armor", "Magic Armor"},
|
||||
{"Wallet", "Progressive Wallet"},
|
||||
{"Hidden Skills", "Progressive Hidden Skill"},
|
||||
{"Poe Souls", "Poe Soul"},
|
||||
{"Fused Shadows", "Progressive Fused Shadow"},
|
||||
{"Mirror Shards", "Progressive Mirror Shard"},
|
||||
{"Gate Keys", "Gate Keys"},
|
||||
{"Gerudo Desert Bulblin Camp Key", "Gerudo Desert Bulblin Camp Key"},
|
||||
{"Forest Temple Small Keys", "Forest Temple Small Key"},
|
||||
{"Goron Mines Small Keys", "Goron Mines Small Key"},
|
||||
{"Lakebed Temple Small Keys", "Lakebed Temple Small Key"},
|
||||
{"Arbiter's Grounds Small Keys", "Arbiters Grounds Small Key"},
|
||||
{"Snowpeak Ruins Small Keys", "Snowpeak Ruins Small Key"},
|
||||
{"Ordon Pumpkin", "Ordon Pumpkin"},
|
||||
{"Ordon Cheese", "Ordon Cheese"},
|
||||
{"Temple of Time Small Keys", "Temple of Time Small Key"},
|
||||
{"City in the Sky Small Keys", "City in the Sky Small Key"},
|
||||
{"Palace of Twilight Small Keys", "Palace of Twilight Small Key"},
|
||||
{"Hyrule Castle Small Keys", "Hyrule Castle Small Key"},
|
||||
{"Forest Temple Big Key", "Forest Temple Big Key"},
|
||||
{"Goron Mines Key Shards", "Goron Mines Key Shard"},
|
||||
{"Lakebed Temple Big Key", "Lakebed Temple Big Key"},
|
||||
{"Arbiter's Grounds Big Key", "Arbiters Grounds Big Key"},
|
||||
{"Snowpeak Ruins Bedroom Key", "Snowpeak Ruins Bedroom Key"},
|
||||
{"Temple of Time Big Key", "Temple of Time Big Key"},
|
||||
{"City in the Sky Big Key", "City in the Sky Big Key"},
|
||||
{"Palace of Twilight Big Key", "Palace of Twilight Big Key"},
|
||||
{"Hyrule Castle Big Key", "Hyrule Castle Big Key"},
|
||||
{"Gerudo Desert Portal", "Gerudo Desert Portal"},
|
||||
{"Mirror Chamber Portal", "Mirror Chamber Portal"},
|
||||
{"Snowpeak Portal", "Snowpeak Portal"},
|
||||
{"Sacred Grove Portal", "Sacred Grove Portal"},
|
||||
{"Bridge of Eldin Portal", "Bridge of Eldin Portal"},
|
||||
{"Upper Zora's River Portal", "Upper Zoras River Portal"}
|
||||
};
|
||||
return layoutOrder;
|
||||
}
|
||||
|
||||
UiMenuTabHandle g_menu_tab{};
|
||||
|
||||
FileSelectGateWindowCtx g_file_select_window_ctx{};
|
||||
|
||||
namespace {
|
||||
// Control Helpers
|
||||
void add_button(UiElementHandle pane, const char* label, const char* help_rml,
|
||||
UiPressedFn on_pressed, void* userdata = nullptr, UiElementHandle* out_handle = nullptr)
|
||||
{
|
||||
UiControlDesc desc = UI_CONTROL_DESC_INIT;
|
||||
desc.kind = UI_CONTROL_BUTTON;
|
||||
desc.label = label;
|
||||
desc.help_rml = help_rml;
|
||||
desc.on_pressed = on_pressed;
|
||||
desc.user_data = userdata;
|
||||
session::svc_mng.ui->pane_add_control(session::svc_mng.mod_ctx, pane, &desc, out_handle);
|
||||
}
|
||||
|
||||
void add_section(UiElementHandle pane, const char* label) {
|
||||
session::svc_mng.ui->pane_add_section(session::svc_mng.mod_ctx, pane, label);
|
||||
}
|
||||
|
||||
void add_string_input(UiElementHandle pane, const char* label, const char* help_rml,
|
||||
int32_t max_length, UiControlGetFn getFn, UiControlSetFn setFn, UiElementHandle* out_handle = nullptr)
|
||||
{
|
||||
UiControlDesc desc = UI_CONTROL_DESC_INIT;
|
||||
desc.kind = UI_CONTROL_STRING;
|
||||
desc.label = label;
|
||||
desc.help_rml = help_rml;
|
||||
desc.binding = UI_BINDING_CALLBACKS;
|
||||
desc.get = getFn;
|
||||
desc.set = setFn;
|
||||
desc.max_length = max_length;
|
||||
session::svc_mng.ui->pane_add_control(session::svc_mng.mod_ctx, pane, &desc, out_handle);
|
||||
}
|
||||
|
||||
void add_select(UiElementHandle pane, const char* label, const char* help_rml, const char** options,
|
||||
size_t option_count, UiControlGetFn getFn, UiControlSetFn setFn, UiElementHandle* out_handle = nullptr)
|
||||
{
|
||||
UiControlDesc desc = UI_CONTROL_DESC_INIT;
|
||||
desc.kind = UI_CONTROL_SELECT;
|
||||
desc.label = label;
|
||||
desc.help_rml = help_rml;
|
||||
desc.binding = UI_BINDING_CALLBACKS;
|
||||
desc.get = getFn;
|
||||
desc.set = setFn;
|
||||
desc.options = options;
|
||||
desc.option_count = option_count;
|
||||
session::svc_mng.ui->pane_add_control(session::svc_mng.mod_ctx, pane, &desc, out_handle);
|
||||
}
|
||||
|
||||
void add_select_setting(UiElementHandle pane, const char* key, UiElementHandle* out_handle = nullptr)
|
||||
{
|
||||
auto setting = FindSetting(key);
|
||||
auto info = setting->GetInfo();
|
||||
|
||||
std::vector<const char*> optionsList;
|
||||
std::string help_rml = "";
|
||||
for (size_t i = 0; i < info->GetOptions().size(); ++i) {
|
||||
optionsList.push_back(info->GetOptions()[i].c_str());
|
||||
help_rml += fmt::format("<br/><span style=\"color: #C2A42D;\">{}</span>: {}", info->GetOptions()[i], info->GetDescriptions()[i]);
|
||||
}
|
||||
|
||||
auto getFn = [](ModContext*, void* user_data, UiControlValue* out_value) {
|
||||
auto setting = FindSetting(static_cast<const char*>(user_data));
|
||||
const auto& options = setting->GetInfo()->GetOptions();
|
||||
|
||||
for (size_t i = 0; i < options.size(); ++i) {
|
||||
if (options[i] == setting->GetCurrentOption()) {
|
||||
out_value->int_value = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// default
|
||||
out_value->int_value = 0;
|
||||
};
|
||||
|
||||
auto setFn = [](ModContext*, void* user_data, const UiControlValue* value) {
|
||||
auto setting = FindSetting(static_cast<const char*>(user_data));
|
||||
const auto& options = setting->GetInfo()->GetOptions();
|
||||
|
||||
if (value->int_value >= 0 && value->int_value < options.size()) {
|
||||
setting->SetCurrentOption(options[value->int_value]);
|
||||
SaveRandomizerConfig();
|
||||
}
|
||||
};
|
||||
|
||||
UiControlDesc desc = UI_CONTROL_DESC_INIT;
|
||||
desc.kind = UI_CONTROL_SELECT;
|
||||
desc.label = key;
|
||||
desc.help_rml = help_rml.c_str();
|
||||
desc.binding = UI_BINDING_CALLBACKS;
|
||||
desc.get = getFn;
|
||||
desc.set = setFn;
|
||||
desc.user_data = (void*)key;
|
||||
desc.options = optionsList.data();
|
||||
desc.option_count = optionsList.size();
|
||||
session::svc_mng.ui->pane_add_control(session::svc_mng.mod_ctx, pane, &desc, out_handle);
|
||||
}
|
||||
|
||||
void add_select_number_setting(UiElementHandle pane, const char* key, UiElementHandle* out_handle = nullptr)
|
||||
{
|
||||
auto setting = FindSetting(key);
|
||||
auto info = setting->GetInfo();
|
||||
|
||||
std::vector<const char*> optionsList;
|
||||
for (size_t i = 0; i < info->GetOptions().size(); ++i) {
|
||||
optionsList.push_back(info->GetOptions()[i].c_str());
|
||||
}
|
||||
|
||||
auto getFn = [](ModContext*, void* user_data, UiControlValue* out_value) {
|
||||
auto setting = FindSetting(static_cast<const char*>(user_data));
|
||||
const auto& options = setting->GetInfo()->GetOptions();
|
||||
|
||||
for (size_t i = 0; i < options.size(); ++i) {
|
||||
if (options[i] == setting->GetCurrentOption()) {
|
||||
out_value->int_value = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// default
|
||||
out_value->int_value = 0;
|
||||
};
|
||||
|
||||
auto setFn = [](ModContext*, void* user_data, const UiControlValue* value) {
|
||||
auto setting = FindSetting(static_cast<const char*>(user_data));
|
||||
const auto& options = setting->GetInfo()->GetOptions();
|
||||
|
||||
if (value->int_value >= 0 && value->int_value < options.size()) {
|
||||
setting->SetCurrentOption(options[value->int_value]);
|
||||
SaveRandomizerConfig();
|
||||
}
|
||||
};
|
||||
|
||||
UiControlDesc desc = UI_CONTROL_DESC_INIT;
|
||||
desc.kind = UI_CONTROL_SELECT;
|
||||
desc.label = key;
|
||||
desc.help_rml = "";
|
||||
desc.binding = UI_BINDING_CALLBACKS;
|
||||
desc.get = getFn;
|
||||
desc.set = setFn;
|
||||
desc.user_data = (void*)key;
|
||||
desc.options = optionsList.data();
|
||||
desc.option_count = optionsList.size();
|
||||
session::svc_mng.ui->pane_add_control(session::svc_mng.mod_ctx, pane, &desc, out_handle);
|
||||
}
|
||||
|
||||
// Seed Management Tab
|
||||
ModResult buildSeedManagementTab(ModContext* ctx, UiWindowHandle, UiElementHandle leftPane,
|
||||
UiElementHandle rightPane, void*, ModError*)
|
||||
{
|
||||
add_button(leftPane,
|
||||
"Generate Seed",
|
||||
"Generate a Randomizer seed using the current configuration options, and the supplied seed string.",
|
||||
[](ModContext*, void*) {
|
||||
if (TryCreateRandomSeed()) {
|
||||
mods::log::info("Created new Seed for generator.");
|
||||
}
|
||||
GenerateRandomizerSeed();
|
||||
});
|
||||
|
||||
add_string_input(leftPane,
|
||||
"Seed String",
|
||||
"Current value of the seed used by the randomizer for generation. Leave blank for a random value.",
|
||||
32,
|
||||
[](ModContext*, void*, UiControlValue* out_value) {
|
||||
out_value->string_value = GetRandomizerConfig().GetSeed().c_str();
|
||||
},
|
||||
[](ModContext*, void*, const UiControlValue* value) {
|
||||
GetRandomizerConfig().SetSeed(value->string_value);
|
||||
SaveRandomizerConfig();
|
||||
});
|
||||
|
||||
add_button(leftPane,
|
||||
"Delete Seeds",
|
||||
"Delete any seed not currently being used.",
|
||||
[](ModContext*, void*) {
|
||||
// TODO
|
||||
});
|
||||
|
||||
add_section(leftPane, "Permalink");
|
||||
|
||||
{
|
||||
std::string help_rml = "Copy your current settings permalink to share with others.";
|
||||
help_rml += fmt::format("<br/>Current Permalink: {}", GetRandomizerConfig().GetPermalink());
|
||||
add_button(leftPane,
|
||||
"Copy Permalink",
|
||||
help_rml.c_str(),
|
||||
[](ModContext*, void*) {
|
||||
// TODO: need SDL clipboard access
|
||||
});
|
||||
}
|
||||
|
||||
add_button(leftPane,
|
||||
"Paste Permalink",
|
||||
"Paste in a permalink from your clipboard. This will overwrite your current settings.",
|
||||
[](ModContext*, void*) {
|
||||
// TODO: need SDL clipboard access
|
||||
});
|
||||
|
||||
add_section(leftPane, "Presets");
|
||||
|
||||
add_button(leftPane,
|
||||
"Save Current Settings as Preset",
|
||||
"Save the current settings to your list of presets.",
|
||||
[](ModContext*, void*) {
|
||||
// TODO
|
||||
});
|
||||
|
||||
add_button(leftPane,
|
||||
"Load Preset",
|
||||
"Choose an existing preset to load from.",
|
||||
[](ModContext*, void*) {
|
||||
// TODO
|
||||
});
|
||||
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
ModResult updateSeedManagementTab(ModContext* ctx, void*, ModError*) {
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
// Seed Options Tab
|
||||
ModResult buildSeedOptionsTab(ModContext* ctx, UiWindowHandle, UiElementHandle leftPane,
|
||||
UiElementHandle rightPane, void*, ModError*)
|
||||
{
|
||||
add_button(leftPane,
|
||||
"Reset Settings to Default",
|
||||
"Reset all settings to their default values. This will also clear starting items and excluded locations.",
|
||||
[](ModContext*, void*) {
|
||||
GetRandomizerConfig().ResetSettingsToDefault();
|
||||
SaveRandomizerConfig();
|
||||
});
|
||||
|
||||
add_section(leftPane, "Logic Settings");
|
||||
add_select_setting(leftPane, "Logic Rules");
|
||||
|
||||
add_section(leftPane, "Access Options");
|
||||
add_select_setting(leftPane, "Hyrule Barrier Requirements");
|
||||
add_select_setting(leftPane, "Palace of Twilight Requirements");
|
||||
add_select_setting(leftPane, "Faron Woods Logic");
|
||||
add_select_setting(leftPane, "Mirror Chamber Access");
|
||||
|
||||
add_section(leftPane, "Shuffles");
|
||||
add_select_setting(leftPane, "Golden Bugs");
|
||||
add_select_setting(leftPane, "Sky Characters");
|
||||
add_select_setting(leftPane, "Gifts From NPCs");
|
||||
add_select_setting(leftPane, "Shop Items");
|
||||
add_select_setting(leftPane, "Hidden Skills");
|
||||
add_select_setting(leftPane, "Hidden Rupees");
|
||||
add_select_setting(leftPane, "Freestanding Rupees");
|
||||
add_select_setting(leftPane, "Poe Souls");
|
||||
add_select_setting(leftPane, "Ilia Memory Quest");
|
||||
add_select_setting(leftPane, "Item Scarcity");
|
||||
add_select_setting(leftPane, "Trap Item Frequency");
|
||||
|
||||
add_section(leftPane, "Dungeon Items");
|
||||
add_select_setting(leftPane, "Small Keys");
|
||||
add_select_setting(leftPane, "Big Keys");
|
||||
add_select_setting(leftPane, "Maps and Compasses");
|
||||
add_select_setting(leftPane, "Hyrule Castle Big Key Requirements");
|
||||
add_select_setting(leftPane, "Dungeon Rewards Can Be Anywhere");
|
||||
add_select_setting(leftPane, "No Small Keys on Bosses");
|
||||
add_select_setting(leftPane, "Unrequired Dungeons Are Barren");
|
||||
|
||||
add_section(leftPane, "Timesavers");
|
||||
add_select_setting(leftPane, "Skip Prologue");
|
||||
add_select_setting(leftPane, "Faron Twilight Cleared");
|
||||
add_select_setting(leftPane, "Eldin Twilight Cleared");
|
||||
add_select_setting(leftPane, "Lanayru Twilight Cleared");
|
||||
add_select_setting(leftPane, "Skip Midna's Desparate Hour");
|
||||
add_select_setting(leftPane, "Skip Minor Cutscenes");
|
||||
add_select_setting(leftPane, "Skip Major Cutscenes");
|
||||
add_select_setting(leftPane, "Unlock Map Regions");
|
||||
add_select_setting(leftPane, "Open Door of Time");
|
||||
add_select_setting(leftPane, "Active Goron Mines Magnets");
|
||||
add_select_setting(leftPane, "Lower Hyrule Castle Chandelier");
|
||||
add_select_setting(leftPane, "Skip Bridge Donation");
|
||||
|
||||
add_section(leftPane, "Additional Settings");
|
||||
add_select_setting(leftPane, "Starting Time of Day");
|
||||
add_select_setting(leftPane, "Logic Transform Anywhere");
|
||||
add_select_setting(leftPane, "Logic Increase Wallet Capacity");
|
||||
add_select_setting(leftPane, "Logic Damage Multiplier");
|
||||
|
||||
add_section(leftPane, "Dungeon Entrance Settings");
|
||||
add_select_setting(leftPane, "Lakebed Does Not Require Water Bombs");
|
||||
add_select_setting(leftPane, "Arbiters Does Not Require Bulblin Camp");
|
||||
add_select_setting(leftPane, "Snowpeak Does Not Require Reekfish Scent");
|
||||
add_select_setting(leftPane, "Sacred Grove Does Not Require Skull Kid");
|
||||
add_select_setting(leftPane, "City Does Not Require Filled Skybook");
|
||||
add_select_setting(leftPane, "Goron Mines Entrance");
|
||||
add_select_setting(leftPane, "Temple of Time Sword Requirement");
|
||||
|
||||
add_section(leftPane, "Tricks");
|
||||
add_select_setting(leftPane, "Back Slice as Sword");
|
||||
add_select_setting(leftPane, "Ball and Chain Webs");
|
||||
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
// Hints Tab
|
||||
ModResult buildHintsTab(ModContext* ctx, UiWindowHandle, UiElementHandle leftPane,
|
||||
UiElementHandle rightPane, void*, ModError*)
|
||||
{
|
||||
add_section(leftPane, "Path Hints");
|
||||
add_select_number_setting(leftPane, "Number of Path Hints");
|
||||
add_select_setting(leftPane, "Path Hints on Midna");
|
||||
add_select_setting(leftPane, "Path Hints on Hint Signs");
|
||||
|
||||
add_section(leftPane, "Barren Hints");
|
||||
add_select_number_setting(leftPane, "Number of Barren Hints");
|
||||
add_select_setting(leftPane, "Barren Hints on Midna");
|
||||
add_select_setting(leftPane, "Barren Hints on Hint Signs");
|
||||
|
||||
add_section(leftPane, "Item Hints");
|
||||
add_select_number_setting(leftPane, "Number of Item Hints");
|
||||
add_select_setting(leftPane, "Item Hints on Midna");
|
||||
add_select_setting(leftPane, "Item Hints on Hint Signs");
|
||||
|
||||
add_section(leftPane, "Location Hints");
|
||||
add_select_number_setting(leftPane, "Number of Location Hints");
|
||||
add_select_setting(leftPane, "Location Hints on Midna");
|
||||
add_select_setting(leftPane, "Location Hints on Hint Signs");
|
||||
add_select_setting(leftPane, "Prioritize Remote Location Hints");
|
||||
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
// Starting Inventory Tab
|
||||
ModResult buildStartingInventoryTab(ModContext* ctx, UiWindowHandle, UiElementHandle leftPane,
|
||||
UiElementHandle rightPane, void*, ModError*)
|
||||
{
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
// Excluded Locations Tab
|
||||
ModResult buildExcludedLocationsTab(ModContext* ctx, UiWindowHandle, UiElementHandle leftPane,
|
||||
UiElementHandle rightPane, void*, ModError*)
|
||||
{
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
// Menu Tab
|
||||
void OnMenuTabSelected(ModContext* ctx, void*) {
|
||||
UiTabDesc tabs[5]{};
|
||||
|
||||
tabs[0].struct_size = sizeof(UiTabDesc);
|
||||
tabs[0].title = "Seed Management";
|
||||
tabs[0].build = buildSeedManagementTab;
|
||||
tabs[0].update = updateSeedManagementTab;
|
||||
|
||||
tabs[1].struct_size = sizeof(UiTabDesc);
|
||||
tabs[1].title = "Seed Options";
|
||||
tabs[1].build = buildSeedOptionsTab;
|
||||
|
||||
tabs[2].struct_size = sizeof(UiTabDesc);
|
||||
tabs[2].title = "Hints";
|
||||
tabs[2].build = buildHintsTab;
|
||||
|
||||
tabs[3].struct_size = sizeof(UiTabDesc);
|
||||
tabs[3].title = "Starting Inventory";
|
||||
tabs[3].build = buildStartingInventoryTab;
|
||||
|
||||
tabs[4].struct_size = sizeof(UiTabDesc);
|
||||
tabs[4].title = "Excluded Locations";
|
||||
tabs[4].build = buildExcludedLocationsTab;
|
||||
|
||||
UiWindowDesc desc = UI_WINDOW_DESC_INIT;
|
||||
desc.tabs = tabs;
|
||||
desc.tab_count = 5;
|
||||
UiWindowHandle window{};
|
||||
session::svc_mng.ui->window_push(ctx, &desc, &window);
|
||||
}
|
||||
|
||||
// Play Tab
|
||||
ModResult buildPlayTab(ModContext* ctx, UiWindowHandle, UiElementHandle leftPane,
|
||||
UiElementHandle rightPane, void*, ModError*)
|
||||
{
|
||||
std::filesystem::path seed_dir = paths::GetRandomizerSeedsPath();
|
||||
if (!std::filesystem::exists(seed_dir))
|
||||
std::filesystem::create_directory(seed_dir);
|
||||
|
||||
std::string help_rml = "";
|
||||
if (std::filesystem::is_empty(seed_dir)) {
|
||||
help_rml = "No seeds generated! You can generate a seed from the Seed Management Tab.";
|
||||
} else {
|
||||
help_rml = "Choose which seed you want to play.";
|
||||
}
|
||||
|
||||
std::vector<std::string> seedHashes;
|
||||
for (const auto& entry : std::filesystem::directory_iterator(seed_dir)) {
|
||||
if (entry.is_directory()) {
|
||||
seedHashes.push_back(entry.path().filename().string());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<const char*> availableSeeds;
|
||||
for (const auto& hash : seedHashes) {
|
||||
availableSeeds.push_back(hash.c_str());
|
||||
}
|
||||
|
||||
add_select(leftPane,
|
||||
"Selected Seed",
|
||||
help_rml.c_str(),
|
||||
availableSeeds.data(),
|
||||
availableSeeds.size(),
|
||||
[](ModContext*, void*, UiControlValue* out_value) {
|
||||
int idx = 0;
|
||||
for (const auto& entry : std::filesystem::directory_iterator(paths::GetRandomizerSeedsPath())) {
|
||||
if (entry.is_directory()) {
|
||||
std::string hash = entry.path().filename().string();
|
||||
if (randomizer_GetContext().mHash == hash) {
|
||||
break;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
out_value->int_value = idx;
|
||||
},
|
||||
[](ModContext*, void*, const UiControlValue* value) {
|
||||
int idx = 0;
|
||||
for (const auto& entry : std::filesystem::directory_iterator(paths::GetRandomizerSeedsPath())) {
|
||||
if (entry.is_directory()) {
|
||||
if (idx == value->int_value) {
|
||||
std::string hash = entry.path().filename().string();
|
||||
randomizer_GetContext() = RandomizerContext();
|
||||
randomizer_GetContext().LoadFromHash(hash);
|
||||
session::registerStageEdits();
|
||||
break;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
add_button(leftPane,
|
||||
"Start Randomizer",
|
||||
"",
|
||||
[](ModContext*, void* userdata) {
|
||||
// set flag to move to name screen after window close
|
||||
g_file_select_window_ctx.is_proceed = true;
|
||||
session::svc_mng.ui->window_close(session::svc_mng.mod_ctx, *static_cast<UiWindowHandle*>(userdata));
|
||||
},
|
||||
&g_file_select_window_ctx.window_handle);
|
||||
|
||||
return MOD_OK;
|
||||
}
|
||||
}
|
||||
|
||||
ModResult buildMenuTab() {
|
||||
UiMenuTabDesc desc = UI_MENU_TAB_DESC_INIT;
|
||||
desc.label = "Randomizer";
|
||||
desc.on_selected = OnMenuTabSelected;
|
||||
|
||||
return session::svc_mng.ui->register_menu_tab(session::svc_mng.mod_ctx, &desc, &g_menu_tab);
|
||||
}
|
||||
|
||||
ModResult buildFileSelectGateMenu(dFile_select_c* fileSelect) {
|
||||
UiTabDesc tabs[6]{};
|
||||
|
||||
tabs[0].struct_size = sizeof(UiTabDesc);
|
||||
tabs[0].title = "Play";
|
||||
tabs[0].build = buildPlayTab;
|
||||
|
||||
tabs[1].struct_size = sizeof(UiTabDesc);
|
||||
tabs[1].title = "Seed Management";
|
||||
tabs[1].build = buildSeedManagementTab;
|
||||
tabs[1].update = updateSeedManagementTab;
|
||||
|
||||
tabs[2].struct_size = sizeof(UiTabDesc);
|
||||
tabs[2].title = "Seed Options";
|
||||
tabs[2].build = buildSeedOptionsTab;
|
||||
|
||||
tabs[3].struct_size = sizeof(UiTabDesc);
|
||||
tabs[3].title = "Hints";
|
||||
tabs[3].build = buildHintsTab;
|
||||
|
||||
tabs[4].struct_size = sizeof(UiTabDesc);
|
||||
tabs[4].title = "Starting Inventory";
|
||||
tabs[4].build = buildStartingInventoryTab;
|
||||
|
||||
tabs[5].struct_size = sizeof(UiTabDesc);
|
||||
tabs[5].title = "Excluded Locations";
|
||||
tabs[5].build = buildExcludedLocationsTab;
|
||||
|
||||
UiWindowDesc desc = UI_WINDOW_DESC_INIT;
|
||||
desc.tabs = tabs;
|
||||
desc.tab_count = 6;
|
||||
desc.user_data = fileSelect;
|
||||
desc.on_closed = [](ModContext*, UiWindowHandle, void* userdata) {
|
||||
dFile_select_c* i_this = static_cast<dFile_select_c*>(userdata);
|
||||
|
||||
// if closing the window through backing out, return to file select
|
||||
if (!g_file_select_window_ctx.is_proceed) {
|
||||
i_this->headerTxtSet(0x43, 1, 0);
|
||||
i_this->fileRecScaleAnmInitSet2(0.0f, 1.0f);
|
||||
i_this->nameMoveAnmInitSet(0xd29, 0xd1f);
|
||||
i_this->modoruTxtDispAnmInit(0);
|
||||
i_this->mDataSelProc = dFile_select_c::DATASELPROC_NAME_TO_DATA_SELECT_MOVE;
|
||||
}
|
||||
|
||||
g_dialogSelectModeState = SelectReady;
|
||||
};
|
||||
|
||||
return session::svc_mng.ui->window_push(session::svc_mng.mod_ctx, &desc, &g_file_select_window_ctx.window_handle);
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
#include <mods/api.h>
|
||||
#include "mods/svc/ui.h"
|
||||
|
||||
// Forward declaration
|
||||
namespace randomizer::seedgen::config {
|
||||
class Config;
|
||||
}
|
||||
class dFile_select_c;
|
||||
|
||||
namespace randomizer::ui {
|
||||
|
||||
enum dialogSelectModeState : uint8_t {
|
||||
SelectReady,
|
||||
SelectWait,
|
||||
SelectVanilla,
|
||||
SelectRandomizer
|
||||
};
|
||||
extern dialogSelectModeState g_dialogSelectModeState;
|
||||
|
||||
struct FileSelectGateWindowCtx {
|
||||
UiWindowHandle window_handle{};
|
||||
bool is_proceed{false};
|
||||
};
|
||||
extern FileSelectGateWindowCtx g_file_select_window_ctx;
|
||||
|
||||
void SaveNewRandomizerPreset(const std::string& presetName, bool overwriteExisting = false);
|
||||
void ApplyExistingRandomizerPreset(const std::filesystem::path& presetFilePath);
|
||||
void CopyPermalinkToClipboard();
|
||||
void PastePermalinkFromClipboard();
|
||||
|
||||
ModResult buildMenuTab();
|
||||
ModResult buildFileSelectGateMenu(dFile_select_c*);
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
#include "rando_seed_generation.hpp"
|
||||
|
||||
#include <mods/svc/log.hpp>
|
||||
|
||||
#include "../session.hpp"
|
||||
#include "../randomizer_context.hpp"
|
||||
|
||||
#include "m_Do/m_Do_audio.h"
|
||||
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
|
||||
namespace randomizer::ui {
|
||||
enum class SeedGenerateStatus {
|
||||
Ready,
|
||||
Generating,
|
||||
Success,
|
||||
Error,
|
||||
};
|
||||
|
||||
UiDialogHandle seedGenDialog{0};
|
||||
static std::atomic seedGenStatus = SeedGenerateStatus::Ready;
|
||||
static std::string generationStatusMsg{};
|
||||
|
||||
void OnDialogActionOK(ModContext* ctx, UiDialogHandle dialogHandle, void*) {
|
||||
mDoAud_seStartMenu(Z2SE_SY_MENU_BACK);
|
||||
session::svc_mng.ui->dialog_close(ctx, dialogHandle);
|
||||
}
|
||||
|
||||
static void StartSeedGeneration() {
|
||||
if (GenerateAndWriteSeed(generationStatusMsg)) {
|
||||
seedGenStatus.store(SeedGenerateStatus::Success);
|
||||
} else {
|
||||
seedGenStatus.store(SeedGenerateStatus::Error);
|
||||
}
|
||||
|
||||
mods::log::debug("{}", generationStatusMsg);
|
||||
}
|
||||
|
||||
static ModResult buildDialog() {
|
||||
UiDialogDesc desc = UI_DIALOG_DESC_INIT;
|
||||
desc.title = "Randomizer";
|
||||
desc.body_rml = "Generating Seed...";
|
||||
desc.icon = "verifying";
|
||||
desc.variant = UI_DIALOG_NORMAL;
|
||||
|
||||
UiDialogAction action = {
|
||||
.label = "OK",
|
||||
.on_pressed = OnDialogActionOK,
|
||||
.user_data = nullptr,
|
||||
.keep_open = false,
|
||||
};
|
||||
desc.actions = &action;
|
||||
desc.action_count = 1;
|
||||
|
||||
if (session::svc_mng.ui->dialog_push(session::svc_mng.mod_ctx, &desc, &seedGenDialog) != MOD_OK) {
|
||||
mods::log::error("Failed to push dialog");
|
||||
return MOD_ERROR;
|
||||
}
|
||||
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
void GenerateRandomizerSeed() {
|
||||
if (buildDialog() != MOD_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Start generation thread
|
||||
seedGenStatus.store(SeedGenerateStatus::Generating);
|
||||
std::thread rando_gen_thread(StartSeedGeneration);
|
||||
rando_gen_thread.detach();
|
||||
}
|
||||
|
||||
ModResult UpdateSeedGenerationDialog() {
|
||||
if (seedGenDialog == 0) {
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
auto curSeedGenStatus = seedGenStatus.load();
|
||||
|
||||
// Change the modal text if we've finished attempting to generate
|
||||
if (curSeedGenStatus == SeedGenerateStatus::Success ||
|
||||
curSeedGenStatus == SeedGenerateStatus::Error)
|
||||
{
|
||||
auto* ctx = session::svc_mng.mod_ctx;
|
||||
auto* ui_svc = session::svc_mng.ui;
|
||||
|
||||
if (curSeedGenStatus == SeedGenerateStatus::Success) {
|
||||
mDoAud_seStartMenu(Z2SE_SY_FILE_SAVE_OK);
|
||||
ui_svc->dialog_set_icon(ctx, seedGenDialog, "celebration");
|
||||
} else {
|
||||
mDoAud_seStartMenu(Z2SE_SYS_RESULT_WRONG);
|
||||
ui_svc->dialog_set_icon(ctx, seedGenDialog, "error");
|
||||
}
|
||||
|
||||
ui_svc->dialog_set_body(ctx, seedGenDialog, generationStatusMsg.c_str());
|
||||
seedGenStatus.store(SeedGenerateStatus::Ready);
|
||||
}
|
||||
|
||||
return MOD_OK;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <mods/api.h>
|
||||
|
||||
namespace randomizer::ui {
|
||||
|
||||
void GenerateRandomizerSeed();
|
||||
ModResult UpdateSeedGenerationDialog();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#include "ui.hpp"
|
||||
|
||||
#include <mods/svc/log.hpp>
|
||||
|
||||
#include "rando_seed_generation.hpp"
|
||||
#include "rando_config.hpp"
|
||||
|
||||
namespace randomizer::ui {
|
||||
ModResult initialize() {
|
||||
ModResult res = buildMenuTab();
|
||||
if (res != MOD_OK) {
|
||||
mods::log::error("failed to initialize randomizer menu tab!");
|
||||
return res;
|
||||
}
|
||||
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
void update() {
|
||||
UpdateSeedGenerationDialog();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <mods/api.h>
|
||||
|
||||
namespace randomizer::ui {
|
||||
ModResult initialize();
|
||||
void update();
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <dolphin/types.h>
|
||||
|
||||
inline u8 getAncientDocumentNum() {
|
||||
// TODO
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline u8 getAreaKeyNum(int) {
|
||||
// TODO
|
||||
return 0;
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "d/d_item.h"
|
||||
#include "d/d_item_data.h"
|
||||
#include "item_ids.h"
|
||||
#include "utilities.h"
|
||||
#include "tools.h"
|
||||
|
||||
bool haveItem(u32 item) {
|
||||
return checkItemGet((u8)item, 1);
|
||||
|
||||
Reference in New Issue
Block a user