]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/client.qc
Merge branch 'Lyberta/TeamplayOverhaul2' into 'master'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / client.qc
index 0d344b7f2d6807c96768f7b2e5eca6e819e8fb4f..8e6a26961b02c6b845103254f56222496d24a10e 100644 (file)
@@ -10,7 +10,6 @@
 #include "miscfunctions.qh"
 #include "portals.qh"
 #include "teamplay.qh"
-#include "playerdemo.qh"
 #include "spawnpoints.qh"
 #include "resources.qh"
 #include "g_damage.qh"
@@ -18,6 +17,7 @@
 #include "g_hook.qh"
 #include "command/common.qh"
 #include "command/vote.qh"
+#include "clientkill.qh"
 #include "cheats.qh"
 #include "g_world.qh"
 #include "race.qh"
@@ -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);
@@ -285,25 +285,11 @@ void PutObserverInServer(entity this)
 
        WaypointSprite_PlayerDead(this);
 
-       if (mutator_returnvalue) {
-           // mutator prevents resetting teams+score
-       } else {
-               Player_SetTeamIndex(this, -1);
-               this.frags = FRAGS_SPECTATOR;
-        PlayerScore_Clear(this);  // clear scores when needed
-    }
-
        if (CS(this).killcount != FRAGS_SPECTATOR)
        {
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_SPECTATE, this.netname);
                if(!game_stopped)
                if(autocvar_g_chat_nospectators == 1 || (!warmup_stage && autocvar_g_chat_nospectators == 2))
                        Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_CHAT_NOSPECTATORS);
-
-               if(!CS(this).just_joined)
-                       LogTeamchange(this.playerid, -1, TEAM_CHANGE_SPECTATOR);
-               else
-                       CS(this).just_joined = false;
        }
 
        accuracy_resend(this);
@@ -322,15 +308,14 @@ void PutObserverInServer(entity this)
        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;
@@ -379,6 +364,7 @@ void PutObserverInServer(entity this)
        this.oldvelocity = this.velocity;
        this.fire_endtime = -1;
        this.event_damage = func_null;
+       this.event_heal = func_null;
 
        for(int slot = 0; slot < MAX_AXH; ++slot)
        {
@@ -388,6 +374,16 @@ void PutObserverInServer(entity this)
                if(axh.owner == this && axh != NULL && !wasfreed(axh))
                        delete(axh);
        }
+       
+       if (mutator_returnvalue)
+       {
+               // mutator prevents resetting teams+score
+       }
+       else
+       {
+               SetPlayerTeam(this, -1, TEAM_CHANGE_SPECTATOR);
+               this.frags = FRAGS_SPECTATOR;
+    }
 }
 
 int player_getspecies(entity this)
@@ -520,7 +516,7 @@ void PutPlayerInServer(entity this)
        accuracy_resend(this);
 
        if (this.team < 0)
-               TeamBalance_JoinBestTeam(this, true);
+               TeamBalance_JoinBestTeam(this);
 
        entity spot = SelectSpawnPoint(this, false);
        if (!spot) {
@@ -558,8 +554,8 @@ void PutPlayerInServer(entity this)
                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);
@@ -568,8 +564,8 @@ void PutPlayerInServer(entity this)
                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)
                {
@@ -673,6 +669,7 @@ void PutPlayerInServer(entity this)
        STAT(HUD, this) = HUD_NORMAL;
 
        this.event_damage = PlayerDamage;
+       this.event_heal = PlayerHeal;
 
        if(!this.bot_attack)
                IL_PUSH(g_bot_targets, this);
@@ -752,6 +749,14 @@ void PutPlayerInServer(entity this)
 
        MUTATOR_CALLHOOK(PlayerWeaponSelect, this);
 
+       if (CS(this).impulse) ImpulseCommands(this);
+
+       for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+       {
+               .entity weaponentity = weaponentities[slot];
+               W_WeaponFrame(this, weaponentity);
+       }
+
        if (!warmup_stage && !this.alivetime)
                this.alivetime = time;
 
@@ -892,202 +897,6 @@ void DecodeLevelParms(entity this)
        MUTATOR_CALLHOOK(DecodeLevelParms);
 }
 
