]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/client.qc
Merge branch 'master' into Mario/status_effects_extended
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / client.qc
index 902172cd0d687753e820df8d2b21e2289db16884..d2bb23cf68d96aff2d35064da123de450597405f 100644 (file)
@@ -1,90 +1,82 @@
 #include "client.qh"
 
-#include <common/weapons/_all.qh>
-#include <common/stats.qh>
-#include <server/miscfunctions.qh>
+#include <common/csqcmodel_settings.qh>
+#include <common/deathtypes/all.qh>
 #include <common/effects/all.qh>
-#include "anticheat.qh"
-#include "impulse.qh"
-#include "player.qh"
-#include "ipban.qh"
-#include "miscfunctions.qh"
-#include "portals.qh"
-#include "teamplay.qh"
-#include "spawnpoints.qh"
-#include "resources.qh"
-#include "damage.qh"
-#include "handicap.qh"
-#include "hook.qh"
-#include <server/intermission.qh>
-#include "command/common.qh"
-#include "command/vote.qh"
-#include "clientkill.qh"
-#include "cheats.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"
-#include "scores_rules.qh"
-#include "weapons/common.qh"
-
-#include "bot/api.qh"
-
-#include "../common/ent_cs.qh"
-#include "../common/wepent.qh"
-#include <common/state.qh>
-
-#include "compat/quake3.qh"
-
 #include <common/effects/qc/globalsound.qh>
-
-#include "../common/mapobjects/func/conveyor.qh"
+#include <common/ent_cs.qh>
+#include <common/gamemodes/_mod.qh>
+#include <common/gamemodes/gamemode/nexball/sv_nexball.qh>
+#include <common/items/_mod.qh>
+#include <common/items/inventory.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/subs.qh>
+#include <common/mapobjects/target/spawnpoint.qh>
+#include <common/mapobjects/teleporters.qh>
 #include <common/mapobjects/trigger/counter.qh>
+#include <common/mapobjects/trigger/secret.qh>
 #include <common/mapobjects/trigger/swamp.qh>
-
-#include "../common/vehicles/all.qh"
-
-#include "weapons/hitplot.qh"
-#include "weapons/selection.qh"
-#include "weapons/weaponsystem.qh"
-
-#include "../common/net_notice.qh"
-#include "../common/net_linked.qh"
-#include "../common/physics/player.qh"
-
-#include <common/vehicles/sv_vehicles.qh>
-
-#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/mapobjects/subs.qh"
-#include "../common/mapobjects/triggers.qh"
-#include "../common/mapobjects/trigger/secret.qh"
-
-#include "../common/minigames/sv_minigames.qh"
-
-#include "../common/items/inventory.qh"
-
-#include "../common/monsters/sv_monsters.qh"
-
-#include "../lib/warpzone/server.qh"
-
+#include <common/mapobjects/triggers.qh>
+#include <common/minigames/sv_minigames.qh>
+#include <common/monsters/sv_monsters.qh>
+#include <common/mutators/mutator/instagib/sv_instagib.qh>
+#include <common/mutators/mutator/nades/nades.qh>
 #include <common/mutators/mutator/overkill/oknex.qh>
-
+#include <common/mutators/mutator/status_effects/_mod.qh>
+#include <common/mutators/mutator/waypoints/all.qh>
+#include <common/net_linked.qh>
+#include <common/net_notice.qh>
+#include <common/notifications/all.qh>
+#include <common/physics/player.qh>
+#include <common/playerstats.qh>
+#include <common/state.qh>
+#include <common/stats.qh>
+#include <common/vehicles/all.qh>
+#include <common/vehicles/sv_vehicles.qh>
+#include <common/viewloc.qh>
+#include <common/weapons/_all.qh>
 #include <common/weapons/weapon/vortex.qh>
-
-#define CHAT_NOSPECTATORS() ((autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage))
+#include <common/wepent.qh>
+#include <lib/csqcmodel/sv_model.qh>
+#include <lib/warpzone/common.qh>
+#include <lib/warpzone/server.qh>
+#include <server/anticheat.qh>
+#include <server/antilag.qh>
+#include <server/bot/api.qh>
+#include <server/bot/default/cvars.qh>
+#include <server/campaign.qh>
+#include <server/chat.qh>
+#include <server/cheats.qh>
+#include <server/clientkill.qh>
+#include <server/command/common.qh>
+#include <server/command/common.qh>
+#include <server/command/vote.qh>
+#include <server/compat/quake3.qh>
+#include <server/damage.qh>
+#include <server/gamelog.qh>
+#include <server/handicap.qh>
+#include <server/hook.qh>
+#include <server/impulse.qh>
+#include <server/intermission.qh>
+#include <server/ipban.qh>
+#include <server/main.qh>
+#include <server/mutators/_mod.qh>
+#include <server/player.qh>
+#include <server/portals.qh>
+#include <server/race.qh>
+#include <server/resources.qh>
+#include <server/scores.qh>
+#include <server/scores_rules.qh>
+#include <server/spawnpoints.qh>
+#include <server/teamplay.qh>
+#include <server/weapons/accuracy.qh>
+#include <server/weapons/common.qh>
+#include <server/weapons/hitplot.qh>
+#include <server/weapons/selection.qh>
+#include <server/weapons/tracing.qh>
+#include <server/weapons/weaponsystem.qh>
+#include <server/world.qh>
 
 STATIC_METHOD(Client, Add, void(Client this, int _team))
 {
@@ -208,19 +200,19 @@ string CheckPlayerModel(string plyermodel) {
                FallbackPlayerModel = strzone(cvar_defstring("_cl_playermodel"));
        }
        // only in right path
-       if( substring(plyermodel,0,14) != "models/player/")
+       if(substring(plyermodel, 0, 14) != "models/player/")
                return FallbackPlayerModel;
        // only good file extensions
-       if(substring(plyermodel,-4,4) != ".zym")
-       if(substring(plyermodel,-4,4) != ".dpm")
-       if(substring(plyermodel,-4,4) != ".iqm")
-       if(substring(plyermodel,-4,4) != ".md3")
-       if(substring(plyermodel,-4,4) != ".psk")
+       if(substring(plyermodel, -4, 4) != ".iqm"
+               && substring(plyermodel, -4, 4) != ".zym"
+               && substring(plyermodel, -4, 4) != ".dpm"
+               && substring(plyermodel, -4, 4) != ".md3"
+               && substring(plyermodel, -4, 4) != ".psk")
+       {
                return FallbackPlayerModel;
+       }
        // forbid the LOD models
-       if(substring(plyermodel, -9,5) == "_lod1")
-               return FallbackPlayerModel;
-       if(substring(plyermodel, -9,5) == "_lod2")
+       if(substring(plyermodel, -9, 5) == "_lod1" || substring(plyermodel, -9, 5) == "_lod2")
                return FallbackPlayerModel;
        if(plyermodel != strtolower(plyermodel))
                return FallbackPlayerModel;
@@ -262,6 +254,7 @@ void PutObserverInServer(entity this)
                        if (vote_called) { VoteCount(false); }
                        ReadyCount();
                }
