]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into Lyberta/TeamplayOverhaul
authorLyberta <lyberta@lyberta.net>
Sun, 17 Jun 2018 00:34:27 +0000 (03:34 +0300)
committerLyberta <lyberta@lyberta.net>
Sun, 17 Jun 2018 00:34:27 +0000 (03:34 +0300)
13 files changed:
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/invasion/invasion.qc
qcsrc/common/gamemodes/gamemode/nexball/nexball.qc
qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qc
qcsrc/common/t_items.qc
qcsrc/server/bot/default/bot.qc
qcsrc/server/client.qc
qcsrc/server/g_world.qc
qcsrc/server/mutators/events.qh
qcsrc/server/player.qc

index 1dc5c3f9cdfc94d9a97629175e174f86c0a1d06e,97a7b4df28d0a77c03c3e0c04d489d7c244d5543..e07d4da52df22fa4a8698b3d7c7509292a6b0087
@@@ -19,7 -19,7 +19,7 @@@ STATIC_INIT(g_assault
  void assault_objective_use(entity this, entity actor, entity trigger)
  {
        // activate objective
-       this.health = 100;
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 100);
        //print("^2Activated objective ", this.targetname, "=", etos(this), "\n");
        //print("Activator is ", actor.classname, "\n");
  
@@@ -31,7 -31,7 +31,7 @@@
  
  vector target_objective_spawn_evalfunc(entity this, entity player, entity spot, vector current)
  {
-       if(this.health < 0 || this.health >= ASSAULT_VALUE_INACTIVE)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) < 0 || GetResourceAmount(this, RESOURCE_HEALTH) >= ASSAULT_VALUE_INACTIVE)
                return '-1 0 0';
        return current;
  }
@@@ -40,7 -40,7 +40,7 @@@
  // and when a new round starts
  void assault_objective_reset(entity this)
  {
-       this.health = ASSAULT_VALUE_INACTIVE;
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, ASSAULT_VALUE_INACTIVE);
  }
  
  // decrease the health of targeted objectives
