diff --git a/.gitignore b/.gitignore index 8ee30f8..c782acd 100644 --- a/.gitignore +++ b/.gitignore @@ -391,4 +391,5 @@ FodyWeavers.xsd out/ build/ swf/gfx/ +venv/ diff --git a/.gitmodules b/.gitmodules index 8afe9f4..0c1ce2f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,3 +5,7 @@ path = extern/hint_button_menu url = https://github.com/mlthelama/hint_button_menu.git branch = main +[submodule "extern/CommonLibSSE-NG"] + path = extern/CommonLibSSE-NG + url = https://github.com/mlthelama/CommonLibSSE.git + branch = ng diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f0c031..19012f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.25) set(NAME "SkyrimCharacterSheet") -set(VERSION 1.1.1.0) +set(VERSION 1.1.2.0) # ---- Options ---- @@ -85,10 +85,17 @@ set(Boost_USE_STATIC_LIBS ON) # ---- Dependencies ---- -find_package(CommonLibSSE CONFIG REQUIRED) +#find_package(CommonLibSSE CONFIG REQUIRED) 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") +set(GameVersion "Skyrim") # ---- Add source files ---- @@ -131,14 +138,18 @@ add_library( target_compile_features( ${PROJECT_NAME} PRIVATE - cxx_std_20 + cxx_std_23 ) +add_subdirectory(${CommonLibPath} ${CommonLibName} EXCLUDE_FROM_ALL) + + target_include_directories( ${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CLIB_UTIL_INCLUDE_DIRS} ) target_link_libraries( @@ -148,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/README.md b/README.md index e6fff76..bcf6b2f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Skyrim Character Sheet ![Platform](https://img.shields.io/static/v1?label=platform&message=windows&color=dimgray&style=for-the-badge&logo=windows) -![GitHub release (latest by date)](https://img.shields.io/github/v/release/mlthelama/SkyrimCharacterSheet?style=for-the-badge) +![GitHub release (latest by date)](https://img.shields.io/github/v/release/mlthelama/SkyrimCharacterSheet?style=for-the-badge&include_prereleases) ![GitHub](https://img.shields.io/github/license/mlthelama/SkyrimCharacterSheet?style=for-the-badge) ![GitHub top language](https://img.shields.io/github/languages/top/mlthelama/SkyrimCharacterSheet?style=for-the-badge) ![GitHub language count](https://img.shields.io/github/languages/count/mlthelama/SkyrimCharacterSheet?style=for-the-badge) @@ -13,11 +13,11 @@ ## End User Dependencies * [SKSE64](https://skse.silverlock.org/) * [Address Library for SKSE Plugins](https://www.nexusmods.com/skyrimspecialedition/mods/32444) -* [Scaleform Translation Plus Plus](https://www.nexusmods.com/skyrimspecialedition/mods/22603) -* [Icons for Skyrim Character Sheet](https://www.nexusmods.com/skyrimspecialedition/mods/71282) ## Build Dependencies -* [CommonLibSSE NG](https://github.com/CharmedBaryon/CommonLibSSE-NG) +* [CommonLibSSE NG](https://github.com/mlthelama/CommonLibSSE/tree/ng) + - currently a [fork from](https://github.com/alandtse/CommonLibVR/tree/ng) with minor changes + - added as [submodule](extern/CommonLibSSE-NG) * [spdlog](https://github.com/gabime/spdlog) * [nlohmann-json](https://github.com/nlohmann/json) * [tomlplusplus](https://github.com/marzer/tomlplusplus) @@ -28,6 +28,16 @@ * [cmake](https://cmake.org) - installed dir needs to be added to the `PATH` environment variable - Version `3.25` +* [clib-util](https://github.com/powerof3/CLibUtil) + - added as vcpkg [port](cmake/ports/clib-util) +* [unordered-dense](https://github.com/martinus/unordered_dense) + - added as vcpkg [port](cmake/ports/unordered-dense) + +## Release Dependencies +* [hint_button_menu](https://github.com/mlthelama/hint_button_menu) + - needed [file](extern/hint_button_menu/bin/hint_button_menu.swf) +* [skyui](https://github.com/mlthelama/skyui) + - needed [file](extern/skyui/swf/icons_item_psychosteve_merged.swf) ## Building ``` @@ -37,3 +47,12 @@ cd SkyrimCharacterSheet cmake --preset vs2022-windows cmake --build --preset vs2022-windows --config Release ``` + +## Manual Release +``` +cd SkyrimCharacterSheet + +./scripts/run.sh +or +.\scripts\run.sh +``` diff --git a/cmake/Version.h.in b/cmake/Version.h.in index d668081..0fa1315 100644 --- a/cmake/Version.h.in +++ b/cmake/Version.h.in @@ -9,6 +9,7 @@ namespace Version inline constexpr auto NAME = "@PROJECT_VERSION@"; inline constexpr auto PROJECT = "@PROJECT_NAME@"; inline constexpr auto AUTHOR = "mlthelama"; + inline constexpr auto EMAIL = "mlthelama [at] gmail [dot] com"; inline constexpr std::uint32_t ASINT = (static_cast((MAJOR * 1000000) + (MINOR * 10000) + (PATCH * 100) + (BETA))); 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/config/SkyrimCharacterSheet_Keys.json b/config/SkyrimCharacterSheet_Keys.json index 2dbcbff..239d7b6 100644 --- a/config/SkyrimCharacterSheet_Keys.json +++ b/config/SkyrimCharacterSheet_Keys.json @@ -19,6 +19,10 @@ { "key": "alternative", "name": "$ShowAlternative" + }, + { + "key": "close", + "name": "$ShowClose" } ] } diff --git a/extern/CommonLibSSE-NG b/extern/CommonLibSSE-NG new file mode 160000 index 0000000..69317b2 --- /dev/null +++ b/extern/CommonLibSSE-NG @@ -0,0 +1 @@ +Subproject commit 69317b239722ee2fb93d04ffda27a9a3b89a977b diff --git a/src/PCH.h b/src/PCH.h index cca5bf6..62ecd89 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,24 @@ #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/main.cpp b/src/main.cpp index 9032ba6..d7d72f6 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(); @@ -92,10 +95,11 @@ EXTERN_C [[maybe_unused]] __declspec(dllexport) constinit auto SKSEPlugin_Versio SKSE::PluginVersionData v; v.PluginName(Version::PROJECT); v.AuthorName(Version::AUTHOR); + v.AuthorEmail(Version::EMAIL); v.PluginVersion({ Version::MAJOR, Version::MINOR, Version::PATCH, Version::BETA }); - v.UsesAddressLibrary(true); + v.UsesAddressLibrary(); v.CompatibleVersions({ SKSE::RUNTIME_SSE_LATEST }); - 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..1072167 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() { @@ -175,17 +176,13 @@ namespace scaleform { view_->CreateArray(std::addressof(champion_item_list_provider_)); champion_item_list_.DataProvider(CLIK::Array{ champion_item_list_provider_ }); - menu_close_.Label("Close"); - menu_close_.Disabled(false); - update_title(); update_headers(); update_bottom(); update_lists(); - prev_.Label(previous_menu_name_); - prev_.Disabled(false); + update_buttons(); disable_item_lists(); @@ -195,29 +192,42 @@ namespace scaleform { logger::debug("Shown all Values for Menu {}"sv, menu_name); } - void faction_menu::update_text(CLIK::TextField a_field, const std::string_view a_string) { + void faction_menu::update_text(CLIK::TextField a_field, std::string_view a_string) { + if (util::translation::needs_translation(a_string)) { + a_string = util::translation::get_singleton()->get_translation(a_string); + } + a_field.AutoSize(CLIK::Object{ "left" }); a_field.HTMLText(a_string); a_field.Visible(true); } - void faction_menu::update_text(CLIK::TextField a_field, - const std::string_view a_string, - const std::string& a_auto_size) { + void faction_menu::update_text(CLIK::TextField a_field, std::string_view a_string, const std::string& a_auto_size) { + if (util::translation::needs_translation(a_string)) { + a_string = util::translation::get_singleton()->get_translation(a_string); + } + a_field.AutoSize(CLIK::Object{ a_auto_size }); a_field.HTMLText(a_string); a_field.Visible(true); } - void faction_menu::update_title() const { update_text(title_, menu_name_); } + void faction_menu::update_title() { update_text(title_, menu_name_); } - void faction_menu::update_headers() const { + void faction_menu::update_headers() { update_text(faction_header_, get_column_name(setting_data::menu_data::faction_column_type::faction)); update_text(thane_header_, get_column_name(setting_data::menu_data::faction_column_type::thane)); update_text(champion_header_, get_column_name(setting_data::menu_data::faction_column_type::champion)); } - RE::GFxValue faction_menu::build_gfx_value(const std::string_view& a_key, const std::string& a_val) const { + RE::GFxValue faction_menu::build_gfx_value(std::string_view a_key, std::string& a_val) const { + if (util::translation::needs_translation(a_key)) { + a_key = util::translation::get_singleton()->get_translation(a_key); + } + if (util::translation::needs_translation(a_val)) { + a_val = util::translation::get_singleton()->get_translation(a_val); + } + RE::GFxValue value; view_->CreateObject(std::addressof(value)); value.SetMember("displayName", { a_key }); @@ -407,4 +417,19 @@ namespace scaleform { auto menu_controls = RE::MenuControls::GetSingleton(); menu_controls->RemoveHandler(this); } + + void faction_menu::update_buttons() { + auto close = setting::key_setting::get_key(setting::key_setting::key_name::close); + if (util::translation::needs_translation(close)) { + close = util::translation::get_singleton()->get_translation(close); + } + menu_close_.Label(close); + menu_close_.Disabled(false); + + if (util::translation::needs_translation(previous_menu_name_)) { + previous_menu_name_ = util::translation::get_singleton()->get_translation(previous_menu_name_); + } + prev_.Label(previous_menu_name_); + prev_.Disabled(false); + } } diff --git a/src/scaleform/menus/faction_menu.h b/src/scaleform/menus/faction_menu.h index 97c9725..b7c89bf 100644 --- a/src/scaleform/menus/faction_menu.h +++ b/src/scaleform/menus/faction_menu.h @@ -65,11 +65,11 @@ namespace scaleform { static void update_text(CLIK::TextField a_field, std::string_view a_string, const std::string& a_auto_size); - void update_title() const; + void update_title(); - void update_headers() const; + void update_headers(); - [[nodiscard]] RE::GFxValue build_gfx_value(const std::string_view& a_key, const std::string& a_val) const; + [[nodiscard]] RE::GFxValue build_gfx_value(std::string_view a_key, std::string& a_val) const; void clear_providers(); @@ -99,6 +99,8 @@ namespace scaleform { [[nodiscard]] std::string get_column_name(setting_data::menu_data::faction_column_type a_column) const; + void update_buttons(); + RE::GPtr view_; bool is_active_ = false; diff --git a/src/scaleform/menus/stats_inventory_menu.cpp b/src/scaleform/menus/stats_inventory_menu.cpp index 4e2b448..1f61d43 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,7 +172,11 @@ 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) { + if (util::translation::needs_translation(a_string)) { + a_string = util::translation::get_singleton()->get_translation(a_string); + } + a_field.AutoSize(CLIK::Object{ "left" }); a_field.HTMLText(a_string); a_field.Visible(true); @@ -184,22 +189,26 @@ namespace scaleform { update_text(effect_header_, get_column_name(setting_data::menu_data::stats_inventory_column_type::effect)); } - RE::GFxValue stats_inventory_menu::build_gfx_value(const std::string_view& a_key, + RE::GFxValue stats_inventory_menu::build_gfx_value(std::string& a_key, const std::string& a_val, const std::string_view& a_icon) const { + if (util::translation::needs_translation(a_key)) { + a_key = util::translation::get_singleton()->get_translation(a_key); + } + RE::GFxValue value; view_->CreateObject(std::addressof(value)); - value.SetMember("displayName", { a_key }); + value.SetMember("displayName", { static_cast(a_key) }); value.SetMember("displayValue", { static_cast(a_val) }); value.SetMember("iconKey", { a_icon }); value.SetMember("iconScale", { 18 }); return value; } - RE::GFxValue stats_inventory_menu::build_gfx_value(const std::string_view& a_key, const std::string& a_val) const { + RE::GFxValue stats_inventory_menu::build_gfx_value(const std::string& a_key, const std::string& a_val) const { RE::GFxValue value; view_->CreateObject(std::addressof(value)); - value.SetMember("displayName", { a_key }); + value.SetMember("displayName", { static_cast(a_key) }); value.SetMember("displayValue", { static_cast(a_val) }); value.SetMember("iconScale", { 18 }); return value; diff --git a/src/scaleform/menus/stats_inventory_menu.h b/src/scaleform/menus/stats_inventory_menu.h index b3d594f..6ab81a7 100644 --- a/src/scaleform/menus/stats_inventory_menu.h +++ b/src/scaleform/menus/stats_inventory_menu.h @@ -57,11 +57,10 @@ namespace scaleform { void update_headers() const; - [[nodiscard]] RE::GFxValue build_gfx_value(const std::string_view& a_key, - const std::string& a_val, - const std::string_view& a_icon) const; + RE::GFxValue + build_gfx_value(std::string& a_key, const std::string& a_val, const std::string_view& a_icon) const; - [[nodiscard]] RE::GFxValue build_gfx_value(const std::string_view& a_key, const std::string& a_val) const; + [[nodiscard]] RE::GFxValue build_gfx_value(const std::string& a_key, const std::string& a_val) const; void clear_providers(); diff --git a/src/scaleform/menus/stats_menu.cpp b/src/scaleform/menus/stats_menu.cpp index ab0a367..19ce85b 100644 --- a/src/scaleform/menus/stats_menu.cpp +++ b/src/scaleform/menus/stats_menu.cpp @@ -4,7 +4,9 @@ #include "mod/mod_manager.h" #include "scaleform/menus/faction_menu.h" #include "setting/input_setting.h" +#include "setting/key_setting.h" #include "util/key_util.h" +#include "util/translation.h" namespace scaleform { void stats_menu::Register() { @@ -194,17 +196,13 @@ namespace scaleform { view_->CreateArray(std::addressof(perks_thief_item_list_provider_)); perks_thief_item_list_.DataProvider(CLIK::Array{ perks_thief_item_list_provider_ }); - menu_close_.Label("Close"); - menu_close_.Disabled(false); - update_title(); update_headers(); update_bottom(); update_lists(); - next_.Label(next_menu_name_); - next_.Disabled(false); + update_buttons(); disable_item_lists(); @@ -214,14 +212,21 @@ namespace scaleform { logger::debug("Shown all Values for Menu {}"sv, menu_name); } - void stats_menu::update_text(CLIK::TextField a_field, const std::string_view a_string) { + void stats_menu::update_text(CLIK::TextField a_field, std::string_view a_string) { + if (util::translation::needs_translation(a_string)) { + a_string = util::translation::get_singleton()->get_translation(a_string); + } + a_field.AutoSize(CLIK::Object{ "left" }); a_field.HTMLText(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) { + + void stats_menu::update_text(CLIK::TextField a_field, std::string_view a_string, const std::string& a_auto_size) { + if (util::translation::needs_translation(a_string)) { + a_string = util::translation::get_singleton()->get_translation(a_string); + } + a_field.AutoSize(CLIK::Object{ a_auto_size }); a_field.HTMLText(a_string); a_field.Visible(true); @@ -237,12 +242,16 @@ namespace scaleform { update_text(perks_thief_header_, get_column_name(setting_data::menu_data::stats_column_type::thief)); } - RE::GFxValue stats_menu::build_gfx_value(const std::string_view& a_key, + RE::GFxValue stats_menu::build_gfx_value(std::string& a_key, const std::string& a_val, const std::string_view& a_icon) const { + if (util::translation::needs_translation(a_key)) { + a_key = util::translation::get_singleton()->get_translation(a_key); + } + RE::GFxValue value; view_->CreateObject(std::addressof(value)); - value.SetMember("displayName", { a_key }); + value.SetMember("displayName", { static_cast(a_key) }); value.SetMember("displayValue", { static_cast(a_val) }); value.SetMember("iconKey", { a_icon }); value.SetMember("iconScale", { 22 }); @@ -427,4 +436,19 @@ namespace scaleform { auto menu_controls = RE::MenuControls::GetSingleton(); menu_controls->RemoveHandler(this); } + + void stats_menu::update_buttons() { + auto close = setting::key_setting::get_key(setting::key_setting::key_name::close); + if (util::translation::needs_translation(close)) { + close = util::translation::get_singleton()->get_translation(close); + } + menu_close_.Label(close); + menu_close_.Disabled(false); + + if (util::translation::needs_translation(next_menu_name_)) { + next_menu_name_ = util::translation::get_singleton()->get_translation(next_menu_name_); + } + next_.Label(next_menu_name_); + next_.Disabled(false); + } } diff --git a/src/scaleform/menus/stats_menu.h b/src/scaleform/menus/stats_menu.h index 371575b..fe9e12d 100644 --- a/src/scaleform/menus/stats_menu.h +++ b/src/scaleform/menus/stats_menu.h @@ -70,9 +70,8 @@ namespace scaleform { void update_headers() const; - [[nodiscard]] RE::GFxValue build_gfx_value(const std::string_view& a_key, - const std::string& a_val, - const std::string_view& a_icon) const; + RE::GFxValue + build_gfx_value(std::string& a_key, const std::string& a_val, const std::string_view& a_icon) const; void clear_providers(); @@ -104,6 +103,8 @@ namespace scaleform { [[nodiscard]] std::string get_column_name(setting_data::menu_data::stats_column_type a_column) const; + void update_buttons(); + RE::GPtr view_; bool is_active_ = false; diff --git a/src/scaleform/menus/tween_hint_menu.cpp b/src/scaleform/menus/tween_hint_menu.cpp index 1709d88..612d6bd 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,7 +130,11 @@ 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) { + void tween_hint_menu::update_text(CLIK::TextField a_field, std::string& a_string) { + if (util::translation::needs_translation(a_string)) { + a_string = util::translation::get_singleton()->get_translation(a_string); + } + a_field.HTMLText(a_string); a_field.Visible(true); } @@ -146,7 +151,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/setting/key_setting.h b/src/setting/key_setting.h index 5406adf..219adde 100644 --- a/src/setting/key_setting.h +++ b/src/setting/key_setting.h @@ -3,7 +3,7 @@ namespace setting { class key_setting { public: - enum class key_name { none, vampire, werewolf, count, in_favor, alternative }; + enum class key_name { none, vampire, werewolf, count, in_favor, alternative, close }; static void load_setting(); diff --git a/src/util/key_util.cpp b/src/util/key_util.cpp index a6dbe4a..a656393 100644 --- a/src/util/key_util.cpp +++ b/src/util/key_util.cpp @@ -14,8 +14,8 @@ namespace util { break; case RE::INPUT_DEVICE::kNone: case RE::INPUT_DEVICE::kVirtualKeyboard: - case RE::INPUT_DEVICE::kVRRight: - case RE::INPUT_DEVICE::kVRLeft: + //case RE::INPUT_DEVICE::kVRRight: + //case RE::INPUT_DEVICE::kVRLeft: case RE::INPUT_DEVICE::kTotal: break; } diff --git a/src/util/player/perkvisitor.cpp b/src/util/player/perkvisitor.cpp index 9998d31..850c688 100644 --- a/src/util/player/perkvisitor.cpp +++ b/src/util/player/perkvisitor.cpp @@ -2,22 +2,39 @@ #include "util/type_util.h" namespace util { - RE::PerkEntryVisitor::ReturnType perk_visitor::Visit(RE::BGSPerkEntry* perk_entry) { + RE::BSContainer::ForEachResult perk_visitor::Visit(RE::BGSPerkEntry* perk_entry) { const auto* entry_point = static_cast(perk_entry); const auto* perk = entry_point->perk; - logger::trace("formid {}, name {}"sv, util::type_util::int_to_hex(perk->GetFormID()), perk->GetName()); + logger::trace("formid {}, name {}, type {}, function {}"sv, + util::type_util::int_to_hex(perk->GetFormID()), + perk->GetName(), + static_cast(entry_point->functionData->GetType()), + entry_point->entryData.function.underlying()); - if (entry_point->functionData && - entry_point->entryData.function == RE::BGSEntryPointPerkEntry::EntryData::Function::kMultiplyValue) { - const RE::BGSEntryPointFunctionDataOneValue* value = - static_cast(entry_point->functionData); + RE::BGSEntryPointFunctionDataOneValue* value = nullptr; + switch (entry_point->functionData->GetType()) { + case RE::BGSEntryPointFunctionData::ENTRY_POINT_FUNCTION_DATA::kOneValue: + value = static_cast(entry_point->functionData); + break; + case RE::BGSEntryPointFunctionData::ENTRY_POINT_FUNCTION_DATA::kInvalid: + case RE::BGSEntryPointFunctionData::ENTRY_POINT_FUNCTION_DATA::kTwoValue: + case RE::BGSEntryPointFunctionData::ENTRY_POINT_FUNCTION_DATA::kLeveledList: + case RE::BGSEntryPointFunctionData::ENTRY_POINT_FUNCTION_DATA::kActivateChoice: + case RE::BGSEntryPointFunctionData::ENTRY_POINT_FUNCTION_DATA::kSpellItem: + case RE::BGSEntryPointFunctionData::ENTRY_POINT_FUNCTION_DATA::kBooleanGraphVariable: + case RE::BGSEntryPointFunctionData::ENTRY_POINT_FUNCTION_DATA::kText: + break; + } + if (value) { result_ = value->data; - logger::trace("Got value for Perk {}"sv, result_); } - return ReturnType::kContinue; + logger::trace("Got value {} for Perk {}"sv, result_, perk->GetName()); + + return RE::BSContainer::ForEachResult::kStop; } float perk_visitor::get_result() const { return result_; } + } // util diff --git a/src/util/player/perkvisitor.h b/src/util/player/perkvisitor.h index 6cffbc8..1364f00 100644 --- a/src/util/player/perkvisitor.h +++ b/src/util/player/perkvisitor.h @@ -3,18 +3,17 @@ namespace util { class perk_visitor : public RE::PerkEntryVisitor { public: - //explicit perk_visitor(RE::Actor* a_actor) { - explicit perk_visitor() { - //actor_ = a_actor; - result_ = 0; + explicit perk_visitor(RE::Actor* a_actor, float a_base) { + actor_ = a_actor; + result_ = a_base; } - RE::PerkEntryVisitor::ReturnType Visit(RE::BGSPerkEntry* perk_entry) override; + RE::BSContainer::ForEachResult Visit(RE::BGSPerkEntry* perk_entry) override; [[nodiscard]] float get_result() const; protected: - //RE::Actor* actor_; + RE::Actor* actor_; float result_; }; } // util diff --git a/src/util/player/player.cpp b/src/util/player/player.cpp index b502ba3..2b9da50 100644 --- a/src/util/player/player.cpp +++ b/src/util/player/player.cpp @@ -116,7 +116,7 @@ namespace util { float player::get_fall_damage_mod(RE::PlayerCharacter*& a_player) { if (a_player->HasPerkEntries(RE::BGSEntryPoint::ENTRY_POINTS::kModFallingDamage)) { - auto perk_visit = util::perk_visitor(); + auto perk_visit = util::perk_visitor(a_player, 0.f); a_player->ForEachPerkEntry(RE::BGSEntryPoint::ENTRY_POINTS::kModFallingDamage, perk_visit); auto fall_damage_mod = perk_visit.get_result(); logger::trace("perk visit got {} for falling damage"sv, fall_damage_mod); @@ -174,14 +174,14 @@ namespace util { return mod::armor_rating_rescaled_remake::calculate_armor_damage_resistance(a_armor_rating, a_pieces_worn); } - auto resistance = a_armor_rating * game_settings->get_armor_scaling_factor() + - game_settings->get_armor_base_factor() * 100 * a_pieces_worn; - if (mod::mod_manager::get_singleton()->get_blade_and_blunt()) { + auto resistance = a_armor_rating * game_settings->get_armor_scaling_factor() + 0.03f * 100 * a_pieces_worn; + return mod::blade_and_blunt::calculate_armor_damage_resistance(resistance); } - return resistance; + return a_armor_rating * game_settings->get_armor_scaling_factor() + + game_settings->get_armor_base_factor() * 100 * a_pieces_worn; } } // util diff --git a/src/util/translation.cpp b/src/util/translation.cpp new file mode 100644 index 0000000..3efa674 --- /dev/null +++ b/src/util/translation.cpp @@ -0,0 +1,66 @@ +#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 {}, count {} ...", a_path.string(), translation_map.size()); + return true; + } +} // util diff --git a/src/util/translation.h b/src/util/translation.h new file mode 100644 index 0000000..b5d59b9 --- /dev/null +++ b/src/util/translation.h @@ -0,0 +1,45 @@ +#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"; + return str; + } + + template + static bool needs_translation(T& a_key) { + if (a_key.starts_with('$')) { + return true; + } + return false; + } + + 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 diff --git a/swf/Translations/SkyrimCharacterSheet_english.txt b/swf/Translations/SkyrimCharacterSheet_english.txt index beb6e0c..1683b74 100644 Binary files a/swf/Translations/SkyrimCharacterSheet_english.txt and b/swf/Translations/SkyrimCharacterSheet_english.txt differ diff --git a/vcpkg.json b/vcpkg.json index 60fc298..a649b05 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,13 +1,19 @@ { "name": "skyrimcharactersheet", - "version-string": "1.1.1.0", + "version-string": "1.1.2.0", "description": "An info page", "homepage": "https://github.com/mlthelama/SkyrimCharacterSheet", "license": "GPL-2.0-or-later", "dependencies": [ "tomlplusplus", - "commonlibsse-ng", "nlohmann-json", - "magic-enum" + "magic-enum", + "unordered-dense", + "clib-util", + "fmt", + "spdlog", + "rapidcsv", + "directxtk", + "rsm-binary-io" ] }