123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300 |
- /**
- * [CSRD] Simple Chat Processor
- *
- * Simple Chat Processor almost-compatible library for TF2.
- * Attempts to fix the one-recipient SayText2 messages, and does not depend on server-side
- * localization nonsense (everyone sees "(TEAM)" and "(DEAD)" in their language).
- */
- #pragma semicolon 1
- #include <sourcemod>
- #include <sdktools_voice>
- #pragma newdecls required
- #define PLUGIN_VERSION "0.3.0"
- public Plugin myinfo = {
- name = "[CSRD] Simple Chat Processor",
- author = "nosoop (based off of Simple Plugins' implementation)",
- description = "Simple Chat Processor almost-compatible library for TF2-specific fixes.",
- version = PLUGIN_VERSION,
- url = "https://git.csrd.science/"
- }
- #define PACKED_TOKEN_DELIMITER ":"
- #define CHATFLAGS_INVALID 0b0000
- #define CHATFLAGS_ALL 0b0001
- #define CHATFLAGS_TEAM 0b0010
- #define CHATFLAGS_SPEC 0b0100
- #define CHATFLAGS_DEAD 0b1000
- public APLRes AskPluginLoad2(Handle hPluginSelf, bool late, char[] error, int maxlen) {
- MarkNativeAsOptional("GetUserMessageType");
- CreateNative("GetMessageFlags", Native_GetMessageFlags);
- RegPluginLibrary("scp");
-
- return APLRes_Success;
- }
- // Holds player-unique messages sent in the current frame.
- StringMap g_QueuedMessages[MAXPLAYERS+1];
- Handle g_fwdOnChatMessage, g_fwdOnChatMessagePost;
- int g_ChatFlags;
- public void OnPluginStart() {
- UserMsg umSayText2 = GetUserMessageId("SayText2");
- if (umSayText2 != INVALID_MESSAGE_ID) {
- HookUserMessage(umSayText2, OnSayText2, true);
- } else {
- SetFailState("Game does not use SayText2.");
- }
-
- g_fwdOnChatMessage = CreateGlobalForward("OnChatMessage", ET_Hook, Param_CellByRef,
- Param_Cell, Param_String, Param_String);
- g_fwdOnChatMessagePost = CreateGlobalForward("OnChatMessage_Post", ET_Ignore, Param_Cell,
- Param_Cell, Param_String, Param_String);
-
- for (int i = 1; i < MaxClients; i++) {
- if (IsClientInGame(i)) {
- OnClientPutInServer(i);
- }
- }
-
- HookEvent("player_say", OnPlayerSayPost, EventHookMode_Post);
- }
- public void OnClientPutInServer(int client) {
- g_QueuedMessages[client] = new StringMap();
- }
- public void OnClientDisconnect(int client) {
- if (g_QueuedMessages[client] && g_QueuedMessages[client].Size > 0) {
- // delete remaining queued messages, don't bother sending
- StringMapSnapshot messages = g_QueuedMessages[client].Snapshot();
-
- for (int m = 0; m < messages.Length; m++) {
- char packedMessage[192];
- messages.GetKey(m, packedMessage, sizeof(packedMessage));
-
- ArrayList clientList;
- g_QueuedMessages[client].GetValue(packedMessage, clientList);
-
- delete clientList;
- }
- delete messages;
-
- g_QueuedMessages[client].Clear();
- }
- delete g_QueuedMessages[client];
- }
- /**
- * Collects previously fired UTIL_SayText2Filter events and holds them in our own internal
- * buffer to be manipulated at a later time. Each usermessage is fired separately.
- *
- * This is handled by game/server/client.cpp::Host_Say
- */
- Action OnSayText2(UserMsg id, Handle buffer, const int[] clients, int nClients,
- bool reliable, bool init) {
- BfRead bitbuf = view_as<BfRead>(buffer);
-
- int author = bitbuf.ReadByte();
-
- if (!author) {
- return Plugin_Continue;
- }
-
- bitbuf.ReadByte(); // bChat, unused?
-
- char localizationToken[32];
- bitbuf.ReadString(localizationToken, sizeof(localizationToken));
-
- if (StrContains(localizationToken, "TF_Chat_") == -1) {
- return Plugin_Continue;
- }
-
- if (!ParseChatMessageFlags(localizationToken)) {
- return Plugin_Continue;
- }
-
- char name[MAX_NAME_LENGTH];
- bitbuf.ReadString(name, sizeof(name));
-
- char message[128];
- bitbuf.ReadString(message, sizeof(message));
-
- /**
- * Pack messages based on localization token and message.
- * Any new similar usermessages in the same frame (matching message and flags) get their
- * recipients added to the same entry.
- */
- char packedMessage[192];
- Format(packedMessage, sizeof(packedMessage), "%s" ... PACKED_TOKEN_DELIMITER ... "%s",
- localizationToken, message);
-
- ArrayList recipients;
-
- if (!g_QueuedMessages[author].GetValue(packedMessage, recipients)) {
- recipients = new ArrayList();
- g_QueuedMessages[author].SetValue(packedMessage, recipients);
- }
-
- for (int i = 0; i < nClients; i++) {
- recipients.Push(clients[i]);
- }
-
- return Plugin_Handled;
- }
- void OnPlayerSayPost(Event event, const char[] name, bool dontBroadcast) {
- int client = GetClientOfUserId(event.GetInt("userid"));
- FlushQueuedMessages(client);
- }
- void FlushQueuedMessages(int author) {
- /**
- * Iterate through all queued messages from OnSayText2
- */
- if (!g_QueuedMessages[author] || g_QueuedMessages[author].Size == 0) {
- return;
- }
- StringMapSnapshot messages = g_QueuedMessages[author].Snapshot();
-
- for (int m = 0; m < messages.Length; m++) {
- char packedMessage[192], localizationToken[32], message[128];
- messages.GetKey(m, packedMessage, sizeof(packedMessage));
-
- ArrayList clientList;
-
- g_QueuedMessages[author].GetValue(packedMessage, clientList);
-
- // unpack localization and message from key
- int d = SplitString(packedMessage, PACKED_TOKEN_DELIMITER, localizationToken,
- sizeof(localizationToken));
- strcopy(message, sizeof(message), packedMessage[d]);
-
- char name[MAX_NAME_LENGTH + 1];
- GetClientName(author, name, sizeof(name));
-
- // Prepare chat message flags.
- g_ChatFlags = ParseChatMessageFlags(localizationToken);
-
- // Forward call.
- Action forwardResult = ForwardOnChatMessage(author, clientList, name, sizeof(name),
- message, sizeof(message));
-
- // Proceed to display message on continue or changed, else drop message.
- if (forwardResult < Plugin_Handled) {
- // convert ArrayList to client array
- int clients[MAXPLAYERS + 1], nClients;
- for (int i = 0; i < clientList.Length; i++) {
- int recipient = clientList.Get(i);
-
- // display to author and players that did not mute the author
- if (ShouldTransmitMessage(author, recipient)) {
- clients[nClients++] = recipient;
- }
- }
-
- // since it's not commented on in the SDK, we can only speculate on why the
- // developers decided to send SayText2 messages individually
- // probably cheat clients?
- SayText(author, clients, nClients, localizationToken, name, message);
-
- ForwardOnChatMessagePost(author, clientList, name, message);
- }
- delete clientList;
-
- g_ChatFlags = CHATFLAGS_INVALID;
- }
- delete messages;
-
- g_QueuedMessages[author].Clear();
- }
- Action ForwardOnChatMessage(int &author, ArrayList clientList, char[] name, int nameLength,
- char[] message, int messageLength) {
- Action forwardResult;
- Call_StartForward(g_fwdOnChatMessage);
- Call_PushCellRef(author);
- Call_PushCell(clientList);
- Call_PushStringEx(name, nameLength,
- SM_PARAM_STRING_UTF8 | SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK);
- Call_PushStringEx(message, messageLength,
- SM_PARAM_STRING_UTF8 | SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK);
-
- int error = Call_Finish(forwardResult);
-
- if (error) {
- ThrowNativeError(error, "Forward failed");
- return Plugin_Stop;
- }
- return forwardResult;
- }
- bool ShouldTransmitMessage(int author, int recipient) {
- return recipient == author || !IsClientMuted(recipient, author);
- }
- void ForwardOnChatMessagePost(int author, ArrayList clientList, const char[] name,
- const char[] message) {
- Call_StartForward(g_fwdOnChatMessagePost);
- Call_PushCell(author);
- Call_PushCell(clientList);
- Call_PushString(name);
- Call_PushString(message);
-
- int error = Call_Finish();
- if (error) {
- ThrowNativeError(error, "Forward failed");
- }
- }
- int Native_GetMessageFlags(Handle hPlugin, int argc) {
- return g_ChatFlags;
- }
- int ParseChatMessageFlags(const char[] localizationToken) {
- int chatFlags;
- if (StrContains(localizationToken, "all", false) != -1) {
- // send to all players, living and dead
- chatFlags |= CHATFLAGS_ALL;
- }
- if (StrContains(localizationToken, "team", false) != -1) {
- // send only to players on the same team
- chatFlags |= CHATFLAGS_TEAM;
- }
- if (StrContains(localizationToken, "spec", false) != -1) {
- // send only to players in spec
- chatFlags |= CHATFLAGS_SPEC;
- }
- if (StrContains(localizationToken, "dead", false) != -1) {
- // send to dead players and team members only
- chatFlags |= CHATFLAGS_DEAD;
- }
- return chatFlags;
- }
- void SayText(int author, int[] clients, int nClients,
- const char[] localizationToken, const char[] name, const char[] message) {
- for (int i; i < nClients; i++) {
- int temp[1];
- temp[0] = clients[i];
-
- Handle buffer = StartMessage("SayText2", temp, 1,
- USERMSG_RELIABLE | USERMSG_BLOCKHOOKS);
-
- BfWrite bitbuf = view_as<BfWrite>(buffer);
-
- bitbuf.WriteByte(author);
- bitbuf.WriteByte(true);
- bitbuf.WriteString(localizationToken);
- bitbuf.WriteString(name);
- bitbuf.WriteString(message);
-
- EndMessage();
- }
- }
|