+               entcs_update_players(this);
        }
 
        entity spot = SelectSpawnPoint(this, true);
@@ -343,9 +336,6 @@ void PutObserverInServer(entity this)
        this.scale = 0;
        this.fade_time = 0;
        this.pain_finished = 0;
-       STAT(STRENGTH_FINISHED, this) = 0;
-       STAT(INVINCIBLE_FINISHED, this) = 0;
-       STAT(SUPERWEAPONS_FINISHED, this) = 0;
        STAT(AIR_FINISHED, this) = 0;
        //this.dphitcontentsmask = 0;
        this.dphitcontentsmask = DPCONTENTS_SOLID;
@@ -381,7 +371,6 @@ void PutObserverInServer(entity this)
        this.punchangle = '0 0 0';
        this.punchvector = '0 0 0';
        this.oldvelocity = this.velocity;
-       this.fire_endtime = -1;
        this.event_damage = func_null;
        this.event_heal = func_null;
 
@@ -403,6 +392,9 @@ void PutObserverInServer(entity this)
                SetPlayerTeam(this, -1, TEAM_CHANGE_SPECTATOR);
                this.frags = FRAGS_SPECTATOR;
        }
+
+       bot_relinkplayerlist();
+
        if (CS(this).just_joined)
                CS(this).just_joined = false;
 }
@@ -598,11 +590,13 @@ void PutPlayerInServer(entity this)
 
        PS(this).dual_weapons = '0 0 0';
 
-       STAT(SUPERWEAPONS_FINISHED, this) = (STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS) ? time + autocvar_g_balance_superweapons_time : 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;
@@ -610,12 +604,14 @@ 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;
@@ -646,16 +642,9 @@ void PutPlayerInServer(entity this)
        this.punchangle = '0 0 0';
        this.punchvector = '0 0 0';
 
-       STAT(STRENGTH_FINISHED, this) = 0;
-       STAT(INVINCIBLE_FINISHED, this) = 0;
-       this.fire_endtime = -1;
        STAT(REVIVE_PROGRESS, this) = 0;
        this.revival_time = 0;
 
-       // TODO: we can't set these in the PlayerSpawn hook since the target code is called before it!
-       STAT(BUFFS, this) = 0;
-       STAT(BUFF_TIME, this) = 0;
-
        STAT(AIR_FINISHED, this) = 0;
        this.waterlevel = WATERLEVEL_NONE;
        this.watertype = CONTENT_EMPTY;
@@ -758,6 +747,10 @@ void PutPlayerInServer(entity this)
                }
        });
 
+       Unfreeze(this, false);
+
+       MUTATOR_CALLHOOK(PlayerSpawn, this, spot);
+
        {
                string s = spot.target;
                if(g_assault || g_race) // TODO: make targeting work in assault & race without this hack
@@ -767,10 +760,6 @@ void PutPlayerInServer(entity this)
                        spot.target = s;
        }
 
-       Unfreeze(this, false);
-
-       MUTATOR_CALLHOOK(PlayerSpawn, this, spot);
-
        if (autocvar_spawn_debug)
        {
                sprint(this, strcat("spawnpoint origin:  ", vtos(spot.origin), "\n"));
@@ -780,21 +769,22 @@ void PutPlayerInServer(entity this)
        for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
        {
                .entity weaponentity = weaponentities[slot];
+               entity w_ent = this.(weaponentity);
                if(slot == 0 || autocvar_g_weaponswitch_debug == 1)
-                       this.(weaponentity).m_switchweapon = w_getbestweapon(this, weaponentity);
+                       w_ent.m_switchweapon = w_getbestweapon(this, weaponentity);
                else
-                       this.(weaponentity).m_switchweapon = WEP_Null;
-               this.(weaponentity).m_weapon = WEP_Null;
-               this.(weaponentity).weaponname = "";
-               this.(weaponentity).m_switchingweapon = WEP_Null;
-               this.(weaponentity).cnt = -1;
+                       w_ent.m_switchweapon = WEP_Null;
+               w_ent.m_weapon = WEP_Null;
+               w_ent.weaponname = "";
+               w_ent.m_switchingweapon = WEP_Null;
+               w_ent.cnt = -1;
        }
 
        MUTATOR_CALLHOOK(PlayerWeaponSelect, this);
 
        if (CS(this).impulse) ImpulseCommands(this);
 
-       W_ResetGunAlign(this, CS(this).cvar_cl_gunalign);
+       W_ResetGunAlign(this, CS_CVAR(this).cvar_cl_gunalign);
        for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
        {
                .entity weaponentity = weaponentities[slot];
@@ -833,6 +823,8 @@ void PutClientInServer(entity this)
        } else if (IS_PLAYER(this)) {
                PutPlayerInServer(this);
        }
+
+       bot_relinkplayerlist();
 }
 
 // TODO do we need all these fields, or should we stop autodetecting runtime
