2022-02-27 07:53:44 -05:00
# include <STDInclude.hpp>
2021-09-19 07:13:46 -04:00
namespace Components
{
2021-09-19 09:49:12 -04:00
Game : : dvar_t * * Chat : : cg_chatHeight = reinterpret_cast < Game : : dvar_t * * > ( 0x7ED398 ) ;
Dvar : : Var Chat : : cg_chatWidth ;
Game : : dvar_t * * Chat : : cg_chatTime = reinterpret_cast < Game : : dvar_t * * > ( 0x9F5DE8 ) ;
2021-09-19 07:13:46 -04:00
bool Chat : : SendChat ;
2022-01-10 13:42:58 -05:00
std : : mutex Chat : : AccessMutex ;
2022-01-01 08:08:02 -05:00
std : : unordered_set < std : : uint64_t > Chat : : MuteList ;
2021-09-19 07:13:46 -04:00
const char * Chat : : EvaluateSay ( char * text , Game : : gentity_t * player )
{
2022-01-01 08:08:02 -05:00
Chat : : SendChat = true ;
2021-09-19 07:13:46 -04:00
if ( text [ 1 ] = = ' / ' )
{
2022-01-01 08:08:02 -05:00
Chat : : SendChat = false ;
2021-09-19 07:13:46 -04:00
text [ 1 ] = text [ 0 ] ;
+ + text ;
}
2022-01-19 09:05:40 -05:00
std : : unique_lock < std : : mutex > lock ( Chat : : AccessMutex ) ;
2022-05-31 12:38:09 -04:00
if ( Chat : : MuteList . contains ( Game : : svs_clients [ player - > s . number ] . steamID ) )
2022-01-01 08:08:02 -05:00
{
2022-01-19 09:05:40 -05:00
lock . unlock ( ) ;
2022-01-01 08:08:02 -05:00
Chat : : SendChat = false ;
Game : : SV_GameSendServerCommand ( player - > s . number , 0 ,
Utils : : String : : VA ( " %c \" You are muted \" " , 0x65 ) ) ;
}
2022-01-19 09:05:40 -05:00
// Test whether the lock is still locked
if ( lock . owns_lock ( ) )
{
lock . unlock ( ) ;
}
2021-09-19 07:13:46 -04:00
TextRenderer : : StripMaterialTextIcons ( text , text , strlen ( text ) + 1 ) ;
Game : : Scr_AddEntity ( player ) ;
Game : : Scr_AddString ( text + 1 ) ;
Game : : Scr_NotifyLevel ( Game : : SL_GetString ( " say " , 0 ) , 2 ) ;
return text ;
}
__declspec ( naked ) void Chat : : PreSayStub ( )
{
__asm
{
mov eax , [ esp + 100 h + 10 h ]
push eax
pushad
2022-05-03 07:44:18 -04:00
push [ esp + 100 h + 28 h ]
2021-09-19 07:13:46 -04:00
push eax
call Chat : : EvaluateSay
add esp , 8 h
2022-05-03 07:44:18 -04:00
mov [ esp + 20 h ] , eax
2021-09-19 07:13:46 -04:00
popad
pop eax
2022-05-03 07:44:18 -04:00
mov [ esp + 100 h + 10 h ] , eax
2021-09-19 07:13:46 -04:00
jmp PlayerName : : CleanStrStub
}
}
__declspec ( naked ) void Chat : : PostSayStub ( )
{
__asm
{
// eax is used by the callee
push eax
xor eax , eax
mov al , Chat : : SendChat
test al , al
jnz return
// Don't send the chat
pop eax
retn
2021-09-20 12:21:38 -04:00
return :
2021-09-19 07:13:46 -04:00
pop eax
// Jump to the target
push 5 DF620h
retn
}
}
2021-09-19 09:49:12 -04:00
void Chat : : CheckChatLineEnd ( const char * & inputBuffer , char * & lineBuffer , float & len , const int chatHeight , const float chatWidth , char * & lastSpacePos , char * & lastFontIconPos , const int lastColor )
2022-05-03 07:44:18 -04:00
{
2021-09-19 09:49:12 -04:00
if ( len > chatWidth )
{
if ( lastSpacePos & & lastSpacePos > lastFontIconPos )
{
inputBuffer + = lastSpacePos - lineBuffer + 1 ;
lineBuffer = lastSpacePos ;
}
else if ( lastFontIconPos )
{
inputBuffer + = lastFontIconPos - lineBuffer ;
lineBuffer = lastFontIconPos ;
}
2022-05-03 07:44:18 -04:00
2021-09-19 09:49:12 -04:00
* lineBuffer = 0 ;
len = 0.0f ;
Game : : cgsArray [ 0 ] . teamChatMsgTimes [ Game : : cgsArray [ 0 ] . teamChatPos % chatHeight ] = Game : : cgArray [ 0 ] . time ;
Game : : cgsArray [ 0 ] . teamChatPos + + ;
lineBuffer = Game : : cgsArray [ 0 ] . teamChatMsgs [ Game : : cgsArray [ 0 ] . teamChatPos % chatHeight ] ;
lineBuffer [ 0 ] = ' ^ ' ;
lineBuffer [ 1 ] = CharForColorIndex ( lastColor ) ;
lineBuffer + = 2 ;
lastSpacePos = nullptr ;
lastFontIconPos = nullptr ;
}
2022-05-03 07:44:18 -04:00
}
2021-09-19 09:49:12 -04:00
void Chat : : CG_AddToTeamChat ( const char * text )
{
// Text can only be 150 characters maximum. This is bigger than the teamChatMsgs buffers with 160 characters
// Therefore it is not needed to check for buffer lengths
const auto chatHeight = ( * cg_chatHeight ) - > current . integer ;
const auto chatWidth = static_cast < float > ( cg_chatWidth . get < int > ( ) ) ;
const auto chatTime = ( * cg_chatTime ) - > current . integer ;
2022-05-03 07:41:46 -04:00
if ( chatHeight < = 0 | | static_cast < unsigned > ( chatHeight ) > std : : extent_v < decltype ( Game : : cgs_t : : teamChatMsgs ) > | | chatWidth < = 0 | | chatTime < = 0 )
2021-09-19 09:49:12 -04:00
{
Game : : cgsArray [ 0 ] . teamLastChatPos = 0 ;
Game : : cgsArray [ 0 ] . teamChatPos = 0 ;
return ;
}
2021-09-20 12:21:38 -04:00
TextRenderer : : FontIconInfo fontIconInfo { } ;
2021-09-19 09:49:12 -04:00
auto len = 0.0f ;
auto lastColor = static_cast < int > ( TEXT_COLOR_DEFAULT ) ;
char * lastSpace = nullptr ;
char * lastFontIcon = nullptr ;
char * p = Game : : cgsArray [ 0 ] . teamChatMsgs [ Game : : cgsArray [ 0 ] . teamChatPos % chatHeight ] ;
p [ 0 ] = ' \0 ' ;
while ( * text )
{
CheckChatLineEnd ( text , p , len , chatHeight , chatWidth , lastSpace , lastFontIcon , lastColor ) ;
const char * fontIconEndPos = & text [ 1 ] ;
2022-05-03 07:44:18 -04:00
if ( text [ 0 ] = = TextRenderer : : FONT_ICON_SEPARATOR_CHARACTER & & TextRenderer : : IsFontIcon ( fontIconEndPos , fontIconInfo ) )
2021-09-19 09:49:12 -04:00
{
// The game calculates width on a per character base. Since the width of a font icon is calculated based on the height of the font
// which is roughly double as much as the average width of a character without an additional multiplier the calculated len of the font icon
// would be less than it most likely would be rendered. Therefore apply a guessed 2.0f multiplier at this location which makes
// the calculated width of a font icon roughly comparable to the width of an average character of the font.
const auto normalizedFontIconWidth = TextRenderer : : GetNormalizedFontIconWidth ( fontIconInfo ) ;
const auto fontIconWidth = normalizedFontIconWidth * FONT_ICON_CHAT_WIDTH_CALCULATION_MULTIPLIER ;
len + = fontIconWidth ;
lastFontIcon = p ;
for ( ; text < fontIconEndPos ; text + + )
{
p [ 0 ] = text [ 0 ] ;
p + + ;
}
CheckChatLineEnd ( text , p , len , chatHeight , chatWidth , lastSpace , lastFontIcon , lastColor ) ;
}
else if ( text [ 0 ] = = ' ^ ' & & text [ 1 ] ! = 0 & & text [ 1 ] > = TextRenderer : : COLOR_FIRST_CHAR & & text [ 1 ] < = TextRenderer : : COLOR_LAST_CHAR )
{
p [ 0 ] = ' ^ ' ;
p [ 1 ] = text [ 1 ] ;
lastColor = ColorIndexForChar ( text [ 1 ] ) ;
p + = 2 ;
text + = 2 ;
}
else
{
if ( text [ 0 ] = = ' ' )
lastSpace = p ;
* p + + = * text + + ;
len + = 1.0f ;
}
}
2021-09-20 14:40:42 -04:00
2021-09-19 09:49:12 -04:00
* p = 0 ;
2021-09-20 14:40:42 -04:00
2021-09-19 09:49:12 -04:00
Game : : cgsArray [ 0 ] . teamChatMsgTimes [ Game : : cgsArray [ 0 ] . teamChatPos % chatHeight ] = Game : : cgArray [ 0 ] . time ;
2021-09-20 14:40:42 -04:00
Game : : cgsArray [ 0 ] . teamChatPos + + ;
if ( Game : : cgsArray [ 0 ] . teamChatPos - Game : : cgsArray [ 0 ] . teamLastChatPos > chatHeight )
2021-09-20 12:21:38 -04:00
Game : : cgsArray [ 0 ] . teamLastChatPos = Game : : cgsArray [ 0 ] . teamChatPos + 1 - chatHeight ;
2021-09-19 09:49:12 -04:00
}
__declspec ( naked ) void Chat : : CG_AddToTeamChat_Stub ( )
{
__asm
{
pushad
push ecx
call CG_AddToTeamChat
add esp , 4 h
popad
ret
}
}
2022-01-01 08:08:02 -05:00
void Chat : : MuteClient ( const Game : : client_t * client )
{
2022-01-19 09:05:40 -05:00
std : : unique_lock < std : : mutex > lock ( Chat : : AccessMutex ) ;
2022-01-10 13:42:58 -05:00
2022-05-31 12:38:09 -04:00
if ( ! Chat : : MuteList . contains ( client - > steamID ) )
2022-01-01 08:08:02 -05:00
{
Chat : : MuteList . insert ( client - > steamID ) ;
2022-01-19 09:05:40 -05:00
lock . unlock ( ) ;
2022-01-01 08:08:02 -05:00
Logger : : Print ( " %s was muted \n " , client - > name ) ;
2022-01-10 13:42:58 -05:00
Game : : SV_GameSendServerCommand ( client - > gentity - > s . number , 0 ,
2022-01-01 08:08:02 -05:00
Utils : : String : : VA ( " %c \" You were muted \" " , 0x65 ) ) ;
return ;
}
2022-01-19 09:05:40 -05:00
lock . unlock ( ) ;
2022-01-01 08:08:02 -05:00
Logger : : Print ( " %s is already muted \n " , client - > name ) ;
Game : : SV_GameSendServerCommand ( - 1 , 0 ,
Utils : : String : : VA ( " %c \" %s is already muted \" " , 0x65 , client - > name ) ) ;
}
void Chat : : UnmuteClient ( const Game : : client_t * client )
{
2022-01-10 13:42:58 -05:00
Chat : : UnmuteInternal ( client - > steamID ) ;
2022-01-01 08:08:02 -05:00
Logger : : Print ( " %s was unmuted \n " , client - > name ) ;
2022-01-10 13:42:58 -05:00
Game : : SV_GameSendServerCommand ( client - > gentity - > s . number , 0 ,
2022-01-01 08:08:02 -05:00
Utils : : String : : VA ( " %c \" You were unmuted \" " , 0x65 ) ) ;
}
2022-01-10 13:42:58 -05:00
void Chat : : UnmuteInternal ( const std : : uint64_t id , bool everyone )
{
2022-01-19 09:05:40 -05:00
std : : unique_lock < std : : mutex > lock ( Chat : : AccessMutex ) ;
2022-01-10 13:42:58 -05:00
if ( everyone )
Chat : : MuteList . clear ( ) ;
else
Chat : : MuteList . erase ( id ) ;
}
2022-01-01 08:08:02 -05:00
void Chat : : AddChatCommands ( )
{
Command : : AddSV ( " muteClient " , [ ] ( Command : : Params * params )
{
if ( ! Dvar : : Var ( " sv_running " ) . get < bool > ( ) )
{
Logger : : Print ( " Server is not running. \n " ) ;
return ;
}
const auto * cmd = params - > get ( 0 ) ;
2022-03-17 14:50:20 -04:00
if ( params - > size ( ) < 2 )
2022-01-01 08:08:02 -05:00
{
Logger : : Print ( " Usage: %s <client number> : prevent the player from using the chat \n " , cmd ) ;
return ;
}
const auto * client = Game : : SV_GetPlayerByNum ( ) ;
if ( client ! = nullptr )
{
Chat : : MuteClient ( client ) ;
}
} ) ;
Command : : AddSV ( " unmute " , [ ] ( Command : : Params * params )
{
if ( ! Dvar : : Var ( " sv_running " ) . get < bool > ( ) )
{
Logger : : Print ( " Server is not running. \n " ) ;
return ;
}
const auto * cmd = params - > get ( 0 ) ;
2022-03-17 14:50:20 -04:00
if ( params - > size ( ) < 2 )
2022-01-01 08:08:02 -05:00
{
2022-01-01 16:21:25 -05:00
Logger : : Print ( " Usage: %s <client number or guid> \n %s all = unmute everyone \n " , cmd , cmd ) ;
2022-01-01 08:08:02 -05:00
return ;
}
const auto * client = Game : : SV_GetPlayerByNum ( ) ;
2022-01-01 16:21:25 -05:00
if ( client ! = nullptr )
2022-01-01 08:08:02 -05:00
{
2022-01-01 16:21:25 -05:00
Chat : : UnmuteClient ( client ) ;
return ;
}
2022-03-17 14:50:20 -04:00
if ( std : : strcmp ( params - > get ( 1 ) , " all " ) = = 0 )
2022-01-01 16:21:25 -05:00
{
Logger : : Print ( " All players were unmuted \n " ) ;
2022-01-10 13:42:58 -05:00
Chat : : UnmuteInternal ( 0 , true ) ;
2022-01-01 08:08:02 -05:00
}
else
{
2022-01-10 13:42:58 -05:00
const auto steamId = std : : strtoull ( params - > get ( 1 ) , nullptr , 16 ) ;
Chat : : UnmuteInternal ( steamId ) ;
2022-01-01 08:08:02 -05:00
}
} ) ;
}
2021-09-19 07:13:46 -04:00
Chat : : Chat ( )
{
2022-01-01 08:08:02 -05:00
Dvar : : OnInit ( [ ]
{
2022-03-08 07:20:28 -05:00
cg_chatWidth = Dvar : : Register < int > ( " cg_chatWidth " , 52 , 1 , std : : numeric_limits < int > : : max ( ) , Game : : DVAR_ARCHIVE , " The normalized maximum width of a chat message " ) ;
2022-01-01 08:08:02 -05:00
Chat : : AddChatCommands ( ) ;
} ) ;
2021-09-19 09:49:12 -04:00
2021-09-19 07:13:46 -04:00
// Intercept chat sending
Utils : : Hook ( 0x4D000B , PreSayStub , HOOK_CALL ) . install ( ) - > quick ( ) ;
Utils : : Hook ( 0x4D00D4 , PostSayStub , HOOK_CALL ) . install ( ) - > quick ( ) ;
Utils : : Hook ( 0x4D0110 , PostSayStub , HOOK_CALL ) . install ( ) - > quick ( ) ;
2021-09-19 09:49:12 -04:00
// Change logic that does word splitting with new lines for chat messages to support fonticons
Utils : : Hook ( 0x592E10 , CG_AddToTeamChat_Stub , HOOK_JUMP ) . install ( ) - > quick ( ) ;
2021-09-19 07:13:46 -04:00
}
}