#pragma once
+#if defined(SVQC)
+ #include <server/chat.qh>
+#endif
+
#ifdef SVQC
/** Use new sound handling. TODO: use when sounds play correctly on clients */
bool autocvar_g_debug_globalsounds = false;
#include <server/anticheat.qc>
#include <server/antilag.qc>
#include <server/campaign.qc>
+#include <server/chat.qc>
#include <server/cheats.qc>
#include <server/client.qc>
#include <server/clientkill.qc>
#include <server/anticheat.qh>
#include <server/antilag.qh>
#include <server/campaign.qh>
+#include <server/chat.qh>
#include <server/cheats.qh>
#include <server/client.qh>
#include <server/clientkill.qh>
--- /dev/null
+#include "chat.qh"
+
+#include <common/gamemodes/_mod.qh>
+#include <common/mapobjects/target/location.qh>
+#include <common/mapobjects/triggers.qh>
+#include <common/teams.qh>
+#include <common/util.qh>
+#include <common/weapons/weapon.qh>
+#include <common/wepent.qh>
+#include <server/command/common.qh>
+#include <server/gamelog.qh>
+#include <server/main.qh>
+#include <server/mapvoting.qh>
+#include <server/miscfunctions.qh>
+
+/**
+ * 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);
+ }
+}
--- /dev/null
+#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);
#include <common/weapons/_all.qh>
#include <common/stats.qh>
+#include <server/chat.qh>
#include <server/miscfunctions.qh>
#include <common/effects/all.qh>
#include "anticheat.qh"
#include <common/weapons/weapon/vortex.qh>
-#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);
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);
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)
{
//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;
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);
#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;
#include "cmd.qh"
+#include <server/chat.qh>
#include <server/world.qh>
#include <server/miscfunctions.qh>
#include "common.qh"
+#include <server/chat.qh>
#include <server/client.qh>
#include <common/weapons/_all.qh>
#include <common/stats.qh>
#include "matrix.qh"
-#include "client.qh"
+#include <server/chat.qh>
var void MX_Handle(int buf, string ancestor)
{
+// 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"
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
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);
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);
// 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