]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/client.qc
Use a trace log for the client program version message and remove networked server...
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / client.qc
index 3c56b1363b56cf489d8af80b7562b6a8b9a01c98..d403f2c111fc58002f86fa98dc6eef9c1429e7fd 100644 (file)
@@ -1,6 +1,7 @@
 #include "client.qh"
 
-#include <server/defs.qh>
+#include <common/weapons/_all.qh>
+#include <common/stats.qh>
 #include <server/miscfunctions.qh>
 #include <common/effects/all.qh>
 #include "anticheat.qh"
 #include "teamplay.qh"
 #include "spawnpoints.qh"
 #include "resources.qh"
-#include "g_damage.qh"
+#include "damage.qh"
 #include "handicap.qh"
-#include "g_hook.qh"
+#include "hook.qh"
+#include <server/intermission.qh>
 #include "command/common.qh"
 #include "command/vote.qh"
 #include "clientkill.qh"
 #include "cheats.qh"
-#include "g_world.qh"
+#include "world.qh"
+#include <server/gamelog.qh>
 #include "race.qh"
+#include <server/main.qh>
 #include "antilag.qh"
 #include "campaign.qh"
 #include "command/common.qh"
@@ -38,6 +42,7 @@
 #include <common/effects/qc/globalsound.qh>
 
 #include "../common/mapobjects/func/conveyor.qh"
+#include <common/mapobjects/func/ladder.qh>
 #include "../common/mapobjects/teleporters.qh"
 #include "../common/mapobjects/target/spawnpoint.qh"
 #include <common/mapobjects/trigger/counter.qh>
@@ -46,6 +51,7 @@
 #include "../common/vehicles/all.qh"
 
 #include "weapons/hitplot.qh"
+#include "weapons/selection.qh"
 #include "weapons/weaponsystem.qh"
 
 #include "../common/net_notice.qh"
@@ -56,6 +62,8 @@
 
 #include "../common/items/_mod.qh"
 
+#include <common/gamemodes/gamemode/nexball/sv_nexball.qh>
+
 #include "../common/mutators/mutator/waypoints/all.qh"
 #include "../common/mutators/mutator/instagib/sv_instagib.qh"
 #include <common/gamemodes/_mod.qh>
 
 #include <common/mutators/mutator/overkill/oknex.qh>
 
+#include <common/weapons/weapon/vortex.qh>
+
+#define CHAT_NOSPECTATORS() ((autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage))
+
 STATIC_METHOD(Client, Add, void(Client this, int _team))
 {
     ClientConnect(this);
@@ -112,9 +124,13 @@ void WriteSpectators(entity player, entity to)
 {
        if(!player) { return; } // not sure how, but best to be safe
 
+       int spec_count = 0;
        FOREACH_CLIENT(IS_REAL_CLIENT(it) && IS_SPEC(it) && it != to && it.enemy == player,
        {
+               if(spec_count >= MAX_SPECTATORS)
+                       break;
                WriteByte(MSG_ENTITY, num_for_edict(it));
+               ++spec_count;
        });
 }
 
@@ -229,7 +245,7 @@ void setplayermodel(entity e, string modelname)
 /** putting a client as observer in the server */
 void PutObserverInServer(entity this)
 {
-    bool mutator_returnvalue = MUTATOR_CALLHOOK(MakePlayerObserver, this);
+       bool mutator_returnvalue = MUTATOR_CALLHOOK(MakePlayerObserver, this);
        PlayerState_detach(this);
 
        if (IS_PLAYER(this))
@@ -246,35 +262,33 @@ void PutObserverInServer(entity this)
                        if (vote_called) { VoteCount(false); }
                        ReadyCount();
                }
-    }
+       }
 
