Merge branch 'Mario/teams_bitflag' into 'master'
authorMario <zacjardine@y7mail.com>
Thu, 28 Jul 2016 15:02:30 +0000 (15:02 +0000)
committerMario <zacjardine@y7mail.com>
Thu, 28 Jul 2016 15:02:30 +0000 (15:02 +0000)
Merge branch Mario/teams_bitflag (M merge request)

Adds support for odd team matches, such as pink vs blue and yellow vs red.

See merge request !335

1  2 
qcsrc/client/hud/panel/modicons.qc
qcsrc/common/gamemodes/gamemode/nexball/nexball.qc
qcsrc/common/gamemodes/gamemode/onslaught/onslaught.qc
qcsrc/server/bot/bot.qc
qcsrc/server/cl_client.qc
qcsrc/server/mutators/mutator/gamemode_assault.qc
qcsrc/server/mutators/mutator/gamemode_ctf.qc
qcsrc/server/mutators/mutator/gamemode_invasion.qc
qcsrc/server/mutators/mutator/gamemode_keyhunt.qc
qcsrc/server/mutators/mutator/gamemode_race.qc

@@@ -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);
  
 +      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 && team_count >= 3);
-       X(pink, myteam != NUM_TEAM_4 && team_count >= 4);
+       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) \
@@@ -72,7 -72,7 +72,7 @@@ float OtherTeam(float t)  //works only 
  const float ST_NEXBALL_GOALS = 1;
  const float SP_NEXBALL_GOALS = 4;
  const float SP_NEXBALL_FAULTS = 5;
- void nb_ScoreRules(float teams)
+ void nb_ScoreRules(int teams)
  {
        ScoreRules_basics(teams, 0, 0, true);
        ScoreInfo_SetLabel_TeamScore(   ST_NEXBALL_GOALS,  "goals", SFL_SORT_PRIO_PRIMARY);
@@@ -178,7 -178,7 +178,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.
@@@ -209,7 -209,7 +209,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;
@@@ -237,7 -237,7 +237,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")
@@@ -261,7 -261,7 +261,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;
        }
@@@ -376,7 -376,7 +376,7 @@@ void GoalTouch(entity this, entity touc
        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);
@@@ -466,7 -466,7 +466,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;
                }
@@@ -549,7 -553,7 +553,7 @@@ void SpawnBall(entity this
                this.glow_trail = true;
        }
  
 -      this.movetype = MOVETYPE_FLY;
 +      set_movetype(this, MOVETYPE_FLY);
  
        if(!autocvar_g_nexball_sound_bounce)
                this.noise = "";
@@@ -818,7 -822,7 +822,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
@@@ -211,7 -211,7 +211,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;
@@@ -358,11 -358,13 +358,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;
 +      });
  }
  
  
@@@ -861,7 -863,7 +861,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
@@@ -1103,7 -1105,7 +1103,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;
@@@ -1287,6 -1289,8 +1287,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)
@@@ -1591,22 -1597,26 +1591,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;
  }
  
@@@ -2229,7 -2250,12 +2229,12 @@@ spawnfunc(onslaught_generator
  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_TAKES,    "takes",     0);
diff --combined qcsrc/server/bot/bot.qc
@@@ -346,28 -346,31 +346,28 @@@ void bot_endgame(
  
  void bot_relinkplayerlist()
  {
 -      entity e;
 -      entity prevbot;
        player_count = 0;
        currentbots = 0;
 -      player_list = e = findchainflags(flags, FL_CLIENT);
        bot_list = NULL;
 -      prevbot = NULL;
 -      while (e)
 +
 +      entity prevbot = NULL;
 +      FOREACH_CLIENT(true,
        {
 -              player_count = player_count + 1;
 -              e.nextplayer = e.chain;
 -              if (IS_BOT_CLIENT(e))
 +              ++player_count;
 +
 +              if(IS_BOT_CLIENT(it))
                {
 -                      if (prevbot)
 -                              prevbot.nextbot = e;
 +                      if(prevbot)
 +                              prevbot.nextbot = it;
                        else
                        {
 -                              bot_list = e;
 +                              bot_list = it;
                                bot_list.nextbot = NULL;
                        }
 -                      prevbot = e;
 -                      currentbots = currentbots + 1;
 +                      prevbot = it;
 +                      ++currentbots;
                }
 -              e = e.chain;
 -      }
 +      });
        LOG_TRACE(strcat("relink: ", ftos(currentbots), " bots seen.\n"));
        bot_strategytoken = bot_list;
        bot_strategytoken_taken = true;
@@@ -424,84 -427,70 +424,84 @@@ void bot_clientconnect(entity this
  
  void bot_removefromlargestteam()
  {
 -      float besttime, bestcount, thiscount;
 -      entity best, head;
        CheckAllowedTeams(NULL);
        GetTeamCounts(NULL);
 -      head = findchainfloat(isbot, true);
 -      if (!head)
 -              return;
 -      best = head;
 -      besttime = head.createdtime;
 -      bestcount = 0;
 -      while (head)
 +
 +      entity best = NULL;
 +      float besttime = 0;
 +      int bestcount = 0;
 +
 +      int bcount = 0;
 +      FOREACH_ENTITY_FLOAT(isbot, true,
        {
 -              if(head.team == NUM_TEAM_1)
 -                      thiscount = c1;
 -              else if(head.team == NUM_TEAM_2)
 -                      thiscount = c2;
 -              else if(head.team == NUM_TEAM_3)
 -                      thiscount = c3;
 -              else if(head.team == NUM_TEAM_4)
 -                      thiscount = c4;
 -              else
 -                      thiscount = 0;
 -              if (thiscount > bestcount)
 +              ++bcount;
 +
 +              if(!best)
 +              {
 +                      best = it;
 +                      besttime = it.createdtime;
 +              }
 +
 +              int thiscount = 0;
 +
 +              switch(it.team)
 +              {
 +                      case NUM_TEAM_1: thiscount = c1; break;
 +                      case NUM_TEAM_2: thiscount = c2; break;
 +                      case NUM_TEAM_3: thiscount = c3; break;
 +                      case NUM_TEAM_4: thiscount = c4; break;
 +              }
 +
 +              if(thiscount > bestcount)
                {
                        bestcount = thiscount;
 -                      besttime = head.createdtime;
 -                      best = head;
 +                      besttime = it.createdtime;
 +                      best = it;
                }
 -              else if (thiscount == bestcount && besttime < head.createdtime)
 +              else if(thiscount == bestcount && besttime < it.createdtime)
                {
 -                      besttime = head.createdtime;
 -                      best = head;
 +                      besttime = it.createdtime;
 +                      best = it;
                }
 -              head = head.chain;
 -      }
 +      });
 +      if(!bcount)
 +              return; // no bots to remove
        currentbots = currentbots - 1;
        dropclient(best);
  }
  
  void bot_removenewest()
  {
 -      float besttime;
 -      entity best, head;
 -
        if(teamplay)
        {
                bot_removefromlargestteam();
                return;
        }
  
 -      head = findchainfloat(isbot, true);
 -      if (!head)
 -              return;
 -      best = head;
 -      besttime = head.createdtime;
 -      while (head)
 +      float besttime = 0;
 +      entity best = NULL;
 +      int bcount = 0;
 +
 +      FOREACH_ENTITY_FLOAT(isbot, true,
        {
 -              if (besttime < head.createdtime)
 +              ++bcount;
 +
 +              if(!best)
                {
 -                      besttime = head.createdtime;
 -                      best = head;
 +                      best = it;
 +                      besttime = it.createdtime;
                }
 -              head = head.chain;
 -      }
 +
 +              if(besttime < it.createdtime)
 +              {
 +                      besttime = it.createdtime;
 +                      best = it;
 +              }
 +      });
 +
 +      if(!bcount)
 +              return; // no bots to remove
 +
        currentbots = currentbots - 1;
        dropclient(best);
  }
@@@ -584,7 -573,7 +584,7 @@@ float bot_fixcount(
        // But don't remove bots immediately on level change, as the real players
        // usually haven't rejoined yet
        bots_would_leave = false;
-       if (teamplay && autocvar_bot_vs_human && (c3==-1 && c4==-1))
+       if (teamplay && autocvar_bot_vs_human && AvailableTeams() == 2)
                bots = min(ceil(fabs(autocvar_bot_vs_human) * activerealplayers), maxclients - realplayers);
        else if ((realplayers || autocvar_bot_join_empty || (currentbots > 0 && time < 5)))
        {
@@@ -670,11 -659,9 +670,11 @@@ void bot_serverframe(
                else
                {
                        // TODO: Make this check cleaner
 -                      entity wp = findchain(classname, "waypoint");
 -                      if(time - wp.nextthink > 10)
 +                      IL_EACH(g_waypoints, time - it.nextthink > 10,
 +                      {
                                waypoint_save_links();
 +                              break;
 +                      });
                }
        }
        else
@@@ -269,7 -269,7 +269,7 @@@ void PutObserverInServer(entity this
        this.health = FRAGS_SPECTATOR;
        this.takedamage = DAMAGE_NO;
        this.solid = SOLID_NOT;
 -      this.movetype = MOVETYPE_FLY_WORLDONLY; // user preference is controlled by playerprethink
 +      set_movetype(this, MOVETYPE_FLY_WORLDONLY); // user preference is controlled by playerprethink
        this.flags = FL_CLIENT | FL_NOTARGET;
        this.armorvalue = 666;
        this.effects = 0;
@@@ -481,7 -481,7 +481,7 @@@ void PutClientInServer(entity this
                this.iscreature = true;
                this.teleportable = TELEPORT_NORMAL;
                this.damagedbycontents = true;
 -              this.movetype = MOVETYPE_WALK;
 +              set_movetype(this, MOVETYPE_WALK);
                this.solid = SOLID_SLIDEBOX;
                this.dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_SOLID;
                if (autocvar_g_playerclip_collisions)
@@@ -1147,7 -1147,7 +1147,7 @@@ void ClientConnect(entity this
        if (!sv_foginterval && world.fog != "")
                stuffcmd(this, strcat("\nfog ", world.fog, "\nr_fog_exp2 0\nr_drawfog 1\n"));
  
-       if (autocvar_sv_teamnagger && !(autocvar_bot_vs_human && (c3==-1 && c4==-1)))
+       if (autocvar_sv_teamnagger && !(autocvar_bot_vs_human && AvailableTeams() == 2))
                if (!g_ca && !g_cts && !g_race) // teamnagger is currently bad for ca, race & cts
                        send_CSQC_teamnagger();
  
@@@ -1289,7 -1289,7 +1289,7 @@@ void respawn(entity this
        {
                this.solid = SOLID_NOT;
                this.takedamage = DAMAGE_NO;
 -              this.movetype = MOVETYPE_FLY;
 +              set_movetype(this, MOVETYPE_FLY);
                this.velocity = '0 0 1' * autocvar_g_respawn_ghosts_speed;
                this.avelocity = randomvec() * autocvar_g_respawn_ghosts_speed * 3 - randomvec() * autocvar_g_respawn_ghosts_speed * 3;
                this.effects |= CSQCMODEL_EF_RESPAWNGHOST;
@@@ -1615,7 -1615,7 +1615,7 @@@ void SpectateCopy(entity this, entity s
        this.angles = spectatee.v_angle;
        STAT(FROZEN, this) = STAT(FROZEN, spectatee);
        this.revive_progress = spectatee.revive_progress;
 -      if(!PHYS_INPUT_BUTTON_USE(this))
 +      if(!PHYS_INPUT_BUTTON_USE(this) && STAT(CAMERA_SPECTATOR, this) != 2)
                this.fixangle = true;
        setorigin(this, spectatee.origin);
        setsize(this, spectatee.mins, spectatee.maxs);
@@@ -1675,7 -1675,7 +1675,7 @@@ bool SpectateSet(entity this
        msg_entity = this;
        WriteByte(MSG_ONE, SVC_SETVIEW);
        WriteEntity(MSG_ONE, this.enemy);
 -      this.movetype = MOVETYPE_NONE;
 +      set_movetype(this, MOVETYPE_NONE);
        accuracy_resend(this);
  
        if(!SpectateUpdate(this))
@@@ -1925,6 -1925,7 +1925,6 @@@ void ObserverThink(entity this
                MinigameImpulse(this, this.impulse);
                this.impulse = 0;
        }
 -      float prefered_movetype;
        if (this.flags & FL_JUMPRELEASED) {
                if (PHYS_INPUT_BUTTON_JUMP(this) && !this.version_mismatch) {
                        this.flags &= ~FL_JUMPRELEASED;
                                TRANSMUTE(Spectator, this);
                        }
                } else {
 -                      prefered_movetype = ((!PHYS_INPUT_BUTTON_USE(this) ? this.cvar_cl_clippedspectating : !this.cvar_cl_clippedspectating) ? MOVETYPE_FLY_WORLDONLY : MOVETYPE_NOCLIP);
 -                      if (this.movetype != prefered_movetype)
 -                              this.movetype = prefered_movetype;
 +                      int preferred_movetype = ((!PHYS_INPUT_BUTTON_USE(this) ? this.cvar_cl_clippedspectating : !this.cvar_cl_clippedspectating) ? MOVETYPE_FLY_WORLDONLY : MOVETYPE_NOCLIP);
 +                      set_movetype(this, preferred_movetype);
                }
        } else {
                if (!(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_JUMP(this))) {
@@@ -1957,13 -1959,6 +1957,13 @@@ void SpectatorThink(entity this
        {
                if(MinigameImpulse(this, this.impulse))
                        this.impulse = 0;
 +
 +              if (this.impulse == IMP_weapon_drop.impulse)
 +              {
 +                      STAT(CAMERA_SPECTATOR, this) = (STAT(CAMERA_SPECTATOR, this) + 1) % 3;
 +                      this.impulse = 0;
 +                      return;
 +              }
        }
        if (this.flags & FL_JUMPRELEASED) {
                if (PHYS_INPUT_BUTTON_JUMP(this) && !this.version_mismatch) {
@@@ -2286,15 -2281,12 +2286,15 @@@ void PlayerPreThink (entity this
          .entity weaponentity = weaponentities[0]; // TODO: unhardcode
                if (this.hook.state) {
                        do_crouch = false;
 +              } else if (this.waterlevel >= WATERLEVEL_SWIMMING) {
 +                      do_crouch = false;
                } else if (this.vehicle) {
                        do_crouch = false;
                } else if (STAT(FROZEN, this)) {
                        do_crouch = false;
 -        } else if ((PS(this).m_weapon == WEP_SHOTGUN || PS(this).m_weapon == WEP_SHOCKWAVE) && this.(weaponentity).wframe == WFRAME_FIRE2 && time < this.(weaponentity).weapon_nextthink) {
 -                  // WEAPONTODO: predict
 +        } else if ((PS(this).m_weapon.spawnflags & WEP_TYPE_MELEE_PRI) && this.(weaponentity).wframe == WFRAME_FIRE1 && time < this.(weaponentity).weapon_nextthink) {
 +                      do_crouch = false;
 +        } else if ((PS(this).m_weapon.spawnflags & WEP_TYPE_MELEE_SEC) && this.(weaponentity).wframe == WFRAME_FIRE2 && time < this.(weaponentity).weapon_nextthink) {
                        do_crouch = false;
          }
  
                entity e = this.teamkill_soundsource;
                entity oldpusher = e.pusher;
                e.pusher = this;
 -              PlayerSound(e, playersound_teamshoot, CH_VOICE, VOICETYPE_LASTATTACKER_ONLY);
 +              PlayerSound(e, playersound_teamshoot, CH_VOICE, VOL_BASEVOICE, VOICETYPE_LASTATTACKER_ONLY);
                e.pusher = oldpusher;
        }
  
        if (this.taunt_soundtime && time > this.taunt_soundtime) {
                this.taunt_soundtime = 0;
 -              PlayerSound(this, playersound_taunt, CH_VOICE, VOICETYPE_AUTOTAUNT);
 +              PlayerSound(this, playersound_taunt, CH_VOICE, VOL_BASEVOICE, VOICETYPE_AUTOTAUNT);
        }
  
        target_voicescript_next(this);
@@@ -2412,7 -2404,7 +2412,7 @@@ void DrownPlayer(entity this
        if (this.waterlevel != WATERLEVEL_SUBMERGED || this.vehicle)
        {
                if(this.air_finished < time)
 -                      PlayerSound(this, playersound_gasp, CH_PLAYER, VOICETYPE_PLAYERSOUND);
 +                      PlayerSound(this, playersound_gasp, CH_PLAYER, VOL_BASE, VOICETYPE_PLAYERSOUND);
                this.air_finished = time + autocvar_g_balance_contents_drowndelay;
                this.dmg = 2;
        }
        }
  }
  
 +void Player_Physics(entity this)
 +{
 +      this.movetype = ((this.move_qcphysics) ? MOVETYPE_NONE : this.move_movetype);
 +
 +      if(!this.move_qcphysics)
 +              return;
 +
 +      int mt = this.move_movetype;
 +
 +      if(mt == MOVETYPE_PUSH || mt == MOVETYPE_FAKEPUSH || mt == MOVETYPE_PHYSICS)
 +      {
 +              this.move_qcphysics = false;
 +              this.movetype = mt;
 +              return;
 +      }
 +
 +      if(!frametime && !this.pm_frametime)
 +              return;
 +
 +      Movetype_Physics_NoMatchTicrate(this, this.pm_frametime, true);
 +
 +      this.pm_frametime = 0;
 +}
 +
  /*
  =============
  PlayerPostThink
@@@ -2460,8 -2428,6 +2460,8 @@@ Called every frame for each client afte
  .float idlekick_lasttimeleft;
  void PlayerPostThink (entity this)
  {
 +      Player_Physics(this);
 +
        if (sv_maxidle > 0)
        if (frametime) // WORKAROUND: only use dropclient in server frames (frametime set). Never use it in cl_movement frames (frametime zero).
        if (IS_REAL_CLIENT(this))
@@@ -273,14 -273,10 +273,14 @@@ void assault_new_round(entity this
        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));
@@@ -429,48 -425,68 +429,48 @@@ spawnfunc(target_assault_roundstart
  // legacy bot code
  void havocbot_goalrating_ast_targets(entity this, float ratingscale)
  {
 -      entity ad, best, wp;
 -      float radius, bestvalue;
 -      bool found;
 -      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;
 -              FOREACH_ENTITY_STRING(targetname, ad.target,
 +              bool found = false;
 +              FOREACH_ENTITY_STRING(targetname, it.target,
                {
 -                      if(it.classname == "target_objective_decrease")
 +                      if(it.classname != "target_objective_decrease")
 +                              continue;
 +
 +                      if(it.enemy.health > 0 && it.enemy.health < ASSAULT_VALUE_INACTIVE)
                        {
 -                              if(it.enemy.health > 0 && it.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)
@@@ -667,7 -683,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();
@@@ -152,39 -152,12 +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)
@@@ -334,7 -307,7 +334,7 @@@ void ctf_CaptureShield_Spawn(entity fla
        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;
@@@ -355,7 -328,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;
@@@ -414,7 -387,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';
@@@ -480,7 -453,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
  }
@@@ -668,7 -638,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';
@@@ -780,11 -750,12 +780,11 @@@ void ctf_CheckFlagReturn(entity flag, i
  bool ctf_Stalemate_Customize(entity this, entity client)
  {
        // make spectators see what the player would see
 -      entity e, wp_owner;
 -      e = WaypointSprite_getviewentity(client);
 -      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; }
  
@@@ -937,9 -908,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))
                        {
@@@ -1138,7 -1109,6 +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;
@@@ -1186,13 -1156,6 +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);
@@@ -1318,13 -1280,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);
@@@ -1528,19 -1490,23 +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)
@@@ -2021,7 -1987,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));
@@@ -2330,7 -2293,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
@@@ -2418,10 -2381,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;
                        }
                }
  
@@@ -2604,27 -2567,44 +2604,44 @@@ void ctf_SpawnTeam (string teamname, in
  
  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);
-               ctf_SpawnTeam("Blue", NUM_TEAM_2);
-               if(ctf_teams >= 3)
+               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 >= 4)
+               if(ctf_teams & BIT(3))
                        ctf_SpawnTeam("Pink", NUM_TEAM_4);
        }
  
@@@ -193,11 -193,7 +193,11 @@@ float Invasion_CheckWinner(
  {
        if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
        {
 -              FOREACH_ENTITY_FLAGS(flags, FL_MONSTER, LAMBDA(Monster_Remove(it)));
 +              IL_EACH(g_monsters, true,
 +              {
 +                      Monster_Remove(it);
 +              });
 +              IL_CLEAR(g_monsters);
  
                Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
                Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
  
        float total_alive_monsters = 0, supermonster_count = 0, red_alive = 0, blue_alive = 0, yellow_alive = 0, pink_alive = 0;
  
 -      FOREACH_ENTITY_FLAGS(flags, FL_MONSTER, LAMBDA(
 -              if(it.health > 0)
 -              {
 -                      if((get_monsterinfo(it.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
 -                              ++supermonster_count;
 -                      ++total_alive_monsters;
 +      IL_EACH(g_monsters, it.health > 0,
 +      {
 +              if((get_monsterinfo(it.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
 +                      ++supermonster_count;
 +              ++total_alive_monsters;
  
 -                      if(teamplay)
 -                      switch(it.team)
 -                      {
 -                              case NUM_TEAM_1: ++red_alive; break;
 -                              case NUM_TEAM_2: ++blue_alive; break;
 -                              case NUM_TEAM_3: ++yellow_alive; break;
 -                              case NUM_TEAM_4: ++pink_alive; break;
 -                      }
 +              if(teamplay)
 +              switch(it.team)
 +              {
 +                      case NUM_TEAM_1: ++red_alive; break;
 +                      case NUM_TEAM_2: ++blue_alive; break;
 +                      case NUM_TEAM_3: ++yellow_alive; break;
 +                      case NUM_TEAM_4: ++pink_alive; break;
                }
 -      ));
 +      });
  
        if((total_alive_monsters + inv_numkilled) < inv_maxspawned && inv_maxcurrent < inv_maxspawned)
        {
                ));
        }
  
 -      FOREACH_ENTITY_FLAGS(flags, FL_MONSTER, LAMBDA(Monster_Remove(it)));
 +      IL_EACH(g_monsters, true,
 +      {
 +              Monster_Remove(it);
 +      });
 +      IL_CLEAR(g_monsters);
  
        if(teamplay)
        {
@@@ -485,7 -479,7 +485,7 @@@ MUTATOR_HOOKFUNCTION(inv, AllowMobButch
        return true;
  }
  
- void invasion_ScoreRules(float inv_teams)
+ void invasion_ScoreRules(int inv_teams)
  {
        if(inv_teams) { CheckAllowedTeams(NULL); }
        ScoreRules_basics(inv_teams, 0, 0, false);
  void invasion_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
  {
        if(autocvar_g_invasion_teams)
+       {
                invasion_teams = bound(2, autocvar_g_invasion_teams, 4);
+               int teams = 0;
+               if(invasion_teams >= 1) teams |= BIT(0);
+               if(invasion_teams >= 2) teams |= BIT(1);
+               if(invasion_teams >= 3) teams |= BIT(2);
+               if(invasion_teams >= 4) teams |= BIT(3);
+               invasion_teams = teams; // now set it?
+       }
        else
                invasion_teams = 0;
  
@@@ -144,7 -144,7 +144,7 @@@ 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(float teams)
+ 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);
@@@ -307,7 -307,7 +307,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;
@@@ -346,7 -346,7 +346,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;
@@@ -959,9 -959,15 +959,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);
@@@ -1056,6 -1062,14 +1062,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);
@@@ -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