@@ -1051,14 +1043,10 @@ 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\nhost is ^9", autocvar_hostname, "^8\n");
+       string s = strcat(versionmessage, "^8\n^8\nserver is ^9", autocvar_hostname, "^8\n");
 
        s = strcat(s, "^8\nmatch type is ^1", gamemode_name, "^8\n");
 
@@ -1104,9 +1092,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;
 
@@ -1127,7 +1112,7 @@ void ClientConnect(entity this)
                CS(this).allowed_timeouts = autocvar_sv_timeout_number;
 
        if (autocvar_sv_eventlog)
-               GameLogEcho(strcat(":join:", ftos(this.playerid), ":", ftos(etof(this)), ":", ((IS_REAL_CLIENT(this)) ? GameLog_ProcessIP(this.netaddress) : "bot"), ":", playername(this, false)));
+               GameLogEcho(strcat(":join:", ftos(this.playerid), ":", ftos(etof(this)), ":", ((IS_REAL_CLIENT(this)) ? GameLog_ProcessIP(this.netaddress) : "bot"), ":", playername(this.netname, this.team, false)));
 
        CS(this).just_joined = true;  // stop spamming the eventlog with additional lines when the client connects
 
@@ -1212,6 +1197,8 @@ Called when a client disconnects from the server
 =============
 */
 .entity chatbubbleentity;
+void player_powerups_remove_all(entity this);
+
 void ClientDisconnect(entity this)
 {
        assert(IS_CLIENT(this), return);
@@ -1232,7 +1219,7 @@ void ClientDisconnect(entity this)
        MUTATOR_CALLHOOK(ClientDisconnect, this);
 
        strfree(CS(this).netname_previous); // needs to be before the CS entity is removed!
-       strfree(CS(this).weaponorder_byimpulse);
+       strfree(CS_CVAR(this).weaponorder_byimpulse);
        ClientState_detach(this);
 
        Portal_ClearAll(this);
@@ -1264,6 +1251,8 @@ void ClientDisconnect(entity this)
        ReadyCount();
        if (vote_called && IS_REAL_CLIENT(this)) VoteCount(false);
 
+       player_powerups_remove_all(this); // stop powerup sound
+
        ONREMOVE(this);
 }
 
@@ -1447,60 +1436,6 @@ void respawn(entity this)
        PutClientInServer(this);
 }
 
-ERASEABLE
-void PrintToChat(entity client, string text)
-{
-       text = strcat("\{1}^7", text, "\n");
-       sprint(client, text);
-}
-
-ERASEABLE
-void DebugPrintToChat(entity client, string text)
-{
-       if (autocvar_developer > 0)
-       {
-               PrintToChat(client, text);
-       }
-}
-
-ERASEABLE
-void PrintToChatAll(string text)
-{
-       text = strcat("\{1}^7", text, "\n");
-       bprint(text);
-}
-
-ERASEABLE
-void DebugPrintToChatAll(string text)
-{
-       if (autocvar_developer > 0)
-       {
-               PrintToChatAll(text);
-       }
-}
-
-ERASEABLE
-void PrintToChatTeam(int team_num, string text)
-{
-       text = strcat("\{1}^7", text, "\n");
-       FOREACH_CLIENT(IS_REAL_CLIENT(it),
-       {
-               if (it.team == team_num)
-               {
-                       sprint(it, text);
-               }
-       });
-}
-
-ERASEABLE
-void DebugPrintToChatTeam(int team_num, string text)
-{
-       if (autocvar_developer > 0)
-       {
-               PrintToChatTeam(team_num, text);
-       }
-}
-
 void play_countdown(entity this, float finished, Sound samp)
 {
        TC(Sound, samp);
@@ -1510,6 +1445,18 @@ void play_countdown(entity this, float finished, Sound samp)
                                sound (this, CH_INFO, samp, VOL_BASE, ATTEN_NORM);
 }
 
+void player_powerups_remove_all(entity this)
+{
+       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 -= (this.items & IT_SUPERWEAPON);
+       }
+}
+
 void player_powerups(entity this)
 {
        if((this.items & IT_USING_JETPACK) && !IS_DEAD(this) && !game_stopped)
@@ -1517,19 +1464,10 @@ void player_powerups(entity this)
        else
                this.modelflags &= ~MF_ROCKET;
 
-       this.effects &= ~(EF_RED | EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT | EF_FLAME | EF_NODEPTHTEST);
+       this.effects &= ~EF_NODEPTHTEST;
 
        if (IS_DEAD(this))
-       {
-               if (this.items & (ITEM_Strength.m_itemid | ITEM_Shield.m_itemid | IT_SUPERWEAPON))
-               {
-                       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);
-               }
-       }
+               player_powerups_remove_all(this);
 
        if((this.alpha < 0 || IS_DEAD(this)) && !this.vehicle) // don't apply the flags if the player is gibbed
                return;
