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
Dvar : : Var Chat : : cg_chatWidth ;
2022-05-31 11:57:44 -04:00
Dvar : : Var Chat : : sv_disableChat ;
Game : : dvar_t * * Chat : : cg_chatHeight = reinterpret_cast < Game : : dvar_t * * > ( 0x7ED398 ) ;
2021-09-19 09:49:12 -04:00
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 ;
2022-06-14 14:43:19 -04:00
bool Chat : : CanAddCallback = true ;
std : : vector < Scripting : : Function > Chat : : SayCallbacks ;
const char * Chat : : EvaluateSay ( char * text , Game : : gentity_t * player , int mode )
2021-09-19 07:13:46 -04:00
{
2022-06-14 14:43:19 -04:00
SendChat = true ;
const auto _0 = gsl : : finally ( [ ]
{
CanAddCallback = true ;
} ) ;
// Prevent callbacks from adding a new callback (would make the vector iterator invalid)
CanAddCallback = false ;
2021-09-19 07:13:46 -04:00
if ( text [ 1 ] = = ' / ' )
{
2022-06-14 14:43:19 -04:00
SendChat = false ;
2021-09-19 07:13:46 -04:00
text [ 1 ] = text [ 0 ] ;
+ + text ;
}
2022-06-14 14:43:19 -04:00
std : : unique_lock lock ( AccessMutex ) ;
if ( 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-06-14 14:43:19 -04:00
SendChat = false ;
2022-05-31 11:57:44 -04:00
Game : : SV_GameSendServerCommand ( player - > s . number , Game : : SV_CMD_CAN_IGNORE ,
2022-01-01 08:08:02 -05:00
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 ( ) ;
}
2022-06-14 14:43:19 -04:00
for ( const auto & callback : SayCallbacks )
{
if ( ! ChatCallback ( player , callback . getPos ( ) , ( text + 1 ) , mode ) )
{
SendChat = false ;
}
}
if ( sv_disableChat . get < bool > ( ) )
2022-05-31 11:57:44 -04:00
{
2022-06-14 14:43:19 -04:00
SendChat = false ;
2022-05-31 11:57:44 -04:00
Game : : SV_GameSendServerCommand ( player - > s . number , Game : : SV_CMD_CAN_IGNORE ,
Utils : : String : : VA ( " %c \" Chat is disabled \" " , 0x65 ) ) ;
}
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
{
2022-06-14 14:43:19 -04:00
mov eax , [ esp + 0x100 + 0x10 ]
2021-09-19 07:13:46 -04:00
push eax
pushad
2022-06-14 14:43:19 -04:00
push [ esp + 0x100 + 0x30 ] // mode
push [ esp + 0x100 + 0x2C ] // player
push eax // text
call EvaluateSay
add esp , 0xC
2021-09-19 07:13:46 -04:00
2022-06-14 14:43:19 -04:00
mov [ esp + 0x20 ] , eax
2021-09-19 07:13:46 -04:00
popad
pop eax
2022-06-14 14:43:19 -04:00
mov [ esp + 0x100 + 0x10 ] , 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
2022-06-14 14:43:19 -04:00
mov al , SendChat
2021-09-19 07:13:46 -04:00
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-06-14 14:43:19 -04:00
std : : unique_lock lock ( AccessMutex ) ;
2022-01-10 13:42:58 -05:00
2022-06-14 14:43:19 -04:00
if ( ! MuteList . contains ( client - > steamID ) )
2022-01-01 08:08:02 -05:00
{
2022-06-14 14:43:19 -04:00
MuteList . insert ( client - > steamID ) ;
2022-01-19 09:05:40 -05:00
lock . unlock ( ) ;
2022-01-01 08:08:02 -05:00
2022-06-12 17:07:53 -04:00
Logger : : Print ( " {} was muted \n " , client - > name ) ;
2022-05-31 11:57:44 -04:00
Game : : SV_GameSendServerCommand ( client - > gentity - > s . number , Game : : SV_CMD_CAN_IGNORE ,
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-06-12 17:07:53 -04:00
Logger : : Print ( " {} is already muted \n " , client - > name ) ;
2022-05-31 11:57:44 -04:00
Game : : SV_GameSendServerCommand ( - 1 , Game : : SV_CMD_CAN_IGNORE ,
2022-01-01 08:08:02 -05:00
Utils : : String : : VA ( " %c \" %s is already muted \" " , 0x65 , client - > name ) ) ;
}
void Chat : : UnmuteClient ( const Game : : client_t * client )
{
2022-06-14 14:43:19 -04:00
UnmuteInternal ( client - > steamID ) ;
2022-01-01 08:08:02 -05:00
2022-06-12 17:07:53 -04:00
Logger : : Print ( " {} was unmuted \n " , client - > name ) ;
2022-05-31 11:57:44 -04:00
Game : : SV_GameSendServerCommand ( client - > gentity - > s . number , Game : : SV_CMD_CAN_IGNORE ,
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-06-14 14:43:19 -04:00
std : : unique_lock lock ( AccessMutex ) ;
2022-01-10 13:42:58 -05:00
if ( everyone )
2022-06-14 14:43:19 -04:00
MuteList . clear ( ) ;
2022-01-10 13:42:58 -05:00
else
2022-06-14 14:43:19 -04:00
MuteList . erase ( id ) ;
2022-01-10 13:42:58 -05:00
}
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
{
2022-06-12 17:07:53 -04:00
Logger : : Print ( " Usage: {} <client number> : prevent the player from using the chat \n " , cmd ) ;
2022-01-01 08:08:02 -05:00
return ;
}
const auto * client = Game : : SV_GetPlayerByNum ( ) ;
if ( client ! = nullptr )
{
2022-06-14 14:43:19 -04:00
MuteClient ( client ) ;
2022-01-01 08:08:02 -05:00
}
} ) ;
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-06-12 17:07:53 -04:00
Logger : : Print ( " Usage: {} <client number or guid> \n {} 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-06-14 14:43:19 -04:00
UnmuteClient ( client ) ;
2022-01-01 16:21:25 -05:00
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-06-14 14:43:19 -04:00
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 ) ;
2022-06-14 14:43:19 -04:00
UnmuteInternal ( steamId ) ;
2022-01-01 08:08:02 -05:00
}
} ) ;
}
2022-06-14 14:43:19 -04:00
int Chat : : GetCallbackReturn ( )
{
if ( Game : : scrVmPub - > inparamcount = = 0 )
{
// Nothing. Let's not mute the player
return 1 ;
}
Game : : Scr_ClearOutParams ( ) ;
Game : : scrVmPub - > outparamcount = Game : : scrVmPub - > inparamcount ;
Game : : scrVmPub - > inparamcount = 0 ;
const auto * result = & Game : : scrVmPub - > top [ 1 - Game : : scrVmPub - > outparamcount ] ;
if ( result - > type ! = Game : : scrParamType_t : : VAR_INTEGER )
{
// Garbage was returned
return 1 ;
}
return result - > u . intValue ;
}
int Chat : : ChatCallback ( Game : : gentity_s * self , const char * codePos , const char * message , int mode )
{
const auto entityId = Game : : Scr_GetEntityId ( self - > s . number , 0 ) ;
2022-06-30 11:07:39 -04:00
Scripting : : StackIsolation _ ;
2022-06-14 14:43:19 -04:00
Game : : Scr_AddInt ( mode ) ;
Game : : Scr_AddString ( message ) ;
Game : : VariableValue value ;
value . type = Game : : scrParamType_t : : VAR_OBJECT ;
value . u . uintValue = entityId ;
Game : : AddRefToValue ( value . type , value . u ) ;
const auto localId = Game : : AllocThread ( entityId ) ;
const auto result = Game : : VM_Execute_0 ( localId , codePos , 2 ) ;
Game : : RemoveRefToObject ( result ) ;
return GetCallbackReturn ( ) ;
}
void Chat : : AddScriptFunctions ( )
{
Script : : AddFunction ( " OnPlayerSay " , [ ] // gsc: OnPlayerSay(<function>)
{
if ( Game : : Scr_GetNumParam ( ) ! = 1 )
{
Game : : Scr_Error ( " ^1OnPlayerSay: Needs one function pointer! \n " ) ;
return ;
}
if ( ! CanAddCallback )
{
Game : : Scr_Error ( " ^1OnPlayerSay: Cannot add a callback in this context " ) ;
return ;
}
const auto * func = Script : : GetCodePosForParam ( 0 ) ;
SayCallbacks . emplace_back ( func ) ;
} ) ;
}
2021-09-19 07:13:46 -04:00
Chat : : Chat ( )
{
2022-05-27 06:19:28 -04: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-06-14 14:43:19 -04:00
sv_disableChat = Dvar : : Register < bool > ( " sv_disableChat " , false , Game : : dvar_flag : : DVAR_NONE , " Disable chat messages from clients " ) ;
2022-06-28 14:57:58 -04:00
Events : : OnSVInit ( 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 ( ) ;
2022-06-14 14:43:19 -04:00
AddScriptFunctions ( ) ;
// Avoid duplicates
Events : : OnVMShutdown ( [ ]
{
SayCallbacks . clear ( ) ;
} ) ;
2021-09-19 07:13:46 -04:00
}
}