]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/common/mutators/mutator/buffs/buffs.qc
Purge self from the damage/death mutator hooks
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / mutators / mutator / buffs / buffs.qc
index 9eb113a96ff86355b809b690b43ceaff159cc79e..01e7f31bb362db86d008d0f7f45d491de621aadb 100644 (file)
@@ -19,6 +19,9 @@ float autocvar_g_buffs_medic_survive_health;
 float autocvar_g_buffs_medic_rot;
 float autocvar_g_buffs_medic_max;
 float autocvar_g_buffs_medic_regen;
+float autocvar_g_buffs_medic_heal_amount = 15;
+float autocvar_g_buffs_medic_heal_delay = 1;
+float autocvar_g_buffs_medic_heal_range = 400;
 float autocvar_g_buffs_vengeance_damage_multiplier;
 float autocvar_g_buffs_bash_force;
 float autocvar_g_buffs_bash_force_self;
@@ -33,7 +36,6 @@ float autocvar_g_buffs_speed_damage_take;
 float autocvar_g_buffs_speed_regen;
 float autocvar_g_buffs_vampire_damage_steal;
 float autocvar_g_buffs_invisible_alpha;
-float autocvar_g_buffs_flight_gravity;
 float autocvar_g_buffs_jump_height;
 float autocvar_g_buffs_inferno_burntime_factor;
 float autocvar_g_buffs_inferno_burntime_min_time;
@@ -42,14 +44,17 @@ float autocvar_g_buffs_inferno_burntime_target_time;
 float autocvar_g_buffs_inferno_damagemultiplier;
 float autocvar_g_buffs_swapper_range;
 float autocvar_g_buffs_magnet_range_item;
+float autocvar_g_buffs_magnet_range_buff = 200;
+float autocvar_g_buffs_luck_chance = 0.15;
+float autocvar_g_buffs_luck_damagemultiplier = 3;
 
 // ammo
 .float buff_ammo_prev_infitems;
 .int buff_ammo_prev_clipload;
 // invisible
 .float buff_invisible_prev_alpha;
-// flight
-.float buff_flight_prev_gravity;
+// medic
+.float buff_medic_healtime;
 // disability
 .float buff_disability_time;
 .float buff_disability_effect_time;
@@ -73,34 +78,22 @@ const vector BUFF_MAX = ('16 16 20');
 
 #ifdef IMPLEMENTATION
 
-#include "../../../triggers/target/music.qh"
-#include "../../../gamemodes/all.qh"
+#include <common/triggers/target/music.qh>
+#include <common/gamemodes/all.qh>
 
-.float buff_time;
-void buffs_DelayedInit();
+.float buff_time = _STAT(BUFF_TIME);
+void buffs_DelayedInit(entity this);
 
 REGISTER_MUTATOR(buffs, cvar("g_buffs"))
 {
        MUTATOR_ONADD
        {
-               addstat(STAT_BUFFS, AS_INT, buffs);
-               addstat(STAT_BUFF_TIME, AS_FLOAT, buff_time);
-
                InitializeEntity(world, buffs_DelayedInit, INITPRIO_FINDTARGET);
        }
 }
 
-entity buff_FirstFromFlags(int _buffs)
+bool buffs_BuffModel_Customize(entity this)
 {
-       if (flags)
-       {
-               FOREACH(Buffs, it.m_itemid & _buffs, LAMBDA(return it));
-       }
-       return BUFF_Null;
-}
-
-bool buffs_BuffModel_Customize()
-{SELFPARAM();
        entity player, myowner;
        bool same_team;
 
@@ -139,7 +132,7 @@ void buffs_BuffModel_Spawn(entity player)
        player.buff_model.scale = 0.7;
        player.buff_model.pflags = PFLAGS_FULLDYNAMIC;
        player.buff_model.light_lev = 200;
-       player.buff_model.customizeentityforclient = buffs_BuffModel_Customize;
+       setcefc(player.buff_model, buffs_BuffModel_Customize);
 }
 
 vector buff_GlowColor(entity buff)
@@ -160,17 +153,17 @@ void buff_Effect(entity player, string eff)
 }
 
 // buff item
-float buff_Waypoint_visible_for_player(entity plr)
-{SELFPARAM();
-       if(!self.owner.buff_active && !self.owner.buff_activetime)
+bool buff_Waypoint_visible_for_player(entity this, entity player, entity view)
+{
+       if(!this.owner.buff_active && !this.owner.buff_activetime)
                return false;
 
-       if (plr.buffs)
+       if (view.buffs)
        {
-               return plr.cvar_cl_buffs_autoreplace == false || plr.buffs != self.owner.buffs;
+               return view.cvar_cl_buffs_autoreplace == false || view.buffs != this.owner.buffs;
        }
 
-       return WaypointSprite_visible_for_player(plr);
+       return WaypointSprite_visible_for_player(this, player, view);
 }
 
 void buff_Waypoint_Spawn(entity e)
@@ -182,53 +175,55 @@ void buff_Waypoint_Spawn(entity e)
        e.buff_waypoint.waypointsprite_visible_for_player = buff_Waypoint_visible_for_player;
 }
 
