]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/client.qc
Merge branch 'master' into LegendaryGuard/cyber
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / client.qc
index e0f0e2b19ef665ae190020c295f6a24b1100e7ba..8d0e04d98eb3f2d14602f881df1f99aeb5b8227e 100644 (file)
@@ -6,7 +6,6 @@
 #include <common/effects/qc/globalsound.qh>
 #include <common/ent_cs.qh>
 #include <common/gamemodes/_mod.qh>
-#include <common/gamemodes/gamemode/lms/sv_lms.qh>
 #include <common/gamemodes/gamemode/nexball/sv_nexball.qh>
 #include <common/items/_mod.qh>
 #include <common/items/inventory.qh>
@@ -99,6 +98,16 @@ void send_CSQC_teamnagger() {
        WriteHeader(MSG_BROADCAST, TE_CSQC_TEAMNAGGER);
 }
 
+void send_TeamNames(int channel, entity to) {
+       msg_entity = to;
+       
+       WriteHeader(channel, TE_CSQC_TEAMNAMES);
+       WriteString(channel, autocvar_g_teamnames_red);
+       WriteString(channel, autocvar_g_teamnames_blue);
+       WriteString(channel, autocvar_g_teamnames_yellow);
+       WriteString(channel, autocvar_g_teamnames_pink);
+}
+
 int CountSpectators(entity player, entity to)
 {
        if(!player) { return 0; } // not sure how, but best to be safe
@@ -236,9 +245,9 @@ void setplayermodel(entity e, string modelname)
 }
 
 /** putting a client as observer in the server */
-void PutObserverInServer(entity this)
+void PutObserverInServer(entity this, bool is_forced)
 {
-       bool mutator_returnvalue = MUTATOR_CALLHOOK(MakePlayerObserver, this);
+       bool mutator_returnvalue = MUTATOR_CALLHOOK(MakePlayerObserver, this, is_forced);
        PlayerState_detach(this);
 
        if (IS_PLAYER(this))
@@ -522,6 +531,36 @@ void FixPlayermodel(entity player)
                                setcolor(player, stof(autocvar_sv_defaultplayercolors));
 }
 
