]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merged master into Lyberta/TeamplayFixes_.
authorLyberta <lyberta@lyberta.net>
Sun, 27 Aug 2017 23:44:07 +0000 (02:44 +0300)
committerLyberta <lyberta@lyberta.net>
Sun, 27 Aug 2017 23:44:07 +0000 (02:44 +0300)
1  2 
qcsrc/server/client.qc
qcsrc/server/client.qh
qcsrc/server/command/cmd.qc
qcsrc/server/command/sv_cmd.qc
qcsrc/server/player.qc
qcsrc/server/scores_rules.qc
qcsrc/server/teamplay.qc
qcsrc/server/teamplay.qh

diff --combined qcsrc/server/client.qc
index e255174c008c32fb715ef65de6cee1d98ac3fa33,f1d417d6bace6987d6f1a72984f6f77260b7db0f..1d3515e849e9bbdc74aba9f625c5e1473a656021
@@@ -1,5 -1,8 +1,8 @@@
  #include "client.qh"
  
+ #include <server/defs.qh>
+ #include <server/miscfunctions.qh>
+ #include <common/effects/all.qh>
  #include "anticheat.qh"
  #include "impulse.qh"
  #include "player.qh"
@@@ -110,20 -113,18 +113,18 @@@ bool ClientData_Send(entity this, entit
        if (IS_SPEC(e)) e = e.enemy;
  
        sf = 0;
-       if (CS(e).race_completed)       sf |= 1; // forced scoreboard
-       if (CS(to).spectatee_status)    sf |= 2; // spectator ent number follows
-       if (CS(e).zoomstate)            sf |= 4; // zoomed
-       if (autocvar_sv_showspectators) sf |= 16; // show spectators
+       if (CS(e).race_completed)       sf |= BIT(0); // forced scoreboard
+       if (CS(to).spectatee_status)    sf |= BIT(1); // spectator ent number follows
+       if (CS(e).zoomstate)            sf |= BIT(2); // zoomed
+       if (autocvar_sv_showspectators) sf |= BIT(4); // show spectators
  
        WriteHeader(MSG_ENTITY, ENT_CLIENT_CLIENTDATA);
        WriteByte(MSG_ENTITY, sf);
  
-       if (sf & 2)
-       {
+       if (sf & BIT(1))
                WriteByte(MSG_ENTITY, CS(to).spectatee_status);
-       }
  
-       if(sf & 16)
+       if(sf & BIT(4))
        {
                float specs = CountSpectators(e, to);
                WriteByte(MSG_ENTITY, specs);
@@@ -267,9 -268,7 +268,9 @@@ void PutObserverInServer(entity this
        if (mutator_returnvalue) {
            // mutator prevents resetting teams+score
        } else {
 +              int oldteam = this.team;
                this.team = -1;  // move this as it is needed to log the player spectating in eventlog
 +              MUTATOR_CALLHOOK(Player_ChangedTeam, this, oldteam, this.team);
          this.frags = FRAGS_SPECTATOR;
          PlayerScore_Clear(this);  // clear scores when needed
      }
@@@ -893,10 -892,8 +894,10 @@@ void ClientKill_Now(entity this
        if(CS(this).killindicator_teamchange)
                ClientKill_Now_TeamChange(this);
  
 -      if(!IS_SPEC(this) && !IS_OBSERVER(this))
 +      if (!IS_SPEC(this) && !IS_OBSERVER(this) && MUTATOR_CALLHOOK(ClientKill_Now, this) == false)
 +      {
                Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0');
 +      }
  
        // now I am sure the player IS dead
  }
@@@ -1190,7 -1187,10 +1191,10 @@@ void ClientConnect(entity this
        PlayerStats_GameReport_AddEvent(sprintf("kills-%d", this.playerid));
  
        // always track bots, don't ask for cl_allow_uidtracking
-     if (IS_BOT_CLIENT(this)) PlayerStats_GameReport_AddPlayer(this);
+       if (IS_BOT_CLIENT(this))
+               PlayerStats_GameReport_AddPlayer(this);
+       else
+               CS(this).allowed_timeouts = autocvar_sv_timeout_number;
  
        if (autocvar_sv_eventlog)
                GameLogEcho(strcat(":join:", ftos(this.playerid), ":", ftos(etof(this)), ":", ((IS_REAL_CLIENT(this)) ? this.netaddress : "bot"), ":", playername(this, false)));
        }
  
        CS(this).jointime = time;
-       CS(this).allowed_timeouts = autocvar_sv_timeout_number;
  
        if (IS_REAL_CLIENT(this))
        {
@@@ -1623,7 -1622,6 +1626,6 @@@ void player_regen(entity this
        regen_health_stable = M_ARGV(9, float);
        regen_health_rotstable = M_ARGV(10, float);
  
        if(!mutator_returnvalue)
        if(!STAT(FROZEN, this))
        {
  
                this.ammo_fuel = CalcRotRegen(this.ammo_fuel, minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, frametime * (time > this.pauseregen_finished) * ((this.items & ITEM_JetpackRegen.m_itemid) != 0), maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, frametime * (time > this.pauserotfuel_finished), limitf);
        }
+       // Ugly hack to make sure the health and armor don't go beyond hard limit.
+       // TODO: Remove this hack when all code uses GivePlayerHealth and
+       // GivePlayerArmor.
+       if (this.health > RESOURCE_AMOUNT_HARD_LIMIT)
+       {
+               this.health = RESOURCE_AMOUNT_HARD_LIMIT;
+       }
+       if (this.armorvalue > RESOURCE_AMOUNT_HARD_LIMIT)
+       {
+               this.armorvalue = RESOURCE_AMOUNT_HARD_LIMIT;
+       }
+       // End hack.
  }
  
  bool zoomstate_set;
@@@ -2312,7 -2322,7 +2326,7 @@@ void ObserverThink(entity this
                                TRANSMUTE(Spectator, this);
                        }
                } else {
-                       int preferred_movetype = ((!PHYS_INPUT_BUTTON_USE(this) ? this.cvar_cl_clippedspectating : !this.cvar_cl_clippedspectating) ? MOVETYPE_FLY_WORLDONLY : MOVETYPE_NOCLIP);
+                       int preferred_movetype = ((!PHYS_INPUT_BUTTON_USE(this) ? CS(this).cvar_cl_clippedspectating : !CS(this).cvar_cl_clippedspectating) ? MOVETYPE_FLY_WORLDONLY : MOVETYPE_NOCLIP);
                        set_movetype(this, preferred_movetype);
                }
        } else {
@@@ -2450,6 -2460,9 +2464,9 @@@ Called every frame for each client befo
  .float last_vehiclecheck;
  void PlayerPreThink (entity this)
  {
+       STAT(GUNALIGN, this) = CS(this).cvar_cl_gunalign; // TODO
+       STAT(MOVEVARS_CL_TRACK_CANJUMP, this) = CS(this).cvar_cl_movement_track_canjump;
        WarpZone_PlayerPhysics_FixVAngle(this);
  
        if (frametime) {
        }
  
        // version nagging
-       if (CS(this).version_nagtime && this.cvar_g_xonoticversion && time > CS(this).version_nagtime) {
+       if (CS(this).version_nagtime && CS(this).cvar_g_xonoticversion && time > CS(this).version_nagtime) {
          CS(this).version_nagtime = 0;
-         if (strstrofs(this.cvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(this.cvar_g_xonoticversion, "autobuild", 0) >= 0) {
+         if (strstrofs(CS(this).cvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(CS(this).cvar_g_xonoticversion, "autobuild", 0) >= 0) {
              // git client
          } else if (strstrofs(autocvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(autocvar_g_xonoticversion, "autobuild", 0) >= 0) {
              // git server
-             Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_BETA, autocvar_g_xonoticversion, this.cvar_g_xonoticversion);
+             Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_BETA, autocvar_g_xonoticversion, CS(this).cvar_g_xonoticversion);
          } else {
-             int r = vercmp(this.cvar_g_xonoticversion, autocvar_g_xonoticversion);
+             int r = vercmp(CS(this).cvar_g_xonoticversion, autocvar_g_xonoticversion);
              if (r < 0) { // old client
-                 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OUTDATED, autocvar_g_xonoticversion, this.cvar_g_xonoticversion);
+                 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OUTDATED, autocvar_g_xonoticversion, CS(this).cvar_g_xonoticversion);
              } else if (r > 0) { // old server
-                 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OLD, autocvar_g_xonoticversion, this.cvar_g_xonoticversion);
+                 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OLD, autocvar_g_xonoticversion, CS(this).cvar_g_xonoticversion);
              }
          }
      }
                this.last_vehiclecheck = time + 1;
        }
  
-       if(!this.cvar_cl_newusekeysupported) // FIXME remove this - it was a stupid idea to begin with, we can JUST use the button
+       if(!CS(this).cvar_cl_newusekeysupported) // FIXME remove this - it was a stupid idea to begin with, we can JUST use the button
        {
                if(PHYS_INPUT_BUTTON_USE(this) && !CS(this).usekeypressed)
                        PlayerUseKey(this);
diff --combined qcsrc/server/client.qh
index 2928aae1c927bd5685acd4f576b629719c14a953,9674872c0faa42bd6bc0c1a466204a0c166d9b08..2282c09cbb1cafd54e3a667886430b98b554c342
@@@ -1,5 -1,8 +1,8 @@@
  #pragma once
  
+ #include "utils.qh"
+ #include <common/sounds/all.qh>
  void ClientState_attach(entity this);
  
  IntrusiveList g_players;
@@@ -107,8 -110,40 +110,40 @@@ CLASS(Client, Object
      ATTRIB(Client, specialcommand_pos, int, this.specialcommand_pos);
      ATTRIB(Client, hitplotfh, int, this.hitplotfh);
      ATTRIB(Client, clientdata, entity, this.clientdata);
+     ATTRIB(Client, cmd_floodcount, int, this.cmd_floodcount);
+     ATTRIB(Client, cmd_floodtime, float, this.cmd_floodtime);
      ATTRIB(Client, wasplayer, bool, this.wasplayer);
  
+     // networked cvars
+     ATTRIB(Client, cvar_cl_allow_uid2name, int, this.cvar_cl_allow_uid2name);
+     ATTRIB(Client, cvar_cl_allow_uidtracking, int, this.cvar_cl_allow_uidtracking);
+     ATTRIB(Client, cvar_cl_autotaunt, float, this.cvar_cl_autotaunt);
+     ATTRIB(Client, cvar_cl_voice_directional, int, this.cvar_cl_voice_directional);
+     ATTRIB(Client, cvar_cl_voice_directional_taunt_attenuation, float, this.cvar_cl_voice_directional_taunt_attenuation);
+     ATTRIB(Client, cvar_cl_physics, string, this.cvar_cl_physics);
+     ATTRIB(Client, cvar_cl_buffs_autoreplace, bool, this.cvar_cl_buffs_autoreplace);
+     ATTRIB(Client, cvar_cl_nade_type, int, this.cvar_cl_nade_type);
+     ATTRIB(Client, cvar_cl_pokenade_type, string, this.cvar_cl_pokenade_type);
+     ATTRIB(Client, cvar_cl_spawn_near_teammate, bool, this.cvar_cl_spawn_near_teammate);
+     ATTRIB(Client, cvar_cl_gunalign, int, this.cvar_cl_gunalign);
+     ATTRIB(Client, cvar_cl_handicap, float, this.cvar_cl_handicap);
+     ATTRIB(Client, cvar_cl_clippedspectating, bool, this.cvar_cl_clippedspectating);
+     ATTRIB(Client, cvar_cl_autoscreenshot, int, this.cvar_cl_autoscreenshot);
+     ATTRIB(Client, cvar_cl_jetpack_jump, bool, this.cvar_cl_jetpack_jump);
+     ATTRIB(Client, cvar_cl_newusekeysupported, bool, this.cvar_cl_newusekeysupported);
+     ATTRIB(Client, cvar_cl_noantilag, bool, this.cvar_cl_noantilag);
+     ATTRIB(Client, cvar_cl_movement_track_canjump, bool, this.cvar_cl_movement_track_canjump);
+     ATTRIB(Client, cvar_cl_weaponimpulsemode, int, this.cvar_cl_weaponimpulsemode);
+     ATTRIB(Client, cvar_g_xonoticversion, string, this.cvar_g_xonoticversion);
+     ATTRIB(Client, autoswitch, bool, this.autoswitch);
+     ATTRIB(Client, cvar_cl_dodging_timeout, float, this.cvar_cl_dodging_timeout);
+     ATTRIB(Client, cvar_cl_multijump, bool, this.cvar_cl_multijump);
+     ATTRIB(Client, cvar_cl_accuracy_data_share, bool, this.cvar_cl_accuracy_data_share);
+     ATTRIB(Client, cvar_cl_accuracy_data_receive, bool, this.cvar_cl_accuracy_data_receive);
+     ATTRIBARRAY(Client, cvar_cl_weaponpriorities, string, 10);
+     ATTRIB(Client, cvar_cl_weaponpriority, string, this.cvar_cl_weaponpriority);
      METHOD(Client, m_unwind, bool(Client this));
  
      STATIC_METHOD(Client, Add, void(Client this, int _team));
@@@ -189,6 -224,8 +224,6 @@@ METHOD(Client, m_unwind, bool(Client th
      return false;
  }
  
 -float c1, c2, c3, c4;
 -
  void play_countdown(entity this, float finished, Sound samp);
  
  float CalcRotRegen(float current, float regenstable, float regenfactor, float regenlinear, float regenframetime, float rotstable, float rotfactor, float rotlinear, float rotframetime, float limit);
index 297ab8f0f37a95736c9c432e41c5deda30b99529,a2c037c5d2d198573a8c5eb63eb7e388560df8bf..09a308ad5c64d6a1001fb31754d2a8d0721f89e0
@@@ -1,4 -1,8 +1,8 @@@
  #include "cmd.qh"
+ #include <server/defs.qh>
+ #include <server/miscfunctions.qh>
  #include <common/command/_mod.qh>
  
  #include "common.qh"
@@@ -45,17 -49,19 +49,19 @@@ void ClientKill_TeamChange(entity this
  
  bool SV_ParseClientCommand_floodcheck(entity this)
  {
+       entity store = IS_CLIENT(this) ? CS(this) : this; // unfortunately, we need to store these on the client initially
        if (!timeout_status)  // not while paused
        {
-               if (time <= (this.cmd_floodtime + autocvar_sv_clientcommand_antispam_time))
+               if (time <= (store.cmd_floodtime + autocvar_sv_clientcommand_antispam_time))
                {
-                       this.cmd_floodcount += 1;
-                       if (this.cmd_floodcount > autocvar_sv_clientcommand_antispam_count)   return false;  // too much spam, halt
+                       store.cmd_floodcount += 1;
+                       if (store.cmd_floodcount > autocvar_sv_clientcommand_antispam_count)   return false;  // too much spam, halt
                }
                else
                {
-                       this.cmd_floodtime = time;
-                       this.cmd_floodcount = 1;
+                       store.cmd_floodtime = time;
+                       store.cmd_floodcount = 1;
                }
        }
        return true;  // continue, as we're not flooding yet
@@@ -74,8 -80,8 +80,8 @@@ void ClientCommand_autoswitch(entity ca
                {
                        if (argv(1) != "")
                        {
-                               caller.autoswitch = InterpretBoolean(argv(1));
-                               sprint(caller, strcat("^1autoswitch is currently turned ", (caller.autoswitch ? "on" : "off"), ".\n"));
+                               CS(caller).autoswitch = InterpretBoolean(argv(1));
+                               sprint(caller, strcat("^1autoswitch is currently turned ", (CS(caller).autoswitch ? "on" : "off"), ".\n"));
                                return;
                        }
                }
@@@ -214,7 -220,7 +220,7 @@@ void ClientCommand_physics(entity calle
                }
  
                default:
-                       sprint(caller, strcat("Current physics set: ^3", caller.cvar_cl_physics, "\n"));
+                       sprint(caller, strcat("Current physics set: ^3", CS(caller).cvar_cl_physics, "\n"));
                case CMD_REQUEST_USAGE:
                {
                        sprint(caller, "\nUsage:^3 cmd physics <physics>\n");
@@@ -319,90 -325,82 +325,90 @@@ void ClientCommand_selectteam(entity ca
        {
                case CMD_REQUEST_COMMAND:
                {
 -                      if (argv(1) != "")
 +                      if (argv(1) == "")
                        {
 -                              if (IS_CLIENT(caller))
 +                              return;
 +                      }
 +                      if (!IS_CLIENT(caller))
 +                      {
 +                              return;
 +                      }
 +                      if (!teamplay)
 +                      {
 +                              sprint(caller, "^7selectteam can only be used in teamgames\n");
 +                              return;
 +                      }
 +                      if (caller.team_forced > 0)
 +                      {
 +                              sprint(caller, "^7selectteam can not be used as your team is forced\n");
 +                              return;
 +                      }
 +                      if (lockteams)
 +                      {
 +                              sprint(caller, "^7The game has already begun, you must wait until the next map to be able to join a team.\n");
 +                              return;
 +                      }
 +                      float selection;
 +                      switch (argv(1))
 +                      {
 +                              case "red":
                                {
 -                                      if (teamplay)
 -                                      {
 -                                              if (caller.team_forced <= 0)
 -                                              {
 -                                                      if (!lockteams)
 -                                                      {
 -                                                              float selection;
 -
 -                                                              switch (argv(1))
 -                                                              {
 -                                                                      case "red": selection = NUM_TEAM_1;
 -                                                                              break;
 -                                                                      case "blue": selection = NUM_TEAM_2;
 -                                                                              break;
 -                                                                      case "yellow": selection = NUM_TEAM_3;
 -                                                                              break;
 -                                                                      case "pink": selection = NUM_TEAM_4;
 -                                                                              break;
 -                                                                      case "auto": selection = (-1);
 -                                                                              break;
 -
 -                                                                      default: selection = 0;
 -                                                                              break;
 -                                                              }
 -
 -                                                              if (selection)
 -                                                              {
 -                                                                      if (caller.team == selection && selection != -1 && !IS_DEAD(caller))
 -                                                                      {
 -                                                                              sprint(caller, "^7You already are on that team.\n");
 -                                                                      }
 -                                                                      else if (CS(caller).wasplayer && autocvar_g_changeteam_banned)
 -                                                                      {
 -                                                                              sprint(caller, "^1You cannot change team, forbidden by the server.\n");
 -                                                                      }
 -                                                                      else
 -                                                                      {
 -                                                                              if (autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance)
 -                                                                              {
 -                                                                                      CheckAllowedTeams(caller);
 -                                                                                      GetTeamCounts(caller);
 -                                                                                      if (!TeamSmallerEqThanTeam(Team_TeamToNumber(selection), Team_TeamToNumber(caller.team), caller))
 -                                                                                      {
 -                                                                                              Send_Notification(NOTIF_ONE, caller, MSG_INFO, INFO_TEAMCHANGE_LARGERTEAM);
 -                                                                                              return;
 -                                                                                      }
 -                                                                              }
 -                                                                              ClientKill_TeamChange(caller, selection);
 -                                                                      }
 -                                                                      if(!IS_PLAYER(caller))
 -                                                                              caller.team_selected = true; // avoids asking again for team selection on join
 -                                                              }
 -                                                      }
 -                                                      else
 -                                                      {
 -                                                              sprint(caller, "^7The game has already begun, you must wait until the next map to be able to join a team.\n");
 -                                                      }
 -                                              }
 -                                              else
 -                                              {
 -                                                      sprint(caller, "^7selectteam can not be used as your team is forced\n");
 -                                              }
 -                                      }
 -                                      else
 -                                      {
 -                                              sprint(caller, "^7selectteam can only be used in teamgames\n");
 -                                      }
 +                                      selection = NUM_TEAM_1;
 +                                      break;
 +                              }
 +                              case "blue":
 +                              {
 +                                      selection = NUM_TEAM_2;
 +                                      break;
                                }
 +                              case "yellow":
 +                              {
 +                                      selection = NUM_TEAM_3;
 +                                      break;
 +                              }
 +                              case "pink":
 +                              {
 +                                      selection = NUM_TEAM_4;
 +                                      break;
 +                              }
 +                              case "auto":
 +                              {
 +                                      selection = (-1);
 +                                      break;
 +                              }
 +                              default:
 +                              {
 +                                      return;
 +                              }
 +                      }
 +                      if (caller.team == selection && selection != -1 && !IS_DEAD(caller))
 +                      {
 +                              sprint(caller, "^7You already are on that team.\n");
 +                              return;
 +                      }
 +                      if (CS(caller).wasplayer && autocvar_g_changeteam_banned)
 +                      {
 +                              sprint(caller, "^1You cannot change team, forbidden by the server.\n");
                                return;
                        }
 +                      if ((selection != -1) && autocvar_g_balance_teams &&
 +                              autocvar_g_balance_teams_prevent_imbalance)
 +                      {
 +                              CheckAllowedTeams(caller);
 +                              GetTeamCounts(caller);
 +                              if ((BIT(Team_TeamToNumber(selection) - 1) & FindBestTeams(caller, false)) == 0)
 +                              {
 +                                      Send_Notification(NOTIF_ONE, caller, MSG_INFO, INFO_TEAMCHANGE_LARGERTEAM);
 +                                      return;
 +                              }
 +                      }
 +                      ClientKill_TeamChange(caller, selection);
 +                      if (!IS_PLAYER(caller))
 +                      {
 +                              caller.team_selected = true; // avoids asking again for team selection on join
 +                      }
 +                      return;
                }
 -
                default:
                        sprint(caller, "Incorrect parameters for ^2selectteam^7\n");
                case CMD_REQUEST_USAGE:
@@@ -456,7 -454,7 +462,7 @@@ void ClientCommand_sentcvar(entity call
                                        tokenize_console(s);
                                }
  
-                               GetCvars(caller, 1);
+                               GetCvars(caller, CS(caller), 1);
  
                                return;
                        }
index 4687403d2a0006124861a204f9e8914bf8081244,2bb0f1365c3b71aa353be44d6bc7522438344293..f5569c08f2697d4baf879ff63dc659efaaa88e74
@@@ -1,6 -1,8 +1,8 @@@
  #include "sv_cmd.qh"
  #include "_mod.qh"
  
+ #include <common/effects/all.qh>
  #include "banning.qh"
  #include "cmd.qh"
  #include "common.qh"
@@@ -41,7 -43,7 +43,7 @@@ void make_mapinfo_Think(entity this
  {
        if (_MapInfo_FilterGametype(MAPINFO_TYPE_ALL, 0, 0, 0, 1))
        {
-               LOG_INFO("Done rebuiling mapinfos.\n");
+               LOG_INFO("Done rebuiling mapinfos.");
                MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 0);
                delete(this);
        }
@@@ -123,7 -125,7 +125,7 @@@ void GameCommand_adminmsg(float request
  
                                        if (accepted <= 0)
                                        {
-                                               LOG_INFO("adminmsg: ", GetClientErrorString(accepted, t), (targets ? ", skipping to next player.\n" : ".\n"));
+                                               LOG_INFO("adminmsg: ", GetClientErrorString(accepted, t), (targets ? ", skipping to next player.\n" : "."));
                                                continue;
                                        }
  
                                }
  
                                if (successful) bprint("Successfully sent message '", admin_message, "' to ", successful, ".\n");
-                               else LOG_INFO("No players given (", original_targets, ") could receive the message.\n");
+                               else LOG_INFO("No players given (", original_targets, ") could receive the message.");
  
                                return;
                        }
                }
  
                default:
-                       LOG_INFO("Incorrect parameters for ^2adminmsg^7\n");
+                       LOG_INFO("Incorrect parameters for ^2adminmsg^7");
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd adminmsg clients \"message\" [infobartime]\n");
-                       LOG_INFO("  'clients' is a list (separated by commas) of player entity ID's or nicknames\n");
-                       LOG_INFO("  If infobartime is provided, the message will be sent to infobar.\n");
-                       LOG_INFO("  Otherwise, it will just be sent as a centerprint message.\n");
-                       LOG_INFO("Examples: adminmsg 2,4 \"this infomessage will last for ten seconds\" 10\n");
-                       LOG_INFO("          adminmsg 2,5 \"this message will be a centerprint\"\n");
+                       LOG_INFO("Usage:^3 sv_cmd adminmsg clients \"message\" [infobartime]");
+                       LOG_INFO("  'clients' is a list (separated by commas) of player entity ID's or nicknames");
+                       LOG_INFO("  If infobartime is provided, the message will be sent to infobar.");
+                       LOG_INFO("  Otherwise, it will just be sent as a centerprint message.");
+                       LOG_INFO("Examples: adminmsg 2,4 \"this infomessage will last for ten seconds\" 10");
+                       LOG_INFO("          adminmsg 2,5 \"this message will be a centerprint\"");
                        return;
                }
        }
@@@ -178,8 -180,8 +180,8 @@@ void GameCommand_allready(float request
                default:
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd allready\n");
-                       LOG_INFO("  No arguments required.\n");
+                       LOG_INFO("Usage:^3 sv_cmd allready");
+                       LOG_INFO("  No arguments required.");
                        return;
                }
        }
@@@ -199,16 -201,16 +201,16 @@@ void GameCommand_allspec(float request
                                ++n;
                        });
                        if (n)   bprint(strcat("Successfully forced all (", ftos(n), ") players to spectate", (reason ? strcat(" for reason: '", reason, "'") : ""), ".\n"));
-                       else   LOG_INFO("No players found to spectate.\n");
+                       else   LOG_INFO("No players found to spectate.");
                        return;
                }
  
                default:
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd allspec [reason]\n");
-                       LOG_INFO("  Where 'reason' is an optional argument for explanation of allspec command.\n");
-                       LOG_INFO("See also: ^2moveplayer, shuffleteams^7\n");
+                       LOG_INFO("Usage:^3 sv_cmd allspec [reason]");
+                       LOG_INFO("  Where 'reason' is an optional argument for explanation of allspec command.");
+                       LOG_INFO("See also: ^2moveplayer, shuffleteams^7");
                        return;
                }
        }
@@@ -230,16 -232,16 +232,16 @@@ void GameCommand_anticheat(float reques
                        }
                        else
                        {
-                               LOG_INFO("anticheat: ", GetClientErrorString(accepted, argv(1)), ".\n");
+                               LOG_INFO("anticheat: ", GetClientErrorString(accepted, argv(1)), ".");
                        }
                }
  
                default:
-                       LOG_INFO("Incorrect parameters for ^2anticheat^7\n");
+                       LOG_INFO("Incorrect parameters for ^2anticheat^7");
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd anticheat client\n");
-                       LOG_INFO("  'client' is the entity number or name of the player.\n");
+                       LOG_INFO("Usage:^3 sv_cmd anticheat client");
+                       LOG_INFO("  'client' is the entity number or name of the player.");
                        return;
                }
        }
@@@ -301,18 -303,18 +303,18 @@@ void GameCommand_bbox(float request
                                NULL);
                        size_max.z = (trace_startsolid) ? world.absmax.z : trace_endpos.z;
  
-                       LOG_INFOF("Original size: %v %v\n", world.absmin, world.absmax);
-                       LOG_INFOF("Currently set size: %v %v\n", world.mins, world.maxs);
-                       LOG_INFOF("Solid bounding box size: %v %v\n", size_min, size_max);
+                       LOG_INFOF("Original size: %v %v", world.absmin, world.absmax);
+                       LOG_INFOF("Currently set size: %v %v", world.mins, world.maxs);
+                       LOG_INFOF("Solid bounding box size: %v %v", size_min, size_max);
                        return;
                }
  
                default:
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd bbox\n");
-                       LOG_INFO("  No arguments required.\n");
-                       LOG_INFO("See also: ^2gettaginfo, trace^7\n");
+                       LOG_INFO("Usage:^3 sv_cmd bbox");
+                       LOG_INFO("  No arguments required.");
+                       LOG_INFO("See also: ^2gettaginfo, trace^7");
                        return;
                }
        }
@@@ -338,7 -340,7 +340,7 @@@ void GameCommand_bot_cmd(float request
                                cvar_settemp("bot_number", "0");
                                bot_fixcount();
                                cvar_settemp("bot_number", argv(2));
-                               if (!bot_fixcount()) LOG_INFO("Sorry, could not set requested bot count\n");
+                               if (!bot_fixcount()) LOG_INFO("Sorry, could not set requested bot count");
                                return;
                        }
                        else if (argv(1) == "load" && argc == 3)
                                fh = fopen(argv(2), FILE_READ);
                                if (fh < 0)
                                {
-                                       LOG_INFO("cannot open the file\n");
+                                       LOG_INFO("cannot open the file");
                                        return;
                                }
  
                                                        cvar_settemp("bot_number", "0");
                                                        bot_fixcount();
                                                        cvar_settemp("bot_number", argv(3));
-                                                       if (!bot_fixcount()) LOG_INFO("Sorry, could not set requested bot count\n");
+                                                       if (!bot_fixcount()) LOG_INFO("Sorry, could not set requested bot count");
                                                }
                                                else
                                                {
  
                                        ++i;
                                }
-                               LOG_INFO(ftos(i), " commands read\n");
+                               LOG_INFO(ftos(i), " commands read");
                                fclose(fh);
                                return;
                        }
                                                bot_num++;
                                        });
                                        if(bot_num)
-                                               LOG_INFO(strcat("Command '", substring(command, argv_start_index(2), -1), "' sent to all bots (", ftos(bot_num), ")\n"));
+                                               LOG_INFO("Command '", substring(command, argv_start_index(2), -1), "' sent to all bots (", ftos(bot_num), ")");
                                        return;
                                }
                                else
                                        if (bot == NULL) bot = find_bot_by_name(argv(1));
                                        if (bot)
                                        {
-                                               LOG_INFO(strcat("Command '", substring(command, argv_start_index(2), -1), "' sent to bot ", bot.netname, "\n"));
+                                               LOG_INFO("Command '", substring(command, argv_start_index(2), -1), "' sent to bot ", bot.netname);
                                                bot_queuecommand(bot, substring(command, argv_start_index(2), -1));
                                                return;
                                        }
                                        else
                                        {
-                                               LOG_INFO(strcat("Error: Can't find bot with the name or id '", argv(1), "' - Did you mistype the command?\n"));  // don't return so that usage is shown
+                                               LOG_INFO("Error: Can't find bot with the name or id '", argv(1), "' - Did you mistype the command?");  // don't return so that usage is shown
                                        }
                                }
                        }
                }
  
                default:
-                       LOG_INFO("Incorrect parameters for ^2bot_cmd^7\n");
+                       LOG_INFO("Incorrect parameters for ^2bot_cmd^7");
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd bot_cmd client command [argument]\n");
-                       LOG_INFO("  'client' can be either the name of the bot or a progressive number (not the entity number!)\n");
-                       LOG_INFO("           can also be '*' or 'all' to allow sending the command to all the bots\n");
-                       LOG_INFO("  For full list of commands, see bot_cmd help [command].\n");
-                       LOG_INFO("Examples: sv_cmd bot_cmd 1 cc \"say something\"\n");
-                       LOG_INFO("          sv_cmd bot_cmd 1 presskey jump\n");
-                       LOG_INFO("          sv_cmd bot_cmd * pause\n");
+                       LOG_INFO("Usage:^3 sv_cmd bot_cmd client command [argument]");
+                       LOG_INFO("  'client' can be either the name of the bot or a progressive number (not the entity number!)");
+                       LOG_INFO("           can also be '*' or 'all' to allow sending the command to all the bots");
+                       LOG_INFO("  For full list of commands, see bot_cmd help [command].");
+                       LOG_INFO("Examples: sv_cmd bot_cmd 1 cc \"say something\"");
+                       LOG_INFO("          sv_cmd bot_cmd 1 presskey jump");
+                       LOG_INFO("          sv_cmd bot_cmd * pause");
                        return;
                }
        }
@@@ -467,8 -469,8 +469,8 @@@ void GameCommand_cointoss(float request
                default:
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd cointoss [result1 result2]\n");
-                       LOG_INFO("  Where 'result1' and 'result2' are user created options.\n");
+                       LOG_INFO("Usage:^3 sv_cmd cointoss [result1 result2]");
+                       LOG_INFO("  Where 'result1' and 'result2' are user created options.");
                        return;
                }
        }
@@@ -485,33 -487,33 +487,33 @@@ void GameCommand_database(float request
                                if (argv(1) == "save")
                                {
                                        db_save(ServerProgsDB, argv(2));
-                                       LOG_INFO(strcat("Copied serverprogs database to '", argv(2), "' in the data directory.\n"));
+                                       LOG_INFO("Copied serverprogs database to '", argv(2), "' in the data directory.");
                                        return;
                                }
                                else if (argv(1) == "dump")
                                {
                                        db_dump(ServerProgsDB, argv(2));
-                                       LOG_INFO("DB dumped.\n");  // wtf does this do?
+                                       LOG_INFO("DB dumped.");  // wtf does this do?
                                        return;
                                }
                                else if (argv(1) == "load")
                                {
                                        db_close(ServerProgsDB);
                                        ServerProgsDB = db_load(argv(2));
-                                       LOG_INFO(strcat("Loaded '", argv(2), "' as new serverprogs database.\n"));
+                                       LOG_INFO("Loaded '", argv(2), "' as new serverprogs database.");
                                        return;
                                }
                        }
                }
  
                default:
-                       LOG_INFO("Incorrect parameters for ^2database^7\n");
+                       LOG_INFO("Incorrect parameters for ^2database^7");
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd database action filename\n");
-                       LOG_INFO("  Where 'action' is the command to complete,\n");
-                       LOG_INFO("  and 'filename' is what it acts upon.\n");
-                       LOG_INFO("  Full list of commands here: \"save, dump, load.\"\n");
+                       LOG_INFO("Usage:^3 sv_cmd database action filename");
+                       LOG_INFO("  Where 'action' is the command to complete,");
+                       LOG_INFO("  and 'filename' is what it acts upon.");
+                       LOG_INFO("  Full list of commands here: \"save, dump, load.\"");
                        return;
                }
        }
@@@ -534,21 -536,21 +536,21 @@@ void GameCommand_defer_clear(float requ
                                if (accepted > 0)
                                {
                                        stuffcmd(client, "defer clear\n");
-                                       LOG_INFO("defer clear stuffed to ", playername(client, false), "\n");
+                                       LOG_INFO("defer clear stuffed to ", playername(client, false));
                                }
-                               else { LOG_INFO("defer_clear: ", GetClientErrorString(accepted, argv(1)), ".\n"); }
+                               else { LOG_INFO("defer_clear: ", GetClientErrorString(accepted, argv(1)), "."); }
  
                                return;
                        }
                }
  
                default:
-                       LOG_INFO("Incorrect parameters for ^2defer_clear^7\n");
+                       LOG_INFO("Incorrect parameters for ^2defer_clear^7");
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd defer_clear client\n");
-                       LOG_INFO("  'client' is the entity number or name of the player.\n");
-                       LOG_INFO("See also: ^2defer_clear_all^7\n");
+                       LOG_INFO("Usage:^3 sv_cmd defer_clear client");
+                       LOG_INFO("  'client' is the entity number or name of the player.");
+                       LOG_INFO("See also: ^2defer_clear_all^7");
                        return;
                }
        }
@@@ -568,16 -570,16 +570,16 @@@ void GameCommand_defer_clear_all(float 
                                GameCommand_defer_clear(CMD_REQUEST_COMMAND, argc);
                                ++n;
                        });
-                       if (n)   LOG_INFO(strcat("Successfully stuffed defer clear to all clients (", ftos(n), ")\n"));  // should a message be added if no players were found?
+                       if (n)   LOG_INFO("Successfully stuffed defer clear to all clients (", ftos(n), ")");  // should a message be added if no players were found?
                        return;
                }
  
                default:
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd defer_clear_all\n");
-                       LOG_INFO("  No arguments required.\n");
-                       LOG_INFO("See also: ^2defer_clear^7\n");
+                       LOG_INFO("Usage:^3 sv_cmd defer_clear_all");
+                       LOG_INFO("  No arguments required.");
+                       LOG_INFO("See also: ^2defer_clear^7");
                        return;
                }
        }
@@@ -598,13 -600,13 +600,13 @@@ void GameCommand_delrec(float request, 
                }
  
                default:
-                       LOG_INFO("Incorrect parameters for ^2delrec^7\n");
+                       LOG_INFO("Incorrect parameters for ^2delrec^7");
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd delrec ranking [map]\n");
-                       LOG_INFO("  'ranking' is which ranking level to clear up to, \n");
-                       LOG_INFO("  it will clear all records up to nth place.\n");
-                       LOG_INFO("  if 'map' is not provided it will use current map.\n");
+                       LOG_INFO("Usage:^3 sv_cmd delrec ranking [map]");
+                       LOG_INFO("  'ranking' is which ranking level to clear up to, ");
+                       LOG_INFO("  it will clear all records up to nth place.");
+                       LOG_INFO("  if 'map' is not provided it will use current map.");
                        return;
                }
        }
@@@ -620,79 -622,79 +622,79 @@@ void GameCommand_effectindexdump(float 
                        string s;
  
                        d = db_create();
-                       LOG_INFO("begin of effects list\n");
+                       LOG_INFO("begin of effects list");
                        db_put(d, "TE_GUNSHOT", "1");
-                       LOG_INFO("effect TE_GUNSHOT is ", ftos(_particleeffectnum("TE_GUNSHOT")), "\n");
+                       LOG_INFO("effect TE_GUNSHOT is ", ftos(_particleeffectnum("TE_GUNSHOT")));
                        db_put(d, "TE_GUNSHOTQUAD", "1");
-                       LOG_INFO("effect TE_GUNSHOTQUAD is ", ftos(_particleeffectnum("TE_GUNSHOTQUAD")), "\n");
+                       LOG_INFO("effect TE_GUNSHOTQUAD is ", ftos(_particleeffectnum("TE_GUNSHOTQUAD")));
                        db_put(d, "TE_SPIKE", "1");
-                       LOG_INFO("effect TE_SPIKE is ", ftos(_particleeffectnum("TE_SPIKE")), "\n");
+                       LOG_INFO("effect TE_SPIKE is ", ftos(_particleeffectnum("TE_SPIKE")));
                        db_put(d, "TE_SPIKEQUAD", "1");
-                       LOG_INFO("effect TE_SPIKEQUAD is ", ftos(_particleeffectnum("TE_SPIKEQUAD")), "\n");
+                       LOG_INFO("effect TE_SPIKEQUAD is ", ftos(_particleeffectnum("TE_SPIKEQUAD")));
                        db_put(d, "TE_SUPERSPIKE", "1");
-                       LOG_INFO("effect TE_SUPERSPIKE is ", ftos(_particleeffectnum("TE_SUPERSPIKE")), "\n");
+                       LOG_INFO("effect TE_SUPERSPIKE is ", ftos(_particleeffectnum("TE_SUPERSPIKE")));
                        db_put(d, "TE_SUPERSPIKEQUAD", "1");
-                       LOG_INFO("effect TE_SUPERSPIKEQUAD is ", ftos(_particleeffectnum("TE_SUPERSPIKEQUAD")), "\n");
+                       LOG_INFO("effect TE_SUPERSPIKEQUAD is ", ftos(_particleeffectnum("TE_SUPERSPIKEQUAD")));
                        db_put(d, "TE_WIZSPIKE", "1");
-                       LOG_INFO("effect TE_WIZSPIKE is ", ftos(_particleeffectnum("TE_WIZSPIKE")), "\n");
+                       LOG_INFO("effect TE_WIZSPIKE is ", ftos(_particleeffectnum("TE_WIZSPIKE")));
                        db_put(d, "TE_KNIGHTSPIKE", "1");
-                       LOG_INFO("effect TE_KNIGHTSPIKE is ", ftos(_particleeffectnum("TE_KNIGHTSPIKE")), "\n");
+                       LOG_INFO("effect TE_KNIGHTSPIKE is ", ftos(_particleeffectnum("TE_KNIGHTSPIKE")));
                        db_put(d, "TE_EXPLOSION", "1");
-                       LOG_INFO("effect TE_EXPLOSION is ", ftos(_particleeffectnum("TE_EXPLOSION")), "\n");
+                       LOG_INFO("effect TE_EXPLOSION is ", ftos(_particleeffectnum("TE_EXPLOSION")));
                        db_put(d, "TE_EXPLOSIONQUAD", "1");
-                       LOG_INFO("effect TE_EXPLOSIONQUAD is ", ftos(_particleeffectnum("TE_EXPLOSIONQUAD")), "\n");
+                       LOG_INFO("effect TE_EXPLOSIONQUAD is ", ftos(_particleeffectnum("TE_EXPLOSIONQUAD")));
                        db_put(d, "TE_TAREXPLOSION", "1");
-                       LOG_INFO("effect TE_TAREXPLOSION is ", ftos(_particleeffectnum("TE_TAREXPLOSION")), "\n");
+                       LOG_INFO("effect TE_TAREXPLOSION is ", ftos(_particleeffectnum("TE_TAREXPLOSION")));
                        db_put(d, "TE_TELEPORT", "1");
-                       LOG_INFO("effect TE_TELEPORT is ", ftos(_particleeffectnum("TE_TELEPORT")), "\n");
+                       LOG_INFO("effect TE_TELEPORT is ", ftos(_particleeffectnum("TE_TELEPORT")));
                        db_put(d, "TE_LAVASPLASH", "1");
-                       LOG_INFO("effect TE_LAVASPLASH is ", ftos(_particleeffectnum("TE_LAVASPLASH")), "\n");
+                       LOG_INFO("effect TE_LAVASPLASH is ", ftos(_particleeffectnum("TE_LAVASPLASH")));
                        db_put(d, "TE_SMALLFLASH", "1");
-                       LOG_INFO("effect TE_SMALLFLASH is ", ftos(_particleeffectnum("TE_SMALLFLASH")), "\n");
+                       LOG_INFO("effect TE_SMALLFLASH is ", ftos(_particleeffectnum("TE_SMALLFLASH")));
                        db_put(d, "TE_FLAMEJET", "1");
-                       LOG_INFO("effect TE_FLAMEJET is ", ftos(_particleeffectnum("TE_FLAMEJET")), "\n");
+                       LOG_INFO("effect TE_FLAMEJET is ", ftos(_particleeffectnum("TE_FLAMEJET")));
                        db_put(d, "EF_FLAME", "1");
-                       LOG_INFO("effect EF_FLAME is ", ftos(_particleeffectnum("EF_FLAME")), "\n");
+                       LOG_INFO("effect EF_FLAME is ", ftos(_particleeffectnum("EF_FLAME")));
                        db_put(d, "TE_BLOOD", "1");
-                       LOG_INFO("effect TE_BLOOD is ", ftos(_particleeffectnum("TE_BLOOD")), "\n");
+                       LOG_INFO("effect TE_BLOOD is ", ftos(_particleeffectnum("TE_BLOOD")));
                        db_put(d, "TE_SPARK", "1");
-                       LOG_INFO("effect TE_SPARK is ", ftos(_particleeffectnum("TE_SPARK")), "\n");
+                       LOG_INFO("effect TE_SPARK is ", ftos(_particleeffectnum("TE_SPARK")));
                        db_put(d, "TE_PLASMABURN", "1");
-                       LOG_INFO("effect TE_PLASMABURN is ", ftos(_particleeffectnum("TE_PLASMABURN")), "\n");
+                       LOG_INFO("effect TE_PLASMABURN is ", ftos(_particleeffectnum("TE_PLASMABURN")));
                        db_put(d, "TE_TEI_G3", "1");
-                       LOG_INFO("effect TE_TEI_G3 is ", ftos(_particleeffectnum("TE_TEI_G3")), "\n");
+                       LOG_INFO("effect TE_TEI_G3 is ", ftos(_particleeffectnum("TE_TEI_G3")));
                        db_put(d, "TE_TEI_SMOKE", "1");
-                       LOG_INFO("effect TE_TEI_SMOKE is ", ftos(_particleeffectnum("TE_TEI_SMOKE")), "\n");
+                       LOG_INFO("effect TE_TEI_SMOKE is ", ftos(_particleeffectnum("TE_TEI_SMOKE")));
                        db_put(d, "TE_TEI_BIGEXPLOSION", "1");
-                       LOG_INFO("effect TE_TEI_BIGEXPLOSION is ", ftos(_particleeffectnum("TE_TEI_BIGEXPLOSION")), "\n");
+                       LOG_INFO("effect TE_TEI_BIGEXPLOSION is ", ftos(_particleeffectnum("TE_TEI_BIGEXPLOSION")));
                        db_put(d, "TE_TEI_PLASMAHIT", "1");
-                       LOG_INFO("effect TE_TEI_PLASMAHIT is ", ftos(_particleeffectnum("TE_TEI_PLASMAHIT")), "\n");
+                       LOG_INFO("effect TE_TEI_PLASMAHIT is ", ftos(_particleeffectnum("TE_TEI_PLASMAHIT")));
                        db_put(d, "EF_STARDUST", "1");
-                       LOG_INFO("effect EF_STARDUST is ", ftos(_particleeffectnum("EF_STARDUST")), "\n");
+                       LOG_INFO("effect EF_STARDUST is ", ftos(_particleeffectnum("EF_STARDUST")));
                        db_put(d, "TR_ROCKET", "1");
-                       LOG_INFO("effect TR_ROCKET is ", ftos(_particleeffectnum("TR_ROCKET")), "\n");
+                       LOG_INFO("effect TR_ROCKET is ", ftos(_particleeffectnum("TR_ROCKET")));
                        db_put(d, "TR_GRENADE", "1");
-                       LOG_INFO("effect TR_GRENADE is ", ftos(_particleeffectnum("TR_GRENADE")), "\n");
+                       LOG_INFO("effect TR_GRENADE is ", ftos(_particleeffectnum("TR_GRENADE")));
                        db_put(d, "TR_BLOOD", "1");
-                       LOG_INFO("effect TR_BLOOD is ", ftos(_particleeffectnum("TR_BLOOD")), "\n");
+                       LOG_INFO("effect TR_BLOOD is ", ftos(_particleeffectnum("TR_BLOOD")));
                        db_put(d, "TR_WIZSPIKE", "1");
-                       LOG_INFO("effect TR_WIZSPIKE is ", ftos(_particleeffectnum("TR_WIZSPIKE")), "\n");
+                       LOG_INFO("effect TR_WIZSPIKE is ", ftos(_particleeffectnum("TR_WIZSPIKE")));
                        db_put(d, "TR_SLIGHTBLOOD", "1");
-                       LOG_INFO("effect TR_SLIGHTBLOOD is ", ftos(_particleeffectnum("TR_SLIGHTBLOOD")), "\n");
+                       LOG_INFO("effect TR_SLIGHTBLOOD is ", ftos(_particleeffectnum("TR_SLIGHTBLOOD")));
                        db_put(d, "TR_KNIGHTSPIKE", "1");
-                       LOG_INFO("effect TR_KNIGHTSPIKE is ", ftos(_particleeffectnum("TR_KNIGHTSPIKE")), "\n");
+                       LOG_INFO("effect TR_KNIGHTSPIKE is ", ftos(_particleeffectnum("TR_KNIGHTSPIKE")));
                        db_put(d, "TR_VORESPIKE", "1");
-                       LOG_INFO("effect TR_VORESPIKE is ", ftos(_particleeffectnum("TR_VORESPIKE")), "\n");
+                       LOG_INFO("effect TR_VORESPIKE is ", ftos(_particleeffectnum("TR_VORESPIKE")));
                        db_put(d, "TR_NEHAHRASMOKE", "1");
-                       LOG_INFO("effect TR_NEHAHRASMOKE is ", ftos(_particleeffectnum("TR_NEHAHRASMOKE")), "\n");
+                       LOG_INFO("effect TR_NEHAHRASMOKE is ", ftos(_particleeffectnum("TR_NEHAHRASMOKE")));
                        db_put(d, "TR_NEXUIZPLASMA", "1");
-                       LOG_INFO("effect TR_NEXUIZPLASMA is ", ftos(_particleeffectnum("TR_NEXUIZPLASMA")), "\n");
+                       LOG_INFO("effect TR_NEXUIZPLASMA is ", ftos(_particleeffectnum("TR_NEXUIZPLASMA")));
                        db_put(d, "TR_GLOWTRAIL", "1");
-                       LOG_INFO("effect TR_GLOWTRAIL is ", ftos(_particleeffectnum("TR_GLOWTRAIL")), "\n");
+                       LOG_INFO("effect TR_GLOWTRAIL is ", ftos(_particleeffectnum("TR_GLOWTRAIL")));
                        db_put(d, "TR_SEEKER", "1");
-                       LOG_INFO("effect TR_SEEKER is ", ftos(_particleeffectnum("TR_SEEKER")), "\n");
+                       LOG_INFO("effect TR_SEEKER is ", ftos(_particleeffectnum("TR_SEEKER")));
                        db_put(d, "SVC_PARTICLE", "1");
-                       LOG_INFO("effect SVC_PARTICLE is ", ftos(_particleeffectnum("SVC_PARTICLE")), "\n");
+                       LOG_INFO("effect SVC_PARTICLE is ", ftos(_particleeffectnum("SVC_PARTICLE")));
  
                        fh = fopen("effectinfo.txt", FILE_READ);
                        while ((s = fgets(fh)))
                                        if (db_get(d, argv(1)) != "1")
                                        {
                                                int i = _particleeffectnum(argv(1));
-                                               if (i >= 0) LOG_INFO("effect ", argv(1), " is ", ftos(i), "\n");
+                                               if (i >= 0) LOG_INFO("effect ", argv(1), " is ", ftos(i));
                                                db_put(d, argv(1), "1");
                                        }
                                }
                        }
