]> git.rm.cloudns.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into z411/bai-server
authorz411 <z411@omaera.org>
Tue, 20 Oct 2020 18:17:45 +0000 (15:17 -0300)
committerz411 <z411@omaera.org>
Tue, 20 Oct 2020 18:17:45 +0000 (15:17 -0300)
# Conflicts:
# qcsrc/client/announcer.qc
# qcsrc/client/draw.qh
# qcsrc/client/hud/panel/chat.qc
# qcsrc/client/main.qc
# qcsrc/client/view.qc
# qcsrc/common/items/inventory.qh
# qcsrc/server/client.qc
# qcsrc/server/client.qh
# qcsrc/server/command/cmd.qc
# qcsrc/server/miscfunctions.qc
# qcsrc/server/world.qc

48 files changed:
1  2 
qcsrc/client/announcer.qc
qcsrc/client/autocvars.qh
qcsrc/client/hud/hud.qh
qcsrc/client/hud/panel/centerprint.qc
qcsrc/client/hud/panel/chat.qc
qcsrc/client/hud/panel/infomessages.qc
qcsrc/client/hud/panel/score.qc
qcsrc/client/hud/panel/scoreboard.qc
qcsrc/client/hud/panel/timer.qc
qcsrc/client/hud/panel/weapons.qc
qcsrc/client/main.qc
qcsrc/client/main.qh
qcsrc/client/view.qc
qcsrc/common/ent_cs.qc
qcsrc/common/gamemodes/gamemode/clanarena/sv_clanarena.qh
qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qc
qcsrc/common/items/inventory.qh
qcsrc/common/monsters/sv_monsters.qc
qcsrc/common/mutators/mutator/attackertext/cl_attackertext.qc
qcsrc/common/mutators/mutator/buffs/sv_buffs.qc
qcsrc/common/mutators/mutator/instagib/sv_instagib.qc
qcsrc/common/mutators/mutator/overkill/oknex.qc
qcsrc/common/net_linked.qh
qcsrc/common/notifications/all.inc
qcsrc/common/notifications/all.qc
qcsrc/ecs/systems/sv_physics.qc
qcsrc/server/autocvars.qh
qcsrc/server/chat.qc
qcsrc/server/chat.qh
qcsrc/server/client.qc
qcsrc/server/client.qh
qcsrc/server/command/cmd.qc
qcsrc/server/command/common.qc
qcsrc/server/command/sv_cmd.qc
qcsrc/server/command/vote.qc
qcsrc/server/damage.qc
qcsrc/server/damage.qh
qcsrc/server/items/items.qc
qcsrc/server/round_handler.qc
qcsrc/server/scores.qc
qcsrc/server/scores.qh
qcsrc/server/teamplay.qc
qcsrc/server/weapons/accuracy.qc
qcsrc/server/weapons/common.qc
qcsrc/server/weapons/tracing.qc
qcsrc/server/weapons/weaponsystem.qc
qcsrc/server/world.qc
qcsrc/server/world.qh

index 01afb0902c7d36561150383b8435459265d4a795,a5b3eecb113348059da3013953e4697ff5b18a42..d6f2f506518ee4df12e394cae61c63e2435e0386
@@@ -1,15 -1,9 +1,14 @@@
  #include "announcer.qh"
  
+ #include <client/hud/panel/centerprint.qh>
  #include <client/mutators/_mod.qh>
 +
  #include <common/notifications/all.qh>
  #include <common/stats.qh>
- #include "hud/panel/centerprint.qh"
 +#include <common/mapinfo.qh>
- #include "miscfunctions.qh"
 +
 +#include <common/ent_cs.qh>
 +#include <common/gamemodes/gamemode/duel/duel.qh>
  
  bool announcer_1min;
  bool announcer_5min;
Simple merge
index 1021d7067e06f3db382643a172be9a6463bd3999,b8740560f98e70178402abe55e0df265e58b53c2..47e39f6ba3fb0b0bc2324029f1543ee7fb34eba6
@@@ -110,6 -122,6 +122,7 @@@ int ts_primary, ts_secondary
  float weapontime;
  float weaponprevtime;
  