@@ -1537,58 +1475,14 @@ void player_powerups(entity this)
        // add a way to see what the items were BEFORE all of these checks for the mutator hook
        int items_prev = this.items;
 
-       Fire_ApplyDamage(this);
-       Fire_ApplyEffect(this);
-
        if (!MUTATOR_IS_ENABLED(mutator_instagib))
        {
-               if (this.items & ITEM_Strength.m_itemid)
-               {
-                       play_countdown(this, STAT(STRENGTH_FINISHED, this), SND_POWEROFF);
-                       this.effects = this.effects | (EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT);
-                       if (time > STAT(STRENGTH_FINISHED, 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 < STAT(STRENGTH_FINISHED, 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, STAT(INVINCIBLE_FINISHED, this), SND_POWEROFF);
-                       this.effects = this.effects | (EF_RED | EF_ADDITIVE | EF_FULLBRIGHT);
-                       if (time > STAT(INVINCIBLE_FINISHED, 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 < STAT(INVINCIBLE_FINISHED, 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))
                        {
-                               STAT(SUPERWEAPONS_FINISHED, this) = 0;
+                               StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_NORMAL);
                                this.items = this.items - (this.items & IT_SUPERWEAPON);
                                //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_LOST, this.netname);
                                Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_LOST);
@@ -1599,8 +1493,8 @@ void player_powerups(entity this)
                        }
                        else
                        {
-                               play_countdown(this, STAT(SUPERWEAPONS_FINISHED, this), SND_POWEROFF);
-                               if (time > STAT(SUPERWEAPONS_FINISHED, this))
+                               play_countdown(this, StatusEffects_gettime(STATUSEFFECT_Superweapons, this), SND_POWEROFF);
+                               if (time > StatusEffects_gettime(STATUSEFFECT_Superweapons, this))
                                {
                                        this.items = this.items - (this.items & IT_SUPERWEAPON);
                                        STAT(WEAPONS, this) &= ~WEPSET_SUPERWEAPONS;
@@ -1611,7 +1505,7 @@ void player_powerups(entity this)
                }
                else if(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS)
                {
-                       if (time < STAT(SUPERWEAPONS_FINISHED, this) || (this.items & IT_UNLIMITED_SUPERWEAPONS))
+                       if (time < StatusEffects_gettime(STATUSEFFECT_Superweapons, this) || (this.items & IT_UNLIMITED_SUPERWEAPONS))
                        {
                                this.items = this.items | IT_SUPERWEAPON;
                                if(!(this.items & IT_UNLIMITED_SUPERWEAPONS))
@@ -1623,13 +1517,14 @@ void player_powerups(entity this)
                        }
                        else
                        {
-                               STAT(SUPERWEAPONS_FINISHED, this) = 0;
+                               if(StatusEffects_active(STATUSEFFECT_Superweapons, this))
+                                       StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_TIMEOUT);
                                STAT(WEAPONS, this) &= ~WEPSET_SUPERWEAPONS;
                        }
                }
-               else
+               else if(StatusEffects_active(STATUSEFFECT_Superweapons, this)) // cheaper to check than to update each frame!
                {
-                       STAT(SUPERWEAPONS_FINISHED, this) = 0;
+                       StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_CLEAR);
                }
        }
 
@@ -1639,10 +1534,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);
 }
 
@@ -1769,6 +1660,14 @@ void SetZoomState(entity this, float newzoom)
 void GetPressedKeys(entity this)
 {
        MUTATOR_CALLHOOK(GetPressedKeys, this);
+       if (game_stopped)
+       {
+               CS(this).pressedkeys = 0;
+               STAT(PRESSED_KEYS, this) = 0;
+               return;
+       }
+
+       // NOTE: GetPressedKeys and PM_dodging_GetPressedKeys use similar code
        int keys = STAT(PRESSED_KEYS, this);
        keys = BITSET(keys, KEY_FORWARD,        CS(this).movement.x > 0);
        keys = BITSET(keys, KEY_BACKWARD,       CS(this).movement.x < 0);
@@ -1811,9 +1710,6 @@ void SpectateCopy(entity this, entity spectatee)
        this.items = spectatee.items;
        STAT(LAST_PICKUP, this) = STAT(LAST_PICKUP, spectatee);
        STAT(HIT_TIME, this) = STAT(HIT_TIME, 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);
        STAT(AIR_FINISHED, this) = STAT(AIR_FINISHED, spectatee);
        STAT(PRESSED_KEYS, this) = STAT(PRESSED_KEYS, spectatee);
        STAT(WEAPONS, this) = STAT(WEAPONS, spectatee);
@@ -2130,23 +2026,6 @@ int nJoinAllowed(entity this, entity ignore)
        return free_slots;
 }
 
-/**
- * Checks whether the client is an observer or spectator, if so, he will get kicked after
- * g_maxplayers_spectator_blocktime seconds
- */
-void checkSpectatorBlock(entity this)
-{
-       if(IS_SPEC(this) || IS_OBSERVER(this))
-       if(!this.caplayer)
-       if(IS_REAL_CLIENT(this))
-       {
-               if( time > (CS(this).spectatortime + autocvar_g_maxplayers_spectator_blocktime) ) {
-                       Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_QUIT_KICK_SPECTATING);
-                       dropclient(this);
-               }
-       }
-}
-
 void PrintWelcomeMessage(entity this)
 {
        if(CS(this).motd_actived_time == 0)
@@ -2326,7 +2205,7 @@ bool PlayerThink(entity this)
        bool dualwielding = W_DualWielding(this);
        if(this.dualwielding_prev != dualwielding)
        {
-               W_ResetGunAlign(this, CS(this).cvar_cl_gunalign);
+               W_ResetGunAlign(this, CS_CVAR(this).cvar_cl_gunalign);
                this.dualwielding_prev = dualwielding;
        }
 
@@ -2364,6 +2243,7 @@ bool PlayerThink(entity this)
 }
 
 .bool would_spectate;
