]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into TimePath/scoreboard_elo
authorTimePath <andrew.hardaker1995@gmail.com>
Sat, 6 Aug 2016 06:59:04 +0000 (16:59 +1000)
committerTimePath <andrew.hardaker1995@gmail.com>
Sat, 6 Aug 2016 06:59:04 +0000 (16:59 +1000)
# Conflicts:
# .gitlab-ci.yml
# qcsrc/common/gamemodes/gamemode/nexball/nexball.qc
# qcsrc/server/mutators/mutator/gamemode_freezetag.qc
# qcsrc/server/mutators/mutator/gamemode_keyhunt.qc
# qcsrc/server/scores_rules.qc

22 files changed:
1  2 
qcsrc/client/hud/panel/infomessages.qc
qcsrc/client/hud/panel/modicons.qc
qcsrc/client/main.qc
qcsrc/client/main.qh
qcsrc/client/scoreboard.qc
qcsrc/common/gamemodes/gamemode/nexball/nexball.qc
qcsrc/common/gamemodes/gamemode/onslaught/onslaught.qc
qcsrc/dpdefs/doc.md
qcsrc/server/defs.qh
qcsrc/server/g_damage.qc
qcsrc/server/mutators/events.qh
qcsrc/server/mutators/mutator/gamemode_assault.qc
qcsrc/server/mutators/mutator/gamemode_ctf.qc
qcsrc/server/mutators/mutator/gamemode_ctf.qh
qcsrc/server/mutators/mutator/gamemode_cts.qc
qcsrc/server/mutators/mutator/gamemode_domination.qc
qcsrc/server/mutators/mutator/gamemode_freezetag.qc
qcsrc/server/mutators/mutator/gamemode_keepaway.qc
qcsrc/server/mutators/mutator/gamemode_keyhunt.qc
qcsrc/server/mutators/mutator/gamemode_race.qc
qcsrc/server/scores.qc
qcsrc/server/scores_rules.qc

index 855f1a79aad6c317067759bf9f0bbfed29642a98,1839263a4563b7f1cd7a73bc3f2db8abad12c15e..640bc404abbee5459a1df91e2929f1b2ac7c2abe
@@@ -5,12 -5,53 +5,53 @@@
  
  // Info messages panel (#14)
  
- #define drawInfoMessage(s) MACRO_BEGIN {                                                                                                                                                      \
-       if(autocvar_hud_panel_infomessages_flip)                                                                                                                                                \
-               o.x = pos.x + mySize.x - stringwidth(s, true, fontsize);                                                                                                        \
-       drawcolorcodedstring(o, s, fontsize, a, DRAWFLAG_NORMAL);                                                                                                               \
-       o.y += fontsize.y;                                                                                                                                                                                              \
+ float autocvar_hud_panel_infomessages_group_fadetime = 0.4;
+ float autocvar_hud_panel_infomessages_group_time = 6;
+ const int IMG_COUNT = 1; // number of InfoMessage Groups
+ float img_fade[IMG_COUNT];
+ int img_cur_msg[IMG_COUNT];
+ float img_time[IMG_COUNT];
+ int img_select(int group_id)
+ {
+       float fadetime = max(0.001, autocvar_hud_panel_infomessages_group_fadetime);
+       if(time > img_time[group_id])
+       {
+               img_fade[group_id] = max(0, img_fade[group_id] - frametime / fadetime);
+               if(!img_fade[group_id])
+               {
+                       ++img_cur_msg[group_id];
+                       img_time[group_id] = floor(time) + autocvar_hud_panel_infomessages_group_time;
+               }
+       }
+       else
+               img_fade[group_id] = min(1, img_fade[group_id] + frametime / fadetime);
+       return img_cur_msg[group_id];
+ }
+ float stringwidth_colors(string s, vector theSize);
+ vector InfoMessages_drawstring(string s, vector pos, vector sz, float a, vector fontsize)
+ {
+       getWrappedLine_remaining = s;
+       float offset = 0;
+       while(getWrappedLine_remaining)
+       {
+               s = getWrappedLine(sz.x - offset, fontsize, stringwidth_colors);
+               if(autocvar_hud_panel_infomessages_flip)
+                       offset = sz.x - stringwidth_colors(s, fontsize) - offset;
+               drawcolorcodedstring(pos + eX * offset, s, fontsize, a, DRAWFLAG_NORMAL);
+               pos.y += fontsize.y;
+               offset = fontsize.x;
+       }
+       pos.y += fontsize.y * 0.25;
+       return pos;
+ }
+ #define InfoMessage(s) MACRO_BEGIN { \
+       pos = InfoMessages_drawstring(s, pos, mySize, ((img_curr_group >= 0) ? panel_fg_alpha * img_fade[img_curr_group] : panel_fg_alpha), fontsize); \
+       img_curr_group = -1; \
  } MACRO_END
  void HUD_InfoMessages()
  {
        if(!autocvar__hud_configure)
                mySize -= '2 2 0' * panel_bg_padding;
        }
  
-       // always force 5:1 aspect
-       vector newSize = '0 0 0';
-       if(mySize.x/mySize.y > 5)
-       {
-               newSize.x = 5 * mySize.y;
-               newSize.y = mySize.y;
-               pos.x = pos.x + (mySize.x - newSize.x) / 2;
-       }
-       else
-       {
-               newSize.y = 1/5 * mySize.x;
-               newSize.x = mySize.x;
-               pos.y = pos.y + (mySize.y - newSize.y) / 2;
-       }
-       mySize = newSize;
-       entity tm;
-       vector o;
-       o = pos;
-       vector fontsize;
-       fontsize = '0.20 0.20 0' * mySize.y;
-       float a;
-       a = panel_fg_alpha;
+       vector fontsize = '0.2 0.2 0' * mySize.y;
        string s;
+       int img_curr_group = -1;
        if(!autocvar__hud_configure)
        {
                if(spectatee_status)
                {
-                       a = 1;
                        if(spectatee_status == -1)
                                s = _("^1Observing");
                        else
                                s = sprintf(_("^1Spectating: ^7%s"), entcs_GetName(current_player));
-                       drawInfoMessage(s);
+                       InfoMessage(s);
  
-                       if(spectatee_status == -1)
-                               s = sprintf(_("^1Press ^3%s^1 to spectate"), getcommandkey("primary fire", "+fire"));
-                       else
-                               s = sprintf(_("^1Press ^3%s^1 or ^3%s^1 for next or previous player"), getcommandkey("next weapon", "weapnext"), getcommandkey("previous weapon", "weapprev"));
-                       drawInfoMessage(s);
-                       if(spectatee_status == -1)
-                               s = sprintf(_("^1Use ^3%s^1 or ^3%s^1 to change the speed"), getcommandkey("next weapon", "weapnext"), getcommandkey("previous weapon", "weapprev"));
-                       else
-                               s = sprintf(_("^1Press ^3%s^1 to observe"), getcommandkey("secondary fire", "+fire2"));
-                       drawInfoMessage(s);
-                       s = sprintf(_("^1Press ^3%s^1 for gamemode info"), getcommandkey("server info", "+show_info"));
-                       drawInfoMessage(s);
+                       img_curr_group = 0;
+                       switch(img_select(img_curr_group) % 3)
+                       {
+                               default:
+                               case 0:
+                                       if(spectatee_status == -1)
+                                               s = sprintf(_("^1Press ^3%s^1 to spectate"), getcommandkey(_("primary fire"), "+fire"));
+                                       else
+                                               s = sprintf(_("^1Press ^3%s^1 or ^3%s^1 for next or previous player"), getcommandkey(_("next weapon"), "weapnext"), getcommandkey(_("previous weapon"), "weapprev"));
+                                       break;
+                               case 1:
+                                       if(spectatee_status == -1)
+                                               s = sprintf(_("^1Use ^3%s^1 or ^3%s^1 to change the speed"), getcommandkey(_("next weapon"), "weapnext"), getcommandkey(_("previous weapon"), "weapprev"));
+                                       else
+                                               s = sprintf(_("^1Press ^3%s^1 to observe, ^3%s^1 to change camera mode"), getcommandkey(_("secondary fire"), "+fire2"), getcommandkey(_("drop weapon"), "dropweapon"));
+                                       break;
+                               case 2:
+                                       s = sprintf(_("^1Press ^3%s^1 for gamemode info"), getcommandkey(_("server info"), "+show_info"));
+                                       break;
+                       }
+                       InfoMessage(s);
  
                        if(gametype == MAPINFO_TYPE_LMS)
                        {
                                entity sk;
                                sk = playerslots[player_localnum];
 -                              if(sk.(scores[ps_primary]) >= 666)
 +                              if(sk.(scores(ps_primary)) >= 666)
                                        s = _("^1Match has already begun");
 -                              else if(sk.(scores[ps_primary]) > 0)
 +                              else if(sk.(scores(ps_primary)) > 0)
                                        s = _("^1You have no more lives left");
                                else
-                                       s = sprintf(_("^1Press ^3%s^1 to join"), getcommandkey("jump", "+jump"));
+                                       s = sprintf(_("^1Press ^3%s^1 to join"), getcommandkey(_("jump"), "+jump"));
                        }
                        else
-                               s = sprintf(_("^1Press ^3%s^1 to join"), getcommandkey("jump", "+jump"));
-                       drawInfoMessage(s);
+                               s = sprintf(_("^1Press ^3%s^1 to join"), getcommandkey(_("jump"), "+jump"));
+                       InfoMessage(s);
                }
  
                if (time < STAT(GAMESTARTTIME))
                        //we need to ceil, otherwise the countdown would be off by .5 when using round()
                        float countdown = ceil(STAT(GAMESTARTTIME) - time);
                        s = sprintf(_("^1Game starts in ^3%d^1 seconds"), countdown);
-                       drawInfoMessage(s);
+                       InfoMessage(s);
                }
  
                if(warmup_stage)
                {
                        s = _("^2Currently in ^1warmup^2 stage!");
-                       drawInfoMessage(s);
+                       InfoMessage(s);
                }
  
                string blinkcolor;
                        if(ready_waiting_for_me)
                        {
                                if(warmup_stage)
-                                       s = sprintf(_("%sPress ^3%s%s to end warmup"), blinkcolor, getcommandkey("ready", "ready"), blinkcolor);
+                                       s = sprintf(_("%sPress ^3%s%s to end warmup"), blinkcolor, getcommandkey(_("ready"), "ready"), blinkcolor);
                                else
-                                       s = sprintf(_("%sPress ^3%s%s once you are ready"), blinkcolor, getcommandkey("ready", "ready"), blinkcolor);
+                                       s = sprintf(_("%sPress ^3%s%s once you are ready"), blinkcolor, getcommandkey(_("ready"), "ready"), blinkcolor);
                        }
                        else
                        {
                                else
                                        s = _("^2Waiting for others to ready up...");
                        }
-                       drawInfoMessage(s);
+                       InfoMessage(s);
                }
                else if(warmup_stage && !spectatee_status)
                {
-                       s = sprintf(_("^2Press ^3%s^2 to end warmup"), getcommandkey("ready", "ready"));
-                       drawInfoMessage(s);
+                       s = sprintf(_("^2Press ^3%s^2 to end warmup"), getcommandkey(_("ready"), "ready"));
+                       InfoMessage(s);
                }
  
                if(teamplay && !spectatee_status && gametype != MAPINFO_TYPE_CA && teamnagger)
                {
                        float ts_min = 0, ts_max = 0;
-                       tm = teams.sort_next;
+                       entity tm = teams.sort_next;
                        if (tm)
                        {
                                for (; tm.sort_next; tm = tm.sort_next)
                                {
                                        s = strcat(blinkcolor, _("Teamnumbers are unbalanced!"));
                                        tm = GetTeam(myteam, false);
-                                       if (tm)
-                                       if (tm.team != NUM_SPECTATOR)
-                                       if (tm.team_size == ts_max)
-                                               s = strcat(s, sprintf(_(" Press ^3%s%s to adjust"), getcommandkey("team menu", "menu_showteamselect"), blinkcolor));
-                                       drawInfoMessage(s);
+                                       if (tm && tm.team != NUM_SPECTATOR && tm.team_size == ts_max)
+                                               s = strcat(s, sprintf(_(" Press ^3%s%s to adjust"), getcommandkey(_("team menu"), "menu_showteamselect"), blinkcolor));
+                                       InfoMessage(s);
                                }
                        }
                }
+               if(autocvar_cl_showspectators)
+               if(num_spectators)
+               //if(spectatee_status != -1)
+               {
+                       s = ((spectatee_status) ? _("^1Spectating this player:") : _("^1Spectating you:"));
+                       //drawInfoMessage(s)
+                       int limit = min(num_spectators, MAX_SPECTATORS);
+                       for(int i = 0; i < limit; ++i)
+                       {
+                               float slot = spectatorlist[i];
+                               if(i == 0)
+                                       s = strcat(s, " ^7", entcs_GetName(slot));
+                               else
+                                       s = strcat("^7", entcs_GetName(slot));
+                               drawInfoMessage(s);
+                       }
+               }
        }
        else
        {
-               s = _("^7Press ^3ESC ^7to show HUD options.");
-               drawInfoMessage(s);
-               s = _("^3Doubleclick ^7a panel for panel-specific options.");
-               drawInfoMessage(s);
-               s = _("^3CTRL ^7to disable collision testing, ^3SHIFT ^7and");
-               drawInfoMessage(s);
-               s = _("^3ALT ^7+ ^3ARROW KEYS ^7for fine adjustments.");
-               drawInfoMessage(s);
+               InfoMessage(_("^7Press ^3ESC ^7to show HUD options."));
+               InfoMessage(_("^3Doubleclick ^7a panel for panel-specific options."));
+               InfoMessage(_("^3CTRL ^7to disable collision testing, ^3SHIFT ^7and"));
+               InfoMessage(_("^3ALT ^7+ ^3ARROW KEYS ^7for fine adjustments."));
        }
  }
index 732d823843f363e7f3d0bd2668a395925a03e10f,5901a16326ca40047480f5e71d67ae7d783f7cfd..2d6774e2efce12d7a94b2f14ba160f7a5c2cc712
@@@ -108,6 -108,7 +108,7 @@@ void HUD_Mod_CTF_Reset(
        redflag_statuschange_time = blueflag_statuschange_time = yellowflag_statuschange_time = pinkflag_statuschange_time = neutralflag_statuschange_time = 0;
  }
  
+ int autocvar__teams_available;
  void HUD_Mod_CTF(vector pos, vector mySize)
  {
        vector redflag_pos, blueflag_pos, yellowflag_pos, pinkflag_pos, neutralflag_pos;
        int redflag, blueflag, yellowflag, pinkflag, neutralflag; // current status
        float redflag_statuschange_elapsedtime = 0, blueflag_statuschange_elapsedtime = 0, yellowflag_statuschange_elapsedtime = 0, pinkflag_statuschange_elapsedtime = 0, neutralflag_statuschange_elapsedtime = 0; // time since the status changed
        bool ctf_oneflag; // one-flag CTF mode enabled/disabled
+       bool ctf_stalemate; // currently in stalemate
        int stat_items = STAT(CTF_FLAGSTATUS);
        float fs, fs2, fs3, size1, size2;
        vector e1, e2;
  
+       int nteams = autocvar__teams_available;
        redflag = (stat_items/CTF_RED_FLAG_TAKEN) & 3;
        blueflag = (stat_items/CTF_BLUE_FLAG_TAKEN) & 3;
        yellowflag = (stat_items/CTF_YELLOW_FLAG_TAKEN) & 3;
  
        ctf_oneflag = (stat_items & CTF_FLAG_NEUTRAL);
  
-       mod_active = (redflag || blueflag || yellowflag || pinkflag || neutralflag);
+       ctf_stalemate = (stat_items & CTF_STALEMATE);
+       mod_active = (redflag || blueflag || yellowflag || pinkflag || neutralflag || (stat_items & CTF_SHIELDED));
  
        if (autocvar__hud_configure) {
                redflag = 1;
                blueflag = 2;
-               if (team_count >= 3)
+               if (nteams & BIT(2))
                        yellowflag = 2;
-               if (team_count >= 4)
+               if (nteams & BIT(3))
                        pinkflag = 3;
                ctf_oneflag = neutralflag = 0; // disable neutral flag in hud editor?
        }
                                break; \
                } \
        } MACRO_END
-       X(red, myteam != NUM_TEAM_1);
-       X(blue, myteam != NUM_TEAM_2);
-       X(yellow, myteam != NUM_TEAM_3);
-       X(pink, myteam != NUM_TEAM_4);
-       X(neutral, true);
+       X(red, myteam != NUM_TEAM_1 && (nteams & BIT(0)));
+       X(blue, myteam != NUM_TEAM_2 && (nteams & BIT(1)));
+       X(yellow, myteam != NUM_TEAM_3 && (nteams & BIT(2)));
+       X(pink, myteam != NUM_TEAM_4 && (nteams & BIT(3)));
+       X(neutral, ctf_oneflag);
        #undef X
  