++float timer;
  float teamnagger;
  
  int hudShiftState;
Simple merge
index 6807c2183b5f47aa6d5f78ad24262f24d8216d3d,13ef78136d61ca8e0d7895faae112fbf4f11a815..cb7729d49089756dab630d339d8de4ecdeade2bc
@@@ -1,8 -1,7 +1,8 @@@
  #include "chat.qh"
  
  #include <client/autocvars.qh>
- #include <client/miscfunctions.qh>
+ #include <client/draw.qh>
 +#include <common/items/inventory.qh>
  
  // Chat (#12)
  
Simple merge
Simple merge
index cfac04ecb7a58e30bdeb06c1245a39e8848a8739,446c6229bdc8f587da981d45d0f228a2c4b0a129..68c28ac5a72e4a0c7bc2a2ac02df53f8a98e7003
@@@ -1,7 -1,7 +1,7 @@@
  #include "timer.qh"
  
  #include <client/autocvars.qh>
- #include <client/miscfunctions.qh>
 -#include <client/draw.qh>
++#include <client/hud/hud.qh>
  #include <client/view.qh>
  
  // Timer (#5)
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
index b3778f468dd4ebc5058e7b62eca33cef08c394b6,c47be669978bb90b00afa5724297fc7d21c48544..c0a94707bc3b5f9dccb4191924f1b89ead07ac9f
@@@ -38,14 -37,7 +37,13 @@@ STATIC_INIT(Inventory
  #endif
  
  #ifdef CSQC
- #include <client/miscfunctions.qh>
 -Inventory g_inventory;
 +#include <client/hud/hud.qh>
 +
 +//Inventory g_inventory;
 +Inventory inventoryslots[255];
 +float last_pickup_timer;
 +entity last_pickup_item;
 +int last_pickup_times;
  NET_HANDLE(ENT_CLIENT_INVENTORY, bool isnew)
  {
      make_pure(this);
@@@ -149,11 -117,7 +147,11 @@@ bool Inventory_Send(Inventory this, Cli
      TC(Inventory, this);
      WriteHeader(MSG_ENTITY, ENT_CLIENT_INVENTORY);
      TC(PlayerState, this.owner);
-       // z411 send entity number
-       WriteByte(MSG_ENTITY, etof(this.owner.m_client));
 +      
-       Inventory_Write(this);
++    // z411 send entity number
++    WriteByte(MSG_ENTITY, etof(this.owner.m_client));
 +    
+     Inventory_Write(this, to.inventory_store);
      return true;
  }
  
@@@ -165,25 -129,13 +163,23 @@@ bool Inventory_customize(entity this, e
  
  void Inventory_new(PlayerState this)
  {
-     Inventory inv = NEW(Inventory), bak = NEW(Inventory);
-     inv.inventory = bak;
-       if(!g_duel)
-               setcefc(inv, Inventory_customize);
+     Inventory inv = NEW(Inventory);
 -    setcefc(inv, Inventory_customize);
++    if(!g_duel) setcefc(inv, Inventory_customize);
      Net_LinkEntity((inv.owner = this).inventory = inv, false, 0, Inventory_Send);
  }
  void Inventory_delete(entity e) { delete(e.inventory.inventory); delete(e.inventory); }
  void Inventory_update(entity e) { e.inventory.SendFlags = 0xFFFFFF; }
  
-       LOG_INFO("Clearing inventory");
-       
+ void InventoryStorage_attach(entity e) { e.inventory_store = NEW(Inventory); e.inventory_store.drawonlytoclient = e; }
+ void InventoryStorage_detach(entity e) { delete(e.inventory_store); }
++
 +void Inventory_ClearAll() {
 +      FOREACH_CLIENT(IS_PLAYER(it), {
 +              entity store = PS(it);          
 +              FOREACH(Items, true, {
 +                      store.inventory.inv_items[it.m_id] = 0;
 +              });
 +        Inventory_update(store);
 +      });
 +}
  #endif
Simple merge
index 2e5dee78fefb5bc95d956bc70430f6b3734aff68,0000000000000000000000000000000000000000..0916d4d5bee54e14c70a4615e908aa2725dac941
mode 100644,000000..100644
--- /dev/null
@@@ -1,61 -1,0 +1,61 @@@
-               s = playername(s, entcs_GetTeam(server_entity_index - 1));
 +#include "cl_attackertext.qh"
 +
 +AUTOCVAR_SAVE(cl_attackertext,                        bool,   true,       "Draw damage dealt where you hit the enemy");
 +AUTOCVAR_SAVE(cl_attackertext_friendlyfire,           bool,   false,      "Show for friendlyfire");
 +AUTOCVAR_SAVE(cl_attackertext_time,                   float,  3,          "Time to show");
 +AUTOCVAR_SAVE(cl_attackertext_fadetime,               float,  2,          "Time to fade");
 +AUTOCVAR_SAVE(cl_attackertext_decolorize,             int,    1,          "1 = decolorize names when teamplay, 2 = decolorize always");
 +
 +REGISTER_MUTATOR(attackertext, true);
 +
 +MUTATOR_HOOKFUNCTION(attackertext, DrawInfoMessages)
 +{
 +      if (autocvar_cl_attackertext == 0) return false;
 +      
 +      float fade_start = max(0, autocvar_cl_attackertext_time);
 +      float fade_time = max(0, autocvar_cl_attackertext_fadetime);
 +              
 +      if (last_attack_time && last_attack_time > time - fade_start - fade_time) {
 +              vector pos = M_ARGV(0, vector);
 +              vector mySize = M_ARGV(1, vector);
 +              vector fontsize = '0.2 0.2 0' * mySize.y;
 +              int img_curr_group = M_ARGV(2, int);
 +              
 +              float alpha_ = 0;
 +              
 +              if (last_attack_time + fade_start > time)
 +                      alpha_ = panel_fg_alpha;
 +              else if (fade_time != 0)
 +                      alpha_ = panel_fg_alpha - bound(0, (time - last_attack_time - fade_start) * (1 / fade_time), 1);
 +              else
 +                      return true;
 +                      
 +              pos = InfoMessages_drawstring(last_attack_name, pos, mySize, alpha_, fontsize);
 +              img_curr_group = -1;
 +
 +              return true;
 +      }
 +              
 +      return false;
 +}
 +
 +
 +NET_HANDLE(attackertext, bool isNew)
 +{
 +    int server_entity_index = ReadByte();
 +      int flags = ReadByte();
 +    bool friendlyfire = flags & ATFLAG_SAMETEAM;
 +      
 +      return = true;
 +      
 +    if (autocvar_cl_attackertext == 0) return;
 +    if (friendlyfire && autocvar_cl_attackertext_friendlyfire == 0) return;
 +      
 +      string s = entcs_GetName(server_entity_index - 1);
 +      if ((autocvar_cl_attackertext_decolorize == 1 && teamplay) || autocvar_cl_attackertext_decolorize == 2)
++              s = playername(s, entcs_GetTeam(server_entity_index - 1), true);
 +
 +      last_attack_time = time;
 +      strfree(last_attack_name);
 +      strcpy(last_attack_name, s);
 +}
index c431c601d815095cb2ce8affcb87df5f70f65f6c,6651c6cb9b75fae79d13d8a38681883c557873a1..4e888e7c31c66bc61dbf15dd0b96e3d50edfbb18
@@@ -7,9 -7,6 +7,10 @@@ REGISTER_NET_TEMP(TE_CSQC_PINGPLREPORT
  REGISTER_NET_TEMP(TE_CSQC_WEAPONCOMPLAIN)
  REGISTER_NET_TEMP(TE_CSQC_VEHICLESETUP)
  
 +REGISTER_NET_TEMP(TE_CSQC_TEAMNAMES)
 +REGISTER_NET_TEMP(TE_CSQC_CHATSOUND)
++REGISTER_NET_TEMP(TE_CSQC_WEAPONPICKUP)
 +
  const int RACE_NET_CHECKPOINT_HIT_QUALIFYING = 0; // byte checkpoint, short time, short recordtime, string recordholder
  const int RACE_NET_CHECKPOINT_CLEAR = 1;
  const int RACE_NET_CHECKPOINT_NEXT_QUALIFYING = 2; // byte nextcheckpoint, short recordtime, string recordholder
Simple merge
Simple merge
Simple merge
Simple merge
index 0000000000000000000000000000000000000000,204ed13410defe587a59a540b5bc207a8787e295..beac6e263183c959b15862b53a9cfe83d61e415c
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,581 +1,618 @@@
 -      if (source)
+ #include "chat.qh"
+ #include <common/gamemodes/_mod.qh>
+ #include <common/mapobjects/target/location.qh>
+ #include <common/mapobjects/triggers.qh>
++#include <common/net_linked.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) || 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 = "";
 -                      event_log_msg = sprintf(":chat_spec:%d:%s", source.playerid, strreplace("\n", " ", msgin));
++      if (source) {
+               namestr = playername(source.netname, source.team, (autocvar_g_chat_teamcolors && IS_PLAYER(source)));
++              
++              if (IS_DEAD(source) || source.frags == FRAGS_PLAYER_OUT_OF_GAME)
++                      namestr = strcat("(DEAD) ", namestr);
++              else if (IS_OBSERVER(source) || IS_SPEC(source))
++                      namestr = strcat("(s) ", namestr);
++      }
+       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.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}^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.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) || 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:%d:%s", source.playerid, strreplace("\n", " ", msgin));
++                      
++                      if(!play_chatsound(source, msgin))
++                              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);
+                       });
++                      
++                      if(!play_chatsound(source, msgin))
++                              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;
+ }
++bool play_chatsound(entity source, string msgin)
++{
++      if(autocvar_sv_chat_sounds && CS(source).cvar_cl_chat_sounds) {
++              var .float flood_sound = floodcontrol_chatsound;
++              
++              if (source.(flood_sound) < time - autocvar_sv_chat_sounds_flood) {
++                      string rawmsg;
++                      bool found = false;
++                      rawmsg = strreplace("\n", " ", msgin);
++                      
++                      FOREACH_WORD(autocvar_sv_chat_sounds_list, it == rawmsg, { found = true; });
++                      if (found) {
++                              FOREACH_CLIENT(IS_REAL_CLIENT(it) && CS(it).cvar_cl_chat_sounds, {
++                                      msg_entity = it;
++                                      WriteHeader(MSG_ONE, TE_CSQC_CHATSOUND);
++                                      WriteString(MSG_ONE, rawmsg);
++                              });
++                              source.(flood_sound) = time;
++                              return true;
++                      }
++              }
++      }
++      
++      return false;
++}
++
+ 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 0000000000000000000000000000000000000000,9c30e89a4dcc3d0a24e7bce1f11275c71dbfa252..1c45d3db7f5fb13b58df26a45776bfa7a44d3786
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,50 +1,52 @@@
+ #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;
++.float floodcontrol_chatsound;
+ #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);
++bool play_chatsound(entity source, string msgin);
+ 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);
Simple merge
index a36758b676f1e1358effef6226109c6804fd0e97,abda1204aba38bed656a63abf7fd76bfce5a62de..2a447b6356a3f0aa19c5792dca6394c72428d8f4
@@@ -399,11 -350,4 +354,9 @@@ 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;
 +
 +float _medal_times;
 +#define Give_Medal(entity,medalname) \
 +      _medal_times = GameRules_scoring_add(entity, MEDAL_##medalname, 1); \
 +      Send_Notification(NOTIF_ONE, entity, MSG_MEDAL, MEDAL_##medalname, _medal_times);
index 2772e4f2c33841bdb80b112a908e7aba1259993d,106da7720b345d3f58aa3ae07ae1689095319b88..0daef91ef5b6eef6cc7a11f050759b8aa7126e17
@@@ -382,11 -371,11 +371,11 @@@ void ClientCommand_ready(entity caller
        {
                case CMD_REQUEST_COMMAND:
                {
 -                      if (IS_CLIENT(caller))
 +                      if (IS_CLIENT(caller) && caller.last_ready < time - 3) // anti-spam
                        {
-                               if (warmup_stage || sv_ready_restart || g_race_qualifying == 2)
+                               if (warmup_stage || autocvar_sv_ready_restart || g_race_qualifying == 2)
                                {
-                                       if (!readyrestart_happened || sv_ready_restart_repeatable)
+                                       if (!readyrestart_happened || autocvar_sv_ready_restart_repeatable)
                                        {
                                                if (time < game_starttime) // game is already restarting
                                                        return;
                                                {
                                                        caller.ready = false;
                                                        if(IS_PLAYER(caller) || caller.caplayer == 1)
-                                                               bprint("\{1}", playername(caller, false), "^2 is ^1NOT^2 ready\n");
 -                                                              bprint(playername(caller.netname, caller.team, false), "^2 is ^1NOT^2 ready\n");
++                                                              bprint("\{1}", playername(caller.netname, caller.team, false), "^2 is ^1NOT^2 ready\n");
                                                }
                                                else
                                                {
                                                        caller.ready = true;
                                                        if(IS_PLAYER(caller) || caller.caplayer == 1)
-                                                               bprint("\{1}", playername(caller, false), "^2 is ready\n");
 -                                                              bprint(playername(caller.netname, caller.team, false), "^2 is ready\n");
++                                                              bprint("\{1}", playername(caller.netname, caller.team, false), "^2 is ready\n");
                                                }
 +                                              
 +                                              caller.last_ready = time;
  
                                                // cannot reset the game while a timeout is active!
                                                if (!timeout_status) ReadyCount();
index 981e79206248612ee01caf836ba06303b9be853d,e4299039e3da1e8ee2774a559721ed6be70c6580..44ab9834290ec28d992ea24877b5723ac94c17bb
@@@ -774,10 -752,10 +774,10 @@@ void CommonCommand_timeout(int request
                                        timeout_time = autocvar_sv_timeout_length;
                                        timeout_leadtime = autocvar_sv_timeout_leadtime;
  
-                                       timeout_handler = spawn();
+                                       timeout_handler = new(timeout_handler);
                                        setthink(timeout_handler, timeout_handler_think);
                                        timeout_handler.nextthink = time;  // always let the entity think asap
 -
 +                                      
                                        Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_TIMEOUT);
                                }
                        }
Simple merge
Simple merge
index 6aa9155077936be44d53aa64e619c5f2096023ce,fd2e9f2a600de9fb44f971eebe963da4f62ff1b9..9ed80e0e1c2206863f0f36575aa13de076556cdf
@@@ -330,7 -315,7 +331,9 @@@ void Obituary(entity attacker, entity i
  
                        Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname);
                        Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker_name);
-                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL), playername(targ, true), playername(attacker, true), deathlocation, CS(targ).killcount);
 -                      Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL), targ.netname, attacker_name, deathlocation, CS(targ).killcount);
++                      Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL),
++                              playername(targ.netname, targ.team, true), playername(attacker.netname, attacker.team, true),
++                              deathlocation, CS(targ).killcount);
  
                        // In this case, the death message will ALWAYS be "foo was betrayed by bar"
                        // No need for specific death/weapon messages...
                        if(deathtype == DEATH_BUFF.m_id)
                                f3 = buff_FirstFromFlags(STAT(BUFFS, attacker)).m_id;
  
