Merge branch 'master' into terencehill/bot_ai
authorterencehill <piuntn@gmail.com>
Tue, 17 Jul 2018 15:19:59 +0000 (17:19 +0200)
committerterencehill <piuntn@gmail.com>
Tue, 17 Jul 2018 15:19:59 +0000 (17:19 +0200)
1  2 
qcsrc/common/gamemodes/gamemode/assault/assault.qc
qcsrc/common/gamemodes/gamemode/ctf/ctf.qc
qcsrc/common/gamemodes/gamemode/domination/domination.qc
qcsrc/common/gamemodes/gamemode/freezetag/freezetag.qc
qcsrc/common/gamemodes/gamemode/keyhunt/keyhunt.qc
qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qc
qcsrc/server/bot/default/bot.qc

index 710f03095b5402c0e837ec8e3e8867576067b9bd,e7134182e497b95facf46c75d680bc5079dd7348..bc048563e83e837df8cf5cc3301e35b9391085e3
@@@ -430,9 -430,9 +430,9 @@@ void havocbot_goalrating_ast_targets(en
                // Find and rate waypoints around it
                found = false;
                entity best = NULL;
 -              float bestvalue = 99999999999;
 +              float bestvalue = FLOAT_MAX;
                entity des = it;
 -              for(float radius = 0; radius < 1500 && !found; radius += 500)
 +              for (float radius = 500; radius <= 1500 && !found; radius += 500)
                {
                        FOREACH_ENTITY_RADIUS(p, radius, it.classname == "waypoint" && !(it.wpflags & WAYPOINTFLAG_GENERATED),
                        {
@@@ -493,11 -493,10 +493,11 @@@ void havocbot_role_ast_offense(entity t
  
        if (navigation_goalrating_timeout(this))
        {
 +              // role: offense
                navigation_goalrating_start(this);
 -              havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650);
 +              havocbot_goalrating_enemyplayers(this, 10000, this.origin, 650);
                havocbot_goalrating_ast_targets(this, 20000);
 -              havocbot_goalrating_items(this, 15000, this.origin, 10000);
 +              havocbot_goalrating_items(this, 30000, this.origin, 10000);
                navigation_goalrating_end(this);
  
                navigation_goalrating_timeout_set(this);
@@@ -528,11 -527,10 +528,11 @@@ void havocbot_role_ast_defense(entity t
  
        if (navigation_goalrating_timeout(this))
        {
 +              // role: defense
                navigation_goalrating_start(this);
 -              havocbot_goalrating_enemyplayers(this, 20000, this.origin, 3000);
 +              havocbot_goalrating_enemyplayers(this, 10000, this.origin, 3000);
                havocbot_goalrating_ast_targets(this, 20000);
 -              havocbot_goalrating_items(this, 15000, this.origin, 10000);
 +              havocbot_goalrating_items(this, 30000, this.origin, 10000);
                navigation_goalrating_end(this);
  
                navigation_goalrating_timeout_set(this);
@@@ -545,10 -543,12 +545,10 @@@ void havocbot_role_ast_setrole(entity t
        {
                case HAVOCBOT_AST_ROLE_DEFENSE:
                        this.havocbot_role = havocbot_role_ast_defense;
 -                      this.havocbot_role_flags = HAVOCBOT_AST_ROLE_DEFENSE;
                        this.havocbot_role_timeout = 0;
                        break;
                case HAVOCBOT_AST_ROLE_OFFENSE:
                        this.havocbot_role = havocbot_role_ast_offense;
 -                      this.havocbot_role_flags = HAVOCBOT_AST_ROLE_OFFENSE;
                        this.havocbot_role_timeout = 0;
                        break;
        }
@@@ -606,10 -606,10 +606,10 @@@ MUTATOR_HOOKFUNCTION(as, PlayHitsound
        return (frag_victim.classname == "func_assault_destructible");
  }
  
- MUTATOR_HOOKFUNCTION(as, CheckAllowedTeams)
+ MUTATOR_HOOKFUNCTION(as, TeamBalance_CheckAllowedTeams)
  {
        // assault always has 2 teams
-       c1 = c2 = 0;
+       M_ARGV(0, float) = BIT(0) | BIT(1);
        return true;
  }
  
index 855b043375c55568d6d92d4ec5da15bd98841a82,812d5da4ec0321625d09484e9f38daee27533a57..13d89c1be13c3c3f6537ee0f0592ee883f906c05
@@@ -449,6 -449,7 +449,6 @@@ void ctf_Handle_Throw(entity player, en
        flag.solid = SOLID_TRIGGER;
        flag.ctf_dropper = player;
        flag.ctf_droptime = time;
 -      navigation_dynamicgoal_set(flag);
  
        flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
  
                        flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & ITEM_Strength.m_itemid) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
                        flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
                        ctf_Handle_Drop(flag, player, droptype);
 +                      navigation_dynamicgoal_set(flag, player);
                        break;
                }
  
                {
                        flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), false);
                        ctf_Handle_Drop(flag, player, droptype);
 +                      navigation_dynamicgoal_set(flag, player);
                        break;
                }
        }
@@@ -1516,28 -1515,17 +1516,28 @@@ void havocbot_goalrating_ctf_enemyflag(
                head = head.ctf_worldflagnext;
        }
        if (head)
 +      {
 +              if (head.ctf_status == FLAG_CARRY)
 +              {
 +                      // adjust rating of our flag carrier depending on his health
 +                      head = head.tag_entity;
 +                      float f = bound(0, (head.health + head.armorvalue) / 100, 2) - 1;
 +                      ratingscale += ratingscale * f * 0.1;
 +              }
                navigation_routerating(this, head, ratingscale, 10000);
 +      }
  }
  
  void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
  {
 +      // disabled because we always spawn waypoints for flags with waypoint_spawnforitem_force
 +      /*
        if (!bot_waypoints_for_items)
        {
                havocbot_goalrating_ctf_enemyflag(this, ratingscale);
                return;
        }
 -
 +      */
        entity head;
  
        head = havocbot_ctf_find_enemy_flag(this);
@@@ -1584,10 -1572,28 +1584,10 @@@ void havocbot_goalrating_ctf_droppedfla
        }
  }
  
 -void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
 -{
 -      IL_EACH(g_items, it.bot_pickup,
 -      {
 -              // gather health and armor only
 -              if (it.solid)
 -              if (GetResourceAmount(it, RESOURCE_HEALTH) || GetResourceAmount(it, RESOURCE_ARMOR))
 -              if (vdist(it.origin - org, <, sradius))
 -              {
 -                      // get the value of the item
 -                      float t = it.bot_pickupevalfunc(this, it) * 0.0001;
 -                      if (t > 0)
 -                              navigation_routerating(this, it, t * ratingscale, 500);
 -              }
 -      });
 -}
 -
  void havocbot_ctf_reset_role(entity this)
  {
        float cdefense, cmiddle, coffense;
        entity mf, ef;
 -      float c;
  
        if(IS_DEAD(this))
                return;
                return;
        }
  
 -      // if there is only me on the team switch to offense
 -      c = 0;
 -      FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), { ++c; });
 +      // if there is no one else on the team switch to offense
 +      int count = 0;
 +      // don't check if this bot is a player since it isn't true when the bot is added to the server
 +      FOREACH_CLIENT(it != this && IS_PLAYER(it) && SAME_TEAM(it, this), { ++count; });
  
 -      if(c==1)
 +      if (count == 0)
        {
                havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
                return;
        }
 +      else if (time < CS(this).jointime + 1)
 +      {
 +              // if bots spawn all at once set good default roles
 +              if (count == 1)
 +              {
 +                      havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
 +                      return;
 +              }
 +              else if (count == 2)
 +              {
 +                      havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
 +                      return;
 +              }
 +      }
  
        // Evaluate best position to take
        // Count mates on middle position
                havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
        else
                havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
 +
 +      // if bots spawn all at once assign them a more appropriated role after a while
 +      if (time < CS(this).jointime + 1 && count > 2)
 +              this.havocbot_role_timeout = time + 10 + random() * 10;
 +}
 +
 +bool havocbot_ctf_is_basewaypoint(entity item)
 +{
 +      if (item.classname != "waypoint")
 +              return false;
 +
 +      entity head = ctf_worldflaglist;
 +      while (head)
 +      {
 +              if (item == head.bot_basewaypoint)
 +                      return true;
 +              head = head.ctf_worldflagnext;
 +      }
 +      return false;
  }
  
  void havocbot_role_ctf_carrier(entity this)
        {
                navigation_goalrating_start(this);
  
 +              // role: carrier
 +              entity mf = havocbot_ctf_find_flag(this);
 +              vector base_org = mf.dropped_origin;
 +              float base_rating = (mf.ctf_status == FLAG_BASE) ? 10000 : (vdist(this.origin - base_org, >, 100) ? 2000 : 1000);
                if(ctf_oneflag)
 -                      havocbot_goalrating_ctf_enemybase(this, 50000);
 +                      havocbot_goalrating_ctf_enemybase(this, base_rating);
                else
 -                      havocbot_goalrating_ctf_ourbase(this, 50000);
 +                      havocbot_goalrating_ctf_ourbase(this, base_rating);
 +
 +              // start collecting items very close to the bot but only inside of own base radius
 +              if (vdist(this.origin - base_org, <, havocbot_middlepoint_radius))
 +                      havocbot_goalrating_items(this, 15000, this.origin, min(500, havocbot_middlepoint_radius * 0.5));
  
 -              if(GetResourceAmount(this, RESOURCE_HEALTH) < 100)
 -                      havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
 +              havocbot_goalrating_items(this, 10000, base_org, havocbot_middlepoint_radius * 0.5);
  
                navigation_goalrating_end(this);
  
                navigation_goalrating_timeout_set(this);
  
 -              entity head = ctf_worldflaglist;
 -              while (head)
 -              {
 -                      if (this.goalentity == head.bot_basewaypoint)
 -                      {
 -                              this.goalentity_lock_timeout = time + 5;
 -                              break;
 -                      }
 -                      head = head.ctf_worldflagnext;
 -              }
 +              entity goal = this.goalentity;
 +              if (havocbot_ctf_is_basewaypoint(goal) && vdist(goal.origin - this.origin, <, 100))
 +                      this.goalentity_lock_timeout = time + ((this.bot_aimtarg) ? 2 : 3);
  
 -              if (this.goalentity)
 +              if (goal)
                        this.havocbot_cantfindflag = time + 10;
                else if (time > this.havocbot_cantfindflag)
                {
@@@ -1755,15 -1727,11 +1755,15 @@@ void havocbot_role_ctf_escort(entity th
                this.havocbot_role_timeout = 0;
                return;
        }
 +      if (ef.ctf_status == FLAG_DROPPED)
 +      {
 +              navigation_goalrating_timeout_expire(this, 1);
 +              return;
 +      }
  
        // If the flag carrier reached the base switch to defense
        mf = havocbot_ctf_find_flag(this);
 -      if(mf.ctf_status!=FLAG_BASE)
 -      if(vdist(ef.origin - mf.dropped_origin, <, 300))
 +      if (mf.ctf_status != FLAG_BASE && vdist(ef.origin - mf.dropped_origin, <, 900))
        {
                havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
                return;
        {
                navigation_goalrating_start(this);
  
 -              havocbot_goalrating_ctf_enemyflag(this, 30000);
 -              havocbot_goalrating_ctf_ourstolenflag(this, 40000);
 -              havocbot_goalrating_items(this, 10000, this.origin, 10000);
 +              // role: escort
 +              havocbot_goalrating_ctf_enemyflag(this, 10000);
 +              havocbot_goalrating_ctf_ourstolenflag(this, 6000);
 +              havocbot_goalrating_items(this, 21000, this.origin, 10000);
  
                navigation_goalrating_end(this);
  
@@@ -1851,6 -1818,13 +1851,6 @@@ void havocbot_role_ctf_offense(entity t
                }
        }
  
 -      // About to fail, switch to middlefield
 -      if(GetResourceAmount(this, RESOURCE_HEALTH) < 50)
 -      {
 -              havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
 -              return;
 -      }
 -
        // Set the role timeout if necessary
        if (!this.havocbot_role_timeout)
                this.havocbot_role_timeout = time + 120;
        {
                navigation_goalrating_start(this);
  
 -              havocbot_goalrating_ctf_ourstolenflag(this, 50000);
 -              havocbot_goalrating_ctf_enemybase(this, 20000);
 -              havocbot_goalrating_items(this, 5000, this.origin, 1000);
 -              havocbot_goalrating_items(this, 1000, this.origin, 10000);
 +              // role: offense
 +              havocbot_goalrating_ctf_ourstolenflag(this, 10000);
 +              havocbot_goalrating_ctf_enemybase(this, 10000);
 +              havocbot_goalrating_items(this, 22000, this.origin, 10000);
  
                navigation_goalrating_end(this);
  
@@@ -1914,20 -1888,15 +1914,20 @@@ void havocbot_role_ctf_retriever(entit
  
        if (navigation_goalrating_timeout(this))
        {
 -              float rt_radius;
 -              rt_radius = 10000;
 +              const float RT_RADIUS = 10000;
  
                navigation_goalrating_start(this);
  
 -              havocbot_goalrating_ctf_ourstolenflag(this, 50000);
 -              havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
 -              havocbot_goalrating_ctf_enemybase(this, 30000);
 -              havocbot_goalrating_items(this, 500, this.origin, rt_radius);
 +              // role: retriever
 +              havocbot_goalrating_ctf_ourstolenflag(this, 10000);
 +              havocbot_goalrating_ctf_droppedflags(this, 12000, this.origin, RT_RADIUS);
 +              havocbot_goalrating_ctf_enemybase(this, 8000);
 +              entity ef = havocbot_ctf_find_enemy_flag(this);
 +              vector enemy_base_org = ef.dropped_origin;
 +              // start collecting items very close to the bot but only inside of enemy base radius
 +              if (vdist(this.origin - enemy_base_org, <, havocbot_middlepoint_radius))
 +                      havocbot_goalrating_items(this, 27000, this.origin, min(500, havocbot_middlepoint_radius * 0.5));
 +              havocbot_goalrating_items(this, 18000, this.origin, havocbot_middlepoint_radius);
  
                navigation_goalrating_end(this);
  
@@@ -1976,20 -1945,15 +1976,20 @@@ void havocbot_role_ctf_middle(entity th
  
                navigation_goalrating_start(this);
  
 -              havocbot_goalrating_ctf_ourstolenflag(this, 50000);
 -              havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
 -              havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_middlepoint_radius * 0.5);
 -              havocbot_goalrating_items(this, 5000, org, havocbot_middlepoint_radius * 0.5);
 -              havocbot_goalrating_items(this, 2500, this.origin, 10000);
 -              havocbot_goalrating_ctf_enemybase(this, 2500);
 +              // role: middle
 +              havocbot_goalrating_ctf_ourstolenflag(this, 8000);
 +              havocbot_goalrating_ctf_droppedflags(this, 9000, this.origin, 10000);
 +              havocbot_goalrating_enemyplayers(this, 25000, org, havocbot_middlepoint_radius * 0.5);
 +              havocbot_goalrating_items(this, 25000, org, havocbot_middlepoint_radius * 0.5);
 +              havocbot_goalrating_items(this, 18000, this.origin, 10000);
 +              havocbot_goalrating_ctf_enemybase(this, 3000);
  
                navigation_goalrating_end(this);
  
 +              entity goal = this.goalentity;
 +              if (havocbot_ctf_is_basewaypoint(goal) && vdist(goal.origin - this.origin, <, 100))
 +                      this.goalentity_lock_timeout = time + 2;
 +
                navigation_goalrating_timeout_set(this);
        }
  }
@@@ -2044,18 -2008,17 +2044,18 @@@ void havocbot_role_ctf_defense(entity t
                        }
                });
  
 +              // role: defense
                if(closestplayer)
                if(DIFF_TEAM(closestplayer, this))
                if(vdist(org - this.origin, >, 1000))
                if(checkpvs(this.origin,closestplayer)||random()<0.5)
 -                      havocbot_goalrating_ctf_ourbase(this, 30000);
 +                      havocbot_goalrating_ctf_ourbase(this, 10000);
  
 -              havocbot_goalrating_ctf_ourstolenflag(this, 20000);
 -              havocbot_goalrating_ctf_droppedflags(this, 20000, org, havocbot_middlepoint_radius);
 -              havocbot_goalrating_enemyplayers(this, 15000, org, havocbot_middlepoint_radius);
 -              havocbot_goalrating_items(this, 10000, org, havocbot_middlepoint_radius);
 -              havocbot_goalrating_items(this, 5000, this.origin, 10000);
 +              havocbot_goalrating_ctf_ourstolenflag(this, 5000);
 +              havocbot_goalrating_ctf_droppedflags(this, 6000, org, havocbot_middlepoint_radius);
 +              havocbot_goalrating_enemyplayers(this, 25000, org, havocbot_middlepoint_radius);
 +              havocbot_goalrating_items(this, 25000, org, havocbot_middlepoint_radius);
 +              havocbot_goalrating_items(this, 18000, this.origin, 10000);
  
                navigation_goalrating_end(this);
  
@@@ -2503,11 -2466,9 +2503,9 @@@ MUTATOR_HOOKFUNCTION(ctf, HavocBot_Choo
        return true;
  }
  
- MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams)
+ MUTATOR_HOOKFUNCTION(ctf, TeamBalance_CheckAllowedTeams)
  {
-       //M_ARGV(0, float) = ctf_teams;
        M_ARGV(1, string) = "ctf_team";
-       return true;
  }
  
  MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
@@@ -2733,7 -2694,6 +2731,6 @@@ spawnfunc(team_CTL_bluelolly)  { spawnf
  // scoreboard setup
  void ctf_ScoreRules(int teams)
  {
-       CheckAllowedTeams(NULL);
        GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
          field_team(ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
          field(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
index e27a701c1591820393ac11e5efbb6829be271867,0cea02f2f2aef7325b0c7dc64a5bf13c1573cea2..b8f08a5a1e09b35f28aa9d3e4ae6b852cd59d196
@@@ -297,47 -297,55 +297,55 @@@ void dom_controlpoint_setup(entity this
        WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT);
  }
  
float total_controlpoints;
int total_control_points;
  void Domination_count_controlpoints()
  {
-       total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
+       total_control_points = 0;
+       for (int i = 1; i <= NUM_TEAMS; ++i)
+       {
+               Team_SetNumberOfControlPoints(Team_GetTeamFromIndex(i), 0);
+       }
        IL_EACH(g_dompoints, true,
        {
-               ++total_controlpoints;
-               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);
+               ++total_control_points;
+               if (!Entity_HasValidTeam(it.goalentity))
+               {
+                       continue;
+               }
+               entity team_ = Entity_GetTeam(it.goalentity);
+               int num_control_points = Team_GetNumberOfControlPoints(team_);
+               ++num_control_points;
+               Team_SetNumberOfControlPoints(team_, num_control_points);
        });
  }
  
float Domination_GetWinnerTeam()
int Domination_GetWinnerTeam()
  {
-       float winner_team = 0;
-       if(redowned == total_controlpoints)
-               winner_team = NUM_TEAM_1;
-       if(blueowned == total_controlpoints)
+       int winner_team = 0;
+       if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(1)) ==
+               total_control_points)
        {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_2;
+               winner_team = NUM_TEAM_1;
        }
-       if(yellowowned == total_controlpoints)
+       for (int i = 2; i <= NUM_TEAMS; ++i)
        {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_3;
+               if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(i)) ==
+                       total_control_points)
+               {
+                       if (winner_team != 0)
+                       {
+                               return 0;
+                       }
+                       winner_team = Team_IndexToTeam(i);
+               }
        }