-    {
-        entity spot = SelectSpawnPoint(this, true);
-        if (!spot) LOG_FATAL("No spawnpoints for observers?!?");
-        this.angles = vec2(spot.angles);
-        this.fixangle = true;
-        // offset it so that the spectator spawns higher off the ground, looks better this way
-        setorigin(this, spot.origin + STAT(PL_VIEW_OFS, this));
-        if (IS_REAL_CLIENT(this))
-        {
-            msg_entity = this;
-            WriteByte(MSG_ONE, SVC_SETVIEW);
-            WriteEntity(MSG_ONE, this);
-        }
-        // give the spectator some space between walls for MOVETYPE_FLY_WORLDONLY
-        // so that your view doesn't go into the ceiling with MOVETYPE_FLY_WORLDONLY, previously "PL_VIEW_OFS"
-        if(!autocvar_g_debug_globalsounds)
-        {
-               // needed for player sounds
-               this.model = "";
-               FixPlayermodel(this);
-        }
-        setmodel(this, MDL_Null);
-        setsize(this, STAT(PL_CROUCH_MIN, this), STAT(PL_CROUCH_MAX, this));
-        this.view_ofs = '0 0 0';
-    }
+       entity spot = SelectSpawnPoint(this, true);
+       if (!spot) LOG_FATAL("No spawnpoints for observers?!?");
+       this.angles = vec2(spot.angles);
+       this.fixangle = true;
+       // offset it so that the spectator spawns higher off the ground, looks better this way
+       setorigin(this, spot.origin + STAT(PL_VIEW_OFS, this));
+       if (IS_REAL_CLIENT(this))
+       {
+               msg_entity = this;
+               WriteByte(MSG_ONE, SVC_SETVIEW);
+               WriteEntity(MSG_ONE, this);
+       }
+       // give the spectator some space between walls for MOVETYPE_FLY_WORLDONLY
+       // so that your view doesn't go into the ceiling with MOVETYPE_FLY_WORLDONLY, previously "PL_VIEW_OFS"
+       if(!autocvar_g_debug_globalsounds)
+       {
+               // needed for player sounds
+               this.model = "";
+               FixPlayermodel(this);
+       }
+       setmodel(this, MDL_Null);
+       setsize(this, STAT(PL_CROUCH_MIN, this), STAT(PL_CROUCH_MAX, this));
+       this.view_ofs = '0 0 0';
 
-    RemoveGrapplingHooks(this);
+       RemoveGrapplingHooks(this);
        Portal_ClearAll(this);
        Unfreeze(this, false);
        SetSpectatee(this, NULL);
@@ -290,12 +304,8 @@ void PutObserverInServer(entity this)
 
        WaypointSprite_PlayerDead(this);
 
-       if (CS(this).killcount != FRAGS_SPECTATOR)
-       {
-               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).killcount != FRAGS_SPECTATOR && !game_stopped && CHAT_NOSPECTATORS())
+               Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_CHAT_NOSPECTATORS);
 
        accuracy_resend(this);
 
@@ -306,7 +316,7 @@ void PutObserverInServer(entity this)
        if(this.monster_attack)
                IL_REMOVE(g_monster_targets, this);
        this.monster_attack = false;
-    STAT(HUD, this) = HUD_NORMAL;
+       STAT(HUD, this) = HUD_NORMAL;
        TRANSMUTE(Observer, this);
        this.iscreature = false;
        this.teleportable = TELEPORT_SIMPLE;
@@ -332,12 +342,11 @@ void PutObserverInServer(entity this)
        this.alpha = 0;
        this.scale = 0;
        this.fade_time = 0;
-       this.pain_frame = 0;
        this.pain_finished = 0;
        STAT(STRENGTH_FINISHED, this) = 0;
        STAT(INVINCIBLE_FINISHED, this) = 0;
        STAT(SUPERWEAPONS_FINISHED, this) = 0;
-       this.air_finished = 0;
+       STAT(AIR_FINISHED, this) = 0;
        //this.dphitcontentsmask = 0;
        this.dphitcontentsmask = DPCONTENTS_SOLID;
        if (autocvar_g_playerclip_collisions)
@@ -347,7 +356,7 @@ void PutObserverInServer(entity this)
        setthink(this, func_null);
        this.nextthink = 0;
        this.deadflag = DEAD_NO;
-       this.crouch = false;
+       UNSET_DUCKED(this);
        STAT(REVIVE_PROGRESS, this) = 0;
        this.revival_time = 0;
        this.draggable = drag_undraggable;
@@ -615,7 +624,6 @@ void PutPlayerInServer(entity this)
        bool q3dfcompat = autocvar_sv_q3defragcompat && autocvar_sv_q3defragcompat_changehitbox;
        this.scale = ((q3dfcompat) ? 0.9 : autocvar_sv_player_scale);
        this.fade_time = 0;
-       this.pain_frame = 0;
        this.pain_finished = 0;
        this.pushltime = 0;
        setthink(this, func_null); // players have no think function