-/*
-=============
-ClientKill
-
-Called when a client types 'kill' in the console
-=============
-*/
-
-.float clientkill_nexttime;
-void ClientKill_Now_TeamChange(entity this)
-{
-       if(this.killindicator_teamchange == -1)
-       {
-               TeamBalance_JoinBestTeam(this, true);
-       }
-       else if(this.killindicator_teamchange == -2)
-       {
-               if(blockSpectators)
-                       Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_SPECTATE_WARNING, autocvar_g_maxplayers_spectator_blocktime);
-               PutObserverInServer(this);
-       }
-       else
-               SV_ChangeTeam(this, this.killindicator_teamchange - 1);
-       this.killindicator_teamchange = 0;
-}
-
-void ClientKill_Now(entity this)
-{
-       if(this.vehicle)
-       {
-           vehicles_exit(this.vehicle, VHEF_RELEASE);
-           if(!this.killindicator_teamchange)
-           {
-            this.vehicle_health = -1;
-            Damage(this, this, this, 1 , DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0');
-           }
-       }
-
-       if(this.killindicator && !wasfreed(this.killindicator))
-               delete(this.killindicator);
-
-       this.killindicator = NULL;
-
-       if(this.killindicator_teamchange)
-               ClientKill_Now_TeamChange(this);
-
-       if (!IS_SPEC(this) && !IS_OBSERVER(this) && MUTATOR_CALLHOOK(ClientKill_Now, this) == false)
-       {
-               Damage(this, this, this, 100000, DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0');
-       }
-
-       // now I am sure the player IS dead
-}
-void KillIndicator_Think(entity this)
-{
-       if (game_stopped)
-       {
-               this.owner.killindicator = NULL;
-               delete(this);
-               return;
-       }
-
-       if (this.owner.alpha < 0 && !this.owner.vehicle)
-       {
-               this.owner.killindicator = NULL;
-               delete(this);
-               return;
-       }
-
-       if(this.cnt <= 0)
-       {
-               ClientKill_Now(this.owner);
-               return;
-       }
-    else if(this.health == 1) // health == 1 means that it's silent
-    {
-        this.nextthink = time + 1;
-        this.cnt -= 1;
-    }
-       else
-       {
-               if(this.cnt <= 10)
-                       setmodel(this, MDL_NUM(this.cnt));
-               if(IS_REAL_CLIENT(this.owner))
-               {
-                       if(this.cnt <= 10)
-                               { Send_Notification(NOTIF_ONE, this.owner, MSG_ANNCE, Announcer_PickNumber(CNT_KILL, this.cnt)); }
-               }
-               this.nextthink = time + 1;
-               this.cnt -= 1;
-       }
-}
-
-float clientkilltime;
-void ClientKill_TeamChange (entity this, float targetteam) // 0 = don't change, -1 = auto, -2 = spec
-{
-       float killtime;
-       float starttime;
-
-       if (game_stopped)
-               return;
-
-       killtime = autocvar_g_balance_kill_delay;
-
-    if(MUTATOR_CALLHOOK(ClientKill, this, killtime))
-       return;
-    killtime = M_ARGV(1, float);
-
-       this.killindicator_teamchange = targetteam;
-
-    if(!this.killindicator)
-       {
-               if(!IS_DEAD(this))
-               {
-                       killtime = max(killtime, this.clientkill_nexttime - time);
-                       this.clientkill_nexttime = time + killtime + autocvar_g_balance_kill_antispam;
-               }
-
-               if(killtime <= 0 || !IS_PLAYER(this) || IS_DEAD(this))
-               {
-                       ClientKill_Now(this);
-               }
-               else
-               {
-                       starttime = max(time, clientkilltime);
-
-                       this.killindicator = spawn();
-                       this.killindicator.owner = this;
-                       this.killindicator.scale = 0.5;
-                       setattachment(this.killindicator, this, "");
-                       setorigin(this.killindicator, '0 0 52');
-                       setthink(this.killindicator, KillIndicator_Think);
-                       this.killindicator.nextthink = starttime + (this.lip) * 0.05;
-                       clientkilltime = max(clientkilltime, this.killindicator.nextthink + 0.05);
-                       this.killindicator.cnt = ceil(killtime);
-                       this.killindicator.count = bound(0, ceil(killtime), 10);
-                       //sprint(this, strcat("^1You'll be dead in ", ftos(this.killindicator.cnt), " seconds\n"));
-
-                       IL_EACH(g_clones, it.enemy == this && !(it.effects & CSQCMODEL_EF_RESPAWNGHOST),
-                       {
-                               it.killindicator = spawn();
-                               it.killindicator.owner = it;
-                               it.killindicator.scale = 0.5;
-                               setattachment(it.killindicator, it, "");
-                               setorigin(it.killindicator, '0 0 52');
-                               setthink(it.killindicator, KillIndicator_Think);
-                               it.killindicator.nextthink = starttime + (it.lip) * 0.05;
-                               //clientkilltime = max(clientkilltime, it.killindicator.nextthink + 0.05);
-                               it.killindicator.cnt = ceil(killtime);
-                       });
-                       this.lip = 0;
-               }
-       }
-       if(this.killindicator)
-       {
-               if(targetteam == 0) // just die
-               {
-                       this.killindicator.colormod = '0 0 0';
-                       if(IS_REAL_CLIENT(this))
-                       if(this.killindicator.cnt > 0)
-                               Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_TEAMCHANGE_SUICIDE, this.killindicator.cnt);
-               }
-               else if(targetteam == -1) // auto
-               {
-                       this.killindicator.colormod = '0 1 0';
-                       if(IS_REAL_CLIENT(this))
-                       if(this.killindicator.cnt > 0)
-                               Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_TEAMCHANGE_AUTO, this.killindicator.cnt);
-               }
-               else if(targetteam == -2) // spectate
-               {
-                       this.killindicator.colormod = '0.5 0.5 0.5';
-                       if(IS_REAL_CLIENT(this))
-                       if(this.killindicator.cnt > 0)
-                               Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_TEAMCHANGE_SPECTATE, this.killindicator.cnt);
-               }
-               else
-               {
-                       this.killindicator.colormod = Team_ColorRGB(targetteam);
-                       if(IS_REAL_CLIENT(this))
-                       if(this.killindicator.cnt > 0)
-                               Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, APP_TEAM_NUM(targetteam, CENTER_TEAMCHANGE), this.killindicator.cnt);
-               }
-       }
-
-}
-
-void ClientKill (entity this)
-{
-       if(game_stopped) return;
-       if(this.player_blocked) return;
-       if(STAT(FROZEN, this)) return;
-
-       ClientKill_TeamChange(this, 0);
-}
-
 void FixClientCvars(entity e)
 {
        // send prediction settings to the client
@@ -1253,43 +1062,11 @@ void ClientConnect(entity this)
        TRANSMUTE(Client, this);
        CS(this).version_nagtime = time + 10 + random() * 10;
 
-       // identify the right forced team
-       if (autocvar_g_campaign)
-       {
-               if (IS_REAL_CLIENT(this)) // only players, not bots
-               {
-                       switch (autocvar_g_campaign_forceteam)
-                       {
-                               case 1: this.team_forced = NUM_TEAM_1; break;
-                               case 2: this.team_forced = NUM_TEAM_2; break;
-                               case 3: this.team_forced = NUM_TEAM_3; break;
-                               case 4: this.team_forced = NUM_TEAM_4; break;
-                               default: this.team_forced = 0;
-                       }
-               }
-       }
-       else if (PlayerInList(this, autocvar_g_forced_team_red))    this.team_forced = NUM_TEAM_1;
-       else if (PlayerInList(this, autocvar_g_forced_team_blue))   this.team_forced = NUM_TEAM_2;
-       else if (PlayerInList(this, autocvar_g_forced_team_yellow)) this.team_forced = NUM_TEAM_3;
-       else if (PlayerInList(this, autocvar_g_forced_team_pink))   this.team_forced = NUM_TEAM_4;
-       else switch (autocvar_g_forced_team_otherwise)
-       {
-               default: this.team_forced = 0; break;
-               case "red": this.team_forced = NUM_TEAM_1; break;
-               case "blue": this.team_forced = NUM_TEAM_2; break;
-               case "yellow": this.team_forced = NUM_TEAM_3; break;
-               case "pink": this.team_forced = NUM_TEAM_4; break;
-               case "spectate":
-               case "spectator":
-                       this.team_forced = -1;
-                       break;
-       }
-       if (!teamplay && this.team_forced > 0) this.team_forced = 0;
+       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_CONNECT, this.netname);
 