-                       LOG_INFO("end of effects list\n");
+                       LOG_INFO("end of effects list");
  
                        db_close(d);
                        return;
                default:
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd effectindexdump\n");
-                       LOG_INFO("  No arguments required.\n");
+                       LOG_INFO("Usage:^3 sv_cmd effectindexdump");
+                       LOG_INFO("  No arguments required.");
                        return;
                }
        }
@@@ -737,9 -739,9 +739,9 @@@ void GameCommand_extendmatchtime(float 
                default:
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd extendmatchtime\n");
-                       LOG_INFO("  No arguments required.\n");
-                       LOG_INFO("See also: ^2reducematchtime^7\n");
+                       LOG_INFO("Usage:^3 sv_cmd extendmatchtime");
+                       LOG_INFO("  No arguments required.");
+                       LOG_INFO("See also: ^2reducematchtime^7");
                        return;
                }
        }
@@@ -784,12 -786,12 +786,12 @@@ void GameCommand_gametype(float request
                }
  
                default:
-                       LOG_INFO("Incorrect parameters for ^2gametype^7\n");
+                       LOG_INFO("Incorrect parameters for ^2gametype^7");
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd gametype mode\n");
-                       LOG_INFO("  Where 'mode' is the gametype mode to switch to.\n");
-                       LOG_INFO("See also: ^2gotomap^7\n");
+                       LOG_INFO("Usage:^3 sv_cmd gametype mode");
+                       LOG_INFO("  Where 'mode' is the gametype mode to switch to.");
+                       LOG_INFO("See also: ^2gotomap^7");
                        return;
                }
        }
@@@ -824,13 -826,15 +826,15 @@@ void GameCommand_gettaginfo(float reque
                                if (i)
                                {
                                        v = gettaginfo(tmp_entity, i);
-                                       LOG_INFO("model ", tmp_entity.model, " frame ", ftos(tmp_entity.frame), " tag ", gettaginfo_name);
-                                       LOG_INFO(" index ", ftos(i), " parent ", ftos(gettaginfo_parent), "\n");
-                                       LOG_INFO(" vector = ", ftos(v.x), " ", ftos(v.y), " ", ftos(v.z), "\n");
-                                       LOG_INFO(" offset = ", ftos(gettaginfo_offset.x), " ", ftos(gettaginfo_offset.y), " ", ftos(gettaginfo_offset.z), "\n");
-                                       LOG_INFO(" forward = ", ftos(gettaginfo_forward.x), " ", ftos(gettaginfo_forward.y), " ", ftos(gettaginfo_forward.z), "\n");
-                                       LOG_INFO(" right = ", ftos(gettaginfo_right.x), " ", ftos(gettaginfo_right.y), " ", ftos(gettaginfo_right.z), "\n");
-                                       LOG_INFO(" up = ", ftos(gettaginfo_up.x), " ", ftos(gettaginfo_up.y), " ", ftos(gettaginfo_up.z), "\n");
+                                       LOG_INFOF(
+                                           "model %s frame %s tag %s index %s parent %s",
+                                           tmp_entity.model, ftos(tmp_entity.frame), gettaginfo_name, ftos(i), ftos(gettaginfo_parent)
+                                       );
+                                       LOG_INFOF(" vector = %s %s %s", ftos(v.x), ftos(v.y), ftos(v.z));
+                                       LOG_INFOF(" offset = %s %s %s", ftos(gettaginfo_offset.x), ftos(gettaginfo_offset.y), ftos(gettaginfo_offset.z));
+                                       LOG_INFOF(" forward = %s %s %s", ftos(gettaginfo_forward.x), ftos(gettaginfo_forward.y), ftos(gettaginfo_forward.z));
+                                       LOG_INFOF(" right = %s %s %s", ftos(gettaginfo_right.x), ftos(gettaginfo_right.y), ftos(gettaginfo_right.z));
+                                       LOG_INFOF(" up = %s %s %s", ftos(gettaginfo_up.x), ftos(gettaginfo_up.y), ftos(gettaginfo_up.z));
                                        if (argc >= 6)
                                        {
                                                v.y = -v.y;
                                }
                                else
                                {
-                                       LOG_INFO("bone not found\n");
+                                       LOG_INFO("bone not found");
                                }
  
                                delete(tmp_entity);
                }
  
                default:
-                       LOG_INFO("Incorrect parameters for ^2gettaginfo^7\n");
+                       LOG_INFO("Incorrect parameters for ^2gettaginfo^7");
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd gettaginfo model frame index [command one] [command two]\n");
-                       LOG_INFO("See also: ^2bbox, trace^7\n");
+                       LOG_INFO("Usage:^3 sv_cmd gettaginfo model frame index [command one] [command two]");
+                       LOG_INFO("See also: ^2bbox, trace^7");
                        return;
                }
        }
@@@ -898,8 -902,8 +902,8 @@@ void GameCommand_animbench(float reques
                                        t2 += gettime(GETTIME_HIRES) - t0;
                                        n += 1;
                                }
-                               LOG_INFO("model ", tmp_entity.model, " frame ", ftos(f1), " animtime ", ftos(n / t1), "/s\n");
-                               LOG_INFO("model ", tmp_entity.model, " frame ", ftos(f2), " animtime ", ftos(n / t2), "/s\n");
+                               LOG_INFO("model ", tmp_entity.model, " frame ", ftos(f1), " animtime ", ftos(n / t1), "/s");
+                               LOG_INFO("model ", tmp_entity.model, " frame ", ftos(f2), " animtime ", ftos(n / t2), "/s");
  
                                delete(tmp_entity);
                                return;
                }
  
                default:
-                       LOG_INFO("Incorrect parameters for ^2gettaginfo^7\n");
+                       LOG_INFO("Incorrect parameters for ^2gettaginfo^7");
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd gettaginfo model frame index [command one] [command two]\n");
-                       LOG_INFO("See also: ^2bbox, trace^7\n");
+                       LOG_INFO("Usage:^3 sv_cmd gettaginfo model frame index [command one] [command two]");
+                       LOG_INFO("See also: ^2bbox, trace^7");
                        return;
                }
        }
@@@ -925,18 -929,18 +929,18 @@@ void GameCommand_gotomap(float request
                {
                        if (argv(1))
                        {
-                               LOG_INFO(GotoMap(argv(1)), "\n");
+                               LOG_INFO(GotoMap(argv(1)));
                                return;
                        }
                }
  
                default:
-                       LOG_INFO("Incorrect parameters for ^2gotomap^7\n");
+                       LOG_INFO("Incorrect parameters for ^2gotomap^7");
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd gotomap map\n");
-                       LOG_INFO("  Where 'map' is the *.bsp file to change to.\n");
-                       LOG_INFO("See also: ^2gametype^7\n");
+                       LOG_INFO("Usage:^3 sv_cmd gotomap map");
+                       LOG_INFO("  Where 'map' is the *.bsp file to change to.");
+                       LOG_INFO("See also: ^2gametype^7");
                        return;
                }
        }
@@@ -963,9 -967,9 +967,9 @@@ void GameCommand_lockteams(float reques
                default:
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd lockteams\n");
-                       LOG_INFO("  No arguments required.\n");
-                       LOG_INFO("See also: ^2unlockteams^7\n");
+                       LOG_INFO("Usage:^3 sv_cmd lockteams");
+                       LOG_INFO("  No arguments required.");
+                       LOG_INFO("See also: ^2unlockteams^7");
                        return;
                }
        }
@@@ -989,9 -993,9 +993,9 @@@ void GameCommand_make_mapinfo(float req
                default:
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd make_mapinfo\n");
-                       LOG_INFO("  No arguments required.\n");
-                       LOG_INFO("See also: ^2radarmap^7\n");
+                       LOG_INFO("Usage:^3 sv_cmd make_mapinfo");
+                       LOG_INFO("  No arguments required.");
+                       LOG_INFO("See also: ^2radarmap^7");
                        return;
                }
        }
@@@ -1027,7 -1031,7 +1031,7 @@@ void GameCommand_moveplayer(float reque
  
                                        if (accepted <= 0)
                                        {
-                                               LOG_INFO("moveplayer: ", GetClientErrorString(accepted, t), (targets ? ", skipping to next player.\n" : ".\n"));
+                                               LOG_INFO("moveplayer: ", GetClientErrorString(accepted, t), (targets ? ", skipping to next player.\n" : "."));
                                                continue;
                                        }
  
                                                }
                                                else
                                                {
-                                                       LOG_INFO("Player ", ftos(GetFilteredNumber(t)), " (", playername(client, false), ") is already spectating.\n");
+                                                       LOG_INFO("Player ", ftos(GetFilteredNumber(t)), " (", playername(client, false), ") is already spectating.");
                                                }
                                                continue;
                                        }
                                                                if (team_id == client.team)  // already on the destination team
                                                                {
                                                                        // keep the forcing undone
-                                                                       LOG_INFO("Player ", ftos(GetFilteredNumber(t)), " (", playername(client, false), ") is already on the ", Team_ColoredFullName(client.team), (targets ? "^7, skipping to next player.\n" : "^7.\n"));
+                                                                       LOG_INFO("Player ", ftos(GetFilteredNumber(t)), " (", playername(client, false), ") is already on the ", Team_ColoredFullName(client.team), (targets ? "^7, skipping to next player.\n" : "^7."));
                                                                        continue;
                                                                }
                                                                else if (team_id == 0)  // auto team
                                                                // Check to see if the destination team is even available
                                                                switch (team_id)
                                                                {
-                                                                       case NUM_TEAM_1: if (c1 == -1) { LOG_INFO("Sorry, can't move player to red team if it doesn't exist.\n"); return; } break;
-                                                                       case NUM_TEAM_2: if (c2 == -1) { LOG_INFO("Sorry, can't move player to blue team if it doesn't exist.\n"); return; } break;
-                                                                       case NUM_TEAM_3: if (c3 == -1) { LOG_INFO("Sorry, can't move player to yellow team if it doesn't exist.\n"); return; } break;
-                                                                       case NUM_TEAM_4: if (c4 == -1) { LOG_INFO("Sorry, can't move player to pink team if it doesn't exist.\n"); return; } break;
+                                                                       case NUM_TEAM_1: if (c1 == -1) { LOG_INFO("Sorry, can't move player to red team if it doesn't exist."); return; } break;
+                                                                       case NUM_TEAM_2: if (c2 == -1) { LOG_INFO("Sorry, can't move player to blue team if it doesn't exist."); return; } break;
+                                                                       case NUM_TEAM_3: if (c3 == -1) { LOG_INFO("Sorry, can't move player to yellow team if it doesn't exist."); return; } break;
+                                                                       case NUM_TEAM_4: if (c4 == -1) { LOG_INFO("Sorry, can't move player to pink team if it doesn't exist."); return; } break;
  
-                                                                       default: LOG_INFO("Sorry, can't move player here if team ", destination, " doesn't exist.\n");
+                                                                       default: LOG_INFO("Sorry, can't move player here if team ", destination, " doesn't exist.");
                                                                                return;
                                                                }
  
                                                                // If so, lets continue and finally move the player
                                                                client.team_forced = 0;
 -                                                              MoveToTeam(client, team_id, 6);
 -                                                              successful = strcat(successful, (successful ? ", " : ""), playername(client, false));
 -                                                              LOG_INFO("Player ", ftos(GetFilteredNumber(t)), " (", playername(client, false), ") has been moved to the ", Team_ColoredFullName(team_id), "^7.");
 +                                                              if (MoveToTeam(client, team_id, 6))
 +                                                              {
 +                                                                      successful = strcat(successful, (successful ? ", " : ""), playername(client, false));
-                                                                       LOG_INFO("Player ", ftos(GetFilteredNumber(t)), " (", playername(client, false), ") has been moved to the ", Team_ColoredFullName(team_id), "^7.\n");
++                                                                      LOG_INFO("Player ", ftos(GetFilteredNumber(t)), " (", playername(client, false), ") has been moved to the ", Team_ColoredFullName(team_id), "^7.");
 +                                                              }
 +                                                              else
 +                                                              {
 +                                                                      LOG_INFO("Unable to move player ", ftos(GetFilteredNumber(t)), " (", playername(client, false), ")");
 +                                                              }
                                                                continue;
                                                        }
                                                        else
                                                        {
-                                                               LOG_INFO("Can't change teams when currently not playing a team game.\n");
+                                                               LOG_INFO("Can't change teams when currently not playing a team game.");
                                                                return;
                                                        }
                                                }
                                                else
                                                {
-                                                       LOG_INFO("Can't change teams if the player isn't in the game.\n");  // well technically we could, but should we allow that? :P
+                                                       LOG_INFO("Can't change teams if the player isn't in the game.");  // well technically we could, but should we allow that? :P
                                                        return;
                                                }
                                        }
                                }
  
                                if (successful) bprint("Successfully moved players ", successful, " to destination ", destination, ".\n");
-                               else LOG_INFO("No players given (", original_targets, ") are able to move.\n");
+                               else LOG_INFO("No players given (", original_targets, ") are able to move.");
  
                                return;  // still correct parameters so return to avoid usage print
                        }
                }
  
                default:
-                       LOG_INFO("Incorrect parameters for ^2moveplayer^7\n");
+                       LOG_INFO("Incorrect parameters for ^2moveplayer^7");
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd moveplayer clients destination\n");
-                       LOG_INFO("  'clients' is a list (separated by commas) of player entity ID's or nicknames\n");
-                       LOG_INFO("  'destination' is what to send the player to, be it team or spectating\n");
-                       LOG_INFO("  Full list of destinations here: \"spec, spectator, red, blue, yellow, pink, auto.\"\n");
-                       LOG_INFO("Examples: sv_cmd moveplayer 1,3,5 red 3\n");
-                       LOG_INFO("          sv_cmd moveplayer 2 spec \n");
-                       LOG_INFO("See also: ^2allspec, shuffleteams^7\n");
+                       LOG_INFO("Usage:^3 sv_cmd moveplayer clients destination");
+                       LOG_INFO("  'clients' is a list (separated by commas) of player entity ID's or nicknames");
+                       LOG_INFO("  'destination' is what to send the player to, be it team or spectating");
+                       LOG_INFO("  Full list of destinations here: \"spec, spectator, red, blue, yellow, pink, auto.\"");
+                       LOG_INFO("Examples: sv_cmd moveplayer 1,3,5 red 3");
+                       LOG_INFO("          sv_cmd moveplayer 2 spec ");
+                       LOG_INFO("See also: ^2allspec, shuffleteams^7");
                        return;
                }
        }
@@@ -1161,8 -1159,8 +1165,8 @@@ void GameCommand_nospectators(float req
                default:
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd nospectators\n");
-                       LOG_INFO("  No arguments required.\n");
+                       LOG_INFO("Usage:^3 sv_cmd nospectators");
+                       LOG_INFO("  No arguments required.");
                        return;
                }
        }
@@@ -1188,7 -1186,7 +1192,7 @@@ void GameCommand_playerdemo(float reque
  
                                                if (accepted <= 0)
                                                {
-                                                       LOG_INFO("playerdemo: read: ", GetClientErrorString(accepted, argv(2)), ".\n");
+                                                       LOG_INFO("playerdemo: read: ", GetClientErrorString(accepted, argv(2)), ".");
                                                        return;
                                                }
  
  
                                                if (accepted <= 0)
                                                {
-                                                       LOG_INFO("playerdemo: write: ", GetClientErrorString(accepted, argv(2)), ".\n");
+                                                       LOG_INFO("playerdemo: write: ", GetClientErrorString(accepted, argv(2)), ".");
                                                        return;
                                                }
  
                }
  
                default:
-                       LOG_INFO("Incorrect parameters for ^2playerdemo^7\n");
+                       LOG_INFO("Incorrect parameters for ^2playerdemo^7");
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd playerdemo command (entitynumber filename | entitynumber botnumber)\n");
-                       LOG_INFO("  Full list of commands here: \"read, write, auto_read_and_write, auto_read.\"\n");
+                       LOG_INFO("Usage:^3 sv_cmd playerdemo command (entitynumber filename | entitynumber botnumber)");
+                       LOG_INFO("  Full list of commands here: \"read, write, auto_read_and_write, auto_read.\"");
                        return;
                }
        }