@@ -648,7 +656,7 @@ void PutPlayerInServer(entity this)
        STAT(BUFFS, this) = 0;
        STAT(BUFF_TIME, this) = 0;
 
-       this.air_finished = 0;
+       STAT(AIR_FINISHED, this) = 0;
        this.waterlevel = WATERLEVEL_NONE;
        this.watertype = CONTENT_EMPTY;
 
@@ -676,7 +684,7 @@ void PutPlayerInServer(entity this)
 
        this.spawnpoint_targ = NULL;
 
-       this.crouch = false;
+       UNSET_DUCKED(this);
        this.view_ofs = STAT(PL_VIEW_OFS, this);
        setsize(this, STAT(PL_MIN, this), STAT(PL_MAX, this));
        this.spawnorigin = spot.origin;
@@ -690,6 +698,9 @@ void PutPlayerInServer(entity this)
                IL_REMOVE(g_swamped, this);
        this.swampslug = NULL;
        this.swamp_interval = 0;
+       if(this.ladder_entity)
+               IL_REMOVE(g_ladderents, this);
+       this.ladder_entity = NULL;
        IL_EACH(g_counters, it.realowner == this,
        {
                delete(it);
@@ -852,7 +863,7 @@ void ClientInit_misc(entity this)
        WriteInt24_t(channel, compressShotOrigin(arc_shotorigin[2]));
        WriteInt24_t(channel, compressShotOrigin(arc_shotorigin[3]));
 
-       if(sv_foginterval && world.fog != "")
+       if(autocvar_sv_foginterval && world.fog != "")
                WriteString(channel, world.fog);
        else
                WriteString(channel, "");
@@ -925,7 +936,7 @@ void DecodeLevelParms(entity this)
                CS(this).parm_idlesince = time;
 
        // whatever happens, allow 60 seconds of idling directly after connect for map loading
-       CS(this).parm_idlesince = max(CS(this).parm_idlesince, time - sv_maxidle + 60);
+       CS(this).parm_idlesince = max(CS(this).parm_idlesince, time - autocvar_sv_maxidle + 60);
 
        MUTATOR_CALLHOOK(DecodeLevelParms);
 }
@@ -1038,7 +1049,7 @@ string getwelcomemessage(entity this)
                modifications = strcat(modifications, ", Low gravity");
        if(g_weapon_stay && !g_cts)
                modifications = strcat(modifications, ", Weapons stay");
-       if(g_jetpack)
+       if(autocvar_g_jetpack)
                modifications = strcat(modifications, ", Jet pack");
        if(autocvar_g_powerups == 0)
                modifications = strcat(modifications, ", No powerups");
@@ -1093,9 +1104,6 @@ void ClientConnect(entity this)
        this.flags |= FL_CLIENT;
        assert(player_count >= 0, player_count = 0);
 
-#ifdef WATERMARK
-       Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_WATERMARK, WATERMARK);
-#endif
        TRANSMUTE(Client, this);
        CS(this).version_nagtime = time + 10 + random() * 10;
 
@@ -1157,7 +1165,7 @@ void ClientConnect(entity this)
                        stuffcmd(this, "cl_cmd settemp chase_active 1\n");
        }
 
-       if (!sv_foginterval && world.fog != "")
+       if (!autocvar_sv_foginterval && world.fog != "")
                stuffcmd(this, strcat("\nfog ", world.fog, "\nr_fog_exp2 0\nr_drawfog 1\n"));
 
        if (autocvar_sv_teamnagger && !(autocvar_bot_vs_human && AvailableTeams() == 2))
@@ -1218,7 +1226,7 @@ void ClientDisconnect(entity this)
        if(IS_SPEC(this))
                SetSpectatee(this, NULL);
 
-    MUTATOR_CALLHOOK(ClientDisconnect, this);
+       MUTATOR_CALLHOOK(ClientDisconnect, this);
 
        strfree(CS(this).netname_previous); // needs to be before the CS entity is removed!
        strfree(CS(this).weaponorder_byimpulse);
@@ -1304,6 +1312,91 @@ void UpdateChatBubble(entity this)
        }
 }
 
