From c1df89425e6d8bddbf4e46d11786027779b44caa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BB=D0=B0=D0=B4=D0=B8=D1=81=D0=BB=D0=B0=D0=B2=20?= =?UTF-8?q?=D0=A1=D1=83=D1=85=D0=BE=D0=B2?= <22411953+Vladislav4KZ@users.noreply.github.com> Date: Tue, 13 Jan 2026 09:34:15 +0000 Subject: [PATCH 1/2] client: allow toggling hud_saytext in the spectator GUI --- cl_dll/hud/spectator_gui.cpp | 2 +- cl_dll/hud_spectator.cpp | 18 ++++++++++++++++++ cl_dll/hud_spectator.h | 1 + cl_dll/saytext.cpp | 2 +- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/cl_dll/hud/spectator_gui.cpp b/cl_dll/hud/spectator_gui.cpp index 95d9b7ae..815b8f05 100644 --- a/cl_dll/hud/spectator_gui.cpp +++ b/cl_dll/hud/spectator_gui.cpp @@ -438,7 +438,7 @@ void CHudSpectatorGui::UserCmd_ToggleSpectatorMenuOptionsSettings() if( !(m_menuFlags & MENU_OPTIONS_SETTINGS) ) { m_menuFlags |= MENU_OPTIONS_SETTINGS; - gMobileAPI.pfnTouchAddClientButton( "_spec_opt_chat_msgs", "*white", "messagemode; _spec_toggle_menu_options_settings", + gMobileAPI.pfnTouchAddClientButton( "_spec_opt_chat_msgs", "*white", "hud_saytext t; _spec_toggle_menu_options_settings", PLACE_DEFAULT_SIZE_BUTTON_AT_X_Y( 4.5f, 4.5f ), color, 0, 1.0f, 0 ); gMobileAPI.pfnTouchAddClientButton( "_spec_opt_set_status", "*white", "spec_drawstatus t; _spec_toggle_menu_options_settings", PLACE_DEFAULT_SIZE_BUTTON_AT_X_Y( 4.5f, 5.5f ), color, 0, 1.0f, 0 ); diff --git a/cl_dll/hud_spectator.cpp b/cl_dll/hud_spectator.cpp index 723b0a68..2713ea8a 100644 --- a/cl_dll/hud_spectator.cpp +++ b/cl_dll/hud_spectator.cpp @@ -188,6 +188,23 @@ void SpecPip( void ) gEngfuncs.Cvar_Set( name, gEngfuncs.Cmd_Argv(1) ); } +void HudSayText( void ) +{ + if ( gEngfuncs.Cmd_Argc() <= 1 ) + { + gEngfuncs.Con_Printf( "usage: hud_saytext <0|1>\n" ); + return; + } + + const char *name = "hud_saytext_internal"; + char *arg = gEngfuncs.Cmd_Argv(1); + + if( arg[0] == 't' && arg[1] == '\0' ) + gEngfuncs.Cvar_SetValue( name, !gEngfuncs.pfnGetCvarFloat(name) ); + else + gEngfuncs.Cvar_Set( name, gEngfuncs.Cmd_Argv(1) ); +} + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -213,6 +230,7 @@ int CHudSpectator::Init() gEngfuncs.pfnAddCommand ("spec_drawnames", SpecDrawNames ); gEngfuncs.pfnAddCommand ("spec_drawcone", SpecDrawCone ); gEngfuncs.pfnAddCommand ("spec_drawstatus", SpecDrawStatus ); + gEngfuncs.pfnAddCommand ("hud_saytext", HudSayText ); gEngfuncs.pfnAddCommand ("spec_autodirector", SpecAutoDirector ); gEngfuncs.pfnAddCommand ("spec_pip", SpecPip ); diff --git a/cl_dll/hud_spectator.h b/cl_dll/hud_spectator.h index 26ef700e..26d2ef57 100644 --- a/cl_dll/hud_spectator.h +++ b/cl_dll/hud_spectator.h @@ -99,6 +99,7 @@ class CHudSpectator : public CHudBase cvar_t * m_drawnames; cvar_t * m_drawcone; cvar_t * m_drawstatus; + cvar_t * m_HUD_saytext; cvar_t * m_autoDirector; float m_lastAutoDirector; cvar_t * m_pip; diff --git a/cl_dll/saytext.cpp b/cl_dll/saytext.cpp index bb8cd7c8..febfabab 100644 --- a/cl_dll/saytext.cpp +++ b/cl_dll/saytext.cpp @@ -55,7 +55,7 @@ int CHudSayText :: Init( void ) InitHUDData(); - m_HUD_saytext = gEngfuncs.pfnRegisterVariable( "hud_saytext", "1", 0 ); + m_HUD_saytext = gEngfuncs.pfnRegisterVariable( "hud_saytext_internal", "1", 0 ); m_HUD_saytext_time = gEngfuncs.pfnRegisterVariable( "hud_saytext_time", "5", 0 ); m_iFlags |= HUD_INTERMISSION; // is always drawn during an intermission From f9f857026108165bd755f8cd49f256cf3d68f07c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BB=D0=B0=D0=B4=D0=B8=D1=81=D0=BB=D0=B0=D0=B2=20?= =?UTF-8?q?=D0=A1=D1=83=D1=85=D0=BE=D0=B2?= <22411953+Vladislav4KZ@users.noreply.github.com> Date: Tue, 13 Jan 2026 10:30:08 +0000 Subject: [PATCH 2/2] spectator_gui: add checkboxes, arrow icons instead of `<` and `>` symbols and highlight buttons with dropdown menu client: add INSET_CHASE_LOCKED for Locked Chase Cam in the inset (Picture-in-Picture) window like in the original CS 1.6 --- cl_dll/hud.h | 5 ++ cl_dll/hud/spectator_gui.cpp | 121 +++++++++++++++++++++++++++++++++-- cl_dll/hud_spectator.cpp | 22 ++++++- cl_dll/hud_spectator.h | 9 +-- cl_dll/view.cpp | 4 ++ 5 files changed, 150 insertions(+), 11 deletions(-) diff --git a/cl_dll/hud.h b/cl_dll/hud.h index b75b3c9c..1d6d3921 100644 --- a/cl_dll/hud.h +++ b/cl_dll/hud.h @@ -918,6 +918,11 @@ class CHudSpectatorGui: public CHudBase char m_szNameAndHealth[80]; } label; int m_hTimerTexture; + int m_hChecked; + int m_hArrowDown; + int m_hArrowUp; + int m_hArrowLeft; + int m_hArrowRight; enum { ROOT_MENU = (1<<0), diff --git a/cl_dll/hud/spectator_gui.cpp b/cl_dll/hud/spectator_gui.cpp index 815b8f05..6018e13a 100644 --- a/cl_dll/hud/spectator_gui.cpp +++ b/cl_dll/hud/spectator_gui.cpp @@ -99,21 +99,69 @@ int CHudSpectatorGui::VidInit() } m_hTimerTexture = gRenderAPI.GL_LoadTexture("gfx/vgui/timer.tga", NULL, 0, TF_NEAREST |TF_NOMIPMAP|TF_CLAMP ); + m_hChecked = gRenderAPI.GL_LoadTexture("gfx/vgui/640_checked.tga", NULL, 0, TF_NEAREST | TF_NOMIPMAP | TF_CLAMP ); + m_hArrowDown = gRenderAPI.GL_LoadTexture("gfx/vgui/1920_arrowdown.tga", NULL, 0, TF_NEAREST | TF_NOMIPMAP | TF_CLAMP ); + m_hArrowUp = gRenderAPI.GL_LoadTexture("gfx/vgui/1920_arrowup.tga", NULL, 0, TF_NEAREST | TF_NOMIPMAP | TF_CLAMP ); + m_hArrowLeft = gRenderAPI.GL_LoadTexture("gfx/vgui/1920_arrowleft.tga", NULL, 0, TF_NEAREST | TF_NOMIPMAP | TF_CLAMP ); + m_hArrowRight = gRenderAPI.GL_LoadTexture("gfx/vgui/1920_arrowright.tga", NULL, 0, TF_NEAREST | TF_NOMIPMAP | TF_CLAMP ); return 1; } void CHudSpectatorGui::Shutdown() { gRenderAPI.GL_FreeTexture( m_hTimerTexture ); + gRenderAPI.GL_FreeTexture( m_hChecked ); + gRenderAPI.GL_FreeTexture( m_hArrowDown ); + gRenderAPI.GL_FreeTexture( m_hArrowUp ); + gRenderAPI.GL_FreeTexture( m_hArrowLeft ); + gRenderAPI.GL_FreeTexture( m_hArrowRight ); } -inline void DrawButtonWithText( int x1, int y1, int wide, int tall, const char *sz, int r, int g, int b ) +inline void DrawButtonWithText( int x1, int y1, int wide, int tall, const char *sz, int r, int g, int b, bool highlight = false ) { DrawUtils::DrawRectangle(x1, y1, wide, tall); + + if ( highlight ) + { + FillRGBABlend(x1, y1, wide, tall, r, g, b, 48); + } + DrawUtils::DrawHudString(x1 + INT_XPOS(0.5), y1 + tall*0.5 - gHUD.GetCharHeight() * 0.5, x1 + wide, sz, r, g, b ); } +// Unified icon drawing helper. align: -1 = left, 0 = center, 1 = right +static void DrawIconOnButton( int x1, int y1, int wide, int tall, int hTex, int align = -1, int r = 255, int g = 255, int b = 255, float alpha = 1.0f, int pad = 15 ) +{ + if( !hTex ) + return; + + gRenderAPI.GL_SelectTexture( 0 ); + gRenderAPI.GL_Bind( 0, hTex ); + gEngfuncs.pTriAPI->RenderMode( kRenderTransAlpha ); + gEngfuncs.pTriAPI->Color4f( r / 255.0f, g / 255.0f, b / 255.0f, alpha ); + + int uploadW = (int)gRenderAPI.RenderGetParm( PARM_TEX_WIDTH, hTex ); + int uploadH = (int)gRenderAPI.RenderGetParm( PARM_TEX_HEIGHT, hTex ); + + + // compute quad position in pixels + float quadX = x1; + float quadY = y1 + ( tall - uploadH ) / 2.0f; + + if( align == -1 ) // left + quadX = x1 + pad; // small padding from left + else if( align == 0 ) // center + quadX = x1 + ( wide - uploadW ) * 0.5f; + else if( align == 1 ) // right + quadX = x1 + wide - uploadW - pad; // small padding from right + + DrawUtils::Draw2DQuad( quadX * gHUD.m_flScale, + quadY * gHUD.m_flScale, + (quadX + (float)uploadW) * gHUD.m_flScale, + (quadY + (float)uploadH) * gHUD.m_flScale ); +} + int CHudSpectatorGui::Draw( float flTime ) { if( !g_iUser1 ) @@ -206,28 +254,89 @@ int CHudSpectatorGui::Draw( float flTime ) if( m_menuFlags & ROOT_MENU ) { // draw the root menu - DrawButtonWithText(INT_XPOS(0.5), INT_YPOS(8.5), INT_XPOS(4), INT_YPOS(1), "Options", r, g, b); - DrawButtonWithText(INT_XPOS(5), INT_YPOS(8.5), INT_XPOS(1), INT_YPOS(1), "<", r, g, b); + + // options + { + // highlight when opened + if( m_menuFlags & MENU_OPTIONS ) + DrawButtonWithText(INT_XPOS(0.5), INT_YPOS(8.5), INT_XPOS(4), INT_YPOS(1), "Options", r, g, b, true); + else + DrawButtonWithText(INT_XPOS(0.5), INT_YPOS(8.5), INT_XPOS(4), INT_YPOS(1), "Options", r, g, b ); + // arrow on right part of button: down when closed, up when open + if( m_menuFlags & MENU_OPTIONS ) + DrawIconOnButton( INT_XPOS(0.5), INT_YPOS(8.5), INT_XPOS(4), INT_YPOS(1), m_hArrowUp, 1, r, g, b ); + else + DrawIconOnButton( INT_XPOS(0.5), INT_YPOS(8.5), INT_XPOS(4), INT_YPOS(1), m_hArrowDown, 1, r, g, b ); + } + + DrawUtils::DrawRectangle(INT_XPOS(5), INT_YPOS(8.5), INT_XPOS(1), INT_YPOS(1)); + DrawIconOnButton( INT_XPOS(5), INT_YPOS(8.5), INT_XPOS(1), INT_YPOS(1), m_hArrowLeft, 0, r, g, b ); DrawUtils::DrawRectangle(INT_XPOS(6), INT_YPOS(8.5), INT_XPOS(4), INT_YPOS(1)); // name will be drawn later - DrawButtonWithText(INT_XPOS(10), INT_YPOS(8.5), INT_XPOS(1), INT_YPOS(1), ">", r, g, b ); - DrawButtonWithText(INT_XPOS(11.5), INT_YPOS(8.5), INT_XPOS(4), INT_YPOS(1), "Spectate Options", r, g, b); + DrawUtils::DrawRectangle(INT_XPOS(10), INT_YPOS(8.5), INT_XPOS(1), INT_YPOS(1)); + DrawIconOnButton( INT_XPOS(10), INT_YPOS(8.5), INT_XPOS(1), INT_YPOS(1), m_hArrowRight, 0, r, g, b ); + + // spectate options + { + // highlight when opened + if( m_menuFlags & MENU_SPEC_OPTIONS ) + DrawButtonWithText(INT_XPOS(11.5), INT_YPOS(8.5), INT_XPOS(4), INT_YPOS(1), "Spectate Options", r, g, b, true); + else + DrawButtonWithText(INT_XPOS(11.5), INT_YPOS(8.5), INT_XPOS(4), INT_YPOS(1), "Spectate Options", r, g, b ); + // arrow on right part of button: down when closed, up when open + if( m_menuFlags & MENU_SPEC_OPTIONS ) + DrawIconOnButton( INT_XPOS(11.5), INT_YPOS(8.5), INT_XPOS(4), INT_YPOS(1), m_hArrowUp, 1, r, g, b ); + else + DrawIconOnButton( INT_XPOS(11.5), INT_YPOS(8.5), INT_XPOS(4), INT_YPOS(1), m_hArrowDown, 1, r, g, b ); + } + if( m_menuFlags & MENU_OPTIONS ) { DrawButtonWithText(INT_XPOS(0.5), INT_YPOS(2.5), INT_XPOS(4), INT_YPOS(1), "Close", r, g, b ); DrawButtonWithText(INT_XPOS(0.5), INT_YPOS(3.5), INT_XPOS(4), INT_YPOS(1), "Help", r, g, b ); - DrawButtonWithText(INT_XPOS(0.5), INT_YPOS(4.5), INT_XPOS(4), INT_YPOS(1), "Settings", r, g, b ); + + // settings + { + // highlight when opened + if( m_menuFlags & MENU_OPTIONS_SETTINGS ) + DrawButtonWithText(INT_XPOS(0.5), INT_YPOS(4.5), INT_XPOS(4), INT_YPOS(1), "Settings", r, g, b, true ); + else + DrawButtonWithText(INT_XPOS(0.5), INT_YPOS(4.5), INT_XPOS(4), INT_YPOS(1), "Settings", r, g, b ); + + DrawIconOnButton( INT_XPOS(0.5), INT_YPOS(4.5), INT_XPOS(4), INT_YPOS(1), m_hArrowRight, 1, r, g, b ); + } + DrawButtonWithText(INT_XPOS(0.5), INT_YPOS(5.5), INT_XPOS(4), INT_YPOS(1), "Picture-in-Picture", r, g, b ); + if( gHUD.m_Spectator.m_pip && gHUD.m_Spectator.m_pip->value != INSET_OFF ) + DrawIconOnButton( INT_XPOS(0.5), INT_YPOS(5.5), INT_XPOS(4), INT_YPOS(1), m_hChecked, -1, r, g, b ); + DrawButtonWithText(INT_XPOS(0.5), INT_YPOS(6.5), INT_XPOS(4), INT_YPOS(1), "Autodirector", r, g, b ); + if( gHUD.m_Spectator.m_autoDirector && gHUD.m_Spectator.m_autoDirector->value ) + DrawIconOnButton( INT_XPOS(0.5), INT_YPOS(6.5), INT_XPOS(4), INT_YPOS(1), m_hChecked, -1, r, g, b ); + DrawButtonWithText(INT_XPOS(0.5), INT_YPOS(7.5), INT_XPOS(4), INT_YPOS(1), "Show scores", r, g, b ); + if( gHUD.m_Scoreboard.m_bForceDraw || gHUD.m_Scoreboard.m_bShowscoresHeld ) + DrawIconOnButton( INT_XPOS(0.5), INT_YPOS(7.5), INT_XPOS(4), INT_YPOS(1), m_hChecked, -1, r, g, b ); + if( m_menuFlags & MENU_OPTIONS_SETTINGS ) { DrawButtonWithText(INT_XPOS(4.5), INT_YPOS(4.5), INT_XPOS(4), INT_YPOS(1), "Chat messages", r, g, b ); + if( gHUD.m_Spectator.m_HUD_saytext && gHUD.m_Spectator.m_HUD_saytext->value ) + DrawIconOnButton( INT_XPOS(4.5), INT_YPOS(4.5), INT_XPOS(4), INT_YPOS(1), m_hChecked, -1, r, g, b ); + DrawButtonWithText(INT_XPOS(4.5), INT_YPOS(5.5), INT_XPOS(4), INT_YPOS(1), "Show status", r, g, b ); + if( gHUD.m_Spectator.m_drawstatus && gHUD.m_Spectator.m_drawstatus->value ) + DrawIconOnButton( INT_XPOS(4.5), INT_YPOS(5.5), INT_XPOS(4), INT_YPOS(1), m_hChecked, -1, r, g, b ); + DrawButtonWithText(INT_XPOS(4.5), INT_YPOS(6.5), INT_XPOS(4), INT_YPOS(1), "View cone", r, g, b ); + if( gHUD.m_Spectator.m_drawcone && gHUD.m_Spectator.m_drawcone->value ) + DrawIconOnButton( INT_XPOS(4.5), INT_YPOS(6.5), INT_XPOS(4), INT_YPOS(1), m_hChecked, -1, r, g, b ); + DrawButtonWithText(INT_XPOS(4.5), INT_YPOS(7.5), INT_XPOS(4), INT_YPOS(1), "Player names", r, g, b ); + if( gHUD.m_Spectator.m_drawnames && gHUD.m_Spectator.m_drawnames->value ) + DrawIconOnButton( INT_XPOS(4.5), INT_YPOS(7.5), INT_XPOS(4), INT_YPOS(1), m_hChecked, -1, r, g, b ); } } diff --git a/cl_dll/hud_spectator.cpp b/cl_dll/hud_spectator.cpp index 2713ea8a..f2dc1757 100644 --- a/cl_dll/hud_spectator.cpp +++ b/cl_dll/hud_spectator.cpp @@ -183,9 +183,28 @@ void SpecPip( void ) char *arg = gEngfuncs.Cmd_Argv(1); if( arg[0] == 't' && arg[1] == '\0' ) - gEngfuncs.Cvar_SetValue( name, !gEngfuncs.pfnGetCvarFloat(name) ); + { + if ( gHUD.m_Spectator.m_pip && (int)gHUD.m_Spectator.m_pip->value != INSET_OFF ) + { + gHUD.m_Spectator.SetModes( -1, INSET_OFF ); + } + else + { + // ensure overview data and map sprite are loaded before enabling PiP + gHUD.m_Spectator.ParseOverviewFile(); + gHUD.m_Spectator.LoadMapSprites(); + + // if map overview is open in fullscreen, show Locked Chase Cam in inset (player chase) + int insetMode = INSET_MAP_FREE; + if ( g_iUser1 == OBS_MAP_FREE || g_iUser1 == OBS_MAP_CHASE ) + insetMode = INSET_CHASE_LOCKED; + gHUD.m_Spectator.SetModes( -1, insetMode ); + } + } else + { gEngfuncs.Cvar_Set( name, gEngfuncs.Cmd_Argv(1) ); + } } void HudSayText( void ) @@ -238,6 +257,7 @@ int CHudSpectator::Init() m_drawcone = gEngfuncs.pfnRegisterVariable("spec_drawcone_internal","1",0); m_drawstatus = gEngfuncs.pfnRegisterVariable("spec_drawstatus_internal","1",0); m_autoDirector = gEngfuncs.pfnRegisterVariable("spec_autodirector_internal","1",0); + m_HUD_saytext = gEngfuncs.pfnRegisterVariable("hud_saytext_internal","1",0); m_pip = gEngfuncs.pfnRegisterVariable("spec_pip_internal","1",0); m_lastAutoDirector = 0.0f; diff --git a/cl_dll/hud_spectator.h b/cl_dll/hud_spectator.h index 26d2ef57..e98c4ef7 100644 --- a/cl_dll/hud_spectator.h +++ b/cl_dll/hud_spectator.h @@ -13,10 +13,11 @@ #define INSET_OFF 0 -#define INSET_CHASE_FREE 1 -#define INSET_IN_EYE 2 -#define INSET_MAP_FREE 3 -#define INSET_MAP_CHASE 4 +#define INSET_CHASE_LOCKED 1 +#define INSET_CHASE_FREE 2 +#define INSET_IN_EYE 3 +#define INSET_MAP_FREE 4 +#define INSET_MAP_CHASE 5 #define MAX_SPEC_HUD_MESSAGES 8 diff --git a/cl_dll/view.cpp b/cl_dll/view.cpp index 579b2d6c..b3a56515 100644 --- a/cl_dll/view.cpp +++ b/cl_dll/view.cpp @@ -1725,6 +1725,10 @@ void V_CalcSpectatorRefdef ( struct ref_params_s * pparams ) // override some settings in certain modes switch ( (int)gHUD.m_Spectator.m_pip->value ) { + case INSET_CHASE_LOCKED: + V_GetChasePos( g_iUser2, NULL, v_origin, v_angles ); + break; + case INSET_CHASE_FREE: V_GetChasePos( g_iUser2, v_cl_angles, v_origin, v_angles ); break;