+       int tcount = 2;
+       if(nteams & BIT(2))
+               tcount = 3;
+       if(nteams & BIT(3))
+               tcount = 4;
        if (ctf_oneflag) {
                // hacky, but these aren't needed
                red_icon = red_icon_prevstatus = blue_icon = blue_icon_prevstatus = yellow_icon = yellow_icon_prevstatus = pink_icon = pink_icon_prevstatus = string_null;
                fs = fs2 = fs3 = 1;
-       } else switch (team_count) {
+       } else switch (tcount) {
                default:
                case 2: fs = 0.5; fs2 = 0.5; fs3 = 0.5; break;
                case 3: fs = 1; fs2 = 0.35; fs3 = 0.35; break;
  
        #define X(team) MACRO_BEGIN { \
                f = bound(0, team##flag_statuschange_elapsedtime * 2, 1); \
+               if (team##_icon && ctf_stalemate) \
+                       drawpic_aspect_skin(team##flag_pos, "flag_stalemate", flag_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL); \
                if (team##_icon_prevstatus && f < 1) \
                        drawpic_aspect_skin_expanding(team##flag_pos, team##_icon_prevstatus, flag_size, '1 1 1', panel_fg_alpha * team##_alpha_prevstatus, DRAWFLAG_NORMAL, f); \
                if (team##_icon) \
@@@ -518,9 -532,9 +532,9 @@@ void HUD_Mod_Race(vector pos, vector my
        entity me;
        me = playerslots[player_localnum];
        float score;
 -      score = me.(scores[ps_primary]);
 +      score = me.(scores(ps_primary));
  
 -      if(!(scores_flags[ps_primary] & SFL_TIME) || teamplay) // race/cts record display on HUD
 +      if(!(scores_flags(ps_primary) & SFL_TIME) || teamplay) // race/cts record display on HUD
                return; // no records in the actual race
  
        // clientside personal record
diff --combined qcsrc/client/main.qc
index e7a4e3ef8da3332f2fc173f1629195b6dcc6a924,2905b8b2da90e8ff51c6e135e6bac8206586a846..680e65ac05e50e33bd5b19832bb664cad6480b25
@@@ -185,8 -185,8 +185,8 @@@ void Shutdown(
  {
        WarpZone_Shutdown();
  
-       remove(teams);
-       remove(players);
+       delete(teams);
+       delete(players);
        db_close(binddb);
        db_close(tempdb);
        if(autocvar_cl_db_saveasdump)
@@@ -385,21 -385,23 +385,21 @@@ void Ent_RemovePlayerScore(entity this
        if(this.owner) {
                SetTeam(this.owner, -1);
                this.owner.gotscores = 0;
 -              for(int i = 0; i < MAX_SCORE; ++i) {
 -                      this.owner.(scores[i]) = 0; // clear all scores
 -              }
 +              FOREACH(Scores, true, {
 +                      this.owner.(scores(it)) = 0; // clear all scores
 +              });
        }
  }
  
  NET_HANDLE(ENT_CLIENT_SCORES, bool isnew)
  {
        make_pure(this);
 -      int i, n;
 -      bool isNew;
        entity o;
  
        // damnit -.- don't want to go change every single .sv_entnum in hud.qc AGAIN
        // (no I've never heard of M-x replace-string, sed, or anything like that)
 -      isNew = !this.owner; // workaround for DP bug
 -      n = ReadByte()-1;
 +      bool isNew = !this.owner; // workaround for DP bug
 +      int n = ReadByte()-1;
  
  #ifdef DP_CSQC_ENTITY_REMOVE_IS_B0RKED
        if(!isNew && n != this.sv_entnum)
        //playerchecker will do this for us later, if it has not already done so
  
      int sf, lf;
 -#if MAX_SCORE <= 8
 -      sf = ReadByte();
 -      lf = ReadByte();
 -#else
        sf = ReadShort();
        lf = ReadShort();
 -#endif
 -    int p;
 -      for(i = 0, p = 1; i < MAX_SCORE; ++i, p *= 2)
 -              if(sf & p)
 +      FOREACH(Scores, true, {
 +        int p = 1 << (i % 16);
 +              if (sf & p)
                {
 -                      if(lf & p)
 -                              o.(scores[i]) = ReadInt24_t();
 +                      if (lf & p)
 +                              o.(scores(it)) = ReadInt24_t();
                        else
 -                              o.(scores[i]) = ReadChar();
 +                              o.(scores(it)) = ReadChar();
                }
 +    });
  
        return = true;
  
@@@ -470,9 -476,9 +470,9 @@@ NET_HANDLE(ENT_CLIENT_TEAMSCORES, bool 
                if(sf & p)
                {
                        if(lf & p)
 -                              o.(teamscores[i]) = ReadInt24_t();
 +                              o.(teamscores(i)) = ReadInt24_t();
                        else
 -                              o.(teamscores[i]) = ReadChar();
 +                              o.(teamscores(i)) = ReadChar();
                }
  
        return = true;
@@@ -510,6 -516,22 +510,22 @@@ NET_HANDLE(ENT_CLIENT_CLIENTDATA, bool 
        else
                angles_held_status = 0;
  
+       if(f & 16)
+       {
+               num_spectators = ReadByte();
+               float i, slot;
+               for(i = 0; i < MAX_SPECTATORS; ++i)
+                       spectatorlist[i] = 0; // reset list first
+               for(i = 0; i < num_spectators; ++i)
+               {
+                       slot = ReadByte();
+                       spectatorlist[i] = slot - 1;
+               }
+       }
        return = true;
  
        if(newspectatee_status != spectatee_status)
@@@ -678,6 -700,8 +694,8 @@@ NET_HANDLE(ENT_CLIENT_SPAWNPOINT, bool 
        spn_origin.y = ReadCoord();
        spn_origin.z = ReadCoord();
  
+       this.team = (teamnum + 1);
        //if(is_new)
        //{
                this.origin = spn_origin;
                        precache_model(this.mdl);
                        setmodel(this, this.mdl);
                        this.drawmask = MASK_NORMAL;
-                       //this.movetype = MOVETYPE_NOCLIP;
+                       //this.move_movetype = MOVETYPE_NOCLIP;
                        //this.draw = Spawn_Draw;
+                       IL_PUSH(g_drawables, this);
                }*/
                if(autocvar_cl_spawn_point_particles)
                {
                        else { this.cnt = particleeffectnum(EFFECT_SPAWNPOINT_NEUTRAL); }
  
                        this.draw = Spawn_Draw;
+                       if (is_new) IL_PUSH(g_drawables, this);
                        setpredraw(this, Spawn_PreDraw);
                        this.fade_start = autocvar_cl_spawn_point_dist_min;
                        this.fade_end = autocvar_cl_spawn_point_dist_max;
@@@ -774,8 -800,8 +794,8 @@@ NET_HANDLE(ENT_CLIENT_SPAWNEVENT, bool 
  
  // CSQC_Ent_Update : Called every frame that the server has indicated an update to the SSQC / CSQC entity has occured.
  // The only parameter reflects if the entity is "new" to the client, meaning it just came into the client's PVS.
- void CSQC_Ent_Update(bool isnew)
- {ENGINE_EVENT();
+ void CSQC_Ent_Update(entity this, bool isnew)
+ {
        this.sourceLoc = __FILE__ ":" STR(__LINE__);
        int t = ReadByte();
  
                if (autocvar_developer_csqcentities)
              LOG_INFOF("CSQC_Ent_Update(%d) at %f with this=%i {.entnum=%d, .enttype=%d} t=%s (%d)\n", isnew, savetime, this, this.entnum, this.enttype, this.classname, t);
                done = it.m_read(this, NULL, isnew);
+               MUTATOR_CALLHOOK(Ent_Update, this, isnew);
                break;
        });
        time = savetime;
@@@ -854,8 -881,8 +875,8 @@@ void Ent_Remove(entity this
        // TODO possibly set more stuff to defaults
  }
  // CSQC_Ent_Remove : Called when the server requests a SSQC / CSQC entity to be removed.  Essentially call remove(this) as well.
- void CSQC_Ent_Remove()
- {ENGINE_EVENT();
+ void CSQC_Ent_Remove(entity this)
+ {
        if (autocvar_developer_csqcentities) LOG_INFOF("CSQC_Ent_Remove() with this=%i {.entnum=%d, .enttype=%d}\n", this, this.entnum, this.enttype);
        if (wasfreed(this))
        {
                return;
        }
        if (this.enttype) Ent_Remove(this);
-       remove(this);
+       delete(this);
  }
  
  void Gamemode_Init()
@@@ -931,16 -958,17 +952,16 @@@ NET_HANDLE(ENT_CLIENT_SCORES_INFO, boo
        make_pure(this);
        gametype = ReadInt24_t();
        HUD_ModIcons_SetFunc();
 -      for (int i = 0; i < MAX_SCORE; ++i)
 -      {
 -              if (scores_label[i]) strunzone(scores_label[i]);
 -              scores_label[i] = strzone(ReadString());
 -              scores_flags[i] = ReadByte();
 -      }
 +      FOREACH(Scores, true, {
 +              if (scores_label(it)) strunzone(scores_label(it));
 +              scores_label(it) = strzone(ReadString());
 +              scores_flags(it) = ReadByte();
 +      });
        for (int i = 0; i < MAX_TEAMSCORE; ++i)
        {
 -              if (teamscores_label[i]) strunzone(teamscores_label[i]);
 -              teamscores_label[i] = strzone(ReadString());
 -              teamscores_flags[i] = ReadByte();
 +              if (teamscores_label(i)) strunzone(teamscores_label(i));
 +              teamscores_label(i) = strzone(ReadString());
 +              teamscores_flags(i) = ReadByte();
        }
        return = true;
        HUD_InitScores();
@@@ -976,6 -1004,42 +997,42 @@@ NET_HANDLE(ENT_CLIENT_INIT, bool isnew
        if (!postinit) PostInit();
  }
  
+ float GetSpeedUnitFactor(int speed_unit)
+ {
+       switch(speed_unit)
+       {
+               default:
+               case 1:
+                       return 1.0;
+               case 2:
+                       return 0.0254;
+               case 3:
+                       return 0.0254 * 3.6;
+               case 4:
+                       return 0.0254 * 3.6 * 0.6213711922;
+               case 5:
+                       return 0.0254 * 1.943844492; // 1 m/s = 1.943844492 knots, because 1 knot = 1.852 km/h
+       }
+ }
+ string GetSpeedUnit(int speed_unit)
+ {
+       switch(speed_unit)
+       {
+               default:
+               case 1:
+                       return _(" qu/s");
+               case 2:
+                       return _(" m/s");
+               case 3:
+                       return _(" km/h");
+               case 4:
+                       return _(" mph");
+               case 5:
+                       return _(" knots");
+       }
+ }
  NET_HANDLE(TE_CSQC_RACE, bool isNew)
  {
        int b = ReadByte();
                        race_server_record = ReadInt24_t();
                        break;
                case RACE_NET_SPEED_AWARD:
-                       race_speedaward = ReadInt24_t();
+                       race_speedaward = ReadInt24_t() * GetSpeedUnitFactor(autocvar_hud_panel_physics_speed_unit);
                        if(race_speedaward_holder)
                                strunzone(race_speedaward_holder);
                        race_speedaward_holder = strzone(ReadString());
+                       if(race_speedaward_unit)
+                               strunzone(race_speedaward_unit);
+                       race_speedaward_unit = strzone(GetSpeedUnit(autocvar_hud_panel_physics_speed_unit));
                        break;
                case RACE_NET_SPEED_AWARD_BEST:
-                       race_speedaward_alltimebest = ReadInt24_t();
+                       race_speedaward_alltimebest = ReadInt24_t() * GetSpeedUnitFactor(autocvar_hud_panel_physics_speed_unit);
                        if(race_speedaward_alltimebest_holder)
                                strunzone(race_speedaward_alltimebest_holder);
                        race_speedaward_alltimebest_holder = strzone(ReadString());
+                       if(race_speedaward_alltimebest_unit)
+                               strunzone(race_speedaward_alltimebest_unit);
+                       race_speedaward_alltimebest_unit = strzone(GetSpeedUnit(autocvar_hud_panel_physics_speed_unit));
                        break;
                case RACE_NET_SERVER_RANKINGS:
                        float prevpos, del;
@@@ -1168,13 -1238,13 +1231,13 @@@ NET_HANDLE(TE_CSQC_WEAPONCOMPLAIN, boo
        }
  }
  
- string getcommandkey(string text, string command)
+ string _getcommandkey(string cmd_name, string command, bool forcename)
  {
        string keys;
        float n, j, k, l = 0;
  
        if (!autocvar_hud_showbinds)
-               return text;
+               return cmd_name;
  
        keys = db_get(binddb, command);
        if (keys == "")
  
        if (keys == "NO_KEY") {
                if (autocvar_hud_showbinds > 1)
-                       return sprintf(_("%s (not bound)"), text);
+                       return sprintf(_("%s (not bound)"), cmd_name);
                else
-                       return text;
+                       return cmd_name;
        }
-       else if (autocvar_hud_showbinds > 1)
-               return sprintf("%s (%s)", text, keys);
+       else if (autocvar_hud_showbinds > 1 || forcename)
+               return sprintf("%s (%s)", cmd_name, keys);
        else
                return keys;
  }
diff --combined qcsrc/client/main.qh
index ab3758ee8531a2119c555600eb9f7110f5258f99,b47836f23caca4f5ec4b3fb400d5f5e1034d5ee1..4822ffed29423b95cbad1a9ebb923dd16a825deb
@@@ -40,13 -40,36 +40,13 @@@ void LoadMenuSkinValues()
  // --------------------------------------------------------------------------
  // Scoreboard stuff
  
 -const int MAX_HUD_FIELDS = 16;
 +const int MAX_HUD_FIELDS = MAX_SCORE;
  
 -const int SP_END = -1;
 -
 -const int SP_PING = -2;
 -const int SP_NAME = -3;
 -const int SP_KDRATIO = -4;
 -const int SP_CLRATIO = -5;
 -const int SP_PL = -6;
 -const int SP_FRAGS = -7;
 -const int SP_SUM = -8;
 -
 -const int SP_SEPARATOR = -100;
 -
 -float hud_field[MAX_HUD_FIELDS + 1];
 +PlayerScoreField hud_field[MAX_HUD_FIELDS + 1];
  float hud_size[MAX_HUD_FIELDS + 1];
  string hud_title[MAX_HUD_FIELDS + 1];
  int hud_num_fields;
  
 -string scores_label[MAX_SCORE];
 -int scores_flags[MAX_SCORE];
 -string teamscores_label[MAX_SCORE];
 -int teamscores_flags[MAX_SCORE];
 -.int scores[MAX_SCORE];
 -.float teamscores[MAX_TEAMSCORE];
 -
 -#define IS_INCREASING(x) ( (x)&SFL_LOWER_IS_BETTER )
 -#define IS_DECREASING(x) ( !((x)&SFL_LOWER_IS_BETTER) )
 -
 -
  vector hud_fontsize;
  
  float RANKINGS_RECEIVED_CNT;
@@@ -62,11 -85,20 +62,20 @@@ entity teamslots[17];    // 17 teams (i
  .float eliminated;
  
  .void(entity) draw;
+ IntrusiveList g_drawables;
+ STATIC_INIT(g_drawables) { g_drawables = IL_NEW(); }
  .void(entity) draw2d;
+ IntrusiveList g_drawables_2d;
+ STATIC_INIT(g_drawables_2d) { g_drawables_2d = IL_NEW(); }
  .void(entity) entremove;
  float drawframetime;
  vector view_origin, view_forward, view_right, view_up;
  
+ IntrusiveList g_radarlinks;
+ STATIC_INIT(g_radarlinks) { g_radarlinks = IL_NEW(); }
+ IntrusiveList g_radaricons;
+ STATIC_INIT(g_radaricons) { g_radaricons = IL_NEW(); }
  bool button_zoom;
  bool spectatorbutton_zoom;
  bool button_attack2;
@@@ -80,7 -112,9 +89,9 @@@ float warmup_stage
  
  void Fog_Force();
  
- string getcommandkey(string text, string command);
+ string _getcommandkey(string text, string command, bool forcename);
+ #define getcommandkey(cmd_name, command) _getcommandkey(cmd_name, command, false)
+ #define getcommandkey_forcename(cmd_name, command) _getcommandkey(cmd_name, command, true)
  
  string vote_called_vote;
  float ready_waiting;
@@@ -117,5 -151,13 +128,13 @@@ float g_trueaim_minrange
  
  float hud;
  float view_quality;
+ int num_spectators;
+ const int MAX_SPECTATORS = 7;
+ int spectatorlist[MAX_SPECTATORS];
  int framecount;
  .float health;
+ float GetSpeedUnitFactor(int speed_unit);
+ string GetSpeedUnit(int speed_unit);
index 518d1653ef2adf83f2be0d2fb79952dced64809e,5e51e9b7ffe7198921cbecac40cfa1dbcb0169b6..91f3e9556ec4c87e566fac5cdcdd6cbe45bcc65e
@@@ -72,21 -72,21 +72,21 @@@ void HUD_InitScores(
  {
        int i, f;
  
 -      ps_primary = ps_secondary = ts_primary = ts_secondary = -1;
 -      for(i = 0; i < MAX_SCORE; ++i)
 -      {
 -              f = (scores_flags[i] & SFL_SORT_PRIO_MASK);
 +      ps_primary = ps_secondary = NULL;
 +      ts_primary = ts_secondary = -1;
 +      FOREACH(Scores, true, {
 +              f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
                if(f == SFL_SORT_PRIO_PRIMARY)
 -                      ps_primary = i;
 +                      ps_primary = it;
                if(f == SFL_SORT_PRIO_SECONDARY)
 -                      ps_secondary = i;
 -      }
 -      if(ps_secondary == -1)
 +                      ps_secondary = it;
 +      });
 +      if(ps_secondary == NULL)
                ps_secondary = ps_primary;
  
        for(i = 0; i < MAX_TEAMSCORE; ++i)
        {
 -              f = (teamscores_flags[i] & SFL_SORT_PRIO_MASK);
 +              f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
                if(f == SFL_SORT_PRIO_PRIMARY)
                        ts_primary = i;
                if(f == SFL_SORT_PRIO_SECONDARY)
@@@ -170,18 -170,21 +170,18 @@@ float HUD_ComparePlayerScores(entity le
                return false;
        }
  
 -      r = HUD_CompareScore(left.scores[ps_primary], right.scores[ps_primary], scores_flags[ps_primary]);
 +      r = HUD_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
        if (r >= 0)
                return r;
  
 -      r = HUD_CompareScore(left.scores[ps_secondary], right.scores[ps_secondary], scores_flags[ps_secondary]);
 +      r = HUD_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
        if (r >= 0)
                return r;
  
 -      int i;
 -      for(i = 0; i < MAX_SCORE; ++i)
 -      {
 -              r = HUD_CompareScore(left.scores[i], right.scores[i], scores_flags[i]);
 -              if (r >= 0)
 -                      return r;
 -      }
 +      FOREACH(Scores, true, {
 +              r = HUD_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
 +              if (r >= 0) return r;
 +      });
  
        if (left.sv_entnum < right.sv_entnum)
                return true;
  
  void HUD_UpdatePlayerPos(entity player)
  {
-       for(other = player.sort_next; other && HUD_ComparePlayerScores(player, other); other = player.sort_next)
+       entity ent;
+       for(ent = player.sort_next; ent && HUD_ComparePlayerScores(player, ent); ent = player.sort_next)
        {
-               SORT_SWAP(player, other);
+               SORT_SWAP(player, ent);
        }
-       for(other = player.sort_prev; other != players && HUD_ComparePlayerScores(other, player); other = player.sort_prev)
+       for(ent = player.sort_prev; ent != players && HUD_ComparePlayerScores(ent, player); ent = player.sort_prev)
        {
-               SORT_SWAP(other, player);
+               SORT_SWAP(ent, player);
        }
  }
  
@@@ -210,17 -214,17 +211,17 @@@ float HUD_CompareTeamScores(entity left
        if(right.team == NUM_SPECTATOR)
                return 0;
  
 -      r = HUD_CompareScore(left.teamscores[ts_primary], right.teamscores[ts_primary], teamscores_flags[ts_primary]);
 +      r = HUD_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
        if (r >= 0)
                return r;
  
 -      r = HUD_CompareScore(left.teamscores[ts_secondary], right.teamscores[ts_secondary], teamscores_flags[ts_secondary]);
 +      r = HUD_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
        if (r >= 0)
                return r;
  
 -      for(i = 0; i < MAX_SCORE; ++i)
 +      for(i = 0; i < MAX_TEAMSCORE; ++i)
        {
 -              r = HUD_CompareScore(left.teamscores[i], right.teamscores[i], teamscores_flags[i]);
 +              r = HUD_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
                if (r >= 0)
                        return r;
        }
  
  void HUD_UpdateTeamPos(entity Team)
  {
-       for(other = Team.sort_next; other && HUD_CompareTeamScores(Team, other); other = Team.sort_next)
+       entity ent;
+       for(ent = Team.sort_next; ent && HUD_CompareTeamScores(Team, ent); ent = Team.sort_next)
        {
-               SORT_SWAP(Team, other);
+               SORT_SWAP(Team, ent);
        }
-       for(other = Team.sort_prev; other != teams && HUD_CompareTeamScores(other, Team); other = Team.sort_prev)
+       for(ent = Team.sort_prev; ent != teams && HUD_CompareTeamScores(ent, Team); ent = Team.sort_prev)
        {
-               SORT_SWAP(other, Team);
+               SORT_SWAP(ent, Team);
        }
  }
  
@@@ -251,13 -256,11 +253,13 @@@ void Cmd_HUD_Help(
        LOG_INFO(_("^2scoreboard_columns_set default\n"));
        LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
        LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
 -      LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n\n"));
 +      LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
 +      LOG_INFO("\n");
  
        LOG_INFO(_("^3name^7 or ^3nick^7             Name of a player\n"));
        LOG_INFO(_("^3ping^7                     Ping time\n"));
        LOG_INFO(_("^3pl^7                       Packet loss\n"));
 +      LOG_INFO(_("^3elo^7                      Player ELO\n"));
        LOG_INFO(_("^3kills^7                    Number of kills\n"));
        LOG_INFO(_("^3deaths^7                   Number of deaths\n"));
        LOG_INFO(_("^3suicides^7                 Number of suicides\n"));
        LOG_INFO(_("^3takes^7                    Number of domination points taken (DOM)\n"));
        LOG_INFO(_("^3bckills^7                  Number of ball carrier kills\n"));
        LOG_INFO(_("^3bctime^7                   Total amount of time holding the ball in Keepaway\n"));
 -      LOG_INFO(_("^3score^7                    Total score\n\n"));
 +      LOG_INFO(_("^3score^7                    Total score\n"));
 +      LOG_INFO("\n");
  
        LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
                "of game types, then a slash, to make the field show up only in these\n"
  void Cmd_HUD_SetFields(int argc)
  {
      TC(int, argc);
 -      int i, j, slash;
 +      int i, slash;
        string str, pattern;
        float have_name = 0, have_primary = 0, have_secondary = 0, have_separator = 0;
        float missing;
                // set up a temporary scoreboard layout
                // no layout can be properly set up until score_info data haven't been received
                argc = tokenizebyseparator("0 1 ping pl name | score", " ");
 -              ps_primary = 0;
 -              scores_label[ps_primary] = strzone("score");
 -              scores_flags[ps_primary] = SFL_ALLOW_HIDE;
 +              ps_primary = SP_SCORE;
 +              scores_label(ps_primary) = strzone("score");
 +              scores_flags(ps_primary) = SFL_ALLOW_HIDE;
        }
  
        // TODO: re enable with gametype dependant cvars?
                {
                        string s;
                        s = "ping pl name |";
 -                      for(i = 0; i < MAX_SCORE; ++i)
 -                      {
 -                              if(i != ps_primary)
 -                              if(i != ps_secondary)
 -                              if(scores_label[i] != "")
 -                                      s = strcat(s, " ", scores_label[i]);
 -                      }
 +                      FOREACH(Scores, true, {
 +                              if(it != ps_primary)
 +                              if(it != ps_secondary)
 +                              if(scores_label(it) != "")
 +                                      s = strcat(s, " ", scores_label(it));
 +                      });
                        if(ps_secondary != ps_primary)
 -                              s = strcat(s, " ", scores_label[ps_secondary]);
 -                      s = strcat(s, " ", scores_label[ps_primary]);
 +                              s = strcat(s, " ", scores_label(ps_secondary));
 +                      s = strcat(s, " ", scores_label(ps_primary));
                        argc = tokenizebyseparator(strcat("0 1 ", s), " ");
                }
        }
                hud_size[hud_num_fields] = stringwidth(hud_title[hud_num_fields], false, hud_fontsize);
                str = strtolower(str);
  
 +        PlayerScoreField j;
                switch(str)
                {
                        case "ping": hud_field[hud_num_fields] = SP_PING; break;
                        case "dmgtaken": hud_field[hud_num_fields] = SP_DMGTAKEN; break;
                        default:
                        {
 -                              for(j = 0; j < MAX_SCORE; ++j)
 -                                      if(str == strtolower(scores_label[j]))
 +                              FOREACH(Scores, true, {
 +                                      if (str == strtolower(scores_label(it))) {
 +                                          j = it;
                                                goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
 +                    }
 +                });
  
  LABEL(notfound)
                                if(str == "frags")
@@@ -443,9 -442,9 +445,9 @@@ LABEL(found
                        break;
        }
  
 -      if(scores_flags[ps_primary] & SFL_ALLOW_HIDE)
 +      if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
                have_primary = 1;
 -      if(scores_flags[ps_secondary] & SFL_ALLOW_HIDE)
 +      if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
                have_secondary = 1;
        if(ps_primary == ps_secondary)
                have_secondary = 1;
                if(!have_secondary)
                {
                        strunzone(hud_title[hud_num_fields]);
 -                      hud_title[hud_num_fields] = strzone(TranslateScoresLabel(scores_label[ps_secondary]));
 +                      hud_title[hud_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
                        hud_size[hud_num_fields] = stringwidth(hud_title[hud_num_fields], false, hud_fontsize);
                        hud_field[hud_num_fields] = ps_secondary;
                        ++hud_num_fields;
 -                      LOG_INFOF("fixed missing field '%s'\n", scores_label[ps_secondary]);
 +                      LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
                }
                if(!have_primary)
                {
                        strunzone(hud_title[hud_num_fields]);
 -                      hud_title[hud_num_fields] = strzone(TranslateScoresLabel(scores_label[ps_primary]));
 +                      hud_title[hud_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
                        hud_size[hud_num_fields] = stringwidth(hud_title[hud_num_fields], false, hud_fontsize);
                        hud_field[hud_num_fields] = ps_primary;
                        ++hud_num_fields;
 -                      LOG_INFOF("fixed missing field '%s'\n", scores_label[ps_primary]);
 +                      LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
                }
        }
  
@@@ -526,8 -525,9 +528,8 @@@ vector hud_field_icon2_rgb
  float hud_field_icon0_alpha;
  float hud_field_icon1_alpha;
  float hud_field_icon2_alpha;
 -string HUD_GetField(entity pl, int field)
 +string HUD_GetField(entity pl, PlayerScoreField field)
  {
 -    TC(int, field);
        float tmp, num, denom;
        int f;
        string str;
                        return entcs_GetName(pl.sv_entnum);
  
                case SP_FRAGS:
 -                      f = pl.(scores[SP_KILLS]);
 -                      f -= pl.(scores[SP_SUICIDES]);
 +                      f = pl.(scores(SP_KILLS));
 +                      f -= pl.(scores(SP_SUICIDES));
                        return ftos(f);
  
                case SP_KDRATIO:
 -                      num = pl.(scores[SP_KILLS]);
 -                      denom = pl.(scores[SP_DEATHS]);
 +                      num = pl.(scores(SP_KILLS));
 +                      denom = pl.(scores(SP_DEATHS));
  
                        if(denom == 0) {
                                hud_field_rgb = '0 1 0';
                        return str;
  
                case SP_SUM:
 -                      f = pl.(scores[SP_KILLS]);
 -                      f -= pl.(scores[SP_DEATHS]);
 +                      f = pl.(scores(SP_KILLS));
 +                      f -= pl.(scores(SP_DEATHS));
  
                        if(f > 0) {
                                hud_field_rgb = '0 1 0';
                        return ftos(f);
  
                case SP_DMG:
 -                      num = pl.(scores[SP_DMG]);
 +                      num = pl.(scores(SP_DMG));
                        denom = 1000;
  
                        str = sprintf("%.1f k", num/denom);
                        return str;
  
                case SP_DMGTAKEN:
 -                      num = pl.(scores[SP_DMGTAKEN]);
 +                      num = pl.(scores(SP_DMGTAKEN));
                        denom = 1000;
  
                        str = sprintf("%.1f k", num/denom);
                        return str;
  
                default:
 -                      tmp = pl.(scores[field]);
 -                      f = scores_flags[field];
 +                      tmp = pl.(scores(field));
 +                      f = scores_flags(field);
                        if(field == ps_primary)
                                hud_field_rgb = '1 1 0';
                        else if(field == ps_secondary)
@@@ -653,9 -653,9 +655,9 @@@ float hud_fixscoreboardcolumnwidth_marg
  string HUD_FixScoreboardColumnWidth(int i, string str)
  {
      TC(int, i);
 -      float field, f;
 +      float f;
        vector sz;
 -      field = hud_field[i];
 +      PlayerScoreField field = hud_field[i];
  
        hud_fixscoreboardcolumnwidth_iconlen = 0;
  
  void HUD_PrintScoreboardItem(vector pos, vector item_size, entity pl, bool is_self, int pl_number)
  {
      TC(bool, is_self); TC(int, pl_number);
 -      vector tmp, rgb;
 -      rgb = Team_ColorRGB(pl.team);
 +      vector tmp;
 +      vector rgb = Team_ColorRGB(pl.team);
        string str;
 -      int field;
 -      float is_spec;
 -      is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
 +      bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
  
        if((rgb == '1 1 1') && (!is_spec)) {
                rgb.x = autocvar_scoreboard_color_bg_r + 0.5;
        tmp.y = 0;
        tmp.z = 0;
        int i;
 +    PlayerScoreField field;
        for(i = 0; i < hud_num_fields; ++i)
        {
                field = hud_field[i];
@@@ -1336,12 -1337,12 +1338,12 @@@ void HUD_DrawScoreboard(
  
                        draw_beginBoldFont();
                        rgb = Team_ColorRGB(tm.team);
 -                      str = ftos(tm.(teamscores[ts_primary]));
 +                      str = ftos(tm.(teamscores(ts_primary)));
                        drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
  
                        if(ts_primary != ts_secondary)
                        {
 -                              str = ftos(tm.(teamscores[ts_secondary]));
 +                              str = ftos(tm.(teamscores(ts_secondary)));
                                drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize) + eY * hud_fontsize.y * 1.5, str, hud_fontsize, rgb, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
                        }
                        draw_endBoldFont();
  
        if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
                if(race_speedaward) {
-                       drawcolorcodedstring(pos, sprintf(_("Speed award: %d ^7(%s^7)"), race_speedaward, race_speedaward_holder), hud_fontsize, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
+                       drawcolorcodedstring(pos, sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, race_speedaward_unit, race_speedaward_holder), hud_fontsize, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
                        pos.y += 1.25 * hud_fontsize.y;
                }
                if(race_speedaward_alltimebest) {
-                       drawcolorcodedstring(pos, sprintf(_("All-time fastest: %d ^7(%s^7)"), race_speedaward_alltimebest, race_speedaward_alltimebest_holder), hud_fontsize, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
+                       drawcolorcodedstring(pos, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, race_speedaward_alltimebest_unit, race_speedaward_alltimebest_holder), hud_fontsize, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
                        pos.y += 1.25 * hud_fontsize.y;
                }
                pos = HUD_DrawScoreboardRankings(pos, playerslots[player_localnum], rgb, bg_size);
                                str = strcat(str, _(" or"));
                        if(teamplay)
                        {
 -                              str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], fl),
 -                                      (teamscores_label[ts_primary] == "score")   ? CTX(_("SCO^points")) :
 -                                      (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
 -                                      TranslateScoresLabel(teamscores_label[ts_primary])));
 +                              str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
 +                                      (teamscores_label(ts_primary) == "score")   ? CTX(_("SCO^points")) :
 +                                      (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
 +                                      TranslateScoresLabel(teamscores_label(ts_primary))));
                        }
                        else
                        {
 -                              str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags[ps_primary], fl),
 -                                      (scores_label[ps_primary] == "score")   ? CTX(_("SCO^points")) :
 -                                      (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
 -                                      TranslateScoresLabel(scores_label[ps_primary])));
 +                              str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
 +                                      (scores_label(ps_primary) == "score")   ? CTX(_("SCO^points")) :
 +                                      (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
 +                                      TranslateScoresLabel(scores_label(ps_primary))));
                        }
                }
                if(ll > 0)
                                str = strcat(str, _(" or"));
                        if(teamplay)
                        {
 -                              str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], ll),
 -                                      (teamscores_label[ts_primary] == "score")   ? CTX(_("SCO^points")) :
 -                                      (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
 -                                      TranslateScoresLabel(teamscores_label[ts_primary])));
 +                              str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
 +                                      (teamscores_label(ts_primary) == "score")   ? CTX(_("SCO^points")) :
 +                                      (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
 +                                      TranslateScoresLabel(teamscores_label(ts_primary))));
                        }
                        else
                        {
 -                              str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags[ps_primary], ll),
 -                                      (scores_label[ps_primary] == "score")   ? CTX(_("SCO^points")) :
 -                                      (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
 -                                      TranslateScoresLabel(scores_label[ps_primary])));
 +                              str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
 +                                      (scores_label(ps_primary) == "score")   ? CTX(_("SCO^points")) :
 +                                      (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
 +                                      TranslateScoresLabel(scores_label(ps_primary))));
                        }
                }
        }
                        );
                }
                else if(time >= respawn_time)
-                       str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
+                       str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey(_("jump"), "+jump"));
  
                pos.y += 1.2 * hud_fontsize.y;
                drawcolorcodedstring(pos + '0.5 0 0' * (sbwidth - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
index 0f45e93e5c7c30b775323161a0c0f05d64c037b6,b65b558fdb1027352b45ab0bca37dc1a104b6b23..b50013ff7bd7c31fde8701588f285b1813c10855
@@@ -52,8 -52,8 +52,8 @@@ float autocvar_g_balance_nexball_second
  float autocvar_g_balance_nexball_secondary_refire;
  float autocvar_g_balance_nexball_secondary_speed;
  
- void basketball_touch(entity this);
- void football_touch(entity this);
+ void basketball_touch(entity this, entity toucher);
+ void football_touch(entity this, entity toucher);
  void ResetBall(entity this);
  const int NBM_NONE = 0;
  const int NBM_FOOTBALL = 2;
@@@ -70,11 -70,13 +70,11 @@@ float OtherTeam(float t)  //works only 
  }
  
  const float ST_NEXBALL_GOALS = 1;
- void nb_ScoreRules(float teams)
 -const float SP_NEXBALL_GOALS = 4;
 -const float SP_NEXBALL_FAULTS = 5;
+ void nb_ScoreRules(int teams)
  {
        ScoreRules_basics(teams, 0, 0, true);
        ScoreInfo_SetLabel_TeamScore(   ST_NEXBALL_GOALS,  "goals", SFL_SORT_PRIO_PRIMARY);
-       ScoreInfo_SetLabel_PlayerScore(SP_NEXBALL_GOALS,  "goals", SFL_SORT_PRIO_PRIMARY);
+       ScoreInfo_SetLabel_PlayerScore( SP_NEXBALL_GOALS,  "goals", SFL_SORT_PRIO_PRIMARY);
        ScoreInfo_SetLabel_PlayerScore(SP_NEXBALL_FAULTS, "faults", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER);
        ScoreRules_basics_end();
  }
@@@ -176,7 -178,7 +176,7 @@@ void GiveBall(entity plyr, entity ball
        ball.effects &= ~autocvar_g_nexball_basketball_effects_default;
  
        ball.velocity = '0 0 0';
-       ball.movetype = MOVETYPE_NONE;
+       set_movetype(ball, MOVETYPE_NONE);
        settouch(ball, func_null);
        ball.effects |= EF_NOSHADOW;
        ball.scale = 1; // scale down.
@@@ -207,7 -209,7 +207,7 @@@ void DropBall(entity ball, vector org, 
  
        setattachment(ball, NULL, "");
        setorigin(ball, org);
-       ball.movetype = MOVETYPE_BOUNCE;
+       set_movetype(ball, MOVETYPE_BOUNCE);
        UNSET_ONGROUND(ball);
        ball.scale = ball_scale;
        ball.velocity = vel;
@@@ -235,7 -237,7 +235,7 @@@ void InitBall(entity this
  {
        if(gameover) return;
        UNSET_ONGROUND(this);
-       this.movetype = MOVETYPE_BOUNCE;
+       set_movetype(this, MOVETYPE_BOUNCE);
        if(this.classname == "nexball_basketball")
                settouch(this, basketball_touch);
        else if(this.classname == "nexball_football")
@@@ -259,7 -261,7 +259,7 @@@ void ResetBall(entity this
                        bprint("The ", Team_ColoredFullName(this.team), " held the ball for too long.\n");
  
                settouch(this, func_null);
-               this.movetype = MOVETYPE_NOCLIP;
+               set_movetype(this, MOVETYPE_NOCLIP);
                this.velocity = '0 0 0'; // just in case?
                if(!this.cnt)
                        LogNB("resetidle", NULL);
                                   vtos(this.origin - this.spawnorigin), " Velocity: ", vtos(this.velocity), "\n");
                this.velocity = '0 0 0';
                setorigin(this, this.spawnorigin); // make sure it's positioned correctly anyway
-               this.movetype = MOVETYPE_NONE;
+               set_movetype(this, MOVETYPE_NONE);
                setthink(this, InitBall);
                this.nextthink = max(time, game_starttime) + autocvar_g_nexball_delay_start;
        }
  }
  
- void football_touch(entity this)
+ void football_touch(entity this, entity toucher)
  {
-       if(other.solid == SOLID_BSP)
+       if(toucher.solid == SOLID_BSP)
        {
                if(time > this.lastground + 0.1)
                {
                        this.nextthink = time + autocvar_g_nexball_delay_idle;
                return;
        }
-       if (!IS_PLAYER(other))
+       if (!IS_PLAYER(toucher))
                return;
-       if(other.health < 1)
+       if(toucher.health < 1)
                return;
        if(!this.cnt)
                this.nextthink = time + autocvar_g_nexball_delay_idle;
  
-       this.pusher = other;
-       this.team = other.team;
+       this.pusher = toucher;
+       this.team = toucher.team;
  
        if(autocvar_g_nexball_football_physics == -1)   // MrBougo try 1, before decompiling Rev's original
        {
-               if(other.velocity)
-                       this.velocity = other.velocity * 1.5 + '0 0 1' * autocvar_g_nexball_football_boost_up;
+               if(toucher.velocity)
+                       this.velocity = toucher.velocity * 1.5 + '0 0 1' * autocvar_g_nexball_football_boost_up;
        }
        else if(autocvar_g_nexball_football_physics == 1)         // MrBougo's modded Rev style: partially independant of the height of the aiming point
        {
-               makevectors(other.v_angle);
-               this.velocity = other.velocity + v_forward * autocvar_g_nexball_football_boost_forward + '0 0 1' * autocvar_g_nexball_football_boost_up;
+               makevectors(toucher.v_angle);
+               this.velocity = toucher.velocity + v_forward * autocvar_g_nexball_football_boost_forward + '0 0 1' * autocvar_g_nexball_football_boost_up;
        }
        else if(autocvar_g_nexball_football_physics == 2)         // 2nd mod try: totally independant. Really playable!
        {
-               makevectors(other.v_angle.y * '0 1 0');
-               this.velocity = other.velocity + v_forward * autocvar_g_nexball_football_boost_forward + v_up * autocvar_g_nexball_football_boost_up;
+               makevectors(toucher.v_angle.y * '0 1 0');
+               this.velocity = toucher.velocity + v_forward * autocvar_g_nexball_football_boost_forward + v_up * autocvar_g_nexball_football_boost_up;
        }
        else     // Revenant's original style (from the original mod's disassembly, acknowledged by Revenant)
        {
-               makevectors(other.v_angle);
-               this.velocity = other.velocity + v_forward * autocvar_g_nexball_football_boost_forward + v_up * autocvar_g_nexball_football_boost_up;
+               makevectors(toucher.v_angle);
+               this.velocity = toucher.velocity + v_forward * autocvar_g_nexball_football_boost_forward + v_up * autocvar_g_nexball_football_boost_up;
        }
        this.avelocity = -250 * v_forward;  // maybe there is a way to make it look better?
  }
  
- void basketball_touch(entity this)
+ void basketball_touch(entity this, entity toucher)
  {
-       if(other.ballcarried)
+       if(toucher.ballcarried)
        {
-               football_touch(this);
+               football_touch(this, toucher);
                return;
        }
-       if(!this.cnt && IS_PLAYER(other) && !STAT(FROZEN, other) && !IS_DEAD(other) && (other != this.nb_dropper || time > this.nb_droptime + autocvar_g_nexball_delay_collect))
+       if(!this.cnt && IS_PLAYER(toucher) && !STAT(FROZEN, toucher) && !IS_DEAD(toucher) && (toucher != this.nb_dropper || time > this.nb_droptime + autocvar_g_nexball_delay_collect))
        {
-               if(other.health <= 0)
+               if(toucher.health <= 0)
                        return;
-               LogNB("caught", other);
-               GiveBall(other, this);
+               LogNB("caught", toucher);
+               GiveBall(toucher, this);
        }
-       else if(other.solid == SOLID_BSP)
+       else if(toucher.solid == SOLID_BSP)
        {
                _sound(this, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM);
                if(this.velocity && !this.cnt)
        }
  }
  
- void GoalTouch(entity this)
+ void GoalTouch(entity this, entity toucher)
  {
        entity ball;
        float isclient, pscore, otherteam;
        string pname;
  
        if(gameover) return;
-       if((this.spawnflags & GOAL_TOUCHPLAYER) && other.ballcarried)
-               ball = other.ballcarried;
+       if((this.spawnflags & GOAL_TOUCHPLAYER) && toucher.ballcarried)
+               ball = toucher.ballcarried;
        else
-               ball = other;
+               ball = toucher;
        if(ball.classname != "nexball_basketball")
                if(ball.classname != "nexball_football")
                        return;
        if((!ball.pusher && this.team != GOAL_OUT) || ball.cnt)
                return;
-       EXACTTRIGGER_TOUCH;
+       EXACTTRIGGER_TOUCH(this, toucher);
  
  
-       if(nb_teams == 2)
+       if(NumTeams(nb_teams) == 2)
                otherteam = OtherTeam(ball.team);
        else
                otherteam = 0;
        else if(this.team == GOAL_FAULT)
        {
                LogNB("fault", ball.pusher);
-               if(nb_teams == 2)
+               if(NumTeams(nb_teams) == 2)
                        bprint(Team_ColoredFullName(otherteam), " gets a point due to ", pname, "^7's silliness.\n");
                else
                        bprint(Team_ColoredFullName(ball.team), " loses a point due to ", pname, "^7's silliness.\n");
  
        if(ball.team && pscore)
        {
-               if(nb_teams == 2 && pscore < 0)
+               if(NumTeams(nb_teams) == 2 && pscore < 0)
                        TeamScore_AddToTeam(otherteam, ST_NEXBALL_GOALS, -pscore);
                else
                        TeamScore_AddToTeam(ball.team, ST_NEXBALL_GOALS, pscore);
@@@ -451,7 -453,7 +451,7 @@@ spawnfunc(nexball_team
  {
        if(!g_nexball)
        {
-               remove(this);
+               delete(this);
                return;
        }
        this.team = this.cnt + 1;
@@@ -464,7 -466,7 +464,7 @@@ void nb_spawnteam(string teamname, floa
        e.netname = teamname;
        e.cnt = teamcolor;
        e.team = e.cnt + 1;
-       nb_teams += 1;
+       //nb_teams += 1;
  }
  
  void nb_spawnteams()
                        if(!t_red)
                        {
                                nb_spawnteam("Red", e.team-1)   ;
+                               nb_teams |= BIT(0);
                                t_red = true;
                        }
                        break;
                        {
                                nb_spawnteam("Blue", e.team-1)  ;
                                t_blue = true;
+                               nb_teams |= BIT(1);
                        }
                        break;
                case NUM_TEAM_3:
                        {
                                nb_spawnteam("Yellow", e.team-1);
                                t_yellow = true;
+                               nb_teams |= BIT(2);
                        }
                        break;
                case NUM_TEAM_4:
                        {
                                nb_spawnteam("Pink", e.team-1)  ;
                                t_pink = true;
+                               nb_teams |= BIT(3);
                        }
                        break;
                }
@@@ -521,7 -527,7 +525,7 @@@ void nb_delayedinit(entity this
  
  void SpawnBall(entity this)
  {
-       if(!g_nexball) { remove(this); return; }
+       if(!g_nexball) { delete(this); return; }
  
  //    balls += 4; // using the remaining bits to count balls will leave more than the max edict count, so it's fine
  
                this.glow_trail = true;
        }
  
-       this.movetype = MOVETYPE_FLY;
+       set_movetype(this, MOVETYPE_FLY);
  
        if(!autocvar_g_nexball_sound_bounce)
                this.noise = "";
@@@ -607,11 -613,10 +611,10 @@@ spawnfunc(nexball_football
        SpawnBall(this);
  }
  
float nb_Goal_Customize(entity this)
bool nb_Goal_Customize(entity this, entity client)
  {
-       entity e, wp_owner;
-       e = WaypointSprite_getviewentity(other);
-       wp_owner = this.owner;
+       entity e = WaypointSprite_getviewentity(client);
+       entity wp_owner = this.owner;
        if(SAME_TEAM(e, wp_owner)) { return false; }
  
        return true;
  
  void SpawnGoal(entity this)
  {
-       if(!g_nexball) { remove(this); return; }
+       if(!g_nexball) { delete(this); return; }
  
        EXACTTRIGGER_INIT;
  
@@@ -728,35 -733,35 +731,35 @@@ void W_Nexball_Think(entity this
        this.nextthink = time;
  }
  
- void W_Nexball_Touch(entity this)
+ void W_Nexball_Touch(entity this, entity toucher)
  {
        entity ball, attacker;
        attacker = this.owner;
        //this.think = func_null;
        //this.enemy = NULL;
  
-       PROJECTILE_TOUCH(this);
-       if(attacker.team != other.team || autocvar_g_nexball_basketball_teamsteal)
-               if((ball = other.ballcarried) && !STAT(FROZEN, other) && !IS_DEAD(other) && (IS_PLAYER(attacker)))
+       PROJECTILE_TOUCH(this, toucher);
+       if(attacker.team != toucher.team || autocvar_g_nexball_basketball_teamsteal)
+               if((ball = toucher.ballcarried) && !STAT(FROZEN, toucher) && !IS_DEAD(toucher) && (IS_PLAYER(attacker)))
                {
-                       other.velocity = other.velocity + normalize(this.velocity) * other.damageforcescale * autocvar_g_balance_nexball_secondary_force;
-                       UNSET_ONGROUND(other);
+                       toucher.velocity = toucher.velocity + normalize(this.velocity) * toucher.damageforcescale * autocvar_g_balance_nexball_secondary_force;
+                       UNSET_ONGROUND(toucher);
                        if(!attacker.ballcarried)
                        {
                                LogNB("stole", attacker);
-                               _sound(other, CH_TRIGGER, ball.noise2, VOL_BASE, ATTEN_NORM);
+                               _sound(toucher, CH_TRIGGER, ball.noise2, VOL_BASE, ATTEN_NORM);
  
-                               if(SAME_TEAM(attacker, other) && time > attacker.teamkill_complain)
+                               if(SAME_TEAM(attacker, toucher) && time > attacker.teamkill_complain)
                                {
                                        attacker.teamkill_complain = time + 5;
                                        attacker.teamkill_soundtime = time + 0.4;
-                                       attacker.teamkill_soundsource = other;
+                                       attacker.teamkill_soundsource = toucher;
                                }
  
-                               GiveBall(attacker, other.ballcarried);
+                               GiveBall(attacker, toucher.ballcarried);
                        }
                }
-       remove(this);
+       delete(this);
  }
  
  void W_Nexball_Attack(entity actor, float t)
@@@ -817,7 -822,7 +820,7 @@@ void W_Nexball_Attack2(entity actor
  
        missile.owner = actor;
  
-       missile.movetype = MOVETYPE_FLY;
+       set_movetype(missile, MOVETYPE_FLY);
        PROJECTILE_MAKETRIGGER(missile);
  
        //setmodel(missile, "models/elaser.mdl");  // precision set below
  
        missile.effects = EF_BRIGHTFIELD | EF_LOWPRECISION;
        missile.flags = FL_PROJECTILE;
+       IL_PUSH(g_projectiles, missile);
  
        CSQCProjectile(missile, true, PROJECTILE_ELECTRO, true);
  }
  
float ball_customize(entity this)
bool ball_customize(entity this, entity client)
  {
        if(!this.owner)
        {
                return true;
        }
  
-       if(other == this.owner)
+       if(client == this.owner)
        {
                this.scale = autocvar_g_nexball_viewmodel_scale;
                if(this.enemy)
@@@ -1071,6 -1077,17 +1075,17 @@@ MUTATOR_HOOKFUNCTION(nb, FilterItem
        return false;
  }
  
+ MUTATOR_HOOKFUNCTION(nb, ItemTouch)
+ {
+       entity item = M_ARGV(0, entity);
+       entity toucher = M_ARGV(1, entity);
+       if(item.weapon && toucher.ballcarried)
+               return MUT_ITEMTOUCH_RETURN; // no new weapons for you, mister!
+       return MUT_ITEMTOUCH_CONTINUE;
+ }
  MUTATOR_HOOKFUNCTION(nb, GetTeamCount)
  {
        M_ARGV(1, string) = "nexball_team";
index b35d8488319d805b34e6e3ca8c4b0fff4e955134,d3c52e04b21290b08dc80cf2b7eab74165e0f28a..14ee4eceb9acc1bca5b7930c426b18aa033e1bee
@@@ -120,6 -120,8 +120,6 @@@ void havocbot_goalrating_enemyplayers(e
  
  // score rule declarations
  const int ST_ONS_CAPS = 1;
 -const int SP_ONS_CAPS = 4;
 -const int SP_ONS_TAKES = 6;
  
  #endif
  #endif
@@@ -160,9 -162,9 +160,9 @@@ void FixSize(entity e)
  // CaptureShield Functions
  // =======================
  
- bool ons_CaptureShield_Customize(entity this)
+ bool ons_CaptureShield_Customize(entity this, entity client)
  {
-       entity e = WaypointSprite_getviewentity(other);
+       entity e = WaypointSprite_getviewentity(client);
  
        if(!this.enemy.isshielded && (ons_ControlPoint_Attackable(this.enemy, e.team) > 0 || this.enemy.classname != "onslaught_controlpoint")) { return false; }
        if(SAME_TEAM(this, e)) { return false; }
        return true;
  }
  
- void ons_CaptureShield_Touch(entity this)
+ void ons_CaptureShield_Touch(entity this, entity toucher)
  {
-       if(!this.enemy.isshielded && (ons_ControlPoint_Attackable(this.enemy, other.team) > 0 || this.enemy.classname != "onslaught_controlpoint")) { return; }
-       if(!IS_PLAYER(other)) { return; }
-       if(SAME_TEAM(other, this)) { return; }
+       if(!this.enemy.isshielded && (ons_ControlPoint_Attackable(this.enemy, toucher.team) > 0 || this.enemy.classname != "onslaught_controlpoint")) { return; }
+       if(!IS_PLAYER(toucher)) { return; }
+       if(SAME_TEAM(toucher, this)) { return; }
  
        vector mymid = (this.absmin + this.absmax) * 0.5;
-       vector othermid = (other.absmin + other.absmax) * 0.5;
+       vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
  
-       Damage(other, this, this, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(othermid - mymid) * ons_captureshield_force);
+       Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(theirmid - mymid) * ons_captureshield_force);
  
-       if(IS_REAL_CLIENT(other))
+       if(IS_REAL_CLIENT(toucher))
        {
-               play2(other, SND(ONS_DAMAGEBLOCKEDBYSHIELD));
+               play2(toucher, SND(ONS_DAMAGEBLOCKEDBYSHIELD));
  
                if(this.enemy.classname == "onslaught_generator")
-                       Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_GENERATOR_SHIELDED);
+                       Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_GENERATOR_SHIELDED);
                else
-                       Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_CONTROLPOINT_SHIELDED);
+                       Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_CONTROLPOINT_SHIELDED);
        }
  }
  
@@@ -209,7 -211,7 +209,7 @@@ void ons_CaptureShield_Spawn(entity gen
        settouch(shield, ons_CaptureShield_Touch);
        setcefc(shield, ons_CaptureShield_Customize);
        shield.effects = EF_ADDITIVE;
-       shield.movetype = MOVETYPE_NOCLIP;
+       set_movetype(shield, MOVETYPE_NOCLIP);
        shield.solid = SOLID_TRIGGER;
        shield.avelocity = '7 0 11';
        shield.scale = 1;
@@@ -356,13 -358,11 +356,11 @@@ void onslaught_updatelinks(
                }
                ons_ControlPoint_UpdateSprite(l);
        }
-       l = findchain(classname, "ons_captureshield");
-       while(l)
+       FOREACH_ENTITY_CLASS("ons_captureshield", true,
        {
-               l.team = l.enemy.team;
-               l.colormap = l.enemy.colormap;
-               l = l.chain;
-       }
+               it.team = it.enemy.team;
+               it.colormap = it.enemy.colormap;
+       });
  }
  
  
@@@ -551,7 -551,7 +549,7 @@@ void ons_ControlPoint_Icon_Damage(entit
                        setmodel_fixsize(this.owner, MDL_ONS_CP_PAD1);
                //setsize(this, '-32 -32 0', '32 32 8');
  
-               remove(this);
+               delete(this);
        }
  
        this.SendFlags |= CPSF_STATUS;
@@@ -774,9 -774,8 +772,8 @@@ void ons_ControlPoint_UpdateSprite(enti
        }
  }
  
- void ons_ControlPoint_Touch(entity this)
+ void ons_ControlPoint_Touch(entity this, entity toucher)
  {
-       entity toucher = other;
        int attackable;
  
        if(IS_VEHICLE(toucher) && toucher.owner)
@@@ -820,7 -819,7 +817,7 @@@ void ons_ControlPoint_Think(entity this
  void ons_ControlPoint_Reset(entity this)
  {
        if(this.goalentity)
-               remove(this.goalentity);
+               delete(this.goalentity);
  
        this.goalentity = NULL;
        this.team = 0;
@@@ -862,7 -861,7 +859,7 @@@ void ons_ControlPoint_Setup(entity cp
        cp.netname = "Control point";
        cp.team = 0;
        cp.solid = SOLID_BBOX;
-       cp.movetype = MOVETYPE_NONE;
+       set_movetype(cp, MOVETYPE_NONE);
        settouch(cp, ons_ControlPoint_Touch);
        setthink(cp, ons_ControlPoint_Think);
        cp.nextthink = time + ONS_CP_THINKRATE;
        if((cp.spawnflags & 1) || cp.noalign) // don't drop to floor, just stay at fixed location
        {
                cp.noalign = true;
-               cp.movetype = MOVETYPE_NONE;
+               set_movetype(cp, MOVETYPE_NONE);
        }
        else // drop to floor, automatically find a platform and set that as spawn origin
        {
                setorigin(cp, cp.origin + '0 0 20');
                cp.noalign = false;
                droptofloor(cp);
-               cp.movetype = MOVETYPE_TOSS;
+               set_movetype(cp, MOVETYPE_TOSS);
        }
  
        // waypointsprites
@@@ -1081,13 -1080,13 +1078,13 @@@ void ons_DelayedGeneratorSetup(entity t
  }
  
  
- void onslaught_generator_touch(entity this)
+ void onslaught_generator_touch(entity this, entity toucher)
  {
-       if ( IS_PLAYER(other) )
-       if ( SAME_TEAM(this,other) )
+       if ( IS_PLAYER(toucher) )
+       if ( SAME_TEAM(this,toucher) )
        if ( this.iscaptured )
        {
-               Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_TELEPORT);
+               Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT);
        }
  }
  
@@@ -1104,7 -1103,7 +1101,7 @@@ void ons_GeneratorSetup(entity gen) // 
        gen.classname = "onslaught_generator";
        gen.solid = SOLID_BBOX;
        gen.team_saved = teamnumber;
-       gen.movetype = MOVETYPE_NONE;
+       set_movetype(gen, MOVETYPE_NONE);
        gen.lasthealth = gen.max_health = gen.health = autocvar_g_onslaught_gen_health;
        gen.takedamage = DAMAGE_AIM;
        gen.bot_attack = true;
@@@ -1288,8 -1287,6 +1285,6 @@@ void Onslaught_RoundStart(
  
  void havocbot_goalrating_ons_offenseitems(entity this, float ratingscale, vector org, float sradius)
  {
-       entity head;
-       float t, c;
        bool needarmor = false, needweapons = false;
  
        // Needs armor/health?
                needarmor = true;
  
        // Needs weapons?
-       c = 0;
+       int c = 0;
        FOREACH(Weapons, it != WEP_Null, {
                if(this.weapons & (it.m_wepset))
                if(++c >= 4)
        LOG_DEBUG(strcat(this.netname, " needs armor ", ftos(needarmor) , "\n"));
  
        // See what is around
-       head = findchainfloat(bot_pickup, true);
-       while (head)
+       FOREACH_ENTITY_FLOAT(bot_pickup, true,
        {
                // gather health and armor only
-               if (head.solid)
-               if ( ((head.health || head.armorvalue) && needarmor) || (head.weapons && needweapons ) )
-               if (vdist(head.origin - org, <, sradius))
+               if (it.solid)
+               if ( ((it.health || it.armorvalue) && needarmor) || (it.weapons && needweapons ) )
+               if (vdist(it.origin - org, <, sradius))
                {
-                       t = head.bot_pickupevalfunc(this, head);
+                       int t = it.bot_pickupevalfunc(this, it);
                        if (t > 0)
-                               navigation_routerating(this, head, t * ratingscale, 500);
+                               navigation_routerating(this, it, t * ratingscale, 500);
                }
-               head = head.chain;
-       }
+       });
  }
  
  void havocbot_role_ons_setrole(entity this, int role)
@@@ -1596,26 -1591,22 +1589,22 @@@ void havocbot_ons_reset_role(entity thi
   */
  entity ons_Nearest_ControlPoint(entity this, vector pos, float max_dist)
  {
-       entity tmp_entity, closest_target = NULL;
-       tmp_entity = findchain(classname, "onslaught_controlpoint");
-       while(tmp_entity)
+       entity closest_target = NULL;
+       FOREACH_ENTITY_CLASS("onslaught_controlpoint", true,
        {
-               if(SAME_TEAM(tmp_entity, this))
-               if(tmp_entity.iscaptured)
-               if(max_dist <= 0 || vdist(tmp_entity.origin - pos, <=, max_dist))
-               if(vlen2(tmp_entity.origin - pos) <= vlen2(closest_target.origin - pos) || closest_target == NULL)
-                       closest_target = tmp_entity;
-               tmp_entity = tmp_entity.chain;
-       }
-       tmp_entity = findchain(classname, "onslaught_generator");
-       while(tmp_entity)
+               if(SAME_TEAM(it, this))
+               if(it.iscaptured)
+               if(max_dist <= 0 || vdist(it.origin - pos, <=, max_dist))
+               if(vlen2(it.origin - pos) <= vlen2(closest_target.origin - pos) || closest_target == NULL)
+                       closest_target = it;
+       });
+       FOREACH_ENTITY_CLASS("onslaught_generator", true,
        {
-               if(SAME_TEAM(tmp_entity, this))
-               if(max_dist <= 0 || vdist(tmp_entity.origin - pos, <, max_dist))
-               if(vlen2(tmp_entity.origin - pos) <= vlen2(closest_target.origin - pos) || closest_target == NULL)
-                       closest_target = tmp_entity;
-               tmp_entity = tmp_entity.chain;
-       }
+               if(SAME_TEAM(it, this))
+               if(max_dist <= 0 || vdist(it.origin - pos, <, max_dist))
+               if(vlen2(it.origin - pos) <= vlen2(closest_target.origin - pos) || closest_target == NULL)
+                       closest_target = it;
+       });
  
        return closest_target;
  }
   */
  entity ons_Nearest_ControlPoint_2D(entity this, vector pos, float max_dist)
  {
-       entity tmp_entity, closest_target = NULL;
+       entity closest_target = NULL;
        vector delta;
        float smallest_distance = 0, distance;
  
-       tmp_entity = findchain(classname, "onslaught_controlpoint");
-       while(tmp_entity)
+       FOREACH_ENTITY_CLASS("onslaught_controlpoint", true,
        {
-               delta = tmp_entity.origin - pos;
+               delta = it.origin - pos;
                delta_z = 0;
                distance = vlen(delta);
  
-               if(SAME_TEAM(tmp_entity, this))
-               if(tmp_entity.iscaptured)
+               if(SAME_TEAM(it, this))
+               if(it.iscaptured)
                if(max_dist <= 0 || distance <= max_dist)
                if(closest_target == NULL || distance <= smallest_distance )
                {
-                       closest_target = tmp_entity;
+                       closest_target = it;
                        smallest_distance = distance;
                }
-               tmp_entity = tmp_entity.chain;
-       }
-       tmp_entity = findchain(classname, "onslaught_generator");
-       while(tmp_entity)
+       });
+       FOREACH_ENTITY_CLASS("onslaught_generator", true,
        {
-               delta = tmp_entity.origin - pos;
+               delta = it.origin - pos;
                delta_z = 0;
                distance = vlen(delta);
  
-               if(SAME_TEAM(tmp_entity, this))
+               if(SAME_TEAM(it, this))
                if(max_dist <= 0 || distance <= max_dist)
                if(closest_target == NULL || distance <= smallest_distance )
                {
-                       closest_target = tmp_entity;
+                       closest_target = it;
                        smallest_distance = distance;
                }
-               tmp_entity = tmp_entity.chain;
-       }
+       });
  
        return closest_target;
  }
   */
  int ons_Count_SelfControlPoints(entity this)
  {
-       entity tmp_entity;
-       tmp_entity = findchain(classname, "onslaught_controlpoint");
        int n = 0;
-       while(tmp_entity)
+       FOREACH_ENTITY_CLASS("onslaught_controlpoint", true,
        {
-               if(SAME_TEAM(tmp_entity, this))
-               if(tmp_entity.iscaptured)
+               if(SAME_TEAM(it, this))
+               if(it.iscaptured)
                        n++;
-               tmp_entity = tmp_entity.chain;
-       }
-       tmp_entity = findchain(classname, "onslaught_generator");
-       while(tmp_entity)
+       });
+       FOREACH_ENTITY_CLASS("onslaught_generator", true,
        {
-               if(SAME_TEAM(tmp_entity, this))
+               if(SAME_TEAM(it, this))
                        n++;
-               tmp_entity = tmp_entity.chain;
-       }
+       });
        return n;
  }
  
@@@ -1757,7 -1737,7 +1735,7 @@@ MUTATOR_HOOKFUNCTION(ons, reset_map_glo
        FOREACH_CLIENT(IS_PLAYER(it), {
                it.ons_roundlost = false;
                it.ons_deathloc = '0 0 0';
-               WITHSELF(it, PutClientInServer());
+               PutClientInServer(it);
        });
        return false;
  }
@@@ -1943,7 -1923,7 +1921,7 @@@ void ons_MonsterSpawn_Delayed(entity th
  {
        entity own = this.owner;
  
-       if(!own) { remove(this); return; }
+       if(!own) { delete(this); return; }
  
        if(own.targetname)
        {
                }
        }
  
-       remove(this);
+       delete(this);
  }
  
  MUTATOR_HOOKFUNCTION(ons, MonsterSpawn)
@@@ -1972,7 -1952,7 +1950,7 @@@ void ons_TurretSpawn_Delayed(entity thi
  {
        entity own = this.owner;
  
-       if(!own) { remove(this); return; }
+       if(!own) { delete(this); return; }
  
        if(own.targetname)
        {
                }
        }
  
-       remove(this);
+       delete(this);
  }
  
  MUTATOR_HOOKFUNCTION(ons, TurretSpawn)
@@@ -2198,7 -2178,7 +2176,7 @@@ keys
   */
  spawnfunc(onslaught_link)
  {
-       if(!g_onslaught) { remove(this); return; }
+       if(!g_onslaught) { delete(this); return; }
  
        if (this.target == "" || this.target2 == "")
                objerror(this, "target and target2 must be set\n");
@@@ -2223,7 -2203,7 +2201,7 @@@ keys
  
  spawnfunc(onslaught_controlpoint)
  {
-       if(!g_onslaught) { remove(this); return; }
+       if(!g_onslaught) { delete(this); return; }
  
        ons_ControlPoint_Setup(this);
  }
@@@ -2239,7 -2219,7 +2217,7 @@@ keys
   */
  spawnfunc(onslaught_generator)
  {
-       if(!g_onslaught) { remove(this); return; }
+       if(!g_onslaught) { delete(this); return; }
        if(!this.team) { objerror(this, "team must be set"); }
  
        ons_GeneratorSetup(this);
  void ons_ScoreRules()
  {
        CheckAllowedTeams(NULL);
-       ScoreRules_basics(((c4>=0) ? 4 : (c3>=0) ? 3 : 2), SFL_SORT_PRIO_PRIMARY, 0, true);
+       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, 0, true);
        ScoreInfo_SetLabel_TeamScore  (ST_ONS_CAPS,     "destroyed", SFL_SORT_PRIO_PRIMARY);
 -      ScoreInfo_SetLabel_PlayerScore(SP_ONS_CAPS,     "caps",      SFL_SORT_PRIO_SECONDARY);
 +      ScoreInfo_SetLabel_PlayerScore(SP_ONS_CAPS,    "caps",      SFL_SORT_PRIO_SECONDARY);
        ScoreInfo_SetLabel_PlayerScore(SP_ONS_TAKES,    "takes",     0);
        ScoreRules_basics_end();
  }
diff --combined qcsrc/dpdefs/doc.md
index 3d96358fdbdbe42118ea126b131eb736f50a88b6,01fa43aaf28d2669bdc0132b2ecc5df5ad503c80..b49bb6f57ad72e7ab0ff6bed1b20925d6e26362a
@@@ -18,6 -18,7 +18,7 @@@
  bool CSQC_InputEvent(int eventtype, int x, int y);
  
  void CSQC_UpdateView(int width, int height);
+ // catch commands registered with registercommand
  bool CSQC_ConsoleCommand(string cmd);
  bool CSQC_Parse_TempEntity();
  bool CSQC_Parse_StuffCmd(string msg);
@@@ -54,6 -55,21 +55,21 @@@ float sound_starttime
  
  # SVQC
  
+ Main loop:
+ * SV_Physics()
+     * StartFrame()
+     * if (force_retouch)
+         * foreach entity:
+             * .touch()
+     * foreach client:
+         * PlayerPreThink()
+         * .think()
+         * PlayerPostThink()
+     * foreach nonclient:
+         * .think()
+     * EndFrame()
  ```
  
  .entity clientcamera;
@@@ -198,10 -214,6 +214,10 @@@ void SV_PlayerPhysics()
  //   self
  void SV_ParseClientCommand(string cmd);
  
 +// qcstatus server field
 +string worldstatus;
 +.string clientstatus;
 +
  ```
  
  # MENUQC
@@@ -233,3 -245,85 +249,85 @@@ void URI_Get_Callback(int id, int statu
  void GameCommand(string cmd);
  
  ```
+ # Misc
+ ## Trace
+ ### tracebox
+     void tracebox(vector v1, vector min, vector max, vector v2, int tryents, entity ignoreentity);
+ attempt to move an object from v1 to v2 of given size
+ tryents:
+  * MOVE_NORMAL (0)
+  * MOVE_NOMONSTERS (1): ignore monsters
+  * MOVE_MISSILE (2): +15 to every extent
+  * MOVE_WORLDONLY (3): ignore everything except bsp
+  * MOVE_HITMODEL (4): hit model, not bbox
+ ### traceline
+     void traceline(vector v1, vector v2, int tryents, entity ignoreentity);
+ degenerate case of tracebox when min and max are equal
+ ### result globals
+     bool trace_allsolid;
+ trace never left solid
+     bool trace_startsolid;
+ trace started inside solid
+     float trace_fraction;
+ distance before collision: 0..1, 1 if no collision
+     vector trace_endpos;
+ v1 + (v2 - v1) * trace_fraction
+     vector trace_plane_normal;
+ normalized plane normal, '0 0 0' if no collision.
+ May be present if edges touch without clipping, use `trace_fraction < 1` as a determinant instead
+     float trace_plane_dist;
+     entity trace_ent;
+ entity hit, if any
+     bool trace_inopen;
+     bool trace_inwater;
+     int trace_dpstartcontents;
+ DPCONTENTS_ value at start position of trace
+     int trace_dphitcontents;
+ DPCONTENTS_ value of impacted surface (not contents at impact point, just contents of the surface that was hit)
+     int trace_dphitq3surfaceflags;
+ Q3SURFACEFLAG_ value of impacted surface
+     string trace_dphittexturename;
+ texture name of impacted surface
+     int trace_networkentity;
diff --combined qcsrc/server/defs.qh
index caff3f59c6ad4ba3cb2a5943897869fc4c00cbc9,109f3ad0219ccd83349b077186df1500a4580cd2..53aa317e6874c97cba35cde6a7f3276284a9de3b
@@@ -35,7 -35,7 +35,7 @@@ float player_count
  float currentbots;
  float bots_would_leave;
  
 -void UpdateFrags(entity player, float f);
 +void UpdateFrags(entity player, int f);
  .float totalfrags;
  
  float team1_score, team2_score, team3_score, team4_score;
@@@ -253,6 -253,8 +253,8 @@@ float lockteams
  .float parm_idlesince;
  float sv_maxidle;
  float sv_maxidle_spectatorsareidle;
+ int sv_maxidle_slots;
+ bool sv_maxidle_slots_countbots;
  
  float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end);
  
@@@ -393,7 -395,6 +395,6 @@@ const float ACTIVE_TOGGLE  = 3
  .float player_blocked;
  .float weapon_blocked; // weapon use disabled
  
- .float frozen = _STAT(FROZEN); // for freeze attacks
  .float revive_progress = _STAT(REVIVE_PROGRESS);
  .float revival_time; // time at which player was last revived
  .float revive_speed; // NOTE: multiplier (anything above 1 is instaheal)
@@@ -442,3 -443,21 +443,21 @@@ const int MIF_GUIDED_CONFUSABLE = MIF_G
  
  .bool init_for_player_needed;
  .void(entity this, entity player) init_for_player;
+ IntrusiveList g_monsters;
+ STATIC_INIT(g_monsters) { g_monsters = IL_NEW(); }
+ IntrusiveList g_waypoints;
+ STATIC_INIT(g_waypoints) { g_waypoints = IL_NEW(); }
+ IntrusiveList g_vehicles;
+ STATIC_INIT(g_vehicles) { g_vehicles = IL_NEW(); }
+ IntrusiveList g_turrets;
+ STATIC_INIT(g_turrets) { g_turrets = IL_NEW(); }
+ IntrusiveList g_mines;
+ STATIC_INIT(g_mines) { g_mines = IL_NEW(); }
+ IntrusiveList g_projectiles;
+ STATIC_INIT(g_projectiles) { g_projectiles = IL_NEW(); }
diff --combined qcsrc/server/g_damage.qc
index 1fb6588c8f57f5b5bf2a4906346250facc6d20bf,ef1e78802efbe4ca679530fb5cbda2198d4e4fb5..debc54743275b0e1bfc64e9f6c5a2a4e21ef05f9
@@@ -25,7 -25,7 +25,7 @@@
  #include "../lib/csqcmodel/sv_model.qh"
  #include "../lib/warpzone/common.qh"
  
 -void UpdateFrags(entity player, float f)
 +void UpdateFrags(entity player, int f)
  {
        PlayerTeamScore_AddScore(player, f);
  }
@@@ -503,7 -503,7 +503,7 @@@ void Ice_Think(entity this
  {
        if(!STAT(FROZEN, this.owner) || this.owner.iceblock != this)
        {
-               remove(this);
+               delete(this);
                return;
        }
        setorigin(this, this.owner.origin - '0 0 16');
@@@ -569,7 -569,7 +569,7 @@@ void Unfreeze (entity targ
  
        // remove the ice block
        if(targ.iceblock)
-               remove(targ.iceblock);
+               delete(targ.iceblock);
        targ.iceblock = NULL;
  }
  
@@@ -837,7 -837,7 +837,7 @@@ void Damage (entity targ, entity inflic
        if (!IS_PLAYER(targ) || time >= targ.spawnshieldtime || targ == attacker)
        {
                vector farce = damage_explosion_calcpush(targ.damageforcescale * force, targ.velocity, autocvar_g_balance_damagepush_speedfactor);
-               if(targ.movetype == MOVETYPE_PHYSICS)
+               if(targ.move_movetype == MOVETYPE_PHYSICS)
                {
                        entity farcent = new(farce);
                        farcent.enemy = targ;
                else
                {
                        targ.velocity = targ.velocity + farce;
-                       targ.move_velocity = targ.velocity;
                }
                UNSET_ONGROUND(targ);
-               targ.move_flags &= ~FL_ONGROUND;
                UpdateCSQCProjectile(targ);
        }
        // apply damage
@@@ -1253,14 -1251,14 +1251,14 @@@ void fireburner_think(entity this
        // for players, this is done in the regular loop
        if(wasfreed(this.owner))
        {
-               remove(this);
+               delete(this);
                return;
        }
        Fire_ApplyEffect(this.owner);
        if(!Fire_IsBurning(this.owner))
        {
                this.owner.fire_burner = NULL;
-               remove(this);
+               delete(this);
                return;
        }
        Fire_ApplyDamage(this.owner);
index 6c3c8c1b7b31d4457aa7e4c1d047c88f90ce016e,257416538f52a5b1c3a9b5c0e09edf81c1dea250..0d53e054f48513fbdd716e289f5ad402f3c679be
@@@ -656,7 -656,7 +656,7 @@@ MUTATOR_HOOKABLE(SetChangeParms, EV_NO_
  MUTATOR_HOOKABLE(DecodeLevelParms, EV_NO_ARGS);
  
  #define EV_GetRecords(i, o) \
-     /** page */           i(int, MUTATOR_ARGV_1_int) \
+     /** page */           i(int, MUTATOR_ARGV_0_int) \
      /** record list */    i(string, MUTATOR_ARGV_1_string) \
      /**/                  o(string, MUTATOR_ARGV_1_string) \
      /**/
@@@ -748,7 -748,7 +748,7 @@@ MUTATOR_HOOKABLE(CheckRules_World, EV_C
  MUTATOR_HOOKABLE(WantWeapon, EV_WantWeapon);
  
  #define EV_AddPlayerScore(i, o) \
 -    /** score field */  i(int, MUTATOR_ARGV_0_int) \
 +    /** score field */  i(PlayerScoreField, MUTATOR_ARGV_0_entity) \
      /** score */        i(float, MUTATOR_ARGV_1_float) \
      /**/                o(float, MUTATOR_ARGV_1_float) \
      /** player */       i(entity, MUTATOR_ARGV_2_entity) \
index 8fd215b097d08fa7af513a569199b176f93a3a59,cace20ccf46fa9c1cfa2360a5056df842a01a79e..092e07b798be7518b135277dcc4c885bc5093224
@@@ -59,6 -59,7 +59,6 @@@ void(entity this, float ratingscale, ve
  
  // scoreboard stuff
  const float ST_ASSAULT_OBJECTIVES = 1;
 -const float SP_ASSAULT_OBJECTIVES = 4;
  
  // predefined spawnfuncs
  void target_objective_decrease_activate(entity this);
@@@ -139,18 -140,17 +139,17 @@@ void assault_objective_decrease_use(ent
  
  void assault_setenemytoobjective(entity this)
  {
-       entity objective;
-       for(objective = NULL; (objective = find(objective, targetname, this.target)); )
+       FOREACH_ENTITY_STRING(targetname, this.target,
        {
-               if(objective.classname == "target_objective")
+               if(it.classname == "target_objective")
                {
                        if(this.enemy == NULL)
-                               this.enemy = objective;
+                               this.enemy = it;
                        else
                                objerror(this, "more than one objective as target - fix the map!");
                        break;
                }
-       }
+       });
  
        if(this.enemy == NULL)
                objerror(this, "no objective as target - fix the map!");
@@@ -166,32 -166,32 +165,32 @@@ bool assault_decreaser_sprite_visible(e
  
  void target_objective_decrease_activate(entity this)
  {
-       entity ent, spr;
+       entity spr;
        this.owner = NULL;
-       for(ent = NULL; (ent = find(ent, target, this.targetname)); )
+       FOREACH_ENTITY_STRING(target, this.targetname,
        {
-               if(ent.assault_sprite != NULL)
+               if(it.assault_sprite != NULL)
                {
-                       WaypointSprite_Disown(ent.assault_sprite, waypointsprite_deadlifetime);
-                       if(ent.classname == "func_assault_destructible")
-                               ent.sprite = NULL; // TODO: just unsetting it?!
+                       WaypointSprite_Disown(it.assault_sprite, waypointsprite_deadlifetime);
+                       if(it.classname == "func_assault_destructible")
+                               it.sprite = NULL; // TODO: just unsetting it?!
                }
  
-               spr = WaypointSprite_SpawnFixed(WP_Assault, 0.5 * (ent.absmin + ent.absmax), ent, assault_sprite, RADARICON_OBJECTIVE);
+               spr = WaypointSprite_SpawnFixed(WP_Assault, 0.5 * (it.absmin + it.absmax), it, assault_sprite, RADARICON_OBJECTIVE);
                spr.assault_decreaser = this;
                spr.waypointsprite_visible_for_player = assault_decreaser_sprite_visible;
                spr.classname = "sprite_waypoint";
                WaypointSprite_UpdateRule(spr, assault_attacker_team, SPRITERULE_TEAMPLAY);
-               if(ent.classname == "func_assault_destructible")
+               if(it.classname == "func_assault_destructible")
                {
                        WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultDestroy, WP_AssaultDestroy);
-                       WaypointSprite_UpdateMaxHealth(spr, ent.max_health);
-                       WaypointSprite_UpdateHealth(spr, ent.health);
-                       ent.sprite = spr;
+                       WaypointSprite_UpdateMaxHealth(spr, it.max_health);
+                       WaypointSprite_UpdateHealth(spr, it.health);
+                       it.sprite = spr;
                }
                else
                        WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultPush, WP_AssaultPush);
-       }
+       });
  }
  
  void target_objective_decrease_findtarget(entity this)
@@@ -259,10 -259,11 +258,11 @@@ void assault_new_round(entity this
        // Eject players from vehicles
      FOREACH_CLIENT(IS_PLAYER(it) && it.vehicle, vehicles_exit(it.vehicle, VHEF_RELEASE));
  
-     FOREACH_ENTITY_FLAGS(vehicle_flags, VHF_ISVEHICLE, LAMBDA(
+     IL_EACH(g_vehicles, true,
+     {
          vehicles_clearreturn(it);
          vehicles_spawn(it);
-     ));
+     });
  
        // up round counter
        this.winning = this.winning + 1;
        else
                assault_attacker_team = NUM_TEAM_1;
  
-       FOREACH_ENTITY(IS_NOT_A_CLIENT(it), LAMBDA(
+       FOREACH_ENTITY_FLOAT(pure_data, false,
+       {
+               if(IS_CLIENT(it))
+                       continue;
                if (it.team_saved == NUM_TEAM_1) it.team_saved = NUM_TEAM_2;
                else if (it.team_saved == NUM_TEAM_2) it.team_saved = NUM_TEAM_1;
-       ));
+       });
  
        // reset the level with a countdown
        cvar_set("timelimit", ftos(ceil(time - game_starttime) / 60));
@@@ -328,7 -333,7 +332,7 @@@ int WinningCondition_Assault(
  // spawnfuncs
  spawnfunc(info_player_attacker)
  {
-       if (!g_assault) { remove(this); return; }
+       if (!g_assault) { delete(this); return; }
  
        this.team = NUM_TEAM_1; // red, gets swapped every round
        spawnfunc_info_player_deathmatch(this);
  
  spawnfunc(info_player_defender)
  {
-       if (!g_assault) { remove(this); return; }
+       if (!g_assault) { delete(this); return; }
  
        this.team = NUM_TEAM_2; // blue, gets swapped every round
        spawnfunc_info_player_deathmatch(this);
  
  spawnfunc(target_objective)
  {
-       if (!g_assault) { remove(this); return; }
+       if (!g_assault) { delete(this); return; }
  
        this.classname = "target_objective";
        this.use = assault_objective_use;
  
  spawnfunc(target_objective_decrease)
  {
-       if (!g_assault) { remove(this); return; }
+       if (!g_assault) { delete(this); return; }
  
        this.classname = "target_objective_decrease";
  
  spawnfunc(func_breakable);
  spawnfunc(func_assault_destructible)
  {
-       if (!g_assault) { remove(this); return; }
+       if (!g_assault) { delete(this); return; }
  
        this.spawnflags = 3;
        this.classname = "func_assault_destructible";
  
  spawnfunc(func_assault_wall)
  {
-       if (!g_assault) { remove(this); return; }
+       if (!g_assault) { delete(this); return; }
  
        this.classname = "func_assault_wall";
        this.mdl = this.model;
  
  spawnfunc(target_assault_roundend)
  {
-       if (!g_assault) { remove(this); return; }
+       if (!g_assault) { delete(this); return; }
  
        this.winning = 0; // round not yet won by attackers
        this.classname = "target_assault_roundend";
  
  spawnfunc(target_assault_roundstart)
  {
-       if (!g_assault) { remove(this); return; }
+       if (!g_assault) { delete(this); return; }
  
        assault_attacker_team = NUM_TEAM_1;
        this.classname = "target_assault_roundstart";
  // legacy bot code
  void havocbot_goalrating_ast_targets(entity this, float ratingscale)
  {
-       entity ad, best, wp, tod;
-       float radius, found, bestvalue;
-       vector p;
-       ad = findchain(classname, "func_assault_destructible");
-       for (; ad; ad = ad.chain)
+       FOREACH_ENTITY_CLASS("func_assault_destructible", it.bot_attack,
        {
-               if (ad.target == "")
+               if (it.target == "")
                        continue;
  
-               if (!ad.bot_attack)
-                       continue;
-               found = false;
-               for(tod = NULL; (tod = find(tod, targetname, ad.target)); )
+               bool found = false;
+               FOREACH_ENTITY_STRING(targetname, it.target,
                {
-                       if(tod.classname == "target_objective_decrease")
+                       if(it.classname != "target_objective_decrease")
+                               continue;
+                       if(it.enemy.health > 0 && it.enemy.health < ASSAULT_VALUE_INACTIVE)
                        {
-                               if(tod.enemy.health > 0 && tod.enemy.health < ASSAULT_VALUE_INACTIVE)
-                               {
-                               //      dprint(etos(ad),"\n");
-                                       found = true;
-                                       break;
-                               }
+                               found = true;
+                               break;
                        }
-               }
+               });
  
                if(!found)
-               {
-               ///     dprint("target not found\n");
                        continue;
-               }
-               /// dprint("target #", etos(ad), " found\n");
  
-               p = 0.5 * (ad.absmin + ad.absmax);
-       //      dprint(vtos(ad.origin), " ", vtos(ad.absmin), " ", vtos(ad.absmax),"\n");
-       //      te_knightspike(p);
-       //      te_lightning2(NULL, '0 0 0', p);
+               vector p = 0.5 * (it.absmin + it.absmax);
  
                // Find and rate waypoints around it
                found = false;
-               best = NULL;
-               bestvalue = 99999999999;
-               for(radius=0; radius<1500 && !found; radius+=500)
+               entity best = NULL;
+               float bestvalue = 99999999999;
+               entity des = it;
+               for(float radius = 0; radius < 1500 && !found; radius += 500)
                {
-                       for(wp=findradius(p, radius); wp; wp=wp.chain)
+                       FOREACH_ENTITY_RADIUS(p, radius, it.classname == "waypoint" && !(it.wpflags & WAYPOINTFLAG_GENERATED),
                        {
-                               if(!(wp.wpflags & WAYPOINTFLAG_GENERATED))
-                               if(wp.classname=="waypoint")
-                               if(checkpvs(wp.origin, ad))
+                               if(checkpvs(it.origin, des))
                                {
                                        found = true;
-                                       if(wp.cnt<bestvalue)
+                                       if(it.cnt < bestvalue)
                                        {
-                                               best = wp;
-                                               bestvalue = wp.cnt;
+                                               best = it;
+                                               bestvalue = it.cnt;
                                        }
                                }
-                       }
+                       });
                }
  
                if(best)
  
                        this.havocbot_attack_time = 0;
  
-                       if(checkpvs(this.view_ofs,ad))
+                       if(checkpvs(this.view_ofs,it))
                        if(checkpvs(this.view_ofs,best))
                        {
                        //      dprint("increasing attack time for this target\n");
                                this.havocbot_attack_time = time + 2;
                        }
                }
-       }
+       });
  }
  
  void havocbot_role_ast_offense(entity this)
@@@ -682,7 -668,11 +667,11 @@@ MUTATOR_HOOKFUNCTION(as, OnEntityPreSpa
  // scoreboard setup
  void assault_ScoreRules()
  {
-       ScoreRules_basics(2, SFL_SORT_PRIO_SECONDARY, SFL_SORT_PRIO_SECONDARY, true);
+       int teams = 0;
+       teams |= BIT(0);
+       teams |= BIT(1); // always red vs blue
+       ScoreRules_basics(teams, SFL_SORT_PRIO_SECONDARY, SFL_SORT_PRIO_SECONDARY, true);
        ScoreInfo_SetLabel_TeamScore(  ST_ASSAULT_OBJECTIVES,    "objectives",      SFL_SORT_PRIO_PRIMARY);
        ScoreInfo_SetLabel_PlayerScore(SP_ASSAULT_OBJECTIVES,    "objectives",      SFL_SORT_PRIO_PRIMARY);
        ScoreRules_basics_end();
index 86ce54bc8e18d979d1cfb0b861b9c8d632f9745d,5274072cf1ff85e9f9e1a885a9430199db30f267..7f5b0ccbd1e4c61c5cd2ca9e415b380e7d4c7082
@@@ -152,12 -152,39 +152,39 @@@ void ctf_CaptureRecord(entity flag, ent
        }
  }
  
+ bool ctf_Return_Customize(entity this, entity client)
+ {
+       // only to the carrier
+       return boolean(client == this.owner);
+ }
  void ctf_FlagcarrierWaypoints(entity player)
  {
        WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
        WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
        WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
        WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
+       if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
+       {
+               if(!player.wps_enemyflagcarrier)
+               {
+                       entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG);
+                       wp.colormod = WPCOLOR_ENEMYFC(player.team);
+                       setcefc(wp, ctf_Stalemate_Customize);
+                       if(IS_REAL_CLIENT(player) && !ctf_stalemate)
+                               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE);
+               }
+               if(!player.wps_flagreturn)
+               {
+                       entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG);
+                       owp.colormod = '0 0.8 0.8';
+                       //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1'));
+                       setcefc(owp, ctf_Return_Customize);
+               }
+       }
  }
  
  void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
@@@ -233,7 -260,7 +260,7 @@@ bool ctf_CaptureShield_CheckStatus(enti
        if(ctf_captureshield_max_ratio <= 0)
                return false;
  
 -      s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
 +      s  = PlayerScore_Add(p, SP_CTF_CAPS,    0);
        s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
        s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
        s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
        FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
                if(DIFF_TEAM(it, p))
                        continue;
 -              se = PlayerScore_Add(it, SP_CTF_CAPS, 0);
 +              se  = PlayerScore_Add(it, SP_CTF_CAPS,    0);
                se2 = PlayerScore_Add(it, SP_CTF_PICKUPS, 0);
                se3 = PlayerScore_Add(it, SP_CTF_RETURNS, 0);
                se4 = PlayerScore_Add(it, SP_CTF_FCKILLS, 0);
@@@ -278,24 -305,24 +305,24 @@@ void ctf_CaptureShield_Update(entity pl
        }
  }
  
- bool ctf_CaptureShield_Customize(entity this)
+ bool ctf_CaptureShield_Customize(entity this, entity client)
  {
-       if(!other.ctf_captureshielded) { return false; }
-       if(CTF_SAMETEAM(this, other)) { return false; }
+       if(!client.ctf_captureshielded) { return false; }
+       if(CTF_SAMETEAM(this, client)) { return false; }
  
        return true;
  }
  
- void ctf_CaptureShield_Touch(entity this)
+ void ctf_CaptureShield_Touch(entity this, entity toucher)
  {
-       if(!other.ctf_captureshielded) { return; }
-       if(CTF_SAMETEAM(this, other)) { return; }
+       if(!toucher.ctf_captureshielded) { return; }
+       if(CTF_SAMETEAM(this, toucher)) { return; }
  
        vector mymid = (this.absmin + this.absmax) * 0.5;
-       vector othermid = (other.absmin + other.absmax) * 0.5;
+       vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
  
-       Damage(other, this, this, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
-       if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
+       Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
+       if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
  }
  
  void ctf_CaptureShield_Spawn(entity flag)
        settouch(shield, ctf_CaptureShield_Touch);
        setcefc(shield, ctf_CaptureShield_Customize);
        shield.effects = EF_ADDITIVE;
-       shield.movetype = MOVETYPE_NOCLIP;
+       set_movetype(shield, MOVETYPE_NOCLIP);
        shield.solid = SOLID_TRIGGER;
        shield.avelocity = '7 0 11';
        shield.scale = 0.5;
@@@ -328,7 -355,7 +355,7 @@@ void ctf_Handle_Drop(entity flag, entit
        player = (player ? player : flag.pass_sender);
  
        // main
-       flag.movetype = MOVETYPE_TOSS;
+       set_movetype(flag, MOVETYPE_TOSS);
        flag.takedamage = DAMAGE_YES;
        flag.angles = '0 0 0';
        flag.health = flag.max_flag_health;
@@@ -387,7 -414,7 +414,7 @@@ void ctf_Handle_Retrieve(entity flag, e
                setattachment(flag, player, "");
                setorigin(flag, FLAG_CARRY_OFFSET);
        }
-       flag.movetype = MOVETYPE_NONE;
+       set_movetype(flag, MOVETYPE_NONE);
        flag.takedamage = DAMAGE_NO;
        flag.solid = SOLID_NOT;
        flag.angles = '0 0 0';
@@@ -453,7 -480,7 +480,7 @@@ void ctf_Handle_Throw(entity player, en
                        ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
  
                        // main
-                       flag.movetype = MOVETYPE_FLY;
+                       set_movetype(flag, MOVETYPE_FLY);
                        flag.takedamage = DAMAGE_NO;
                        flag.pass_sender = player;
                        flag.pass_target = receiver;
        if(player.wps_enemyflagcarrier)
                WaypointSprite_Kill(player.wps_enemyflagcarrier);
  
+       if(player.wps_flagreturn)
+               WaypointSprite_Kill(player.wps_flagreturn);
        // captureshield
        ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
  }
@@@ -638,7 -668,7 +668,7 @@@ void ctf_Handle_Pickup(entity flag, ent
        }
  
        // flag setup
-       flag.movetype = MOVETYPE_NONE;
+       set_movetype(flag, MOVETYPE_NONE);
        flag.takedamage = DAMAGE_NO;
        flag.solid = SOLID_NOT;
        flag.angles = '0 0 0';
@@@ -747,15 -777,14 +777,14 @@@ void ctf_CheckFlagReturn(entity flag, i
        }
  }
  
- bool ctf_Stalemate_Customize(entity this)
+ bool ctf_Stalemate_Customize(entity this, entity client)
  {
        // make spectators see what the player would see
-       entity e, wp_owner;
-       e = WaypointSprite_getviewentity(other);
-       wp_owner = this.owner;
+       entity e = WaypointSprite_getviewentity(client);
+       entity wp_owner = this.owner;
  
        // team waypoints
-       if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
+       //if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
        if(SAME_TEAM(wp_owner, e)) { return false; }
        if(!IS_PLAYER(e)) { return false; }
  
@@@ -908,9 -937,9 +937,9 @@@ void ctf_FlagThink(entity this
                                        if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
                                                { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
                                        else
-                                               { this.movetype = MOVETYPE_FLY; }
+                                               { set_movetype(this, MOVETYPE_FLY); }
                                }
-                               else if(this.movetype == MOVETYPE_FLY) { this.movetype = MOVETYPE_TOSS; }
+                               else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
                        }
                        if(autocvar_g_ctf_flag_return_dropped)
                        {
                        if((this.pass_target == NULL)
                                || (IS_DEAD(this.pass_target))
                                || (this.pass_target.flagcarried)
-                               || (vdist(this.origin - targ_origin, <, autocvar_g_ctf_pass_radius))
+                               || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
                                || ((trace_fraction < 1) && (trace_ent != this.pass_target))
                                || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
                        {
@@@ -1109,6 -1138,7 +1138,7 @@@ void ctf_RespawnFlag(entity flag
        if((flag.owner) && (flag.owner.flagcarried == flag))
        {
                WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
+               WaypointSprite_Kill(flag.owner.wps_flagreturn);
                WaypointSprite_Kill(flag.wps_flagcarrier);
  
                flag.owner.flagcarried = NULL;
        setattachment(flag, NULL, "");
        setorigin(flag, flag.ctf_spawnorigin);
  
-       flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
+       set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
        flag.takedamage = DAMAGE_NO;
        flag.health = flag.max_flag_health;
        flag.solid = SOLID_TRIGGER;
@@@ -1156,6 -1186,13 +1186,13 @@@ void ctf_Reset(entity this
        ctf_RespawnFlag(this);
  }
  
+ bool ctf_FlagBase_Customize(entity this, entity client)
+ {
+       if(client.flagcarried && CTF_SAMETEAM(client, client.flagcarried))
+               return false;
+       return true;
+ }
  void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
  {
        // bot waypoints
        entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
        wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
        WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
+       setcefc(wp, ctf_FlagBase_Customize);
  
        // captureshield setup
        ctf_CaptureShield_Spawn(this);
@@@ -1280,13 -1318,13 +1318,13 @@@ void ctf_FlagSetup(int teamnumber, enti
        {
                flag.dropped_origin = flag.origin;
                flag.noalign = true;
-               flag.movetype = MOVETYPE_NONE;
+               set_movetype(flag, MOVETYPE_NONE);
        }
        else // drop to floor, automatically find a platform and set that as spawn origin
        {
                flag.noalign = false;
                droptofloor(flag);
-               flag.movetype = MOVETYPE_TOSS;
+               set_movetype(flag, MOVETYPE_NONE);
        }
  
        InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
@@@ -1490,23 -1528,19 +1528,19 @@@ void havocbot_goalrating_ctf_droppedfla
  
  void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
  {
-       entity head;
-       float t;
-       head = findchainfloat(bot_pickup, true);
-       while (head)
+       FOREACH_ENTITY_FLOAT(bot_pickup, true,
        {
                // gather health and armor only
-               if (head.solid)
-               if (head.health || head.armorvalue)
-               if (vdist(head.origin - org, <, sradius))
+               if (it.solid)
+               if (it.health || it.armorvalue)
+               if (vdist(it.origin - org, <, sradius))
                {
                        // get the value of the item
-                       t = head.bot_pickupevalfunc(this, head) * 0.0001;
+                       float t = it.bot_pickupevalfunc(this, it) * 0.0001;
                        if (t > 0)
-                               navigation_routerating(this, head, t * ratingscale, 500);
+                               navigation_routerating(this, it, t * ratingscale, 500);
                }
-               head = head.chain;
-       }
+       });
  }
  
  void havocbot_ctf_reset_role(entity this)
@@@ -1987,7 -2021,7 +2021,7 @@@ MUTATOR_HOOKFUNCTION(ctf, PlayerPreThin
                                                   | CTF_YELLOW_FLAG_CARRYING   | CTF_YELLOW_FLAG_TAKEN         | CTF_YELLOW_FLAG_LOST
                                                   | CTF_PINK_FLAG_CARRYING     | CTF_PINK_FLAG_TAKEN           | CTF_PINK_FLAG_LOST
                                                   | CTF_NEUTRAL_FLAG_CARRYING  | CTF_NEUTRAL_FLAG_TAKEN        | CTF_NEUTRAL_FLAG_LOST
-                                                  | CTF_FLAG_NEUTRAL | CTF_SHIELDED);
+                                                  | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
  
        // scan through all the flags and notify the client about them
        for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
        if(player.ctf_captureshielded)
                player.ctf_flagstatus |= CTF_SHIELDED;
  
+       if(ctf_stalemate)
+               player.ctf_flagstatus |= CTF_STALEMATE;
        // update the health of the flag carrier waypointsprite
        if(player.wps_flagcarrier)
                WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
@@@ -2238,14 -2275,13 +2275,13 @@@ MUTATOR_HOOKFUNCTION(ctf, VehicleEnter
  
        if(player.flagcarried)
        {
-               player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
                if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
                {
                        ctf_Handle_Throw(player, NULL, DROP_NORMAL);
                }
                else
                {
+                       player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
                        setattachment(player.flagcarried, veh, "");
                        setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
                        player.flagcarried.scale = VEHICLE_FLAG_SCALE;
@@@ -2294,7 -2330,7 +2330,7 @@@ MUTATOR_HOOKFUNCTION(ctf, MatchEnd
                        case FLAG_PASSING:
                        {
                                // lock the flag, game is over
-                               flag.movetype = MOVETYPE_NONE;
+                               set_movetype(flag, MOVETYPE_NONE);
                                flag.takedamage = DAMAGE_NO;
                                flag.solid = SOLID_NOT;
                                flag.nextthink = false; // stop thinking
@@@ -2382,10 -2418,10 +2418,10 @@@ MUTATOR_HOOKFUNCTION(ctf, SV_ParseClien
                {
                        switch(argv(1))
                        {
-                               case "red": _team = NUM_TEAM_1; break;
-                               case "blue": _team = NUM_TEAM_2; break;
-                               case "yellow": if(ctf_teams >= 3) _team = NUM_TEAM_3; break;
-                               case "pink": if(ctf_teams >= 4) _team = NUM_TEAM_4; break;
+                               case "red":    if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
+                               case "blue":   if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
+                               case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
+                               case "pink":   if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
                        }
                }
  
@@@ -2431,7 -2467,7 +2467,7 @@@ Keys
  "noise5" sound played when flag touches the ground... */
  spawnfunc(item_flag_team1)
  {
-       if(!g_ctf) { remove(this); return; }
+       if(!g_ctf) { delete(this); return; }
  
        ctf_FlagSetup(NUM_TEAM_1, this);
  }
@@@ -2449,7 -2485,7 +2485,7 @@@ Keys
  "noise5" sound played when flag touches the ground... */
  spawnfunc(item_flag_team2)
  {
-       if(!g_ctf) { remove(this); return; }
+       if(!g_ctf) { delete(this); return; }
  
        ctf_FlagSetup(NUM_TEAM_2, this);
  }
@@@ -2467,7 -2503,7 +2503,7 @@@ Keys
  "noise5" sound played when flag touches the ground... */
  spawnfunc(item_flag_team3)
  {
-       if(!g_ctf) { remove(this); return; }
+       if(!g_ctf) { delete(this); return; }
  
        ctf_FlagSetup(NUM_TEAM_3, this);
  }
@@@ -2485,7 -2521,7 +2521,7 @@@ Keys
  "noise5" sound played when flag touches the ground... */
  spawnfunc(item_flag_team4)
  {
-       if(!g_ctf) { remove(this); return; }
+       if(!g_ctf) { delete(this); return; }
  
        ctf_FlagSetup(NUM_TEAM_4, this);
  }
@@@ -2503,8 -2539,8 +2539,8 @@@ Keys
  "noise5" sound played when flag touches the ground... */
  spawnfunc(item_flag_neutral)
  {
-       if(!g_ctf) { remove(this); return; }
-       if(!cvar("g_ctf_oneflag")) { remove(this); return; }
+       if(!g_ctf) { delete(this); return; }
+       if(!cvar("g_ctf_oneflag")) { delete(this); return; }
  
        ctf_FlagSetup(0, this);
  }
@@@ -2517,7 -2553,7 +2553,7 @@@ Keys
  "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
  spawnfunc(ctf_team)
  {
-       if(!g_ctf) { remove(this); return; }
+       if(!g_ctf) { delete(this); return; }
  
        this.classname = "ctf_team";
        this.team = this.cnt + 1;
@@@ -2547,12 -2583,12 +2583,12 @@@ void ctf_ScoreRules(int teams
        CheckAllowedTeams(NULL);
        ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
        ScoreInfo_SetLabel_TeamScore  (ST_CTF_CAPS,     "caps",      SFL_SORT_PRIO_PRIMARY);
 -      ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS,     "caps",      SFL_SORT_PRIO_SECONDARY);
 -      ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME,  "captime",   SFL_LOWER_IS_BETTER | SFL_TIME);
 -      ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS,  "pickups",   0);
 -      ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS,  "fckills",   0);
 -      ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS,  "returns",   0);
 -      ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS,    "drops",     SFL_LOWER_IS_BETTER);
 +      ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS,    "caps",      SFL_SORT_PRIO_SECONDARY);
 +      ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime",   SFL_LOWER_IS_BETTER | SFL_TIME);
 +      ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups",   0);
 +      ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills",   0);
 +      ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns",   0);
 +      ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS,   "drops",     SFL_LOWER_IS_BETTER);
        ScoreRules_basics_end();
  }
  
@@@ -2561,35 -2597,52 +2597,52 @@@ void ctf_SpawnTeam (string teamname, in
  {
        entity this = new_pure(ctf_team);
        this.netname = teamname;
-       this.cnt = teamcolor;
+       this.cnt = teamcolor - 1;
        this.spawnfunc_checked = true;
-       spawnfunc_ctf_team(this);
+       this.team = teamcolor;
  }
  
  void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
  {
-       ctf_teams = 2;
+       ctf_teams = 0;
  
        entity tmp_entity;
        for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
        {
-               if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
-               if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
+               //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
+               //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
+               switch(tmp_entity.team)
+               {
+                       case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
+                       case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
+                       case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
+                       case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
+               }
                if(tmp_entity.team == 0) { ctf_oneflag = true; }
        }
  
-       ctf_teams = bound(2, ctf_teams, 4);
+       if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
+       {
+               ctf_teams = 0; // so set the default red and blue teams
+               BITSET_ASSIGN(ctf_teams, BIT(0));
+               BITSET_ASSIGN(ctf_teams, BIT(1));
+       }
+       //ctf_teams = bound(2, ctf_teams, 4);
  
        // if no teams are found, spawn defaults
        if(find(NULL, classname, "ctf_team") == NULL)
        {
                LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.\n");
-               ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
-               ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
-               if(ctf_teams >= 3)
-                       ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1);
-               if(ctf_teams >= 4)
-                       ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1);
+               if(ctf_teams & BIT(0))
+                       ctf_SpawnTeam("Red", NUM_TEAM_1);
+               if(ctf_teams & BIT(1))
+                       ctf_SpawnTeam("Blue", NUM_TEAM_2);
+               if(ctf_teams & BIT(2))
+                       ctf_SpawnTeam("Yellow", NUM_TEAM_3);
+               if(ctf_teams & BIT(3))
+                       ctf_SpawnTeam("Pink", NUM_TEAM_4);
        }
  
        ctf_ScoreRules(ctf_teams);
index 20e67a69103f3fc475ff7308d098c2ca211afa09,b4d2459c3e26ed078e84ff89a11afa0b5725939c..c9d5b5aedcce94b2a64755784f120131295c43af
@@@ -8,13 -8,19 +8,13 @@@ void ctf_RespawnFlag(entity flag)
  
  // score rule declarations
  const int ST_CTF_CAPS = 1;
 -const int SP_CTF_CAPS = 4;
 -const int SP_CTF_CAPTIME = 5;
 -const int SP_CTF_PICKUPS = 6;
 -const int SP_CTF_DROPS = 7;
 -const int SP_CTF_FCKILLS = 8;
 -const int SP_CTF_RETURNS = 9;
  
  CLASS(Flag, Pickup)
      ATTRIB(Flag, m_mins, vector, PL_MIN_CONST + '0 0 -13')
      ATTRIB(Flag, m_maxs, vector, PL_MAX_CONST + '0 0 -13')
  ENDCLASS(Flag)
  Flag CTF_FLAG; STATIC_INIT(Flag) { CTF_FLAG = NEW(Flag); }
- void ctf_FlagTouch(entity this) { ITEM_HANDLE(Pickup, CTF_FLAG, this, other); }
+ void ctf_FlagTouch(entity this, entity toucher) { ITEM_HANDLE(Pickup, CTF_FLAG, this, toucher); }
  
  // flag constants // for most of these, there is just one question to be asked: WHYYYYY?
  
@@@ -64,6 -70,7 +64,7 @@@ entity ctf_worldflaglist
  .entity wps_flagbase;
  .entity wps_flagcarrier;
  .entity wps_flagdropped;
+ .entity wps_flagreturn;
  .entity wps_enemyflagcarrier;
  .float wps_helpme_time;
  bool wpforenemy_announced;
@@@ -92,6 -99,8 +93,8 @@@ const int RETURN_DAMAGE = 3
  const int RETURN_SPEEDRUN = 4;
  const int RETURN_NEEDKILL = 5;
  
+ bool ctf_Stalemate_Customize(entity this, entity client);
  void ctf_Handle_Throw(entity player, entity receiver, float droptype);
  
  // flag properties
@@@ -165,3 -174,4 +168,4 @@@ const int CTF_NEUTRAL_FLAG_LOST                    = 512
  const int CTF_NEUTRAL_FLAG_CARRYING           = 768;
  const int CTF_FLAG_NEUTRAL                            = 2048;
  const int CTF_SHIELDED                                        = 4096;
+ const int CTF_STALEMATE                                       = 8192;
index 312a2adaaf37d72750a169edfc7c6b932534c8d4,187b4aa951c46c8239bc86e5ffe8f89121ced3d0..8efa3a35dacf17aa9380de07a23976134946fac6
@@@ -38,6 -38,9 +38,6 @@@ REGISTER_MUTATOR(cts, false
  
  // scores
  const float ST_CTS_LAPS = 1;
 -const float SP_CTS_LAPS = 4;
 -const float SP_CTS_TIME = 5;
 -const float SP_CTS_FASTEST = 6;
  #endif
  
  #ifdef IMPLEMENTATION
@@@ -124,7 -127,7 +124,7 @@@ MUTATOR_HOOKFUNCTION(cts, PlayerPhysics
                if(player.race_penalty)
                {
                        player.velocity = '0 0 0';
-                       player.movetype = MOVETYPE_NONE;
+                       set_movetype(player, MOVETYPE_NONE);
                        player.disableclientprediction = 2;
                }
        }
@@@ -403,7 -406,7 +403,7 @@@ MUTATOR_HOOKFUNCTION(cts, ClientKill
  
        if(player.killindicator && player.killindicator.health == 1) // player.killindicator.health == 1 means that the kill indicator was spawned by CTS_ClientKill
        {
-               remove(player.killindicator);
+               delete(player.killindicator);
                player.killindicator = NULL;
  
                ClientKill_Now(player); // allow instant kill in this case
index 546690fe2098e8b47a9cc46cdad0d93ef72a01ba,7d36e3a622d12ae1c285e0190d8a374904155d88..16fa08c81627b49237ac3c908422f464fcc65307
@@@ -37,7 -37,10 +37,7 @@@ REGISTER_MUTATOR(dom, false
  
  // score rule declarations
  const float ST_DOM_TICKS = 1;
 -const float SP_DOM_TICKS = 4;
 -const float SP_DOM_TAKES = 5;
  const float ST_DOM_CAPS = 1;
 -const float SP_DOM_CAPS = 4;
  
  // pps: points per second
  .float dom_total_pps = _STAT(DOM_TOTAL_PPS);
@@@ -162,7 -165,6 +162,6 @@@ void dompoint_captured(entity this
        WaypointSprite_UpdateSprites(this.sprite, msg, WP_Null, WP_Null);
  
        total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
-       for(head = NULL; (head = find(head, classname, "dom_controlpoint")) != NULL; )
        FOREACH_ENTITY_CLASS("dom_controlpoint", true, LAMBDA(
                if (autocvar_g_domination_point_amt)
                        points = autocvar_g_domination_point_amt;
@@@ -247,11 -249,11 +246,11 @@@ void dompointthink(entity this
        }
  }
  
- void dompointtouch(entity this)
+ void dompointtouch(entity this, entity toucher)
  {
-       if (!IS_PLAYER(other))
+       if (!IS_PLAYER(toucher))
                return;
-       if (other.health < 1)
+       if (toucher.health < 1)
                return;
  
        if(round_handler_IsActive() && !round_handler_IsRoundStarted())
  
        // only valid teams can claim it
        entity head = find(NULL, classname, "dom_team");
-       while (head && head.team != other.team)
+       while (head && head.team != toucher.team)
                head = find(head, classname, "dom_team");
        if (!head || head.netname == "" || head == this.goalentity)
                return;
  
        this.team = this.goalentity.team; // this stores the PREVIOUS team!
  
-       this.cnt = other.team;
+       this.cnt = toucher.team;
        this.owner = head; // team to switch to after the delay
-       this.dmg_inflictor = other;
+       this.dmg_inflictor = toucher;
  
        // this.state = 1;
        // this.delay = time + cvar("g_domination_point_capturetime");
        this.modelindex = head.dmg;
        this.skin = head.skin;
  
-       this.enemy = other; // individual player scoring
-       this.enemy_playerid = other.playerid;
+       this.enemy = toucher; // individual player scoring
+       this.enemy_playerid = toucher.playerid;
        dompoint_captured(this);
  }
  
@@@ -359,16 -361,15 +358,15 @@@ void dom_controlpoint_setup(entity this
  float total_controlpoints;
  void Domination_count_controlpoints()
  {
-       entity e;
        total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
-       for(e = NULL; (e = find(e, classname, "dom_controlpoint")) != NULL; )
+       FOREACH_ENTITY_CLASS("dom_controlpoint", true,
        {
                ++total_controlpoints;
-               redowned += (e.goalentity.team == NUM_TEAM_1);
-               blueowned += (e.goalentity.team == NUM_TEAM_2);
-               yellowowned += (e.goalentity.team == NUM_TEAM_3);
-               pinkowned += (e.goalentity.team == NUM_TEAM_4);
-       }
+               redowned += (it.goalentity.team == NUM_TEAM_1);
+               blueowned += (it.goalentity.team == NUM_TEAM_2);
+               yellowowned += (it.goalentity.team == NUM_TEAM_3);
+               pinkowned += (it.goalentity.team == NUM_TEAM_4);
+       });
  }
  
  float Domination_GetWinnerTeam()
@@@ -492,7 -493,7 +490,7 @@@ MUTATOR_HOOKFUNCTION(dom, reset_map_pla
  {
        total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
        FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
-               WITHSELF(it, PutClientInServer());
+               PutClientInServer(it);
                if(domination_roundbased)
                        it.player_blocked = 1;
                if(IS_REAL_CLIENT(it))
@@@ -534,7 -535,7 +532,7 @@@ spawnfunc(dom_controlpoint
  {
        if(!g_domination)
        {
-               remove(this);
+               delete(this);
                return;
        }
        setthink(this, dom_controlpoint_setup);
@@@ -579,7 -580,7 +577,7 @@@ spawnfunc(dom_team
  {
        if(!g_domination || autocvar_g_domination_teams_override >= 2)
        {
-               remove(this);
+               delete(this);
                return;
        }
        precache_model(this.model);
  }
  
  // scoreboard setup
- void ScoreRules_dom(float teams)
+ void ScoreRules_dom(int teams)
  {
        if(domination_roundbased)
        {
@@@ -683,7 -684,14 +681,14 @@@ void dom_DelayedInit(entity this) // D
        }
  
        CheckAllowedTeams(NULL);
-       domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
+       //domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
+       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);
+       domination_teams = teams;
  
        domination_roundbased = autocvar_g_domination_roundbased;
  
index b20269c4297a9fe335c0b9fa4afe1ac310f24570,93547989097eb43aad853fa417268146eb14d1e9..c64118cd61d56b461aaf8898688863bc1b8fb7ec
@@@ -61,7 -61,8 +61,7 @@@ int autocvar_g_freezetag_teams
  int autocvar_g_freezetag_teams_override;
  float autocvar_g_freezetag_warmup;
  
- void freezetag_ScoreRules(float teams)
 -const float SP_FREEZETAG_REVIVALS = 4;
+ void freezetag_ScoreRules(int teams)
  {
        ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, true); // SFL_SORT_PRIO_PRIMARY
        ScoreInfo_SetLabel_PlayerScore(SP_FREEZETAG_REVIVALS, "revivals", 0);
@@@ -90,7 -91,7 +90,7 @@@ void freezetag_count_alive_players(
        eliminatedPlayers.SendFlags |= 1;
  }
  #define FREEZETAG_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
- #define FREEZETAG_ALIVE_TEAMS_OK() (FREEZETAG_ALIVE_TEAMS() == freezetag_teams)
+ #define FREEZETAG_ALIVE_TEAMS_OK() (FREEZETAG_ALIVE_TEAMS() == NumTeams(freezetag_teams))
  
  float freezetag_CheckTeams()
  {
                prev_missing_teams_mask = -1;
                return 0;
        }
-       float missing_teams_mask = (!redalive) + (!bluealive) * 2;
-       if(freezetag_teams >= 3) missing_teams_mask += (!yellowalive) * 4;
-       if(freezetag_teams >= 4) missing_teams_mask += (!pinkalive) * 8;
+       int missing_teams_mask = 0;
+       if(freezetag_teams & BIT(0))
+               missing_teams_mask += (!redalive) * 1;
+       if(freezetag_teams & BIT(1))
+               missing_teams_mask += (!bluealive) * 2;
+       if(freezetag_teams & BIT(2))
+               missing_teams_mask += (!yellowalive) * 4;
+       if(freezetag_teams & BIT(3))
+               missing_teams_mask += (!pinkalive) * 8;
        if(prev_missing_teams_mask != missing_teams_mask)
        {
                Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
@@@ -463,7 -470,7 +469,7 @@@ MUTATOR_HOOKFUNCTION(ft, reset_map_play
        FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
                it.killcount = 0;
                it.freezetag_frozen_timeout = -1;
-               WITHSELF(it, PutClientInServer());
+               PutClientInServer(it);
                it.freezetag_frozen_timeout = 0;
        ));
        freezetag_count_alive_players();
@@@ -616,6 -623,14 +622,14 @@@ void freezetag_Initialize(
        if(freezetag_teams < 2)
                freezetag_teams = autocvar_g_freezetag_teams;
        freezetag_teams = bound(2, freezetag_teams, 4);
+       int teams = 0;
+       if(freezetag_teams >= 1) teams |= BIT(0);
+       if(freezetag_teams >= 2) teams |= BIT(1);
+       if(freezetag_teams >= 3) teams |= BIT(2);
+       if(freezetag_teams >= 4) teams |= BIT(3);
+       freezetag_teams = teams; // now set it?
        freezetag_ScoreRules(freezetag_teams);
  
        round_handler_Spawn(freezetag_CheckTeams, freezetag_CheckWinner, func_null);
index 7bb9509d868cb0e31437164a2aaf69745c023e77,1ce62672d8b6e5d9eaf6c5ffb0d608dcced47edf..407ac6c98bb7ed05a083c49b4db0801aea3aca5d
@@@ -32,6 -32,10 +32,6 @@@ REGISTER_MUTATOR(ka, false
  
  entity ka_ball;
  
 -const float SP_KEEPAWAY_PICKUPS = 4;
 -const float SP_KEEPAWAY_CARRIERKILLS = 5;
 -const float SP_KEEPAWAY_BCTIME = 6;
 -
  void(entity this) havocbot_role_ka_carrier;
  void(entity this) havocbot_role_ka_collector;
  
@@@ -77,7 -81,7 +77,7 @@@ void ka_EventLog(string mode, entity ac
                GameLogEcho(strcat(":ka:", mode, ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
  }
  
- void ka_TouchEvent(entity this);
+ void ka_TouchEvent(entity this, entity toucher);
  void ka_RespawnBall(entity this);
  void ka_RespawnBall(entity this) // runs whenever the ball needs to be relocated
  {
@@@ -92,7 -96,7 +92,7 @@@
        }
  
        makevectors(this.angles);
-       this.movetype = MOVETYPE_BOUNCE;
+       set_movetype(this, MOVETYPE_BOUNCE);
        this.velocity = '0 0 200';
        this.angles = '0 0 0';
        this.effects = autocvar_g_keepawayball_effects;
@@@ -121,7 -125,7 +121,7 @@@ void ka_TimeScoring(entity this
        }
  }
  
- void ka_TouchEvent(entity this) // runs any time that the ball comes in contact with something
+ void ka_TouchEvent(entity this, entity toucher) // runs any time that the ball comes in contact with something
  {
        if(gameover) { return; }
        if(!this) { return; }
                ka_RespawnBall(this);
                return;
        }
-       if(IS_DEAD(other)) { return; }
-       if(STAT(FROZEN, other)) { return; }
-       if (!IS_PLAYER(other))
+       if(IS_DEAD(toucher)) { return; }
+       if(STAT(FROZEN, toucher)) { return; }
+       if (!IS_PLAYER(toucher))
        {  // The ball just touched an object, most likely the world
                Send_Effect(EFFECT_BALL_SPARKS, this.origin, '0 0 0', 1);
                sound(this, CH_TRIGGER, SND_KA_TOUCH, VOL_BASE, ATTEN_NORM);
        else if(this.wait > time) { return; }
  
        // attach the ball to the player
-       this.owner = other;
-       other.ballcarried = this;
-       setattachment(this, other, "");
+       this.owner = toucher;
+       toucher.ballcarried = this;
+       setattachment(this, toucher, "");
        setorigin(this, '0 0 0');
  
        // make the ball invisible/unable to do anything/set up time scoring
        this.velocity = '0 0 0';
-       this.movetype = MOVETYPE_NONE;
+       set_movetype(this, MOVETYPE_NONE);
        this.effects |= EF_NODRAW;
        settouch(this, func_null);
        setthink(this, ka_TimeScoring);
        this.takedamage = DAMAGE_NO;
  
        // apply effects to player
-       other.glow_color = autocvar_g_keepawayball_trail_color;
-       other.glow_trail = true;
-       other.effects |= autocvar_g_keepaway_ballcarrier_effects;
+       toucher.glow_color = autocvar_g_keepawayball_trail_color;
+       toucher.glow_trail = true;
+       toucher.effects |= autocvar_g_keepaway_ballcarrier_effects;
  
        // messages and sounds
-       ka_EventLog("pickup", other);
-       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_KEEPAWAY_PICKUP, other.netname);
-       Send_Notification(NOTIF_ALL_EXCEPT, other, MSG_CENTER, CENTER_KEEPAWAY_PICKUP, other.netname);
-       Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_KEEPAWAY_PICKUP_SELF);
+       ka_EventLog("pickup", toucher);
+       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_KEEPAWAY_PICKUP, toucher.netname);
+       Send_Notification(NOTIF_ALL_EXCEPT, toucher, MSG_CENTER, CENTER_KEEPAWAY_PICKUP, toucher.netname);
+       Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_KEEPAWAY_PICKUP_SELF);
        sound(this.owner, CH_TRIGGER, SND_KA_PICKEDUP, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
  
        // scoring
-       PlayerScore_Add(other, SP_KEEPAWAY_PICKUPS, 1);
+       PlayerScore_Add(toucher, SP_KEEPAWAY_PICKUPS, 1);
  
        // waypoints
-       WaypointSprite_AttachCarrier(WP_KaBallCarrier, other, RADARICON_FLAGCARRIER);
-       other.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = ka_ballcarrier_waypointsprite_visible_for_player;
-       WaypointSprite_UpdateRule(other.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
-       WaypointSprite_Ping(other.waypointsprite_attachedforcarrier);
+       WaypointSprite_AttachCarrier(WP_KaBallCarrier, toucher, RADARICON_FLAGCARRIER);
+       toucher.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = ka_ballcarrier_waypointsprite_visible_for_player;
+       WaypointSprite_UpdateRule(toucher.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
+       WaypointSprite_Ping(toucher.waypointsprite_attachedforcarrier);
        WaypointSprite_Kill(this.waypointsprite_attachedforcarrier);
  }
  
@@@ -187,7 -191,7 +187,7 @@@ void ka_DropEvent(entity plyr) // runs 
  
        // reset the ball
        setattachment(ball, NULL, "");
-       ball.movetype = MOVETYPE_BOUNCE;
+       set_movetype(ball, MOVETYPE_BOUNCE);
        ball.wait = time + 1;
        settouch(ball, ka_TouchEvent);
        setthink(ball, ka_RespawnBall);
        ka_EventLog("dropped", plyr);
        Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_KEEPAWAY_DROPPED, plyr.netname);
        Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEEPAWAY_DROPPED, plyr.netname);
-       sound(other, CH_TRIGGER, SND_KA_DROPPED, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
+       sound(NULL, CH_TRIGGER, SND_KA_DROPPED, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
  
        // scoring
        // PlayerScore_Add(plyr, SP_KEEPAWAY_DROPS, 1); Not anymore, this is 100% the same as pickups and is useless.
@@@ -486,7 -490,7 +486,7 @@@ void ka_SpawnBall() // loads various va
        e.damageforcescale = autocvar_g_keepawayball_damageforcescale;
        e.takedamage = DAMAGE_YES;
        e.solid = SOLID_TRIGGER;
-       e.movetype = MOVETYPE_BOUNCE;
+       set_movetype(e, MOVETYPE_BOUNCE);
        e.glow_color = autocvar_g_keepawayball_trail_color;
        e.glow_trail = true;
        e.flags = FL_ITEM;
  void ka_ScoreRules()
  {
        ScoreRules_basics(0, SFL_SORT_PRIO_PRIMARY, 0, true); // SFL_SORT_PRIO_PRIMARY
 -      ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_PICKUPS,                     "pickups",              0);
 -      ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_CARRIERKILLS,        "bckills",              0);
 -      ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_BCTIME,                      "bctime",               SFL_SORT_PRIO_SECONDARY);
 +      ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_PICKUPS,             "pickups",              0);
 +      ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_CARRIERKILLS,   "bckills",           0);
 +      ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_BCTIME,                  "bctime",           SFL_SORT_PRIO_SECONDARY);
        ScoreRules_basics_end();
  }
  
index bb28b5ef646cc126d7b315c3590939416f706075,64b0397aa957dacc47c49fc261c915e7d4060786..236db8aa86fad3dd93cafc3faf907a6901d5dd24
@@@ -138,16 -138,22 +138,16 @@@ float kh_interferemsg_time, kh_interfer
  float kh_key_dropped, kh_key_carried;
  
  const float ST_KH_CAPS = 1;
- void kh_ScoreRules(float teams)
 -const float SP_KH_CAPS = 4;
 -const float SP_KH_PUSHES = 5;
 -const float SP_KH_DESTROYS = 6;
 -const float SP_KH_PICKUPS = 7;
 -const float SP_KH_KCKILLS = 8;
 -const float SP_KH_LOSSES = 9;
+ void kh_ScoreRules(int teams)
  {
        ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, true);
        ScoreInfo_SetLabel_TeamScore(  ST_KH_CAPS,      "caps",      SFL_SORT_PRIO_SECONDARY);
-       ScoreInfo_SetLabel_PlayerScore(SP_KH_CAPS,     "caps",      SFL_SORT_PRIO_SECONDARY);
-       ScoreInfo_SetLabel_PlayerScore(SP_KH_PUSHES,   "pushes",    0);
-       ScoreInfo_SetLabel_PlayerScore(SP_KH_DESTROYS, "destroyed", SFL_LOWER_IS_BETTER);
-       ScoreInfo_SetLabel_PlayerScore(SP_KH_PICKUPS,  "pickups",   0);
-       ScoreInfo_SetLabel_PlayerScore(SP_KH_KCKILLS,  "kckills",   0);
-       ScoreInfo_SetLabel_PlayerScore(SP_KH_LOSSES,   "losses",    SFL_LOWER_IS_BETTER);
+       ScoreInfo_SetLabel_PlayerScore(SP_KH_CAPS,      "caps",      SFL_SORT_PRIO_SECONDARY);
+       ScoreInfo_SetLabel_PlayerScore(SP_KH_PUSHES,    "pushes",    0);
+       ScoreInfo_SetLabel_PlayerScore(SP_KH_DESTROYS,  "destroyed", SFL_LOWER_IS_BETTER);
+       ScoreInfo_SetLabel_PlayerScore(SP_KH_PICKUPS,   "pickups",   0);
+       ScoreInfo_SetLabel_PlayerScore(SP_KH_KCKILLS,   "kckills",   0);
+       ScoreInfo_SetLabel_PlayerScore(SP_KH_LOSSES,    "losses",    SFL_LOWER_IS_BETTER);
        ScoreRules_basics_end();
  }
  
@@@ -301,7 -307,7 +301,7 @@@ void kh_Key_Attach(entity key)  // run
  #endif
        key.flags = 0;
        key.solid = SOLID_NOT;
-       key.movetype = MOVETYPE_NONE;
+       set_movetype(key, MOVETYPE_NONE);
        key.team = key.owner.team;
        key.nextthink = time;
        key.damageforcescale = 0;
@@@ -340,7 -346,7 +340,7 @@@ void kh_Key_Detach(entity key) // runs 
  #endif
        key.flags = FL_ITEM;
        key.solid = SOLID_TRIGGER;
-       key.movetype = MOVETYPE_TOSS;
+       set_movetype(key, MOVETYPE_TOSS);
        key.pain_finished = time + autocvar_g_balance_keyhunt_delay_return;
        key.damageforcescale = autocvar_g_balance_keyhunt_damageforcescale;
        key.takedamage = DAMAGE_YES;
@@@ -499,7 -505,7 +499,7 @@@ void kh_Key_Collect(entity key, entity 
        kh_Key_AssignTo(key, player); // this also updates .kh_state
  }
  
- void kh_Key_Touch(entity this)  // runs many, many times when a key has been dropped and can be picked up
+ void kh_Key_Touch(entity this, entity toucher)  // runs many, many times when a key has been dropped and can be picked up
  {
        if(intermission_running)
                return;
                // maybe start a shorter countdown?
        }
  
-       if (!IS_PLAYER(other))
+       if (!IS_PLAYER(toucher))
                return;
-       if(IS_DEAD(other))
+       if(IS_DEAD(toucher))
                return;
-       if(other == this.enemy)
+       if(toucher == this.enemy)
                if(time < this.kh_droptime + autocvar_g_balance_keyhunt_delay_collect)
                        return;  // you just dropped it!
-       kh_Key_Collect(this, other);
+       kh_Key_Collect(this, toucher);
  }
  
  void kh_Key_Remove(entity key)  // runs after when all the keys have been collected or when a key has been dropped for more than X seconds
                }
        }
  
-       remove(key);
+       delete(key);
  
        kh_update_state();
  }
@@@ -953,9 -959,15 +953,15 @@@ void kh_WaitForPlayers()  // delay star
                }
                else
                {
-                       float missing_teams_mask = boolean(p1) + boolean(p2) * 2;
-                       if(kh_teams >= 3) missing_teams_mask += boolean(p3) * 4;
-                       if(kh_teams >= 4) missing_teams_mask += boolean(p4) * 8;
+                       int missing_teams_mask = 0;
+                       if(kh_teams & BIT(0))
+                               missing_teams_mask += boolean(p1) * 1;
+                       if(kh_teams & BIT(1))
+                               missing_teams_mask += boolean(p2) * 2;
+                       if(kh_teams & BIT(2))
+                               missing_teams_mask += boolean(p3) * 4;
+                       if(kh_teams & BIT(3))
+                               missing_teams_mask += boolean(p4) * 8;
                        if(prev_missing_teams_mask != missing_teams_mask)
                        {
                                Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
@@@ -1050,6 -1062,14 +1056,14 @@@ void kh_Initialize()  // sets up th KH 
                kh_teams = autocvar_g_keyhunt_teams;
        kh_teams = bound(2, kh_teams, 4);
  
+       int teams = 0;
+       if(kh_teams >= 1) teams |= BIT(0);
+       if(kh_teams >= 2) teams |= BIT(1);
+       if(kh_teams >= 3) teams |= BIT(2);
+       if(kh_teams >= 4) teams |= BIT(3);
+       kh_teams = teams; // now set it?
        // make a KH entity for controlling the game
        kh_controller = spawn();
        setthink(kh_controller, kh_Controller_Think);
@@@ -1079,7 -1099,7 +1093,7 @@@ void kh_finalize(
  {
        // to be called before intermission
        kh_FinishRound();
-       remove(kh_controller);
+       delete(kh_controller);
        kh_controller = NULL;
  }
  
index a7282a2c8b018ce015dabc7e093d305f375d2f4e,63fbd15a272476c1184fd26786daf3ce478b5d9c..a496ef2e221801637f7727c010eb2a90e96bb2a1
@@@ -78,7 -78,7 +78,7 @@@ void race_ScoreRules(
        ScoreRules_basics(race_teams, 0, 0, false);
        if(race_teams)
        {
 -              ScoreInfo_SetLabel_TeamScore(  ST_RACE_LAPS,    "laps",      SFL_SORT_PRIO_PRIMARY);
 +              ScoreInfo_SetLabel_TeamScore(  ST_RACE_LAPS,    "laps",       SFL_SORT_PRIO_PRIMARY);
                ScoreInfo_SetLabel_PlayerScore(SP_RACE_LAPS,    "laps",      SFL_SORT_PRIO_PRIMARY);
                ScoreInfo_SetLabel_PlayerScore(SP_RACE_TIME,    "time",      SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
                ScoreInfo_SetLabel_PlayerScore(SP_RACE_FASTEST, "fastest",   SFL_LOWER_IS_BETTER | SFL_TIME);
@@@ -159,7 -159,7 +159,7 @@@ MUTATOR_HOOKFUNCTION(rc, PlayerPhysics
                if(player.race_penalty)
                {
                        player.velocity = '0 0 0';
-                       player.movetype = MOVETYPE_NONE;
+                       set_movetype(player, MOVETYPE_NONE);
                        player.disableclientprediction = 2;
                }
        }
@@@ -463,6 -463,14 +463,14 @@@ void rc_SetLimits(
        {
                ActivateTeamplay();
                race_teams = bound(2, autocvar_g_race_teams, 4);
+               int teams = 0;
+               if(race_teams >= 1) teams |= BIT(0);
+               if(race_teams >= 2) teams |= BIT(1);
+               if(race_teams >= 3) teams |= BIT(2);
+               if(race_teams >= 4) teams |= BIT(3);
+               race_teams = teams; // now set it?
                have_team_spawns = -1; // request team spawns
        }
        else
diff --combined qcsrc/server/scores.qc
index c66d23ca7c887d10a43dda357177f70ec2b0453c,1a51c803d5244b86207982ad712cf17ad53ebbfb..17c56fb543dd167e3f8302bf8155efb79e2323ab
@@@ -7,6 -7,10 +7,6 @@@
  
  .entity scorekeeper;
  entity teamscorekeepers[16];
 -string scores_label[MAX_SCORE];
 -float scores_flags[MAX_SCORE];
 -string teamscores_label[MAX_TEAMSCORE];
 -float teamscores_flags[MAX_TEAMSCORE];
  float teamscores_entities_count;
  var .float scores_primary;
  var .float teamscores_primary;
@@@ -61,7 -65,7 +61,7 @@@ bool TeamScore_SendEntity(entity this, 
  
        longflags = 0;
        for(i = 0, p = 1; i < MAX_TEAMSCORE; ++i, p *= 2)
 -              if(this.(teamscores[i]) > 127 || this.(teamscores[i]) <= -128)
 +              if(this.(teamscores(i)) > 127 || this.(teamscores(i)) <= -128)
                        longflags |= p;
  
  #if MAX_TEAMSCORE <= 8
@@@ -75,9 -79,9 +75,9 @@@
                if(sendflags & p)
                {
                        if(longflags & p)
 -                              WriteInt24_t(MSG_ENTITY, this.(teamscores[i]));
 +                              WriteInt24_t(MSG_ENTITY, this.(teamscores(i)));
                        else
 -                              WriteChar(MSG_ENTITY, this.(teamscores[i]));
 +                              WriteChar(MSG_ENTITY, this.(teamscores(i)));
                }
  
        return true;
@@@ -116,9 -120,9 +116,9 @@@ float TeamScore_AddToTeam(float t, floa
                error("Adding score to unknown team!");
        }
        if(score)
 -              if(teamscores_label[scorefield] != "")
 +              if(teamscores_label(scorefield) != "")
                        s.SendFlags |= pow(2, scorefield);
 -      return (s.(teamscores[scorefield]) += score);
 +      return (s.(teamscores(scorefield)) += score);
  }
  
  float TeamScore_Add(entity player, float scorefield, float score)
@@@ -135,8 -139,8 +135,8 @@@ float TeamScore_Compare(entity t1, enti
        for(i = 0; i < MAX_TEAMSCORE; ++i)
        {
                var .float f;
 -              f = teamscores[i];
 -              result = ScoreField_Compare(t1, t2, f, teamscores_flags[i], result, strict);
 +              f = teamscores(i);
 +              result = ScoreField_Compare(t1, t2, f, teamscores_flags(i), result, strict);
        }
  
        if (result.x == 0 && strict)
   * the scoreinfo entity
   */
  
 -void ScoreInfo_SetLabel_PlayerScore(float i, string label, float scoreflags)
 +void ScoreInfo_SetLabel_PlayerScore(PlayerScoreField i, string label, float scoreflags)
  {
 -      scores_label[i] = label;
 -      scores_flags[i] = scoreflags;
 +      scores_label(i) = label;
 +      scores_flags(i) = scoreflags;
        if((scoreflags & SFL_SORT_PRIO_MASK) == SFL_SORT_PRIO_PRIMARY)
        {
 -              scores_primary = scores[i];
 +              scores_primary = scores(i);
                scores_flags_primary = scoreflags;
        }
        if(label != "")
  
  void ScoreInfo_SetLabel_TeamScore(float i, string label, float scoreflags)
  {
 -      teamscores_label[i] = label;
 -      teamscores_flags[i] = scoreflags;
 +      teamscores_label(i) = label;
 +      teamscores_flags(i) = scoreflags;
        if((scoreflags & SFL_SORT_PRIO_MASK) == SFL_SORT_PRIO_PRIMARY)
        {
 -              teamscores_primary = teamscores[i];
 +              teamscores_primary = teamscores(i);
                teamscores_flags_primary = scoreflags;
        }
        if(label != "")
@@@ -186,19 -190,20 +186,19 @@@ bool ScoreInfo_SendEntity(entity this, 
        float i;
        WriteHeader(MSG_ENTITY, ENT_CLIENT_SCORES_INFO);
        WriteInt24_t(MSG_ENTITY, MapInfo_LoadedGametype);
 -      for(i = 0; i < MAX_SCORE; ++i)
 -      {
 -              WriteString(MSG_ENTITY, scores_label[i]);
 -              WriteByte(MSG_ENTITY, scores_flags[i]);
 -      }
 +      FOREACH(Scores, true, {
 +              WriteString(MSG_ENTITY, scores_label(it));
 +              WriteByte(MSG_ENTITY, scores_flags(it));
 +      });
        for(i = 0; i < MAX_TEAMSCORE; ++i)
        {
 -              WriteString(MSG_ENTITY, teamscores_label[i]);
 -              WriteByte(MSG_ENTITY, teamscores_flags[i]);
 +              WriteString(MSG_ENTITY, teamscores_label(i));
 +              WriteByte(MSG_ENTITY, teamscores_flags(i));
        }
        return true;
  }
  
- void ScoreInfo_Init(float teams)
+ void ScoreInfo_Init(int teams)
  {
        if(scores_initialized)
        {
                scores_initialized = new_pure(ent_client_scoreinfo);
                Net_LinkEntity(scores_initialized, false, 0, ScoreInfo_SendEntity);
        }
-       if(teams >= 1)
+       if(teams & BIT(0))
                TeamScore_Spawn(NUM_TEAM_1, "Red");
-       if(teams >= 2)
+       if(teams & BIT(1))
                TeamScore_Spawn(NUM_TEAM_2, "Blue");
-       if(teams >= 3)
+       if(teams & BIT(2))
                TeamScore_Spawn(NUM_TEAM_3, "Yellow");
-       if(teams >= 4)
+       if(teams & BIT(3))
                TeamScore_Spawn(NUM_TEAM_4, "Pink");
  }
  
  
  bool PlayerScore_SendEntity(entity this, entity to, float sendflags)
  {
 -      float i, p, longflags;
 -
        WriteHeader(MSG_ENTITY, ENT_CLIENT_SCORES);
        WriteByte(MSG_ENTITY, etof(this.owner));
  
 -      longflags = 0;
 -      for(i = 0, p = 1; i < MAX_SCORE; ++i, p *= 2)
 -              if(this.(scores[i]) > 127 || this.(scores[i]) <= -128)
 +      int longflags = 0;
 +      FOREACH(Scores, true, {
 +          int p = 1 << (i % 16);
 +              if (this.(scores(it)) > 127 || this.(scores(it)) <= -128)
                        longflags |= p;
 +    });
  
 -#if MAX_SCORE <= 8
 -      WriteByte(MSG_ENTITY, sendflags);
 -      WriteByte(MSG_ENTITY, longflags);
 -#else
        WriteShort(MSG_ENTITY, sendflags);
        WriteShort(MSG_ENTITY, longflags);
 -#endif
 -      for(i = 0, p = 1; i < MAX_SCORE; ++i, p *= 2)
 -              if(sendflags & p)
 +      FOREACH(Scores, true, {
 +          int p = 1 << (i % 16);
 +              if (sendflags & p)
                {
                        if(longflags & p)
 -                              WriteInt24_t(MSG_ENTITY, this.(scores[i]));
 +                              WriteInt24_t(MSG_ENTITY, this.(scores(it)));
                        else
 -                              WriteChar(MSG_ENTITY, this.(scores[i]));
 +                              WriteChar(MSG_ENTITY, this.(scores(it)));
                }
 +    });
  
        return true;
  }
  float PlayerScore_Clear(entity player)
  {
        entity sk;
 -      float i;
  
        if(teamscores_entities_count)
                return 0;
        if(MUTATOR_CALLHOOK(ForbidPlayerScore_Clear)) return 0;
  
        sk = player.scorekeeper;
 -      for(i = 0; i < MAX_SCORE; ++i)
 -      {
 -              if(sk.(scores[i]) != 0)
 -                      if(scores_label[i] != "")
 -                              sk.SendFlags |= pow(2, i);
 -              sk.(scores[i]) = 0;
 -      }
 +      FOREACH(Scores, true, {
 +              if(sk.(scores(it)) != 0)
 +                      if(scores_label(it) != "")
 +                              sk.SendFlags |= pow(2, i % 16);
 +              sk.(scores(it)) = 0;
 +      });
  
        return 1;
  }
@@@ -275,15 -285,18 +275,15 @@@ void Score_ClearAll(
  {
        entity sk;
        float t;
 -      FOREACH_CLIENTSLOT(true,
 -      {
 +      FOREACH_CLIENTSLOT(true, {
                sk = it.scorekeeper;
 -              if(!sk)
 -                      continue;
 -              for(int j = 0; j < MAX_SCORE; ++j)
 -              {
 -                      if(sk.(scores[j]) != 0)
 -                              if(scores_label[j] != "")
 -                                      sk.SendFlags |= pow(2, j);
 -                      sk.(scores[j]) = 0;
 -              }
 +              if (!sk) continue;
 +              FOREACH(Scores, true, {
 +                      if(sk.(scores(it)) != 0)
 +                              if(scores_label(it) != "")
 +                                      sk.SendFlags |= pow(2, i % 16);
 +                      sk.(scores(it)) = 0;
 +              });
        });
        for(t = 0; t < 16; ++t)
        {
                        continue;
                for(int j = 0; j < MAX_TEAMSCORE; ++j)
                {
 -                      if(sk.(teamscores[j]) != 0)
 -                              if(teamscores_label[j] != "")
 +                      if(sk.(teamscores(j)) != 0)
 +                              if(teamscores_label(j) != "")
                                        sk.SendFlags |= pow(2, j);
 -                      sk.(teamscores[j]) = 0;
 +                      sk.(teamscores(j)) = 0;
                }
        }
  }
@@@ -314,11 -327,11 +314,11 @@@ void PlayerScore_Detach(entity player
  {
        if(!player.scorekeeper)
                error("player has no scorekeeper");
-       remove(player.scorekeeper);
+       delete(player.scorekeeper);
        player.scorekeeper = NULL;
  }
  
 -float PlayerScore_Add(entity player, float scorefield, float score)
 +float PlayerScore_Add(entity player, PlayerScoreField scorefield, float score)
  {
        bool mutator_returnvalue = MUTATOR_CALLHOOK(AddPlayerScore, scorefield, score, player);
        score = M_ARGV(1, float);
                return 0;
        }
        if(score)
 -              if(scores_label[scorefield] != "")
 -                      s.SendFlags |= pow(2, scorefield);
 +              if(scores_label(scorefield) != "")
 +                      s.SendFlags |= pow(2, scorefield.m_id % 16);
        if(!warmup_stage)
 -              PS_GR_P_ADDVAL(s.owner, strcat(PLAYERSTATS_TOTAL, scores_label[scorefield]), score);
 -      return (s.(scores[scorefield]) += score);
 +              PS_GR_P_ADDVAL(s.owner, strcat(PLAYERSTATS_TOTAL, scores_label(scorefield)), score);
 +      return (s.(scores(scorefield)) += score);
  }
  
 -float PlayerTeamScore_Add(entity player, float pscorefield, float tscorefield, float score)
 +float PlayerTeamScore_Add(entity player, PlayerScoreField pscorefield, float tscorefield, float score)
  {
        float r;
        r = PlayerScore_Add(player, pscorefield, score);
@@@ -358,10 -371,13 +358,10 @@@ float PlayerScore_Compare(entity t1, en
        if(!t1 || !t2) return (!t2) - !t1;
  
        vector result = '0 0 0';
 -      float i;
 -      for(i = 0; i < MAX_SCORE; ++i)
 -      {
 -              var .float f;
 -              f = scores[i];
 -              result = ScoreField_Compare(t1, t2, f, scores_flags[i], result, strict);
 -      }
 +      FOREACH(Scores, true, {
 +              var .float f = scores(it);
 +              result = ScoreField_Compare(t1, t2, f, scores_flags(it), result, strict);
 +      });
  
        if (result.x == 0 && strict)
                result.x = etof(t1.owner) - etof(t2.owner);
@@@ -546,59 -562,53 +546,59 @@@ string GetPlayerScoreString(entity pl, 
  {
        string out;
        entity sk;
 -      float i, f;
 +      float f;
        string l;
  
        out = "";
        if(!pl)
        {
                // label
 -              for(i = 0; i < MAX_SCORE; ++i)
 -                      if((scores_flags[i] & SFL_SORT_PRIO_MASK) == SFL_SORT_PRIO_PRIMARY)
 +              FOREACH(Scores, true, {
 +                      if ((scores_flags(it) & SFL_SORT_PRIO_MASK) == SFL_SORT_PRIO_PRIMARY)
                        {
 -                              f = scores_flags[i];
 -                              l = scores_label[i];
 +                              f = scores_flags(it);
 +                              l = scores_label(it);
                                out = strcat(out, GetScoreLogLabel(l, f), ",");
                        }
 +        });
                if(shortString < 2)
 -              for(i = 0; i < MAX_SCORE; ++i)
 -                      if((scores_flags[i] & SFL_SORT_PRIO_MASK) == SFL_SORT_PRIO_SECONDARY)
 +              FOREACH(Scores, true, {
 +                      if((scores_flags(it) & SFL_SORT_PRIO_MASK) == SFL_SORT_PRIO_SECONDARY)
                        {
 -                              f = scores_flags[i];
 -                              l = scores_label[i];
 +                              f = scores_flags(it);
 +                              l = scores_label(it);
                                out = strcat(out, GetScoreLogLabel(l, f), ",");
                        }
 +        });
                if(shortString < 1)
 -              for(i = 0; i < MAX_SCORE; ++i)
 -                      if((scores_flags[i] & SFL_SORT_PRIO_MASK) != SFL_SORT_PRIO_PRIMARY)
 -                      if((scores_flags[i] & SFL_SORT_PRIO_MASK) != SFL_SORT_PRIO_SECONDARY)
 +              FOREACH(Scores, true, {
 +                      if((scores_flags(it) & SFL_SORT_PRIO_MASK) != SFL_SORT_PRIO_PRIMARY)
 +                      if((scores_flags(it) & SFL_SORT_PRIO_MASK) != SFL_SORT_PRIO_SECONDARY)
                        {
 -                              f = scores_flags[i];
 -                              l = scores_label[i];
 +                              f = scores_flags(it);
 +                              l = scores_label(it);
                                out = strcat(out, GetScoreLogLabel(l, f), ",");
                        }
 +        });
                out = substring(out, 0, strlen(out) - 1);
        }
        else if((sk = pl.scorekeeper))
        {
 -              for(i = 0; i < MAX_SCORE; ++i)
 -                      if((scores_flags[i] & SFL_SORT_PRIO_MASK) == SFL_SORT_PRIO_PRIMARY)
 -                              out = strcat(out, ftos(sk.(scores[i])), ",");
 +              FOREACH(Scores, true, {
 +                      if ((scores_flags(it) & SFL_SORT_PRIO_MASK) == SFL_SORT_PRIO_PRIMARY)
 +                              out = strcat(out, ftos(sk.(scores(it))), ",");
 +        });
                if(shortString < 2)
 -              for(i = 0; i < MAX_SCORE; ++i)
 -                      if((scores_flags[i] & SFL_SORT_PRIO_MASK) == SFL_SORT_PRIO_SECONDARY)
 -                              out = strcat(out, ftos(sk.(scores[i])), ",");
 +              FOREACH(Scores, true, {
 +                      if ((scores_flags(it) & SFL_SORT_PRIO_MASK) == SFL_SORT_PRIO_SECONDARY)
 +                              out = strcat(out, ftos(sk.(scores(it))), ",");
 +        });
                if(shortString < 1)
 -              for(i = 0; i < MAX_SCORE; ++i)
 -                      if((scores_flags[i] & SFL_SORT_PRIO_MASK) != SFL_SORT_PRIO_PRIMARY)
 -                      if((scores_flags[i] & SFL_SORT_PRIO_MASK) != SFL_SORT_PRIO_SECONDARY)
 -                              out = strcat(out, ftos(sk.(scores[i])), ",");
 +              FOREACH(Scores, true, {
 +                      if((scores_flags(it) & SFL_SORT_PRIO_MASK) != SFL_SORT_PRIO_PRIMARY)
 +                      if((scores_flags(it) & SFL_SORT_PRIO_MASK) != SFL_SORT_PRIO_SECONDARY)
 +                              out = strcat(out, ftos(sk.(scores(it))), ",");
 +        });
                out = substring(out, 0, strlen(out) - 1);
        }
        return out;
@@@ -616,27 -626,27 +616,27 @@@ string GetTeamScoreString(float tm, flo
        {
                // label
                for(i = 0; i < MAX_TEAMSCORE; ++i)
 -                      if((teamscores_flags[i] & SFL_SORT_PRIO_MASK) == SFL_SORT_PRIO_PRIMARY)
 +                      if((teamscores_flags(i) & SFL_SORT_PRIO_MASK) == SFL_SORT_PRIO_PRIMARY)
                        {
 -                              f = teamscores_flags[i];
 -                              l = teamscores_label[i];
 +                              f = teamscores_flags(i);
 +                              l = teamscores_label(i);
                                out = strcat(out, GetScoreLogLabel(l, f), ",");
                        }
                if(shortString < 2)
                for(i = 0; i < MAX_TEAMSCORE; ++i)
 -                      if((teamscores_flags[i] & SFL_SORT_PRIO_MASK) == SFL_SORT_PRIO_SECONDARY)
 +                      if((teamscores_flags(i) & SFL_SORT_PRIO_MASK) == SFL_SORT_PRIO_SECONDARY)
                        {
 -                              f = teamscores_flags[i];
 -                              l = teamscores_label[i];
 +                              f = teamscores_flags(i);
 +                              l = teamscores_label(i);
                                out = strcat(out, GetScoreLogLabel(l, f), ",");
                        }
                if(shortString < 1)
                for(i = 0; i < MAX_TEAMSCORE; ++i)
 -                      if((teamscores_flags[i] & SFL_SORT_PRIO_MASK) != SFL_SORT_PRIO_PRIMARY)
 -                      if((teamscores_flags[i] & SFL_SORT_PRIO_MASK) != SFL_SORT_PRIO_SECONDARY)
 +                      if((teamscores_flags(i) & SFL_SORT_PRIO_MASK) != SFL_SORT_PRIO_PRIMARY)
 +                      if((teamscores_flags(i) & SFL_SORT_PRIO_MASK) != SFL_SORT_PRIO_SECONDARY)
                        {
 -                              f = teamscores_flags[i];
 -                              l = teamscores_label[i];
 +                              f = teamscores_flags(i);
 +                              l = teamscores_label(i);
                                out = strcat(out, GetScoreLogLabel(l, f), ",");
                        }
                out = substring(out, 0, strlen(out) - 1);
        else if((sk = teamscorekeepers[tm - 1]))
        {
                for(i = 0; i < MAX_TEAMSCORE; ++i)
 -                      if((teamscores_flags[i] & SFL_SORT_PRIO_MASK) == SFL_SORT_PRIO_PRIMARY)
 -                              out = strcat(out, ftos(sk.(teamscores[i])), ",");
 +                      if((teamscores_flags(i) & SFL_SORT_PRIO_MASK) == SFL_SORT_PRIO_PRIMARY)
 +                              out = strcat(out, ftos(sk.(teamscores(i))), ",");
                if(shortString < 2)
                for(i = 0; i < MAX_TEAMSCORE; ++i)
 -                      if((teamscores_flags[i] & SFL_SORT_PRIO_MASK) == SFL_SORT_PRIO_SECONDARY)
 -                              out = strcat(out, ftos(sk.(teamscores[i])), ",");
 +                      if((teamscores_flags(i) & SFL_SORT_PRIO_MASK) == SFL_SORT_PRIO_SECONDARY)
 +                              out = strcat(out, ftos(sk.(teamscores(i))), ",");
                if(shortString < 1)
                for(i = 0; i < MAX_TEAMSCORE; ++i)
 -                      if((teamscores_flags[i] & SFL_SORT_PRIO_MASK) != SFL_SORT_PRIO_PRIMARY)
 -                      if((teamscores_flags[i] & SFL_SORT_PRIO_MASK) != SFL_SORT_PRIO_SECONDARY)
 -                              out = strcat(out, ftos(sk.(teamscores[i])), ",");
 +                      if((teamscores_flags(i) & SFL_SORT_PRIO_MASK) != SFL_SORT_PRIO_PRIMARY)
 +                      if((teamscores_flags(i) & SFL_SORT_PRIO_MASK) != SFL_SORT_PRIO_SECONDARY)
 +                              out = strcat(out, ftos(sk.(teamscores(i))), ",");
                out = substring(out, 0, strlen(out) - 1);
        }
        return out;
@@@ -789,10 -799,10 +789,10 @@@ void Score_NicePrint_Team(entity to, fl
        {
                s = strcat(s, Team_ColoredFullName(t));
                for(i = 0; i < MAX_TEAMSCORE; ++i)
 -                      if(teamscores_label[i] != "")
 +                      if(teamscores_label(i) != "")
                        {
 -                              fl = teamscores_flags[i];
 -                              sc = sk.(teamscores[i]);
 +                              fl = teamscores_flags(i);
 +                              sc = sk.(teamscores(i));
                                s = strcat(s, " ", Score_NicePrint_ItemColor(fl), ScoreString(fl, sc));
                        }
        }
  
        s = strcat(s, strpad(max(0, NAMEWIDTH - strlennocol(s)), ""));
  
 -      for(i = 0; i < MAX_SCORE; ++i)
 -              if(scores_label[i] != "")
 +      FOREACH(Scores, true, {
 +              if(scores_label(it) != "")
                {
 -                      fl = scores_flags[i];
 -                      s2 = scores_label[i];
 +                      fl = scores_flags(it);
 +                      s2 = scores_label(it);
                        s = strcat(s, " ", Score_NicePrint_ItemColor(fl), strpad(-w, substring(s2, 0, w)));
                }
 +    });
  
        print_to(to, s);
  }
@@@ -836,14 -845,13 +836,14 @@@ void Score_NicePrint_Player(entity to, 
                }
        }
  
 -      for(i = 0; i < MAX_SCORE; ++i)
 -              if(scores_label[i] != "")
 +      FOREACH(Scores, true, {
 +              if(scores_label(it) != "")
                {
 -                      fl = scores_flags[i];
 -                      sc = sk.(scores[i]);
 +                      fl = scores_flags(it);
 +                      sc = sk.(scores(it));
                        s = strcat(s, " ", Score_NicePrint_ItemColor(fl), strpad(-w, ScoreString(fl, sc)));
                }
 +    });
  
        print_to(to, s);
  }
@@@ -862,13 -870,13 +862,13 @@@ void Score_NicePrint_Spectator(entity t
  void Score_NicePrint(entity to)
  {
        entity p;
 -      float i;
        float w;
  
        int t = 0;
 -      for(i = 0; i < MAX_SCORE; ++i)
 -              if(scores_label[i] != "")
 +      FOREACH(Scores, true, {
 +              if(scores_label(it) != "")
                        ++t;
 +    });
        w = bound(6, floor(SCORESWIDTH / t - 1), 9);
  
        p = PlayerScore_Sort(score_dummyfield, 1, 1, 0);
  
  void PlayerScore_PlayerStats(entity p)
  {
 -      entity s;
 -      float i;
 -      s = p.scorekeeper;
 -
 -      for(i = 0; i < MAX_SCORE; ++i)
 -              if(s.(scores[i]) != 0)
 -                      if(scores_label[i] != "")
 -                              PS_GR_P_ADDVAL(s.owner, strcat(PLAYERSTATS_SCOREBOARD, scores_label[i]), s.(scores[i]));
 +      entity s = p.scorekeeper;
 +      FOREACH(Scores, true, {
 +              if(s.(scores(it)) != 0)
 +                      if(scores_label(it) != "")
 +                              PS_GR_P_ADDVAL(s.owner, strcat(PLAYERSTATS_SCOREBOARD, scores_label(it)), s.(scores(it)));
 +    });
  }
  
  void PlayerScore_TeamStats()
                if(!sk)
                        continue;
                for(i = 0; i < MAX_TEAMSCORE; ++i)
 -                      if(sk.(teamscores[i]) != 0)
 -                              if(teamscores_label[i] != "")
 +                      if(sk.(teamscores(i)) != 0)
 +                              if(teamscores_label(i) != "")
                                        // the +1 is important here!
 -                                      PS_GR_T_ADDVAL(t+1, strcat(PLAYERSTATS_SCOREBOARD, teamscores_label[i]), sk.(teamscores[i]));
 +                                      PS_GR_T_ADDVAL(t+1, strcat(PLAYERSTATS_SCOREBOARD, teamscores_label(i)), sk.(teamscores(i)));
        }
  }
index cb9c2e3ce9c839b0f7570ed479475bdae48ce01b,b93c114c06eb40f1c18fa829d3196a93a32ac795..d3aceac50b2b28e8f2606d74ab4fe4713b12bba7
@@@ -3,17 -3,30 +3,30 @@@
  #include "cl_client.qh"
  #include "scores.qh"
  
+ int ScoreRules_teams;
  void CheckAllowedTeams (entity for_whom);
  
 -// NOTE: SP_ constants may not be >= MAX_SCORE; ST_constants may not be >= MAX_TEAMSCORE
+ int NumTeams(int teams)
+ {
+       return boolean(teams & BIT(0)) + boolean(teams & BIT(1)) + boolean(teams & BIT(2)) + boolean(teams & BIT(3));
+ }
+ int AvailableTeams()
+ {
+       return NumTeams(ScoreRules_teams);
+       // NOTE: this method is unreliable, as forced teams set the c* globals to weird values
+       //return boolean(c1 >= 0) + boolean(c2 >= 0) + boolean(c3 >= 0) + boolean(c4 >= 0);
+ }
 +// NOTE: ST_constants may not be >= MAX_TEAMSCORE
  // scores that should be in all modes:
- float ScoreRules_teams;
- void ScoreRules_basics(float teams, float sprio, float stprio, float score_enabled)
+ void ScoreRules_basics(int teams, float sprio, float stprio, float score_enabled)
  {
 -      float i;
 -      for(i = 0; i < MAX_SCORE; ++i)
 -              ScoreInfo_SetLabel_PlayerScore(i, "", 0);
 -      for(i = 0; i < MAX_TEAMSCORE; ++i)
 +      FOREACH(Scores, true, {
 +              ScoreInfo_SetLabel_PlayerScore(it, "", 0);
 +    });
 +      for(int i = 0; i < MAX_TEAMSCORE; ++i)
                ScoreInfo_SetLabel_TeamScore(i, "", 0);
  
        ScoreRules_teams = teams;
@@@ -34,7 -47,6 +47,7 @@@
  
        ScoreInfo_SetLabel_PlayerScore(SP_DMG, "damage", 0);
        ScoreInfo_SetLabel_PlayerScore(SP_DMGTAKEN, "damagetaken", SFL_LOWER_IS_BETTER);
 +      ScoreInfo_SetLabel_PlayerScore(SP_ELO, "elo", 0);
  }
  void ScoreRules_basics_end()
  {
@@@ -45,7 -57,12 +58,12 @@@ void ScoreRules_generic(
        if(teamplay)
        {
                CheckAllowedTeams(NULL);
-               ScoreRules_basics(((c4>=0) ? 4 : (c3>=0) ? 3 : 2), SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, true);
+               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);
        }
        else
                ScoreRules_basics(0, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, true);