+void calculate_player_respawn_time(entity this)
+{
+       if(MUTATOR_CALLHOOK(CalculateRespawnTime, this))
+               return;
+
+       float gametype_setting_tmp;
+       float sdelay_max = GAMETYPE_DEFAULTED_SETTING(respawn_delay_max);
+       float sdelay_small = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small);
+       float sdelay_large = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large);
+       float sdelay_small_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small_count);
+       float sdelay_large_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large_count);
+       float waves = GAMETYPE_DEFAULTED_SETTING(respawn_waves);
+
+       float pcount = 1;  // Include myself whether or not team is already set right and I'm a "player".
+       if (teamplay)
+       {
+               FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
+                       if(it.team == this.team)
+                               ++pcount;
+               });
+               if (sdelay_small_count == 0)
+                       sdelay_small_count = 1;
+               if (sdelay_large_count == 0)
+                       sdelay_large_count = 1;
+       }
+       else
+       {
+               FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
+                       ++pcount;
+               });
+               if (sdelay_small_count == 0)
+               {
+                       if (IS_INDEPENDENT_PLAYER(this))
+                       {
+                               // Players play independently. No point in requiring enemies.
+                               sdelay_small_count = 1;
+                       }
+                       else
+                       {
+                               // Players play AGAINST each other. Enemies required.
+                               sdelay_small_count = 2;
+                       }
+               }
+               if (sdelay_large_count == 0)
+               {
+                       if (IS_INDEPENDENT_PLAYER(this))
+                       {
+                               // Players play independently. No point in requiring enemies.
+                               sdelay_large_count = 1;
+                       }
+                       else
+                       {
+                               // Players play AGAINST each other. Enemies required.
+                               sdelay_large_count = 2;
+                       }
+               }
+       }
+
+       float sdelay;
+
+       if (pcount <= sdelay_small_count)
+               sdelay = sdelay_small;
+       else if (pcount >= sdelay_large_count)
+               sdelay = sdelay_large;
+       else  // NOTE: this case implies sdelay_large_count > sdelay_small_count.
+               sdelay = sdelay_small + (sdelay_large - sdelay_small) * (pcount - sdelay_small_count) / (sdelay_large_count - sdelay_small_count);
+
+       if(waves)
+               this.respawn_time = ceil((time + sdelay) / waves) * waves;
+       else
+               this.respawn_time = time + sdelay;
+
+       if(sdelay < sdelay_max)
+               this.respawn_time_max = time + sdelay_max;
+       else
+               this.respawn_time_max = this.respawn_time;
+
+       if((sdelay + waves >= 5.0) && (this.respawn_time - time > 1.75))
+               this.respawn_countdown = 10; // first number to count down from is 10
+       else
+               this.respawn_countdown = -1; // do not count down
+
+       if(autocvar_g_forced_respawn)
+               this.respawn_flags = this.respawn_flags | RESPAWN_FORCE;
+}
 
 // LordHavoc: this hack will be removed when proper _pants/_shirt layers are
 // added to the model skins
@@ -1718,7 +1811,7 @@ void SpectateCopy(entity this, entity spectatee)
        STAT(STRENGTH_FINISHED, this) = STAT(STRENGTH_FINISHED, spectatee);
        STAT(INVINCIBLE_FINISHED, this) = STAT(INVINCIBLE_FINISHED, spectatee);
        STAT(SUPERWEAPONS_FINISHED, this) = STAT(SUPERWEAPONS_FINISHED, spectatee);
-       this.air_finished = spectatee.air_finished;
+       STAT(AIR_FINISHED, this) = STAT(AIR_FINISHED, spectatee);
        STAT(PRESSED_KEYS, this) = STAT(PRESSED_KEYS, spectatee);
        STAT(WEAPONS, this) = STAT(WEAPONS, spectatee);
        this.punchangle = spectatee.punchangle;