+void ResetPlayerResources(entity this)
+{
+       if (warmup_stage) {
+               SetResource(this, RES_SHELLS, warmup_start_ammo_shells);
+               SetResource(this, RES_BULLETS, warmup_start_ammo_nails);
+               SetResource(this, RES_ROCKETS, warmup_start_ammo_rockets);
+               SetResource(this, RES_CELLS, warmup_start_ammo_cells);
+               SetResource(this, RES_PLASMA, warmup_start_ammo_plasma);
+               SetResource(this, RES_FUEL, warmup_start_ammo_fuel);
+               SetResource(this, RES_HEALTH, warmup_start_health);
+               SetResource(this, RES_ARMOR, warmup_start_armorvalue);
+               STAT(WEAPONS, this) = WARMUP_START_WEAPONS;
+       } else {
+               SetResource(this, RES_SHELLS, start_ammo_shells);
+               SetResource(this, RES_BULLETS, start_ammo_nails);
+               SetResource(this, RES_ROCKETS, start_ammo_rockets);
+               SetResource(this, RES_CELLS, start_ammo_cells);
+               SetResource(this, RES_PLASMA, start_ammo_plasma);
+               SetResource(this, RES_FUEL, start_ammo_fuel);
+               SetResource(this, RES_HEALTH, start_health);
+               SetResource(this, RES_ARMOR, start_armorvalue);
+               STAT(WEAPONS, this) = start_weapons;
+               if (MUTATOR_CALLHOOK(ForbidRandomStartWeapons, this) == false)
+               {
+                       GiveRandomWeapons(this, random_start_weapons_count,
+                               autocvar_g_random_start_weapons, random_start_ammo);
+               }
+       }
+}
+
 void PutPlayerInServer(entity this)
 {
        if (this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE);
@@ -561,39 +600,19 @@ void PutPlayerInServer(entity this)
        this.takedamage = DAMAGE_AIM;
        this.effects = EF_TELEPORT_BIT | EF_RESTARTANIM_BIT;
 
-       if (warmup_stage) {
-               SetResource(this, RES_SHELLS, warmup_start_ammo_shells);
-               SetResource(this, RES_BULLETS, warmup_start_ammo_nails);
-               SetResource(this, RES_ROCKETS, warmup_start_ammo_rockets);
-               SetResource(this, RES_CELLS, warmup_start_ammo_cells);
-               SetResource(this, RES_PLASMA, warmup_start_ammo_plasma);
-               SetResource(this, RES_FUEL, warmup_start_ammo_fuel);
-               SetResource(this, RES_HEALTH, warmup_start_health);
-               SetResource(this, RES_ARMOR, warmup_start_armorvalue);
-               STAT(WEAPONS, this) = WARMUP_START_WEAPONS;
-       } else {
-               SetResource(this, RES_SHELLS, start_ammo_shells);
-               SetResource(this, RES_BULLETS, start_ammo_nails);
-               SetResource(this, RES_ROCKETS, start_ammo_rockets);
-               SetResource(this, RES_CELLS, start_ammo_cells);
-               SetResource(this, RES_PLASMA, start_ammo_plasma);
-               SetResource(this, RES_FUEL, start_ammo_fuel);
-               SetResource(this, RES_HEALTH, start_health);
-               SetResource(this, RES_ARMOR, start_armorvalue);
-               STAT(WEAPONS, this) = start_weapons;
-               if (MUTATOR_CALLHOOK(ForbidRandomStartWeapons, this) == false)
-               {
-                       GiveRandomWeapons(this, random_start_weapons_count,
-                               autocvar_g_random_start_weapons, random_start_ammo);
-               }
-       }
+       ResetPlayerResources(this);
+       
        SetSpectatee_status(this, 0);
 
        PS(this).dual_weapons = '0 0 0';
 
+       if(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS)
+               StatusEffects_apply(STATUSEFFECT_Superweapons, this, time + autocvar_g_balance_superweapons_time, 0);
+
        this.items = start_items;
 
-       this.spawnshieldtime = time + autocvar_g_spawnshieldtime;
+       float shieldtime = time + autocvar_g_spawnshieldtime;
+
        this.pauserotarmor_finished = time + autocvar_g_balance_pause_armor_rot_spawn;
        this.pauserothealth_finished = time + autocvar_g_balance_pause_health_rot_spawn;
        this.pauserotfuel_finished = time + autocvar_g_balance_pause_fuel_rot_spawn;
@@ -601,19 +620,20 @@ void PutPlayerInServer(entity this)
        if (!sv_ready_restart_after_countdown && time < game_starttime)
        {
                float f = game_starttime - time;
-               this.spawnshieldtime += f;
+               shieldtime += f;
                this.pauserotarmor_finished += f;
                this.pauserothealth_finished += f;
                this.pauseregen_finished += f;
        }
 
+       StatusEffects_apply(STATUSEFFECT_SpawnShield, this, shieldtime, 0);
+
        this.damageforcescale = autocvar_g_player_damageforcescale;
        this.death_time = 0;
        this.respawn_flags = 0;
        this.respawn_time = 0;
        STAT(RESPAWN_TIME, this) = 0;
-       bool q3dfcompat = autocvar_sv_q3defragcompat && autocvar_sv_q3defragcompat_changehitbox;
-       this.scale = ((q3dfcompat) ? 0.9 : autocvar_sv_player_scale);
+       this.scale = ((q3compat && autocvar_sv_q3compat_changehitbox) ? 0.9 : autocvar_sv_player_scale);
        this.fade_time = 0;
        this.pain_finished = 0;
        this.pushltime = 0;
@@ -814,11 +834,13 @@ void PutClientInServer(entity this)
        MUTATOR_CALLHOOK(PutClientInServer, this);
 
        if (IS_OBSERVER(this)) {
-               PutObserverInServer(this);
+               PutObserverInServer(this, false);
        } else if (IS_PLAYER(this)) {
                PutPlayerInServer(this);
        }
-
+       // send team names
+       if(teamplay && IS_REAL_CLIENT(this))
+               send_TeamNames(MSG_ONE, this);
        bot_relinkplayerlist();
 }
 
