diff --git a/CMakeLists.txt b/CMakeLists.txt index cb1330a..19012f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,9 @@ set(Boost_USE_STATIC_LIBS ON) find_package(spdlog REQUIRED CONFIG) find_package(nlohmann_json CONFIG REQUIRED) find_package(magic_enum CONFIG REQUIRED) +find_package(unordered_dense CONFIG REQUIRED) + +find_path(CLIB_UTIL_INCLUDE_DIRS "ClibUtil/utils.hpp") set(CommonLibPath "extern/CommonLibSSE-NG") set(CommonLibName "CommonLibSSE") @@ -146,6 +149,7 @@ target_include_directories( PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CLIB_UTIL_INCLUDE_DIRS} ) target_link_libraries( @@ -155,6 +159,7 @@ target_link_libraries( CommonLibSSE::CommonLibSSE nlohmann_json::nlohmann_json magic_enum::magic_enum + unordered_dense::unordered_dense ) if (MSVC) diff --git a/CMakePresets.json b/CMakePresets.json index c740a99..71a8bf4 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -1,68 +1,75 @@ { - "configurePresets": [{ - "binaryDir": "${sourceDir}/build", - "cacheVariables": { - "CMAKE_BUILD_TYPE": { - "type": "STRING", - "value": "Release" - } - }, - "errors": { - "deprecated": true - }, - "hidden": true, - "name": "cmake-dev", - "warnings": { - "deprecated": true, - "dev": true - } - }, - { - "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": { - "type": "STRING", - "value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" - } - }, - "hidden": true, - "name": "vcpkg" + "configurePresets": [ + { + "binaryDir": "${sourceDir}/build", + "cacheVariables": { + "CMAKE_BUILD_TYPE": { + "type": "STRING", + "value": "Release" + } + }, + "errors": { + "deprecated": true + }, + "hidden": true, + "name": "cmake-dev", + "warnings": { + "deprecated": true, + "dev": true + } + }, + { + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": { + "type": "STRING", + "value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" }, - { - "cacheVariables": { - "CMAKE_MSVC_RUNTIME_LIBRARY": { - "type": "STRING", - "value": "MultiThreaded$<$:Debug>DLL" - }, - "VCPKG_TARGET_TRIPLET": { - "type": "STRING", - "value": "x64-windows-static-md" - } - }, - "hidden": true, - "name": "windows" + "VCPKG_OVERLAY_PORTS": { + "type": "STRING", + "value": "${sourceDir}/cmake/ports/" + } + }, + "hidden": true, + "name": "vcpkg" + }, + { + "cacheVariables": { + "CMAKE_MSVC_RUNTIME_LIBRARY": { + "type": "STRING", + "value": "MultiThreaded$<$:Debug>DLL" }, - { - "architecture": { - "strategy": "set", - "value": "x64" - }, - "cacheVariables": { - "CMAKE_CXX_FLAGS": "/EHsc /MP /W4 /WX" - }, - "generator": "Visual Studio 17 2022", - "inherits": [ - "cmake-dev", - "vcpkg", - "windows" - ], - "name": "vs2022-windows", - "toolset": "v143" + "VCPKG_TARGET_TRIPLET": { + "type": "STRING", + "value": "x64-windows-static-md" } - ], - "buildPresets": [{ - "name": "vs2022-windows", - "configurePreset": "vs2022-windows", - "configuration": "Release" - }], - "version": 3 + }, + "hidden": true, + "name": "windows" + }, + { + "architecture": { + "strategy": "set", + "value": "x64" + }, + "cacheVariables": { + "CMAKE_CXX_FLAGS": "/EHsc /MP /W4 /WX" + }, + "generator": "Visual Studio 17 2022", + "inherits": [ + "cmake-dev", + "vcpkg", + "windows" + ], + "name": "vs2022-windows", + "toolset": "v143" + } + ], + "buildPresets": [ + { + "name": "vs2022-windows", + "configurePreset": "vs2022-windows", + "configuration": "Release" + } + ], + "version": 3 } diff --git a/cmake/ports/clib-util/portfile.cmake b/cmake/ports/clib-util/portfile.cmake new file mode 100644 index 0000000..93085dc --- /dev/null +++ b/cmake/ports/clib-util/portfile.cmake @@ -0,0 +1,14 @@ +# header-only library +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO powerof3/CLibUtil + REF a801992250ee8c6178159cc73ce3354a742be6b4 + SHA512 3eb37eb0958bf5cf01c8cd80f673b5ab60c562757d76024937fde529b82938481ad8d6fa30d6ca21d5ef93ab8a2d92e7ad093ddc242fb2462aa234689efd31eb + HEAD_REF master +) + +# Install codes +set(CLIBUTIL_SOURCE ${SOURCE_PATH}/include/ClibUtil) +file(INSTALL ${CLIBUTIL_SOURCE} DESTINATION ${CURRENT_PACKAGES_DIR}/include) + +vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE") \ No newline at end of file diff --git a/cmake/ports/clib-util/vcpkg.json b/cmake/ports/clib-util/vcpkg.json new file mode 100644 index 0000000..2e645f1 --- /dev/null +++ b/cmake/ports/clib-util/vcpkg.json @@ -0,0 +1,7 @@ +{ + "name": "clib-util", + "version-string": "1.3.5", + "port-version": 1, + "description": "", + "homepage": "https://github.com/powerof3/CLibUtil" +} \ No newline at end of file diff --git a/cmake/ports/unordered-dense/portfile.cmake b/cmake/ports/unordered-dense/portfile.cmake new file mode 100644 index 0000000..1c21e4b --- /dev/null +++ b/cmake/ports/unordered-dense/portfile.cmake @@ -0,0 +1,24 @@ +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO martinus/unordered_dense + REF a570a0e87f95027e8544c72295236df5d84c3dcc + SHA512 3f06daf92b68ef7e1ec0c7bbd7070aac8d0bf7ce57bfc6408770ab001f4c3c5efe084b01405a65d6b9e3d99eb0f16c95dd6c5dfc88387d246552f850a9ec6cad + HEAD_REF main +) + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" +) + +vcpkg_cmake_install() +vcpkg_cmake_config_fixup( + PACKAGE_NAME unordered_dense + CONFIG_PATH lib/cmake/unordered_dense +) + +file(REMOVE_RECURSE + "${CURRENT_PACKAGES_DIR}/debug" + "${CURRENT_PACKAGES_DIR}/lib" +) + +file(INSTALL "${SOURCE_PATH}/LICENSE" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright) \ No newline at end of file diff --git a/cmake/ports/unordered-dense/vcpkg.json b/cmake/ports/unordered-dense/vcpkg.json new file mode 100644 index 0000000..2afb0e1 --- /dev/null +++ b/cmake/ports/unordered-dense/vcpkg.json @@ -0,0 +1,17 @@ +{ + "name": "unordered-dense", + "version-string": "4.1.0", + "port-version": 1, + "description": "", + "homepage": "https://github.com/martinus/unordered_dense", + "dependencies": [ + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ] +} \ No newline at end of file diff --git a/cmake/sourcelist.cmake b/cmake/sourcelist.cmake index 0005bb4..7d25431 100644 --- a/cmake/sourcelist.cmake +++ b/cmake/sourcelist.cmake @@ -79,6 +79,8 @@ set(sources ${sources} src/util/player/player.h src/util/quest/quest.cpp src/util/quest/quest.h + src/util/translation.cpp + src/util/translation.h src/util/type_util.cpp src/util/type_util.h ) diff --git a/src/PCH.h b/src/PCH.h index cca5bf6..1fae26c 100644 --- a/src/PCH.h +++ b/src/PCH.h @@ -2,6 +2,7 @@ #define WIN32_LEAN_AND_MEAN #define NOMINMAX +#define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING #include #include @@ -10,9 +11,26 @@ #include "magic_enum.hpp" #include "nlohmann/json.hpp" +#include +#include +#include using namespace std::literals; +struct string_hash +{ + using is_transparent = void; // enable heterogeneous overloads + using is_avalanching = void; // mark class as high quality avalanching hash + + [[nodiscard]] std::uint64_t operator()(std::string_view str) const noexcept + { + return ankerl::unordered_dense::hash{}(str); + } +}; + +template +using string_map = ankerl::unordered_dense::segmented_map>; + namespace stl { using namespace SKSE::stl; } diff --git a/src/actor/player.cpp b/src/actor/player.cpp index 473d9a5..acea340 100644 --- a/src/actor/player.cpp +++ b/src/actor/player.cpp @@ -16,7 +16,7 @@ namespace actor { } logger::trace("name {}, value {}, icon {}, column {}"sv, name, value, icon, column_name); } - + std::vector player::get_player_data(RE::PlayerCharacter*& a_player, setting_data::menu_data::menu_type a_menu) { if (!a_player) { diff --git a/src/main.cpp b/src/main.cpp index 9032ba6..eb200b7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,7 @@ #include "scaleform/scaleform.h" #include "setting/input_setting.h" #include "setting/setting.h" +#include "util/translation.h" void init_logger() { if (static bool initialized = false; !initialized) { @@ -74,6 +75,8 @@ EXTERN_C [[maybe_unused]] __declspec(dllexport) bool SKSEAPI SKSEPlugin_Load(con init_config_setting(); mod::mod::init_mod_support(); + util::translation::get_singleton()->build_translation_map(); + scaleform::Register(); input::menu_key_input_holder::get_singleton()->set_all(); @@ -95,7 +98,8 @@ EXTERN_C [[maybe_unused]] __declspec(dllexport) constinit auto SKSEPlugin_Versio v.PluginVersion({ Version::MAJOR, Version::MINOR, Version::PATCH, Version::BETA }); v.UsesAddressLibrary(true); v.CompatibleVersions({ SKSE::RUNTIME_SSE_LATEST }); - v.HasNoStructUse(true); + //v.HasNoStructUse(true); + v.UsesNoStructs(); return v; }(); diff --git a/src/scaleform/menus/faction_menu.cpp b/src/scaleform/menus/faction_menu.cpp index 9733f4e..26987af 100644 --- a/src/scaleform/menus/faction_menu.cpp +++ b/src/scaleform/menus/faction_menu.cpp @@ -9,6 +9,7 @@ #include "setting/input_setting.h" #include "setting/key_setting.h" #include "util/key_util.h" +#include "util/translation.h" namespace scaleform { void faction_menu::Register() { @@ -197,7 +198,7 @@ namespace scaleform { void faction_menu::update_text(CLIK::TextField a_field, const std::string_view a_string) { a_field.AutoSize(CLIK::Object{ "left" }); - a_field.HTMLText(a_string); + a_field.HTMLText(TRANSLATE(a_string)); a_field.Visible(true); } @@ -205,7 +206,7 @@ namespace scaleform { const std::string_view a_string, const std::string& a_auto_size) { a_field.AutoSize(CLIK::Object{ a_auto_size }); - a_field.HTMLText(a_string); + a_field.HTMLText(TRANSLATE(a_string)); a_field.Visible(true); } @@ -220,8 +221,9 @@ namespace scaleform { RE::GFxValue faction_menu::build_gfx_value(const std::string_view& a_key, const std::string& a_val) const { RE::GFxValue value; view_->CreateObject(std::addressof(value)); - value.SetMember("displayName", { a_key }); - value.SetMember("displayValue", { static_cast(a_val) }); + value.SetMember("displayName", { TRANSLATE(a_key) }); + //value.SetMember("displayValue", { static_cast(a_val) }); + value.SetMember("displayValue", { TRANSLATE(a_val) }); return value; } diff --git a/src/scaleform/menus/stats_inventory_menu.cpp b/src/scaleform/menus/stats_inventory_menu.cpp index 4e2b448..72fe9b3 100644 --- a/src/scaleform/menus/stats_inventory_menu.cpp +++ b/src/scaleform/menus/stats_inventory_menu.cpp @@ -3,6 +3,7 @@ #include "setting/config_setting.h" #include "setting/data/menu_data.h" #include "util/player/player.h" +#include "util/translation.h" namespace scaleform { @@ -171,9 +172,9 @@ namespace scaleform { logger::debug("Shown all Values for Menu {}"sv, menu_name); } - void stats_inventory_menu::update_text(CLIK::TextField a_field, const std::string_view a_string) { + void stats_inventory_menu::update_text(CLIK::TextField a_field, std::string_view a_string) { a_field.AutoSize(CLIK::Object{ "left" }); - a_field.HTMLText(a_string); + a_field.HTMLText(TRANSLATE(a_string)); a_field.Visible(true); } @@ -189,7 +190,7 @@ namespace scaleform { const std::string_view& a_icon) const { RE::GFxValue value; view_->CreateObject(std::addressof(value)); - value.SetMember("displayName", { a_key }); + value.SetMember("displayName", { TRANSLATE(a_key) }); value.SetMember("displayValue", { static_cast(a_val) }); value.SetMember("iconKey", { a_icon }); value.SetMember("iconScale", { 18 }); @@ -199,7 +200,7 @@ namespace scaleform { RE::GFxValue stats_inventory_menu::build_gfx_value(const std::string_view& a_key, const std::string& a_val) const { RE::GFxValue value; view_->CreateObject(std::addressof(value)); - value.SetMember("displayName", { a_key }); + value.SetMember("displayName", { TRANSLATE(a_key) }); value.SetMember("displayValue", { static_cast(a_val) }); value.SetMember("iconScale", { 18 }); return value; diff --git a/src/scaleform/menus/stats_menu.cpp b/src/scaleform/menus/stats_menu.cpp index ab0a367..cb99861 100644 --- a/src/scaleform/menus/stats_menu.cpp +++ b/src/scaleform/menus/stats_menu.cpp @@ -5,6 +5,7 @@ #include "scaleform/menus/faction_menu.h" #include "setting/input_setting.h" #include "util/key_util.h" +#include "util/translation.h" namespace scaleform { void stats_menu::Register() { @@ -216,14 +217,14 @@ namespace scaleform { void stats_menu::update_text(CLIK::TextField a_field, const std::string_view a_string) { a_field.AutoSize(CLIK::Object{ "left" }); - a_field.HTMLText(a_string); + a_field.HTMLText(TRANSLATE(a_string)); a_field.Visible(true); } void stats_menu::update_text(CLIK::TextField a_field, const std::string_view a_string, const std::string& a_auto_size) { a_field.AutoSize(CLIK::Object{ a_auto_size }); - a_field.HTMLText(a_string); + a_field.HTMLText(TRANSLATE(a_string)); a_field.Visible(true); } void stats_menu::update_title() const { update_text(title_, menu_name_); } @@ -242,7 +243,7 @@ namespace scaleform { const std::string_view& a_icon) const { RE::GFxValue value; view_->CreateObject(std::addressof(value)); - value.SetMember("displayName", { a_key }); + value.SetMember("displayName", { TRANSLATE(a_key) }); value.SetMember("displayValue", { static_cast(a_val) }); value.SetMember("iconKey", { a_icon }); value.SetMember("iconScale", { 22 }); diff --git a/src/scaleform/menus/tween_hint_menu.cpp b/src/scaleform/menus/tween_hint_menu.cpp index 1709d88..f88d77d 100644 --- a/src/scaleform/menus/tween_hint_menu.cpp +++ b/src/scaleform/menus/tween_hint_menu.cpp @@ -2,6 +2,7 @@ #include "input/menu_key_input_holder.h" #include "mod/mod_manager.h" #include "scaleform/menus/stats_menu.h" +#include "util/translation.h" namespace scaleform { @@ -129,8 +130,8 @@ namespace scaleform { logger::debug("{}: {}"sv, menu_name, a_params[0].GetString()); } - void tween_hint_menu::update_text(CLIK::TextField a_field, const std::string_view a_string) { - a_field.HTMLText(a_string); + void tween_hint_menu::update_text(CLIK::TextField a_field, std::string& a_string) { + a_field.HTMLText(TRANSLATE(a_string)); a_field.Visible(true); } @@ -146,7 +147,8 @@ namespace scaleform { uint32_t key = 0; if (mod::mod_manager::get_singleton()->get_wait_menu_redirected()) { auto* interface_strings = RE::InterfaceStrings::GetSingleton(); - update_text(right_hint_text_, interface_strings->sleepWaitMenu); + auto name = static_cast(interface_strings->sleepWaitMenu); + update_text(right_hint_text_, name); //input switch is noticed this way RE::UserEvents* user_events = RE::UserEvents::GetSingleton(); diff --git a/src/scaleform/menus/tween_hint_menu.h b/src/scaleform/menus/tween_hint_menu.h index 9fe243e..a7e7ba8 100644 --- a/src/scaleform/menus/tween_hint_menu.h +++ b/src/scaleform/menus/tween_hint_menu.h @@ -53,7 +53,7 @@ namespace scaleform { static void log(const RE::FxDelegateArgs& a_params); - static void update_text(CLIK::TextField a_field, std::string_view a_string); + static void update_text(CLIK::TextField a_field, std::string& a_string); [[nodiscard]] RE::GFxValue build_key_gfx_value(uint32_t a_right_key, uint32_t a_left_key) const; diff --git a/src/util/translation.cpp b/src/util/translation.cpp new file mode 100644 index 0000000..4b08956 --- /dev/null +++ b/src/util/translation.cpp @@ -0,0 +1,63 @@ +#include "translation.h" + +namespace util { + translation* translation::get_singleton() { + static translation singleton; + return std::addressof(singleton); + } + + std::string translation::get_game_language() { + const auto ini_setting = RE::INISettingCollection::GetSingleton(); + const auto setting = ini_setting ? ini_setting->GetSetting("sLanguage:General") : nullptr; + + return (setting && setting->GetType() == RE::Setting::Type::kString) ? clib_util::string::toupper(setting->data.s) : "ENGLISH"s; + } + + void translation::build_translation_map() { + std::filesystem::path path{ fmt::format(R"(Data\Interface\Translations\SkyrimCharacterSheet_{}.txt)", get_game_language()) }; + + if (!load_translation(path)) { + load_translation(R"(Data\Interface\Translations\SkyrimCharacterSheet_ENGLISH.txt)"sv); + } + } + + bool translation::load_translation(const std::filesystem::path& a_path) { + if (!std::filesystem::exists(a_path)) { + return false; + } + + std::wfstream filestream(a_path, std::wfstream::in | std::wfstream::binary); + if (!filestream.good()) { + return false; + } else { + logger::info("Reading translations from {}...", a_path.string()); + } + + filestream.imbue(std::locale(filestream.getloc(), new std::codecvt_utf16)); + + // check if the BOM is UTF-16 + constexpr wchar_t BOM_UTF16LE = 0xFEFF; + if (filestream.get() != BOM_UTF16LE) { + logger::info("BOM Error, file must be encoded in UCS-2 LE."); + return false; + } + + std::wstring line, key, value; + + while (std::getline(filestream, line)) { + std::wstringstream ss(line); + ss >> key; + // remove leading whitespace + std::getline(ss >> std::ws, value); + // remove space/new line at end + if (std::isspace(value.back())) { + value.pop_back(); + } + translation_map.emplace(*stl::utf16_to_utf8(key), *stl::utf16_to_utf8(value)); + } + + logger::info("Read translations from {}...", a_path.string()); + return true; + } + +} // util diff --git a/src/util/translation.h b/src/util/translation.h new file mode 100644 index 0000000..7c17add --- /dev/null +++ b/src/util/translation.h @@ -0,0 +1,42 @@ +#pragma once + +namespace util { + class translation { + public: + static translation* get_singleton(); + + static std::string get_game_language(); + + void build_translation_map(); + + bool load_translation(const std::filesystem::path& a_path); + + template + const std::string& get_translation(const T& a_key) { + if (const auto it = translation_map.find(a_key); it != translation_map.end()) { + return it->second; + } + + static std::string str = "TRANSLATION FAILED"; + //static auto str = static_cast(a_key); + return str; + //return static_cast(a_key); + } + + translation(const translation&) = delete; + translation(translation&&) = delete; + + translation& operator=(const translation&) = delete; + translation& operator=(translation&&) = delete; + + protected: + translation() = default; + ~translation() = default; + + private: + string_map translation_map{}; + }; + +} // util + +#define TRANSLATE(STR) util::translation::get_singleton()->get_translation(STR).c_str() diff --git a/vcpkg.json b/vcpkg.json index fd5b3b3..a649b05 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -8,6 +8,8 @@ "tomlplusplus", "nlohmann-json", "magic-enum", + "unordered-dense", + "clib-util", "fmt", "spdlog", "rapidcsv",