From 5a5336495f5bbce8ecda553571d68f5396144cbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E8=AE=B0?= Date: Mon, 18 May 2026 12:00:22 +0800 Subject: [PATCH 1/9] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=8E=A9=E5=AE=B6?= =?UTF-8?q?=E5=90=8D=E7=A7=B0=E6=98=BE=E7=A4=BA=E9=80=89=E9=A1=B9=E5=8F=8A?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Classes/Entities/CCSPlayerController.h | 2 + Source/Config/ConfigSchema.h | 2 + Source/Config/ConfigVariableTypes.h | 1 + .../PlayerInfoInWorldConfigVariables.h | 1 + .../PlayerInfoInWorldPanelFactory.h | 14 ++++ .../PlayerInfoInWorld/PlayerInfoPanelTypes.h | 2 + .../PlayerInfoInWorld/PlayerNamePanel.h | 64 +++++++++++++++++++ .../PlayerInfoInWorld/PlayerNamePanelParams.h | 31 +++++++++ Source/GameClient/Entities/PlayerController.h | 9 +++ Source/UI/Panorama/CreateGUI.js | 3 + Source/UI/Panorama/VisualsTab.h | 2 + Tests/Configs/config_current.cfg | 2 +- .../Config/ConfigCompatibilityTests.cpp | 8 ++- Tests/Mocks/MockPlayerController.h | 1 + 14 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 Source/Features/Visuals/PlayerInfoInWorld/PlayerNamePanel.h create mode 100644 Source/Features/Visuals/PlayerInfoInWorld/PlayerNamePanelParams.h diff --git a/Source/CS2/Classes/Entities/CCSPlayerController.h b/Source/CS2/Classes/Entities/CCSPlayerController.h index a739a407ac4..e6d3b12b58d 100644 --- a/Source/CS2/Classes/Entities/CCSPlayerController.h +++ b/Source/CS2/Classes/Entities/CCSPlayerController.h @@ -2,6 +2,7 @@ #include +#include #include #include @@ -25,6 +26,7 @@ struct CCSPlayerController : C_BaseEntity { using m_hPawn = CEntityHandle; using m_iCompTeammateColor = PlayerColorIndex; + using m_sSanitizedPlayerName = CUtlString; }; } diff --git a/Source/Config/ConfigSchema.h b/Source/Config/ConfigSchema.h index b5c622265af..603263bc434 100644 --- a/Source/Config/ConfigSchema.h +++ b/Source/Config/ConfigSchema.h @@ -145,6 +145,8 @@ class ConfigSchema { configConversion.uint(u8"ColorMode", loadVariable(), saveVariable()); configConversion.endObject(); + configConversion.boolean(u8"PlayerName", loadVariable(), saveVariable()); + configConversion.beginObject(u8"Health"); configConversion.boolean(u8"Enabled", loadVariable(), saveVariable()); configConversion.uint(u8"ColorMode", loadVariable(), saveVariable()); diff --git a/Source/Config/ConfigVariableTypes.h b/Source/Config/ConfigVariableTypes.h index f327bd972ae..dd33f947eda 100644 --- a/Source/Config/ConfigVariableTypes.h +++ b/Source/Config/ConfigVariableTypes.h @@ -84,6 +84,7 @@ using ConfigVariableTypes = TypeList< player_info_vars::OnlyEnemies, player_info_vars::PlayerPositionArrowEnabled, player_info_vars::PlayerPositionArrowColorMode, + player_info_vars::PlayerNameEnabled, player_info_vars::PlayerHealthEnabled, player_info_vars::PlayerHealthColorMode, player_info_vars::ActiveWeaponIconEnabled, diff --git a/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorldConfigVariables.h b/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorldConfigVariables.h index a6f44fd64b2..607c6b71286 100644 --- a/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorldConfigVariables.h +++ b/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorldConfigVariables.h @@ -15,6 +15,7 @@ CONFIG_VARIABLE(Enabled, bool, false); CONFIG_VARIABLE(OnlyEnemies, bool, false); CONFIG_VARIABLE(PlayerPositionArrowEnabled, bool, true); CONFIG_VARIABLE(PlayerPositionArrowColorMode, PlayerPositionArrowColorType, PlayerPositionArrowColorType::PlayerOrTeamColor); +CONFIG_VARIABLE(PlayerNameEnabled, bool, true); CONFIG_VARIABLE(PlayerHealthEnabled, bool, true); CONFIG_VARIABLE(PlayerHealthColorMode, PlayerHealthTextColor, PlayerHealthTextColor::HealthBased); CONFIG_VARIABLE(ActiveWeaponIconEnabled, bool, true); diff --git a/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorldPanelFactory.h b/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorldPanelFactory.h index cb3407a7e7f..b83f480c86e 100644 --- a/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorldPanelFactory.h +++ b/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorldPanelFactory.h @@ -11,6 +11,8 @@ #include "PlayerHealth/PlayerHealthPanel.h" #include "PlayerHealth/PlayerHealthPanelParams.h" #include "PlayerInfoContainerPanelParams.h" +#include "PlayerNamePanel.h" +#include "PlayerNamePanelParams.h" #include "PlayerPositionArrow/PlayerPositionArrowPanel.h" #include "PlayerPositionArrow/PlayerPositionArrowPanelParams.h" #include "PlayerStateIcons/PlayerStateIconsPanel.h" @@ -56,6 +58,18 @@ class PlayerInfoInWorldPanelFactory { }.applyTo(uiPanel); } + void createPanel(std::type_identity>, auto&& containerPanel) const noexcept + { + using namespace player_name_panel_params; + + auto&& label = hookContext.panelFactory().createLabelPanel(containerPanel).uiPanel(); + label.setFont(kFont); + label.setAlign(kAlignment); + label.setMargin(kMargin); + label.setTextShadow(kShadowParams); + label.setColor(kColor); + } + void createPanel(std::type_identity>, auto&& containerPanel) const noexcept { auto&& ammoPanel = createActiveWeaponAmmoContainerPanel(containerPanel); diff --git a/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoPanelTypes.h b/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoPanelTypes.h index 47e5082b209..b950c372bfb 100644 --- a/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoPanelTypes.h +++ b/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoPanelTypes.h @@ -4,6 +4,7 @@ #include "ActiveWeaponAmmo/PlayerActiveWeaponAmmoPanel.h" #include "PlayerHealth/PlayerHealthPanel.h" +#include "PlayerNamePanel.h" #include "PlayerPositionArrow/PlayerPositionArrowPanel.h" #include "PlayerStateIcons/PlayerStateIconsPanel.h" #include "PlayerWeaponIcon/PlayerWeaponIconPanel.h" @@ -11,6 +12,7 @@ template using PlayerInfoPanelTypes = std::tuple< PlayerPositionArrowPanel, + PlayerNamePanel, PlayerHealthPanel, PlayerWeaponIconPanel, PlayerActiveWeaponAmmoPanel, diff --git a/Source/Features/Visuals/PlayerInfoInWorld/PlayerNamePanel.h b/Source/Features/Visuals/PlayerInfoInWorld/PlayerNamePanel.h new file mode 100644 index 00000000000..f4e2c7eac87 --- /dev/null +++ b/Source/Features/Visuals/PlayerInfoInWorld/PlayerNamePanel.h @@ -0,0 +1,64 @@ +#pragma once + +#include + +#include +#include +#include +#include + +#include "PlayerNamePanelParams.h" + +template +class PlayerNamePanelContext { +public: + PlayerNamePanelContext(HookContext& hookContext, cs2::CUIPanel* panel, auto&) noexcept + : _hookContext{hookContext} + , _panel{panel} + { + } + + [[nodiscard]] decltype(auto) config() const noexcept + { + return _hookContext.config(); + } + + [[nodiscard]] decltype(auto) panel() const noexcept + { + return _hookContext.template make(_panel); + } + +private: + HookContext& _hookContext; + cs2::CUIPanel* _panel; +}; + +template > +class PlayerNamePanel { +public: + template + explicit PlayerNamePanel(Args&&... args) noexcept + : context{std::forward(args)...} + { + } + + void update(auto&& playerPawn) const noexcept + { + if (!context.config().template getVariable()) { + context.panel().setVisible(false); + return; + } + + const auto playerName = playerPawn.playerController().getName(); + if (!playerName || !*playerName) { + context.panel().setVisible(false); + return; + } + + context.panel().setVisible(true); + context.panel().clientPanel().template as().setText(playerName); + } + +private: + Context context; +}; diff --git a/Source/Features/Visuals/PlayerInfoInWorld/PlayerNamePanelParams.h b/Source/Features/Visuals/PlayerInfoInWorld/PlayerNamePanelParams.h new file mode 100644 index 00000000000..5e73b2a0509 --- /dev/null +++ b/Source/Features/Visuals/PlayerInfoInWorld/PlayerNamePanelParams.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace player_name_panel_params +{ + static constexpr auto kFont = PanelFontParams{ + .fontFamily = "Stratum2, 'Arial Unicode MS'", + .fontSize = 20, + .fontWeight = cs2::k_EFontWeightBold + }; + static constexpr auto kAlignment = PanelAlignmentParams{ + .horizontalAlignment = cs2::k_EHorizontalAlignmentCenter, + .verticalAlignment = cs2::k_EVerticalAlignmentTop + }; + static constexpr auto kMargin = PanelMarginParams{.marginBottom = cs2::CUILength::pixels(1)}; + static constexpr auto kColor = cs2::kColorWhite; + static constexpr auto kShadowParams = PanelShadowParams{ + .horizontalOffset{cs2::CUILength::pixels(0)}, + .verticalOffset{cs2::CUILength::pixels(0)}, + .blurRadius{cs2::CUILength::pixels(3)}, + .strength = 3, + .color{cs2::kColorBlack} + }; +} diff --git a/Source/GameClient/Entities/PlayerController.h b/Source/GameClient/Entities/PlayerController.h index 8df6adeff13..be3ac9250dd 100644 --- a/Source/GameClient/Entities/PlayerController.h +++ b/Source/GameClient/Entities/PlayerController.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -51,6 +52,14 @@ class PlayerController { return hookContext.patternSearchResults().template get().of(playerControllerPointer).toOptional(); } + [[nodiscard]] const char* getName() const noexcept + { + constexpr auto kOffsetFromPlayerColorToSanitizedName{0x18}; + if (const auto playerColor = hookContext.patternSearchResults().template get().of(playerControllerPointer).get()) + return reinterpret_cast(reinterpret_cast(playerColor) + kOffsetFromPlayerColorToSanitizedName)->m_pString; + return static_cast(nullptr); + } + private: HookContext& hookContext; cs2::CCSPlayerController* playerControllerPointer; diff --git a/Source/UI/Panorama/CreateGUI.js b/Source/UI/Panorama/CreateGUI.js index 08c9ec76680..b00ffc7722d 100644 --- a/Source/UI/Panorama/CreateGUI.js +++ b/Source/UI/Panorama/CreateGUI.js @@ -499,6 +499,9 @@ u8R"( separator(playerPosition); createDropDown(playerPosition, "Player Position Arrow Color", 'visuals', 'player_info_position_color', ['Player / Team Color', 'Team Color']); + var playerName = createSection(playerInfoTab, 'Player Name'); + createYesNoDropDown(playerName, "Show Player Name", 'visuals', 'player_info_name'); + var playerHealth = createSection(playerInfoTab, 'Player Health'); createYesNoDropDown(playerHealth, "Show Player Health", 'visuals', 'player_info_health'); separator(playerHealth); diff --git a/Source/UI/Panorama/VisualsTab.h b/Source/UI/Panorama/VisualsTab.h index edf8f9ce367..16aaa100b93 100644 --- a/Source/UI/Panorama/VisualsTab.h +++ b/Source/UI/Panorama/VisualsTab.h @@ -46,6 +46,7 @@ class VisualsTab { initDropDown>(guiPanel, "player_information_through_walls"); initDropDown>(guiPanel, "player_info_position"); initDropDown>(guiPanel, "player_info_position_color"); + initDropDown>(guiPanel, "player_info_name"); initDropDown>(guiPanel, "player_info_health"); initDropDown>(guiPanel, "player_info_health_color"); initDropDown>(guiPanel, "player_info_weapon"); @@ -148,6 +149,7 @@ class VisualsTab { setDropDownSelectedIndex(mainMenu, "player_information_through_walls", playerInfoDropDownIndex()); setDropDownSelectedIndex(mainMenu, "player_info_position", !GET_CONFIG_VAR(player_info_vars::PlayerPositionArrowEnabled)); setDropDownSelectedIndex(mainMenu, "player_info_position_color", static_cast(GET_CONFIG_VAR(player_info_vars::PlayerPositionArrowColorMode))); + setDropDownSelectedIndex(mainMenu, "player_info_name", !GET_CONFIG_VAR(player_info_vars::PlayerNameEnabled)); setDropDownSelectedIndex(mainMenu, "player_info_health", !GET_CONFIG_VAR(player_info_vars::PlayerHealthEnabled)); setDropDownSelectedIndex(mainMenu, "player_info_health_color", static_cast(GET_CONFIG_VAR(player_info_vars::PlayerHealthColorMode))); setDropDownSelectedIndex(mainMenu, "player_info_weapon", !GET_CONFIG_VAR(player_info_vars::ActiveWeaponIconEnabled)); diff --git a/Tests/Configs/config_current.cfg b/Tests/Configs/config_current.cfg index ab4d11bfd5f..e796a48f477 100644 --- a/Tests/Configs/config_current.cfg +++ b/Tests/Configs/config_current.cfg @@ -1 +1 @@ -{"Combat":{"NoScopeInaccuracyVis":{"Enabled":true}},"Hud":{"BombTimer":{"Enabled":true},"BombDefuseAlert":{"Enabled":true},"PreserveKillfeed":{"Enabled":true},"PostRoundTimer":{"Enabled":true},"BombPlantAlert":{"Enabled":true}},"Visuals":{"ModelGlow":{"Enabled":true,"Players":{"Enabled":true,"OnlyEnemies":true,"ColorMode":0},"Weapons":true,"DroppedBomb":false,"TickingBomb":false,"DefuseKits":true,"GrenadeProjectiles":true,"Hues":{"PlayerBlue":203,"PlayerGreen":133,"PlayerYellow":48,"PlayerOrange":13,"PlayerPurple":269,"TeamT":30,"TeamCT":220,"LowHealth":311,"HighHealth":256,"Enemy":353,"Ally":74,"Molotov":37,"Flashbang":205,"HEGrenade":333,"SmokeGrenade":116,"DroppedBomb":69,"TickingBomb":303,"DefuseKit":227}},"OutlineGlow":{"Enabled":true,"Players":{"Enabled":true,"OnlyEnemies":false,"ColorMode":2},"Weapons":true,"DroppedBomb":true,"TickingBomb":false,"DefuseKits":true,"Hostages":true,"GrenadeProjectiles":true,"Hues":{"PlayerBlue":200,"PlayerGreen":134,"PlayerYellow":57,"PlayerOrange":12,"PlayerPurple":256,"TeamT":37,"TeamCT":227,"LowHealth":287,"HighHealth":171,"Enemy":304,"Ally":103,"Molotov":60,"Flashbang":250,"HEGrenade":300,"SmokeGrenade":140,"DroppedBomb":302,"TickingBomb":26,"DefuseKit":160,"Hostage":334}},"PlayerInfoInWorld":{"Enabled":true,"OnlyEnemies":true,"PlayerPositionArrow":{"Enabled":true,"ColorMode":0},"Health":{"Enabled":true,"ColorMode":0},"ActiveWeaponIcon":true,"BombCarrierIcon":true,"BombPlantIcon":true,"ActiveWeaponAmmo":false,"BombDefuseIcon":true,"HostagePickupIcon":true,"HostageRescueIcon":true,"BlindedIcon":true},"ViewmodelMod":{"Enabled":true,"ModifyFov":true,"Fov":90}},"Sound":{"Visualizations":{"Footsteps":{"Enabled":true},"BombPlant":{"Enabled":true},"BombBeep":{"Enabled":true},"BombDefuse":{"Enabled":true},"WeaponScope":{"Enabled":true},"WeaponReload":{"Enabled":true}}}} \ No newline at end of file +{"Combat":{"NoScopeInaccuracyVis":{"Enabled":true}},"Hud":{"BombTimer":{"Enabled":true},"BombDefuseAlert":{"Enabled":true},"PreserveKillfeed":{"Enabled":true},"PostRoundTimer":{"Enabled":true},"BombPlantAlert":{"Enabled":true}},"Visuals":{"ModelGlow":{"Enabled":true,"Players":{"Enabled":true,"OnlyEnemies":true,"ColorMode":0},"Weapons":true,"DroppedBomb":false,"TickingBomb":false,"DefuseKits":true,"GrenadeProjectiles":true,"Hues":{"PlayerBlue":203,"PlayerGreen":133,"PlayerYellow":48,"PlayerOrange":13,"PlayerPurple":269,"TeamT":30,"TeamCT":220,"LowHealth":311,"HighHealth":256,"Enemy":353,"Ally":74,"Molotov":37,"Flashbang":205,"HEGrenade":333,"SmokeGrenade":116,"DroppedBomb":69,"TickingBomb":303,"DefuseKit":227}},"OutlineGlow":{"Enabled":true,"Players":{"Enabled":true,"OnlyEnemies":false,"ColorMode":2},"Weapons":true,"DroppedBomb":true,"TickingBomb":false,"DefuseKits":true,"Hostages":true,"GrenadeProjectiles":true,"Hues":{"PlayerBlue":200,"PlayerGreen":134,"PlayerYellow":57,"PlayerOrange":12,"PlayerPurple":256,"TeamT":37,"TeamCT":227,"LowHealth":287,"HighHealth":171,"Enemy":304,"Ally":103,"Molotov":60,"Flashbang":250,"HEGrenade":300,"SmokeGrenade":140,"DroppedBomb":302,"TickingBomb":26,"DefuseKit":160,"Hostage":334}},"PlayerInfoInWorld":{"Enabled":true,"OnlyEnemies":true,"PlayerPositionArrow":{"Enabled":true,"ColorMode":0},"PlayerName":true,"Health":{"Enabled":true,"ColorMode":0},"ActiveWeaponIcon":true,"BombCarrierIcon":true,"BombPlantIcon":true,"ActiveWeaponAmmo":false,"BombDefuseIcon":true,"HostagePickupIcon":true,"HostageRescueIcon":true,"BlindedIcon":true},"ViewmodelMod":{"Enabled":true,"ModifyFov":true,"Fov":90}},"Sound":{"Visualizations":{"Footsteps":{"Enabled":true},"BombPlant":{"Enabled":true},"BombBeep":{"Enabled":true},"BombDefuse":{"Enabled":true},"WeaponScope":{"Enabled":true},"WeaponReload":{"Enabled":true}}}} \ No newline at end of file diff --git a/Tests/FunctionalTests/Config/ConfigCompatibilityTests.cpp b/Tests/FunctionalTests/Config/ConfigCompatibilityTests.cpp index be7c60932ab..39ea988b41f 100644 --- a/Tests/FunctionalTests/Config/ConfigCompatibilityTests.cpp +++ b/Tests/FunctionalTests/Config/ConfigCompatibilityTests.cpp @@ -171,9 +171,15 @@ class ConfigCompatibilityTest : public testing::Test { get() = true; } - void setVariableExpectationsCurrent() + void setVariableExpectationsV11() { setVariableExpectationsV10(); + get() = true; + } + + void setVariableExpectationsCurrent() + { + setVariableExpectationsV11(); } struct VariableChecker { diff --git a/Tests/Mocks/MockPlayerController.h b/Tests/Mocks/MockPlayerController.h index f3e6eae7e7d..e2099b427c0 100644 --- a/Tests/Mocks/MockPlayerController.h +++ b/Tests/Mocks/MockPlayerController.h @@ -8,5 +8,6 @@ struct MockBaseEntity; struct MockPlayerController { MOCK_METHOD(Optional, playerColorIndex, ()); + MOCK_METHOD(const char*, getName, ()); MOCK_METHOD(MockBaseEntity&, pawn, ()); }; From 09ebfde36cf7ebfb6e9ea46c5a44ca601686cae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E8=AE=B0?= <61319276+starnotes-xj@users.noreply.github.com> Date: Mon, 18 May 2026 14:00:58 +0800 Subject: [PATCH 2/9] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- Source/GameClient/Entities/PlayerController.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/GameClient/Entities/PlayerController.h b/Source/GameClient/Entities/PlayerController.h index be3ac9250dd..01fe1bc82d7 100644 --- a/Source/GameClient/Entities/PlayerController.h +++ b/Source/GameClient/Entities/PlayerController.h @@ -57,7 +57,7 @@ class PlayerController { constexpr auto kOffsetFromPlayerColorToSanitizedName{0x18}; if (const auto playerColor = hookContext.patternSearchResults().template get().of(playerControllerPointer).get()) return reinterpret_cast(reinterpret_cast(playerColor) + kOffsetFromPlayerColorToSanitizedName)->m_pString; - return static_cast(nullptr); + return nullptr; } private: From 932e88f31cb3ca19ce80124282d25e9de74d84db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E8=AE=B0?= Date: Mon, 18 May 2026 14:33:57 +0800 Subject: [PATCH 3/9] Resolve player name offset independently Use a dedicated player-name offset pattern so the name field no longer depends on the player color field layout. Co-Authored-By: Claude Opus 4.7 (1M context) --- Source/GameClient/Entities/PlayerController.h | 6 ++---- Source/MemoryPatterns/Linux/PlayerControllerPatternsLinux.h | 3 ++- .../PatternTypes/PlayerControllerPatternTypes.h | 1 + .../Windows/PlayerControllerPatternsWindows.h | 3 ++- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Source/GameClient/Entities/PlayerController.h b/Source/GameClient/Entities/PlayerController.h index 01fe1bc82d7..82d7450a860 100644 --- a/Source/GameClient/Entities/PlayerController.h +++ b/Source/GameClient/Entities/PlayerController.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -54,9 +53,8 @@ class PlayerController { [[nodiscard]] const char* getName() const noexcept { - constexpr auto kOffsetFromPlayerColorToSanitizedName{0x18}; - if (const auto playerColor = hookContext.patternSearchResults().template get().of(playerControllerPointer).get()) - return reinterpret_cast(reinterpret_cast(playerColor) + kOffsetFromPlayerColorToSanitizedName)->m_pString; + if (const auto playerName = hookContext.patternSearchResults().template get().of(playerControllerPointer).get()) + return playerName->m_pString; return nullptr; } diff --git a/Source/MemoryPatterns/Linux/PlayerControllerPatternsLinux.h b/Source/MemoryPatterns/Linux/PlayerControllerPatternsLinux.h index 6d743de6ce4..8fabe915944 100644 --- a/Source/MemoryPatterns/Linux/PlayerControllerPatternsLinux.h +++ b/Source/MemoryPatterns/Linux/PlayerControllerPatternsLinux.h @@ -8,6 +8,7 @@ struct PlayerControllerPatterns { { return clientPatterns .template addPattern() - .template addPattern(); + .template addPattern() + .template addPattern(); } }; diff --git a/Source/MemoryPatterns/PatternTypes/PlayerControllerPatternTypes.h b/Source/MemoryPatterns/PatternTypes/PlayerControllerPatternTypes.h index ab712adb731..bf0ece31533 100644 --- a/Source/MemoryPatterns/PatternTypes/PlayerControllerPatternTypes.h +++ b/Source/MemoryPatterns/PatternTypes/PlayerControllerPatternTypes.h @@ -11,3 +11,4 @@ using PlayerControllerOffset = FieldOffset); STRONG_TYPE_ALIAS(OffsetToPlayerColor, PlayerControllerOffset); +STRONG_TYPE_ALIAS(OffsetToSanitizedPlayerName, PlayerControllerOffset); diff --git a/Source/MemoryPatterns/Windows/PlayerControllerPatternsWindows.h b/Source/MemoryPatterns/Windows/PlayerControllerPatternsWindows.h index 811f3e74fc3..0cc9b0f84e8 100644 --- a/Source/MemoryPatterns/Windows/PlayerControllerPatternsWindows.h +++ b/Source/MemoryPatterns/Windows/PlayerControllerPatternsWindows.h @@ -8,6 +8,7 @@ struct PlayerControllerPatterns { { return clientPatterns .template addPattern() - .template addPattern(); + .template addPattern() + .template addPattern(); } }; From 0dc0c82bdd2acf2c8813dcc79ce7e495db94edb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E8=AE=B0?= Date: Mon, 18 May 2026 14:54:06 +0800 Subject: [PATCH 4/9] Restore player name lookup Revert the incorrect sanitized-name pattern so the player name display uses the working offset path again. Co-Authored-By: Claude Opus 4.7 (1M context) --- Source/GameClient/Entities/PlayerController.h | 6 ++++-- Source/MemoryPatterns/Linux/PlayerControllerPatternsLinux.h | 3 +-- .../PatternTypes/PlayerControllerPatternTypes.h | 1 - .../Windows/PlayerControllerPatternsWindows.h | 3 +-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Source/GameClient/Entities/PlayerController.h b/Source/GameClient/Entities/PlayerController.h index 82d7450a860..01fe1bc82d7 100644 --- a/Source/GameClient/Entities/PlayerController.h +++ b/Source/GameClient/Entities/PlayerController.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -53,8 +54,9 @@ class PlayerController { [[nodiscard]] const char* getName() const noexcept { - if (const auto playerName = hookContext.patternSearchResults().template get().of(playerControllerPointer).get()) - return playerName->m_pString; + constexpr auto kOffsetFromPlayerColorToSanitizedName{0x18}; + if (const auto playerColor = hookContext.patternSearchResults().template get().of(playerControllerPointer).get()) + return reinterpret_cast(reinterpret_cast(playerColor) + kOffsetFromPlayerColorToSanitizedName)->m_pString; return nullptr; } diff --git a/Source/MemoryPatterns/Linux/PlayerControllerPatternsLinux.h b/Source/MemoryPatterns/Linux/PlayerControllerPatternsLinux.h index 8fabe915944..6d743de6ce4 100644 --- a/Source/MemoryPatterns/Linux/PlayerControllerPatternsLinux.h +++ b/Source/MemoryPatterns/Linux/PlayerControllerPatternsLinux.h @@ -8,7 +8,6 @@ struct PlayerControllerPatterns { { return clientPatterns .template addPattern() - .template addPattern() - .template addPattern(); + .template addPattern(); } }; diff --git a/Source/MemoryPatterns/PatternTypes/PlayerControllerPatternTypes.h b/Source/MemoryPatterns/PatternTypes/PlayerControllerPatternTypes.h index bf0ece31533..ab712adb731 100644 --- a/Source/MemoryPatterns/PatternTypes/PlayerControllerPatternTypes.h +++ b/Source/MemoryPatterns/PatternTypes/PlayerControllerPatternTypes.h @@ -11,4 +11,3 @@ using PlayerControllerOffset = FieldOffset); STRONG_TYPE_ALIAS(OffsetToPlayerColor, PlayerControllerOffset); -STRONG_TYPE_ALIAS(OffsetToSanitizedPlayerName, PlayerControllerOffset); diff --git a/Source/MemoryPatterns/Windows/PlayerControllerPatternsWindows.h b/Source/MemoryPatterns/Windows/PlayerControllerPatternsWindows.h index 0cc9b0f84e8..811f3e74fc3 100644 --- a/Source/MemoryPatterns/Windows/PlayerControllerPatternsWindows.h +++ b/Source/MemoryPatterns/Windows/PlayerControllerPatternsWindows.h @@ -8,7 +8,6 @@ struct PlayerControllerPatterns { { return clientPatterns .template addPattern() - .template addPattern() - .template addPattern(); + .template addPattern(); } }; From 6fe3e8b642cbecbdc3a324348f21e70bd8d02966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E8=AE=B0?= Date: Sun, 31 May 2026 22:28:16 +0800 Subject: [PATCH 5/9] =?UTF-8?q?=E5=9C=A8=E5=A4=B4=E9=83=A8=E4=B8=8A?= =?UTF-8?q?=E6=96=B9=E5=B1=95=E7=A4=BA=E7=8E=A9=E5=AE=B6=E5=90=8D=E7=A7=B0?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E9=87=87=E7=94=A8=E7=8B=AC=E7=AB=8B=E7=9A=84?= =?UTF-8?q?=E4=B8=96=E7=95=8C=E7=A9=BA=E9=97=B4=E6=8A=95=E5=BD=B1=E6=96=B9?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 从PlayerInfoPanelTypes(脚部面板)中移除PlayerNamePanel - 在InWorldPanels中添加单独的PlayerNamePanel池 - 修复面板创建问题:使用容器+标签子级模式,而不是仅使用标签 - 使用多级回退机制更新PlayerController::getName()方法 - 修复了OffsetToPlayerColor和UiItem3dPanel的过时Windows样式 - 添加build-release.ps1便捷构建脚本 --- .gitignore | 181 +++++++++++++++++- Source/Features/Common/InWorldPanels.h | 11 ++ .../Common/InWorldPanelsPerHookState.h | 1 + Source/Features/Common/InWorldPanelsState.h | 2 + .../PlayerInfoInWorld/PlayerInfoInWorld.h | 10 +- .../PlayerInfoInWorldPanelFactory.h | 29 ++- .../PlayerInfoInWorld/PlayerInfoPanelTypes.h | 2 - .../PlayerInfoInWorld/PlayerNamePanel.h | 42 +++- .../PlayerInfoInWorld/PlayerNamePanelParams.h | 15 +- Source/GameClient/Entities/PlayerController.h | 47 ++++- Source/GameClient/Panorama/UiItem3dPanel.h | 24 --- .../Linux/UiItem3dPanelPatternsLinux.h | 4 +- .../Windows/PlayerControllerPatternsWindows.h | 2 +- .../Windows/UiItem3dPanelPatternsWindows.h | 4 +- 14 files changed, 324 insertions(+), 50 deletions(-) diff --git a/.gitignore b/.gitignore index f00a0eba354..7bf9ae39123 100644 --- a/.gitignore +++ b/.gitignore @@ -331,4 +331,183 @@ ASALocalRun/ .mfractor/ # Local History for Visual Studio -.localhistory/ \ No newline at end of file +.localhistory/ +/cmake-build-claude/_deps/googletest-build/googlemock/ALL_BUILD.vcxproj +/cmake-build-claude/_deps/googletest-build/googletest/ALL_BUILD.vcxproj +/cmake-build-claude/_deps/googletest-build/ALL_BUILD.vcxproj +/cmake-build-claude/_deps/googletest-subbuild/ALL_BUILD.vcxproj +/cmake-build-claude/ALL_BUILD.vcxproj +/cmake-build-claude/_deps/googletest-build/googlemock/ALL_BUILD.vcxproj.filters +/cmake-build-claude/_deps/googletest-build/googletest/ALL_BUILD.vcxproj.filters +/cmake-build-claude/_deps/googletest-build/ALL_BUILD.vcxproj.filters +/cmake-build-claude/_deps/googletest-subbuild/ALL_BUILD.vcxproj.filters +/cmake-build-claude/ALL_BUILD.vcxproj.filters +/build-release.ps1 +/cmake-build-claude/_deps/googletest-subbuild/CMakeFiles/cmake.check_cache +/cmake-build-claude/CMakeFiles/cmake.check_cache +/cmake-build-claude/_deps/googletest-build/googlemock/cmake_install.cmake +/cmake-build-claude/_deps/googletest-build/googletest/cmake_install.cmake +/cmake-build-claude/_deps/googletest-build/cmake_install.cmake +/cmake-build-claude/_deps/googletest-subbuild/cmake_install.cmake +/cmake-build-claude/Source/cmake_install.cmake +/cmake-build-claude/Tests/FunctionalTests/cmake_install.cmake +/cmake-build-claude/Tests/cmake_install.cmake +/cmake-build-claude/cmake_install.cmake +/cmake-build-claude/Tests/FunctionalTests/cmake_test_discovery_bb91720271.json +/cmake-build-claude/CMakeFiles/4.2.2/CMakeASM_MASMCompiler.cmake +/cmake-build-claude/_deps/googletest-subbuild/CMakeCache.txt +/cmake-build-claude/CMakeCache.txt +/cmake-build-claude/CMakeFiles/4.2.2/CMakeCCompiler.cmake +/cmake-build-claude/CMakeFiles/4.2.2/CompilerIdC/CMakeCCompilerId.c +/cmake-build-claude/_deps/googletest-subbuild/CMakeFiles/CMakeConfigureLog.yaml +/cmake-build-claude/CMakeFiles/CMakeConfigureLog.yaml +/cmake-build-claude/CMakeFiles/4.2.2/CMakeCXXCompiler.cmake +/cmake-build-claude/CMakeFiles/4.2.2/CompilerIdCXX/CMakeCXXCompilerId.cpp +/cmake-build-claude/CMakeFiles/4.2.2/CMakeDetermineCompilerABI_C.bin +/cmake-build-claude/CMakeFiles/4.2.2/CMakeDetermineCompilerABI_CXX.bin +/cmake-build-claude/_deps/googletest-subbuild/CMakeLists.txt +/cmake-build-claude/CMakeFiles/4.2.2/CMakeRCCompiler.cmake +/cmake-build-claude/_deps/googletest-subbuild/CMakeFiles/4.2.2/CMakeSystem.cmake +/cmake-build-claude/CMakeFiles/4.2.2/CMakeSystem.cmake +/cmake-build-claude/CMakeFiles/4.2.2/CompilerIdC/CompilerIdC.exe +/cmake-build-claude/CMakeFiles/4.2.2/CompilerIdC/CompilerIdC.vcxproj +/cmake-build-claude/CMakeFiles/4.2.2/CompilerIdCXX/CompilerIdCXX.exe +/cmake-build-claude/CMakeFiles/4.2.2/CompilerIdCXX/CompilerIdCXX.vcxproj +/cmake-build-claude/Testing/Temporary/CTestCostData.txt +/cmake-build-claude/_deps/googletest-build/googlemock/CTestTestfile.cmake +/cmake-build-claude/_deps/googletest-build/googletest/CTestTestfile.cmake +/cmake-build-claude/_deps/googletest-build/CTestTestfile.cmake +/cmake-build-claude/Tests/FunctionalTests/CTestTestfile.cmake +/cmake-build-claude/Tests/CTestTestfile.cmake +/cmake-build-claude/CTestTestfile.cmake +/cmake-build-claude/Tests/FunctionalTests/FunctionalTests.vcxproj +/cmake-build-claude/Tests/FunctionalTests/FunctionalTests.vcxproj.filters +/cmake-build-claude/Tests/FunctionalTests/FunctionalTests[1]_include.cmake +/cmake-build-claude/Tests/FunctionalTests/FunctionalTests[1]_tests.cmake +/cmake-build-claude/_deps/googletest-build/CMakeFiles/generate.stamp +/cmake-build-claude/_deps/googletest-build/googlemock/CMakeFiles/generate.stamp +/cmake-build-claude/_deps/googletest-build/googletest/CMakeFiles/generate.stamp +/cmake-build-claude/_deps/googletest-subbuild/CMakeFiles/generate.stamp +/cmake-build-claude/CMakeFiles/generate.stamp +/cmake-build-claude/Source/CMakeFiles/generate.stamp +/cmake-build-claude/Tests/CMakeFiles/generate.stamp +/cmake-build-claude/Tests/FunctionalTests/CMakeFiles/generate.stamp +/cmake-build-claude/_deps/googletest-build/CMakeFiles/generate.stamp.depend +/cmake-build-claude/_deps/googletest-build/googlemock/CMakeFiles/generate.stamp.depend +/cmake-build-claude/_deps/googletest-build/googletest/CMakeFiles/generate.stamp.depend +/cmake-build-claude/_deps/googletest-subbuild/CMakeFiles/generate.stamp.depend +/cmake-build-claude/CMakeFiles/generate.stamp.depend +/cmake-build-claude/Source/CMakeFiles/generate.stamp.depend +/cmake-build-claude/Tests/CMakeFiles/generate.stamp.depend +/cmake-build-claude/Tests/FunctionalTests/CMakeFiles/generate.stamp.depend +/cmake-build-claude/_deps/googletest-subbuild/CMakeFiles/generate.stamp.list +/cmake-build-claude/CMakeFiles/generate.stamp.list +/cmake-build-claude/_deps/googletest-subbuild/CMakeFiles/3ce4ee0848a1bcad7d50e07705b14f56/generate.stamp.rule +/cmake-build-claude/CMakeFiles/090db5696957d7344b1533f722519f07/generate.stamp.rule +/cmake-build-claude/_deps/googletest-build/googletest/generated/gmock.pc +/cmake-build-claude/_deps/googletest-build/googlemock/gmock.sln +/cmake-build-claude/_deps/googletest-build/googlemock/gmock.vcxproj +/cmake-build-claude/_deps/googletest-build/googlemock/gmock.vcxproj.filters +/cmake-build-claude/_deps/googletest-build/googletest/generated/gmock_main.pc +/cmake-build-claude/_deps/googletest-build/googlemock/gmock_main.vcxproj +/cmake-build-claude/_deps/googletest-build/googlemock/gmock_main.vcxproj.filters +/cmake-build-claude/_deps/googletest-build/googletest-distribution.sln +/cmake-build-claude/_deps/googletest-subbuild/CMakeFiles/3ce4ee0848a1bcad7d50e07705b14f56/googletest-populate.rule +/cmake-build-claude/_deps/googletest-subbuild/googletest-populate.sln +/cmake-build-claude/_deps/googletest-subbuild/googletest-populate.vcxproj +/cmake-build-claude/_deps/googletest-subbuild/googletest-populate.vcxproj.filters +/cmake-build-claude/_deps/googletest-subbuild/CMakeFiles/f0f5af92941f7b8dbb7badbb3a544d12/googletest-populate-build.rule +/cmake-build-claude/_deps/googletest-subbuild/googletest-populate-prefix/tmp/googletest-populate-cfgcmd.txt +/cmake-build-claude/_deps/googletest-subbuild/CMakeFiles/eb063c0671a5c439a2f6f76ca2d388b2/googletest-populate-complete.rule +/cmake-build-claude/_deps/googletest-subbuild/CMakeFiles/f0f5af92941f7b8dbb7badbb3a544d12/googletest-populate-configure.rule +/cmake-build-claude/_deps/googletest-subbuild/CMakeFiles/f0f5af92941f7b8dbb7badbb3a544d12/googletest-populate-download.rule +/cmake-build-claude/_deps/googletest-subbuild/googletest-populate-prefix/tmp/googletest-populate-gitclone.cmake +/cmake-build-claude/_deps/googletest-subbuild/googletest-populate-prefix/src/googletest-populate-stamp/googletest-populate-gitclone-lastrun.txt +/cmake-build-claude/_deps/googletest-subbuild/googletest-populate-prefix/src/googletest-populate-stamp/googletest-populate-gitinfo.txt +/cmake-build-claude/_deps/googletest-subbuild/googletest-populate-prefix/tmp/googletest-populate-gitupdate.cmake +/cmake-build-claude/_deps/googletest-subbuild/CMakeFiles/f0f5af92941f7b8dbb7badbb3a544d12/googletest-populate-install.rule +/cmake-build-claude/_deps/googletest-subbuild/CMakeFiles/f0f5af92941f7b8dbb7badbb3a544d12/googletest-populate-mkdir.rule +/cmake-build-claude/_deps/googletest-subbuild/googletest-populate-prefix/tmp/googletest-populate-mkdirs.cmake +/cmake-build-claude/_deps/googletest-subbuild/CMakeFiles/f0f5af92941f7b8dbb7badbb3a544d12/googletest-populate-patch.rule +/cmake-build-claude/_deps/googletest-subbuild/googletest-populate-prefix/src/googletest-populate-stamp/googletest-populate-patch-info.txt +/cmake-build-claude/_deps/googletest-subbuild/CMakeFiles/f0f5af92941f7b8dbb7badbb3a544d12/googletest-populate-test.rule +/cmake-build-claude/_deps/googletest-subbuild/CMakeFiles/f0f5af92941f7b8dbb7badbb3a544d12/googletest-populate-update.rule +/cmake-build-claude/_deps/googletest-subbuild/googletest-populate-prefix/src/googletest-populate-stamp/googletest-populate-update-info.txt +/cmake-build-claude/_deps/googletest-build/googletest/generated/gtest.pc +/cmake-build-claude/_deps/googletest-build/googletest/gtest.sln +/cmake-build-claude/_deps/googletest-build/googletest/gtest.vcxproj +/cmake-build-claude/_deps/googletest-build/googletest/gtest.vcxproj.filters +/cmake-build-claude/_deps/googletest-build/googletest/generated/gtest_main.pc +/cmake-build-claude/_deps/googletest-build/googletest/gtest_main.vcxproj +/cmake-build-claude/_deps/googletest-build/googletest/gtest_main.vcxproj.filters +/cmake-build-claude/_deps/googletest-build/googletest/generated/GTestConfig.cmake +/cmake-build-claude/_deps/googletest-build/googletest/generated/GTestConfigVersion.cmake +/cmake-build-claude/_deps/googletest-build/googletest/CMakeFiles/Export/0c08b8e77dd885bfe55a19a9659d9fc1/GTestTargets.cmake +/cmake-build-claude/_deps/googletest-build/googletest/CMakeFiles/Export/0c08b8e77dd885bfe55a19a9659d9fc1/GTestTargets-debug.cmake +/cmake-build-claude/_deps/googletest-build/googletest/CMakeFiles/Export/0c08b8e77dd885bfe55a19a9659d9fc1/GTestTargets-minsizerel.cmake +/cmake-build-claude/_deps/googletest-build/googletest/CMakeFiles/Export/0c08b8e77dd885bfe55a19a9659d9fc1/GTestTargets-release.cmake +/cmake-build-claude/_deps/googletest-build/googletest/CMakeFiles/Export/0c08b8e77dd885bfe55a19a9659d9fc1/GTestTargets-relwithdebinfo.cmake +/hashcat_sessions.db +/cmake-build-claude/_deps/googletest-build/googlemock/INSTALL.vcxproj +/cmake-build-claude/_deps/googletest-build/googletest/INSTALL.vcxproj +/cmake-build-claude/_deps/googletest-build/INSTALL.vcxproj +/cmake-build-claude/Source/INSTALL.vcxproj +/cmake-build-claude/Tests/FunctionalTests/INSTALL.vcxproj +/cmake-build-claude/Tests/INSTALL.vcxproj +/cmake-build-claude/INSTALL.vcxproj +/cmake-build-claude/_deps/googletest-build/googlemock/INSTALL.vcxproj.filters +/cmake-build-claude/_deps/googletest-build/googletest/INSTALL.vcxproj.filters +/cmake-build-claude/_deps/googletest-build/INSTALL.vcxproj.filters +/cmake-build-claude/Source/INSTALL.vcxproj.filters +/cmake-build-claude/Tests/FunctionalTests/INSTALL.vcxproj.filters +/cmake-build-claude/Tests/INSTALL.vcxproj.filters +/cmake-build-claude/INSTALL.vcxproj.filters +/cmake-build-claude/CMakeFiles/5b26066ca47612a6ffb2129f7a2b906b/INSTALL_force.rule +/cmake-build-claude/CMakeFiles/9df0d25c3fa44b64adb1e9f385b73dc7/INSTALL_force.rule +/cmake-build-claude/CMakeFiles/090db5696957d7344b1533f722519f07/INSTALL_force.rule +/cmake-build-claude/CMakeFiles/94b27cd4ee1b5abaafa1ebccf94eab43/INSTALL_force.rule +/cmake-build-claude/CMakeFiles/d3d2a362a1b9da1e8967d3a2ed14592b/INSTALL_force.rule +/cmake-build-claude/CMakeFiles/e468e9cc5c24f40831c9b6af1416da43/INSTALL_force.rule +/cmake-build-claude/CMakeFiles/e7555a16e5476792e82f5929305f9d82/INSTALL_force.rule +/cmake-build-claude/_deps/googletest-subbuild/CMakeFiles/InstallScripts.json +/cmake-build-claude/CMakeFiles/InstallScripts.json +/cmake-build-claude/_deps/googletest-subbuild/CMakeFiles/googletest-populate.dir/Labels.json +/cmake-build-claude/_deps/googletest-subbuild/CMakeFiles/googletest-populate.dir/Labels.txt +/cmake-build-claude/Osiris.sln +/cmake-build-claude/Source/Osiris.vcxproj +/cmake-build-claude/Source/Osiris.vcxproj.filters +/cmake-build-claude/_deps/googletest-build/googlemock/RUN_TESTS.vcxproj +/cmake-build-claude/_deps/googletest-build/googletest/RUN_TESTS.vcxproj +/cmake-build-claude/_deps/googletest-build/RUN_TESTS.vcxproj +/cmake-build-claude/Source/RUN_TESTS.vcxproj +/cmake-build-claude/Tests/FunctionalTests/RUN_TESTS.vcxproj +/cmake-build-claude/Tests/RUN_TESTS.vcxproj +/cmake-build-claude/RUN_TESTS.vcxproj +/cmake-build-claude/_deps/googletest-build/googlemock/RUN_TESTS.vcxproj.filters +/cmake-build-claude/_deps/googletest-build/googletest/RUN_TESTS.vcxproj.filters +/cmake-build-claude/_deps/googletest-build/RUN_TESTS.vcxproj.filters +/cmake-build-claude/Source/RUN_TESTS.vcxproj.filters +/cmake-build-claude/Tests/FunctionalTests/RUN_TESTS.vcxproj.filters +/cmake-build-claude/Tests/RUN_TESTS.vcxproj.filters +/cmake-build-claude/RUN_TESTS.vcxproj.filters +/cmake-build-claude/CMakeFiles/5b26066ca47612a6ffb2129f7a2b906b/RUN_TESTS_force.rule +/cmake-build-claude/CMakeFiles/9df0d25c3fa44b64adb1e9f385b73dc7/RUN_TESTS_force.rule +/cmake-build-claude/CMakeFiles/090db5696957d7344b1533f722519f07/RUN_TESTS_force.rule +/cmake-build-claude/CMakeFiles/94b27cd4ee1b5abaafa1ebccf94eab43/RUN_TESTS_force.rule +/cmake-build-claude/CMakeFiles/d3d2a362a1b9da1e8967d3a2ed14592b/RUN_TESTS_force.rule +/cmake-build-claude/CMakeFiles/e468e9cc5c24f40831c9b6af1416da43/RUN_TESTS_force.rule +/cmake-build-claude/CMakeFiles/e7555a16e5476792e82f5929305f9d82/RUN_TESTS_force.rule +/cmake-build-claude/_deps/googletest-subbuild/CMakeFiles/TargetDirectories.txt +/cmake-build-claude/CMakeFiles/TargetDirectories.txt +/cmake-build-claude/_deps/googletest-subbuild/CMakeFiles/4.2.2/VCTargetsPath.txt +/cmake-build-claude/CMakeFiles/4.2.2/VCTargetsPath.txt +/cmake-build-claude/_deps/googletest-subbuild/CMakeFiles/4.2.2/VCTargetsPath.vcxproj +/cmake-build-claude/CMakeFiles/4.2.2/VCTargetsPath.vcxproj +/cmake-build-claude/_deps/googletest-subbuild/ZERO_CHECK.vcxproj +/cmake-build-claude/ZERO_CHECK.vcxproj +/cmake-build-claude/_deps/googletest-subbuild/ZERO_CHECK.vcxproj.filters +/cmake-build-claude/ZERO_CHECK.vcxproj.filters +/cmake-build-claude/Tests/FunctionalTests/FunctionalTests[1]_include.cmake +/cmake-build-claude/Tests/FunctionalTests/FunctionalTests[1]_tests.cmake +/cmake-build-claude/Tests/FunctionalTests/FunctionalTests[1]_include.cmake +/cmake-build-claude/Tests/FunctionalTests/FunctionalTests[1]_tests.cmake diff --git a/Source/Features/Common/InWorldPanels.h b/Source/Features/Common/InWorldPanels.h index a3ae137615b..50cc713351e 100644 --- a/Source/Features/Common/InWorldPanels.h +++ b/Source/Features/Common/InWorldPanels.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +45,15 @@ class InWorldPanels { return newPanel.template as(hookContext.template make().nextEntry()); } + [[nodiscard]] decltype(auto) getNextPlayerNamePanel() const noexcept + { + if (auto&& existingPanel = getNextExistingPanel(state().playerNamePanelListHead, perHookState().lastUsedPlayerNamePanelIndex)) + return existingPanel.template as(state()); + auto&& newPanel = hookContext.template make().createPlayerNamePanel(getOrCreateContainerPanel()); + registerNewPanel(state().playerNamePanelListHead, perHookState().lastUsedPlayerNamePanelIndex); + return newPanel.template as(state()); + } + template [[nodiscard]] decltype(auto) getNextSoundVisualizationPanel() const noexcept { @@ -57,6 +67,7 @@ class InWorldPanels { void hideUnusedPanels() const noexcept { hideUnusedPanels(state().playerInfoPanelListHead, perHookState().lastUsedPlayerInfoPanelIndex); + hideUnusedPanels(state().playerNamePanelListHead, perHookState().lastUsedPlayerNamePanelIndex); for (std::size_t i = 0; i < SoundVisualizationPanelTypes::size(); ++i) hideUnusedPanels(state().soundVisualizationPanelListHeads[i], perHookState().lastUsedSoundVisualizationPanelIndexes[i]); } diff --git a/Source/Features/Common/InWorldPanelsPerHookState.h b/Source/Features/Common/InWorldPanelsPerHookState.h index 2c2b47c3332..47dfb51a48a 100644 --- a/Source/Features/Common/InWorldPanelsPerHookState.h +++ b/Source/Features/Common/InWorldPanelsPerHookState.h @@ -6,5 +6,6 @@ struct InWorldPanelsPerHookState { InWorldPanelIndex lastUsedPlayerInfoPanelIndex{}; + InWorldPanelIndex lastUsedPlayerNamePanelIndex{}; std::array lastUsedSoundVisualizationPanelIndexes{}; }; diff --git a/Source/Features/Common/InWorldPanelsState.h b/Source/Features/Common/InWorldPanelsState.h index 3245dc775b3..e57139ce4e6 100644 --- a/Source/Features/Common/InWorldPanelsState.h +++ b/Source/Features/Common/InWorldPanelsState.h @@ -13,6 +13,7 @@ struct InWorldPanelsState { cs2::PanelHandle containerPanelHandle; DynamicArray panelList; InWorldPanelIndex playerInfoPanelListHead{}; + InWorldPanelIndex playerNamePanelListHead{}; std::array soundVisualizationPanelListHeads{}; void reset() noexcept @@ -20,6 +21,7 @@ struct InWorldPanelsState { containerPanelHandle = {}; panelList.clear(); playerInfoPanelListHead = {}; + playerNamePanelListHead = {}; soundVisualizationPanelListHeads = {}; } }; diff --git a/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorld.h b/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorld.h index 63014418c36..80d57f8e128 100644 --- a/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorld.h +++ b/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorld.h @@ -32,9 +32,17 @@ class PlayerInfoInWorld { if (!positionInClipSpace.onScreen()) return; - auto&& playerInformationPanel = hookContext.template make().getNextPlayerInfoPanel(); + auto&& inWorldPanels = hookContext.template make(); + auto&& playerInformationPanel = inWorldPanels.getNextPlayerInfoPanel(); playerInformationPanel.drawPlayerInfo(playerPawn); playerInformationPanel.updatePosition(absOrigin.value()); + + constexpr auto kPlayerNameHeightOffset{74.0f}; + auto playerNameOrigin = absOrigin.value(); + playerNameOrigin.z += kPlayerNameHeightOffset; + auto&& playerNamePanel = inWorldPanels.getNextPlayerNamePanel(); + playerNamePanel.update(playerPawn); + playerNamePanel.updatePosition(playerNameOrigin); } private: diff --git a/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorldPanelFactory.h b/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorldPanelFactory.h index b83f480c86e..880e6fb1b5e 100644 --- a/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorldPanelFactory.h +++ b/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorldPanelFactory.h @@ -60,14 +60,29 @@ class PlayerInfoInWorldPanelFactory { void createPanel(std::type_identity>, auto&& containerPanel) const noexcept { - using namespace player_name_panel_params; + createPlayerNamePanel(containerPanel); + } - auto&& label = hookContext.panelFactory().createLabelPanel(containerPanel).uiPanel(); - label.setFont(kFont); - label.setAlign(kAlignment); - label.setMargin(kMargin); - label.setTextShadow(kShadowParams); - label.setColor(kColor); + [[nodiscard]] decltype(auto) createPlayerNamePanel(auto&& parentPanel) const noexcept + { + using namespace player_name_panel_params::container_panel_params; + + auto&& container = hookContext.panelFactory().createPanel(parentPanel).uiPanel(); + container.setWidth(kWidth); + container.setHeight(kHeight); + container.setPosition(kPositionX, kPositionY); + container.setTransformOrigin(kTransformOriginX, kTransformOriginY); + + { + using namespace player_name_panel_params::label_params; + auto&& label = hookContext.panelFactory().createLabelPanel(container).uiPanel(); + label.setFont(kFont); + label.setAlign(kAlignment); + label.setTextShadow(kShadowParams); + label.setColor(kColor); + } + + return utils::lvalue(container); } void createPanel(std::type_identity>, auto&& containerPanel) const noexcept diff --git a/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoPanelTypes.h b/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoPanelTypes.h index b950c372bfb..47e5082b209 100644 --- a/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoPanelTypes.h +++ b/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoPanelTypes.h @@ -4,7 +4,6 @@ #include "ActiveWeaponAmmo/PlayerActiveWeaponAmmoPanel.h" #include "PlayerHealth/PlayerHealthPanel.h" -#include "PlayerNamePanel.h" #include "PlayerPositionArrow/PlayerPositionArrowPanel.h" #include "PlayerStateIcons/PlayerStateIconsPanel.h" #include "PlayerWeaponIcon/PlayerWeaponIconPanel.h" @@ -12,7 +11,6 @@ template using PlayerInfoPanelTypes = std::tuple< PlayerPositionArrowPanel, - PlayerNamePanel, PlayerHealthPanel, PlayerWeaponIconPanel, PlayerActiveWeaponAmmoPanel, diff --git a/Source/Features/Visuals/PlayerInfoInWorld/PlayerNamePanel.h b/Source/Features/Visuals/PlayerInfoInWorld/PlayerNamePanel.h index f4e2c7eac87..fd68c6aa4b4 100644 --- a/Source/Features/Visuals/PlayerInfoInWorld/PlayerNamePanel.h +++ b/Source/Features/Visuals/PlayerInfoInWorld/PlayerNamePanel.h @@ -1,11 +1,16 @@ #pragma once +#include #include +#include #include #include #include +#include #include +#include +#include #include "PlayerNamePanelParams.h" @@ -28,6 +33,21 @@ class PlayerNamePanelContext { return _hookContext.template make(_panel); } + [[nodiscard]] decltype(auto) toClipSpace(const cs2::Vector& worldPosition) const noexcept + { + return _hookContext.template make().toClipSpace(worldPosition); + } + + [[nodiscard]] float getFovScale() const noexcept + { + return ViewToProjectionMatrix{_hookContext}.getFovScale(); + } + + [[nodiscard]] decltype(auto) panoramaTransformFactory() const noexcept + { + return _hookContext.panoramaTransformFactory(); + } + private: HookContext& _hookContext; cs2::CUIPanel* _panel; @@ -56,7 +76,27 @@ class PlayerNamePanel { } context.panel().setVisible(true); - context.panel().clientPanel().template as().setText(playerName); + context.panel().children()[0].clientPanel().template as().setText(playerName); + } + + void updatePosition(const cs2::Vector& origin) const noexcept + { + const auto positionInClipSpace = context.toClipSpace(origin); + if (!positionInClipSpace.onScreen()) { + context.panel().setVisible(false); + return; + } + + context.panel().setZIndex(-positionInClipSpace.z); + + constexpr auto kMaxScale{1.0f}; + const auto scale = std::clamp(500.0f / (positionInClipSpace.z / context.getFovScale() + 400.0f), 0.4f, kMaxScale); + const auto deviceCoordinates = positionInClipSpace.toNormalizedDeviceCoordinates(); + + PanoramaTransformations{ + context.panoramaTransformFactory().scale(scale), + context.panoramaTransformFactory().translate(deviceCoordinates.getX(), deviceCoordinates.getY()) + }.applyTo(context.panel()); } private: diff --git a/Source/Features/Visuals/PlayerInfoInWorld/PlayerNamePanelParams.h b/Source/Features/Visuals/PlayerInfoInWorld/PlayerNamePanelParams.h index 5e73b2a0509..a191af9015e 100644 --- a/Source/Features/Visuals/PlayerInfoInWorld/PlayerNamePanelParams.h +++ b/Source/Features/Visuals/PlayerInfoInWorld/PlayerNamePanelParams.h @@ -8,7 +8,17 @@ #include #include -namespace player_name_panel_params +namespace player_name_panel_params::container_panel_params +{ + static constexpr auto kWidth = cs2::CUILength::pixels(256); + static constexpr auto kHeight = cs2::CUILength::pixels(32); + static constexpr auto kPositionX = cs2::CUILength::pixels(-kWidth.m_flValue * 0.5f); + static constexpr auto kPositionY = cs2::CUILength::pixels(-kHeight.m_flValue); + static constexpr auto kTransformOriginX = cs2::CUILength::percent(50); + static constexpr auto kTransformOriginY = cs2::CUILength::percent(100); +} + +namespace player_name_panel_params::label_params { static constexpr auto kFont = PanelFontParams{ .fontFamily = "Stratum2, 'Arial Unicode MS'", @@ -17,9 +27,8 @@ namespace player_name_panel_params }; static constexpr auto kAlignment = PanelAlignmentParams{ .horizontalAlignment = cs2::k_EHorizontalAlignmentCenter, - .verticalAlignment = cs2::k_EVerticalAlignmentTop + .verticalAlignment = cs2::k_EVerticalAlignmentCenter }; - static constexpr auto kMargin = PanelMarginParams{.marginBottom = cs2::CUILength::pixels(1)}; static constexpr auto kColor = cs2::kColorWhite; static constexpr auto kShadowParams = PanelShadowParams{ .horizontalOffset{cs2::CUILength::pixels(0)}, diff --git a/Source/GameClient/Entities/PlayerController.h b/Source/GameClient/Entities/PlayerController.h index 01fe1bc82d7..ceac7cfa192 100644 --- a/Source/GameClient/Entities/PlayerController.h +++ b/Source/GameClient/Entities/PlayerController.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -49,18 +50,56 @@ class PlayerController { [[nodiscard]] decltype(auto) playerColorIndex() const noexcept { - return hookContext.patternSearchResults().template get().of(playerControllerPointer).toOptional(); + return offsetToPlayerColor().of(playerControllerPointer).toOptional(); } [[nodiscard]] const char* getName() const noexcept { - constexpr auto kOffsetFromPlayerColorToSanitizedName{0x18}; - if (const auto playerColor = hookContext.patternSearchResults().template get().of(playerControllerPointer).get()) - return reinterpret_cast(reinterpret_cast(playerColor) + kOffsetFromPlayerColorToSanitizedName)->m_pString; + if (const auto name = playerName(); name && *name) + return name; + if (const auto name = sanitizedPlayerName(); name && *name) + return name; return nullptr; } private: + [[nodiscard]] const char* sanitizedPlayerName() const noexcept + { + constexpr auto kOffsetFromPlayerColorToSanitizedName{0x18}; + if (const auto playerColor = offsetToPlayerColor().of(playerControllerPointer).get()) { + if (const auto name = reinterpret_cast(reinterpret_cast(playerColor) + kOffsetFromPlayerColorToSanitizedName)->m_pString) + return name; + } + + // Fallback from current public schema (CCSPlayerController::m_sSanitizedPlayerName). + constexpr auto kFallbackOffsetToSanitizedPlayerName{std::int32_t{0x860}}; + if (playerControllerPointer != nullptr) { + if (const auto name = reinterpret_cast(reinterpret_cast(playerControllerPointer) + kFallbackOffsetToSanitizedPlayerName)->m_pString) + return name; + } + return nullptr; + } + + [[nodiscard]] const char* playerName() const noexcept + { + // Fallback from current public schema (CBasePlayerController::m_iszPlayerName). + constexpr auto kFallbackOffsetToPlayerName{std::int32_t{0x6F4}}; + if (playerControllerPointer != nullptr) + return reinterpret_cast(reinterpret_cast(playerControllerPointer) + kFallbackOffsetToPlayerName); + return nullptr; + } + + [[nodiscard]] auto offsetToPlayerColor() const noexcept + { + const auto offset = hookContext.patternSearchResults().template get(); + if (offset) + return offset; + + // Fallback for game builds where the color signature is out-of-date. + constexpr auto kFallbackOffsetToPlayerColor{std::int32_t{0x848}}; + return decltype(offset){kFallbackOffsetToPlayerColor}; + } + HookContext& hookContext; cs2::CCSPlayerController* playerControllerPointer; }; diff --git a/Source/GameClient/Panorama/UiItem3dPanel.h b/Source/GameClient/Panorama/UiItem3dPanel.h index 77bb8ae979b..22da4feb9db 100644 --- a/Source/GameClient/Panorama/UiItem3dPanel.h +++ b/Source/GameClient/Panorama/UiItem3dPanel.h @@ -16,9 +16,7 @@ class UiItem3dPanel { void createItem(cs2::ItemId itemId) const noexcept { - forceItemEntityToBeCreated(); setItemId(itemId); - assert(unknownFieldHasDefaultValue()); } void startWeaponLookAt() const noexcept @@ -27,34 +25,12 @@ class UiItem3dPanel { } private: - void forceItemEntityToBeCreated() const noexcept - { - assert(unknownFieldHasDefaultValue()); - unknownField() = 0; - } - void setItemId(cs2::ItemId itemId) const noexcept { if (const auto setItemItemIdFunction = hookContext.patternSearchResults().template get(); setItemItemIdFunction && uiItem3dPanel) setItemItemIdFunction(uiItem3dPanel, itemId, ""); } - [[nodiscard]] [[maybe_unused]] bool unknownFieldHasDefaultValue() const noexcept - { - constexpr auto kDefaultValue = -1; - return unknownField().valueOr(kDefaultValue) == kDefaultValue; - } - - [[nodiscard]] decltype(auto) unknownField() const noexcept - { - return hookContext.patternSearchResults().template get().of(properties()); - } - - [[nodiscard]] decltype(auto) properties() const noexcept - { - return hookContext.patternSearchResults().template get().of(uiItem3dPanel).get(); - } - HookContext& hookContext; cs2::CUI_Item3dPanel* uiItem3dPanel; }; diff --git a/Source/MemoryPatterns/Linux/UiItem3dPanelPatternsLinux.h b/Source/MemoryPatterns/Linux/UiItem3dPanelPatternsLinux.h index 17b273fe7a2..5e349046bce 100644 --- a/Source/MemoryPatterns/Linux/UiItem3dPanelPatternsLinux.h +++ b/Source/MemoryPatterns/Linux/UiItem3dPanelPatternsLinux.h @@ -8,8 +8,6 @@ struct UiItem3dPanelPatterns { { return clientPatterns .template addPattern() - .template addPattern() - .template addPattern() - .template addPattern(); + .template addPattern(); } }; diff --git a/Source/MemoryPatterns/Windows/PlayerControllerPatternsWindows.h b/Source/MemoryPatterns/Windows/PlayerControllerPatternsWindows.h index 811f3e74fc3..ef41b950ecc 100644 --- a/Source/MemoryPatterns/Windows/PlayerControllerPatternsWindows.h +++ b/Source/MemoryPatterns/Windows/PlayerControllerPatternsWindows.h @@ -8,6 +8,6 @@ struct PlayerControllerPatterns { { return clientPatterns .template addPattern() - .template addPattern(); + .template addPattern(); } }; diff --git a/Source/MemoryPatterns/Windows/UiItem3dPanelPatternsWindows.h b/Source/MemoryPatterns/Windows/UiItem3dPanelPatternsWindows.h index 3e43013667c..c9c41bb4518 100644 --- a/Source/MemoryPatterns/Windows/UiItem3dPanelPatternsWindows.h +++ b/Source/MemoryPatterns/Windows/UiItem3dPanelPatternsWindows.h @@ -8,8 +8,6 @@ struct UiItem3dPanelPatterns { { return clientPatterns .template addPattern() - .template addPattern() - .template addPattern() - .template addPattern(); + .template addPattern(); } }; From ae1e14a278531ecd015f097977a6c94b65d52da5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E8=AE=B0?= Date: Sun, 31 May 2026 22:37:33 +0800 Subject: [PATCH 6/9] Add player name display feature and pattern scan fixes --- README.md | 5 + skill-update/SKILL.md | 135 ++++++++++++++++++ .../references/pattern-hotfix-playbook.md | 32 +++++ 3 files changed, 172 insertions(+) create mode 100644 skill-update/SKILL.md create mode 100644 skill-update/references/pattern-hotfix-playbook.md diff --git a/README.md b/README.md index 7fdcfd919c1..e992050a1d8 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,11 @@ Cross-platform (Windows, Linux) game hack for **Counter-Strike 2** with GUI and ## What's new +* 31 May 2026 + * Player name now displayed above the player's head in world space (independent projection at head height) + * Fixed pattern scan failures after CS2 update (`OffsetToPlayerColor`, `UiItem3dPanel`) + * Added multi-level fallback for player name lookup to improve robustness across game updates + * 04 November 2025 * Improved smoothness of "Player Info in World" on moving players diff --git a/skill-update/SKILL.md b/skill-update/SKILL.md new file mode 100644 index 00000000000..cfed518f9ad --- /dev/null +++ b/skill-update/SKILL.md @@ -0,0 +1,135 @@ +# osiris-cs2-pattern-hotfix + +## Purpose +Diagnose and fix CS2 game-update breakage in the Osiris ESP project: +1. Pattern scan failures ("Failed to find pattern …") +2. Feature regressions after CS2 updates (name display, panel rendering, etc.) + +Workspace: `D:\CLionProjects\Osiris` +Build script: `D:\CLionProjects\Osiris\build-release.ps1` + +--- + +## Quick-start + +### A. Pattern errors on inject +``` +Failed to find pattern +``` +→ Run `scripts/map-pattern-errors.ps1` with the log line, identify the pattern type, + update the Windows/Linux pattern file, rebuild DLL. + +### B. Feature not showing (no crash, no pattern error) +→ Check panel creation chain. See "Head-top name display" section below. + +--- + +## Key code paths + +``` +ViewRenderHook_onRenderStart (EntryPoints.h) + └─ RenderingHookEntityLoop::run() + └─ PlayerInfoInWorld::drawPlayerInformation(playerPawn) + ├─ InWorldPanels::getNextPlayerInfoPanel() ← feet panel (health/weapon/ammo) + │ └─ PlayerInfoPanel::drawPlayerInfo() + │ └─ PlayerController::getName() ← name data + └─ InWorldPanels::getNextPlayerNamePanel() ← head panel (name only) + └─ PlayerNamePanel::update() + └─ PlayerNamePanel::updatePosition() ← world-to-screen at absOrigin.z+74 +``` + +### PlayerController::getName() — multi-level fallback +```cpp +// 1. m_iszPlayerName at offset 0x6F4 (fixed char[] — try first) +// 2. playerColor+0x18 sanitized name (via OffsetToPlayerColor pattern) +// 3. m_sSanitizedPlayerName at 0x860 (fallback offset) +``` +If all three return null/empty → panel hidden. Verify offsets against current CS2 dump. + +--- + +## Head-top name display (implemented 2026-05-31) + +### Architecture +- `PlayerNamePanel` is **NOT** in `PlayerInfoPanelTypes` (feet panel). +- It has its **own panel pool** in `InWorldPanels` (`playerNamePanelListHead`). +- World position: `absOrigin.z + 74.0f` (≈ head height above feet). +- Same scale+translate transform as `PlayerInfoPanel`. + +### Panel creation pattern — CRITICAL +Use **container + label child**, NOT bare label: +```cpp +// PlayerInfoInWorldPanelFactory.h :: createPlayerNamePanel() +auto&& container = panelFactory.createPanel(parentPanel).uiPanel(); +container.setWidth(kWidth); // 256px +container.setHeight(kHeight); // 32px +container.setPosition(kPositionX, kPositionY); // (-128, -32) +container.setTransformOrigin(kTransformOriginX, kTransformOriginY); // (50%, 100%) + +auto&& label = panelFactory.createLabelPanel(container).uiPanel(); +label.setFont(kFont); +label.setAlign(kAlignment); +label.setTextShadow(kShadowParams); +label.setColor(kColor); +``` + +Text update: +```cpp +// PlayerNamePanel::update() +context.panel().children()[0].clientPanel().template as().setText(playerName); +``` + +> ⚠️ A bare label as direct outer-container child does NOT render text reliably. +> Always use the container+child pattern (same as PlayerActiveWeaponAmmoPanel). + +### Transform params (PlayerNamePanelParams.h) +``` +container: width=256px, height=32px, pos=(-128,-32), transformOrigin=(50%,100%) +label: font=Stratum2 bold 20px, align=center/center, white text, black shadow +``` + +--- + +## Pattern fix workflow + +### 1. Identify pattern → source file mapping +| Pattern bytes | File | Symbol | +|---|---|---| +| `E8????84 C0 74?41 8B ?????` | PlayerControllerPatternsWindows.h | OffsetToPlayerColor | +| `7E ? 75 28` | UiItem3dPanelPatternsWindows.h | OffsetToItem3dPanelUnknownField | +| `41 39 44 24? 44` | UiItem3dPanelPatternsWindows.h | (removed — non-critical) | + +### 2. Fix options +- **Update pattern**: find new byte sequence in current CS2 binary. +- **Add offset fallback**: hardcode known-good offset as fallback when pattern fails. +- **Remove dependency**: if field is non-critical, remove the pattern entirely. + +### 3. Build +```powershell +# From project root: +.\build-release.ps1 +# Output: D:\CLionProjects\Osiris\cmake-build-release-visual-studio\Source\Release\Osiris.dll +# Also copied to Desktop automatically. +``` + +### 4. Commit & push +```powershell +cd D:\CLionProjects\Osiris +# Delete .git/index.lock if present (CLion may leave it) +# Then: +git add +git commit -m "Fix: " +git push +``` + +--- + +## Common pitfalls + +| Symptom | Cause | Fix | +|---|---|---| +| Name not showing, no error | Bare label as panel (can't setText) | Use container+label child pattern | +| Name not showing, no error | getName() returns null | Check offsets 0x6F4 / 0x860 / OffsetToPlayerColor | +| Pattern error on inject | CS2 updated, bytes moved | Update pattern bytes or add offset fallback | +| `index.lock` blocks git | CLion/Git GUI holding lock | Delete `.git\index.lock`, run git from PowerShell | +| Panel visible but at wrong height | absOrigin.z offset wrong | Adjust `kPlayerNameHeightOffset` in PlayerInfoInWorld.h (currently 74.0f) | diff --git a/skill-update/references/pattern-hotfix-playbook.md b/skill-update/references/pattern-hotfix-playbook.md new file mode 100644 index 00000000000..6a486635f40 --- /dev/null +++ b/skill-update/references/pattern-hotfix-playbook.md @@ -0,0 +1,32 @@ +# Pattern Hotfix Playbook + +## Session history + +### 2026-05-18 — Initial name display feature +- Added `PlayerNamePanel` to `PlayerInfoPanelTypes` (feet panel, flow layout) +- Added `PlayerController::getName()` reading `m_iszPlayerName` at 0x6F4 +- Pattern errors: `OffsetToPlayerColor` scan failing → added offset fallback 0x848 + +### 2026-05-18 — Pattern scan fixes +- `E8????84 C0 74?41 8B ?????` (OffsetToPlayerColor): updated Windows pattern +- `7E ? 75 28` (UiItem3dPanel): updated Windows pattern +- `41 39 44 24? 44` (UiItem3dPanel unknown field): removed dependency entirely + +### 2026-05-18 — getName() multi-level fallback +- Order: `m_iszPlayerName(0x6F4)` → `playerColor+0x18` → `0x860 fallback` +- Reason: single pattern dependency too fragile; name must show even if pattern drifts + +### 2026-05-31 — Head-top name display +- Moved `PlayerNamePanel` OUT of `PlayerInfoPanelTypes` +- Added separate panel pool (`playerNamePanelListHead` in `InWorldPanelsState`) +- World-space projection at `absOrigin.z + 74.0f` +- **Fix**: changed bare-label creation to container+label child + (bare label's `clientPanel().as()` unreliable for standalone panels) +- Adjusted height offset: `74.0f` units above feet ≈ CS2 head height + +## Offset reference (CS2, as of 2026-05-31) +| Field | Class | Offset | +|---|---|---| +| m_iszPlayerName | CBasePlayerController | 0x6F4 | +| m_sSanitizedPlayerName | CCSPlayerController | 0x860 | +| m_iPlayerColor | CCSPlayerController | 0x848 (fallback) | From e201c7b39d6cdde0379be8ae67e9db78efaf16f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E8=AE=B0?= Date: Mon, 1 Jun 2026 14:00:09 +0800 Subject: [PATCH 7/9] Address review feedback for in-world player name - Improve readability: larger font, black weight, stronger shadow - Prefer sanitized name to match scoreboard/killfeed (e.g. bot names) - Render name below health via container child, reverting the independent world-space projection - Add name color option (white / team color / health-based) Co-Authored-By: Claude Opus 4.7 --- Source/Config/ConfigSchema.h | 5 +- Source/Config/ConfigVariableTypes.h | 1 + Source/Features/Common/InWorldPanels.h | 11 --- .../Common/InWorldPanelsPerHookState.h | 1 - Source/Features/Common/InWorldPanelsState.h | 2 - .../PlayerInfoInWorld/PlayerInfoInWorld.h | 7 -- .../PlayerInfoInWorldConfigVariables.h | 7 ++ .../PlayerInfoInWorldPanelFactory.h | 29 ++------ .../PlayerInfoPanelCacheEntry.h | 1 + .../PlayerInfoInWorld/PlayerInfoPanelTypes.h | 2 + .../PlayerInfoInWorld/PlayerNamePanel.h | 71 ++++++++++--------- .../PlayerInfoInWorld/PlayerNamePanelParams.h | 23 ++---- Source/GameClient/Entities/PlayerController.h | 5 +- Source/UI/Panorama/CreateGUI.js | 2 + ...eColorModeDropdownSelectionChangeHandler.h | 28 ++++++++ Source/UI/Panorama/VisualsTab.h | 3 + Tests/Configs/config_current.cfg | 2 +- .../Config/ConfigCompatibilityTests.cpp | 1 + 18 files changed, 104 insertions(+), 97 deletions(-) create mode 100644 Source/UI/Panorama/Tabs/VisualsTab/PlayerInfoInWorldPlayerNameColorModeDropdownSelectionChangeHandler.h diff --git a/Source/Config/ConfigSchema.h b/Source/Config/ConfigSchema.h index 603263bc434..d1fb6240e72 100644 --- a/Source/Config/ConfigSchema.h +++ b/Source/Config/ConfigSchema.h @@ -145,7 +145,10 @@ class ConfigSchema { configConversion.uint(u8"ColorMode", loadVariable(), saveVariable()); configConversion.endObject(); - configConversion.boolean(u8"PlayerName", loadVariable(), saveVariable()); + configConversion.beginObject(u8"PlayerName"); + configConversion.boolean(u8"Enabled", loadVariable(), saveVariable()); + configConversion.uint(u8"ColorMode", loadVariable(), saveVariable()); + configConversion.endObject(); configConversion.beginObject(u8"Health"); configConversion.boolean(u8"Enabled", loadVariable(), saveVariable()); diff --git a/Source/Config/ConfigVariableTypes.h b/Source/Config/ConfigVariableTypes.h index dd33f947eda..8331f6ffb78 100644 --- a/Source/Config/ConfigVariableTypes.h +++ b/Source/Config/ConfigVariableTypes.h @@ -85,6 +85,7 @@ using ConfigVariableTypes = TypeList< player_info_vars::PlayerPositionArrowEnabled, player_info_vars::PlayerPositionArrowColorMode, player_info_vars::PlayerNameEnabled, + player_info_vars::PlayerNameColorMode, player_info_vars::PlayerHealthEnabled, player_info_vars::PlayerHealthColorMode, player_info_vars::ActiveWeaponIconEnabled, diff --git a/Source/Features/Common/InWorldPanels.h b/Source/Features/Common/InWorldPanels.h index 50cc713351e..a3ae137615b 100644 --- a/Source/Features/Common/InWorldPanels.h +++ b/Source/Features/Common/InWorldPanels.h @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -45,15 +44,6 @@ class InWorldPanels { return newPanel.template as(hookContext.template make().nextEntry()); } - [[nodiscard]] decltype(auto) getNextPlayerNamePanel() const noexcept - { - if (auto&& existingPanel = getNextExistingPanel(state().playerNamePanelListHead, perHookState().lastUsedPlayerNamePanelIndex)) - return existingPanel.template as(state()); - auto&& newPanel = hookContext.template make().createPlayerNamePanel(getOrCreateContainerPanel()); - registerNewPanel(state().playerNamePanelListHead, perHookState().lastUsedPlayerNamePanelIndex); - return newPanel.template as(state()); - } - template [[nodiscard]] decltype(auto) getNextSoundVisualizationPanel() const noexcept { @@ -67,7 +57,6 @@ class InWorldPanels { void hideUnusedPanels() const noexcept { hideUnusedPanels(state().playerInfoPanelListHead, perHookState().lastUsedPlayerInfoPanelIndex); - hideUnusedPanels(state().playerNamePanelListHead, perHookState().lastUsedPlayerNamePanelIndex); for (std::size_t i = 0; i < SoundVisualizationPanelTypes::size(); ++i) hideUnusedPanels(state().soundVisualizationPanelListHeads[i], perHookState().lastUsedSoundVisualizationPanelIndexes[i]); } diff --git a/Source/Features/Common/InWorldPanelsPerHookState.h b/Source/Features/Common/InWorldPanelsPerHookState.h index 47dfb51a48a..2c2b47c3332 100644 --- a/Source/Features/Common/InWorldPanelsPerHookState.h +++ b/Source/Features/Common/InWorldPanelsPerHookState.h @@ -6,6 +6,5 @@ struct InWorldPanelsPerHookState { InWorldPanelIndex lastUsedPlayerInfoPanelIndex{}; - InWorldPanelIndex lastUsedPlayerNamePanelIndex{}; std::array lastUsedSoundVisualizationPanelIndexes{}; }; diff --git a/Source/Features/Common/InWorldPanelsState.h b/Source/Features/Common/InWorldPanelsState.h index e57139ce4e6..3245dc775b3 100644 --- a/Source/Features/Common/InWorldPanelsState.h +++ b/Source/Features/Common/InWorldPanelsState.h @@ -13,7 +13,6 @@ struct InWorldPanelsState { cs2::PanelHandle containerPanelHandle; DynamicArray panelList; InWorldPanelIndex playerInfoPanelListHead{}; - InWorldPanelIndex playerNamePanelListHead{}; std::array soundVisualizationPanelListHeads{}; void reset() noexcept @@ -21,7 +20,6 @@ struct InWorldPanelsState { containerPanelHandle = {}; panelList.clear(); playerInfoPanelListHead = {}; - playerNamePanelListHead = {}; soundVisualizationPanelListHeads = {}; } }; diff --git a/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorld.h b/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorld.h index 80d57f8e128..e192fe9f3bc 100644 --- a/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorld.h +++ b/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorld.h @@ -36,13 +36,6 @@ class PlayerInfoInWorld { auto&& playerInformationPanel = inWorldPanels.getNextPlayerInfoPanel(); playerInformationPanel.drawPlayerInfo(playerPawn); playerInformationPanel.updatePosition(absOrigin.value()); - - constexpr auto kPlayerNameHeightOffset{74.0f}; - auto playerNameOrigin = absOrigin.value(); - playerNameOrigin.z += kPlayerNameHeightOffset; - auto&& playerNamePanel = inWorldPanels.getNextPlayerNamePanel(); - playerNamePanel.update(playerPawn); - playerNamePanel.updatePosition(playerNameOrigin); } private: diff --git a/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorldConfigVariables.h b/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorldConfigVariables.h index 607c6b71286..0b092fac2f2 100644 --- a/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorldConfigVariables.h +++ b/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorldConfigVariables.h @@ -8,6 +8,12 @@ enum class PlayerHealthTextColor : std::uint8_t { White }; +enum class PlayerNameColorType : std::uint8_t { + White, + TeamColor, + HealthBased +}; + namespace player_info_vars { @@ -16,6 +22,7 @@ CONFIG_VARIABLE(OnlyEnemies, bool, false); CONFIG_VARIABLE(PlayerPositionArrowEnabled, bool, true); CONFIG_VARIABLE(PlayerPositionArrowColorMode, PlayerPositionArrowColorType, PlayerPositionArrowColorType::PlayerOrTeamColor); CONFIG_VARIABLE(PlayerNameEnabled, bool, true); +CONFIG_VARIABLE(PlayerNameColorMode, PlayerNameColorType, PlayerNameColorType::White); CONFIG_VARIABLE(PlayerHealthEnabled, bool, true); CONFIG_VARIABLE(PlayerHealthColorMode, PlayerHealthTextColor, PlayerHealthTextColor::HealthBased); CONFIG_VARIABLE(ActiveWeaponIconEnabled, bool, true); diff --git a/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorldPanelFactory.h b/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorldPanelFactory.h index 880e6fb1b5e..b83f480c86e 100644 --- a/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorldPanelFactory.h +++ b/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoInWorldPanelFactory.h @@ -60,29 +60,14 @@ class PlayerInfoInWorldPanelFactory { void createPanel(std::type_identity>, auto&& containerPanel) const noexcept { - createPlayerNamePanel(containerPanel); - } + using namespace player_name_panel_params; - [[nodiscard]] decltype(auto) createPlayerNamePanel(auto&& parentPanel) const noexcept - { - using namespace player_name_panel_params::container_panel_params; - - auto&& container = hookContext.panelFactory().createPanel(parentPanel).uiPanel(); - container.setWidth(kWidth); - container.setHeight(kHeight); - container.setPosition(kPositionX, kPositionY); - container.setTransformOrigin(kTransformOriginX, kTransformOriginY); - - { - using namespace player_name_panel_params::label_params; - auto&& label = hookContext.panelFactory().createLabelPanel(container).uiPanel(); - label.setFont(kFont); - label.setAlign(kAlignment); - label.setTextShadow(kShadowParams); - label.setColor(kColor); - } - - return utils::lvalue(container); + auto&& label = hookContext.panelFactory().createLabelPanel(containerPanel).uiPanel(); + label.setFont(kFont); + label.setAlign(kAlignment); + label.setMargin(kMargin); + label.setTextShadow(kShadowParams); + label.setColor(kColor); } void createPanel(std::type_identity>, auto&& containerPanel) const noexcept diff --git a/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoPanelCacheEntry.h b/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoPanelCacheEntry.h index 777c0ab448a..dbb29246c82 100644 --- a/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoPanelCacheEntry.h +++ b/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoPanelCacheEntry.h @@ -25,6 +25,7 @@ struct PlayerInfoPanelCacheEntry { }; Cached playerPositionArrowColor{cs2::Color{0, 0, 0, 0}}; + Cached playerNameColor{cs2::Color{0, 0, 0, 0}}; Cached playerHealthTextColor{cs2::Color{0, 0, 0, 0}}; Cached playerHealth{-1}; Cached activeWeaponAmmo{-1}; diff --git a/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoPanelTypes.h b/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoPanelTypes.h index 47e5082b209..2375dd494cf 100644 --- a/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoPanelTypes.h +++ b/Source/Features/Visuals/PlayerInfoInWorld/PlayerInfoPanelTypes.h @@ -4,6 +4,7 @@ #include "ActiveWeaponAmmo/PlayerActiveWeaponAmmoPanel.h" #include "PlayerHealth/PlayerHealthPanel.h" +#include "PlayerNamePanel.h" #include "PlayerPositionArrow/PlayerPositionArrowPanel.h" #include "PlayerStateIcons/PlayerStateIconsPanel.h" #include "PlayerWeaponIcon/PlayerWeaponIconPanel.h" @@ -12,6 +13,7 @@ template using PlayerInfoPanelTypes = std::tuple< PlayerPositionArrowPanel, PlayerHealthPanel, + PlayerNamePanel, PlayerWeaponIconPanel, PlayerActiveWeaponAmmoPanel, PlayerStateIconsPanel>; diff --git a/Source/Features/Visuals/PlayerInfoInWorld/PlayerNamePanel.h b/Source/Features/Visuals/PlayerInfoInWorld/PlayerNamePanel.h index fd68c6aa4b4..8f14a2edbec 100644 --- a/Source/Features/Visuals/PlayerInfoInWorld/PlayerNamePanel.h +++ b/Source/Features/Visuals/PlayerInfoInWorld/PlayerNamePanel.h @@ -1,25 +1,27 @@ #pragma once #include +#include #include -#include +#include +#include #include #include +#include #include -#include #include -#include -#include +#include -#include "PlayerNamePanelParams.h" +#include "PlayerInfoPanelCacheEntry.h" template class PlayerNamePanelContext { public: - PlayerNamePanelContext(HookContext& hookContext, cs2::CUIPanel* panel, auto&) noexcept + PlayerNamePanelContext(HookContext& hookContext, cs2::CUIPanel* panel, PlayerInfoPanelCacheEntry& cache) noexcept : _hookContext{hookContext} , _panel{panel} + , _cache{cache} { } @@ -33,24 +35,15 @@ class PlayerNamePanelContext { return _hookContext.template make(_panel); } - [[nodiscard]] decltype(auto) toClipSpace(const cs2::Vector& worldPosition) const noexcept + [[nodiscard]] auto& cache() const noexcept { - return _hookContext.template make().toClipSpace(worldPosition); - } - - [[nodiscard]] float getFovScale() const noexcept - { - return ViewToProjectionMatrix{_hookContext}.getFovScale(); - } - - [[nodiscard]] decltype(auto) panoramaTransformFactory() const noexcept - { - return _hookContext.panoramaTransformFactory(); + return _cache; } private: HookContext& _hookContext; cs2::CUIPanel* _panel; + PlayerInfoPanelCacheEntry& _cache; }; template > @@ -76,29 +69,39 @@ class PlayerNamePanel { } context.panel().setVisible(true); - context.panel().children()[0].clientPanel().template as().setText(playerName); + + if (const auto nameColor = getColor(playerPawn); context.cache().playerNameColor(nameColor)) + context.panel().setColor(nameColor); + + context.panel().clientPanel().template as().setText(playerName); } - void updatePosition(const cs2::Vector& origin) const noexcept +private: + [[nodiscard]] cs2::Color getColor(auto&& playerPawn) const noexcept { - const auto positionInClipSpace = context.toClipSpace(origin); - if (!positionInClipSpace.onScreen()) { - context.panel().setVisible(false); - return; + switch (context.config().template getVariable()) { + case PlayerNameColorType::TeamColor: return teamColor(playerPawn); + case PlayerNameColorType::HealthBased: return healthColor(playerPawn).value_or(cs2::kColorWhite); + default: return cs2::kColorWhite; } + } - context.panel().setZIndex(-positionInClipSpace.z); - - constexpr auto kMaxScale{1.0f}; - const auto scale = std::clamp(500.0f / (positionInClipSpace.z / context.getFovScale() + 400.0f), 0.4f, kMaxScale); - const auto deviceCoordinates = positionInClipSpace.toNormalizedDeviceCoordinates(); + [[nodiscard]] static cs2::Color teamColor(auto&& playerPawn) noexcept + { + switch (playerPawn.teamNumber()) { + using enum TeamNumber; + case TT: return cs2::kColorTeamTT; + case CT: return cs2::kColorTeamCT; + default: return cs2::kColorWhite; + } + } - PanoramaTransformations{ - context.panoramaTransformFactory().scale(scale), - context.panoramaTransformFactory().translate(deviceCoordinates.getX(), deviceCoordinates.getY()) - }.applyTo(context.panel()); + [[nodiscard]] static std::optional healthColor(auto&& playerPawn) noexcept + { + if (const auto healthValue = playerPawn.health(); healthValue.hasValue()) + return color::HSBtoRGB(color::Hue{color::kRedHue + (color::kGreenHue - color::kRedHue) * (std::clamp(healthValue.value(), 0, 100) / 100.0f)}, color::Saturation{0.7f}, color::Brightness{1.0f}); + return {}; } -private: Context context; }; diff --git a/Source/Features/Visuals/PlayerInfoInWorld/PlayerNamePanelParams.h b/Source/Features/Visuals/PlayerInfoInWorld/PlayerNamePanelParams.h index a191af9015e..a23c5345f04 100644 --- a/Source/Features/Visuals/PlayerInfoInWorld/PlayerNamePanelParams.h +++ b/Source/Features/Visuals/PlayerInfoInWorld/PlayerNamePanelParams.h @@ -8,33 +8,24 @@ #include #include -namespace player_name_panel_params::container_panel_params -{ - static constexpr auto kWidth = cs2::CUILength::pixels(256); - static constexpr auto kHeight = cs2::CUILength::pixels(32); - static constexpr auto kPositionX = cs2::CUILength::pixels(-kWidth.m_flValue * 0.5f); - static constexpr auto kPositionY = cs2::CUILength::pixels(-kHeight.m_flValue); - static constexpr auto kTransformOriginX = cs2::CUILength::percent(50); - static constexpr auto kTransformOriginY = cs2::CUILength::percent(100); -} - -namespace player_name_panel_params::label_params +namespace player_name_panel_params { static constexpr auto kFont = PanelFontParams{ .fontFamily = "Stratum2, 'Arial Unicode MS'", - .fontSize = 20, - .fontWeight = cs2::k_EFontWeightBold + .fontSize = 26, + .fontWeight = cs2::k_EFontWeightBlack }; static constexpr auto kAlignment = PanelAlignmentParams{ .horizontalAlignment = cs2::k_EHorizontalAlignmentCenter, - .verticalAlignment = cs2::k_EVerticalAlignmentCenter + .verticalAlignment = cs2::k_EVerticalAlignmentTop }; + static constexpr auto kMargin = PanelMarginParams{.marginBottom = cs2::CUILength::pixels(2)}; static constexpr auto kColor = cs2::kColorWhite; static constexpr auto kShadowParams = PanelShadowParams{ .horizontalOffset{cs2::CUILength::pixels(0)}, .verticalOffset{cs2::CUILength::pixels(0)}, - .blurRadius{cs2::CUILength::pixels(3)}, - .strength = 3, + .blurRadius{cs2::CUILength::pixels(4)}, + .strength = 6, .color{cs2::kColorBlack} }; } diff --git a/Source/GameClient/Entities/PlayerController.h b/Source/GameClient/Entities/PlayerController.h index ceac7cfa192..3f5d1dd8a0b 100644 --- a/Source/GameClient/Entities/PlayerController.h +++ b/Source/GameClient/Entities/PlayerController.h @@ -55,10 +55,11 @@ class PlayerController { [[nodiscard]] const char* getName() const noexcept { - if (const auto name = playerName(); name && *name) - return name; + // Prefer the sanitized name so it matches the scoreboard and killfeed (e.g. bot names). if (const auto name = sanitizedPlayerName(); name && *name) return name; + if (const auto name = playerName(); name && *name) + return name; return nullptr; } diff --git a/Source/UI/Panorama/CreateGUI.js b/Source/UI/Panorama/CreateGUI.js index b00ffc7722d..89ce7b692d4 100644 --- a/Source/UI/Panorama/CreateGUI.js +++ b/Source/UI/Panorama/CreateGUI.js @@ -501,6 +501,8 @@ u8R"( var playerName = createSection(playerInfoTab, 'Player Name'); createYesNoDropDown(playerName, "Show Player Name", 'visuals', 'player_info_name'); + separator(playerName); + createDropDown(playerName, "Player Name Color", 'visuals', 'player_info_name_color', ['White', 'Team Color', 'Health-based']); var playerHealth = createSection(playerInfoTab, 'Player Health'); createYesNoDropDown(playerHealth, "Show Player Health", 'visuals', 'player_info_health'); diff --git a/Source/UI/Panorama/Tabs/VisualsTab/PlayerInfoInWorldPlayerNameColorModeDropdownSelectionChangeHandler.h b/Source/UI/Panorama/Tabs/VisualsTab/PlayerInfoInWorldPlayerNameColorModeDropdownSelectionChangeHandler.h new file mode 100644 index 00000000000..e5c7bf67eba --- /dev/null +++ b/Source/UI/Panorama/Tabs/VisualsTab/PlayerInfoInWorldPlayerNameColorModeDropdownSelectionChangeHandler.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +template +struct PlayerInfoInWorldPlayerNameColorModeDropdownSelectionChangeHandler { + explicit PlayerInfoInWorldPlayerNameColorModeDropdownSelectionChangeHandler(HookContext& hookContext) noexcept + : hookContext{hookContext} + { + } + + void onSelectionChanged(int selectedIndex) + { + PlayerNameColorType colorMode; + switch (selectedIndex) { + case 0: colorMode = PlayerNameColorType::White; break; + case 1: colorMode = PlayerNameColorType::TeamColor; break; + case 2: colorMode = PlayerNameColorType::HealthBased; break; + default: return; + } + + SET_CONFIG_VAR(player_info_vars::PlayerNameColorMode, colorMode); + } + +private: + HookContext& hookContext; +}; diff --git a/Source/UI/Panorama/VisualsTab.h b/Source/UI/Panorama/VisualsTab.h index 16aaa100b93..25c0abd9f99 100644 --- a/Source/UI/Panorama/VisualsTab.h +++ b/Source/UI/Panorama/VisualsTab.h @@ -10,6 +10,7 @@ #include "Tabs/VisualsTab/PlayerInfoInWorldDropdownSelectionChangeHandler.h" #include "Tabs/VisualsTab/PlayerInfoInWorldPlayerHealthColorModeDropdownSelectionChangeHandler.h" +#include "Tabs/VisualsTab/PlayerInfoInWorldPlayerNameColorModeDropdownSelectionChangeHandler.h" #include "Tabs/VisualsTab/PlayerInfoInWorldPlayerPositionArrowColorModeDropdownSelectionChangeHandler.h" #include "Tabs/VisualsTab/PlayerModelGlowColorModeDropdownSelectionChangeHandler.h" #include "Tabs/VisualsTab/PlayerModelGlowDropdownSelectionChangeHandler.h" @@ -47,6 +48,7 @@ class VisualsTab { initDropDown>(guiPanel, "player_info_position"); initDropDown>(guiPanel, "player_info_position_color"); initDropDown>(guiPanel, "player_info_name"); + initDropDown>(guiPanel, "player_info_name_color"); initDropDown>(guiPanel, "player_info_health"); initDropDown>(guiPanel, "player_info_health_color"); initDropDown>(guiPanel, "player_info_weapon"); @@ -150,6 +152,7 @@ class VisualsTab { setDropDownSelectedIndex(mainMenu, "player_info_position", !GET_CONFIG_VAR(player_info_vars::PlayerPositionArrowEnabled)); setDropDownSelectedIndex(mainMenu, "player_info_position_color", static_cast(GET_CONFIG_VAR(player_info_vars::PlayerPositionArrowColorMode))); setDropDownSelectedIndex(mainMenu, "player_info_name", !GET_CONFIG_VAR(player_info_vars::PlayerNameEnabled)); + setDropDownSelectedIndex(mainMenu, "player_info_name_color", static_cast(GET_CONFIG_VAR(player_info_vars::PlayerNameColorMode))); setDropDownSelectedIndex(mainMenu, "player_info_health", !GET_CONFIG_VAR(player_info_vars::PlayerHealthEnabled)); setDropDownSelectedIndex(mainMenu, "player_info_health_color", static_cast(GET_CONFIG_VAR(player_info_vars::PlayerHealthColorMode))); setDropDownSelectedIndex(mainMenu, "player_info_weapon", !GET_CONFIG_VAR(player_info_vars::ActiveWeaponIconEnabled)); diff --git a/Tests/Configs/config_current.cfg b/Tests/Configs/config_current.cfg index e796a48f477..34e1a67e883 100644 --- a/Tests/Configs/config_current.cfg +++ b/Tests/Configs/config_current.cfg @@ -1 +1 @@ -{"Combat":{"NoScopeInaccuracyVis":{"Enabled":true}},"Hud":{"BombTimer":{"Enabled":true},"BombDefuseAlert":{"Enabled":true},"PreserveKillfeed":{"Enabled":true},"PostRoundTimer":{"Enabled":true},"BombPlantAlert":{"Enabled":true}},"Visuals":{"ModelGlow":{"Enabled":true,"Players":{"Enabled":true,"OnlyEnemies":true,"ColorMode":0},"Weapons":true,"DroppedBomb":false,"TickingBomb":false,"DefuseKits":true,"GrenadeProjectiles":true,"Hues":{"PlayerBlue":203,"PlayerGreen":133,"PlayerYellow":48,"PlayerOrange":13,"PlayerPurple":269,"TeamT":30,"TeamCT":220,"LowHealth":311,"HighHealth":256,"Enemy":353,"Ally":74,"Molotov":37,"Flashbang":205,"HEGrenade":333,"SmokeGrenade":116,"DroppedBomb":69,"TickingBomb":303,"DefuseKit":227}},"OutlineGlow":{"Enabled":true,"Players":{"Enabled":true,"OnlyEnemies":false,"ColorMode":2},"Weapons":true,"DroppedBomb":true,"TickingBomb":false,"DefuseKits":true,"Hostages":true,"GrenadeProjectiles":true,"Hues":{"PlayerBlue":200,"PlayerGreen":134,"PlayerYellow":57,"PlayerOrange":12,"PlayerPurple":256,"TeamT":37,"TeamCT":227,"LowHealth":287,"HighHealth":171,"Enemy":304,"Ally":103,"Molotov":60,"Flashbang":250,"HEGrenade":300,"SmokeGrenade":140,"DroppedBomb":302,"TickingBomb":26,"DefuseKit":160,"Hostage":334}},"PlayerInfoInWorld":{"Enabled":true,"OnlyEnemies":true,"PlayerPositionArrow":{"Enabled":true,"ColorMode":0},"PlayerName":true,"Health":{"Enabled":true,"ColorMode":0},"ActiveWeaponIcon":true,"BombCarrierIcon":true,"BombPlantIcon":true,"ActiveWeaponAmmo":false,"BombDefuseIcon":true,"HostagePickupIcon":true,"HostageRescueIcon":true,"BlindedIcon":true},"ViewmodelMod":{"Enabled":true,"ModifyFov":true,"Fov":90}},"Sound":{"Visualizations":{"Footsteps":{"Enabled":true},"BombPlant":{"Enabled":true},"BombBeep":{"Enabled":true},"BombDefuse":{"Enabled":true},"WeaponScope":{"Enabled":true},"WeaponReload":{"Enabled":true}}}} \ No newline at end of file +{"Combat":{"NoScopeInaccuracyVis":{"Enabled":true}},"Hud":{"BombTimer":{"Enabled":true},"BombDefuseAlert":{"Enabled":true},"PreserveKillfeed":{"Enabled":true},"PostRoundTimer":{"Enabled":true},"BombPlantAlert":{"Enabled":true}},"Visuals":{"ModelGlow":{"Enabled":true,"Players":{"Enabled":true,"OnlyEnemies":true,"ColorMode":0},"Weapons":true,"DroppedBomb":false,"TickingBomb":false,"DefuseKits":true,"GrenadeProjectiles":true,"Hues":{"PlayerBlue":203,"PlayerGreen":133,"PlayerYellow":48,"PlayerOrange":13,"PlayerPurple":269,"TeamT":30,"TeamCT":220,"LowHealth":311,"HighHealth":256,"Enemy":353,"Ally":74,"Molotov":37,"Flashbang":205,"HEGrenade":333,"SmokeGrenade":116,"DroppedBomb":69,"TickingBomb":303,"DefuseKit":227}},"OutlineGlow":{"Enabled":true,"Players":{"Enabled":true,"OnlyEnemies":false,"ColorMode":2},"Weapons":true,"DroppedBomb":true,"TickingBomb":false,"DefuseKits":true,"Hostages":true,"GrenadeProjectiles":true,"Hues":{"PlayerBlue":200,"PlayerGreen":134,"PlayerYellow":57,"PlayerOrange":12,"PlayerPurple":256,"TeamT":37,"TeamCT":227,"LowHealth":287,"HighHealth":171,"Enemy":304,"Ally":103,"Molotov":60,"Flashbang":250,"HEGrenade":300,"SmokeGrenade":140,"DroppedBomb":302,"TickingBomb":26,"DefuseKit":160,"Hostage":334}},"PlayerInfoInWorld":{"Enabled":true,"OnlyEnemies":true,"PlayerPositionArrow":{"Enabled":true,"ColorMode":0},"PlayerName":{"Enabled":true,"ColorMode":0},"Health":{"Enabled":true,"ColorMode":0},"ActiveWeaponIcon":true,"BombCarrierIcon":true,"BombPlantIcon":true,"ActiveWeaponAmmo":false,"BombDefuseIcon":true,"HostagePickupIcon":true,"HostageRescueIcon":true,"BlindedIcon":true},"ViewmodelMod":{"Enabled":true,"ModifyFov":true,"Fov":90}},"Sound":{"Visualizations":{"Footsteps":{"Enabled":true},"BombPlant":{"Enabled":true},"BombBeep":{"Enabled":true},"BombDefuse":{"Enabled":true},"WeaponScope":{"Enabled":true},"WeaponReload":{"Enabled":true}}}} \ No newline at end of file diff --git a/Tests/FunctionalTests/Config/ConfigCompatibilityTests.cpp b/Tests/FunctionalTests/Config/ConfigCompatibilityTests.cpp index 39ea988b41f..7a541c32136 100644 --- a/Tests/FunctionalTests/Config/ConfigCompatibilityTests.cpp +++ b/Tests/FunctionalTests/Config/ConfigCompatibilityTests.cpp @@ -175,6 +175,7 @@ class ConfigCompatibilityTest : public testing::Test { { setVariableExpectationsV10(); get() = true; + get() = PlayerNameColorType::White; } void setVariableExpectationsCurrent() From f9d7caefd7a896310ab66445ff291888c73b79bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E8=AE=B0?= Date: Mon, 1 Jun 2026 15:38:51 +0800 Subject: [PATCH 8/9] Add pattern scan crash diagnostics and Chinese/English i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Pattern scan logging: write all not-found patterns with module name and index to %APPDATA%/OsirisCS2/pattern_scan.log before hooks initialization, preventing silent crash on null pointer dereference - i18n: add Language config variable (enum class), translations for ~130 UI strings, language toggle button (EN/中文) in navbar, live label refresh via $.Osiris.registerTranslatable() / refreshLanguage() - Config variables now support enum class types via existing code path - Add /utf-8 MSVC flag for Chinese character support in source files Co-Authored-By: Claude Opus 4.8 --- Source/CMakeLists.txt | 2 +- Source/Config/ConfigSchema.h | 1 + Source/Config/ConfigVariableTypes.h | 2 + Source/Config/LanguageConfigVariables.h | 18 + Source/GlobalContext/OsirisDirectoryPath.h | 13 + Source/GlobalContext/PartialGlobalContext.h | 14 +- .../AllMemoryPatternSearchResults.h | 4 + Source/MemorySearch/PatternFinder.h | 10 +- Source/MemorySearch/PatternNotFoundLogger.h | 88 +++ Source/MemorySearch/PatternSearchResult.h | 5 + Source/Osiris.vcxproj | 1 + Source/Osiris.vcxproj.filters | 3 + Source/UI/Panorama/CreateGUI.js | 532 ++++++++++++------ .../UI/Panorama/PanoramaCommandDispatcher.h | 7 + Source/UI/Panorama/PanoramaGUI.h | 8 + 15 files changed, 533 insertions(+), 175 deletions(-) create mode 100644 Source/Config/LanguageConfigVariables.h diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 9fe2fc83a33..213e5faef5c 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -16,7 +16,7 @@ if(MSVC) set_target_properties(Osiris PROPERTIES MSVC_RUNTIME_LIBRARY "$<$:MultiThreadedDebugDLL>") string(REGEX REPLACE "/EH[a-z]+" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) target_sources(Osiris PRIVATE $<$:Platform/Windows/CRTWindows.cpp>) - target_compile_options(Osiris PRIVATE $<$:/W4 $<$:/sdl- /GS->>) + target_compile_options(Osiris PRIVATE $<$:/W4 /utf-8 $<$:/sdl- /GS->>) target_compile_definitions(Osiris PRIVATE $<$:_USE_STD_VECTOR_ALGORITHMS=0>) target_link_options(Osiris PRIVATE $<$:/nodefaultlib /ENTRY:"DllMain">) endif() diff --git a/Source/Config/ConfigSchema.h b/Source/Config/ConfigSchema.h index d1fb6240e72..3193401da07 100644 --- a/Source/Config/ConfigSchema.h +++ b/Source/Config/ConfigSchema.h @@ -16,6 +16,7 @@ class ConfigSchema { [[nodiscard]] decltype(auto) performConversion(auto&& configConversion) { configConversion.beginRoot(); + configConversion.uint(u8"Language", loadVariable(), saveVariable()); combatObject(configConversion); hudObject(configConversion); visualsObject(configConversion); diff --git a/Source/Config/ConfigVariableTypes.h b/Source/Config/ConfigVariableTypes.h index 8331f6ffb78..a1d7cd66939 100644 --- a/Source/Config/ConfigVariableTypes.h +++ b/Source/Config/ConfigVariableTypes.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -14,6 +15,7 @@ #include using ConfigVariableTypes = TypeList< + Language, BombTimerEnabled, DefusingAlertEnabled, KillfeedPreserverEnabled, diff --git a/Source/Config/LanguageConfigVariables.h b/Source/Config/LanguageConfigVariables.h new file mode 100644 index 00000000000..74d7640b51e --- /dev/null +++ b/Source/Config/LanguageConfigVariables.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include "ConfigVariable.h" + +namespace lang_vars +{ + +enum class Language : std::uint8_t { + English = 0, + Chinese = 1 +}; + +} + +// CONFIG_VARIABLE must be outside the namespace to avoid struct/enum name collision +CONFIG_VARIABLE(Language, lang_vars::Language, lang_vars::Language::English); diff --git a/Source/GlobalContext/OsirisDirectoryPath.h b/Source/GlobalContext/OsirisDirectoryPath.h index 1f7c9034034..92e6c474015 100644 --- a/Source/GlobalContext/OsirisDirectoryPath.h +++ b/Source/GlobalContext/OsirisDirectoryPath.h @@ -11,11 +11,14 @@ #if IS_WIN64() #include #include +#include #include #elif IS_LINUX() #include #endif +#include + class OsirisDirectoryPath { public: OsirisDirectoryPath() noexcept @@ -48,6 +51,12 @@ class OsirisDirectoryPath { std::ranges::copy(build::kOsirisDirectoryName, pathString.get() + writeIndex); writeIndex += build::kOsirisDirectoryName.length(); pathString.get()[writeIndex++] = L'\0'; + + // ensure the Osiris directory exists and set up pattern scan log path + if (pathString) { + WindowsFileSystem::createDirectory(pathString.get()); + PatternNotFoundLogger::setLogDirectoryPath(pathString.get()); + } #elif IS_LINUX() const auto home = LinuxPlatformApi::getenv("HOME"); if (!home) @@ -67,6 +76,10 @@ class OsirisDirectoryPath { std::ranges::copy(build::kOsirisDirectoryName, pathString.get() + writeIndex); writeIndex += build::kOsirisDirectoryName.length(); pathString.get()[writeIndex++] = '\0'; + + // set up pattern scan log path for Linux + if (pathString) + PatternNotFoundLogger::setLogDirectoryPath(pathString.get()); #endif } diff --git a/Source/GlobalContext/PartialGlobalContext.h b/Source/GlobalContext/PartialGlobalContext.h index dd6d8153b96..5c320495cc8 100644 --- a/Source/GlobalContext/PartialGlobalContext.h +++ b/Source/GlobalContext/PartialGlobalContext.h @@ -12,13 +12,13 @@ struct PartialGlobalContext { explicit PartialGlobalContext(DynamicLibrary clientDLL, DynamicLibrary panoramaDLL, SdlDll sdlDLL) noexcept : patternFinders{ - PatternFinder{clientDLL.getCodeSection().raw()}, - PatternFinder{DynamicLibrary{cs2::TIER0_DLL}.getCodeSection().raw()}, - PatternFinder{DynamicLibrary{cs2::SOUNDSYSTEM_DLL}.getCodeSection().raw()}, - PatternFinder{DynamicLibrary{cs2::FILESYSTEM_DLL}.getCodeSection().raw()}, - PatternFinder{panoramaDLL.getCodeSection().raw()}, - PatternFinder{sdlDLL.getCodeSection().raw()}, - PatternFinder{DynamicLibrary{cs2::SCENESYSTEM_DLL}.getCodeSection().raw()}} + PatternFinder{clientDLL.getCodeSection().raw(), "client.dll"}, + PatternFinder{DynamicLibrary{cs2::TIER0_DLL}.getCodeSection().raw(), "tier0.dll"}, + PatternFinder{DynamicLibrary{cs2::SOUNDSYSTEM_DLL}.getCodeSection().raw(), "soundsystem.dll"}, + PatternFinder{DynamicLibrary{cs2::FILESYSTEM_DLL}.getCodeSection().raw(), "filesystem.dll"}, + PatternFinder{panoramaDLL.getCodeSection().raw(), "panorama.dll"}, + PatternFinder{sdlDLL.getCodeSection().raw(), "sdl"}, + PatternFinder{DynamicLibrary{cs2::SCENESYSTEM_DLL}.getCodeSection().raw(), "scenesystem.dll"}} , clientDLL{clientDLL} , panoramaDLL{panoramaDLL} , peepEventsHook{MemoryPatterns{patternFinders}.sdlPatterns().peepEventsPointer(sdlDLL.peepEvents())} diff --git a/Source/MemoryPatterns/AllMemoryPatternSearchResults.h b/Source/MemoryPatterns/AllMemoryPatternSearchResults.h index 2fadcfba405..b5e9a8231ae 100644 --- a/Source/MemoryPatterns/AllMemoryPatternSearchResults.h +++ b/Source/MemoryPatterns/AllMemoryPatternSearchResults.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include struct AllMemoryPatternSearchResults { @@ -12,6 +13,9 @@ struct AllMemoryPatternSearchResults { , soundSystemPatternSearchResults{memoryPatterns.patternFinders.soundSystemPatternFinder.findPatterns(kSoundSystemPatterns)} , panoramaPatternSearchResults{memoryPatterns.patternFinders.panoramaPatternFinder.findPatterns(kPanoramaPatterns)} { + // flush pattern scan log to file immediately after all scans complete, + // before any downstream code uses the results (which might crash on null pointers) + PatternNotFoundLogger::flushLogToFile(); } template diff --git a/Source/MemorySearch/PatternFinder.h b/Source/MemorySearch/PatternFinder.h index 60485b8b402..15c7c1204cb 100644 --- a/Source/MemorySearch/PatternFinder.h +++ b/Source/MemorySearch/PatternFinder.h @@ -25,8 +25,9 @@ template struct PatternFinder { public: - explicit PatternFinder(std::span bytes) noexcept + explicit PatternFinder(std::span bytes, const char* moduleName = "") noexcept : bytes{bytes} + , moduleName{moduleName} { } @@ -47,6 +48,8 @@ struct PatternFinder { { patterns.forEach([patternIndex = std::size_t{0}, results, this](BytePattern pattern, std::uint8_t offset, CodePatternOperation operation) mutable { auto result = operator()(pattern); + if (!result) + NotFoundHandler::onPatternNotFound(moduleName, patternIndex, pattern); result.add(offset); std::array resultToStore{}; @@ -67,8 +70,8 @@ struct PatternFinder { auto patternFinder = HybridPatternFinder{bytes, pattern}; const auto found = patternFinder.findNextOccurrence(); assert(patternFinder.findNextOccurrence() == nullptr && "Pattern should be unique!"); - if (!found) - NotFoundHandler::onPatternNotFound(pattern); + // onPatternNotFound is called from findPatterns() with module name and pattern index + // for richer diagnostics. Direct callers should check the result themselves. return makeResult(found, pattern.length()); } @@ -106,4 +109,5 @@ struct PatternFinder { } std::span bytes; + const char* moduleName; }; diff --git a/Source/MemorySearch/PatternNotFoundLogger.h b/Source/MemorySearch/PatternNotFoundLogger.h index cef1ecd1c55..8b9f615c47c 100644 --- a/Source/MemorySearch/PatternNotFoundLogger.h +++ b/Source/MemorySearch/PatternNotFoundLogger.h @@ -1,11 +1,19 @@ #pragma once +#include #include +#include #include +#include +#include #include #include +#if IS_WIN64() +#include +#endif + struct PatternNotFoundLogger { static void onPatternNotFound(BytePattern pattern) noexcept { @@ -36,4 +44,84 @@ struct PatternNotFoundLogger { SimpleMessageBox{}.showWarning("Osiris", builder.cstring()); } + + static void onPatternNotFound(const char* moduleName, std::size_t patternIndex, BytePattern pattern) noexcept + { + // write to persistent log file first (before MessageBox, in case MessageBox hangs in overlay) + appendToLogBuffer(moduleName, patternIndex, pattern); + + // also show MessageBox for immediate visibility + onPatternNotFound(pattern); + } + + static void setLogDirectoryPath(const platform::PathCharType* path) noexcept + { + logDirectoryPath = path; + } + + static void flushLogToFile() noexcept + { + if (!logDirectoryPath || logWritePos == 0) + return; + +#if IS_WIN64() + // build full log file path: \pattern_scan.log + constexpr std::wstring_view logFileName{L"\\pattern_scan.log"}; + wchar_t logFilePath[512]{}; + std::size_t writeIdx = 0; + + // copy directory path + for (const wchar_t* p = logDirectoryPath; *p != L'\0' && writeIdx < 510; ++p) + logFilePath[writeIdx++] = *p; + + // append log file name + for (const wchar_t c : logFileName) { + if (writeIdx < 510) + logFilePath[writeIdx++] = c; + } + logFilePath[writeIdx] = L'\0'; + + // ensure directory exists + WindowsFileSystem::createDirectory(logDirectoryPath); + + // write buffer to file + if (const auto handle = WindowsFileSystem::createFileForOverwrite(logFilePath); handle != INVALID_HANDLE_VALUE) { + WindowsFileSystem::writeFile(handle, 0, logBuffer.data(), logWritePos); + WindowsSyscalls::NtClose(handle); + } +#endif + } + +private: + static void appendToLogBuffer(const char* moduleName, std::size_t patternIndex, BytePattern pattern) noexcept + { + const auto remaining = std::span{logBuffer}.subspan(logWritePos); + if (remaining.size() < 128) return; // buffer nearly full, skip + + StringBuilder builder{remaining}; + + builder.put("[NOT FOUND] #", totalNotFound, " module=", moduleName, " idx=", patternIndex, " bytes="); + + const auto wildcardChar{pattern.getWildcardChar()}; + for (const auto byte : pattern.raw()) { + if (byte != wildcardChar) { + if ((byte & 0xF0) == 0) + builder.put('0'); + builder.putHex(static_cast(byte)); + } else { + builder.put('?'); + } + } + + builder.put('\n'); + + logWritePos += builder.string().size(); + ++totalNotFound; + } + + static constexpr std::size_t kLogBufferSize{4096}; + inline static std::array logBuffer{}; + inline static std::size_t logWritePos{0}; + inline static std::size_t totalNotFound{0}; + inline static const platform::PathCharType* logDirectoryPath{nullptr}; }; diff --git a/Source/MemorySearch/PatternSearchResult.h b/Source/MemorySearch/PatternSearchResult.h index 730bdd182d7..39a2f2c0b97 100644 --- a/Source/MemorySearch/PatternSearchResult.h +++ b/Source/MemorySearch/PatternSearchResult.h @@ -22,6 +22,11 @@ class PatternSearchResult { PatternSearchResult() = default; + [[nodiscard]] explicit operator bool() const noexcept + { + return static_cast(base); + } + PatternSearchResult& add(std::size_t offset) noexcept { if (base) { diff --git a/Source/Osiris.vcxproj b/Source/Osiris.vcxproj index fb59f9a80c8..a08712fed3a 100644 --- a/Source/Osiris.vcxproj +++ b/Source/Osiris.vcxproj @@ -25,6 +25,7 @@ + diff --git a/Source/Osiris.vcxproj.filters b/Source/Osiris.vcxproj.filters index caf9928fbe7..43520b1bc0a 100644 --- a/Source/Osiris.vcxproj.filters +++ b/Source/Osiris.vcxproj.filters @@ -1708,6 +1708,9 @@ Config + + Config + Features\Hud\BombTimer diff --git a/Source/UI/Panorama/CreateGUI.js b/Source/UI/Panorama/CreateGUI.js index 89ce7b692d4..1f4d7249e99 100644 --- a/Source/UI/Panorama/CreateGUI.js +++ b/Source/UI/Panorama/CreateGUI.js @@ -3,7 +3,173 @@ $.Osiris = (function () { var activeTab; var activeSubTab = {}; - return { + // ===== Translation System ===== + // Capture language set by C++ injection BEFORE $.Osiris is overwritten + var _language = ($.Osiris && $.Osiris.language === 1) ? 1 : 0; + + var _strings = { + // Tabs + combat: { en: "Combat", zh: "战斗" }, + hud: { en: "HUD", zh: "HUD" }, + visuals: { en: "Visuals", zh: "视觉" }, + sound: { en: "Sound", zh: "声音" }, + // Sub-tabs + sniper_rifles: { en: "Sniper rifles", zh: "狙击步枪" }, + player_info_in_world:{ en: "Player Info In World", zh: "世界玩家信息" }, + outline_glow: { en: "Outline Glow", zh: "轮廓发光" }, + model_glow: { en: "Model Glow", zh: "模型发光" }, + viewmodel: { en: "Viewmodel", zh: "视角模型" }, + // Section titles + sec_no_scope: { en: "No scope", zh: "无准镜" }, + sec_bomb: { en: "Bomb", zh: "炸弹" }, + sec_killfeed: { en: "Killfeed", zh: "击杀信息" }, + sec_time: { en: "Time", zh: "时间" }, + sec_player_info: { en: "Player Info In World", zh: "世界玩家信息" }, + sec_player_position:{ en: "Player Position", zh: "玩家位置" }, + sec_player_name: { en: "Player Name", zh: "玩家名称" }, + sec_player_health: { en: "Player Health", zh: "玩家血量" }, + sec_player_weapon: { en: "Player Weapon", zh: "玩家武器" }, + sec_icons: { en: "Icons", zh: "图标" }, + sec_outline_glow: { en: "Outline Glow", zh: "轮廓发光" }, + sec_players: { en: "Players", zh: "玩家" }, + sec_weapons: { en: "Weapons", zh: "武器" }, + sec_bomb_defuse: { en: "Bomb & Defuse Kit", zh: "炸弹和拆弹包" }, + sec_hostages: { en: "Hostages", zh: "人质" }, + sec_model_glow: { en: "Model Glow", zh: "模型发光" }, + sec_viewmodel_mod: { en: "Viewmodel Modification", zh: "视角模型修改" }, + sec_viewmodel_fov: { en: "Viewmodel Fov", zh: "视角模型视野" }, + sec_player_sound: { en: "Player Sound Visualization", zh: "玩家声音可视化" }, + sec_bomb_sound: { en: "Bomb Sound Visualization", zh: "炸弹声音可视化" }, + sec_weapon_sound: { en: "Weapon Sound Visualization", zh: "武器声音可视化" }, + // Labels + lbl_master_switch: { en: "Master Switch", zh: "总开关" }, + lbl_bomb_timer: { en: "Show Bomb Explosion Countdown And Site", zh: "显示炸弹爆炸倒计时和位置" }, + lbl_defuse_alert: { en: "Show Bomb Defuse Countdown", zh: "显示拆弹倒计时" }, + lbl_bomb_plant: { en: "Show Bomb Plant Alert", zh: "显示安放炸弹警报" }, + lbl_preserve_kf: { en: "Preserve My Killfeed During The Round", zh: "回合内保留我的击杀信息" }, + lbl_postround_timer:{ en: "Show Post-round Timer", zh: "显示回合结束计时器" }, + lbl_pos_arrow: { en: "Show Player Position Arrow", zh: "显示玩家位置箭头" }, + lbl_pos_arrow_color:{ en: "Player Position Arrow Color", zh: "玩家位置箭头颜色" }, + lbl_show_name: { en: "Show Player Name", zh: "显示玩家名称" }, + lbl_name_color: { en: "Player Name Color", zh: "玩家名称颜色" }, + lbl_show_health: { en: "Show Player Health", zh: "显示玩家血量" }, + lbl_health_color: { en: "Player Health Text Color", zh: "血量文字颜色" }, + lbl_weapon_icon: { en: "Show Player Active Weapon Icon", zh: "显示玩家当前武器图标" }, + lbl_weapon_ammo: { en: "Show Player Active Weapon Ammo", zh: "显示玩家当前武器弹药" }, + lbl_bomb_carrier: { en: "Show Bomb Carrier Icon", zh: "显示炸弹携带者图标" }, + lbl_bomb_planting: { en: "Show Bomb Planting Icon", zh: "显示安放炸弹图标" }, + lbl_defuse_icon: { en: "Show Defuse Icon", zh: "显示拆弹图标" }, + lbl_hostage_pickup: { en: "Show Picking Up Hostage Icon", zh: "显示营救人质图标" }, + lbl_hostage_rescue: { en: "Show Rescuing Hostage Icon", zh: "显示拯救人质图标" }, + lbl_blinded: { en: "Show Blinded By Flashbang Icon", zh: "显示被闪光弹致盲图标" }, + lbl_glow_players: { en: "Glow Players", zh: "玩家发光" }, + lbl_glow_color: { en: "Player Glow Color", zh: "玩家发光颜色" }, + lbl_glow_weapons: { en: "Glow Weapons on Ground Nearby", zh: "附近地面武器发光" }, + lbl_glow_grenade: { en: "Glow Grenade Projectiles", zh: "投掷物发光" }, + lbl_glow_bomb_drop: { en: "Glow Dropped Bomb", zh: "掉落炸弹发光" }, + lbl_glow_bomb_tick: { en: "Glow Ticking Bomb", zh: "计时炸弹发光" }, + lbl_glow_defuse: { en: "Glow Defuse Kits on Ground Nearby", zh: "附近地面拆弹包发光" }, + lbl_glow_hostages: { en: "Glow Hostages", zh: "人质发光" }, + lbl_mglow_players: { en: "Glow Player Models", zh: "玩家模型发光" }, + lbl_mglow_color: { en: "Player Model Glow Color Mode", zh: "玩家模型发光颜色模式" }, + lbl_mglow_weapons: { en: "Glow Weapon Models on Ground", zh: "地面武器模型发光" }, + lbl_mglow_grenade: { en: "Glow Grenade Projectile Models", zh: "投掷物模型发光" }, + lbl_mglow_bomb_drop:{ en: "Glow Dropped Bomb Model", zh: "掉落炸弹模型发光" }, + lbl_mglow_bomb_tick:{ en: "Glow Ticking Bomb Model", zh: "计时炸弹模型发光" }, + lbl_mglow_defuse: { en: "Glow Defuse Kit Models on Ground", zh: "地面拆弹包模型发光" }, + lbl_fov_mod: { en: "Modify Viewmodel Fov", zh: "修改视角模型视野" }, + lbl_fov: { en: "Fov", zh: "视野" }, + lbl_footstep_vis: { en: "Visualize Player Footstep Sound", zh: "可视化玩家脚步声" }, + lbl_bomb_plant_vis: { en: "Visualize Bomb Plant Sound", zh: "可视化安放炸弹声音" }, + lbl_bomb_beep_vis: { en: "Visualize Bomb Beep Sound", zh: "可视化炸弹蜂鸣声" }, + lbl_bomb_defuse_vis:{ en: "Visualize Bomb Defuse Sound", zh: "可视化拆弹声音" }, + lbl_scope_vis: { en: "Visualize Weapon Scope Sound", zh: "可视化武器开镜声音" }, + lbl_reload_vis: { en: "Visualize Weapon Reload Sound", zh: "可视化武器换弹声音" }, + lbl_inacc_vis: { en: "Visualize Inaccuracy When Not Using a Scope", zh: "未开镜时显示不准确度" }, + // Hue labels + hue_player_blue: { en: "Player Blue Hue", zh: "玩家蓝色色相" }, + hue_player_green: { en: "Player Green Hue", zh: "玩家绿色色相" }, + hue_player_yellow: { en: "Player Yellow Hue", zh: "玩家黄色色相" }, + hue_player_orange: { en: "Player Orange Hue", zh: "玩家橙色色相" }, + hue_player_purple: { en: "Player Purple Hue", zh: "玩家紫色色相" }, + hue_t_hue: { en: "Team T Hue", zh: "T阵营色相" }, + hue_ct_hue: { en: "Team CT Hue", zh: "CT阵营色相" }, + hue_high_hp: { en: "High Health Hue", zh: "高血量色相" }, + hue_low_hp: { en: "Low Health Hue", zh: "低血量色相" }, + hue_enemy: { en: "Enemy Hue", zh: "敌人色相" }, + hue_ally: { en: "Ally Hue", zh: "队友色相" }, + hue_flashbang: { en: "Flashbang Hue", zh: "闪光弹色相" }, + hue_hegrenade: { en: "HE Grenade Hue", zh: "高爆手雷色相" }, + hue_smoke: { en: "Smoke Grenade Hue", zh: "烟雾弹色相" }, + hue_molotov: { en: "Molotov / Incendiary Grenade Hue", zh: "燃烧弹色相" }, + hue_dropped_bomb: { en: "Dropped Bomb Hue", zh: "掉落炸弹色相" }, + hue_ticking_bomb: { en: "Ticking Bomb Hue", zh: "计时炸弹色相" }, + hue_defuse_kit: { en: "Defuse Kit Hue", zh: "拆弹包色相" }, + hue_hostage: { en: "Hostage Hue", zh: "人质色相" }, + // Dropdown options + opt_on: { en: "On", zh: "开启" }, + opt_off: { en: "Off", zh: "关闭" }, + opt_yes: { en: "Yes", zh: "是" }, + opt_no: { en: "No", zh: "否" }, + opt_enemies: { en: "Enemies", zh: "敌人" }, + opt_all_players: { en: "All Players", zh: "所有玩家" }, + opt_player_team: { en: "Player / Team Color", zh: "玩家/队伍颜色" }, + opt_team_color: { en: "Team Color", zh: "队伍颜色" }, + opt_white: { en: "White", zh: "白色" }, + opt_health_based: { en: "Health-based", zh: "基于血量" }, + opt_enemy_ally: { en: "Enemy / Ally", zh: "敌人/队友" }, + // Preview + preview_title: { en: "Preview", zh: "预览" }, + weapons_ground: { en: "Weapons on the Ground", zh: "地面武器" }, + // Navbar + active_cfg_tooltip: { en: "Active config file. Changes are saved automatically.", zh: "当前配置文件。更改会自动保存。" }, + restore_defaults: { en: "Restore Defaults", zh: "恢复默认" }, + restore_confirm: { en: "Are you sure you want to restore default settings in the active config file (default.cfg)?", zh: "确定要将当前配置文件(default.cfg)恢复为默认设置吗?" }, + restore_btn: { en: "RESTORE DEFAULTS", zh: "恢复默认" }, + restore_return: { en: "RETURN", zh: "返回" }, + restore_tooltip: { en: "Restore defaults", zh: "恢复默认设置" }, + unload_title: { en: "Unload Osiris", zh: "卸载 Osiris" }, + unload_confirm: { en: "Are you sure you want to unload Osiris?", zh: "确定要卸载 Osiris 吗?" }, + unload_btn: { en: "UNLOAD", zh: "卸载" }, + unload_return: { en: "RETURN", zh: "返回" }, + unload_tooltip: { en: "Unload", zh: "卸载" }, + // Language + lang_reload_hint: { en: "Language will change next time you load Osiris", zh: "语言将在下次加载 Osiris 时生效" } + }; + + var _translatables = []; + + function _tr(key) { + var s = _strings[key]; + if (!s) return key; + return _language === 1 ? s.zh : s.en; + } + + function _registerTranslatable(panel, key) { + _translatables.push({ panel: panel, key: key }); + } + + function _refreshLanguage() { + var i; + for (i = 0; i < _translatables.length; ++i) { + var item = _translatables[i]; + item.panel.text = _tr(item.key); + } + // update language indicator label + var rootPanel = _self.rootPanel; + if (rootPanel) { + var langLabel = rootPanel.FindChildInLayoutFile('LanguageLabel'); + if (langLabel) + langLabel.text = (_language === 1) ? "中文" : "EN"; + } + } + + var _self = { + get language() { return _language; }, + tr: _tr, + registerTranslatable: _registerTranslatable, + refreshLanguage: _refreshLanguage, + rootPanel: (function () { const rootPanel = $.CreatePanel('Panel', $.GetContextPanel(), 'OsirisMenuTab', { class: "mainmenu-content__container", @@ -29,25 +195,25 @@ $.Osiris = (function () { return rootPanel; })(), goHome: function () { - $.DispatchEvent('Activated', this.rootPanel.GetParent().GetParent().GetParent().FindChildInLayoutFile("MainMenuNavBarHome"), 'mouse'); + $.DispatchEvent('Activated', _self.rootPanel.GetParent().GetParent().GetParent().FindChildInLayoutFile("MainMenuNavBarHome"), 'mouse'); }, addCommand: function (command, value = '') { - var existingCommands = this.rootPanel.GetAttributeString('cmd', ''); - this.rootPanel.SetAttributeString('cmd', existingCommands + command + ' ' + value); + var existingCommands = _self.rootPanel.GetAttributeString('cmd', ''); + _self.rootPanel.SetAttributeString('cmd', existingCommands + command + ' ' + value); }, navigateToTab: function (tabID) { if (activeTab === tabID) return; if (activeTab) { - var panelToHide = this.rootPanel.FindChildInLayoutFile(activeTab); + var panelToHide = _self.rootPanel.FindChildInLayoutFile(activeTab); panelToHide.RemoveClass('Active'); } - this.rootPanel.FindChildInLayoutFile(tabID + '_button').checked = true; + _self.rootPanel.FindChildInLayoutFile(tabID + '_button').checked = true; activeTab = tabID; - var activePanel = this.rootPanel.FindChildInLayoutFile(tabID); + var activePanel = _self.rootPanel.FindChildInLayoutFile(tabID); activePanel.AddClass('Active'); activePanel.visible = true; activePanel.SetReadyForDisplay(true); @@ -57,28 +223,44 @@ $.Osiris = (function () { return; if (activeSubTab[tabID]) { - var panelToHide = this.rootPanel.FindChildInLayoutFile(activeSubTab[tabID]); + var panelToHide = _self.rootPanel.FindChildInLayoutFile(activeSubTab[tabID]); panelToHide.RemoveClass('Active'); } - this.rootPanel.FindChildInLayoutFile(subTabID + '_button').checked = true; + _self.rootPanel.FindChildInLayoutFile(subTabID + '_button').checked = true; activeSubTab[tabID] = subTabID; - var activePanel = this.rootPanel.FindChildInLayoutFile(subTabID); + var activePanel = _self.rootPanel.FindChildInLayoutFile(subTabID); activePanel.AddClass('Active'); activePanel.visible = true; activePanel.SetReadyForDisplay(true); }, sliderUpdated: function (tabID, sliderID, slider) { - this.addCommand('set', tabID + '/' + sliderID + '/' + Math.floor(slider.value)); + _self.addCommand('set', tabID + '/' + sliderID + '/' + Math.floor(slider.value)); }, sliderTextEntryUpdated: function (tabID, sliderID, panel) { - this.addCommand('set', tabID + '/' + sliderID + '/' + panel.text); + _self.addCommand('set', tabID + '/' + sliderID + '/' + panel.text); + }, + setLanguage: function (langValue) { + _language = langValue; + _self.addCommand('language', String(langValue)); + _refreshLanguage(); + UiToolkitAPI.ShowTextTooltip('LanguageButton', _tr('lang_reload_hint')); + }, + toggleLanguage: function () { + _self.setLanguage(_language === 0 ? 1 : 0); } }; -})(); + return _self; +})(); +)" +// split the string literal because MSVC does not support string literals longer than 16k chars - error C2026 +u8R"( (function () { + var tr = $.Osiris.tr; + var register = $.Osiris.registerTranslatable; + var createNavbar = function () { var navbar = $.CreatePanel('Panel', $.Osiris.rootPanel, '', { class: "content-navbar__tabs content-navbar__tabs--noflow" @@ -92,23 +274,23 @@ $.Osiris = (function () { text: "default.cfg" }); - activeCfgNameLabel.SetPanelEvent('onmouseover', function () { UiToolkitAPI.ShowTextTooltip('ActiveConfigName', 'Active config file. Changes are saved automatically.'); }); + activeCfgNameLabel.SetPanelEvent('onmouseover', function () { UiToolkitAPI.ShowTextTooltip('ActiveConfigName', tr('active_cfg_tooltip')); }); activeCfgNameLabel.SetPanelEvent('onmouseout', function () { UiToolkitAPI.HideTextTooltip(); }); var restoreDefaultsButton = $.CreatePanel('Button', leftContainer, 'RestoreDefaultsButton', { class: "content-navbar__tabs__btn", style: "margin-left: 5px;", - onactivate: "UiToolkitAPI.ShowGenericPopupOneOptionCustomCancelBgStyle('Restore Defaults', 'Are you sure you want to restore default settings in the active config file (default.cfg)?', '', 'RESTORE DEFAULTS', function() { $.Osiris.addCommand('restore_defaults'); }, 'RETURN', function() {}, 'dim');" + onactivate: "UiToolkitAPI.ShowGenericPopupOneOptionCustomCancelBgStyle('" + tr('restore_defaults') + "', '" + tr('restore_confirm') + "', '', '" + tr('restore_btn') + "', function() { $.Osiris.addCommand('restore_defaults'); }, '" + tr('restore_return') + "', function() {}, 'dim');" }); - restoreDefaultsButton.SetPanelEvent('onmouseover', function () { UiToolkitAPI.ShowTextTooltip('RestoreDefaultsButton', 'Restore defaults'); }); + restoreDefaultsButton.SetPanelEvent('onmouseover', function () { UiToolkitAPI.ShowTextTooltip('RestoreDefaultsButton', tr('restore_tooltip')); }); restoreDefaultsButton.SetPanelEvent('onmouseout', function () { UiToolkitAPI.HideTextTooltip(); }); $.CreatePanel('Image', restoreDefaultsButton, '', { src: "s2r://panorama/images/icons/ui/recent.vsvg", texturewidth: "24" }); - + var centerContainer = $.CreatePanel('Panel', navbar, '', { class: "content-navbar__tabs__center-container", }); @@ -119,7 +301,7 @@ $.Osiris = (function () { onactivate: "$.Osiris.navigateToTab('combat');" }); - $.CreatePanel('Label', combatTabButton, '', { text: "Combat" }); + $.CreatePanel('Label', combatTabButton, '', { text: tr('combat') }); var hudTabButton = $.CreatePanel('RadioButton', centerContainer, 'hud_button', { group: "SettingsNavBar", @@ -127,7 +309,7 @@ $.Osiris = (function () { onactivate: "$.Osiris.navigateToTab('hud');" }); - $.CreatePanel('Label', hudTabButton, '', { text: "HUD" }); + $.CreatePanel('Label', hudTabButton, '', { text: tr('hud') }); var visualsTabButton = $.CreatePanel('RadioButton', centerContainer, 'visuals_button', { group: "SettingsNavBar", @@ -135,26 +317,41 @@ $.Osiris = (function () { onactivate: "$.Osiris.navigateToTab('visuals');" }); - $.CreatePanel('Label', visualsTabButton, '', { text: "Visuals" }); - + $.CreatePanel('Label', visualsTabButton, '', { text: tr('visuals') }); + var soundTabButton = $.CreatePanel('RadioButton', centerContainer, 'sound_button', { group: "SettingsNavBar", class: "content-navbar__tabs__btn", onactivate: "$.Osiris.navigateToTab('sound');" }); - $.CreatePanel('Label', soundTabButton, '', { text: "Sound" }); + $.CreatePanel('Label', soundTabButton, '', { text: tr('sound') }); var rightContainer = $.CreatePanel('Panel', navbar, '', { style: "horizontal-align: right; flow-children: right; height: 100%; margin-right: 70px;" }); + // Language toggle button - shows current language, click to switch + var langButton = $.CreatePanel('Button', rightContainer, 'LanguageButton', { + class: "content-navbar__tabs__btn", + style: "margin-right: 5px; min-width: 50px;", + onactivate: "$.Osiris.toggleLanguage();" + }); + + var langLabel = $.CreatePanel('Label', langButton, 'LanguageLabel', { + text: ($.Osiris.language === 1) ? "中文" : "EN", + style: "font-size: 16px; color: #ccccccff; horizontal-align: center; vertical-align: center;" + }); + + langButton.SetPanelEvent('onmouseover', function () { UiToolkitAPI.ShowTextTooltip('LanguageButton', tr('lang_reload_hint')); }); + langButton.SetPanelEvent('onmouseout', function () { UiToolkitAPI.HideTextTooltip(); }); + var unloadButton = $.CreatePanel('Button', rightContainer, 'UnloadButton', { class: "content-navbar__tabs__btn", - onactivate: "UiToolkitAPI.ShowGenericPopupOneOptionCustomCancelBgStyle('Unload Osiris', 'Are you sure you want to unload Osiris?', '', 'UNLOAD', function() { $.Osiris.goHome(); $.Osiris.addCommand('unload'); }, 'RETURN', function() {}, 'dim');" + onactivate: "UiToolkitAPI.ShowGenericPopupOneOptionCustomCancelBgStyle('" + tr('unload_title') + "', '" + tr('unload_confirm') + "', '', '" + tr('unload_btn') + "', function() { $.Osiris.goHome(); $.Osiris.addCommand('unload'); }, '" + tr('unload_return') + "', function() {}, 'dim');" }); - unloadButton.SetPanelEvent('onmouseover', function () { UiToolkitAPI.ShowTextTooltip('UnloadButton', 'Unload'); }); + unloadButton.SetPanelEvent('onmouseover', function () { UiToolkitAPI.ShowTextTooltip('UnloadButton', tr('unload_tooltip')); }); unloadButton.SetPanelEvent('onmouseout', function () { UiToolkitAPI.HideTextTooltip(); }); $.CreatePanel('Image', unloadButton, '', { @@ -179,7 +376,7 @@ $.Osiris = (function () { onactivate: "$.Osiris.navigateToSubTab('visuals', 'player_info');" }); - $.CreatePanel('Label', playerInfoTabButton, '', { text: "Player Info In World" }); + $.CreatePanel('Label', playerInfoTabButton, '', { text: tr('player_info_in_world') }); var outlineGlowTabButton = $.CreatePanel('RadioButton', centerContainer, 'outline_glow_button', { group: "VisualsNavBar", @@ -187,7 +384,7 @@ $.Osiris = (function () { onactivate: "$.Osiris.navigateToSubTab('visuals', 'outline_glow');" }); - $.CreatePanel('Label', outlineGlowTabButton, '', { text: "Outline Glow" }); + $.CreatePanel('Label', outlineGlowTabButton, '', { text: tr('outline_glow') }); var modelGlowTabButton = $.CreatePanel('RadioButton', centerContainer, 'model_glow_button', { group: "VisualsNavBar", @@ -195,15 +392,15 @@ $.Osiris = (function () { onactivate: "$.Osiris.navigateToSubTab('visuals', 'model_glow');" }); - $.CreatePanel('Label', modelGlowTabButton, '', { text: "Model Glow" }); - + $.CreatePanel('Label', modelGlowTabButton, '', { text: tr('model_glow') }); + var viewmodelTabButton = $.CreatePanel('RadioButton', centerContainer, 'viewmodel_button', { group: "VisualsNavBar", class: "content-navbar__tabs__btn", onactivate: "$.Osiris.navigateToSubTab('visuals', 'viewmodel');" }); - $.CreatePanel('Label', viewmodelTabButton, '', { text: "Viewmodel" }); + $.CreatePanel('Label', viewmodelTabButton, '', { text: tr('viewmodel') }); }; var createCombatNavbar = function () { @@ -221,7 +418,7 @@ $.Osiris = (function () { onactivate: "$.Osiris.navigateToSubTab('combat', 'sniper_rifles');" }); - $.CreatePanel('Label', sniperRiflesTabButton, '', { text: "Sniper rifles" }); + $.CreatePanel('Label', sniperRiflesTabButton, '', { text: tr('sniper_rifles') }); }; createNavbar(); @@ -239,7 +436,7 @@ $.Osiris = (function () { var content = $.CreatePanel('Panel', tab, '', { class: "SettingsMenuTabContent vscroll" }); - + return content; }; @@ -254,7 +451,7 @@ $.Osiris = (function () { var content = $.CreatePanel('Panel', tab, '', { class: "full-width full-height" }); - + return content; }; @@ -269,7 +466,7 @@ $.Osiris = (function () { var content = $.CreatePanel('Panel', tab, '', { class: "full-width full-height" }); - + return content; }; @@ -285,7 +482,7 @@ $.Osiris = (function () { return content; }; - var createSection = function(tab, sectionName) { + var createSection = function(tab, sectionKey) { var background = $.CreatePanel('Panel', tab, '', { class: "SettingsBackground" }); @@ -294,10 +491,11 @@ $.Osiris = (function () { class: "SettingsSectionTitleContianer" }); - $.CreatePanel('Label', titleContainer, '', { + var label = $.CreatePanel('Label', titleContainer, '', { class: "SettingsSectionTitleLabel", - text: sectionName + text: tr(sectionKey) }); + register(label, sectionKey); var content = $.CreatePanel('Panel', background, '', { class: "top-bottom-flow full-width" @@ -306,32 +504,33 @@ $.Osiris = (function () { return content; }; - var createDropDown = function (parent, labelText, section, feature, options) { + var createDropDown = function (parent, labelKey, section, feature, optionKeys) { var container = $.CreatePanel('Panel', parent, '', { class: "SettingsMenuDropdownContainer" }); - $.CreatePanel('Label', container, '', { + var label = $.CreatePanel('Label', container, '', { class: "half-width", - text: labelText + text: tr(labelKey) }); + register(label, labelKey); var dropdown = $.CreatePanel('CSGOSettingsEnumDropDown', container, feature, { class: "PopupButton White" }); - for (let i = 0; i < options.length; ++i) { + for (let i = 0; i < optionKeys.length; ++i) { dropdown.AddOption($.CreatePanel('Label', dropdown, i, { value: i, - text: options[i] + text: tr(optionKeys[i]) })); } }; - var createOnOffDropDown = function (parent, labelText, section, feature) { - createDropDown(parent, labelText, section, feature, ["On", "Off"]); + var createOnOffDropDown = function (parent, labelKey, section, feature) { + createDropDown(parent, labelKey, section, feature, ["opt_on", "opt_off"]); }; - var createYesNoDropDown = function (parent, labelText, section, feature) { - createDropDown(parent, labelText, section, feature, ["Yes", "No"]); + var createYesNoDropDown = function (parent, labelKey, section, feature) { + createDropDown(parent, labelKey, section, feature, ["opt_yes", "opt_no"]); }; var separator = function (parent) { @@ -382,15 +581,16 @@ $.Osiris = (function () { )" // split the string literal because MSVC does not support string literals longer than 16k chars - error C2026 u8R"( - var createSlider = function (parent, name, id, min, max) { + var createSlider = function (parent, nameKey, id, min, max) { var container = $.CreatePanel('Panel', parent, '', { class: "SettingsMenuDropdownContainer" }); - $.CreatePanel('Label', container, '', { + var label = $.CreatePanel('Label', container, '', { class: "half-width", - text: name + text: tr(nameKey) }); + register(label, nameKey); var sliderContainer = $.CreatePanel('Panel', container, id, { style: "vertical-align: center; horizontal-align: right; flow-children: right; margin-right: 8px;" @@ -420,15 +620,16 @@ u8R"( textEntry.SetPanelEvent('onmouseout', function () { if (!textEntry.BHasKeyFocus()) textEntry.style.backgroundColor = 'none'; }); } - var createHueSlider = function (parent, name, id, min, max) { + var createHueSlider = function (parent, nameKey, id, min, max) { var container = $.CreatePanel('Panel', parent, '', { class: "SettingsMenuDropdownContainer" }); - $.CreatePanel('Label', container, '', { + var label = $.CreatePanel('Label', container, '', { class: "half-width", - text: name + text: tr(nameKey) }); + register(label, nameKey); var sliderContainer = $.CreatePanel('Panel', container, id, { style: "vertical-align: center; horizontal-align: right; flow-children: right; margin-right: 8px;" @@ -464,149 +665,151 @@ u8R"( u8R"( var combat = createCombatTab(); var sniperRiflesTab = createSubTab(combat, 'sniper_rifles'); - var noScope = createSection(sniperRiflesTab, 'No scope'); + var noScope = createSection(sniperRiflesTab, 'sec_no_scope'); separator(noScope); - createYesNoDropDown(noScope, "Visualize Inaccuracy When Not Using a Scope", 'combat', 'no_scope_inacc_vis'); + createYesNoDropDown(noScope, "lbl_inacc_vis", 'combat', 'no_scope_inacc_vis'); $.Osiris.navigateToSubTab('combat', 'sniper_rifles'); var hud = createTab('hud'); - - var bomb = createSection(hud, 'Bomb'); - createYesNoDropDown(bomb, "Show Bomb Explosion Countdown And Site", 'hud', 'bomb_timer'); + + var bomb = createSection(hud, 'sec_bomb'); + createYesNoDropDown(bomb, "lbl_bomb_timer", 'hud', 'bomb_timer'); separator(bomb); - createYesNoDropDown(bomb, "Show Bomb Defuse Countdown", 'hud', 'defusing_alert'); + createYesNoDropDown(bomb, "lbl_defuse_alert", 'hud', 'defusing_alert'); separator(bomb); - createYesNoDropDown(bomb, "Show Bomb Plant Alert", 'hud', 'bomb_plant_alert'); + createYesNoDropDown(bomb, "lbl_bomb_plant", 'hud', 'bomb_plant_alert'); - var killfeed = createSection(hud, 'Killfeed'); + var killfeed = createSection(hud, 'sec_killfeed'); separator(killfeed); - createYesNoDropDown(killfeed, "Preserve My Killfeed During The Round", 'hud', 'preserve_killfeed'); + createYesNoDropDown(killfeed, "lbl_preserve_kf", 'hud', 'preserve_killfeed'); - var time = createSection(hud, 'Time'); + var time = createSection(hud, 'sec_time'); separator(time); - createYesNoDropDown(time, "Show Post-round Timer", 'hud', 'postround_timer'); + createYesNoDropDown(time, "lbl_postround_timer", 'hud', 'postround_timer'); var visuals = createVisualsTab(); var playerInfoTab = createSubTab(visuals, 'player_info'); - var playerInfo = createSection(playerInfoTab, 'Player Info In World'); - createDropDown(playerInfo, "Master Switch", 'visuals', 'player_information_through_walls', ['Enemies', 'All Players', 'Off']); + var playerInfo = createSection(playerInfoTab, 'sec_player_info'); + createDropDown(playerInfo, "lbl_master_switch", 'visuals', 'player_information_through_walls', ['opt_enemies', 'opt_all_players', 'opt_off']); - var playerPosition = createSection(playerInfoTab, 'Player Position'); - createYesNoDropDown(playerPosition, "Show Player Position Arrow", 'visuals', 'player_info_position'); + var playerPosition = createSection(playerInfoTab, 'sec_player_position'); + createYesNoDropDown(playerPosition, "lbl_pos_arrow", 'visuals', 'player_info_position'); separator(playerPosition); - createDropDown(playerPosition, "Player Position Arrow Color", 'visuals', 'player_info_position_color', ['Player / Team Color', 'Team Color']); + createDropDown(playerPosition, "lbl_pos_arrow_color", 'visuals', 'player_info_position_color', ['opt_player_team', 'opt_team_color']); - var playerName = createSection(playerInfoTab, 'Player Name'); - createYesNoDropDown(playerName, "Show Player Name", 'visuals', 'player_info_name'); + var playerName = createSection(playerInfoTab, 'sec_player_name'); + createYesNoDropDown(playerName, "lbl_show_name", 'visuals', 'player_info_name'); separator(playerName); - createDropDown(playerName, "Player Name Color", 'visuals', 'player_info_name_color', ['White', 'Team Color', 'Health-based']); + createDropDown(playerName, "lbl_name_color", 'visuals', 'player_info_name_color', ['opt_white', 'opt_team_color', 'opt_health_based']); - var playerHealth = createSection(playerInfoTab, 'Player Health'); - createYesNoDropDown(playerHealth, "Show Player Health", 'visuals', 'player_info_health'); + var playerHealth = createSection(playerInfoTab, 'sec_player_health'); + createYesNoDropDown(playerHealth, "lbl_show_health", 'visuals', 'player_info_health'); separator(playerHealth); - createDropDown(playerHealth, "Player Health Text Color", 'visuals', 'player_info_health_color', ['Health-based', 'White']); + createDropDown(playerHealth, "lbl_health_color", 'visuals', 'player_info_health_color', ['opt_health_based', 'opt_white']); - var playerWeapon = createSection(playerInfoTab, 'Player Weapon'); - createYesNoDropDown(playerWeapon, "Show Player Active Weapon Icon", 'visuals', 'player_info_weapon'); + var playerWeapon = createSection(playerInfoTab, 'sec_player_weapon'); + createYesNoDropDown(playerWeapon, "lbl_weapon_icon", 'visuals', 'player_info_weapon'); separator(playerWeapon); - createYesNoDropDown(playerWeapon, "Show Player Active Weapon Ammo", 'visuals', 'player_info_weapon_clip'); + createYesNoDropDown(playerWeapon, "lbl_weapon_ammo", 'visuals', 'player_info_weapon_clip'); separator(playerWeapon); - createYesNoDropDown(playerWeapon, 'Show Bomb Carrier Icon', 'visuals', 'player_info_bomb_carrier'); + createYesNoDropDown(playerWeapon, "lbl_bomb_carrier", 'visuals', 'player_info_bomb_carrier'); separator(playerWeapon); - createYesNoDropDown(playerWeapon, 'Show Bomb Planting Icon', 'visuals', 'player_info_bomb_planting'); + createYesNoDropDown(playerWeapon, "lbl_bomb_planting", 'visuals', 'player_info_bomb_planting'); - var playerIcons = createSection(playerInfoTab, 'Icons'); - createYesNoDropDown(playerIcons, "Show Defuse Icon", 'visuals', 'player_info_defuse'); + var playerIcons = createSection(playerInfoTab, 'sec_icons'); + createYesNoDropDown(playerIcons, "lbl_defuse_icon", 'visuals', 'player_info_defuse'); separator(playerIcons); - createYesNoDropDown(playerIcons, 'Show Picking Up Hostage Icon', 'visuals', 'player_info_hostage_pickup'); + createYesNoDropDown(playerIcons, "lbl_hostage_pickup", 'visuals', 'player_info_hostage_pickup'); separator(playerIcons); - createYesNoDropDown(playerIcons, 'Show Rescuing Hostage Icon', 'visuals', 'player_info_hostage_rescue'); + createYesNoDropDown(playerIcons, "lbl_hostage_rescue", 'visuals', 'player_info_hostage_rescue'); separator(playerIcons); - createYesNoDropDown(playerIcons, 'Show Blinded By Flashbang Icon', 'visuals', 'player_info_blinded'); + createYesNoDropDown(playerIcons, "lbl_blinded", 'visuals', 'player_info_blinded'); var outlineGlowTab = createSubTab(visuals, 'outline_glow'); - var outlineGlow = createSection(outlineGlowTab, 'Outline Glow'); - createOnOffDropDown(outlineGlow, "Master Switch", 'visuals', 'outline_glow_enable'); + var outlineGlow = createSection(outlineGlowTab, 'sec_outline_glow'); + createOnOffDropDown(outlineGlow, "lbl_master_switch", 'visuals', 'outline_glow_enable'); - var playerOutlineGlow = createSection(outlineGlowTab, 'Players'); - createDropDown(playerOutlineGlow, "Glow Players", 'visuals', 'player_outline_glow', ['Enemies', 'All Players', 'Off']); + var playerOutlineGlow = createSection(outlineGlowTab, 'sec_players'); + createDropDown(playerOutlineGlow, "lbl_glow_players", 'visuals', 'player_outline_glow', ['opt_enemies', 'opt_all_players', 'opt_off']); separator(playerOutlineGlow); - createDropDown(playerOutlineGlow, "Player Glow Color", 'visuals', 'player_outline_glow_color', ['Player / Team Color', 'Team Color', 'Health-based', 'Enemy / Ally']); + createDropDown(playerOutlineGlow, "lbl_glow_color", 'visuals', 'player_outline_glow_color', ['opt_player_team', 'opt_team_color', 'opt_health_based', 'opt_enemy_ally']); separator(playerOutlineGlow); separator(playerOutlineGlow); - createHueSlider(playerOutlineGlow, "Player Blue Hue", 'player_outline_glow_blue_hue', 191, 240); + createHueSlider(playerOutlineGlow, "hue_player_blue", 'player_outline_glow_blue_hue', 191, 240); separator(playerOutlineGlow); - createHueSlider(playerOutlineGlow, "Player Green Hue", 'player_outline_glow_green_hue', 110, 140); + createHueSlider(playerOutlineGlow, "hue_player_green", 'player_outline_glow_green_hue', 110, 140); separator(playerOutlineGlow); - createHueSlider(playerOutlineGlow, "Player Yellow Hue", 'player_outline_glow_yellow_hue', 47, 60); + createHueSlider(playerOutlineGlow, "hue_player_yellow", 'player_outline_glow_yellow_hue', 47, 60); separator(playerOutlineGlow); - createHueSlider(playerOutlineGlow, "Player Orange Hue", 'player_outline_glow_orange_hue', 11, 20); + createHueSlider(playerOutlineGlow, "hue_player_orange", 'player_outline_glow_orange_hue', 11, 20); separator(playerOutlineGlow); - createHueSlider(playerOutlineGlow, "Player Purple Hue", 'player_outline_glow_purple_hue', 250, 280); + createHueSlider(playerOutlineGlow, "hue_player_purple", 'player_outline_glow_purple_hue', 250, 280); separator(playerOutlineGlow); separator(playerOutlineGlow); - createHueSlider(playerOutlineGlow, "Team T Hue", 'player_outline_glow_t_hue', 30, 40); + createHueSlider(playerOutlineGlow, "hue_t_hue", 'player_outline_glow_t_hue', 30, 40); separator(playerOutlineGlow); - createHueSlider(playerOutlineGlow, "Team CT Hue", 'player_outline_glow_ct_hue', 210, 230); + createHueSlider(playerOutlineGlow, "hue_ct_hue", 'player_outline_glow_ct_hue', 210, 230); separator(playerOutlineGlow); separator(playerOutlineGlow); - createHueSlider(playerOutlineGlow, "High Health Hue", 'player_outline_glow_high_hp_hue', 0, 359); + createHueSlider(playerOutlineGlow, "hue_high_hp", 'player_outline_glow_high_hp_hue', 0, 359); separator(playerOutlineGlow); - createHueSlider(playerOutlineGlow, "Low Health Hue", 'player_outline_glow_low_hp_hue', 0, 359); + createHueSlider(playerOutlineGlow, "hue_low_hp", 'player_outline_glow_low_hp_hue', 0, 359); separator(playerOutlineGlow); separator(playerOutlineGlow); - createHueSlider(playerOutlineGlow, "Enemy Hue", 'player_outline_glow_enemy_hue', 0, 359); + createHueSlider(playerOutlineGlow, "hue_enemy", 'player_outline_glow_enemy_hue', 0, 359); separator(playerOutlineGlow); - createHueSlider(playerOutlineGlow, "Ally Hue", 'player_outline_glow_ally_hue', 0, 359); + createHueSlider(playerOutlineGlow, "hue_ally", 'player_outline_glow_ally_hue', 0, 359); - var weaponOutlineGlow = createSection(outlineGlowTab, 'Weapons'); - createYesNoDropDown(weaponOutlineGlow, "Glow Weapons on Ground Nearby", 'visuals', 'weapon_outline_glow'); + var weaponOutlineGlow = createSection(outlineGlowTab, 'sec_weapons'); + createYesNoDropDown(weaponOutlineGlow, "lbl_glow_weapons", 'visuals', 'weapon_outline_glow'); separator(weaponOutlineGlow); - createYesNoDropDown(weaponOutlineGlow, "Glow Grenade Projectiles", 'visuals', 'grenade_proj_outline_glow'); + createYesNoDropDown(weaponOutlineGlow, "lbl_glow_grenade", 'visuals', 'grenade_proj_outline_glow'); separator(weaponOutlineGlow); separator(weaponOutlineGlow); - createHueSlider(weaponOutlineGlow, "Flashbang Hue", 'outline_glow_flashbang_hue', 191, 250); + createHueSlider(weaponOutlineGlow, "hue_flashbang", 'outline_glow_flashbang_hue', 191, 250); separator(weaponOutlineGlow); - createHueSlider(weaponOutlineGlow, "HE Grenade Hue", 'outline_glow_hegrenade_hue', 300, 359); + createHueSlider(weaponOutlineGlow, "hue_hegrenade", 'outline_glow_hegrenade_hue', 300, 359); separator(weaponOutlineGlow); - createHueSlider(weaponOutlineGlow, "Smoke Grenade Hue", 'outline_glow_smoke_hue', 110, 140); + createHueSlider(weaponOutlineGlow, "hue_smoke", 'outline_glow_smoke_hue', 110, 140); separator(weaponOutlineGlow); - createHueSlider(weaponOutlineGlow, "Molotov / Incendiary Grenade Hue", 'outline_glow_molotov_hue', 20, 60); + createHueSlider(weaponOutlineGlow, "hue_molotov", 'outline_glow_molotov_hue', 20, 60); - var bombAndDefuseKitOutlineGlow = createSection(outlineGlowTab, 'Bomb & Defuse Kit'); - createYesNoDropDown(bombAndDefuseKitOutlineGlow, "Glow Dropped Bomb", 'visuals', 'dropped_bomb_outline_glow'); + var bombAndDefuseKitOutlineGlow = createSection(outlineGlowTab, 'sec_bomb_defuse'); + createYesNoDropDown(bombAndDefuseKitOutlineGlow, "lbl_glow_bomb_drop", 'visuals', 'dropped_bomb_outline_glow'); separator(bombAndDefuseKitOutlineGlow); - createYesNoDropDown(bombAndDefuseKitOutlineGlow, "Glow Ticking Bomb", 'visuals', 'ticking_bomb_outline_glow'); + createYesNoDropDown(bombAndDefuseKitOutlineGlow, "lbl_glow_bomb_tick", 'visuals', 'ticking_bomb_outline_glow'); separator(bombAndDefuseKitOutlineGlow); - createYesNoDropDown(bombAndDefuseKitOutlineGlow, "Glow Defuse Kits on Ground Nearby", 'visuals', 'defuse_kit_outline_glow'); + createYesNoDropDown(bombAndDefuseKitOutlineGlow, "lbl_glow_defuse", 'visuals', 'defuse_kit_outline_glow'); separator(bombAndDefuseKitOutlineGlow); separator(bombAndDefuseKitOutlineGlow); - createHueSlider(bombAndDefuseKitOutlineGlow, "Dropped Bomb Hue", 'outline_glow_dropped_bomb_hue', 0, 359); + createHueSlider(bombAndDefuseKitOutlineGlow, "hue_dropped_bomb", 'outline_glow_dropped_bomb_hue', 0, 359); separator(bombAndDefuseKitOutlineGlow); - createHueSlider(bombAndDefuseKitOutlineGlow, "Ticking Bomb Hue", 'outline_glow_ticking_bomb_hue', 0, 359); + createHueSlider(bombAndDefuseKitOutlineGlow, "hue_ticking_bomb", 'outline_glow_ticking_bomb_hue', 0, 359); separator(bombAndDefuseKitOutlineGlow); - createHueSlider(bombAndDefuseKitOutlineGlow, "Defuse Kit Hue", 'outline_glow_defuse_kit_hue', 0, 359); + createHueSlider(bombAndDefuseKitOutlineGlow, "hue_defuse_kit", 'outline_glow_defuse_kit_hue', 0, 359); - var hostageOutlineGlow = createSection(outlineGlowTab, 'Hostages'); - createYesNoDropDown(hostageOutlineGlow, "Glow Hostages", 'visuals', 'hostage_outline_glow'); + var hostageOutlineGlow = createSection(outlineGlowTab, 'sec_hostages'); + createYesNoDropDown(hostageOutlineGlow, "lbl_glow_hostages", 'visuals', 'hostage_outline_glow'); separator(hostageOutlineGlow); - createHueSlider(hostageOutlineGlow, "Hostage Hue", 'outline_glow_hostage_hue', 0, 359); + createHueSlider(hostageOutlineGlow, "hue_hostage", 'outline_glow_hostage_hue', 0, 359); var _modelGlowTab = createSubTab(visuals, 'model_glow'); _modelGlowTab.style.overflow = 'squish squish'; _modelGlowTab.style.flowChildren = 'right'; var modelGlowPreview = $.CreatePanel('Panel', _modelGlowTab, '', { style: 'flow-children: down;' }); - $.CreatePanel('Label', modelGlowPreview, '', { style: 'vertical-align: top; horizontal-align: center; font-size: 40;', text: 'Preview' }); + var previewLabel = $.CreatePanel('Label', modelGlowPreview, '', { style: 'vertical-align: top; horizontal-align: center; font-size: 40;', text: tr('preview_title') }); + register(previewLabel, 'preview_title'); var playerModelGlowPreview = $.CreatePanel('Panel', modelGlowPreview, '', { style: 'flow-children: right; margin-top: 20px;' }); createPlayerModelGlowPreview(playerModelGlowPreview, 'ModelGlowPreviewPlayerTT', 'ModelGlowPreviewPlayerTTLabel', 'agents/models/tm_professional/tm_professional_varf.vmdl', makeFauxItemId(7, 921)); createPlayerModelGlowPreview(playerModelGlowPreview, 'ModelGlowPreviewPlayerCT', 'ModelGlowPreviewPlayerCTLabel', 'agents/models/ctm_st6/ctm_st6_variante.vmdl', makeFauxItemId(9, 819)); - $.CreatePanel('Label', modelGlowPreview, '', { style: 'horizontal-align: center; margin-top: 20px;', text: 'Weapons on the Ground' }); + var weaponsGroundLabel = $.CreatePanel('Label', modelGlowPreview, '', { style: 'horizontal-align: center; margin-top: 20px;', text: tr('weapons_ground') }); + register(weaponsGroundLabel, 'weapons_ground'); var weaponModelGlowPreview = $.CreatePanel('Panel', modelGlowPreview, '', { style: 'flow-children: right;' }); @@ -636,76 +839,77 @@ u8R"( var modelGlowTab = $.CreatePanel('Panel', _modelGlowTab, '', { style: 'flow-children: down; margin-right: 40px; overflow: squish scroll;' }); - var modelGlow = createSection(modelGlowTab, 'Model Glow'); - createOnOffDropDown(modelGlow, "Master Switch", 'visuals', 'model_glow_enable'); + var modelGlow = createSection(modelGlowTab, 'sec_model_glow'); + createOnOffDropDown(modelGlow, "lbl_master_switch", 'visuals', 'model_glow_enable'); - var playerModelGlow = createSection(modelGlowTab, 'Players'); - createDropDown(playerModelGlow, "Glow Player Models", 'visuals', 'player_model_glow', ['Enemies', 'All Players', 'Off']); + var playerModelGlow = createSection(modelGlowTab, 'sec_players'); + createDropDown(playerModelGlow, "lbl_mglow_players", 'visuals', 'player_model_glow', ['opt_enemies', 'opt_all_players', 'opt_off']); separator(playerModelGlow); - createDropDown(playerModelGlow, "Player Model Glow Color Mode", 'visuals', 'player_model_glow_color', ['Player / Team Color', 'Team Color', 'Health-based', 'Enemy / Ally']); + createDropDown(playerModelGlow, "lbl_mglow_color", 'visuals', 'player_model_glow_color', ['opt_player_team', 'opt_team_color', 'opt_health_based', 'opt_enemy_ally']); separator(playerModelGlow); separator(playerModelGlow); - createHueSlider(playerModelGlow, "Player Blue Hue", 'player_model_glow_blue_hue', 191, 240); + createHueSlider(playerModelGlow, "hue_player_blue", 'player_model_glow_blue_hue', 191, 240); separator(playerModelGlow); - createHueSlider(playerModelGlow, "Player Green Hue", 'player_model_glow_green_hue', 110, 140); + createHueSlider(playerModelGlow, "hue_player_green", 'player_model_glow_green_hue', 110, 140); separator(playerModelGlow); - createHueSlider(playerModelGlow, "Player Yellow Hue", 'player_model_glow_yellow_hue', 47, 60); + createHueSlider(playerModelGlow, "hue_player_yellow", 'player_model_glow_yellow_hue', 47, 60); separator(playerModelGlow); - createHueSlider(playerModelGlow, "Player Orange Hue", 'player_model_glow_orange_hue', 11, 20); + createHueSlider(playerModelGlow, "hue_player_orange", 'player_model_glow_orange_hue', 11, 20); separator(playerModelGlow); - createHueSlider(playerModelGlow, "Player Purple Hue", 'player_model_glow_purple_hue', 250, 280); + createHueSlider(playerModelGlow, "hue_player_purple", 'player_model_glow_purple_hue', 250, 280); separator(playerModelGlow); separator(playerModelGlow); - createHueSlider(playerModelGlow, "Team T Hue", 'player_model_glow_t_hue', 30, 40); + createHueSlider(playerModelGlow, "hue_t_hue", 'player_model_glow_t_hue', 30, 40); separator(playerModelGlow); - createHueSlider(playerModelGlow, "Team CT Hue", 'player_model_glow_ct_hue', 210, 230); + createHueSlider(playerModelGlow, "hue_ct_hue", 'player_model_glow_ct_hue', 210, 230); separator(playerModelGlow); separator(playerModelGlow); - createHueSlider(playerModelGlow, "High Health Hue", 'player_model_glow_high_hp_hue', 0, 359); + createHueSlider(playerModelGlow, "hue_high_hp", 'player_model_glow_high_hp_hue', 0, 359); separator(playerModelGlow); - createHueSlider(playerModelGlow, "Low Health Hue", 'player_model_glow_low_hp_hue', 0, 359); + createHueSlider(playerModelGlow, "hue_low_hp", 'player_model_glow_low_hp_hue', 0, 359); separator(playerModelGlow); separator(playerModelGlow); - createHueSlider(playerModelGlow, "Enemy Hue", 'player_model_glow_enemy_hue', 0, 359); + createHueSlider(playerModelGlow, "hue_enemy", 'player_model_glow_enemy_hue', 0, 359); separator(playerModelGlow); - createHueSlider(playerModelGlow, "Ally Hue", 'player_model_glow_ally_hue', 0, 359); + createHueSlider(playerModelGlow, "hue_ally", 'player_model_glow_ally_hue', 0, 359); )" // split the string literal because MSVC does not support string literals longer than 16k chars - error C2026 u8R"( - var weaponModelGlow = createSection(modelGlowTab, 'Weapons'); - createYesNoDropDown(weaponModelGlow, "Glow Weapon Models on Ground", 'visuals', 'weapon_model_glow'); + var weaponModelGlow = createSection(modelGlowTab, 'sec_weapons'); + createYesNoDropDown(weaponModelGlow, "lbl_mglow_weapons", 'visuals', 'weapon_model_glow'); separator(weaponModelGlow); - createYesNoDropDown(weaponModelGlow, "Glow Grenade Projectile Models", 'visuals', 'grenade_proj_model_glow'); + createYesNoDropDown(weaponModelGlow, "lbl_mglow_grenade", 'visuals', 'grenade_proj_model_glow'); separator(weaponModelGlow); separator(weaponModelGlow); - createHueSlider(weaponModelGlow, "Flashbang Hue", 'model_glow_flashbang_hue', 191, 250); + createHueSlider(weaponModelGlow, "hue_flashbang", 'model_glow_flashbang_hue', 191, 250); separator(weaponModelGlow); - createHueSlider(weaponModelGlow, "HE Grenade Hue", 'model_glow_hegrenade_hue', 300, 359); + createHueSlider(weaponModelGlow, "hue_hegrenade", 'model_glow_hegrenade_hue', 300, 359); separator(weaponModelGlow); - createHueSlider(weaponModelGlow, "Smoke Grenade Hue", 'model_glow_smoke_hue', 110, 140); + createHueSlider(weaponModelGlow, "hue_smoke", 'model_glow_smoke_hue', 110, 140); separator(weaponModelGlow); - createHueSlider(weaponModelGlow, "Molotov / Incendiary Grenade Hue", 'model_glow_molotov_hue', 20, 60); + createHueSlider(weaponModelGlow, "hue_molotov", 'model_glow_molotov_hue', 20, 60); - var bombModelGlow = createSection(modelGlowTab, 'Bomb & Defuse Kit'); - createYesNoDropDown(bombModelGlow, "Glow Dropped Bomb Model", 'visuals', 'dropped_bomb_model_glow'); + var bombModelGlow = createSection(modelGlowTab, 'sec_bomb_defuse'); + createYesNoDropDown(bombModelGlow, "lbl_mglow_bomb_drop", 'visuals', 'dropped_bomb_model_glow'); separator(bombModelGlow); - createYesNoDropDown(bombModelGlow, "Glow Ticking Bomb Model", 'visuals', 'ticking_bomb_model_glow'); + createYesNoDropDown(bombModelGlow, "lbl_mglow_bomb_tick", 'visuals', 'ticking_bomb_model_glow'); separator(bombModelGlow); - createYesNoDropDown(bombModelGlow, "Glow Defuse Kit Models on Ground", 'visuals', 'defuse_kit_model_glow'); + createYesNoDropDown(bombModelGlow, "lbl_mglow_defuse", 'visuals', 'defuse_kit_model_glow'); separator(bombModelGlow); separator(bombModelGlow); - createHueSlider(bombModelGlow, "Dropped Bomb Hue", 'model_glow_dropped_bomb_hue', 0, 359); + createHueSlider(bombModelGlow, "hue_dropped_bomb", 'model_glow_dropped_bomb_hue', 0, 359); separator(bombModelGlow); - createHueSlider(bombModelGlow, "Ticking Bomb Hue", 'model_glow_ticking_bomb_hue', 0, 359); + createHueSlider(bombModelGlow, "hue_ticking_bomb", 'model_glow_ticking_bomb_hue', 0, 359); separator(bombModelGlow); - createHueSlider(bombModelGlow, "Defuse Kit Hue", 'model_glow_defuse_kit_hue', 0, 359); + createHueSlider(bombModelGlow, "hue_defuse_kit", 'model_glow_defuse_kit_hue', 0, 359); var _viewmodelTab = createSubTab(visuals, 'viewmodel'); _viewmodelTab.style.overflow = 'squish squish'; _viewmodelTab.style.flowChildren = 'right'; var viewmodelPreviewContainer = $.CreatePanel('Panel', _viewmodelTab, '', { style: 'flow-children: down;' }); - $.CreatePanel('Label', viewmodelPreviewContainer, '', { style: 'vertical-align: top; horizontal-align: center; font-size: 40;', text: 'Preview' }); + var viewmodelPreviewLabel = $.CreatePanel('Label', viewmodelPreviewContainer, '', { style: 'vertical-align: top; horizontal-align: center; font-size: 40;', text: tr('preview_title') }); + register(viewmodelPreviewLabel, 'preview_title'); var viewmodelPreview = $.CreatePanel('MapItemPreviewPanel', viewmodelPreviewContainer, 'ViewmodelPreview', { map: "ui/xpshop_item", @@ -723,33 +927,33 @@ u8R"( var viewmodelTab = $.CreatePanel('Panel', _viewmodelTab, '', { style: 'flow-children: down; margin-right: 40px; overflow: squish scroll;' }); - var viewmodelModification = createSection(viewmodelTab, 'Viewmodel Modification'); - createOnOffDropDown(viewmodelModification, "Master Switch", 'visuals', 'viewmodel_mod'); + var viewmodelModification = createSection(viewmodelTab, 'sec_viewmodel_mod'); + createOnOffDropDown(viewmodelModification, "lbl_master_switch", 'visuals', 'viewmodel_mod'); - var viewmodelFov = createSection(viewmodelTab, 'Viewmodel Fov'); - createYesNoDropDown(viewmodelFov, "Modify Viewmodel Fov", 'visuals', 'viewmodel_fov_mod'); + var viewmodelFov = createSection(viewmodelTab, 'sec_viewmodel_fov'); + createYesNoDropDown(viewmodelFov, "lbl_fov_mod", 'visuals', 'viewmodel_fov_mod'); separator(viewmodelFov); - createSlider(viewmodelFov, "Fov", 'viewmodel_fov', 40, 90); + createSlider(viewmodelFov, "lbl_fov", 'viewmodel_fov', 40, 90); $.Osiris.navigateToSubTab('visuals', 'player_info'); var sound = createTab('sound'); - - var playerSoundVisualization = createSection(sound, 'Player Sound Visualization'); + + var playerSoundVisualization = createSection(sound, 'sec_player_sound'); separator(playerSoundVisualization); - createYesNoDropDown(playerSoundVisualization, "Visualize Player Footstep Sound", 'sound', 'visualize_player_footsteps'); + createYesNoDropDown(playerSoundVisualization, "lbl_footstep_vis", 'sound', 'visualize_player_footsteps'); - var bombSoundVisualization = createSection(sound, 'Bomb Sound Visualization'); - createYesNoDropDown(bombSoundVisualization, "Visualize Bomb Plant Sound", 'sound', 'visualize_bomb_plant'); + var bombSoundVisualization = createSection(sound, 'sec_bomb_sound'); + createYesNoDropDown(bombSoundVisualization, "lbl_bomb_plant_vis", 'sound', 'visualize_bomb_plant'); separator(bombSoundVisualization); - createYesNoDropDown(bombSoundVisualization, "Visualize Bomb Beep Sound", 'sound', 'visualize_bomb_beep'); + createYesNoDropDown(bombSoundVisualization, "lbl_bomb_beep_vis", 'sound', 'visualize_bomb_beep'); separator(bombSoundVisualization); - createYesNoDropDown(bombSoundVisualization, "Visualize Bomb Defuse Sound", 'sound', 'visualize_bomb_defuse'); + createYesNoDropDown(bombSoundVisualization, "lbl_bomb_defuse_vis", 'sound', 'visualize_bomb_defuse'); - var weaponSoundVisualization = createSection(sound, 'Weapon Sound Visualization'); - createYesNoDropDown(weaponSoundVisualization, "Visualize Weapon Scope Sound", 'sound', 'visualize_scope_sound'); + var weaponSoundVisualization = createSection(sound, 'sec_weapon_sound'); + createYesNoDropDown(weaponSoundVisualization, "lbl_scope_vis", 'sound', 'visualize_scope_sound'); separator(weaponSoundVisualization); - createYesNoDropDown(weaponSoundVisualization, "Visualize Weapon Reload Sound", 'sound', 'visualize_reload_sound'); + createYesNoDropDown(weaponSoundVisualization, "lbl_reload_vis", 'sound', 'visualize_reload_sound'); $.Osiris.navigateToTab('hud'); })(); diff --git a/Source/UI/Panorama/PanoramaCommandDispatcher.h b/Source/UI/Panorama/PanoramaCommandDispatcher.h index 751cecd2818..c7e3d065325 100644 --- a/Source/UI/Panorama/PanoramaCommandDispatcher.h +++ b/Source/UI/Panorama/PanoramaCommandDispatcher.h @@ -2,6 +2,7 @@ #include +#include #include #include @@ -39,6 +40,12 @@ struct PanoramaCommandDispatcher { SetCommandHandler{parser, hookContext}(); } else if (command == "restore_defaults") { hookContext.config().restoreDefaults(); + } else if (command == "language") { + unsigned int langValue = 0; + const auto langStr = parseNextCommand(); + StringParser{langStr.data()}.parseInt(langValue); + if (langValue <= 1) + hookContext.config().template setVariable(static_cast(langValue)); } } diff --git a/Source/UI/Panorama/PanoramaGUI.h b/Source/UI/Panorama/PanoramaGUI.h index a7dc9196da3..391df8b1b88 100644 --- a/Source/UI/Panorama/PanoramaGUI.h +++ b/Source/UI/Panorama/PanoramaGUI.h @@ -116,6 +116,14 @@ class PanoramaGUI { if (settings) state().settingsPanelHandle = settings.getHandle(); + // inject language setting from config before UI creation + { + StringBuilderStorage<64> langStorage; + auto langBuilder = langStorage.builder(); + langBuilder.put("$.Osiris = $.Osiris || {}; $.Osiris.language = ", static_cast(GET_CONFIG_VAR(Language)), ";"); + uiEngine().runScript(settings, langBuilder.cstring()); + } + uiEngine().runScript(settings, reinterpret_cast( #include "CreateGUI.js" )); From d625727c8ff40434cf479ab9c6b747b32f4516e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E8=AE=B0?= Date: Mon, 1 Jun 2026 15:54:33 +0800 Subject: [PATCH 9/9] Always write pattern scan log file, even when all patterns match Previously flushLogToFile() returned early when logWritePos == 0, so no log file was created when all patterns were found successfully. Now it always writes a summary line (OK or count of failures). Co-Authored-By: Claude Opus 4.8 --- Source/MemorySearch/PatternNotFoundLogger.h | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Source/MemorySearch/PatternNotFoundLogger.h b/Source/MemorySearch/PatternNotFoundLogger.h index 8b9f615c47c..8ab47417852 100644 --- a/Source/MemorySearch/PatternNotFoundLogger.h +++ b/Source/MemorySearch/PatternNotFoundLogger.h @@ -59,11 +59,26 @@ struct PatternNotFoundLogger { logDirectoryPath = path; } + static void addSummary() noexcept + { + const auto remaining = std::span{logBuffer}.subspan(logWritePos); + if (remaining.size() < 64) return; + + StringBuilder builder{remaining}; + if (totalNotFound == 0) + builder.put("[OK] All patterns matched. No failures detected.\n"); + else + builder.put("[SUMMARY] ", totalNotFound, " pattern(s) NOT found.\n"); + logWritePos += builder.string().size(); + } + static void flushLogToFile() noexcept { - if (!logDirectoryPath || logWritePos == 0) + if (!logDirectoryPath) return; + addSummary(); + #if IS_WIN64() // build full log file path: \pattern_scan.log constexpr std::wstring_view logFileName{L"\\pattern_scan.log"};