]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/cl_player.qc
Bots: define the API boundaries
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / cl_player.qc
index c6b0359c4ee89bd20f3b05de8df71eae2c251f7f..a542e6431345095828ccd90dc87dd9ecc081406e 100644 (file)
@@ -1,6 +1,6 @@
 #include "cl_player.qh"
 
-#include "bot/bot.qh"
+#include "bot/api.qh"
 #include "cheats.qh"
 #include "g_damage.qh"
 #include "g_subs.qh"
@@ -9,6 +9,7 @@
 #include "teamplay.qh"
 #include "weapons/throwing.qh"
 #include "command/common.qh"
+#include "../common/state.qh"
 #include "../common/anim.qh"
 #include "../common/animdecide.qh"
 #include "../common/csqcmodel_settings.qh"
@@ -19,6 +20,8 @@
 
 #include "../common/minigames/sv_minigames.qh"
 
+#include "../common/physics/player.qh"
+#include "../common/effects/qc/all.qh"
 #include "../common/mutators/mutator/waypoints/waypointsprites.qh"
 #include "../common/triggers/include.qh"
 
@@ -34,107 +37,107 @@ void Drop_Special_Items(entity player)
        MUTATOR_CALLHOOK(DropSpecialItems, player);
 }
 
-void CopyBody_Think()
-{SELFPARAM();
-       if(self.CopyBody_nextthink && time > self.CopyBody_nextthink)
+void CopyBody_Think(entity this)
+{
+       if(this.CopyBody_nextthink && time > this.CopyBody_nextthink)
        {
-               self.CopyBody_think();
-               if(wasfreed(self))
+               this.CopyBody_think(this);
+               if(wasfreed(this))
                        return;
-               self.CopyBody_nextthink = self.nextthink;
-               self.CopyBody_think = self.think;
-               self.think = CopyBody_Think;
+               this.CopyBody_nextthink = this.nextthink;
+               this.CopyBody_think = getthink(this);
+               setthink(this, CopyBody_Think);
        }
-       CSQCMODEL_AUTOUPDATE(self);
-       self.nextthink = time;
+       CSQCMODEL_AUTOUPDATE(this);
+       this.nextthink = time;
 }
-void CopyBody(float keepvelocity)
-{SELFPARAM();
-       if (self.effects & EF_NODRAW)
+void CopyBody(entity this, float keepvelocity)
+{
+       if (this.effects & EF_NODRAW)
                return;
-       setself(new(body));
-       self.enemy = this;
-       self.lip = this.lip;
-       self.colormap = this.colormap;
-       self.iscreature = this.iscreature;
-       self.teleportable = this.teleportable;
-       self.damagedbycontents = this.damagedbycontents;
-       self.angles = this.angles;
-       self.v_angle = this.v_angle;
-       self.avelocity = this.avelocity;
-       self.damageforcescale = this.damageforcescale;
-       self.effects = this.effects;
-       self.glowmod = this.glowmod;
-       self.event_damage = this.event_damage;
-       self.anim_state = this.anim_state;
-       self.anim_time = this.anim_time;
-       self.anim_lower_action = this.anim_lower_action;
-       self.anim_lower_time = this.anim_lower_time;
-       self.anim_upper_action = this.anim_upper_action;
-       self.anim_upper_time = this.anim_upper_time;
-       self.anim_implicit_state = this.anim_implicit_state;
-       self.anim_implicit_time = this.anim_implicit_time;
-       self.anim_lower_implicit_action = this.anim_lower_implicit_action;
-       self.anim_lower_implicit_time = this.anim_lower_implicit_time;
-       self.anim_upper_implicit_action = this.anim_upper_implicit_action;
-       self.anim_upper_implicit_time = this.anim_upper_implicit_time;
-       self.dphitcontentsmask = this.dphitcontentsmask;
-       self.death_time = this.death_time;
-       self.pain_finished = this.pain_finished;
-       self.health = this.health;
-       self.armorvalue = this.armorvalue;
-       self.armortype = this.armortype;
-       self.model = this.model;
-       self.modelindex = this.modelindex;
-       self.skin = this.skin;
-       self.species = this.species;
-       self.movetype = this.movetype;
-       self.solid = this.solid;
-       self.ballistics_density = this.ballistics_density;
-       self.takedamage = this.takedamage;
-       self.customizeentityforclient = this.customizeentityforclient;
-       self.uncustomizeentityforclient = this.uncustomizeentityforclient;
-       self.uncustomizeentityforclient_set = this.uncustomizeentityforclient_set;
+       entity clone = new(body);
+       clone.enemy = this;
+       clone.lip = this.lip;
+       clone.colormap = this.colormap;
+       clone.iscreature = this.iscreature;
+       clone.teleportable = this.teleportable;
+       clone.damagedbycontents = this.damagedbycontents;
+       clone.angles = this.angles;
+       clone.v_angle = this.v_angle;
+       clone.avelocity = this.avelocity;
+       clone.damageforcescale = this.damageforcescale;
+       clone.effects = this.effects;
+       clone.glowmod = this.glowmod;
+       clone.event_damage = this.event_damage;
+       clone.anim_state = this.anim_state;
+       clone.anim_time = this.anim_time;
+       clone.anim_lower_action = this.anim_lower_action;
+       clone.anim_lower_time = this.anim_lower_time;
+       clone.anim_upper_action = this.anim_upper_action;
+       clone.anim_upper_time = this.anim_upper_time;
+       clone.anim_implicit_state = this.anim_implicit_state;
+       clone.anim_implicit_time = this.anim_implicit_time;
+       clone.anim_lower_implicit_action = this.anim_lower_implicit_action;
+       clone.anim_lower_implicit_time = this.anim_lower_implicit_time;
+       clone.anim_upper_implicit_action = this.anim_upper_implicit_action;
+       clone.anim_upper_implicit_time = this.anim_upper_implicit_time;
+       clone.dphitcontentsmask = this.dphitcontentsmask;
+       clone.death_time = this.death_time;
+       clone.pain_finished = this.pain_finished;
+       clone.health = this.health;
+       clone.armorvalue = this.armorvalue;
+       clone.armortype = this.armortype;
+       clone.model = this.model;
+       clone.modelindex = this.modelindex;
+       clone.skin = this.skin;
+       clone.species = this.species;
+       clone.move_qcphysics = false; // don't run gamecode logic on clones, too many
+       set_movetype(clone, this.move_movetype);
+       clone.solid = this.solid;
+       clone.ballistics_density = this.ballistics_density;
+       clone.takedamage = this.takedamage;
+       setcefc(clone, getcefc(this));
+       clone.uncustomizeentityforclient = this.uncustomizeentityforclient;
+       clone.uncustomizeentityforclient_set = this.uncustomizeentityforclient_set;
        if (keepvelocity == 1)
-               self.velocity = this.velocity;
-       self.oldvelocity = self.velocity;
-       self.alpha = this.alpha;
-       self.fade_time = this.fade_time;
-       self.fade_rate = this.fade_rate;
-       //self.weapon = this.weapon;
-       setorigin(self, this.origin);
-       setsize(self, this.mins, this.maxs);
-       self.prevorigin = this.origin;
-       self.reset = SUB_Remove;
-
-       Drag_MoveDrag(this, self);
-
-       if(self.colormap <= maxclients && self.colormap > 0)
-               self.colormap = 1024 + this.clientcolors;
-
-       CSQCMODEL_AUTOINIT(self);
-       self.CopyBody_nextthink = this.nextthink;
-       self.CopyBody_think = this.think;
-       self.nextthink = time;
-       self.think = CopyBody_Think;
+               clone.velocity = this.velocity;
+       clone.oldvelocity = clone.velocity;
+       clone.alpha = this.alpha;
+       clone.fade_time = this.fade_time;
+       clone.fade_rate = this.fade_rate;
+       //clone.weapon = this.weapon;
+       setorigin(clone, this.origin);
+       setsize(clone, this.mins, this.maxs);
+       clone.prevorigin = this.origin;
+       clone.reset = SUB_Remove;
+       clone._ps = this._ps;
+
+       Drag_MoveDrag(this, clone);
+
+       if(clone.colormap <= maxclients && clone.colormap > 0)
+               clone.colormap = 1024 + this.clientcolors;
+
+       CSQCMODEL_AUTOINIT(clone);
+       clone.CopyBody_nextthink = this.nextthink;
+       clone.CopyBody_think = getthink(this);
+       clone.nextthink = time;
+       setthink(clone, CopyBody_Think);
        // "bake" the current animation frame for clones (they don't get clientside animation)
-       animdecide_load_if_needed(self);
-       animdecide_setframes(self, false, frame, frame1time, frame2, frame2time);
-
-       setself(this);
+       animdecide_load_if_needed(clone);
+       animdecide_setframes(clone, false, frame, frame1time, frame2, frame2time);
 }
 
-void player_setupanimsformodel()
-{SELFPARAM();
+void player_setupanimsformodel(entity this)
+{
        // load animation info
-       animdecide_load_if_needed(self);
-       animdecide_setstate(self, 0, false);
+       animdecide_load_if_needed(this);
+       animdecide_setstate(this, 0, false);
 }
 
-void player_anim ()
-{SELFPARAM();
-       int deadbits = (self.anim_state & (ANIMSTATE_DEAD1 | ANIMSTATE_DEAD2));
-       if(IS_DEAD(self)) {
+void player_anim(entity this)
+{
+       int deadbits = (this.anim_state & (ANIMSTATE_DEAD1 | ANIMSTATE_DEAD2));
+       if(IS_DEAD(this)) {
                if (!deadbits) {
                        // Decide on which death animation to use.
                        if(random() < 0.5)
@@ -147,14 +150,14 @@ void player_anim ()
                deadbits = 0;
        }
        int animbits = deadbits;
-       if(STAT(FROZEN, self))
+       if(STAT(FROZEN, this))
                animbits |= ANIMSTATE_FROZEN;
-       if(self.movetype == MOVETYPE_FOLLOW)
+       if(this.move_movetype == MOVETYPE_FOLLOW)
                animbits |= ANIMSTATE_FOLLOW;
-       if(self.crouch)
+       if(this.crouch)
                animbits |= ANIMSTATE_DUCK;
-       animdecide_setstate(self, animbits, false);
-       animdecide_setimplicitstate(self, (IS_ONGROUND(self)));
+       animdecide_setstate(this, animbits, false);
+       animdecide_setimplicitstate(this, IS_ONGROUND(this));
 }
 
 void PlayerCorpseDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
@@ -362,7 +365,7 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
        {
                this.pusher = attacker;
                this.pushltime = time + autocvar_g_maxpushtime;
-               this.istypefrag = this.BUTTON_CHAT;
+               this.istypefrag = PHYS_INPUT_BUTTON_CHAT(this);
        }
        else if(time < this.pushltime)
        {
@@ -375,10 +378,16 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
                this.istypefrag = 0;
        }
 
-       frag_damage = damage;
-       MUTATOR_CALLHOOK(PlayerDamage_SplitHealthArmor, inflictor, attacker, this, force, take, save);
-       take = bound(0, damage_take, this.health);
-       save = bound(0, damage_save, this.armorvalue);
+       if (time < this.spawnshieldtime && autocvar_g_spawnshield_blockdamage < 1)
+       {
+               vector v = healtharmor_applydamage(this.armorvalue, max(0, autocvar_g_spawnshield_blockdamage), deathtype, damage);
+               take = v.x;
+               save = v.y;
+       }
+
+       MUTATOR_CALLHOOK(PlayerDamage_SplitHealthArmor, inflictor, attacker, this, force, take, save, deathtype, damage);
+       take = bound(0, M_ARGV(4, float), this.health);
+       save = bound(0, M_ARGV(5, float), this.armorvalue);
        excess = max(0, damage - take - save);
 
        if(sound_allowed(MSG_BROADCAST, attacker))
@@ -396,7 +405,7 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
        if (take > 100)
                Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, this, attacker);
 
-       if (time >= this.spawnshieldtime)
+       if (time >= this.spawnshieldtime || autocvar_g_spawnshield_blockdamage < 1)
        {
                if (!(this.flags & FL_GODMODE))
                {
@@ -428,15 +437,15 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
                                        // exclude pain sounds for laserjumps as long as you aren't REALLY low on health and would die of the next two
                                        {
                                                if(deathtype == DEATH_FALL.m_id)
-                                                       PlayerSound(this, playersound_fall, CH_PAIN, VOICETYPE_PLAYERSOUND);
-                                               else if(this.health > 75) // TODO make a "gentle" version?
-                                                       PlayerSound(this, playersound_pain100, CH_PAIN, VOICETYPE_PLAYERSOUND);
+                                                       PlayerSound(this, playersound_fall, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
+                                               else if(this.health > 75)
+                                                       PlayerSound(this, playersound_pain100, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
                                                else if(this.health > 50)
-                                                       PlayerSound(this, playersound_pain75, CH_PAIN, VOICETYPE_PLAYERSOUND);
+                                                       PlayerSound(this, playersound_pain75, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
                                                else if(this.health > 25)
-                                                       PlayerSound(this, playersound_pain50, CH_PAIN, VOICETYPE_PLAYERSOUND);
+                                                       PlayerSound(this, playersound_pain50, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
                                                else
-                                                       PlayerSound(this, playersound_pain25, CH_PAIN, VOICETYPE_PLAYERSOUND);
+                                                       PlayerSound(this, playersound_pain25, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
                                        }
                                }
                        }
@@ -486,11 +495,14 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
                valid_damage_for_weaponstats = 1;
        }
 
+       dh = dh - max(this.health, 0);
+       da = da - max(this.armorvalue, 0);
        if(valid_damage_for_weaponstats)
        {
-               dh = dh - max(this.health, 0);
-               da = da - max(this.armorvalue, 0);
                WeaponStats_LogDamage(awep.m_id, abot, PS(this).m_weapon.m_id, vbot, dh + da);
+       }
+       if (dh + da)
+       {
                MUTATOR_CALLHOOK(PlayerDamaged, attacker, this, dh, da, hitloc, deathtype);
        }
 
@@ -508,20 +520,20 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
                if(valid_damage_for_weaponstats)
                        WeaponStats_LogKill(awep.m_id, abot, PS(this).m_weapon.m_id, vbot);
 
-               if(autocvar_sv_gentle < 1) // TODO make a "gentle" version?
+               if(autocvar_sv_gentle < 1)
                if(sound_allowed(MSG_BROADCAST, attacker))
                {
                        if(deathtype == DEATH_DROWN.m_id)
-                               PlayerSound(this, playersound_drown, CH_PAIN, VOICETYPE_PLAYERSOUND);
+                               PlayerSound(this, playersound_drown, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
                        else
-                               PlayerSound(this, playersound_death, CH_PAIN, VOICETYPE_PLAYERSOUND);
+                               PlayerSound(this, playersound_death, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
                }
 
                // get rid of kill indicator
                if(this.killindicator)
                {
-                       remove(this.killindicator);
-                       this.killindicator = world;
+                       delete(this.killindicator);
+                       this.killindicator = NULL;
                        if(this.killindicator_teamchange)
                                defer_ClientKill_Now_TeamChange = true;
 
@@ -543,11 +555,11 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
        if(accuracy_isgooddamage(attacker, this))
         attacker.accuracy.(accuracy_frags[w.m_id-1]) += 1;
 
-               MUTATOR_CALLHOOK(PlayerDies, inflictor, attacker, this, deathtype);
-               excess = frag_damage;
+               MUTATOR_CALLHOOK(PlayerDies, inflictor, attacker, this, deathtype, damage);
+               excess = M_ARGV(4, float);
 
                Weapon wep = PS(this).m_weapon;
-               WITH(entity, self, this, wep.wr_playerdeath(wep));
+               wep.wr_playerdeath(wep, this);
 
                RemoveGrapplingHook(this);
 
@@ -583,7 +595,7 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
                // view from the floor
                this.view_ofs = '0 0 -8';
                // toss the corpse
-               this.movetype = MOVETYPE_TOSS;
+               set_movetype(this, MOVETYPE_TOSS);
                // shootable corpse
                this.solid = SOLID_CORPSE;
                this.ballistics_density = autocvar_g_ballistics_density_corpse;
@@ -613,10 +625,10 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
                // set up to fade out later
                SUB_SetFade (this, time + 6 + random (), 1);
                // reset body think wrapper broken by SUB_SetFade
-               if(this.classname == "body" && this.think != CopyBody_Think) {
-                       this.CopyBody_think = this.think;
+               if(this.classname == "body" && getthink(this) != CopyBody_Think) {
+                       this.CopyBody_think = getthink(this);
                        this.CopyBody_nextthink = this.nextthink;
-                       this.think = CopyBody_Think;
+                       setthink(this, CopyBody_Think);
                        this.nextthink = time;
                }
 
@@ -628,7 +640,7 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
 
                // reset fields the weapons may use just in case
                FOREACH(Weapons, it != WEP_Null, LAMBDA(
-                       WITH(entity, self, this, it.wr_resetplayer(it));
+                       it.wr_resetplayer(it, this);
                        for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
                        {
                                ATTACK_FINISHED_FOR(this, it.m_id, slot) = 0;
@@ -648,6 +660,12 @@ void MoveToTeam(entity client, int team_colour, int type)
        LogTeamchange(client.playerid, client.team, type);
 }
 
+/** print(), but only print if the server is not local */
+void dedicated_print(string input)
+{
+       if (server_is_dedicated) print(input);
+}
+
 /**
  * message "": do not say, just test flood control
  * return value:
@@ -655,20 +673,14 @@ void MoveToTeam(entity client, int team_colour, int type)
  *   0 = reject
  *  -1 = fake accept
  */
-int Say(entity source, float teamsay, entity privatesay, string msgin, bool floodcontrol)
+int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodcontrol)
 {
-       string msgstr, colorstr, cmsgstr, namestr, fullmsgstr, sourcemsgstr, fullcmsgstr, sourcecmsgstr, colorprefix;
-       float flood;
-       var .float flood_field;
-       float ret;
-       string privatemsgprefix = string_null; float privatemsgprefixlen = 0;
-
-       if(!teamsay && !privatesay)
-               if(substring(msgin, 0, 1) == " ")
-                       msgin = substring(msgin, 1, strlen(msgin) - 1); // work around DP say bug (say_team does not have this!)
+       if (!teamsay && !privatesay) if (substring(msgin, 0, 1) == " ")
+        msgin = substring(msgin, 1, -1); // work around DP say bug (say_team does not have this!)
 
-       msgin = formatmessage(msgin);
+       msgin = formatmessage(source, msgin);
 
+    string colorstr;
        if (!IS_PLAYER(source))
                colorstr = "^0"; // black for spectators
        else if(teamplay)
@@ -682,6 +694,11 @@ int Say(entity source, float teamsay, entity privatesay, string msgin, bool floo
        if(intermission_running)
                teamsay = false;
 
+    if (!source) {
+               colorstr = "";
+               teamsay = false;
+    }
+
        if(msgin != "")
                msgin = trigger_magicear_processmessage_forallears(source, teamsay, privatesay, msgin);
 
@@ -691,23 +708,23 @@ int Say(entity source, float teamsay, entity privatesay, string msgin, bool floo
        // for now, just give "say" back and only handle say_team
        if(!teamsay)
        {
-               clientcommand(self, strcat("say ", msgin));
+               clientcommand(source, strcat("say ", msgin));
                return;
        }
        */
 
-       if(autocvar_g_chat_teamcolors)
-               namestr = playername(source);
-       else
-               namestr = source.netname;
+    string namestr = "";
+    if (source)
+        namestr = autocvar_g_chat_teamcolors ? playername(source) : source.netname;
 
-       if(strdecolorize(namestr) == namestr)
-               colorprefix = "^3";
-       else
-               colorprefix = "^7";
+    string colorprefix = (strdecolorize(namestr) == namestr) ? "^3" : "^7";
 
-       if(msgin != "")
-       {
+    string msgstr, cmsgstr;
+    string privatemsgprefix = string_null;
+    int privatemsgprefixlen = 0;
+       if (msgin == "") {
+        msgstr = cmsgstr = "";
+       } else {
                if(privatesay)
                {
                        msgstr = strcat("\{1}\{13}* ", colorprefix, namestr, "^3 tells you: ^7");
@@ -741,23 +758,22 @@ int Say(entity source, float teamsay, entity privatesay, string msgin, bool floo
                                msgin = strreplace("/me", strcat(colorprefix, namestr), msgin);
                                msgstr = strcat("\{1}^4* ", "^7", msgin);
                        }
-                       else
-                               msgstr = strcat("\{1}", colorprefix, namestr, "^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
        }
-       else
-       {
-               msgstr = cmsgstr = "";
-       }
 
-       fullmsgstr = msgstr;
-       fullcmsgstr = cmsgstr;
+       string fullmsgstr = msgstr;
+       string fullcmsgstr = cmsgstr;
 
        // FLOOD CONTROL
-       flood = 0;
-       flood_field = floodcontrol_chat;
+       int flood = 0;
+       var .float flood_field = floodcontrol_chat;
        if(floodcontrol)
        {
                float flood_spl;
@@ -829,6 +845,7 @@ int Say(entity source, float teamsay, entity privatesay, string msgin, bool floo
                        source.(flood_field) = flood = 0;
        }
 
+    string sourcemsgstr, sourcecmsgstr;
        if(flood == 2) // cannot happen for empty msgstr
        {
                if(autocvar_g_chat_flood_notify_flooder)
@@ -849,8 +866,7 @@ int Say(entity source, float teamsay, entity privatesay, string msgin, bool floo
                sourcecmsgstr = cmsgstr;
        }
 
-       if(!privatesay)
-       if (!IS_PLAYER(source))
+       if (!privatesay && source && !IS_PLAYER(source))
        {
                if (!intermission_running)
                        if(teamsay || (autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !(warmup_stage || gameover)))
@@ -864,6 +880,7 @@ int Say(entity source, float teamsay, entity privatesay, string msgin, bool floo
        if(privatesay)
                sourcemsgstr = strcat(privatemsgprefix, substring(sourcemsgstr, privatemsgprefixlen, -1));
 
+    int ret;
        if(source.muted)
        {
                // always fake the message
@@ -904,7 +921,7 @@ int Say(entity source, float teamsay, entity privatesay, string msgin, bool floo
                {
                        sprint(source, sourcemsgstr);
                        dedicated_print(msgstr); // send to server console too
-                       FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && it.active_minigame == source.active_minigame, LAMBDA(sprint(it, msgstr)));
+                       FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && it.active_minigame == source.active_minigame, sprint(it, msgstr));
                }
                else if(teamsay > 0) // team message, only sent to team mates
                {
@@ -912,26 +929,27 @@ int Say(entity source, float teamsay, entity privatesay, string msgin, bool floo
                        dedicated_print(msgstr); // send to server console too
                        if(sourcecmsgstr != "")
                                centerprint(source, sourcecmsgstr);
-                       FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && it != source && it.team == source.team, LAMBDA(
+                       FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && it != source && it.team == source.team, {
                                sprint(it, msgstr);
                                if(cmsgstr != "")
                                        centerprint(it, cmsgstr);
-                       ));
+                       });
                }
                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) && IS_REAL_CLIENT(it) && it != source, LAMBDA(sprint(it, msgstr)));
-               }
-               else if(sourcemsgstr != msgstr) // trimmed/server fixed message, sent to all players
-               {
-                       sprint(source, sourcemsgstr);
-                       dedicated_print(msgstr); // send to server console too
-                       FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source, LAMBDA(sprint(it, msgstr)));
+                       FOREACH_CLIENT(!IS_PLAYER(it) && IS_REAL_CLIENT(it) && it != source, sprint(it, msgstr));
                }
                else
-                       bprint(msgstr); // entirely normal message, sent to all players -- bprint sends to server console too.
+               {
+            if (source) {
+                sprint(source, sourcemsgstr);
+                dedicated_print(msgstr); // send to server console too
+                MX_Say(strcat(playername(source), "^7: ", msgin));
+            }
+            FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source, sprint(it, msgstr));
+        }
        }
 
        return ret;