]> git.rm.cloudns.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into TimePath/menu_chat TimePath/menu_chat 1073/head
authorMario <mario.mario@y7mail.com>
Wed, 28 Sep 2022 04:40:46 +0000 (14:40 +1000)
committerMario <mario.mario@y7mail.com>
Wed, 28 Sep 2022 04:40:46 +0000 (14:40 +1000)
1  2 
qcsrc/client/main.qc
qcsrc/common/notifications/all.qc
qcsrc/common/weapons/weapon/tuba.qc
qcsrc/lib/cvar.qh
qcsrc/lib/vector.qh
qcsrc/menu/menu.qc
qcsrc/menu/modules/chat/commands.qc
qcsrc/menu/modules/extensions/commands.qc
qcsrc/menu/xonotic/mainwindow.qc
qcsrc/server/chat.qc
qcsrc/server/command/sv_cmd.qc

index 2d8483c834742955fd567f4a3fd88f60fd2286f4,22f0438f411fd76aa4dda07953d3e09c3f1345ef..da36877e82e779412c9d29ab3712738081145fd0
@@@ -913,33 -1030,10 +1032,33 @@@ void CSQC_Parse_StuffCmd(string strMess
  void CSQC_Parse_Print(string strMessage)
  {
        if (autocvar_developer_csqcentities) LOG_INFOF("CSQC_Parse_Print(\"%s\")", strMessage);
 -      print(ColorTranslateRGB(strMessage));
 +      if (autocvar__cl_hook_print != "") {
 +              int flags = NOTICE_SILENT;
 +              for (int i = 0, n = tokenizebyseparator(strMessage, "\n"); i < n; ++i) {
 +                      string s = substring(argv(i), 0, -1);
 +                      int c = str2chr(s, 0);
 +                      if (c == 1 || c == 2 || c == 3) {
 +                              s = substring(s, 1, -1);
 +                              if (c == 1) {
 +                                      flags &= ~NOTICE_SILENT;
 +                                      if (str2chr(s, 0) == '\r') {
 +                                              s = substring(s, 1, -1);
 +                                              flags |= NOTICE_PRIVATE;
 +                                      }
 +                              }
 +                              if (c != 2) {
 +                                      flags |= NOTICE_CHAT;
 +                              }
 +                              s = strcat("^3", s);
 +                      }
 +                      localcmd("\n", autocvar__cl_hook_print, " ", console_encode(s), " ", itos(flags), "\n");
 +              }
 +      } else {
 +              print(ColorTranslateRGB(strMessage));
 +      }
  }
  
- // CSQC_Parse_CenterPrint : Provides the centerprint_hud string in the first parameter that the server provided.
+ // CSQC_Parse_CenterPrint : Provides the centerprint_AddStandard string in the first parameter that the server provided.
  void CSQC_Parse_CenterPrint(string strMessage)
  {
        if (autocvar_developer_csqcentities) LOG_INFOF("CSQC_Parse_CenterPrint(\"%s\")", strMessage);
Simple merge
Simple merge
index cdd1b23a8e0648b826d3f4eaf287a70ef670d181,d5b47ce43ee70bfdda9f758b3d2d36969bdd9ccb..02c82603a1e655ad275301a942ce69abf0c4b2d7
@@@ -31,28 -30,74 +31,86 @@@ string MakeConsoleSafe(string input
        return input;
  }
  
 +ERASEABLE
 +string console_encode(string input)
 +{
 +      return json_stringify_string(strreplace("$", "$$", input));
 +}
 +
 +ERASEABLE
 +string console_decode(string input)
 +{
 +      return json_parse_string(input);
 +}
 +
+ /**
+  * Evaluate an expression of the form: [+ | -]? [var[op]val | [op]var | val | var] ...
+  * +: all must match. this is the default
+  * -: one must NOT match
+  *
+  * var>x
+  * var<x
+  * var>=x
+  * var<=x
+  * var==x
+  * var!=x
+  * var===x
+  * var!==x
+  */
  ERASEABLE
void cvar_describe(string name, string desc)
bool expr_evaluate(string s)
  {
-       localcmd(sprintf("\nset %1$s \"$%1$s\" \"%2$s\"\n", name, MakeConsoleSafe(desc)));
- }
+     bool ret = false;
+     if (str2chr(s, 0) == '+') {
+         s = substring(s, 1, -1);
+     } else if (str2chr(s, 0) == '-') {
+         ret = true;
+         s = substring(s, 1, -1);
+     }
+     bool expr_fail = false;
+     for (int i = 0, n = tokenize_console(s); i < n; ++i) {
+         int o;
+         string k, v;
+         s = argv(i);
+         #define X(expr) \
+             if (expr) \
+                 continue; \
+             expr_fail = true; \
+             break;
  
- ERASEABLE
- void cvar_archive(string name)
- {
-       localcmd(sprintf("\nseta %1$s \"$%1$s\"\n", name));
+         #define BINOP(op, len, expr) \
+             if ((o = strstrofs(s, op, 0)) >= 0) { \
+                 k = substring(s, 0, o); \
+                 v = substring(s, o + len, -1); \
+                 X(expr); \
+             }
+         BINOP(">=", 2, cvar(k) >= stof(v));
+         BINOP("<=", 2, cvar(k) <= stof(v));
+         BINOP(">",  1, cvar(k) >  stof(v));
+         BINOP("<",  1, cvar(k) <  stof(v));
+         BINOP("==", 2, cvar(k) == stof(v));
+         BINOP("!=", 2, cvar(k) != stof(v));
+         BINOP("===", 3, cvar_string(k) == v);
+         BINOP("!==", 3, cvar_string(k) != v);
+         {
+             k = s;
+             bool b = true;
+             if (str2chr(k, 0) == '!') {
+                 k = substring(s, 1, -1);
+                 b = false;
+             }
+             float f = stof(k);
+             bool isnum = ftos(f) == k;
+             X(boolean(isnum ? f : cvar(k)) == b);
+         }
+         #undef BINOP
+         #undef X
+     }
+     if (!expr_fail) {
+         ret = !ret;
+     }
+     // now ret is true if we want to keep the item, and false if we want to get rid of it
+     return ret;
  }
  
  ERASEABLE
index 6e622b4161f142f735e7ce3f5899b15fcbbd653a,23bfdf05593ff178f4f31c35d4319d4985d6626a..fa1bb5d0190b19f88f8807704af430b66a3a63d7
@@@ -104,15 -86,21 +86,22 @@@ float boxinsidebox(vector smins, vecto
  // vector vec2(vector v); // returns a vector with just the x and y components of the given vector
  // vector vec2(float x, float y); // returns a vector with the given x and y components
  
 -noref vector _vec2;
 -#define vec2(...) EVAL(OVERLOAD(vec2, __VA_ARGS__))
 -#define vec2_1(v) (_vec2 = (v), _vec2.z = 0, _vec2)
 -#define vec2_2(x, y) (_vec2_x = (x), _vec2_y = (y), _vec2)
 +noref vector _vec_zero;
 +noref vector _vec2_tmp;
 +noref vector _vec3_tmp;
  
 -noref vector _vec3;
 -#define vec3(_x, _y, _z) (_vec3.x = (_x), _vec3.y = (_y), _vec3.z = (_z), _vec3)
 +#define vec2(...) EVAL(OVERLOAD(vec2, __VA_ARGS__))
 +#define vec2_1(v) (_vec2_tmp = (v), _vec2_tmp.z = 0, _vec2_tmp + _vec_zero)
 +#define vec2_2(_x, _y) (_vec2_tmp.x = (_x), _vec2_tmp.y = (_y), _vec2_tmp + _vec_zero)
 +#define vec3(_x, _y, _z) (_vec3_tmp.x = (_x), _vec3_tmp.y = (_y), _vec3_tmp.z = (_z), _vec3_tmp + _vec_zero)
  
+ #define VEC_NAN vec3(FLOAT_NAN, FLOAT_NAN, FLOAT_NAN);
+ ERASEABLE
+ bool is_all_nans(vector v) {
+       return isnan(v.x) && isnan(v.y) && isnan(v.z);
+ }
  ERASEABLE
  vector Rotate(vector v, float a)
  {
Simple merge
index 03e779e15920dce4e9436761b3220b141edbc40b,0000000000000000000000000000000000000000..8e16915d358bc17aec71ee731ee2e816f784ea00
mode 100644,000000..100644
--- /dev/null
@@@ -1,119 -1,0 +1,119 @@@
- GENERIC_COMMAND(notice, "Append a notice to chat")
 +#include "commands.qh"
 +
 +#include <common/sounds/all.qh>
 +
 +#include "state.qh"
 +
 +noref string autocvar__cl_hook_print = "menu_cmd notice";
 +
- GENERIC_COMMAND(commandmode, "input a console command")
++GENERIC_COMMAND(notice, "Append a notice to chat", true)
 +{
 +    switch(request)
 +    {
 +        case CMD_REQUEST_COMMAND:
 +        {
 +            string s = console_decode(substring(command, argv_start_index(1), argv_end_index(1) - argv_start_index(1)));
 +            int flags = stoi(argv(2));
 +            s = ColorTranslateRGB(s);
 +            string prefix = (flags & NOTICE_CHAT) ? "\{3}" : "";
 +            if (!(flags & NOTICE_SILENT)) {
 +                entity snd = (flags & NOTICE_PRIVATE) ? SND_TALK2 : SND_TALK;
 +                localsound(Sound_fixpath(snd));
 +            }
 +            chat_lines_push(s);
 +            print(prefix, "^7", s, "\n");
 +            return;
 +        }
 +
 +        default:
 +        case CMD_REQUEST_USAGE:
 +        {
 +            LOG_INFO("Usage:^3 ", GetProgramCommandPrefix(), " notice message");
 +            LOG_INFO("  Where 'message' is the message to append.");
 +            return;
 +        }
 +    }
 +}
 +
 +void m_goto(string name);
 +
- GENERIC_COMMAND(messagemode, "input a chat message to say to everyone")
++GENERIC_COMMAND(commandmode, "input a console command", true)
 +{
 +    switch(request)
 +    {
 +        case CMD_REQUEST_COMMAND:
 +        {
 +            if (!autoextension_chat) {
 +                return;
 +            }
 +            string prefix = arguments > 1 ? strcat(substring(command, argv_start_index(1), argv_end_index(-1)), " ") : "";
 +            chat_command = "";
 +            strcpy(chat_text, prefix);
 +            if (!isdemo()) {
 +                m_goto("Chat");
 +            }
 +            return;
 +        }
 +
 +        default:
 +        case CMD_REQUEST_USAGE:
 +        {
 +            LOG_INFO("Usage:^3 ", GetProgramCommandPrefix(), " commandmode [prefix]");
 +            return;
 +        }
 +    }
 +}
 +
- GENERIC_COMMAND(messagemode2, "input a chat message to say to only your team")
++GENERIC_COMMAND(messagemode, "input a chat message to say to everyone", true)
 +{
 +    switch(request)
 +    {
 +        case CMD_REQUEST_COMMAND:
 +        {
 +            if (!autoextension_chat) {
 +                return;
 +            }
 +            string prefix = arguments > 1 ? strcat(substring(command, argv_start_index(1), argv_end_index(-1)), " ") : "";
 +            chat_command = "say ";
 +            strcpy(chat_text, prefix);
 +            if (!isdemo()) {
 +                m_goto("Chat");
 +            }
 +            return;
 +        }
 +
 +        default:
 +        case CMD_REQUEST_USAGE:
 +        {
 +            LOG_INFO("Usage:^3 ", GetProgramCommandPrefix(), " messagemode [prefix]");
 +            return;
 +        }
 +    }
 +}
 +
++GENERIC_COMMAND(messagemode2, "input a chat message to say to only your team", true)
 +{
 +    switch(request)
 +    {
 +        case CMD_REQUEST_COMMAND:
 +        {
 +            if (!autoextension_chat) {
 +                return;
 +            }
 +            string prefix = arguments > 1 ? strcat(substring(command, argv_start_index(1), argv_end_index(-1)), " ") : "";
 +            chat_command = "say_team ";
 +            strcpy(chat_text, prefix);
 +            if (!isdemo()) {
 +                m_goto("Chat");
 +            }
 +            return;
 +        }
 +
 +        default:
 +        case CMD_REQUEST_USAGE:
 +        {
 +            LOG_INFO("Usage:^3 ", GetProgramCommandPrefix(), " messagemode2 [prefix]");
 +            return;
 +        }
 +    }
 +}
index 77eab7fa47b6eacdba15a4a7c07237b776f19639,0000000000000000000000000000000000000000..e28913510897734a83b451074e9e82e5b5219e70
mode 100644,000000..100644
--- /dev/null
@@@ -1,54 -1,0 +1,54 @@@
- GENERIC_COMMAND(_cl_hook_gamestart, "_cl_hook_gamestart")
 +#include "commands.qh"
 +
 +#include "state.qh"
 +
 +// space separated list of extensions
 +void extensions_set(string s)
 +{
 +    for (int i = 0, n = extensions_count(); i < n; ++i) {
 +        string id = extensions_get_name(i);
 +        void(bool) set = extensions_get_setter(i);
 +        bool enable = strhasword(s, id);
 +        set(enable);
 +    }
 +}
 +
- GENERIC_COMMAND(_cl_extensions, "_cl_extensions")
++GENERIC_COMMAND(_cl_hook_gamestart, "_cl_hook_gamestart", true)
 +{
 +    switch(request)
 +    {
 +        case CMD_REQUEST_COMMAND:
 +        {
 +            string s = substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1));
 +            localcmd("\nset _cl_hook_gametype ", s, "; _cl_hook_gamestart_stage2\n");
 +            extensions_set(cvar_string("_cl_extensions"));
 +            cvar_set("_cl_extensions", "");
 +            return;
 +        }
 +
 +        default:
 +        case CMD_REQUEST_USAGE:
 +        {
 +            return;
 +        }
 +    }
 +}
 +
++GENERIC_COMMAND(_cl_extensions, "_cl_extensions", true)
 +{
 +    switch(request)
 +    {
 +        case CMD_REQUEST_COMMAND:
 +        {
 +            string s = substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1));
 +            extensions_set(s);
 +            return;
 +        }
 +
 +        default:
 +        case CMD_REQUEST_USAGE:
 +        {
 +            return;
 +        }
 +    }
 +}
