From 51b6f4b7b74c01310c8a09436a915da2ff14563d Mon Sep 17 00:00:00 2001 From: Mario Date: Fri, 25 Sep 2020 17:58:16 +1000 Subject: [PATCH] Move server-side chat handling to its own file, and add a note about miscfunctions --- qcsrc/common/effects/qc/globalsound.qh | 4 + qcsrc/server/_mod.inc | 1 + qcsrc/server/_mod.qh | 1 + qcsrc/server/chat.qc | 579 +++++++++++++++++++++++++ qcsrc/server/chat.qh | 50 +++ qcsrc/server/client.qc | 383 +--------------- qcsrc/server/client.qh | 38 -- qcsrc/server/command/cmd.qc | 1 + qcsrc/server/command/common.qc | 1 + qcsrc/server/matrix.qc | 2 +- qcsrc/server/miscfunctions.qc | 187 +------- qcsrc/server/miscfunctions.qh | 8 - 12 files changed, 641 insertions(+), 614 deletions(-) create mode 100644 qcsrc/server/chat.qc create mode 100644 qcsrc/server/chat.qh diff --git a/qcsrc/common/effects/qc/globalsound.qh b/qcsrc/common/effects/qc/globalsound.qh index 5460d72ac..cec501c55 100644 --- a/qcsrc/common/effects/qc/globalsound.qh +++ b/qcsrc/common/effects/qc/globalsound.qh @@ -1,5 +1,9 @@ #pragma once +#if defined(SVQC) + #include +#endif + #ifdef SVQC /** Use new sound handling. TODO: use when sounds play correctly on clients */ bool autocvar_g_debug_globalsounds = false; diff --git a/qcsrc/server/_mod.inc b/qcsrc/server/_mod.inc index cdd7d0d26..d6bc0528b 100644 --- a/qcsrc/server/_mod.inc +++ b/qcsrc/server/_mod.inc @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include diff --git a/qcsrc/server/_mod.qh b/qcsrc/server/_mod.qh index 7d1728f80..e881b403d 100644 --- a/qcsrc/server/_mod.qh +++ b/qcsrc/server/_mod.qh @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include diff --git a/qcsrc/server/chat.qc b/qcsrc/server/chat.qc new file mode 100644 index 000000000..2ed7fa1ed --- /dev/null +++ b/qcsrc/server/chat.qc @@ -0,0 +1,579 @@ +#include "chat.qh" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * message "": do not say, just test flood control + * return value: + * 1 = accept + * 0 = reject + * -1 = fake accept + */ +int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodcontrol) +{ + if (!teamsay && !privatesay && substring(msgin, 0, 1) == " ") + msgin = substring(msgin, 1, -1); // work around DP say bug (say_team does not have this!) + + if (source) + msgin = formatmessage(source, msgin); + + string colorstr; + if (!(IS_PLAYER(source) || source.caplayer)) + colorstr = "^0"; // black for spectators + else if(teamplay) + colorstr = Team_ColorCode(source.team); + else + { + colorstr = ""; + teamsay = false; + } + + if (!source) { + colorstr = ""; + teamsay = false; + } + + if(msgin != "") + msgin = trigger_magicear_processmessage_forallears(source, teamsay, privatesay, msgin); + + /* + * using bprint solves this... me stupid + // how can we prevent the message from appearing in a listen server? + // for now, just give "say" back and only handle say_team + if(!teamsay) + { + clientcommand(source, strcat("say ", msgin)); + return; + } + */ + + string namestr = ""; + if (source) + namestr = playername(source, autocvar_g_chat_teamcolors); + + string colorprefix = (strdecolorize(namestr) == namestr) ? "^3" : "^7"; + + string msgstr = "", cmsgstr = ""; + string privatemsgprefix = string_null; + int privatemsgprefixlen = 0; + if (msgin != "") + { + bool found_me = false; + if(strstrofs(msgin, "/me", 0) >= 0) + { + string newmsgin = ""; + string newnamestr = ((teamsay) ? strcat(colorstr, "(", colorprefix, namestr, colorstr, ")", "^7") : strcat(colorprefix, namestr, "^7")); + FOREACH_WORD(msgin, true, + { + if(strdecolorize(it) == "/me") + { + found_me = true; + newmsgin = cons(newmsgin, newnamestr); + } + else + newmsgin = cons(newmsgin, it); + }); + msgin = newmsgin; + } + + if(privatesay) + { + msgstr = strcat("\{1}\{13}* ", colorprefix, namestr, "^3 tells you: ^7"); + privatemsgprefixlen = strlen(msgstr); + msgstr = strcat(msgstr, msgin); + cmsgstr = strcat(colorstr, colorprefix, namestr, "^3 tells you:\n^7", msgin); + privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", playername(privatesay, autocvar_g_chat_teamcolors), ": ^7"); + } + else if(teamsay) + { + if(found_me) + { + //msgin = strreplace("/me", "", msgin); + //msgin = substring(msgin, 3, strlen(msgin)); + //msgin = strreplace("/me", strcat(colorstr, "(", colorprefix, namestr, colorstr, ")^7"), msgin); + msgstr = strcat("\{1}\{13}^4* ", "^7", msgin); + } + else + msgstr = strcat("\{1}\{13}", colorstr, "(", colorprefix, namestr, colorstr, ") ^7", msgin); + cmsgstr = strcat(colorstr, "(", colorprefix, namestr, colorstr, ")\n^7", msgin); + } + else + { + if(found_me) + { + //msgin = strreplace("/me", "", msgin); + //msgin = substring(msgin, 3, strlen(msgin)); + //msgin = strreplace("/me", strcat(colorprefix, namestr), msgin); + msgstr = strcat("\{1}^4* ^7", msgin); + } + else { + msgstr = "\{1}"; + msgstr = strcat(msgstr, (namestr != "") ? strcat(colorprefix, namestr, "^7: ") : "^7"); + msgstr = strcat(msgstr, msgin); + } + cmsgstr = ""; + } + msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint + } + + string fullmsgstr = msgstr; + string fullcmsgstr = cmsgstr; + + // FLOOD CONTROL + int flood = 0; + var .float flood_field = floodcontrol_chat; + if(floodcontrol && source) + { + float flood_spl, flood_burst, flood_lmax; + if(privatesay) + { + flood_spl = autocvar_g_chat_flood_spl_tell; + flood_burst = autocvar_g_chat_flood_burst_tell; + flood_lmax = autocvar_g_chat_flood_lmax_tell; + flood_field = floodcontrol_chattell; + } + else if(teamsay) + { + flood_spl = autocvar_g_chat_flood_spl_team; + flood_burst = autocvar_g_chat_flood_burst_team; + flood_lmax = autocvar_g_chat_flood_lmax_team; + flood_field = floodcontrol_chatteam; + } + else + { + flood_spl = autocvar_g_chat_flood_spl; + flood_burst = autocvar_g_chat_flood_burst; + flood_lmax = autocvar_g_chat_flood_lmax; + flood_field = floodcontrol_chat; + } + flood_burst = max(0, flood_burst - 1); + // to match explanation in default.cfg, a value of 3 must allow three-line bursts and not four! + + // do flood control for the default line size + if(msgstr != "") + { + getWrappedLine_remaining = msgstr; + msgstr = ""; + int lines = 0; + while(getWrappedLine_remaining && (!flood_lmax || lines <= flood_lmax)) + { + msgstr = strcat(msgstr, " ", getWrappedLineLen(82.4289758859709, strlennocol)); // perl averagewidth.pl < gfx/vera-sans.width + ++lines; + } + msgstr = substring(msgstr, 1, strlen(msgstr) - 1); + + if(getWrappedLine_remaining != "") + { + msgstr = strcat(msgstr, "\n"); + flood = 2; + } + + if (time >= source.(flood_field)) + { + source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + lines * flood_spl; + } + else + { + flood = 1; + msgstr = fullmsgstr; + } + } + else + { + if (time >= source.(flood_field)) + source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + flood_spl; + else + flood = 1; + } + + if (timeout_status == TIMEOUT_ACTIVE) // when game is paused, no flood protection + source.(flood_field) = flood = 0; + } + + string sourcemsgstr, sourcecmsgstr; + if(flood == 2) // cannot happen for empty msgstr + { + if(autocvar_g_chat_flood_notify_flooder) + { + sourcemsgstr = strcat(msgstr, "\n^3FLOOD CONTROL: ^7message too long, trimmed\n"); + sourcecmsgstr = ""; + } + else + { + sourcemsgstr = fullmsgstr; + sourcecmsgstr = fullcmsgstr; + } + cmsgstr = ""; + } + else + { + sourcemsgstr = msgstr; + sourcecmsgstr = cmsgstr; + } + + if (!privatesay && source && !(IS_PLAYER(source) || source.caplayer) && !game_stopped + && (teamsay || CHAT_NOSPECTATORS())) + { + teamsay = -1; // spectators + } + + if(flood) + LOG_INFO("NOTE: ", playername(source, true), "^7 is flooding."); + + // build sourcemsgstr by cutting off a prefix and replacing it by the other one + if(privatesay) + sourcemsgstr = strcat(privatemsgprefix, substring(sourcemsgstr, privatemsgprefixlen, -1)); + + int ret; + if(source && CS(source).muted) + { + // always fake the message + ret = -1; + } + else if(flood == 1) + { + if (autocvar_g_chat_flood_notify_flooder) + { + sprint(source, strcat("^3FLOOD CONTROL: ^7wait ^1", ftos(source.(flood_field) - time), "^3 seconds\n")); + ret = 0; + } + else + ret = -1; + } + else + { + ret = 1; + } + + if (privatesay && source && !(IS_PLAYER(source) || source.caplayer) && !game_stopped + && (IS_PLAYER(privatesay) || privatesay.caplayer) && CHAT_NOSPECTATORS()) + { + ret = -1; // just hide the message completely + } + + MUTATOR_CALLHOOK(ChatMessage, source, ret); + ret = M_ARGV(1, int); + + string event_log_msg = ""; + + if(sourcemsgstr != "" && ret != 0) + { + if(ret < 0) // faked message, because the player is muted + { + sprint(source, sourcemsgstr); + if(sourcecmsgstr != "" && !privatesay) + centerprint(source, sourcecmsgstr); + } + else if(privatesay) // private message, between 2 people only + { + sprint(source, sourcemsgstr); + if (!autocvar_g_chat_tellprivacy) { dedicated_print(msgstr); } // send to server console too if "tellprivacy" is disabled + if(!MUTATOR_CALLHOOK(ChatMessageTo, privatesay, source)) + { + sprint(privatesay, msgstr); + if(cmsgstr != "") + centerprint(privatesay, cmsgstr); + } + } + else if ( teamsay && CS(source).active_minigame ) + { + sprint(source, sourcemsgstr); + dedicated_print(msgstr); // send to server console too + FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && CS(it).active_minigame == CS(source).active_minigame && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), { + sprint(it, msgstr); + }); + event_log_msg = sprintf(":chat_minigame:%d:%s:%s", source.playerid, CS(source).active_minigame.netname, msgin); + + } + else if(teamsay > 0) // team message, only sent to team mates + { + sprint(source, sourcemsgstr); + dedicated_print(msgstr); // send to server console too + if(sourcecmsgstr != "") + centerprint(source, sourcecmsgstr); + FOREACH_CLIENT((IS_PLAYER(it) || it.caplayer) && IS_REAL_CLIENT(it) && it != source && it.team == source.team && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), { + sprint(it, msgstr); + if(cmsgstr != "") + centerprint(it, cmsgstr); + }); + event_log_msg = sprintf(":chat_team:%d:%d:%s", source.playerid, source.team, strreplace("\n", " ", msgin)); + } + else if(teamsay < 0) // spectator message, only sent to spectators + { + sprint(source, sourcemsgstr); + dedicated_print(msgstr); // send to server console too + FOREACH_CLIENT(!(IS_PLAYER(it) || it.caplayer) && IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), { + sprint(it, msgstr); + }); + event_log_msg = sprintf(":chat_spec:%d:%s", source.playerid, strreplace("\n", " ", msgin)); + } + else + { + if (source) { + sprint(source, sourcemsgstr); + dedicated_print(msgstr); // send to server console too + MX_Say(strcat(playername(source, true), "^7: ", msgin)); + } + FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), { + sprint(it, msgstr); + }); + event_log_msg = sprintf(":chat:%d:%s", source.playerid, strreplace("\n", " ", msgin)); + } + } + + if (autocvar_sv_eventlog && (event_log_msg != "")) { + GameLogEcho(event_log_msg); + } + + return ret; +} + +entity findnearest(vector point, bool checkitems, vector axismod) +{ + vector dist; + int num_nearest = 0; + + IL_EACH(((checkitems) ? g_items : g_locations), ((checkitems) ? (it.target == "###item###") : (it.classname == "target_location")), + { + if ((it.items == IT_KEY1 || it.items == IT_KEY2) && it.target == "###item###") + dist = it.oldorigin; + else + dist = it.origin; + dist = dist - point; + dist = dist.x * axismod.x * '1 0 0' + dist.y * axismod.y * '0 1 0' + dist.z * axismod.z * '0 0 1'; + float len = vlen2(dist); + + int l; + for (l = 0; l < num_nearest; ++l) + { + if (len < nearest_length[l]) + break; + } + + // now i tells us where to insert at + // INSERTION SORT! YOU'VE SEEN IT! RUN! + if (l < NUM_NEAREST_ENTITIES) + { + for (int j = NUM_NEAREST_ENTITIES - 1; j >= l; --j) + { + nearest_length[j + 1] = nearest_length[j]; + nearest_entity[j + 1] = nearest_entity[j]; + } + nearest_length[l] = len; + nearest_entity[l] = it; + if (num_nearest < NUM_NEAREST_ENTITIES) + num_nearest = num_nearest + 1; + } + }); + + // now use the first one from our list that we can see + for (int j = 0; j < num_nearest; ++j) + { + traceline(point, nearest_entity[j].origin, true, NULL); + if (trace_fraction == 1) + { + if (j != 0) + LOG_TRACEF("Nearest point (%s) is not visible, using a visible one.", nearest_entity[0].netname); + return nearest_entity[j]; + } + } + + if (num_nearest == 0) + return NULL; + + LOG_TRACE("Not seeing any location point, using nearest as fallback."); + /* DEBUGGING CODE: + dprint("Candidates were: "); + for(j = 0; j < num_nearest; ++j) + { + if(j != 0) + dprint(", "); + dprint(nearest_entity[j].netname); + } + dprint("\n"); + */ + + return nearest_entity[0]; +} + +string NearestLocation(vector p) +{ + string ret = "somewhere"; + entity loc = findnearest(p, false, '1 1 1'); + if (loc) + ret = loc.message; + else + { + loc = findnearest(p, true, '1 1 4'); + if (loc) + ret = loc.netname; + } + return ret; +} + +string PlayerHealth(entity this) +{ + float myhealth = floor(GetResource(this, RES_HEALTH)); + if(myhealth == -666) + return "spectating"; + else if(myhealth == -2342 || (myhealth == 2342 && mapvote_initialized)) + return "observing"; + else if(myhealth <= 0 || IS_DEAD(this)) + return "dead"; + return ftos(myhealth); +} + +string WeaponNameFromWeaponentity(entity this, .entity weaponentity) +{ + entity wepent = this.(weaponentity); + if(!wepent) + return "none"; + else if(wepent.m_weapon != WEP_Null) + return wepent.m_weapon.m_name; + else if(wepent.m_switchweapon != WEP_Null) + return wepent.m_switchweapon.m_name; + return "none"; //REGISTRY_GET(Weapons, wepent.cnt).m_name; +} + +string formatmessage(entity this, string msg) +{ + float p, p1, p2; + float n; + vector cursor = '0 0 0'; + entity cursor_ent = NULL; + string escape; + string replacement; + p = 0; + n = 7; + bool traced = false; + + MUTATOR_CALLHOOK(PreFormatMessage, this, msg); + msg = M_ARGV(1, string); + + while (1) { + if (n < 1) + break; // too many replacements + + n = n - 1; + p1 = strstrofs(msg, "%", p); // NOTE: this destroys msg as it's a tempstring! + p2 = strstrofs(msg, "\\", p); // NOTE: this destroys msg as it's a tempstring! + + if (p1 < 0) + p1 = p2; + + if (p2 < 0) + p2 = p1; + + p = min(p1, p2); + + if (p < 0) + break; + + if(!traced) + { + WarpZone_crosshair_trace_plusvisibletriggers(this); + cursor = trace_endpos; + cursor_ent = trace_ent; + traced = true; + } + + replacement = substring(msg, p, 2); + escape = substring(msg, p + 1, 1); + + .entity weaponentity = weaponentities[0]; // TODO: unhardcode + + switch(escape) + { + case "%": replacement = "%"; break; + case "\\":replacement = "\\"; break; + case "n": replacement = "\n"; break; + case "a": replacement = ftos(floor(GetResource(this, RES_ARMOR))); break; + case "h": replacement = PlayerHealth(this); break; + case "l": replacement = NearestLocation(this.origin); break; + case "y": replacement = NearestLocation(cursor); break; + case "d": replacement = NearestLocation(this.death_origin); break; + case "w": replacement = WeaponNameFromWeaponentity(this, weaponentity); break; + case "W": replacement = GetAmmoName(this.(weaponentity).m_weapon.ammo_type); break; + case "x": replacement = ((cursor_ent.netname == "" || !cursor_ent) ? "nothing" : cursor_ent.netname); break; + case "s": replacement = ftos(vlen(this.velocity - this.velocity_z * '0 0 1')); break; + case "S": replacement = ftos(vlen(this.velocity)); break; + case "t": replacement = seconds_tostring(ceil(max(0, autocvar_timelimit * 60 + game_starttime - time))); break; + case "T": replacement = seconds_tostring(floor(time - game_starttime)); break; + default: + { + MUTATOR_CALLHOOK(FormatMessage, this, escape, replacement, msg); + replacement = M_ARGV(2, string); + break; + } + } + + msg = strcat(substring(msg, 0, p), replacement, substring(msg, p+2, strlen(msg) - (p+2))); + p = p + strlen(replacement); + } + return msg; +} + +ERASEABLE +void PrintToChat(entity client, string text) +{ + text = strcat("\{1}^7", text, "\n"); + sprint(client, text); +} + +ERASEABLE +void DebugPrintToChat(entity client, string text) +{ + if (autocvar_developer > 0) + { + PrintToChat(client, text); + } +} + +ERASEABLE +void PrintToChatAll(string text) +{ + text = strcat("\{1}^7", text, "\n"); + bprint(text); +} + +ERASEABLE +void DebugPrintToChatAll(string text) +{ + if (autocvar_developer > 0) + { + PrintToChatAll(text); + } +} + +ERASEABLE +void PrintToChatTeam(int team_num, string text) +{ + text = strcat("\{1}^7", text, "\n"); + FOREACH_CLIENT(IS_REAL_CLIENT(it), + { + if (it.team == team_num) + { + sprint(it, text); + } + }); +} + +ERASEABLE +void DebugPrintToChatTeam(int team_num, string text) +{ + if (autocvar_developer > 0) + { + PrintToChatTeam(team_num, text); + } +} diff --git a/qcsrc/server/chat.qh b/qcsrc/server/chat.qh new file mode 100644 index 000000000..9c30e89a4 --- /dev/null +++ b/qcsrc/server/chat.qh @@ -0,0 +1,50 @@ +#pragma once + +const float NUM_NEAREST_ENTITIES = 4; +entity nearest_entity[NUM_NEAREST_ENTITIES]; +float nearest_length[NUM_NEAREST_ENTITIES]; + +.float floodcontrol_chat; +.float floodcontrol_chatteam; +.float floodcontrol_chattell; +.float floodcontrol_voice; +.float floodcontrol_voiceteam; + +#define CHAT_NOSPECTATORS() ((autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage)) + +int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodcontrol); + +string NearestLocation(vector p); + +string formatmessage(entity this, string msg); + +/// \brief Print the string to the client's chat. +/// \param[in] client Client to print to. +/// \param[in] text Text to print. +void PrintToChat(entity client, string text); + +/// \brief Print the string to the client's chat if the server cvar "developer" +/// is not 0. +/// \param[in] client Client to print to. +/// \param[in] text Text to print. +void DebugPrintToChat(entity client, string text); + +/// \brief Prints the string to all clients' chat. +/// \param[in] text Text to print. +void PrintToChatAll(string text); + +/// \brief Prints the string to all clients' chat if the server cvar "developer" +/// is not 0. +/// \param[in] text Text to print. +void DebugPrintToChatAll(string text); + +/// \brief Print the string to chat of all clients of the specified team. +/// \param[in] team_num Team to print to. See NUM_TEAM constants. +/// \param[in] text Text to print. +void PrintToChatTeam(int team_num, string text); + +/// \brief Print the string to chat of all clients of the specified team if the +/// server cvar "developer" is not 0. +/// \param[in] team_num Team to print to. See NUM_TEAM constants. +/// \param[in] text Text to print. +void DebugPrintToChatTeam(int team_num, string text); diff --git a/qcsrc/server/client.qc b/qcsrc/server/client.qc index d403f2c11..04fba8093 100644 --- a/qcsrc/server/client.qc +++ b/qcsrc/server/client.qc @@ -2,6 +2,7 @@ #include #include +#include #include #include #include "anticheat.qh" @@ -84,8 +85,6 @@ #include -#define CHAT_NOSPECTATORS() ((autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage)) - STATIC_METHOD(Client, Add, void(Client this, int _team)) { ClientConnect(this); @@ -1444,60 +1443,6 @@ void respawn(entity this) PutClientInServer(this); } -ERASEABLE -void PrintToChat(entity client, string text) -{ - text = strcat("\{1}^7", text, "\n"); - sprint(client, text); -} - -ERASEABLE -void DebugPrintToChat(entity client, string text) -{ - if (autocvar_developer > 0) - { - PrintToChat(client, text); - } -} - -ERASEABLE -void PrintToChatAll(string text) -{ - text = strcat("\{1}^7", text, "\n"); - bprint(text); -} - -ERASEABLE -void DebugPrintToChatAll(string text) -{ - if (autocvar_developer > 0) - { - PrintToChatAll(text); - } -} - -ERASEABLE -void PrintToChatTeam(int team_num, string text) -{ - text = strcat("\{1}^7", text, "\n"); - FOREACH_CLIENT(IS_REAL_CLIENT(it), - { - if (it.team == team_num) - { - sprint(it, text); - } - }); -} - -ERASEABLE -void DebugPrintToChatTeam(int team_num, string text) -{ - if (autocvar_developer > 0) - { - PrintToChatTeam(team_num, text); - } -} - void play_countdown(entity this, float finished, Sound samp) { TC(Sound, samp); @@ -2840,332 +2785,6 @@ void PlayerPostThink (entity this) CSQCMODEL_AUTOUPDATE(this); } -/** - * message "": do not say, just test flood control - * return value: - * 1 = accept - * 0 = reject - * -1 = fake accept - */ -int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodcontrol) -{ - if (!teamsay && !privatesay && substring(msgin, 0, 1) == " ") - msgin = substring(msgin, 1, -1); // work around DP say bug (say_team does not have this!) - - if (source) - msgin = formatmessage(source, msgin); - - string colorstr; - if (!(IS_PLAYER(source) || source.caplayer)) - colorstr = "^0"; // black for spectators - else if(teamplay) - colorstr = Team_ColorCode(source.team); - else - { - colorstr = ""; - teamsay = false; - } - - if (!source) { - colorstr = ""; - teamsay = false; - } - - if(msgin != "") - msgin = trigger_magicear_processmessage_forallears(source, teamsay, privatesay, msgin); - - /* - * using bprint solves this... me stupid - // how can we prevent the message from appearing in a listen server? - // for now, just give "say" back and only handle say_team - if(!teamsay) - { - clientcommand(source, strcat("say ", msgin)); - return; - } - */ - - string namestr = ""; - if (source) - namestr = playername(source, autocvar_g_chat_teamcolors); - - string colorprefix = (strdecolorize(namestr) == namestr) ? "^3" : "^7"; - - string msgstr = "", cmsgstr = ""; - string privatemsgprefix = string_null; - int privatemsgprefixlen = 0; - if (msgin != "") - { - bool found_me = false; - if(strstrofs(msgin, "/me", 0) >= 0) - { - string newmsgin = ""; - string newnamestr = ((teamsay) ? strcat(colorstr, "(", colorprefix, namestr, colorstr, ")", "^7") : strcat(colorprefix, namestr, "^7")); - FOREACH_WORD(msgin, true, - { - if(strdecolorize(it) == "/me") - { - found_me = true; - newmsgin = cons(newmsgin, newnamestr); - } - else - newmsgin = cons(newmsgin, it); - }); - msgin = newmsgin; - } - - if(privatesay) - { - msgstr = strcat("\{1}\{13}* ", colorprefix, namestr, "^3 tells you: ^7"); - privatemsgprefixlen = strlen(msgstr); - msgstr = strcat(msgstr, msgin); - cmsgstr = strcat(colorstr, colorprefix, namestr, "^3 tells you:\n^7", msgin); - privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", playername(privatesay, autocvar_g_chat_teamcolors), ": ^7"); - } - else if(teamsay) - { - if(found_me) - { - //msgin = strreplace("/me", "", msgin); - //msgin = substring(msgin, 3, strlen(msgin)); - //msgin = strreplace("/me", strcat(colorstr, "(", colorprefix, namestr, colorstr, ")^7"), msgin); - msgstr = strcat("\{1}\{13}^4* ", "^7", msgin); - } - else - msgstr = strcat("\{1}\{13}", colorstr, "(", colorprefix, namestr, colorstr, ") ^7", msgin); - cmsgstr = strcat(colorstr, "(", colorprefix, namestr, colorstr, ")\n^7", msgin); - } - else - { - if(found_me) - { - //msgin = strreplace("/me", "", msgin); - //msgin = substring(msgin, 3, strlen(msgin)); - //msgin = strreplace("/me", strcat(colorprefix, namestr), msgin); - msgstr = strcat("\{1}^4* ^7", msgin); - } - else { - msgstr = "\{1}"; - msgstr = strcat(msgstr, (namestr != "") ? strcat(colorprefix, namestr, "^7: ") : "^7"); - msgstr = strcat(msgstr, msgin); - } - cmsgstr = ""; - } - msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint - } - - string fullmsgstr = msgstr; - string fullcmsgstr = cmsgstr; - - // FLOOD CONTROL - int flood = 0; - var .float flood_field = floodcontrol_chat; - if(floodcontrol && source) - { - float flood_spl, flood_burst, flood_lmax; - if(privatesay) - { - flood_spl = autocvar_g_chat_flood_spl_tell; - flood_burst = autocvar_g_chat_flood_burst_tell; - flood_lmax = autocvar_g_chat_flood_lmax_tell; - flood_field = floodcontrol_chattell; - } - else if(teamsay) - { - flood_spl = autocvar_g_chat_flood_spl_team; - flood_burst = autocvar_g_chat_flood_burst_team; - flood_lmax = autocvar_g_chat_flood_lmax_team; - flood_field = floodcontrol_chatteam; - } - else - { - flood_spl = autocvar_g_chat_flood_spl; - flood_burst = autocvar_g_chat_flood_burst; - flood_lmax = autocvar_g_chat_flood_lmax; - flood_field = floodcontrol_chat; - } - flood_burst = max(0, flood_burst - 1); - // to match explanation in default.cfg, a value of 3 must allow three-line bursts and not four! - - // do flood control for the default line size - if(msgstr != "") - { - getWrappedLine_remaining = msgstr; - msgstr = ""; - int lines = 0; - while(getWrappedLine_remaining && (!flood_lmax || lines <= flood_lmax)) - { - msgstr = strcat(msgstr, " ", getWrappedLineLen(82.4289758859709, strlennocol)); // perl averagewidth.pl < gfx/vera-sans.width - ++lines; - } - msgstr = substring(msgstr, 1, strlen(msgstr) - 1); - - if(getWrappedLine_remaining != "") - { - msgstr = strcat(msgstr, "\n"); - flood = 2; - } - - if (time >= source.(flood_field)) - { - source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + lines * flood_spl; - } - else - { - flood = 1; - msgstr = fullmsgstr; - } - } - else - { - if (time >= source.(flood_field)) - source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + flood_spl; - else - flood = 1; - } - - if (timeout_status == TIMEOUT_ACTIVE) // when game is paused, no flood protection - source.(flood_field) = flood = 0; - } - - string sourcemsgstr, sourcecmsgstr; - if(flood == 2) // cannot happen for empty msgstr - { - if(autocvar_g_chat_flood_notify_flooder) - { - sourcemsgstr = strcat(msgstr, "\n^3FLOOD CONTROL: ^7message too long, trimmed\n"); - sourcecmsgstr = ""; - } - else - { - sourcemsgstr = fullmsgstr; - sourcecmsgstr = fullcmsgstr; - } - cmsgstr = ""; - } - else - { - sourcemsgstr = msgstr; - sourcecmsgstr = cmsgstr; - } - - if (!privatesay && source && !(IS_PLAYER(source) || source.caplayer) && !game_stopped - && (teamsay || CHAT_NOSPECTATORS())) - { - teamsay = -1; // spectators - } - - if(flood) - LOG_INFO("NOTE: ", playername(source, true), "^7 is flooding."); - - // build sourcemsgstr by cutting off a prefix and replacing it by the other one - if(privatesay) - sourcemsgstr = strcat(privatemsgprefix, substring(sourcemsgstr, privatemsgprefixlen, -1)); - - int ret; - if(source && CS(source).muted) - { - // always fake the message - ret = -1; - } - else if(flood == 1) - { - if (autocvar_g_chat_flood_notify_flooder) - { - sprint(source, strcat("^3FLOOD CONTROL: ^7wait ^1", ftos(source.(flood_field) - time), "^3 seconds\n")); - ret = 0; - } - else - ret = -1; - } - else - { - ret = 1; - } - - if (privatesay && source && !(IS_PLAYER(source) || source.caplayer) && !game_stopped - && (IS_PLAYER(privatesay) || privatesay.caplayer) && CHAT_NOSPECTATORS()) - { - ret = -1; // just hide the message completely - } - - MUTATOR_CALLHOOK(ChatMessage, source, ret); - ret = M_ARGV(1, int); - - string event_log_msg = ""; - - if(sourcemsgstr != "" && ret != 0) - { - if(ret < 0) // faked message, because the player is muted - { - sprint(source, sourcemsgstr); - if(sourcecmsgstr != "" && !privatesay) - centerprint(source, sourcecmsgstr); - } - else if(privatesay) // private message, between 2 people only - { - sprint(source, sourcemsgstr); - if (!autocvar_g_chat_tellprivacy) { dedicated_print(msgstr); } // send to server console too if "tellprivacy" is disabled - if(!MUTATOR_CALLHOOK(ChatMessageTo, privatesay, source)) - { - sprint(privatesay, msgstr); - if(cmsgstr != "") - centerprint(privatesay, cmsgstr); - } - } - else if ( teamsay && CS(source).active_minigame ) - { - sprint(source, sourcemsgstr); - dedicated_print(msgstr); // send to server console too - FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && CS(it).active_minigame == CS(source).active_minigame && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), { - sprint(it, msgstr); - }); - event_log_msg = sprintf(":chat_minigame:%d:%s:%s", source.playerid, CS(source).active_minigame.netname, msgin); - - } - else if(teamsay > 0) // team message, only sent to team mates - { - sprint(source, sourcemsgstr); - dedicated_print(msgstr); // send to server console too - if(sourcecmsgstr != "") - centerprint(source, sourcecmsgstr); - FOREACH_CLIENT((IS_PLAYER(it) || it.caplayer) && IS_REAL_CLIENT(it) && it != source && it.team == source.team && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), { - sprint(it, msgstr); - if(cmsgstr != "") - centerprint(it, cmsgstr); - }); - event_log_msg = sprintf(":chat_team:%d:%d:%s", source.playerid, source.team, strreplace("\n", " ", msgin)); - } - else if(teamsay < 0) // spectator message, only sent to spectators - { - sprint(source, sourcemsgstr); - dedicated_print(msgstr); // send to server console too - FOREACH_CLIENT(!(IS_PLAYER(it) || it.caplayer) && IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), { - sprint(it, msgstr); - }); - event_log_msg = sprintf(":chat_spec:%d:%s", source.playerid, strreplace("\n", " ", msgin)); - } - else - { - if (source) { - sprint(source, sourcemsgstr); - dedicated_print(msgstr); // send to server console too - MX_Say(strcat(playername(source, true), "^7: ", msgin)); - } - FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), { - sprint(it, msgstr); - }); - event_log_msg = sprintf(":chat:%d:%s", source.playerid, strreplace("\n", " ", msgin)); - } - } - - if (autocvar_sv_eventlog && (event_log_msg != "")) { - GameLogEcho(event_log_msg); - } - - return ret; -} - // hack to copy the button fields from the client entity to the Client State void PM_UpdateButtons(entity this, entity store) { diff --git a/qcsrc/server/client.qh b/qcsrc/server/client.qh index d074ebe4b..abda1204a 100644 --- a/qcsrc/server/client.qh +++ b/qcsrc/server/client.qh @@ -268,11 +268,6 @@ bool independent_players; //flood fields .float nickspamtime; // time of last nick change .float nickspamcount; -.float floodcontrol_chat; -.float floodcontrol_chatteam; -.float floodcontrol_chattell; -.float floodcontrol_voice; -.float floodcontrol_voiceteam; // respawning .int respawn_flags; @@ -329,37 +324,6 @@ void FixClientCvars(entity e); IntrusiveList g_initforplayer; STATIC_INIT(g_initforplayer) { g_initforplayer = IL_NEW(); } -/// \brief Print the string to the client's chat. -/// \param[in] client Client to print to. -/// \param[in] text Text to print. -void PrintToChat(entity client, string text); - -/// \brief Print the string to the client's chat if the server cvar "developer" -/// is not 0. -/// \param[in] client Client to print to. -/// \param[in] text Text to print. -void DebugPrintToChat(entity client, string text); - -/// \brief Prints the string to all clients' chat. -/// \param[in] text Text to print. -void PrintToChatAll(string text); - -/// \brief Prints the string to all clients' chat if the server cvar "developer" -/// is not 0. -/// \param[in] text Text to print. -void DebugPrintToChatAll(string text); - -/// \brief Print the string to chat of all clients of the specified team. -/// \param[in] team_num Team to print to. See NUM_TEAM constants. -/// \param[in] text Text to print. -void PrintToChatTeam(int team_num, string text); - -/// \brief Print the string to chat of all clients of the specified team if the -/// server cvar "developer" is not 0. -/// \param[in] team_num Team to print to. See NUM_TEAM constants. -/// \param[in] text Text to print. -void DebugPrintToChatTeam(int team_num, string text); - void play_countdown(entity this, float finished, Sound samp); void RotRegen(entity this, float current, float regenstable, float regenfactor, float regenlinear, float regenframetime, float rotstable, float rotfactor, float rotlinear, float rotframetime, float limit_mod); @@ -386,6 +350,4 @@ void Join(entity this); #define SPECTATE_COPY() ACCUMULATE void SpectateCopy(entity this, entity spectatee) #define SPECTATE_COPYFIELD(fld) SPECTATE_COPY() { this.(fld) = spectatee.(fld); } -int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodcontrol); - const int MAX_SPECTATORS = 7; diff --git a/qcsrc/server/command/cmd.qc b/qcsrc/server/command/cmd.qc index 67ac6d641..c2c7c0992 100644 --- a/qcsrc/server/command/cmd.qc +++ b/qcsrc/server/command/cmd.qc @@ -1,5 +1,6 @@ #include "cmd.qh" +#include #include #include diff --git a/qcsrc/server/command/common.qc b/qcsrc/server/command/common.qc index 01fb5ad48..c5622ead3 100644 --- a/qcsrc/server/command/common.qc +++ b/qcsrc/server/command/common.qc @@ -1,5 +1,6 @@ #include "common.qh" +#include #include #include #include diff --git a/qcsrc/server/matrix.qc b/qcsrc/server/matrix.qc index b495b4b0d..ca8dd3230 100644 --- a/qcsrc/server/matrix.qc +++ b/qcsrc/server/matrix.qc @@ -1,6 +1,6 @@ #include "matrix.qh" -#include "client.qh" +#include var void MX_Handle(int buf, string ancestor) { diff --git a/qcsrc/server/miscfunctions.qc b/qcsrc/server/miscfunctions.qc index b748c17a0..9a0c4ae65 100644 --- a/qcsrc/server/miscfunctions.qc +++ b/qcsrc/server/miscfunctions.qc @@ -1,3 +1,5 @@ +// NOTE: Please do NOT add new functions to this file! It is a dumping ground that is in the process of being cleaned up, please find a proper home for your code! + #include "miscfunctions.qh" #include "antilag.qh" @@ -88,191 +90,6 @@ void WarpZone_crosshair_trace(entity pl) WarpZone_traceline_antilag(pl, CS(pl).cursor_trace_start, CS(pl).cursor_trace_start + normalize(CS(pl).cursor_trace_endpos - CS(pl).cursor_trace_start) * max_shot_distance, MOVE_NORMAL, pl, ANTILAG_LATENCY(pl)); } -entity findnearest(vector point, bool checkitems, vector axismod) -{ - vector dist; - int num_nearest = 0; - - IL_EACH(((checkitems) ? g_items : g_locations), ((checkitems) ? (it.target == "###item###") : (it.classname == "target_location")), - { - if ((it.items == IT_KEY1 || it.items == IT_KEY2) && it.target == "###item###") - dist = it.oldorigin; - else - dist = it.origin; - dist = dist - point; - dist = dist.x * axismod.x * '1 0 0' + dist.y * axismod.y * '0 1 0' + dist.z * axismod.z * '0 0 1'; - float len = vlen2(dist); - - int l; - for (l = 0; l < num_nearest; ++l) - { - if (len < nearest_length[l]) - break; - } - - // now i tells us where to insert at - // INSERTION SORT! YOU'VE SEEN IT! RUN! - if (l < NUM_NEAREST_ENTITIES) - { - for (int j = NUM_NEAREST_ENTITIES - 1; j >= l; --j) - { - nearest_length[j + 1] = nearest_length[j]; - nearest_entity[j + 1] = nearest_entity[j]; - } - nearest_length[l] = len; - nearest_entity[l] = it; - if (num_nearest < NUM_NEAREST_ENTITIES) - num_nearest = num_nearest + 1; - } - }); - - // now use the first one from our list that we can see - for (int j = 0; j < num_nearest; ++j) - { - traceline(point, nearest_entity[j].origin, true, NULL); - if (trace_fraction == 1) - { - if (j != 0) - LOG_TRACEF("Nearest point (%s) is not visible, using a visible one.", nearest_entity[0].netname); - return nearest_entity[j]; - } - } - - if (num_nearest == 0) - return NULL; - - LOG_TRACE("Not seeing any location point, using nearest as fallback."); - /* DEBUGGING CODE: - dprint("Candidates were: "); - for(j = 0; j < num_nearest; ++j) - { - if(j != 0) - dprint(", "); - dprint(nearest_entity[j].netname); - } - dprint("\n"); - */ - - return nearest_entity[0]; -} - -string NearestLocation(vector p) -{ - string ret = "somewhere"; - entity loc = findnearest(p, false, '1 1 1'); - if (loc) - ret = loc.message; - else - { - loc = findnearest(p, true, '1 1 4'); - if (loc) - ret = loc.netname; - } - return ret; -} - -string PlayerHealth(entity this) -{ - float myhealth = floor(GetResource(this, RES_HEALTH)); - if(myhealth == -666) - return "spectating"; - else if(myhealth == -2342 || (myhealth == 2342 && mapvote_initialized)) - return "observing"; - else if(myhealth <= 0 || IS_DEAD(this)) - return "dead"; - return ftos(myhealth); -} - -string WeaponNameFromWeaponentity(entity this, .entity weaponentity) -{ - entity wepent = this.(weaponentity); - if(!wepent) - return "none"; - else if(wepent.m_weapon != WEP_Null) - return wepent.m_weapon.m_name; - else if(wepent.m_switchweapon != WEP_Null) - return wepent.m_switchweapon.m_name; - return "none"; //REGISTRY_GET(Weapons, wepent.cnt).m_name; -} - -string formatmessage(entity this, string msg) -{ - float p, p1, p2; - float n; - vector cursor = '0 0 0'; - entity cursor_ent = NULL; - string escape; - string replacement; - p = 0; - n = 7; - bool traced = false; - - MUTATOR_CALLHOOK(PreFormatMessage, this, msg); - msg = M_ARGV(1, string); - - while (1) { - if (n < 1) - break; // too many replacements - - n = n - 1; - p1 = strstrofs(msg, "%", p); // NOTE: this destroys msg as it's a tempstring! - p2 = strstrofs(msg, "\\", p); // NOTE: this destroys msg as it's a tempstring! - - if (p1 < 0) - p1 = p2; - - if (p2 < 0) - p2 = p1; - - p = min(p1, p2); - - if (p < 0) - break; - - if(!traced) - { - WarpZone_crosshair_trace_plusvisibletriggers(this); - cursor = trace_endpos; - cursor_ent = trace_ent; - traced = true; - } - - replacement = substring(msg, p, 2); - escape = substring(msg, p + 1, 1); - - .entity weaponentity = weaponentities[0]; // TODO: unhardcode - - switch(escape) - { - case "%": replacement = "%"; break; - case "\\":replacement = "\\"; break; - case "n": replacement = "\n"; break; - case "a": replacement = ftos(floor(GetResource(this, RES_ARMOR))); break; - case "h": replacement = PlayerHealth(this); break; - case "l": replacement = NearestLocation(this.origin); break; - case "y": replacement = NearestLocation(cursor); break; - case "d": replacement = NearestLocation(this.death_origin); break; - case "w": replacement = WeaponNameFromWeaponentity(this, weaponentity); break; - case "W": replacement = GetAmmoName(this.(weaponentity).m_weapon.ammo_type); break; - case "x": replacement = ((cursor_ent.netname == "" || !cursor_ent) ? "nothing" : cursor_ent.netname); break; - case "s": replacement = ftos(vlen(this.velocity - this.velocity_z * '0 0 1')); break; - case "S": replacement = ftos(vlen(this.velocity)); break; - case "t": replacement = seconds_tostring(ceil(max(0, autocvar_timelimit * 60 + game_starttime - time))); break; - case "T": replacement = seconds_tostring(floor(time - game_starttime)); break; - default: - { - MUTATOR_CALLHOOK(FormatMessage, this, escape, replacement, msg); - replacement = M_ARGV(2, string); - break; - } - } - - msg = strcat(substring(msg, 0, p), replacement, substring(msg, p+2, strlen(msg) - (p+2))); - p = p + strlen(replacement); - } - return msg; -} - /* ============= GetCvars diff --git a/qcsrc/server/miscfunctions.qh b/qcsrc/server/miscfunctions.qh index 0f37cfac7..87b02725b 100644 --- a/qcsrc/server/miscfunctions.qh +++ b/qcsrc/server/miscfunctions.qh @@ -46,8 +46,6 @@ void detach_sameorigin(entity e); void follow_sameorigin(entity e, entity to); -string formatmessage(entity this, string msg); - void GetCvars(entity this, entity store, int f); float LostMovetypeFollow(entity ent); @@ -58,8 +56,6 @@ bool MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance); -string NearestLocation(vector p); - string playername(entity p, bool team_colorize); void SetMovetypeFollow(entity ent, entity e); @@ -77,10 +73,6 @@ void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomo // copies a string to a tempstring (so one can strunzone it) string strcat1(string s) = #115; // FRIK_FILE -const float NUM_NEAREST_ENTITIES = 4; -entity nearest_entity[NUM_NEAREST_ENTITIES]; -float nearest_length[NUM_NEAREST_ENTITIES]; - //#NO AUTOCVARS START -- 2.39.2