-void buff_SetCooldown(float cd)
-{SELFPARAM();
+void buff_SetCooldown(entity this, float cd)
+{
        cd = max(0, cd);
 
-       if(!self.buff_waypoint)
-               buff_Waypoint_Spawn(self);
+       if(!this.buff_waypoint)
+               buff_Waypoint_Spawn(this);
 
-       WaypointSprite_UpdateBuildFinished(self.buff_waypoint, time + cd);
-       self.buff_activetime = cd;
-       self.buff_active = !cd;
+       WaypointSprite_UpdateBuildFinished(this.buff_waypoint, time + cd);
+       this.buff_activetime = cd;
+       this.buff_active = !cd;
 }
 
-void buff_Respawn(entity ent)
-{SELFPARAM();
+void buff_Respawn(entity this)
+{
        if(gameover) { return; }
 
-       vector oldbufforigin = ent.origin;
+       vector oldbufforigin = this.origin;
+       this.velocity = '0 0 200';
 
-       if(!MoveToRandomMapLocation(ent, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, ((autocvar_g_buffs_random_location_attempts > 0) ? autocvar_g_buffs_random_location_attempts : 10), 1024, 256))
+       if(!MoveToRandomMapLocation(this, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY,
+               ((autocvar_g_buffs_random_location_attempts > 0) ? autocvar_g_buffs_random_location_attempts : 10), 1024, 256))
        {
-               entity spot = SelectSpawnPoint(true);
-               setorigin(ent, spot.origin + '0 0 200');
-               ent.angles = spot.angles;
+               entity spot = SelectSpawnPoint(this, true);
+               setorigin(this, spot.origin);
+               this.velocity = ((randomvec() * 100) + '0 0 200');
+               this.angles = spot.angles;
        }
 
-       tracebox(ent.origin, ent.mins * 1.5, self.maxs * 1.5, ent.origin, MOVE_NOMONSTERS, ent);
+       tracebox(this.origin, this.mins * 1.5, this.maxs * 1.5, this.origin, MOVE_NOMONSTERS, this);
 
-       setorigin(ent, trace_endpos); // attempt to unstick
+       setorigin(this, trace_endpos); // attempt to unstick
 
-       ent.movetype = MOVETYPE_TOSS;
+       this.movetype = MOVETYPE_TOSS;
 
-       makevectors(ent.angles);
-       ent.velocity = '0 0 200';
-       ent.angles = '0 0 0';
+       makevectors(this.angles);
+       this.angles = '0 0 0';
        if(autocvar_g_buffs_random_lifetime > 0)
-               ent.lifetime = time + autocvar_g_buffs_random_lifetime;
+               this.lifetime = time + autocvar_g_buffs_random_lifetime;
 
-       Send_Effect(EFFECT_ELECTRO_COMBO, oldbufforigin + ((ent.mins + ent.maxs) * 0.5), '0 0 0', 1);
-       Send_Effect(EFFECT_ELECTRO_COMBO, CENTER_OR_VIEWOFS(ent), '0 0 0', 1);
+       Send_Effect(EFFECT_ELECTRO_COMBO, oldbufforigin + ((this.mins + this.maxs) * 0.5), '0 0 0', 1);
+       Send_Effect(EFFECT_ELECTRO_COMBO, CENTER_OR_VIEWOFS(this), '0 0 0', 1);
 
-       WaypointSprite_Ping(ent.buff_waypoint);
+       WaypointSprite_Ping(this.buff_waypoint);
 
-       sound(ent, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
+       sound(this, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
 }
 
-void buff_Touch()
-{SELFPARAM();
+void buff_Touch(entity this)
+{
        if(gameover) { return; }
 
        if(ITEM_TOUCH_NEEDKILL())
@@ -238,7 +233,7 @@ void buff_Touch()
        }
 
        if((self.team && DIFF_TEAM(other, self))
-       || (other.frozen)
+       || (STAT(FROZEN, other))
        || (other.vehicle)
        || (!self.buff_active)
        )
@@ -303,82 +298,82 @@ void buff_NewType(entity ent, float cb)
        ent.buffs = RandomSelection_chosen_float;
 }
 
-void buff_Think()
-{SELFPARAM();
-       if(self.buffs != self.oldbuffs)
+void buff_Think(entity this)
+{
+       if(this.buffs != this.oldbuffs)
        {
-               entity buff = buff_FirstFromFlags(self.buffs);
-               self.color = buff.m_color;
-               self.glowmod = buff_GlowColor(buff);
-               self.skin = buff.m_skin;
+               entity buff = buff_FirstFromFlags(this.buffs);
+               this.color = buff.m_color;
+               this.glowmod = buff_GlowColor(buff);
+               this.skin = buff.m_skin;
 
-               setmodel(self, MDL_BUFF);
+               setmodel(this, MDL_BUFF);
 
-               if(self.buff_waypoint)
+               if(this.buff_waypoint)
                {
-                       //WaypointSprite_Disown(self.buff_waypoint, 1);
-                       WaypointSprite_Kill(self.buff_waypoint);
-                       buff_Waypoint_Spawn(self);
-                       if(self.buff_activetime)
-                               WaypointSprite_UpdateBuildFinished(self.buff_waypoint, time + self.buff_activetime - frametime);
+                       //WaypointSprite_Disown(this.buff_waypoint, 1);
+                       WaypointSprite_Kill(this.buff_waypoint);
+                       buff_Waypoint_Spawn(this);
+                       if(this.buff_activetime)
+                               WaypointSprite_UpdateBuildFinished(this.buff_waypoint, time + this.buff_activetime - frametime);
                }
 
-               self.oldbuffs = self.buffs;
+               this.oldbuffs = this.buffs;
        }
 
        if(!gameover)
        if((round_handler_IsActive() && !round_handler_IsRoundStarted()) || time >= game_starttime)
-       if(!self.buff_activetime_updated)
+       if(!this.buff_activetime_updated)
        {
-               buff_SetCooldown(self.buff_activetime);
-               self.buff_activetime_updated = true;
+               buff_SetCooldown(this, this.buff_activetime);
+               this.buff_activetime_updated = true;
        }
 
-       if(!self.buff_active && !self.buff_activetime)
-       if(!self.owner || self.owner.frozen || self.owner.deadflag != DEAD_NO || !self.owner.iscreature || !(self.owner.buffs & self.buffs))
+       if(!this.buff_active && !this.buff_activetime)
+       if(!this.owner || STAT(FROZEN, this.owner) || IS_DEAD(this.owner) || !this.owner.iscreature || !(this.owner.buffs & this.buffs))
        {
-               buff_SetCooldown(autocvar_g_buffs_cooldown_respawn + frametime);
-               self.owner = world;
+               buff_SetCooldown(this, autocvar_g_buffs_cooldown_respawn + frametime);
+               this.owner = world;
                if(autocvar_g_buffs_randomize)
-                       buff_NewType(self, self.buffs);
+                       buff_NewType(this, this.buffs);
 
-               if(autocvar_g_buffs_random_location || (self.spawnflags & 64))
-                       buff_Respawn(self);
+               if(autocvar_g_buffs_random_location || (this.spawnflags & 64))
+                       buff_Respawn(this);
        }
 
-       if(self.buff_activetime)
+       if(this.buff_activetime)
        if(!gameover)
        if((round_handler_IsActive() && !round_handler_IsRoundStarted()) || time >= game_starttime)
        {
-               self.buff_activetime = max(0, self.buff_activetime - frametime);
+               this.buff_activetime = max(0, this.buff_activetime - frametime);
 
-               if(!self.buff_activetime)
+               if(!this.buff_activetime)
                {
-                       self.buff_active = true;
-                       sound(self, CH_TRIGGER, SND_STRENGTH_RESPAWN, VOL_BASE, ATTN_NORM);
-                       Send_Effect(EFFECT_ITEM_RESPAWN, CENTER_OR_VIEWOFS(self), '0 0 0', 1);
+                       this.buff_active = true;
+                       sound(this, CH_TRIGGER, SND_STRENGTH_RESPAWN, VOL_BASE, ATTN_NORM);
+                       Send_Effect(EFFECT_ITEM_RESPAWN, CENTER_OR_VIEWOFS(this), '0 0 0', 1);
                }
        }
 
-       if(self.buff_active)
+       if(this.buff_active)
        {
-               if(self.team && !self.buff_waypoint)
-                       buff_Waypoint_Spawn(self);
+               if(this.team && !this.buff_waypoint)
+                       buff_Waypoint_Spawn(this);
 
-               if(self.lifetime)
-               if(time >= self.lifetime)
-                       buff_Respawn(self);
+               if(this.lifetime)
+               if(time >= this.lifetime)
+                       buff_Respawn(this);
        }
 
-       self.nextthink = time;
-       //self.angles_y = time * 110.1;
+       this.nextthink = time;
+       //this.angles_y = time * 110.1;
 }
 
-void buff_Waypoint_Reset()
-{SELFPARAM();
-       WaypointSprite_Kill(self.buff_waypoint);
+void buff_Waypoint_Reset(entity this)
+{
+       WaypointSprite_Kill(this.buff_waypoint);
 
-       if(self.buff_activetime) { buff_Waypoint_Spawn(self); }
+       if(this.buff_activetime) { buff_Waypoint_Spawn(this); }
 }
 
 void buff_Reset(entity this)
@@ -386,79 +381,76 @@ void buff_Reset(entity this)
        if(autocvar_g_buffs_randomize)
                buff_NewType(this, this.buffs);
        this.owner = world;
-       buff_SetCooldown(autocvar_g_buffs_cooldown_activate);
-       buff_Waypoint_Reset();
+       buff_SetCooldown(this, autocvar_g_buffs_cooldown_activate);
+       buff_Waypoint_Reset(this);
        this.buff_activetime_updated = false;
 
        if(autocvar_g_buffs_random_location || (this.spawnflags & 64))
                buff_Respawn(this);
 }
 
-float buff_Customize()
-{SELFPARAM();
+float buff_Customize(entity this)
+{
        entity player = WaypointSprite_getviewentity(other);
-       if(!self.buff_active || (self.team && DIFF_TEAM(player, self)))
+       if(!this.buff_active || (this.team && DIFF_TEAM(player, this)))
        {
-               self.alpha = 0.3;
-               if(self.effects & EF_FULLBRIGHT) { self.effects &= ~(EF_FULLBRIGHT); }
-               self.pflags = 0;
+               this.alpha = 0.3;
+               if(this.effects & EF_FULLBRIGHT) { this.effects &= ~(EF_FULLBRIGHT); }
+               this.pflags = 0;
        }
        else
        {
-               self.alpha = 1;
-               if(!(self.effects & EF_FULLBRIGHT)) { self.effects |= EF_FULLBRIGHT; }
-               self.light_lev = 220 + 36 * sin(time);
-               self.pflags = PFLAGS_FULLDYNAMIC;
+               this.alpha = 1;
+               if(!(this.effects & EF_FULLBRIGHT)) { this.effects |= EF_FULLBRIGHT; }
+               this.light_lev = 220 + 36 * sin(time);
+               this.pflags = PFLAGS_FULLDYNAMIC;
        }
        return true;
 }
 
-void buff_Init(entity ent)
-{SELFPARAM();
-       if(!cvar("g_buffs")) { remove(ent); return; }
-
-       if(!teamplay && ent.team) { ent.team = 0; }
-
-       entity buff = buff_FirstFromFlags(self.buffs);
-
-       setself(ent);
-       if(!self.buffs || buff_Available(buff))
-               buff_NewType(self, 0);
-
-       self.classname = "item_buff";
-       self.solid = SOLID_TRIGGER;
-       self.flags = FL_ITEM;
-       self.think = buff_Think;
-       self.touch = buff_Touch;
-       self.reset = buff_Reset;
-       self.nextthink = time + 0.1;
-       self.gravity = 1;
-       self.movetype = MOVETYPE_TOSS;
-       self.scale = 1;
-       self.skin = buff.m_skin;
-       self.effects = EF_FULLBRIGHT | EF_STARDUST | EF_NOSHADOW;
-       self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY;
-       self.customizeentityforclient = buff_Customize;
-       //self.gravity = 100;
-       self.color = buff.m_color;
-       self.glowmod = buff_GlowColor(self);
-       buff_SetCooldown(autocvar_g_buffs_cooldown_activate + game_starttime);
-       self.buff_active = !self.buff_activetime;
-       self.pflags = PFLAGS_FULLDYNAMIC;
-
-       if(self.spawnflags & 1)
-               self.noalign = true;
-
-       if(self.noalign)
-               self.movetype = MOVETYPE_NONE; // reset by random location
-
-       setmodel(self, MDL_BUFF);
-       setsize(self, BUFF_MIN, BUFF_MAX);
-
-       if(cvar("g_buffs_random_location") || (self.spawnflags & 64))
-               buff_Respawn(self);
-
-       setself(this);
+void buff_Init(entity this)
+{
+       if(!cvar("g_buffs")) { remove(this); return; }
+
+       if(!teamplay && this.team) { this.team = 0; }
+
+       entity buff = buff_FirstFromFlags(this.buffs);
+
+       if(!this.buffs || buff_Available(buff))
+               buff_NewType(this, 0);
+
+       this.classname = "item_buff";
+       this.solid = SOLID_TRIGGER;
+       this.flags = FL_ITEM;
+       setthink(this, buff_Think);
+       settouch(this, buff_Touch);
+       this.reset = buff_Reset;
+       this.nextthink = time + 0.1;
+       this.gravity = 1;
+       this.movetype = MOVETYPE_TOSS;
+       this.scale = 1;
+       this.skin = buff.m_skin;
+       this.effects = EF_FULLBRIGHT | EF_STARDUST | EF_NOSHADOW;
+       this.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY;
+       setcefc(this, buff_Customize);
+       //this.gravity = 100;
+       this.color = buff.m_color;
+       this.glowmod = buff_GlowColor(this);
+       buff_SetCooldown(this, autocvar_g_buffs_cooldown_activate + game_starttime);
+       this.buff_active = !this.buff_activetime;
+       this.pflags = PFLAGS_FULLDYNAMIC;
+
+       if(this.spawnflags & 1)
+               this.noalign = true;
+
+       if(this.noalign)
+               this.movetype = MOVETYPE_NONE; // reset by random location
+
+       setmodel(this, MDL_BUFF);
+       setsize(this, BUFF_MIN, BUFF_MAX);
+
+       if(cvar("g_buffs_random_location") || (this.spawnflags & 64))
+               buff_Respawn(this);
 }
 
 void buff_Init_Compat(entity ent, entity replacement)
@@ -482,15 +474,29 @@ void buff_SpawnReplacement(entity ent, entity old)
        buff_Init(ent);
 }
 
-void buff_Vengeance_DelayedDamage()
-{SELFPARAM();
-       if(self.enemy)
-               Damage(self.enemy, self.owner, self.owner, self.dmg, DEATH_BUFF.m_id, self.enemy.origin, '0 0 0');
+void buff_Vengeance_DelayedDamage(entity this)
+{
+       if(this.enemy)
+               Damage(this.enemy, this.owner, this.owner, this.dmg, DEATH_BUFF.m_id, this.enemy.origin, '0 0 0');
 
-       remove(self);
+       remove(this);
        return;
 }
 
+// note: only really useful in teamplay
+void buff_Medic_Heal(entity this)
+{
+       FOREACH_CLIENT(IS_PLAYER(it) && it != this && vdist(it.origin - this.origin, <=, autocvar_g_buffs_medic_heal_range),
+       {
+               if(SAME_TEAM(it, this))
+               if(it.health < autocvar_g_balance_health_regenstable)
+               {
+                       Send_Effect(EFFECT_HEALING, it.origin, '0 0 0', 1);
+                       it.health = bound(0, it.health + autocvar_g_buffs_medic_heal_amount, autocvar_g_balance_health_regenstable);
+               }
+       });
+}
+
 float buff_Inferno_CalculateTime(float x, float offset_x, float offset_y, float intersect_x, float intersect_y, float base)
 {
        return offset_y + (intersect_y - offset_y) * logn(((x - offset_x) * ((base - 1) / intersect_x)) + 1, base);
@@ -499,13 +505,17 @@ float buff_Inferno_CalculateTime(float x, float offset_x, float offset_y, float
 // mutator hooks
 MUTATOR_HOOKFUNCTION(buffs, PlayerDamage_SplitHealthArmor)
 {
+       entity frag_target = M_ARGV(2, entity);
+       float frag_deathtype = M_ARGV(6, float);
+       float frag_damage = M_ARGV(7, float);
+
        if(frag_deathtype == DEATH_BUFF.m_id) { return false; }
 
        if(frag_target.buffs & BUFF_RESISTANCE.m_itemid)
        {
                vector v = healtharmor_applydamage(50, autocvar_g_buffs_resistance_blockpercent, frag_deathtype, frag_damage);
-               damage_take = v.x;
-               damage_save = v.y;
+               M_ARGV(4, float) = v.x; // take
+               M_ARGV(5, float) = v.y; // save
        }
 
        return false;
@@ -513,6 +523,12 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerDamage_SplitHealthArmor)
 
 MUTATOR_HOOKFUNCTION(buffs, PlayerDamage_Calculate)
 {
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+       float frag_deathtype = M_ARGV(3, float);
+       float frag_damage = M_ARGV(4, float);
+       vector frag_force = M_ARGV(6, vector);
+
        if(frag_deathtype == DEATH_BUFF.m_id) { return false; }
 
        if(frag_target.buffs & BUFF_SPEED.m_itemid)
@@ -540,17 +556,16 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerDamage_Calculate)
                dmgent.dmg = frag_damage * autocvar_g_buffs_vengeance_damage_multiplier;
                dmgent.enemy = frag_attacker;
                dmgent.owner = frag_target;
-               dmgent.think = buff_Vengeance_DelayedDamage;
+               setthink(dmgent, buff_Vengeance_DelayedDamage);
                dmgent.nextthink = time + 0.1;
        }
 
        if(frag_target.buffs & BUFF_BASH.m_itemid)
        if(frag_attacker != frag_target)
-       if(vlen(frag_force))
                frag_force = '0 0 0';
 
        if(frag_attacker.buffs & BUFF_BASH.m_itemid)
-       if(vlen(frag_force))
+       if(frag_force)
        if(frag_attacker == frag_target)
                frag_force *= autocvar_g_buffs_bash_force_self;
        else
@@ -560,18 +575,23 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerDamage_Calculate)
        if(frag_target != frag_attacker)
                frag_target.buff_disability_time = time + autocvar_g_buffs_disability_slowtime;
 
-       if(frag_attacker.buffs & BUFF_MEDIC.m_itemid)
-       if(DEATH_WEAPONOF(frag_deathtype) != WEP_ARC)
-       if(SAME_TEAM(frag_attacker, frag_target))
-       if(frag_attacker != frag_target)
+       if(frag_target.buffs & BUFF_INFERNO.m_itemid)
        {
-               frag_target.health = min(g_pickup_healthmega_max, frag_target.health + frag_damage);
-               frag_damage = 0;
+               if(frag_deathtype == DEATH_FIRE.m_id)
+                       frag_damage = 0;
+               if(frag_deathtype == DEATH_LAVA.m_id)
+                       frag_damage *= 0.5; // TODO: cvarize?
        }
 
+       if(frag_attacker.buffs & BUFF_LUCK.m_itemid)
+       if(frag_attacker != frag_target)
+       if(autocvar_g_buffs_luck_damagemultiplier > 0)
+       if(random() <= autocvar_g_buffs_luck_chance)
+               frag_damage *= autocvar_g_buffs_luck_damagemultiplier;
+
        if(frag_attacker.buffs & BUFF_INFERNO.m_itemid)
        if(frag_target != frag_attacker) {
-               float time = buff_Inferno_CalculateTime(
+               float btime = buff_Inferno_CalculateTime(
                        frag_damage,
                        0,
                        autocvar_g_buffs_inferno_burntime_min_time,
@@ -579,18 +599,17 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerDamage_Calculate)
                        autocvar_g_buffs_inferno_burntime_target_time,
                        autocvar_g_buffs_inferno_burntime_factor
                );
-               Fire_AddDamage(frag_target, frag_attacker, (frag_damage * autocvar_g_buffs_inferno_damagemultiplier) * time, time, DEATH_BUFF.m_id);
+               Fire_AddDamage(frag_target, frag_attacker, (frag_damage * autocvar_g_buffs_inferno_damagemultiplier), btime, DEATH_BUFF.m_id);
        }
 
        // this... is ridiculous (TODO: fix!)
        if(frag_attacker.buffs & BUFF_VAMPIRE.m_itemid)
        if(!frag_target.vehicle)
-       if(DEATH_WEAPONOF(frag_deathtype) != WEP_ARC)
        if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype))
-       if(frag_target.deadflag == DEAD_NO)
+       if(!IS_DEAD(frag_target))
        if(IS_PLAYER(frag_target) || IS_MONSTER(frag_target))
        if(frag_attacker != frag_target)
-       if(!frag_target.frozen)
+       if(!STAT(FROZEN, frag_target))
        if(frag_target.takedamage)
        if(DIFF_TEAM(frag_attacker, frag_target))
        {
@@ -599,15 +618,20 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerDamage_Calculate)
                        frag_attacker.armorvalue = bound(0, frag_attacker.armorvalue + bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal, frag_target.armorvalue), g_pickup_armorsmall_max);
        }
 
+       M_ARGV(4, float) = frag_damage;
+       M_ARGV(6, vector) = frag_force;
+
        return false;
 }
 
-MUTATOR_HOOKFUNCTION(buffs,PlayerSpawn)
-{SELFPARAM();
-       self.buffs = 0;
+MUTATOR_HOOKFUNCTION(buffs, PlayerSpawn)
+{
+       entity player = M_ARGV(0, entity);
+
+       player.buffs = 0;
        // reset timers here to prevent them continuing after re-spawn
-       self.buff_disability_time = 0;
-       self.buff_disability_effect_time = 0;
+       player.buff_disability_time = 0;
+       player.buff_disability_effect_time = 0;
        return false;
 }
 
@@ -658,17 +682,19 @@ MUTATOR_HOOKFUNCTION(buffs, MonsterMove)
 }
 
 MUTATOR_HOOKFUNCTION(buffs, PlayerDies)
-{SELFPARAM();
-       if(self.buffs)
+{
+       entity frag_target = M_ARGV(2, entity);
+       
+       if(frag_target.buffs)
        {
-               int buffid = buff_FirstFromFlags(self.buffs).m_id;
-               Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, buffid);
-               self.buffs = 0;
+               int buffid = buff_FirstFromFlags(frag_target.buffs).m_id;
+               Send_Notification(NOTIF_ALL_EXCEPT, frag_target, MSG_INFO, INFO_ITEM_BUFF_LOST, frag_target.netname, buffid);
+               frag_target.buffs = 0;
 
-               if(self.buff_model)
+               if(frag_target.buff_model)
                {
-                       remove(self.buff_model);
-                       self.buff_model = world;
+                       remove(frag_target.buff_model);
+                       frag_target.buff_model = world;
                }
        }
        return false;
@@ -698,15 +724,18 @@ MUTATOR_HOOKFUNCTION(buffs, ForbidThrowCurrentWeapon)
        {
                float best_distance = autocvar_g_buffs_swapper_range;
                entity closest = world;
-               entity player;
-               FOR_EACH_PLAYER(player)
-               if(DIFF_TEAM(self, player))
-               if(player.deadflag == DEAD_NO && !player.frozen && !player.vehicle)
-               if(vlen(self.origin - player.origin) <= best_distance)
-               {
-                       best_distance = vlen(self.origin - player.origin);
-                       closest = player;
-               }
+               FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
+                       if(!IS_DEAD(it) && !STAT(FROZEN, it) && !it.vehicle)
+                       if(DIFF_TEAM(it, self))
+                       {
+                               float test = vlen2(self.origin - it.origin);
+                               if(test <= best_distance * best_distance)
+                               {
+                                       best_distance = sqrt(test);
+                                       closest = it;
+                               }
+                       }
+               ));
 
                if(closest)
                {
@@ -740,7 +769,7 @@ MUTATOR_HOOKFUNCTION(buffs, ForbidThrowCurrentWeapon)
                        // set pusher so self gets the kill if they fall into void
                        closest.pusher = self;
                        closest.pushltime = time + autocvar_g_maxpushtime;
-                       closest.istypefrag = closest.BUTTON_CHAT;
+                       closest.istypefrag = PHYS_INPUT_BUTTON_CHAT(closest);
 
                        Send_Effect(EFFECT_ELECTRO_COMBO, their_org, '0 0 0', 1);
                        Send_Effect(EFFECT_ELECTRO_COMBO, my_org, '0 0 0', 1);
@@ -770,8 +799,8 @@ bool buffs_RemovePlayer(entity player)
 
        return false;
 }
-MUTATOR_HOOKFUNCTION(buffs, MakePlayerObserver) { return buffs_RemovePlayer(self); }
-MUTATOR_HOOKFUNCTION(buffs, ClientDisconnect) { return buffs_RemovePlayer(self); }
+MUTATOR_HOOKFUNCTION(buffs, MakePlayerObserver) { SELFPARAM(); return buffs_RemovePlayer(self); }
+MUTATOR_HOOKFUNCTION(buffs, ClientDisconnect) { SELFPARAM(); return buffs_RemovePlayer(self); }
 
 MUTATOR_HOOKFUNCTION(buffs, CustomizeWaypoint)
 {SELFPARAM();
@@ -788,11 +817,6 @@ MUTATOR_HOOKFUNCTION(buffs, CustomizeWaypoint)
 
 MUTATOR_HOOKFUNCTION(buffs, OnEntityPreSpawn, CBC_ORDER_LAST)
 {SELFPARAM();
-       if (self.classname == "item_flight" && cvar("g_buffs") && cvar("g_buffs_flight"))
-       {
-               buff_Init_Compat(self, BUFF_FLIGHT);
-               return true;
-       }
        if(autocvar_g_buffs_replace_powerups)
        switch(self.classname)
        {
@@ -831,7 +855,7 @@ MUTATOR_HOOKFUNCTION(buffs, WeaponSpeedFactor)
 
 MUTATOR_HOOKFUNCTION(buffs, PlayerPreThink)
 {SELFPARAM();
-       if(gameover || self.deadflag != DEAD_NO) { return false; }
+       if(gameover || IS_DEAD(self)) { return false; }
 
        if(time < self.buff_disability_time)
        if(time >= self.buff_disability_effect_time)
@@ -849,7 +873,7 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerPreThink)
        if(time >= self.buff_time)
                buff_lost = 2;
 
-       if(self.frozen) { buff_lost = 1; }
+       if(STAT(FROZEN, self)) { buff_lost = 1; }
 
        if(buff_lost)
        {
@@ -868,27 +892,42 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerPreThink)
 
        if(self.buffs & BUFF_MAGNET.m_itemid)
        {
-               vector pickup_size = '1 1 1' * autocvar_g_buffs_magnet_range_item;
-               for(other = world; (other = findflags(other, flags, FL_ITEM)); )
-               if(boxesoverlap(self.absmin - pickup_size, self.absmax + pickup_size, other.absmin, other.absmax))
+               vector pickup_size;
+               FOREACH_ENTITY_FLAGS(flags, FL_ITEM,
                {
-                       setself(other);
-                       other = this;
-                       if(self.touch)
-                               self.touch();
-                       other = self;
-                       setself(this);
-               }
+                       if(it.buffs)
+                               pickup_size = '1 1 1' * autocvar_g_buffs_magnet_range_buff;
+                       else
+                               pickup_size = '1 1 1' * autocvar_g_buffs_magnet_range_item;
+
+                       if(boxesoverlap(self.absmin - pickup_size, self.absmax + pickup_size, it.absmin, it.absmax))
+                       {
+                               if(gettouch(it))
+                               {
+                                       entity oldother = other;
+                                       other = self;
+                                       WITHSELF(it, gettouch(it)(it));
+                                       other = oldother;
+                               }
+                       }
+               });
        }
 
        if(self.buffs & BUFF_AMMO.m_itemid)
        if(self.clip_size)
-               self.clip_load = self.(weapon_load[self.switchweapon]) = self.clip_size;
+               self.clip_load = self.(weapon_load[PS(self).m_switchweapon.m_id]) = self.clip_size;
 
        if((self.buffs & BUFF_INVISIBLE.m_itemid) && (self.oldbuffs & BUFF_INVISIBLE.m_itemid))
        if(self.alpha != autocvar_g_buffs_invisible_alpha)
                self.alpha = autocvar_g_buffs_invisible_alpha; // powerups reset alpha, so we must enforce this (TODO)
 
+       if(self.buffs & BUFF_MEDIC.m_itemid)
+       if(time >= self.buff_medic_healtime)
+       {
+               buff_Medic_Heal(self);
+               self.buff_medic_healtime = time + autocvar_g_buffs_medic_heal_delay;
+       }
+
 #define BUFF_ONADD(b) if ( (self.buffs & (b).m_itemid) && !(self.oldbuffs & (b).m_itemid))
 #define BUFF_ONREM(b) if (!(self.buffs & (b).m_itemid) &&  (self.oldbuffs & (b).m_itemid))
 
@@ -905,7 +944,7 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerPreThink)
 
                        if(self.clip_load)
                                self.buff_ammo_prev_clipload = self.clip_load;
-                       self.clip_load = self.(weapon_load[self.switchweapon]) = self.clip_size;
+                       self.clip_load = self.(weapon_load[PS(self).m_switchweapon.m_id]) = self.clip_size;
                }
 
                BUFF_ONREM(BUFF_AMMO)
@@ -931,15 +970,6 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerPreThink)
                BUFF_ONREM(BUFF_INVISIBLE)
                        self.alpha = self.buff_invisible_prev_alpha;
 
-               BUFF_ONADD(BUFF_FLIGHT)
-               {
-                       self.buff_flight_prev_gravity = self.gravity;
-                       self.gravity = autocvar_g_buffs_flight_gravity;
-               }
-
-               BUFF_ONREM(BUFF_FLIGHT)
-                       self.gravity = self.buff_flight_prev_gravity;
-
                self.oldbuffs = self.buffs;
                if(self.buffs)
                {
@@ -985,7 +1015,7 @@ MUTATOR_HOOKFUNCTION(buffs, VehicleEnter)
 {
        vh_vehicle.buffs = vh_player.buffs;
        vh_player.buffs = 0;
-       vh_vehicle.buff_time = vh_player.buff_time - time;
+       vh_vehicle.buff_time = max(0, vh_player.buff_time - time);
        vh_player.buff_time = 0;
        return false;
 }
@@ -1014,11 +1044,7 @@ MUTATOR_HOOKFUNCTION(buffs, PlayerRegen)
        return false;
 }
 
-MUTATOR_HOOKFUNCTION(buffs, GetCvars)
-{
-       GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_buffs_autoreplace, "cl_buffs_autoreplace");
-       return false;
-}
+REPLICATE(cvar_cl_buffs_autoreplace, bool, "cl_buffs_autoreplace");
 
 MUTATOR_HOOKFUNCTION(buffs, BuildMutatorsString)
 {
@@ -1032,7 +1058,7 @@ MUTATOR_HOOKFUNCTION(buffs, BuildMutatorsPrettyString)
        return false;
 }
 
-void buffs_DelayedInit()
+void buffs_DelayedInit(entity this)
 {
        if(autocvar_g_buffs_spawn_count > 0)
        if(find(world, classname, "item_buff") == world)