From f4c4190791d83fa7874db84cb0421db57fec7951 Mon Sep 17 00:00:00 2001 From: AdamTadeusz Date: Sun, 2 Aug 2026 07:47:31 +0100 Subject: [PATCH 1/5] minimum delay of a quarter of a second between switching speaking type --- src/game/client/c_baseplayer.cpp | 5 ++ src/game/client/hud_voicestatus.cpp | 4 ++ src/game/client/in_main.cpp | 71 +++++++++++++++++++++++ src/game/client/in_main.h | 4 ++ src/game/shared/gamerules.cpp | 2 + src/game/shared/hl2mp/hl2mp_gamerules.cpp | 4 ++ src/game/shared/neo/neo_gamerules.cpp | 42 ++++++++++++++ src/game/shared/neo/neo_gamerules.h | 2 + src/game/shared/usercmd.cpp | 20 +++++++ src/game/shared/usercmd.h | 12 ++++ src/game/shared/voice_common.h | 13 +++++ src/game/shared/voice_gamemgr.cpp | 44 +++++++++++++- 12 files changed, 221 insertions(+), 2 deletions(-) diff --git a/src/game/client/c_baseplayer.cpp b/src/game/client/c_baseplayer.cpp index 21e5f5170f..f414c64af0 100644 --- a/src/game/client/c_baseplayer.cpp +++ b/src/game/client/c_baseplayer.cpp @@ -65,6 +65,7 @@ #include "c_neo_player.h" #include "weapon_tachi.h" #include "ivieweffects.h" +#include "in_main.h" #endif // memdbgon must be the last include file in a .cpp file!!! @@ -1373,6 +1374,10 @@ bool C_BasePlayer::CreateMove( float flInputSampleTime, CUserCmd *pCmd ) // Check to see if we're in vgui input mode... DetermineVguiInputMode( pCmd ); +#ifdef NEO + pCmd->voiceTransmitType = GetVoiceTransmitType(); +#endif // NEO + return true; } diff --git a/src/game/client/hud_voicestatus.cpp b/src/game/client/hud_voicestatus.cpp index b52446012c..b3739f5db0 100644 --- a/src/game/client/hud_voicestatus.cpp +++ b/src/game/client/hud_voicestatus.cpp @@ -208,7 +208,11 @@ void CHudVoiceStatus::OnThink( void ) for ( int iPlayerIndex=1; iPlayerIndex<=gpGlobals->maxClients; iPlayerIndex++ ) { int activeSpeakerIndex = FindActiveSpeaker(iPlayerIndex); +#ifdef NEO + bool bSpeaking = GetClientVoiceMgr()->IsPlayerAudible(iPlayerIndex); +#else bool bSpeaking = GetClientVoiceMgr()->IsPlayerSpeaking(iPlayerIndex); +#endif // NEO if (activeSpeakerIndex != m_SpeakingList.InvalidIndex() ) { diff --git a/src/game/client/in_main.cpp b/src/game/client/in_main.cpp index a414ab7b87..f886f70947 100644 --- a/src/game/client/in_main.cpp +++ b/src/game/client/in_main.cpp @@ -161,6 +161,8 @@ static kbutton_t in_thermoptic; static kbutton_t in_vision; static kbutton_t in_spec_next; static kbutton_t in_spec_prev; +static kbutton_t in_voicerecordlocal; +static kbutton_t in_voicerecordglobalteam; #endif /* @@ -678,6 +680,67 @@ void IN_SpecNextDown(const CCommand &args) { KeyDown(&in_spec_next, args[1]); } void IN_SpecPrevUp(const CCommand &args) { KeyUp(&in_spec_prev, args[1]); } void IN_SpecPrevDown(const CCommand &args) { KeyDown(&in_spec_prev, args[1]); } +static NeoVoiceTransmitType voiceTransmitType = NeoVoiceTransmitType::NEO_VOICE_TRANSMIT_NONE; +NeoVoiceTransmitType GetVoiceTransmitType() +{ + return voiceTransmitType; +} + +static float nextLocalSpeakTime = 0.f; +static float nextGlobalTeamSpeakTime = 0.f; +static constexpr float DELAY_BETWEEN_SWITCHING_SPEAK_TYPE = 0.25f; +void IN_VoiceRecordLocalUp( const CCommand &args ) { + KeyUp(&in_voicerecordlocal, args[1]); + if (const bool isSpeaking = GetClientVoiceMgr() ? GetClientVoiceMgr()->IsLocalPlayerSpeaking() : true; + isSpeaking && GetVoiceTransmitType() == NeoVoiceTransmitType::NEO_VOICE_TRANSMIT_LOCAL) + { + nextGlobalTeamSpeakTime = Max(nextGlobalTeamSpeakTime, gpGlobals->curtime + DELAY_BETWEEN_SWITCHING_SPEAK_TYPE); + engine->ExecuteClientCmd("-voicerecord"); + voiceTransmitType = NeoVoiceTransmitType::NEO_VOICE_TRANSMIT_NONE; + } +} +void IN_VoiceRecordLocalDown(const CCommand& args) { + KeyDown(&in_voicerecordlocal, args[1]); + if (gpGlobals->curtime < nextLocalSpeakTime) + { + return; // NEO TODO (Adam) Show in local voice status ui element that cannot talk, and clear when releasing button + } + + if (const bool isSpeaking = GetClientVoiceMgr() ? GetClientVoiceMgr()->IsLocalPlayerSpeaking() : true) + { + return; + } + + voiceTransmitType = NeoVoiceTransmitType::NEO_VOICE_TRANSMIT_LOCAL; + engine->ExecuteClientCmd("+voicerecord"); +} + +void IN_VoiceRecordGlobalTeamUp( const CCommand &args ) { + KeyUp(&in_voicerecordglobalteam, args[1]); + if (const bool isSpeaking = GetClientVoiceMgr() ? GetClientVoiceMgr()->IsLocalPlayerSpeaking() : true; + isSpeaking && GetVoiceTransmitType() == NeoVoiceTransmitType::NEO_VOICE_TRANSMIT_GLOBALTEAM) + { + nextLocalSpeakTime = Max(nextLocalSpeakTime, gpGlobals->curtime + DELAY_BETWEEN_SWITCHING_SPEAK_TYPE); + engine->ExecuteClientCmd("-voicerecord"); + voiceTransmitType = NeoVoiceTransmitType::NEO_VOICE_TRANSMIT_NONE; + } +} +void IN_VoiceRecordGlobalTeamDown(const CCommand& args) { + KeyDown(&in_voicerecordglobalteam, args[1]); + if (gpGlobals->curtime < nextGlobalTeamSpeakTime) + { + return; + } + + if (const bool isSpeaking = GetClientVoiceMgr() ? GetClientVoiceMgr()->IsLocalPlayerSpeaking() : true) + { + return; + } + + voiceTransmitType = NeoVoiceTransmitType::NEO_VOICE_TRANSMIT_GLOBALTEAM; + engine->ExecuteClientCmd("+voicerecord"); +} + void IN_LeanLeftToggle(const CCommand& args) { if (::input->KeyState(&in_lean_left)) @@ -1946,6 +2009,12 @@ static ConCommand endspecnextplayer("-specnextplayer", IN_SpecNextUp); static ConCommand startspecprevplayer("+specprevplayer", IN_SpecPrevDown); static ConCommand endspecprevplayer("-specprevplayer", IN_SpecPrevUp); + +static ConCommand startvoicerecordlocal("+voicerecordlocal", IN_VoiceRecordLocalDown); +static ConCommand endvoicerecordlocal("-voicerecordlocal", IN_VoiceRecordLocalUp); + +static ConCommand startvoicerecordglobalteam("+voicerecordglobalteam", IN_VoiceRecordGlobalTeamDown); +static ConCommand endvoicerecordglobalteam("-voicerecordglobalteam", IN_VoiceRecordGlobalTeamUp); #endif /* @@ -2018,6 +2087,8 @@ void CInput::LevelInit( void ) #ifdef NEO nextMouseWheelUp = 0.f; nextMouseWheelDown = 0.f; + nextLocalSpeakTime = 0.f; + nextGlobalTeamSpeakTime = 0.f; #endif // NEO } diff --git a/src/game/client/in_main.h b/src/game/client/in_main.h index eb42cd44f7..eea22aa4ad 100644 --- a/src/game/client/in_main.h +++ b/src/game/client/in_main.h @@ -13,6 +13,9 @@ #include "kbutton.h" +#ifdef NEO +#include "voice_common.h" +#endif // NEO extern kbutton_t in_commandermousemove; @@ -25,6 +28,7 @@ void IN_LeanReset(); void IN_SpeedReset(); void IN_LeanToggleReset(); void LiftAllToggleKeys(); +NeoVoiceTransmitType GetVoiceTransmitType(); #endif // NEO #endif // IN_MAIN_H diff --git a/src/game/shared/gamerules.cpp b/src/game/shared/gamerules.cpp index 3c2c432779..b4dd4fd8c7 100644 --- a/src/game/shared/gamerules.cpp +++ b/src/game/shared/gamerules.cpp @@ -619,7 +619,9 @@ ConVar skill( "skill", "1" ); void CGameRules::Think() { +#ifndef NEO // NEO TODO (Adam) do this here once neogamerules::Think is fixed GetVoiceGameMgr()->Update( gpGlobals->frametime ); +#endif // NEO SetSkillLevel( skill.GetInt() ); if ( log_verbose_enable.GetBool() ) diff --git a/src/game/shared/hl2mp/hl2mp_gamerules.cpp b/src/game/shared/hl2mp/hl2mp_gamerules.cpp index e5ec839e10..15dbd61db2 100644 --- a/src/game/shared/hl2mp/hl2mp_gamerules.cpp +++ b/src/game/shared/hl2mp/hl2mp_gamerules.cpp @@ -30,7 +30,9 @@ #include "hl2mp_player.h" #include "weapon_hl2mpbasehlmpcombatweapon.h" #include "team.h" +#ifndef NEO #include "voice_gamemgr.h" +#endif // NEO #include "hl2mp_gameinterface.h" #include "hl2mp_cvars.h" @@ -168,6 +170,7 @@ static const char *s_PreserveEnts[] = END_SEND_TABLE() #endif +#ifndef NEO #ifndef CLIENT_DLL class CVoiceGameMgrHelper : public IVoiceGameMgrHelper @@ -182,6 +185,7 @@ static const char *s_PreserveEnts[] = IVoiceGameMgrHelper *g_pVoiceGameMgrHelper = &g_VoiceGameMgrHelper; #endif +#endif // NEO // NOTE: the indices here must match TEAM_TERRORIST, TEAM_CT, TEAM_SPECTATOR, etc. const char *sTeamNames[] = diff --git a/src/game/shared/neo/neo_gamerules.cpp b/src/game/shared/neo/neo_gamerules.cpp index b6faeae680..883fa032db 100644 --- a/src/game/shared/neo/neo_gamerules.cpp +++ b/src/game/shared/neo/neo_gamerules.cpp @@ -36,6 +36,8 @@ #include "neo_npc_dummy.h" #include "materialsystem/imaterialsystem.h" #include "neo_gamerules_restore.h" +#include "voice_gamemgr.h" +#include "voice_common.h" #include @@ -261,6 +263,45 @@ void sndVictoryVolumeChangeCallback(IConVar* cvar [[maybe_unused]], const char* ConVar snd_victory_volume("snd_victory_volume", "0.33", FCVAR_ARCHIVE | FCVAR_DONTRECORD | FCVAR_USERINFO, "Loudness of the victory jingle (0-1).", true, 0.0, true, 1.0, sndVictoryVolumeChangeCallback); #endif // CLIENT_DLL +#ifdef GAME_DLL + class CVoiceGameMgrHelper : public IVoiceGameMgrHelper + { + public: + virtual bool CanPlayerHearPlayer( CBasePlayer *pListener, CBasePlayer *pTalker, bool &bProximity ) + { + if (const bool notTalking = pTalker->GetLastUserCommand() ? (pTalker->GetLastUserCommand()->voiceTransmitType == NeoVoiceTransmitType::NEO_VOICE_TRANSMIT_NONE) : true) + { + return false; + } + + if (const bool talkingLocally = pTalker->GetLastUserCommand() ? (pTalker->GetLastUserCommand()->voiceTransmitType == NeoVoiceTransmitType::NEO_VOICE_TRANSMIT_LOCAL) : false; + talkingLocally && pTalker->IsAlive()) + { + bProximity = true; + return true; + } + + if (pListener->GetTeamNumber() == pTalker->GetTeamNumber()) + { + return true; + } + + if (pListener->GetTeamNumber() == TEAM_SPECTATOR && (pListener->GetObserverMode() == OBS_MODE_IN_EYE || pListener->GetObserverMode() == OBS_MODE_CHASE)) + { + if (CBaseEntity* pListenerSpectateTarget = static_cast(pListener->GetObserverTarget()); + pListenerSpectateTarget && pListenerSpectateTarget->GetTeamNumber() == pTalker->GetTeamNumber()) + { + return true; + } + } + + return false; + } + }; + CVoiceGameMgrHelper g_VoiceGameMgrHelper; + IVoiceGameMgrHelper *g_pVoiceGameMgrHelper = &g_VoiceGameMgrHelper; +#endif + #ifdef CLIENT_DLL void CNEOGameRulesProxy::OnDataChanged(DataUpdateType_t updateType) { @@ -1124,6 +1165,7 @@ bool CNEORules::CheckShouldNotThink() void CNEORules::Think(void) { #ifdef GAME_DLL + GetVoiceGameMgr()->Update( gpGlobals->frametime ); CheckGameConfig(); if (CheckShouldNotThink()) { diff --git a/src/game/shared/neo/neo_gamerules.h b/src/game/shared/neo/neo_gamerules.h index 7d3045be32..9c9492a980 100644 --- a/src/game/shared/neo/neo_gamerules.h +++ b/src/game/shared/neo/neo_gamerules.h @@ -421,6 +421,8 @@ class CNEORules : public CHL2MPRules, public CGameEventListener bool IsClassFull(int team, int classId) const; int GetFallbackClass(int team, int preferredClass) const; + bool ShouldDrawHeadLabels() override { return false; }; + public: #ifdef GAME_DLL // Workaround for bot spawning. See Bot_f() for details. diff --git a/src/game/shared/usercmd.cpp b/src/game/shared/usercmd.cpp index 84ffa24314..398963dc99 100644 --- a/src/game/shared/usercmd.cpp +++ b/src/game/shared/usercmd.cpp @@ -9,6 +9,9 @@ #include "usercmd.h" #include "bitbuf.h" #include "checksum_md5.h" +#ifdef NEO +#include "voice_common.h" +#endif // NEO // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -169,6 +172,17 @@ void WriteUsercmd( bf_write *buf, const CUserCmd *to, const CUserCmd *from ) buf->WriteOneBit( 0 ); } +#ifdef NEO + if (to->voiceTransmitType != from->voiceTransmitType) + { + buf->WriteOneBit(1); + buf->WriteUBitLong(to->voiceTransmitType, NEO_VOICE_TRANSMIT__BITS); + } + else + { + buf->WriteOneBit(0); + } +#endif // NEO #if defined( HL2_CLIENT_DLL ) if ( to->entitygroundcontact.Count() != 0 ) { @@ -288,6 +302,12 @@ void ReadUsercmd( bf_read *buf, CUserCmd *move, CUserCmd *from ) { move->mousedy = buf->ReadShort(); } +#ifdef NEO + if (buf->ReadOneBit()) + { + move->voiceTransmitType = buf->ReadUBitLong(NEO_VOICE_TRANSMIT__BITS); + } +#endif // NEO #if defined( HL2_DLL ) if ( buf->ReadOneBit() ) diff --git a/src/game/shared/usercmd.h b/src/game/shared/usercmd.h index 0005bde99f..53bdac0335 100644 --- a/src/game/shared/usercmd.h +++ b/src/game/shared/usercmd.h @@ -56,6 +56,9 @@ class CUserCmd #endif mousedx = 0; mousedy = 0; +#ifdef NEO + voiceTransmitType = 0; +#endif // NEO hasbeenpredicted = false; #if defined( HL2_DLL ) || defined( HL2_CLIENT_DLL ) @@ -84,6 +87,9 @@ class CUserCmd #endif mousedx = src.mousedx; mousedy = src.mousedy; +#ifdef NEO + voiceTransmitType = src.voiceTransmitType; +#endif // NEO hasbeenpredicted = src.hasbeenpredicted; @@ -117,6 +123,9 @@ class CUserCmd CRC32_ProcessBuffer( &crc, &random_seed, sizeof( random_seed ) ); CRC32_ProcessBuffer( &crc, &mousedx, sizeof( mousedx ) ); CRC32_ProcessBuffer( &crc, &mousedy, sizeof( mousedy ) ); +#ifdef NEO + CRC32_ProcessBuffer( &crc, &voiceTransmitType, sizeof( voiceTransmitType ) ); +#endif // NEO CRC32_Final( &crc ); return crc; @@ -163,6 +172,9 @@ class CUserCmd short mousedx; // mouse accum in x from create move short mousedy; // mouse accum in y from create move +#ifdef NEO + int voiceTransmitType; +#endif // NEO // Client only, tracks whether we've predicted this command at least once bool hasbeenpredicted; diff --git a/src/game/shared/voice_common.h b/src/game/shared/voice_common.h index 86511e9db2..1540b750b0 100644 --- a/src/game/shared/voice_common.h +++ b/src/game/shared/voice_common.h @@ -23,5 +23,18 @@ typedef CBitVec CPlayerBitVec; #define VOICE_DEFAULT_PROXIMITY_RANGE 1200 //100 feet +#ifdef NEO +enum NeoVoiceTransmitType +{ + NEO_VOICE_TRANSMIT_NONE = 0, + NEO_VOICE_TRANSMIT_GLOBALTEAM, + NEO_VOICE_TRANSMIT_LOCAL, + + NEO_VOICE_TRANSMIT__TOTAL = NEO_VOICE_TRANSMIT_LOCAL, + NEO_VOICE_TRANSMIT__BITS = 2 +}; + +COMPILE_TIME_ASSERT(1 << NEO_VOICE_TRANSMIT__BITS > NEO_VOICE_TRANSMIT__TOTAL); +#endif // NEO #endif // VOICE_COMMON_H diff --git a/src/game/shared/voice_gamemgr.cpp b/src/game/shared/voice_gamemgr.cpp index 7993389638..30fc75f578 100644 --- a/src/game/shared/voice_gamemgr.cpp +++ b/src/game/shared/voice_gamemgr.cpp @@ -17,7 +17,11 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" +#ifdef NEO +#define UPDATE_INTERVAL 0.1 +#else #define UPDATE_INTERVAL 0.3 +#endif // NEO // These are stored off as CVoiceGameMgr is created and deleted. @@ -103,7 +107,11 @@ CVoiceGameMgr::CVoiceGameMgr() { m_UpdateInterval = 0; m_nMaxPlayers = 0; +#ifdef NEO + m_iProximityDistance = 10; +#else m_iProximityDistance = -1; +#endif // NEO } @@ -117,7 +125,7 @@ bool CVoiceGameMgr::Init( { m_pHelper = pHelper; m_nMaxPlayers = VOICE_MAX_PLAYERS < maxClients ? VOICE_MAX_PLAYERS : maxClients; - + return true; } @@ -132,8 +140,10 @@ void CVoiceGameMgr::Update(double frametime) { // Only update periodically. m_UpdateInterval += frametime; +#ifndef NEO if(m_UpdateInterval < UPDATE_INTERVAL) return; +#endif // NEO UpdateMasks(); } @@ -199,7 +209,9 @@ bool CVoiceGameMgr::ClientCommand( CBasePlayer *pPlayer, const CCommand &args ) void CVoiceGameMgr::UpdateMasks() { +#ifndef NEO m_UpdateInterval = 0; +#endif // NEO bool bAllTalk = !!sv_alltalk.GetInt(); @@ -241,34 +253,62 @@ void CVoiceGameMgr::UpdateMasks() } } +#ifdef NEO + if (m_UpdateInterval >= UPDATE_INTERVAL) + { + m_UpdateInterval = 0; + CPlayerBitVec NotProximityMask; + ProximityMask.Not(&NotProximityMask); + CPlayerBitVec NonProximityGameRulesMask; + gameRulesMask.And(NotProximityMask, &NonProximityGameRulesMask); +#endif // NEO // If this is different from what the client has, send an update. +#ifdef NEO + if(NonProximityGameRulesMask != g_SentGameRulesMasks[iClient] || +#else if(gameRulesMask != g_SentGameRulesMasks[iClient] || +#endif // NEO g_BanMasks[iClient] != g_SentBanMasks[iClient]) { +#ifdef NEO + g_SentGameRulesMasks[iClient] = NonProximityGameRulesMask; +#else g_SentGameRulesMasks[iClient] = gameRulesMask; +#endif // NEO g_SentBanMasks[iClient] = g_BanMasks[iClient]; UserMessageBegin( user, "VoiceMask" ); int dw; for(dw=0; dw < VOICE_MAX_PLAYERS_DW; dw++) { +#ifdef NEO + WRITE_LONG(NonProximityGameRulesMask.GetDWord(dw)); +#else WRITE_LONG(gameRulesMask.GetDWord(dw)); +#endif // NEO WRITE_LONG(g_BanMasks[iClient].GetDWord(dw)); } WRITE_BYTE( !!g_PlayerModEnable[iClient] ); MessageEnd(); } +#ifdef NEO + } +#endif // NEO // Tell the engine. for(int iOtherClient=0; iOtherClient < m_nMaxPlayers; iOtherClient++) { bool bCanHear = gameRulesMask[iOtherClient] && !g_BanMasks[iClient][iOtherClient]; g_pVoiceServer->SetClientListening( iClient+1, iOtherClient+1, bCanHear ); - + +#ifdef NEO + g_pVoiceServer->SetClientProximity( iClient+1, iOtherClient+1, !!ProximityMask[iOtherClient] ); +#else if ( bCanHear ) { g_pVoiceServer->SetClientProximity( iClient+1, iOtherClient+1, !!ProximityMask[iOtherClient] ); } +#endif // NEO } } } From 979ee7c2ae8315ddd3992568ab9e23e6426ed452 Mon Sep 17 00:00:00 2001 From: AdamTadeusz Date: Sun, 2 Aug 2026 07:58:31 +0100 Subject: [PATCH 2/5] cleanup, comment --- src/game/shared/voice_gamemgr.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/game/shared/voice_gamemgr.cpp b/src/game/shared/voice_gamemgr.cpp index 30fc75f578..afe88da2db 100644 --- a/src/game/shared/voice_gamemgr.cpp +++ b/src/game/shared/voice_gamemgr.cpp @@ -257,6 +257,9 @@ void CVoiceGameMgr::UpdateMasks() if (m_UpdateInterval >= UPDATE_INTERVAL) { m_UpdateInterval = 0; + + // I do not want the local player to know which players are speaking in proximity, instead of appending a new long to + // the VoiceMask message, pass a new mask that contains all speaking players - players speaking locally CPlayerBitVec NotProximityMask; ProximityMask.Not(&NotProximityMask); CPlayerBitVec NonProximityGameRulesMask; @@ -300,15 +303,10 @@ void CVoiceGameMgr::UpdateMasks() { bool bCanHear = gameRulesMask[iOtherClient] && !g_BanMasks[iClient][iOtherClient]; g_pVoiceServer->SetClientListening( iClient+1, iOtherClient+1, bCanHear ); - -#ifdef NEO - g_pVoiceServer->SetClientProximity( iClient+1, iOtherClient+1, !!ProximityMask[iOtherClient] ); -#else if ( bCanHear ) { g_pVoiceServer->SetClientProximity( iClient+1, iOtherClient+1, !!ProximityMask[iOtherClient] ); } -#endif // NEO } } } From ab633c0940e1a3401660f4519d25559e7644dc38 Mon Sep 17 00:00:00 2001 From: AdamTadeusz Date: Sun, 2 Aug 2026 12:50:36 +0100 Subject: [PATCH 3/5] better hud, still not quite right with proximity chat and sv_alltalk 1 --- game/neo/scripts/kb_act.lst | 3 +- game/neo/scripts/mod_textures.txt | 24 +++++++++++ src/game/client/hud_voicestatus.cpp | 52 +++++++++++++++++++++++- src/game/client/in_main.cpp | 6 ++- src/game/client/in_main.h | 1 + src/game/shared/hl2/hl2_usermessages.cpp | 4 ++ src/game/shared/sdk/sdk_usermessages.cpp | 2 +- src/game/shared/voice_gamemgr.cpp | 45 ++++++++++++-------- src/game/shared/voice_status.cpp | 17 ++++++++ src/game/shared/voice_status.h | 9 ++++ 10 files changed, 141 insertions(+), 22 deletions(-) diff --git a/game/neo/scripts/kb_act.lst b/game/neo/scripts/kb_act.lst index 966a3d43e2..fc68ad3c09 100644 --- a/game/neo/scripts/kb_act.lst +++ b/game/neo/scripts/kb_act.lst @@ -44,7 +44,8 @@ "blank" "==========================" "blank" "#Valve_Communication_Title" "blank" "==========================" -"+voicerecord" "#Valve_Use_Voice_Communication" +"+voicerecordglobalteam" "Team / Global Voice Chat" +"+voicerecordlocal" "Proximity Voice Chat" "messagemode" "#Valve_Chat_Message" "messagemode2" "#Valve_Team_Message" "+attack3" "Ping Location" diff --git a/game/neo/scripts/mod_textures.txt b/game/neo/scripts/mod_textures.txt index 3479d785dc..6b9caeae42 100644 --- a/game/neo/scripts/mod_textures.txt +++ b/game/neo/scripts/mod_textures.txt @@ -154,5 +154,29 @@ "width" "64" "height" "64" } + "voice_global" + { + "file" "vgui/hud/voice_global" + "x" "0" + "y" "0" + "width" "64" + "height" "64" + } + "voice_team" + { + "file" "vgui/hud/voice_team" + "x" "0" + "y" "0" + "width" "64" + "height" "64" + } + "voice_mute" + { + "file" "vgui/hud/voice_mute" + "x" "0" + "y" "0" + "width" "64" + "height" "64" + } } } diff --git a/src/game/client/hud_voicestatus.cpp b/src/game/client/hud_voicestatus.cpp index b3739f5db0..f154c553cd 100644 --- a/src/game/client/hud_voicestatus.cpp +++ b/src/game/client/hud_voicestatus.cpp @@ -15,6 +15,9 @@ #include "c_playerresource.h" #include "voice_common.h" #include "vgui_avatarimage.h" +#ifdef NEO +#include "in_main.h" +#endif // NEO ConVar *sv_alltalk = NULL; @@ -35,6 +38,11 @@ class CHudVoiceSelfStatus : public CHudElement, public vgui::Panel private: CHudTexture *m_pVoiceIcon; +#ifdef NEO + CHudTexture *m_pTeamVoiceIcon; + CHudTexture *m_pGlobalVoiceIcon; + CHudTexture *m_pNoVoiceIcon; +#endif // NEO Color m_clrIcon; }; @@ -67,22 +75,64 @@ void CHudVoiceSelfStatus::ApplySchemeSettings(vgui::IScheme *pScheme) void CHudVoiceSelfStatus::VidInit( void ) { m_pVoiceIcon = gHUD.GetIcon( "voice_self" ); +#ifdef NEO + m_pGlobalVoiceIcon = gHUD.GetIcon("voice_global"); + m_pTeamVoiceIcon = gHUD.GetIcon("voice_team"); + m_pNoVoiceIcon = gHUD.GetIcon("voice_mute"); +#endif // NEO } bool CHudVoiceSelfStatus::ShouldDraw() { +#ifdef NEO + return IsLocalPlayerHoldingAVoiceTransmitKey();// || GetClientVoiceMgr()->IsLocalPlayerSpeaking(); +#else return GetClientVoiceMgr()->IsLocalPlayerSpeaking(); +#endif // NEO } void CHudVoiceSelfStatus::Paint() { +#ifdef NEO + static CHudTexture* icon = m_pVoiceIcon; + switch (GetVoiceTransmitType()) + { + case NeoVoiceTransmitType::NEO_VOICE_TRANSMIT_NONE: + if (IsLocalPlayerHoldingAVoiceTransmitKey()) + { + icon = m_pNoVoiceIcon; + } + break; + case NeoVoiceTransmitType::NEO_VOICE_TRANSMIT_GLOBALTEAM: + if (sv_alltalk->GetBool()) + { + icon = m_pGlobalVoiceIcon; + } + else + { + icon = m_pTeamVoiceIcon; + } + break; + case NeoVoiceTransmitType::NEO_VOICE_TRANSMIT_LOCAL: + icon = m_pVoiceIcon; + break; + } + + if (!icon) + return; +#else if( !m_pVoiceIcon ) return; +#endif // NEO int x, y, w, h; GetBounds( x, y, w, h ); +#ifdef NEO + icon->DrawSelf( 0, 0, w, h, m_clrIcon ); +#else m_pVoiceIcon->DrawSelf( 0, 0, w, h, m_clrIcon ); +#endif // NEO } @@ -209,7 +259,7 @@ void CHudVoiceStatus::OnThink( void ) { int activeSpeakerIndex = FindActiveSpeaker(iPlayerIndex); #ifdef NEO - bool bSpeaking = GetClientVoiceMgr()->IsPlayerAudible(iPlayerIndex); + bool bSpeaking = GetClientVoiceMgr()->IsPlayerSpeakingNonProximity(iPlayerIndex); #else bool bSpeaking = GetClientVoiceMgr()->IsPlayerSpeaking(iPlayerIndex); #endif // NEO diff --git a/src/game/client/in_main.cpp b/src/game/client/in_main.cpp index f886f70947..3bcb6d3622 100644 --- a/src/game/client/in_main.cpp +++ b/src/game/client/in_main.cpp @@ -703,7 +703,7 @@ void IN_VoiceRecordLocalDown(const CCommand& args) { KeyDown(&in_voicerecordlocal, args[1]); if (gpGlobals->curtime < nextLocalSpeakTime) { - return; // NEO TODO (Adam) Show in local voice status ui element that cannot talk, and clear when releasing button + return; } if (const bool isSpeaking = GetClientVoiceMgr() ? GetClientVoiceMgr()->IsLocalPlayerSpeaking() : true) @@ -741,6 +741,10 @@ void IN_VoiceRecordGlobalTeamDown(const CCommand& args) { engine->ExecuteClientCmd("+voicerecord"); } +bool IsLocalPlayerHoldingAVoiceTransmitKey() { + return (in_voicerecordglobalteam.state & 1) || (in_voicerecordlocal.state & 1); +} + void IN_LeanLeftToggle(const CCommand& args) { if (::input->KeyState(&in_lean_left)) diff --git a/src/game/client/in_main.h b/src/game/client/in_main.h index eea22aa4ad..b7992dbcda 100644 --- a/src/game/client/in_main.h +++ b/src/game/client/in_main.h @@ -29,6 +29,7 @@ void IN_SpeedReset(); void IN_LeanToggleReset(); void LiftAllToggleKeys(); NeoVoiceTransmitType GetVoiceTransmitType(); +bool IsLocalPlayerHoldingAVoiceTransmitKey(); #endif // NEO #endif // IN_MAIN_H diff --git a/src/game/shared/hl2/hl2_usermessages.cpp b/src/game/shared/hl2/hl2_usermessages.cpp index 1c06c89765..033ba90b8d 100644 --- a/src/game/shared/hl2/hl2_usermessages.cpp +++ b/src/game/shared/hl2/hl2_usermessages.cpp @@ -34,7 +34,11 @@ void RegisterUserMessages( void ) usermessages->Register( "Rumble", 3 ); // Send a rumble to a controller usermessages->Register( "Battery", 2 ); usermessages->Register( "Damage", 18 ); // BUG: floats are sent for coords, no variable bitfields in hud & fixed size Msg +#ifdef NEO + usermessages->Register( "VoiceMask", VOICE_MAX_PLAYERS_DW*4 * 3 + 1 ); +#else usermessages->Register( "VoiceMask", VOICE_MAX_PLAYERS_DW*4 * 2 + 1 ); +#endif // NEO usermessages->Register( "RequestState", 0 ); usermessages->Register( "CloseCaption", -1 ); // Show a caption (by string id number)(duration in 10th of a second) usermessages->Register( "HintText", -1 ); // Displays hint text display diff --git a/src/game/shared/sdk/sdk_usermessages.cpp b/src/game/shared/sdk/sdk_usermessages.cpp index e42267a8ec..07f02fa2fc 100644 --- a/src/game/shared/sdk/sdk_usermessages.cpp +++ b/src/game/shared/sdk/sdk_usermessages.cpp @@ -28,7 +28,7 @@ void RegisterUserMessages() usermessages->Register( "SendAudio", -1 ); // play radion command - usermessages->Register( "VoiceMask", VOICE_MAX_PLAYERS_DW*4 * 2 + 1 ); + usermessages->Register( "VoiceMask", VOICE_MAX_PLAYERS_DW*4 * 2 + 1 ); // NEO TODO (Adam) increase to VOICE_MAX_PLAYERS_DW*4 * 3 + 1 when building this usermessages->Register( "RequestState", 0 ); usermessages->Register( "BarTime", -1 ); // For the C4 progress bar. diff --git a/src/game/shared/voice_gamemgr.cpp b/src/game/shared/voice_gamemgr.cpp index afe88da2db..69c8136224 100644 --- a/src/game/shared/voice_gamemgr.cpp +++ b/src/game/shared/voice_gamemgr.cpp @@ -33,8 +33,15 @@ CPlayerBitVec g_BanMasks[VOICE_MAX_PLAYERS]; // Tells which players don't want t // These are indexed as clients and each bit represents a client // (so player entity is bit+1). +#ifdef NEO +CPlayerBitVec g_ProximityMasks[VOICE_MAX_PLAYERS]; +#endif // NEO + CPlayerBitVec g_SentGameRulesMasks[VOICE_MAX_PLAYERS]; // These store the masks we last sent to each client so we can determine if CPlayerBitVec g_SentBanMasks[VOICE_MAX_PLAYERS]; // we need to resend them. +#ifdef NEO +CPlayerBitVec g_SentProximityMasks[VOICE_MAX_PLAYERS]; +#endif // NEO CPlayerBitVec g_bWantModEnable; ConVar voice_serverdebug( "voice_serverdebug", "0" ); @@ -243,13 +250,25 @@ void CVoiceGameMgr::UpdateMasks() // Build a mask of who they can hear based on the game rules. for(int iOtherClient=0; iOtherClient < m_nMaxPlayers; iOtherClient++) { - CBaseEntity *pEnt = UTIL_PlayerByIndex(iOtherClient+1); +#ifdef NEO + if (CBaseEntity *pEnt = UTIL_PlayerByIndex(iOtherClient+1); + pEnt && pEnt->IsPlayer()) + { + if (const bool canHearPlayer = m_pHelper->CanPlayerHearPlayer(pPlayer, (CBasePlayer*)pEnt, bProximity); + bAllTalk || canHearPlayer) + { + gameRulesMask[iOtherClient] = true; + ProximityMask[iOtherClient] = bProximity; + } + } +#else if(pEnt && pEnt->IsPlayer() && (bAllTalk || m_pHelper->CanPlayerHearPlayer(pPlayer, (CBasePlayer*)pEnt, bProximity )) ) { gameRulesMask[iOtherClient] = true; ProximityMask[iOtherClient] = bProximity; } +#endif // NEO } } @@ -257,26 +276,17 @@ void CVoiceGameMgr::UpdateMasks() if (m_UpdateInterval >= UPDATE_INTERVAL) { m_UpdateInterval = 0; - - // I do not want the local player to know which players are speaking in proximity, instead of appending a new long to - // the VoiceMask message, pass a new mask that contains all speaking players - players speaking locally - CPlayerBitVec NotProximityMask; - ProximityMask.Not(&NotProximityMask); - CPlayerBitVec NonProximityGameRulesMask; - gameRulesMask.And(NotProximityMask, &NonProximityGameRulesMask); #endif // NEO - // If this is different from what the client has, send an update. + // If this is different from what the client has, send an update. + if(gameRulesMask != g_SentGameRulesMasks[iClient] || #ifdef NEO - if(NonProximityGameRulesMask != g_SentGameRulesMasks[iClient] || -#else - if(gameRulesMask != g_SentGameRulesMasks[iClient] || + ProximityMask!= g_SentProximityMasks[iClient] || #endif // NEO g_BanMasks[iClient] != g_SentBanMasks[iClient]) { -#ifdef NEO - g_SentGameRulesMasks[iClient] = NonProximityGameRulesMask; -#else g_SentGameRulesMasks[iClient] = gameRulesMask; +#ifdef NEO + g_SentProximityMasks[iClient] = ProximityMask; #endif // NEO g_SentBanMasks[iClient] = g_BanMasks[iClient]; @@ -284,10 +294,9 @@ void CVoiceGameMgr::UpdateMasks() int dw; for(dw=0; dw < VOICE_MAX_PLAYERS_DW; dw++) { -#ifdef NEO - WRITE_LONG(NonProximityGameRulesMask.GetDWord(dw)); -#else WRITE_LONG(gameRulesMask.GetDWord(dw)); +#ifdef NEO + WRITE_LONG(ProximityMask.GetDWord(dw)); #endif // NEO WRITE_LONG(g_BanMasks[iClient].GetDWord(dw)); } diff --git a/src/game/shared/voice_status.cpp b/src/game/shared/voice_status.cpp index 0272c55408..45f82572c9 100644 --- a/src/game/shared/voice_status.cpp +++ b/src/game/shared/voice_status.cpp @@ -429,12 +429,18 @@ void CVoiceStatus::HandleVoiceMaskMsg(bf_read &msg) for(dw=0; dw < VOICE_MAX_PLAYERS_DW; dw++) { m_AudiblePlayers.SetDWord(dw, (unsigned long)msg.ReadLong()); +#ifdef NEO + m_ProximityPlayers.SetDWord(dw, (unsigned long)msg.ReadLong()); +#endif // NEO m_ServerBannedPlayers.SetDWord(dw, (unsigned long)msg.ReadLong()); if( voice_clientdebug.GetInt()) { Msg("CVoiceStatus::HandleVoiceMaskMsg\n"); Msg(" - m_AudiblePlayers[%d] = %u\n", dw, m_AudiblePlayers.GetDWord(dw)); +#ifdef NEO + Msg(" - m_ProximityMasks[%d] = %u\n", dw, m_ProximityPlayers.GetDWord(dw)); +#endif // NEO Msg(" - m_ServerBannedPlayers[%d] = %u\n", dw, m_ServerBannedPlayers.GetDWord(dw)); } } @@ -525,6 +531,17 @@ bool CVoiceStatus::IsPlayerSpeaking(int iPlayerIndex) return m_VoicePlayers[iPlayerIndex-1] != 0; } +#ifdef NEO +//----------------------------------------------------------------------------- +// returns true if the player is currently speaking through proximity chat +// Calls IsPlayerAudible to stop hud item popping up for a split second after stopping talking +//----------------------------------------------------------------------------- +bool CVoiceStatus::IsPlayerSpeakingNonProximity(int iPlayerIndex) +{ + return IsPlayerAudible(iPlayerIndex) && IsPlayerSpeaking(iPlayerIndex) && (m_ProximityPlayers[iPlayerIndex - 1] == 0); +} +#endif // NEO + //----------------------------------------------------------------------------- // returns true if the local player is attempting to speak //----------------------------------------------------------------------------- diff --git a/src/game/shared/voice_status.h b/src/game/shared/voice_status.h index f9bc4268e1..a67cc171bb 100644 --- a/src/game/shared/voice_status.h +++ b/src/game/shared/voice_status.h @@ -125,6 +125,11 @@ class CVoiceStatus /*: public vgui::CDefaultInputSignal*/ // returns true if the player is currently speaking bool IsPlayerSpeaking(int iPlayerIndex); +#ifdef NEO + // returns true if the player is speaking in non proximity voice chat + bool IsPlayerSpeakingNonProximity(int iPlayerIndex); +#endif // NEO + // returns true if the local player is attempting to speak bool IsLocalPlayerSpeaking( void ); @@ -154,6 +159,10 @@ class CVoiceStatus /*: public vgui::CDefaultInputSignal*/ // and is totally separate from the ban list. Indexed by client index. CPlayerBitVec m_AudiblePlayers; +#ifdef NEO + CPlayerBitVec m_ProximityPlayers; +#endif // NEO + // Players who have spoken at least once in the game so far CPlayerBitVec m_VoiceEnabledPlayers; From 052d96cca02846fabbccc4335292d928495e2e98 Mon Sep 17 00:00:00 2001 From: AdamTadeusz Date: Sun, 2 Aug 2026 15:28:05 +0100 Subject: [PATCH 4/5] add server toggle for proximity chat --- src/game/client/hud_voicestatus.cpp | 13 +++++++++++-- src/game/shared/neo/neo_gamerules.cpp | 12 ++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/game/client/hud_voicestatus.cpp b/src/game/client/hud_voicestatus.cpp index f154c553cd..8c6281b913 100644 --- a/src/game/client/hud_voicestatus.cpp +++ b/src/game/client/hud_voicestatus.cpp @@ -95,13 +95,15 @@ void CHudVoiceSelfStatus::Paint() { #ifdef NEO static CHudTexture* icon = m_pVoiceIcon; + static ConVarRef sv_neo_proximity_VoIP("sv_neo_proximity_VoIP"); switch (GetVoiceTransmitType()) { case NeoVoiceTransmitType::NEO_VOICE_TRANSMIT_NONE: - if (IsLocalPlayerHoldingAVoiceTransmitKey()) + if (!IsLocalPlayerHoldingAVoiceTransmitKey()) { - icon = m_pNoVoiceIcon; + return; } + icon = m_pNoVoiceIcon; break; case NeoVoiceTransmitType::NEO_VOICE_TRANSMIT_GLOBALTEAM: if (sv_alltalk->GetBool()) @@ -114,8 +116,15 @@ void CHudVoiceSelfStatus::Paint() } break; case NeoVoiceTransmitType::NEO_VOICE_TRANSMIT_LOCAL: + if (!sv_neo_proximity_VoIP.GetBool()) + { + return; + } icon = m_pVoiceIcon; break; + default: + Assert(false); + return; } if (!icon) diff --git a/src/game/shared/neo/neo_gamerules.cpp b/src/game/shared/neo/neo_gamerules.cpp index 883fa032db..1f849a81eb 100644 --- a/src/game/shared/neo/neo_gamerules.cpp +++ b/src/game/shared/neo/neo_gamerules.cpp @@ -263,6 +263,7 @@ void sndVictoryVolumeChangeCallback(IConVar* cvar [[maybe_unused]], const char* ConVar snd_victory_volume("snd_victory_volume", "0.33", FCVAR_ARCHIVE | FCVAR_DONTRECORD | FCVAR_USERINFO, "Loudness of the victory jingle (0-1).", true, 0.0, true, 1.0, sndVictoryVolumeChangeCallback); #endif // CLIENT_DLL +ConVar sv_neo_proximity_VoIP("sv_neo_proximity_VoIP", "1", FCVAR_NONE, "Enable proximity VoIP", true, 0, true, 1); #ifdef GAME_DLL class CVoiceGameMgrHelper : public IVoiceGameMgrHelper { @@ -274,11 +275,14 @@ ConVar snd_victory_volume("snd_victory_volume", "0.33", FCVAR_ARCHIVE | FCVAR_DO return false; } - if (const bool talkingLocally = pTalker->GetLastUserCommand() ? (pTalker->GetLastUserCommand()->voiceTransmitType == NeoVoiceTransmitType::NEO_VOICE_TRANSMIT_LOCAL) : false; - talkingLocally && pTalker->IsAlive()) + if (sv_neo_proximity_VoIP.GetBool()) { - bProximity = true; - return true; + if (const bool talkingLocally = pTalker->GetLastUserCommand() ? (pTalker->GetLastUserCommand()->voiceTransmitType == NeoVoiceTransmitType::NEO_VOICE_TRANSMIT_LOCAL) : false; + talkingLocally && pTalker->IsAlive()) + { + bProximity = true; + return true; + } } if (pListener->GetTeamNumber() == pTalker->GetTeamNumber()) From e593d71556b21f4dfe53bbad53136c94efde284b Mon Sep 17 00:00:00 2001 From: AdamTadeusz Date: Sun, 2 Aug 2026 15:44:24 +0100 Subject: [PATCH 5/5] fix plus convar to disable hearing players as spectator --- src/game/shared/neo/neo_gamerules.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/game/shared/neo/neo_gamerules.cpp b/src/game/shared/neo/neo_gamerules.cpp index 1f849a81eb..2257618aa2 100644 --- a/src/game/shared/neo/neo_gamerules.cpp +++ b/src/game/shared/neo/neo_gamerules.cpp @@ -263,7 +263,8 @@ void sndVictoryVolumeChangeCallback(IConVar* cvar [[maybe_unused]], const char* ConVar snd_victory_volume("snd_victory_volume", "0.33", FCVAR_ARCHIVE | FCVAR_DONTRECORD | FCVAR_USERINFO, "Loudness of the victory jingle (0-1).", true, 0.0, true, 1.0, sndVictoryVolumeChangeCallback); #endif // CLIENT_DLL -ConVar sv_neo_proximity_VoIP("sv_neo_proximity_VoIP", "1", FCVAR_NONE, "Enable proximity VoIP", true, 0, true, 1); +ConVar sv_neo_proximity_VoIP("sv_neo_proximity_VoIP", "1", FCVAR_NOTIFY | FCVAR_REPLICATED, "Enable proximity VoIP", true, 0, true, 1); +ConVar sv_neo_spectators_hear_spectate_target_team("sv_neo_spectators_hear_spectate_target_team", "1", FCVAR_NOTIFY | FCVAR_REPLICATED, "Can spectators hear the spectate target's team when in Eye or Chase observer mode", true, 0, true, 1); #ifdef GAME_DLL class CVoiceGameMgrHelper : public IVoiceGameMgrHelper { @@ -275,14 +276,15 @@ ConVar sv_neo_proximity_VoIP("sv_neo_proximity_VoIP", "1", FCVAR_NONE, "Enable p return false; } - if (sv_neo_proximity_VoIP.GetBool()) + if (const bool talkingLocally = pTalker->GetLastUserCommand() ? (pTalker->GetLastUserCommand()->voiceTransmitType == NeoVoiceTransmitType::NEO_VOICE_TRANSMIT_LOCAL) : false; + talkingLocally && pTalker->IsAlive()) { - if (const bool talkingLocally = pTalker->GetLastUserCommand() ? (pTalker->GetLastUserCommand()->voiceTransmitType == NeoVoiceTransmitType::NEO_VOICE_TRANSMIT_LOCAL) : false; - talkingLocally && pTalker->IsAlive()) + if (!sv_neo_proximity_VoIP.GetBool()) { - bProximity = true; - return true; + return false; } + bProximity = true; + return true; } if (pListener->GetTeamNumber() == pTalker->GetTeamNumber()) @@ -295,6 +297,10 @@ ConVar sv_neo_proximity_VoIP("sv_neo_proximity_VoIP", "1", FCVAR_NONE, "Enable p if (CBaseEntity* pListenerSpectateTarget = static_cast(pListener->GetObserverTarget()); pListenerSpectateTarget && pListenerSpectateTarget->GetTeamNumber() == pTalker->GetTeamNumber()) { + if (!sv_neo_spectators_hear_spectate_target_team.GetBool()) + { + return false; + } return true; } }