@@@ -1255,15 -1253,15 +1259,15 @@@ void GameCommand_printstats(float reque
                case CMD_REQUEST_COMMAND:
                {
                        DumpStats(false);
-                       LOG_INFO("stats dumped.\n");
+                       LOG_INFO("stats dumped.");
                        return;
                }
  
                default:
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd printstats\n");
-                       LOG_INFO("  No arguments required.\n");
+                       LOG_INFO("Usage:^3 sv_cmd printstats");
+                       LOG_INFO("  No arguments required.");
                        return;
                }
        }
@@@ -1279,13 -1277,13 +1283,13 @@@ void GameCommand_radarmap(float request
                }
  
                default:
-                       LOG_INFO("Incorrect parameters for ^2radarmap^7\n");
+                       LOG_INFO("Incorrect parameters for ^2radarmap^7");
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd radarmap [--force] [--loop] [--quit] [--block | --trace | --sample | --lineblock] [--sharpen N] [--res W H] [--qual Q]\n");
-                       LOG_INFO("  The quality factor Q is roughly proportional to the time taken.\n");
-                       LOG_INFO("  trace supports no quality factor; its result should look like --block with infinite quality factor.\n");
-                       LOG_INFO("See also: ^2make_mapinfo^7\n");
+                       LOG_INFO("Usage:^3 sv_cmd radarmap [--force] [--loop] [--quit] [--block | --trace | --sample | --lineblock] [--sharpen N] [--res W H] [--qual Q]");
+                       LOG_INFO("  The quality factor Q is roughly proportional to the time taken.");
+                       LOG_INFO("  trace supports no quality factor; its result should look like --block with infinite quality factor.");
+                       LOG_INFO("See also: ^2make_mapinfo^7");
                        return;
                }
        }
@@@ -1304,9 -1302,9 +1308,9 @@@ void GameCommand_reducematchtime(float 
                default:
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd reducematchtime\n");
-                       LOG_INFO("  No arguments required.\n");
-                       LOG_INFO("See also: ^2extendmatchtime^7\n");
+                       LOG_INFO("Usage:^3 sv_cmd reducematchtime");
+                       LOG_INFO("  No arguments required.");
+                       LOG_INFO("See also: ^2extendmatchtime^7");
                        return;
                }
        }
@@@ -1328,12 -1326,12 +1332,12 @@@ void GameCommand_setbots(float request
                }
  
                default:
-                       LOG_INFO("Incorrect parameters for ^2setbots^7\n");
+                       LOG_INFO("Incorrect parameters for ^2setbots^7");
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd setbots botnumber\n");
-                       LOG_INFO("  Where 'botnumber' is the amount of bots to set bot_number cvar to.\n");
-                       LOG_INFO("See also: ^2bot_cmd^7\n");
+                       LOG_INFO("Usage:^3 sv_cmd setbots botnumber");
+                       LOG_INFO("  Where 'botnumber' is the amount of bots to set bot_number cvar to.");
+                       LOG_INFO("See also: ^2bot_cmd^7");
                        return;
                }
        }
@@@ -1347,7 -1345,7 +1351,7 @@@ void GameCommand_shuffleteams(float req
                {
                        if (!teamplay)
                        {
-                               LOG_INFO("Can't shuffle teams when currently not playing a team game.\n");
+                               LOG_INFO("Can't shuffle teams when currently not playing a team game.");
                                return;
                        }
  
                                        // we could theoretically assign forced players to their teams
                                        // and shuffle the rest to fill the empty spots but in practise
                                        // either all players or none are gonna have forced teams
-                                       LOG_INFO("Can't shuffle teams because at least one player has a forced team.\n");
+                                       LOG_INFO("Can't shuffle teams because at least one player has a forced team.");
                                        return;
                                }
                        });
                default:
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd shuffleteams\n");
-                       LOG_INFO("  No arguments required.\n");
-                       LOG_INFO("See also: ^2moveplayer, allspec^7\n");
+                       LOG_INFO("Usage:^3 sv_cmd shuffleteams");
+                       LOG_INFO("  No arguments required.");
+                       LOG_INFO("See also: ^2moveplayer, allspec^7");
                        return;
                }
        }
@@@ -1409,11 -1407,11 +1413,11 @@@ void GameCommand_stuffto(float request
                                        if (accepted > 0)
                                        {
                                                stuffcmd(client, strcat("\n", argv(next_token), "\n"));
-                                               LOG_INFO(strcat("Command: \"", argv(next_token), "\" sent to ", GetCallerName(client), " (", argv(1), ").\n"));
+                                               LOG_INFO("Command: \"", argv(next_token), "\" sent to ", GetCallerName(client), " (", argv(1), ").");
                                        }
                                        else
                                        {
-                                               LOG_INFO("stuffto: ", GetClientErrorString(accepted, argv(1)), ".\n");
+                                               LOG_INFO("stuffto: ", GetClientErrorString(accepted, argv(1)), ".");
                                        }
  
                                        return;
                        }
  
                        default:
-                               LOG_INFO("Incorrect parameters for ^2stuffto^7\n");
+                               LOG_INFO("Incorrect parameters for ^2stuffto^7");
                        case CMD_REQUEST_USAGE:
                        {
-                               LOG_INFO("\nUsage:^3 sv_cmd stuffto client \"command\"\n");
-                               LOG_INFO("  'client' is the entity number or name of the player,\n");
-                               LOG_INFO("  and 'command' is the command to be sent to that player.\n");
+                               LOG_INFO("Usage:^3 sv_cmd stuffto client \"command\"");
+                               LOG_INFO("  'client' is the entity number or name of the player,");
+                               LOG_INFO("  and 'command' is the command to be sent to that player.");
                                return;
                        }
                }
  #else
                if (request)
                {
-                       LOG_INFO("stuffto command is not enabled on this server.\n");
+                       LOG_INFO("stuffto command is not enabled on this server.");
                        return;
                }
  #endif
@@@ -1454,7 -1452,7 +1458,7 @@@ void GameCommand_trace(float request, f
                                case "debug":
                                {
                                        float hitcount = 0;
-                                       LOG_INFO("TEST CASE. If this returns the runaway loop counter error, possibly everything is oaky.\n");
+                                       LOG_INFO("TEST CASE. If this returns the runaway loop counter error, possibly everything is oaky.");
                                        float worst_endpos_bug = 0;
                                        for ( ; ; )
                                        {
                                                                        }
                                                                }
  
-                                                               LOG_INFO("safe distance to back off: ", ftos(safe * vlen(p - start)), "qu\n");
-                                                               LOG_INFO("unsafe distance to back off: ", ftos(unsafe * vlen(p - start)), "qu\n");
+                                                               LOG_INFO("safe distance to back off: ", ftos(safe * vlen(p - start)), "qu");
+                                                               LOG_INFO("unsafe distance to back off: ", ftos(unsafe * vlen(p - start)), "qu");
  
                                                                tracebox(p, PL_MIN_CONST + '0.1 0.1 0.1', PL_MAX_CONST - '0.1 0.1 0.1', p, MOVE_NOMONSTERS, NULL);
-                                                               if (trace_startsolid) LOG_INFO("trace_endpos much in solid when tracing from ", vtos(start), " to ", vtos(end), " endpos ", vtos(p), "\n");
-                                                               else LOG_INFO("trace_endpos just in solid when tracing from ", vtos(start), " to ", vtos(end), " endpos ", vtos(p), "\n");
+                                                               if (trace_startsolid) LOG_INFO("trace_endpos much in solid when tracing from ", vtos(start), " to ", vtos(end), " endpos ", vtos(p));
+                                                               else LOG_INFO("trace_endpos just in solid when tracing from ", vtos(start), " to ", vtos(end), " endpos ", vtos(p));
                                                                if (++hitcount >= 10) break;
                                                        }
                                                        else
                                                                if (dq > worst_endpos_bug)
                                                                {
                                                                        worst_endpos_bug = dq;
-                                                                       LOG_INFO("trace_endpos still before solid when tracing from ", vtos(start), " to ", vtos(end), " endpos ", vtos(p), "\n");
-                                                                       LOG_INFO("could go ", ftos(dq), " units further to ", vtos(q), "\n");
+                                                                       LOG_INFO("trace_endpos still before solid when tracing from ", vtos(start), " to ", vtos(end), " endpos ", vtos(p));
+                                                                       LOG_INFO("could go ", ftos(dq), " units further to ", vtos(q));
                                                                        if (++hitcount >= 10) break;
                                                                }
                                                        }
                                        vv = trace_endpos;
                                        if (trace_fraction == 1)
                                        {
-                                               LOG_INFO("not above ground, aborting\n");
+                                               LOG_INFO("not above ground, aborting");
                                                return;
                                        }
                                        f = 0;
                                                dv = randomvec();
                                                if (dv.z > 0) dv = -1 * dv;
                                                tracebox(vv, e.mins, e.maxs, vv + dv, MOVE_NORMAL, e);
-                                               if (trace_startsolid) LOG_INFO("bug 1\n");
+                                               if (trace_startsolid) LOG_INFO("bug 1");
                                                if (trace_fraction == 1)
                                                {
                                                        if (dv.z < f)
                                                        {
                                                                LOG_INFO("bug 2: ", ftos(dv.x), " ", ftos(dv.y), " ", ftos(dv.z));
-                                                               LOG_INFO(" (", ftos(asin(dv.z / vlen(dv)) * 180 / M_PI), " degrees)\n");
+                                                               LOG_INFO(" (", ftos(asin(dv.z / vlen(dv)) * 180 / M_PI), " degrees)");
                                                                f = dv.z;
                                                        }
                                                }
                                        }
-                                       LOG_INFO("highest possible dist: ", ftos(f), "\n");
+                                       LOG_INFO("highest possible dist: ", ftos(f));
                                        return;
                                }
  
                                        if (argc == 4)
                                        {
                                                e = nextent(NULL);
-                                               if (tracewalk(e, stov(argv(2)), e.mins, e.maxs, stov(argv(3)), MOVE_NORMAL)) LOG_INFO("can walk\n");
-                                               else LOG_INFO("cannot walk\n");
+                                               if (tracewalk(e, stov(argv(2)), e.mins, e.maxs, stov(argv(3)), MOVE_NORMAL)) LOG_INFO("can walk");
+                                               else LOG_INFO("cannot walk");
                                                return;
                                        }
                                }
                }
  
                default:
-                       LOG_INFO("Incorrect parameters for ^2trace^7\n");
+                       LOG_INFO("Incorrect parameters for ^2trace^7");
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd trace command (startpos endpos)\n");
-                       LOG_INFO("  Full list of commands here: \"debug, debug2, walk, showline.\"\n");
-                       LOG_INFO("See also: ^2bbox, gettaginfo^7\n");
+                       LOG_INFO("Usage:^3 sv_cmd trace command (startpos endpos)");
+                       LOG_INFO("  Full list of commands here: \"debug, debug2, walk, showline.\"");
+                       LOG_INFO("See also: ^2bbox, gettaginfo^7");
                        return;
                }
        }
@@@ -1629,9 -1627,9 +1633,9 @@@ void GameCommand_unlockteams(float requ
                default:
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd unlockteams\n");
-                       LOG_INFO("  No arguments required.\n");
-                       LOG_INFO("See also: ^2lockteams^7\n");
+                       LOG_INFO("Usage:^3 sv_cmd unlockteams");
+                       LOG_INFO("  No arguments required.");
+                       LOG_INFO("See also: ^2lockteams^7");
                        return;
                }
        }
@@@ -1648,17 -1646,17 +1652,17 @@@ void GameCommand_warp(float request, fl
                                if (argc >= 2)
                                {
                                        CampaignLevelWarp(stof(argv(1)));
-                                       LOG_INFO("Successfully warped to campaign level ", argv(1), ".\n");
+                                       LOG_INFO("Successfully warped to campaign level ", argv(1), ".");
                                }
                                else
                                {
                                        CampaignLevelWarp(-1);
-                                       LOG_INFO("Successfully warped to next campaign level.\n");
+                                       LOG_INFO("Successfully warped to next campaign level.");
                                }
                        }
                        else
                        {
-                               LOG_INFO("Not in campaign, can't level warp\n");
+                               LOG_INFO("Not in campaign, can't level warp");
                        }
                        return;
                }
                default:
                case CMD_REQUEST_USAGE:
                {
-                       LOG_INFO("\nUsage:^3 sv_cmd warp [level]\n");
-                       LOG_INFO("  'level' is the level to change campaign mode to.\n");
-                       LOG_INFO("  if 'level' is not provided it will change to the next level.\n");
+                       LOG_INFO("Usage:^3 sv_cmd warp [level]");
+                       LOG_INFO("  'level' is the level to change campaign mode to.");
+                       LOG_INFO("  if 'level' is not provided it will change to the next level.");
                        return;
                }
        }
@@@ -1737,7 -1735,7 +1741,7 @@@ SERVER_COMMAND(warp, "Choose different 
  
  void GameCommand_macro_help()
  {
-       FOREACH(SERVER_COMMANDS, true, { LOG_INFOF("  ^2%s^7: %s\n", it.m_name, it.m_description); });
+       FOREACH(SERVER_COMMANDS, true, { LOG_INFOF("  ^2%s^7: %s", it.m_name, it.m_description); });
  }
  
  float GameCommand_macro_command(float argc, string command)
@@@ -1784,20 -1782,22 +1788,22 @@@ void GameCommand(string command
        {
                if (argc == 1)
                {
-                       LOG_INFO("\nServer console commands:\n");
+                       LOG_INFO("Server console commands:");
                        GameCommand_macro_help();
  
-                       LOG_INFO("\nBanning commands:\n");
+                       LOG_INFO("\nBanning commands:");
                        BanCommand_macro_help();
  
-                       LOG_INFO("\nCommon networked commands:\n");
+                       LOG_INFO("\nCommon networked commands:");
                        CommonCommand_macro_help(NULL);
  
-                       LOG_INFO("\nGeneric commands shared by all programs:\n");
+                       LOG_INFO("\nGeneric commands shared by all programs:");
                        GenericCommand_macro_help();
  
-                       LOG_INFO("\nUsage:^3 sv_cmd COMMAND...^7, where possible commands are listed above.\n");
-                       LOG_INFO("For help about a specific command, type sv_cmd help COMMAND\n");
+                       LOG_INFO(
+                           "\nUsage:^3 sv_cmd COMMAND...^7, where possible commands are listed above.\n"
+                 "For help about a specific command, type sv_cmd help COMMAND"
+             );
  
                        return;
                }
        }
  
        // nothing above caught the command, must be invalid
-       LOG_INFO(((command != "") ? strcat("Unknown server command \"", command, "\"") : "No command provided"), ". For a list of supported commands, try sv_cmd help.\n");
+       LOG_INFO(((command != "") ? strcat("Unknown server command \"", command, "\"") : "No command provided"), ". For a list of supported commands, try sv_cmd help.");
  }
diff --combined qcsrc/server/player.qc
index e41bf931b7cf1d1402fcf74aedad0aca9d4193f7,44a877791b19673f539ac4dd065ffe5eaafa032f..dd061526265c12501deb2add452d694b5b7752be
@@@ -1,5 -1,6 +1,6 @@@
  #include "player.qh"
  
+ #include <common/effects/all.qh>
  #include "bot/api.qh"
  #include "cheats.qh"
  #include "g_damage.qh"
@@@ -21,7 -22,7 +22,7 @@@
  #include "../common/minigames/sv_minigames.qh"
  
  #include "../common/physics/player.qh"
- #include "../common/effects/qc/all.qh"
+ #include "../common/effects/qc/_mod.qh"
  #include "../common/mutators/mutator/waypoints/waypointsprites.qh"
  #include "../common/triggers/include.qh"
  #include "../common/wepent.qh"
@@@ -316,9 -317,9 +317,9 @@@ void PlayerDamage(entity this, entity i
  
        if(!DEATH_ISSPECIAL(deathtype))
        {
-               damage *= bound(1.0, this.cvar_cl_handicap, 10.0);
-               if(this != attacker)
-                       damage /= bound(1.0, attacker.cvar_cl_handicap, 10.0);
+               damage *= bound(1.0, CS(this).cvar_cl_handicap, 10.0);
+               if(this != attacker && IS_PLAYER(attacker))
+                       damage /= bound(1.0, CS(attacker).cvar_cl_handicap, 10.0);
        }
  
        if (time < this.spawnshieldtime && autocvar_g_spawnshield_blockdamage < 1)
        if (this != attacker) {
                float realdmg = damage - excess;
                if (IS_PLAYER(attacker)) {
-                       PlayerScore_Add(attacker, SP_DMG, realdmg);
+                       GameRules_scoring_add(attacker, DMG, realdmg);
                }
                if (IS_PLAYER(this)) {
-                       PlayerScore_Add(this, SP_DMGTAKEN, realdmg);
+                       GameRules_scoring_add(this, DMGTAKEN, realdmg);
                }
        }
  
        }
  }
  
 -void MoveToTeam(entity client, int team_colour, int type)
 +bool MoveToTeam(entity client, int team_colour, int type)
  {
        int lockteams_backup = lockteams;  // backup any team lock
        lockteams = 0;  // disable locked teams
        TeamchangeFrags(client);  // move the players frags
 -      SetPlayerColors(client, team_colour - 1);  // set the players colour
 +      if (!SetPlayerTeamSimple(client, team_colour))
 +      {
 +              return false;
 +      }
        Damage(client, client, client, 100000, DEATH_AUTOTEAMCHANGE.m_id, client.origin, '0 0 0');  // kill the player
        lockteams = lockteams_backup;  // restore the team lock
        LogTeamchange(client.playerid, client.team, type);
 +      return true;
  }
  
  /** print(), but only print if the server is not local */