+// merged SpectatorThink and ObserverThink (old names are here so you can grep for them)
 void ObserverOrSpectatorThink(entity this)
 {
        bool is_spec = IS_SPEC(this);
@@ -2394,6 +2274,8 @@ void ObserverOrSpectatorThink(entity this)
                                TRANSMUTE(Observer, this);
                                PutClientInServer(this);
                        }
+                       else
+                               this.would_spectate = false; // unable to spectate anyone
                        if (is_spec)
                                CS(this).impulse = 0;
                } else if (is_spec) {
@@ -2417,10 +2299,13 @@ void ObserverOrSpectatorThink(entity this)
                        }
                }
                else {
-                       int preferred_movetype = ((!PHYS_INPUT_BUTTON_USE(this) ? CS(this).cvar_cl_clippedspectating : !CS(this).cvar_cl_clippedspectating) ? MOVETYPE_FLY_WORLDONLY : MOVETYPE_NOCLIP);
+                       bool wouldclip = CS_CVAR(this).cvar_cl_clippedspectating;
+                       if (PHYS_INPUT_BUTTON_USE(this))
+                               wouldclip = !wouldclip;
+                       int preferred_movetype = (wouldclip ? MOVETYPE_FLY_WORLDONLY : MOVETYPE_NOCLIP);
                        set_movetype(this, preferred_movetype);
                }
-       } else {
+       } else { // jump pressed
                if ((is_spec && !(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_ATCK2(this)))
                        || (!is_spec && !(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_JUMP(this)))) {
                        this.flags |= FL_JUMPRELEASED;
@@ -2496,20 +2381,24 @@ Called every frame for each client before the physics are run
 .float last_vehiclecheck;
 void PlayerPreThink (entity this)
 {
-       STAT(GUNALIGN, this) = CS(this).cvar_cl_gunalign; // TODO
-       STAT(MOVEVARS_CL_TRACK_CANJUMP, this) = CS(this).cvar_cl_movement_track_canjump;
+       STAT(GUNALIGN, this) = CS_CVAR(this).cvar_cl_gunalign; // TODO
+       STAT(MOVEVARS_CL_TRACK_CANJUMP, this) = CS_CVAR(this).cvar_cl_movement_track_canjump;
 
        WarpZone_PlayerPhysics_FixVAngle(this);
 
        if (frametime) {
                // physics frames: update anticheat stuff
                anticheat_prethink(this);
-       }
 
-       if (blockSpectators && frametime) {
                // WORKAROUND: only use dropclient in server frames (frametime set).
                // Never use it in cl_movement frames (frametime zero).
-               checkSpectatorBlock(this);
+               if (blockSpectators && IS_REAL_CLIENT(this)
+                       && (IS_SPEC(this) || IS_OBSERVER(this)) && !this.caplayer
+                       && time > (CS(this).spectatortime + autocvar_g_maxplayers_spectator_blocktime))
+               {
+                       if (dropclient_schedule(this))
+                               Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_QUIT_KICK_SPECTATING);
+               }
        }
 
        zoomstate_set = false;
@@ -2534,24 +2423,24 @@ void PlayerPreThink (entity this)
                        // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe?
                }
                if (!assume_unchanged && autocvar_sv_eventlog)
-                       GameLogEcho(strcat(":name:", ftos(this.playerid), ":", playername(this, false)));
+                       GameLogEcho(strcat(":name:", ftos(this.playerid), ":", playername(this.netname, this.team, false)));
                strcpy(CS(this).netname_previous, this.netname);
        }
 
        // version nagging