-                       if (!Obituary_WeaponDeath(targ, attacker, true, deathtype, playername(targ, true), playername(attacker, true), deathlocation, CS(targ).killcount, kill_count_to_attacker))
-                               Obituary_SpecialDeath(targ, true, deathtype, playername(targ, true), playername(attacker, true), deathlocation, CS(targ).killcount, kill_count_to_attacker, f3);
 -                      if (!Obituary_WeaponDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker))
 -                              Obituary_SpecialDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker, f3);
++                      if (!Obituary_WeaponDeath(targ, attacker, true, deathtype, playername(targ.netname, targ.team, true), playername(attacker.netname, targ.team, true), deathlocation, CS(targ).killcount, kill_count_to_attacker))
++                              Obituary_SpecialDeath(targ, true, deathtype, playername(targ.netname, targ.team, true), playername(attacker.netname, attacker.team, true), deathlocation, CS(targ).killcount, kill_count_to_attacker, f3);
                }
        }
  
                        case DEATH_HURTTRIGGER:
                        {
                                Obituary_SpecialDeath(targ, false, deathtype,
-                                       playername(targ, true),
 -                                      targ.netname,
++                                      playername(targ.netname, targ.team, true),
                                        inflictor.message,
                                        deathlocation,
                                        CS(targ).killcount,
                        case DEATH_CUSTOM:
                        {
                                Obituary_SpecialDeath(targ, false, deathtype,
-                                       playername(targ, true),
 -                                      targ.netname,
++                                      playername(targ.netname, targ.team, true),
                                        ((strstrofs(deathmessage, "%", 0) < 0) ? strcat("%s ", deathmessage) : deathmessage),
                                        deathlocation,
                                        CS(targ).killcount,
  
                        default:
                        {
-                               Obituary_SpecialDeath(targ, false, deathtype, playername(targ, true), deathlocation, "", CS(targ).killcount, 0, 0);
 -                              Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
++                              Obituary_SpecialDeath(targ, false, deathtype, playername(targ.netname, targ.team, true), deathlocation, "", CS(targ).killcount, 0, 0);
                                break;
                        }
                }
Simple merge
Simple merge
Simple merge
index 99b55083b0fb760cfd8cacb44377f4a0f9f75189,3113e8a965f9b56ac52974ae3543f3e81e1a0190..8906547c496cbdf43b6fdb10f1778fc9f8fbd97b
@@@ -511,17 -511,9 +511,17 @@@ void WinningConditionHelper(entity this
                        }
                });
  
-               WinningConditionHelper_equality = (PlayerScore_Compare(winnerscorekeeper, secondscorekeeper, 0) == 0);
+               WinningConditionHelper_equality = (PlayerScore_Compare(winnerscorekeeper, secondscorekeeper, false) == 0);
                if(WinningConditionHelper_equality)
 +              {
 +                      WinningConditionHelper_equality_one = WinningConditionHelper_winner;
 +                      WinningConditionHelper_equality_two = WinningConditionHelper_second;
                        WinningConditionHelper_winner = WinningConditionHelper_second = NULL;
 +              }
 +              else
 +              {
 +                      WinningConditionHelper_equality_one = WinningConditionHelper_equality_two = NULL;
 +              }
  
                WinningConditionHelper_topscore = winnerscorekeeper.scores_primary;
                WinningConditionHelper_secondscore = secondscorekeeper.scores_primary;
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
index 4b930263ce23f7ff0763e6ef0f709dd5412cbb1d,0aed2e5078ca3f0f19510e723df3b07451c84d84..4ab109901fb707c6c841bbd2e008e357ff07dece
@@@ -1017,12 -1009,9 +1017,12 @@@ spawnfunc(worldspawn
        modname = strzone(modname);
  
        WinningConditionHelper(this); // set worldstatus
-       
        world_initialized = 1;
        __spawnfunc_spawn_all();
 +      
 +      if(!warmup_stage)
 +              round_handler_Activate(true);
  }
  
  spawnfunc(light)
@@@ -1760,11 -1397,7 +1444,6 @@@ void ClearWinners(
        FOREACH_CLIENT(IS_PLAYER(it), { it.winning = 0; });
  }
  
- void ShuffleMaplist()
- {
-       cvar_set("g_maplist", shufflewords(autocvar_g_maplist));
- }
 -int fragsleft_last;
  float WinningCondition_Scores(float limit, float leadlimit)
  {
        // TODO make everything use THIS winning condition (except LMS)
Simple merge