-       int playerid_save = this.playerid;
-       this.playerid = 0; // silent
-       TeamBalance_JoinBestTeam(this, false); // if the team number is valid, keep it
-       this.playerid = playerid_save;
+       bot_clientconnect(this);
+
+       Player_DetermineForcedTeam(this);
 
        TRANSMUTE(Observer, this);
 
@@ -1304,15 +1081,8 @@ void ClientConnect(entity 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, TEAM_CHANGE_CONNECT);
-
        CS(this).just_joined = true;  // stop spamming the eventlog with additional lines when the client connects
 
-       if(teamplay && IS_PLAYER(this))
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_JOIN_CONNECT_TEAM), this.netname);
-       else
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_CONNECT, this.netname);
-
        stuffcmd(this, clientstuff, "\n");
        stuffcmd(this, "cl_particles_reloadeffects\n"); // TODO do we still need this?
 
@@ -1578,7 +1348,7 @@ void DebugPrintToChatTeam(int team_num, string text)
 
 void play_countdown(entity this, float finished, Sound samp)
 {
-    TC(Sound, samp);
+       TC(Sound, samp);
        if(IS_REAL_CLIENT(this))
                if(floor(finished - time - frametime) != floor(finished - time))
                        if(finished - time < 6)
@@ -1789,13 +1559,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);
@@ -1811,20 +1585,10 @@ void player_regen(entity this)
                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;