-       if(pinkowned == total_controlpoints)
+       if (winner_team)
        {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_4;
-       }
-       if(winner_team)
                return winner_team;
+       }
        return -1; // no control points left?
  }
  
- #define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
- #define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
  float Domination_CheckWinner()
  {
        if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
@@@ -393,9 -401,9 +401,9 @@@ void havocbot_goalrating_controlpoints(
                if(it.cnt > -1) // this is just being fought
                        navigation_routerating(this, it, ratingscale, 5000);
                else if(it.goalentity.cnt == 0) // unclaimed
 -                      navigation_routerating(this, it, ratingscale * 0.5, 5000);
 +                      navigation_routerating(this, it, ratingscale, 5000);
                else if(it.goalentity.team != this.team) // other team's point
 -                      navigation_routerating(this, it, ratingscale * 0.2, 5000);
 +                      navigation_routerating(this, it, ratingscale, 5000);
        });
  }
  
@@@ -408,8 -416,8 +416,8 @@@ void havocbot_role_dom(entity this
        {
                navigation_goalrating_start(this);
                havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
 -              havocbot_goalrating_items(this, 8000, this.origin, 8000);
 -              //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000);
 +              havocbot_goalrating_items(this, 20000, this.origin, 8000);
 +              //havocbot_goalrating_enemyplayers(this, 1500, this.origin, 2000);
                havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
                navigation_goalrating_end(this);
  
        }
  }
  