-       if (CS(this).version_nagtime && CS(this).cvar_g_xonoticversion && time > CS(this).version_nagtime) {
+       if (CS(this).version_nagtime && CS_CVAR(this).cvar_g_xonoticversion && time > CS(this).version_nagtime) {
         CS(this).version_nagtime = 0;
-        if (strstrofs(CS(this).cvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(CS(this).cvar_g_xonoticversion, "autobuild", 0) >= 0) {
+        if (strstrofs(CS_CVAR(this).cvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(CS_CVAR(this).cvar_g_xonoticversion, "autobuild", 0) >= 0) {
             // git client
         } else if (strstrofs(autocvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(autocvar_g_xonoticversion, "autobuild", 0) >= 0) {
             // git server
-            Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_BETA, autocvar_g_xonoticversion, CS(this).cvar_g_xonoticversion);
+            Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_BETA, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
         } else {
-            int r = vercmp(CS(this).cvar_g_xonoticversion, autocvar_g_xonoticversion);
+            int r = vercmp(CS_CVAR(this).cvar_g_xonoticversion, autocvar_g_xonoticversion);
             if (r < 0) { // old client
-                Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OUTDATED, autocvar_g_xonoticversion, CS(this).cvar_g_xonoticversion);
+                Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OUTDATED, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
             } else if (r > 0) { // old server
-                Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OLD, autocvar_g_xonoticversion, CS(this).cvar_g_xonoticversion);
+                Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OLD, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
             }
         }
     }
@@ -2563,7 +2452,7 @@ void PlayerPreThink (entity this)
                this.max_armorvalue = 0;
        }
 
-       if (frametime && IS_PLAYER(this))
+       if (frametime && IS_PLAYER(this) && time >= game_starttime)
        {
                if (STAT(FROZEN, this) == FROZEN_TEMP_REVIVING)
                {
@@ -2615,7 +2504,7 @@ void PlayerPreThink (entity this)
                this.last_vehiclecheck = time + 1;
        }
 
-       if(!CS(this).cvar_cl_newusekeysupported) // FIXME remove this - it was a stupid idea to begin with, we can JUST use the button
+       if(!CS_CVAR(this).cvar_cl_newusekeysupported) // FIXME remove this - it was a stupid idea to begin with, we can JUST use the button
        {
                if(PHYS_INPUT_BUTTON_USE(this) && !CS(this).usekeypressed)
                        PlayerUseKey(this);
@@ -2686,15 +2575,6 @@ void PlayerPreThink (entity this)
        }
 
        target_voicescript_next(this);
-
-       // WEAPONTODO: Move into weaponsystem somehow
-       // if a player goes unarmed after holding a loaded weapon, empty his clip size and remove the crosshair ammo ring
-       for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
-       {
-               .entity weaponentity = weaponentities[slot];
-               if(this.(weaponentity).m_weapon == WEP_Null)
-                       this.(weaponentity).clip_load = this.(weaponentity).clip_size = 0;
-       }
 }
 
 void DrownPlayer(entity this)
@@ -2755,13 +2635,14 @@ void PlayerPostThink (entity this)
 {
        Player_Physics(this);
 
-       if (autocvar_sv_maxidle > 0)
+       if (autocvar_sv_maxidle > 0 || (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 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) || autocvar_sv_maxidle_spectatorsareidle)
+       if (IS_PLAYER(this) || autocvar_sv_maxidle_alsokickspectators)
+       if (!intermission_running) // NextLevel() kills all centerprints after setting this true
        {
                int totalClients = 0;
-               if(autocvar_sv_maxidle_slots > 0)
+               if(autocvar_sv_maxidle > 0 && autocvar_sv_maxidle_slots > 0)
                {
                        FOREACH_CLIENT(IS_REAL_CLIENT(it) || autocvar_sv_maxidle_slots_countbots,
                        {
@@ -2769,7 +2650,7 @@ void PlayerPostThink (entity this)
                        });
                }
 
-               if (autocvar_sv_maxidle_slots > 0 && (maxclients - totalClients) > autocvar_sv_maxidle_slots)
+               if (autocvar_sv_maxidle > 0 && 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
                {
@@ -2781,19 +2662,36 @@ void PlayerPostThink (entity this)
                }
                else
                {
-                       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)
+                       float maxidle_time = autocvar_sv_maxidle;
+                       if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)
+                               maxidle_time = autocvar_sv_maxidle_playertospectator;
+                       float timeleft = ceil(maxidle_time - (time - CS(this).parm_idlesince));
+                       float countdown_time = max(min(10, maxidle_time - 1), ceil(maxidle_time * 0.33)); // - 1 to support maxidle_time <= 10
+                       if (timeleft == countdown_time && !CS(this).idlekick_lasttimeleft)
+                       {
+                               if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)
+                                       Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_MOVETOSPEC_IDLING, timeleft);
+                               else
                                        Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_DISCONNECT_IDLING, timeleft);
                        }
                        if (timeleft <= 0) {
-                               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_KICK_IDLING, this.netname);
-                               dropclient(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;
+                                       PutObserverInServer(this);
+                               }
+                               else
+                               {
+                                       if (dropclient_schedule(this))
+                                               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_KICK_IDLING, this.netname, maxidle_time);
+                               }
                                return;
                        }
-                       else if (timeleft <= 10) {
+                       else if (timeleft <= countdown_time) {
                                if (timeleft != CS(this).idlekick_lasttimeleft)
-                                       Send_Notification(NOTIF_ONE, this, MSG_ANNCE, Announcer_PickNumber(CNT_IDLE, timeleft));
+                                       play2(this, SND(TALK2));
                                CS(this).idlekick_lasttimeleft = timeleft;
                        }
                }
@@ -2822,12 +2720,12 @@ void PlayerPostThink (entity this)
                DrownPlayer(this);
                UpdateChatBubble(this);
                if (CS(this).impulse) ImpulseCommands(this);
+               GetPressedKeys(this);
                if (game_stopped)
                {
                        CSQCMODEL_AUTOUPDATE(this);
                        return;
                }
-               GetPressedKeys(this);
        }
        else if (IS_OBSERVER(this) && STAT(PRESSED_KEYS, this))
        {
@@ -2843,332 +2741,6 @@ void PlayerPostThink (entity this)
        CSQCMODEL_AUTOUPDATE(this);
 }
 