@@@ -61,18 -61,18 +61,18 @@@ void assault_objective_decrease_use(ent
        else
                return; // already activated! cannot activate again!
  
-       if(this.enemy.health < ASSAULT_VALUE_INACTIVE)
+       if(GetResourceAmount(this.enemy, RESOURCE_HEALTH) < ASSAULT_VALUE_INACTIVE)
        {
-               if(this.enemy.health - this.dmg > 0.5)
+               if(GetResourceAmount(this.enemy, RESOURCE_HEALTH) - this.dmg > 0.5)
                {
                        GameRules_scoring_add_team(actor, SCORE, this.dmg);
-                       this.enemy.health = this.enemy.health - this.dmg;
+                       TakeResource(this.enemy, RESOURCE_HEALTH, this.dmg);
                }
                else
                {
-                       GameRules_scoring_add_team(actor, SCORE, this.enemy.health);
+                       GameRules_scoring_add_team(actor, SCORE, GetResourceAmount(this.enemy, RESOURCE_HEALTH));
                        GameRules_scoring_add_team(actor, ASSAULT_OBJECTIVES, 1);
-                       this.enemy.health = -1;
+                       SetResourceAmountExplicit(this.enemy, RESOURCE_HEALTH, -1);
  
                        if(this.enemy.message)
                                FOREACH_CLIENT(IS_PLAYER(it), { centerprint(it, this.enemy.message); });
@@@ -99,7 -99,7 +99,7 @@@ void assault_setenemytoobjective(entit
  
  bool assault_decreaser_sprite_visible(entity this, entity player, entity view)
  {
-       if(this.assault_decreaser.enemy.health >= ASSAULT_VALUE_INACTIVE)
+       if(GetResourceAmount(this.assault_decreaser.enemy, RESOURCE_HEALTH) >= ASSAULT_VALUE_INACTIVE)
                return false;
  
        return true;
@@@ -127,7 -127,7 +127,7 @@@ void target_objective_decrease_activate
                {
                        WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultDestroy, WP_AssaultDestroy);
                        WaypointSprite_UpdateMaxHealth(spr, it.max_health);
-                       WaypointSprite_UpdateHealth(spr, it.health);
+                       WaypointSprite_UpdateHealth(spr, GetResourceAmount(it, RESOURCE_HEALTH));
                        it.sprite = spr;
                }
                else
@@@ -176,7 -176,7 +176,7 @@@ void assault_roundstart_use_this(entit
  
  void assault_wall_think(entity this)
  {
-       if(this.enemy.health < 0)
+       if(GetResourceAmount(this.enemy, RESOURCE_HEALTH) < 0)
        {
                this.model = "";
                this.solid = SOLID_NOT;
@@@ -323,7 -323,7 +323,7 @@@ spawnfunc(target_objective_decrease
                this.dmg = 101;
  
        this.use = assault_objective_decrease_use;
-       this.health = ASSAULT_VALUE_INACTIVE;
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, ASSAULT_VALUE_INACTIVE);
        this.max_health = ASSAULT_VALUE_INACTIVE;
        this.enemy = NULL;
  
@@@ -395,7 -395,7 +395,7 @@@ void havocbot_goalrating_ast_targets(en
                entity destr = it;
                IL_EACH(g_assault_objectivedecreasers, it.targetname == destr.target,
                {
-                       if(it.enemy.health > 0 && it.enemy.health < ASSAULT_VALUE_INACTIVE)
+                       if(GetResourceAmount(it.enemy, RESOURCE_HEALTH) > 0 && GetResourceAmount(it.enemy, RESOURCE_HEALTH) < ASSAULT_VALUE_INACTIVE)
                        {
                                found = true;
                                break;
@@@ -586,10 -586,10 +586,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 322f8979d9fadba5c583c0adfb3fb2e587fd9a4d,3f96b41142f162360dac01cf770a52b24391653f..7546973a451e409428f0bfd99acdc7f04a1fe544
@@@ -147,7 -147,7 +147,7 @@@ void ctf_FlagcarrierWaypoints(entity pl
  {
        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_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(GetResourceAmount(player, RESOURCE_HEALTH), GetResourceAmount(player, RESOURCE_ARMOR), 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))
@@@ -343,7 -343,7 +343,7 @@@ void ctf_Handle_Drop(entity flag, entit
        set_movetype(flag, MOVETYPE_TOSS);
        flag.takedamage = DAMAGE_YES;
        flag.angles = '0 0 0';
-       flag.health = flag.max_flag_health;
+       SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health);
        flag.ctf_droptime = time;
        flag.ctf_dropper = player;
        flag.ctf_status = FLAG_DROPPED;
        if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
        {
                WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
-               WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
+               WaypointSprite_UpdateHealth(flag.wps_flagdropped, GetResourceAmount(flag, RESOURCE_HEALTH));
        }
  
        player.throw_antispam = time + autocvar_g_ctf_pass_wait;
@@@ -680,7 -680,7 +680,7 @@@ void ctf_Handle_Pickup(entity flag, ent
        switch(pickuptype)
        {
                case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
-               case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
+               case PICKUP_DROPPED: SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health); break; // reset health/return timelimit
                default: break;
        }
  
@@@ -762,9 -762,9 +762,9 @@@ void ctf_CheckFlagReturn(entity flag, i
  {
        if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
        {
-               if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
+               if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, GetResourceAmount(flag, RESOURCE_HEALTH)); }
  
-               if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
+               if((GetResourceAmount(flag, RESOURCE_HEALTH) <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
                {
                        switch(returntype)
                        {
@@@ -875,7 -875,7 +875,7 @@@ void ctf_FlagDamage(entity this, entit
                        this.ctf_flagdamaged_byworld = true;
                else
                {
-                       this.health = 0;
+                       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
                        ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
                }
                return;
        if(autocvar_g_ctf_flag_return_damage)
        {
                // reduce health and check if it should be returned
-               this.health = this.health - damage;
+               TakeResource(this, RESOURCE_HEALTH, damage);
                ctf_CheckFlagReturn(this, RETURN_DAMAGE);
                return;
        }
@@@ -946,20 -946,20 +946,20 @@@ void ctf_FlagThink(entity this
                        {
                                if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
                                {
-                                       this.health = 0;
+                                       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
                                        ctf_CheckFlagReturn(this, RETURN_DROPPED);
                                        return;
                                }
                        }
                        if(this.ctf_flagdamaged_byworld)
                        {
-                               this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
+                               TakeResource(this, RESOURCE_HEALTH, ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE));
                                ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
                                return;
                        }
                        else if(autocvar_g_ctf_flag_return_time)
                        {
-                               this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
+                               TakeResource(this, RESOURCE_HEALTH, ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE));
                                ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
                                return;
                        }
                {
                        if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
                        {
-                               this.health = 0;
+                               SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
                                ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
  
                                CS(this.owner).impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
@@@ -1039,7 -1039,7 +1039,7 @@@ METHOD(Flag, giveTo, bool(Flag this, en
        {
                if(!autocvar_g_ctf_flag_return_damage_delay)
                {
-                       flag.health = 0;
+                       SetResourceAmountExplicit(flag, RESOURCE_HEALTH, 0);
                        ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
                }
                if(!flag.ctf_flagdamaged_byworld) { return; }
@@@ -1163,7 -1163,7 +1163,7 @@@ void ctf_RespawnFlag(entity flag
  
        set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
        flag.takedamage = DAMAGE_NO;
-       flag.health = flag.max_flag_health;
+       SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health);
        flag.solid = SOLID_TRIGGER;
        flag.velocity = '0 0 0';
        flag.angles = flag.mangle;
@@@ -1250,7 -1250,7 +1250,7 @@@ void ctf_FlagSetup(int teamnumber, enti
        flag.takedamage = DAMAGE_NO;
        flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
        flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
-       flag.health = flag.max_flag_health;
+       SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health);
        flag.event_damage = ctf_FlagDamage;
        flag.pushable = true;
        flag.teleportable = TELEPORT_NORMAL;
@@@ -1578,7 -1578,7 +1578,7 @@@ void havocbot_goalrating_ctf_carrierite
        {
                // gather health and armor only
                if (it.solid)
-               if (it.health || it.armorvalue)
+               if (GetResourceAmount(it, RESOURCE_HEALTH) || GetResourceAmount(it, RESOURCE_ARMOR))
                if (vdist(it.origin - org, <, sradius))
                {
                        // get the value of the item
@@@ -1673,7 -1673,7 +1673,7 @@@ void havocbot_role_ctf_carrier(entity t
                else
                        havocbot_goalrating_ctf_ourbase(this, 50000);
  
-               if(this.health<100)
+               if(GetResourceAmount(this, RESOURCE_HEALTH) < 100)
                        havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
  
                navigation_goalrating_end(this);
@@@ -1819,7 -1819,7 +1819,7 @@@ void havocbot_role_ctf_offense(entity t
        }
  
        // About to fail, switch to middlefield
-       if(this.health<50)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) < 50)
        {
                havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
                return;
@@@ -2131,7 -2131,7 +2131,7 @@@ MUTATOR_HOOKFUNCTION(ctf, PlayerPreThin
  
        // 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));
+               WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(GetResourceAmount(player, RESOURCE_HEALTH), GetResourceAmount(player, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
  }
  
  MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
        }
        else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
        {
-               if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id)))
+               if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(GetResourceAmount(frag_target, RESOURCE_HEALTH), GetResourceAmount(frag_target, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id)))
                if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
                {
                        frag_target.wps_helpme_time = time;
@@@ -2466,9 -2466,11 +2466,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)
@@@ -2694,7 -2696,7 +2694,7 @@@ spawnfunc(team_CTL_bluelolly)  { spawnf
  // scoreboard setup
  void ctf_ScoreRules(int teams)
  {
 -      CheckAllowedTeams(NULL);
 +      //CheckAllowedTeams(NULL); // Bug? Need to get allowed teams?
        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 7a1b9809ab22e660e290c7886291a86e2acf7b98,24a82208cb30c80fc036b5960559906562e59c69..08989624d42de25b1d85de458115e256084241fb
@@@ -188,9 -188,9 +188,9 @@@ void dompointthink(entity this
  
  void dompointtouch(entity this, entity toucher)
  {
-       if (!IS_PLAYER(toucher))
+       if(!IS_PLAYER(toucher))
                return;
-       if (toucher.health < 1)
+       if(GetResourceAmount(toucher, RESOURCE_HEALTH) < 1)
                return;
  
        if(round_handler_IsActive() && !round_handler_IsRoundStarted())
@@@ -297,52 -297,47 +297,52 @@@ 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;
 +              entity team_ = Entity_GetTeam(it.goalentity);
 +              //TODO: team_ seems to be NULL
 +              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)
@@@ -422,7 -417,7 +422,7 @@@ void havocbot_role_dom(entity 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);
                        }
                }
  
@@@ -646,9 -644,14 +646,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 e86f6b72f5cdaad4adde20fb59914be4e6a759e4,1cdd4d1f84da90444aba85eb3521f195141b4f2f..f2ce080ebbe9c99e60e3167698ba9f37d1dad582
@@@ -2,7 -2,6 +2,7 @@@
  
  // TODO: sv_freezetag
  #ifdef SVQC
 +
  #include <server/resources.qh>
  
  float autocvar_g_freezetag_frozen_maxtime;
@@@ -14,40 -13,27 +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(GetResourceAmount(it, RESOURCE_HEALTH) >= 1 && STAT(FROZEN, it) != 1) ++redalive; break;
 -                      case NUM_TEAM_2: ++total_players; if(GetResourceAmount(it, RESOURCE_HEALTH) >= 1 && STAT(FROZEN, it) != 1) ++bluealive; break;
 -                      case NUM_TEAM_3: ++total_players; if(GetResourceAmount(it, RESOURCE_HEALTH) >= 1 && STAT(FROZEN, it) != 1) ++yellowalive; break;
 -                      case NUM_TEAM_4: ++total_players; if(GetResourceAmount(it, RESOURCE_HEALTH) >= 1 && STAT(FROZEN, it) != 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
  }
  
@@@ -126,10 -112,8 +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)
@@@ -158,7 -142,7 +158,7 @@@ entity freezetag_LastPlayerForTeam(enti
  {
        entity last_pl = NULL;
        FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
-               if(it.health >= 1)
+               if(GetResourceAmount(it, RESOURCE_HEALTH) >= 1)
                if(!STAT(FROZEN, it))
                if(SAME_TEAM(it, this))
                if(!last_pl)
@@@ -250,7 -234,7 +250,7 @@@ void havocbot_goalrating_freeplayers(en
                {
                        // If teamate is not frozen still seek them out as fight better
                        // in a group.
-                       t = 0.2 * 150 / (this.health + this.armorvalue);
+                       t = 0.2 * 150 / (GetResourceAmount(this, RESOURCE_HEALTH) + GetResourceAmount(this, RESOURCE_ARMOR));
                        navigation_routerating(this, it, t * ratingscale, 2000);
                }
        });
@@@ -326,7 -310,7 +326,7 @@@ void havocbot_role_ft_freeing(entity th
  
  void ft_RemovePlayer(entity this)
  {
-       this.health = 0; // neccessary to update correctly alive stats
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0); // neccessary to update correctly alive stats
        if(!STAT(FROZEN, this))
                freezetag_LastPlayerForTeam_Notify(this);
        freezetag_Unfreeze(this);
@@@ -377,7 -361,7 +377,7 @@@ MUTATOR_HOOKFUNCTION(ft, PlayerDies
                }
                else
                        freezetag_Unfreeze(frag_target); // remove ice
-               frag_target.health = 0; // Unfreeze resets health
+               SetResourceAmountExplicit(frag_target, RESOURCE_HEALTH, 0); // Unfreeze resets health
                frag_target.freezetag_frozen_timeout = -2; // freeze on respawn
                return true;
        }
@@@ -486,7 -470,7 +486,7 @@@ MUTATOR_HOOKFUNCTION(ft, PlayerPreThink
        if(n && STAT(FROZEN, player) == 1) // OK, there is at least one teammate reviving us
        {
                STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
-               player.health = max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health));
+               SetResourceAmountExplicit(player, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health)));
  
                if(STAT(REVIVE_PROGRESS, player) >= 1)
                {
        else if(!n && STAT(FROZEN, player) == 1) // only if no teammate is nearby will we reset
        {
                STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) - frametime * autocvar_g_freezetag_revive_clearspeed, 1);
-               player.health = max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health));
+               SetResourceAmountExplicit(player, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health)));
        }
        else if(!n && !STAT(FROZEN, player))
        {
@@@ -558,10 -542,9 +558,10 @@@ MUTATOR_HOOKFUNCTION(ft, HavocBot_Choos
        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)
@@@ -583,7 -566,8 +583,8 @@@ MUTATOR_HOOKFUNCTION(ft, FragCenterMess
                return; // target was already frozen, so this is just pushing them off the cliff
  
        Send_Notification(NOTIF_ONE, frag_attacker, MSG_CHOICE, CHOICE_FRAG_FREEZE, frag_target.netname, kill_count_to_attacker, (IS_BOT_CLIENT(frag_target) ? -1 : CS(frag_target).ping));
-       Send_Notification(NOTIF_ONE, frag_target, MSG_CHOICE, CHOICE_FRAGGED_FREEZE, frag_attacker.netname, kill_count_to_target, frag_attacker.health, frag_attacker.armorvalue, (IS_BOT_CLIENT(frag_attacker) ? -1 : CS(frag_attacker).ping));
+       Send_Notification(NOTIF_ONE, frag_target, MSG_CHOICE, CHOICE_FRAGGED_FREEZE, frag_attacker.netname, kill_count_to_target, 
+                                                                               GetResourceAmount(frag_attacker, RESOURCE_HEALTH), GetResourceAmount(frag_attacker, RESOURCE_ARMOR), (IS_BOT_CLIENT(frag_attacker) ? -1 : CS(frag_attacker).ping));
  
        return true;
  }
index 9fb6e49699003f1a6b82a38f852ddd0eed6abca1,3dff701959ef82c11f8f7e205daa3abfeb6af14b..15bc077bcc13979d036cbb90171532233379979d
@@@ -290,7 -290,7 +290,7 @@@ bool Invasion_CheckWinner(
  
        float total_alive_monsters = 0, supermonster_count = 0, red_alive = 0, blue_alive = 0, yellow_alive = 0, pink_alive = 0;
  
-       IL_EACH(g_monsters, it.health > 0,
+       IL_EACH(g_monsters, GetResourceAmount(it, RESOURCE_HEALTH) > 0,
        {
                if((get_monsterinfo(it.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
                        ++supermonster_count;
@@@ -548,10 -548,9 +548,10 @@@ MUTATOR_HOOKFUNCTION(inv, CheckRules_Wo
        return true;
  }
  
 -MUTATOR_HOOKFUNCTION(inv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
 +MUTATOR_HOOKFUNCTION(inv, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
  {
        M_ARGV(0, float) = invasion_teams;
 +      return true;
  }
  
  MUTATOR_HOOKFUNCTION(inv, AllowMobButcher)
  
  void invasion_ScoreRules(int inv_teams)
  {
 -      if(inv_teams) { CheckAllowedTeams(NULL); }
 +      //if(inv_teams) { CheckAllowedTeams(NULL); } // Another bug?
        GameRules_score_enabled(false);
        GameRules_scoring(inv_teams, 0, 0, {
            if (inv_teams) {
index 82e5ed8cf16f683a219912910d873f86426a57f2,487012aa50902e7a834fdd708a7c0e45bb2928cc..89c53a82abc22eb1b7b4b8fc1a8784e8f5b6ea23
@@@ -308,7 -308,7 +308,7 @@@ void football_touch(entity this, entit
        }
        if (!IS_PLAYER(toucher))
                return;
-       if(toucher.health < 1)
+       if(GetResourceAmount(toucher, RESOURCE_HEALTH) < 1)
                return;
        if(!this.cnt)
                this.nextthink = time + autocvar_g_nexball_delay_idle;
@@@ -348,7 -348,7 +348,7 @@@ void basketball_touch(entity this, enti
        }
        if(!this.cnt && IS_PLAYER(toucher) && !STAT(FROZEN, toucher) && !IS_DEAD(toucher) && (toucher != this.nb_dropper || time > this.nb_droptime + autocvar_g_nexball_delay_collect))
        {
-               if(toucher.health <= 0)
+               if(GetResourceAmount(toucher, RESOURCE_HEALTH) < 1)
                        return;
                LogNB("caught", toucher);
                GiveBall(toucher, this);
@@@ -635,7 -635,7 +635,7 @@@ void SpawnGoal(entity this
  
        EXACTTRIGGER_INIT;
  
 -      if(this.team != GOAL_OUT && Team_TeamToNumber(this.team) != -1)
 +      if(this.team != GOAL_OUT && Team_IsValidTeam(this.team))
        {
                entity wp = WaypointSprite_SpawnFixed(WP_NbGoal, (this.absmin + this.absmax) * 0.5, this, sprite, RADARICON_NONE);
                wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 0.5 0');
@@@ -920,7 -920,7 +920,7 @@@ MUTATOR_HOOKFUNCTION(nb, ItemTouch
        return MUT_ITEMTOUCH_CONTINUE;
  }
  
 -MUTATOR_HOOKFUNCTION(nb, CheckAllowedTeams)
 +MUTATOR_HOOKFUNCTION(nb, TeamBalance_CheckAllowedTeams)
  {
        M_ARGV(1, string) = "nexball_team";
        return true;
index 4815ae8d28376624f37c4accf197e2283a2ae894,15aedc732d15c95283baf0c1f58cad87efc20022..3ae794ea08d72429f7cd32746cd56f11c5d939f4
@@@ -400,11 -400,11 +400,11 @@@ void ons_ControlPoint_Icon_Damage(entit
                ons_notification_time[this.team] = time;
        }
  
-       this.health = this.health - damage;
+       TakeResource(this, RESOURCE_HEALTH, damage);
        if(this.owner.iscaptured)
-               WaypointSprite_UpdateHealth(this.owner.sprite, this.health);
+               WaypointSprite_UpdateHealth(this.owner.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
        else
-               WaypointSprite_UpdateBuildFinished(this.owner.sprite, time + (this.max_health - this.health) / (this.count / ONS_CP_THINKRATE));
+               WaypointSprite_UpdateBuildFinished(this.owner.sprite, time + (this.max_health - GetResourceAmount(this, RESOURCE_HEALTH)) / (this.count / ONS_CP_THINKRATE));
        this.pain_finished = time + 1;
        // particles on every hit
        pointparticles(EFFECT_SPARKS, hitloc, force*-1, 1);
        else
                sound(this, CH_TRIGGER, SND_ONS_HIT2, VOL_BASE+0.3, ATTEN_NORM);
  
-       if (this.health < 0)
+       if (GetResourceAmount(this, RESOURCE_HEALTH) < 0)
        {
                sound(this, CH_TRIGGER, SND_GRENADE_IMPACT, VOL_BASE, ATTEN_NORM);
                pointparticles(EFFECT_ROCKET_EXPLODE, this.origin, '0 0 0', 1);
@@@ -469,9 -469,9 +469,9 @@@ void ons_ControlPoint_Icon_Think(entit
                _friendly_count = _friendly_count * (autocvar_g_onslaught_cp_proxydecap_dps * ONS_CP_THINKRATE);
                _enemy_count = _enemy_count * (autocvar_g_onslaught_cp_proxydecap_dps * ONS_CP_THINKRATE);
  
-               this.health = bound(0, this.health + (_friendly_count - _enemy_count), this.max_health);
+               GiveResourceWithLimit(this, RESOURCE_HEALTH, (_friendly_count - _enemy_count), this.max_health);
                this.SendFlags |= CPSF_STATUS;
-               if(this.health <= 0)
+               if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                {
                        ons_ControlPoint_Icon_Damage(this, this, this, 1, 0, DMG_NOWEP, this.origin, '0 0 0');
                        return;
  
        if (time > this.pain_finished + 5)
        {
-               if(this.health < this.max_health)
+               if(GetResourceAmount(this, RESOURCE_HEALTH) < this.max_health)
                {
-                       this.health = this.health + this.count;
-                       if (this.health >= this.max_health)
-                               this.health = this.max_health;
-                       WaypointSprite_UpdateHealth(this.owner.sprite, this.health);
+                       GiveResourceWithLimit(this, RESOURCE_HEALTH, this.count, this.max_health);
+                       WaypointSprite_UpdateHealth(this.owner.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
                }
        }
  
        }
  
        // damaged fx
-       if(random() < 0.6 - this.health / this.max_health)
+       if(random() < 0.6 - GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health)
        {
                Send_Effect(EFFECT_ELECTRIC_SPARKS, this.origin + randompos('-10 -10 -20', '10 10 20'), '0 0 0', 1);
  
@@@ -526,13 -524,13 +524,13 @@@ void ons_ControlPoint_Icon_BuildThink(e
        if(!a)
                return;
  
-       this.health = this.health + this.count;
+       GiveResource(this, RESOURCE_HEALTH, this.count);
  
        this.SendFlags |= CPSF_STATUS;
  
-       if (this.health >= this.max_health)
+       if (GetResourceAmount(this, RESOURCE_HEALTH) >= this.max_health)
        {
-               this.health = this.max_health;
+               SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
                this.count = autocvar_g_onslaught_cp_regen * ONS_CP_THINKRATE; // slow repair rate from now on
                setthink(this, ons_ControlPoint_Icon_Think);
                sound(this, CH_TRIGGER, SND_ONS_CONTROLPOINT_BUILT, VOL_BASE, ATTEN_NORM);
                Send_Effect(EFFECT_CAP(this.owner.team), this.owner.origin, '0 0 0', 1);
  
                WaypointSprite_UpdateMaxHealth(this.owner.sprite, this.max_health);
-               WaypointSprite_UpdateHealth(this.owner.sprite, this.health);
+               WaypointSprite_UpdateHealth(this.owner.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
  
                if(IS_PLAYER(this.owner.ons_toucher))
                {
        if(this.owner.model != MDL_ONS_CP_PAD2.model_str())
                setmodel_fixsize(this.owner, MDL_ONS_CP_PAD2);
  
-       if(random() < 0.9 - this.health / this.max_health)
+       if(random() < 0.9 - GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health)
                Send_Effect(EFFECT_RAGE, this.origin + 10 * randomvec(), '0 0 -1', 1);
  }
  
@@@ -580,7 -578,7 +578,7 @@@ void ons_ControlPoint_Icon_Spawn(entit
  
        e.owner = cp;
        e.max_health = autocvar_g_onslaught_cp_health;
-       e.health = autocvar_g_onslaught_cp_buildhealth;
+       SetResourceAmountExplicit(e, RESOURCE_HEALTH, autocvar_g_onslaught_cp_buildhealth);
        e.solid = SOLID_NOT;
        e.takedamage = DAMAGE_AIM;
        e.bot_attack = true;
        e.event_damage = ons_ControlPoint_Icon_Damage;
        e.team = player.team;
        e.colormap = 1024 + (e.team - 1) * 17;
-       e.count = (e.max_health - e.health) * ONS_CP_THINKRATE / autocvar_g_onslaught_cp_buildtime; // how long it takes to build
+       e.count = (e.max_health - GetResourceAmount(e, RESOURCE_HEALTH)) * ONS_CP_THINKRATE / autocvar_g_onslaught_cp_buildtime; // how long it takes to build
  
        sound(e, CH_TRIGGER, SND_ONS_CONTROLPOINT_BUILD, VOL_BASE, ATTEN_NORM);
  
  
        Send_Effect(EFFECT_FLAG_TOUCH(player.team), e.origin, '0 0 0', 1);
  
-       WaypointSprite_UpdateBuildFinished(cp.sprite, time + (e.max_health - e.health) / (e.count / ONS_CP_THINKRATE));
+       WaypointSprite_UpdateBuildFinished(cp.sprite, time + (e.max_health - GetResourceAmount(e, RESOURCE_HEALTH)) / (e.count / ONS_CP_THINKRATE));
        WaypointSprite_UpdateRule(cp.sprite,cp.team,SPRITERULE_TEAMPLAY);
        cp.sprite.SendFlags |= 16;
  
@@@ -640,7 -638,7 +638,7 @@@ void ons_ControlPoint_UpdateSprite(enti
                        else
                        {
                                WaypointSprite_UpdateMaxHealth(e.sprite, e.goalentity.max_health);
-                               WaypointSprite_UpdateHealth(e.sprite, e.goalentity.health);
+                               WaypointSprite_UpdateHealth(e.sprite, GetResourceAmount(e.goalentity, RESOURCE_HEALTH));
                        }
                }
                if(e.lastshielded)
@@@ -889,14 -887,14 +887,14 @@@ void ons_GeneratorDamage(entity this, e
                        play2team(this.team, SND(ONS_GENERATOR_UNDERATTACK));
                }
        }
-       this.health = this.health - damage;
-       WaypointSprite_UpdateHealth(this.sprite, this.health);
+       TakeResource(this, RESOURCE_HEALTH, damage);
+       WaypointSprite_UpdateHealth(this.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
        // choose an animation frame based on health
-       this.frame = 10 * bound(0, (1 - this.health / this.max_health), 1);
+       this.frame = 10 * bound(0, (1 - GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health), 1);
        // see if the generator is still functional, or dying
-       if (this.health > 0)
+       if (GetResourceAmount(this, RESOURCE_HEALTH) > 0)
        {
-               this.lasthealth = this.health;
+               this.lasthealth = GetResourceAmount(this, RESOURCE_HEALTH);
        }
        else
        {
@@@ -970,7 -968,8 +968,8 @@@ void ons_GeneratorThink(entity this
  void ons_GeneratorReset(entity this)
  {
        this.team = this.team_saved;
-       this.lasthealth = this.max_health = this.health = autocvar_g_onslaught_gen_health;
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, autocvar_g_onslaught_gen_health);
+       this.lasthealth = this.max_health = autocvar_g_onslaught_gen_health;
        this.takedamage = DAMAGE_AIM;
        this.bot_attack = true;
        if(!IL_CONTAINS(g_bot_targets, this))
        this.SendFlags |= GSF_STATUS;
  
        WaypointSprite_UpdateMaxHealth(this.sprite, this.max_health);
-       WaypointSprite_UpdateHealth(this.sprite, this.health);
+       WaypointSprite_UpdateHealth(this.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
        WaypointSprite_UpdateRule(this.sprite,this.team,SPRITERULE_TEAMPLAY);
  
        onslaught_updatelinks();
@@@ -1035,7 -1034,8 +1034,8 @@@ void ons_GeneratorSetup(entity gen) // 
        gen.team_saved = teamnumber;
        IL_PUSH(g_saved_team, gen);
        set_movetype(gen, MOVETYPE_NONE);
-       gen.lasthealth = gen.max_health = gen.health = autocvar_g_onslaught_gen_health;
+       gen.lasthealth = gen.max_health = autocvar_g_onslaught_gen_health;
+       SetResourceAmountExplicit(gen, RESOURCE_HEALTH, autocvar_g_onslaught_gen_health);
        gen.takedamage = DAMAGE_AIM;
        gen.bot_attack = true;
        IL_PUSH(g_bot_targets, gen);
        WaypointSprite_SpawnFixed(WP_Null, gen.origin + CPGEN_WAYPOINT_OFFSET, gen, sprite, RADARICON_NONE);
        WaypointSprite_UpdateRule(gen.sprite, gen.team, SPRITERULE_TEAMPLAY);
        WaypointSprite_UpdateMaxHealth(gen.sprite, gen.max_health);
-       WaypointSprite_UpdateHealth(gen.sprite, gen.health);
+       WaypointSprite_UpdateHealth(gen.sprite, GetResourceAmount(gen, RESOURCE_HEALTH));
  
        InitializeEntity(gen, ons_DelayedGeneratorSetup, INITPRIO_SETLOCATION);
  }
@@@ -1075,52 -1075,46 +1075,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();
  
@@@ -1231,7 -1223,7 +1231,7 @@@ void havocbot_goalrating_ons_offenseite
        bool needarmor = false, needweapons = false;
  
        // Needs armor/health?
-       if(this.health<100)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) < 100)
                needarmor = true;
  
        // Needs weapons?
        {
                // gather health and armor only
                if (it.solid)
-               if ( ((it.health || it.armorvalue) && needarmor) || (STAT(WEAPONS, it) && needweapons ) )
+               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);
@@@ -1930,14 -1922,17 +1930,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);
                }
        }
  
@@@ -1977,7 -1972,7 +1977,7 @@@ MUTATOR_HOOKFUNCTION(ons, SV_ParseClien
                        {
                                entity source_point = ons_Nearest_ControlPoint(player, player.origin, autocvar_g_onslaught_teleport_radius);
  
-                               if ( !source_point && player.health > 0 )
+                               if ( !source_point && GetResourceAmount(player, RESOURCE_HEALTH) > 0 )
                                {
                                        sprint(player, "\nYou need to be next to a control point\n");
                                        return true;
                                        return true;
                                }
  
-                               if ( player.health <= 0 )
+                               if ( GetResourceAmount(player, RESOURCE_HEALTH) <= 0 )
                                {
                                        player.ons_spawn_by = closest_target;
                                        player.respawn_flags = player.respawn_flags | RESPAWN_FORCE;
@@@ -2058,14 -2053,14 +2058,14 @@@ MUTATOR_HOOKFUNCTION(ons, SendWaypoint
                {
                        entity wp_owner = wp.owner;
                        entity e = WaypointSprite_getviewentity(to);
-                       if(SAME_TEAM(e, wp_owner) && wp_owner.goalentity.health >= wp_owner.goalentity.max_health) { wp_flag |= 2; }
+                       if(SAME_TEAM(e, wp_owner) && GetResourceAmount(wp_owner.goalentity, RESOURCE_HEALTH) >= wp_owner.goalentity.max_health) { wp_flag |= 2; }
                        if(!ons_ControlPoint_Attackable(wp_owner, e.team)) { wp_flag |= 2; }
                }
                if(wp.owner.classname == "onslaught_generator")
                {
                        entity wp_owner = wp.owner;
-                       if(wp_owner.isshielded && wp_owner.health >= wp_owner.max_health) { wp_flag |= 2; }
-                       if(wp_owner.health <= 0) { wp_flag |= 2; }
+                       if(wp_owner.isshielded && GetResourceAmount(wp_owner, RESOURCE_HEALTH) >= wp_owner.max_health) { wp_flag |= 2; }
+                       if(GetResourceAmount(wp_owner, RESOURCE_HEALTH) <= 0) { wp_flag |= 2; }
                }
        }
  
@@@ -2167,9 -2162,12 +2167,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);
diff --combined qcsrc/common/t_items.qc
index 9987138b098e539aa38e6c01949421e41552dfd8,68fa7ef578ee302b29fd3697e22f89b5a2b6d856..04c97948ebb7db6135f9439f17b3c5a5545299fa
@@@ -613,18 -613,14 +613,18 @@@ float adjust_respawntime(float normal_r
                return normal_respawntime;
        }
  
 -      CheckAllowedTeams(NULL);
 -      GetTeamCounts(NULL);
 +      entity balance = TeamBalance_CheckAllowedTeams(NULL);
 +      TeamBalance_GetTeamCounts(balance, NULL);
        int players = 0;
 -      if (c1 != -1) players += c1;
 -      if (c2 != -1) players += c2;
 -      if (c3 != -1) players += c3;
 -      if (c4 != -1) players += c4;
 -
 +      for (int i = 1; i <= NUM_TEAMS; ++i)
 +      {
 +              if (TeamBalance_IsTeamAllowed(balance, i))
 +              {
 +                      players += TeamBalance_GetNumberOfPlayers(balance, i);
 +              }
 +      }
 +      TeamBalance_Destroy(balance);
 +      
        if (players >= 2) {
                return normal_respawntime * (r / (players + o) + l);
        } else {
@@@ -1075,12 -1071,12 +1075,12 @@@ float ammo_pickupevalfunc(entity player
        if(item.itemdef.instanceOfWeaponPickup)
        {
                entity ammo = NULL;
-               if(item.ammo_shells)       { need_shells  = true; ammo = ITEM_Shells;      }
-               else if(item.ammo_nails)   { need_nails   = true; ammo = ITEM_Bullets;     }
-               else if(item.ammo_rockets) { need_rockets = true; ammo = ITEM_Rockets;     }
-               else if(item.ammo_cells)   { need_cells   = true; ammo = ITEM_Cells;       }
-               else if(item.ammo_plasma)  { need_plasma  = true; ammo = ITEM_Plasma;      }
-               else if(item.ammo_fuel)    { need_fuel    = true; ammo = ITEM_JetpackFuel; }
+               if(GetResourceAmount(item, RESOURCE_SHELLS))       { need_shells  = true; ammo = ITEM_Shells;      }
+               else if(GetResourceAmount(item, RESOURCE_BULLETS))   { need_nails   = true; ammo = ITEM_Bullets;     }
+               else if(GetResourceAmount(item, RESOURCE_ROCKETS)) { need_rockets = true; ammo = ITEM_Rockets;     }
+               else if(GetResourceAmount(item, RESOURCE_CELLS))   { need_cells   = true; ammo = ITEM_Cells;       }
+               else if(GetResourceAmount(item, RESOURCE_PLASMA))  { need_plasma  = true; ammo = ITEM_Plasma;      }
+               else if(GetResourceAmount(item, RESOURCE_FUEL))    { need_fuel    = true; ammo = ITEM_JetpackFuel; }
  
                if(!ammo)
                        return 0;
  
        float noammorating = 0.5;
  
-       if ((need_shells) && (item.ammo_shells) && (GetResourceAmount(player, RESOURCE_SHELLS) < g_pickup_shells_max))
-               c = item.ammo_shells / max(noammorating, GetResourceAmount(player, RESOURCE_SHELLS));
+       if ((need_shells) && GetResourceAmount(item, RESOURCE_SHELLS) && (GetResourceAmount(player, RESOURCE_SHELLS) < g_pickup_shells_max))
+               c = GetResourceAmount(item, RESOURCE_SHELLS) / max(noammorating, GetResourceAmount(player, RESOURCE_SHELLS));
  
-       if ((need_nails) && (item.ammo_nails) && (GetResourceAmount(player, RESOURCE_BULLETS) < g_pickup_nails_max))
-               c = item.ammo_nails / max(noammorating, GetResourceAmount(player, RESOURCE_BULLETS));
+       if ((need_nails) && GetResourceAmount(item, RESOURCE_BULLETS) && (GetResourceAmount(player, RESOURCE_BULLETS) < g_pickup_nails_max))
+               c = GetResourceAmount(item, RESOURCE_BULLETS) / max(noammorating, GetResourceAmount(player, RESOURCE_BULLETS));
  
-       if ((need_rockets) && (item.ammo_rockets) && (GetResourceAmount(player, RESOURCE_ROCKETS) < g_pickup_rockets_max))
-               c = item.ammo_rockets / max(noammorating, GetResourceAmount(player, RESOURCE_ROCKETS));
+       if ((need_rockets) && GetResourceAmount(item, RESOURCE_ROCKETS) && (GetResourceAmount(player, RESOURCE_ROCKETS) < g_pickup_rockets_max))
+               c = GetResourceAmount(item, RESOURCE_ROCKETS) / max(noammorating, GetResourceAmount(player, RESOURCE_ROCKETS));
  
-       if ((need_cells) && (item.ammo_cells) && (GetResourceAmount(player, RESOURCE_CELLS) < g_pickup_cells_max))
-               c = item.ammo_cells / max(noammorating, GetResourceAmount(player, RESOURCE_CELLS));
+       if ((need_cells) && GetResourceAmount(item, RESOURCE_CELLS) && (GetResourceAmount(player, RESOURCE_CELLS) < g_pickup_cells_max))
+               c = GetResourceAmount(item, RESOURCE_CELLS) / max(noammorating, GetResourceAmount(player, RESOURCE_CELLS));
  
-       if ((need_plasma) && (item.ammo_plasma) && (GetResourceAmount(player, RESOURCE_PLASMA) < g_pickup_plasma_max))
-               c = item.ammo_plasma / max(noammorating, GetResourceAmount(player, RESOURCE_PLASMA));
+       if ((need_plasma) && GetResourceAmount(item, RESOURCE_PLASMA) && (GetResourceAmount(player, RESOURCE_PLASMA) < g_pickup_plasma_max))
+               c = GetResourceAmount(item, RESOURCE_PLASMA) / max(noammorating, GetResourceAmount(player, RESOURCE_PLASMA));
  
-       if ((need_fuel) && (item.ammo_fuel) && (GetResourceAmount(player, RESOURCE_FUEL) < g_pickup_fuel_max))
-               c = item.ammo_fuel / max(noammorating, GetResourceAmount(player, RESOURCE_FUEL));
+       if ((need_fuel) && GetResourceAmount(item, RESOURCE_FUEL) && (GetResourceAmount(player, RESOURCE_FUEL) < g_pickup_fuel_max))
+               c = GetResourceAmount(item, RESOURCE_FUEL) / max(noammorating, GetResourceAmount(player, RESOURCE_FUEL));
  
        rating *= min(c, 2);
        if(wpn)
@@@ -1137,8 -1133,8 +1137,8 @@@ float healtharmor_pickupevalfunc(entit
        float c = 0;
        float rating = item.bot_pickupbasevalue;
  
-       float itemarmor = item.armorvalue;
-       float itemhealth = item.health;
+       float itemarmor = GetResourceAmount(item, RESOURCE_ARMOR);
+       float itemhealth = GetResourceAmount(item, RESOURCE_HEALTH);
  
        if(item.item_group)
        {
                itemhealth *= min(4, item.item_group_count);
        }
  
-       if (itemarmor && (player.armorvalue < item.max_armorvalue))
-               c = itemarmor / max(1, player.armorvalue * 2/3 + player.health * 1/3);
+       if (itemarmor && (GetResourceAmount(player, RESOURCE_ARMOR) < item.max_armorvalue))
+               c = itemarmor / max(1, GetResourceAmount(player, RESOURCE_ARMOR) * 2/3 + GetResourceAmount(player, RESOURCE_HEALTH) * 1/3);
  
-       if (itemhealth && (player.health < item.max_health))
-               c = itemhealth / max(1, player.health);
+       if (itemhealth && (GetResourceAmount(player, RESOURCE_HEALTH) < item.max_health))
+               c = itemhealth / max(1, GetResourceAmount(player, RESOURCE_HEALTH));
  
        rating *= min(2, c);
        return rating;
@@@ -1339,7 -1335,7 +1339,7 @@@ void _StartItem(entity this, entity def
                if(def.instanceOfPowerup)
                        this.ItemStatus |= ITS_ANIMATE1;
  
-               if(this.armorvalue || this.health)
+               if(GetResourceAmount(this, RESOURCE_ARMOR) || GetResourceAmount(this, RESOURCE_HEALTH))
                        this.ItemStatus |= ITS_ANIMATE2;
        }
  
@@@ -1559,14 -1555,14 +1559,14 @@@ spawnfunc(target_items
                this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, this.superweapons_finished * boolean(this.items & IT_SUPERWEAPON), "superweapons");
                this.netname = sprintf("%s %s%d %s", this.netname, itemprefix, boolean(this.items & ITEM_Jetpack.m_itemid), "jetpack");
                this.netname = sprintf("%s %s%d %s", this.netname, itemprefix, boolean(this.items & ITEM_JetpackRegen.m_itemid), "fuel_regen");
-               if(this.ammo_shells != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.ammo_shells), "shells");
-               if(this.ammo_nails != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.ammo_nails), "nails");
-               if(this.ammo_rockets != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.ammo_rockets), "rockets");
-               if(this.ammo_cells != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.ammo_cells), "cells");
-               if(this.ammo_plasma != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.ammo_plasma), "plasma");
-               if(this.ammo_fuel != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.ammo_fuel), "fuel");
-               if(this.health != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.health), "health");
-               if(this.armorvalue != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.armorvalue), "armor");
+               if(GetResourceAmount(this, RESOURCE_SHELLS) != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, GetResourceAmount(this, RESOURCE_SHELLS)), "shells");
+               if(GetResourceAmount(this, RESOURCE_BULLETS) != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, GetResourceAmount(this, RESOURCE_BULLETS)), "nails");
+               if(GetResourceAmount(this, RESOURCE_ROCKETS) != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, GetResourceAmount(this, RESOURCE_ROCKETS)), "rockets");
+               if(GetResourceAmount(this, RESOURCE_CELLS) != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, GetResourceAmount(this, RESOURCE_CELLS)), "cells");
+               if(GetResourceAmount(this, RESOURCE_PLASMA) != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, GetResourceAmount(this, RESOURCE_PLASMA)), "plasma");
+               if(GetResourceAmount(this, RESOURCE_FUEL) != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, GetResourceAmount(this, RESOURCE_FUEL)), "fuel");
+               if(GetResourceAmount(this, RESOURCE_HEALTH) != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, GetResourceAmount(this, RESOURCE_HEALTH)), "health");
+               if(GetResourceAmount(this, RESOURCE_ARMOR) != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, GetResourceAmount(this, RESOURCE_ARMOR)), "armor");
                FOREACH(Buffs, it != BUFF_Null, this.netname = sprintf("%s %s%d %s", this.netname, itemprefix, !!(STAT(BUFFS, this) & (it.m_itemid)), it.m_name));
                FOREACH(Weapons, it != WEP_Null, this.netname = sprintf("%s %s%d %s", this.netname, itemprefix, !!(STAT(WEAPONS, this) & (it.m_wepset)), it.netname));
        }
@@@ -1666,6 -1662,31 +1666,31 @@@ void GiveRot(entity e, float v0, float 
        else if(v0 > v1)
                e.(regenfield) = max(e.(regenfield), time + regentime);
  }
+ bool GiveResourceValue(entity e, int resource_type, int op, int val)
+ {
+       int v0 = GetResourceAmount(e, resource_type);
+       switch (op)
+       {
+               case OP_SET:
+                       SetResourceAmount(e, resource_type, val);
+                       break;
+               case OP_MIN:
+                       SetResourceAmount(e, resource_type, max(v0, val)); // min 100 cells = at least 100 cells
+                       break;
+               case OP_MAX:
+                       SetResourceAmount(e, resource_type, min(v0, val));
+                       break;
+               case OP_PLUS:
+                       SetResourceAmount(e, resource_type, v0 + val);
+                       break;
+               case OP_MINUS:
+                       SetResourceAmount(e, resource_type, v0 - val);
+                       break;
+       }
+       int v1 = GetResourceAmount(e, resource_type);
+       return v0 != v1;
+ }
  float GiveItems(entity e, float beginarg, float endarg)
  {
        float got, i, val, op;
        PREGIVE(e, strength_finished);
        PREGIVE(e, invincible_finished);
        PREGIVE(e, superweapons_finished);
-       PREGIVE(e, ammo_nails);
-       PREGIVE(e, ammo_cells);
-       PREGIVE(e, ammo_plasma);
-       PREGIVE(e, ammo_shells);
-       PREGIVE(e, ammo_rockets);
-       PREGIVE(e, ammo_fuel);
-       PREGIVE(e, armorvalue);
-       PREGIVE(e, health);
+       PREGIVE_RESOURCE(e, RESOURCE_BULLETS);
+       PREGIVE_RESOURCE(e, RESOURCE_CELLS);
+       PREGIVE_RESOURCE(e, RESOURCE_PLASMA);
+       PREGIVE_RESOURCE(e, RESOURCE_SHELLS);
+       PREGIVE_RESOURCE(e, RESOURCE_ROCKETS);
+       PREGIVE_RESOURCE(e, RESOURCE_FUEL);
+       PREGIVE_RESOURCE(e, RESOURCE_ARMOR);
+       PREGIVE_RESOURCE(e, RESOURCE_HEALTH);
  
        for(i = beginarg; i < endarg; ++i)
        {
                                got += GiveBit(e, items, IT_UNLIMITED_AMMO, op, val);
                        case "all":
                                got += GiveBit(e, items, ITEM_Jetpack.m_itemid, op, val);
-                               got += GiveValue(e, health, op, val);
-                               got += GiveValue(e, armorvalue, op, val);
+                               got += GiveResourceValue(e, RESOURCE_HEALTH, op, val);
+                               got += GiveResourceValue(e, RESOURCE_ARMOR, op, val);
                        case "allweapons":
                                FOREACH(Weapons, it != WEP_Null && !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED), got += GiveWeapon(e, it.m_id, op, val));
                        //case "allbuffs": // all buffs makes a player god, do not want!
                                //FOREACH(Buffs, it != BUFF_Null, got += GiveBuff(e, it.m_itemid, op, val));
                        case "allammo":
-                               got += GiveValue(e, ammo_cells, op, val);
-                               got += GiveValue(e, ammo_plasma, op, val);
-                               got += GiveValue(e, ammo_shells, op, val);
-                               got += GiveValue(e, ammo_nails, op, val);
-                               got += GiveValue(e, ammo_rockets, op, val);
-                               got += GiveValue(e, ammo_fuel, op, val);
+                               got += GiveResourceValue(e, RESOURCE_CELLS, op, val);
+                               got += GiveResourceValue(e, RESOURCE_PLASMA, op, val);
+                               got += GiveResourceValue(e, RESOURCE_SHELLS, op, val);
+                               got += GiveResourceValue(e, RESOURCE_BULLETS, op, val);
+                               got += GiveResourceValue(e, RESOURCE_ROCKETS, op, val);
+                               got += GiveResourceValue(e, RESOURCE_FUEL, op, val);
                                break;
                        case "unlimited_ammo":
                                got += GiveBit(e, items, IT_UNLIMITED_AMMO, op, val);
                                got += GiveValue(e, superweapons_finished, op, val);
                                break;
                        case "cells":
-                               got += GiveValue(e, ammo_cells, op, val);
+                               got += GiveResourceValue(e, RESOURCE_CELLS, op, val);
                                break;
                        case "plasma":
-                               got += GiveValue(e, ammo_plasma, op, val);
+                               got += GiveResourceValue(e, RESOURCE_PLASMA, op, val);
                                break;
                        case "shells":
-                               got += GiveValue(e, ammo_shells, op, val);
+                               got += GiveResourceValue(e, RESOURCE_SHELLS, op, val);
                                break;
                        case "nails":
                        case "bullets":
-                               got += GiveValue(e, ammo_nails, op, val);
+                               got += GiveResourceValue(e, RESOURCE_BULLETS, op, val);
                                break;
                        case "rockets":
-                               got += GiveValue(e, ammo_rockets, op, val);
+                               got += GiveResourceValue(e, RESOURCE_ROCKETS, op, val);
                                break;
                        case "health":
-                               got += GiveValue(e, health, op, val);
+                               got += GiveResourceValue(e, RESOURCE_HEALTH, op, val);
                                break;
                        case "armor":
-                               got += GiveValue(e, armorvalue, op, val);
+                               got += GiveResourceValue(e, RESOURCE_ARMOR, op, val);
                                break;
                        case "fuel":
-                               got += GiveValue(e, ammo_fuel, op, val);
+                               got += GiveResourceValue(e, RESOURCE_FUEL, op, val);
                                break;
                        default:
                                FOREACH(Buffs, it != BUFF_Null && Buff_UndeprecateName(cmd) == it.m_name,
        POSTGIVE_VALUE(e, strength_finished, 1, SND_POWERUP, SND_POWEROFF);
        POSTGIVE_VALUE(e, invincible_finished, 1, SND_Shield, SND_POWEROFF);
        //POSTGIVE_VALUE(e, superweapons_finished, 1, SND_Null, SND_Null);
-       POSTGIVE_VALUE(e, ammo_nails, 0, SND_ITEMPICKUP, SND_Null);
-       POSTGIVE_VALUE(e, ammo_cells, 0, SND_ITEMPICKUP, SND_Null);
-       POSTGIVE_VALUE(e, ammo_plasma, 0, SND_ITEMPICKUP, SND_Null);
-       POSTGIVE_VALUE(e, ammo_shells, 0, SND_ITEMPICKUP, SND_Null);
-       POSTGIVE_VALUE(e, ammo_rockets, 0, SND_ITEMPICKUP, SND_Null);
-       POSTGIVE_VALUE_ROT(e, ammo_fuel, 1, pauserotfuel_finished, autocvar_g_balance_pause_fuel_rot, pauseregen_finished, autocvar_g_balance_pause_fuel_regen, SND_ITEMPICKUP, SND_Null);
-       POSTGIVE_VALUE_ROT(e, armorvalue, 1, pauserotarmor_finished, autocvar_g_balance_pause_armor_rot, pauseregen_finished, autocvar_g_balance_pause_health_regen, SND_ARMOR25, SND_Null);
-       POSTGIVE_VALUE_ROT(e, health, 1, pauserothealth_finished, autocvar_g_balance_pause_health_rot, pauseregen_finished, autocvar_g_balance_pause_health_regen, SND_MEGAHEALTH, SND_Null);
+       POSTGIVE_RESOURCE(e, RESOURCE_BULLETS, 0, SND_ITEMPICKUP, SND_Null);
+       POSTGIVE_RESOURCE(e, RESOURCE_CELLS, 0, SND_ITEMPICKUP, SND_Null);
+       POSTGIVE_RESOURCE(e, RESOURCE_PLASMA, 0, SND_ITEMPICKUP, SND_Null);
+       POSTGIVE_RESOURCE(e, RESOURCE_SHELLS, 0, SND_ITEMPICKUP, SND_Null);
+       POSTGIVE_RESOURCE(e, RESOURCE_ROCKETS, 0, SND_ITEMPICKUP, SND_Null);
+       POSTGIVE_RESOURCE_ROT(e, RESOURCE_FUEL, 1, pauserotfuel_finished, autocvar_g_balance_pause_fuel_rot, pauseregen_finished, autocvar_g_balance_pause_fuel_regen, SND_ITEMPICKUP, SND_Null);
+       POSTGIVE_RESOURCE_ROT(e, RESOURCE_ARMOR, 1, pauserotarmor_finished, autocvar_g_balance_pause_armor_rot, pauseregen_finished, autocvar_g_balance_pause_health_regen, SND_ARMOR25, SND_Null);
+       POSTGIVE_RESOURCE_ROT(e, RESOURCE_HEALTH, 1, pauserothealth_finished, autocvar_g_balance_pause_health_rot, pauseregen_finished, autocvar_g_balance_pause_health_regen, SND_MEGAHEALTH, SND_Null);
  
        if(e.superweapons_finished <= 0)
                if(STAT(WEAPONS, e) & WEPSET_SUPERWEAPONS)
index 48543821097abeeb3371c125f395f630c03d28c7,2c5627bf63daa7311af2cdcf3e6bd84c7680787c..1cea15ecd0d77cde454da5b0349494f8a86f34f7
@@@ -74,12 -74,6 +74,6 @@@ void bot_think(entity this
  
        this.bot_nextthink = max(time, this.bot_nextthink) + max(0.01, autocvar_bot_ai_thinkinterval * (0.5 ** this.bot_aiskill) * min(14 / (skill + 14), 1));
  
-       //if (this.bot_painintensity > 0)
-       //      this.bot_painintensity = this.bot_painintensity - (skill + 1) * 40 * frametime;
-       //this.bot_painintensity = this.bot_painintensity + this.bot_oldhealth - this.health;
-       //this.bot_painintensity = bound(0, this.bot_painintensity, 100);
        if (!IS_PLAYER(this) || (autocvar_g_campaign && !campaign_bots_may_start))
        {
                CS(this).movement = '0 0 0';
@@@ -430,15 -424,15 +424,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;
diff --combined qcsrc/server/client.qc
index 0d344b7f2d6807c96768f7b2e5eca6e819e8fb4f,0c740545c08d3593af6c5fe8f098125bf0544f2d..4bc46051f6fb11bd6b9d13333147946342e2892f
@@@ -229,7 -229,7 +229,7 @@@ void PutObserverInServer(entity this
  
        if (IS_PLAYER(this))
        {
-               if(this.health >= 1)
+               if(GetResourceAmount(this, RESOURCE_HEALTH) >= 1)
                {
                        // despawn effect
                        Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1);
        if (mutator_returnvalue) {
            // mutator prevents resetting teams+score
        } else {
 -              int oldteam = this.team;
 -              this.team = -1;  // move this as it is needed to log the player spectating in eventlog
 -              MUTATOR_CALLHOOK(Player_ChangedTeam, this, oldteam, this.team);
 -        this.frags = FRAGS_SPECTATOR;
 +              Player_SetTeamIndex(this, -1);
 +              this.frags = FRAGS_SPECTATOR;
          PlayerScore_Clear(this);  // clear scores when needed
      }
  
                        Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_CHAT_NOSPECTATORS);
  
                if(!CS(this).just_joined)
 -                      LogTeamchange(this.playerid, -1, 4);
 +                      LogTeamchange(this.playerid, -1, TEAM_CHANGE_SPECTATOR);
                else
                        CS(this).just_joined = false;
        }
        if(this.damagedbycontents)
                IL_REMOVE(g_damagedbycontents, this);
        this.damagedbycontents = false;
-       this.health = FRAGS_SPECTATOR;
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, FRAGS_SPECTATOR);
        SetSpectatee_status(this, etof(this));
        this.takedamage = DAMAGE_NO;
        this.solid = SOLID_NOT;
        set_movetype(this, MOVETYPE_FLY_WORLDONLY); // user preference is controlled by playerprethink
        this.flags = FL_CLIENT | FL_NOTARGET;
-       this.armorvalue = 666;
        this.effects = 0;
-       this.armorvalue = autocvar_g_balance_armor_start;
+       SetResourceAmountExplicit(this, RESOURCE_ARMOR, autocvar_g_balance_armor_start); // was 666?!
        this.pauserotarmor_finished = 0;
        this.pauserothealth_finished = 0;
        this.pauseregen_finished = 0;
@@@ -520,7 -521,7 +519,7 @@@ void PutPlayerInServer(entity this
        accuracy_resend(this);
  
        if (this.team < 0)
 -              JoinBestTeam(this, true);
 +              TeamBalance_JoinBestTeam(this, true);
  
        entity spot = SelectSpawnPoint(this, false);
        if (!spot) {
                SetResourceAmount(this, RESOURCE_CELLS, warmup_start_ammo_cells);
                SetResourceAmount(this, RESOURCE_PLASMA, warmup_start_ammo_plasma);
                SetResourceAmount(this, RESOURCE_FUEL, warmup_start_ammo_fuel);
-               this.health = warmup_start_health;
-               this.armorvalue = warmup_start_armorvalue;
+               SetResourceAmount(this, RESOURCE_HEALTH, warmup_start_health);
+               SetResourceAmount(this, RESOURCE_ARMOR, warmup_start_armorvalue);
                STAT(WEAPONS, this) = WARMUP_START_WEAPONS;
        } else {
                SetResourceAmount(this, RESOURCE_SHELLS, start_ammo_shells);
                SetResourceAmount(this, RESOURCE_CELLS, start_ammo_cells);
                SetResourceAmount(this, RESOURCE_PLASMA, start_ammo_plasma);
                SetResourceAmount(this, RESOURCE_FUEL, start_ammo_fuel);
-               this.health = start_health;
-               this.armorvalue = start_armorvalue;
+               SetResourceAmount(this, RESOURCE_HEALTH, start_health);
+               SetResourceAmount(this, RESOURCE_ARMOR, start_armorvalue);
                STAT(WEAPONS, this) = start_weapons;
                if (MUTATOR_CALLHOOK(ForbidRandomStartWeapons, this) == false)
                {
@@@ -905,7 -906,7 +904,7 @@@ void ClientKill_Now_TeamChange(entity t
  {
        if(this.killindicator_teamchange == -1)
        {
 -              JoinBestTeam( this, true );
 +              TeamBalance_JoinBestTeam(this, true);
        }
        else if(this.killindicator_teamchange == -2)
        {
@@@ -966,7 -967,7 +965,7 @@@ void KillIndicator_Think(entity this
                ClientKill_Now(this.owner);
                return;
        }
-     else if(this.health == 1) // health == 1 means that it's silent
+     else if(this.count == 1) // count == 1 means that it's silent
      {
          this.nextthink = time + 1;
          this.cnt -= 1;
@@@ -1081,6 -1082,8 +1080,8 @@@ void ClientKill_TeamChange (entity this
  
  void ClientKill (entity this)
  {
+       // TODO: once .health is removed, will need to check it here for the "already dead" message!
        if(game_stopped) return;
        if(this.player_blocked) return;
        if(STAT(FROZEN, this)) return;
@@@ -1163,76 -1166,6 +1164,76 @@@ void ClientPreConnect(entity this
  }
  #endif
  
 +string GetClientVersionMessage(entity this)
 +{
 +      if (CS(this).version_mismatch) {
 +              if(CS(this).version < autocvar_gameversion) {
 +                      return strcat("This is Xonotic ", autocvar_g_xonoticversion,
 +                              "\n^3Your client version is outdated.\n\n\n### YOU WON'T BE ABLE TO PLAY ON THIS SERVER ###\n\n\nPlease update!!!^8");
 +              } else {
 +                      return strcat("This is Xonotic ", autocvar_g_xonoticversion,
 +                              "\n^3This server is using an outdated Xonotic version.\n\n\n ### THIS SERVER IS INCOMPATIBLE AND THUS YOU CANNOT JOIN ###.^8");
 +              }
 +      } else {
 +              return strcat("Welcome to Xonotic ", autocvar_g_xonoticversion);
 +      }
 +}
 +
 +string getwelcomemessage(entity this)
 +{
 +      MUTATOR_CALLHOOK(BuildMutatorsPrettyString, "");
 +      string modifications = M_ARGV(0, string);
 +
 +      if(g_weaponarena)
 +      {
 +              if(g_weaponarena_random)
 +                      modifications = strcat(modifications, ", ", ftos(g_weaponarena_random), " of ", g_weaponarena_list, " Arena");
 +              else
 +                      modifications = strcat(modifications, ", ", g_weaponarena_list, " Arena");
 +      }
 +      else if(cvar("g_balance_blaster_weaponstartoverride") == 0)
 +              modifications = strcat(modifications, ", No start weapons");
 +      if(cvar("sv_gravity") < stof(cvar_defstring("sv_gravity")))
 +              modifications = strcat(modifications, ", Low gravity");
 +      if(g_weapon_stay && !g_cts)
 +              modifications = strcat(modifications, ", Weapons stay");
 +      if(g_jetpack)
 +              modifications = strcat(modifications, ", Jet pack");
 +      if(autocvar_g_powerups == 0)
 +              modifications = strcat(modifications, ", No powerups");
 +      if(autocvar_g_powerups > 0)
 +              modifications = strcat(modifications, ", Powerups");
 +      modifications = substring(modifications, 2, strlen(modifications) - 2);
 +
 +      string versionmessage = GetClientVersionMessage(this);
 +      string s = strcat(versionmessage, "^8\n^8\nmatch type is ^1", gamemode_name, "^8\n");
 +
 +      if(modifications != "")
 +              s = strcat(s, "^8\nactive modifications: ^3", modifications, "^8\n");
 +
 +      if(cache_lastmutatormsg != autocvar_g_mutatormsg)
 +      {
 +              strcpy(cache_lastmutatormsg, autocvar_g_mutatormsg);
 +              strcpy(cache_mutatormsg, cache_lastmutatormsg);
 +      }
 +
 +      if (cache_mutatormsg != "") {
 +              s = strcat(s, "\n\n^8special gameplay tips: ^7", cache_mutatormsg);
 +      }
 +
 +      string mutator_msg = "";
 +      MUTATOR_CALLHOOK(BuildGameplayTipsString, mutator_msg);
 +      mutator_msg = M_ARGV(0, string);
 +
 +      s = strcat(s, mutator_msg); // trust that the mutator will do proper formatting
 +
 +      string motd = autocvar_sv_motd;
 +      if (motd != "") {
 +              s = strcat(s, "\n\n^8MOTD: ^7", strreplace("\\n", "\n", motd));
 +      }
 +      return s;
 +}
 +
  /**
  =============
  ClientConnect
@@@ -1288,7 -1221,7 +1289,7 @@@ void ClientConnect(entity this
  
        int playerid_save = this.playerid;
        this.playerid = 0; // silent
 -      JoinBestTeam(this, false); // if the team number is valid, keep it
 +      TeamBalance_JoinBestTeam(this, false); // if the team number is valid, keep it
        this.playerid = playerid_save;
  
        TRANSMUTE(Observer, this);
        if (autocvar_sv_eventlog)
                GameLogEcho(strcat(":join:", ftos(this.playerid), ":", ftos(etof(this)), ":", ((IS_REAL_CLIENT(this)) ? this.netaddress : "bot"), ":", playername(this, false)));
  
 -      LogTeamchange(this.playerid, this.team, 1);
 +      LogTeamchange(this.playerid, this.team, TEAM_CHANGE_CONNECT);
  
        CS(this).just_joined = true;  // stop spamming the eventlog with additional lines when the client connects
  
        // notify about available teams
        if (teamplay)
        {
 -              CheckAllowedTeams(this);
 -              int t = 0;
 -              if (c1 >= 0) t |= BIT(0);
 -              if (c2 >= 0) t |= BIT(1);
 -              if (c3 >= 0) t |= BIT(2);
 -              if (c4 >= 0) t |= BIT(3);
 +              entity balance = TeamBalance_CheckAllowedTeams(this);
 +              int t = TeamBalance_GetAllowedTeams(balance);
 +              TeamBalance_Destroy(balance);
                stuffcmd(this, sprintf("set _teams_available %d\n", t));
        }
        else
@@@ -1789,13 -1725,17 +1790,17 @@@ void player_regen(entity this
                limith = limith * limit_mod;
                limita = limita * limit_mod;
  
-               this.armorvalue = CalcRotRegen(this.armorvalue, mina, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, regen_mod * frametime * (time > this.pauseregen_finished), maxa, autocvar_g_balance_armor_rot, autocvar_g_balance_armor_rotlinear, rot_mod * frametime * (time > this.pauserotarmor_finished), limita);
-               this.health = CalcRotRegen(this.health, regen_health_stable, regen_health, regen_health_linear, regen_mod * frametime * (time > this.pauseregen_finished), regen_health_rotstable, regen_health_rot, regen_health_rotlinear, rot_mod * frametime * (time > this.pauserothealth_finished), limith);
+               SetResourceAmount(this, RESOURCE_ARMOR, CalcRotRegen(GetResourceAmount(this, RESOURCE_ARMOR), mina, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, 
+                                                                       regen_mod * frametime * (time > this.pauseregen_finished), maxa, autocvar_g_balance_armor_rot, autocvar_g_balance_armor_rotlinear,
+                                                                       rot_mod * frametime * (time > this.pauserotarmor_finished), limita));
+               SetResourceAmount(this, RESOURCE_HEALTH, CalcRotRegen(GetResourceAmount(this, RESOURCE_HEALTH), regen_health_stable, regen_health, regen_health_linear,
+                                                                       regen_mod * frametime * (time > this.pauseregen_finished), regen_health_rotstable, regen_health_rot, regen_health_rotlinear,
+                                                                       rot_mod * frametime * (time > this.pauserothealth_finished), limith));
        }
  
        // if player rotted to death...  die!
        // check this outside above checks, as player may still be able to rot to death
-       if(this.health < 1)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) < 1)
        {
                if(this.vehicle)
                        vehicles_exit(this.vehicle, VHEF_RELEASE);
                minf = autocvar_g_balance_fuel_regenstable;
                limitf = GetResourceLimit(this, RESOURCE_FUEL);
  
-               this.ammo_fuel = CalcRotRegen(this.ammo_fuel, minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, frametime * (time > this.pauseregen_finished) * ((this.items & ITEM_JetpackRegen.m_itemid) != 0), maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, frametime * (time > this.pauserotfuel_finished), limitf);
-       }
-       // Ugly hack to make sure the health and armor don't go beyond hard limit.
-       // TODO: Remove this hack when all code uses GivePlayerHealth and
-       // GivePlayerArmor.
-       if (this.health > RESOURCE_AMOUNT_HARD_LIMIT)
-       {
-               this.health = RESOURCE_AMOUNT_HARD_LIMIT;
-       }
-       if (this.armorvalue > RESOURCE_AMOUNT_HARD_LIMIT)
-       {
-               this.armorvalue = RESOURCE_AMOUNT_HARD_LIMIT;
+               SetResourceAmount(this, RESOURCE_FUEL, CalcRotRegen(GetResourceAmount(this, RESOURCE_FUEL), minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, 
+                                                                               frametime * (time > this.pauseregen_finished) * ((this.items & ITEM_JetpackRegen.m_itemid) != 0),
+                                                                               maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, frametime * (time > this.pauserotfuel_finished), limitf));
        }
-       // End hack.
  }
  
  bool zoomstate_set;
@@@ -1869,15 -1799,15 +1864,15 @@@ void SpectateCopy(entity this, entity s
        MUTATOR_CALLHOOK(SpectateCopy, spectatee, this);
        PS(this) = PS(spectatee);
        this.armortype = spectatee.armortype;
-       this.armorvalue = spectatee.armorvalue;
-       this.ammo_cells = spectatee.ammo_cells; // TODO: these will be a part of inventory, so no need to worry about setting them later!
-       this.ammo_plasma = spectatee.ammo_plasma;
-       this.ammo_shells = spectatee.ammo_shells;
-       this.ammo_nails = spectatee.ammo_nails;
-       this.ammo_rockets = spectatee.ammo_rockets;
-       this.ammo_fuel = spectatee.ammo_fuel;
+       SetResourceAmountExplicit(this, RESOURCE_ARMOR, GetResourceAmount(spectatee, RESOURCE_ARMOR));
+       SetResourceAmountExplicit(this, RESOURCE_CELLS, GetResourceAmount(spectatee, RESOURCE_CELLS));
+       SetResourceAmountExplicit(this, RESOURCE_PLASMA, GetResourceAmount(spectatee, RESOURCE_PLASMA));
+       SetResourceAmountExplicit(this, RESOURCE_SHELLS, GetResourceAmount(spectatee, RESOURCE_SHELLS));
+       SetResourceAmountExplicit(this, RESOURCE_BULLETS, GetResourceAmount(spectatee, RESOURCE_BULLETS));
+       SetResourceAmountExplicit(this, RESOURCE_ROCKETS, GetResourceAmount(spectatee, RESOURCE_ROCKETS));
+       SetResourceAmountExplicit(this, RESOURCE_FUEL, GetResourceAmount(spectatee, RESOURCE_FUEL));
        this.effects = spectatee.effects & EFMASK_CHEAP; // eat performance
-       this.health = spectatee.health;
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, GetResourceAmount(spectatee, RESOURCE_HEALTH));
        CS(this).impulse = 0;
        this.items = spectatee.items;
        STAT(LAST_PICKUP, this) = STAT(LAST_PICKUP, spectatee);
@@@ -2117,7 -2047,7 +2112,7 @@@ void Join(entity this
  
        if(!this.team_selected)
        if(autocvar_g_campaign || autocvar_g_balance_teams)
 -              JoinBestTeam(this, true);
 +              TeamBalance_JoinBestTeam(this, true);
  
        if(autocvar_g_campaign)
                campaign_bots_may_start = true;
  
        if(IS_PLAYER(this))
        if(teamplay && this.team != -1)
 -              Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_JOIN_PLAY_TEAM), this.netname);
 +      {
 +              //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_JOIN_PLAY_TEAM), this.netname);
 +      }
        else
                Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_PLAY, this.netname);
        this.team_selected = false;
@@@ -2376,7 -2304,7 +2371,7 @@@ bool PlayerThink(entity this
                }
  
                this.items_added = 0;
-               if ((this.items & ITEM_Jetpack.m_itemid) && ((this.items & ITEM_JetpackRegen.m_itemid) || this.ammo_fuel >= 0.01))
+               if ((this.items & ITEM_Jetpack.m_itemid) && ((this.items & ITEM_JetpackRegen.m_itemid) || GetResourceAmount(this, RESOURCE_FUEL) >= 0.01))
              this.items_added |= IT_FUEL;
  
                this.items |= this.items_added;
@@@ -2632,7 -2560,7 +2627,7 @@@ void PlayerPreThink (entity this
                if (STAT(FROZEN, this) == 2)
                {
                        STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + frametime * this.revive_speed, 1);
-                       this.health = max(1, STAT(REVIVE_PROGRESS, this) * start_health);
+                       SetResourceAmountExplicit(this, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * start_health));
                        this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1);
  
                        if (STAT(REVIVE_PROGRESS, this) >= 1)
                else if (STAT(FROZEN, this) == 3)
                {
                        STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) - frametime * this.revive_speed, 1);
-                       this.health = max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this) );
+                       SetResourceAmountExplicit(this, RESOURCE_HEALTH, max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this)));
  
-                       if (this.health < 1)
+                       if (GetResourceAmount(this, RESOURCE_HEALTH) < 1)
                        {
                                if (this.vehicle)
                                        vehicles_exit(this.vehicle, VHEF_RELEASE);
@@@ -2885,7 -2813,7 +2880,7 @@@ void PlayerPostThink (entity this
        }
  
        if (this.waypointsprite_attachedforcarrier) {
-           vector v = healtharmor_maxdamage(this.health, this.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id);
+           vector v = healtharmor_maxdamage(GetResourceAmount(this, RESOURCE_HEALTH), GetResourceAmount(this, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id);
                WaypointSprite_UpdateHealth(this.waypointsprite_attachedforcarrier, '1 0 0' * v);
      }
  
diff --combined qcsrc/server/g_world.qc
index 46d5337f8a768d5d3f5028689ba86a2a5897ac64,5c55e4ff50473ef065049d2b3ac25d1972f0f017..c6bef111597ae5201fe35e9ec063159f681547be
@@@ -16,7 -16,6 +16,7 @@@
  #include <server/mutators/_mod.qh>
  #include "race.qh"
  #include "scores.qh"
 +#include "scores_rules.qh"
  #include "teamplay.qh"
  #include "weapons/weaponstats.qh"
  #include "../common/constants.qh"
@@@ -578,58 -577,6 +578,58 @@@ STATIC_INIT_EARLY(maxclients
        }
  }
  
 +void default_delayedinit(entity this)
 +{
 +      if(!scores_initialized)
 +              ScoreRules_generic();
 +}
 +
 +void InitGameplayMode()
 +{
 +      VoteReset();
 +
 +      // find out good world mins/maxs bounds, either the static bounds found by looking for solid, or the mapinfo specified bounds
 +      get_mi_min_max(1);
 +      // assign reflectively to avoid "assignment to world" warning
 +      int done = 0; for (int i = 0, n = numentityfields(); i < n; ++i) {
 +          string k = entityfieldname(i); vector v = (k == "mins") ? mi_min : (k == "maxs") ? mi_max : '0 0 0';
 +          if (v) {
 +            putentityfieldstring(i, world, sprintf("%v", v));
 +            if (++done == 2) break;
 +        }
 +      }
 +      // currently, NetRadiant's limit is 131072 qu for each side
 +      // distance from one corner of a 131072qu cube to the opposite corner is approx. 227023 qu
 +      // set the distance according to map size but don't go over the limit to avoid issues with float precision
 +      // in case somebody makes extremely large maps
 +      max_shot_distance = min(230000, vlen(world.maxs - world.mins));
 +
 +      MapInfo_LoadMapSettings(mapname);
 +      GameRules_teams(false);
 +
 +      if (!cvar_value_issafe(world.fog))
 +      {
 +              LOG_INFO("The current map contains a potentially harmful fog setting, ignored");
 +              world.fog = string_null;
 +      }
 +      if(MapInfo_Map_fog != "")
 +              if(MapInfo_Map_fog == "none")
 +                      world.fog = string_null;
 +              else
 +                      world.fog = strzone(MapInfo_Map_fog);
 +      clientstuff = strzone(MapInfo_Map_clientstuff);
 +
 +      MapInfo_ClearTemps();
 +
 +      gamemode_name = MapInfo_Type_ToText(MapInfo_LoadedGametype);
 +
 +      cache_mutatormsg = strzone("");
 +      cache_lastmutatormsg = strzone("");
 +
 +      InitializeEntity(NULL, default_delayedinit, INITPRIO_GAMETYPE_FALLBACK);
 +}
 +
 +void Map_MarkAsRecent(string m);
  float world_already_spawned;
  spawnfunc(worldspawn)
  {
@@@ -1519,7 -1466,7 +1519,7 @@@ void FixIntermissionClient(entity e
        if(!e.autoscreenshot) // initial call
        {
                e.autoscreenshot = time + 0.8;  // used for autoscreenshot
-               e.health = -2342;
+               SetResourceAmountExplicit(e, RESOURCE_HEALTH, -2342);
                // first intermission phase; voting phase has positive health (used to decide whether to send SVC_FINALE or not)
                for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
                {
@@@ -1698,11 -1645,10 +1698,11 @@@ float WinningCondition_Scores(float lim
  
        if(teamplay)
        {
 -              team1_score = TeamScore_GetCompareValue(NUM_TEAM_1);
 -              team2_score = TeamScore_GetCompareValue(NUM_TEAM_2);
 -              team3_score = TeamScore_GetCompareValue(NUM_TEAM_3);
 -              team4_score = TeamScore_GetCompareValue(NUM_TEAM_4);
 +              for (int i = 1; i < 5; ++i)
 +              {
 +                      Team_SetTeamScore(Team_GetTeamFromIndex(i),
 +                              TeamScore_GetCompareValue(Team_IndexToTeam(i)));
 +              }
        }
  
        ClearWinners();
@@@ -1772,32 -1718,30 +1772,32 @@@ float WinningCondition_RanOutOfSpawns(
        if(!some_spawn_has_been_used)
                return WINNING_NO;
  
 -      team1_score = team2_score = team3_score = team4_score = 0;
 -
 -      FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), {
 -              switch(it.team)
 +      for (int i = 1; i < 5; ++i)
 +      {
 +              Team_SetTeamScore(Team_GetTeamFromIndex(i), 0);
 +      }
 +      
 +      FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
 +      {
 +              if (Team_IsValidTeam(it.team))
                {
 -                      case NUM_TEAM_1: team1_score = 1; break;
 -                      case NUM_TEAM_2: team2_score = 1; break;
 -                      case NUM_TEAM_3: team3_score = 1; break;
 -                      case NUM_TEAM_4: team4_score = 1; break;
 +                      Team_SetTeamScore(Team_GetTeam(it.team), 1);
                }
        });
  
        IL_EACH(g_spawnpoints, true,
        {
 -              switch(it.team)
 +              if (Team_IsValidTeam(it.team))
                {
 -                      case NUM_TEAM_1: team1_score = 1; break;
 -                      case NUM_TEAM_2: team2_score = 1; break;
 -                      case NUM_TEAM_3: team3_score = 1; break;
 -                      case NUM_TEAM_4: team4_score = 1; break;
 +                      Team_SetTeamScore(Team_GetTeam(it.team), 1);
                }
        });
  
        ClearWinners();
 +      float team1_score = Team_GetTeamScore(Team_GetTeamFromIndex(1));
 +      float team2_score = Team_GetTeamScore(Team_GetTeamFromIndex(2));
 +      float team3_score = Team_GetTeamScore(Team_GetTeamFromIndex(3));
 +      float team4_score = Team_GetTeamScore(Team_GetTeamFromIndex(4));
        if(team1_score + team2_score + team3_score + team4_score == 0)
        {
                checkrules_equality = true;
        {
                float t, i;
                if(team1_score)
 -                      t = NUM_TEAM_1;
 +                      t = 1;
                else if(team2_score)
 -                      t = NUM_TEAM_2;
 +                      t = 2;
                else if(team3_score)
 -                      t = NUM_TEAM_3;
 +                      t = 3;
                else // if(team4_score)
 -                      t = NUM_TEAM_4;
 -              CheckAllowedTeams(NULL);
 +                      t = 4;
 +              entity balance = TeamBalance_CheckAllowedTeams(NULL);
                for(i = 0; i < MAX_TEAMSCORE; ++i)
                {
 -                      if(t != NUM_TEAM_1) if(c1 >= 0) TeamScore_AddToTeam(NUM_TEAM_1, i, -1000);
 -                      if(t != NUM_TEAM_2) if(c2 >= 0) TeamScore_AddToTeam(NUM_TEAM_2, i, -1000);
 -                      if(t != NUM_TEAM_3) if(c3 >= 0) TeamScore_AddToTeam(NUM_TEAM_3, i, -1000);
 -                      if(t != NUM_TEAM_4) if(c4 >= 0) TeamScore_AddToTeam(NUM_TEAM_4, i, -1000);
 +                      for (int j = 1; j <= NUM_TEAMS; ++j)
 +                      {
 +                              if (t == j)
 +                              {
 +                                      continue;
 +                              }
 +                              if (!TeamBalance_IsTeamAllowed(balance, j))
 +                              {
 +                                      continue;
 +                              }
 +                              TeamScore_AddToTeam(Team_IndexToTeam(j), i, -1000);
 +                      }
                }
  
                AddWinners(team, t);
index f02808eadade62aafca4817236c13a22dbc18a7a,de21a59035a5d4d913ab3ba33aa9b2e1ffbbef14..7ef442dddd2fbe5e4b514e6318a5a85546f95748
@@@ -137,51 -137,40 +137,51 @@@ MUTATOR_HOOKABLE(GiveFragsForKill, EV_G
  /** called when the match ends */
  MUTATOR_HOOKABLE(MatchEnd, EV_NO_ARGS);
  
 -/** allows adjusting allowed teams */
 -#define EV_CheckAllowedTeams(i, o) \
 +/** Allows adjusting allowed teams. Return true to use the bitmask value and set
 + * non-empty string to use team entity name. Both behaviors can be active at the
 + * same time and will stack allowed teams.
 + */
 +#define EV_TeamBalance_CheckAllowedTeams(i, o) \
      /** mask of teams      */ i(float, MUTATOR_ARGV_0_float) \
      /**/                      o(float, MUTATOR_ARGV_0_float) \
      /** team entity name   */ i(string, MUTATOR_ARGV_1_string) \
      /**/                      o(string, MUTATOR_ARGV_1_string) \
      /** player checked     */ i(entity, MUTATOR_ARGV_2_entity) \
      /**/
 -MUTATOR_HOOKABLE(CheckAllowedTeams, EV_CheckAllowedTeams);
 +MUTATOR_HOOKABLE(TeamBalance_CheckAllowedTeams,
 +      EV_TeamBalance_CheckAllowedTeams);
  
  /** return true to manually override team counts */
 -MUTATOR_HOOKABLE(GetTeamCounts, EV_NO_ARGS);
 +MUTATOR_HOOKABLE(TeamBalance_GetTeamCounts, EV_NO_ARGS);
  
 -/** allow overriding of team counts */
 -#define EV_GetTeamCount(i, o) \
 -    /** team to count                   */ i(float, MUTATOR_ARGV_0_float) \
 +/** allows overriding of team counts */
 +#define EV_TeamBalance_GetTeamCount(i, o) \
 +    /** team index to count             */ i(float, MUTATOR_ARGV_0_float) \
      /** player to ignore                */ i(entity, MUTATOR_ARGV_1_entity) \
 -    /** number of players in a team     */ i(float, MUTATOR_ARGV_2_float) \
 -    /**/                                   o(float, MUTATOR_ARGV_2_float) \
 -    /** number of bots in a team        */ i(float, MUTATOR_ARGV_3_float) \
 -    /**/                                   o(float, MUTATOR_ARGV_3_float) \
 -    /** lowest scoring human in a team  */ i(entity, MUTATOR_ARGV_4_entity) \
 -    /**/                                   o(entity, MUTATOR_ARGV_4_entity) \
 -    /** lowest scoring bot in a team    */ i(entity, MUTATOR_ARGV_5_entity) \
 -    /**/                                   o(entity, MUTATOR_ARGV_5_entity) \
 -    /**/
 -MUTATOR_HOOKABLE(GetTeamCount, EV_GetTeamCount);
 -
 -/** allows overriding best teams */
 -#define EV_FindBestTeams(i, o) \
 +    /** number of players in a team     */ o(float, MUTATOR_ARGV_2_float) \
 +    /** number of bots in a team        */ o(float, MUTATOR_ARGV_3_float) \
 +    /**/
 +MUTATOR_HOOKABLE(TeamBalance_GetTeamCount, EV_TeamBalance_GetTeamCount);
 +
 +/** allows overriding the teams that will make the game most balanced if the
 + *  player joins any of them.
 + */
 +#define EV_TeamBalance_FindBestTeams(i, o) \
      /** player checked   */ i(entity, MUTATOR_ARGV_0_entity) \
      /** bitmask of teams */ o(float, MUTATOR_ARGV_1_float) \
      /**/
 -MUTATOR_HOOKABLE(FindBestTeams, EV_FindBestTeams);
 +MUTATOR_HOOKABLE(TeamBalance_FindBestTeams, EV_TeamBalance_FindBestTeams);
 +
 +/** Called during autobalance. Return true to override the player that will be
 +switched. */
 +#define EV_TeamBalance_GetPlayerForTeamSwitch(i, o) \
 +    /** source team index      */ i(int, MUTATOR_ARGV_0_int) \
 +    /** destination team index */ i(int, MUTATOR_ARGV_1_int) \
 +    /** is looking for bot     */ i(bool, MUTATOR_ARGV_2_bool) \
 +    /** player to switch       */ o(entity, MUTATOR_ARGV_3_entity) \
 +    /**/
 +MUTATOR_HOOKABLE(TeamBalance_GetPlayerForTeamSwitch,
 +      EV_TeamBalance_GetPlayerForTeamSwitch);
  
  /** copies variables for spectating "spectatee" to "this" */
  #define EV_SpectateCopy(i, o) \
@@@ -756,6 -745,30 +756,30 @@@ RESOURCE_* constants for resource types
        /**/
  MUTATOR_HOOKABLE(GiveResourceWithLimit, EV_GiveResourceWithLimit);
  
+ /** Called when some resource is being taken from an entity. See RESOURCE_* constants
+ for resource types. Return true to forbid giving. */
+ #define EV_TakeResource(i, o) \
+     /** receiver */      i(entity, MUTATOR_ARGV_0_entity) \
+     /** resource type */ i(int, MUTATOR_ARGV_1_int) \
+     /**/                 o(int, MUTATOR_ARGV_1_int) \
+     /** amount */        i(float, MUTATOR_ARGV_2_float) \
+     /**/                 o(float, MUTATOR_ARGV_2_float) \
+     /**/
+ MUTATOR_HOOKABLE(TakeResource, EV_TakeResource);
+ /** Called when some resource is being taken from an entity, with a limit. See
+ RESOURCE_* constants for resource types. Return true to forbid giving. */
+ #define EV_TakeResourceWithLimit(i, o) \
+     /** receiver */      i(entity, MUTATOR_ARGV_0_entity) \
+     /** resource type */ i(int, MUTATOR_ARGV_1_int) \
+     /**/                 o(int, MUTATOR_ARGV_1_int) \
+     /** amount */        i(float, MUTATOR_ARGV_2_float) \
+     /**/                 o(float, MUTATOR_ARGV_2_float) \
+     /** limit */         i(float, MUTATOR_ARGV_3_float) \
+     /**/                 o(float, MUTATOR_ARGV_3_float) \
+     /**/
+ MUTATOR_HOOKABLE(TakeResourceWithLimit, EV_TakeResourceWithLimit);
  /** called at when a player connect */
  #define EV_ClientConnect(i, o) \
      /** player */ i(entity, MUTATOR_ARGV_0_entity) \
@@@ -1028,9 -1041,9 +1052,9 @@@ MUTATOR_HOOKABLE(MonsterModel, EV_Monst
   * Called before player changes their team. Return true to block team change.
   */
  #define EV_Player_ChangeTeam(i, o) \
 -    /** player */         i(entity, MUTATOR_ARGV_0_entity) \
 -      /** current team */   i(float, MUTATOR_ARGV_1_float) \
 -      /** new team */       i(float, MUTATOR_ARGV_2_float) \
 +    /** player */             i(entity, MUTATOR_ARGV_0_entity) \
 +    /** current team index */ i(float, MUTATOR_ARGV_1_float) \
 +    /** new team index */     i(float, MUTATOR_ARGV_2_float) \
      /**/
  MUTATOR_HOOKABLE(Player_ChangeTeam, EV_Player_ChangeTeam);
  
   * Called after player has changed their team.
   */
  #define EV_Player_ChangedTeam(i, o) \
 -    /** player */         i(entity, MUTATOR_ARGV_0_entity) \
 -      /** old team */       i(float, MUTATOR_ARGV_1_float) \
 -      /** current team */   i(float, MUTATOR_ARGV_2_float) \
 +    /** player */             i(entity, MUTATOR_ARGV_0_entity) \
 +    /** old team index */     i(float, MUTATOR_ARGV_1_float) \
 +    /** current team index */ i(float, MUTATOR_ARGV_2_float) \
      /**/
  MUTATOR_HOOKABLE(Player_ChangedTeam, EV_Player_ChangedTeam);
  
diff --combined qcsrc/server/player.qc
index 03e0bfb64f6d6a80d2977ae34c31bf0bae6624af,4e39cf6b779297478ec2d3ed55170e2e13cfc90c..0b9e36b5503d79692d00a05212c45c68732acc75
@@@ -89,8 -89,8 +89,8 @@@ void CopyBody(entity this, float keepve
        clone.dphitcontentsmask = this.dphitcontentsmask;
        clone.death_time = this.death_time;
        clone.pain_finished = this.pain_finished;
-       clone.health = this.health;
-       clone.armorvalue = this.armorvalue;
+       SetResourceAmountExplicit(clone, RESOURCE_HEALTH, GetResourceAmount(this, RESOURCE_HEALTH));
+       SetResourceAmountExplicit(clone, RESOURCE_ARMOR, GetResourceAmount(this, RESOURCE_ARMOR));
        clone.armortype = this.armortype;
        clone.model = this.model;
        clone.modelindex = this.modelindex;
@@@ -173,7 -173,7 +173,7 @@@ void PlayerCorpseDamage(entity this, en
        vector v;
        Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, this, attacker);
  
-       v = healtharmor_applydamage(this.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage);
+       v = healtharmor_applydamage(GetResourceAmount(this, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, damage);
        take = v.x;
        save = v.y;
  
        if (take > 100)
                Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, this, attacker);
  
-       this.armorvalue = this.armorvalue - save;
-       this.health = this.health - take;
+       TakeResource(this, RESOURCE_ARMOR, save);
+       TakeResource(this, RESOURCE_HEALTH, take);
        // pause regeneration for 5 seconds
        this.pauseregen_finished = max(this.pauseregen_finished, time + autocvar_g_balance_pause_health_regen);
  
        this.dmg_take = this.dmg_take + take;//max(take - 10, 0);
        this.dmg_inflictor = inflictor;
  
-       if (this.health <= -autocvar_sv_gibhealth && this.alpha >= 0)
+       if (GetResourceAmount(this, RESOURCE_HEALTH) <= -autocvar_sv_gibhealth && this.alpha >= 0)
        {
                // don't use any animations as a gib
                this.frame = 0;
@@@ -310,8 -310,8 +310,8 @@@ void PlayerDamage(entity this, entity i
        vector v;
        float excess;
  
-       dh = max(this.health, 0);
-       da = max(this.armorvalue, 0);
+       dh = max(GetResourceAmount(this, RESOURCE_HEALTH), 0);
+       da = max(GetResourceAmount(this, RESOURCE_ARMOR), 0);
  
        if(!DEATH_ISSPECIAL(deathtype))
        {
        else
                Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, this, attacker);
  
-       v = healtharmor_applydamage(this.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage);
+       v = healtharmor_applydamage(GetResourceAmount(this, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, damage);
        take = v.x;
        save = v.y;
  
        }
  
        MUTATOR_CALLHOOK(PlayerDamage_SplitHealthArmor, inflictor, attacker, this, force, take, save, deathtype, damage);
-       take = bound(0, M_ARGV(4, float), this.health);
-       save = bound(0, M_ARGV(5, float), this.armorvalue);
+       take = bound(0, M_ARGV(4, float), GetResourceAmount(this, RESOURCE_HEALTH));
+       save = bound(0, M_ARGV(5, float), GetResourceAmount(this, RESOURCE_ARMOR));
        excess = max(0, damage - take - save);
  
        if(sound_allowed(MSG_BROADCAST, attacker))
        {
                if (!(this.flags & FL_GODMODE))
                {
-                       this.armorvalue = this.armorvalue - save;
-                       this.health = this.health - take;
+                       TakeResource(this, RESOURCE_ARMOR, save);
+                       TakeResource(this, RESOURCE_HEALTH, take);
                        // pause regeneration for 5 seconds
                        if(take)
                                this.pauseregen_finished = max(this.pauseregen_finished, time + autocvar_g_balance_pause_health_regen);
                                                                animdecide_setaction(this, ANIMACTION_PAIN2, true);
                                                }
                                        }
+                                       float myhp = GetResourceAmount(this, RESOURCE_HEALTH);
+                                       if(myhp > 1)
+                                       if(myhp < 25 || !(DEATH_WEAPONOF(deathtype).spawnflags & WEP_FLAG_CANCLIMB) || take > 20 || attacker != this)
                                        if(sound_allowed(MSG_BROADCAST, attacker))
-                                       if(this.health < 25 || !(DEATH_WEAPONOF(deathtype).spawnflags & WEP_FLAG_CANCLIMB) || take > 20 || attacker != this)
-                                       if(this.health > 1)
                                        // exclude pain sounds for laserjumps as long as you aren't REALLY low on health and would die of the next two
                                        {
                                                if(deathtype == DEATH_FALL.m_id)
                                                        PlayerSound(this, playersound_fall, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
-                                               else if(this.health > 75)
+                                               else if(myhp > 75)
                                                        PlayerSound(this, playersound_pain100, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
-                                               else if(this.health > 50)
+                                               else if(myhp > 50)
                                                        PlayerSound(this, playersound_pain75, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
-                                               else if(this.health > 25)
+                                               else if(myhp > 25)
                                                        PlayerSound(this, playersound_pain50, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
                                                else
                                                        PlayerSound(this, playersound_pain25, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
  
                        // throw off bot aim temporarily
                        float shake;
-                       if(IS_BOT_CLIENT(this) && this.health >= 1)
+                       if(IS_BOT_CLIENT(this) && GetResourceAmount(this, RESOURCE_HEALTH) >= 1)
                        {
                                shake = damage * 5 / (bound(0,skill,100) + 1);
                                this.v_angle_x = this.v_angle.x + (random() * 2 - 1) * shake;
                valid_damage_for_weaponstats = true;
        }
  
-       dh = dh - max(this.health, 0);
-       da = da - max(this.armorvalue, 0);
+       dh = dh - max(GetResourceAmount(this, RESOURCE_HEALTH), 0);
+       da = da - max(GetResourceAmount(this, RESOURCE_ARMOR), 0);
        if(valid_damage_for_weaponstats)
        {
                WeaponStats_LogDamage(awep.m_id, abot, this.(weaponentity).m_weapon.m_id, vbot, dh + da);
  
        MUTATOR_CALLHOOK(PlayerDamaged, attacker, this, dh, da, hitloc, deathtype, damage);
  
-       if (this.health < 1)
+       if (GetResourceAmount(this, RESOURCE_HEALTH) < 1)
        {
                float defer_ClientKill_Now_TeamChange;
                defer_ClientKill_Now_TeamChange = false;
  
                // player could have been miraculously resuscitated ;)
                // e.g. players in freezetag get frozen, they don't really die
-               if(this.health >= 1 || !(IS_PLAYER(this) || this.classname == "body"))
+               if(GetResourceAmount(this, RESOURCE_HEALTH) >= 1 || !(IS_PLAYER(this) || this.classname == "body"))
                        return;
  
                if (!this.respawn_time) // can be set in the mutator hook PlayerDies
                // when we get here, player actually dies
  
                Unfreeze(this); // remove any icy remains
-               this.health = 0; // Unfreeze resets health, so we need to set it back
+               SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0); // Unfreeze resets health, so we need to set it back
  
                // clear waypoints
                WaypointSprite_PlayerDead(this);
        }
  }
  
 -bool MoveToTeam(entity client, int team_colour, int type)
 -{
 -      int lockteams_backup = lockteams;  // backup any team lock
 -      lockteams = 0;  // disable locked teams
 -      TeamchangeFrags(client);  // move the players frags
 -      if (!SetPlayerTeamSimple(client, team_colour))
 -      {
 -              return false;
 -      }
 -      Damage(client, client, client, 100000, DEATH_AUTOTEAMCHANGE.m_id, DMG_NOWEP, client.origin, '0 0 0');  // kill the player
 -      lockteams = lockteams_backup;  // restore the team lock
 -      LogTeamchange(client.playerid, client.team, type);
 -      return true;
 -}
 -
  /**
   * message "": do not say, just test flood control
   * return value: