diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..85e3489 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,24 @@ +name: Main + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + +jobs: + run: + if: github.repository_owner == 'powerof3' + uses: adya/pack-skse-mod/.github/workflows/pack.yml@main + with: + CMAKE_VR_CONFIG_PRESET: '' + CMAKE_VR_BUILD_PRESET: '' + AE_353_BRANCH: master-1.6.353 + FOMOD_INCLUDE_PDB: true + FOMOD_MOD_NAME: "Base Object Swapper" + FOMOD_MOD_AUTHOR: "powerofthree" + FOMOD_MOD_NEXUS_ID: "60805" + PUBLISH_ARCHIVE_TYPE: '7z' diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c3ab3a..7bcfcd1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.20) -set(NAME "po3_BaseObjectSwapper") -set(VERSION 1.6.0) -set(VR_VERSION 5) +set(NAME "po3_BaseObjectSwapper" CACHE STRING "") +set(VERSION 2.0.0 CACHE STRING "") +set(VR_VERSION 1) set(AE_VERSION 1) # ---- Options ---- @@ -18,8 +18,6 @@ macro(set_from_environment VARIABLE) endif () endmacro() -set_from_environment(VCPKG_ROOT) - macro(find_commonlib_path) if (CommonLibName AND NOT ${CommonLibName} STREQUAL "") # Check extern @@ -37,6 +35,7 @@ endmacro() set_from_environment(VCPKG_ROOT) if(BUILD_SKYRIMAE) add_compile_definitions(SKYRIM_AE) + add_compile_definitions(SKYRIM_SUPPORT_AE) set(CommonLibName "CommonLibSSE") set_from_environment(SkyrimAEPath) set(SkyrimPath ${SkyrimAEPath}) @@ -59,7 +58,7 @@ endif() find_commonlib_path() message( STATUS - "Building for ${SkyrimVersion} at ${SkyrimPath} with ${CommonLibName} at ${CommonLibPath}." + "Building ${NAME} ${VERSION} for ${SkyrimVersion} at ${SkyrimPath} with ${CommonLibName} at ${CommonLibPath}." ) if (DEFINED VCPKG_ROOT) @@ -110,12 +109,6 @@ add_compile_definitions( SKSE_SUPPORT_XBYAK ) -if (BUILD_SKYRIMAE) - add_compile_definitions( - SKYRIM_SUPPORT_AE - ) -endif() - if (MSVC) if (NOT ${CMAKE_GENERATOR} STREQUAL "Ninja") add_compile_options( @@ -174,6 +167,7 @@ add_library( ${CMAKE_CURRENT_BINARY_DIR}/include/Version.h ${CMAKE_CURRENT_BINARY_DIR}/version.rc .clang-format + .editorconfig ${MERGEMAPPER_INCLUDE_DIRS}/MergeMapperPluginAPI.cpp ) @@ -215,39 +209,14 @@ if (MSVC) target_compile_options( ${PROJECT_NAME} PRIVATE - /sdl # Enable Additional Security Checks - /utf-8 # Set Source and Executable character sets to UTF-8 - /Zi # Debug Information Format - - /permissive- # Standards conformance - - /Zc:alignedNew # C++17 over-aligned allocation - /Zc:auto # Deduce Variable Type - /Zc:char8_t - /Zc:__cplusplus # Enable updated __cplusplus macro - /Zc:externC - /Zc:externConstexpr # Enable extern constexpr variables - /Zc:forScope # Force Conformance in for Loop Scope - /Zc:hiddenFriend - /Zc:implicitNoexcept # Implicit Exception Specifiers - /Zc:lambda - /Zc:noexceptTypes # C++17 noexcept rules - /Zc:preprocessor # Enable preprocessor conformance mode - /Zc:referenceBinding # Enforce reference binding rules - /Zc:rvalueCast # Enforce type conversion rules - /Zc:sizedDealloc # Enable Global Sized Deallocation Functions - /Zc:strictStrings # Disable string literal type conversion - /Zc:ternary # Enforce conditional operator rules - /Zc:threadSafeInit # Thread-safe Local Static Initialization - /Zc:tlsGuards - /Zc:trigraphs # Trigraphs Substitution - /Zc:wchar_t # wchar_t Is Native Type - - /external:anglebrackets - /external:W0 - - /W4 # Warning level - /WX # Warning level (warnings are errors) + /sdl # Enable Additional Security Checks + /utf-8 # Set Source and Executable character sets to UTF-8 + /Zi # Debug Information Format + + /permissive- # Standards conformance + /Zc:preprocessor # Enable preprocessor conformance mode + + /wd4200 # nonstandard extension used : zero-sized array in struct/union "$<$:>" "$<$:/Zc:inline;/JMC-;/Ob3>" @@ -256,8 +225,6 @@ if (MSVC) target_link_options( ${PROJECT_NAME} PRIVATE - /WX # Treat Linker Warnings as Errors - "$<$:/INCREMENTAL;/OPT:NOREF;/OPT:NOICF>" "$<$:/INCREMENTAL:NO;/OPT:REF;/OPT:ICF;/DEBUG:FULL>" ) @@ -270,8 +237,8 @@ if (COPY_BUILD) add_custom_command( TARGET ${PROJECT_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy $ ${SkyrimPath}/Data/SKSE/Plugins/ - COMMAND ${CMAKE_COMMAND} -E copy $ ${SkyrimPath}/Data/SKSE/Plugins/ + COMMAND ${CMAKE_COMMAND} -E copy $ ${SkyrimPath}/SKSE/Plugins/ + COMMAND ${CMAKE_COMMAND} -E copy $ ${SkyrimPath}/SKSE/Plugins/ ) else () message( diff --git a/CMakePresets.json b/CMakePresets.json index 7ecd3dc..4ace03c 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -22,7 +22,7 @@ "cacheVariables": { "CMAKE_TOOLCHAIN_FILE": { "type": "STRING", - "value": "$env{VCPKG_INSTALLATION_ROOT}/scripts/buildsystems/vcpkg.cmake" + "value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" }, "VCPKG_OVERLAY_PORTS": { "type": "STRING", @@ -48,47 +48,7 @@ }, { "cacheVariables": { - "CMAKE_CXX_FLAGS": "/EHsc /MP /W4 /external:anglebrackets /external:W0 $penv{CXXFLAGS}" - }, - "generator": "Visual Studio 16 2019", - "inherits": [ - "cmake-dev", - "vcpkg", - "windows" - ], - "name": "vs2019-windows-vcpkg-se" - }, - { - "binaryDir": "${sourceDir}/buildvr", - "cacheVariables": { - "CMAKE_CXX_FLAGS": "/EHsc /MP /W4 /external:anglebrackets /external:W0 $penv{CXXFLAGS}", - "BUILD_SKYRIMVR": true - }, - "generator": "Visual Studio 16 2019", - "inherits": [ - "cmake-dev", - "vcpkg", - "windows" - ], - "name": "vs2019-windows-vcpkg-vr" - }, - { - "binaryDir": "${sourceDir}/buildae", - "cacheVariables": { - "CMAKE_CXX_FLAGS": "/EHsc /MP /W4 /external:anglebrackets /external:W0 $penv{CXXFLAGS}", - "BUILD_SKYRIMAE": true - }, - "generator": "Visual Studio 16 2019", - "inherits": [ - "cmake-dev", - "vcpkg", - "windows" - ], - "name": "vs2019-windows-vcpkg-ae" - }, - { - "cacheVariables": { - "CMAKE_CXX_FLAGS": "/EHsc /MP /W4 /WX /external:W0" + "CMAKE_CXX_FLAGS": "/EHsc /MP /W4 /WX /external:W0 $penv{CXXFLAGS}" }, "generator": "Visual Studio 17 2022", "inherits": [ @@ -102,7 +62,7 @@ { "binaryDir": "${sourceDir}/buildvr", "cacheVariables": { - "CMAKE_CXX_FLAGS": "/EHsc /MP /W4 /WX /external:W0", + "CMAKE_CXX_FLAGS": "/EHsc /MP /W4 /WX /external:W0 $penv{CXXFLAGS}", "BUILD_SKYRIMVR": true }, "generator": "Visual Studio 17 2022", @@ -117,7 +77,7 @@ { "binaryDir": "${sourceDir}/buildae", "cacheVariables": { - "CMAKE_CXX_FLAGS": "/EHsc /MP /W4 /WX /external:W0", + "CMAKE_CXX_FLAGS": "/EHsc /MP /W4 /WX /external:W0 $penv{CXXFLAGS}", "BUILD_SKYRIMAE": true }, "generator": "Visual Studio 17 2022", diff --git a/cmake/headerlist.cmake b/cmake/headerlist.cmake index bf967ad..9655fb7 100644 --- a/cmake/headerlist.cmake +++ b/cmake/headerlist.cmake @@ -1,5 +1,7 @@ set(headers ${headers} + src/Hooks.h src/Manager.h src/PCH.h src/SwapData.h + src/XoshiroCpp.hpp ) diff --git a/cmake/ports/mergemapper/portfile.cmake b/cmake/ports/mergemapper/portfile.cmake index a4c21fc..747af66 100644 --- a/cmake/ports/mergemapper/portfile.cmake +++ b/cmake/ports/mergemapper/portfile.cmake @@ -2,8 +2,8 @@ vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO alandtse/MergeMapper - REF 3eefc4eb6336e456303d6405c4c87ba03e3f832a - SHA512 a633011206d1fe76968d5ac5741ce754ddf51b8349b6f2fdecb0025696062f6831601bf6a6f14b0208b77bb37e241ad6bd4c0d77913daef34e262442ad77f1f9 + REF 16f8f427b0fa54b26f7825a7f10671088bd54f34 + SHA512 0f8114cd1e441971521bc902090eae99f2eac5c8c7ab938254c7ed867510384a0afa7730d9ffbe1942348f3a6893bd1db8edea247bd8c7e269f4753b4b1413bc HEAD_REF main ) diff --git a/cmake/ports/mergemapper/vcpkg.json b/cmake/ports/mergemapper/vcpkg.json index 80c0b63..39d46ac 100644 --- a/cmake/ports/mergemapper/vcpkg.json +++ b/cmake/ports/mergemapper/vcpkg.json @@ -1,7 +1,7 @@ { - "name": "mergemapper", - "version-string": "1.0.0", - "port-version": 1, - "description": "A SKSE plugin to dynamically map zmerges.", - "homepage": "https://github.com/alandtse/MergeMapper" - } + "name": "mergemapper", + "version-string": "1.2.0", + "port-version": 1, + "description": "A SKSE plugin to dynamically map zmerges.", + "homepage": "https://github.com/alandtse/MergeMapper" +} diff --git a/cmake/sourcelist.cmake b/cmake/sourcelist.cmake index ae09ef7..20c5d5d 100644 --- a/cmake/sourcelist.cmake +++ b/cmake/sourcelist.cmake @@ -1,4 +1,5 @@ set(sources ${sources} + src/Hooks.cpp src/Manager.cpp src/PCH.cpp src/SwapData.cpp diff --git a/extern/CommonLibSSE b/extern/CommonLibSSE index def4def..33979b7 160000 --- a/extern/CommonLibSSE +++ b/extern/CommonLibSSE @@ -1 +1 @@ -Subproject commit def4def7e0821ddf320b554633382a92e47a67d0 +Subproject commit 33979b75cc4f95eb694931ec4e9e3074ecc2f810 diff --git a/extern/CommonLibVR b/extern/CommonLibVR index e897b93..48e8b97 160000 --- a/extern/CommonLibVR +++ b/extern/CommonLibVR @@ -1 +1 @@ -Subproject commit e897b930425a6a62599f78617efbdf3cf77042aa +Subproject commit 48e8b975c0913bceaa2dcd9611bd56e5865f0dbc diff --git a/src/Hooks.cpp b/src/Hooks.cpp new file mode 100644 index 0000000..295c49c --- /dev/null +++ b/src/Hooks.cpp @@ -0,0 +1,46 @@ +#include "Hooks.h" +#include "Manager.h" + +namespace BaseObjectSwapper +{ + namespace TESObjectREFR + { + struct InitItemImpl + { + static void thunk(RE::TESObjectREFR* a_ref) + { + if (const auto base = a_ref->GetBaseObject(); base) { + FormSwap::Manager::GetSingleton()->LoadFormsOnce(); + + const auto& [swapBase, transformData] = FormSwap::Manager::GetSingleton()->GetSwapData(a_ref, base); + if (swapBase) { + if (swapBase != base) { + a_ref->SetObjectReference(swapBase); + } + if (transformData) { + transformData->SetTransform(a_ref); + } + } + } + + func(a_ref); + } + static inline REL::Relocation func; + + static inline constexpr std::size_t size = 0x13; + }; + + void Install() + { + stl::write_vfunc(); + logger::info("Installed reference form swap"sv); + } + } + + void Install() + { + logger::info("{:*^30}", "HOOKS"); + + TESObjectREFR::Install(); + } +} diff --git a/src/Hooks.h b/src/Hooks.h new file mode 100644 index 0000000..a2b9c09 --- /dev/null +++ b/src/Hooks.h @@ -0,0 +1,6 @@ +#pragma once + +namespace BaseObjectSwapper +{ + void Install(); +} diff --git a/src/Manager.cpp b/src/Manager.cpp index d31f0ae..5156612 100644 --- a/src/Manager.cpp +++ b/src/Manager.cpp @@ -2,51 +2,44 @@ namespace FormSwap { - Manager::SwapMap& Manager::get_form_map(const std::string& a_str) + SwapMap& Manager::get_form_map(const std::string& a_str) { return a_str == "Forms" ? swapForms : swapRefs; } - Manager::ConflictMap& Manager::get_conflict_map(const std::string& a_str) - { - return a_str == "Forms" ? conflictForms : conflictRefs; - } - - std::pair Manager::get_forms_impl(const std::string& a_str, std::function a_func) + void Manager::get_forms_impl(const std::string& a_path, const std::string& a_str, const std::function a_func) { const auto formPair = string::split(a_str, "|"); - auto baseFormID = SwapData::GetFormID(formPair[0]); - auto swapFormID = SwapData::GetFormID(formPair[1]); - - const auto data = formPair.size() > 2 ? formPair[2] : std::string{}; - - if (swapFormID != 0 && baseFormID != 0) { - SwapData swapData{ - swapFormID, - Transform{ data } - }; - - a_func(baseFormID, swapData); - - return { true, baseFormID }; + if (const auto baseFormID = SwapData::GetFormID(formPair[0]); baseFormID != 0) { + if (const auto swapFormID = SwapData::GetSwapFormID(formPair[1]); !swap_empty(swapFormID)) { + const SwapData::Input input( + formPair.size() > 2 ? formPair[2] : std::string{}, // transform + formPair.size() > 3 ? formPair[3] : std::string{}, // traits + a_str, + a_path); + SwapData swapData(swapFormID, input); + a_func(baseFormID, swapData); + } else { + logger::error(" failed to process {} (SWAP formID not found)", a_str); + } + } else { + logger::error(" failed to process {} (BASE formID not found)", a_str); } - - logger::error(" failed to process {} [{:x}|{:x}|{}] (formID not found)", a_str, baseFormID, swapFormID, data); - - return { false, 0 }; } - std::pair Manager::get_forms(const std::string& a_str, SwapMap& a_map) + void Manager::get_forms(const std::string& a_path, const std::string& a_str, SwapMap& a_map) { - return get_forms_impl(a_str, [&](RE::FormID a_baseID, SwapData& a_swapData) { a_map.insert_or_assign(a_baseID, a_swapData); }); + return get_forms_impl(a_path, a_str, [&](RE::FormID a_baseID, const SwapData& a_swapData) { + a_map[a_baseID].push_back(a_swapData); + }); } - std::pair Manager::get_forms(const std::string& a_str, const std::vector>& a_conditionalIDs, SwapMap& a_map) + void Manager::get_forms(const std::string& a_path, const std::string& a_str, const std::vector& a_conditionalIDs, SwapMap& a_map) { - return get_forms_impl(a_str, [&](RE::FormID a_baseID, SwapData& a_swapData) { + return get_forms_impl(a_path, a_str, [&](const RE::FormID a_baseID, const SwapData& a_swapData) { for (auto& id : a_conditionalIDs) { - a_map[a_baseID].emplace(id, a_swapData); + a_map[a_baseID][id].push_back(a_swapData); } }); } @@ -59,6 +52,8 @@ namespace FormSwap init = true; + logger::info("{:*^30}", "INI"); + std::vector configs; auto constexpr folder = R"(Data\)"; @@ -96,46 +91,49 @@ namespace FormSwap ini.GetAllSections(sections); sections.sort(CSimpleIniA::Entry::LoadOrder()); - constexpr auto map_conflicts = [](const std::string& a_str, const std::string& a_path, RE::FormID a_baseID, ConflictMap& a_conflictMap) { - a_conflictMap[a_baseID].emplace_back(std::make_pair(a_str, a_path)); + constexpr auto push_filter = [](const std::string& a_condition, std::vector& a_processedFilters) { + if (const auto processedID = SwapData::GetFormID(a_condition); processedID != 0) { + a_processedFilters.emplace_back(processedID); + } else { + logger::error(" Filter [{}] INFO - unable to find form, treating filter as string", a_condition); + a_processedFilters.emplace_back(a_condition); + } }; for (auto& [section, comment, keyOrder] : sections) { - if (!string::icontains(section, "|")) { - logger::info(" reading [{}]", section); - if (const auto values = ini.GetSection(section); values && !values->empty()) { - logger::info(" {} swaps found", values->size()); - auto& map = get_form_map(section); - auto& conflictMap = get_conflict_map(section); - for (const auto& key : *values | std::views::keys) { - std::string value(key.pItem); - if (auto [result, baseID] = get_forms(value, map); result) { - map_conflicts(value, path, baseID, conflictMap); - } - } - } - } else { + if (string::icontains(section, "|")) { auto conditions = string::split(string::split(section, "|")[1], ","); //[Forms|EditorID,EditorID2] logger::info(" reading [Forms] : {} conditions", conditions.size()); - std::vector> processedConditions; + std::vector processedConditions; processedConditions.reserve(conditions.size()); for (auto& condition : conditions) { - if (const auto processedID = SwapData::GetFormID(condition); processedID != 0) { - processedConditions.push_back(processedID); - } else { - processedConditions.push_back(condition); + push_filter(condition, processedConditions); + } + + CSimpleIniA::TNamesDepend values; + ini.GetAllKeys(section, values); + values.sort(CSimpleIniA::Entry::LoadOrder()); + + if (!values.empty()) { + logger::info(" {} swaps found", values.size()); + for (const auto& key : values) { + get_forms(path, key.pItem, processedConditions, swapFormsConditional); } } + } else { + logger::info(" reading [{}]", section); - if (const auto values = ini.GetSection(section); values && !values->empty()) { - logger::info(" {} swaps found", values->size()); - for (const auto& key : *values | std::views::keys) { - std::string value(key.pItem); - if (auto [result, baseID] = get_forms(key.pItem, processedConditions, swapFormsConditional); result) { - map_conflicts(value, path, baseID, conflictFormsConditional); - } + CSimpleIniA::TNamesDepend values; + ini.GetAllKeys(section, values); + values.sort(CSimpleIniA::Entry::LoadOrder()); + + if (!values.empty()) { + logger::info(" {} swaps found", values.size()); + auto& map = get_form_map(section); + for (const auto& key : values) { + get_forms(path, key.pItem, map); } } } @@ -150,27 +148,37 @@ namespace FormSwap logger::info("{:*^30}", "CONFLICTS"); - const auto log_conflicts = [&](std::string_view a_type, const ConflictMap& a_conflictMap) { - logger::info("[{}]", a_type); - for (auto& [baseID, conflicts] : a_conflictMap) { - if (conflicts.size() > 1) { - hasConflicts = true; - - auto winningForm = string::split(conflicts.back().first, "|"); + const auto log_conflicts = [&](std::string_view a_type, const SwapMap& a_map) { + if (a_map.empty()) { + return; + } + logger::info("[{}]", a_type); + bool conflicts = false; + for (auto& [baseID, swapDataVec] : a_map) { + if (swapDataVec.size() > 1) { + auto winningForm = string::split(swapDataVec.back().record, "|"); + if (winningForm.size() > 3 && winningForm[3].contains("chance")) { //ignore if winning record is randomized + continue; + } + conflicts = true; logger::warn(" {}", winningForm[0]); - logger::warn(" winning record : {} [{}]", winningForm[1], conflicts.back().second); - logger::warn(" {} conflicts", conflicts.size() - 1); - for (auto it = conflicts.begin(); it != std::prev(conflicts.end()); ++it) { - auto& [record, path] = *it; - auto conflictForm = string::split(record, "|"); - logger::warn(" {} [{}]", conflictForm[1], path); + logger::warn(" winning record : {} [{}]", winningForm[1], swapDataVec.back().path); + logger::warn(" {} conflicts", swapDataVec.size() - 1); + for (auto it = swapDataVec.rbegin() + 1; it != swapDataVec.rend(); ++it) { + auto losingRecord = it->record.substr(it->record.find('|') + 1); + logger::warn(" {} [{}]", losingRecord, it->path); } } } + if (!conflicts) { + logger::info(" No conflicts found"); + } else { + hasConflicts = true; + } }; - log_conflicts("Forms"sv, conflictForms); - log_conflicts("References"sv, conflictRefs); + log_conflicts("Forms"sv, swapForms); + log_conflicts("References"sv, swapRefs); logger::info("{:*^30}", "END"); @@ -180,32 +188,32 @@ namespace FormSwap void Manager::PrintConflicts() const { if (const auto console = RE::ConsoleLog::GetSingleton(); hasConflicts) { - console->Print("~BASE OBJECT SWAPPER~"); - console->Print("Conflicts detected, check po3_BaseObjectSwapper.log in %s for more details\n", logger::log_directory()->string().c_str()); + console->Print("[BOS] Conflicts found, check po3_BaseObjectSwapper.log in %s for more info\n", logger::log_directory()->string().c_str()); } } - Manager::SwapResult Manager::GetSwapConditionalBase(const RE::TESObjectREFR* a_ref, const RE::TESForm* a_base) + SwapResult Manager::GetSwapConditionalBase(const RE::TESObjectREFR* a_ref, const RE::TESForm* a_base) { if (const auto it = swapFormsConditional.find(a_base->GetFormID()); it != swapFormsConditional.end()) { auto cell = a_ref->GetParentCell(); if (!cell) { cell = a_ref->GetSaveParentCell(); } - const auto location = a_ref->GetCurrentLocation(); + const auto currentLocation = a_ref->GetCurrentLocation(); const auto result = std::ranges::find_if(it->second, [&](const auto& formData) { if (std::holds_alternative(formData.first)) { if (auto form = RE::TESForm::LookupByID(std::get(formData.first)); form) { switch (form->GetFormType()) { case RE::FormType::Location: - return location && location == form; - case RE::FormType::Cell: - return cell && cell == form; + { + auto location = form->As(); + return currentLocation && (currentLocation == location || currentLocation->IsParent(location)); + } case RE::FormType::Keyword: { auto keyword = form->As(); - return (location && location->HasKeyword(keyword)) || a_ref->HasKeyword(keyword); + return currentLocation && currentLocation->HasKeyword(keyword) || a_ref->HasKeyword(keyword); } default: break; @@ -213,42 +221,66 @@ namespace FormSwap } } else { const std::string editorID = std::get(formData.first); - return cell && cell->GetFormEditorID() == editorID; + if (cell && cell->GetFormEditorID() == editorID) { + return true; + } + if (currentLocation && currentLocation->HasKeywordString(editorID)) { + return true; + } + if (const auto keywordForm = a_base->As()) { + return keywordForm->HasKeywordString(editorID); + } } return false; }); if (result != it->second.end()) { - return { RE::TESForm::LookupByID(result->second.formID), result->second.transform }; + for (auto& swapData : result->second | std::ranges::views::reverse) { + if (auto swapObject = swapData.GetSwapBase(a_ref)) { + return { swapObject, swapData.transform }; + } + } } } - return { nullptr, Transform() }; + return { nullptr, std::nullopt }; } - Manager::SwapResult Manager::GetSwapData(const RE::TESObjectREFR* a_ref, const RE::TESForm* a_base) + SwapResult Manager::GetSwapData(const RE::TESObjectREFR* a_ref, const RE::TESForm* a_base) { - constexpr auto get_swap_base = [](const RE::TESForm* a_form, const SwapMap& a_map) { + const auto get_swap_base = [a_ref](const RE::TESForm* a_form, const SwapMap& a_map) { if (const auto it = a_map.find(a_form->GetFormID()); it != a_map.end()) { - return SwapResult{ RE::TESForm::LookupByID(it->second.formID), it->second.transform }; + for (auto& swapData : it->second | std::ranges::views::reverse) { + if (auto swapObject = swapData.GetSwapBase(a_ref)) { + return SwapResult{ swapObject, swapData.transform }; + } + } } - return SwapResult{}; + return SwapResult{ nullptr, std::nullopt }; }; - SwapResult swapData{ std::make_pair(nullptr, Transform()) }; + SwapResult swapData{ nullptr, std::nullopt }; if (!a_ref->IsDynamicForm()) { swapData = get_swap_base(a_ref, swapRefs); } - if (!swapData.first) { + if (!std::get<0>(swapData)) { swapData = GetSwapConditionalBase(a_ref, a_base); } - if (!swapData.first) { + if (!std::get<0>(swapData)) { swapData = get_swap_base(a_base, swapForms); } + if (const auto swapLvlBase = std::get<0>(swapData) ? std::get<0>(swapData)->As() : nullptr) { + RE::BSScrapArray calcedObjects{}; + swapLvlBase->CalculateCurrentFormList(a_ref->GetCalcLevel(false), 1, calcedObjects, 0, true); + if (!calcedObjects.empty()) { + std::get<0>(swapData) = static_cast(calcedObjects.front().form); + } + } + return swapData; } } diff --git a/src/Manager.h b/src/Manager.h index 105449b..fd4a316 100644 --- a/src/Manager.h +++ b/src/Manager.h @@ -4,11 +4,16 @@ namespace FormSwap { + template + using SwapMap = Map; + + using SwapDataVec = std::vector; + using SwapDataConditional = Map; + using SwapResult = std::tuple>; + class Manager { public: - using SwapResult = std::pair; - [[nodiscard]] static Manager* GetSingleton() { static Manager singleton; @@ -32,35 +37,18 @@ namespace FormSwap Manager& operator=(Manager&&) = delete; private: - template - using Map = robin_hood::unordered_flat_map; - - template - using SwapMap = Map; - using SwapDataConditional = Map, SwapData>; - - using ConflictMap = Map>>; //record, path + SwapMap& get_form_map(const std::string& a_str); - using Lock = std::mutex; - using Locker = std::scoped_lock; + static void get_forms_impl(const std::string& a_path,const std::string& a_str, std::function a_func); - SwapMap& get_form_map(const std::string& a_str); - ConflictMap& get_conflict_map(const std::string& a_str); + static void get_forms(const std::string& a_path, const std::string& a_str, SwapMap& a_map); + static void get_forms(const std::string& a_path, const std::string& a_str, const std::vector& a_conditionalIDs, SwapMap& a_map); - static std::pair get_forms_impl(const std::string& a_str, std::function a_func); - - static std::pair get_forms(const std::string& a_str, SwapMap& a_map); - static std::pair get_forms(const std::string& a_str, const std::vector>& a_conditionalIDs, SwapMap& a_map); - - ConflictMap conflictForms{}; - ConflictMap conflictRefs{}; - ConflictMap conflictFormsConditional{}; - bool hasConflicts{ false }; - - SwapMap swapForms{}; - SwapMap swapRefs{}; + SwapMap swapForms{}; + SwapMap swapRefs{}; SwapMap swapFormsConditional{}; + bool hasConflicts{ false }; std::atomic_bool init{ false }; }; } diff --git a/src/PCH.h b/src/PCH.h index 0d1be24..23cc654 100644 --- a/src/PCH.h +++ b/src/PCH.h @@ -1,13 +1,17 @@ #pragma once +#define WIN32_LEAN_AND_MEAN + #include "RE/Skyrim.h" #include "SKSE/SKSE.h" +#include "XoshiroCpp.hpp" +#include #include #include #include #include -#include "srell.hpp" +#include #define DLLEXPORT __declspec(dllexport) diff --git a/src/SwapData.cpp b/src/SwapData.cpp index 606c54b..69fdfdb 100644 --- a/src/SwapData.cpp +++ b/src/SwapData.cpp @@ -1,5 +1,4 @@ #include "SwapData.h" -#include "MergeMapperPluginAPI.h" namespace FormSwap { @@ -12,34 +11,16 @@ namespace FormSwap }; if (const auto splitNum = string::split(a_str, R"(/)"); splitNum.size() > 1) { - return { get_float(splitNum[0]), get_float(splitNum[1]) }; + return { get_float(splitNum[0]), get_float(splitNum[1]) }; } else { auto num = get_float(splitNum[0]); return { num, num }; } } - - static float get_random_value(float a_min, float a_max) - { - if (a_min == a_max) { - return a_min; - } - - return stl::RNG::GetSingleton()->Generate(a_min, a_max); - } - - static RE::NiPoint3 get_random_value(const RE::NiPoint3& a_min, const RE::NiPoint3& a_max) - { - if (a_min == a_max) { - return a_min; - } - - return { get_random_value(a_min.x, a_max.x), get_random_value(a_min.y, a_max.y), get_random_value(a_min.z, a_max.z) }; - } } Transform::relData Transform::get_transform_from_string(const std::string& a_str) - { + { bool relative = a_str.contains('R'); minMax transformData; @@ -62,20 +43,43 @@ namespace FormSwap } std::optional> Transform::get_scale_from_string(const std::string& a_str) - { - minMax scale{ 1.0f, 1.0f }; - + { srell::cmatch match; - if (srell::regex_search(a_str.c_str(), match, scaleRegex)) { - scale = detail::get_min_max(match[1].str()); + if (srell::regex_search(a_str.c_str(), match, genericRegex)) { + return detail::get_min_max(match[1].str()); + } + + return minMax{ 1.0f, 1.0f }; + } + + float Transform::get_random_value(const RandInput& a_input, float a_min, float a_max) + { + if (stl::numeric::essentially_equal(a_min, a_max)) { + return a_min; + } + + return a_input.trueRandom ? stl::RNG::GetSingleton()->Generate(a_min, a_max) : + SeedRNG(a_input.refSeed).Generate(a_min, a_max); + } + + RE::NiPoint3 Transform::get_random_value(const RandInput& a_input, const std::pair& a_minMax) + { + auto& [min, max] = a_minMax; + + if (min == max) { + return min; } - return scale; + return RE::NiPoint3{ + get_random_value(a_input, min.x, max.x), + get_random_value(a_input, min.y, max.y), + get_random_value(a_input, min.z, max.z) + }; } Transform::Transform(const std::string& a_str) { - if (!a_str.empty()) { + if (!a_str.empty() && !string::icontains(a_str, "NONE")) { const auto transformStrs = string::split(a_str, ","); for (auto& transformStr : transformStrs) { if (transformStr.contains("pos")) { @@ -89,45 +93,123 @@ namespace FormSwap } } - void Transform::SetTransform(RE::TESObjectREFR* a_refr) + void Transform::SetTransform(RE::TESObjectREFR* a_refr) const { - if (location) { - auto [relative, minMax] = *location; - auto [min, max] = minMax; - a_refr->data.location = relative ? a_refr->data.location + detail::get_random_value(min, max) : detail::get_random_value(min, max); - } - if (rotation) { - auto [relative, minMax] = *location; - auto [min, max] = minMax; - a_refr->data.angle = relative ? a_refr->data.angle + detail::get_random_value(min, max) : detail::get_random_value(min, max); + if (location || rotation || refScale) { + const RandInput input(useTrueRandom, a_refr->GetFormID()); + if (location) { + auto& [relative, minMax] = *location; + if (relative) { + a_refr->data.location += get_random_value(input, minMax); + } else { + a_refr->data.location = get_random_value(input, minMax); + } + } + if (rotation) { + auto& [relative, minMax] = *rotation; + if (relative) { + a_refr->data.angle += get_random_value(input, minMax); + } else { + a_refr->data.angle = get_random_value(input, minMax); + } + } + if (refScale) { + auto& [min, max] = *refScale; + const auto scale = std::clamp(get_random_value(input, min, max), 0.0f, 1000.0f); + a_refr->refScale = static_cast(a_refr->refScale * scale); + } } - if (refScale) { - auto& [min, max] = *refScale; - const auto scale = std::clamp(detail::get_random_value(min, max), 0.0f, 1000.0f); - a_refr->refScale = static_cast(a_refr->refScale * scale); + } + + Traits::Traits(const std::string& a_str) + { + if (!a_str.empty() && !string::icontains(a_str, "NONE")) { + if (a_str.contains("chance")) { + if (a_str.contains("R")) { + trueRandom = true; + } + srell::cmatch match; + if (srell::regex_search(a_str.c_str(), match, genericRegex)) { + chance = string::lexical_cast(match[1].str()); + } + } } } - SwapData::SwapData(RE::FormID a_id, Transform a_transform) : - formID(a_id), - transform(std::move(a_transform)) - {} + SwapData::SwapData(FormIDOrSet a_id, const Input& a_input) : + formIDSet(std::move(a_id)), + transform(a_input.transformStr), + traits(a_input.traitsStr), + record(a_input.record), + path(a_input.path) + { + if (traits.trueRandom) { + transform.useTrueRandom = true; + } + } RE::FormID SwapData::GetFormID(const std::string& a_str) { - if (a_str.find('~') != std::string::npos) { - const auto formPair = string::split(a_str, "~"); - if (g_mergeMapperInterface){ - const auto [modName, formID] = g_mergeMapperInterface->GetNewFormID(formPair[1].c_str(), std::stoi(formPair[0], 0, 16)); - return RE::TESDataHandler::GetSingleton()->LookupFormID(formID, (const char*) modName); - }else{ - return RE::TESDataHandler::GetSingleton()->LookupFormID(std::stoi(formPair[0], 0, 16), formPair[1]); + if (a_str.contains('~')) { + if (const auto splitID = string::split(a_str, "~"); splitID.size() == 2) { + const auto formID = string::lexical_cast(splitID[0], true); + const auto& modName = splitID[1]; + if (g_mergeMapperInterface) { + const auto [mergedModName, mergedFormID] = g_mergeMapperInterface->GetNewFormID(modName.c_str(), formID); + return RE::TESDataHandler::GetSingleton()->LookupFormID(mergedFormID, mergedModName); + } else { + return RE::TESDataHandler::GetSingleton()->LookupFormID(formID, modName); + } } } - if (const auto form = RE::TESForm::LookupByEditorID(a_str); form) { + if (const auto form = RE::TESForm::LookupByEditorID(a_str)) { return form->GetFormID(); } - return static_cast(0); } + + FormIDOrSet SwapData::GetSwapFormID(const std::string& a_str) + { + if (a_str.contains(",")) { + FormIDSet set; + const auto IDStrs = string::split(a_str, ","); + set.reserve(IDStrs.size()); + for (auto& IDStr : IDStrs) { + if (auto formID = GetFormID(IDStr); formID != 0) { + set.emplace(formID); + } else { + logger::error(" failed to process {} (SWAP formID not found)", IDStr); + } + } + return set; + } else { + return GetFormID(a_str); + } + } + + RE::TESBoundObject* SwapData::GetSwapBase(const RE::TESObjectREFR* a_ref) const + { + auto seededRNG = SeedRNG(a_ref->GetFormID()); + auto staticRNG = stl::RNG::GetSingleton(); + + if (traits.chance != 100) { + const auto rng = traits.trueRandom ? staticRNG->Generate(0, 100) : + seededRNG.Generate(0, 100); + if (rng > traits.chance) { + return nullptr; + } + } + + if (const auto formID = std::get_if(&formIDSet); formID) { + return RE::TESForm::LookupByID(*formID); + } else { // return random element from set + auto& set = std::get(formIDSet); + + const auto setEnd = std::distance(set.begin(), set.end()) - 1; + const auto randIt = traits.trueRandom ? staticRNG->Generate(0, setEnd) : + seededRNG.Generate(0, setEnd); + + return RE::TESForm::LookupByID(*std::next(set.begin(), randIt)); + } + } } diff --git a/src/SwapData.h b/src/SwapData.h index 349f8c1..9c1a5e8 100644 --- a/src/SwapData.h +++ b/src/SwapData.h @@ -2,8 +2,53 @@ namespace FormSwap { - struct Transform + using FormIDStr = std::variant; + + template + using Map = robin_hood::unordered_flat_map; + template + using Set = robin_hood::unordered_flat_set; + + using FormIDSet = Set; + using FormIDOrSet = std::variant; + inline bool swap_empty(const FormIDOrSet& a_set) + { + if (const auto formID = std::get_if(&a_set); formID) { + return *formID == 0; + } else { + return std::get(a_set).empty(); + } + } + + inline srell::regex genericRegex{ R"(\((.*?)\))" }; + + class SeedRNG + { + public: + SeedRNG() = delete; + explicit SeedRNG(const RE::FormID a_seed) : + rng(a_seed) + {} + + template >> + T Generate(T a_min, T a_max) + { + if constexpr (std::is_integral_v) { + std::uniform_int_distribution distr(a_min, a_max); + return distr(rng); + } else { + std::uniform_real_distribution distr(a_min, a_max); + return distr(rng); + } + } + + private: + XoshiroCpp::Xoshiro256StarStar rng; + }; + + class Transform { + public: template using minMax = std::pair; template @@ -12,29 +57,68 @@ namespace FormSwap Transform() = default; explicit Transform(const std::string& a_str); - void SetTransform(RE::TESObjectREFR* a_refr); + void SetTransform(RE::TESObjectREFR* a_refr) const; private: [[nodiscard]] static relData get_transform_from_string(const std::string& a_str); [[nodiscard]] static std::optional> get_scale_from_string(const std::string& a_str); + struct RandInput + { + bool trueRandom{ false }; + RE::FormID refSeed{ 0 }; + }; + + static float get_random_value(const RandInput& a_input, float a_min, float a_max); + static RE::NiPoint3 get_random_value(const RandInput& a_input, const std::pair& a_minMax); + + // members std::optional> location{ std::nullopt }; std::optional> rotation{ std::nullopt }; std::optional> refScale{ std::nullopt }; + bool useTrueRandom{ false }; + static inline srell::regex transformRegex{ R"(\((.*?),(.*?),(.*?)\))" }; - static inline srell::regex scaleRegex{ R"(\((.*?)\))" }; + + friend class SwapData; + }; + + struct Traits + { + Traits() = default; + explicit Traits(const std::string& a_str); + + // members + bool trueRandom{ false }; + std::uint32_t chance{ 100 }; }; class SwapData { public: + struct Input + { + std::string transformStr; + std::string traitsStr; + std::string record; + std::string path; + }; + SwapData() = delete; - SwapData(RE::FormID a_id, Transform a_transform); + SwapData(FormIDOrSet a_id, const Input& a_input); [[nodiscard]] static RE::FormID GetFormID(const std::string& a_str); + [[nodiscard]] static FormIDOrSet GetSwapFormID(const std::string& a_str); + + RE::TESBoundObject* GetSwapBase(const RE::TESObjectREFR* a_ref) const; - RE::FormID formID{}; + // members + FormIDOrSet formIDSet{}; Transform transform{}; + Traits traits{}; + + std::string record{}; + std::string path{}; }; } diff --git a/src/XoshiroCpp.hpp b/src/XoshiroCpp.hpp new file mode 100644 index 0000000..1ecfdfb --- /dev/null +++ b/src/XoshiroCpp.hpp @@ -0,0 +1,1633 @@ +//---------------------------------------------------------------------------------------- +// +// Xoshiro-cpp +// Xoshiro PRNG wrapper library for C++17 / C++20 +// +// Copyright (C) 2020 Ryo Suzuki +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//---------------------------------------------------------------------------------------- + +# pragma once +# include +# include +# include +# include +# if __has_cpp_attribute(nodiscard) >= 201907L +# define XOSHIROCPP_NODISCARD_CXX20 [[nodiscard]] +# else +# define XOSHIROCPP_NODISCARD_CXX20 +# endif + +namespace XoshiroCpp +{ + // A default seed value for the generators + inline constexpr std::uint64_t DefaultSeed = 1234567890ULL; + + // Converts given uint32 value `i` into a 32-bit floating + // point value in the range of [0.0f, 1.0f) + template >* = nullptr> + [[nodiscard]] + inline constexpr float FloatFromBits(Uint32 i) noexcept; + + // Converts given uint64 value `i` into a 64-bit floating + // point value in the range of [0.0, 1.0) + template >* = nullptr> + [[nodiscard]] + inline constexpr double DoubleFromBits(Uint64 i) noexcept; + + // SplitMix64 + // Output: 64 bits + // Period: 2^64 + // Footprint: 8 bytes + // Original implementation: http://prng.di.unimi.it/splitmix64.c + class SplitMix64 + { + public: + + using state_type = std::uint64_t; + using result_type = std::uint64_t; + + XOSHIROCPP_NODISCARD_CXX20 + explicit constexpr SplitMix64(state_type state = DefaultSeed) noexcept; + + constexpr result_type operator()() noexcept; + + template + [[nodiscard]] + constexpr std::array generateSeedSequence() noexcept; + + [[nodiscard]] + static constexpr result_type min() noexcept; + + [[nodiscard]] + static constexpr result_type max() noexcept; + + [[nodiscard]] + constexpr state_type serialize() const noexcept; + + constexpr void deserialize(state_type state) noexcept; + + [[nodiscard]] + friend bool operator ==(const SplitMix64& lhs, const SplitMix64& rhs) noexcept + { + return (lhs.m_state == rhs.m_state); + } + + [[nodiscard]] + friend bool operator !=(const SplitMix64& lhs, const SplitMix64& rhs) noexcept + { + return (lhs.m_state != rhs.m_state); + } + + private: + + state_type m_state; + }; + + // xoshiro256+ + // Output: 64 bits + // Period: 2^256 - 1 + // Footprint: 32 bytes + // Original implementation: http://prng.di.unimi.it/xoshiro256plus.c + // Version: 1.0 + class Xoshiro256Plus + { + public: + + using state_type = std::array; + using result_type = std::uint64_t; + + XOSHIROCPP_NODISCARD_CXX20 + explicit constexpr Xoshiro256Plus(std::uint64_t seed = DefaultSeed) noexcept; + + XOSHIROCPP_NODISCARD_CXX20 + explicit constexpr Xoshiro256Plus(state_type state) noexcept; + + constexpr result_type operator()() noexcept; + + // This is the jump function for the generator. It is equivalent + // to 2^128 calls to operator(); it can be used to generate 2^128 + // non-overlapping subsequences for parallel computations. + constexpr void jump() noexcept; + + // This is the long-jump function for the generator. It is equivalent to + // 2^192 calls to next(); it can be used to generate 2^64 starting points, + // from each of which jump() will generate 2^64 non-overlapping + // subsequences for parallel distributed computations. + constexpr void longJump() noexcept; + + [[nodiscard]] + static constexpr result_type min() noexcept; + + [[nodiscard]] + static constexpr result_type max() noexcept; + + [[nodiscard]] + constexpr state_type serialize() const noexcept; + + constexpr void deserialize(state_type state) noexcept; + + [[nodiscard]] + friend bool operator ==(const Xoshiro256Plus& lhs, const Xoshiro256Plus& rhs) noexcept + { + return (lhs.m_state == rhs.m_state); + } + + [[nodiscard]] + friend bool operator !=(const Xoshiro256Plus& lhs, const Xoshiro256Plus& rhs) noexcept + { + return (lhs.m_state != rhs.m_state); + } + + private: + + state_type m_state; + }; + + // xoshiro256++ + // Output: 64 bits + // Period: 2^256 - 1 + // Footprint: 32 bytes + // Original implementation: http://prng.di.unimi.it/xoshiro256plusplus.c + // Version: 1.0 + class Xoshiro256PlusPlus + { + public: + + using state_type = std::array; + using result_type = std::uint64_t; + + XOSHIROCPP_NODISCARD_CXX20 + explicit constexpr Xoshiro256PlusPlus(std::uint64_t seed = DefaultSeed) noexcept; + + XOSHIROCPP_NODISCARD_CXX20 + explicit constexpr Xoshiro256PlusPlus(state_type state) noexcept; + + constexpr result_type operator()() noexcept; + + // This is the jump function for the generator. It is equivalent + // to 2^128 calls to next(); it can be used to generate 2^128 + // non-overlapping subsequences for parallel computations. + constexpr void jump() noexcept; + + // This is the long-jump function for the generator. It is equivalent to + // 2^192 calls to next(); it can be used to generate 2^64 starting points, + // from each of which jump() will generate 2^64 non-overlapping + // subsequences for parallel distributed computations. + constexpr void longJump() noexcept; + + [[nodiscard]] + static constexpr result_type min() noexcept; + + [[nodiscard]] + static constexpr result_type max() noexcept; + + [[nodiscard]] + constexpr state_type serialize() const noexcept; + + constexpr void deserialize(state_type state) noexcept; + + [[nodiscard]] + friend bool operator ==(const Xoshiro256PlusPlus& lhs, const Xoshiro256PlusPlus& rhs) noexcept + { + return (lhs.m_state == rhs.m_state); + } + + [[nodiscard]] + friend bool operator !=(const Xoshiro256PlusPlus& lhs, const Xoshiro256PlusPlus& rhs) noexcept + { + return (lhs.m_state != rhs.m_state); + } + + private: + + state_type m_state; + }; + + // xoshiro256** + // Output: 64 bits + // Period: 2^256 - 1 + // Footprint: 32 bytes + // Original implementation: http://prng.di.unimi.it/xoshiro256starstar.c + // Version: 1.0 + class Xoshiro256StarStar + { + public: + + using state_type = std::array; + using result_type = std::uint64_t; + + XOSHIROCPP_NODISCARD_CXX20 + explicit constexpr Xoshiro256StarStar(std::uint64_t seed = DefaultSeed) noexcept; + + XOSHIROCPP_NODISCARD_CXX20 + explicit constexpr Xoshiro256StarStar(state_type state) noexcept; + + constexpr result_type operator()() noexcept; + + // This is the jump function for the generator. It is equivalent + // to 2^128 calls to next(); it can be used to generate 2^128 + // non-overlapping subsequences for parallel computations. + constexpr void jump() noexcept; + + // This is the long-jump function for the generator. It is equivalent to + // 2^192 calls to next(); it can be used to generate 2^64 starting points, + // from each of which jump() will generate 2^64 non-overlapping + // subsequences for parallel distributed computations. + constexpr void longJump() noexcept; + + [[nodiscard]] + static constexpr result_type min() noexcept; + + [[nodiscard]] + static constexpr result_type max() noexcept; + + [[nodiscard]] + constexpr state_type serialize() const noexcept; + + constexpr void deserialize(state_type state) noexcept; + + [[nodiscard]] + friend bool operator ==(const Xoshiro256StarStar& lhs, const Xoshiro256StarStar& rhs) noexcept + { + return (lhs.m_state == rhs.m_state); + } + + [[nodiscard]] + friend bool operator !=(const Xoshiro256StarStar& lhs, const Xoshiro256StarStar& rhs) noexcept + { + return (lhs.m_state != rhs.m_state); + } + + private: + + state_type m_state; + }; + + // xoroshiro128+ + // Output: 64 bits + // Period: 2^128 - 1 + // Footprint: 16 bytes + // Original implementation: http://prng.di.unimi.it/xoroshiro128plus.c + // Version: 1.0 + class Xoroshiro128Plus + { + public: + + using state_type = std::array; + using result_type = std::uint64_t; + + XOSHIROCPP_NODISCARD_CXX20 + explicit constexpr Xoroshiro128Plus(std::uint64_t seed = DefaultSeed) noexcept; + + XOSHIROCPP_NODISCARD_CXX20 + explicit constexpr Xoroshiro128Plus(state_type state) noexcept; + + constexpr result_type operator()() noexcept; + + // This is the jump function for the generator. It is equivalent + // to 2^64 calls to next(); it can be used to generate 2^64 + // non-overlapping subsequences for parallel computations. + constexpr void jump() noexcept; + + // This is the long-jump function for the generator. It is equivalent to + // 2^96 calls to next(); it can be used to generate 2^32 starting points, + // from each of which jump() will generate 2^32 non-overlapping + // subsequences for parallel distributed computations. + constexpr void longJump() noexcept; + + [[nodiscard]] + static constexpr result_type min() noexcept; + + [[nodiscard]] + static constexpr result_type max() noexcept; + + [[nodiscard]] + constexpr state_type serialize() const noexcept; + + constexpr void deserialize(state_type state) noexcept; + + [[nodiscard]] + friend bool operator ==(const Xoroshiro128Plus& lhs, const Xoroshiro128Plus& rhs) noexcept + { + return (lhs.m_state == rhs.m_state); + } + + [[nodiscard]] + friend bool operator !=(const Xoroshiro128Plus& lhs, const Xoroshiro128Plus& rhs) noexcept + { + return (lhs.m_state != rhs.m_state); + } + + private: + + state_type m_state; + }; + + // xoroshiro128++ + // Output: 64 bits + // Period: 2^128 - 1 + // Footprint: 16 bytes + // Original implementation: http://prng.di.unimi.it/xoroshiro128plusplus.c + // Version: 1.0 + class Xoroshiro128PlusPlus + { + public: + + using state_type = std::array; + using result_type = std::uint64_t; + + XOSHIROCPP_NODISCARD_CXX20 + explicit constexpr Xoroshiro128PlusPlus(std::uint64_t seed = DefaultSeed) noexcept; + + XOSHIROCPP_NODISCARD_CXX20 + explicit constexpr Xoroshiro128PlusPlus(state_type state) noexcept; + + constexpr result_type operator()() noexcept; + + // This is the jump function for the generator. It is equivalent + // to 2^64 calls to next(); it can be used to generate 2^64 + // non-overlapping subsequences for parallel computations. + constexpr void jump() noexcept; + + // This is the long-jump function for the generator. It is equivalent to + // 2^96 calls to next(); it can be used to generate 2^32 starting points, + // from each of which jump() will generate 2^32 non-overlapping + // subsequences for parallel distributed computations. + constexpr void longJump() noexcept; + + [[nodiscard]] + static constexpr result_type min() noexcept; + + [[nodiscard]] + static constexpr result_type max() noexcept; + + [[nodiscard]] + constexpr state_type serialize() const noexcept; + + constexpr void deserialize(state_type state) noexcept; + + [[nodiscard]] + friend bool operator ==(const Xoroshiro128PlusPlus& lhs, const Xoroshiro128PlusPlus& rhs) noexcept + { + return (lhs.m_state == rhs.m_state); + } + + [[nodiscard]] + friend bool operator !=(const Xoroshiro128PlusPlus& lhs, const Xoroshiro128PlusPlus& rhs) noexcept + { + return (lhs.m_state != rhs.m_state); + } + + private: + + state_type m_state; + }; + + // xoroshiro128** + // Output: 64 bits + // Period: 2^128 - 1 + // Footprint: 16 bytes + // Original implementation: http://prng.di.unimi.it/xoroshiro128starstar.c + // Version: 1.0 + class Xoroshiro128StarStar + { + public: + + using state_type = std::array; + using result_type = std::uint64_t; + + XOSHIROCPP_NODISCARD_CXX20 + explicit constexpr Xoroshiro128StarStar(std::uint64_t seed = DefaultSeed) noexcept; + + XOSHIROCPP_NODISCARD_CXX20 + explicit constexpr Xoroshiro128StarStar(state_type state) noexcept; + + constexpr result_type operator()() noexcept; + + // This is the jump function for the generator. It is equivalent + // to 2^64 calls to next(); it can be used to generate 2^64 + // non-overlapping subsequences for parallel computations. + constexpr void jump() noexcept; + + // This is the long-jump function for the generator. It is equivalent to + // 2^96 calls to next(); it can be used to generate 2^32 starting points, + // from each of which jump() will generate 2^32 non-overlapping + // subsequences for parallel distributed computations. + constexpr void longJump() noexcept; + + [[nodiscard]] + static constexpr result_type min() noexcept; + + [[nodiscard]] + static constexpr result_type max() noexcept; + + [[nodiscard]] + constexpr state_type serialize() const noexcept; + + constexpr void deserialize(state_type state) noexcept; + + [[nodiscard]] + friend bool operator ==(const Xoroshiro128StarStar& lhs, const Xoroshiro128StarStar& rhs) noexcept + { + return (lhs.m_state == rhs.m_state); + } + + [[nodiscard]] + friend bool operator !=(const Xoroshiro128StarStar& lhs, const Xoroshiro128StarStar& rhs) noexcept + { + return (lhs.m_state != rhs.m_state); + } + + private: + + state_type m_state; + }; + + // xoshiro128+ + // Output: 32 bits + // Period: 2^128 - 1 + // Footprint: 16 bytes + // Original implementation: http://prng.di.unimi.it/xoshiro128plus.c + // Version: 1.0 + class Xoshiro128Plus + { + public: + + using state_type = std::array; + using result_type = std::uint32_t; + + XOSHIROCPP_NODISCARD_CXX20 + explicit constexpr Xoshiro128Plus(std::uint64_t seed = DefaultSeed) noexcept; + + XOSHIROCPP_NODISCARD_CXX20 + explicit constexpr Xoshiro128Plus(state_type state) noexcept; + + constexpr result_type operator()() noexcept; + + // This is the jump function for the generator. It is equivalent + // to 2^64 calls to next(); it can be used to generate 2^64 + // non-overlapping subsequences for parallel computations. + constexpr void jump() noexcept; + + // This is the long-jump function for the generator. It is equivalent to + // 2^96 calls to next(); it can be used to generate 2^32 starting points, + // from each of which jump() will generate 2^32 non-overlapping + // subsequences for parallel distributed computations. + constexpr void longJump() noexcept; + + [[nodiscard]] + static constexpr result_type min() noexcept; + + [[nodiscard]] + static constexpr result_type max() noexcept; + + [[nodiscard]] + constexpr state_type serialize() const noexcept; + + constexpr void deserialize(state_type state) noexcept; + + [[nodiscard]] + friend bool operator ==(const Xoshiro128Plus& lhs, const Xoshiro128Plus& rhs) noexcept + { + return (lhs.m_state == rhs.m_state); + } + + [[nodiscard]] + friend bool operator !=(const Xoshiro128Plus& lhs, const Xoshiro128Plus& rhs) noexcept + { + return (lhs.m_state != rhs.m_state); + } + + private: + + state_type m_state; + }; + + // xoshiro128++ + // Output: 32 bits + // Period: 2^128 - 1 + // Footprint: 16 bytes + // Original implementation: http://prng.di.unimi.it/xoshiro128plusplus.c + // Version: 1.0 + class Xoshiro128PlusPlus + { + public: + + using state_type = std::array; + using result_type = std::uint32_t; + + XOSHIROCPP_NODISCARD_CXX20 + explicit constexpr Xoshiro128PlusPlus(std::uint64_t seed = DefaultSeed) noexcept; + + XOSHIROCPP_NODISCARD_CXX20 + explicit constexpr Xoshiro128PlusPlus(state_type state) noexcept; + + constexpr result_type operator()() noexcept; + + // This is the jump function for the generator. It is equivalent + // to 2^64 calls to next(); it can be used to generate 2^64 + // non-overlapping subsequences for parallel computations. + constexpr void jump() noexcept; + + // This is the long-jump function for the generator. It is equivalent to + // 2^96 calls to next(); it can be used to generate 2^32 starting points, + // from each of which jump() will generate 2^32 non-overlapping + // subsequences for parallel distributed computations. + constexpr void longJump() noexcept; + + [[nodiscard]] + static constexpr result_type min() noexcept; + + [[nodiscard]] + static constexpr result_type max() noexcept; + + [[nodiscard]] + constexpr state_type serialize() const noexcept; + + constexpr void deserialize(state_type state) noexcept; + + [[nodiscard]] + friend bool operator ==(const Xoshiro128PlusPlus& lhs, const Xoshiro128PlusPlus& rhs) noexcept + { + return (lhs.m_state == rhs.m_state); + } + + [[nodiscard]] + friend bool operator !=(const Xoshiro128PlusPlus& lhs, const Xoshiro128PlusPlus& rhs) noexcept + { + return (lhs.m_state != rhs.m_state); + } + + private: + + state_type m_state; + }; + + // xoshiro128** + // Output: 32 bits + // Period: 2^128 - 1 + // Footprint: 16 bytes + // Original implementation: http://prng.di.unimi.it/xoshiro128starstar.c + // Version: 1.1 + class Xoshiro128StarStar + { + public: + + using state_type = std::array; + using result_type = std::uint32_t; + + XOSHIROCPP_NODISCARD_CXX20 + explicit constexpr Xoshiro128StarStar(std::uint64_t seed = DefaultSeed) noexcept; + + XOSHIROCPP_NODISCARD_CXX20 + explicit constexpr Xoshiro128StarStar(state_type state) noexcept; + + constexpr result_type operator()() noexcept; + + // This is the jump function for the generator. It is equivalent + // to 2^64 calls to next(); it can be used to generate 2^64 + // non-overlapping subsequences for parallel computations. + constexpr void jump() noexcept; + + // This is the long-jump function for the generator. It is equivalent to + // 2^96 calls to next(); it can be used to generate 2^32 starting points, + // from each of which jump() will generate 2^32 non-overlapping + // subsequences for parallel distributed computations. + constexpr void longJump() noexcept; + + [[nodiscard]] + static constexpr result_type min() noexcept; + + [[nodiscard]] + static constexpr result_type max() noexcept; + + [[nodiscard]] + constexpr state_type serialize() const noexcept; + + constexpr void deserialize(state_type state) noexcept; + + [[nodiscard]] + friend bool operator ==(const Xoshiro128StarStar& lhs, const Xoshiro128StarStar& rhs) noexcept + { + return (lhs.m_state == rhs.m_state); + } + + [[nodiscard]] + friend bool operator !=(const Xoshiro128StarStar& lhs, const Xoshiro128StarStar& rhs) noexcept + { + return (lhs.m_state != rhs.m_state); + } + + private: + + state_type m_state; + }; +} + +//////////////////////////////////////////////////////////////// + +namespace XoshiroCpp +{ + template >*> + inline constexpr float FloatFromBits(const Uint32 i) noexcept + { + return (i >> 8) * 0x1.0p-24f; + } + + template >*> + inline constexpr double DoubleFromBits(const Uint64 i) noexcept + { + return (i >> 11) * 0x1.0p-53; + } + + namespace detail + { + [[nodiscard]] + static constexpr std::uint64_t RotL(const std::uint64_t x, const int s) noexcept + { + return (x << s) | (x >> (64 - s)); + } + + [[nodiscard]] + static constexpr std::uint32_t RotL(const std::uint32_t x, const int s) noexcept + { + return (x << s) | (x >> (32 - s)); + } + } + + //////////////////////////////////////////////////////////////// + // + // SplitMix64 + // + inline constexpr SplitMix64::SplitMix64(const state_type state) noexcept + : m_state(state) {} + + inline constexpr SplitMix64::result_type SplitMix64::operator()() noexcept + { + std::uint64_t z = (m_state += 0x9e3779b97f4a7c15); + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; + z = (z ^ (z >> 27)) * 0x94d049bb133111eb; + return z ^ (z >> 31); + } + + template + inline constexpr std::array SplitMix64::generateSeedSequence() noexcept + { + std::array seeds = {}; + + for (auto& seed : seeds) + { + seed = operator()(); + } + + return seeds; + } + + inline constexpr SplitMix64::result_type SplitMix64::min() noexcept + { + return std::numeric_limits::lowest(); + } + + inline constexpr SplitMix64::result_type SplitMix64::max() noexcept + { + return std::numeric_limits::max(); + } + + inline constexpr SplitMix64::state_type SplitMix64::serialize() const noexcept + { + return m_state; + } + + inline constexpr void SplitMix64::deserialize(const state_type state) noexcept + { + m_state = state; + } + + //////////////////////////////////////////////////////////////// + // + // xoshiro256+ + // + inline constexpr Xoshiro256Plus::Xoshiro256Plus(const std::uint64_t seed) noexcept + : m_state(SplitMix64{ seed }.generateSeedSequence<4>()) {} + + inline constexpr Xoshiro256Plus::Xoshiro256Plus(const state_type state) noexcept + : m_state(state) {} + + inline constexpr Xoshiro256Plus::result_type Xoshiro256Plus::operator()() noexcept + { + const std::uint64_t result = m_state[0] + m_state[3]; + const std::uint64_t t = m_state[1] << 17; + m_state[2] ^= m_state[0]; + m_state[3] ^= m_state[1]; + m_state[1] ^= m_state[2]; + m_state[0] ^= m_state[3]; + m_state[2] ^= t; + m_state[3] = detail::RotL(m_state[3], 45); + return result; + } + + inline constexpr void Xoshiro256Plus::jump() noexcept + { + constexpr std::uint64_t JUMP[] = { 0x180ec6d33cfd0aba, 0xd5a61266f0c9392c, 0xa9582618e03fc9aa, 0x39abdc4529b1661c }; + + std::uint64_t s0 = 0; + std::uint64_t s1 = 0; + std::uint64_t s2 = 0; + std::uint64_t s3 = 0; + + for (std::uint64_t jump : JUMP) + { + for (int b = 0; b < 64; ++b) + { + if (jump & UINT64_C(1) << b) + { + s0 ^= m_state[0]; + s1 ^= m_state[1]; + s2 ^= m_state[2]; + s3 ^= m_state[3]; + } + operator()(); + } + } + + m_state[0] = s0; + m_state[1] = s1; + m_state[2] = s2; + m_state[3] = s3; + } + + inline constexpr void Xoshiro256Plus::longJump() noexcept + { + constexpr std::uint64_t LONG_JUMP[] = { 0x76e15d3efefdcbbf, 0xc5004e441c522fb3, 0x77710069854ee241, 0x39109bb02acbe635 }; + + std::uint64_t s0 = 0; + std::uint64_t s1 = 0; + std::uint64_t s2 = 0; + std::uint64_t s3 = 0; + + for (std::uint64_t jump : LONG_JUMP) + { + for (int b = 0; b < 64; ++b) + { + if (jump & UINT64_C(1) << b) + { + s0 ^= m_state[0]; + s1 ^= m_state[1]; + s2 ^= m_state[2]; + s3 ^= m_state[3]; + } + operator()(); + } + } + + m_state[0] = s0; + m_state[1] = s1; + m_state[2] = s2; + m_state[3] = s3; + } + + inline constexpr Xoshiro256Plus::result_type Xoshiro256Plus::min() noexcept + { + return std::numeric_limits::lowest(); + } + + inline constexpr Xoshiro256Plus::result_type Xoshiro256Plus::max() noexcept + { + return std::numeric_limits::max(); + } + + inline constexpr Xoshiro256Plus::state_type Xoshiro256Plus::serialize() const noexcept + { + return m_state; + } + + inline constexpr void Xoshiro256Plus::deserialize(const state_type state) noexcept + { + m_state = state; + } + + //////////////////////////////////////////////////////////////// + // + // xoshiro256++ + // + inline constexpr Xoshiro256PlusPlus::Xoshiro256PlusPlus(const std::uint64_t seed) noexcept + : m_state(SplitMix64{ seed }.generateSeedSequence<4>()) {} + + inline constexpr Xoshiro256PlusPlus::Xoshiro256PlusPlus(const state_type state) noexcept + : m_state(state) {} + + inline constexpr Xoshiro256PlusPlus::result_type Xoshiro256PlusPlus::operator()() noexcept + { + const std::uint64_t result = detail::RotL(m_state[0] + m_state[3], 23) + m_state[0]; + const std::uint64_t t = m_state[1] << 17; + m_state[2] ^= m_state[0]; + m_state[3] ^= m_state[1]; + m_state[1] ^= m_state[2]; + m_state[0] ^= m_state[3]; + m_state[2] ^= t; + m_state[3] = detail::RotL(m_state[3], 45); + return result; + } + + inline constexpr void Xoshiro256PlusPlus::jump() noexcept + { + constexpr std::uint64_t JUMP[] = { 0x180ec6d33cfd0aba, 0xd5a61266f0c9392c, 0xa9582618e03fc9aa, 0x39abdc4529b1661c }; + + std::uint64_t s0 = 0; + std::uint64_t s1 = 0; + std::uint64_t s2 = 0; + std::uint64_t s3 = 0; + + for (std::uint64_t jump : JUMP) + { + for (int b = 0; b < 64; ++b) + { + if (jump & UINT64_C(1) << b) + { + s0 ^= m_state[0]; + s1 ^= m_state[1]; + s2 ^= m_state[2]; + s3 ^= m_state[3]; + } + operator()(); + } + } + + m_state[0] = s0; + m_state[1] = s1; + m_state[2] = s2; + m_state[3] = s3; + } + + inline constexpr void Xoshiro256PlusPlus::longJump() noexcept + { + constexpr std::uint64_t LONG_JUMP[] = { 0x76e15d3efefdcbbf, 0xc5004e441c522fb3, 0x77710069854ee241, 0x39109bb02acbe635 }; + + std::uint64_t s0 = 0; + std::uint64_t s1 = 0; + std::uint64_t s2 = 0; + std::uint64_t s3 = 0; + + for (std::uint64_t jump : LONG_JUMP) + { + for (int b = 0; b < 64; ++b) + { + if (jump & UINT64_C(1) << b) + { + s0 ^= m_state[0]; + s1 ^= m_state[1]; + s2 ^= m_state[2]; + s3 ^= m_state[3]; + } + operator()(); + } + } + + m_state[0] = s0; + m_state[1] = s1; + m_state[2] = s2; + m_state[3] = s3; + } + + inline constexpr Xoshiro256PlusPlus::result_type Xoshiro256PlusPlus::min() noexcept + { + return std::numeric_limits::lowest(); + } + + inline constexpr Xoshiro256PlusPlus::result_type Xoshiro256PlusPlus::max() noexcept + { + return std::numeric_limits::max(); + } + + inline constexpr Xoshiro256PlusPlus::state_type Xoshiro256PlusPlus::serialize() const noexcept + { + return m_state; + } + + inline constexpr void Xoshiro256PlusPlus::deserialize(const state_type state) noexcept + { + m_state = state; + } + + //////////////////////////////////////////////////////////////// + // + // xoshiro256** + // + inline constexpr Xoshiro256StarStar::Xoshiro256StarStar(const std::uint64_t seed) noexcept + : m_state(SplitMix64{ seed }.generateSeedSequence<4>()) {} + + inline constexpr Xoshiro256StarStar::Xoshiro256StarStar(const state_type state) noexcept + : m_state(state) {} + + inline constexpr Xoshiro256StarStar::result_type Xoshiro256StarStar::operator()() noexcept + { + const std::uint64_t result = detail::RotL(m_state[1] * 5, 7) * 9; + const std::uint64_t t = m_state[1] << 17; + m_state[2] ^= m_state[0]; + m_state[3] ^= m_state[1]; + m_state[1] ^= m_state[2]; + m_state[0] ^= m_state[3]; + m_state[2] ^= t; + m_state[3] = detail::RotL(m_state[3], 45); + return result; + } + + inline constexpr void Xoshiro256StarStar::jump() noexcept + { + constexpr std::uint64_t JUMP[] = { 0x180ec6d33cfd0aba, 0xd5a61266f0c9392c, 0xa9582618e03fc9aa, 0x39abdc4529b1661c }; + + std::uint64_t s0 = 0; + std::uint64_t s1 = 0; + std::uint64_t s2 = 0; + std::uint64_t s3 = 0; + + for (std::uint64_t jump : JUMP) + { + for (int b = 0; b < 64; ++b) + { + if (jump & UINT64_C(1) << b) + { + s0 ^= m_state[0]; + s1 ^= m_state[1]; + s2 ^= m_state[2]; + s3 ^= m_state[3]; + } + operator()(); + } + } + + m_state[0] = s0; + m_state[1] = s1; + m_state[2] = s2; + m_state[3] = s3; + } + + inline constexpr void Xoshiro256StarStar::longJump() noexcept + { + constexpr std::uint64_t LONG_JUMP[] = { 0x76e15d3efefdcbbf, 0xc5004e441c522fb3, 0x77710069854ee241, 0x39109bb02acbe635 }; + + std::uint64_t s0 = 0; + std::uint64_t s1 = 0; + std::uint64_t s2 = 0; + std::uint64_t s3 = 0; + + for (std::uint64_t jump : LONG_JUMP) + { + for (int b = 0; b < 64; ++b) + { + if (jump & UINT64_C(1) << b) + { + s0 ^= m_state[0]; + s1 ^= m_state[1]; + s2 ^= m_state[2]; + s3 ^= m_state[3]; + } + operator()(); + } + } + + m_state[0] = s0; + m_state[1] = s1; + m_state[2] = s2; + m_state[3] = s3; + } + + inline constexpr Xoshiro256StarStar::result_type Xoshiro256StarStar::min() noexcept + { + return std::numeric_limits::lowest(); + } + + inline constexpr Xoshiro256StarStar::result_type Xoshiro256StarStar::max() noexcept + { + return std::numeric_limits::max(); + } + + inline constexpr Xoshiro256StarStar::state_type Xoshiro256StarStar::serialize() const noexcept + { + return m_state; + } + + inline constexpr void Xoshiro256StarStar::deserialize(const state_type state) noexcept + { + m_state = state; + } + + //////////////////////////////////////////////////////////////// + // + // xoroshiro128+ + // + inline constexpr Xoroshiro128Plus::Xoroshiro128Plus(const std::uint64_t seed) noexcept + : m_state(SplitMix64{ seed }.generateSeedSequence<2>()) {} + + inline constexpr Xoroshiro128Plus::Xoroshiro128Plus(const state_type state) noexcept + : m_state(state) {} + + inline constexpr Xoroshiro128Plus::result_type Xoroshiro128Plus::operator()() noexcept + { + const std::uint64_t s0 = m_state[0]; + std::uint64_t s1 = m_state[1]; + const std::uint64_t result = s0 + s1; + s1 ^= s0; + m_state[0] = detail::RotL(s0, 24) ^ s1 ^ (s1 << 16); + m_state[1] = detail::RotL(s1, 37); + return result; + } + + inline constexpr void Xoroshiro128Plus::jump() noexcept + { + constexpr std::uint64_t JUMP[] = { 0xdf900294d8f554a5, 0x170865df4b3201fc }; + + std::uint64_t s0 = 0; + std::uint64_t s1 = 0; + + for (std::uint64_t jump : JUMP) + { + for (int b = 0; b < 64; ++b) + { + if (jump & UINT64_C(1) << b) + { + s0 ^= m_state[0]; + s1 ^= m_state[1]; + } + operator()(); + } + } + + m_state[0] = s0; + m_state[1] = s1; + } + + inline constexpr void Xoroshiro128Plus::longJump() noexcept + { + constexpr std::uint64_t LONG_JUMP[] = { 0xd2a98b26625eee7b, 0xdddf9b1090aa7ac1 }; + + std::uint64_t s0 = 0; + std::uint64_t s1 = 0; + + for (std::uint64_t jump : LONG_JUMP) + { + for (int b = 0; b < 64; ++b) + { + if (jump & UINT64_C(1) << b) + { + s0 ^= m_state[0]; + s1 ^= m_state[1]; + } + operator()(); + } + } + + m_state[0] = s0; + m_state[1] = s1; + } + + inline constexpr Xoroshiro128Plus::result_type Xoroshiro128Plus::min() noexcept + { + return std::numeric_limits::lowest(); + } + + inline constexpr Xoroshiro128Plus::result_type Xoroshiro128Plus::max() noexcept + { + return std::numeric_limits::max(); + } + + inline constexpr Xoroshiro128Plus::state_type Xoroshiro128Plus::serialize() const noexcept + { + return m_state; + } + + inline constexpr void Xoroshiro128Plus::deserialize(const state_type state) noexcept + { + m_state = state; + } + + //////////////////////////////////////////////////////////////// + // + // xoroshiro128++ + // + inline constexpr Xoroshiro128PlusPlus::Xoroshiro128PlusPlus(const std::uint64_t seed) noexcept + : m_state(SplitMix64{ seed }.generateSeedSequence<2>()) {} + + inline constexpr Xoroshiro128PlusPlus::Xoroshiro128PlusPlus(const state_type state) noexcept + : m_state(state) {} + + inline constexpr Xoroshiro128PlusPlus::result_type Xoroshiro128PlusPlus::operator()() noexcept + { + const std::uint64_t s0 = m_state[0]; + std::uint64_t s1 = m_state[1]; + const std::uint64_t result = detail::RotL(s0 + s1, 17) + s0; + s1 ^= s0; + m_state[0] = detail::RotL(s0, 49) ^ s1 ^ (s1 << 21); + m_state[1] = detail::RotL(s1, 28); + return result; + } + + inline constexpr void Xoroshiro128PlusPlus::jump() noexcept + { + constexpr std::uint64_t JUMP[] = { 0x2bd7a6a6e99c2ddc, 0x0992ccaf6a6fca05 }; + + std::uint64_t s0 = 0; + std::uint64_t s1 = 0; + + for (std::uint64_t jump : JUMP) + { + for (int b = 0; b < 64; ++b) + { + if (jump & UINT64_C(1) << b) + { + s0 ^= m_state[0]; + s1 ^= m_state[1]; + } + operator()(); + } + } + + m_state[0] = s0; + m_state[1] = s1; + } + + inline constexpr void Xoroshiro128PlusPlus::longJump() noexcept + { + constexpr std::uint64_t LONG_JUMP[] = { 0x360fd5f2cf8d5d99, 0x9c6e6877736c46e3 }; + + std::uint64_t s0 = 0; + std::uint64_t s1 = 0; + + for (std::uint64_t jump : LONG_JUMP) + { + for (int b = 0; b < 64; ++b) + { + if (jump & UINT64_C(1) << b) + { + s0 ^= m_state[0]; + s1 ^= m_state[1]; + } + operator()(); + } + } + + m_state[0] = s0; + m_state[1] = s1; + } + + inline constexpr Xoroshiro128PlusPlus::result_type Xoroshiro128PlusPlus::min() noexcept + { + return std::numeric_limits::lowest(); + } + + inline constexpr Xoroshiro128PlusPlus::result_type Xoroshiro128PlusPlus::max() noexcept + { + return std::numeric_limits::max(); + } + + inline constexpr Xoroshiro128PlusPlus::state_type Xoroshiro128PlusPlus::serialize() const noexcept + { + return m_state; + } + + inline constexpr void Xoroshiro128PlusPlus::deserialize(const state_type state) noexcept + { + m_state = state; + } + + //////////////////////////////////////////////////////////////// + // + // xoroshiro128** + // + inline constexpr Xoroshiro128StarStar::Xoroshiro128StarStar(const std::uint64_t seed) noexcept + : m_state(SplitMix64{ seed }.generateSeedSequence<2>()) {} + + inline constexpr Xoroshiro128StarStar::Xoroshiro128StarStar(const state_type state) noexcept + : m_state(state) {} + + inline constexpr Xoroshiro128StarStar::result_type Xoroshiro128StarStar::operator()() noexcept + { + const std::uint64_t s0 = m_state[0]; + std::uint64_t s1 = m_state[1]; + const std::uint64_t result = detail::RotL(s0 * 5, 7) * 9; + s1 ^= s0; + m_state[0] = detail::RotL(s0, 24) ^ s1 ^ (s1 << 16); + m_state[1] = detail::RotL(s1, 37); + return result; + } + + inline constexpr void Xoroshiro128StarStar::jump() noexcept + { + constexpr std::uint64_t JUMP[] = { 0xdf900294d8f554a5, 0x170865df4b3201fc }; + + std::uint64_t s0 = 0; + std::uint64_t s1 = 0; + + for (std::uint64_t jump : JUMP) + { + for (int b = 0; b < 64; ++b) + { + if (jump & UINT64_C(1) << b) + { + s0 ^= m_state[0]; + s1 ^= m_state[1]; + } + operator()(); + } + } + + m_state[0] = s0; + m_state[1] = s1; + } + + inline constexpr void Xoroshiro128StarStar::longJump() noexcept + { + constexpr std::uint64_t LONG_JUMP[] = { 0xd2a98b26625eee7b, 0xdddf9b1090aa7ac1 }; + + std::uint64_t s0 = 0; + std::uint64_t s1 = 0; + + for (std::uint64_t jump : LONG_JUMP) + { + for (int b = 0; b < 64; ++b) + { + if (jump & UINT64_C(1) << b) + { + s0 ^= m_state[0]; + s1 ^= m_state[1]; + } + operator()(); + } + } + + m_state[0] = s0; + m_state[1] = s1; + } + + inline constexpr Xoroshiro128StarStar::result_type Xoroshiro128StarStar::min() noexcept + { + return std::numeric_limits::lowest(); + } + + inline constexpr Xoroshiro128StarStar::result_type Xoroshiro128StarStar::max() noexcept + { + return std::numeric_limits::max(); + } + + inline constexpr Xoroshiro128StarStar::state_type Xoroshiro128StarStar::serialize() const noexcept + { + return m_state; + } + + inline constexpr void Xoroshiro128StarStar::deserialize(const state_type state) noexcept + { + m_state = state; + } + + //////////////////////////////////////////////////////////////// + // + // xoshiro128+ + // + inline constexpr Xoshiro128Plus::Xoshiro128Plus(const std::uint64_t seed) noexcept + : m_state() + { + SplitMix64 splitmix{ seed }; + + for (auto& state : m_state) + { + state = static_cast(splitmix()); + } + } + + inline constexpr Xoshiro128Plus::Xoshiro128Plus(const state_type state) noexcept + : m_state(state) {} + + inline constexpr Xoshiro128Plus::result_type Xoshiro128Plus::operator()() noexcept + { + const std::uint32_t result = m_state[0] + m_state[3]; + const std::uint32_t t = m_state[1] << 9; + m_state[2] ^= m_state[0]; + m_state[3] ^= m_state[1]; + m_state[1] ^= m_state[2]; + m_state[0] ^= m_state[3]; + m_state[2] ^= t; + m_state[3] = detail::RotL(m_state[3], 11); + return result; + } + + inline constexpr void Xoshiro128Plus::jump() noexcept + { + constexpr std::uint32_t JUMP[] = { 0x8764000b, 0xf542d2d3, 0x6fa035c3, 0x77f2db5b }; + + std::uint32_t s0 = 0; + std::uint32_t s1 = 0; + std::uint32_t s2 = 0; + std::uint32_t s3 = 0; + + for (std::uint32_t jump : JUMP) + { + for (int b = 0; b < 32; ++b) + { + if (jump & UINT32_C(1) << b) + { + s0 ^= m_state[0]; + s1 ^= m_state[1]; + s2 ^= m_state[2]; + s3 ^= m_state[3]; + } + operator()(); + } + } + + m_state[0] = s0; + m_state[1] = s1; + m_state[2] = s2; + m_state[3] = s3; + } + + inline constexpr void Xoshiro128Plus::longJump() noexcept + { + constexpr std::uint32_t LONG_JUMP[] = { 0xb523952e, 0x0b6f099f, 0xccf5a0ef, 0x1c580662 }; + + std::uint32_t s0 = 0; + std::uint32_t s1 = 0; + std::uint32_t s2 = 0; + std::uint32_t s3 = 0; + + for (std::uint32_t jump : LONG_JUMP) + { + for (int b = 0; b < 32; ++b) + { + if (jump & UINT32_C(1) << b) + { + s0 ^= m_state[0]; + s1 ^= m_state[1]; + s2 ^= m_state[2]; + s3 ^= m_state[3]; + } + operator()(); + } + } + + m_state[0] = s0; + m_state[1] = s1; + m_state[2] = s2; + m_state[3] = s3; + } + + inline constexpr Xoshiro128Plus::result_type Xoshiro128Plus::min() noexcept + { + return std::numeric_limits::lowest(); + } + + inline constexpr Xoshiro128Plus::result_type Xoshiro128Plus::max() noexcept + { + return std::numeric_limits::max(); + } + + inline constexpr Xoshiro128Plus::state_type Xoshiro128Plus::serialize() const noexcept + { + return m_state; + } + + inline constexpr void Xoshiro128Plus::deserialize(const state_type state) noexcept + { + m_state = state; + } + + //////////////////////////////////////////////////////////////// + // + // xoshiro128++ + // + inline constexpr Xoshiro128PlusPlus::Xoshiro128PlusPlus(const std::uint64_t seed) noexcept + : m_state() + { + SplitMix64 splitmix{ seed }; + + for (auto& state : m_state) + { + state = static_cast(splitmix()); + } + } + + inline constexpr Xoshiro128PlusPlus::Xoshiro128PlusPlus(const state_type state) noexcept + : m_state(state) {} + + inline constexpr Xoshiro128PlusPlus::result_type Xoshiro128PlusPlus::operator()() noexcept + { + const std::uint32_t result = detail::RotL(m_state[0] + m_state[3], 7) + m_state[0]; + const std::uint32_t t = m_state[1] << 9; + m_state[2] ^= m_state[0]; + m_state[3] ^= m_state[1]; + m_state[1] ^= m_state[2]; + m_state[0] ^= m_state[3]; + m_state[2] ^= t; + m_state[3] = detail::RotL(m_state[3], 11); + return result; + } + + inline constexpr void Xoshiro128PlusPlus::jump() noexcept + { + constexpr std::uint32_t JUMP[] = { 0x8764000b, 0xf542d2d3, 0x6fa035c3, 0x77f2db5b }; + + std::uint32_t s0 = 0; + std::uint32_t s1 = 0; + std::uint32_t s2 = 0; + std::uint32_t s3 = 0; + + for (std::uint32_t jump : JUMP) + { + for (int b = 0; b < 32; ++b) + { + if (jump & UINT32_C(1) << b) + { + s0 ^= m_state[0]; + s1 ^= m_state[1]; + s2 ^= m_state[2]; + s3 ^= m_state[3]; + } + operator()(); + } + } + + m_state[0] = s0; + m_state[1] = s1; + m_state[2] = s2; + m_state[3] = s3; + } + + inline constexpr void Xoshiro128PlusPlus::longJump() noexcept + { + constexpr std::uint32_t LONG_JUMP[] = { 0xb523952e, 0x0b6f099f, 0xccf5a0ef, 0x1c580662 }; + + std::uint32_t s0 = 0; + std::uint32_t s1 = 0; + std::uint32_t s2 = 0; + std::uint32_t s3 = 0; + + for (std::uint32_t jump : LONG_JUMP) + { + for (int b = 0; b < 32; ++b) + { + if (jump & UINT32_C(1) << b) + { + s0 ^= m_state[0]; + s1 ^= m_state[1]; + s2 ^= m_state[2]; + s3 ^= m_state[3]; + } + operator()(); + } + } + + m_state[0] = s0; + m_state[1] = s1; + m_state[2] = s2; + m_state[3] = s3; + } + + inline constexpr Xoshiro128PlusPlus::result_type Xoshiro128PlusPlus::min() noexcept + { + return std::numeric_limits::lowest(); + } + + inline constexpr Xoshiro128PlusPlus::result_type Xoshiro128PlusPlus::max() noexcept + { + return std::numeric_limits::max(); + } + + inline constexpr Xoshiro128PlusPlus::state_type Xoshiro128PlusPlus::serialize() const noexcept + { + return m_state; + } + + inline constexpr void Xoshiro128PlusPlus::deserialize(const state_type state) noexcept + { + m_state = state; + } + + //////////////////////////////////////////////////////////////// + // + // xoshiro128** + // + inline constexpr Xoshiro128StarStar::Xoshiro128StarStar(const std::uint64_t seed) noexcept + : m_state() + { + SplitMix64 splitmix{ seed }; + + for (auto& state : m_state) + { + state = static_cast(splitmix()); + } + } + + inline constexpr Xoshiro128StarStar::Xoshiro128StarStar(const state_type state) noexcept + : m_state(state) {} + + inline constexpr Xoshiro128StarStar::result_type Xoshiro128StarStar::operator()() noexcept + { + const std::uint32_t result = detail::RotL(m_state[1] * 5, 7) * 9; + const std::uint32_t t = m_state[1] << 9; + m_state[2] ^= m_state[0]; + m_state[3] ^= m_state[1]; + m_state[1] ^= m_state[2]; + m_state[0] ^= m_state[3]; + m_state[2] ^= t; + m_state[3] = detail::RotL(m_state[3], 11); + return result; + } + + inline constexpr void Xoshiro128StarStar::jump() noexcept + { + constexpr std::uint32_t JUMP[] = { 0x8764000b, 0xf542d2d3, 0x6fa035c3, 0x77f2db5b }; + + std::uint32_t s0 = 0; + std::uint32_t s1 = 0; + std::uint32_t s2 = 0; + std::uint32_t s3 = 0; + + for (std::uint32_t jump : JUMP) + { + for (int b = 0; b < 32; ++b) + { + if (jump & UINT32_C(1) << b) + { + s0 ^= m_state[0]; + s1 ^= m_state[1]; + s2 ^= m_state[2]; + s3 ^= m_state[3]; + } + operator()(); + } + } + + m_state[0] = s0; + m_state[1] = s1; + m_state[2] = s2; + m_state[3] = s3; + } + + inline constexpr void Xoshiro128StarStar::longJump() noexcept + { + constexpr std::uint32_t LONG_JUMP[] = { 0xb523952e, 0x0b6f099f, 0xccf5a0ef, 0x1c580662 }; + + std::uint32_t s0 = 0; + std::uint32_t s1 = 0; + std::uint32_t s2 = 0; + std::uint32_t s3 = 0; + + for (std::uint32_t jump : LONG_JUMP) + { + for (int b = 0; b < 32; ++b) + { + if (jump & UINT32_C(1) << b) + { + s0 ^= m_state[0]; + s1 ^= m_state[1]; + s2 ^= m_state[2]; + s3 ^= m_state[3]; + } + operator()(); + } + } + + m_state[0] = s0; + m_state[1] = s1; + m_state[2] = s2; + m_state[3] = s3; + } + + inline constexpr Xoshiro128StarStar::result_type Xoshiro128StarStar::min() noexcept + { + return std::numeric_limits::lowest(); + } + + inline constexpr Xoshiro128StarStar::result_type Xoshiro128StarStar::max() noexcept + { + return std::numeric_limits::max(); + } + + inline constexpr Xoshiro128StarStar::state_type Xoshiro128StarStar::serialize() const noexcept + { + return m_state; + } + + inline constexpr void Xoshiro128StarStar::deserialize(const state_type state) noexcept + { + m_state = state; + } +} diff --git a/src/main.cpp b/src/main.cpp index b84826b..aa7256d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,60 +1,40 @@ +#include "Hooks.h" #include "Manager.h" -#include "MergeMapperPluginAPI.h" -namespace FormSwap +void MessageHandler(SKSE::MessagingInterface::Message* a_message) { - struct InitItemImpl - { - static void thunk(RE::TESObjectREFR* a_ref) + switch (a_message->type) { + case SKSE::MessagingInterface::kPostLoad: + BaseObjectSwapper::Install(); + break; + case SKSE::MessagingInterface::kPostPostLoad: { - if (const auto base = a_ref->GetBaseObject(); base) { - Manager::GetSingleton()->LoadFormsOnce(); - - auto [swapBase, transformData] = Manager::GetSingleton()->GetSwapData(a_ref, base); - - if (swapBase && base != swapBase) { - a_ref->SetObjectReference(swapBase); - transformData.SetTransform(a_ref); - } + logger::info("{:*^30}", "MERGES"); + MergeMapperPluginAPI::GetMergeMapperInterface001(); + if (g_mergeMapperInterface) { + const auto version = g_mergeMapperInterface->GetBuildNumber(); + logger::info("Got MergeMapper interface buildnumber {}", version); + } else { + logger::info("MergeMapper not detected"); } - - func(a_ref); } - static inline REL::Relocation func; - - static inline constexpr std::size_t size = 0x13; - }; - - inline void Install() - { - stl::write_vfunc(); - - logger::info("Installed form swap"sv); + break; + case SKSE::MessagingInterface::kDataLoaded: + FormSwap::Manager::GetSingleton()->PrintConflicts(); + break; + default: + break; } } -void MessageHandler(SKSE::MessagingInterface::Message* a_message) -{ - if (a_message->type == SKSE::MessagingInterface::kPostPostLoad) { - MergeMapperPluginAPI::GetMergeMapperInterface001(); - if (g_mergeMapperInterface) { - const auto version = g_mergeMapperInterface->GetBuildNumber(); - logger::info("Got MergeMapper interface buildnumber {}", version); - }else - logger::info("MergeMapper not detected"); - } - // if (a_message->type == SKSE::MessagingInterface::kDataLoaded) { - // FormSwap::Manager::GetSingleton()->PrintConflicts(); - // } -} - #ifdef SKYRIM_AE extern "C" DLLEXPORT constinit auto SKSEPlugin_Version = []() { SKSE::PluginVersionData v; v.PluginVersion(Version::MAJOR); v.PluginName("Base Object Swapper"); v.AuthorName("powerofthree"); - v.UsesAddressLibrary(true); + v.UsesAddressLibrary(); + v.UsesNoStructs(); v.CompatibleVersions({ SKSE::RUNTIME_LATEST }); return v; @@ -103,7 +83,7 @@ void InitializeLog() log->flush_on(spdlog::level::info); spdlog::set_default_logger(std::move(log)); - spdlog::set_pattern("[%l] %v"s); + spdlog::set_pattern("[%H:%M:%S:%e] %v"s); logger::info(FMT_STRING("{} v{}"), Version::PROJECT, Version::NAME); } @@ -112,15 +92,12 @@ extern "C" DLLEXPORT bool SKSEAPI SKSEPlugin_Load(const SKSE::LoadInterface* a_s { InitializeLog(); - logger::info("loaded"); + logger::info("Game version : {}", a_skse->RuntimeVersion().string()); SKSE::Init(a_skse); - logger::info("{:*^30}", "HOOKS"); - - FormSwap::Install(); const auto messaging = SKSE::GetMessagingInterface(); messaging->RegisterListener(MessageHandler); return true; -} +} \ No newline at end of file diff --git a/vcpkg b/vcpkg deleted file mode 100644 index e69de29..0000000 diff --git a/vcpkg.json b/vcpkg.json index a1af26d..2bc652b 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,6 +1,6 @@ { "name": "base-object-swapper", - "version-string": "1.6.0", + "version-string": "2.0.0", "description": "Base Object Swapper", "homepage": "", "license": "MIT",