-/**
- * message "": do not say, just test flood control
- * return value:
- *   1 = accept
- *   0 = reject
- *  -1 = fake accept
- */
-int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodcontrol)
-{
-       if (!teamsay && !privatesay && substring(msgin, 0, 1) == " ")
-               msgin = substring(msgin, 1, -1); // work around DP say bug (say_team does not have this!)
-
-       if (source)
-               msgin = formatmessage(source, msgin);
-
-       string colorstr;
-       if (!(IS_PLAYER(source) || source.caplayer))
-               colorstr = "^0"; // black for spectators
-       else if(teamplay)
-               colorstr = Team_ColorCode(source.team);
-       else
-       {
-               colorstr = "";
-               teamsay = false;
-       }
-
-       if (!source) {
-               colorstr = "";
-               teamsay = false;
-       }
-
-       if(msgin != "")
-               msgin = trigger_magicear_processmessage_forallears(source, teamsay, privatesay, msgin);
-
-       /*
-        * using bprint solves this... me stupid
-       // how can we prevent the message from appearing in a listen server?
-       // for now, just give "say" back and only handle say_team
-       if(!teamsay)
-       {
-               clientcommand(source, strcat("say ", msgin));
-               return;
-       }
-       */
-
-       string namestr = "";
-       if (source)
-               namestr = playername(source, autocvar_g_chat_teamcolors);
-
-       string colorprefix = (strdecolorize(namestr) == namestr) ? "^3" : "^7";
-
-       string msgstr = "", cmsgstr = "";
-       string privatemsgprefix = string_null;
-       int privatemsgprefixlen = 0;
-       if (msgin != "")
-       {
-               bool found_me = false;
-               if(strstrofs(msgin, "/me", 0) >= 0)
-               {
-                       string newmsgin = "";
-                       string newnamestr = ((teamsay) ? strcat(colorstr, "(", colorprefix, namestr, colorstr, ")", "^7") : strcat(colorprefix, namestr, "^7"));
-                       FOREACH_WORD(msgin, true,
-                       {
-                               if(strdecolorize(it) == "/me")
-                               {
-                                       found_me = true;
-                                       newmsgin = cons(newmsgin, newnamestr);
-                               }
-                               else
-                                       newmsgin = cons(newmsgin, it);
-                       });
-                       msgin = newmsgin;
-               }
-
-               if(privatesay)
-               {
-                       msgstr = strcat("\{1}\{13}* ", colorprefix, namestr, "^3 tells you: ^7");
-                       privatemsgprefixlen = strlen(msgstr);
-                       msgstr = strcat(msgstr, msgin);
-                       cmsgstr = strcat(colorstr, colorprefix, namestr, "^3 tells you:\n^7", msgin);
-                       privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", playername(privatesay, autocvar_g_chat_teamcolors), ": ^7");
-               }
-               else if(teamsay)
-               {
-                       if(found_me)
-                       {
-                               //msgin = strreplace("/me", "", msgin);
-                               //msgin = substring(msgin, 3, strlen(msgin));
-                               //msgin = strreplace("/me", strcat(colorstr, "(", colorprefix, namestr, colorstr, ")^7"), msgin);
-                               msgstr = strcat("\{1}\{13}^4* ", "^7", msgin);
-                       }
-                       else
-                               msgstr = strcat("\{1}\{13}", colorstr, "(", colorprefix, namestr, colorstr, ") ^7", msgin);
-                       cmsgstr = strcat(colorstr, "(", colorprefix, namestr, colorstr, ")\n^7", msgin);
-               }
-               else
-               {
-                       if(found_me)
-                       {
-                               //msgin = strreplace("/me", "", msgin);
-                               //msgin = substring(msgin, 3, strlen(msgin));
-                               //msgin = strreplace("/me", strcat(colorprefix, namestr), msgin);
-                               msgstr = strcat("\{1}^4* ^7", msgin);
-                       }
-                       else {
-                               msgstr = "\{1}";
-                               msgstr = strcat(msgstr, (namestr != "") ? strcat(colorprefix, namestr, "^7: ") : "^7");
-                               msgstr = strcat(msgstr, msgin);
-                       }
-                       cmsgstr = "";
-               }
-               msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint
-       }
-
-       string fullmsgstr = msgstr;
-       string fullcmsgstr = cmsgstr;
-
-       // FLOOD CONTROL
-       int flood = 0;
-       var .float flood_field = floodcontrol_chat;
-       if(floodcontrol && source)
-       {
-               float flood_spl, flood_burst, flood_lmax;
-               if(privatesay)
-               {
-                       flood_spl = autocvar_g_chat_flood_spl_tell;
-                       flood_burst = autocvar_g_chat_flood_burst_tell;
-                       flood_lmax = autocvar_g_chat_flood_lmax_tell;
-                       flood_field = floodcontrol_chattell;
-               }
-               else if(teamsay)
-               {
-                       flood_spl = autocvar_g_chat_flood_spl_team;
-                       flood_burst = autocvar_g_chat_flood_burst_team;
-                       flood_lmax = autocvar_g_chat_flood_lmax_team;
-                       flood_field = floodcontrol_chatteam;
-               }
-               else
-               {
-                       flood_spl = autocvar_g_chat_flood_spl;
-                       flood_burst = autocvar_g_chat_flood_burst;
-                       flood_lmax = autocvar_g_chat_flood_lmax;
-                       flood_field = floodcontrol_chat;
-               }
-               flood_burst = max(0, flood_burst - 1);
-               // to match explanation in default.cfg, a value of 3 must allow three-line bursts and not four!
-
-               // do flood control for the default line size
-               if(msgstr != "")
-               {
-                       getWrappedLine_remaining = msgstr;
-                       msgstr = "";
-                       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
-                               ++lines;
-                       }
-                       msgstr = substring(msgstr, 1, strlen(msgstr) - 1);
-
-                       if(getWrappedLine_remaining != "")
-                       {
-                               msgstr = strcat(msgstr, "\n");
-                               flood = 2;
-                       }
-
-                       if (time >= source.(flood_field))
-                       {
-                               source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + lines * flood_spl;
-                       }
-                       else
-                       {
-                               flood = 1;
-                               msgstr = fullmsgstr;
-                       }
-               }
-               else
-               {
-                       if (time >= source.(flood_field))
-                               source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + flood_spl;
-                       else
-                               flood = 1;
-               }
-
-               if (timeout_status == TIMEOUT_ACTIVE) // when game is paused, no flood protection
-                       source.(flood_field) = flood = 0;
-       }
-
-       string sourcemsgstr, sourcecmsgstr;
-       if(flood == 2) // cannot happen for empty msgstr
-       {
-               if(autocvar_g_chat_flood_notify_flooder)
-               {
-                       sourcemsgstr = strcat(msgstr, "\n^3FLOOD CONTROL: ^7message too long, trimmed\n");
-                       sourcecmsgstr = "";
-               }
-               else
-               {
-                       sourcemsgstr = fullmsgstr;
-                       sourcecmsgstr = fullcmsgstr;
-               }
-               cmsgstr = "";
-       }
-       else
-       {
-               sourcemsgstr = msgstr;
-               sourcecmsgstr = cmsgstr;
-       }
-
-       if (!privatesay && source && !(IS_PLAYER(source) || source.caplayer) && !game_stopped
-               && (teamsay || CHAT_NOSPECTATORS()))
-       {
-               teamsay = -1; // spectators
-       }
-
-       if(flood)
-               LOG_INFO("NOTE: ", playername(source, true), "^7 is flooding.");
-
-       // build sourcemsgstr by cutting off a prefix and replacing it by the other one
-       if(privatesay)
-               sourcemsgstr = strcat(privatemsgprefix, substring(sourcemsgstr, privatemsgprefixlen, -1));
-
-       int ret;
-       if(source && CS(source).muted)
-       {
-               // always fake the message
-               ret = -1;
-       }
-       else if(flood == 1)
-       {
-               if (autocvar_g_chat_flood_notify_flooder)
-               {
-                       sprint(source, strcat("^3FLOOD CONTROL: ^7wait ^1", ftos(source.(flood_field) - time), "^3 seconds\n"));
-                       ret = 0;
-               }
-               else
-                       ret = -1;
-       }
-       else
-       {
-               ret = 1;
-       }
-
-       if (privatesay && source && !(IS_PLAYER(source) || source.caplayer) && !game_stopped
-               && (IS_PLAYER(privatesay) || privatesay.caplayer) && CHAT_NOSPECTATORS())
-       {
-               ret = -1; // just hide the message completely
-       }
-
-       MUTATOR_CALLHOOK(ChatMessage, source, ret);
-       ret = M_ARGV(1, int);
-
-       string event_log_msg = "";
-
-       if(sourcemsgstr != "" && ret != 0)
-       {
-               if(ret < 0) // faked message, because the player is muted
-               {
-                       sprint(source, sourcemsgstr);
-                       if(sourcecmsgstr != "" && !privatesay)
-                               centerprint(source, sourcecmsgstr);
-               }
-               else if(privatesay) // private message, between 2 people only
-               {
-                       sprint(source, sourcemsgstr);
-                       if (!autocvar_g_chat_tellprivacy) { dedicated_print(msgstr); } // send to server console too if "tellprivacy" is disabled
-                       if(!MUTATOR_CALLHOOK(ChatMessageTo, privatesay, source))
-                       {
-                               sprint(privatesay, msgstr);
-                               if(cmsgstr != "")
-                                       centerprint(privatesay, cmsgstr);
-                       }
-               }
-               else if ( teamsay && CS(source).active_minigame )
-               {
-                       sprint(source, sourcemsgstr);
-                       dedicated_print(msgstr); // send to server console too
-                       FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && CS(it).active_minigame == CS(source).active_minigame && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
-                               sprint(it, msgstr);
-                       });
-                       event_log_msg = sprintf(":chat_minigame:%d:%s:%s", source.playerid, CS(source).active_minigame.netname, msgin);
-
-               }
-               else if(teamsay > 0) // team message, only sent to team mates
-               {
-                       sprint(source, sourcemsgstr);
-                       dedicated_print(msgstr); // send to server console too
-                       if(sourcecmsgstr != "")
-                               centerprint(source, sourcecmsgstr);
-                       FOREACH_CLIENT((IS_PLAYER(it) || it.caplayer) && IS_REAL_CLIENT(it) && it != source && it.team == source.team && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
-                               sprint(it, msgstr);
-                               if(cmsgstr != "")
-                                       centerprint(it, cmsgstr);
-                       });
-                       event_log_msg = sprintf(":chat_team:%d:%d:%s", source.playerid, source.team, strreplace("\n", " ", msgin));
-               }
-               else if(teamsay < 0) // spectator message, only sent to spectators
-               {
-                       sprint(source, sourcemsgstr);
-                       dedicated_print(msgstr); // send to server console too
-                       FOREACH_CLIENT(!(IS_PLAYER(it) || it.caplayer) && IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
-                               sprint(it, msgstr);
-                       });
-                       event_log_msg = sprintf(":chat_spec:%d:%s", source.playerid, strreplace("\n", " ", msgin));
-               }
-               else
-               {
-                       if (source) {
-                               sprint(source, sourcemsgstr);
-                               dedicated_print(msgstr); // send to server console too
-                               MX_Say(strcat(playername(source, true), "^7: ", msgin));
-                       }
-                       FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
-                               sprint(it, msgstr);
-                       });
-                       event_log_msg = sprintf(":chat:%d:%s", source.playerid, strreplace("\n", " ", msgin));
-               }
-       }
-
-       if (autocvar_sv_eventlog && (event_log_msg != "")) {
-               GameLogEcho(event_log_msg);
-       }
-
-       return ret;
-}
-
 // hack to copy the button fields from the client entity to the Client State
 void PM_UpdateButtons(entity this, entity store)
 {