@@ -858,6 +880,10 @@ void ClientInit_misc(entity this)
        WriteByte(channel, this.cnt * 255.0); // g_balance_damagepush_speedfactor
        WriteByte(channel, serverflags);
        WriteCoord(channel, autocvar_g_trueaim_minrange);
+       
+       // z411 send full hostname
+       WriteString(channel, (autocvar_hostname_full != "" ? autocvar_hostname_full : autocvar_hostname));
+       WriteString(channel, autocvar_sv_motd_permanent);
 }
 
 void ClientInit_CheckUpdate(entity this)
@@ -1038,19 +1064,17 @@ string getwelcomemessage(entity this)
                modifications = strcat(modifications, ", Weapons stay");
        if(autocvar_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\nserver is ^9", autocvar_hostname, "^8\n");
+       //string versionmessage = GetClientVersionMessage(this);
+       //string s = strcat(versionmessage, "^8\n^9", (autocvar_hostname_full ? autocvar_hostname_full : autocvar_hostname));
+       //string s = strcat(versionmessage, "^8\n^8\nserver is ^9", autocvar_hostname, "^8\n");
+       string s = (autocvar_hostname_full != "" ? autocvar_hostname_full : autocvar_hostname);
 
-       s = strcat(s, "^8\nmatch type is ^1", gamemode_name, "^8\n");
+       s = strcat(s, "^8\n^7", gamemode_name);
 
        if(modifications != "")
-               s = strcat(s, "^8\nactive modifications: ^3", modifications, "^8\n");
+               s = strcat(s, "^7 | ^3", modifications);
 
        if(cache_lastmutatormsg != autocvar_g_mutatormsg)
        {
@@ -1059,7 +1083,7 @@ string getwelcomemessage(entity this)
        }
 
        if (cache_mutatormsg != "") {
-               s = strcat(s, "\n\n^8special gameplay tips: ^7", cache_mutatormsg);
+               s = strcat(s, "\n^8tips: ^7", cache_mutatormsg);
        }
 
        string mutator_msg = "";
@@ -1070,13 +1094,11 @@ string getwelcomemessage(entity this)
 
        string motd = autocvar_sv_motd;
        if (motd != "") {
-               s = strcat(s, "\n\n^8MOTD: ^7", strreplace("\\n", "\n", motd));
+               s = strcat(s, "\n\n^7", strreplace("\\n", "\n", motd));
        }
        return s;
 }
 