@@ -1897,10 +1990,10 @@ bool SpectatePrev(entity this)
        switch (MUTATOR_CALLHOOK(SpectatePrev, this, ent, first))
        {
                case MUT_SPECPREV_FOUND:
-                   ent = M_ARGV(1, entity);
-                   break;
+                       ent = M_ARGV(1, entity);
+                       break;
                case MUT_SPECPREV_RETURN:
-                   return true;
+                       return true;
                case MUT_SPECPREV_CONTINUE:
                default:
                {
@@ -2018,10 +2111,10 @@ int nJoinAllowed(entity this, entity ignore)
 
        int player_limit = GetPlayerLimit();
 
-       float free_slots = 0;
+       int free_slots = 0;
        if (!player_limit)
                free_slots = maxclients - totalClients;
-       else if(currentlyPlaying < player_limit)
+       else if(player_limit > 0 && currentlyPlaying < player_limit)
                free_slots = min(maxclients - totalClients, player_limit - currentlyPlaying);
 
        static float msg_time = 0;
@@ -2133,7 +2226,7 @@ bool PlayerThink(entity this)
        }
 
        if (timeout_status == TIMEOUT_ACTIVE) {
-        // don't allow the player to turn around while game is paused
+               // 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;
@@ -2262,7 +2355,6 @@ bool PlayerThink(entity this)
                this.dmg_team = max(0, this.dmg_team - autocvar_g_teamdamage_resetspeed * frametime);
        }
 
-       secrets_setstatus(this);
        monsters_setstatus(this);
 
        return true;
@@ -2291,7 +2383,7 @@ void ObserverOrSpectatorThink(entity this)
                        this.flags &= ~FL_JUMPRELEASED;
                        this.flags |= FL_SPAWNING;
                } else if((is_spec && (PHYS_INPUT_BUTTON_ATCK(this) || CS(this).impulse == 10 || CS(this).impulse == 15 || CS(this).impulse == 18 || (CS(this).impulse >= 200 && CS(this).impulse <= 209)))
-                       || (!is_spec && (PHYS_INPUT_BUTTON_ATCK(this) && !CS(this).version_mismatch || this.would_spectate))) {
+                       || (!is_spec && ((PHYS_INPUT_BUTTON_ATCK(this) && !CS(this).version_mismatch) || this.would_spectate))) {
                        this.flags &= ~FL_JUMPRELEASED;
                        if(SpectateNext(this)) {
                                TRANSMUTE(Spectator, this);
@@ -2474,7 +2566,8 @@ void PlayerPreThink (entity this)
                {
                        STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + frametime * this.revive_speed, 1);
                        SetResourceExplicit(this, RES_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * start_health));
-                       this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1);
+                       if (this.iceblock)
+                               this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1);
 
                        if (STAT(REVIVE_PROGRESS, this) >= 1)
                                Unfreeze(this, false);
