From 589e25948e39f243e717e1cc7cbcb4f728ed30c5 Mon Sep 17 00:00:00 2001 From: Adam Bird Date: Wed, 25 Jan 2023 18:33:27 -0500 Subject: [PATCH] Add manual seed input for rando generation (#2057) * add manual seed input for rando generation * add tooltip for seed input * switch to calloc * add seed testing count generator * add console command for rando gen * add boost and custom hash_32 functions * use hash_32 funcs for rando generation * limit seed input field to uint32 * rename custom boost header imports to boost_custom --- soh/CMakeLists.txt | 27 +++ .../container_hash/detail/hash_mix_32.hpp | 47 +++++ .../container_hash/detail/hash_range_32.hpp | 117 ++++++++++++ .../boost_custom/container_hash/hash_32.hpp | 172 ++++++++++++++++++ .../container_hash/hash_fwd_32.hpp | 25 +++ .../boost_custom/container_hash/version.hpp | 9 + soh/soh/Enhancements/debugconsole.cpp | 33 ++++ .../Enhancements/randomizer/3drando/menu.cpp | 46 +++-- .../Enhancements/randomizer/3drando/menu.hpp | 2 +- .../randomizer/3drando/playthrough.cpp | 12 +- .../randomizer/3drando/playthrough.hpp | 4 +- .../randomizer/3drando/rando_main.cpp | 5 +- .../randomizer/3drando/rando_main.hpp | 2 +- .../randomizer/3drando/random.cpp | 11 +- .../randomizer/3drando/settings.cpp | 2 +- .../randomizer/3drando/settings.hpp | 2 +- .../randomizer/3drando/spoiler_log.cpp | 8 +- .../Enhancements/randomizer/randomizer.cpp | 51 ++++-- soh/soh/Enhancements/randomizer/randomizer.h | 3 + soh/soh/UIWidgets.hpp | 9 + 20 files changed, 540 insertions(+), 47 deletions(-) create mode 100644 soh/include/boost_custom/container_hash/detail/hash_mix_32.hpp create mode 100644 soh/include/boost_custom/container_hash/detail/hash_range_32.hpp create mode 100644 soh/include/boost_custom/container_hash/hash_32.hpp create mode 100644 soh/include/boost_custom/container_hash/hash_fwd_32.hpp create mode 100644 soh/include/boost_custom/container_hash/version.hpp diff --git a/soh/CMakeLists.txt b/soh/CMakeLists.txt index 98997bc76..89d7da99e 100644 --- a/soh/CMakeLists.txt +++ b/soh/CMakeLists.txt @@ -1935,6 +1935,32 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows") set_target_properties(${PROJECT_NAME} PROPERTIES MSVC_RUNTIME_LIBRARY ${MSVC_RUNTIME_LIBRARY_STR}) endif() ################################################################################ +# Find/download Boost +################################################################################ +include(FetchContent) +FetchContent_Declare( + Boost + URL https://boostorg.jfrog.io/artifactory/main/release/1.81.0/source/boost_1_81_0.tar.gz + URL_HASH SHA256=205666dea9f6a7cfed87c7a6dfbeb52a2c1b9de55712c9c1a87735d7181452b6 + SOURCE_SUBDIR "null" # Set to a nonexistent directory so boost is not built (we don't need to build it) + DOWNLOAD_EXTRACT_TIMESTAMP false # supress timestamp warning, not needed since the url wont change +) + +set(Boost_NO_BOOST_CMAKE false) +set(BOOST_INCLUDEDIR ${FETCHCONTENT_BASE_DIR}/boost-src) # Location where FetchContent stores the source +message("Searching for Boost installation") +find_package(Boost) + +if (NOT ${Boost_FOUND}) + message("Boost not found. Downloading now...") + FetchContent_MakeAvailable(Boost) + message("Boost downloaded to " ${FETCHCONTENT_BASE_DIR}/boost-src) + set(BOOST-INCLUDE ${FETCHCONTENT_BASE_DIR}/boost-src) +else() + message("Boost found in " ${Boost_INCLUDE_DIRS}) + set(BOOST-INCLUDE ${Boost_INCLUDE_DIRS}) +endif() +################################################################################ # Compile definitions ################################################################################ find_package(SDL2) @@ -1971,6 +1997,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE assets ${CMAKE_CURRENT_SOURCE_DIR}/../ZAPDTR/ZAPD/resource/type ${SDL2-INCLUDE} ${SDL2-NET-INCLUDE} + ${BOOST-INCLUDE} ${CMAKE_CURRENT_SOURCE_DIR}/assets/ . ) diff --git a/soh/include/boost_custom/container_hash/detail/hash_mix_32.hpp b/soh/include/boost_custom/container_hash/detail/hash_mix_32.hpp new file mode 100644 index 000000000..95cfdcd77 --- /dev/null +++ b/soh/include/boost_custom/container_hash/detail/hash_mix_32.hpp @@ -0,0 +1,47 @@ +// 32 bit implementation based off of Boost hash + +#ifndef BOOST_HASH_DETAIL_HASH_MIX_32_HPP +#define BOOST_HASH_DETAIL_HASH_MIX_32_HPP + +#include +#include +#include + +namespace boost +{ +namespace hash_detail +{ + +template struct hash_mix_impl_32; + +// hash_mix for 32 bit +// +// We use the "best xmxmx" implementation from +// https://github.com/skeeto/hash-prospector/issues/19 + +template<> struct hash_mix_impl_32<32> +{ + inline static boost::uint32_t fn( boost::uint32_t x ) + { + boost::uint32_t const m1 = 0x21f0aaad; + boost::uint32_t const m2 = 0x735a2d97; + + x ^= x >> 16; + x *= m1; + x ^= x >> 15; + x *= m2; + x ^= x >> 15; + + return x; + } +}; + +inline uint32_t hash_mix_32( uint32_t v ) +{ + return hash_mix_impl_32<32>::fn( v ); +} + +} // namespace hash_detail +} // namespace boost + +#endif // #ifndef BOOST_HASH_DETAIL_HASH_MIX_32_HPP diff --git a/soh/include/boost_custom/container_hash/detail/hash_range_32.hpp b/soh/include/boost_custom/container_hash/detail/hash_range_32.hpp new file mode 100644 index 000000000..7430152ab --- /dev/null +++ b/soh/include/boost_custom/container_hash/detail/hash_range_32.hpp @@ -0,0 +1,117 @@ +// 32 bit implementation based off of Boost hash +// Only implementing 32 bit version of char based ranges + +#ifndef BOOST_HASH_DETAIL_HASH_RANGE_32_HPP +#define BOOST_HASH_DETAIL_HASH_RANGE_32_HPP + +#include +#include + +#if BOOST_VERSION_HAS_HASH_RANGE +#include +#else +#include +#include +#include +#include +#include +#include +#include +#endif // #if BOOST_VERSION_HAS_HASH_RANGE + +namespace boost +{ +namespace hash_detail +{ + +#if !BOOST_VERSION_HAS_HASH_RANGE + +template struct is_char_type: public boost::false_type {}; + +#if CHAR_BIT == 8 + +template<> struct is_char_type: public boost::true_type {}; +template<> struct is_char_type: public boost::true_type {}; +template<> struct is_char_type: public boost::true_type {}; + +#if defined(__cpp_char8_t) && __cpp_char8_t >= 201811L +template<> struct is_char_type: public boost::true_type {}; +#endif + +#if defined(__cpp_lib_byte) && __cpp_lib_byte >= 201603L +template<> struct is_char_type: public boost::true_type {}; +#endif + +#endif + +#endif // #if !BOOST_VERSION_HAS_HASH_RANGE + +template +inline typename boost::enable_if_< + is_char_type::value_type>::value && + is_same::iterator_category, std::random_access_iterator_tag>::value, +std::size_t>::type + hash_range_32( uint32_t seed, It first, It last ) +{ + std::size_t n = static_cast( last - first ); + + for( ; n >= 4; first += 4, n -= 4 ) + { + // clang 5+, gcc 5+ figure out this pattern and use a single mov on x86 + // gcc on s390x and power BE even knows how to use load-reverse + + boost::uint32_t w = + static_cast( static_cast( first[0] ) ) | + static_cast( static_cast( first[1] ) ) << 8 | + static_cast( static_cast( first[2] ) ) << 16 | + static_cast( static_cast( first[3] ) ) << 24; + + hash_combine_32( seed, w ); + } + + { + // add a trailing suffix byte of 0x01 because otherwise sequences of + // trailing zeroes are indistinguishable from end of string + + boost::uint32_t w = 0x01u; + + switch( n ) + { + case 1: + + w = + static_cast( static_cast( first[0] ) ) | + 0x0100u; + + break; + + case 2: + + w = + static_cast( static_cast( first[0] ) ) | + static_cast( static_cast( first[1] ) ) << 8 | + 0x010000u; + + break; + + case 3: + + w = + static_cast( static_cast( first[0] ) ) | + static_cast( static_cast( first[1] ) ) << 8 | + static_cast( static_cast( first[2] ) ) << 16 | + 0x01000000u; + + break; + } + + hash_combine_32( seed, w ); + } + + return seed; +} + +} // namespace hash_detail +} // namespace boost + +#endif // #ifndef BOOST_HASH_DETAIL_HASH_RANGE_32_HPP diff --git a/soh/include/boost_custom/container_hash/hash_32.hpp b/soh/include/boost_custom/container_hash/hash_32.hpp new file mode 100644 index 000000000..4b955c43a --- /dev/null +++ b/soh/include/boost_custom/container_hash/hash_32.hpp @@ -0,0 +1,172 @@ +// 32 bit implementation based off of Boost hash +// Only implementing 32 bit versions integral and string based hashes + +#ifndef BOOST_FUNCTIONAL_HASH_HASH_32_HPP +#define BOOST_FUNCTIONAL_HASH_HASH_32_HPP + +#include +#include +#include +#include +#include + +#if !BOOST_VERSION_HAS_HASH_RANGE +#include +#include + +#if BOOST_WORKAROUND(__GNUC__, < 3) \ + && !defined(__SGI_STL_PORT) && !defined(_STLPORT_VERSION) +#define BOOST_HASH_CHAR_TRAITS string_char_traits +#else +#define BOOST_HASH_CHAR_TRAITS char_traits +#endif + +#endif // #if !BOOST_VERSION_HAS_HASH_RANGE + +namespace boost +{ + + // + // boost::hash_value + // + + // integral types + + namespace hash_detail + { + template sizeof(uint32_t)), + bool is_unsigned = boost::is_unsigned::value, + std::size_t size_t_bits = sizeof(uint32_t) * CHAR_BIT, + std::size_t type_bits = sizeof(T) * CHAR_BIT> + struct hash_integral_impl_32; + + template struct hash_integral_impl_32 + { + static uint32_t fn( T v ) + { + return static_cast( v ); + } + }; + + template struct hash_integral_impl_32 + { + static uint32_t fn( T v ) + { + typedef typename boost::make_unsigned::type U; + + if( v >= 0 ) + { + return hash_integral_impl_32::fn( static_cast( v ) ); + } + else + { + return ~hash_integral_impl_32::fn( static_cast( ~static_cast( v ) ) ); + } + } + }; + + template struct hash_integral_impl_32 + { + static uint32_t fn( T v ) + { + uint32_t seed = 0; + + seed = static_cast( v >> 32 ) + hash_detail::hash_mix_32( seed ); + seed = static_cast( v ) + hash_detail::hash_mix_32( seed ); + + return seed; + } + }; + + template struct hash_integral_impl_32 + { + static uint32_t fn( T v ) + { + uint32_t seed = 0; + + seed = static_cast( v >> 96 ) + hash_detail::hash_mix_32( seed ); + seed = static_cast( v >> 64 ) + hash_detail::hash_mix_32( seed ); + seed = static_cast( v >> 32 ) + hash_detail::hash_mix_32( seed ); + seed = static_cast( v ) + hash_detail::hash_mix_32( seed ); + + return seed; + } + }; + + } // namespace hash_detail + + template + typename boost::enable_if_::value, uint32_t>::type + hash_value_32( T v ) + { + return hash_detail::hash_integral_impl_32::fn( v ); + } + + // contiguous ranges (string, vector, array) +#if BOOST_VERSION_HAS_HASH_RANGE + template + typename boost::enable_if_::value, uint32_t>::type + hash_value_32( T const& v ) + { + return boost::hash_range_32( v.data(), v.data() + v.size() ); + } +#else + template + inline uint32_t hash_value_32( + std::basic_string, A> const& v) + { + return boost::hash_range_32( v.data(), v.data() + v.size() ); + } +#endif + + // + // boost::hash_combine + // + + template + inline void hash_combine_32( uint32_t& seed, T const& v ) + { + seed = boost::hash_detail::hash_mix_32( seed + 0x9e3779b9 + boost::hash_32()( v ) ); + } + + // + // boost::hash_range + // + + template + inline void hash_range_32( uint32_t& seed, It first, It last ) + { + seed = hash_detail::hash_range_32( seed, first, last ); + } + + template + inline uint32_t hash_range_32( It first, It last ) + { + uint32_t seed = 0; + + hash_range_32( seed, first, last ); + + return seed; + } + + // + // boost::hash + // + + template struct hash_32 + { + typedef T argument_type; + typedef uint32_t result_type; + + uint32_t operator()( T const& val ) const + { + return hash_value_32( val ); + } + }; + +} // namespace boost + +#undef BOOST_HASH_CHAR_TRAITS + +#endif // #ifndef BOOST_FUNCTIONAL_HASH_HASH_32_HPP diff --git a/soh/include/boost_custom/container_hash/hash_fwd_32.hpp b/soh/include/boost_custom/container_hash/hash_fwd_32.hpp new file mode 100644 index 000000000..2ad101f2b --- /dev/null +++ b/soh/include/boost_custom/container_hash/hash_fwd_32.hpp @@ -0,0 +1,25 @@ +// 32 bit implementation based off of Boost hash + +#ifndef BOOST_FUNCTIONAL_HASH_FWD_32_HPP +#define BOOST_FUNCTIONAL_HASH_FWD_32_HPP + +#include + +namespace boost +{ + +namespace container_hash +{ + +} // namespace container_hash + +template struct hash_32; + +template void hash_combine_32( uint32_t& seed, T const& v ); + +template void hash_range_32( uint32_t&, It, It ); +template uint32_t hash_range_32( It, It ); + +} // namespace boost + +#endif // #ifndef BOOST_FUNCTIONAL_HASH_FWD_32_HPP diff --git a/soh/include/boost_custom/container_hash/version.hpp b/soh/include/boost_custom/container_hash/version.hpp new file mode 100644 index 000000000..22ad6634c --- /dev/null +++ b/soh/include/boost_custom/container_hash/version.hpp @@ -0,0 +1,9 @@ + +#ifndef BOOST_CONTAINER_HASH_VERSION_HPP +#define BOOST_CONTAINER_HASH_VERSION_HPP + +#include + +#define BOOST_VERSION_HAS_HASH_RANGE ((BOOST_VERSION / 100 % 1000) >= 81) + +#endif // #ifndef BOOST_CONTAINER_HASH_VERSION_HPP diff --git a/soh/soh/Enhancements/debugconsole.cpp b/soh/soh/Enhancements/debugconsole.cpp index f3d461657..88748370a 100644 --- a/soh/soh/Enhancements/debugconsole.cpp +++ b/soh/soh/Enhancements/debugconsole.cpp @@ -1157,6 +1157,34 @@ static bool CuccoStormHandler(std::shared_ptr Console, const std: } } +static bool GenerateRandoHandler(std::shared_ptr Console, const std::vector& args) { + if (args.size() == 1) { + if (GenerateRandomizer()) { + return CMD_SUCCESS; + } + } + + try { + uint32_t value = std::stoi(args[1], NULL, 10); + std::string seed = ""; + if (args.size() == 3) { + int testing = std::stoi(args[1], nullptr, 10); + seed = "seed_testing_count"; + } + + if (GenerateRandomizer(seed + std::to_string(value))){ + return CMD_SUCCESS; + } + } catch (std::invalid_argument const& ex) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] seed|count value must be a number."); + return CMD_FAILED; + } + + + SohImGui::GetConsole()->SendErrorMessage("[SOH] Rando generation already in progress"); + return CMD_FAILED; +} + static bool CosmeticsHandler(std::shared_ptr Console, const std::vector& args) { if (args.size() != 2) { SohImGui::GetConsole()->SendErrorMessage("[SOH] Unexpected arguments passed"); @@ -1448,6 +1476,11 @@ void DebugConsole_Init(void) { CMD_REGISTER("cucco_storm", { CuccoStormHandler, "Cucco Storm" }); + CMD_REGISTER("gen_rando", { GenerateRandoHandler, "Generate a randomizer seed", { + { "seed|count", Ship::ArgumentType::NUMBER, true }, + { "testing", Ship::ArgumentType::NUMBER, true }, + }}); + CMD_REGISTER("cosmetics", { CosmeticsHandler, "Change cosmetics.", { { "reset|randomize", Ship::ArgumentType::TEXT }, }}); diff --git a/soh/soh/Enhancements/randomizer/3drando/menu.cpp b/soh/soh/Enhancements/randomizer/3drando/menu.cpp index 28e43c175..07bb809ac 100644 --- a/soh/soh/Enhancements/randomizer/3drando/menu.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/menu.cpp @@ -33,14 +33,13 @@ void PrintTopScreen() { SPDLOG_DEBUG(" Select: Exit to Homebrew Menu\n"); SPDLOG_DEBUG(" Y: New Random Seed\n"); SPDLOG_DEBUG(" X: Input Custom Seed\n"); - SPDLOG_DEBUG("\x1b[11;7HCurrent Seed: %s", Settings::seed.c_str()); + SPDLOG_DEBUG("\x1b[11;7HCurrent Seed: %u", Settings::seed); } void MenuInit() { Settings::InitSettings(); seedChanged = false; - pastSeedLength = Settings::seed.length(); Menu* main = new Menu("Main", MenuType::MainMenu, &Settings::mainMenu, MAIN_MENU); menuList.push_back(main); @@ -191,24 +190,19 @@ void MenuUpdate(uint32_t kDown, bool updatedByHeld) { // New Random Seed if (kDown & KEY_Y) { - pastSeedLength = Settings::seed.length(); - Settings::seed = std::to_string(rand()); + Settings::seed = rand(); seedChanged = true; } // Input Custom Seed if (kDown & KEY_X) { - pastSeedLength = Settings::seed.length(); - Settings::seed = GetInput("Enter Seed"); + // Settings::seed = GetInput("Enter Seed"); seedChanged = true; } // Reprint seed if it changed if (seedChanged) { - std::string spaces = ""; - spaces.append(pastSeedLength, ' '); - printf("\x1b[11;21H%s", spaces.c_str()); - printf("\x1b[11;21H%s", Settings::seed.c_str()); + printf("\x1b[11;21H%u", Settings::seed); seedChanged = false; } } @@ -517,12 +511,34 @@ void PrintOptionDescription() { printf("\x1b[22;0H%s", description.data()); } -std::string GenerateRandomizer(std::unordered_map cvarSettings, std::set excludedLocations) { - // if a blank seed was entered, make a random one - srand(time(NULL)); - Settings::seed = std::to_string(rand()); +std::string GenerateRandomizer(std::unordered_map cvarSettings, std::set excludedLocations, + std::string seedInput) { - int ret = Playthrough::Playthrough_Init(std::hash{}(Settings::seed), cvarSettings, excludedLocations); + srand(time(NULL)); + // if a blank seed was entered, make a random one + if (seedInput.empty()) { + Settings::seed = rand() & 0xFFFFFFFF; + } else if (seedInput.rfind("seed_testing_count", 0) == 0 && seedInput.length() > 18) { + int count; + try { + count = std::stoi(seedInput.substr(18), nullptr); + } catch (std::invalid_argument &e) { + count = 1; + } catch (std::out_of_range &e) { + count = 1; + } + Playthrough::Playthrough_Repeat(cvarSettings, excludedLocations, count); + return ""; + } else { + try { + int seed = std::stoi(seedInput, nullptr); + Settings::seed = seed; + } catch (...) { + return ""; + } + } + + int ret = Playthrough::Playthrough_Init(Settings::seed, cvarSettings, excludedLocations); if (ret < 0) { if (ret == -1) { // Failed to generate after 5 tries printf("\n\nFailed to generate after 5 tries.\nPress B to go back to the menu.\nA different seed might be " diff --git a/soh/soh/Enhancements/randomizer/3drando/menu.hpp b/soh/soh/Enhancements/randomizer/3drando/menu.hpp index 6be641f03..0057c6a6e 100644 --- a/soh/soh/Enhancements/randomizer/3drando/menu.hpp +++ b/soh/soh/Enhancements/randomizer/3drando/menu.hpp @@ -46,7 +46,7 @@ void PrintResetToDefaultsMenu(); void PrintGenerateMenu(); void ClearDescription(); void PrintOptionDescription(); -std::string GenerateRandomizer(std::unordered_map cvarSetting, std::set excludedLocations); +std::string GenerateRandomizer(std::unordered_map cvarSetting, std::set excludedLocations, std::string seedInput); std::string GetInput(const char* hintText); extern void MenuInit(); diff --git a/soh/soh/Enhancements/randomizer/3drando/playthrough.cpp b/soh/soh/Enhancements/randomizer/3drando/playthrough.cpp index 87fba2e50..67ccb5276 100644 --- a/soh/soh/Enhancements/randomizer/3drando/playthrough.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/playthrough.cpp @@ -1,5 +1,6 @@ #include "playthrough.hpp" +#include #include "custom_messages.hpp" #include "fill.hpp" #include "location_access.hpp" @@ -37,7 +38,8 @@ int Playthrough_Init(uint32_t seed, std::unordered_map{}(Settings::seed + settingsStr); + + uint32_t finalHash = boost::hash_32{}(std::to_string(Settings::seed) + settingsStr); Random_Init(finalHash); Settings::hash = std::to_string(finalHash); @@ -81,15 +83,15 @@ int Playthrough_Init(uint32_t seed, std::unordered_map cvarSettings, std::set excludedLocations, int count /*= 1*/) { printf("\x1b[0;0HGENERATING %d SEEDS", count); uint32_t repeatedSeed = 0; for (int i = 0; i < count; i++) { repeatedSeed = rand() % 0xFFFFFFFF; - Settings::seed = std::to_string(repeatedSeed); - CitraPrint("testing seed: " + Settings::seed); + Settings::seed = repeatedSeed; + CitraPrint("testing seed: " + std::to_string(Settings::seed)); ClearProgress(); - // Playthrough_Init(std::hash{}(Settings::seed)); + Playthrough_Init(Settings::seed, cvarSettings, excludedLocations); printf("\x1b[15;15HSeeds Generated: %d\n", i + 1); } diff --git a/soh/soh/Enhancements/randomizer/3drando/playthrough.hpp b/soh/soh/Enhancements/randomizer/3drando/playthrough.hpp index 0adcce387..24cb68f29 100644 --- a/soh/soh/Enhancements/randomizer/3drando/playthrough.hpp +++ b/soh/soh/Enhancements/randomizer/3drando/playthrough.hpp @@ -4,6 +4,6 @@ #include "item_location.hpp" namespace Playthrough { -int Playthrough_Init(uint32_t seed, std::unordered_map cvarSettings, std::set excludedLocations); - int Playthrough_Repeat(int count = 1); + int Playthrough_Init(uint32_t seed, std::unordered_map cvarSettings, std::set excludedLocations); + int Playthrough_Repeat(std::unordered_map cvarSettings, std::set excludedLocations, int count = 1); } diff --git a/soh/soh/Enhancements/randomizer/3drando/rando_main.cpp b/soh/soh/Enhancements/randomizer/3drando/rando_main.cpp index c7114dd37..c8fb8ab39 100644 --- a/soh/soh/Enhancements/randomizer/3drando/rando_main.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/rando_main.cpp @@ -11,7 +11,8 @@ #define TICKS_PER_SEC 268123480.0 -void RandoMain::GenerateRando(std::unordered_map cvarSettings, std::set excludedLocations) { +void RandoMain::GenerateRando(std::unordered_map cvarSettings, std::set excludedLocations, + std::string seedInput) { HintTable_Init(); ItemTable_Init(); LocationTable_Init(); @@ -19,7 +20,7 @@ void RandoMain::GenerateRando(std::unordered_map cvarS // std::string settingsFileName = "./randomizer/latest_settings.json"; // CVarSetString("gLoadedPreset", settingsFileName.c_str()); - std::string fileName = Ship::Window::GetPathRelativeToAppDirectory(GenerateRandomizer(cvarSettings, excludedLocations).c_str()); + std::string fileName = Ship::Window::GetPathRelativeToAppDirectory(GenerateRandomizer(cvarSettings, excludedLocations, seedInput).c_str()); CVarSetString("gSpoilerLog", fileName.c_str()); CVarSave(); diff --git a/soh/soh/Enhancements/randomizer/3drando/rando_main.hpp b/soh/soh/Enhancements/randomizer/3drando/rando_main.hpp index 907e780e9..4e08f753f 100644 --- a/soh/soh/Enhancements/randomizer/3drando/rando_main.hpp +++ b/soh/soh/Enhancements/randomizer/3drando/rando_main.hpp @@ -3,6 +3,6 @@ #include "item.hpp" namespace RandoMain { -void GenerateRando(std::unordered_map cvarSettings, std::set excludedLocations); +void GenerateRando(std::unordered_map cvarSettings, std::set excludedLocations, std::string seedInput); std::array* GetFullItemTable(); } diff --git a/soh/soh/Enhancements/randomizer/3drando/random.cpp b/soh/soh/Enhancements/randomizer/3drando/random.cpp index 8fe012476..92cf22b85 100644 --- a/soh/soh/Enhancements/randomizer/3drando/random.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/random.cpp @@ -1,14 +1,17 @@ #include "random.hpp" #include +#include +#include +#include static bool init = false; -static std::mt19937_64 generator; +static boost::random::mt19937 generator; //Initialize with seed specified void Random_Init(uint32_t seed) { init = true; - generator = std::mt19937_64{seed}; + generator = boost::random::mt19937{seed}; } //Returns a random integer in range [min, max-1] @@ -22,12 +25,12 @@ uint32_t Random(int min, int max) { #endif Random_Init(seed); } - std::uniform_int_distribution distribution(min, max-1); + boost::random::uniform_int_distribution distribution(min, max-1); return distribution(generator); } //Returns a random floating point number in [0.0, 1.0] double RandomDouble() { - std::uniform_real_distribution distribution(0.0, 1.0); + boost::random::uniform_real_distribution distribution(0.0, 1.0); return distribution(generator); } diff --git a/soh/soh/Enhancements/randomizer/3drando/settings.cpp b/soh/soh/Enhancements/randomizer/3drando/settings.cpp index b8a05c345..a10d3006b 100644 --- a/soh/soh/Enhancements/randomizer/3drando/settings.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/settings.cpp @@ -19,7 +19,7 @@ using namespace Music; using namespace SFX; namespace Settings { - std::string seed; + uint32_t seed; std::string hash; std::string version = RANDOMIZER_VERSION "-" COMMIT_NUMBER; std::array hashIconIndexes; diff --git a/soh/soh/Enhancements/randomizer/3drando/settings.hpp b/soh/soh/Enhancements/randomizer/3drando/settings.hpp index ed3eb97f6..e6bd4e785 100644 --- a/soh/soh/Enhancements/randomizer/3drando/settings.hpp +++ b/soh/soh/Enhancements/randomizer/3drando/settings.hpp @@ -887,7 +887,7 @@ void UpdateSettings(std::unordered_map cvarSettin const std::vector GetAllOptionMenus(); - extern std::string seed; + extern uint32_t seed; extern std::string version; extern std::array hashIconIndexes; extern std::string hash; diff --git a/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp b/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp index 0c5a03820..a7541c3e0 100644 --- a/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp @@ -772,11 +772,11 @@ const char* SpoilerLog_Write(int language) { auto rootNode = spoilerLog.NewElement("spoiler-log"); spoilerLog.InsertEndChild(rootNode); - rootNode->SetAttribute("version", Settings::version.c_str()); - rootNode->SetAttribute("seed", Settings::seed.c_str()); - jsonData.clear(); + jsonData["_version"] = (char*) gBuildVersion; + jsonData["_seed"] = Settings::seed; + // Write Hash int index = 0; for (uint8_t seed_value : Settings::hashIconIndexes) { @@ -844,7 +844,7 @@ bool PlacementLog_Write() { placementLog.InsertEndChild(rootNode); rootNode->SetAttribute("version", Settings::version.c_str()); - rootNode->SetAttribute("seed", Settings::seed.c_str()); + rootNode->SetAttribute("seed", Settings::seed); // WriteSettings(placementLog, true); // Include hidden settings. // WriteExcludedLocations(placementLog); diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index e001ccb0f..96c9b29f2 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -39,6 +39,7 @@ std::multimap, RandomizerCheckObject> checkFromActorMu std::set excludedLocations; u8 generated; +char* seedInputBuffer; const std::string Randomizer::getItemMessageTableID = "Randomizer"; const std::string Randomizer::hintMessageTableID = "RandomizerHints"; @@ -2766,7 +2767,7 @@ RandomizerCheck Randomizer::GetCheckFromRandomizerInf(RandomizerInf randomizerIn std::thread randoThread; -void GenerateRandomizerImgui() { +void GenerateRandomizerImgui(std::string seed = "") { CVarSetInteger("gRandoGenerating", 1); CVarSave(); @@ -2938,7 +2939,9 @@ void GenerateRandomizerImgui() { excludedLocations.insert((RandomizerCheck)std::stoi(excludedLocationString)); } - RandoMain::GenerateRando(cvarSettings, excludedLocations); + RandoMain::GenerateRando(cvarSettings, excludedLocations, seed); + + memset(seedInputBuffer, 0, MAX_SEED_BUFFER_SIZE); CVarSetInteger("gRandoGenerating", 0); CVarSave(); @@ -2947,6 +2950,14 @@ void GenerateRandomizerImgui() { generated = 1; } +bool GenerateRandomizer(std::string seed /*= ""*/) { + if (CVarGetInteger("gRandoGenerating", 0) == 0) { + randoThread = std::thread(&GenerateRandomizerImgui, seed); + return true; + } + return false; +} + void DrawRandoEditor(bool& open) { if (generated) { generated = 0; @@ -3023,19 +3034,37 @@ void DrawRandoEditor(bool& open) { return; } - DrawPresetSelector(PRESET_TYPE_RANDOMIZER); - bool disableEditingRandoSettings = CVarGetInteger("gRandoGenerating", 0) || CVarGetInteger("gOnFileSelectNameEntry", 0); ImGui::PushItemFlag(ImGuiItemFlags_Disabled, disableEditingRandoSettings); ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * (disableEditingRandoSettings ? 0.5f : 1.0f)); - ImGui::Dummy(ImVec2(0.0f, 0.0f)); - if (ImGui::Button("Generate Seed")) { - if (CVarGetInteger("gRandoGenerating", 0) == 0) { - randoThread = std::thread(&GenerateRandomizerImgui); - } + DrawPresetSelector(PRESET_TYPE_RANDOMIZER); + + UIWidgets::Spacer(0); + + ImGui::Text("Seed"); + if (ImGui::InputText("##RandomizerSeed", seedInputBuffer, MAX_SEED_BUFFER_SIZE, ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CallbackCharFilter, UIWidgets::TextFilters::FilterNumbers)) { + uint32_t seedInput; + ImGui::DataTypeApplyFromText(seedInputBuffer, ImGuiDataType_U32, &seedInput, "%u"); + strncpy(seedInputBuffer, std::to_string(seedInput).c_str(), MAX_SEED_BUFFER_SIZE); } - ImGui::Dummy(ImVec2(0.0f, 0.0f)); + UIWidgets::Tooltip("Leaving this field blank will use a random seed value automatically\nSeed range is 0 - 4,294,967,295"); + ImGui::SameLine(); + if (ImGui::Button("New Seed")) { + strncpy(seedInputBuffer, std::to_string(rand() & 0xFFFFFFFF).c_str(), MAX_SEED_BUFFER_SIZE); + } + UIWidgets::Tooltip("Creates a new random seed value to be used when generating a randomizer"); + ImGui::SameLine(); + if (ImGui::Button("Clear Seed")) { + memset(seedInputBuffer, 0, MAX_SEED_BUFFER_SIZE); + } + + UIWidgets::Spacer(0); + if (ImGui::Button("Generate Randomizer")) { + GenerateRandomizer(seedInputBuffer); + } + + UIWidgets::Spacer(0); std::string spoilerfilepath = CVarGetString("gSpoilerLog", ""); ImGui::Text("Spoiler File: %s", spoilerfilepath.c_str()); @@ -5282,6 +5311,7 @@ void InitRandoItemTable() { void InitRando() { SohImGui::AddWindow("Randomizer", "Randomizer Settings", DrawRandoEditor); Randomizer::CreateCustomMessages(); + seedInputBuffer = (char*)calloc(MAX_SEED_BUFFER_SIZE, sizeof(char)); InitRandoItemTable(); } @@ -5292,4 +5322,3 @@ void Rando_Init(void) { } } - diff --git a/soh/soh/Enhancements/randomizer/randomizer.h b/soh/soh/Enhancements/randomizer/randomizer.h index 32dda67e7..a8c97858f 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.h +++ b/soh/soh/Enhancements/randomizer/randomizer.h @@ -12,9 +12,11 @@ #include #include "soh/Enhancements/item-tables/ItemTableTypes.h" +#define MAX_SEED_BUFFER_SIZE 11 // digits for uint32 #define NUM_NAVI_MESSAGES 19 #define NUM_ICE_TRAP_MESSAGES 23 #define NUM_GORON_MESSAGES 9 + class Randomizer { private: std::unordered_map itemLocations; @@ -100,6 +102,7 @@ extern "C" { #endif void Rando_Init(void); +bool GenerateRandomizer(std::string seed = ""); #ifdef __cplusplus } diff --git a/soh/soh/UIWidgets.hpp b/soh/soh/UIWidgets.hpp index 40d80c716..ffe900f94 100644 --- a/soh/soh/UIWidgets.hpp +++ b/soh/soh/UIWidgets.hpp @@ -15,6 +15,15 @@ namespace UIWidgets { + struct TextFilters { + static int FilterNumbers(ImGuiInputTextCallbackData* data) { + if (data->EventChar < 256 && strchr("1234567890", (char)data->EventChar)) { + return 0; + } + return 1; + } + }; + // MARK: - Enums enum class CheckboxGraphics {