+               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));
        }
-       if (this.armorvalue > RESOURCE_AMOUNT_HARD_LIMIT)
-       {
-               this.armorvalue = RESOURCE_AMOUNT_HARD_LIMIT;
-       }
-       // End hack.
 }
 
 bool zoomstate_set;
@@ -1848,7 +1612,7 @@ void GetPressedKeys(entity this)
        keys = BITSET(keys, KEY_LEFT,           CS(this).movement.y < 0);
 
        keys = BITSET(keys, KEY_JUMP,           PHYS_INPUT_BUTTON_JUMP(this));
-       keys = BITSET(keys, KEY_CROUCH,         PHYS_INPUT_BUTTON_CROUCH(this));
+       keys = BITSET(keys, KEY_CROUCH,         IS_DUCKED(this)); // workaround: player can't un-crouch until their path is clear, so we keep the button held here
        keys = BITSET(keys, KEY_ATCK,           PHYS_INPUT_BUTTON_ATCK(this));
        keys = BITSET(keys, KEY_ATCK2,          PHYS_INPUT_BUTTON_ATCK2(this));
        CS(this).pressedkeys = keys; // store for other users
@@ -1864,20 +1628,20 @@ spectate mode routines
 
 void SpectateCopy(entity this, entity spectatee)
 {
-    TC(Client, this); TC(Client, spectatee);
+       TC(Client, this); TC(Client, spectatee);
 
        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);
@@ -2106,7 +1870,7 @@ void ShowRespawnCountdown(entity this)
 .bool team_selected;
 bool ShowTeamSelection(entity this)
 {
-       if(!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || this.team_selected || (CS(this).wasplayer && autocvar_g_changeteam_banned) || this.team_forced > 0)
+       if (!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || this.team_selected || (CS(this).wasplayer && autocvar_g_changeteam_banned) || Player_HasRealForcedTeam(this))
                return false;
        stuffcmd(this, "menu_showteamselect\n");
        return true;
@@ -2117,7 +1881,7 @@ void Join(entity this)
 
        if(!this.team_selected)
        if(autocvar_g_campaign || autocvar_g_balance_teams)
-               TeamBalance_JoinBestTeam(this, true);
+               TeamBalance_JoinBestTeam(this);
 
        if(autocvar_g_campaign)
                campaign_bots_may_start = true;
@@ -2129,7 +1893,6 @@ void Join(entity this)
        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);
        }
        else
                Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_PLAY, this.netname);
@@ -2154,7 +1917,7 @@ int nJoinAllowed(entity this, entity ignore)
                        return 0;
        }
 
-       if(this && this.team_forced < 0)
+       if(this && (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
                return 0; // forced spectators can never join
 
        // TODO simplify this
@@ -2376,7 +2139,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 +2395,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)
@@ -2641,9 +2404,9 @@ void PlayerPreThink (entity this)
                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);
@@ -2706,7 +2469,7 @@ void PlayerPreThink (entity this)
                // don't do this in ClientConnect
                // many things can go wrong if a client is spawned as player on connection
                if (MUTATOR_CALLHOOK(AutoJoinOnConnection, this)
-                       || (!(autocvar_sv_spectate || autocvar_g_campaign || this.team_forced < 0)
+                       || (!(autocvar_sv_spectate || autocvar_g_campaign || (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
                                && (!teamplay || autocvar_g_balance_teams)))
                {
                        campaign_bots_may_start = true;
@@ -2885,12 +2648,10 @@ 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);
     }
 
-       playerdemo_write(this);
-
        CSQCMODEL_AUTOUPDATE(this);
 }