@@ -2606,21 +2699,21 @@ void DrownPlayer(entity this)
        if(IS_DEAD(this) || game_stopped || time < game_starttime || this.vehicle
                || STAT(FROZEN, this) || this.watertype != CONTENT_WATER)
        {
-               this.air_finished = 0;
+               STAT(AIR_FINISHED, this) = 0;
                return;
        }
 
        if (this.waterlevel != WATERLEVEL_SUBMERGED)
        {
-               if(this.air_finished && this.air_finished < time)
+               if(STAT(AIR_FINISHED, this) && STAT(AIR_FINISHED, this) < time)
                        PlayerSound(this, playersound_gasp, CH_PLAYER, VOL_BASE, VOICETYPE_PLAYERSOUND);
-               this.air_finished = 0;
+               STAT(AIR_FINISHED, this) = 0;
        }
        else
        {
-               if (!this.air_finished)
-                       this.air_finished = time + autocvar_g_balance_contents_drowndelay;
-               if (this.air_finished < time)
+               if (!STAT(AIR_FINISHED, this))
+                       STAT(AIR_FINISHED, this) = time + autocvar_g_balance_contents_drowndelay;
+               if (STAT(AIR_FINISHED, this) < time)
                {       // drown!
                        if (this.pain_finished < time)
                        {
@@ -2659,21 +2752,21 @@ void PlayerPostThink (entity this)
 {
        Player_Physics(this);
 
-       if (sv_maxidle > 0)
+       if (autocvar_sv_maxidle > 0)
        if (frametime) // WORKAROUND: only use dropclient in server frames (frametime set). Never use it in cl_movement frames (frametime zero).
        if (IS_REAL_CLIENT(this))
-       if (IS_PLAYER(this) || sv_maxidle_spectatorsareidle)
+       if (IS_PLAYER(this) || autocvar_sv_maxidle_spectatorsareidle)
        {
                int totalClients = 0;
-               if(sv_maxidle_slots > 0)
+               if(autocvar_sv_maxidle_slots > 0)
                {
-                       FOREACH_CLIENT(IS_REAL_CLIENT(it) || sv_maxidle_slots_countbots,
+                       FOREACH_CLIENT(IS_REAL_CLIENT(it) || autocvar_sv_maxidle_slots_countbots,
                        {
                                ++totalClients;
                        });
                }
 
-               if (sv_maxidle_slots > 0 && (maxclients - totalClients) > sv_maxidle_slots)
+               if (autocvar_sv_maxidle_slots > 0 && (maxclients - totalClients) > autocvar_sv_maxidle_slots)
                { /* do nothing */ }
                else if (time - CS(this).parm_idlesince < 1) // instead of (time == this.parm_idlesince) to support sv_maxidle <= 10
                {
@@ -2685,8 +2778,8 @@ void PlayerPostThink (entity this)
                }
                else
                {
-                       float timeleft = ceil(sv_maxidle - (time - CS(this).parm_idlesince));
-                       if (timeleft == min(10, sv_maxidle - 1)) { // - 1 to support sv_maxidle <= 10
+                       float timeleft = ceil(autocvar_sv_maxidle - (time - CS(this).parm_idlesince));
+                       if (timeleft == min(10, autocvar_sv_maxidle - 1)) { // - 1 to support sv_maxidle <= 10
                                if (!CS(this).idlekick_lasttimeleft)
                                        Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_DISCONNECT_IDLING, timeleft);
                        }
@@ -2696,9 +2789,8 @@ void PlayerPostThink (entity this)
                                return;
                        }
                        else if (timeleft <= 10) {
-                               if (timeleft != CS(this).idlekick_lasttimeleft) {
-                                   Send_Notification(NOTIF_ONE, this, MSG_ANNCE, Announcer_PickNumber(CNT_IDLE, timeleft));
-                }
+                               if (timeleft != CS(this).idlekick_lasttimeleft)
+                                       Send_Notification(NOTIF_ONE, this, MSG_ANNCE, Announcer_PickNumber(CNT_IDLE, timeleft));
                                CS(this).idlekick_lasttimeleft = timeleft;
                        }
                }
@@ -2711,6 +2803,9 @@ void PlayerPostThink (entity this)
                this.solid = SOLID_NOT;
                this.takedamage = DAMAGE_NO;
                set_movetype(this, MOVETYPE_NONE);
+               CS(this).teamkill_complain = 0;
+               CS(this).teamkill_soundtime = 0;
+               CS(this).teamkill_soundsource = NULL;
        }
 
        if (IS_PLAYER(this)) {
@@ -2867,10 +2962,7 @@ int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodc
        var .float flood_field = floodcontrol_chat;
        if(floodcontrol && source)
        {
-               float flood_spl;
-               float flood_burst;
-               float flood_lmax;
-               float lines;
+               float flood_spl, flood_burst, flood_lmax;
                if(privatesay)
                {
                        flood_spl = autocvar_g_chat_flood_spl_tell;
@@ -2900,7 +2992,7 @@ int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodc
                {
                        getWrappedLine_remaining = msgstr;
                        msgstr = "";
-                       lines = 0;
+                       int lines = 0;
                        while(getWrappedLine_remaining && (!flood_lmax || lines <= flood_lmax))
                        {
                                msgstr = strcat(msgstr, " ", getWrappedLineLen(82.4289758859709, strlennocol)); // perl averagewidth.pl < gfx/vera-sans.width
@@ -2957,11 +3049,10 @@ int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodc
                sourcecmsgstr = cmsgstr;
        }
 
-       if (!privatesay && source && !(IS_PLAYER(source) || source.caplayer))
+       if (!privatesay && source && !(IS_PLAYER(source) || source.caplayer) && !game_stopped
+               && (teamsay || CHAT_NOSPECTATORS()))
        {
-               if (!game_stopped)
-               if (teamsay || (autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage))
-                       teamsay = -1; // spectators
+               teamsay = -1; // spectators
        }
 
        if(flood)
@@ -2992,11 +3083,10 @@ int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodc
                ret = 1;
        }
 
-       if (privatesay && source && !(IS_PLAYER(source) || source.caplayer))
+       if (privatesay && source && !(IS_PLAYER(source) || source.caplayer) && !game_stopped
+               && (IS_PLAYER(privatesay) || privatesay.caplayer) && CHAT_NOSPECTATORS())
        {
-               if (!game_stopped)
-               if ((privatesay && (IS_PLAYER(privatesay) || privatesay.caplayer)) && ((autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage)))
-                       ret = -1; // just hide the message completely
+               ret = -1; // just hide the message completely
        }
 
        MUTATOR_CALLHOOK(ChatMessage, source, ret);