- MUTATOR_HOOKFUNCTION(dom, CheckAllowedTeams)
+ MUTATOR_HOOKFUNCTION(dom, TeamBalance_CheckAllowedTeams)
  {
        // fallback?
        M_ARGV(0, float) = domination_teams;
        {
                if(head.netname != "")
                {
-                       switch(head.team)
+                       if (Team_IsValidTeam(head.team))
                        {
-                               case NUM_TEAM_1: c1 = 0; break;
-                               case NUM_TEAM_2: c2 = 0; break;
-                               case NUM_TEAM_3: c3 = 0; break;
-                               case NUM_TEAM_4: c4 = 0; break;
+                               M_ARGV(0, float) |= Team_TeamToBit(head.team);
                        }
                }
  
@@@ -584,10 -589,10 +589,10 @@@ void ScoreRules_dom(int teams
  }
  
  // code from here on is just to support maps that don't have control point and team entities
- void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
+ void dom_spawnteam(string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
  {
-     TC(Sound, capsound);
-     entity e = new_pure(dom_team);
+       TC(Sound, capsound);
+       entity e = new_pure(dom_team);
        e.netname = strzone(teamname);
        e.cnt = teamcolor;
        e.model = pointmodel;
@@@ -621,7 -626,7 +626,7 @@@ void dom_spawnpoint(vector org
  // spawn some default teams if the map is not set up for domination
  void dom_spawnteams(int teams)
  {
-     TC(int, teams);
+       TC(int, teams);
        dom_spawnteam(Team_ColoredFullName(NUM_TEAM_1), NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND_DOM_CLAIM, "", "Red team has captured a control point");
        dom_spawnteam(Team_ColoredFullName(NUM_TEAM_2), NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, SND_DOM_CLAIM, "", "Blue team has captured a control point");
        if(teams >= 3)
@@@ -644,14 -649,9 +649,9 @@@ void dom_DelayedInit(entity this) // D
                dom_spawnteams(domination_teams);
        }
  
-       CheckAllowedTeams(NULL);
-       //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);
+       entity balance = TeamBalance_CheckAllowedTeams(NULL);
+       int teams = TeamBalance_GetAllowedTeams(balance);
+       TeamBalance_Destroy(balance);
        domination_teams = teams;
  
        domination_roundbased = autocvar_g_domination_roundbased;
index 4d9175574b80ec79db6207523fb5fb7cc75ec5a4,15726ada31d03b5442b12e15e175d34533af9768..6919518b8340ff1e65fa1f16d615eaed9eb7582b
@@@ -2,6 -2,7 +2,7 @@@
  
  // TODO: sv_freezetag
  #ifdef SVQC
  #include <server/resources.qh>
  
  float autocvar_g_freezetag_frozen_maxtime;
@@@ -13,27 -14,40 +14,40 @@@ float autocvar_g_freezetag_warmup
  
  void freezetag_count_alive_players()
  {
-       total_players = redalive = bluealive = yellowalive = pinkalive = 0;
-       FOREACH_CLIENT(IS_PLAYER(it), {
-               switch(it.team)
+       total_players = 0;
+       for (int i = 1; i <= NUM_TEAMS; ++i)
+       {
+               Team_SetNumberOfAlivePlayers(Team_GetTeamFromIndex(i), 0);
+       }
+       FOREACH_CLIENT(IS_PLAYER(it) && Entity_HasValidTeam(it),
+       {
+               ++total_players;
+               if ((GetResourceAmount(it, RESOURCE_HEALTH) < 1) ||
+                       (STAT(FROZEN, it) == 1))
                {
-                       case NUM_TEAM_1: ++total_players; if(STAT(FROZEN, it) != 1 && GetResourceAmount(it, RESOURCE_HEALTH) >= 1) ++redalive; break;
-                       case NUM_TEAM_2: ++total_players; if(STAT(FROZEN, it) != 1 && GetResourceAmount(it, RESOURCE_HEALTH) >= 1) ++bluealive; break;
-                       case NUM_TEAM_3: ++total_players; if(STAT(FROZEN, it) != 1 && GetResourceAmount(it, RESOURCE_HEALTH) >= 1) ++yellowalive; break;
-                       case NUM_TEAM_4: ++total_players; if(STAT(FROZEN, it) != 1 && GetResourceAmount(it, RESOURCE_HEALTH) >= 1) ++pinkalive; break;
+                       continue;
                }
+               entity team_ = Entity_GetTeam(it);
+               int num_alive = Team_GetNumberOfAlivePlayers(team_);
+               ++num_alive;
+               Team_SetNumberOfAlivePlayers(team_, num_alive);
        });
-       FOREACH_CLIENT(IS_REAL_CLIENT(it), {
-               STAT(REDALIVE, it) = redalive;
-               STAT(BLUEALIVE, it) = bluealive;
-               STAT(YELLOWALIVE, it) = yellowalive;
-               STAT(PINKALIVE, it) = pinkalive;
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               STAT(REDALIVE, it) = Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(
+                       1));
+               STAT(BLUEALIVE, it) = Team_GetNumberOfAlivePlayers(
+                       Team_GetTeamFromIndex(2));
+               STAT(YELLOWALIVE, it) = Team_GetNumberOfAlivePlayers(
+                       Team_GetTeamFromIndex(3));
+               STAT(PINKALIVE, it) = Team_GetNumberOfAlivePlayers(
+                       Team_GetTeamFromIndex(4));
        });
  
        eliminatedPlayers.SendFlags |= 1;
  }
- #define FREEZETAG_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
- #define FREEZETAG_ALIVE_TEAMS_OK() (FREEZETAG_ALIVE_TEAMS() == NumTeams(freezetag_teams))
+ #define FREEZETAG_ALIVE_TEAMS_OK() (Team_GetNumberOfAliveTeams() == NumTeams(freezetag_teams))
  
  float freezetag_CheckTeams()
  {
                return 0;
        }
        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;
+       for (int i = 1; i <= NUM_TEAMS; ++i)
+       {
+               if ((freezetag_teams & Team_IndexToBit(i)) &&
+                       (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) == 0))
+               {
+                       missing_teams_mask |= Team_IndexToBit(i);
+               }
+       }
        if(prev_missing_teams_mask != missing_teams_mask)
        {
                Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
        return 0;
  }
  
float freezetag_getWinnerTeam()
int freezetag_getWinnerTeam()
  {
-       float winner_team = 0;
-       if(redalive >= 1)
-               winner_team = NUM_TEAM_1;
-       if(bluealive >= 1)
+       int winner_team = 0;
+       if (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(1)) >= 1)
        {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_2;
+               winner_team = NUM_TEAM_1;
        }
-       if(yellowalive >= 1)
+       for (int i = 2; i <= NUM_TEAMS; ++i)
        {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_3;
+               if (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) >= 1)
+               {
+                       if (winner_team != 0)
+                       {
+                               return 0;
+                       }
+                       winner_team = Team_IndexToTeam(i);
+               }
        }
-       if(pinkalive >= 1)
+       if (winner_team)
        {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_4;
-       }
-       if(winner_team)
                return winner_team;
+       }
        return -1; // no player left
  }
  
@@@ -112,8 -126,10 +126,10 @@@ float freezetag_CheckWinner(
                return 1;
        }
  
-       if(FREEZETAG_ALIVE_TEAMS() > 1)
+       if (Team_GetNumberOfAliveTeams() > 1)
+       {
                return 0;
+       }
  
        int winner_team = freezetag_getWinnerTeam();
        if(winner_team > 0)
@@@ -213,10 -229,9 +229,10 @@@ float freezetag_isEliminated(entity e
  void(entity this) havocbot_role_ft_freeing;
  void(entity this) havocbot_role_ft_offense;
  
 -void havocbot_goalrating_freeplayers(entity this, float ratingscale, vector org, float sradius)
 +void havocbot_goalrating_ft_freeplayers(entity this, float ratingscale, vector org, float sradius)
  {
 -      float t;
 +      entity best_pl = NULL;
 +      float best_dist2 = FLOAT_MAX;
        FOREACH_CLIENT(IS_PLAYER(it) && it != this && SAME_TEAM(it, this), {
                if (STAT(FROZEN, it) == 1)
                {
                                continue;
                        navigation_routerating(this, it, ratingscale, 2000);
                }
 -              else if(vdist(it.origin - org, >, 400)) // avoid gathering all teammates in one place
 +              else if (best_dist2
 +                      && GetResourceAmount(it, RESOURCE_HEALTH) < GetResourceAmount(this, RESOURCE_HEALTH) + 30
 +                      && vlen2(it.origin - org) < best_dist2)
                {
                        // If teamate is not frozen still seek them out as fight better
                        // in a group.
 -                      t = 0.2 * 150 / (GetResourceAmount(this, RESOURCE_HEALTH) + GetResourceAmount(this, RESOURCE_ARMOR));
 -                      navigation_routerating(this, it, t * ratingscale, 2000);
 +                      best_dist2 = vlen2(it.origin - org);
 +                      if (best_dist2 < 700 ** 2)
 +                      {
 +                              best_pl = NULL;
 +                              best_dist2 = 0; // already close to a teammate
 +                      }
 +                      else
 +                              best_pl = it;
                }
        });
 +      if (best_pl)
 +              navigation_routerating(this, best_pl, ratingscale / 2, 2000);
  }
  
  void havocbot_role_ft_offense(entity this)
  
        // Count how many players on team are unfrozen.
        int unfrozen = 0;
 -      FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this) && !(STAT(FROZEN, it) != 1), { unfrozen++; });
 +      FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this) && !STAT(FROZEN, it), { unfrozen++; });
  
        // If only one left on team or if role has timed out then start trying to free players.
 -      if (((unfrozen == 0) && (!STAT(FROZEN, this))) || (time > this.havocbot_role_timeout))
 +      if ((unfrozen == 0 && !STAT(FROZEN, this)) || time > this.havocbot_role_timeout)
        {
                LOG_TRACE("changing role to freeing");
                this.havocbot_role = havocbot_role_ft_freeing;
        if (navigation_goalrating_timeout(this))
        {
                navigation_goalrating_start(this);
 -              havocbot_goalrating_items(this, 10000, this.origin, 10000);
 -              havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
 -              havocbot_goalrating_freeplayers(this, 9000, this.origin, 10000);
 +              havocbot_goalrating_items(this, 12000, this.origin, 10000);
 +              havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
 +              havocbot_goalrating_ft_freeplayers(this, 9000, this.origin, 10000);
                havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
                navigation_goalrating_end(this);
  
@@@ -297,9 -302,9 +313,9 @@@ void havocbot_role_ft_freeing(entity th
        if (navigation_goalrating_timeout(this))
        {
                navigation_goalrating_start(this);
 -              havocbot_goalrating_items(this, 8000, this.origin, 10000);
 -              havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
 -              havocbot_goalrating_freeplayers(this, 20000, this.origin, 10000);
 +              havocbot_goalrating_items(this, 10000, this.origin, 10000);
 +              havocbot_goalrating_enemyplayers(this, 5000, this.origin, 10000);
 +              havocbot_goalrating_ft_freeplayers(this, 20000, this.origin, 10000);
                havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
                navigation_goalrating_end(this);
  
@@@ -552,16 -557,13 +568,17 @@@ MUTATOR_HOOKFUNCTION(ft, HavocBot_Choos
                        bot.havocbot_role = havocbot_role_ft_offense;
        }
  
 +      // if bots spawn all at once assign them a more appropriated role after a while
 +      if (time < CS(bot).jointime + 1)
 +              bot.havocbot_role_timeout = time + 10 + random() * 10;
 +
        return true;
  }
  
- MUTATOR_HOOKFUNCTION(ft, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+ MUTATOR_HOOKFUNCTION(ft, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
  {
        M_ARGV(0, float) = freezetag_teams;
+       return true;
  }
  
  MUTATOR_HOOKFUNCTION(ft, SetWeaponArena)
index 00eb886d5336b274398bb0739a51f9c6d8d1a78c,5e0d3fcbbfaa0e475a8b6d0f3f87dc99ae18ac32..5af4c45b7ddc9b7752f5aee5eb8a0c194efe08e5
@@@ -299,7 -299,7 +299,7 @@@ void kh_Key_Detach(entity key) // runs 
        key.takedamage = DAMAGE_YES;
        // let key.team stay
        key.modelindex = kh_key_dropped;
 -      navigation_dynamicgoal_set(key);
 +      navigation_dynamicgoal_set(key, key.owner);
        key.kh_previous_owner = key.owner;
        key.kh_previous_owner_playerid = key.owner.playerid;
  }
@@@ -1077,9 -1077,9 +1077,9 @@@ void havocbot_role_kh_carrier(entity th
                navigation_goalrating_start(this);
  
                if(kh_Key_AllOwnedByWhichTeam() == this.team)
 -                      havocbot_goalrating_kh(this, 10, 0.1, 0.1); // bring home
 +                      havocbot_goalrating_kh(this, 10, 0.1, 0.05); // bring home
                else
 -                      havocbot_goalrating_kh(this, 4, 4, 1); // play defensively
 +                      havocbot_goalrating_kh(this, 4, 4, 0.5); // play defensively
  
                navigation_goalrating_end(this);
  
@@@ -1117,11 -1117,11 +1117,11 @@@ void havocbot_role_kh_defense(entity th
  
                key_owner_team = kh_Key_AllOwnedByWhichTeam();
                if(key_owner_team == this.team)
 -                      havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend key carriers
 +                      havocbot_goalrating_kh(this, 10, 0.1, 0.05); // defend key carriers
                else if(key_owner_team == -1)
 -                      havocbot_goalrating_kh(this, 4, 1, 0.1); // play defensively
 +                      havocbot_goalrating_kh(this, 4, 1, 0.05); // play defensively
                else
 -                      havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY
 +                      havocbot_goalrating_kh(this, 0.1, 0.1, 5); // ATTACK ANYWAY
  
                navigation_goalrating_end(this);
  
@@@ -1160,11 -1160,11 +1160,11 @@@ void havocbot_role_kh_offense(entity th
  
                key_owner_team = kh_Key_AllOwnedByWhichTeam();
                if(key_owner_team == this.team)
 -                      havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend anyway
 +                      havocbot_goalrating_kh(this, 10, 0.1, 0.05); // defend anyway
                else if(key_owner_team == -1)
 -                      havocbot_goalrating_kh(this, 0.1, 1, 4); // play offensively
 +                      havocbot_goalrating_kh(this, 0.1, 1, 2); // play offensively
                else
 -                      havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK! EMERGENCY!
 +                      havocbot_goalrating_kh(this, 0.1, 0.1, 5); // ATTACK! EMERGENCY!
  
                navigation_goalrating_end(this);
  
@@@ -1209,11 -1209,11 +1209,11 @@@ void havocbot_role_kh_freelancer(entit
  
                int key_owner_team = kh_Key_AllOwnedByWhichTeam();
                if(key_owner_team == this.team)
 -                      havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend anyway
 +                      havocbot_goalrating_kh(this, 10, 0.1, 0.05); // defend anyway
                else if(key_owner_team == -1)
 -                      havocbot_goalrating_kh(this, 1, 10, 4); // prefer dropped keys
 +                      havocbot_goalrating_kh(this, 1, 10, 2); // prefer dropped keys
                else
 -                      havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY
 +                      havocbot_goalrating_kh(this, 0.1, 0.1, 5); // ATTACK ANYWAY
  
                navigation_goalrating_end(this);
  
@@@ -1264,9 -1264,10 +1264,10 @@@ MUTATOR_HOOKFUNCTION(kh, MatchEnd
        kh_finalize();
  }
  
- MUTATOR_HOOKFUNCTION(kh, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+ MUTATOR_HOOKFUNCTION(kh, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
  {
        M_ARGV(0, float) = kh_teams;
+       return true;
  }
  
  MUTATOR_HOOKFUNCTION(kh, SpectateCopy)
index 9b175f179a7cc6ff0dc95de64cd6a4564c80fac0,c20a4fac74a160b62e9d876a35092fd6afc5a2b4..6d719eb977c512997131382e30a0698e0fcd14c0
@@@ -1113,46 -1113,52 +1113,52 @@@ int total_generators
  void Onslaught_count_generators()
  {
        entity e;
-       total_generators = redowned = blueowned = yellowowned = pinkowned = 0;
+       total_generators = 0;
+       for (int i = 1; i <= NUM_TEAMS; ++i)
+       {
+               Team_SetNumberOfControlPoints(Team_GetTeamFromIndex(i), 0);
+       }
        for(e = ons_worldgeneratorlist; e; e = e.ons_worldgeneratornext)
        {
                ++total_generators;
-               redowned += (e.team == NUM_TEAM_1 && GetResourceAmount(e, RESOURCE_HEALTH) > 0);
-               blueowned += (e.team == NUM_TEAM_2 && GetResourceAmount(e, RESOURCE_HEALTH) > 0);
-               yellowowned += (e.team == NUM_TEAM_3 && GetResourceAmount(e, RESOURCE_HEALTH) > 0);
-               pinkowned += (e.team == NUM_TEAM_4 && GetResourceAmount(e, RESOURCE_HEALTH) > 0);
+               if (GetResourceAmount(e, RESOURCE_HEALTH) < 1)
+               {
+                       continue;
+               }
+               entity team_ = Entity_GetTeam(e);
+               int num_control_points = Team_GetNumberOfControlPoints(team_);
+               ++num_control_points;
+               Team_SetNumberOfControlPoints(team_, num_control_points);
        }
  }
  
  int Onslaught_GetWinnerTeam()
  {
        int winner_team = 0;
-       if(redowned > 0)
-               winner_team = NUM_TEAM_1;
-       if(blueowned > 0)
+       if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(1)) >= 1)
        {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_2;
+               winner_team = NUM_TEAM_1;
        }
-       if(yellowowned > 0)
+       for (int i = 2; i <= NUM_TEAMS; ++i)
        {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_3;
+               if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(i)) >= 1)
+               {
+                       if (winner_team != 0)
+                       {
+                               return 0;
+                       }
+                       winner_team = Team_IndexToTeam(i);
+               }
        }
-       if(pinkowned > 0)
+       if (winner_team)
        {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_4;
-       }
-       if(winner_team)
                return winner_team;
+       }
        return -1; // no generators left?
  }
  
  void nades_Clear(entity e);
  
- #define ONS_OWNED_GENERATORS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
- #define ONS_OWNED_GENERATORS_OK() (ONS_OWNED_GENERATORS() > 1)
  bool Onslaught_CheckWinner()
  {
        if ((autocvar_timelimit && time > game_starttime + autocvar_timelimit * 60) || (round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0))
  
        Onslaught_count_generators();
  
-       if(ONS_OWNED_GENERATORS_OK())
+       if (Team_GetNumberOfTeamsWithControlPoints() > 1)
+       {
                return 0;
+       }
  
        int winner_team = Onslaught_GetWinnerTeam();
  
@@@ -1256,26 -1264,71 +1264,26 @@@ void Onslaught_RoundStart(
  
  // NOTE: LEGACY CODE, needs to be re-written!
  
 -void havocbot_goalrating_ons_offenseitems(entity this, float ratingscale, vector org, float sradius)
 -{
 -      bool needarmor = false, needweapons = false;
 -
 -      // Needs armor/health?
 -      if(GetResourceAmount(this, RESOURCE_HEALTH) < 100)
 -              needarmor = true;
 -
 -      // Needs weapons?
 -      int c = 0;
 -      FOREACH(Weapons, it != WEP_Null, {
 -              if(STAT(WEAPONS, this) & (it.m_wepset))
 -              if(++c >= 4)
 -                      break;
 -      });
 -
 -      if(c<4)
 -              needweapons = true;
 -
 -      if(!needweapons && !needarmor)
 -              return;
 -
 -      LOG_DEBUG(this.netname, " needs weapons ", ftos(needweapons));
 -      LOG_DEBUG(this.netname, " needs armor ", ftos(needarmor));
 -
 -      // See what is around
 -      IL_EACH(g_items, it.bot_pickup,
 -      {
 -              // gather health and armor only
 -              if (it.solid)
 -              if ( ((GetResourceAmount(it, RESOURCE_HEALTH) || GetResourceAmount(it, RESOURCE_ARMOR)) && needarmor) || (STAT(WEAPONS, it) && needweapons ) )
 -              if (vdist(it.origin - org, <, sradius))
 -              {
 -                      int t = it.bot_pickupevalfunc(this, it);
 -                      if (t > 0)
 -                              navigation_routerating(this, it, t * ratingscale, 500);
 -              }
 -      });
 -}
 -
  void havocbot_role_ons_setrole(entity this, int role)
  {
 -      LOG_DEBUG(this.netname," switched to ");
        switch(role)
        {
                case HAVOCBOT_ONS_ROLE_DEFENSE:
 -                      LOG_DEBUG("defense");
 +                      LOG_DEBUG(this.netname, " switched to defense");
                        this.havocbot_role = havocbot_role_ons_defense;
 -                      this.havocbot_role_flags = HAVOCBOT_ONS_ROLE_DEFENSE;
                        this.havocbot_role_timeout = 0;
                        break;
                case HAVOCBOT_ONS_ROLE_ASSISTANT:
 -                      LOG_DEBUG("assistant");
 +                      LOG_DEBUG(this.netname, " switched to assistant");
                        this.havocbot_role = havocbot_role_ons_assistant;
 -                      this.havocbot_role_flags = HAVOCBOT_ONS_ROLE_ASSISTANT;
                        this.havocbot_role_timeout = 0;
                        break;
                case HAVOCBOT_ONS_ROLE_OFFENSE:
 -                      LOG_DEBUG("offense");
 +                      LOG_DEBUG(this.netname, " switched to offense");
                        this.havocbot_role = havocbot_role_ons_offense;
 -                      this.havocbot_role_flags = HAVOCBOT_ONS_ROLE_OFFENSE;
                        this.havocbot_role_timeout = 0;
                        break;
        }
 -      LOG_DEBUG("");
  }
  
  void havocbot_goalrating_ons_controlpoints_attack(entity this, float ratingscale)
  
                // Count team mates interested in this control point
                // (easier and cleaner than keeping counters per cp and teams)
 -              FOREACH_CLIENT(IS_PLAYER(it), {
 +              FOREACH_CLIENT(it != this && IS_PLAYER(it), {
                        if(SAME_TEAM(it, this))
 -                      if(it.havocbot_role_flags & HAVOCBOT_ONS_ROLE_OFFENSE)
 +                      if(it.havocbot_role == havocbot_role_ons_offense)
                        if(it.havocbot_ons_target == cp2)
                                ++c;
                });
        }
  
        // We'll consider only the best case
 -      bestvalue = 99999999999;
 +      bestvalue = FLOAT_MAX;
        cp = NULL;
        for(cp1 = ons_worldcplist; cp1; cp1 = cp1.ons_worldcpnext)
        {
                // Rate waypoints near it
                found = false;
                best = NULL;
 -              bestvalue = 99999999999;
 -              for(radius=0; radius<1000 && !found; radius+=500)
 +              bestvalue = FLOAT_MAX;
 +              for (radius = 500; radius <= 1000 && !found; radius += 500)
                {
 -                      for(wp=findradius(cp.origin,radius); wp; wp=wp.chain)
 +                      IL_EACH(g_waypoints, vdist(cp.origin - it.origin, <, radius),
                        {
 -                              if(!(wp.wpflags & WAYPOINTFLAG_GENERATED))
 -                              if(wp.classname=="waypoint")
 -                              if(checkpvs(wp.origin,cp))
 +                              if (!(it.wpflags & WAYPOINTFLAG_GENERATED) && checkpvs(it.origin, cp))
                                {
                                        found = true;
 -                                      if(wp.cnt<bestvalue)
 +                                      if (it.cnt < bestvalue)
                                        {
 -                                              best = wp;
 -                                              bestvalue = wp.cnt;
 +                                              best = it;
 +                                              bestvalue = it.cnt;
                                        }
                                }
 -                      }
 +                      });
                }
  
                if(best)
        {
                // Should be touched
                LOG_DEBUG(this.netname, " found a touchable controlpoint at ", vtos(cp.origin));
 -              found = false;
 -
 -              // Look for auto generated waypoint
 -              if (!bot_waypoints_for_items)
 -              for (wp = findradius(cp.origin,100); wp; wp = wp.chain)
 -              {
 -                      if(wp.classname=="waypoint")
 -                      {
 -                              navigation_routerating(this, wp, ratingscale, 10000);
 -                              found = true;
 -                      }
 -              }
 -
 -              // Nothing found, rate the controlpoint itself
 -              if (!found)
 -                      navigation_routerating(this, cp, ratingscale, 10000);
 +              navigation_routerating(this, cp, ratingscale * 2, 10000);
        }
  }
  
@@@ -1384,7 -1454,7 +1392,7 @@@ bool havocbot_goalrating_ons_generator_
  {
        entity g, wp, bestwp;
        bool found;
 -      int best;
 +      int bestvalue;
  
        for(g = ons_worldgeneratorlist; g; g = g.ons_worldgeneratornext)
        {
                // Rate waypoints near it
                found = false;
                bestwp = NULL;
 -              best = 99999999999;
 +              bestvalue = FLOAT_MAX;
  
 -              for(wp=findradius(g.origin,400); wp; wp=wp.chain)
 +              IL_EACH(g_waypoints, vdist(g.origin - it.origin, <, 400),
                {
 -                      if(wp.classname=="waypoint")
 -                      if(checkpvs(wp.origin,g))
 +                      if (checkpvs(it.origin, g))
                        {
                                found = true;
 -                              if(wp.cnt<best)
 +                              if (it.cnt < bestvalue)
                                {
 -                                      bestwp = wp;
 -                                      best = wp.cnt;
 +                                      bestwp = it;
 +                                      bestvalue = it.cnt;
                                }
                        }
 -              }
 +              });
  
                if(bestwp)
                {
@@@ -1461,9 -1532,9 +1469,9 @@@ void havocbot_role_ons_offense(entity t
        {
                navigation_goalrating_start(this);
                havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650);
 -              if(!havocbot_goalrating_ons_generator_attack(this, 20000))
 -                      havocbot_goalrating_ons_controlpoints_attack(this, 20000);
 -              havocbot_goalrating_ons_offenseitems(this, 10000, this.origin, 10000);
 +              if(!havocbot_goalrating_ons_generator_attack(this, 10000))
 +                      havocbot_goalrating_ons_controlpoints_attack(this, 10000);
 +              havocbot_goalrating_items(this, 25000, this.origin, 10000);
                navigation_goalrating_end(this);
  
                navigation_goalrating_timeout_set(this);
@@@ -1897,17 -1968,14 +1905,14 @@@ MUTATOR_HOOKFUNCTION(ons, HavocBot_Choo
        return true;
  }
  
- MUTATOR_HOOKFUNCTION(ons, CheckAllowedTeams)
+ MUTATOR_HOOKFUNCTION(ons, TeamBalance_CheckAllowedTeams)
  {
        // onslaught is special
        for(entity tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext)
        {
-               switch(tmp_entity.team)
+               if (Team_IsValidTeam(tmp_entity.team))
                {
-                       case NUM_TEAM_1: c1 = 0; break;
-                       case NUM_TEAM_2: c2 = 0; break;
-                       case NUM_TEAM_3: c3 = 0; break;
-                       case NUM_TEAM_4: c4 = 0; break;
+                       M_ARGV(0, float) |= Team_TeamToBit(tmp_entity.team);
                }
        }
  
@@@ -2137,12 -2205,9 +2142,9 @@@ spawnfunc(onslaught_generator
  // scoreboard setup
  void ons_ScoreRules()
  {
-       CheckAllowedTeams(NULL);
-       int teams = 0;
-       if(c1 >= 0) teams |= BIT(0);
-       if(c2 >= 0) teams |= BIT(1);
-       if(c3 >= 0) teams |= BIT(2);
-       if(c4 >= 0) teams |= BIT(3);
+       entity balance = TeamBalance_CheckAllowedTeams(NULL);
+       int teams = TeamBalance_GetAllowedTeams(balance);
+       TeamBalance_Destroy(balance);
        GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
            field_team(ST_ONS_CAPS, "destroyed", SFL_SORT_PRIO_PRIMARY);
            field(SP_ONS_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
index b3a143b9a7b02a6142dd7019694df6521957e153,2b427f4fbc81580d91cd4037ec960a002db40d68..f03bdfc74db470c44ba0927aef9383dd079d7bf1
@@@ -123,9 -123,6 +123,9 @@@ void bot_think(entity this
        // if dead, just wait until we can respawn
        if (IS_DEAD(this))
        {
 +              if (bot_waypoint_queue_owner == this)
 +                      bot_waypoint_queue_owner = NULL;
 +              this.aistatus = 0;
                CS(this).movement = '0 0 0';
                if (this.deadflag == DEAD_DEAD)
                {
@@@ -434,15 -431,15 +434,15 @@@ void bot_clientconnect(entity this
        else if(this.bot_forced_team==4)
                this.team = NUM_TEAM_4;
        else
-               JoinBestTeam(this, true);
+               TeamBalance_JoinBestTeam(this, true);
  
        havocbot_setupbot(this);
  }
  
  void bot_removefromlargestteam()
  {
-       CheckAllowedTeams(NULL);
-       GetTeamCounts(NULL);
+       entity balance = TeamBalance_CheckAllowedTeams(NULL);
+       TeamBalance_GetTeamCounts(balance, NULL);
  
        entity best = NULL;
        float besttime = 0;
  
                int thiscount = 0;
  
-               switch(it.team)
+               if (Team_IsValidTeam(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;
+                       thiscount = TeamBalance_GetNumberOfPlayers(balance,
+                               Team_TeamToIndex(it.team));
                }
  
                if(thiscount > bestcount)
                        best = it;
                }
        });
+       TeamBalance_Destroy(balance);
        if(!bcount)
                return; // no bots to remove
        currentbots = currentbots - 1;