-bool autocvar_sv_qcphysics = true; // TODO this is for testing - remove when qcphysics work
-
 /**
 =============
 ClientConnect
@@ -1446,14 +1468,12 @@ void play_countdown(entity this, float finished, Sound samp)
 
 void player_powerups_remove_all(entity this)
 {
-       if (this.items & (ITEM_Strength.m_itemid | ITEM_Shield.m_itemid | IT_SUPERWEAPON))
+       if (this.items & IT_SUPERWEAPON)
        {
                // don't play the poweroff sound when the game restarts or the player disconnects
                if (time > game_starttime + 1 && IS_CLIENT(this))
                        sound(this, CH_INFO, SND_POWEROFF, VOL_BASE, ATTEN_NORM);
                stopsound(this, CH_TRIGGER_SINGLE); // get rid of the pickup sound
-               this.items &= ~ITEM_Strength.m_itemid;
-               this.items &= ~ITEM_Shield.m_itemid;
                this.items -= (this.items & IT_SUPERWEAPON);
        }
 }
@@ -1465,7 +1485,7 @@ void player_powerups(entity this)
        else
                this.modelflags &= ~MF_ROCKET;
 
-       this.effects &= ~(EF_RED | EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT | EF_NODEPTHTEST);
+       this.effects &= ~EF_NODEPTHTEST;
 
        if (IS_DEAD(this))
                player_powerups_remove_all(this);
@@ -1478,48 +1498,7 @@ void player_powerups(entity this)
 
        if (!MUTATOR_IS_ENABLED(mutator_instagib))
        {
-               if (this.items & ITEM_Strength.m_itemid)
-               {
-                       play_countdown(this, StatusEffects_gettime(STATUSEFFECT_Strength, this), SND_POWEROFF);
-                       this.effects = this.effects | (EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT);
-                       if (time > StatusEffects_gettime(STATUSEFFECT_Strength, this))
-                       {
-                               this.items = this.items - (this.items & ITEM_Strength.m_itemid);
-                               //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERDOWN_STRENGTH, this.netname);
-                               Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_POWERDOWN_STRENGTH);
-                       }
-               }
-               else
-               {
-                       if (time < StatusEffects_gettime(STATUSEFFECT_Strength, this))
-                       {
-                               this.items = this.items | ITEM_Strength.m_itemid;
-                               if(!g_cts)
-                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERUP_STRENGTH, this.netname);
-                               Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_POWERUP_STRENGTH);
-                       }
-               }
-               if (this.items & ITEM_Shield.m_itemid)
-               {
-                       play_countdown(this, StatusEffects_gettime(STATUSEFFECT_Shield, this), SND_POWEROFF);
-                       this.effects = this.effects | (EF_RED | EF_ADDITIVE | EF_FULLBRIGHT);
-                       if (time > StatusEffects_gettime(STATUSEFFECT_Shield, this))
-                       {
-                               this.items = this.items - (this.items & ITEM_Shield.m_itemid);
-                               //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERDOWN_SHIELD, this.netname);
-                               Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_POWERDOWN_SHIELD);
-                       }
-               }
-               else
-               {
-                       if (time < StatusEffects_gettime(STATUSEFFECT_Shield, this))
-                       {
-                               this.items = this.items | ITEM_Shield.m_itemid;
-                               if(!g_cts)
-                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERUP_SHIELD, this.netname);
-                               Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_POWERUP_SHIELD);
-                       }
-               }
+               // NOTE: superweapons are a special case and as such are handled here instead of the status effects system
                if (this.items & IT_SUPERWEAPON)
                {
                        if (!(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS))
@@ -1576,10 +1555,6 @@ void player_powerups(entity this)
        if(autocvar_g_fullbrightplayers)
                this.effects = this.effects | EF_FULLBRIGHT;
 
-       if (time >= game_starttime)
-       if (time < this.spawnshieldtime)
-               this.effects = this.effects | (EF_ADDITIVE | EF_FULLBRIGHT);
-
        MUTATOR_CALLHOOK(PlayerPowerups, this, items_prev);
 }
 
@@ -1603,7 +1578,9 @@ float CalcRot(float current, float stable, float rotfactor, float rotframetime)
                return max(stable, current + (stable - current) * rotfactor * rotframetime);
 }
 
-void RotRegen(entity this, int res, float regenstable, float regenfactor, float regenlinear, float regenframetime, float rotstable, float rotfactor, float rotlinear, float rotframetime, float limit_mod)
+void RotRegen(entity this, int res, float limit_mod,
+       float regenstable, float regenfactor, float regenlinear, float regenframetime,
+       float rotstable, float rotfactor, float rotlinear, float rotframetime)
 {
        float old = GetResource(this, res);
        float current = old;
@@ -1656,19 +1633,27 @@ void player_regen(entity this)
        regen_health_stable = M_ARGV(9, float);
        regen_health_rotstable = M_ARGV(10, float);
 
+       float rotstable, regenstable, rotframetime, regenframetime;
+
        if(!mutator_returnvalue)
        if(!STAT(FROZEN, this))
        {
-               float maxa = autocvar_g_balance_armor_rotstable;
-               float mina = autocvar_g_balance_armor_regenstable;
-
-               RotRegen(this, RES_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), limit_mod);
+               regenstable = autocvar_g_balance_armor_regenstable;
+               rotstable = autocvar_g_balance_armor_rotstable;
+               regenframetime = (time > this.pauseregen_finished) ? (regen_mod * frametime) : 0;
+               rotframetime = (time > this.pauserotarmor_finished) ? (rot_mod * frametime) : 0;
+               RotRegen(this, RES_ARMOR, limit_mod,
+                       regenstable, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, regenframetime,
+                       rotstable, autocvar_g_balance_armor_rot, autocvar_g_balance_armor_rotlinear, rotframetime);
 
-               RotRegen(this, RES_HEALTH, regen_health_stable * max_mod, regen_health, regen_health_linear,
-                       regen_mod * frametime * (time > this.pauseregen_finished), regen_health_rotstable * max_mod, regen_health_rot, regen_health_rotlinear,
-                       rot_mod * frametime * (time > this.pauserothealth_finished), limit_mod);
+               // NOTE: max_mod is only applied to health
+               regenstable = regen_health_stable * max_mod;
+               rotstable = regen_health_rotstable * max_mod;
+               regenframetime = (time > this.pauseregen_finished) ? (regen_mod * frametime) : 0;
+               rotframetime = (time > this.pauserothealth_finished) ? (rot_mod * frametime) : 0;
+               RotRegen(this, RES_HEALTH, limit_mod,
+                       regenstable, regen_health, regen_health_linear, regenframetime,
+                       rotstable, regen_health_rot, regen_health_rotlinear, rotframetime);
        }
 
        // if player rotted to death...  die!
@@ -1683,12 +1668,13 @@ void player_regen(entity this)
 
        if (!(this.items & IT_UNLIMITED_AMMO))
        {
-               float maxf = autocvar_g_balance_fuel_rotstable;
-               float minf = autocvar_g_balance_fuel_regenstable;
-
-               RotRegen(this, RES_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), 1);
+               regenstable = autocvar_g_balance_fuel_regenstable;
+               rotstable = autocvar_g_balance_fuel_rotstable;
+               regenframetime = ((time > this.pauseregen_finished) && (this.items & ITEM_JetpackRegen.m_itemid)) ? frametime : 0;
+               rotframetime = (time > this.pauserotfuel_finished) ? frametime : 0;
+               RotRegen(this, RES_FUEL, 1,
+                       regenstable, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, regenframetime,
+                       rotstable, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, rotframetime);
        }
 }
 
@@ -1836,7 +1822,7 @@ bool SpectateSet(entity this)
        accuracy_resend(this);
 
        if(!SpectateUpdate(this))
-               PutObserverInServer(this);
+               PutObserverInServer(this, false);
 
        return true;
 }
@@ -2010,6 +1996,11 @@ void Join(entity this)
        else
                Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_PLAY, this.netname);
        this.team_selected = false;
+       
+       // z411
+       // send constant ready notification
+       if(warmup_stage)
+               Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_MISSING_READY);
 }
 
 int GetPlayerLimit()
@@ -2049,8 +2040,7 @@ int nJoinAllowed(entity this, entity ignore)
        FOREACH_CLIENT(true, {
                if(it != ignore)
                        ++totalClients;
-               if(IS_REAL_CLIENT(it))
-               if(IS_PLAYER(it) || it.caplayer)
+               if(IS_REAL_CLIENT(it) && (IS_PLAYER(it) || INGAME(it)))
                        ++currentlyPlaying;
        });
 
@@ -2063,7 +2053,7 @@ int nJoinAllowed(entity this, entity ignore)
                free_slots = min(maxclients - totalClients, player_limit - currentlyPlaying);
 
        static float msg_time = 0;
-       if(this && !this.caplayer && ignore && !free_slots && time > msg_time)
+       if(this && !INGAME(this) && ignore && !free_slots && time > msg_time)
        {
                Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_PREVENT);
                msg_time = time + 0.5;
@@ -2153,12 +2143,13 @@ bool PlayerThink(entity this)
                return false;
        }
 
-       if (timeout_status == TIMEOUT_ACTIVE) {
-               // don't allow the player to turn around while game is paused
+       if (game_timeout) {
+        // don't allow the player to turn around while game is paused
                // FIXME turn this into CSQC stuff
                this.v_angle = this.lastV_angle;
                this.angles = this.lastV_angle;
                this.fixangle = true;
+               return false;
        }
 
        if (frametime) player_powerups(this);
@@ -2340,7 +2331,7 @@ void ObserverOrSpectatorThink(entity this)
                                TRANSMUTE(Observer, this);
                                PutClientInServer(this);
                        } else if(!SpectateUpdate(this) && !SpectateNext(this)) {
-                               PutObserverInServer(this);
+                               PutObserverInServer(this, false);
                                this.would_spectate = true;
                        }
                }
@@ -2366,7 +2357,7 @@ void ObserverOrSpectatorThink(entity this)
                        }
                }
                if(is_spec && !SpectateUpdate(this))
-                       PutObserverInServer(this);
+                       PutObserverInServer(this, false);
        }
        if (is_spec)
                this.flags |= FL_CLIENT | FL_NOTARGET;
@@ -2439,7 +2430,7 @@ void PlayerPreThink (entity this)
                // WORKAROUND: only use dropclient in server frames (frametime set).
                // Never use it in cl_movement frames (frametime zero).
                if (blockSpectators && IS_REAL_CLIENT(this)
-                       && (IS_SPEC(this) || IS_OBSERVER(this)) && !this.caplayer
+                       && (IS_SPEC(this) || IS_OBSERVER(this)) && !INGAME(this)
                        && time > (CS(this).spectatortime + autocvar_g_maxplayers_spectator_blocktime))
                {
                        if (dropclient_schedule(this))
@@ -2690,14 +2681,30 @@ void PlayerPostThink (entity this)
                int totalClients = 0;
                if(autocvar_sv_maxidle > 0 && autocvar_sv_maxidle_slots > 0)
                {
-                       FOREACH_CLIENT(IS_REAL_CLIENT(it) || autocvar_sv_maxidle_slots_countbots,
+                       // maxidle disabled in local matches by not counting clients (totalClients 0)
+                       if (server_is_dedicated)
+                       {
+                               FOREACH_CLIENT(IS_REAL_CLIENT(it) || autocvar_sv_maxidle_slots_countbots,
+                               {
+                                       ++totalClients;
+                               });
+                               if (maxclients - totalClients > autocvar_sv_maxidle_slots)
+                                       totalClients = 0;
+                       }
+               }
+               else if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)
+               {
+                       FOREACH_CLIENT(IS_REAL_CLIENT(it),
                        {
                                ++totalClients;
                        });
                }
 
-               if (autocvar_sv_maxidle > 0 && autocvar_sv_maxidle_slots > 0 && (maxclients - totalClients) > autocvar_sv_maxidle_slots)
-               { /* do nothing */ }
+               if (totalClients < autocvar_sv_maxidle_minplayers)
+               {
+                       // idle kick disabled
+                       CS(this).parm_idlesince = time;
+               }
                else if (time - CS(this).parm_idlesince < 1) // instead of (time == this.parm_idlesince) to support sv_maxidle <= 10
                {
                        if (CS(this).idlekick_lasttimeleft)
@@ -2724,10 +2731,7 @@ void PlayerPostThink (entity this)
                                if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)
                                {
                                        Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MOVETOSPEC_IDLING, this.netname, maxidle_time);
-                                       if (this.caplayer)
-                                               this.caplayer = 0;
-                                       this.lms_spectate_warning = 2; // TODO: mutator hook for players forcibly moved to spectator?
-                                       PutObserverInServer(this);
+                                       PutObserverInServer(this, true);
                                }
                                else
                                {