@@@ -942,7 -939,7 +943,7 @@@ int Say(entity source, int teamsay, ent
        }
  
        if(flood)
-               LOG_INFO("NOTE: ", playername(source, true), "^7 is flooding.\n");
+               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)
index 5ee25f5bf369d4574c1d4064734672e906784c9b,2b539c999668bbcc5acca5304ef53100f7e37285..d46d95bd1c6114ee5138527c74cb726afc8a7dab
@@@ -1,8 -1,10 +1,11 @@@
  #include "scores_rules.qh"
  
+ #include <server/defs.qh>
+ #include <server/miscfunctions.qh>
  #include "client.qh"
  #include "scores.qh"
+ #include <common/gamemodes/rules.qh>
 +#include "teamplay.qh"
  
  int ScoreRules_teams;
  
@@@ -56,17 -58,13 +59,13 @@@ void ScoreRules_basics_end(
  }
  void ScoreRules_generic()
  {
-       if(teamplay)
-       {
+     int teams = 0;
+       if (teamplay) {
                CheckAllowedTeams(NULL);
-               int teams = 0;
-               if(c1 >= 0) teams |= BIT(0);
-               if(c2 >= 0) teams |= BIT(1);
-               if(c3 >= 0) teams |= BIT(2);
-               if(c4 >= 0) teams |= BIT(3);
-               ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, true);
+               if (c1 >= 0) teams |= BIT(0);
+               if (c2 >= 0) teams |= BIT(1);
+               if (c3 >= 0) teams |= BIT(2);
+               if (c4 >= 0) teams |= BIT(3);
        }
-       else
-               ScoreRules_basics(0, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, true);
-       ScoreRules_basics_end();
+       GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, {});
  }
diff --combined qcsrc/server/teamplay.qc
index dfa9b89f996a04ab398dcd020c4eb412fdffa8aa,e93a03201a9ddf4233b229126b69d15bca035de6..77c65319d4da888a35c6afe6c211430896d80d20
@@@ -37,13 -37,6 +37,6 @@@ void default_delayedinit(entity this
                ScoreRules_generic();
  }
  