index 5c22e249c074f185c735ce6aae3ee079ddbd92b2,e215e5080bd883bd26086268cf32aa9473b97a19..ac0eb8fd1aa5f78b21fc172eed0f4907084977fb
@@@ -1,7 -1,6 +1,7 @@@
  #include "mainwindow.qh"
  
- #include "../mutators/events.qh"
 +#include <menu/modules/chat/dialog.qh>
+ #include <menu/mutators/_mod.qh>
  
  #include "nexposee.qh"
  #include "inputbox.qh"
index 0000000000000000000000000000000000000000,a73de2a1f6c58f2ed97e4303ae5179a7354b07db..06ae8f7ac057f7b35b7cd4ee619a501c0d7066c0
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,581 +1,581 @@@
 -                      msgstr = strcat("\{1}\{13}* ", colorprefix, namestr, "^3 tells you: ^7");
+ #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/mutators/_mod.qh>
+ #include <server/weapons/tracing.qh>
+ #include <server/world.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) || INGAME(source)))
+               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.netname, source.team, (autocvar_g_chat_teamcolors && IS_PLAYER(source)));
+       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)
+               {
 -                      privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", playername(privatesay.netname, privatesay.team, (autocvar_g_chat_teamcolors && IS_PLAYER(privatesay))), ": ^7");
++                      msgstr = strcat("\{1}\r* ", colorprefix, namestr, "^3 tells you: ^7");
+                       privatemsgprefixlen = strlen(msgstr);
+                       msgstr = strcat(msgstr, msgin);
+                       cmsgstr = strcat(colorstr, colorprefix, namestr, "^3 tells you:\n^7", msgin);
 -                              msgstr = strcat("\{1}\{13}^4* ", "^7", msgin);
++                      privatemsgprefix = strcat("\{1}\r* ^3You tell ", playername(privatesay.netname, privatesay.team, (autocvar_g_chat_teamcolors && IS_PLAYER(privatesay))), ": ^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}", colorstr, "(", colorprefix, namestr, colorstr, ") ^7", msgin);
++                              msgstr = strcat("\{1}\r^4* ", "^7", msgin);
+                       }
+                       else
++                              msgstr = strcat("\{1}\r", 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) || INGAME(source)) && !game_stopped
+               && (teamsay || CHAT_NOSPECTATORS()))
+       {
+               teamsay = -1; // spectators
+       }
+       if(flood)
+               LOG_INFO("NOTE: ", playername(source.netname, source.team, IS_PLAYER(source)), "^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) || INGAME(source)) && !game_stopped
+               && (IS_PLAYER(privatesay) || INGAME(privatesay)) && 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) || INGAME(it)) && 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) || INGAME(it)) && 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.netname, source.team, IS_PLAYER(source)), "^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);
+       }
+ }
index 1e9a119cf6356c80b7dbf9adca982f5d068b3b2a,b35f4fb53ae4e6ad52687a964a8782a58889d701..92430a9d6ee85a71b7dc7b5fe133f422d082b654
@@@ -137,11 -126,11 +126,11 @@@ void GameCommand_adminmsg(int request, 
                                        else
                                        {
                                                centerprint(client, strcat("^3", GetCallerName(NULL), ":\n^7", admin_message));
 -                                              sprint(client, strcat("\{1}\{13}^3", GetCallerName(NULL), "^7: ", admin_message, "\n"));
 +                                              sprint(client, strcat("\{1}\r^3", GetCallerName(NULL), "^7: ", admin_message, "\n"));
                                        }
  
-                                       successful = strcat(successful, (successful ? ", " : ""), playername(client, false));
-                                       LOG_TRACE("Message sent to ", playername(client, false));
+                                       successful = strcat(successful, (successful ? ", " : ""), playername(client.netname, client.team, false));
+                                       LOG_TRACE("Message sent to ", playername(client.netname, client.team, false));
                                        continue;
                                }