- void ActivateTeamplay()
- {
-       serverflags |= SERVERFLAG_TEAMPLAY;
-       teamplay = 1;
-       cvar_set("teamplay", "2");  // DP needs this for sending proper getstatus replies.
- }
  void InitGameplayMode()
  {
        VoteReset();
        max_shot_distance = min(230000, vlen(world.maxs - world.mins));
  
        MapInfo_LoadMapSettings(mapname);
-       serverflags &= ~SERVERFLAG_TEAMPLAY;
-       teamplay = 0;
-       cvar_set("teamplay", "0");  // DP needs this for sending proper getstatus replies.
+       GameRules_teams(false);
  
        if (!cvar_value_issafe(world.fog))
        {
-               LOG_INFO("The current map contains a potentially harmful fog setting, ignored\n");
+               LOG_INFO("The current map contains a potentially harmful fog setting, ignored");
                world.fog = string_null;
        }
        if(MapInfo_Map_fog != "")
@@@ -169,79 -160,58 +160,79 @@@ void setcolor(entity this, int clr
  #endif
  }
  
 -void SetPlayerColors(entity pl, float _color)
 +void SetPlayerColors(entity player, float _color)
  {
 -      /*string s;
 -      s = ftos(cl);
 -      stuffcmd(pl, strcat("color ", s, " ", s, "\n")  );
 -      pl.team = cl + 1;
 -      //pl.clientcolors = pl.clientcolors - (pl.clientcolors & 15) + cl;
 -      pl.clientcolors = 16*cl + cl;*/
 -
 -      float pants, shirt;
 -      pants = _color & 0x0F;
 -      shirt = _color & 0xF0;
 -
 -
 -      if(teamplay) {
 -              setcolor(pl, 16*pants + pants);
 -      } else {
 -              setcolor(pl, shirt + pants);
 +      float pants = _color & 0x0F;
 +      float shirt = _color & 0xF0;
 +      if (teamplay)
 +      {
 +              setcolor(player, 16 * pants + pants);
 +      }
 +      else
 +      {
 +              setcolor(player, shirt + pants);
        }
  }
  
 -void SetPlayerTeam(entity pl, float t, float s, float noprint)
 +void KillPlayerForTeamChange(entity player)
  {
 -      float _color;
 -
 -      if(t == 4)
 -              _color = NUM_TEAM_4 - 1;
 -      else if(t == 3)
 -              _color = NUM_TEAM_3 - 1;
 -      else if(t == 2)
 -              _color = NUM_TEAM_2 - 1;
 -      else
 -              _color = NUM_TEAM_1 - 1;
 -
 -      SetPlayerColors(pl,_color);
 -
 -      if(t != s) {
 -              LogTeamchange(pl.playerid, pl.team, 3);  // log manual team join
 +      if (IS_DEAD(player))
 +      {
 +              return;
 +      }
 +      if (MUTATOR_CALLHOOK(Player_ChangeTeamKill, player) == true)
 +      {
 +              return;
 +      }
 +      Damage(player, player, player, 100000, DEATH_TEAMCHANGE.m_id, player.origin,
 +              '0 0 0');
 +}
  
 -              if(!noprint)
 -                      bprint(playername(pl, false), "^7 has changed from ", Team_NumberToColoredFullName(s), "^7 to ", Team_NumberToColoredFullName(t), "\n");
 +bool SetPlayerTeamSimple(entity player, int team_num)
 +{
 +      if (player.team == team_num)
 +      {
 +              // This is important when players join the game and one of their color
 +              // matches the team color while other doesn't. For example [BOT]Lion.
 +              SetPlayerColors(player, team_num - 1);
 +              return true;
        }
 +      if (MUTATOR_CALLHOOK(Player_ChangeTeam, player, Team_TeamToNumber(
 +              player.team), Team_TeamToNumber(team_num)) == true)
 +      {
 +              // Mutator has blocked team change.
 +              return false;
 +      }
 +      int old_team = player.team;
 +      SetPlayerColors(player, team_num - 1);
 +      MUTATOR_CALLHOOK(Player_ChangedTeam, player, old_team, player.team);
 +      return true;
 +}
  
 +bool SetPlayerTeam(entity player, int destination_team, int source_team,
 +      bool no_print)
 +{
 +      int team_num = Team_NumberToTeam(destination_team);
 +      if (!SetPlayerTeamSimple(player, team_num))
 +      {
 +              return false;
 +      }
 +      LogTeamchange(player.playerid, player.team, 3);  // log manual team join
 +      if (no_print)
 +      {
 +              return true;
 +      }
 +      bprint(playername(player, false), "^7 has changed from ", Team_NumberToColoredFullName(source_team), "^7 to ", Team_NumberToColoredFullName(destination_team), "\n");
 +      return true;
  }
  
  // set c1...c4 to show what teams are allowed
 -void CheckAllowedTeams (entity for_whom)
 +void CheckAllowedTeams(entity for_whom)
  {
        int teams_mask = 0;
  
        c1 = c2 = c3 = c4 = -1;
 -      cb1 = cb2 = cb3 = cb4 = 0;
 +      num_bots_team1 = num_bots_team2 = num_bots_team3 = num_bots_team4 = 0;
  
        string teament_name = string_null;
  
@@@ -343,570 -313,284 +334,570 @@@ float PlayerValue(entity p
  // teams that are allowed will now have their player counts stored in c1...c4
  void GetTeamCounts(entity ignore)
  {
 -      float value, bvalue;
 -      // now count how many players are on each team already
 -
 -      // FIXME: also find and memorize the lowest-scoring bot on each team (in case players must be shuffled around)
 -      // also remember the lowest-scoring player
 -
 -      FOREACH_CLIENT(true, {
 -              float t;
 -              if(IS_PLAYER(it) || it.caplayer)
 -                      t = it.team;
 -              else if(it.team_forced > 0)
 -                      t = it.team_forced; // reserve the spot
 -              else
 -                      continue;
 -              if(it != ignore)// && it.netname != "")
 +      if (MUTATOR_CALLHOOK(GetTeamCounts) == true)
 +      {
 +              if (c1 >= 0)
 +              {
 +                      MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_1, ignore, c1,
 +                              num_bots_team1, lowest_human_team1, lowest_bot_team1);
 +                      c1 = M_ARGV(2, float);
 +                      num_bots_team1 = M_ARGV(3, float);
 +                      lowest_human_team1 = M_ARGV(4, entity);
 +                      lowest_bot_team1 = M_ARGV(5, entity);
 +              }
 +              if (c2 >= 0)
 +              {
 +                      MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_2, ignore, c2,
 +                              num_bots_team2, lowest_human_team2, lowest_bot_team2);
 +                      c2 = M_ARGV(2, float);
 +                      num_bots_team2 = M_ARGV(3, float);
 +                      lowest_human_team2 = M_ARGV(4, entity);
 +                      lowest_bot_team2 = M_ARGV(5, entity);
 +              }
 +              if (c3 >= 0)
 +              {
 +                      MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_3, ignore, c3,
 +                              num_bots_team3, lowest_human_team3, lowest_bot_team3);
 +                      c3 = M_ARGV(2, float);
 +                      num_bots_team3 = M_ARGV(3, float);
 +                      lowest_human_team3 = M_ARGV(4, entity);
 +                      lowest_bot_team3 = M_ARGV(5, entity);
 +              }
 +              if (c4 >= 0)
 +              {
 +                      MUTATOR_CALLHOOK(GetTeamCount, NUM_TEAM_4, ignore,
 +                              c4, num_bots_team4, lowest_human_team4, lowest_bot_team4);
 +                      c4 = M_ARGV(2, float);
 +                      num_bots_team4 = M_ARGV(3, float);
 +                      lowest_human_team4 = M_ARGV(4, entity);
 +                      lowest_bot_team4 = M_ARGV(5, entity);
 +              }
 +      }
 +      else
 +      {
 +              float value, bvalue;
 +              // now count how many players are on each team already
 +              float lowest_human_score1 = FLOAT_MAX;
 +              float lowest_bot_score1 = FLOAT_MAX;
 +              float lowest_human_score2 = FLOAT_MAX;
 +              float lowest_bot_score2 = FLOAT_MAX;
 +              float lowest_human_score3 = FLOAT_MAX;
 +              float lowest_bot_score3 = FLOAT_MAX;
 +              float lowest_human_score4 = FLOAT_MAX;
 +              float lowest_bot_score4 = FLOAT_MAX;
 +              FOREACH_CLIENT(true,
                {
 +                      float t;
 +                      if (IS_PLAYER(it) || it.caplayer)
 +                      {
 +                              t = it.team;
 +                      }
 +                      else if (it.team_forced > 0)
 +                      {
 +                              t = it.team_forced; // reserve the spot
 +                      }
 +                      else
 +                      {
 +                              continue;
 +                      }
 +                      if (it == ignore)
 +                      {
 +                              continue;
 +                      }
                        value = PlayerValue(it);
 -                      if(IS_BOT_CLIENT(it))
 +                      if (IS_BOT_CLIENT(it))
 +                      {
                                bvalue = value;
 +                      }
                        else
 +                      {
                                bvalue = 0;
 -                      if(t == NUM_TEAM_1)
 +                      }
 +                      if (value == 0)
                        {
 -                              if(c1 >= 0)
 -                              {
 -                                      c1 = c1 + value;
 -                                      cb1 = cb1 + bvalue;
 -                              }
 +                              continue;
                        }
 -                      else if(t == NUM_TEAM_2)
 +                      switch (t)
                        {
 -                              if(c2 >= 0)
 +                              case NUM_TEAM_1:
                                {
 -                                      c2 = c2 + value;
 -                                      cb2 = cb2 + bvalue;
 +                                      if (c1 < 0)
 +                                      {
 +                                              break;
 +                                      }
 +                                      c1 += value;
 +                                      num_bots_team1 += bvalue;
 +                                      float temp_score = PlayerScore_Get(it, SP_SCORE);
 +                                      if (!bvalue)
 +                                      {
 +                                              if (temp_score < lowest_human_score1)
 +                                              {
 +                                                      lowest_human_team1 = it;
 +                                                      lowest_human_score1 = temp_score;
 +                                              }
 +                                              break;
 +                                      }
 +                                      if (temp_score < lowest_bot_score1)
 +                                      {
 +                                              lowest_bot_team1 = it;
 +                                              lowest_bot_score1 = temp_score;
 +                                      }
 +                                      break;
                                }
 -                      }
 -                      else if(t == NUM_TEAM_3)
 -                      {
 -                              if(c3 >= 0)
 +                              case NUM_TEAM_2:
                                {
 -                                      c3 = c3 + value;
 -                                      cb3 = cb3 + bvalue;
 +                                      if (c2 < 0)
 +                                      {
 +                                              break;
 +                                      }
 +                                      c2 += value;
 +                                      num_bots_team2 += bvalue;
 +                                      float temp_score = PlayerScore_Get(it, SP_SCORE);
 +                                      if (!bvalue)
 +                                      {
 +                                              if (temp_score < lowest_human_score2)
 +                                              {
 +                                                      lowest_human_team2 = it;
 +                                                      lowest_human_score2 = temp_score;
 +                                              }
 +                                              break;
 +                                      }
 +                                      if (temp_score < lowest_bot_score2)
 +                                      {
 +                                              lowest_bot_team2 = it;
 +                                              lowest_bot_score2 = temp_score;
 +                                      }
 +                                      break;
                                }
 -                      }
 -                      else if(t == NUM_TEAM_4)
 -                      {
 -                              if(c4 >= 0)
 +                              case NUM_TEAM_3:
 +                              {
 +                                      if (c3 < 0)
 +                                      {
 +                                              break;
 +                                      }
 +                                      c3 += value;
 +                                      num_bots_team3 += bvalue;
 +                                      float temp_score = PlayerScore_Get(it, SP_SCORE);
 +                                      if (!bvalue)
 +                                      {
 +                                              if (temp_score < lowest_human_score3)
 +                                              {
 +                                                      lowest_human_team3 = it;
 +                                                      lowest_human_score3 = temp_score;
 +                                              }
 +                                              break;
 +                                      }
 +                                      if (temp_score < lowest_bot_score3)
 +                                      {
 +                                              lowest_bot_team3 = it;
 +                                              lowest_bot_score3 = temp_score;
 +                                      }
 +                                      break;
 +                              }
 +                              case NUM_TEAM_4:
                                {
 -                                      c4 = c4 + value;
 -                                      cb4 = cb4 + bvalue;
 +                                      if (c4 < 0)
 +                                      {
 +                                              break;
 +                                      }
 +                                      c4 += value;
 +                                      num_bots_team4 += bvalue;
 +                                      float temp_score = PlayerScore_Get(it, SP_SCORE);
 +                                      if (!bvalue)
 +                                      {
 +                                              if (temp_score < lowest_human_score4)
 +                                              {
 +                                                      lowest_human_team4 = it;
 +                                                      lowest_human_score4 = temp_score;
 +                                              }
 +                                              break;
 +                                      }
 +                                      if (temp_score < lowest_bot_score4)
 +                                      {
 +                                              lowest_bot_team4 = it;
 +                                              lowest_bot_score4 = temp_score;
 +                                      }
 +                                      break;
                                }
                        }
 -              }
 -      });
 +              });
 +      }
  
        // if the player who has a forced team has not joined yet, reserve the spot
        if(autocvar_g_campaign)
        {
                switch(autocvar_g_campaign_forceteam)
                {
 -                      case 1: if(c1 == cb1) ++c1; break;
 -                      case 2: if(c2 == cb2) ++c2; break;
 -                      case 3: if(c3 == cb3) ++c3; break;
 -                      case 4: if(c4 == cb4) ++c4; break;
 +                      case 1: if(c1 == num_bots_team1) ++c1; break;
 +                      case 2: if(c2 == num_bots_team2) ++c2; break;
 +                      case 3: if(c3 == num_bots_team3) ++c3; break;
 +                      case 4: if(c4 == num_bots_team4) ++c4; break;
                }
        }
  }
  
 -float TeamSmallerEqThanTeam(float ta, float tb, entity e)
 +bool IsTeamSmallerThanTeam(int team_a, int team_b, entity player,
 +      bool use_score)
  {
 +      if (team_a == team_b)
 +      {
 +              return false;
 +      }
        // we assume that CheckAllowedTeams and GetTeamCounts have already been called
 -      float f;
 -      float ca = -1, cb = -1, cba = 0, cbb = 0, sa = 0, sb = 0;
 -
 -      switch(ta)
 +      int num_players_team_a = -1, num_players_team_b = -1;
 +      int num_bots_team_a = 0, num_bots_team_b = 0;
 +      float score_team_a = 0, score_team_b = 0;
 +      switch (team_a)
        {
 -              case 1: ca = c1; cba = cb1; sa = team1_score; break;
 -              case 2: ca = c2; cba = cb2; sa = team2_score; break;
 -              case 3: ca = c3; cba = cb3; sa = team3_score; break;
 -              case 4: ca = c4; cba = cb4; sa = team4_score; break;
 +              case 1:
 +              {
 +                      num_players_team_a = c1;
 +                      num_bots_team_a = num_bots_team1;
 +                      score_team_a = team1_score;
 +                      break;
 +              }
 +              case 2:
 +              {
 +                      num_players_team_a = c2;
 +                      num_bots_team_a = num_bots_team2;
 +                      score_team_a = team2_score;
 +                      break;
 +              }
 +              case 3:
 +              {
 +                      num_players_team_a = c3;
 +                      num_bots_team_a = num_bots_team3;
 +                      score_team_a = team3_score;
 +                      break;
 +              }
 +              case 4:
 +              {
 +                      num_players_team_a = c4;
 +                      num_bots_team_a = num_bots_team4;
 +                      score_team_a = team4_score;
 +                      break;
 +              }
        }
 -      switch(tb)
 +      switch (team_b)
        {
 -              case 1: cb = c1; cbb = cb1; sb = team1_score; break;
 -              case 2: cb = c2; cbb = cb2; sb = team2_score; break;
 -              case 3: cb = c3; cbb = cb3; sb = team3_score; break;
 -              case 4: cb = c4; cbb = cb4; sb = team4_score; break;
 +              case 1:
 +              {
 +                      num_players_team_b = c1;
 +                      num_bots_team_b = num_bots_team1;
 +                      score_team_b = team1_score;
 +                      break;
 +              }
 +              case 2:
 +              {
 +                      num_players_team_b = c2;
 +                      num_bots_team_b = num_bots_team2;
 +                      score_team_b = team2_score;
 +                      break;
 +              }
 +              case 3:
 +              {
 +                      num_players_team_b = c3;
 +                      num_bots_team_b = num_bots_team3;
 +                      score_team_b = team3_score;
 +                      break;
 +              }
 +              case 4:
 +              {
 +                      num_players_team_b = c4;
 +                      num_bots_team_b = num_bots_team4;
 +                      score_team_b = team4_score;
 +                      break;
 +              }
        }
 -
        // invalid
 -      if(ca < 0 || cb < 0)
 +      if (num_players_team_a < 0 || num_players_team_b < 0)
 +      {
                return false;
 -
 -      // equal
 -      if(ta == tb)
 +      }
 +      if (IS_REAL_CLIENT(player) && bots_would_leave)
 +      {
 +              num_players_team_a -= num_bots_team_a;
 +              num_players_team_b -= num_bots_team_b;
 +      }
 +      if (!use_score)
 +      {
 +              return num_players_team_a < num_players_team_b;
 +      }
 +      if (num_players_team_a < num_players_team_b)
 +      {
                return true;
 +      }
 +      if (num_players_team_a > num_players_team_b)
 +      {
 +              return false;
 +      }
 +      return score_team_a < score_team_b;
 +}
  
 -      if(IS_REAL_CLIENT(e))
 +bool IsTeamEqualToTeam(int team_a, int team_b, entity player, bool use_score)
 +{
 +      if (team_a == team_b)
        {
 -              if(bots_would_leave)
 +              return true;
 +      }
 +      // we assume that CheckAllowedTeams and GetTeamCounts have already been called
 +      int num_players_team_a = -1, num_players_team_b = -1;
 +      int num_bots_team_a = 0, num_bots_team_b = 0;
 +      float score_team_a = 0, score_team_b = 0;
 +      switch (team_a)
 +      {
 +              case 1:
                {
 -                      ca -= cba * 0.999;
 -                      cb -= cbb * 0.999;
 +                      num_players_team_a = c1;
 +                      num_bots_team_a = num_bots_team1;
 +                      score_team_a = team1_score;
 +                      break;
 +              }
 +              case 2:
 +              {
 +                      num_players_team_a = c2;
 +                      num_bots_team_a = num_bots_team2;
 +                      score_team_a = team2_score;
 +                      break;
 +              }
 +              case 3:
 +              {
 +                      num_players_team_a = c3;
 +                      num_bots_team_a = num_bots_team3;
 +                      score_team_a = team3_score;
 +                      break;
 +              }
 +              case 4:
 +              {
 +                      num_players_team_a = c4;
 +                      num_bots_team_a = num_bots_team4;
 +                      score_team_a = team4_score;
 +                      break;
                }
        }
 +      switch (team_b)
 +      {
 +              case 1:
 +              {
 +                      num_players_team_b = c1;
 +                      num_bots_team_b = num_bots_team1;
 +                      score_team_b = team1_score;
 +                      break;
 +              }
 +              case 2:
 +              {
 +                      num_players_team_b = c2;
 +                      num_bots_team_b = num_bots_team2;
 +                      score_team_b = team2_score;
 +                      break;
 +              }
 +              case 3:
 +              {
 +                      num_players_team_b = c3;
 +                      num_bots_team_b = num_bots_team3;
 +                      score_team_b = team3_score;
 +                      break;
 +              }
 +              case 4:
 +              {
 +                      num_players_team_b = c4;
 +                      num_bots_team_b = num_bots_team4;
 +                      score_team_b = team4_score;
 +                      break;
 +              }
 +      }
 +      // invalid
 +      if (num_players_team_a < 0 || num_players_team_b < 0)
 +              return false;
  
 -      // keep teams alive (teams of size 0 always count as smaller, ignoring score)
 -      if(ca < 1)
 -              if(cb >= 1)
 -                      return true;
 -      if(ca >= 1)
 -              if(cb < 1)
 -                      return false;
 -
 -      // first, normalize
 -      f = max(ca, cb, 1);
 -      ca /= f;
 -      cb /= f;
 -      f = max(sa, sb, 1);
 -      sa /= f;
 -      sb /= f;
 -
 -      // the more we're at the end of the match, the more take scores into account
 -      f = bound(0, game_completion_ratio * autocvar_g_balance_teams_scorefactor, 1);
 -      ca += (sa - ca) * f;
 -      cb += (sb - cb) * f;
 +      if (IS_REAL_CLIENT(player) && bots_would_leave)
 +      {
 +              num_players_team_a -= num_bots_team_a;
 +              num_players_team_b -= num_bots_team_b;
 +      }
 +      if (!use_score)
 +      {
 +              return num_players_team_a == num_players_team_b;
 +      }
 +      if (num_players_team_a != num_players_team_b)
 +      {
 +              return false;
 +      }
 +      return score_team_a == score_team_b;
 +}
  
 -      return ca <= cb;
 +int FindBestTeams(entity player, bool use_score)
 +{
 +      if (MUTATOR_CALLHOOK(FindBestTeams, player) == true)
 +      {
 +              return M_ARGV(1, float);
 +      }
 +      int team_bits = 0;
 +      int previous_team = 0;
 +      if (c1 >= 0)
 +      {
 +              team_bits = BIT(0);
 +              previous_team = 1;
 +      }
 +      if (c2 >= 0)
 +      {
 +              if (previous_team == 0)
 +              {
 +                      team_bits = BIT(1);
 +                      previous_team = 2;
 +              }
 +              else if (IsTeamSmallerThanTeam(2, previous_team, player, use_score))
 +              {
 +                      team_bits = BIT(1);
 +                      previous_team = 2;
 +              }
 +              else if (IsTeamEqualToTeam(2, previous_team, player, use_score))
 +              {
 +                      team_bits |= BIT(1);
 +                      previous_team = 2;
 +              }
 +      }
 +      if (c3 >= 0)
 +      {
 +              if (previous_team == 0)
 +              {
 +                      team_bits = BIT(2);
 +                      previous_team = 3;
 +              }
 +              else if (IsTeamSmallerThanTeam(3, previous_team, player, use_score))
 +              {
 +                      team_bits = BIT(2);
 +                      previous_team = 3;
 +              }
 +              else if (IsTeamEqualToTeam(3, previous_team, player, use_score))
 +              {
 +                      team_bits |= BIT(2);
 +                      previous_team = 3;
 +              }
 +      }
 +      if (c4 >= 0)
 +      {
 +              if (previous_team == 0)
 +              {
 +                      team_bits = BIT(3);
 +              }
 +              else if (IsTeamSmallerThanTeam(4, previous_team, player, use_score))
 +              {
 +                      team_bits = BIT(3);
 +              }
 +              else if (IsTeamEqualToTeam(4, previous_team, player, use_score))
 +              {
 +                      team_bits |= BIT(3);
 +              }
 +      }
 +      return team_bits;
  }
  
  // returns # of smallest team (1, 2, 3, 4)
  // NOTE: Assumes CheckAllowedTeams has already been called!
 -float FindSmallestTeam(entity pl, float ignore_pl)
 +int FindSmallestTeam(entity player, float ignore_player)
  {
 -      int totalteams = 0;
 -      int t = 1; // initialize with a random team?
 -      if(c4 >= 0) t = 4;
 -      if(c3 >= 0) t = 3;
 -      if(c2 >= 0) t = 2;
 -      if(c1 >= 0) t = 1;
 -
 -      // find out what teams are available
 -      //CheckAllowedTeams();
 -
 -      // make sure there are at least 2 teams to join
 -      if(c1 >= 0)
 -              totalteams = totalteams + 1;
 -      if(c2 >= 0)
 -              totalteams = totalteams + 1;
 -      if(c3 >= 0)
 -              totalteams = totalteams + 1;
 -      if(c4 >= 0)
 -              totalteams = totalteams + 1;
 -
 -      if((autocvar_bot_vs_human || pl.team_forced > 0) && totalteams == 1)
 -              totalteams += 1;
 -
 -      if(totalteams <= 1)
 +      // count how many players are in each team
 +      if (ignore_player)
        {
 -              if(autocvar_g_campaign && pl && IS_REAL_CLIENT(pl))
 -                      return 1; // special case for campaign and player joining
 -              else if(totalteams == 1) // single team
 -                      LOG_TRACEF("Only 1 team available for %s, you may need to fix your map", MapInfo_Type_ToString(MapInfo_CurrentGametype()));
 -              else // no teams, major no no
 -                      error(sprintf("No teams available for %s\n", MapInfo_Type_ToString(MapInfo_CurrentGametype())));
 +              GetTeamCounts(player);
        }
 -
 -      // count how many players are in each team
 -      if(ignore_pl)
 -              GetTeamCounts(pl);
        else
 +      {
                GetTeamCounts(NULL);
 -
 +      }
 +      int team_bits = FindBestTeams(player, true);
 +      if (team_bits == 0)
 +      {
 +              error(sprintf("No teams available for %s\n", MapInfo_Type_ToString(MapInfo_CurrentGametype())));
 +      }
        RandomSelection_Init();
 -
 -      if(TeamSmallerEqThanTeam(1, t, pl))
 -              t = 1;
 -      if(TeamSmallerEqThanTeam(2, t, pl))
 -              t = 2;
 -      if(TeamSmallerEqThanTeam(3, t, pl))
 -              t = 3;
 -      if(TeamSmallerEqThanTeam(4, t, pl))
 -              t = 4;
 -
 -      // now t is the minimum, or A minimum!
 -      if(t == 1 || TeamSmallerEqThanTeam(1, t, pl))
 +      if ((team_bits & BIT(0)) != 0)
 +      {
                RandomSelection_AddFloat(1, 1, 1);
 -      if(t == 2 || TeamSmallerEqThanTeam(2, t, pl))
 +      }
 +      if ((team_bits & BIT(1)) != 0)
 +      {
                RandomSelection_AddFloat(2, 1, 1);
 -      if(t == 3 || TeamSmallerEqThanTeam(3, t, pl))
 +      }
 +      if ((team_bits & BIT(2)) != 0)
 +      {
                RandomSelection_AddFloat(3, 1, 1);
 -      if(t == 4 || TeamSmallerEqThanTeam(4, t, pl))
 +      }
 +      if ((team_bits & BIT(3)) != 0)
 +      {
                RandomSelection_AddFloat(4, 1, 1);
 -
 +      }
        return RandomSelection_chosen_float;
  }
  
 -int JoinBestTeam(entity this, bool only_return_best, bool forcebestteam)
 +int JoinBestTeam(entity this, bool only_return_best, bool force_best_team)
  {
 -      float smallest, selectedteam;
 -
        // don't join a team if we're not playing a team game
 -      if(!teamplay)
 +      if (!teamplay)
 +      {
                return 0;
 +      }
  
        // find out what teams are available
        CheckAllowedTeams(this);
  
        // if we don't care what team he ends up on, put him on whatever team he entered as.
        // if he's not on a valid team, then let other code put him on the smallest team
 -      if(!forcebestteam)
 +      if (!force_best_team)
        {
 +              int selected_team;
                if(     c1 >= 0 && this.team == NUM_TEAM_1)
 -                      selectedteam = this.team;
 +                      selected_team = this.team;
                else if(c2 >= 0 && this.team == NUM_TEAM_2)
 -                      selectedteam = this.team;
 +                      selected_team = this.team;
                else if(c3 >= 0 && this.team == NUM_TEAM_3)
 -                      selectedteam = this.team;
 +                      selected_team = this.team;
                else if(c4 >= 0 && this.team == NUM_TEAM_4)
 -                      selectedteam = this.team;
 +                      selected_team = this.team;
                else
 -                      selectedteam = -1;
 +                      selected_team = -1;
  
 -              if(selectedteam > 0)
 +              if (selected_team > 0)
                {
 -                      if(!only_return_best)
 +                      if (!only_return_best)
                        {
 -                              SetPlayerColors(this, selectedteam - 1);
 +                              SetPlayerTeamSimple(this, selected_team);
  
                                // when JoinBestTeam is called by client.qc/ClientKill_Now_TeamChange the players team is -1 and thus skipped
                                // when JoinBestTeam is called by client.qc/ClientConnect the player_id is 0 the log attempt is rejected
                                LogTeamchange(this.playerid, this.team, 99);
                        }
 -                      return selectedteam;
 +                      return selected_team;
                }
                // otherwise end up on the smallest team (handled below)
        }
  
 -      smallest = FindSmallestTeam(this, true);
 -
 -      if(!only_return_best && !this.bot_forced_team)
 +      int best_team = FindSmallestTeam(this, true);
 +      if (only_return_best || this.bot_forced_team)
        {
 -              TeamchangeFrags(this);
 -              if(smallest == 1)
 -              {
 -                      SetPlayerColors(this, NUM_TEAM_1 - 1);
 -              }
 -              else if(smallest == 2)
 -              {
 -                      SetPlayerColors(this, NUM_TEAM_2 - 1);
 -              }
 -              else if(smallest == 3)
 -              {
 -                      SetPlayerColors(this, NUM_TEAM_3 - 1);
 -              }
 -              else if(smallest == 4)
 -              {
 -                      SetPlayerColors(this, NUM_TEAM_4 - 1);
 -              }
 -              else
 -              {
 -                      error("smallest team: invalid team\n");
 -              }
 -
 -              LogTeamchange(this.playerid, this.team, 2); // log auto join
 -
 -              if(!IS_DEAD(this))
 -                      Damage(this, this, this, 100000, DEATH_TEAMCHANGE.m_id, this.origin, '0 0 0');
 +              return best_team;
        }
 -
 -      return smallest;
 +      best_team = Team_NumberToTeam(best_team);
 +      if (best_team == -1)
 +      {
 +              error("JoinBestTeam: invalid team\n");
 +      }
 +      int old_team = Team_TeamToNumber(this.team);
 +      TeamchangeFrags(this);
 +      SetPlayerTeamSimple(this, best_team);
 +      LogTeamchange(this.playerid, this.team, 2); // log auto join
 +      if (!IS_BOT_CLIENT(this))
 +      {
 +              AutoBalanceBots(old_team, Team_TeamToNumber(best_team));
 +      }
 +      KillPlayerForTeamChange(this);
 +      return best_team;
  }
  
 -//void() ctf_playerchanged;
  void SV_ChangeTeam(entity this, float _color)
  {
 -      float scolor, dcolor, steam, dteam; //, dbotcount, scount, dcount;
 +      float source_color, destination_color, source_team, destination_team;
  
        // in normal deathmatch we can just apply the color and we're done
        if(!teamplay)
        if(!teamplay)
                return;
  
 -      scolor = this.clientcolors & 0x0F;
 -      dcolor = _color & 0x0F;
 -
 -      if(scolor == NUM_TEAM_1 - 1)
 -              steam = 1;
 -      else if(scolor == NUM_TEAM_2 - 1)
 -              steam = 2;
 -      else if(scolor == NUM_TEAM_3 - 1)
 -              steam = 3;
 -      else // if(scolor == NUM_TEAM_4 - 1)
 -              steam = 4;
 -      if(dcolor == NUM_TEAM_1 - 1)
 -              dteam = 1;
 -      else if(dcolor == NUM_TEAM_2 - 1)
 -              dteam = 2;
 -      else if(dcolor == NUM_TEAM_3 - 1)
 -              dteam = 3;
 -      else // if(dcolor == NUM_TEAM_4 - 1)
 -              dteam = 4;
 +      source_color = this.clientcolors & 0x0F;
 +      destination_color = _color & 0x0F;
 +
 +      source_team = Team_TeamToNumber(source_color + 1);
 +      destination_team = Team_TeamToNumber(destination_color + 1);
  
        CheckAllowedTeams(this);
  
 -      if(dteam == 1 && c1 < 0) dteam = 4;
 -      if(dteam == 4 && c4 < 0) dteam = 3;
 -      if(dteam == 3 && c3 < 0) dteam = 2;
 -      if(dteam == 2 && c2 < 0) dteam = 1;
 +      if (destination_team == 1 && c1 < 0) destination_team = 4;
 +      if (destination_team == 4 && c4 < 0) destination_team = 3;
 +      if (destination_team == 3 && c3 < 0) destination_team = 2;
 +      if (destination_team == 2 && c2 < 0) destination_team = 1;
  
        // not changing teams
 -      if(scolor == dcolor)
 +      if (source_color == destination_color)
        {
 -              //bprint("same team change\n");
 -              SetPlayerTeam(this, dteam, steam, true);
 +              SetPlayerTeam(this, destination_team, source_team, true);
                return;
        }
  
        }
  
        // autocvar_g_balance_teams_prevent_imbalance only makes sense if autocvar_g_balance_teams is on, as it makes the team selection dialog pointless
 -      if(autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance)
 +      if (autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance)
        {
                GetTeamCounts(this);
 -              if(!TeamSmallerEqThanTeam(dteam, steam, this))
 +              if ((BIT(destination_team - 1) & FindBestTeams(this, false)) == 0)
                {
                        Send_Notification(NOTIF_ONE, this, MSG_INFO, INFO_TEAMCHANGE_LARGERTEAM);
                        return;
                }
        }
 -
 -//    bprint("allow change teams from ", ftos(steam), " to ", ftos(dteam), "\n");
 -
 -      if(IS_PLAYER(this) && steam != dteam)
 +      if(IS_PLAYER(this) && source_team != destination_team)
        {
                // reduce frags during a team change
                TeamchangeFrags(this);
        }
 -
 -      MUTATOR_CALLHOOK(Player_ChangeTeam, this, steam, dteam);
 -
 -      SetPlayerTeam(this, dteam, steam, !IS_CLIENT(this));
 -
 -      if(IS_PLAYER(this) && steam != dteam)
 +      if (!SetPlayerTeam(this, destination_team, source_team, !IS_CLIENT(this)))
 +      {
 +              return;
 +      }
 +      AutoBalanceBots(source_team, destination_team);
 +      if (!IS_PLAYER(this) || (source_team == destination_team))
        {
 -              // kill player when changing teams
 -              if(!IS_DEAD(this))
 -                      Damage(this, this, this, 100000, DEATH_TEAMCHANGE.m_id, this.origin, '0 0 0');
 +              return;
        }
 +      KillPlayerForTeamChange(this);
  }
  
 -void ShufflePlayerOutOfTeam (float source_team)
 +void AutoBalanceBots(int source_team, int destination_team)
  {
 -      float smallestteam, smallestteam_count, steam;
 -      float lowest_bot_score, lowest_player_score;
 -      entity lowest_bot, lowest_player, selected;
 -
 -      smallestteam = 0;
 -      smallestteam_count = 999999999;
 -
 -      if(c1 >= 0 && c1 < smallestteam_count)
 -      {
 -              smallestteam = 1;
 -              smallestteam_count = c1;
 -      }
 -      if(c2 >= 0 && c2 < smallestteam_count)
 -      {
 -              smallestteam = 2;
 -              smallestteam_count = c2;
 -      }
 -      if(c3 >= 0 && c3 < smallestteam_count)
 +      if ((source_team == -1) || (destination_team == -1))
        {
 -              smallestteam = 3;
 -              smallestteam_count = c3;
 +              return;
        }
 -      if(c4 >= 0 && c4 < smallestteam_count)
 +      if (!autocvar_g_balance_teams ||
 +              !autocvar_g_balance_teams_prevent_imbalance)
        {
 -              smallestteam = 4;
 -              smallestteam_count = c4;
 -      }
 -
 -      if(!smallestteam)
 -      {
 -              bprint("warning: no smallest team\n");
                return;
        }
 -
 -      if(source_team == 1)
 -              steam = NUM_TEAM_1;
 -      else if(source_team == 2)
 -              steam = NUM_TEAM_2;
 -      else if(source_team == 3)
 -              steam = NUM_TEAM_3;
 -      else // if(source_team == 4)
 -              steam = NUM_TEAM_4;
 -
 -      lowest_bot = NULL;
 -      lowest_bot_score = 999999999;
 -      lowest_player = NULL;
 -      lowest_player_score = 999999999;
 -
 -      // find the lowest-scoring player & bot of that team
 -      FOREACH_CLIENT(IS_PLAYER(it) && it.team == steam, {
 -              if(it.isbot)
 +      int num_players_source_team = 0;
 +      int num_players_destination_team = 0;
 +      entity lowest_bot_destination_team = NULL;
 +      switch (source_team)
 +      {
 +              case 1:
                {
 -                      if(it.totalfrags < lowest_bot_score)
 -                      {
 -                              lowest_bot = it;
 -                              lowest_bot_score = it.totalfrags;
 -                      }
 +                      num_players_source_team = c1;
 +                      break;
                }
 -              else
 +              case 2:
                {
 -                      if(it.totalfrags < lowest_player_score)
 -                      {
 -                              lowest_player = it;
 -                              lowest_player_score = it.totalfrags;
 -                      }
 +                      num_players_source_team = c2;
 +                      break;
 +              }
 +              case 3:
 +              {
 +                      num_players_source_team = c3;
 +                      break;
 +              }
 +              case 4:
 +              {
 +                      num_players_source_team = c4;
 +                      break;
                }
 -      });
 -
 -      // prefers to move a bot...
 -      if(lowest_bot != NULL)
 -              selected = lowest_bot;
 -      // but it will move a player if it has to
 -      else
 -              selected = lowest_player;
 -      // don't do anything if it couldn't find anyone
 -      if(!selected)
 -      {
 -              bprint("warning: couldn't find a player to move from team\n");
 -              return;
 -      }
 -
 -      // smallest team gains a member
 -      if(smallestteam == 1)
 -      {
 -              c1 = c1 + 1;
 -      }
 -      else if(smallestteam == 2)
 -      {
 -              c2 = c2 + 1;
 -      }
 -      else if(smallestteam == 3)
 -      {
 -              c3 = c3 + 1;
 -      }
 -      else if(smallestteam == 4)
 -      {
 -              c4 = c4 + 1;
 -      }
 -      else
 -      {
 -              bprint("warning: destination team invalid\n");
 -              return;
 -      }
 -      // source team loses a member
 -      if(source_team == 1)
 -      {
 -              c1 = c1 + 1;
 -      }
 -      else if(source_team == 2)
 -      {
 -              c2 = c2 + 2;
 -      }
 -      else if(source_team == 3)
 -      {
 -              c3 = c3 + 3;
        }
 -      else if(source_team == 4)
 +      switch (destination_team)
        {
 -              c4 = c4 + 4;
 +              case 1:
 +              {
 +                      num_players_destination_team = c1;
 +                      lowest_bot_destination_team = lowest_bot_team1;
 +                      break;
 +              }
 +              case 2:
 +              {
 +                      num_players_destination_team = c2;
 +                      lowest_bot_destination_team = lowest_bot_team2;
 +                      break;
 +              }
 +              case 3:
 +              {
 +                      num_players_destination_team = c3;
 +                      lowest_bot_destination_team = lowest_bot_team3;
 +                      break;
 +              }
 +              case 4:
 +              {
 +                      num_players_destination_team = c4;
 +                      lowest_bot_destination_team = lowest_bot_team4;
 +                      break;
 +              }
        }
 -      else
 +      if ((num_players_destination_team <= num_players_source_team) ||
 +              (lowest_bot_destination_team == NULL))
        {
 -              bprint("warning: source team invalid\n");
                return;
        }
 -
 -      // move the player to the new team
 -      TeamchangeFrags(selected);
 -      SetPlayerTeam(selected, smallestteam, source_team, false);
 -
 -      if(!IS_DEAD(selected))
 -              Damage(selected, selected, selected, 100000, DEATH_AUTOTEAMCHANGE.m_id, selected.origin, '0 0 0');
 -      Send_Notification(NOTIF_ONE, selected, MSG_CENTER, CENTER_DEATH_SELF_AUTOTEAMCHANGE, selected.team);
 +      SetPlayerTeamSimple(lowest_bot_destination_team,
 +              Team_NumberToTeam(source_team));
 +      KillPlayerForTeamChange(lowest_bot_destination_team);
  }
diff --combined qcsrc/server/teamplay.qh
index 5311d4cb01d54d37c056333feb3a566e518296d6,8d0ea9cb8ae6d846c326fd59b66732295bf7ee6e..1813db04d8d111387a6a1667c6033e9b2e890f6c
@@@ -3,29 -3,10 +3,29 @@@
  string cache_mutatormsg;
  string cache_lastmutatormsg;
  
 -// client counts for each team
 -//float c1, c2, c3, c4;
 -// # of bots on those teams
 -float cb1, cb2, cb3, cb4;
 +// The following variables are used for balancing. They are not updated
 +// automatically. You need to call CheckAllowedTeams and GetTeamCounts to get
 +// proper values.
 +
 +// These four have 2 different states. If they are equal to -1, it means that
 +// the player can't join the team. Zero or positive value means that player can
 +// join the team and means the number of players on that team.
 +float c1;
 +float c2;
 +float c3;
 +float c4;
 +float num_bots_team1; ///< Number of bots in the first team.
 +float num_bots_team2; ///< Number of bots in the second team.
 +float num_bots_team3; ///< Number of bots in the third team.
 +float num_bots_team4; ///< Number of bots in the fourth team.
 +entity lowest_human_team1; ///< Human with the lowest score in the first team.
 +entity lowest_human_team2; ///< Human with the lowest score in the second team.
 +entity lowest_human_team3; ///< Human with the lowest score in the third team.
 +entity lowest_human_team4; ///< Human with the lowest score in the fourth team.
 +entity lowest_bot_team1; ///< Bot with the lowest score in the first team.
 +entity lowest_bot_team2; ///< Bot with the lowest score in the second team.
 +entity lowest_bot_team3; ///< Bot with the lowest score in the third team.
 +entity lowest_bot_team4; ///< Bot with the lowest score in the fourth team.
  
  int redowned, blueowned, yellowowned, pinkowned;
  
@@@ -37,38 -18,18 +37,36 @@@ void LogTeamchange(float player_id, flo
  
  void default_delayedinit(entity this);
  
- void ActivateTeamplay();
  void InitGameplayMode();
  
  string GetClientVersionMessage(entity this);
  
  string getwelcomemessage(entity this);
  
 -void SetPlayerColors(entity pl, float _color);
 +void SetPlayerColors(entity player, float _color);
  
 -void SetPlayerTeam(entity pl, float t, float s, float noprint);
 +/// \brief Kills player as a result of team change.
 +/// \param[in,out] player Player to kill.
 +/// \return No return.
 +void KillPlayerForTeamChange(entity player);
 +
 +/// \brief Sets the team of the player.
 +/// \param[in,out] player Player to adjust.
 +/// \param[in] team_num Team number to set. See TEAM_NUM constants.
 +/// \return True if team switch was successful, false otherwise.
 +bool SetPlayerTeamSimple(entity player, int team_num);
 +
 +/// \brief Sets the team of the player.
 +/// \param[in,out] player Player to adjust.
 +/// \param[in] destination_team Team to set.
 +/// \param[in] source_team Previous team of the player.
 +/// \param[in] no_print Whether to print this event to players' console.
 +/// \return True if team switch was successful, false otherwise.
 +bool SetPlayerTeam(entity player, int destination_team, int source_team,
 +      bool no_print);
  
  // set c1...c4 to show what teams are allowed
 -void CheckAllowedTeams (entity for_whom);
 +void CheckAllowedTeams(entity for_whom);
  
  float PlayerValue(entity p);
  
  // teams that are allowed will now have their player counts stored in c1...c4
  void GetTeamCounts(entity ignore);
  
 -float TeamSmallerEqThanTeam(float ta, float tb, entity e);
 +/// \brief Returns whether one team is smaller than the other.
 +/// \param[in] team_a First team.
 +/// \param[in] team_b Second team.
 +/// \param[in] player Player to check.
 +/// \param[in] use_score Whether to take into account team scores.
 +/// \return True if first team is smaller than the second one, false otherwise.
 +/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have
 +/// been called.
 +bool IsTeamSmallerThanTeam(int team_a, int team_b, entity player,
 +      bool use_score);
 +
 +/// \brief Returns whether one team is equal to the other.
 +/// \param[in] team_a First team.
 +/// \param[in] team_b Second team.
 +/// \param[in] player Player to check.
 +/// \param[in] use_score Whether to take into account team scores.
 +/// \return True if first team is equal to the second one, false otherwise.
 +/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have
 +/// been called.
 +bool IsTeamEqualToTeam(int team_a, int team_b, entity player, bool use_score);
 +
 +/// \brief Returns the bitmask of the best teams for the player to join.
 +/// \param[in] player Player to check.
 +/// \param[in] use_score Whether to take into account team scores.
 +/// \return Bitmask of the best teams for the player to join.
 +/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have
 +/// been called.
 +int FindBestTeams(entity player, bool use_score);
  
  // returns # of smallest team (1, 2, 3, 4)
  // NOTE: Assumes CheckAllowedTeams has already been called!
 -float FindSmallestTeam(entity pl, float ignore_pl);
 -
 -int JoinBestTeam(entity this, bool only_return_best, bool forcebestteam);
 +int FindSmallestTeam(entity player, float ignore_player);
  
 -//void() ctf_playerchanged;
 +int JoinBestTeam(entity this, bool only_return_best, bool force_best_team);
  
 -void ShufflePlayerOutOfTeam (float source_team);
 +/// \brief Auto balances bots in teams after the player has changed team.
 +/// \param[in] source_team Previous team of the player (1, 2, 3, 4).
 +/// \param[in] destination_team Current team of the player (1, 2, 3, 4).
 +/// \return No return.
 +/// \note This function assumes that CheckAllowedTeams and GetTeamCounts have
 +/// been called.
 +void AutoBalanceBots(int source_team, int destination_team);
  
  void setcolor(entity this, int clr);